Files
2026-07-06 19:25:38 +02:00

491 lines
20 KiB
R
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
})
})
}