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
|
||||
+70
-38
@@ -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) {
|
||||
ns <- session$ns
|
||||
|
||||
# * Reactive Variablen ----
|
||||
# ── 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
|
||||
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
|
||||
)
|
||||
|
||||
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()
|
||||
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
|
||||
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(), {
|
||||
@@ -58,29 +64,55 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
||||
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({
|
||||
reset_trigger()
|
||||
f_reactable(
|
||||
daten = gefilterte_daten(), # ← geändert
|
||||
coldefs = coldef_entries_tabelle,
|
||||
selection = "single",
|
||||
hoehe = "60vh",
|
||||
defaultSelected = current_main_idx(),
|
||||
daten = gefilterte_daten(),
|
||||
coldefs = coldef_entries_tabelle,
|
||||
selection = "single",
|
||||
hoehe = "60vh",
|
||||
defaultSelected = current_main_idx(),
|
||||
highlight_valuta = highlighted_valuta()
|
||||
)
|
||||
})
|
||||
|
||||
# * Details-Tabelle laden ----
|
||||
# ── 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() # ← geändert
|
||||
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(
|
||||
@@ -91,13 +123,13 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
||||
)
|
||||
})
|
||||
|
||||
# * Detail hinzufügen ----
|
||||
# ── 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) ----
|
||||
# ── Detail editieren ───────────────────────────────────────────────────────
|
||||
sel_detail <- reactive(getReactableState("details_table", "selected"))
|
||||
|
||||
observeEvent(sel_detail(), ignoreInit = TRUE, {
|
||||
@@ -105,41 +137,42 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
||||
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, {
|
||||
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())
|
||||
updateReactable("buchungen_table", data = gefilterte_daten())
|
||||
})
|
||||
|
||||
# * Neue Transaktion ----
|
||||
# ── 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),
|
||||
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())
|
||||
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))
|
||||
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 ----
|
||||
# ── Transaktion löschen ────────────────────────────────────────────────────
|
||||
observeEvent(input$del_trans, ignoreInit = TRUE, {
|
||||
req(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)
|
||||
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
|
||||
updateReactable("buchungen_table", data = gefilterte_daten())
|
||||
})
|
||||
|
||||
|
||||
# ── Anhänge ────────────────────────────────────────────────────────────────
|
||||
output$attachments_ui <- renderUI({
|
||||
req(selected_trans_id())
|
||||
att <- dbxSelect(conn, paste0(
|
||||
@@ -164,10 +196,10 @@ buchungenServer <- function(id, conn, aktiver_filter = reactive("alle")) {
|
||||
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;"),
|
||||
tags$iframe(src = filename, width = "100%", height = "600px",
|
||||
style = "border:none;"),
|
||||
title = att$original_name[i],
|
||||
size = "l",
|
||||
easyClose = TRUE,
|
||||
|
||||
@@ -22,7 +22,7 @@ read_buch_tabelle <- function(conn, trans_id = NULL){
|
||||
left_join(accounts, by = c("account_id" = "id")) |>
|
||||
left_join(projects, by = c("project_id" = "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() %>%
|
||||
mutate(
|
||||
saldo = 0,
|
||||
@@ -43,6 +43,8 @@ coldef_entries_tabelle <-
|
||||
account_name = colDef(name = "Kontoname", width = 200),
|
||||
projektname = colDef(name = "Projektname", width = 150),
|
||||
display_name = colDef(name = "Kontakt", width = 250),
|
||||
account_id = colDef(show = FALSE),
|
||||
entry_id = colDef(show = FALSE),
|
||||
amount = colDef(name = "Betrag", width = 120,
|
||||
format = colFormat(prefix = "", separators = TRUE, digits = 2),
|
||||
style = function(value) {
|
||||
|
||||
Binary file not shown.
@@ -1,14 +1,49 @@
|
||||
server <- function(input, output) {
|
||||
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)
|
||||
server <- function(input, output, session) {
|
||||
|
||||
# ── 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 ##
|
||||
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
|
||||
dashboardSidebar(
|
||||
sidebarMenu(id = "tabs",
|
||||
@@ -38,8 +48,8 @@ dashboardPage(
|
||||
buchungenUI("buchungen_tab")
|
||||
),
|
||||
# Second tab content
|
||||
tabItem(tabName = "widgets",
|
||||
h2("Widgets tab content")
|
||||
tabItem(tabName = "konten",
|
||||
accountsUI("accounts_tab")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user