add homekeeper stack

This commit is contained in:
2026-07-06 19:25:38 +02:00
commit 58983e6855
90 changed files with 6816 additions and 0 deletions
+209
View File
@@ -0,0 +1,209 @@
screen_apiaries_ui <- function(id) {
ns <- NS(id)
shiny::tagList(
shiny::div(class = "mb-3", style = "border-radius:8px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,.15)",
leaflet::leafletOutput(ns("overview_map"), height = "200px", width = "100%")
),
shiny::uiOutput(ns("cards")),
shiny::br(),
shiny::actionButton(ns("add_btn"), shiny::tagList(shiny::icon("plus"), " Neuer Stand"),
class = "add-dashed btn", style = "width:100%;min-height:80px"
)
)
}
screen_apiaries_server <- function(id, db, nav) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
reload <- reactiveVal(0)
edit_id <- reactiveVal(NULL)
add_coords <- reactiveValues(lat = NA_real_, lng = NA_real_)
edit_coords <- reactiveValues(lat = NA_real_, lng = NA_real_)
apiaries <- reactive({ reload(); db_apiaries_list(db) })
output$cards <- renderUI({
ap <- apiaries()
if (nrow(ap) == 0) return(shiny::p(class = "text-muted", "Noch keine Bienenstände angelegt."))
lapply(seq_len(nrow(ap)), function(i) {
row <- ap[i, ]
shiny::div(
class = "card tap-card mb-3",
onclick = sprintf("Shiny.setInputValue('%s', %d, {priority:'event'})",
ns("select"), row$id),
bslib::card_body(
class = "d-flex justify-content-between align-items-start",
shiny::div(
shiny::tags$strong(class = "fs-5", row$name),
shiny::tags$p(class = "text-muted mb-0 small",
if (!is.na(row$location_text) && nchar(row$location_text) > 0) row$location_text
)
),
shiny::div(
class = "d-flex flex-column align-items-end gap-1",
shiny::tags$span(class = "badge bg-secondary",
paste(row$colony_count, "Völker")),
shiny::div(
shiny::actionButton(ns(paste0("edit_", row$id)), shiny::icon("pencil"),
class = "btn btn-sm btn-link p-0 text-muted",
onclick = "event.stopPropagation()"
)
)
)
)
)
})
})
observeEvent(input$select, {
nav$apiary_id <- input$select
nav$screen <- "colonies"
})
# ---- Overview map -------------------------------------------------------
output$overview_map <- leaflet::renderLeaflet({
ap <- apiaries()
m <- leaflet::leaflet(options = leaflet::leafletOptions(zoomControl = FALSE, attributionControl = FALSE)) |>
leaflet::addTiles(group = "Karte") |>
leaflet::addProviderTiles("Esri.WorldImagery", group = "Satellit") |>
leaflet::addLayersControl(baseGroups = c("Karte", "Satellit"),
options = leaflet::layersControlOptions(collapsed = TRUE))
with_coords <- ap[!is.na(ap$lat) & !is.na(ap$lng), ]
if (nrow(with_coords) > 0) {
m <- leaflet::addMarkers(m,
lng = with_coords$lng, lat = with_coords$lat,
label = with_coords$name, layerId = as.character(with_coords$id),
clusterOptions = leaflet::markerClusterOptions()
)
} else {
m <- leaflet::setView(m, lng = 10.45, lat = 51.17, zoom = 6)
}
m
})
observeEvent(input$overview_map_marker_click, {
click <- input$overview_map_marker_click
if (!is.null(click$id)) {
nav$apiary_id <- as.integer(click$id)
nav$screen <- "colonies"
}
})
# ---- Add modal -----------------------------------------------------------
output$add_map <- leaflet::renderLeaflet({
leaflet::leaflet() |>
leaflet::addTiles(group = "Karte") |>
leaflet::addProviderTiles("Esri.WorldImagery", group = "Satellit") |>
leaflet::addLayersControl(baseGroups = c("Karte", "Satellit"),
options = leaflet::layersControlOptions(collapsed = TRUE)) |>
leaflet::setView(lng = 10.45, lat = 51.17, zoom = 6)
})
observeEvent(input$add_map_click, {
cl <- input$add_map_click
add_coords$lat <- cl$lat; add_coords$lng <- cl$lng
p <- leaflet::leafletProxy("add_map", session)
leaflet::clearMarkers(p)
leaflet::addMarkers(p, lng = cl$lng, lat = cl$lat)
})
observeEvent(input$add_btn, {
add_coords$lat <- NA_real_; add_coords$lng <- NA_real_
shiny::showModal(shiny::modalDialog(
title = "Neuer Bienenstand", size = "l",
footer = shiny::tagList(
shiny::modalButton("Abbrechen"),
shiny::actionButton(ns("confirm_add"), "Anlegen", class = "btn-primary")
),
shiny::textInput(ns("new_name"), "Name *"),
shiny::textInput(ns("new_loc"), "Standortbeschreibung"),
shiny::tags$p(class = "text-muted small mt-3 mb-1",
shiny::tags$em("Standort auf Karte setzen (optional):")),
leaflet::leafletOutput(ns("add_map"), height = "280px"),
shiny::uiOutput(ns("add_coords_lbl")),
shiny::textAreaInput(ns("new_notes"), "Notizen", rows = 2)
))
})
output$add_coords_lbl <- renderUI({
req(!is.na(add_coords$lat))
shiny::tags$p(class = "text-muted small mt-1",
sprintf("%.5f, %.5f", add_coords$lat, add_coords$lng))
})
observeEvent(input$confirm_add, {
req(nchar(trimws(input$new_name)) > 0)
db_apiaries_insert(db,
name = trimws(input$new_name), location_text = input$new_loc,
lat = add_coords$lat, lng = add_coords$lng, notes = input$new_notes
)
shiny::removeModal(); reload(reload() + 1L)
})
# ---- Edit modal ----------------------------------------------------------
output$edit_map <- leaflet::renderLeaflet({
lat <- if (!is.na(edit_coords$lat)) edit_coords$lat else 51.17
lng <- if (!is.na(edit_coords$lng)) edit_coords$lng else 10.45
zoom <- if (!is.na(edit_coords$lat)) 13L else 6L
m <- leaflet::leaflet() |>
leaflet::addTiles(group = "Karte") |>
leaflet::addProviderTiles("Esri.WorldImagery", group = "Satellit") |>
leaflet::addLayersControl(baseGroups = c("Karte", "Satellit"),
options = leaflet::layersControlOptions(collapsed = TRUE)) |>
leaflet::setView(lng = lng, lat = lat, zoom = zoom)
if (!is.na(edit_coords$lat)) m <- leaflet::addMarkers(m, lng = lng, lat = lat)
m
})
observeEvent(input$edit_map_click, {
cl <- input$edit_map_click
edit_coords$lat <- cl$lat; edit_coords$lng <- cl$lng
p <- leaflet::leafletProxy("edit_map", session)
leaflet::clearMarkers(p)
leaflet::addMarkers(p, lng = cl$lng, lat = cl$lat)
})
observe({
ap <- apiaries()
lapply(ap$id, function(aid) {
local({
lid <- aid
observeEvent(input[[paste0("edit_", lid)]], {
row <- db_apiaries_get(db, lid)
edit_id(lid)
edit_coords$lat <- row$lat %||% NA_real_
edit_coords$lng <- row$lng %||% NA_real_
shiny::showModal(shiny::modalDialog(
title = "Stand bearbeiten", size = "l",
footer = shiny::tagList(
shiny::modalButton("Abbrechen"),
shiny::actionButton(ns("confirm_edit"), "Speichern", class = "btn-primary"),
shiny::actionButton(ns("confirm_delete"), "Löschen", class = "btn-danger ms-auto")
),
shiny::textInput(ns("edit_name"), "Name *", value = row$name),
shiny::textInput(ns("edit_loc"), "Standortbeschreibung", value = row$location_text %||% ""),
shiny::tags$p(class = "text-muted small mt-3 mb-1",
shiny::tags$em("Standort auf Karte setzen:")),
leaflet::leafletOutput(ns("edit_map"), height = "280px"),
shiny::uiOutput(ns("edit_coords_lbl")),
shiny::textAreaInput(ns("edit_notes"), "Notizen", rows = 2, value = row$notes %||% "")
))
}, ignoreInit = TRUE)
})
})
})
output$edit_coords_lbl <- renderUI({
req(!is.na(edit_coords$lat))
shiny::tags$p(class = "text-muted small mt-1",
sprintf("%.5f, %.5f", edit_coords$lat, edit_coords$lng))
})
observeEvent(input$confirm_edit, {
req(edit_id())
db_apiaries_update(db, id = edit_id(),
name = trimws(input$edit_name), location_text = input$edit_loc,
lat = edit_coords$lat, lng = edit_coords$lng, notes = input$edit_notes
)
shiny::removeModal(); reload(reload() + 1L)
})
observeEvent(input$confirm_delete, {
req(edit_id())
db_apiaries_delete(db, edit_id())
shiny::removeModal(); reload(reload() + 1L)
})
})
}