All checks were successful
Deploy stAndrews / deploy (push) Successful in 5s
321 lines
8.9 KiB
R
321 lines
8.9 KiB
R
# 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(desc(listed_date)) |>
|
|
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(
|
|
"<b>", address, "</b><br>",
|
|
"Owner: ", owner_1, "<br>",
|
|
"Price: ", price_fmt, "<br>",
|
|
"Sq Ft: ", sqft, "<br>",
|
|
"$/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,
|
|
scrollX = TRUE,
|
|
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(
|
|
"<b>", address, "</b><br>",
|
|
"Sale Date: ", listed_date, "<br>",
|
|
"Price: ", price_fmt, "<br>",
|
|
"Sq Ft: ", sqft, "<br>",
|
|
"$/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,
|
|
scrollX = TRUE,
|
|
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
|
|
)
|
|
})
|
|
}
|