From 089a93048813faeba81b0be34222b240ee265696 Mon Sep 17 00:00:00 2001 From: Christian Oswald Date: Tue, 28 Apr 2026 14:39:37 +0200 Subject: [PATCH] Adress-Modul erstellt --- R/_funktionen/f_table.R | 26 ++++++ R/_funktionen/f_zeitstempel.R | 18 ++++ R/contacts/contact_io.R | 44 +++++++++ R/contacts/contacts.R | 19 ++++ R/contacts/module_contacts.R | 167 ++++++++++++++++++++++++++++++++++ R/postings/buchungen.R | 2 +- R/postings/buchungen_mod.R | 12 +-- db/development.sqlite | Bin 1024000 -> 1024000 bytes server.R | 1 + ui.R | 6 +- 10 files changed, 287 insertions(+), 8 deletions(-) create mode 100644 R/_funktionen/f_zeitstempel.R create mode 100644 R/contacts/contact_io.R create mode 100644 R/contacts/module_contacts.R diff --git a/R/_funktionen/f_table.R b/R/_funktionen/f_table.R index 5b96ed0..778d5b7 100755 --- a/R/_funktionen/f_table.R +++ b/R/_funktionen/f_table.R @@ -32,4 +32,30 @@ f_reactable <- function(daten, coldefs = NULL, selection = "single", onClick = "select", columns = coldefs ) +} + +f_reactable_sub <- function(daten, coldefs = NULL) { + reactable( + daten, + selection = "single", + pagination = T, + defaultPageSize = 17, + showPageSizeOptions = TRUE, + filterable = TRUE, + highlight = TRUE, + bordered = TRUE, + striped = FALSE, + compact = TRUE, + # Styling + theme = reactableTheme( + highlightColor = "#e6f7ff", # Etwas dezenter als knallgrün, optional + # borderColor = "#dfe2e5", + rowSelectedStyle = list(backgroundColor = "#98F5FF")# + ), + rowStyle = function(index) { + style <- list(cursor = "pointer") # immer aktiv + }, + onClick = "select", + columns = coldefs + ) } \ No newline at end of file diff --git a/R/_funktionen/f_zeitstempel.R b/R/_funktionen/f_zeitstempel.R new file mode 100644 index 0000000..1a8011b --- /dev/null +++ b/R/_funktionen/f_zeitstempel.R @@ -0,0 +1,18 @@ +## +## Datum: 2024-10-17_14-31 +## Name: Christian Oswald +## Projekt: etatverwaltung +## Datei: f_zeitstempel.R +## Kommentar: Erstellt einen Zeitstempel für die DB-Felder updatea und created +## + +# Funkton die prüft ob das Format stimmt +is.ymd_hms <- function(x) !is.na(lubridate::ymd_hms(x, quiet = TRUE)) +# Zeitstempel +f_zeitstempel <- function(ts){ + # Erstellt einen wenn leer + if(is.na(ts)) ts <- format(Sys.time(), tz="") + # Erstellt einen wenn das Format nicht stimmt + if(!is.ymd_hms(ts)) ts <- format(Sys.time(), tz="") + return(ts) +} diff --git a/R/contacts/contact_io.R b/R/contacts/contact_io.R new file mode 100644 index 0000000..e6e8150 --- /dev/null +++ b/R/contacts/contact_io.R @@ -0,0 +1,44 @@ +## +## Datum : 2026-04-28_13-43 +## Name : Christian Oswald +## Datei : contact_io.R +## Projekt : gemfin-shiny +## Kommentar: Befüllt die Felder und liest sie aus +## + +contact_io <- function(session, input, output, record, flag){ + if(flag == "lesen"){ + record[1,"id"] <- input$cid + record$first_name <- input$first_name + record$last_name <- input$last_name + record$postal_code <- input$postal_code + record$city <- input$city + record$street <- input$street + record$phone <- input$phone + record$mobile <- input$mobile + record$email <- input$email + record$member <- input$member + record$display_name <- input$display_name + record$is_company <- input$is_company + record$notes <- input$note + record$created_at <- f_zeitstempel( record$created_at) + record$updated_at <- format(Sys.time(), tz="") + return(record) + } + if(flag == "schreiben"){ + updateNumericInput(session,"cid", value = record$id) + updateTextInput(session,"display_name", value = record$display_name) + updateTextInput(session,"first_name", value = record$first_name) + updateTextInput(session,"last_name", value = record$last_name) + updateTextInput(session,"postal_code", value = record$postal_code) + updateTextInput(session,"city", value = record$city) + updateTextInput(session,"email", value = record$email) + updateTextInput(session,"street", value = record$street) + updateTextInput(session,"mobile", value = record$mobile) + updateTextInput(session,"phone", value = record$phone) + updateTextAreaInput(session,"note", value = record$notes) + updateCheckboxInput(session, "is_company", value = record$is_company) + updateCheckboxInput(session, "member", value = record$member) + } + +} diff --git a/R/contacts/contacts.R b/R/contacts/contacts.R index ca4e2f4..eb375e2 100755 --- a/R/contacts/contacts.R +++ b/R/contacts/contacts.R @@ -1,3 +1,22 @@ +## +## Datum : 2026-04-28_13-11 +## Name : Christian Oswald +## Datei : contacts.R +## Projekt : gemfin-shiny +## Kommentar: model für contacts +## + + +f_contacts_tabelle <- function(conn){ + tbl(conn, "contacts") %>% + select(id, display_name, street, city, member) %>% + arrange(display_name) %>% + collect + } + +f_contact <- function(conn, idwert){ + dbxSelect(conn, paste0("SELECT * FROM contacts WHERE id=", idwert)) +} get_contact_choices <- function(conn) { contacts <- tbl(conn, "contacts") |> diff --git a/R/contacts/module_contacts.R b/R/contacts/module_contacts.R new file mode 100644 index 0000000..5f58522 --- /dev/null +++ b/R/contacts/module_contacts.R @@ -0,0 +1,167 @@ +contactsUI <- function(id) { + ns <- NS(id) + tagList( + useShinyjs(), + # Sync-Button oben + div( + style = "display: flex; justify-content: flex-end; margin-bottom: 8px;", + actionBttn(ns("sync"), "Sync Hibiscus", + size = "xs", style = "minimal", + icon = icon("rotate"), color = "primary") + ), + sidebarLayout( + sidebarPanel(width = 6, + reactableOutput(ns("contacts_table"), height = 800) + + ), + mainPanel(width = 6, + fluidRow( + column(8, textInput(ns("display_name"), label = "Bezeichnung", value = NA, width = "100%")), + column(4, numericInput(ns("cid"), label = "ID", value = NA, width = "100%")) + ), + fluidRow( + column(6, textInput(ns("first_name"), label = "Vorname", value = NA, width = "100%")), + column(6, textInput(ns("last_name"), label = "Nachname", value = NA, width = "100%")) + ), + fluidRow( + column(12, textInput(ns("street"), label = "Strasse", value = NA, width = "100%")) + ), + fluidRow( + column(4, textInput(ns("postal_code"), label = "PLZ", value = NA, width = "100%")), + column(8, textInput(ns("city"), label = "Ort", value = NA, width = "100%")) + ), + fluidRow( + column(4, textInput(ns("phone"), label = "Telefon", value = NA, width = "100%")), + column(4, textInput(ns("mobile"), label = "Mobil", value = NA, width = "100%")), + column(4, textInput(ns("email"), label = "E-Mail", value = NA, width = "100%")) + ), + fluidRow( + column(4, checkboxInput(ns("is_company"), label = "Firma", value = F, width = "100%")), + column(4, checkboxInput(ns("member"), label = "Mitglied", value = F, width = "100%")) + ), + fluidRow( + column(12, textAreaInput(ns("note"), label = "Notiz", value = NA, width = "100%")) + ), + fluidRow( + column(4, actionBttn(ns("add"), "Neu", size = "sm", style = "material-flat", color = "warning", icon = icon("plus"), block = T)), + column(4, actionBttn(ns("del"), "Löschen", size = "sm", style = "material-flat", color = "danger", icon = icon("thrash"), block = T)), + column(4, actionBttn(ns("save"), "Speichern", size = "sm", style = "material-flat", color = "success", icon = icon("floppy-disk"), block = T)), + ), + br(), + reactableOutput(ns("entries")) + + ) + ) + ) +} + +contactsServer <- function(id, conn, r_global) { + moduleServer(id, function(input, output, session) { + ns <- session$ns + + # ── Daten ---- + refresh <- reactiveVal(0) + zeige_alle <- reactiveVal(FALSE) + + contact_data <- reactive({ + f_contacts_tabelle(conn) + }) + + + # ── Filter-Buttons ---- + observeEvent(input$mitglieder, { zeige_alle(FALSE) }) + observeEvent(input$filter_alle, { zeige_alle(TRUE) }) + + # ── Tabelle ---- + output$contacts_table <- renderReactable({ + reactable( + contact_data(), + striped = TRUE, + highlight = TRUE, + filterable = T, + pagination = F, + selection = "single", + onClick = "select", + defaultSorted = list(display_name = "asc"), + columns = list( + id = colDef(show = FALSE), + display_name = colDef( name = "Bezeichnung", minWidth = 150 ), + street = colDef(name = "Strasse", minWidth = 150), + city = colDef(name = "Ort", minWidth = 150), + member = colDef(name = "Mitglied", maxWidth = 80, + align = "center") + ) + ) + }) + + ## * Selektierter Umsatz ---- + sel <- reactive(getReactableState("contacts_table", "selected")) + observeEvent(sel(), ignoreInit = T, { + idwert <- contact_data()$id[sel()] + record <- f_contact(conn, idwert) + contact_io(session, input, output, record, "schreiben") + # ── Buchungs-Panel ---- + entries <- read_buch_tabelle(conn) %>% + filter(contact_id == idwert) %>% + select(id, valuta, account_name, projektname, amount) + output$entries <- renderReactable( + reactable(entries, + striped = TRUE, + highlight = TRUE, + searchable = T, + filterable = F, + pagination = T, + defaultPageSize = 6, + selection = "single", + onClick = "select", + columns = list( + id = colDef(show = FALSE), + valuta = colDef(name = "Wertstellung", minWidth = 80), + account_name = colDef( name = "Konto", minWidth = 150 ), + projektname = colDef(name = "Projekt", minWidth = 150), + amount = colDef(name = "Betrag", minWidth = 150) + ) + ) + ) + }) + + ## * Speichern ---- + observeEvent(input$save, ignoreInit = T, { + record <- leer_df_from_table(conn, "contacts") + record <- contact_io(session, input, output, record, "lesen") + print(record) + dbxUpsert(conn, "contacts", records = record, where_cols = c("id")) + }) + + ## * Hinzufügen ---- + observeEvent(input$add, ignoreInit = T, { + record <- leer_df_from_table(conn, "contacts") + record$id <- max_id(conn, "contacts") + record <- contact_io(session, input, output, record, "schreiben") + }) + ## * Löschen ---- + observeEvent(input$del, ignoreInit = T, { + dbxDelete(conn, "contacts", where = data.frame(id = input$cid)) + }) + + + + + + + # ── Zur Buchung springen ---- + # observeEvent(input$goto_buchung, { + # req(selected_umsatz()) + # r_global$nav_history <- c(r_global$nav_history, list(list(tab = "umsatz"))) + # r_global$jump_to_entry_id <- selected_umsatz()$entry_id + # r_global$active_tab <- "buchungen" + # }) + + + + + + + + }) +} diff --git a/R/postings/buchungen.R b/R/postings/buchungen.R index 003f20a..5cae1b4 100755 --- a/R/postings/buchungen.R +++ b/R/postings/buchungen.R @@ -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, account_id) |> + select(id, valuta, account_name, projektname, display_name, amount, entry_id, n_attachments, account_id, contact_id) |> collect() %>% mutate( saldo = 0, diff --git a/R/postings/buchungen_mod.R b/R/postings/buchungen_mod.R index 693929c..b1987b1 100755 --- a/R/postings/buchungen_mod.R +++ b/R/postings/buchungen_mod.R @@ -123,13 +123,13 @@ buchungenServer <- function(id, conn, r_global) { ) }) - # ── 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 ─────────────────────────────────────────────────────── + # ── Detail editieren ---- sel_detail <- reactive(getReactableState("details_table", "selected")) observeEvent(sel_detail(), ignoreInit = TRUE, { @@ -137,7 +137,7 @@ buchungenServer <- function(id, conn, r_global) { 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())) @@ -145,7 +145,7 @@ buchungenServer <- function(id, conn, r_global) { 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)) @@ -172,7 +172,7 @@ buchungenServer <- function(id, conn, r_global) { 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())) @@ -184,7 +184,7 @@ buchungenServer <- function(id, conn, r_global) { updateReactable("buchungen_table", data = gefilterte_daten()) }) - # ── Anhänge ──────────────────────────────────────────────────────────────── + # ── Anhänge ---- output$attachments_ui <- renderUI({ req(selected_trans_id()) att <- dbxSelect(conn, paste0( diff --git a/db/development.sqlite b/db/development.sqlite index 3b131ee0507cf110ca2c3bb14dc42cbe3f8f8afd..5ed575b963041d536ddf0570f7d1c031c164c7f9 100755 GIT binary patch delta 806 zcmZvZZAep57{|{&_jRwAqDE@ywM{f~dhgx5bBjcqGn3}bG<#8S%&S?t$?Q!;^97oN zh$QMjM4{l90wMfxKU5}+UcN+D7(p5pL_&dqgfS^PQ^7BtbAE@zdH(mRC0@-lEDLNPhFAd>QXs}IBaZ<6#=*$o-3vMZhq|}yy_qCx&_l?# z!Rj*fah^y=)>XJ&>&=E&XbQLX0~vDR;+o~+h6Vjz!Jc3)-1L0Og%)iw56-HK@5rUiW}>LEhu@V`?!$#- zG1We$0n p?VacejX(FSNKG)YgrCCDnADlYa2Op%k1=437!$^Q+z^Kp1^pt4=lM>@1 z#z2NQ41JRgFL-Ug?Z6n$q~z5UD*;rG3RD>wIE{_NE0c3F)6%AYh-TE@Zsg2({tg>Z znF?d|ban+MeHQlb?CjI66qtM`J6!m(y;_l}hn1xZgr*?LdIr#1|VKJCj&!M zF@v?L>hydj{>{_x-C>lPo~Fnoy8XEZ)3uA!`%{@%ryu&o_vGmlA2Nxw z%l~HDF8`bPsS2|N!@BM43M_y5SqvCT7^ce^uvm$)ZD)&O{moj)s?BnprHaLv`6hD) zv+VW=1C~Y1+n0E;$TM%3G-r9uA;Q6Xn1O#4e=@%m-zC0Te4%`dyoWa%Ua;ZW>~JBU ovHh?w3lOscF&hxG12G2>a{@6J5OV`D4-oSLG2iyXzWla90N!77=Kufz diff --git a/server.R b/server.R index 5eedd76..87f261e 100755 --- a/server.R +++ b/server.R @@ -54,4 +54,5 @@ server <- function(input, output, session) { accountsServer("accounts_tab", conn, r_global) buchungenServer("buchungen_tab", conn, r_global) umsatzServer("umsatz_tab", conn, r_global) + contactsServer("contacts_tab", conn, r_global) } \ No newline at end of file diff --git a/ui.R b/ui.R index fb736fe..d2af0c5 100755 --- a/ui.R +++ b/ui.R @@ -49,7 +49,8 @@ dashboardPage( sidebarMenu(id = "tabs", menuItem("Buchungen", tabName = "buchungen", icon = icon("list")), menuItem("Umsätze", tabName = "umsatz", icon = icon("bank")), - menuItem("Konten", tabName = "konten", icon = icon("building-columns")) + menuItem("Konten", tabName = "konten", icon = icon("building-columns")), + menuItem("Adressen", tabName = "contacts", icon = icon("people-group")) ) ), @@ -63,6 +64,9 @@ dashboardPage( ), tabItem(tabName = "konten", accountsUI("accounts_tab") + ), + tabItem(tabName = "contacts", + contactsUI("contacts_tab") ) ) )