# Server logic for St. Andrews Shiny App create_server <- function(input, output, session) { # Load data owners <- readRDS(app_config$data_paths$owners) listings <- readRDS(app_config$data_paths$listings) |> arrange(price_per_sqft) |> mutate( price_fmt = scales::dollar(price), ppsf_fmt = scales::dollar(price_per_sqft) ) sales <- readRDS(app_config$data_paths$sales) |> arrange(desc(listed_date)) |> mutate( price_fmt = scales::dollar(price), ppsf_fmt = scales::dollar(price_per_sqft) ) last_sale_date <- format(attr(owners, "last_sale_date"), "%Y-%m-%d") sbdvn <- sf::st_read(app_config$data_paths$plats) beaches <- readRDS(app_config$data_paths$beaches) # update tabs depending on side panel observeEvent(input$menu, { updateF7Tabs(id = "tabs", selected = input$menu) }) # resources sub-section toggle res_section <- reactiveVal("beaches") observeEvent(input$res_beaches, { res_section("beaches") }) observeEvent(input$res_links, { res_section("links") }) observeEvent(input$res_restart, { res_section("restart") }) output$resources_content <- renderUI({ if (res_section() == "beaches") { tagList( f7Card( title = "Beaches", divider = TRUE, leafletOutput("beach_map"), footer = p("Source: Environmental Protection Agency") ), f7Block( h3("Helpful Links:"), f7List( inset = TRUE, dividers = TRUE, strong = TRUE, outline = FALSE, f7ListItem(title = "EPA Beaches", href = "https://www.epa.gov/beaches", external = TRUE), f7ListItem(title = "Red Tide Forecast", href = "https://habforecast.gcoos.org/", external = TRUE), f7ListItem(title = "Healthy Beaches Program", href = "https://fdoh.maps.arcgis.com/apps/instant/nearby/index.html?appid=7106a20597de4bff98cc5ebc7f932047&findSource=0&find=1600%2520Harbor%2520Dr%2520S%252C%2520Venice%252C%2520Florida%252C%252034285&sliderDistance=2", external = TRUE), f7ListItem(title = "MOTE Beach Conditions", href = "https://visitbeaches.org/beach/6/report/53033", external = TRUE), f7ListItem(title = "National Hurricane Center", href = "https://www.nhc.noaa.gov/", external = TRUE) ) ) ) } else { tagList( f7BlockTitle(title = "Services:", size = "medium"), f7Block( f7List( mode = "links", inset = TRUE, outline = TRUE, dividers = TRUE, strong = TRUE, f7Link(label = "City of Venice", href = "https://www.venicegov.com/"), f7Link(label = "Florida Power & Light", href = "https://www.fpl.com/"), f7Link(label = "Sarasota County", href = "https://www.scgov.net/"), f7Link(label = "Property Appraiser", href = "https://www.sc-pa.com/"), f7Link(label = "Open GIS Portal", href = "https://data-sarco.opendata.arcgis.com/"), f7Link(label = "Waste & Recycling", href = "https://www.venicegov.com/government/public-works/waste-and-recycling"), f7Link(label = "Condo Regulation", href = "https://condos.myfloridalicense.com/"), f7Link(label = "Property Records Search", href = "https://www.sarasotaclerk.com/records/official-records/search-land-records") ) ), f7BlockTitle(title = "Documents:", size = "medium"), f7Block( f7List( mode = "links", inset = TRUE, outline = TRUE, dividers = TRUE, strong = TRUE, f7Link(label = "St. Andrews Covenants", href = "docs/2000_01_01_st_andrews_covenants.pdf"), f7Link(label = "St. Andrews (unrecorded)", href = "docs/2004_06_23_sap_map.pdf"), f7Link(label = "Patios 2", href = "docs/1997_08_12_patios_2_plat.pdf"), f7Link(label = "Patios 3", href = "docs/1998_11_17_patios_3_plat.pdf"), f7Link(label = "Villas 2", href = "docs/1998_09_14_villas_2_plat.pdf") ) ) ) } }) # Initialize filteredOwners with all owners filteredOwners <- reactiveVal(owners) # Function to filter owners based on inputs filterOwners <- function() { filtered <- owners # Filter by subdivision if specified if (!is.null(input$sub_name) && input$sub_name != "All") { # Get the subdivision IDs for the selected subdivision name sub_ids <- sbdvn %>% filter(sub_name == input$sub_name) %>% pull(max_sub_id) filtered <- filtered %>% filter(subdivision %in% sub_ids) } # Filter by name if specified if (!is.null(input$name) && input$name != "") { filtered <- filtered %>% filter( grepl(input$name, owner_1, ignore.case = TRUE) | grepl(input$name, owner_2, ignore.case = TRUE) ) } # Filter by location if specified if (!is.null(input$location) && input$location != "") { filtered <- filtered %>% filter(grepl(input$location, location, ignore.case = TRUE)) } return(filtered) } # Update filtered owners when filter button is clicked observeEvent(input$filterButton, { filteredOwners(filterOwners()) }) # Also update when any of the filter inputs change observe({ # Track dependencies input$name input$location input$sub_name # Update filtered owners, but only if the filter button has been clicked at least once # To prevent immediate filtering on app start if (!is.null(input$filterButton)) { if (input$filterButton > 0) { filteredOwners(filterOwners()) } } }) mean_lat <- reactive({ if (!is.null(filteredOwners()) && nrow(filteredOwners()) > 0) { filteredOwners() %>% st_coordinates() %>% .[, "Y"] %>% mean() } else { app_config$map_config$default_lat } }) mean_lng <- reactive({ if (!is.null(filteredOwners()) && nrow(filteredOwners()) > 0) { filteredOwners() %>% st_coordinates() %>% .[, "X"] %>% mean() } else { app_config$map_config$default_lng } }) output$map <- renderLeaflet({ leaflet() %>% addProviderTiles("CartoDB.Voyager") %>% addPolygons( data = sbdvn, color = "red", weight = 2, opacity = 0.5, fillOpacity = 0.2, label = ~sub_name, group = "Subdivisions" ) }) # Update map markers when filteredOwners changes observe({ leafletProxy("map") %>% clearGroup("Owners") %>% addMarkers( data = filteredOwners(), popup = popupTable( filteredOwners(), row.numbers = FALSE, feature.id = FALSE, zcol = c( "label", "owner_1", "owner_2" ) ), group = "Owners" ) %>% addLayersControl( overlayGroups = c("Subdivisions", "Owners"), options = layersControlOptions(collapsed = FALSE) ) %>% setView(lng = mean_lng(), lat = mean_lat(), zoom = app_config$map_config$default_zoom) }) output$table <- renderDT({ if (!is.null(filteredOwners()) && nrow(filteredOwners()) > 0) { my_table <- filteredOwners() %>% st_drop_geometry() %>% select(label, owner_1, owner_2, homestead) datatable(my_table, colnames = c("Address", "Owner 1", "Owner 2", "Homestead"), rownames = FALSE, options = list( pageLength = 10, scrollX = TRUE, searching = FALSE, lengthMenu = c(5, 10, 25, 50), dom = 'tpi' ) ) } else { # Return empty table with same structure my_table <- data.frame( label = character(0), owner_1 = character(0), owner_2 = character(0), homestead = numeric(0) ) datatable(my_table, colnames = c("Address", "Owner 1", "Owner 2", "Homestead"), rownames = FALSE, options = list( pageLength = 10, scrollX = TRUE, searching = FALSE, lengthMenu = c(5, 10, 25, 50), dom = 'tpi' ) ) } }) prep_mailing <- function(data) { data |> sf::st_drop_geometry() |> dplyr::select(owner_1, owner_2, mailing_address_1, mailing_address_2, mailing_city, mailing_state, mailing_zip_code) |> dplyr::mutate( owner_2 = ifelse(is.na(owner_2), "", owner_2), mailing_address_2 = ifelse(is.na(mailing_address_2), "", mailing_address_2) ) |> dplyr::rename( address_1 = mailing_address_1, address_2 = mailing_address_2, city = mailing_city, state = mailing_state, zip = mailing_zip_code ) } output$download_filtered <- downloadHandler( filename = "st_andrews_owners_filtered.csv", content = function(file) { write.csv(prep_mailing(filteredOwners()), file, row.names = FALSE) } ) output$download_all <- downloadHandler( filename = "st_andrews_owners_all.csv", content = function(file) { write.csv(prep_mailing(owners), file, row.names = FALSE) } ) # listings map ---- output$listings_map <- renderLeaflet({ leaflet(listings) |> addProviderTiles("CartoDB.Voyager") |> addPolygons( data = sbdvn, color = "red", weight = 2, opacity = 0.5, fillOpacity = 0.2, label = ~sub_name ) |> addMarkers( lat = ~latitude, lng = ~longitude, popup = ~paste0( "", address, "
", "Price: ", price_fmt, "
", "Sq Ft: ", sqft, "
", "$/Sq Ft: ", ppsf_fmt ) ) |> setView(lng = app_config$map_config$default_lng, lat = app_config$map_config$default_lat, zoom = app_config$map_config$default_zoom) }) # listings table ---- output$listings_table <- renderDT({ datatable( listings |> select(listed_date, address, sqft, price_fmt, ppsf_fmt), colnames = c("Listed", "Address", "Sq Ft", "Price", "$/Sq Ft"), rownames = FALSE, options = list( pageLength = 25, searching = FALSE, dom = 't' ) ) }) # sales map ---- output$sales_map <- renderLeaflet({ leaflet(sales) |> addProviderTiles("CartoDB.Voyager") |> addPolygons( data = sbdvn, color = "red", weight = 2, opacity = 0.5, fillOpacity = 0.2, label = ~sub_name ) |> addMarkers( lat = ~latitude, lng = ~longitude, popup = ~paste0( "", address, "
", "Sale Date: ", listed_date, "
", "Price: ", price_fmt, "
", "Sq Ft: ", sqft, "
", "$/Sq Ft: ", ppsf_fmt ) ) |> setView(lng = app_config$map_config$default_lng, lat = app_config$map_config$default_lat, zoom = app_config$map_config$default_zoom) }) # sales table ---- output$sales_table <- renderDT({ datatable( sales |> select(listed_date, address, sqft, price_fmt, ppsf_fmt), colnames = c("Date", "Address", "Sq Ft", "Price", "$/Sq Ft"), rownames = FALSE, options = list( pageLength = 10, searching = FALSE, dom = 't' ) ) }) # beach map ---- output$beach_map <- renderLeaflet({ leaflet() %>% addProviderTiles("CartoDB.Voyager") %>% setView(lng = app_config$map_config$beach_lng, lat = app_config$map_config$beach_lat, zoom = app_config$map_config$beach_zoom) %>% addMarkers( data = beaches, lat = ~lat, lng = ~lng, popup = ~beach_name ) }) } # Server logic for St. Andrews Shiny App library(shiny) library(shinyMobile) library(sf) library(dplyr) library(leaflet) library(DT) create_server <- function(input, output, session) { # Load configuration source("./R/config.R", local = TRUE) # Load owners data owners_data <- reactive({ req(app_config$data_paths$owners) data <- readRDS(app_config$data_paths$owners) return(data) }) # Get last sale date last_sale_date <- reactive({ owners <- owners_data() date <- attr(owners, "last_sale_date") if (is.null(date)) { return(Sys.Date()) } return(date) }) # Display last sale date output$last_sale_date_display <- renderText({ date <- last_sale_date() paste("Last sale date:", format(date, "%B %d, %Y")) }) # More server logic for the map, table, etc. would go here # For now, I'll add placeholders for the outputs referenced in the UI # Map output output$map <- renderLeaflet({ leaflet() %>% addTiles() %>% setView(lng = -82.4, lat = 27.1, zoom = 12) }) # Table output output$table <- renderDT({ datatable(data.frame(Note = "Owner data would be displayed here")) }) # Listings map output$listings_map <- renderLeaflet({ leaflet() %>% addTiles() %>% setView(lng = -82.4, lat = 27.1, zoom = 12) }) # Listings table output$listings_table <- renderDT({ datatable(data.frame(Note = "Active listings would be displayed here")) }) # Sales map output$sales_map <- renderLeaflet({ leaflet() %>% addTiles() %>% setView(lng = -82.4, lat = 27.1, zoom = 12) }) # Sales table output$sales_table <- renderDT({ datatable(data.frame(Note = "Recent sales would be displayed here")) }) # Resources content output$resources_content <- renderUI({ f7Card( title = "Community Resources", "Links and documents will be displayed here." ) }) # Download handlers output$download_filtered <- downloadHandler( filename = function() { paste("owners-filtered-", Sys.Date(), ".csv", sep = "") }, content = function(file) { write.csv(data.frame(Note = "Filtered owner data"), file) } ) output$download_all <- downloadHandler( filename = function() { paste("owners-all-", Sys.Date(), ".csv", sep = "") }, content = function(file) { write.csv(data.frame(Note = "All owner data"), file) } ) # Button observers for resources tab observeEvent(input$res_beaches, { f7Dialog( title = "Beaches", text = "Beach information would be displayed here." ) }) observeEvent(input$res_links, { f7Dialog( title = "Links", text = "Community links would be displayed here." ) }) observeEvent(input$res_restart, { session$reload() }) }