screen_colony_ui <- function(id) { ns <- NS(id) shiny::tagList( shiny::uiOutput(ns("header_card")), shiny::div( class = "row g-2 mb-4", lapply(list( list(id = "btn_insp", label = "Durchsicht", icon = "clipboard-list", cls = "btn-primary", screen = "inspection"), list(id = "btn_varroa", label = "Varroa", icon = "bug", cls = "btn-warning", screen = "varroa"), list(id = "btn_harvest",label = "Ernte", icon = "jar", cls = "btn-success", screen = "harvest"), list(id = "btn_queen", label = "Königin", icon = "crown", cls = "btn-info", screen = "queens"), list(id = "btn_check", label = "Checkliste", icon = "list-check", cls = "btn-secondary", screen = "checklists"), list(id = "btn_actions", label = "Maßnahmen", icon = "bolt", cls = "btn-info", screen = "actions"), list(id = "btn_edit", label = "Bearbeiten", icon = "pencil", cls = "btn-outline-secondary", screen = NULL) ), function(b) { shiny::div(class = "col-6 col-md-4", shiny::actionButton(ns(b$id), shiny::tagList(shiny::icon(b$icon), shiny::tags$br(), b$label), class = paste("btn action-btn w-100", b$cls) ) ) }) ), shiny::uiOutput(ns("activity_log")) ) } screen_colony_server <- function(id, db, nav) { moduleServer(id, function(input, output, session) { ns <- session$ns reload <- reactiveVal(0) edit_insp_id <- reactiveVal(NULL) log_filter <- reactiveVal("alle") log_sel_type <- reactiveVal(NULL) log_sel_id <- reactiveVal(NULL) observeEvent(nav$screen, { if (nav$screen == "colony") { reload(reload() + 1L) log_filter("alle") } }, ignoreInit = TRUE) lapply(c("alle", "durchsicht", "varroa", "massnahme", "ernte"), function(f) { observeEvent(input[[paste0("log_filter_", f)]], { log_filter(f) }, ignoreInit = TRUE) }) colony <- reactive({ req(nav$colony_id); reload() db_colonies_get(db, nav$colony_id) }) output$header_card <- renderUI({ co <- colony() req(nrow(co) > 0) last <- db_inspections_last(db, nav$colony_id) bslib::card( class = "mb-3", bslib::card_body( shiny::div(class = "d-flex justify-content-between align-items-start mb-2", shiny::div( shiny::tags$p(class = "text-muted small mb-0", co$apiary_name %||% ""), if (!is.na(co$parent_name)) shiny::tags$p(class = "text-muted small mb-0", paste("Abstammung:", co$parent_name)) ), shiny::uiOutput(ns("status_selector")) ), if (nrow(last) > 0) shiny::div(class = "border-top pt-2 mt-2", shiny::tags$small(class = "text-muted", paste0("Letzte Durchsicht: ", last$date[1]), shiny::tags$br(), paste0( if (!is.na(last$brood_frames)) paste(last$brood_frames, "BW") else "", if (!is.na(last$honey_frames)) paste0(" ", last$honey_frames, " HW") else "", if (!is.na(last$colony_strength)) paste0(" Stärke ", last$colony_strength) else "", if (last$queen_seen) " ♛ gesehen" else "" ) ) ) ) ) }) output$status_selector <- renderUI({ co <- colony() req(nrow(co) > 0) shiny::div( class = "d-flex flex-wrap gap-1", lapply(seq_along(COLONY_STATUSES), function(i) { s <- COLONY_STATUSES[[i]] lbl <- names(COLONY_STATUSES)[i] cls <- if (co$status == s) { paste("btn btn-sm", paste0("btn-", colony_status_color(s))) } else "btn btn-sm btn-outline-secondary" shiny::actionButton(ns(paste0("status_", i)), lbl, class = cls) }) ) }) observe({ lapply(seq_along(COLONY_STATUSES), function(i) { local({ li <- i; s <- COLONY_STATUSES[[i]] observeEvent(input[[paste0("status_", li)]], { req(nav$colony_id) if (s == "eingegangen") { shiny::showModal(shiny::modalDialog( title = "Volk als eingegangen markieren?", footer = shiny::tagList( shiny::modalButton("Abbrechen"), shiny::actionButton(ns("confirm_close"), "Bestätigen", class = "btn-danger") ), shiny::p("Das Volk wird als eingegangen markiert.") )) } else { db_colonies_update(db, id = nav$colony_id, name = isolate(colony()$name), apiary_id = isolate(colony()$apiary_id), status = s, notes = isolate(colony()$notes) ) reload(reload() + 1L) } }, ignoreInit = TRUE) }) }) }) observeEvent(input$confirm_close, { db_colonies_close(db, nav$colony_id) shiny::removeModal() nav$screen <- "colonies" }) # Action button navigation observeEvent(input$btn_insp, nav$screen <- "inspection") observeEvent(input$btn_varroa, nav$screen <- "varroa") observeEvent(input$btn_harvest, nav$screen <- "harvest") observeEvent(input$btn_queen, nav$screen <- "queens") observeEvent(input$btn_check, nav$screen <- "checklists") observeEvent(input$btn_actions, nav$screen <- "actions") # Edit colony modal observeEvent(input$btn_edit, { co <- colony() ap <- db_apiaries_list(db) shiny::showModal(shiny::modalDialog( title = paste("Volk bearbeiten:", co$name), footer = shiny::tagList( shiny::modalButton("Abbrechen"), shiny::actionButton(ns("confirm_edit"), "Speichern", class = "btn-primary") ), shiny::textInput(ns("edit_name"), "Name *", value = co$name), shiny::selectInput(ns("edit_apiary"), "Bienenstand", choices = setNames(ap$id, ap$name), selected = co$apiary_id ), shiny::textAreaInput(ns("edit_notes"), "Notizen", rows = 2, value = co$notes %||% "") )) }) observeEvent(input$confirm_edit, { req(nchar(trimws(input$edit_name)) > 0) co <- isolate(colony()) db_colonies_update(db, id = nav$colony_id, name = trimws(input$edit_name), apiary_id = as.integer(input$edit_apiary), status = co$status, notes = input$edit_notes ) shiny::removeModal(); reload(reload() + 1L) }) output$activity_log <- renderUI({ reload() req(nav$colony_id) log <- db_colony_log(db, nav$colony_id) flt <- log_filter() type_label <- c( durchsicht = "Durchsicht", kontrolle = "Kontrolle", behandlung = "Behandlung", nachkontrolle = "Nachkontrolle", ernte = "Ernte", massnahme = "Maßnahme" ) type_badge <- c( durchsicht = "bg-primary", kontrolle = "bg-warning text-dark", behandlung = "bg-danger", nachkontrolle = "bg-secondary", ernte = "bg-success", massnahme = "bg-info text-dark" ) filter_cats <- list( list(id = "alle", label = "Alle"), list(id = "durchsicht", label = "Durchsicht"), list(id = "varroa", label = "Varroa"), list(id = "massnahme", label = "Maßnahmen"), list(id = "ernte", label = "Ernte") ) chips <- shiny::div(class = "d-flex flex-wrap gap-1 px-3 pt-3 pb-2", lapply(filter_cats, function(f) { cls <- if (flt == f$id) "btn btn-sm btn-primary" else "btn btn-sm btn-outline-secondary" shiny::actionButton(ns(paste0("log_filter_", f$id)), f$label, class = cls) }) ) if (nrow(log) > 0 && flt != "alle") { keep <- switch(flt, durchsicht = log$type == "durchsicht", varroa = log$type %in% c("kontrolle", "behandlung", "nachkontrolle"), massnahme = log$type == "massnahme", ernte = log$type == "ernte", rep(TRUE, nrow(log)) ) log <- log[keep, , drop = FALSE] } items <- if (nrow(log) == 0) { shiny::div(class = "px-3 py-2 text-muted small", "Keine Einträge.") } else { shiny::tags$ul(class = "list-group list-group-flush", lapply(seq_len(nrow(log)), function(i) { r <- log[i, ] is_insp <- r$type == "durchsicht" onclick_val <- if (is_insp) sprintf("Shiny.setInputValue('%s', %s, {priority:'event'})", ns("insp_select"), r$ref_id) else sprintf("Shiny.setInputValue('%s', '%s:%s', {priority:'event'})", ns("log_select"), r$type, r$ref_id) shiny::tags$li( class = "list-group-item py-2 px-3 tap-card", onclick = onclick_val, shiny::div(class = "d-flex justify-content-between align-items-center gap-2", shiny::div(class = "d-flex align-items-center gap-2 min-width-0", shiny::tags$span(class = paste("badge flex-shrink-0", type_badge[r$type]), type_label[r$type]), shiny::tags$span(class = "small text-truncate", r$summary) ), shiny::div(class = "d-flex align-items-center gap-1 flex-shrink-0", shiny::tags$span(class = "text-muted small", r$date), shiny::icon("chevron-right", class = "text-muted small") ) ) ) }) ) } bslib::card( class = "mb-3", bslib::card_header("Aktivitätslog"), bslib::card_body(class = "p-0", chips, items) ) }) observeEvent(input$insp_select, { id <- as.integer(input$insp_select) insp <- db_inspections_get(db, id) req(nrow(insp) > 0) traits_df <- db_inspections_traits(db, id) get_trait <- function(tval) { s <- traits_df$score[traits_df$trait == tval] if (length(s) > 0 && !is.na(s[1])) as.character(s[1]) else "" } edit_insp_id(id) shiny::showModal(shiny::modalDialog( title = paste("Durchsicht", insp$date), size = "l", footer = shiny::tagList( shiny::modalButton("Abbrechen"), shiny::actionButton(ns("confirm_insp_edit"), "Speichern", class = "btn-primary"), shiny::actionButton(ns("confirm_insp_delete"), "Löschen", class = "btn-danger ms-auto") ), shiny::div(class = "row g-2", shiny::div(class = "col-6", shiny::dateInput(ns("ei_date"), "Datum", value = as.Date(insp$date), language = "de", weekstart = 1) ), shiny::div(class = "col-6", shiny::textInput(ns("ei_inspector"), "Imker/in", value = insp$inspector %||% "") ) ), shiny::div(class = "row g-2 mt-1", shiny::div(class = "col-4", shiny::numericInput(ns("ei_brood"), "Brutwaben", value = insp$brood_frames, min = 0, step = 1) ), shiny::div(class = "col-4", shiny::numericInput(ns("ei_honey"), "Honigwaben", value = insp$honey_frames, min = 0, step = 1) ), shiny::div(class = "col-4", shiny::numericInput(ns("ei_food"), "Futter (kg)", value = insp$food_stores_kg, min = 0, step = 0.5) ) ), shiny::div(class = "row g-2 mt-1", shiny::div(class = "col-6", shiny::selectInput(ns("ei_strength"), "Volksstärke", choices = c("–" = "", setNames(as.character(1:9), 1:9)), selected = if (!is.na(insp$colony_strength)) as.character(insp$colony_strength) else "" ) ), shiny::div(class = "col-6", shiny::numericInput(ns("ei_qcells"), "Weiselzellen", value = insp$queen_cells_count %||% 0, min = 0, step = 1) ) ), shiny::div(class = "d-flex gap-4 mt-2 mb-2", shiny::checkboxInput(ns("ei_queen"), "Königin gesehen", value = isTRUE(insp$queen_seen == 1)), shiny::checkboxInput(ns("ei_eggs"), "Eier / Stifte gesehen", value = isTRUE(insp$eggs_seen == 1)) ), shiny::div(class = "row g-2", lapply(names(TRAITS), function(tname) { tval <- TRAITS[[tname]] shiny::div(class = "col-6", shiny::selectInput(ns(paste0("ei_t_", tval)), tname, choices = c("–" = "", setNames(as.character(1:4), 1:4)), selected = get_trait(tval) ) ) }) ), shiny::textAreaInput(ns("ei_notes"), "Notizen / Auffälligkeiten", value = insp$notes %||% "", rows = 3, width = "100%") )) }) observeEvent(input$confirm_insp_edit, { id <- edit_insp_id(); req(id) traits <- setNames( lapply(TRAITS, function(tval) { v <- input[[paste0("ei_t_", tval)]] if (!is.null(v) && v != "") as.integer(v) else NA_integer_ }), TRAITS ) db_inspections_update(db, id = id, date = input$ei_date, inspector = input$ei_inspector, brood_frames = input$ei_brood, honey_frames = input$ei_honey, food_stores_kg = input$ei_food, colony_strength = if (!is.null(input$ei_strength) && input$ei_strength != "") as.integer(input$ei_strength) else NA_integer_, queen_seen = isTRUE(input$ei_queen), eggs_seen = isTRUE(input$ei_eggs), queen_cells_count = as.integer(input$ei_qcells %||% 0L), notes = input$ei_notes, traits = traits ) shiny::removeModal(); reload(reload() + 1L) }) observeEvent(input$confirm_insp_delete, { id <- edit_insp_id(); req(id) db_inspections_delete(db, id) shiny::removeModal(); reload(reload() + 1L) }) observeEvent(input$log_select, { parts <- strsplit(input$log_select, ":", fixed = TRUE)[[1]] type <- parts[1] id <- as.integer(parts[2]) log_sel_type(type); log_sel_id(id) footer <- shiny::tagList( shiny::modalButton("Abbrechen"), shiny::actionButton(ns("confirm_log_save"), "Speichern", class = "btn-primary"), shiny::actionButton(ns("confirm_log_delete"), "Löschen", class = "btn-danger ms-auto") ) if (type == "kontrolle") { vc <- db_varroa_counts_get(db, id); req(nrow(vc) > 0) shiny::showModal(shiny::modalDialog( title = paste("Varroa-Kontrolle", vc$date), footer = footer, shiny::div(class = "row g-2", shiny::div(class = "col-6", shiny::dateInput(ns("el_date_from"), "Von", value = if (!is.na(vc$date_from)) as.Date(vc$date_from) else NA, language = "de", weekstart = 1) ), shiny::div(class = "col-6", shiny::dateInput(ns("el_date"), "Bis", value = as.Date(vc$date), language = "de", weekstart = 1) ) ), shiny::numericInput(ns("el_count"), "Anzahl Milben", value = vc$count, min = 0, step = 1, width = "100%"), shiny::selectInput(ns("el_method"), "Methode", choices = VARROA_METHODS, selected = vc$method), shiny::textAreaInput(ns("el_notes"), "Notizen", value = vc$notes %||% "", rows = 2, width = "100%") )) } else if (type == "behandlung") { vt <- db_varroa_treatments_get(db, id); req(nrow(vt) > 0) shiny::showModal(shiny::modalDialog( title = paste("Varroa-Behandlung", vt$date), footer = footer, shiny::dateInput(ns("el_date"), "Datum", value = as.Date(vt$date), language = "de", weekstart = 1), shiny::selectizeInput(ns("el_product"), "Mittel", choices = c("Mittel wählen…" = "", VARROA_PRODUCTS), selected = vt$product, options = list(create = TRUE)), shiny::textInput(ns("el_method_txt"), "Anwendung", value = vt$method %||% ""), shiny::textInput(ns("el_dosage"), "Dosierung", value = vt$dosage %||% ""), shiny::textAreaInput(ns("el_notes"), "Notizen", value = vt$notes %||% "", rows = 2, width = "100%") )) } else if (type == "nachkontrolle") { vc2 <- db_varroa_controls_get(db, id); req(nrow(vc2) > 0) shiny::showModal(shiny::modalDialog( title = paste("Nachkontrolle", vc2$date), footer = footer, shiny::dateInput(ns("el_date"), "Datum", value = as.Date(vc2$date), language = "de", weekstart = 1), shiny::textInput(ns("el_result"), "Ergebnis / Befund", value = vc2$result %||% ""), shiny::textAreaInput(ns("el_notes"), "Notizen", value = vc2$notes %||% "", rows = 2, width = "100%") )) } else if (type == "ernte") { h <- db_harvests_get(db, id); req(nrow(h) > 0) shiny::showModal(shiny::modalDialog( title = paste("Honigernte", h$date), footer = footer, shiny::dateInput(ns("el_date"), "Datum", value = as.Date(h$date), language = "de", weekstart = 1), shiny::numericInput(ns("el_weight"), "Gewicht (kg)", value = h$weight_kg, min = 0, step = 0.1, width = "100%"), shiny::textAreaInput(ns("el_notes"), "Notizen", value = h$notes %||% "", rows = 2, width = "100%") )) } else if (type == "massnahme") { ac <- db_actions_get(db, id); req(nrow(ac) > 0) shiny::showModal(shiny::modalDialog( title = paste("Maßnahme", ac$date), footer = footer, shiny::dateInput(ns("el_date"), "Datum", value = as.Date(ac$date), language = "de", weekstart = 1), shiny::selectInput(ns("el_action_type"), "Maßnahme", choices = COLONY_ACTION_TYPES, selected = ac$type), shiny::textAreaInput(ns("el_notes"), "Notizen", value = ac$notes %||% "", rows = 2, width = "100%") )) } }) observeEvent(input$confirm_log_save, { type <- log_sel_type(); id <- log_sel_id(); req(type, id) if (type == "kontrolle") { db_varroa_counts_update(db, id, date_from = input$el_date_from, date = input$el_date, method = input$el_method, count = input$el_count, notes = input$el_notes) } else if (type == "behandlung") { db_varroa_treatments_update(db, id, date = input$el_date, product = input$el_product, method = input$el_method_txt, dosage = input$el_dosage, notes = input$el_notes) } else if (type == "nachkontrolle") { db_varroa_controls_update(db, id, date = input$el_date, result = input$el_result, notes = input$el_notes) } else if (type == "ernte") { db_harvests_update(db, id, date = input$el_date, weight_kg = input$el_weight, notes = input$el_notes) } else if (type == "massnahme") { db_actions_update(db, id, date = input$el_date, type = input$el_action_type, notes = input$el_notes) } shiny::removeModal(); reload(reload() + 1L) }) observeEvent(input$confirm_log_delete, { type <- log_sel_type(); id <- log_sel_id(); req(type, id) switch(type, kontrolle = db_varroa_counts_delete(db, id), behandlung = db_varroa_treatments_delete(db, id), nachkontrolle = db_varroa_controls_delete(db, id), ernte = db_harvests_delete(db, id), massnahme = db_actions_delete(db, id) ) shiny::removeModal(); reload(reload() + 1L) }) }) }