# module_buchungen.R buchungenUI <- function(id) { ns <- NS(id) tagList( useShinyjs(), h3("Hauptbuchungen"), reactableOutput(ns("buchungen_table")), hr(), h3("Details / Gegenbuchungen"), reactableOutput(ns("details_table")), h3("Anhänge"), uiOutput(ns("attachments_ui")), fileInput(ns("anhang"), NULL, multiple = TRUE, accept = c(".pdf", ".jpg", ".png", ".xlsx", ".docx"), width = "100%"), br(), actionBttn(ns("add_trans"), "Transaktion hinzu", size = "sm", style = "material-flat", color = "warning"), actionBttn(ns("del_trans"), "Transaktion Löschen", size = "sm", style = "material-flat", color = "danger"), actionBttn(ns("add_detail"), "Detail hinzu", size = "sm", style = "material-flat", color = "success"), # Das UI-Element des Moduls (auch wenn es leer ist) postingModuleUI(ns("posting_modal")) ) } buchungenServer <- function(id, conn, r_global) { moduleServer(id, function(input, output, session) { ns <- session$ns # ── Reactive Variablen ───────────────────────────────────────────────────── postings_data <- reactiveVal(read_buch_tabelle(conn)) details_data <- reactiveVal(NULL) selected_trans_id <- reactiveVal(NULL) current_main_idx <- reactiveVal(NULL) reset_trigger <- reactiveVal(0) modal_trigger <- reactiveVal(list(post_id = NULL, counter = 0)) highlighted_valuta <- reactiveVal(NULL) update_db_trigger <- postingModuleServer( "posting_modal", conn, selected_trans_id, modal_trigger ) # ── 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 }) observeEvent(aktiver_filter(), { current_main_idx(NULL) selected_trans_id(NULL) details_data(NULL) }) # ── Sprung von anderem Modul ─────────────────────────────────────────────── observeEvent(r_global$jump_to_entry_id, ignoreInit = TRUE, { req(r_global$jump_to_entry_id) t_id <- r_global$jump_to_entry_id # Daten neu laden postings_data(read_buch_tabelle(conn)) # Zeile suchen idx <- which(gefilterte_daten()$entry_id == t_id)[1] req(!is.na(idx)) current_main_idx(idx) selected_trans_id(t_id) details_data(read_buch_tabelle(conn, trans_id = t_id)) highlighted_valuta(gefilterte_daten()[idx, "valuta"]) reset_trigger(isolate(reset_trigger()) + 1) scroll_to_row(ns("buchungen_table"), idx) # Sprungziel zurücksetzen r_global$jump_to_entry_id <- NULL }) # ── Haupttabelle rendern ─────────────────────────────────────────────────── output$buchungen_table <- renderReactable({ reset_trigger() f_reactable( daten = gefilterte_daten(), coldefs = coldef_entries_tabelle, selection = "single", hoehe = "60vh", defaultSelected = current_main_idx(), highlight_valuta = highlighted_valuta() ) }) # ── Details laden ────────────────────────────────────────────────────────── sel_details <- reactive(getReactableState("buchungen_table", "selected")) observeEvent(sel_details(), ignoreInit = TRUE, { req(sel_details()) highlighted_valuta(gefilterte_daten()[sel_details(), "valuta"]) current_main_idx(sel_details()) t_id <- gefilterte_daten()[sel_details(), "entry_id"] |> pull() selected_trans_id(t_id) details_data(read_buch_tabelle(conn, trans_id = t_id)) }) output$details_table <- renderReactable({ req(details_data()) f_reactable( details_data(), coldefs = coldef_entries_tabelle, selection = "single", hoehe = NULL ) }) # ── Detail hinzufügen ────────────────────────────────────────────────────── observeEvent(input$add_detail, { req(selected_trans_id()) modal_trigger(list(post_id = NULL, counter = modal_trigger()$counter + 1)) }) # ── Detail editieren ─────────────────────────────────────────────────────── sel_detail <- reactive(getReactableState("details_table", "selected")) observeEvent(sel_detail(), ignoreInit = TRUE, { p_id <- details_data()$id[sel_detail()] modal_trigger(list(post_id = p_id, counter = modal_trigger()$counter + 1)) }) # ── DB-Update durch Modal ────────────────────────────────────────────────── observeEvent(update_db_trigger(), ignoreInit = TRUE, { req(selected_trans_id()) details_data(read_buch_tabelle(conn, trans_id = selected_trans_id())) postings_data(read_buch_tabelle(conn)) updateReactable("buchungen_table", data = gefilterte_daten()) }) # ── Neue Transaktion ─────────────────────────────────────────────────────── observeEvent(input$add_trans, { new_t_id <- max_id(conn, "entries") + 1 dbxInsert(conn, "entries", data.frame(id = new_t_id)) p_id1 <- max_id(conn, "postings") + 1 p_id2 <- p_id1 + 1 dbxInsert(conn, "postings", data.frame( id = c(p_id1, p_id2), entry_id = c(new_t_id, new_t_id), amount = c(0, 0), account_id = c(0, 0), valuta = c(Sys.Date(), Sys.Date()) )) # Filter zurücksetzen damit neue Transaktion sichtbar ist r_global$buchungen_filter <- "alle" postings_data(read_buch_tabelle(conn)) selected_trans_id(new_t_id) details_data(read_buch_tabelle(conn, trans_id = new_t_id)) neue_zeile <- which(postings_data()$id == p_id1) current_main_idx(neue_zeile) reset_trigger(isolate(reset_trigger()) + 1) scroll_to_row(ns("buchungen_table"), neue_zeile) }) # ── Transaktion löschen ──────────────────────────────────────────────────── observeEvent(input$del_trans, ignoreInit = TRUE, { req(selected_trans_id()) dbxDelete(conn, "postings", where = data.frame(entry_id = selected_trans_id())) dbxDelete(conn, "entries", where = data.frame(id = selected_trans_id())) postings_data(read_buch_tabelle(conn)) current_main_idx(NULL) selected_trans_id(NULL) details_data(NULL) updateReactable("buchungen_table", data = gefilterte_daten()) }) # ── Anhänge ──────────────────────────────────────────────────────────────── output$attachments_ui <- renderUI({ req(selected_trans_id()) att <- dbxSelect(conn, paste0( "SELECT * FROM attachments WHERE entry_id = ", selected_trans_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;") ) })) }) }) }