Files
gemfin-shiny/R/buchungen_mod.R
T

186 lines
6.7 KiB
R
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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, aktiver_filter = reactive("alle")) {
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) # ← neu: erzwingt Re-render mit Filter-Reset
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)
gefilterte_daten <- reactive({
d <- postings_data()
switch(aktiver_filter(),
"giro" = d |> filter(grepl("0130", account_name)),
"monat" = d |> filter(
floor_date(as.Date(valuta), "month") == floor_date(Sys.Date(), "month")
),
d
)
})
observeEvent(aktiver_filter(), {
current_main_idx(NULL)
selected_trans_id(NULL)
details_data(NULL)
})
# * Haupttabelle rendern ----
output$buchungen_table <- renderReactable({
reset_trigger()
f_reactable(
daten = gefilterte_daten(), # ← geändert
coldefs = coldef_entries_tabelle,
selection = "single",
hoehe = "60vh",
defaultSelected = current_main_idx(),
highlight_valuta = highlighted_valuta()
)
})
# * Details-Tabelle 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() # ← geändert
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 (Klick auf Detail-Tabelle) ----
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))
# Kein Re-render nötig Filter/Sortierung bleibt erhalten
updateReactable("buchungen_table", data = postings_data())
})
# * 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())
))
## ** Daten aktualisieren ----
postings_data(read_buch_tabelle(conn))
selected_trans_id(new_t_id)
details_data(read_buch_tabelle(conn, trans_id = new_t_id))
## ** Index in neuen Daten suchen ----
neue_zeile <- which(postings_data()$id == p_id1)
current_main_idx(neue_zeile)
## ** Re-render erzwingen → Filter wird zurückgesetzt, neue Zeile selektiert ----
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 = postings_data())
# details_table re-rendert automatisch weil details_data(NULL) → req() schlägt fehl
})
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)
# Observer direkt hier registrieren
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;")
)
}))
})
})
}