# module_buchungen.R buchungenUI <- function(id) { ns <- NS(id) tagList( useShinyjs(), h3("Hauptbuchungen"), fluidRow( column(8, reactableOutput(ns("buchungen_table"))), column(4, selectizeInput(ns("contact"), label = "Kontakt", choices = get_contact_choices(conn), selected = NA, width = "100%"), textAreaInput(ns("note"), label = "Verwendungszweck", value = NA, width = "100%"), h3("Dateien"), uiOutput(ns("attachments_ui")), fileInput(ns("anhang"), NULL, multiple = TRUE, accept = c(".pdf", ".jpg", ".png", ".xlsx", ".docx"), width = "100%"), div( style = "display: flex; flex-direction: column; gap: 10px; align-items: flex-start;", actionBttn(ns("add_detail"), "Detail hinzu", size = "sm", style = "material-flat", color = "default", block = TRUE), actionBttn(ns("del_detail"), "Detail Löschen", size = "sm", style = "material-flat", color = "danger", block = TRUE), actionBttn(ns("add_trans"), "Transaktion hinzu", size = "sm", style = "material-flat", color = "default", block = TRUE), actionBttn(ns("del_trans"), "Transaktion Löschen", size = "sm", style = "material-flat", color = "danger", block = TRUE), actionBttn(ns("save_trans"), "Transaktion speichern", size = "sm", style = "material-flat", color = "success", block = TRUE, icon = icon("floppy-disk")), ) ) ), hr(), h3("Details / Gegenbuchungen"), uiOutput(ns("details_table")) , ) } buchungenServer <- function(id, conn, r_global) { moduleServer(id, function(input, output, session) { ns <- session$ns # ── Reactive Variablen ---- postings_data <- reactiveVal(read_buch_tabelle(conn)) sel_details <- reactiveVal(NULL) selected_trans_id <- reactiveVal(NULL) reset_trigger <- reactiveVal(NULL) current_main_idx <- reactiveVal(NULL) # ── Filter ---- aktiver_filter <- reactive(r_global$buchungen_filter) gefilterte_daten <- reactive({ d <- postings_data() f <- aktiver_filter() if (f == "alle") d else if (f == "giro") d |> filter(grepl("0130", account_name)) else if (f == "monat") d |> filter( floor_date(as.Date(valuta), "month") == floor_date(Sys.Date(), "month") ) else if (startsWith(f, "contact:")) d |> filter(contact_id == as.integer(sub("contact:", "", f))) else if (startsWith(f, "project:")) d |> filter(project_id == as.integer(sub("project:", "", f))) else if (startsWith(f, "account:")) d |> filter(account_id == as.integer(sub("account:", "", f))) else d }) # ── Haupttabelle rendern ---- output$buchungen_table <- renderReactable({ reset_trigger() f_reactable( daten = gefilterte_daten(), coldefs = coldef_entries_tabelle, selection = "single", hoehe = "60vh", filterable = TRUE, ) }) sel <- reactive(getReactableState("buchungen_table", "selected")) observeEvent(sel(), ignoreInit = T, { output$details_table <- renderUI({ req(sel()) # Daten laden df <- read_buch_entries(conn, gefilterte_daten()$entry_id[sel()]) sel_details(df) rows <- lapply(1:nrow(df), function(ind) { fluidRow(class = "details-row", # HIER: Index an die ID hängen! column(1, numericInput(ns(paste0("id_", ind)), label = NULL, width = "100%", value = df$id[ind])), column(2, dateInput(ns(paste0("valuta_", ind)), label = NULL, width = "100%", value = df$valuta[ind])), column(3, selectizeInput(ns(paste0("account_", ind)), label = NULL, choices = get_account_choices(conn), selected = df$account_id[ind], width = "100%")), column(3, selectizeInput(ns(paste0("projekt_", ind)), label = NULL, choices = get_project_choices(conn), selected = df$projekt_id[ind], width = "100%")), column(2, numericInput(ns(paste0("betrag_", ind)), label = NULL, width = "100%", value = df$amount[ind])) ) }) tagList(rows) }) }) observeEvent(input$save_trans, { req(sel_details()) n_rows <- nrow(sel_details()) # Alle Zeilen in einer Liste sammeln all_records <- lapply(1:n_rows, function(ind) { data.frame( id = as.integer(input[[paste0("id_", ind)]]), valuta = as.Date(input[[paste0("valuta_", ind)]]), amount = as.numeric(input[[paste0("betrag_", ind)]]), account_id = input[[paste0("account_", ind)]], projekt_id = input[[paste0("projekt_", ind)]], stringsAsFactors = FALSE ) }) # Zu einem großen Dataframe zusammenfügen final_df <- do.call(rbind, all_records) browser() # Datenbank-Update-Logik tryCatch({ # Beispiel: Für jede Zeile ein Update ausführen for(i in 1:nrow(final_df)) { update_buchung_detail(conn, final_df[i, ]) # Deine DB-Funktion } showNotification("Daten erfolgreich gespeichert", type = "message") # Optional: Haupttabelle aktualisieren, falls sich Summen geändert haben # trigger_refresh(trigger_refresh() + 1) }, error = function(e) { showNotification(paste("Fehler beim Speichern:", e$message), type = "error") }) }) # ── Anhänge ---- output$attachments_ui <- renderUI({ req(sel_details()) entry_id <- dbxSelect(conn, paste0("SELECT entry_id FROM postings WHERE id=", sel_details()$id[1])) %>% pull att <- dbxSelect(conn, paste0( "SELECT * FROM attachments WHERE entry_id = ", entry_id )) if (nrow(att) == 0) return(p("Keine Anhänge vorhanden.")) tagList(lapply(seq_len(nrow(att)), function(i) { ext <- tools::file_ext(att$original_name[i]) filename <- paste0("attachments/", att$id[i], ".", ext) observeEvent(input[[paste0("open_att_", att$id[i])]], { showModal(modalDialog( tags$iframe(src = filename, width = "100%", height = "600px", style = "border:none;"), title = att$original_name[i], size = "l", easyClose = TRUE, footer = modalButton("Schließen") )) }, ignoreInit = TRUE, once = FALSE) div( actionLink(ns(paste0("open_att_", att$id[i])), att$original_name[i]), actionLink(ns(paste0("del_att_", att$id[i])), "✕", style = "color:red; margin-left:8px;") ) })) }) }) }