diff --git a/.gitignore b/.gitignore index b24d71e..ab618b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,50 +1,6 @@ -# These are some examples of commonly ignored file patterns. -# You should customize this list as applicable to your project. -# Learn more about .gitignore: -# https://www.atlassian.com/git/tutorials/saving-changes/gitignore -# Node artifact files -node_modules/ -dist/ - -# Compiled Java class files -*.class - -# Compiled Python bytecode -*.py[cod] - -# Log files -*.log - -# Package files -*.jar - -# Maven -target/ -dist/ - -# JetBrains IDE -.idea/ - -# Unit test reports -TEST*.xml - -# Generated by MacOS -.DS_Store - -# Generated by Windows -Thumbs.db - -# Applications -*.app -*.exe -*.war - -# Large media files -*.mp4 -*.tiff -*.avi -*.flv -*.mov -*.wmv +.Rproj.user +.Rhistory +.RData +.Ruserdata diff --git a/R/accounts/accounts.R b/R/accounts/accounts.R new file mode 100644 index 0000000..a098629 --- /dev/null +++ b/R/accounts/accounts.R @@ -0,0 +1,13 @@ +read_accounts <- function(conn){ + tbl(conn, "accounts") + +} + + +get_account_choices <- function(conn) { + tbl(conn, "accounts") |> + select(id, konto) |> + collect() |> + arrange(konto) |> + (\(df) setNames(df$id, df$konto))() +} diff --git a/R/buchungen/buchungen.R b/R/buchungen/buchungen.R new file mode 100644 index 0000000..de8ef24 --- /dev/null +++ b/R/buchungen/buchungen.R @@ -0,0 +1,21 @@ +read_buch_tabelle <- function(conn){ + postings <- tbl(conn, "postings") + entries <- tbl(conn, "entries") + accounts <- tbl(conn, "accounts") + projects <- tbl(conn, "projects") + contacts <- tbl(conn, "contacts") + result <- postings |> + left_join(entries, by = c("entry_id" = "id")) |> + left_join(contacts, by = c("contact_id" = "id")) |> # contact_id jetzt verfügbar + left_join(accounts, by = c("account_id" = "id")) |> + left_join(projects, by = c("project_id" = "id")) |> + select(id, valuta, konto, projektname, display_name, amount, entry_id) |> + collect() +} + +read_posting <- function(conn, id){ + dbxSelect(conn, paste0("SELECT * FROM postings WHERE id =", id)) +} +read_postings_by_entry <- function(conn, id){ + dbxSelect(conn, paste0("SELECT * FROM postings WHERE entry_id =", id)) +} diff --git a/R/buchungen_mod.R b/R/buchungen_mod.R new file mode 100644 index 0000000..c335c3e --- /dev/null +++ b/R/buchungen_mod.R @@ -0,0 +1,92 @@ +buchungenUI <- function(id) { + ns <- NS(id) + tagList( + reactableOutput(ns("buchungen_table")) + ) +} + +buchungenServer <- function(id) { + moduleServer( id, function(input, output, session) { + ns <- session$ns + + postings <- reactiveVal(read_buch_tabelle(conn)) + + + output$buchungen_table <- renderReactable({ + reactable( + postings(), + onClick = "expand", + selection = "single", + details = function(index) { + entry_id <- postings()$entry_id[index] + detail_rows <- postings()[postings()$entry_id == entry_id, ] + + div( + style = "padding: 10px; background: #f4f4f4; border-left: 4px solid #3c8dbc; max-width: 800px; margin-left: auto", + + reactable( + dplyr::select(detail_rows, konto, projektname, amount), + fullWidth = TRUE, + columns = list( + konto = colDef(name = "Konto", minWidth = 150), + projektname = colDef(name = "Projekt", minWidth = 150), + amount = colDef(name = "Betrag", minWidth = 80) + ) + ) + ) + }, + columns = list( + id = colDef(name = "ID", minWidth = 80), + valuta = colDef(name = "Wertstellung", minWidth = 80), + konto = colDef(name = "Kontoname", minWidth = 200), + entry_id = colDef(show = FALSE) + ) + ) + }) + + # Modal zum editieren + selected <- reactive(getReactableState("buchungen_table", "selected")) + observeEvent(selected(),{ + idwert <- postings()[selected(), "id"] + selected_row <- read_posting(conn, idwert) %>% + mutate(verzicht = ifelse(is.na(verzicht), F, verzicht)) + showModal(modalDialog( + title = "Buchung bearbeiten", + size = "l", + tags$style(HTML(".modal-dialog { max-width: 90% !important; width: 90% !important; }")), + + entryEditUI(ns("entry_edit")), + footer = tagList( + modalButton("Abbrechen"), + actionButton(ns("speichern"), "Speichern") + ) + )) + + entryEditServer("entry_edit", entry_id = selected_row$entry_id, conn = conn) + + }, ignoreInit = TRUE) + + observeEvent(input$speichern, { + browser() + dbExecute(conn, "UPDATE postings SET + account_id = ?, project_id = ?, amount = ?, valuta = ?, + notiz = ?, rechnungsnummer = ?, betrag_muenzen = ?, verzicht = ? + WHERE id = ?", + params = list( + input$account_id, input$project_id, input$amount, input$valuta, + input$notiz, input$rechnungsnummer, input$betrag_muenzen, input$verzicht, + selected_row$id + ) + ) + + removeModal() + buchungen(read_buch_tabelle(conn)) # Tabelle neu laden + }) + + + + + + } + ) +} \ No newline at end of file diff --git a/R/contacts/contacts.R b/R/contacts/contacts.R new file mode 100644 index 0000000..63b155f --- /dev/null +++ b/R/contacts/contacts.R @@ -0,0 +1,8 @@ + +get_contact_choices <- function(conn) { + tbl(conn, "contacts") |> + select(id, display_name) |> + collect() |> + arrange(display_name) |> + (\(df) setNames(df$id, df$display_name))() +} diff --git a/R/entries/entries.R b/R/entries/entries.R new file mode 100644 index 0000000..0660fec --- /dev/null +++ b/R/entries/entries.R @@ -0,0 +1,3 @@ +read_entry <- function(conn, idwert) { + dbxSelect(conn, paste0("SELECT * FROM entries WHERE id=", idwert)) +} \ No newline at end of file diff --git a/R/entry_edit_modal.R b/R/entry_edit_modal.R new file mode 100644 index 0000000..51bf23b --- /dev/null +++ b/R/entry_edit_modal.R @@ -0,0 +1,93 @@ +# entry_edit_mod.R +entryEditUI <- function(id) { + ns <- NS(id) + tagList( + + uiOutput(ns("entry_ui")), + uiOutput(ns("postings_ui")) + ) +} + +entryEditServer <- function(id, entry_id, conn) { + moduleServer(id, function(input, output, session) { + ns <- session$ns + + entry_postings <- reactiveVal(read_postings_by_entry(conn, entry_id)) + entry_data <- reactiveVal(read_entry(conn, entry_id)) + + output$entry_ui <- renderUI({ + e <- entry_data() + tagList( + fluidRow( + column(6, selectizeInput(ns("contact_id"), "Kontakt", + choices = get_contact_choices(conn), + selected = e$contact_id)), + column(6, textInput(ns("purpose"), "Verwendungszweck", value = e$purpose)) + ), + hr() + ) + }) + + # Choices für contact nach dem Flush setzen + + + output$postings_ui <- renderUI({ + alle <- entry_postings() + lapply(seq_len(nrow(alle)), function(i) { + print(paste("project selected:", alle$project_id[i])) + print(head(get_project_choices(conn))) + print(class(get_project_choices(conn))) + div( + style = "border-left: 3px solid #3c8dbc; padding: 5px; margin-bottom: 5px", + fluidRow( + column(3, + selectInput(ns(paste0("account_id_", i)), "Konto", + choices = get_account_choices(conn), + selected = as.character(alle$account_id[i])) + ), + column(3, selectizeInput(ns(paste0("project_id_", i)), "Projekt", + choices = get_project_choices(conn), + selected = ifelse(is.na(alle$project_id[i]), "", as.character(alle$project_id[i]))) + ), + column(2, numericInput(ns(paste0("amount_", i)), "Betrag", value = alle$amount[i])), + column(3, textInput(ns(paste0("notiz_", i)), "Notiz", value = alle$notiz[i])), + column(1, actionButton(ns(paste0("delete_", i)), "", icon = icon("trash"), class = "btn-danger btn-sm")) + ) + ) + }) + }) + + # Choices befüllen nachdem renderUI fertig ist + observe({ + alle <- entry_postings() + e <- entry_data() + req(nrow(alle) > 0, nrow(e) > 0) + + session$onFlushed(function() { + # Contact + updateSelectizeInput(session, "contact_id", + choices = get_contact_choices(conn), + selected = e$contact_id) + + # Postings + lapply(seq_len(nrow(alle)), function(i) { + updateSelectizeInput(session, paste0("account_id_", i), + choices = get_account_choices(conn), + selected = alle$account_id[i]) + updateSelectizeInput(session, paste0("project_id_", i), + choices = get_project_choices(conn), + selected = alle$project_id[i]) + }) + }, once = TRUE) + }) + + # Speichern-Logik + observeEvent(input$speichern, { + alle <- entry_postings() + lapply(seq_len(nrow(alle)), function(i) { + # update posting i in DB + }) + }) + + }) +} \ No newline at end of file diff --git a/R/projects/projects.R b/R/projects/projects.R new file mode 100644 index 0000000..be8d57a --- /dev/null +++ b/R/projects/projects.R @@ -0,0 +1,9 @@ +get_project_choices <- function(conn) { + projekte <- tbl(conn, "projects") |> + select(id, projektname) |> + collect() |> + arrange(projektname) + + choices <- setNames(as.character(projekte$id), projekte$projektname) + c("-" = "", choices) +} \ No newline at end of file diff --git a/db/development.sqlite3 b/db/development.sqlite3 new file mode 100644 index 0000000..65a0603 Binary files /dev/null and b/db/development.sqlite3 differ diff --git a/gemfin-shiny.Rproj b/gemfin-shiny.Rproj new file mode 100644 index 0000000..8e3c2eb --- /dev/null +++ b/gemfin-shiny.Rproj @@ -0,0 +1,13 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX diff --git a/global.R b/global.R new file mode 100644 index 0000000..b31571e --- /dev/null +++ b/global.R @@ -0,0 +1,19 @@ +library("dbplyr") +library("tidyverse") +library("shiny") +library("DBI") +library("RSQLite") +library("dbx") +library("shinydashboard") +library("reactable") +library("conflicted") +library("R.utils") + +conflicts_prefer(dplyr::select) +conflicts_prefer(dplyr::filter) + +options(shiny.reactlog = TRUE) +options(shiny.error = browser) + +conn <- dbConnect(RSQLite::SQLite(), "db/development.sqlite3") +sourceDirectory("R/") diff --git a/server.R b/server.R new file mode 100644 index 0000000..a1cfc38 --- /dev/null +++ b/server.R @@ -0,0 +1,5 @@ +server <- function(input, output) { + + buchungenServer("buchungen_tab") + +} diff --git a/ui.R b/ui.R new file mode 100644 index 0000000..fb83fc7 --- /dev/null +++ b/ui.R @@ -0,0 +1,24 @@ +## ui.R ## +dashboardPage( + dashboardHeader(), + ## Sidebar content + dashboardSidebar( + sidebarMenu( + menuItem("buchungen", tabName = "buchungen", icon = icon("dashboard")), + menuItem("konten", tabName = "Konten", icon = icon("th")) + ) + ), + dashboardBody( + tabItems( + # First tab content + tabItem(tabName = "buchungen", + buchungenUI("buchungen_tab") + ), + + # Second tab content + tabItem(tabName = "widgets", + h2("Widgets tab content") + ) + ) + ) +)