Buchungen-popup funktioniert

This commit is contained in:
2026-02-24 11:17:17 +01:00
parent 32c0b8edb4
commit 622bdf0281
13 changed files with 304 additions and 48 deletions
+4 -48
View File
@@ -1,50 +1,6 @@
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
# Node artifact files
node_modules/
dist/
# Compiled Java class files
*.class
# Compiled Python bytecode
*.py[cod]
# Log files
*.log
# Package files
*.jar
# Maven
target/
dist/
# JetBrains IDE
.idea/
# Unit test reports
TEST*.xml
# Generated by MacOS
.DS_Store
# Generated by Windows
Thumbs.db
# Applications
*.app
*.exe
*.war
# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv
.Rproj.user
.Rhistory
.RData
.Ruserdata
+13
View File
@@ -0,0 +1,13 @@
read_accounts <- function(conn){
tbl(conn, "accounts")
}
get_account_choices <- function(conn) {
tbl(conn, "accounts") |>
select(id, konto) |>
collect() |>
arrange(konto) |>
(\(df) setNames(df$id, df$konto))()
}
+21
View File
@@ -0,0 +1,21 @@
read_buch_tabelle <- function(conn){
postings <- tbl(conn, "postings")
entries <- tbl(conn, "entries")
accounts <- tbl(conn, "accounts")
projects <- tbl(conn, "projects")
contacts <- tbl(conn, "contacts")
result <- postings |>
left_join(entries, by = c("entry_id" = "id")) |>
left_join(contacts, by = c("contact_id" = "id")) |> # contact_id jetzt verfügbar
left_join(accounts, by = c("account_id" = "id")) |>
left_join(projects, by = c("project_id" = "id")) |>
select(id, valuta, konto, projektname, display_name, amount, entry_id) |>
collect()
}
read_posting <- function(conn, id){
dbxSelect(conn, paste0("SELECT * FROM postings WHERE id =", id))
}
read_postings_by_entry <- function(conn, id){
dbxSelect(conn, paste0("SELECT * FROM postings WHERE entry_id =", id))
}
+92
View File
@@ -0,0 +1,92 @@
buchungenUI <- function(id) {
ns <- NS(id)
tagList(
reactableOutput(ns("buchungen_table"))
)
}
buchungenServer <- function(id) {
moduleServer( id, function(input, output, session) {
ns <- session$ns
postings <- reactiveVal(read_buch_tabelle(conn))
output$buchungen_table <- renderReactable({
reactable(
postings(),
onClick = "expand",
selection = "single",
details = function(index) {
entry_id <- postings()$entry_id[index]
detail_rows <- postings()[postings()$entry_id == entry_id, ]
div(
style = "padding: 10px; background: #f4f4f4; border-left: 4px solid #3c8dbc; max-width: 800px; margin-left: auto",
reactable(
dplyr::select(detail_rows, konto, projektname, amount),
fullWidth = TRUE,
columns = list(
konto = colDef(name = "Konto", minWidth = 150),
projektname = colDef(name = "Projekt", minWidth = 150),
amount = colDef(name = "Betrag", minWidth = 80)
)
)
)
},
columns = list(
id = colDef(name = "ID", minWidth = 80),
valuta = colDef(name = "Wertstellung", minWidth = 80),
konto = colDef(name = "Kontoname", minWidth = 200),
entry_id = colDef(show = FALSE)
)
)
})
# Modal zum editieren
selected <- reactive(getReactableState("buchungen_table", "selected"))
observeEvent(selected(),{
idwert <- postings()[selected(), "id"]
selected_row <- read_posting(conn, idwert) %>%
mutate(verzicht = ifelse(is.na(verzicht), F, verzicht))
showModal(modalDialog(
title = "Buchung bearbeiten",
size = "l",
tags$style(HTML(".modal-dialog { max-width: 90% !important; width: 90% !important; }")),
entryEditUI(ns("entry_edit")),
footer = tagList(
modalButton("Abbrechen"),
actionButton(ns("speichern"), "Speichern")
)
))
entryEditServer("entry_edit", entry_id = selected_row$entry_id, conn = conn)
}, ignoreInit = TRUE)
observeEvent(input$speichern, {
browser()
dbExecute(conn, "UPDATE postings SET
account_id = ?, project_id = ?, amount = ?, valuta = ?,
notiz = ?, rechnungsnummer = ?, betrag_muenzen = ?, verzicht = ?
WHERE id = ?",
params = list(
input$account_id, input$project_id, input$amount, input$valuta,
input$notiz, input$rechnungsnummer, input$betrag_muenzen, input$verzicht,
selected_row$id
)
)
removeModal()
buchungen(read_buch_tabelle(conn)) # Tabelle neu laden
})
}
)
}
+8
View File
@@ -0,0 +1,8 @@
get_contact_choices <- function(conn) {
tbl(conn, "contacts") |>
select(id, display_name) |>
collect() |>
arrange(display_name) |>
(\(df) setNames(df$id, df$display_name))()
}
+3
View File
@@ -0,0 +1,3 @@
read_entry <- function(conn, idwert) {
dbxSelect(conn, paste0("SELECT * FROM entries WHERE id=", idwert))
}
+93
View File
@@ -0,0 +1,93 @@
# entry_edit_mod.R
entryEditUI <- function(id) {
ns <- NS(id)
tagList(
uiOutput(ns("entry_ui")),
uiOutput(ns("postings_ui"))
)
}
entryEditServer <- function(id, entry_id, conn) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
entry_postings <- reactiveVal(read_postings_by_entry(conn, entry_id))
entry_data <- reactiveVal(read_entry(conn, entry_id))
output$entry_ui <- renderUI({
e <- entry_data()
tagList(
fluidRow(
column(6, selectizeInput(ns("contact_id"), "Kontakt",
choices = get_contact_choices(conn),
selected = e$contact_id)),
column(6, textInput(ns("purpose"), "Verwendungszweck", value = e$purpose))
),
hr()
)
})
# Choices für contact nach dem Flush setzen
output$postings_ui <- renderUI({
alle <- entry_postings()
lapply(seq_len(nrow(alle)), function(i) {
print(paste("project selected:", alle$project_id[i]))
print(head(get_project_choices(conn)))
print(class(get_project_choices(conn)))
div(
style = "border-left: 3px solid #3c8dbc; padding: 5px; margin-bottom: 5px",
fluidRow(
column(3,
selectInput(ns(paste0("account_id_", i)), "Konto",
choices = get_account_choices(conn),
selected = as.character(alle$account_id[i]))
),
column(3, selectizeInput(ns(paste0("project_id_", i)), "Projekt",
choices = get_project_choices(conn),
selected = ifelse(is.na(alle$project_id[i]), "", as.character(alle$project_id[i])))
),
column(2, numericInput(ns(paste0("amount_", i)), "Betrag", value = alle$amount[i])),
column(3, textInput(ns(paste0("notiz_", i)), "Notiz", value = alle$notiz[i])),
column(1, actionButton(ns(paste0("delete_", i)), "", icon = icon("trash"), class = "btn-danger btn-sm"))
)
)
})
})
# Choices befüllen nachdem renderUI fertig ist
observe({
alle <- entry_postings()
e <- entry_data()
req(nrow(alle) > 0, nrow(e) > 0)
session$onFlushed(function() {
# Contact
updateSelectizeInput(session, "contact_id",
choices = get_contact_choices(conn),
selected = e$contact_id)
# Postings
lapply(seq_len(nrow(alle)), function(i) {
updateSelectizeInput(session, paste0("account_id_", i),
choices = get_account_choices(conn),
selected = alle$account_id[i])
updateSelectizeInput(session, paste0("project_id_", i),
choices = get_project_choices(conn),
selected = alle$project_id[i])
})
}, once = TRUE)
})
# Speichern-Logik
observeEvent(input$speichern, {
alle <- entry_postings()
lapply(seq_len(nrow(alle)), function(i) {
# update posting i in DB
})
})
})
}
+9
View File
@@ -0,0 +1,9 @@
get_project_choices <- function(conn) {
projekte <- tbl(conn, "projects") |>
select(id, projektname) |>
collect() |>
arrange(projektname)
choices <- setNames(as.character(projekte$id), projekte$projektname)
c("-" = "", choices)
}
Binary file not shown.
+13
View File
@@ -0,0 +1,13 @@
Version: 1.0
RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default
EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8
RnwWeave: Sweave
LaTeX: pdfLaTeX
+19
View File
@@ -0,0 +1,19 @@
library("dbplyr")
library("tidyverse")
library("shiny")
library("DBI")
library("RSQLite")
library("dbx")
library("shinydashboard")
library("reactable")
library("conflicted")
library("R.utils")
conflicts_prefer(dplyr::select)
conflicts_prefer(dplyr::filter)
options(shiny.reactlog = TRUE)
options(shiny.error = browser)
conn <- dbConnect(RSQLite::SQLite(), "db/development.sqlite3")
sourceDirectory("R/")
+5
View File
@@ -0,0 +1,5 @@
server <- function(input, output) {
buchungenServer("buchungen_tab")
}
+24
View File
@@ -0,0 +1,24 @@
## ui.R ##
dashboardPage(
dashboardHeader(),
## Sidebar content
dashboardSidebar(
sidebarMenu(
menuItem("buchungen", tabName = "buchungen", icon = icon("dashboard")),
menuItem("konten", tabName = "Konten", icon = icon("th"))
)
),
dashboardBody(
tabItems(
# First tab content
tabItem(tabName = "buchungen",
buchungenUI("buchungen_tab")
),
# Second tab content
tabItem(tabName = "widgets",
h2("Widgets tab content")
)
)
)
)