# 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")
output$sales_date <- renderText({
paste("Last sale date included was", last_sale_date)
})
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)
})
# 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
)
})
}