From ca91f2189b2366f049cc03b2f566327da3fd61e5 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Wed, 4 Mar 2026 13:05:45 -0700 Subject: [PATCH 01/16] create demo app --- apps/data_library/app-text.R | 25 ++ apps/data_library/app.R | 494 +++++++++++++++++++++++++++++++++ apps/data_library/components.R | 208 ++++++++++++++ apps/data_library/styles.css | 136 +++++++++ 4 files changed, 863 insertions(+) create mode 100644 apps/data_library/app-text.R create mode 100644 apps/data_library/app.R create mode 100644 apps/data_library/components.R create mode 100644 apps/data_library/styles.css diff --git a/apps/data_library/app-text.R b/apps/data_library/app-text.R new file mode 100644 index 0000000..6e760c8 --- /dev/null +++ b/apps/data_library/app-text.R @@ -0,0 +1,25 @@ +# Demo App Text +# Contains text displayed on the landing page tabs. + +# About Page Text +welcome_text = "This demo application allows you to explore and search the reports and datasets + available in the Demo platform from one central location. +

+ Select Reports or Data Catalog from the sidebar to begin + exploring, or click the '+' to expand the boxes below to learn more." + +reports_welcome_text = "The Reports page contains links to and information about the different + interactive dashboards and other analyses hosted in the Demo platform. Scroll to browse + through the reports or use the Technical Area and Tags filters in the sidebar + to narrow your selection. +

+ Click on the report title to be directed to the report in a new tab." + +data_welcome_text = "The Data Catalog page contains information about datasets available in the Demo platform. + Scroll to browse through the datasets or use the Technical Area and Tags filters + in the sidebar to narrow your selection. +

+ Each dataset card includes a brief description, last updated date, and access + restrictions." + +help_welcome_text = "This is a demo app that demonstrates how an organization could use the Civis Platform" diff --git a/apps/data_library/app.R b/apps/data_library/app.R new file mode 100644 index 0000000..c256185 --- /dev/null +++ b/apps/data_library/app.R @@ -0,0 +1,494 @@ +# M-DIVE Landing Page — Demo Version +# Self-contained demo with no external dependencies (no Civis, no mdiver). +# Uses static mock data in place of database queries. +# Derived from apps/mdive_landing_page/app.R + +library(shiny) +library(shinydashboard) +library(stringr) +library(dplyr) +library(shinyBS) +library(shinyWidgets) +library(shinycssloaders) +library(shinyjs) + +source('components.R') +source('app-text.R') + +# ── Static Mock Data ────────────────────────────────────────────────────────── + +reports_data <- data.frame( + name = c( + "Quarterly Report", + "Facility Level Dashboard", + "Cases Dashboard", + "Disease in Pregnancy Dashboard", + "Treatment Dashboard" + ), + description = c( + "Tracks confirmed cases, deaths, and reporting rates across a selection of countries on a quarterly basis.", + "Monitors reporting rates and case data at the facility level for targeted follow-up.", + "Summarizes case data across supported regions.", + "Monitors disease in pregnancy indicators across supported regions.", + "Tracks treatment indicators across supported regions." + ), + report_type = c("Shiny", "Shiny", "Shiny", "Shiny", "Tableau"), + development_status = c("Production", "Production", "In Development", "Production", "Production"), + technical_area = c("Reporting", "Reporting", "Measurement", "Measurement", "Treatment"), + tags = c( + "quarterly;cases;reporting", + "facility;reporting;cases", + "cases;measurement", + "disease;pregnancy;measurement", + "treatment;cases" + ), + category = c("General", "General", "Interventions", "Interventions", "Interventions"), + featured = c(1, 2, 3, 4, 5), + location_link = rep("#", 5), + stringsAsFactors = FALSE +) + +data <- data.frame( + name = c( + "Reporting Table", + "Facility Master List", + "Stock Data", + "Coverage Data", + "Population Estimates" + ), + description = c( + "Monthly reporting data aggregated from national systems across supported countries.", + "Master list of all supported health facilities including location and administrative hierarchy.", + "Logistics management data including commodity stockouts, consumption, and stock on hand.", + "Seasonal campaign coverage data by cycle and administrative unit.", + "Annual population estimates by administrative unit used for rate calculations." + ), + technical_area = c("Case Management", "Health Systems", "Supply Chain", "Coverage", "Population"), + tags = c( + "monthly;reporting;cases", + "facility;reporting;cases", + "stock;logistics;commodities", + "coverage;campaigns;SMC", + "population;denominators" + ), + access_restrictions = c( + "Open use", + "Open use", + "Restricted", + "Open use", + "Publicly Available" + ), + clean_last_data_update = c( + "January 2026", "December 2025", "November 2025", "October 2025", "January 2026" + ), + last_data_update = as.Date(c( + "2026-01-01", "2025-12-01", "2025-11-01", "2025-10-01", "2026-01-01" + )), + stringsAsFactors = FALSE +) + +# ── Derived filter choices ──────────────────────────────────────────────────── + +tag_choices <- get_unique_choices(reports_data, 'tags', data, 'tags') +tag_choices_br <- stringr::str_wrap(tag_choices, width = 35) +tag_choices_br <- stringr::str_replace_all(tag_choices_br, "\n", "
") + +tech_area_choices <- get_unique_choices(reports_data, 'technical_area', data, 'technical_area') +tech_area_choices_br <- stringr::str_wrap(tech_area_choices, width = 35) +tech_area_choices_br <- stringr::str_replace_all(tech_area_choices_br, "\n", "
") + +# ── UI ──────────────────────────────────────────────────────────────────────── + +ui <- function(request) { + dashboardPage(skin = 'black', + + dashboardHeader(title = "Welcome to the Demo App", titleWidth = 350), + + dashboardSidebar( + sidebarMenu( + id = "tabs", + div(class = 'sidebar-menu', id = "sidebar_nav", + menuItem("About", tabName = "home"), + menuItem("Demo Reports", tabName = "reports", selected = TRUE), + menuItem("Demo Data Catalog", tabName = "overview") + ), + div(class = 'sidebar-menu', id = 'sidebar_filters', + conditionalPanel( + "input.tabs == 'reports' | input.tabs == 'overview'", + pickerInput( + width = '200px', + inputId = "tech_area", + label = HTML("Filter by
Technical Area"), + choices = sort_other(tech_area_choices), + multiple = TRUE, + selected = NULL, + choicesOpt = list( + style = rep_len("color:black;", length(tech_area_choices)), + content = sort_other(tech_area_choices_br) + ), + options = pickerOptions( + actionsBox = TRUE, + liveSearch = TRUE, + dropupAuto = FALSE, + size = 8, + iconBase = "", + tickIcon = "fa fa-check", + `selected-text-format` = "count", + `count-selected-text` = "{0} of {1} Technical Areas Chosen" + ) + ), + pickerInput( + width = '200px', + inputId = "dataset_choice", + label = HTML("Or filter by
Tags"), + choices = sort_other(tag_choices), + multiple = TRUE, + selected = NULL, + choicesOpt = list( + style = rep_len("color:black;", length(tag_choices)), + content = sort_other(tag_choices_br) + ), + options = pickerOptions( + actionsBox = TRUE, + liveSearch = TRUE, + dropupAuto = FALSE, + size = 8, + iconBase = "", + tickIcon = "fa fa-check", + `selected-text-format` = "count", + `count-selected-text` = "{0} of {1} Tags Chosen" + ) + ) + ) + ) + ) + ), + + dashboardBody( + shinyjs::useShinyjs(), + tags$script("document.getElementsByClassName('sidebar-toggle')[0].style.visibility = 'hidden';"), + includeCSS('styles.css'), + + tabItems( + + # ── About Tab ────────────────────────────────────────────────────── + tabItem(tabName = 'home', + fluidRow(shinydashboard::box( + id = 'welcome_flip', status = "info", + title = span(icon("home", lib = "font-awesome"), ' Welcome'), + width = 12, closable = FALSE, collapsible = TRUE, + solidHeader = TRUE, collapsed = FALSE, + p(HTML(welcome_text)) + )), + br(), + fluidRow(shinydashboard::box( + id = 'report_flip', status = "info", + title = span(icon("chart-line", lib = "font-awesome"), + actionLink('reports_go', ' Demo Reports', + style = 'text-decoration:underline')), + width = 12, closable = FALSE, collapsible = TRUE, + solidHeader = TRUE, collapsed = TRUE, + p(HTML(reports_welcome_text)) + )), + br(), + fluidRow(shinydashboard::box( + id = 'data_flip', status = "info", + title = span(icon("database", lib = "font-awesome"), + actionLink('data_go', ' Demo Data Catalog', + style = 'text-decoration:underline')), + width = 12, closable = FALSE, collapsible = TRUE, + solidHeader = TRUE, collapsed = TRUE, + p(HTML(data_welcome_text)) + )), + br(), + fluidRow(shinydashboard::box( + id = 'help_flip', status = "info", + title = span(icon("question", lib = "font-awesome"), " Help"), + width = 12, closable = FALSE, collapsible = TRUE, + solidHeader = TRUE, collapsed = TRUE, + p(HTML(help_welcome_text)) + )) + ), + + # ── Demo Reports Tab ───────────────────────────────────────────── + tabItem(tabName = 'reports', + div(id = "demo_reports", + style = 'overflow-x:hidden;overflow-y:scroll;max-height:600px;width:100%;padding-right:4px', + fluidRow( + column(6, + textInput("search_box_reports", label = "Search", + placeholder = "Search...", width = '86.5%')), + column(6, + pickerInput( + inputId = 'order_results_reports', + label = "Order by", + choices = c( + 'Featured' = 'featured', + 'Report Name: A-Z' = 'name', + 'Category' = 'category' + ), + choicesOpt = list(style = rep_len("color:black;", 3)) + )) + ), + uiOutput('reportHeading'), + uiOutput('reports_page') %>% withSpinner(color = "#2474a4") + ) + ), + + # ── Demo Data Catalog Tab ─────────────────────────────────────────────── + tabItem(tabName = 'overview', + fluidPage( + fluidRow( + column(6, + textInput("search_box", label = "Search", + placeholder = "Search...", width = '86.5%')), + column(6, + selectInput('order_results', "Order by", + choices = c( + 'Dataset Name: A-Z' = 'name', + 'Last Updated: Oldest to Newest' = 'last_data_update' + ))) + ) + ), + fluidPage( + div(style = 'overflow-x:hidden;overflow-y:scroll;max-height:600px;width:100%;padding-right:4px', + id = "data_catalog", + uiOutput('cardHeading'), + uiOutput('cardContents') %>% withSpinner(color = "#2474a4")) + ) + ) + + ) # end tabItems + ) # end dashboardBody + ) +} + +# ── Server ──────────────────────────────────────────────────────────────────── + +server <- function(input, output, session) { + + # ── Filtered Data Reactives ─────────────────────────────────────────────── + + filtered_data <- reactive({ + tag_selections <- add_backslash_to_string(input$dataset_choice) + tech_area_selections <- add_backslash_to_string(input$tech_area) + + output <- data + + if (!is.null(input$dataset_choice)) { + output <- output %>% dplyr::filter(str_detect(tags, tag_selections)) + } + + if (!is.null(input$tech_area)) { + output <- output %>% dplyr::filter(str_detect(technical_area, tech_area_selections)) + } + + if (!is.na(input$search_box) & input$search_box != "") { + output <- output %>% + filter_all(any_vars(agrepl(input$search_box, ., ignore.case = TRUE))) + } + + if (!is.null(input$order_results)) { + output <- output %>% arrange_at(.vars = c(input$order_results, 'name')) + } + + output + }) + + filtered_reports <- reactive({ + tag_selections <- add_backslash_to_string(input$dataset_choice) + tech_area_selections <- add_backslash_to_string(input$tech_area) + + output <- reports_data + + if (!is.null(input$dataset_choice)) { + output <- output %>% dplyr::filter(str_detect(tags, tag_selections)) + } + + if (!is.null(input$tech_area)) { + output <- output %>% dplyr::filter(str_detect(technical_area, tech_area_selections)) + } + + if (!is.na(input$search_box_reports) & input$search_box_reports != "") { + output <- output %>% + filter_all(any_vars(agrepl(input$search_box_reports, ., ignore.case = TRUE))) + } + + if (!is.null(input$order_results_reports)) { + output <- output %>% arrange_at(.vars = c(input$order_results_reports, 'name')) + } + + output + }) + + # ── Reports Tab ─────────────────────────────────────────────────────────── + + output$reports_page <- renderUI({ + if (nrow(filtered_reports()) >= 1) { + + results <- lapply(1:nrow(filtered_reports()), function(rownum) { + boxButtonUI( + id = str_replace_all( + filtered_reports()[rownum, 'name'], + c(':' = '', ',' = '', '-' = '', ' ' = '_', '\\(' = '', '\\)' = '', '&' = '') + ), + box_link = filtered_reports()[rownum, 'location_link'], + development_status = filtered_reports()[rownum, 'development_status'], + description = filtered_reports()[rownum, 'description'], + tooltip_text = "Click to open this report in a new tab", + tag_list = strsplit(filtered_reports()[rownum, 'tags'], ';') %>% unlist() %>% trimws('both'), + tag_filter_list = input$dataset_choice, + tech_area_list = strsplit(filtered_reports()[rownum, 'technical_area'], ';') %>% unlist() %>% trimws('both'), + tech_area_filter_list = input$tech_area + ) + }) + + # Insert category/featured headers when sorting by category or featured + if (input$order_results_reports %in% c('featured', 'category')) { + category_counts <- filtered_reports() %>% + dplyr::mutate(position = row_number()) %>% + dplyr::group_by(!!dplyr::sym(input$order_results_reports)) %>% + dplyr::mutate(group_start_position = row_number()) %>% + dplyr::filter(group_start_position == 1) %>% + dplyr::select(!!sym(input$order_results_reports), position) + + for (i in 1:nrow(category_counts)) { + insert_position <- category_counts$position[i] + i - 1 + html_headers <- strong(h3(category_counts[[input$order_results_reports]][[i]])) + results <- append(results, list(html_headers), insert_position - 1) + } + } + + } else { + results <- fluidRow(width = 12, + actionLink("reset_input", "Reset your filters to view more results.", + style = "margin-left: 15px")) + } + results + }) + + dataset_choices <- reactive(input$dataset_choice) + tech_area_input_choices <- reactive(input$tech_area) + + lapply(1:nrow(reports_data), function(rownum) { + boxButtonServer( + id = str_replace_all( + reports_data[rownum, 'name'], + c(':' = '', ',' = '', '-' = '', ' ' = '_', '\\(' = '', '\\)' = '', '&' = '') + ), + link = reports_data[rownum, 'location_link'], + box_title_text = h3(span(reports_data[rownum, 'name'], + icon("external-link-alt", lib = "font-awesome"))), + tag_list = strsplit(reports_data[rownum, 'tags'], ';') %>% unlist() %>% trimws('both'), + tag_filter_list = dataset_choices, + tech_area_list = strsplit(reports_data[rownum, 'technical_area'], ';') %>% unlist() %>% trimws('both'), + tech_area_filter_list = tech_area_input_choices, + parent_session = session + ) + }) + + # ── Data Catalog Tab ────────────────────────────────────────────────────── + + output$cardContents <- renderUI({ + if (nrow(filtered_data()) >= 1) { + lapply(1:nrow(filtered_data()), function(rownum) { + resultsCardUI( + str_replace_all( + filtered_data()[rownum, 'name'], + c(':' = '', ',' = '', '-' = '', ' ' = '_', '\\(' = '', '\\)' = '', '&' = '') + ), + filtered_data(), rownum, + tag_filter_list = input$dataset_choice, + tech_area_filter_list = input$tech_area + ) + }) + } else { + fluidRow(width = 12, + actionLink("reset_input", "Reset your filters to view more results.", + style = "margin-left: 15px")) + } + }) + + lapply(1:nrow(data), function(rownum) { + resultsCardServer( + str_replace_all( + data[rownum, 'name'], + c(':' = '', ',' = '', '-' = '', ' ' = '_', '\\(' = '', '\\)' = '', '&' = '') + ), + parent_session = session, + data = data, + row = rownum, + tag_filter_list = dataset_choices, + tech_area_filter_list = tech_area_input_choices + ) + }) + + # ── Headings ────────────────────────────────────────────────────────────── + + output$cardHeading <- renderUI({ + tech_picks <- paste(input$tech_area, collapse = ', ') + tag_picks <- paste(input$dataset_choice, collapse = ', ') + + if (nrow(filtered_data()) == 0) { + out <- "Sorry! No datasets match selected filters." + } else { + out <- paste0("Showing ", nrow(filtered_data()), " dataset(s)") + } + + if (tech_picks != "" & tag_picks == "" & nrow(filtered_data()) > 0) + out <- HTML(out, " tagged with ", tech_picks, " technical area(s)") + if (tech_picks == "" & tag_picks != "" & nrow(filtered_data()) > 0) + out <- HTML(out, " tagged with ", tag_picks, ".") + if (tag_picks != "" & tech_picks != "" & nrow(filtered_data()) > 0) + out <- HTML(out, " tagged with ", tag_picks, " AND ", tech_picks, " technical area(s)") + + out + }) + + output$reportHeading <- renderUI({ + tech_picks <- paste(input$tech_area, collapse = ', ') + tag_picks <- paste(input$dataset_choice, collapse = ', ') + + if (nrow(filtered_reports()) == 0) { + out <- "Sorry! No reports match selected filters." + } else { + out <- paste0("Showing ", nrow(filtered_reports()), " report(s)") + } + + if (tech_picks != "" & tag_picks == "" & nrow(filtered_reports()) > 0) + out <- HTML(out, " tagged with ", tech_picks, " technical area(s)") + if (tech_picks == "" & tag_picks != "" & nrow(filtered_reports()) > 0) + out <- HTML(out, " tagged with ", tag_picks, ".") + if (tag_picks != "" & tech_picks != "" & nrow(filtered_reports()) > 0) + out <- HTML(out, " tagged with ", tag_picks, " AND ", tech_picks, " technical area(s)") + + out + }) + + # ── Navigation observers ────────────────────────────────────────────────── + + observeEvent(input$reset_input, { + updatePickerInput(session, "dataset_choice", selected = "") + updatePickerInput(session, "tech_area", selected = "") + updateTextInput(session, "search_box", value = "") + updateTextInput(session, "search_box_reports", value = "") + }) + + observeEvent(input$reports_go, { + updateTabItems(session, "tabs", "reports") + }) + + observeEvent(input$data_go, { + updateTabItems(session, "tabs", "overview") + }) + + # Tab bookmarking + observeEvent(reactiveValuesToList(input), session$doBookmark()) + onBookmarked(updateQueryString) + observe({ + setBookmarkExclude(dplyr::setdiff(names(input), "tabs")) + }) +} + +#### App #### +shinyApp(ui, server, enableBookmarking = "url") diff --git a/apps/data_library/components.R b/apps/data_library/components.R new file mode 100644 index 0000000..7a95c59 --- /dev/null +++ b/apps/data_library/components.R @@ -0,0 +1,208 @@ +# Demo App - UI Components +# Derived from apps/mdive_landing_page/components.R +# Changes: removed civis dependency (get_org_admins), inlined 18f_custom_ui.R, +# simplified boxButtonServer to always grant access (no access gate). + +# Inlined from style_files/18f_custom_ui.R +actionButton18F <- function(..., theme = 'light') { + actionButton(..., class = ifelse(theme == 'light', 'btn-18f-light', 'btn-18f-dark')) +} + + +#' Adds "//" to "(" and ")" characters so str_detect works correctly for filtering +#' @param x a string +add_backslash_to_string = function(x){ + v = paste(x, collapse = '|') + v = gsub("(", "\\(", v, fixed = TRUE) + v = gsub(")", "\\)", v, fixed = TRUE) +} + + +#' Gets unique categories from a ';'-separated column across both reports and data inventories. +get_unique_choices = function(reports_data, report_col, data, data_col){ + + report_choices <- unlist(lapply(reports_data[report_col], function(x) strsplit(x, ';'))) %>% + trimws('both') %>% + unique() + + catalog_choices <- unlist(lapply(data[data_col], function(x) strsplit(x, ';'))) %>% + trimws('both') %>% + unique() + + all_choices <- unique(c(report_choices, catalog_choices)) + + return(all_choices) +} + + +#' Orders a vector so 'Other' appears at the bottom. +sort_other = function(unique_choices){ + if ('Other' %in% unique_choices){ + choices = unique_choices[!unique_choices == 'Other'] + choices = sort(choices) + choices = c(choices, 'Other') + } else { + choices = sort(unique_choices) + } + return(choices) +} + + +create_buttons = function(text_list, filter_list, ns, tag_separator = ';'){ + + button_tags <- tagList() + + if (length(text_list) == 0){ + button_tags = "None" + } else { + text_list_length <- length(text_list) + i <- 1 + for (text in text_list) { + if (i == text_list_length) { + text_label <- text + } else { + text_label <- paste0(text, tag_separator) + } + if (text != '') { + btn_class <- ifelse(text %in% filter_list, + 'btn-18f-selected', + 'btn-18f') + button_text <- ifelse( + text %in% filter_list, + 'Click to remove from filter', + 'Click to add to filter' + ) + button_tags <- tagList(button_tags, + actionLink( + inputId = ns(str_replace_all(text, "[^A-Za-z]", "")), + label = text_label, + ), + bsTooltip(ns(str_replace_all(text, "[^A-Za-z]", "")), button_text) + ) + } + i <- i + 1 + } + } + + return(button_tags) +} + + +update_input = function(input, text_list, filter_list, parent_session, input_id) { + lapply( + text_list, + function(text) { + observeEvent(input[[str_replace_all(text, "[^A-Za-z,]", "")]], { + if (text %in% filter_list()) { + new_list <- filter_list()[!filter_list() == text] + } else { + new_list <- c(filter_list(), text) + } + updatePickerInput(parent_session, input_id, selected = new_list) + }) + } + ) +} + + +resultsCardUI <- function(id, input_data, rownum, + tag_filter_list, tech_area_filter_list) { + + ns <- NS(id) + + tag_list <- input_data[rownum, 'tags'] %>% str_split(';') %>% unlist() %>% trimws('both') + tech_area_list <- input_data[rownum, 'technical_area'] %>% str_split(';') %>% unlist() %>% trimws('both') + + tag_buttons = create_buttons(tag_list, tag_filter_list, ns) + tech_area_buttons = create_buttons(tech_area_list, tech_area_filter_list, ns) + + wellPanel(style = "background-color:white; color:black", + bsTooltip(id = ns('link_title'), title = 'Click to view full description', + placement = "right", trigger = 'hover', options = list(container = "body")), + div(class = 'row results-header', style = 'padding-left:12px', + strong(input_data[rownum, "name"]) + ), + div(class = 'row results-body', + div(class = 'col-sm-8 results-body-main', + p(HTML(str_trunc(gsub("\n", "
", input_data[rownum, "description"]), + width = 450, + ellipsis = "..."))) + ), + div(class = 'col-sm-4 results-body-side border-left', + h4("Data Updated on:"), + p(input_data[rownum, "clean_last_data_update"]), + h4("Access Restrictions"), + p(input_data[rownum, "access_restrictions"])) + ), + div(class = 'results-details', + 'Technical area:', tech_area_buttons, + br(), + 'Tags:', tag_buttons + ) + ) +} + + +resultsCardServer <- function(id, parent_session, data, row, + tag_filter_list, tech_area_filter_list) { + + moduleServer(id, function(input, output, session) { + tag_list <- data[row, 'tags'] %>% str_split(';') %>% unlist() %>% trimws('both') + tech_area_list <- data[row, 'technical_area'] %>% str_split(';') %>% unlist() %>% trimws('both') + + update_input(input, tag_list, tag_filter_list, parent_session, 'dataset_choice') + update_input(input, tech_area_list, tech_area_filter_list, parent_session, 'tech_area') + }) +} + + +boxButtonUI <- function(id, box_link, description, tooltip_text, development_status, + tag_list, tag_filter_list = c(), + tech_area_list, tech_area_filter_list = c()) { + ns <- NS(id) + + tag_buttons = create_buttons(tag_list, tag_filter_list, ns) + tech_area_buttons = create_buttons(tech_area_list, tech_area_filter_list, ns) + + box( + id = ns('box_info'), + status = 'primary', + title = tags$div( + style = 'display:inline-block', + uiOutput(ns("box_title")), + class = 'report-header' + ), + div(class = 'results-body-report-main', style = 'border-bottom:1px solid rgb(241, 241, 241)', + tags$p(HTML(' Status: ', development_status)), + tags$p(description) + ), + bsTooltip(id = ns('box_title'), title = tooltip_text, + placement = "right", trigger = 'hover', options = list(container = "body")), + div(class = 'results-body-report-details', + div(id = ns("icon_box"), 'Technical Area:', tech_area_buttons), + div(id = ns("icon_box"), 'Tags:', tag_buttons) + ), + width = 12 + ) +} + + +# Simplified: all reports are always accessible in the demo (no access gate) +boxButtonServer <- function(id, link, box_title_text, + tag_list = c(), tag_filter_list = NULL, + tech_area_list = c(), tech_area_filter_list = NULL, + parent_session = NULL) { + moduleServer(id, function(input, output, session) { + + update_input(input, tag_list, tag_filter_list, parent_session, 'dataset_choice') + update_input(input, tech_area_list, tech_area_filter_list, parent_session, 'tech_area') + + output$box_title <- renderUI({ + tags$a( + href = link, + HTML(paste(tags$u(box_title_text))), + target = '_blank' + ) + }) + }) +} diff --git a/apps/data_library/styles.css b/apps/data_library/styles.css new file mode 100644 index 0000000..2221b92 --- /dev/null +++ b/apps/data_library/styles.css @@ -0,0 +1,136 @@ +/* +This file specifies 18F guidelines for the MDIVE landing page +It adjusts the outline color of boxes, dashboard sidebar color, +hover color for dashboard navigation, etc. +*/ + +.center { + display: block; + margin-left: auto; + margin-right: auto; + width: 80%; +} + +.btn-18f:hover { + background-color:#046b99; + color:white; +} + +.btn-18f-selected { + background-color:#046b99; + color:white; +} + +.btn-18f-selected:hover { + background-color:#046b99; + color:white; +} + + +/* 18F medium blue*/ +.main-sidebar { + background-color: #046b99 !important; + } + +/* 18F lightest gray*/ +.box.box-primary { + border:3px solid #f1f1f1; +} + +/* 18F Colors (white) - app background*/ +.content-wrapper { + background-color: white !important; + } + +/* logo */ + .main-header .logo { + background-color: #ffffff !important; + } + + /* logo when hovered */ + .main-header .logo:hover { + background-color: #ffffff !important; + } + + /* navbar (rest of the header) */ + .main-header .navbar { + background-color: #ffffff !important; + } + + +/* 18F Colors (medium blue hover)*/ + /* active selected tab in the sidebarmenu */ + .main-sidebar .sidebar .sidebar-menu .active a{ + background-color: #034c6d; + } + +/* 18F medium hover blue*/ + /* other links in the sidebarmenu when hovered */ + .main-sidebar .sidebar .sidebar-menu a:hover{ + background-color: #034c6d; + } + + /* 18F medium hover blue*/ + /* toggle button when hovered */ + .main-header .navbar .sidebar-toggle:hover{ + background-color: #034c6d; + } + + /* 18F medium blue*/ + .main-sidebar .sidebar .sidebar-menu .treeview li.active a { + background-color: #046b99 !important; + } + + /* 18F medium hover blue*/ + .main-sidebar .sidebar .sidebar-menu .treeview-menu a { + background-color: #034c6d !important; + } + + /* 18F medium blue*/ + .main-sidebar .sidebar .sidebar-menu .treeview-menu a:hover { + background-color: #046b99 !important; + } + +.main-sidebar .sidebar .sidebar-menu .dropdown a:hover{ + background-color: #f1f1f1; + } + +.main-sidebar .sidebar .sidebar-menu .dropdown-menu .active a{ + background-color: #f1f1f1; + } + + +/* 18F medium blue*/ +a { + color:#046b99; +} + +/* 18F medium hover blue*/ +a:hover { + color:#034c6d; +} + +.well { + border: 3px solid #f1f1f1; +} + +::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; +} + +::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, .5); + box-shadow: 0 0 1px rgba(255, 255, 255, .5); +} + +.btn-18f-dark { + background-color:#046b99; + color:white; +} + +.btn-18f-dark:hover { + background-color:#034c6d; + color:white; +} From 0cfe94d8759dc0b17fd8fa2eea63c0ceeea4cff7 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Wed, 4 Mar 2026 13:12:29 -0700 Subject: [PATCH 02/16] try removing the shiny css loaders --- apps/data_library/app.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/data_library/app.R b/apps/data_library/app.R index c256185..028ac25 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -9,7 +9,7 @@ library(stringr) library(dplyr) library(shinyBS) library(shinyWidgets) -library(shinycssloaders) +# library(shinycssloaders) library(shinyjs) source('components.R') From 0f2000ecba43318bf74238745a99df2b2b181789 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Wed, 4 Mar 2026 13:18:12 -0700 Subject: [PATCH 03/16] add an install for shiny cssloaders --- apps/data_library/app.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/data_library/app.R b/apps/data_library/app.R index 028ac25..1621178 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -3,13 +3,16 @@ # Uses static mock data in place of database queries. # Derived from apps/mdive_landing_page/app.R +# install shinycssloaders +install.packages("shinycssloaders") + library(shiny) library(shinydashboard) library(stringr) library(dplyr) library(shinyBS) library(shinyWidgets) -# library(shinycssloaders) +library(shinycssloaders) library(shinyjs) source('components.R') From 2388506f3094ad3caea8aa2d4e012d9c221b387f Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Wed, 4 Mar 2026 13:56:56 -0700 Subject: [PATCH 04/16] update style sheet --- apps/data_library/styles.css | 649 +++++++++++++++++++++++++++++------ 1 file changed, 539 insertions(+), 110 deletions(-) diff --git a/apps/data_library/styles.css b/apps/data_library/styles.css index 2221b92..fc242a4 100644 --- a/apps/data_library/styles.css +++ b/apps/data_library/styles.css @@ -1,136 +1,565 @@ /* This file specifies 18F guidelines for the MDIVE landing page It adjusts the outline color of boxes, dashboard sidebar color, -hover color for dashboard navigation, etc. +hover color for dashboard navigation, etc. It is similar to the file +used in the QR Dashboard and contains some specifications that will only +be necessary if we start using submenus in the MDIVE landing page */ -.center { - display: block; - margin-left: auto; - margin-right: auto; - width: 80%; -} +/****************************** Navbar/Top Header ******************************/ + /* Header background color for logo and text formatting */ + .main-header .logo { + background-color: #1c304a !important; + color:white !important; + height: 70px; + font-size: 30px; + font-weight: 500; + padding-top: 10px; + border:0 !important; + } -.btn-18f:hover { - background-color:#046b99; - color:white; -} + .main-header .logo:hover { + background-color: #f4b943; + } -.btn-18f-selected { - background-color:#046b99; - color:white; -} + /* navbar (rest of the header) */ + .main-header .navbar { + background-color: #1c304a !important; + color:white !important; + height: 70px; + } -.btn-18f-selected:hover { - background-color:#046b99; - color:white; -} + /* Sidebar toggle button - 18F white */ + .main-header .navbar .sidebar-toggle{ + color: white !important; + border:0 !important; + padding-top: 30px; + } + + /* Sidebar toggle button when hovered - 18Fmedium blue hover */ + .main-header .navbar .sidebar-toggle:hover{ + color: white !important; + background-color:#034c6d !important; + border:0; + } + + /* header button - 18F Dark */ + .btn-18f-header { + background: none; + color:white; + padding-top:30px; + margin-right:40px; + border: none; + touch-action: manipulation; + } + + .btn-header-lessmarg { + background: none; + color:white; + padding-top:10px; + border: none; + touch-action: manipulation; + } + .btn-header-lessmarg:hover { + background: none; + color:white; + padding-top:10px; + border: none; + touch-action: manipulation; + } + + + + .btn-18f-header:hover { + background: none; + color:white; + padding-top:30px; + margin-right:40px; + border: none; + touch-action: manipulation; + } + +/****************************** Sidebar ******************************/ + /* Sidebar background color - 18F medium blue*/ + .main-sidebar { + background-color: #046b99 !important; + padding-top: 70px; + } + /* Sidebar navigation font - 18F h2*/ + .main-sidebar .sidebar .sidebar-menu #sidebar_nav li{ + font-size: 20px; + font-weight: 600; + } + + /* Sidebar filters title font - 18F p*/ + .main-sidebar .sidebar .sidebar-menu .control-label { + font-size: 14px; + font-weight: normal; + } + + /* Sidebar filters display font - 18F p*/ + .main-sidebar .sidebar .sidebar-menu .filter-option { + font-size: 14px; + font-weight: normal; + margin-left: 10px; + } + + + /* Sidebar filters dropdown font - 18F p*/ + .main-sidebar .sidebar .sidebar-menu #sidebar_filters li{ + font-size: 14px; + } + + /* Activate selected tab in sidebar - 18F medium blue hover */ + .main-sidebar .sidebar .sidebar-menu .active a{ + background-color: #034c6d; + } + + /* Other links in sidebar when hovered - 18F medium blue hover */ + .main-sidebar .sidebar .sidebar-menu a:hover{ + background-color: #034c6d; + } + + /* Treeview - 18F medium blue*/ + .main-sidebar .sidebar .sidebar-menu .treeview-menu { + background-color: #046b99; + font-size:14px; + } + .main-sidebar .sidebar .sidebar-menu .treeview-menu .active { + background-color: #034c6d; + font-size:14px; + color: white; + font-weight: 600; -/* 18F medium blue*/ -.main-sidebar { + } + /* Treeview active - 18F medium blue*/ + .main-sidebar .sidebar .sidebar-menu .treeview li.active a { + background-color: #034c6d !important; + font-size:14px; + color: white; + font-weight: 600; + } + + /* Treeview menu sidebar - 18F medium hover blue*/ + .main-sidebar .sidebar .sidebar-menu .treeview-menu a { background-color: #046b99 !important; } -/* 18F lightest gray*/ -.box.box-primary { - border:3px solid #f1f1f1; -} + /* Treeview menu hover - 18F medium blue*/ + .main-sidebar .sidebar .sidebar-menu .treeview-menu a:hover { + background-color: #034c6d !important; + } -/* 18F Colors (white) - app background*/ -.content-wrapper { - background-color: white !important; + + /* Sidebar menu dropdown hover - 18F lightest gray */ + .main-sidebar .sidebar .sidebar-menu .dropdown{ + background-color: #f1f1f1; + color: black !important; } -/* logo */ - .main-header .logo { - background-color: #ffffff !important; - } - - /* logo when hovered */ - .main-header .logo:hover { - background-color: #ffffff !important; - } - - /* navbar (rest of the header) */ - .main-header .navbar { - background-color: #ffffff !important; - } - - -/* 18F Colors (medium blue hover)*/ - /* active selected tab in the sidebarmenu */ - .main-sidebar .sidebar .sidebar-menu .active a{ - background-color: #034c6d; - } - -/* 18F medium hover blue*/ - /* other links in the sidebarmenu when hovered */ - .main-sidebar .sidebar .sidebar-menu a:hover{ - background-color: #034c6d; - } - - /* 18F medium hover blue*/ - /* toggle button when hovered */ - .main-header .navbar .sidebar-toggle:hover{ - background-color: #034c6d; - } - - /* 18F medium blue*/ - .main-sidebar .sidebar .sidebar-menu .treeview li.active a { - background-color: #046b99 !important; - } - - /* 18F medium hover blue*/ - .main-sidebar .sidebar .sidebar-menu .treeview-menu a { - background-color: #034c6d !important; - } - - /* 18F medium blue*/ - .main-sidebar .sidebar .sidebar-menu .treeview-menu a:hover { - background-color: #046b99 !important; - } - -.main-sidebar .sidebar .sidebar-menu .dropdown a:hover{ - background-color: #f1f1f1; - } - -.main-sidebar .sidebar .sidebar-menu .dropdown-menu .active a{ - background-color: #f1f1f1; - } - - -/* 18F medium blue*/ -a { - color:#046b99; +/*Remove arrow from sidebar */ +.main-sidebar .sidebar .sidebar-menu .fa-angle-left:before { + content: ""; } + /* Sidebar menu dropdown hover - 18F lightest gray */ + .main-sidebar .sidebar .sidebar-menu .dropdown a:hover{ + background-color: #f1f1f1; + color: black !important; + } -/* 18F medium hover blue*/ -a:hover { - color:#034c6d; -} + /* Sidebar menu dropdown active - 18F lightest gray */ + .main-sidebar .sidebar .sidebar-menu .dropdown-menu .active a{ + background-color: #f1f1f1; + color: black !important; + } -.well { - border: 3px solid #f1f1f1; -} +/****************************** Body ******************************/ -::-webkit-scrollbar { - -webkit-appearance: none; - width: 7px; -} + /* Backgrond color for body - 18F white */ + .content-wrapper { + background-color: white !important; + } + .wrapper { + background-color: white !important; + } + label{ + font-weight: 400; + } + + /* Margins for tabs and default text set to p */ + .tab-content .tab-pane { + padding-top: 5px; + padding-left: 20px; + padding-right: 10px; + font-size: 14px; + } + + + /* Border for boxes - 18F lightest gray */ + .box.box-primary { + margin-top: 10px; + margin-bottom: 25px; + border:3px solid #f1f1f1; + border-left-width:3px; + } + + /* Well border (in case the app calls something a well instead of box) - 18F lightest gray */ + .tab-content .tab-pane .well { + margin-top: 10px; + margin-bottom: 10px; + border: 3px solid #f1f1f1; + padding-bottom: 5px; + } -::-webkit-scrollbar-thumb { - border-radius: 4px; - background-color: rgba(0, 0, 0, .5); - box-shadow: 0 0 1px rgba(255, 255, 255, .5); + /*Boxes for 'home' page*/ + .box.box-solid.box-info>.box-header { + color:white; + background:#046b99 } + .box.box-solid.box-info>.box-header .box-title { + font-size: 20px; +} + + .box.box-solid.box-info{ + background-color: #e6e6e6; + border-bottom-color:#046b99; + border-left-color:#046b99; + border-right-color:#046b99; + border-top-color:#046b99; + } + + /* Font size for header of boxes - h4 */ + .tab-content .tab-pane .box.box-primary .box-header { + font-size: 21px; + font-weight: 600; + padding-bottom: 5px; + } -.btn-18f-dark { - background-color:#046b99; - color:white; + /* Font size for body of boxes - p */ + .tab-content .tab-pane .box.box-primary .box-body { + font-size: 14px; + font-weight: normal; + color: black; + padding-top: 5px; + } + + /*Warning boxes*/ + .box.box-solid.box-warning>.box-header { + color:white; + background:#f39c12; } -.btn-18f-dark:hover { - background-color:#034c6d; - color:white; + .box.box-solid.box-warning>.box-header .btn{ + background:#f39c12 !important; + } + + /* Font size for header of wells - h4 */ + .tab-content .tab-pane .well .row.results-header { + font-size: 21px; + font-weight: 600; + padding-bottom: 5px; + } + + /* Font size for body of wells - p */ + .tab-content .tab-pane .well .row.results-body { + font-size: 14px; + font-weight: normal; + color: black; + } + + .tab-content .tab-pane .well .results-details { + font-size: 14px; + font-weight: normal; + color: black; + border-top:1px solid rgb(241, 241, 241); + padding-left:12px; + } + +/* Body filters title font - 18F p*/ + .tab-content .tab-pane .control-label { + color: black; + font-size: 14px; + font-weight: normal; + } + +/* Body filters display font - 18F p*/ + .tab-content .tab-pane .selectize-input{ + color: black; + font-size: 14px; + font-weight: normal; + background-color: #f1f1f1; + } + +/* Body filters dropdown font - 18F p*/ + .tab-content .tab-pane .selectize-dropdown{ + color: black; + font-size: 14px; + font-weight: normal; + background-color: white; + } + +/* Body filters dropdown hover - 18F p*/ + .tab-content .tab-pane .selectize-dropdown .active{ + color: black; + font-size: 14px; + font-weight: normal; + background-color: #f1f1f1; + } + +/* DataTables text default size and weight - 18F p*/ + + .tab-content .tab-pane .dataTables_wrapper{ + font-size: 14px; + font-weight: normal; + } + + .card .avatar { + background: #E0E0E0 !important; + color: black !important; + border: 0px; + border-radius:0px; + } + .card { + background-color: #E0E0E0 !important; + font: white !important; + } + + /*Slider selection on Sidebar */ + .irs-grid-text { + color: white !important; + } + + .irs-grid-pol { + background-color: white !important; } +/****************************** Font Style and Sizes ******************************/ + /* font - 18F Helvetica */ + .sansserif { + font-family: Helvetica, sans-serif; + } + + + h1 { + font-size: 34px; + font-weight: 600; + } + + h2 { + font-size: 26px; + font-weight: 600; + } + + h3 { + font-size: 18px; + font-weight: 600; + margin-top:0px; + } + + h4 { + font-size: 17px; + font-weight: 600; + } + + h5 { + font-size: 16px; + font-weight: 600; + } + + +/****************************** Objects ******************************/ + /* align text to center */ + .center { + display: block; + margin-left: auto; + margin-right: auto; + width: 80%; + } + + /* hyperlink color - 18F medium blue*/ + a { + color:#046b99; + } + + /* hyperlink hover color - 18F medium hover blue*/ + a:hover { + color:#034c6d; + } + + /* Style for scrollbar */ + ::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; + } + + /* Style for the scrolling part of the scrolbar */ + ::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, .5); + box-shadow: 0 0 1px rgba(255, 255, 255, .5); + } + + + /* medium button w/ white background (for preview buttons) */ + .btn-18f-medium { + color: #046b99; + font-size: 14px; + background: none; + padding: 0; + border: none; + } + /* medium button w/ white background (for preview buttons) - hover */ + .btn-18f-medium:hover { + color: #034c6d; + font-size: 14px; + background: none; + padding: 0; + border: none; + } + + /* dark button - 18F medium */ + .btn-18f-dark { + background-color:#046b99; + color:white; + font-size: 14px; + } + + /* dark button hover - 18F medium hover */ + .btn-18f-dark:hover { + background-color:#034c6d; + color:white; + font-size: 14px; + } + + /* sidebar dark button hover */ + .btn-18f-side { + background-color:#034c6d; + color:white; + border: none; + font-size: 14px; + } + /* sidebar dark button - hover */ + .btn-18f-side:hover { + background-color:white; + color:#034c6d; + border: none; + font-size: 14px; + } + /* sidebar dark button - selected */ + .btn-18f-side:focus { + background-color: white; + color:#034c6d; + border: none; + } + + + /* light button - 18F medium */ + .btn-18f-light { + background-color:#046b99; + color:white; + font-size: 14px; + } + + /* light button hover - 18F medium hover */ + .btn-18f-light:hover { + background-color:#034c6d; + color:white; + font-size: 14px; + } + + +/* tooltips - white background and black outline and text */ + .tooltip-inner { + background-color: white; + color: black; + font-size: 14px; + border: 1px solid black; + } + + /* light button in main tab pane - 18F medium*/ + .tab-content .tab-pane .btn{ + color: white; + font-size: 14px; + font-weight: normal; + background-color: #046b99; + } + + +/* light button hover in main tab pane - 18F medium hover*/ + .tab-content .tab-pane .btn:hover { + color: white; + font-size: 14px; + font-weight: normal; + background-color: #034c6d; + } + + .tab-content .tab-pane .btn-18f-selected { + color: white; + font-size: 14px; + font-weight: normal; + background-color: #034c6d; + } + .tab-content .tab-pane .btn-default.active{ + color: white; + font-size: 14px; + font-weight: normal; + background-color: #034c6d; + } + +/*Dropdown menu in main pane for pickerInput style */ + .tab-content .tab-pane .dropdown-toggle { + background-color: #f1f1f1; + color: black; + } + .tab-content .tab-pane .dropdown-toggle:hover { + background-color: #f1f1f1; + color: black; + } + .tab-content .tab-pane .dropdown-menu .inner { + font-size: 14px; + } + .tab-content .tab-pane .dropdown-menu .li .a:hover { + font-size: 14px; + background-color: #f1f1f1 !important; + } + + .dropdown-menu li a { + font-size: 14px; + color: black; + } + .tab-content .tab-pane .dropdown-menu .active { + font-size: 14px; + background-color: white; + color: #777; + } + .tab-content .tab-pane .dropdown-menu .active:hover { + font-size: 14px; + background-color: #f1f1f1; + } + + /*Leaflet options*/ + .leaflet-top, .leaflet-bottom { + z-index: unset !important; + } + + .leaflet-touch .leaflet-control-layers .leaflet-touch .leaflet-bar { + z-index: 10000000000 !important; + } + + .row{ + margin-left: 5px; + margin-right: 5px; + } + + .sticky-footer { + position:fixed; + bottom:0; + right:0; + left:265px; + background:white; + padding:10px; + margin-top: 15px; + border-top-width: 1px; + border-top-color: #f1f1f1; + border-top-style: solid; + } From aa06a3d87382a008af14f99f9ac0cbcae654df63 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Wed, 4 Mar 2026 16:43:26 -0700 Subject: [PATCH 05/16] update the components to use the fixed stylesheet --- apps/data_library/components.R | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/data_library/components.R b/apps/data_library/components.R index 7a95c59..9b31ab5 100644 --- a/apps/data_library/components.R +++ b/apps/data_library/components.R @@ -117,10 +117,9 @@ resultsCardUI <- function(id, input_data, rownum, tech_area_buttons = create_buttons(tech_area_list, tech_area_filter_list, ns) wellPanel(style = "background-color:white; color:black", - bsTooltip(id = ns('link_title'), title = 'Click to view full description', - placement = "right", trigger = 'hover', options = list(container = "body")), div(class = 'row results-header', style = 'padding-left:12px', - strong(input_data[rownum, "name"]) + tags$span(input_data[rownum, "name"], + style = 'margin-top:0px; text-decoration:underline; color:#046b99; font-weight:600; font-size:21px;') ), div(class = 'row results-body', div(class = 'col-sm-8 results-body-main', From b1985cfd319d4ce23b72c8755f61a6844313be86 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 07:56:10 -0700 Subject: [PATCH 06/16] remove numbering --- apps/data_library/app.R | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/data_library/app.R b/apps/data_library/app.R index 1621178..f8e0c6a 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -230,7 +230,6 @@ ui <- function(request) { 'Report Name: A-Z' = 'name', 'Category' = 'category' ), - choicesOpt = list(style = rep_len("color:black;", 3)) )) ), uiOutput('reportHeading'), From c2fa653e7e4c0b178d7e2606c50e394541346101 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 08:04:43 -0700 Subject: [PATCH 07/16] update the numbering --- apps/data_library/app.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/data_library/app.R b/apps/data_library/app.R index f8e0c6a..a03066a 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -346,7 +346,7 @@ server <- function(input, output, session) { }) # Insert category/featured headers when sorting by category or featured - if (input$order_results_reports %in% c('featured', 'category')) { + if (input$order_results_reports %in% c('category')) { category_counts <- filtered_reports() %>% dplyr::mutate(position = row_number()) %>% dplyr::group_by(!!dplyr::sym(input$order_results_reports)) %>% From 0e2732f572067e0db7fbd245e5c7ea8e3e927fa0 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 08:14:27 -0700 Subject: [PATCH 08/16] reinstate ability to click on demo items --- apps/data_library/app-text.R | 17 +++-- apps/data_library/app.R | 136 +++++++++++++-------------------- apps/data_library/components.R | 16 +++- apps/data_library/demo-data.R | 120 +++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 93 deletions(-) create mode 100644 apps/data_library/demo-data.R diff --git a/apps/data_library/app-text.R b/apps/data_library/app-text.R index 6e760c8..2f5b08a 100644 --- a/apps/data_library/app-text.R +++ b/apps/data_library/app-text.R @@ -1,25 +1,28 @@ -# Demo App Text +# M-DIVE Demo App Text # Contains text displayed on the landing page tabs. # About Page Text welcome_text = "This demo application allows you to explore and search the reports and datasets - available in the Demo platform from one central location. + available in the Malaria Data Integration and Visualization (M-DIVE) platform + from one central location.

- Select Reports or Data Catalog from the sidebar to begin + Select M-DIVE Reports or Data Catalog from the sidebar to begin exploring, or click the '+' to expand the boxes below to learn more." -reports_welcome_text = "The Reports page contains links to and information about the different - interactive dashboards and other analyses hosted in the Demo platform. Scroll to browse +reports_welcome_text = "The M-DIVE Reports page contains links to and information about the different + interactive dashboards and other analyses hosted in M-DIVE. Scroll to browse through the reports or use the Technical Area and Tags filters in the sidebar to narrow your selection.

Click on the report title to be directed to the report in a new tab." -data_welcome_text = "The Data Catalog page contains information about datasets available in the Demo platform. +data_welcome_text = "The Data Catalog page contains information about datasets available in M-DIVE. Scroll to browse through the datasets or use the Technical Area and Tags filters in the sidebar to narrow your selection.

Each dataset card includes a brief description, last updated date, and access restrictions." -help_welcome_text = "This is a demo app that demonstrates how an organization could use the Civis Platform" +help_welcome_text = "This is a demo version of the M-DIVE Landing Page, built to illustrate the + core discovery and filtering experience. Contact your M-DIVE administrator for + access to the live platform." diff --git a/apps/data_library/app.R b/apps/data_library/app.R index a03066a..bbe3354 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -3,7 +3,6 @@ # Uses static mock data in place of database queries. # Derived from apps/mdive_landing_page/app.R -# install shinycssloaders install.packages("shinycssloaders") library(shiny) @@ -17,78 +16,7 @@ library(shinyjs) source('components.R') source('app-text.R') - -# ── Static Mock Data ────────────────────────────────────────────────────────── - -reports_data <- data.frame( - name = c( - "Quarterly Report", - "Facility Level Dashboard", - "Cases Dashboard", - "Disease in Pregnancy Dashboard", - "Treatment Dashboard" - ), - description = c( - "Tracks confirmed cases, deaths, and reporting rates across a selection of countries on a quarterly basis.", - "Monitors reporting rates and case data at the facility level for targeted follow-up.", - "Summarizes case data across supported regions.", - "Monitors disease in pregnancy indicators across supported regions.", - "Tracks treatment indicators across supported regions." - ), - report_type = c("Shiny", "Shiny", "Shiny", "Shiny", "Tableau"), - development_status = c("Production", "Production", "In Development", "Production", "Production"), - technical_area = c("Reporting", "Reporting", "Measurement", "Measurement", "Treatment"), - tags = c( - "quarterly;cases;reporting", - "facility;reporting;cases", - "cases;measurement", - "disease;pregnancy;measurement", - "treatment;cases" - ), - category = c("General", "General", "Interventions", "Interventions", "Interventions"), - featured = c(1, 2, 3, 4, 5), - location_link = rep("#", 5), - stringsAsFactors = FALSE -) - -data <- data.frame( - name = c( - "Reporting Table", - "Facility Master List", - "Stock Data", - "Coverage Data", - "Population Estimates" - ), - description = c( - "Monthly reporting data aggregated from national systems across supported countries.", - "Master list of all supported health facilities including location and administrative hierarchy.", - "Logistics management data including commodity stockouts, consumption, and stock on hand.", - "Seasonal campaign coverage data by cycle and administrative unit.", - "Annual population estimates by administrative unit used for rate calculations." - ), - technical_area = c("Case Management", "Health Systems", "Supply Chain", "Coverage", "Population"), - tags = c( - "monthly;reporting;cases", - "facility;reporting;cases", - "stock;logistics;commodities", - "coverage;campaigns;SMC", - "population;denominators" - ), - access_restrictions = c( - "Open use", - "Open use", - "Restricted", - "Open use", - "Publicly Available" - ), - clean_last_data_update = c( - "January 2026", "December 2025", "November 2025", "October 2025", "January 2026" - ), - last_data_update = as.Date(c( - "2026-01-01", "2025-12-01", "2025-11-01", "2025-10-01", "2026-01-01" - )), - stringsAsFactors = FALSE -) +source('demo-data.R') # ── Derived filter choices ──────────────────────────────────────────────────── @@ -105,15 +33,15 @@ tech_area_choices_br <- stringr::str_replace_all(tech_area_choices_br, "\n", "% dplyr::mutate(position = row_number()) %>% dplyr::group_by(!!dplyr::sym(input$order_results_reports)) %>% @@ -421,10 +353,48 @@ server <- function(input, output, session) { data = data, row = rownum, tag_filter_list = dataset_choices, - tech_area_filter_list = tech_area_input_choices + tech_area_filter_list = tech_area_input_choices, + selection = selection ) }) + # ── Dataset detail modal ────────────────────────────────────────────────── + + observeEvent(selection$name, { + req(selection$name) + row <- data[data$name == selection$name, ] + + assoc <- row$associated_reports + assoc_ui <- if (!is.na(assoc) && nchar(trimws(assoc)) > 0) { + report_names <- trimws(strsplit(assoc, ";")[[1]]) + tags$ul(lapply(report_names, function(r) tags$li(r))) + } else { + p("None") + } + + showModal(modalDialog( + title = row$name, + size = "l", + easyClose = TRUE, + footer = modalButton("Close"), + h4("Description"), + p(row$full_description), + tags$hr(), + fluidRow( + column(6, + h4("Technical Area"), p(row$technical_area), + h4("Last Updated"), p(row$clean_last_data_update), + h4("Access"), p(row$access_restrictions) + ), + column(6, + h4("Source"), p(row$source), + h4("Unit of Analysis"), p(row$unit_of_analysis), + h4("Associated Reports"), assoc_ui + ) + ) + )) + }) + # ── Headings ────────────────────────────────────────────────────────────── output$cardHeading <- renderUI({ diff --git a/apps/data_library/components.R b/apps/data_library/components.R index 9b31ab5..665f546 100644 --- a/apps/data_library/components.R +++ b/apps/data_library/components.R @@ -117,9 +117,11 @@ resultsCardUI <- function(id, input_data, rownum, tech_area_buttons = create_buttons(tech_area_list, tech_area_filter_list, ns) wellPanel(style = "background-color:white; color:black", + bsTooltip(id = ns('link_title'), title = 'Click to view full description', + placement = "right", trigger = 'hover', options = list(container = "body")), div(class = 'row results-header', style = 'padding-left:12px', - tags$span(input_data[rownum, "name"], - style = 'margin-top:0px; text-decoration:underline; color:#046b99; font-weight:600; font-size:21px;') + actionLink(inputId = ns('link_title'), label = input_data[rownum, "name"], + style = 'margin-top:0px; text-decoration:underline') ), div(class = 'row results-body', div(class = 'col-sm-8 results-body-main', @@ -143,7 +145,8 @@ resultsCardUI <- function(id, input_data, rownum, resultsCardServer <- function(id, parent_session, data, row, - tag_filter_list, tech_area_filter_list) { + tag_filter_list, tech_area_filter_list, + selection = NULL) { moduleServer(id, function(input, output, session) { tag_list <- data[row, 'tags'] %>% str_split(';') %>% unlist() %>% trimws('both') @@ -151,6 +154,13 @@ resultsCardServer <- function(id, parent_session, data, row, update_input(input, tag_list, tag_filter_list, parent_session, 'dataset_choice') update_input(input, tech_area_list, tech_area_filter_list, parent_session, 'tech_area') + + # When title is clicked, tell the parent which dataset was selected + if (!is.null(selection)) { + observeEvent(input$link_title, { + selection$name <- data[row, "name"] + }) + } }) } diff --git a/apps/data_library/demo-data.R b/apps/data_library/demo-data.R new file mode 100644 index 0000000..da67515 --- /dev/null +++ b/apps/data_library/demo-data.R @@ -0,0 +1,120 @@ +# Demo App - Mock Data +# All static data used by the demo app lives here. + +# ── Reports ─────────────────────────────────────────────────────────────────── + +reports_data <- data.frame( + name = c( + "Malaria Quarterly Report", + "Facility Level Dashboard", + "SMC Dashboard", + "Malaria in Pregnancy Dashboard", + "Vector Control Dashboard" + ), + description = c( + "Tracks confirmed malaria cases, deaths, and reporting rates across PMI-supported countries on a quarterly basis.", + "Monitors HMIS reporting rates and case data at the facility level for targeted follow-up.", + "Summarizes seasonal malaria chemoprevention coverage and distribution across supported regions.", + "Monitors malaria prevention and treatment indicators for pregnant women across PMI countries.", + "Tracks indoor residual spraying coverage and insecticide-treated net distribution and usage." + ), + report_type = c("Shiny", "Shiny", "Shiny", "Shiny", "Tableau"), + development_status = c("Production", "Production", "In Development", "Production", "Production"), + technical_area = c("Case Management", "Health Systems", "SMC", "Malaria in Pregnancy", "Vector Control"), + tags = c( + "malaria;quarterly;HMIS;cases", + "facility;reporting;HMIS", + "SMC;prevention;chemoprevention", + "MIP;pregnancy;prevention", + "IRS;ITN;vector control" + ), + category = c("HMIS", "HMIS", "Interventions", "Interventions", "Interventions"), + featured = c(1, 2, 3, 4, 5), + location_link = rep("#", 5), + stringsAsFactors = FALSE +) + +# ── Data Catalog ────────────────────────────────────────────────────────────── + +data <- data.frame( + name = c( + "QR Reporting Table", + "Facility Master List", + "LMIS Stock Data", + "SMC Coverage Data", + "Population Estimates" + ), + description = c( + "Monthly malaria case and reporting data aggregated from national HMIS systems across PMI-supported countries.", + "Master list of all PMI-supported health facilities including location and administrative hierarchy.", + "Logistics management data including commodity stockouts, consumption, and stock on hand.", + "Seasonal malaria chemoprevention campaign coverage data by cycle and administrative unit.", + "Annual population estimates by administrative unit used for rate calculations." + ), + full_description = c( + "The QR Reporting Table contains monthly malaria surveillance data compiled from national HMIS systems + across all PMI-supported countries. It includes confirmed cases, suspected cases, malaria deaths, + and testing data broken down by administrative unit (admin 1 and admin 2), facility, and month. + Reporting rates and completeness indicators are also included to help users assess data quality. + This table is the primary data source for the Malaria Quarterly Report dashboard.", + "The Facility Master List is the authoritative reference for all PMI-supported health facilities. + It includes facility names, unique identifiers, administrative hierarchy (country, admin 1, admin 2), + GPS coordinates where available, facility type, and ownership category. + It is used as a lookup table across multiple M-DIVE dashboards to ensure consistent facility naming + and geographic attribution.", + "The LMIS Stock Data table contains commodity tracking information from national logistics management + information systems. It covers stock on hand, quantities received, quantities consumed, and stockout + days for key malaria commodities including ACTs, RDTs, and ITNs. Data is reported at the facility + level on a monthly basis. Access is restricted due to the sensitivity of supply chain information.", + "The SMC Coverage Data table contains campaign-level coverage data for seasonal malaria + chemoprevention programs. Each row represents one administrative unit in one campaign cycle, + with fields for target population, children reached, and coverage percentage. + Data is available for all PMI-supported countries with active SMC programs.", + "The Population Estimates table provides annual population denominators by administrative unit + used across M-DIVE for calculating rate-based indicators (e.g. cases per 1,000 population). + Estimates are derived from national census projections and are updated annually." + ), + technical_area = c("Case Management", "Health Systems", "Supply Chain", "SMC", "Cross-cutting"), + tags = c( + "malaria;HMIS;cases;reporting", + "facilities;geo;admin", + "LMIS;stockouts;commodities", + "SMC;coverage;campaigns", + "population;denominators" + ), + access_restrictions = c( + "Open use within M-DIVE", + "Open use within M-DIVE", + "Restricted", + "Open use within M-DIVE", + "Publicly Available" + ), + clean_last_data_update = c( + "January 2026", "December 2025", "November 2025", "October 2025", "January 2026" + ), + last_data_update = as.Date(c( + "2026-01-01", "2025-12-01", "2025-11-01", "2025-10-01", "2026-01-01" + )), + source = c( + "National HMIS (DHIS2)", + "PMI Country Teams", + "National LMIS", + "PMI SMC Program", + "National Census Projections" + ), + unit_of_analysis = c( + "Country / Admin 1 / Admin 2 / Month", + "Facility", + "Facility / Month", + "Admin unit / Campaign cycle", + "Admin unit / Year" + ), + associated_reports = c( + "Malaria Quarterly Report; Facility Level Dashboard", + "Facility Level Dashboard", + "", + "SMC Dashboard", + "Malaria Quarterly Report; Malaria in Pregnancy Dashboard" + ), + stringsAsFactors = FALSE +) From 1f15b0ea4e20ba118648d49856bc1d80bbf34584 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 08:16:45 -0700 Subject: [PATCH 09/16] Revert "reinstate ability to click on demo items" This reverts commit 0e2732f572067e0db7fbd245e5c7ea8e3e927fa0. --- apps/data_library/app-text.R | 17 ++--- apps/data_library/app.R | 136 ++++++++++++++++++++------------- apps/data_library/components.R | 16 +--- apps/data_library/demo-data.R | 120 ----------------------------- 4 files changed, 93 insertions(+), 196 deletions(-) delete mode 100644 apps/data_library/demo-data.R diff --git a/apps/data_library/app-text.R b/apps/data_library/app-text.R index 2f5b08a..6e760c8 100644 --- a/apps/data_library/app-text.R +++ b/apps/data_library/app-text.R @@ -1,28 +1,25 @@ -# M-DIVE Demo App Text +# Demo App Text # Contains text displayed on the landing page tabs. # About Page Text welcome_text = "This demo application allows you to explore and search the reports and datasets - available in the Malaria Data Integration and Visualization (M-DIVE) platform - from one central location. + available in the Demo platform from one central location.

- Select M-DIVE Reports or Data Catalog from the sidebar to begin + Select Reports or Data Catalog from the sidebar to begin exploring, or click the '+' to expand the boxes below to learn more." -reports_welcome_text = "The M-DIVE Reports page contains links to and information about the different - interactive dashboards and other analyses hosted in M-DIVE. Scroll to browse +reports_welcome_text = "The Reports page contains links to and information about the different + interactive dashboards and other analyses hosted in the Demo platform. Scroll to browse through the reports or use the Technical Area and Tags filters in the sidebar to narrow your selection.

Click on the report title to be directed to the report in a new tab." -data_welcome_text = "The Data Catalog page contains information about datasets available in M-DIVE. +data_welcome_text = "The Data Catalog page contains information about datasets available in the Demo platform. Scroll to browse through the datasets or use the Technical Area and Tags filters in the sidebar to narrow your selection.

Each dataset card includes a brief description, last updated date, and access restrictions." -help_welcome_text = "This is a demo version of the M-DIVE Landing Page, built to illustrate the - core discovery and filtering experience. Contact your M-DIVE administrator for - access to the live platform." +help_welcome_text = "This is a demo app that demonstrates how an organization could use the Civis Platform" diff --git a/apps/data_library/app.R b/apps/data_library/app.R index bbe3354..a03066a 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -3,6 +3,7 @@ # Uses static mock data in place of database queries. # Derived from apps/mdive_landing_page/app.R +# install shinycssloaders install.packages("shinycssloaders") library(shiny) @@ -16,7 +17,78 @@ library(shinyjs) source('components.R') source('app-text.R') -source('demo-data.R') + +# ── Static Mock Data ────────────────────────────────────────────────────────── + +reports_data <- data.frame( + name = c( + "Quarterly Report", + "Facility Level Dashboard", + "Cases Dashboard", + "Disease in Pregnancy Dashboard", + "Treatment Dashboard" + ), + description = c( + "Tracks confirmed cases, deaths, and reporting rates across a selection of countries on a quarterly basis.", + "Monitors reporting rates and case data at the facility level for targeted follow-up.", + "Summarizes case data across supported regions.", + "Monitors disease in pregnancy indicators across supported regions.", + "Tracks treatment indicators across supported regions." + ), + report_type = c("Shiny", "Shiny", "Shiny", "Shiny", "Tableau"), + development_status = c("Production", "Production", "In Development", "Production", "Production"), + technical_area = c("Reporting", "Reporting", "Measurement", "Measurement", "Treatment"), + tags = c( + "quarterly;cases;reporting", + "facility;reporting;cases", + "cases;measurement", + "disease;pregnancy;measurement", + "treatment;cases" + ), + category = c("General", "General", "Interventions", "Interventions", "Interventions"), + featured = c(1, 2, 3, 4, 5), + location_link = rep("#", 5), + stringsAsFactors = FALSE +) + +data <- data.frame( + name = c( + "Reporting Table", + "Facility Master List", + "Stock Data", + "Coverage Data", + "Population Estimates" + ), + description = c( + "Monthly reporting data aggregated from national systems across supported countries.", + "Master list of all supported health facilities including location and administrative hierarchy.", + "Logistics management data including commodity stockouts, consumption, and stock on hand.", + "Seasonal campaign coverage data by cycle and administrative unit.", + "Annual population estimates by administrative unit used for rate calculations." + ), + technical_area = c("Case Management", "Health Systems", "Supply Chain", "Coverage", "Population"), + tags = c( + "monthly;reporting;cases", + "facility;reporting;cases", + "stock;logistics;commodities", + "coverage;campaigns;SMC", + "population;denominators" + ), + access_restrictions = c( + "Open use", + "Open use", + "Restricted", + "Open use", + "Publicly Available" + ), + clean_last_data_update = c( + "January 2026", "December 2025", "November 2025", "October 2025", "January 2026" + ), + last_data_update = as.Date(c( + "2026-01-01", "2025-12-01", "2025-11-01", "2025-10-01", "2026-01-01" + )), + stringsAsFactors = FALSE +) # ── Derived filter choices ──────────────────────────────────────────────────── @@ -33,15 +105,15 @@ tech_area_choices_br <- stringr::str_replace_all(tech_area_choices_br, "\n", "% dplyr::mutate(position = row_number()) %>% dplyr::group_by(!!dplyr::sym(input$order_results_reports)) %>% @@ -353,48 +421,10 @@ server <- function(input, output, session) { data = data, row = rownum, tag_filter_list = dataset_choices, - tech_area_filter_list = tech_area_input_choices, - selection = selection + tech_area_filter_list = tech_area_input_choices ) }) - # ── Dataset detail modal ────────────────────────────────────────────────── - - observeEvent(selection$name, { - req(selection$name) - row <- data[data$name == selection$name, ] - - assoc <- row$associated_reports - assoc_ui <- if (!is.na(assoc) && nchar(trimws(assoc)) > 0) { - report_names <- trimws(strsplit(assoc, ";")[[1]]) - tags$ul(lapply(report_names, function(r) tags$li(r))) - } else { - p("None") - } - - showModal(modalDialog( - title = row$name, - size = "l", - easyClose = TRUE, - footer = modalButton("Close"), - h4("Description"), - p(row$full_description), - tags$hr(), - fluidRow( - column(6, - h4("Technical Area"), p(row$technical_area), - h4("Last Updated"), p(row$clean_last_data_update), - h4("Access"), p(row$access_restrictions) - ), - column(6, - h4("Source"), p(row$source), - h4("Unit of Analysis"), p(row$unit_of_analysis), - h4("Associated Reports"), assoc_ui - ) - ) - )) - }) - # ── Headings ────────────────────────────────────────────────────────────── output$cardHeading <- renderUI({ diff --git a/apps/data_library/components.R b/apps/data_library/components.R index 665f546..9b31ab5 100644 --- a/apps/data_library/components.R +++ b/apps/data_library/components.R @@ -117,11 +117,9 @@ resultsCardUI <- function(id, input_data, rownum, tech_area_buttons = create_buttons(tech_area_list, tech_area_filter_list, ns) wellPanel(style = "background-color:white; color:black", - bsTooltip(id = ns('link_title'), title = 'Click to view full description', - placement = "right", trigger = 'hover', options = list(container = "body")), div(class = 'row results-header', style = 'padding-left:12px', - actionLink(inputId = ns('link_title'), label = input_data[rownum, "name"], - style = 'margin-top:0px; text-decoration:underline') + tags$span(input_data[rownum, "name"], + style = 'margin-top:0px; text-decoration:underline; color:#046b99; font-weight:600; font-size:21px;') ), div(class = 'row results-body', div(class = 'col-sm-8 results-body-main', @@ -145,8 +143,7 @@ resultsCardUI <- function(id, input_data, rownum, resultsCardServer <- function(id, parent_session, data, row, - tag_filter_list, tech_area_filter_list, - selection = NULL) { + tag_filter_list, tech_area_filter_list) { moduleServer(id, function(input, output, session) { tag_list <- data[row, 'tags'] %>% str_split(';') %>% unlist() %>% trimws('both') @@ -154,13 +151,6 @@ resultsCardServer <- function(id, parent_session, data, row, update_input(input, tag_list, tag_filter_list, parent_session, 'dataset_choice') update_input(input, tech_area_list, tech_area_filter_list, parent_session, 'tech_area') - - # When title is clicked, tell the parent which dataset was selected - if (!is.null(selection)) { - observeEvent(input$link_title, { - selection$name <- data[row, "name"] - }) - } }) } diff --git a/apps/data_library/demo-data.R b/apps/data_library/demo-data.R deleted file mode 100644 index da67515..0000000 --- a/apps/data_library/demo-data.R +++ /dev/null @@ -1,120 +0,0 @@ -# Demo App - Mock Data -# All static data used by the demo app lives here. - -# ── Reports ─────────────────────────────────────────────────────────────────── - -reports_data <- data.frame( - name = c( - "Malaria Quarterly Report", - "Facility Level Dashboard", - "SMC Dashboard", - "Malaria in Pregnancy Dashboard", - "Vector Control Dashboard" - ), - description = c( - "Tracks confirmed malaria cases, deaths, and reporting rates across PMI-supported countries on a quarterly basis.", - "Monitors HMIS reporting rates and case data at the facility level for targeted follow-up.", - "Summarizes seasonal malaria chemoprevention coverage and distribution across supported regions.", - "Monitors malaria prevention and treatment indicators for pregnant women across PMI countries.", - "Tracks indoor residual spraying coverage and insecticide-treated net distribution and usage." - ), - report_type = c("Shiny", "Shiny", "Shiny", "Shiny", "Tableau"), - development_status = c("Production", "Production", "In Development", "Production", "Production"), - technical_area = c("Case Management", "Health Systems", "SMC", "Malaria in Pregnancy", "Vector Control"), - tags = c( - "malaria;quarterly;HMIS;cases", - "facility;reporting;HMIS", - "SMC;prevention;chemoprevention", - "MIP;pregnancy;prevention", - "IRS;ITN;vector control" - ), - category = c("HMIS", "HMIS", "Interventions", "Interventions", "Interventions"), - featured = c(1, 2, 3, 4, 5), - location_link = rep("#", 5), - stringsAsFactors = FALSE -) - -# ── Data Catalog ────────────────────────────────────────────────────────────── - -data <- data.frame( - name = c( - "QR Reporting Table", - "Facility Master List", - "LMIS Stock Data", - "SMC Coverage Data", - "Population Estimates" - ), - description = c( - "Monthly malaria case and reporting data aggregated from national HMIS systems across PMI-supported countries.", - "Master list of all PMI-supported health facilities including location and administrative hierarchy.", - "Logistics management data including commodity stockouts, consumption, and stock on hand.", - "Seasonal malaria chemoprevention campaign coverage data by cycle and administrative unit.", - "Annual population estimates by administrative unit used for rate calculations." - ), - full_description = c( - "The QR Reporting Table contains monthly malaria surveillance data compiled from national HMIS systems - across all PMI-supported countries. It includes confirmed cases, suspected cases, malaria deaths, - and testing data broken down by administrative unit (admin 1 and admin 2), facility, and month. - Reporting rates and completeness indicators are also included to help users assess data quality. - This table is the primary data source for the Malaria Quarterly Report dashboard.", - "The Facility Master List is the authoritative reference for all PMI-supported health facilities. - It includes facility names, unique identifiers, administrative hierarchy (country, admin 1, admin 2), - GPS coordinates where available, facility type, and ownership category. - It is used as a lookup table across multiple M-DIVE dashboards to ensure consistent facility naming - and geographic attribution.", - "The LMIS Stock Data table contains commodity tracking information from national logistics management - information systems. It covers stock on hand, quantities received, quantities consumed, and stockout - days for key malaria commodities including ACTs, RDTs, and ITNs. Data is reported at the facility - level on a monthly basis. Access is restricted due to the sensitivity of supply chain information.", - "The SMC Coverage Data table contains campaign-level coverage data for seasonal malaria - chemoprevention programs. Each row represents one administrative unit in one campaign cycle, - with fields for target population, children reached, and coverage percentage. - Data is available for all PMI-supported countries with active SMC programs.", - "The Population Estimates table provides annual population denominators by administrative unit - used across M-DIVE for calculating rate-based indicators (e.g. cases per 1,000 population). - Estimates are derived from national census projections and are updated annually." - ), - technical_area = c("Case Management", "Health Systems", "Supply Chain", "SMC", "Cross-cutting"), - tags = c( - "malaria;HMIS;cases;reporting", - "facilities;geo;admin", - "LMIS;stockouts;commodities", - "SMC;coverage;campaigns", - "population;denominators" - ), - access_restrictions = c( - "Open use within M-DIVE", - "Open use within M-DIVE", - "Restricted", - "Open use within M-DIVE", - "Publicly Available" - ), - clean_last_data_update = c( - "January 2026", "December 2025", "November 2025", "October 2025", "January 2026" - ), - last_data_update = as.Date(c( - "2026-01-01", "2025-12-01", "2025-11-01", "2025-10-01", "2026-01-01" - )), - source = c( - "National HMIS (DHIS2)", - "PMI Country Teams", - "National LMIS", - "PMI SMC Program", - "National Census Projections" - ), - unit_of_analysis = c( - "Country / Admin 1 / Admin 2 / Month", - "Facility", - "Facility / Month", - "Admin unit / Campaign cycle", - "Admin unit / Year" - ), - associated_reports = c( - "Malaria Quarterly Report; Facility Level Dashboard", - "Facility Level Dashboard", - "", - "SMC Dashboard", - "Malaria Quarterly Report; Malaria in Pregnancy Dashboard" - ), - stringsAsFactors = FALSE -) From 9af1363c3e9eb67719be544be860fa74989d89bd Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 08:42:14 -0700 Subject: [PATCH 10/16] alter the demo to have clickable items --- apps/data_library/app.R | 126 ++++++++++++--------------------- apps/data_library/components.R | 12 +++- apps/data_library/demo-data.R | 75 ++++++++++++++++++++ 3 files changed, 131 insertions(+), 82 deletions(-) create mode 100644 apps/data_library/demo-data.R diff --git a/apps/data_library/app.R b/apps/data_library/app.R index a03066a..b1cbb54 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -1,9 +1,6 @@ # M-DIVE Landing Page — Demo Version -# Self-contained demo with no external dependencies (no Civis, no mdiver). -# Uses static mock data in place of database queries. # Derived from apps/mdive_landing_page/app.R -# install shinycssloaders install.packages("shinycssloaders") library(shiny) @@ -17,78 +14,7 @@ library(shinyjs) source('components.R') source('app-text.R') - -# ── Static Mock Data ────────────────────────────────────────────────────────── - -reports_data <- data.frame( - name = c( - "Quarterly Report", - "Facility Level Dashboard", - "Cases Dashboard", - "Disease in Pregnancy Dashboard", - "Treatment Dashboard" - ), - description = c( - "Tracks confirmed cases, deaths, and reporting rates across a selection of countries on a quarterly basis.", - "Monitors reporting rates and case data at the facility level for targeted follow-up.", - "Summarizes case data across supported regions.", - "Monitors disease in pregnancy indicators across supported regions.", - "Tracks treatment indicators across supported regions." - ), - report_type = c("Shiny", "Shiny", "Shiny", "Shiny", "Tableau"), - development_status = c("Production", "Production", "In Development", "Production", "Production"), - technical_area = c("Reporting", "Reporting", "Measurement", "Measurement", "Treatment"), - tags = c( - "quarterly;cases;reporting", - "facility;reporting;cases", - "cases;measurement", - "disease;pregnancy;measurement", - "treatment;cases" - ), - category = c("General", "General", "Interventions", "Interventions", "Interventions"), - featured = c(1, 2, 3, 4, 5), - location_link = rep("#", 5), - stringsAsFactors = FALSE -) - -data <- data.frame( - name = c( - "Reporting Table", - "Facility Master List", - "Stock Data", - "Coverage Data", - "Population Estimates" - ), - description = c( - "Monthly reporting data aggregated from national systems across supported countries.", - "Master list of all supported health facilities including location and administrative hierarchy.", - "Logistics management data including commodity stockouts, consumption, and stock on hand.", - "Seasonal campaign coverage data by cycle and administrative unit.", - "Annual population estimates by administrative unit used for rate calculations." - ), - technical_area = c("Case Management", "Health Systems", "Supply Chain", "Coverage", "Population"), - tags = c( - "monthly;reporting;cases", - "facility;reporting;cases", - "stock;logistics;commodities", - "coverage;campaigns;SMC", - "population;denominators" - ), - access_restrictions = c( - "Open use", - "Open use", - "Restricted", - "Open use", - "Publicly Available" - ), - clean_last_data_update = c( - "January 2026", "December 2025", "November 2025", "October 2025", "January 2026" - ), - last_data_update = as.Date(c( - "2026-01-01", "2025-12-01", "2025-11-01", "2025-10-01", "2026-01-01" - )), - stringsAsFactors = FALSE -) +source('demo-data.R') # ── Derived filter choices ──────────────────────────────────────────────────── @@ -105,7 +31,7 @@ tech_area_choices_br <- stringr::str_replace_all(tech_area_choices_br, "\n", "% dplyr::mutate(position = row_number()) %>% dplyr::group_by(!!dplyr::sym(input$order_results_reports)) %>% @@ -421,10 +351,48 @@ server <- function(input, output, session) { data = data, row = rownum, tag_filter_list = dataset_choices, - tech_area_filter_list = tech_area_input_choices + tech_area_filter_list = tech_area_input_choices, + selection = selection ) }) + # ── Dataset detail modal ────────────────────────────────────────────────── + + observeEvent(selection$name, { + req(selection$name) + row <- data[data$name == selection$name, ] + + assoc <- row$associated_reports + assoc_ui <- if (!is.na(assoc) && nchar(trimws(assoc)) > 0) { + report_names <- trimws(strsplit(assoc, ";")[[1]]) + tags$ul(lapply(report_names, function(r) tags$li(r))) + } else { + p("None") + } + + showModal(modalDialog( + title = row$name, + size = "l", + easyClose = TRUE, + footer = modalButton("Close"), + h4("Description"), + p(row$full_description), + tags$hr(), + fluidRow( + column(6, + h4("Technical Area"), p(row$technical_area), + h4("Last Updated"), p(row$clean_last_data_update), + h4("Access"), p(row$access_restrictions) + ), + column(6, + h4("Source"), p(row$source), + h4("Unit of Analysis"), p(row$unit_of_analysis), + h4("Associated Reports"), assoc_ui + ) + ) + )) + }) + # ── Headings ────────────────────────────────────────────────────────────── output$cardHeading <- renderUI({ diff --git a/apps/data_library/components.R b/apps/data_library/components.R index 9b31ab5..ed14fcf 100644 --- a/apps/data_library/components.R +++ b/apps/data_library/components.R @@ -1,7 +1,5 @@ # Demo App - UI Components # Derived from apps/mdive_landing_page/components.R -# Changes: removed civis dependency (get_org_admins), inlined 18f_custom_ui.R, -# simplified boxButtonServer to always grant access (no access gate). # Inlined from style_files/18f_custom_ui.R actionButton18F <- function(..., theme = 'light') { @@ -143,7 +141,8 @@ resultsCardUI <- function(id, input_data, rownum, resultsCardServer <- function(id, parent_session, data, row, - tag_filter_list, tech_area_filter_list) { + tag_filter_list, tech_area_filter_list, + selection = NULL) { moduleServer(id, function(input, output, session) { tag_list <- data[row, 'tags'] %>% str_split(';') %>% unlist() %>% trimws('both') @@ -151,6 +150,13 @@ resultsCardServer <- function(id, parent_session, data, row, update_input(input, tag_list, tag_filter_list, parent_session, 'dataset_choice') update_input(input, tech_area_list, tech_area_filter_list, parent_session, 'tech_area') + + # When title is clicked, tell the parent which dataset was selected + if (!is.null(selection)) { + observeEvent(input$link_title, { + selection$name <- data[row, "name"] + }) + } }) } diff --git a/apps/data_library/demo-data.R b/apps/data_library/demo-data.R new file mode 100644 index 0000000..691c4bf --- /dev/null +++ b/apps/data_library/demo-data.R @@ -0,0 +1,75 @@ +# Demo App - Mock Data +# All static data used by the demo app lives here. + +# ── Reports ─────────────────────────────────────────────────────────────────── + +reports_data <- data.frame( + name = c( + "Quarterly Report", + "Facility Level Dashboard", + "Cases Dashboard", + "Disease in Pregnancy Dashboard", + "Treatment Dashboard" + ), + description = c( + "Tracks confirmed cases, deaths, and reporting rates across a selection of countries on a quarterly basis.", + "Monitors reporting rates and case data at the facility level for targeted follow-up.", + "Summarizes case data across supported regions.", + "Monitors disease in pregnancy indicators across supported regions.", + "Tracks treatment indicators across supported regions." + ), + report_type = c("Shiny", "Shiny", "Shiny", "Shiny", "Tableau"), + development_status = c("Production", "Production", "In Development", "Production", "Production"), + technical_area = c("Reporting", "Reporting", "Measurement", "Measurement", "Treatment"), + tags = c( + "quarterly;cases;reporting", + "facility;reporting;cases", + "cases;measurement", + "disease;pregnancy;measurement", + "treatment;cases" + ), + category = c("General", "General", "Interventions", "Interventions", "Interventions"), + featured = c(1, 2, 3, 4, 5), + location_link = rep("#", 5), + stringsAsFactors = FALSE +) +# ── Data Catalog ────────────────────────────────────────────────────────────── + +data <- data.frame( + name = c( + "Reporting Table", + "Facility Master List", + "Stock Data", + "Coverage Data", + "Population Estimates" + ), + description = c( + "Monthly reporting data aggregated from national systems across supported countries.", + "Master list of all supported health facilities including location and administrative hierarchy.", + "Logistics management data including commodity stockouts, consumption, and stock on hand.", + "Seasonal campaign coverage data by cycle and administrative unit.", + "Annual population estimates by administrative unit used for rate calculations." + ), + technical_area = c("Case Management", "Health Systems", "Supply Chain", "Coverage", "Population"), + tags = c( + "monthly;reporting;cases", + "facility;reporting;cases", + "stock;logistics;commodities", + "coverage;campaigns;SMC", + "population;denominators" + ), + access_restrictions = c( + "Open use", + "Open use", + "Restricted", + "Open use", + "Publicly Available" + ), + clean_last_data_update = c( + "January 2026", "December 2025", "November 2025", "October 2025", "January 2026" + ), + last_data_update = as.Date(c( + "2026-01-01", "2025-12-01", "2025-11-01", "2025-10-01", "2026-01-01" + )), + stringsAsFactors = FALSE +) \ No newline at end of file From 1a54c10b352d152d38efa4eae51a07538857fe29 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 08:44:19 -0700 Subject: [PATCH 11/16] remove sorting to avoid weird numbering --- apps/data_library/app.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/data_library/app.R b/apps/data_library/app.R index b1cbb54..5e84cdd 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -276,7 +276,7 @@ server <- function(input, output, session) { }) # Insert category/featured headers when sorting by category or featured - if (input$order_results_reports %in% c('featured', 'category')) { + if (input$order_results_reports %in% c('category')) { category_counts <- filtered_reports() %>% dplyr::mutate(position = row_number()) %>% dplyr::group_by(!!dplyr::sym(input$order_results_reports)) %>% From e01ba12897f4eb7f5cc3b63493fa30996cafdd1a Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 08:48:52 -0700 Subject: [PATCH 12/16] update the click functionality for the catalog items --- apps/data_library/components.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/data_library/components.R b/apps/data_library/components.R index ed14fcf..3927772 100644 --- a/apps/data_library/components.R +++ b/apps/data_library/components.R @@ -115,9 +115,11 @@ resultsCardUI <- function(id, input_data, rownum, tech_area_buttons = create_buttons(tech_area_list, tech_area_filter_list, ns) wellPanel(style = "background-color:white; color:black", + bsTooltip(id = ns('link_title'), title = 'Click to view full description', + placement = "right", trigger = 'hover', options = list(container = "body")), div(class = 'row results-header', style = 'padding-left:12px', - tags$span(input_data[rownum, "name"], - style = 'margin-top:0px; text-decoration:underline; color:#046b99; font-weight:600; font-size:21px;') + actionLink(inputId = ns('link_title'), label = input_data[rownum, "name"], + style = 'margin-top:0px; text-decoration:underline') ), div(class = 'row results-body', div(class = 'col-sm-8 results-body-main', From b3411b0581b93b19a1cdc8b26d81a079f9403304 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 09:44:31 -0700 Subject: [PATCH 13/16] update the app clicking functionality --- apps/data_library/app.R | 45 ++-------------------------------- apps/data_library/components.R | 44 +++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 51 deletions(-) diff --git a/apps/data_library/app.R b/apps/data_library/app.R index 5e84cdd..ef9a635 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -196,9 +196,6 @@ ui <- function(request) { server <- function(input, output, session) { - # ── Dataset selection (for modal) ───────────────────────────────────────── - selection <- reactiveValues(name = NULL) - # ── Filtered Data Reactives ─────────────────────────────────────────────── filtered_data <- reactive({ @@ -276,7 +273,7 @@ server <- function(input, output, session) { }) # Insert category/featured headers when sorting by category or featured - if (input$order_results_reports %in% c('category')) { + if (input$order_results_reports %in% c('featured', 'category')) { category_counts <- filtered_reports() %>% dplyr::mutate(position = row_number()) %>% dplyr::group_by(!!dplyr::sym(input$order_results_reports)) %>% @@ -351,48 +348,10 @@ server <- function(input, output, session) { data = data, row = rownum, tag_filter_list = dataset_choices, - tech_area_filter_list = tech_area_input_choices, - selection = selection + tech_area_filter_list = tech_area_input_choices ) }) - # ── Dataset detail modal ────────────────────────────────────────────────── - - observeEvent(selection$name, { - req(selection$name) - row <- data[data$name == selection$name, ] - - assoc <- row$associated_reports - assoc_ui <- if (!is.na(assoc) && nchar(trimws(assoc)) > 0) { - report_names <- trimws(strsplit(assoc, ";")[[1]]) - tags$ul(lapply(report_names, function(r) tags$li(r))) - } else { - p("None") - } - - showModal(modalDialog( - title = row$name, - size = "l", - easyClose = TRUE, - footer = modalButton("Close"), - h4("Description"), - p(row$full_description), - tags$hr(), - fluidRow( - column(6, - h4("Technical Area"), p(row$technical_area), - h4("Last Updated"), p(row$clean_last_data_update), - h4("Access"), p(row$access_restrictions) - ), - column(6, - h4("Source"), p(row$source), - h4("Unit of Analysis"), p(row$unit_of_analysis), - h4("Associated Reports"), assoc_ui - ) - ) - )) - }) - # ── Headings ────────────────────────────────────────────────────────────── output$cardHeading <- renderUI({ diff --git a/apps/data_library/components.R b/apps/data_library/components.R index 3927772..93670f9 100644 --- a/apps/data_library/components.R +++ b/apps/data_library/components.R @@ -1,5 +1,7 @@ # Demo App - UI Components # Derived from apps/mdive_landing_page/components.R +# Changes: removed civis dependency (get_org_admins), inlined 18f_custom_ui.R, +# simplified boxButtonServer to always grant access (no access gate). # Inlined from style_files/18f_custom_ui.R actionButton18F <- function(..., theme = 'light') { @@ -143,8 +145,7 @@ resultsCardUI <- function(id, input_data, rownum, resultsCardServer <- function(id, parent_session, data, row, - tag_filter_list, tech_area_filter_list, - selection = NULL) { + tag_filter_list, tech_area_filter_list) { moduleServer(id, function(input, output, session) { tag_list <- data[row, 'tags'] %>% str_split(';') %>% unlist() %>% trimws('both') @@ -153,12 +154,39 @@ resultsCardServer <- function(id, parent_session, data, row, update_input(input, tag_list, tag_filter_list, parent_session, 'dataset_choice') update_input(input, tech_area_list, tech_area_filter_list, parent_session, 'tech_area') - # When title is clicked, tell the parent which dataset was selected - if (!is.null(selection)) { - observeEvent(input$link_title, { - selection$name <- data[row, "name"] - }) - } + observeEvent(input$link_title, { + r <- data[row, ] + + assoc <- r$associated_reports + assoc_ui <- if (!is.na(assoc) && nchar(trimws(assoc)) > 0) { + report_names <- trimws(strsplit(assoc, ";")[[1]]) + tags$ul(lapply(report_names, function(rn) tags$li(rn))) + } else { + p("None") + } + + showModal(modalDialog( + title = r$name, + size = "l", + easyClose = TRUE, + footer = modalButton("Close"), + h4("Description"), + p(r$full_description), + tags$hr(), + fluidRow( + column(6, + h4("Technical Area"), p(r$technical_area), + h4("Last Updated"), p(r$clean_last_data_update), + h4("Access"), p(r$access_restrictions) + ), + column(6, + h4("Source"), p(r$source), + h4("Unit of Analysis"), p(r$unit_of_analysis), + h4("Associated Reports"), assoc_ui + ) + ) + )) + }) }) } From e5a6dd83865e434ca7f38d2b2ce798a29f288d15 Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 10:05:52 -0700 Subject: [PATCH 14/16] test a different UI --- apps/data_library/app.R | 22 ++++++-- apps/data_library/components.R | 97 +++++++++++++++++++++------------- 2 files changed, 80 insertions(+), 39 deletions(-) diff --git a/apps/data_library/app.R b/apps/data_library/app.R index ef9a635..532074f 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -37,9 +37,12 @@ ui <- function(request) { sidebarMenu( id = "tabs", div(class = 'sidebar-menu', id = "sidebar_nav", - menuItem("About", tabName = "home"), - menuItem("Demo Reports", tabName = "reports", selected = TRUE), - menuItem("Demo Data Catalog", tabName = "overview") + menuItem("About", tabName = "home"), + menuItem("Demo Reports", tabName = "reports", selected = TRUE), + menuItem("Demo Data Catalog", tabName = "overview"), + # Hidden — only reachable by clicking a dataset card + conditionalPanel("input.tabs == 'something'", + menuItem("Data Details", tabName = "data_details")) ), div(class = 'sidebar-menu', id = 'sidebar_filters', conditionalPanel( @@ -164,6 +167,12 @@ ui <- function(request) { ) ), + # ── Data Details Tab ─────────────────────────────────────────────── + tabItem(tabName = 'data_details', + dataDetailsUI(id = 'data_details_tab', + dataset_options = unique(data$name)) + ), + # ── Data Catalog Tab ─────────────────────────────────────────────── tabItem(tabName = 'overview', fluidPage( @@ -347,11 +356,14 @@ server <- function(input, output, session) { parent_session = session, data = data, row = rownum, + target_module = 'data_details_tab', tag_filter_list = dataset_choices, tech_area_filter_list = tech_area_input_choices ) }) + dataDetailsServer('data_details_tab', data = data) + # ── Headings ────────────────────────────────────────────────────────────── output$cardHeading <- renderUI({ @@ -411,6 +423,10 @@ server <- function(input, output, session) { updateTabItems(session, "tabs", "overview") }) + observeEvent(input$go_back_button, { + updateTabItems(session, "tabs", "overview") + }) + # Tab bookmarking observeEvent(reactiveValuesToList(input), session$doBookmark()) onBookmarked(updateQueryString) diff --git a/apps/data_library/components.R b/apps/data_library/components.R index 93670f9..b420392 100644 --- a/apps/data_library/components.R +++ b/apps/data_library/components.R @@ -1,7 +1,5 @@ # Demo App - UI Components # Derived from apps/mdive_landing_page/components.R -# Changes: removed civis dependency (get_org_admins), inlined 18f_custom_ui.R, -# simplified boxButtonServer to always grant access (no access gate). # Inlined from style_files/18f_custom_ui.R actionButton18F <- function(..., theme = 'light') { @@ -117,7 +115,7 @@ resultsCardUI <- function(id, input_data, rownum, tech_area_buttons = create_buttons(tech_area_list, tech_area_filter_list, ns) wellPanel(style = "background-color:white; color:black", - bsTooltip(id = ns('link_title'), title = 'Click to view full description', + bsTooltip(id = ns('link_title'), title = 'Click to view full description and data details', placement = "right", trigger = 'hover', options = list(container = "body")), div(class = 'row results-header', style = 'padding-left:12px', actionLink(inputId = ns('link_title'), label = input_data[rownum, "name"], @@ -127,7 +125,7 @@ resultsCardUI <- function(id, input_data, rownum, div(class = 'col-sm-8 results-body-main', p(HTML(str_trunc(gsub("\n", "
", input_data[rownum, "description"]), width = 450, - ellipsis = "..."))) + ellipsis = "...(Click Dataset Name to See Full Description)"))) ), div(class = 'col-sm-4 results-body-side border-left', h4("Data Updated on:"), @@ -144,8 +142,9 @@ resultsCardUI <- function(id, input_data, rownum, } -resultsCardServer <- function(id, parent_session, data, row, +resultsCardServer <- function(id, parent_session, data, row, target_module, tag_filter_list, tech_area_filter_list) { + ns <- NS(target_module) moduleServer(id, function(input, output, session) { tag_list <- data[row, 'tags'] %>% str_split(';') %>% unlist() %>% trimws('both') @@ -154,38 +153,11 @@ resultsCardServer <- function(id, parent_session, data, row, update_input(input, tag_list, tag_filter_list, parent_session, 'dataset_choice') update_input(input, tech_area_list, tech_area_filter_list, parent_session, 'tech_area') - observeEvent(input$link_title, { - r <- data[row, ] - - assoc <- r$associated_reports - assoc_ui <- if (!is.na(assoc) && nchar(trimws(assoc)) > 0) { - report_names <- trimws(strsplit(assoc, ";")[[1]]) - tags$ul(lapply(report_names, function(rn) tags$li(rn))) - } else { - p("None") - } + table_selection <- reactive({ data[row, "name"] }) - showModal(modalDialog( - title = r$name, - size = "l", - easyClose = TRUE, - footer = modalButton("Close"), - h4("Description"), - p(r$full_description), - tags$hr(), - fluidRow( - column(6, - h4("Technical Area"), p(r$technical_area), - h4("Last Updated"), p(r$clean_last_data_update), - h4("Access"), p(r$access_restrictions) - ), - column(6, - h4("Source"), p(r$source), - h4("Unit of Analysis"), p(r$unit_of_analysis), - h4("Associated Reports"), assoc_ui - ) - ) - )) + observeEvent(input$link_title, { + updateTabItems(session = parent_session, "tabs", "data_details") + updateSelectInput(session = parent_session, ns('dataset'), selected = table_selection()) }) }) } @@ -222,6 +194,59 @@ boxButtonUI <- function(id, box_link, description, tooltip_text, development_sta } +# ── Data Details Tab ────────────────────────────────────────────────────────── + +dataDetailsUI <- function(id, dataset_options) { + ns <- NS(id) + fluidPage( + fluidRow( + column(8, actionButton("go_back_button", "← Back to Data Catalog")), + column(4, selectInput(ns("dataset"), "Select dataset", + choices = sort(dataset_options))) + ), + br(), + uiOutput(ns("details_content")) + ) +} + +dataDetailsServer <- function(id, data) { + moduleServer(id, function(input, output, session) { + + output$details_content <- renderUI({ + req(input$dataset) + r <- data[data$name == input$dataset, ] + + assoc <- r$associated_reports + assoc_ui <- if (!is.na(assoc) && nchar(trimws(assoc)) > 0) { + report_names <- trimws(strsplit(assoc, ";")[[1]]) + tags$ul(lapply(report_names, function(rn) tags$li(rn))) + } else { + p("None") + } + + box( + width = 12, status = "primary", + h3(r$name), + p(HTML(gsub("\n", "
", r$full_description))), + tags$hr(), + fluidRow( + column(6, + h4("Technical Area"), p(r$technical_area), + h4("Last Updated"), p(r$clean_last_data_update), + h4("Access Restrictions"), p(r$access_restrictions) + ), + column(6, + h4("Source"), p(r$source), + h4("Unit of Analysis"), p(r$unit_of_analysis), + h4("Associated Reports"), assoc_ui + ) + ) + ) + }) + }) +} + + # Simplified: all reports are always accessible in the demo (no access gate) boxButtonServer <- function(id, link, box_title_text, tag_list = c(), tag_filter_list = NULL, From f474e7f85c8e8a007e0b4d9461082b4820664d0a Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 12:40:47 -0700 Subject: [PATCH 15/16] app updates --- apps/data_library/app.R | 2 +- apps/data_library/components.R | 17 ++++++-- apps/data_library/demo-data.R | 76 +++++++++++++++++++++++++++------- 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/apps/data_library/app.R b/apps/data_library/app.R index 532074f..99ac913 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -362,7 +362,7 @@ server <- function(input, output, session) { ) }) - dataDetailsServer('data_details_tab', data = data) + dataDetailsServer('data_details_tab', data = data, reports_data = reports_data) # ── Headings ────────────────────────────────────────────────────────────── diff --git a/apps/data_library/components.R b/apps/data_library/components.R index b420392..af551cb 100644 --- a/apps/data_library/components.R +++ b/apps/data_library/components.R @@ -209,17 +209,26 @@ dataDetailsUI <- function(id, dataset_options) { ) } -dataDetailsServer <- function(id, data) { +dataDetailsServer <- function(id, data, reports_data) { moduleServer(id, function(input, output, session) { output$details_content <- renderUI({ req(input$dataset) r <- data[data$name == input$dataset, ] + req(nrow(r) > 0) - assoc <- r$associated_reports - assoc_ui <- if (!is.na(assoc) && nchar(trimws(assoc)) > 0) { + assoc <- r$associated_reports[1] + has_assoc <- length(assoc) == 1 && !is.na(assoc) && nchar(trimws(assoc)) > 0 + assoc_ui <- if (has_assoc) { report_names <- trimws(strsplit(assoc, ";")[[1]]) - tags$ul(lapply(report_names, function(rn) tags$li(rn))) + tags$ul(lapply(report_names, function(rn) { + report_row <- reports_data[reports_data$name == rn, ] + if (nrow(report_row) > 0) { + tags$li(tags$a(href = report_row$location_link[1], rn, target = "_blank")) + } else { + tags$li(rn) + } + })) } else { p("None") } diff --git a/apps/data_library/demo-data.R b/apps/data_library/demo-data.R index 691c4bf..7e37ae5 100644 --- a/apps/data_library/demo-data.R +++ b/apps/data_library/demo-data.R @@ -12,7 +12,7 @@ reports_data <- data.frame( "Treatment Dashboard" ), description = c( - "Tracks confirmed cases, deaths, and reporting rates across a selection of countries on a quarterly basis.", + "Tracks confirmed cases and reporting rates across a selection of countries on a quarterly basis.", "Monitors reporting rates and case data at the facility level for targeted follow-up.", "Summarizes case data across supported regions.", "Monitors disease in pregnancy indicators across supported regions.", @@ -37,32 +37,55 @@ reports_data <- data.frame( data <- data.frame( name = c( - "Reporting Table", + "QR Reporting Table", "Facility Master List", - "Stock Data", - "Coverage Data", + "Inventory Stock Data", + "Program Coverage Data", "Population Estimates" ), description = c( - "Monthly reporting data aggregated from national systems across supported countries.", + "Monthly case and reporting data aggregated from national health information systems across supported countries.", "Master list of all supported health facilities including location and administrative hierarchy.", - "Logistics management data including commodity stockouts, consumption, and stock on hand.", - "Seasonal campaign coverage data by cycle and administrative unit.", + "Logistics data including commodity stockouts, consumption, and stock on hand.", + "Campaign coverage data by cycle and administrative unit.", "Annual population estimates by administrative unit used for rate calculations." ), - technical_area = c("Case Management", "Health Systems", "Supply Chain", "Coverage", "Population"), + full_description = c( + "The QR Reporting Table contains monthly surveillance data compiled from national health information systems + across all supported countries. It includes confirmed cases, suspected cases, and testing data broken down + by administrative unit (admin 1 and admin 2), facility, and month. Reporting rates and completeness + indicators are also included to help users assess data quality. This table is the primary data source + for the Quarterly Report dashboard.", + "The Facility Master List is the authoritative reference for all supported health facilities. + It includes facility names, unique identifiers, administrative hierarchy (country, admin 1, admin 2), + GPS coordinates where available, facility type, and ownership category. + It is used as a lookup table across multiple dashboards to ensure consistent facility naming + and geographic attribution.", + "The Inventory Stock Data table contains commodity tracking information from national logistics + information systems. It covers stock on hand, quantities received, quantities consumed, and stockout + days for key commodities. Data is reported at the facility level on a monthly basis. + Access is restricted due to the sensitivity of supply chain information.", + "The Program Coverage Data table contains campaign-level coverage data for seasonal intervention + programs. Each row represents one administrative unit in one campaign cycle, with fields for + target population, individuals reached, and coverage percentage. + Data is available for all supported countries with active programs.", + "The Population Estimates table provides annual population denominators by administrative unit + used across the platform for calculating rate-based indicators (e.g. cases per 1,000 population). + Estimates are derived from national census projections and are updated annually." + ), + technical_area = c("Case Management", "Health Systems", "Supply Chain", "Campaigns", "Cross-cutting"), tags = c( - "monthly;reporting;cases", - "facility;reporting;cases", - "stock;logistics;commodities", - "coverage;campaigns;SMC", + "surveillance;cases;reporting", + "facilities;geo;admin", + "stockouts;commodities;logistics", + "coverage;campaigns", "population;denominators" ), access_restrictions = c( - "Open use", - "Open use", + "Open use within platform", + "Open use within platform", "Restricted", - "Open use", + "Open use within platform", "Publicly Available" ), clean_last_data_update = c( @@ -71,5 +94,26 @@ data <- data.frame( last_data_update = as.Date(c( "2026-01-01", "2025-12-01", "2025-11-01", "2025-10-01", "2026-01-01" )), + source = c( + "National Health Information System", + "Country Program Teams", + "National Logistics System", + "National Program Office", + "National Census Projections" + ), + unit_of_analysis = c( + "Country / Admin 1 / Admin 2 / Month", + "Facility", + "Facility / Month", + "Admin unit / Campaign cycle", + "Admin unit / Year" + ), + associated_reports = c( + "Quarterly Report; Facility Level Dashboard", + "Facility Level Dashboard", + "", + "Cases Dashboard", + "Quarterly Report; Disease in Pregnancy Dashboard" + ), stringsAsFactors = FALSE -) \ No newline at end of file +) From 1b143ace920401b27edf8f4694bf3fb7171352fc Mon Sep 17 00:00:00 2001 From: Maya Mallaby-Kay Date: Thu, 5 Mar 2026 12:49:30 -0700 Subject: [PATCH 16/16] remove the featured sorting --- apps/data_library/app.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/data_library/app.R b/apps/data_library/app.R index 99ac913..1302731 100644 --- a/apps/data_library/app.R +++ b/apps/data_library/app.R @@ -282,7 +282,7 @@ server <- function(input, output, session) { }) # Insert category/featured headers when sorting by category or featured - if (input$order_results_reports %in% c('featured', 'category')) { + if (input$order_results_reports %in% c('category')) { category_counts <- filtered_reports() %>% dplyr::mutate(position = row_number()) %>% dplyr::group_by(!!dplyr::sym(input$order_results_reports)) %>%