Accounts Tree erstellt
This commit is contained in:
@@ -0,0 +1,72 @@
|
|||||||
|
# R/tree_helpers.R
|
||||||
|
|
||||||
|
# Buchungsanzahl rekursiv für einen Knoten und alle Nachkommen
|
||||||
|
count_recursive <- function(num, df, counts) {
|
||||||
|
# Alle Nachkommen: alles im Bereich (num, num+1000) je nach Ebene
|
||||||
|
divisor <- ifelse(num %% 1000 == 0, 1000,
|
||||||
|
ifelse(num %% 100 == 0, 100,
|
||||||
|
ifelse(num %% 10 == 0, 10, 1)))
|
||||||
|
|
||||||
|
descendants <- df$id[
|
||||||
|
df$num >= num &
|
||||||
|
df$num < num + divisor
|
||||||
|
]
|
||||||
|
|
||||||
|
sum(counts$n[counts$account_id %in% descendants], na.rm = TRUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Label mit Buchungsanzahl
|
||||||
|
make_label <- function(num, df, counts) {
|
||||||
|
name <- df$account_name[df$num == num]
|
||||||
|
total <- count_recursive(num, df, counts)
|
||||||
|
ifelse(total > 0,
|
||||||
|
paste0(name, " (", total, ")"),
|
||||||
|
name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Baum rekursiv aufbauen
|
||||||
|
build_level <- function(df, counts, parent_num, divisor) {
|
||||||
|
if (divisor < 1) return(list())
|
||||||
|
|
||||||
|
children <- df[
|
||||||
|
df$num > parent_num &
|
||||||
|
df$num < parent_num + divisor * 10 &
|
||||||
|
df$num %% divisor == 0 &
|
||||||
|
df$num %% (divisor * 10) != 0,
|
||||||
|
]
|
||||||
|
|
||||||
|
if (nrow(children) == 0) return(list())
|
||||||
|
|
||||||
|
result <- lapply(seq_len(nrow(children)), function(i) {
|
||||||
|
child_num <- children$num[i]
|
||||||
|
subtree <- build_level(df, counts, child_num, divisor / 10)
|
||||||
|
if (length(subtree) == 0)
|
||||||
|
structure(list(), sttype = "default")
|
||||||
|
else
|
||||||
|
subtree
|
||||||
|
})
|
||||||
|
|
||||||
|
setNames(result, sapply(children$num, make_label, df = df, counts = counts))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hauptfunktion: kompletten Baum aus accounts-Tabelle bauen
|
||||||
|
build_account_tree <- function(conn) {
|
||||||
|
df <- dbReadTable(conn, "accounts")
|
||||||
|
df$num <- as.integer(substr(df$account_name, 1, 4))
|
||||||
|
|
||||||
|
counts <- tbl(conn, "postings") %>%
|
||||||
|
count(account_id) %>%
|
||||||
|
collect()
|
||||||
|
|
||||||
|
tops <- df[df$num %% 1000 == 0, ]
|
||||||
|
|
||||||
|
tree <- lapply(seq_len(nrow(tops)), function(i) {
|
||||||
|
build_level(df, counts, tops$num[i], 100)
|
||||||
|
})
|
||||||
|
|
||||||
|
setNames(
|
||||||
|
tree,
|
||||||
|
sapply(tops$num, make_label, df = df, counts = counts)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
library(shiny)
|
||||||
|
library(shinyjs)
|
||||||
|
library(shinyTree)
|
||||||
|
library(reactable)
|
||||||
|
library(dplyr)
|
||||||
|
library(DBI)
|
||||||
|
|
||||||
|
# ── UI ────────────────────────────────────────────────────────────────────────
|
||||||
|
accountsUI <- function(id) {
|
||||||
|
ns <- NS(id)
|
||||||
|
tagList(
|
||||||
|
useShinyjs(),
|
||||||
|
sidebarLayout(
|
||||||
|
sidebarPanel(
|
||||||
|
width = 3,
|
||||||
|
h4("Konten"),
|
||||||
|
shinyTree(ns("account_tree"), checkbox = FALSE, search = FALSE)
|
||||||
|
),
|
||||||
|
mainPanel(
|
||||||
|
width = 9,
|
||||||
|
# Accounts-Felder
|
||||||
|
wellPanel(
|
||||||
|
fluidRow(
|
||||||
|
column(12,
|
||||||
|
actionButton(ns("new_account"), "Konto neu",
|
||||||
|
class = "btn-success", icon = icon("plus")),
|
||||||
|
hr()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
fluidRow(
|
||||||
|
column(4,
|
||||||
|
textInput(ns("account_name"), "Kontoname"),
|
||||||
|
textInput(ns("bank_name"), "Bank")
|
||||||
|
),
|
||||||
|
column(4,
|
||||||
|
numericInput(ns("hibiscus_id"), "Hibiscus Account ID", value = NA),
|
||||||
|
numericInput(ns("budget_id"), "Budget ID", value = NA)
|
||||||
|
),
|
||||||
|
column(4,
|
||||||
|
# ID read-only anzeigen
|
||||||
|
disabled(numericInput(ns("account_id"), "ID", value = NA)),
|
||||||
|
br(),
|
||||||
|
actionButton(ns("save_account"), "Speichern",
|
||||||
|
class = "btn-primary", icon = icon("save")),
|
||||||
|
textOutput(ns("save_status"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
# Buchungen
|
||||||
|
h4("Buchungen"),
|
||||||
|
reactableOutput(ns("postings_table"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Server ----
|
||||||
|
accountsServer <- function(id, conn, r_global) {
|
||||||
|
moduleServer(id, function(input, output, session) {
|
||||||
|
ns <- session$ns
|
||||||
|
# ── Refresh-Trigger ----
|
||||||
|
refresh <- reactiveVal(0)
|
||||||
|
|
||||||
|
# ── Kontendaten ----
|
||||||
|
accounts <- reactive({
|
||||||
|
refresh()
|
||||||
|
dbReadTable(conn, "accounts") %>% arrange(account_name)
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Baum aufbauen ----
|
||||||
|
tree_data <- reactive({
|
||||||
|
refresh()
|
||||||
|
build_account_tree(conn)
|
||||||
|
})
|
||||||
|
|
||||||
|
output$account_tree <- renderTree({
|
||||||
|
tree_data()
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Gewähltes Konto ────────────────────────────────────────────────────────
|
||||||
|
selected_name <- reactive({
|
||||||
|
sel <- get_selected(input$account_tree, format = "names")
|
||||||
|
cat("raw selected:", paste(sel, collapse=", "), "\n")
|
||||||
|
if (length(sel) == 0) return(NULL)
|
||||||
|
name <- sub(" \\(\\d+\\)$", "", sel[[1]])
|
||||||
|
cat("cleaned name:", name, "\n")
|
||||||
|
name
|
||||||
|
})
|
||||||
|
|
||||||
|
selected_account <- reactive({
|
||||||
|
req(selected_name())
|
||||||
|
df <- accounts()
|
||||||
|
df[df$account_name == selected_name(), ]
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Inputs befüllen ----
|
||||||
|
observeEvent(selected_account(), {
|
||||||
|
acc <- selected_account()
|
||||||
|
req(nrow(acc) > 0)
|
||||||
|
updateNumericInput(session, "account_id", value = acc$id)
|
||||||
|
updateTextInput(session, "account_name", value = acc$account_name)
|
||||||
|
updateTextInput(session, "bank_name", value = acc$bank_name %||% "")
|
||||||
|
updateNumericInput(session, "hibiscus_id", value = acc$hibiscus_account_id)
|
||||||
|
updateNumericInput(session, "budget_id", value = acc$budget_id)
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Neu-Button ----
|
||||||
|
observeEvent(input$new_account, {
|
||||||
|
neue_id <- max_id(conn, "accounts") + 1L
|
||||||
|
updateNumericInput(session, "account_id", value = neue_id)
|
||||||
|
updateTextInput(session, "account_name", value = "")
|
||||||
|
updateTextInput(session, "bank_name", value = "")
|
||||||
|
updateNumericInput(session, "hibiscus_id", value = NA)
|
||||||
|
updateNumericInput(session, "budget_id", value = 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Buchungsanzahl für gewähltes Konto ----
|
||||||
|
has_postings <- reactive({
|
||||||
|
req(input$account_id)
|
||||||
|
count <- tbl(conn, "postings") %>%
|
||||||
|
filter(account_id == !!input$account_id) %>%
|
||||||
|
count() %>%
|
||||||
|
pull(n)
|
||||||
|
count > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Lösch-Button sperren/freigeben ----
|
||||||
|
observe({
|
||||||
|
req(input$account_id)
|
||||||
|
if (has_postings()) {
|
||||||
|
shinyjs::disable("delete_account")
|
||||||
|
shinyjs::runjs(sprintf(
|
||||||
|
"$('#%s').attr('title', 'Konto hat Buchungen und kann nicht gelöscht werden')",
|
||||||
|
ns("delete_account")
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
shinyjs::enable("delete_account")
|
||||||
|
shinyjs::runjs(sprintf(
|
||||||
|
"$('#%s').removeAttr('title')",
|
||||||
|
ns("delete_account")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Speichern (upsert ----
|
||||||
|
observeEvent(input$save_account, {
|
||||||
|
record <- data.frame(
|
||||||
|
id = input$account_id,
|
||||||
|
account_name = input$account_name,
|
||||||
|
bank_name = input$bank_name,
|
||||||
|
hibiscus_account_id = input$hibiscus_id,
|
||||||
|
budget_id = input$budget_id,
|
||||||
|
stringsAsFactors = FALSE
|
||||||
|
)
|
||||||
|
|
||||||
|
dbxUpsert(conn, "accounts", record, where_cols = c("id"))
|
||||||
|
refresh(refresh() + 1)
|
||||||
|
|
||||||
|
output$save_status <- renderText("✓ Gespeichert")
|
||||||
|
shinyjs::delay(2000, output$save_status <- renderText(""))
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Löschen ----
|
||||||
|
observeEvent(input$delete_account, {
|
||||||
|
req(input$account_id)
|
||||||
|
showModal(modalDialog(
|
||||||
|
title = "Konto löschen",
|
||||||
|
paste0("Konto '", input$account_name, "' wirklich löschen?"),
|
||||||
|
footer = tagList(
|
||||||
|
modalButton("Abbrechen"),
|
||||||
|
actionButton(ns("delete_confirm"), "Ja, löschen", class = "btn-danger")
|
||||||
|
)
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
observeEvent(input$delete_confirm, {
|
||||||
|
dbxDelete(conn, "accounts", data.frame(id = input$account_id))
|
||||||
|
removeModal()
|
||||||
|
refresh(refresh() + 1)
|
||||||
|
|
||||||
|
output$save_status <- renderText("✓ Gelöscht")
|
||||||
|
shinyjs::delay(2000, output$save_status <- renderText(""))
|
||||||
|
|
||||||
|
updateNumericInput(session, "account_id", value = NA)
|
||||||
|
updateTextInput(session, "account_name", value = "")
|
||||||
|
updateTextInput(session, "bank_name", value = "")
|
||||||
|
updateNumericInput(session, "hibiscus_id", value = NA)
|
||||||
|
updateNumericInput(session, "budget_id", value = NA)
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Buchungen laden (read-only ----
|
||||||
|
postings_data <- reactive({
|
||||||
|
req(selected_account())
|
||||||
|
acc <- selected_account()
|
||||||
|
req(nrow(acc) > 0)
|
||||||
|
refresh()
|
||||||
|
read_buch_tabelle(conn) %>%
|
||||||
|
filter(account_id == acc$id)
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Reactable ----
|
||||||
|
output$postings_table <- renderReactable({
|
||||||
|
req(postings_data())
|
||||||
|
|
||||||
|
reactable(
|
||||||
|
postings_data() %>%
|
||||||
|
select(valuta, projektname, display_name, amount, n_attachments),
|
||||||
|
striped = TRUE,
|
||||||
|
highlight = TRUE,
|
||||||
|
selection = "single",
|
||||||
|
onClick = "select",
|
||||||
|
defaultSorted = list(valuta = "desc"),
|
||||||
|
columns = list(
|
||||||
|
valuta = colDef(name = "Datum", maxWidth = 100),
|
||||||
|
projektname = colDef(name = "Projekt", maxWidth = 120),
|
||||||
|
display_name = colDef(name = "Kontakt"),
|
||||||
|
amount = colDef(
|
||||||
|
name = "Betrag",
|
||||||
|
maxWidth = 110,
|
||||||
|
align = "right",
|
||||||
|
format = colFormat(currency = "EUR", separators = TRUE, locales = "de-DE")
|
||||||
|
),
|
||||||
|
n_attachments = colDef(name = "Anhänge", maxWidth = 80, align = "center")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Sprung zu Buchungen via r_global ----
|
||||||
|
sel <- reactive(getReactableState("postings_table", "selected"))
|
||||||
|
|
||||||
|
observeEvent(sel(), ignoreInit = TRUE, {
|
||||||
|
req(sel(), postings_data())
|
||||||
|
entry_id <- postings_data()$entry_id[sel()]
|
||||||
|
# In accountsServer vor dem Sprung:
|
||||||
|
r_global$nav_history <- c(r_global$nav_history, list(list(
|
||||||
|
tab = "konten",
|
||||||
|
account_name = selected_name()
|
||||||
|
)))
|
||||||
|
r_global$jump_to_entry_id <- entry_id
|
||||||
|
r_global$active_tab <- "buchungen"
|
||||||
|
r_global$jump_to_entry_id <- entry_id
|
||||||
|
r_global$active_tab <- "buchungen"
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Rückgabe ───────────────────────────────────────────────────────────────
|
||||||
|
return(list(
|
||||||
|
selected_account = selected_account
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Hilfsfunktion ──────────────────────────────────────────────────────────────
|
||||||
|
`%||%` <- function(a, b) if (is.na(a) || is.null(a) || a == "") b else a
|
||||||
+61
-29
@@ -24,32 +24,38 @@ buchungenUI <- function(id) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
buchungenServer <- function(id, conn, r_global) {
|
||||||
moduleServer(id, function(input, output, session) {
|
moduleServer(id, function(input, output, session) {
|
||||||
ns <- session$ns
|
ns <- session$ns
|
||||||
|
|
||||||
# * Reactive Variablen ----
|
# ── Reactive Variablen ─────────────────────────────────────────────────────
|
||||||
postings_data <- reactiveVal(read_buch_tabelle(conn))
|
postings_data <- reactiveVal(read_buch_tabelle(conn))
|
||||||
details_data <- reactiveVal(NULL)
|
details_data <- reactiveVal(NULL)
|
||||||
selected_trans_id <- reactiveVal(NULL)
|
selected_trans_id <- reactiveVal(NULL)
|
||||||
current_main_idx <- reactiveVal(NULL)
|
current_main_idx <- reactiveVal(NULL)
|
||||||
reset_trigger <- reactiveVal(0) # ← neu: erzwingt Re-render mit Filter-Reset
|
reset_trigger <- reactiveVal(0)
|
||||||
modal_trigger <- reactiveVal(list(post_id = NULL, counter = 0))
|
modal_trigger <- reactiveVal(list(post_id = NULL, counter = 0))
|
||||||
highlighted_valuta <- reactiveVal(NULL)
|
highlighted_valuta <- reactiveVal(NULL)
|
||||||
|
|
||||||
|
update_db_trigger <- postingModuleServer(
|
||||||
|
"posting_modal", conn, selected_trans_id, modal_trigger
|
||||||
|
)
|
||||||
|
|
||||||
update_db_trigger <- postingModuleServer("posting_modal", conn, selected_trans_id, modal_trigger)
|
# ── Filter ─────────────────────────────────────────────────────────────────
|
||||||
|
aktiver_filter <- reactive(r_global$buchungen_filter)
|
||||||
|
|
||||||
gefilterte_daten <- reactive({
|
gefilterte_daten <- reactive({
|
||||||
d <- postings_data()
|
d <- postings_data()
|
||||||
switch(aktiver_filter(),
|
f <- aktiver_filter()
|
||||||
"giro" = d |> filter(grepl("0130", account_name)),
|
if (f == "alle") d
|
||||||
"monat" = d |> filter(
|
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")
|
floor_date(as.Date(valuta), "month") == floor_date(Sys.Date(), "month")
|
||||||
|
|
||||||
),
|
|
||||||
d
|
|
||||||
)
|
)
|
||||||
|
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(), {
|
observeEvent(aktiver_filter(), {
|
||||||
@@ -58,11 +64,35 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
|||||||
details_data(NULL)
|
details_data(NULL)
|
||||||
})
|
})
|
||||||
|
|
||||||
# * Haupttabelle rendern ----
|
# ── 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({
|
output$buchungen_table <- renderReactable({
|
||||||
reset_trigger()
|
reset_trigger()
|
||||||
f_reactable(
|
f_reactable(
|
||||||
daten = gefilterte_daten(), # ← geändert
|
daten = gefilterte_daten(),
|
||||||
coldefs = coldef_entries_tabelle,
|
coldefs = coldef_entries_tabelle,
|
||||||
selection = "single",
|
selection = "single",
|
||||||
hoehe = "60vh",
|
hoehe = "60vh",
|
||||||
@@ -71,16 +101,18 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
# * Details-Tabelle laden ----
|
# ── Details laden ──────────────────────────────────────────────────────────
|
||||||
sel_details <- reactive(getReactableState("buchungen_table", "selected"))
|
sel_details <- reactive(getReactableState("buchungen_table", "selected"))
|
||||||
|
|
||||||
observeEvent(sel_details(), ignoreInit = TRUE, {
|
observeEvent(sel_details(), ignoreInit = TRUE, {
|
||||||
req(sel_details())
|
req(sel_details())
|
||||||
highlighted_valuta(gefilterte_daten()[sel_details(), "valuta"])
|
highlighted_valuta(gefilterte_daten()[sel_details(), "valuta"])
|
||||||
current_main_idx(sel_details())
|
current_main_idx(sel_details())
|
||||||
t_id <- gefilterte_daten()[sel_details(), "entry_id"] |> pull() # ← geändert
|
t_id <- gefilterte_daten()[sel_details(), "entry_id"] |> pull()
|
||||||
selected_trans_id(t_id)
|
selected_trans_id(t_id)
|
||||||
details_data(read_buch_tabelle(conn, trans_id = t_id))
|
details_data(read_buch_tabelle(conn, trans_id = t_id))
|
||||||
})
|
})
|
||||||
|
|
||||||
output$details_table <- renderReactable({
|
output$details_table <- renderReactable({
|
||||||
req(details_data())
|
req(details_data())
|
||||||
f_reactable(
|
f_reactable(
|
||||||
@@ -91,13 +123,13 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
# * Detail hinzufügen ----
|
# ── Detail hinzufügen ──────────────────────────────────────────────────────
|
||||||
observeEvent(input$add_detail, {
|
observeEvent(input$add_detail, {
|
||||||
req(selected_trans_id())
|
req(selected_trans_id())
|
||||||
modal_trigger(list(post_id = NULL, counter = modal_trigger()$counter + 1))
|
modal_trigger(list(post_id = NULL, counter = modal_trigger()$counter + 1))
|
||||||
})
|
})
|
||||||
|
|
||||||
# * Detail editieren (Klick auf Detail-Tabelle) ----
|
# ── Detail editieren ───────────────────────────────────────────────────────
|
||||||
sel_detail <- reactive(getReactableState("details_table", "selected"))
|
sel_detail <- reactive(getReactableState("details_table", "selected"))
|
||||||
|
|
||||||
observeEvent(sel_detail(), ignoreInit = TRUE, {
|
observeEvent(sel_detail(), ignoreInit = TRUE, {
|
||||||
@@ -105,16 +137,15 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
|||||||
modal_trigger(list(post_id = p_id, counter = modal_trigger()$counter + 1))
|
modal_trigger(list(post_id = p_id, counter = modal_trigger()$counter + 1))
|
||||||
})
|
})
|
||||||
|
|
||||||
# * DB-Update durch Modal ----
|
# ── DB-Update durch Modal ──────────────────────────────────────────────────
|
||||||
observeEvent(update_db_trigger(), ignoreInit = TRUE, {
|
observeEvent(update_db_trigger(), ignoreInit = TRUE, {
|
||||||
req(selected_trans_id())
|
req(selected_trans_id())
|
||||||
details_data(read_buch_tabelle(conn, trans_id = selected_trans_id()))
|
details_data(read_buch_tabelle(conn, trans_id = selected_trans_id()))
|
||||||
postings_data(read_buch_tabelle(conn))
|
postings_data(read_buch_tabelle(conn))
|
||||||
# Kein Re-render nötig – Filter/Sortierung bleibt erhalten
|
updateReactable("buchungen_table", data = gefilterte_daten())
|
||||||
updateReactable("buchungen_table", data = postings_data())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# * Neue Transaktion ----
|
# ── Neue Transaktion ───────────────────────────────────────────────────────
|
||||||
observeEvent(input$add_trans, {
|
observeEvent(input$add_trans, {
|
||||||
new_t_id <- max_id(conn, "entries") + 1
|
new_t_id <- max_id(conn, "entries") + 1
|
||||||
dbxInsert(conn, "entries", data.frame(id = new_t_id))
|
dbxInsert(conn, "entries", data.frame(id = new_t_id))
|
||||||
@@ -127,19 +158,21 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
|||||||
account_id = c(0, 0),
|
account_id = c(0, 0),
|
||||||
valuta = c(Sys.Date(), Sys.Date())
|
valuta = c(Sys.Date(), Sys.Date())
|
||||||
))
|
))
|
||||||
## ** Daten aktualisieren ----
|
|
||||||
|
# Filter zurücksetzen damit neue Transaktion sichtbar ist
|
||||||
|
r_global$buchungen_filter <- "alle"
|
||||||
|
|
||||||
postings_data(read_buch_tabelle(conn))
|
postings_data(read_buch_tabelle(conn))
|
||||||
selected_trans_id(new_t_id)
|
selected_trans_id(new_t_id)
|
||||||
details_data(read_buch_tabelle(conn, 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)
|
neue_zeile <- which(postings_data()$id == p_id1)
|
||||||
current_main_idx(neue_zeile)
|
current_main_idx(neue_zeile)
|
||||||
## ** Re-render erzwingen → Filter wird zurückgesetzt, neue Zeile selektiert ----
|
|
||||||
reset_trigger(isolate(reset_trigger()) + 1)
|
reset_trigger(isolate(reset_trigger()) + 1)
|
||||||
scroll_to_row(ns("buchungen_table"), neue_zeile)
|
scroll_to_row(ns("buchungen_table"), neue_zeile)
|
||||||
})
|
})
|
||||||
|
|
||||||
# * Transaktion löschen ----
|
# ── Transaktion löschen ────────────────────────────────────────────────────
|
||||||
observeEvent(input$del_trans, ignoreInit = TRUE, {
|
observeEvent(input$del_trans, ignoreInit = TRUE, {
|
||||||
req(selected_trans_id())
|
req(selected_trans_id())
|
||||||
dbxDelete(conn, "postings", where = data.frame(entry_id = selected_trans_id()))
|
dbxDelete(conn, "postings", where = data.frame(entry_id = selected_trans_id()))
|
||||||
@@ -148,11 +181,10 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
|||||||
current_main_idx(NULL)
|
current_main_idx(NULL)
|
||||||
selected_trans_id(NULL)
|
selected_trans_id(NULL)
|
||||||
details_data(NULL)
|
details_data(NULL)
|
||||||
updateReactable("buchungen_table", data = postings_data())
|
updateReactable("buchungen_table", data = gefilterte_daten())
|
||||||
# details_table re-rendert automatisch weil details_data(NULL) → req() schlägt fehl
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# ── Anhänge ────────────────────────────────────────────────────────────────
|
||||||
output$attachments_ui <- renderUI({
|
output$attachments_ui <- renderUI({
|
||||||
req(selected_trans_id())
|
req(selected_trans_id())
|
||||||
att <- dbxSelect(conn, paste0(
|
att <- dbxSelect(conn, paste0(
|
||||||
@@ -164,10 +196,10 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
|||||||
ext <- tools::file_ext(att$original_name[i])
|
ext <- tools::file_ext(att$original_name[i])
|
||||||
filename <- paste0("attachments/", att$id[i], ".", ext)
|
filename <- paste0("attachments/", att$id[i], ".", ext)
|
||||||
|
|
||||||
# Observer direkt hier registrieren
|
|
||||||
observeEvent(input[[paste0("open_att_", att$id[i])]], {
|
observeEvent(input[[paste0("open_att_", att$id[i])]], {
|
||||||
showModal(modalDialog(
|
showModal(modalDialog(
|
||||||
tags$iframe(src = filename, width = "100%", height = "600px", style = "border:none;"),
|
tags$iframe(src = filename, width = "100%", height = "600px",
|
||||||
|
style = "border:none;"),
|
||||||
title = att$original_name[i],
|
title = att$original_name[i],
|
||||||
size = "l",
|
size = "l",
|
||||||
easyClose = TRUE,
|
easyClose = TRUE,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ read_buch_tabelle <- function(conn, trans_id = NULL){
|
|||||||
left_join(accounts, by = c("account_id" = "id")) |>
|
left_join(accounts, by = c("account_id" = "id")) |>
|
||||||
left_join(projects, by = c("project_id" = "id")) |>
|
left_join(projects, by = c("project_id" = "id")) |>
|
||||||
left_join(attachment_counts, by = "entry_id") |>
|
left_join(attachment_counts, by = "entry_id") |>
|
||||||
select(id, valuta, account_name, projektname, display_name, amount, entry_id, n_attachments) |>
|
select(id, valuta, account_name, projektname, display_name, amount, entry_id, n_attachments, account_id) |>
|
||||||
collect() %>%
|
collect() %>%
|
||||||
mutate(
|
mutate(
|
||||||
saldo = 0,
|
saldo = 0,
|
||||||
@@ -43,6 +43,8 @@ coldef_entries_tabelle <-
|
|||||||
account_name = colDef(name = "Kontoname", width = 200),
|
account_name = colDef(name = "Kontoname", width = 200),
|
||||||
projektname = colDef(name = "Projektname", width = 150),
|
projektname = colDef(name = "Projektname", width = 150),
|
||||||
display_name = colDef(name = "Kontakt", width = 250),
|
display_name = colDef(name = "Kontakt", width = 250),
|
||||||
|
account_id = colDef(show = FALSE),
|
||||||
|
entry_id = colDef(show = FALSE),
|
||||||
amount = colDef(name = "Betrag", width = 120,
|
amount = colDef(name = "Betrag", width = 120,
|
||||||
format = colFormat(prefix = "", separators = TRUE, digits = 2),
|
format = colFormat(prefix = "", separators = TRUE, digits = 2),
|
||||||
style = function(value) {
|
style = function(value) {
|
||||||
|
|||||||
Binary file not shown.
@@ -1,14 +1,49 @@
|
|||||||
server <- function(input, output) {
|
server <- function(input, output, session) {
|
||||||
aktiver_filter <- reactive({
|
|
||||||
if (input$filter_giro > 0 &&
|
|
||||||
input$filter_giro == max(input$filter_giro, input$filter_monat, input$filter_alle))
|
|
||||||
"giro"
|
|
||||||
else if (input$filter_monat > 0 &&
|
|
||||||
input$filter_monat == max(input$filter_giro, input$filter_monat, input$filter_alle))
|
|
||||||
"monat"
|
|
||||||
else
|
|
||||||
"alle"
|
|
||||||
})
|
|
||||||
buchungenServer("buchungen_tab", conn, aktiver_filter = aktiver_filter)
|
|
||||||
|
|
||||||
|
# ── Globaler Navigations-Bus ───────────────────────────────────────────────
|
||||||
|
r_global <- reactiveValues(
|
||||||
|
# Navigation
|
||||||
|
active_tab = NULL,
|
||||||
|
nav_history = list(), # ← Stack der letzten Positionen
|
||||||
|
|
||||||
|
# Filter + Sprungziele
|
||||||
|
buchungen_filter = "alle",
|
||||||
|
jump_to_entry_id = NULL,
|
||||||
|
jump_to_account_id = NULL,
|
||||||
|
jump_to_contact_id = NULL,
|
||||||
|
jump_to_project_id = NULL
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Tab-Wechsel ────────────────────────────────────────────────────────────
|
||||||
|
observeEvent(r_global$active_tab, ignoreInit = TRUE, {
|
||||||
|
req(r_global$active_tab)
|
||||||
|
cat("Tab-Wechsel zu:", r_global$active_tab, "\n")
|
||||||
|
updateTabItems(session, "tabs", selected = r_global$active_tab)
|
||||||
|
r_global$active_tab <- NULL
|
||||||
|
})
|
||||||
|
|
||||||
|
observeEvent(input$back_btn, {
|
||||||
|
req(length(r_global$nav_history) > 0)
|
||||||
|
|
||||||
|
# Letzten Zustand holen und Stack kürzen
|
||||||
|
last <- tail(r_global$nav_history, 1)[[1]]
|
||||||
|
r_global$nav_history <- head(r_global$nav_history, -1)
|
||||||
|
|
||||||
|
# Zurückspringen
|
||||||
|
updateTabItems(session, "tabs", selected = last$tab)
|
||||||
|
|
||||||
|
# Zustand wiederherstellen — je nach Tab
|
||||||
|
if (last$tab == "konten") {
|
||||||
|
r_global$jump_to_account_name <- last$account_name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Filter-Buttons (falls in der Navbar oder UI definiert) ─────────────────
|
||||||
|
observeEvent(input$filter_alle, { r_global$buchungen_filter <- "alle" })
|
||||||
|
observeEvent(input$filter_giro, { r_global$buchungen_filter <- "giro" })
|
||||||
|
observeEvent(input$filter_monat, { r_global$buchungen_filter <- "monat" })
|
||||||
|
|
||||||
|
# ── Module ─────────────────────────────────────────────────────────────────
|
||||||
|
accountsServer("accounts_tab", conn, r_global)
|
||||||
|
buchungenServer("buchungen_tab", conn, r_global)
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
## ui.R ##
|
## ui.R ##
|
||||||
dashboardPage(
|
dashboardPage(
|
||||||
dashboardHeader( ),
|
dashboardHeader(
|
||||||
|
title = "GemFin",
|
||||||
|
tags$li(
|
||||||
|
class = "dropdown",
|
||||||
|
style = "padding: 8px 10px;",
|
||||||
|
actionBttn("back_btn", "Zurück",
|
||||||
|
size = "xs",
|
||||||
|
style = "minimal",
|
||||||
|
icon = icon("arrow-left"))
|
||||||
|
)
|
||||||
|
),
|
||||||
## Sidebar content
|
## Sidebar content
|
||||||
dashboardSidebar(
|
dashboardSidebar(
|
||||||
sidebarMenu(id = "tabs",
|
sidebarMenu(id = "tabs",
|
||||||
@@ -38,8 +48,8 @@ dashboardPage(
|
|||||||
buchungenUI("buchungen_tab")
|
buchungenUI("buchungen_tab")
|
||||||
),
|
),
|
||||||
# Second tab content
|
# Second tab content
|
||||||
tabItem(tabName = "widgets",
|
tabItem(tabName = "konten",
|
||||||
h2("Widgets tab content")
|
accountsUI("accounts_tab")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user