diff --git a/DESCRIPTION b/DESCRIPTION
index 916e5bd..da49139 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,6 +1,6 @@
Package: rdcAVS
Title: SIA Campaign Builder for DRC
-Version: 0.0.0.9008
+Version: 0.0.0.9009
Authors@R:
person("Mervin Keith", "Cuadera", , "xrg9@cdc.com", role = c("aut", "cre"))
Description: This app assists in creating campaigns and setting permissions for
@@ -22,22 +22,26 @@ Imports:
foreach,
fs,
future,
+ ggplot2,
ggpubr,
googledrive,
googlesheets4,
httr,
lubridate,
+ openxlsx,
pkgload,
progressr,
purrr,
rappdirs,
readr,
+ scales,
shiny,
shiny.fluent,
shinyFiles,
shinyjs,
stringr,
- tidyr
+ tidyr,
+ utils
Suggests:
stringi
URL: https://mcuadera.github.io/rdcAVS/
diff --git a/NAMESPACE b/NAMESPACE
index f221fbb..2806a8d 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -10,9 +10,14 @@ export(fluent_card)
export(get_campaign_progress)
export(get_sheet_info)
export(icon_btn)
+export(taskButton)
+export(task_button_css)
+export(task_button_js)
+export(withTaskButton)
import(bsicons)
import(bslib)
import(dplyr)
+import(ggplot2)
import(googledrive)
import(readr)
import(shiny)
@@ -40,13 +45,18 @@ importFrom(googlesheets4,range_write)
importFrom(httr,POST)
importFrom(httr,add_headers)
importFrom(lubridate,as_date)
+importFrom(openxlsx,loadWorkbook)
+importFrom(openxlsx,saveWorkbook)
+importFrom(openxlsx,writeData)
importFrom(progressr,handlers)
importFrom(progressr,progressor)
importFrom(progressr,with_progress)
+importFrom(purrr,map)
importFrom(purrr,pwalk)
importFrom(purrr,walk)
importFrom(rappdirs,user_data_dir)
importFrom(readr,type_convert)
+importFrom(scales,percent)
importFrom(shiny.fluent,DatePicker.shinyInput)
importFrom(shiny.fluent,DefaultButton.shinyInput)
importFrom(shiny.fluent,Dropdown.shinyInput)
@@ -65,3 +75,4 @@ importFrom(shinyjs,show)
importFrom(shinyjs,toggle)
importFrom(shinyjs,useShinyjs)
importFrom(tidyr,pivot_longer)
+importFrom(utils,write.csv)
diff --git a/R/app.R b/R/app.R
index 0f25055..c034169 100644
--- a/R/app.R
+++ b/R/app.R
@@ -19,7 +19,7 @@
#' tabs that creates a campaign, add geographic information, and set Google Drive
#' permissions. Creating a campaign outputs a folder prefixed with "CAMPAGNE_",
#' which contains a hierarchical structure, going from the largest geographic unit (Province)
-#' to the lowest geographic unit (Zone de Sant\u00e9). Within the Zone de Sant\u00e9 folder, contains
+#' to the lowest geographic unit (Zone de Santé). Within the Zone de Santé folder, contains
#' a template file.
#'
#' @details
@@ -45,115 +45,9 @@ campagneApp <- function() {
bootswatch = "yeti"
),
tags$head(
- tags$style(HTML("
- .notification-overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 99998;
- display: none;
- opacity: 0;
- transition: opacity 0.3s ease-in-out;
- }
-
- .notification-overlay.show {
- display: block;
- opacity: 1;
- }
-
- #shiny-notification-panel {
- position: fixed;
- top: 50% !important;
- left: 50% !important;
- right: auto !important;
- bottom: auto !important;
- transform: translate(-50%, -50%) !important;
- width: auto;
- max-width: 500px;
- z-index: 99999;
- }
-
- @keyframes slideDown {
- from {
- opacity: 0;
- transform: translate(-50%, -60%);
- }
- to {
- opacity: 1;
- transform: translate(-50%, -50%);
- }
- }
-
- @keyframes pulse {
- 0%, 100% {
- transform: scale(1);
- }
- 50% {
- transform: scale(1.05);
- }
- }
-
- .shiny-notification {
- background-color: white;
- border: none;
- border-radius: 12px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
- padding: 40px 30px 30px 30px;
- text-align: center;
- font-size: 16px;
- color: #333;
- opacity: 1 !important;
- min-width: 350px;
- position: relative;
- animation: slideDown 0.4s ease-out;
- }
-
- .shiny-notification::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 4px;
- border-radius: 12px 12px 0 0;
- background: linear-gradient(90deg, #3498db, #2980b9);
- }
-
- .shiny-notification-message::before {
- background: linear-gradient(90deg, #3498db, #2980b9);
- }
-
- .shiny-notification-warning::before {
- background: linear-gradient(90deg, #f39c12, #e67e22);
- }
-
- .shiny-notification-error::before {
- background: linear-gradient(90deg, #e74c3c, #c0392b);
- }
-
- .shiny-notification.success-notification::before {
- background: linear-gradient(90deg, #27ae60, #229954);
- }
-
- .shiny-notification-message,
- .shiny-notification-warning,
- .shiny-notification-error {
- background-color: white;
- border-left: none;
- }
-
- .shiny-notification .message-text {
- color: #555;
- line-height: 1.6;
- margin-top: 8px;
- font-size: 15px;
- }"
- )
- )
- ),
+ tags$style(HTML(task_button_css)),
+ tags$script(HTML(task_button_js))
+ ),
useShinyjs(),
bslib::input_dark_mode(id = "mode"),
downloadButton("download_template", label = "", style = "visibility:hidden;"),
@@ -178,7 +72,7 @@ campagneApp <- function() {
style = "display:flex; align-items:center; gap:10px; flex-wrap:nowrap;",
imageOutput("drc_cdc_logo", height = "60px"),
h5(
- "Portail de création des masques de saisie des campagnes de vaccination en RDC",
+ "Portail de cr\u00e9ation des masques de saisie des campagnes de vaccination en RDC",
style = "
margin:0;
font-weight:500;
diff --git a/R/compile_masques.R b/R/compile_masques.R
index 5e49eed..fe3bb12 100644
--- a/R/compile_masques.R
+++ b/R/compile_masques.R
@@ -38,7 +38,7 @@ compile_masques <- function(campaign_name) {
})
# Compile all templates for one sheet at a time
- withProgress(message = "Compilation des données dans: ",
+ withProgress(message = "Compilation des donn\u00e9es dans: ",
{
n <- length(tab_names)
for (i in 1:n) {
diff --git a/R/compile_masques_antenne.R b/R/compile_masques_antenne.R
index c9ca0c1..cb7fbbe 100644
--- a/R/compile_masques_antenne.R
+++ b/R/compile_masques_antenne.R
@@ -55,7 +55,7 @@ compile_masques_antenne <- function(campaign_name) {
# Compile masques
# Compile all templates for one sheet at a time
- withProgress(message = "Compilation des données dans: ",
+ withProgress(message = "Compilation des donn\u00e9es dans: ",
{
n <- length(tab_names)
for (j in 1:n) {
@@ -67,7 +67,7 @@ compile_masques_antenne <- function(campaign_name) {
}
})
}
- showNotification("Masques au niveau des antennes compilés", type = "message")
+ showNotification("Masques au niveau des antennes compil\u00e9es", type = "message")
return(invisible())
}
diff --git a/R/compile_masques_national.R b/R/compile_masques_national.R
index ec3b694..68fdf1c 100644
--- a/R/compile_masques_national.R
+++ b/R/compile_masques_national.R
@@ -53,7 +53,7 @@ compile_masques_national <- function(campaign_name) {
showNotification("Autorisations requises obtenues.", type = "message")
# Compile all templates for one sheet at a time
- withProgress(message = "Compilation des données dans: ",
+ withProgress(message = "Compilation des donn\u00e9es dans: ",
{
n <- length(tab_names)
for (i in 1:n) {
diff --git a/R/compile_masques_province.R b/R/compile_masques_province.R
index e7c2d9b..101e256 100644
--- a/R/compile_masques_province.R
+++ b/R/compile_masques_province.R
@@ -46,7 +46,7 @@ compile_masques_province <- function(campaign_name) {
# Compile masques
# Compile all templates for one sheet at a time
- withProgress(message = "Compilation des données dans: ",
+ withProgress(message = "Compilation des donn\u00e9es dans: ",
{
n <- length(tab_names)
for (j in 1:n) {
@@ -58,7 +58,7 @@ compile_masques_province <- function(campaign_name) {
}
})
}
- showNotification("Masques au niveau des provinces compilés", type = "message")
+ showNotification("Masques au niveau des provinces compil\u00e9es", type = "message")
return(invisible())
}
diff --git a/R/create_campaign_progress_heatmap.R b/R/create_campaign_progress_heatmap.R
index d22a94a..05cef8a 100644
--- a/R/create_campaign_progress_heatmap.R
+++ b/R/create_campaign_progress_heatmap.R
@@ -36,7 +36,7 @@ create_campaign_progress_heatmap <- function(summary) {
ggplot2::ylab("Aire De Sante") +
ggplot2::xlab("") +
ggplot2::labs(subtitle = paste0("Objectif: Jour 1: 40%, Jour 2: 70%, Jour 3: 100%, Jour 4: 100%\n",
- "A risque si à moins de 5 % et en échec si à plus de 5 % de l'objectif")) +
+ "A risque si \u00e0 moins de 5 % et en \u00e9chec si \u00e0 plus de 5 % de l'objectif")) +
ggplot2::theme(axis.text.x = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 14),
axis.title.x = ggplot2::element_text(size = 14, face = "bold"),
diff --git a/R/create_daily_campaign_progress_heatmap.R b/R/create_daily_campaign_progress_heatmap.R
index 9f1f8b7..d53b686 100644
--- a/R/create_daily_campaign_progress_heatmap.R
+++ b/R/create_daily_campaign_progress_heatmap.R
@@ -34,7 +34,7 @@ create_daily_campaign_progress_heatmap <- function(summary) {
ggplot2::ylab("Aire De Sante") +
ggplot2::xlab("") +
ggplot2::labs(subtitle = paste0("Objectif: Jour 1: 40%, Jour 2-3: 30%, Jour 4: 100%\n",
- "A risque si à moins de 5 % et en échec si à plus de 5 % de l'objectif")) +
+ "A risque si \u00e0 moins de 5 % et en \u00e9echec si \u00e0 plus de 5 % de l'objectif")) +
ggplot2::theme(axis.text.x = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 14),
axis.title.x = ggplot2::element_text(size = 14, face = "bold"),
diff --git a/R/create_recovery_heatmap.R b/R/create_recovery_heatmap.R
index cbcd869..d9ab4f1 100644
--- a/R/create_recovery_heatmap.R
+++ b/R/create_recovery_heatmap.R
@@ -5,26 +5,26 @@ create_recovery_heatmap <- function(summary) {
cat_0_11 = case_when(
recovery_0_11 == 0 & recovery_0_11_cumulative > 0 & jour %in% c("Jour 2", "Jour 3", "Jour 4") ~ "Aucun changement",
recovery_0_11_cumulative > 0 ~ "Bien",
- .default = "Zéro"
+ .default = "Z\u00e9ro"
),
cat_12_23 = case_when(
recovery_12_23 == 0 & recovery_12_23_cumulative > 0 & jour %in% c("Jour 2", "Jour 3", "Jour 4") ~ "Aucun changement",
recovery_12_23_cumulative > 0 ~ "Bien",
- .default = "Zéro"
+ .default = "Z\u00e9ro"
),
cat_24_59 = case_when(
recovery_24_59 == 0 & recovery_24_59_cumulative > 0 & jour %in% c("Jour 2", "Jour 3", "Jour 4") ~ "Aucun changement",
recovery_24_59_cumulative > 0 ~ "Bien",
- .default = "Zéro"
+ .default = "Z\u00e9ro"
)
) |>
dplyr::mutate(across(all_of(c("cat_0_11", "cat_12_23", "cat_24_59")),
- \(x) factor(x, levels = c("Bien", "Aucun changement", "Zéro"))))
+ \(x) factor(x, levels = c("Bien", "Aucun changement", "Z\u00e9ro"))))
plot_0_11 <- ggplot2::ggplot(plot_data) +
ggplot2::geom_tile(ggplot2::aes(y =aire_de_sante, x = jour, fill = cat_0_11), show.legend = TRUE) +
- ggplot2::scale_fill_manual(values = c("Zéro" = "lightgrey", "Bien" = "#006B3E", "Aucun changement" = "#338F65"),
+ ggplot2::scale_fill_manual(values = c("Z\u00e9ro" = "lightgrey", "Bien" = "#006B3E", "Aucun changement" = "#338F65"),
name = "Statut") +
ggplot2::geom_text(ggplot2::aes(y = aire_de_sante, x = jour, label = recovery_0_11_cumulative), size = 8) +
ggplot2::theme_void() +
@@ -42,7 +42,7 @@ create_recovery_heatmap <- function(summary) {
plot_12_23 <- ggplot2::ggplot(plot_data) +
ggplot2::geom_tile(ggplot2::aes(y =aire_de_sante, x = jour, fill = cat_12_23), show.legend = TRUE) +
- ggplot2::scale_fill_manual(values = c("Zéro" = "lightgrey", "Bien" = "#006B3E", "Aucun changement" = "#338F65"),
+ ggplot2::scale_fill_manual(values = c("Z\u00e9ro" = "lightgrey", "Bien" = "#006B3E", "Aucun changement" = "#338F65"),
name = "Statut") +
ggplot2::geom_text(ggplot2::aes(y = aire_de_sante, x = jour, label = recovery_12_23_cumulative), size = 8) +
ggplot2::theme_void() +
@@ -60,7 +60,7 @@ create_recovery_heatmap <- function(summary) {
plot_24_59 <- ggplot2::ggplot(plot_data) +
ggplot2::geom_tile(ggplot2::aes(y =aire_de_sante, x = jour, fill = cat_24_59), show.legend = TRUE) +
- ggplot2::scale_fill_manual(values = c("Zéro" = "lightgrey", "Bien" = "#006B3E", "Aucun changement" = "#338F65"),
+ ggplot2::scale_fill_manual(values = c("Z\u00e9ro" = "lightgrey", "Bien" = "#006B3E", "Aucun changement" = "#338F65"),
name = "Statut") +
ggplot2::geom_text(ggplot2::aes(y = aire_de_sante, x = jour, label = recovery_24_59_cumulative), size = 8) +
ggplot2::theme_void() +
diff --git a/R/create_urban_rural_heatmap.R b/R/create_urban_rural_heatmap.R
index 081d91b..938ba43 100644
--- a/R/create_urban_rural_heatmap.R
+++ b/R/create_urban_rural_heatmap.R
@@ -6,15 +6,15 @@ create_urban_rural_heatmap <- function(summary) {
cat_rural = case_when(
between(avg_vax_rural, 1, 99) ~ "Objectif Non Atteint",
avg_vax_rural >= 100 ~ "Bien",
- .default = "Zéro"
+ .default = "Z\u00e9ro"
),
- cat_rural = factor(cat_rural, levels = c("Bien", "Objectif Non Atteint", "Zéro")),
+ cat_rural = factor(cat_rural, levels = c("Bien", "Objectif Non Atteint", "Z\u00e9ro")),
cat_urban = case_when(
between(avg_vax_urban, 1, 199) ~ "Objectif Non Atteint",
avg_vax_urban >= 200 ~ "Bien",
- .default = "Zéro"
+ .default = "Z\u00e9ro"
),
- cat_urban = factor(cat_urban, levels = c("Bien", "Objectif Non Atteint", "Zéro")),
+ cat_urban = factor(cat_urban, levels = c("Bien", "Objectif Non Atteint", "Z\u00e9ro")),
avg_vax_rural = round(avg_vax_rural),
avg_vax_urban = round(avg_vax_urban)
) |>
@@ -22,7 +22,7 @@ create_urban_rural_heatmap <- function(summary) {
rural_plot <- ggplot2::ggplot(data = plot_data) +
ggplot2::geom_tile(ggplot2::aes(y =aire_de_sante, x = jour, fill = cat_rural), show.legend = TRUE) +
- ggplot2::scale_fill_manual(values = c("Zéro" = "lightgrey", "Bien" = "#006B3E", "Objectif Non Atteint" = "#ED2938"),
+ ggplot2::scale_fill_manual(values = c("Z\u00e9ro" = "lightgrey", "Bien" = "#006B3E", "Objectif Non Atteint" = "#ED2938"),
name = "Statut") +
ggplot2::geom_text(ggplot2::aes(y = aire_de_sante, x = jour, label = avg_vax_rural), size = 8) +
ggplot2::theme_void() +
@@ -40,7 +40,7 @@ create_urban_rural_heatmap <- function(summary) {
urban_plot <- ggplot2::ggplot(data = plot_data) +
ggplot2::geom_tile(ggplot2::aes(y =aire_de_sante, x = jour, fill = cat_urban), show.legend = TRUE) +
- ggplot2::scale_fill_manual(values = c("Zéro" = "lightgrey", "Bien" = "#006B3E", "Objectif Non Atteint" = "#ED2938"),
+ ggplot2::scale_fill_manual(values = c("Z\u00e9ro" = "lightgrey", "Bien" = "#006B3E", "Objectif Non Atteint" = "#ED2938"),
name = "Statut") +
ggplot2::geom_text(ggplot2::aes(y = aire_de_sante, x = jour, label = avg_vax_urban), size = 8) +
ggplot2::theme_void() +
diff --git a/R/data.R b/R/data.R
new file mode 100644
index 0000000..c6ec28e
--- /dev/null
+++ b/R/data.R
@@ -0,0 +1,40 @@
+#' Template for user permissions data
+#'
+#' A template dataset showing the expected structure and columns for the user
+#' permissions file. Users can download this template to prepare their own
+#' permissions file before importing it into the application.
+#'
+#' @format A tibble with 9 columns
+#' \describe{
+#' \item{name}{Full name of the user (character)}
+#' \item{phone}{Phone number of the user (numeric)}
+#' \item{notes}{Optional notes about the user (character)}
+#' \item{email}{Email address of the user (character)}
+#' \item{level}{Access level of the user, e.g. \code{"national"} (character)}
+#' \item{role}{Role of the user, e.g. \code{"writer"} (character)}
+#' \item{province}{Province assigned to the user, \code{NA} if not applicable (logical)}
+#' \item{antenne}{Antenne assigned to the user, \code{NA} if not applicable (logical)}
+#' \item{zone_de_sante}{Health zone assigned to the user, \code{NA} if not applicable (logical)}
+#' }
+#' @note The expected file format is \code{.csv}.
+#' @source Internal rdcAVS package template
+"data_perm"
+
+
+#' Template for geographic data
+#'
+#' A template dataset showing the expected structure and columns for the
+#' geographic data file. Users can download this template to prepare their
+#' own geographic file before importing it into the application.
+#'
+#' @format A tibble with 5 columns:
+#' \describe{
+#' \item{provinces}{Name of the province (character)}
+#' \item{antennes}{Name of the antenne (character)}
+#' \item{zones_de_sante}{Name of the health zone (character)}
+#' \item{aires_de_sante}{Name of the health area (character)}
+#' \item{population_totale}{Total population of the health area (numeric)}
+#' }
+#' @note The expected file format is \code{.csv}.
+#' @source Internal rdcAVS package template
+"template_data_geographics"
\ No newline at end of file
diff --git a/R/drive_init_campaign.R b/R/drive_init_campaign.R
index c9954f0..d066e12 100644
--- a/R/drive_init_campaign.R
+++ b/R/drive_init_campaign.R
@@ -41,8 +41,9 @@ drive_init_campaign <- function(start_date,
campaign_name <- Sys.Date()
}
- start_date <- lubridate::as_date(start_date, format = c("%d/%m/%Y", "%Y-%m-%d"))
- end_date <- lubridate::as_date(end_date, format = c("%d/%m/%Y", "%Y-%m-%d"))
+ start_date <- lubridate::as_date(start_date)
+ end_date <- lubridate::as_date(end_date)
+
campaign_name <- paste0("CAMPAGNE_", campaign_name)
if (!is.null(prov_target)) {
diff --git a/R/get_campaign_progress.R b/R/get_campaign_progress.R
index 663527d..610d8e1 100644
--- a/R/get_campaign_progress.R
+++ b/R/get_campaign_progress.R
@@ -92,17 +92,17 @@ get_campaign_progress <- function(dribble, sheets = 5:8) {
dplyr::rename(
"province" = "Province",
"antenne" = "Antenne",
- "zone_de_sante" = "Zone de Santé",
- "aire_de_sante" = "Aire de Santé",
+ "zone_de_sante" = "Zone de Sant\u00e9",
+ "aire_de_sante" = "Aire de Sant\u00e9",
"rapports_de_vaccination_attendus" = "Attendus",
"rapports_de_vaccination_recus" = "Recus",
"cible_0_59_mois" = "Cible 0-59 mois",
"vaccination_0_59_mois" = "Total",
- "avg_vax_rural" = "Nb moyen d'enfants vaccinés/équipe (ref 100 enfants)",
- "avg_vax_urban" = "Nb moyen d'enfants vaccinés/équipe (ref 200 enfants)",
- "recovery_0_11" = "# Récupérations 0-11 mois",
- "recovery_12_23" = "# Récupérations 12-23 mois",
- "recovery_24_59" = "# Récupérations 24-59 mois"
+ "avg_vax_rural" = "Nb moyen d'enfants vaccin\u00e9s/\u00e9quipe (ref 100 enfants)",
+ "avg_vax_urban" = "Nb moyen d'enfants vaccin\u00e9s/\u00e9quipe (ref 200 enfants)",
+ "recovery_0_11" = "# R\u00e9cup\u00e9rations 0-11 mois",
+ "recovery_12_23" = "# R\u00e9cup\u00e9rations 12-23 mois",
+ "recovery_24_59" = "# R\u00e9cup\u00e9rations 24-59 mois"
) |>
dplyr::mutate(cible_0_59_mois = round(cible_0_59_mois, 0),
rapport_completude_pct = round(rapports_de_vaccination_recus / rapports_de_vaccination_attendus * 100, 0),
diff --git a/R/get_sheet_info.R b/R/get_sheet_info.R
index 24a93f4..45f157e 100644
--- a/R/get_sheet_info.R
+++ b/R/get_sheet_info.R
@@ -118,63 +118,63 @@ get_sheet_info <- function(dribble, sheets = 1:8) {
get_range_from_name <- function(sheet, name) {
if (sheet == 1) {
switch(name,
- "Situation des équipes de vaccination par aires de santé" = "E:G",
- "Populations cibles vaccinées à la campagne précédente" = "N:O",
- "Disponilité du vaccin, antigène utilisé" = "V:V",
- "Nombre d'équipes de mobilisation sociale de proxi" = "Y:Y",
+ "Situation des \u00e9quipes de vaccination par aires de sant\u00e9" = "E:G",
+ "Populations cibles vaccin\u00e9es \u00e0 la campagne pr\u00e9c\u00e9dente" = "N:O",
+ "Disponilit\u00e9 du vaccin, antig\u00e8ne utilis\u00e9" = "V:V",
+ "Nombre d'\u00e9quipes de mobilisation sociale de proxi" = "Y:Y",
"RESEAU/ INTERNET" = "Z:AB",
- "Enfants 0 - 11 mois identifiés pour etre récupérés en PEV de routine" = "AC:AQ",
- "Enfants 12 - 23 mois identifiés pour etre récupérés en PEV de routine" = "AS:BG",
- "Enfants 24 - 59 mois identifiés pour etre récupérés en PEV de routine" = "BI:BW",
- "Femmes cibles identifiées pour etre récupérées en PEV de routine" = "BY:CC",
+ "Enfants 0 - 11 mois identifi\u00e9s pour etre r\u00e9cup\u00e9r\u00e9s en PEV de routine" = "AC:AQ",
+ "Enfants 12 - 23 mois identifi\u00e9s pour etre r\u00e9cup\u00e9r\u00e9s en PEV de routine" = "AS:BG",
+ "Enfants 24 - 59 mois identifi\u00e9s pour etre r\u00e9cup\u00e9r\u00e9s en PEV de routine" = "BI:BW",
+ "Femmes cibles identifi\u00e9es pour etre r\u00e9cup\u00e9r\u00e9es en PEV de routine" = "BY:CC",
"LOCALISATION DES POPULATIONS SPECIALES" = "CE:CG",
- "Nombre de ménages dénombrés" = "CH:CH",
- "Populations spéciales (Nombre de personnes selon le Statut)" = "CI:CK",
- "Effectifs des Populations spéciales recensées" = "CL:CU")
+ "Nombre de m\u00e9nages d\u00e9nombr\u00e9s" = "CH:CH",
+ "Populations sp\u00e9ciales (Nombre de personnes selon le Statut)" = "CI:CK",
+ "Effectifs des Populations sp\u00e9ciales recens\u00e9es" = "CL:CU")
} else if (sheet %in% 2:4) {
switch(name,
- "Nombre de ménage" = "G:G",
- "Complétude MoSo" = "I:I",
- "Nombre de personnes de plus de 15 ans sensibilisées" = "K:L",
- "0-11 mois dénombrés" = "N:O",
- "12-59 mois dénombrés" = "Q:R",
- "5-14 ans dénombrés" = "W:X",
+ "Nombre de m\u00e9nage" = "G:G",
+ "Compl\u00e9tude MoSo" = "I:I",
+ "Nombre de personnes de plus de 15 ans sensibilis\u00e9es" = "K:L",
+ "0-11 mois d\u00e9nombr\u00e9s" = "N:O",
+ "12-59 mois d\u00e9nombr\u00e9s" = "Q:R",
+ "5-14 ans d\u00e9nombr\u00e9s" = "W:X",
"Cas de maladies sous surveillance" = "Y:AC",
"REFUS" = "AD:AE",
"Mobilisation communautaire des FEMMES" = "AF:AN",
"Mobilisation communautaire des HOMMES" = "AO:AW",
- "Mobilisation communautaire générale" = "AX:BI",
- "Rattrapage/récupération en PEV" = "BJ:BK"
+ "Mobilisation communautaire g\u00e9n\u00e9rale" = "AX:BI",
+ "Rattrapage/r\u00e9cup\u00e9ration en PEV" = "BJ:BK"
)
} else if (sheet %in% 5:8) {
switch(name,
- "Nombre de ménage" = "G:G",
- "Complétude MoSo" = "I:I",
- "Nombre de personnes de plus de 15 ans sensibilisées" = "K:L",
- "0-11 mois dénombrés" = "N:O",
- "12-59 mois dénombrés" = "Q:R",
- "5-14 ans dénombrés" = "W:X",
+ "Nombre de m\u00e9nage" = "G:G",
+ "Compl\u00e9tude MoSo" = "I:I",
+ "Nombre de personnes de plus de 15 ans sensibilis\u00e9es" = "K:L",
+ "0-11 mois d\u00e9nombr\u00e9s" = "N:O",
+ "12-59 mois d\u00e9nombr\u00e9s" = "Q:R",
+ "5-14 ans d\u00e9nombr\u00e9s" = "W:X",
"Cas de maladies sous surveillance" = "Y:AC",
"REFUS" = "AD:AE",
"Mobilisation communautaire des FEMMES" = "AF:AN",
"Mobilisation communautaire des HOMMES" = "AO:AW",
- "Mobilisation communautaire générale" = "AX:BI",
- "Rattrapage/récupération en PEV" = "BJ:BK",
- "Complétude des rapports de vaccination" = "CL:CN",
- "Vaccines 0-11 mois Zéro Dose" = "CP:CQ",
+ "Mobilisation communautaire g\u00e9n\u00e9rale" = "AX:BI",
+ "Rattrapage/r\u00e9cup\u00e9ration en PEV" = "BJ:BK",
+ "Compl\u00e9tude des rapports de vaccination" = "CL:CN",
+ "Vaccines 0-11 mois Z\u00e9ro Dose" = "CP:CQ",
"Vaccines 0-11 mois 1 dose et plus" = "CR:CS",
- "Vaccines 12-59 mois Zéro Dose" = "CW:CX",
+ "Vaccines 12-59 mois Z\u00e9ro Dose" = "CW:CX",
"Vaccines 12-59 mois 1 dose et plus" = "CY:CZ",
- "Vaccines 0-11 mois Zéro Dose" = "DD:DE",
+ "Vaccines 0-11 mois Z\u00e9ro Dose" = "DD:DE",
"Vaccines 0-11 mois 1 dose et plus" = "DF:DG",
- "Vaccines 12-59 mois Zéro Dose" = "DK:DL",
+ "Vaccines 12-59 mois Z\u00e9ro Dose" = "DK:DL",
"Vaccines 12-59 mois 1 dose et plus" = "DM:DN",
- "Vaccinés par les Equipes en milieu rural" = "EA:EB",
- "Vaccinés par les Equipes en milieu urbain" = "EC:ED",
+ "Vaccin\u00e9s par les Equipes en milieu rural" = "EA:EB",
+ "Vaccin\u00e9s par les Equipes en milieu urbain" = "EC:ED",
"GESTION DES FLACONS" = "EQ:EW",
"Recherche active des cas de MEV" = "EX:FA",
"MAPI" = "FB:FC",
- "Ver de Guinée" = "FD:FD",
+ "Ver de Guin\u00e9e" = "FD:FD",
"BCG (0-11)" = "FE:FF",
"VPI1 (0-11)" = "FG:FH",
"DTC-HepB-Hib 1 (0-11)" = "FI:FJ",
@@ -233,66 +233,66 @@ get_sheet_info <- function(dribble, sheets = 1:8) {
get_names_from_sheet <- function(sheet = NULL) {
if (sheet == 1) {
c(
- "Situation des équipes de vaccination par aires de santé",
- "Populations cibles vaccinées à la campagne précédente",
- "Disponilité du vaccin, antigène utilisé" ,
- "Nombre d'équipes de mobilisation sociale de proxi",
+ "Situation des \u00e9quipes de vaccination par aires de sant\u00e9",
+ "Populations cibles vaccin\u00e9es \u00e0 la campagne pr\u00e9c\u00e9dente",
+ "Disponilit\u00e9 du vaccin, antig\u00e8ne utilis\u00e9" ,
+ "Nombre d'\u00e9quipes de mobilisation sociale de proxi",
"RESEAU/ INTERNET" ,
- "Enfants 0 - 11 mois identifiés pour etre récupérés en PEV de routine",
- "Enfants 12 - 23 mois identifiés pour etre récupérés en PEV de routine",
- "Enfants 24 - 59 mois identifiés pour etre récupérés en PEV de routine",
- "Femmes cibles identifiées pour etre récupérées en PEV de routine",
+ "Enfants 0 - 11 mois identifi\u00e9s pour etre r\u00e9cup\u00e9r\u00e9s en PEV de routine",
+ "Enfants 12 - 23 mois identifi\u00e9s pour etre r\u00e9cup\u00e9r\u00e9s en PEV de routine",
+ "Enfants 24 - 59 mois identifi\u00e9s pour etre r\u00e9cup\u00e9r\u00e9s en PEV de routine",
+ "Femmes cibles identifi\u00e9es pour etre r\u00e9cup\u00e9r\u00e9es en PEV de routine",
"LOCALISATION DES POPULATIONS SPECIALES",
- "Nombre de ménages dénombrés",
- "Populations spéciales (Nombre de personnes selon le Statut)",
- "Effectifs des Populations spéciales recensées"
+ "Nombre de m\u00e9nages d\u00e9nombr\u00e9s",
+ "Populations sp\u00e9ciales (Nombre de personnes selon le Statut)",
+ "Effectifs des Populations sp\u00e9ciales recens\u00e9es"
)
} else if (sheet %in% 2:4) {
- c("Nombre de ménage",
- "Complétude MoSo",
- "Nombre de personnes de plus de 15 ans sensibilisées",
- "0-11 mois dénombrés",
- "12-59 mois dénombrés",
- "5-14 ans dénombrés",
+ c("Nombre de m\u00e9nage",
+ "Compl\u00e9tude MoSo",
+ "Nombre de personnes de plus de 15 ans sensibilis\u00e9es",
+ "0-11 mois d\u00e9nombr\u00e9s",
+ "12-59 mois d\u00e9nombr\u00e9s",
+ "5-14 ans d\u00e9nombr\u00e9s",
"Cas de maladies sous surveillance",
"REFUS",
"Mobilisation communautaire des FEMMES",
"Mobilisation communautaire des HOMMES",
- "Mobilisation communautaire générale",
- "Rattrapage/récupération en PEV")
+ "Mobilisation communautaire g\u00e9n\u00e9rale",
+ "Rattrapage/r\u00e9cup\u00e9ration en PEV")
} else if (sheet %in% 5:8) {
c(
- "Nombre de ménage",
- "Complétude MoSo",
- "Nombre de personnes de plus de 15 ans sensibilisées",
- "0-11 mois dénombrés",
- "12-59 mois dénombrés",
- "5-14 ans dénombrés",
+ "Nombre de m\u00e9nage",
+ "Compl\u00e9tude MoSo",
+ "Nombre de personnes de plus de 15 ans sensibilis\u00e9es",
+ "0-11 mois d\u00e9nombr\u00e9s",
+ "12-59 mois d\u00e9nombr\u00e9s",
+ "5-14 ans d\u00e9nombr\u00e9s",
"Cas de maladies sous surveillance",
"REFUS",
"Mobilisation communautaire des FEMMES",
"Mobilisation communautaire des HOMMES",
- "Mobilisation communautaire générale",
- "Rattrapage/récupération en PEV",
- "Complétude des rapports de vaccination",
- "Vaccines 0-11 mois Zéro Dose",
+ "Mobilisation communautaire g\u00e9n\u00e9rale",
+ "Rattrapage/r\u00e9cup\u00e9ration en PEV",
+ "Compl\u00e9tude des rapports de vaccination",
+ "Vaccines 0-11 mois Z\u00e9ro Dose",
"Vaccines 0-11 mois 1 dose et plus",
- "Vaccines 12-59 mois Zéro Dose",
+ "Vaccines 12-59 mois Z\u00e9ro Dose",
"Vaccines 12-59 mois 1 dose et plus",
- "Vaccines 0-11 mois Zéro Dose",
+ "Vaccines 0-11 mois Z\u00e9ro Dose",
"Vaccines 0-11 mois 1 dose et plus",
- "Vaccines 12-59 mois Zéro Dose",
+ "Vaccines 12-59 mois Z\u00e9ro Dose",
"Vaccines 12-59 mois 1 dose et plus",
- "Vaccinés par les Equipes en milieu rural",
- "Vaccinés par les Equipes en milieu urbain",
+ "Vaccin\u00e9s par les Equipes en milieu rural",
+ "Vaccin\u00e9s par les Equipes en milieu urbain",
"GESTION DES FLACONS",
"Recherche active des cas de MEV",
"MAPI",
- "Ver de Guinée"
+ "Ver de Guin\u00e9e"
)
}
diff --git a/R/login_page_ui.R b/R/login_page_ui.R
index ddb53e5..e0402fd 100644
--- a/R/login_page_ui.R
+++ b/R/login_page_ui.R
@@ -32,7 +32,7 @@ ui_authenticate <- function() {
tokens = list(childrenGap = 2),
Text(
variant = "xLarge",
- "Portail de création des masques de saisie des campagnes de vaccination en RDC"
+ "Portail de cr\u00e9ation des masques de saisie des campagnes de vaccination en RDC"
),
Text(
variant = "small",
@@ -47,9 +47,9 @@ ui_authenticate <- function() {
isMultiline = TRUE,
styles = list(text = list(fontSize = 14, lineHeight = "1.55")),
Stack(tokens = list(childrenGap = 6),
- Text("Veuillez saisir votre adresse mail, ensuite cliquez sur “S’authentifier”."),
- Text("Cette application ne collecte ni n’enregistre votre mot de passe."),
- Text("Une fenêtre va s’ouvrir : suivez les instructions pour compléter l’authentification.")
+ Text("Veuillez saisir votre adresse mail, ensuite cliquez sur 'S\u0027authentifier'."),
+ Text("Cette application ne collecte ni n\u0027enregistre votre mot de passe."),
+ Text("Une fen\u00eatre va s\u0027ouvrir : suivez les instructions pour compl\u00e9ter l\u0027authentification.")
)
),
Separator(),
diff --git a/R/rdcAVS-package.R b/R/rdcAVS-package.R
index 5b3f6d5..0128058 100644
--- a/R/rdcAVS-package.R
+++ b/R/rdcAVS-package.R
@@ -9,6 +9,9 @@
#' @import dplyr stringr readr
#' @import googledrive
#' @import shiny shinyFiles
+#' @import ggplot2
+#' @importFrom openxlsx loadWorkbook writeData saveWorkbook
+#' @importFrom scales percent
#' @importFrom cli cli_abort
#' @importFrom cli cli_alert
#' @importFrom cli cli_alert_info
@@ -35,13 +38,14 @@
#' @importFrom progressr progressor
#' @importFrom progressr with_progress
#' @importFrom purrr pwalk
-#' @importFrom purrr walk
+#' @importFrom purrr walk map
#' @importFrom rappdirs user_data_dir
#' @importFrom readr type_convert
#' @importFrom shinyjs disable
#' @importFrom shinyjs show toggle click
#' @importFrom shinyjs useShinyjs
#' @importFrom tidyr pivot_longer
+#' @importFrom utils write.csv
#' @importFrom shiny.fluent Stack MessageBar updateDropdown.shinyInput Dropdown.shinyInput DefaultButton.shinyInput IconButton.shinyInput TooltipHost DatePicker.shinyInput Text TextField.shinyInput Separator PrimaryButton.shinyInput
## usethis namespace: end
@@ -49,7 +53,19 @@ utils::globalVariables(c(
"aires_de_sante", "antenne", "antennes", "debut", "email",
"fin", "level", "local_path", "masque_names",
"mime_type", "path", "population_totale", "province", "provinces", "role",
- "x", "zone_de_sante", "zones_de_sante"
+ "x", "zone_de_sante", "zones_de_sante",
+ "name", "web_view_link", "grid_columns", "max_rows",
+ "aire_de_sante", "jour", "couverture_campaign_cumulative",
+ "couverture_campagne_pct", "cat_0_11", "recovery_0_11_cumulative",
+ "cat_12_23", "recovery_12_23_cumulative", "cat_24_59",
+ "recovery_24_59_cumulative", "cat_rural", "cat_urban",
+ "avg_vax_rural", "avg_vax_urban", "period", "zs_path",
+ "...1", "section", "empty_cells", "filled_cells", "total_cells",
+ "prov", "completeness", "cible_0_59_mois", "vaccination_0_59_mois",
+ "rapports_de_vaccination_recus", "rapports_de_vaccination_attendus",
+ "recovery_0_11", "recovery_12_23", "recovery_24_59",
+ "template_data_geographics", "data_perm", "dir_path",
+ "write.csv"
))
NULL
diff --git a/R/server.R b/R/server.R
index e1118be..fa1e377 100644
--- a/R/server.R
+++ b/R/server.R
@@ -486,23 +486,29 @@ server <- function(input, output, session) {
})
##### Campaign creation button ----
- observeEvent(input$create_campaign, {
+
+ observeEvent(input$create_campaign, {
req(
input$campaign_name,
input$start_date,
input$end_date,
- input$zs_template_url
- )
- req(
+ input$zs_template_url,
input$selected_provinces,
input$selected_ant,
input$selected_zs
)
- showNotification("Veuillez patienter pendant la cr\u00e9ation de la campagne...",
- type = "default")
-
- tryCatch(
+ campaign_name <- input$campaign_name
+ start_date <- input$start_date
+ end_date <- input$end_date
+ template_url <- input$zs_template_url
+ prov_target <- input$selected_provinces
+ ant_target <- input$selected_ant
+ zs_target <- input$selected_zs
+ gdb <- geo_data_reactive()
+
+ withTaskButton(session, "create_campaign", label_busy = "En cours...", {
+ tryCatch(
{
zs_masque_dribble <- googledrive::drive_get(stringr::str_trim(input$zs_template_url))
campagne_folder_url <- drive_init_campaign(
@@ -516,19 +522,21 @@ server <- function(input, output, session) {
zs_masque = zs_masque_dribble
)
- showNotification("Campagne initialis\u00e9e avec succ\u00e8s",
- type = "message")
- output$campagne_folder_url <- renderUI({tagList(a("Lien vers le dossier Campagne", href = campagne_folder_url))})
+ session$sendCustomMessage("taskBtnReady", list(id = "create_campaign_wrapper"))
+ showNotification("Campagne initialis\u00e9e avec succ\u00e8s", type = "message")
+ output$campagne_folder_url <- renderUI({
+ tagList(a("Lien vers le dossier Campagne", href = campagne_folder_url))
+ })
},
error = function(e) {
- showNotification(paste("Erreur - " , "Quelque chose s'est mal pass\u00e9:", e$message),
- type = "error",
- duration = NULL)
-
+ session$sendCustomMessage("taskBtnReady", list(id = "create_campaign_wrapper"))
+ showNotification(paste("Erreur:", e$message), type = "error", duration = NULL)
}
)
+ })
})
+
##### Geographic table editing ----
###### Upload option for geo table ----
@@ -876,7 +884,7 @@ observeEvent(input$download_geo,{
user_email(user$emailAddress)
showNotification(
- paste("Authentifié avec succès en tant que", user_email()),
+ paste("Authentifi\u00e9 avec succ\u00e8s en tant que", user_email()),
type = "message"
)
showNotification("Donn\u00e9es Google Drive collect\u00e9es.",
@@ -957,23 +965,27 @@ observeEvent(input$download_geo,{
###### Display available campaigns ----
output$campaign_drive_picker <- renderUI({
folders <- campaign_drive_folders()
- selectInput(
- "selected_campaign_drive_folder",
- NULL,
- choices = folders$name
+ column(3,
+ shiny.fluent::Dropdown.shinyInput(
+ "selected_campaign_drive_folder",
+ options = map(folders$name, ~ list(key = .x, text = .x)),
+ value = folders$name[1]
+ )
)
- })
+})
###### Display available campaigns in the surveillance tab ----
output$campaign_surveillance <- renderUI({
- shiny.fluent::Dropdown.shinyInput(
+ column(3,
+ shiny.fluent::Dropdown.shinyInput(
inputId = "selected_surveillance_drive_folder",
label = shiny::h6("Campagne"),
options = lapply(
campaign_drive_folders()$name,
function(x) list(key = x, text = x)
- )
-)
+ )
+ )
+ )
})
output$refresh_date <- renderText({
refresh_status()
@@ -1381,8 +1393,12 @@ observeEvent(input$download_geo,{
showNotification("Veuillez patienter pendant que les autorisations sont d\u00e9finies...",
type = "default")
-
-
+
+ withTaskButton(
+ session,
+ inputId = "set_permissions_btn",
+ label_busy = "En cours...",
+ {
folders <- campaign_drive_folders()
files <- drive_files()
@@ -1402,8 +1418,9 @@ observeEvent(input$download_geo,{
files <- googledrive::drive_reveal(files, what = "path")
drive_files(files)
}
-
+
tryCatch(
+
{
set_permissions(
input$selected_campaign_drive_folder,
@@ -1420,7 +1437,10 @@ observeEvent(input$download_geo,{
paste("Erreur - Quelque chose s'est mal pass\u00e9:", e$message),
type = "error")
}
- )
+ )
+ }
+ )
+
})
###### Undo/redo/clear all perm table ----
@@ -1468,9 +1488,16 @@ observeEvent(input$download_geo,{
observeEvent(input$compile_campaign_btn, {
req(input$selected_surveillance_drive_folder)
+ withTaskButton(
+ session,
+ "compile_campaign_btn",
+ label_busy = "En cours...",
+ {
+
surveillance_folder <- campaign_drive_folders() |>
- dplyr::filter(name == input$selected_surveillance_drive_folder)
-
+ dplyr::filter(name == input$selected_surveillance_drive_folder)
+
+
withProgress(
{
compile_start <- Sys.time()
@@ -1487,7 +1514,7 @@ observeEvent(input$download_geo,{
output$campaign_template_url <- renderUI({tagList(a("Lien vers le masque de campagne",
href = national_dribble_url))})
# Obtain completeness information
- incProgress(1/5, message = "Analyse de la qualité des données")
+ incProgress(1/5, message = "Analyse de la qualit\u00e9 des donn\u00e9es")
national_template_dribble <- googledrive::drive_get(national_dribble_url)
surveillance_summary(get_sheet_info(national_template_dribble))
data_quality_info <- surveillance_summary()
@@ -1508,10 +1535,14 @@ observeEvent(input$download_geo,{
strftime(Sys.time(), format = "%d/%m/%Y %R", usetz = TRUE),
"; Compiled in ", round(compile_end, 2), "mins"))
show("refresh_date")
- showNotification("Traitement terminé", type = "message")
+ showNotification("Traitement termin\u00e9", type = "message")
}
)
- })
+
+ }
+
+ )
+})
##### Download current data quality monitoring table ----
@@ -1642,9 +1673,9 @@ observeEvent(input$click_download_campaign_quality_monitoring,{
"Section",
"Cellules Remplies",
"Cellules Totales",
- "Exhaustivité",
- "Jours Depuis la Dernière Modification",
- "Date d'Exécution"
+ "Exhaustivit\u00e9",
+ "Jours Depuis la Derni\u00e8re Modification",
+ "Date d'Ex\u00e9cution"
)
)
)
@@ -1670,11 +1701,11 @@ observeEvent(input$click_download_campaign_quality_monitoring,{
"Rapport Completude (%)",
"Couverture (%)",
"Couverture (Cumulative)",
- "Nb moyen d'enfants vaccinés/équipe (Rural)",
- "Nb moyen d'enfants vaccinés/équipe (Urban)",
- "Récupérations (0-11 mois, cumulative)",
- "Récupérations (12-23 mois, cumulative)",
- "Récupérations (24-59 mois, cumulative)"
+ "Nb moyen d'enfants vaccin\u00e9s/\u00e9quipe (Rural)",
+ "Nb moyen d'enfants vaccin\u00e9s/\u00e9quipe (Urban)",
+ "R\u00e9cup\u00e9rations (0-11 mois, cumulative)",
+ "R\u00e9cup\u00e9rations (12-23 mois, cumulative)",
+ "R\u00e9cup\u00e9rations (24-59 mois, cumulative)"
)
)
)
diff --git a/R/shiny_fluent_task_button.R b/R/shiny_fluent_task_button.R
new file mode 100644
index 0000000..eda8ccf
--- /dev/null
+++ b/R/shiny_fluent_task_button.R
@@ -0,0 +1,228 @@
+#' JS handlers for taskButton busy/ready states
+#' @keywords internal
+#' @export
+
+task_button_js <- "
+(function() {
+ var style = document.createElement('style');
+ style.textContent = '@keyframes task-spin { to { transform: rotate(360deg); } }';
+ document.head.appendChild(style);
+
+ Shiny.addCustomMessageHandler('taskBtnBusy', function(msg) {
+ var wrapper = document.getElementById(msg.id);
+ if (!wrapper) return;
+ wrapper.setAttribute('data-state', 'busy');
+ var fluentBtn = wrapper.querySelector('button');
+ if (fluentBtn) fluentBtn.disabled = true;
+ var fluentLabel = wrapper.querySelector('.ms-Button-label');
+ if (fluentLabel) fluentLabel.textContent = msg.label_busy;
+ var icon = wrapper.querySelector('.ms-Button-icon');
+ if (icon) {
+ icon.dataset.originalContent = icon.innerHTML;
+ icon.innerHTML = '';
+ }
+ });
+
+ Shiny.addCustomMessageHandler('taskBtnReady', function(msg) {
+ var wrapper = document.getElementById(msg.id);
+ if (!wrapper) return;
+ wrapper.setAttribute('data-state', 'ready');
+ var fluentBtn = wrapper.querySelector('button');
+ if (fluentBtn) fluentBtn.disabled = false;
+ var fluentLabel = wrapper.querySelector('.ms-Button-label');
+ var labelReady = wrapper.querySelector('.task-btn-label-ready');
+ if (fluentLabel && labelReady) fluentLabel.textContent = labelReady.textContent;
+ var icon = wrapper.querySelector('.ms-Button-icon');
+ if (icon && icon.dataset.originalContent) {
+ icon.innerHTML = icon.dataset.originalContent;
+ }
+ });
+
+})();
+"
+#' CSS for taskButton spinner animation
+#' @keywords internal
+#' @export
+
+
+task_button_css <- "
+@keyframes task-spin {
+ to { transform: rotate(360deg); }
+}
+
+.task-btn-spinner {
+ display: none;
+ width: 14px;
+ height: 14px;
+ border: 2px solid rgba(255,255,255,0.3);
+ border-top-color: #fff;
+ border-radius: 50%;
+ animation: task-spin 0.8s linear infinite;
+ margin-right: 6px;
+ vertical-align: middle;
+}
+
+[data-state='busy'] .task-btn-spinner { display: inline-block; }
+[data-state='ready'] .task-btn-spinner { display: none; }
+
+ .notification-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 99998;
+ display: none;
+ opacity: 0;
+ transition: opacity 0.3s ease-in-out;
+ }
+
+ .notification-overlay.show {
+ display: block;
+ opacity: 1;
+ }
+
+ #shiny-notification-panel {
+ position: fixed;
+ top: 50% !important;
+ left: 50% !important;
+ right: auto !important;
+ bottom: auto !important;
+ transform: translate(-50%, -50%) !important;
+ width: auto;
+ max-width: 500px;
+ z-index: 99999;
+ }
+
+ @keyframes slideDown {
+ from {
+ opacity: 0;
+ transform: translate(-50%, -60%);
+ }
+ to {
+ opacity: 1;
+ transform: translate(-50%, -50%);
+ }
+ }
+
+ @keyframes pulse {
+ 0%, 100% {
+ transform: scale(1);
+ }
+ 50% {
+ transform: scale(1.05);
+ }
+ }
+
+ .shiny-notification {
+ background-color: white;
+ border: none;
+ border-radius: 12px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
+ padding: 40px 30px 30px 30px;
+ text-align: center;
+ font-size: 16px;
+ color: #333;
+ opacity: 1 !important;
+ min-width: 350px;
+ position: relative;
+ animation: slideDown 0.4s ease-out;
+ }
+
+ .shiny-notification::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 4px;
+ border-radius: 12px 12px 0 0;
+ background: linear-gradient(90deg, #3498db, #2980b9);
+ }
+
+ .shiny-notification-message::before {
+ background: linear-gradient(90deg, #3498db, #2980b9);
+ }
+
+ .shiny-notification-warning::before {
+ background: linear-gradient(90deg, #f39c12, #e67e22);
+ }
+
+ .shiny-notification-error::before {
+ background: linear-gradient(90deg, #e74c3c, #c0392b);
+ }
+
+ .shiny-notification.success-notification::before {
+ background: linear-gradient(90deg, #27ae60, #229954);
+ }
+
+ .shiny-notification-message,
+ .shiny-notification-warning,
+ .shiny-notification-error {
+ background-color: white;
+ border-left: none;
+ }
+
+ .shiny-notification .message-text {
+ color: #555;
+ line-height: 1.6;
+ margin-top: 8px;
+ font-size: 15px;
+ }"
+
+#' Task button wrapper for shiny.fluent
+#'
+#' Wraps a \code{PrimaryButton.shinyInput} with busy/ready state management.
+#'
+#' @param inputId `chr` The input that will be used to access the value.
+#' @param label `chr` The button label.
+#' @param label_busy `chr` Label shown during busy state. Defaults to \code{"Processing..."}
+#' @param ... Additional arguments passed to \code{PrimaryButton.shinyInput}.
+#' @return A \code{tagList} containing the button with CSS and JS handlers.
+#'
+#' @export
+
+taskButton <- function(inputId, label, label_busy = "Processing...", ...) {
+ div(
+ id = paste0(inputId, "_wrapper"),
+ `data-state` = "ready",
+ tags$span(class = "task-btn-label-ready", style = "display:none;", label),
+ tags$span(class = "task-btn-label-busy", style = "display:none;", label_busy),
+ PrimaryButton.shinyInput(
+ inputId = inputId,
+ text = label,
+ ...
+ )
+ )
+}
+
+#' Server-side handler for task button busy/ready states
+#'
+#' Use inside an \code{observeEvent} to toggle a \code{taskButton}
+#' between busy and ready states around a long-running expression.
+#'
+#' @param session Shiny session object.
+#' @param inputId `chr` The input ID of the target \code{taskButton}.
+#' @param expr Expression to evaluate while the button is in busy state.
+#' @param label_busy `chr` Label shown during processing. Defaults to \code{"Processing..."}.
+#'
+#' @return Invisibly returns the result of \code{expr}.
+#'
+#' @export
+
+withTaskButton <- function(session, inputId, expr,
+ label_busy = "Processing...") {
+ session$sendCustomMessage("taskBtnBusy", list(
+ id = paste0(inputId, "_wrapper"),
+ label_busy = label_busy
+ ))
+
+ result <- force(expr)
+
+ session$sendCustomMessage("taskBtnReady", list(
+ id = paste0(inputId, "_wrapper")
+ ))
+
+ invisible(result)
+}
\ No newline at end of file
diff --git a/R/ui_campaign_creation.R b/R/ui_campaign_creation.R
index b6487d9..bd78598 100644
--- a/R/ui_campaign_creation.R
+++ b/R/ui_campaign_creation.R
@@ -1,6 +1,6 @@
ui_campaign_creation <- function() {
nav_panel(
- "Créer Une Campagne",
+ "Cr\u00e9er Une Campagne",
Stack(
tokens = list(childrenGap = 14),
br(),
@@ -24,7 +24,7 @@ ui_campaign_creation <- function() {
style = "flex:1; min-width:220px;",
DatePicker.shinyInput(
"start_date",
- label = "Début",
+ label = "D\u00e9but",
isRequired = TRUE
)
),
@@ -52,7 +52,7 @@ ui_campaign_creation <- function() {
Text(variant = "mediumPlus", "Provinces"),
DefaultButton.shinyInput(
"select_all_prov",
- text = "Tout sélectionner / désélectionner",
+ text = "Tout s\u00e9lectionner / d\u00e9s\u00e9lectionner",
iconProps = list(iconName = "CheckList")
),
div(
@@ -72,7 +72,7 @@ ui_campaign_creation <- function() {
Text(variant = "mediumPlus", "Antennes"),
DefaultButton.shinyInput(
"select_all_ant",
- text = "Tout sélectionner / désélectionner",
+ text = "Tout s\u00e9lectionner / d\u00e9s\u00e9lectionner",
iconProps = list(iconName = "CheckList")
),
div(
@@ -89,10 +89,10 @@ ui_campaign_creation <- function() {
div(
style = "flex:1; min-width:260px;",
Stack(tokens = list(childrenGap = 6),
- Text(variant = "mediumPlus", "Zones de santé"),
+ Text(variant = "mediumPlus", "Zones de sant\u00e9"),
DefaultButton.shinyInput(
"select_all_zs",
- text = "Tout sélectionner / désélectionner",
+ text = "Tout s\u00e9lectionner / d\u00e9s\u00e9lectionner",
iconProps = list(iconName = "CheckList")
),
div(
@@ -117,17 +117,17 @@ ui_campaign_creation <- function() {
style = "flex:2; min-width:300px;",
TextField.shinyInput(
"zs_template_url",
- label = "Adresse URL du modèle de masque",
- placeholder = "https://…",
+ label = "Adresse URL du mod\u00e8le de masque",
+ placeholder = "https://...",
iconProps = list(iconName = "Link")
)
),
div(
style = "flex:1; min-width:240px;",
- PrimaryButton.shinyInput(
+ taskButton(
"create_campaign",
- text = "Créer une campagne",
+ label = "Cr\u00e9er une campagne",
iconProps = list(iconName = "Rocket")
)
)
diff --git a/R/ui_geodatabase.R b/R/ui_geodatabase.R
index fbe8838..0ef2a23 100644
--- a/R/ui_geodatabase.R
+++ b/R/ui_geodatabase.R
@@ -1,7 +1,7 @@
ui_geodatabase <- function() {
nav_panel(
- "Informations Géographiques",br(),
+ "Informations G\u00e9ographiques",br(),
Stack(
tokens = list(childrenGap = 14),
MessageBar(
@@ -22,7 +22,7 @@ ui_geodatabase <- function() {
horizontal = TRUE,
verticalAlign = "center",
tokens = list(childrenGap = 6),
- Text("Vous pouvez télécharger au besoin un template"),
+ Text("Vous pouvez t\u00e9l\u00e9charger au besoin un template"),
actionLink(
"download_template_link",
"ici",
@@ -36,12 +36,12 @@ ui_geodatabase <- function() {
tokens = list(childrenGap = 10),
Text(variant = "large", "Charger un fichier CSV"),
- Text("Veuillez charger un fichier CSV avec les informations géographiques."),
+ Text("Veuillez charger un fichier CSV avec les informations g\u00e9ographiques."),
fileInput(
"upload_geo",
label = NULL,
accept = ".csv",
- buttonLabel = "Parcourir…",
+ buttonLabel = "Parcourir...",
placeholder = "Aucun fichier choisi"
)
)
@@ -55,12 +55,12 @@ ui_geodatabase <- function() {
verticalAlign = "center",
tokens = list(childrenGap = 8),
- icon_btn("delete_row", "Delete", "Supprimer la sélection", type = "danger"),
+ icon_btn("delete_row", "Delete", "Supprimer la s\u00e9lection", type = "danger"),
icon_btn("undo_geo", "Undo", "Annuler", type = "primary"),
- icon_btn("redo_geo", "Redo", "Rétablir", type = "primary"),
+ icon_btn("redo_geo", "Redo", "R\u00e9tablir", type = "primary"),
icon_btn("clear_geo", "DeleteRows", "Tout effacer", type = "danger"),
- icon_btn("download_geo", "Download", "Télécharger", type = "success"),
- icon_btn("add_row_question", "Add", "Ajouter une entrée", type = "success")
+ icon_btn("download_geo", "Download", "T\u00e9l\u00e9charger", type = "success"),
+ icon_btn("add_row_question", "Add", "Ajouter une entr\u00e9e", type = "success")
),
div(
id = "add_new_row",
@@ -76,7 +76,7 @@ ui_geodatabase <- function() {
Stack(
tokens = list(childrenGap = 12),
- Text(variant = "mediumPlus", "Nouvelle entrée"),
+ Text(variant = "mediumPlus", "Nouvelle entr\u00e9e"),
Stack(
horizontal = TRUE,
@@ -103,16 +103,16 @@ ui_geodatabase <- function() {
style = "flex:1; min-width:240px;",
TextField.shinyInput(
"new_zs",
- label = "Zones de santé",
- placeholder = "Veuillez entrer le nom de la Zones de santé ici"
+ label = "Zones de sant\u00e9",
+ placeholder = "Veuillez entrer le nom de la Zones de sant\u00e9 ici"
)
),
div(
style = "flex:1; min-width:240px;",
TextField.shinyInput(
"new_as",
- label = "Aires de santé",
- placeholder = "Veuillez entrer le nom de l'aire de santé ici"
+ label = "Aires de sant\u00e9",
+ placeholder = "Veuillez entrer le nom de l'aire de sant\u00e9 ici"
)
),
div(
diff --git a/R/ui_monitoring.R b/R/ui_monitoring.R
index afc372b..cb89900 100644
--- a/R/ui_monitoring.R
+++ b/R/ui_monitoring.R
@@ -134,9 +134,9 @@ ui_monitoring <- function() {
style = list(fontWeight = "600", color = "#201f1e")
),
uiOutput("campaign_surveillance"),
- shiny.fluent::PrimaryButton.shinyInput(
- inputId = "compile_campaign_btn",
- text = "Compiler des masques",
+ taskButton(
+ "compile_campaign_btn",
+ label = "Compiler des masques",
iconProps = list(iconName = "CompactDisc")
),
shiny.fluent::Stack(
@@ -164,19 +164,19 @@ ui_monitoring <- function() {
),
tags$h3(
class = "fluent-accordion-title",
- "Informations sur la complétude"
+ "Informations sur la compl\u00e9tude"
)
),
tags$span(
class = "fluent-accordion-chevron",
- "▼"
+ "\u25BC"
)
),
tags$div(
class = "fluent-accordion-content",
tags$div(
class = "fluent-download-btn",
- icon_btn("click_download_data_quality_monitoring", "Download", "Télécharger", type = "success")
+ icon_btn("click_download_data_quality_monitoring", "Download", "T\u00e9l\u00e9charger", type = "success")
),
DT::DTOutput("campaign_info_table")
)
@@ -203,7 +203,7 @@ ui_monitoring <- function() {
tags$span(
class = "fluent-accordion-chevron",
- "▼"
+ "\u25BC"
)
),
@@ -211,7 +211,7 @@ ui_monitoring <- function() {
class = "fluent-accordion-content",
tags$div(
class = "fluent-download-btn",
- icon_btn("click_download_campaign_quality_monitoring", "Download", "Télécharger", type = "success")
+ icon_btn("click_download_campaign_quality_monitoring", "Download", "T\u00e9l\u00e9charger", type = "success")
),
DT::DTOutput("campaign_progress_table")
)
@@ -238,7 +238,7 @@ ui_monitoring <- function() {
tags$span(
class = "fluent-accordion-chevron",
- "▼"
+ "\u25BC"
)
),
@@ -248,17 +248,17 @@ ui_monitoring <- function() {
class = "selector-group",
shiny.fluent::Dropdown.shinyInput(
inputId = "prov_selector_campaign_completeness",
- label = "Sélectionner une province:",
+ label = "S\u00e9lectionner une province:",
options = list()
),
shiny.fluent::Dropdown.shinyInput(
inputId = "ant_selector_campaign_completeness",
- label = "Sélectionner une antenne:",
+ label = "S\u00e9lectionner une antenne:",
options = list()
),
shiny.fluent::Dropdown.shinyInput(
inputId = "zs_selector_campaign_completeness",
- label = "Sélectionner une zone de sante:",
+ label = "S\u00e9lectionner une zone de sante:",
options = list()
)
),
@@ -274,11 +274,11 @@ ui_monitoring <- function() {
plotOutput("campaign_completeness_plot_daily", height = "700px")
),
shiny.fluent::PivotItem(
- headerText = "Nb moyen d'enfants vaccinés/équipe",
+ headerText = "Nb moyen d'enfants vaccin\u00e9s/\u00e9quipe",
plotOutput("campaign_urban_rural_plot", height = "700px")
),
shiny.fluent::PivotItem(
- headerText = "Récupérations",
+ headerText = "R\u00e9cup\u00e9rations",
plotOutput("campaign_recovery_plot", height = "700px")
)
)
diff --git a/R/ui_perm_table.R b/R/ui_perm_table.R
index 5b894c4..c8eb2a3 100644
--- a/R/ui_perm_table.R
+++ b/R/ui_perm_table.R
@@ -1,12 +1,12 @@
ui_perm_table <- function() {
nav_panel(
- "Gérer les Autorisations",br(),
+ "G\u00e9rer les Autorisations",br(),
Stack(
tokens = list(childrenGap = 14),
- Text(variant = "xLarge", "Gérer les Niveaux d'Autorisation"),
+ Text(variant = "xLarge", "G\u00e9rer les Niveaux d'Autorisation"),
fluent_card(
Stack(
@@ -18,8 +18,8 @@ ui_perm_table <- function() {
"upload_permissions",
label = NULL,
accept = ".csv",
- buttonLabel = "Parcourir…",
- placeholder = "Aucun fichier sélectionné"
+ buttonLabel = "Parcourir...",
+ placeholder = "Aucun fichier s\u00e9lectionn\u00e9"
),
MessageBar(
@@ -27,15 +27,15 @@ ui_perm_table <- function() {
isMultiline = TRUE,
styles = list(text = list(fontSize = 14, lineHeight = "1.55")),
Stack(tokens = list(childrenGap = 6),
- Text('Si le niveau est "national", les zones géographiques sont facultatives.'),
- Text("Une zone géographique valide doit être incluse dans les autres niveaux."),
- Text('Exemple : si le niveau est "zone de santé", incluez une valeur pour "province", "antenne" et "zone de santé".')
+ Text('Si le niveau est "national", les zones g\u00e9ographiques sont facultatives.'),
+ Text("Une zone g\u00e9ographique valide doit \u00eatre incluse dans les autres niveaux."),
+ Text('Exemple : si le niveau est "zone de sant\u00e9", incluez une valeur pour "province", "antenne" et "zone de sant\u00e9".')
),br(),
Stack(
horizontal = TRUE,
verticalAlign = "center",
tokens = list(childrenGap = 6),
- Text("Vous pouvez télécharger au besoin un template"),
+ Text("Vous pouvez t\u00e9l\u00e9charger au besoin un template"),
actionLink(
"download_template_link_permissions",
"ici",
@@ -58,12 +58,12 @@ ui_perm_table <- function() {
verticalAlign = "center",
tokens = list(childrenGap = 8),
- icon_btn("delete_permission", "Delete", "Supprimer la sélection", type = "danger"),
+ icon_btn("delete_permission", "Delete", "Supprimer la s\u00e9lection", type = "danger"),
icon_btn("undo_perm", "Undo", "Annuler", type = "primary"),
- icon_btn("redo_perm", "Redo", "Rétablir", type = "primary"),
+ icon_btn("redo_perm", "Redo", "R\u00e9tablir", type = "primary"),
icon_btn("clear_perm", "DeleteRows", "Tout effacer", type = "danger"),
- icon_btn("press_download_permissions", "Download", "Télécharger", type = "success"),
- icon_btn("add_row_question_perm", "Add", "Ajouter une entrée", type = "success")
+ icon_btn("press_download_permissions", "Download", "T\u00e9l\u00e9charger", type = "success"),
+ icon_btn("add_row_question_perm", "Add", "Ajouter une entr\u00e9e", type = "success")
),
br(),
div(
@@ -91,11 +91,11 @@ ui_perm_table <- function() {
),
div(
style = "flex:1; min-width:240px;",
- TextField.shinyInput("perm_phone", label = "Phone", placeholder = "+243…")
+ TextField.shinyInput("perm_phone", label = "Phone", placeholder = "+243...")
),
div(
style = "flex:2; min-width:280px;",
- TextField.shinyInput("perm_notes", label = "Notes", placeholder = "Affiliation, Poste, etc…")
+ TextField.shinyInput("perm_notes", label = "Notes", placeholder = "Affiliation, Poste, etc...")
)
),
@@ -144,7 +144,7 @@ ui_perm_table <- function() {
Dropdown.shinyInput(
"perm_province",
label = "Province",
- placeholder = "Sélectionnez une province",
+ placeholder = "S\u00e9lectionnez une province",
options = list(),
required = TRUE
)
@@ -157,7 +157,7 @@ ui_perm_table <- function() {
Dropdown.shinyInput(
"perm_antenne",
label = "Antenne",
- placeholder = "Sélectionnez une antenne",
+ placeholder = "S\u00e9lectionnez une antenne",
required = TRUE,
options = list()
)
@@ -170,8 +170,8 @@ ui_perm_table <- function() {
style = "flex:1; min-width:240px;",
Dropdown.shinyInput(
"perm_zs",
- label = "Zones de Santé",
- placeholder = "Sélectionnez une zone de santé",
+ label = "Zones de Sant\u00e9",
+ placeholder = "S\u00e9lectionnez une zone de sant\u00e9",
options = list(),
required = TRUE
)
@@ -185,7 +185,7 @@ ui_perm_table <- function() {
PrimaryButton.shinyInput(
"add_permission",
- text = "Ajouter une entrée",
+ text = "Ajouter une entr\u00e9e",
iconProps = list(iconName = "Add")
)
)
@@ -203,21 +203,17 @@ ui_perm_table <- function() {
Stack(
tokens = list(childrenGap = 12),
- Text(variant = "xLarge", "Sélection de Campagne"),
-
+ Text(variant = "xLarge", "S\u00e9lection de Campagne"),
uiOutput("campaign_drive_picker"),
-
Stack(
horizontal = TRUE,
wrap = TRUE,
tokens = list(childrenGap = 10),
-
- PrimaryButton.shinyInput(
+ taskButton(
"set_permissions_btn",
- text = "Définir l'autorisation",
+ label = "D\u00e9finir l'autorisation",
iconProps = list(iconName = "Permissions")
),
-
DefaultButton.shinyInput(
"refresh_drive",
text = "Actualiser",
diff --git a/man/campagneApp.Rd b/man/campagneApp.Rd
index 4348e8c..962e169 100644
--- a/man/campagneApp.Rd
+++ b/man/campagneApp.Rd
@@ -14,7 +14,7 @@ The rdcAVS application deploys to the web browser locally. It consists of three
tabs that creates a campaign, add geographic information, and set Google Drive
permissions. Creating a campaign outputs a folder prefixed with "CAMPAGNE_",
which contains a hierarchical structure, going from the largest geographic unit (Province)
-to the lowest geographic unit (Zone de Sant\u00e9). Within the Zone de Sant\u00e9 folder, contains
+to the lowest geographic unit (Zone de Santé). Within the Zone de Santé folder, contains
a template file.
}
\details{
diff --git a/man/data_perm.Rd b/man/data_perm.Rd
new file mode 100644
index 0000000..8b50948
--- /dev/null
+++ b/man/data_perm.Rd
@@ -0,0 +1,35 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/data.R
+\docType{data}
+\name{data_perm}
+\alias{data_perm}
+\title{Template for user permissions data}
+\format{
+A tibble with 9 columns
+\describe{
+\item{name}{Full name of the user (character)}
+\item{phone}{Phone number of the user (numeric)}
+\item{notes}{Optional notes about the user (character)}
+\item{email}{Email address of the user (character)}
+\item{level}{Access level of the user, e.g. \code{"national"} (character)}
+\item{role}{Role of the user, e.g. \code{"writer"} (character)}
+\item{province}{Province assigned to the user, \code{NA} if not applicable (logical)}
+\item{antenne}{Antenne assigned to the user, \code{NA} if not applicable (logical)}
+\item{zone_de_sante}{Health zone assigned to the user, \code{NA} if not applicable (logical)}
+}
+}
+\source{
+Internal rdcAVS package template
+}
+\usage{
+data_perm
+}
+\description{
+A template dataset showing the expected structure and columns for the user
+permissions file. Users can download this template to prepare their own
+permissions file before importing it into the application.
+}
+\note{
+The expected file format is \code{.csv}.
+}
+\keyword{datasets}
diff --git a/man/taskButton.Rd b/man/taskButton.Rd
new file mode 100644
index 0000000..a5ccf78
--- /dev/null
+++ b/man/taskButton.Rd
@@ -0,0 +1,23 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/shiny_fluent_task_button.R
+\name{taskButton}
+\alias{taskButton}
+\title{Task button wrapper for shiny.fluent}
+\usage{
+taskButton(inputId, label, label_busy = "Processing...", ...)
+}
+\arguments{
+\item{inputId}{\code{chr} The input that will be used to access the value.}
+
+\item{label}{\code{chr} The button label.}
+
+\item{label_busy}{\code{chr} Label shown during busy state. Defaults to \code{"Processing..."}}
+
+\item{...}{Additional arguments passed to \code{PrimaryButton.shinyInput}.}
+}
+\value{
+A \code{tagList} containing the button with CSS and JS handlers.
+}
+\description{
+Wraps a \code{PrimaryButton.shinyInput} with busy/ready state management.
+}
diff --git a/man/task_button_css.Rd b/man/task_button_css.Rd
new file mode 100644
index 0000000..49a11fd
--- /dev/null
+++ b/man/task_button_css.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/shiny_fluent_task_button.R
+\docType{data}
+\name{task_button_css}
+\alias{task_button_css}
+\title{CSS for taskButton spinner animation}
+\format{
+An object of class \code{character} of length 1.
+}
+\usage{
+task_button_css
+}
+\description{
+CSS for taskButton spinner animation
+}
+\keyword{internal}
diff --git a/man/task_button_js.Rd b/man/task_button_js.Rd
new file mode 100644
index 0000000..7d68077
--- /dev/null
+++ b/man/task_button_js.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/shiny_fluent_task_button.R
+\docType{data}
+\name{task_button_js}
+\alias{task_button_js}
+\title{JS handlers for taskButton busy/ready states}
+\format{
+An object of class \code{character} of length 1.
+}
+\usage{
+task_button_js
+}
+\description{
+JS handlers for taskButton busy/ready states
+}
+\keyword{internal}
diff --git a/man/template_data_geographics.Rd b/man/template_data_geographics.Rd
new file mode 100644
index 0000000..110c18f
--- /dev/null
+++ b/man/template_data_geographics.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/data.R
+\docType{data}
+\name{template_data_geographics}
+\alias{template_data_geographics}
+\title{Template for geographic data}
+\format{
+A tibble with 5 columns:
+\describe{
+\item{provinces}{Name of the province (character)}
+\item{antennes}{Name of the antenne (character)}
+\item{zones_de_sante}{Name of the health zone (character)}
+\item{aires_de_sante}{Name of the health area (character)}
+\item{population_totale}{Total population of the health area (numeric)}
+}
+}
+\source{
+Internal rdcAVS package template
+}
+\usage{
+template_data_geographics
+}
+\description{
+A template dataset showing the expected structure and columns for the
+geographic data file. Users can download this template to prepare their
+own geographic file before importing it into the application.
+}
+\note{
+The expected file format is \code{.csv}.
+}
+\keyword{datasets}
diff --git a/man/withTaskButton.Rd b/man/withTaskButton.Rd
new file mode 100644
index 0000000..a2ec17c
--- /dev/null
+++ b/man/withTaskButton.Rd
@@ -0,0 +1,24 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/shiny_fluent_task_button.R
+\name{withTaskButton}
+\alias{withTaskButton}
+\title{Server-side handler for task button busy/ready states}
+\usage{
+withTaskButton(session, inputId, expr, label_busy = "Processing...")
+}
+\arguments{
+\item{session}{Shiny session object.}
+
+\item{inputId}{\code{chr} The input ID of the target \code{taskButton}.}
+
+\item{expr}{Expression to evaluate while the button is in busy state.}
+
+\item{label_busy}{\code{chr} Label shown during processing. Defaults to \code{"Processing..."}.}
+}
+\value{
+Invisibly returns the result of \code{expr}.
+}
+\description{
+Use inside an \code{observeEvent} to toggle a \code{taskButton}
+between busy and ready states around a long-running expression.
+}
diff --git a/renv.lock b/renv.lock
index d9bdefc..4ae5298 100644
--- a/renv.lock
+++ b/renv.lock
@@ -15,7 +15,7 @@
"Source": "Repository",
"Type": "Package",
"Title": "A Wrapper of the JavaScript Library 'DataTables'",
- "Authors@R": "c( person(\"Yihui\", \"Xie\", role = \"aut\"), person(\"Joe\", \"Cheng\", , \"joe@posit.co\", role = \"aut\"), person(\"Xianying\", \"Tan\", role = \"aut\"), person(\"Garrick\", \"Aden-Buie\", , \"garrick@posit.co\", role = c(\"aut\", \"cre\"), comment = c(ORCID = \"0000-0002-7111-0077\")), person(\"JJ\", \"Allaire\", role = \"ctb\"), person(\"Maximilian\", \"Girlich\", role = \"ctb\"), person(\"Greg\", \"Freedman Ellis\", role = \"ctb\"), person(\"Johannes\", \"Rauh\", role = \"ctb\"), person(\"SpryMedia Limited\", role = c(\"ctb\", \"cph\"), comment = \"DataTables in htmlwidgets/lib\"), person(\"Brian\", \"Reavis\", role = c(\"ctb\", \"cph\"), comment = \"selectize.js in htmlwidgets/lib\"), person(\"Leon\", \"Gersen\", role = c(\"ctb\", \"cph\"), comment = \"noUiSlider in htmlwidgets/lib\"), person(\"Bartek\", \"Szopka\", role = c(\"ctb\", \"cph\"), comment = \"jquery.highlight.js in htmlwidgets/lib\"), person(\"Alex\", \"Pickering\", role = \"ctb\"), person(\"William\", \"Holmes\", role = \"ctb\"), person(\"Mikko\", \"Marttila\", role = \"ctb\"), person(\"Andres\", \"Quintero\", role = \"ctb\"), person(\"Stéphane\", \"Laurent\", role = \"ctb\"), person(\"Posit Software, PBC\", role = c(\"cph\", \"fnd\")) )",
+ "Authors@R": "c( person(\"Yihui\", \"Xie\", role = \"aut\"), person(\"Joe\", \"Cheng\", , \"joe@posit.co\", role = \"aut\"), person(\"Xianying\", \"Tan\", role = \"aut\"), person(\"Garrick\", \"Aden-Buie\", , \"garrick@posit.co\", role = c(\"aut\", \"cre\"), comment = c(ORCID = \"0000-0002-7111-0077\")), person(\"JJ\", \"Allaire\", role = \"ctb\"), person(\"Maximilian\", \"Girlich\", role = \"ctb\"), person(\"Greg\", \"Freedman Ellis\", role = \"ctb\"), person(\"Johannes\", \"Rauh\", role = \"ctb\"), person(\"SpryMedia Limited\", role = c(\"ctb\", \"cph\"), comment = \"DataTables in htmlwidgets/lib\"), person(\"Brian\", \"Reavis\", role = c(\"ctb\", \"cph\"), comment = \"selectize.js in htmlwidgets/lib\"), person(\"Leon\", \"Gersen\", role = c(\"ctb\", \"cph\"), comment = \"noUiSlider in htmlwidgets/lib\"), person(\"Bartek\", \"Szopka\", role = c(\"ctb\", \"cph\"), comment = \"jquery.highlight.js in htmlwidgets/lib\"), person(\"Alex\", \"Pickering\", role = \"ctb\"), person(\"William\", \"Holmes\", role = \"ctb\"), person(\"Mikko\", \"Marttila\", role = \"ctb\"), person(\"Andres\", \"Quintero\", role = \"ctb\"), person(\"St\u00e9phane\", \"Laurent\", role = \"ctb\"), person(\"Posit Software, PBC\", role = c(\"cph\", \"fnd\")) )",
"Description": "Data objects in R can be rendered as HTML tables using the JavaScript library 'DataTables' (typically via R Markdown or Shiny). The 'DataTables' library has been included in this R package. The package name 'DT' is an abbreviation of 'DataTables'.",
"License": "MIT + file LICENSE",
"URL": "https://github.com/rstudio/DT",
@@ -43,7 +43,7 @@
"Encoding": "UTF-8",
"RoxygenNote": "7.3.2",
"NeedsCompilation": "no",
- "Author": "Yihui Xie [aut], Joe Cheng [aut], Xianying Tan [aut], Garrick Aden-Buie [aut, cre] (ORCID: ), JJ Allaire [ctb], Maximilian Girlich [ctb], Greg Freedman Ellis [ctb], Johannes Rauh [ctb], SpryMedia Limited [ctb, cph] (DataTables in htmlwidgets/lib), Brian Reavis [ctb, cph] (selectize.js in htmlwidgets/lib), Leon Gersen [ctb, cph] (noUiSlider in htmlwidgets/lib), Bartek Szopka [ctb, cph] (jquery.highlight.js in htmlwidgets/lib), Alex Pickering [ctb], William Holmes [ctb], Mikko Marttila [ctb], Andres Quintero [ctb], Stéphane Laurent [ctb], Posit Software, PBC [cph, fnd]",
+ "Author": "Yihui Xie [aut], Joe Cheng [aut], Xianying Tan [aut], Garrick Aden-Buie [aut, cre] (ORCID: ), JJ Allaire [ctb], Maximilian Girlich [ctb], Greg Freedman Ellis [ctb], Johannes Rauh [ctb], SpryMedia Limited [ctb, cph] (DataTables in htmlwidgets/lib), Brian Reavis [ctb, cph] (selectize.js in htmlwidgets/lib), Leon Gersen [ctb, cph] (noUiSlider in htmlwidgets/lib), Bartek Szopka [ctb, cph] (jquery.highlight.js in htmlwidgets/lib), Alex Pickering [ctb], William Holmes [ctb], Mikko Marttila [ctb], Andres Quintero [ctb], St\u00e9phane Laurent [ctb], Posit Software, PBC [cph, fnd]",
"Maintainer": "Garrick Aden-Buie ",
"Repository": "RSPM"
},
@@ -2181,7 +2181,7 @@
"Source": "Repository",
"Type": "Package",
"Title": "Scientific Journal and Sci-Fi Themed Color Palettes for 'ggplot2'",
- "Authors@R": "c( person(\"Nan\", \"Xiao\", email = \"me@nanx.me\", role = c(\"aut\", \"cre\", \"cph\"), comment = c(ORCID = \"0000-0002-0250-5673\")), person(\"Joshua\", \"Cook\", email = \"joshuacook0023@gmail.com\", role = \"ctb\"), person(\"Clara\", \"Jégousse\", email = \"cat3@hi.is\", role = \"ctb\"), person(\"Hui\", \"Chen\", email = \"huichen@zju.edu.cn\", role = \"ctb\"), person(\"Miaozhu\", \"Li\", email = \"miaozhu.li@duke.edu\", role = \"ctb\"), person(\"iTerm2-Color-Schemes contributors\", role = c(\"ctb\", \"cph\"), comment = \"iTerm2-Color-Schemes project\"), person(\"Winston\", \"Chang\", role = c(\"ctb\", \"cph\"), comment = \"staticimports.R\") )",
+ "Authors@R": "c( person(\"Nan\", \"Xiao\", email = \"me@nanx.me\", role = c(\"aut\", \"cre\", \"cph\"), comment = c(ORCID = \"0000-0002-0250-5673\")), person(\"Joshua\", \"Cook\", email = \"joshuacook0023@gmail.com\", role = \"ctb\"), person(\"Clara\", \"J\u00e9gousse\", email = \"cat3@hi.is\", role = \"ctb\"), person(\"Hui\", \"Chen\", email = \"huichen@zju.edu.cn\", role = \"ctb\"), person(\"Miaozhu\", \"Li\", email = \"miaozhu.li@duke.edu\", role = \"ctb\"), person(\"iTerm2-Color-Schemes contributors\", role = c(\"ctb\", \"cph\"), comment = \"iTerm2-Color-Schemes project\"), person(\"Winston\", \"Chang\", role = c(\"ctb\", \"cph\"), comment = \"staticimports.R\") )",
"Maintainer": "Nan Xiao ",
"Description": "A collection of 'ggplot2' color palettes inspired by plots in scientific journals, data visualization libraries, science fiction movies, and TV shows.",
"License": "GPL (>= 3)",
@@ -2206,7 +2206,7 @@
"Encoding": "UTF-8",
"RoxygenNote": "7.3.3",
"NeedsCompilation": "no",
- "Author": "Nan Xiao [aut, cre, cph] (ORCID: ), Joshua Cook [ctb], Clara Jégousse [ctb], Hui Chen [ctb], Miaozhu Li [ctb], iTerm2-Color-Schemes contributors [ctb, cph] (iTerm2-Color-Schemes project), Winston Chang [ctb, cph] (staticimports.R)",
+ "Author": "Nan Xiao [aut, cre, cph] (ORCID: ), Joshua Cook [ctb], Clara J\u00e9gousse [ctb], Hui Chen [ctb], Miaozhu Li [ctb], iTerm2-Color-Schemes contributors [ctb, cph] (iTerm2-Color-Schemes project), Winston Chang [ctb, cph] (staticimports.R)",
"Repository": "RSPM"
},
"ggsignif": {
@@ -3529,7 +3529,7 @@
"Date": "2025-03-31",
"Priority": "recommended",
"Title": "Linear and Nonlinear Mixed Effects Models",
- "Authors@R": "c(person(\"José\", \"Pinheiro\", role = \"aut\", comment = \"S version\"), person(\"Douglas\", \"Bates\", role = \"aut\", comment = \"up to 2007\"), person(\"Saikat\", \"DebRoy\", role = \"ctb\", comment = \"up to 2002\"), person(\"Deepayan\", \"Sarkar\", role = \"ctb\", comment = \"up to 2005\"), person(\"EISPACK authors\", role = \"ctb\", comment = \"src/rs.f\"), person(\"Siem\", \"Heisterkamp\", role = \"ctb\", comment = \"Author fixed sigma\"), person(\"Bert\", \"Van Willigen\",role = \"ctb\", comment = \"Programmer fixed sigma\"), person(\"Johannes\", \"Ranke\", role = \"ctb\", comment = \"varConstProp()\"), person(\"R Core Team\", email = \"R-core@R-project.org\", role = c(\"aut\", \"cre\"), comment = c(ROR = \"02zz1nj61\")))",
+ "Authors@R": "c(person(\"Jos\u00e9\", \"Pinheiro\", role = \"aut\", comment = \"S version\"), person(\"Douglas\", \"Bates\", role = \"aut\", comment = \"up to 2007\"), person(\"Saikat\", \"DebRoy\", role = \"ctb\", comment = \"up to 2002\"), person(\"Deepayan\", \"Sarkar\", role = \"ctb\", comment = \"up to 2005\"), person(\"EISPACK authors\", role = \"ctb\", comment = \"src/rs.f\"), person(\"Siem\", \"Heisterkamp\", role = \"ctb\", comment = \"Author fixed sigma\"), person(\"Bert\", \"Van Willigen\",role = \"ctb\", comment = \"Programmer fixed sigma\"), person(\"Johannes\", \"Ranke\", role = \"ctb\", comment = \"varConstProp()\"), person(\"R Core Team\", email = \"R-core@R-project.org\", role = c(\"aut\", \"cre\"), comment = c(ROR = \"02zz1nj61\")))",
"Contact": "see 'MailingList'",
"Description": "Fit and compare Gaussian linear and nonlinear mixed-effects models.",
"Depends": [
@@ -3552,7 +3552,7 @@
"MailingList": "R-help@r-project.org",
"URL": "https://svn.r-project.org/R-packages/trunk/nlme/",
"NeedsCompilation": "yes",
- "Author": "José Pinheiro [aut] (S version), Douglas Bates [aut] (up to 2007), Saikat DebRoy [ctb] (up to 2002), Deepayan Sarkar [ctb] (up to 2005), EISPACK authors [ctb] (src/rs.f), Siem Heisterkamp [ctb] (Author fixed sigma), Bert Van Willigen [ctb] (Programmer fixed sigma), Johannes Ranke [ctb] (varConstProp()), R Core Team [aut, cre] (02zz1nj61)",
+ "Author": "Jos\u00e9 Pinheiro [aut] (S version), Douglas Bates [aut] (up to 2007), Saikat DebRoy [ctb] (up to 2002), Deepayan Sarkar [ctb] (up to 2005), EISPACK authors [ctb] (src/rs.f), Siem Heisterkamp [ctb] (Author fixed sigma), Bert Van Willigen [ctb] (Programmer fixed sigma), Johannes Ranke [ctb] (varConstProp()), R Core Team [aut, cre] (02zz1nj61)",
"Maintainer": "R Core Team ",
"Repository": "CRAN"
},
@@ -5754,7 +5754,7 @@
"Type": "Package",
"Title": "Colorblind-Friendly Color Maps (Lite Version)",
"Date": "2023-05-02",
- "Authors@R": "c( person(\"Simon\", \"Garnier\", email = \"garnier@njit.edu\", role = c(\"aut\", \"cre\")), person(\"Noam\", \"Ross\", email = \"noam.ross@gmail.com\", role = c(\"ctb\", \"cph\")), person(\"Bob\", \"Rudis\", email = \"bob@rud.is\", role = c(\"ctb\", \"cph\")), person(\"Marco\", \"Sciaini\", email = \"sciaini.marco@gmail.com\", role = c(\"ctb\", \"cph\")), person(\"Antônio Pedro\", \"Camargo\", role = c(\"ctb\", \"cph\")), person(\"Cédric\", \"Scherer\", email = \"scherer@izw-berlin.de\", role = c(\"ctb\", \"cph\")) )",
+ "Authors@R": "c( person(\"Simon\", \"Garnier\", email = \"garnier@njit.edu\", role = c(\"aut\", \"cre\")), person(\"Noam\", \"Ross\", email = \"noam.ross@gmail.com\", role = c(\"ctb\", \"cph\")), person(\"Bob\", \"Rudis\", email = \"bob@rud.is\", role = c(\"ctb\", \"cph\")), person(\"Marco\", \"Sciaini\", email = \"sciaini.marco@gmail.com\", role = c(\"ctb\", \"cph\")), person(\"Antônio Pedro\", \"Camargo\", role = c(\"ctb\", \"cph\")), person(\"C\u00e9dric\", \"Scherer\", email = \"scherer@izw-berlin.de\", role = c(\"ctb\", \"cph\")) )",
"Maintainer": "Simon Garnier ",
"Description": "Color maps designed to improve graph readability for readers with common forms of color blindness and/or color vision deficiency. The color maps are also perceptually-uniform, both in regular form and also when converted to black-and-white for printing. This is the 'lite' version of the 'viridis' package that also contains 'ggplot2' bindings for discrete and continuous color and fill scales and can be found at .",
"License": "MIT + file LICENSE",
@@ -5772,7 +5772,7 @@
"BugReports": "https://github.com/sjmgarnier/viridisLite/issues/",
"RoxygenNote": "7.2.3",
"NeedsCompilation": "no",
- "Author": "Simon Garnier [aut, cre], Noam Ross [ctb, cph], Bob Rudis [ctb, cph], Marco Sciaini [ctb, cph], Antônio Pedro Camargo [ctb, cph], Cédric Scherer [ctb, cph]",
+ "Author": "Simon Garnier [aut, cre], Noam Ross [ctb, cph], Bob Rudis [ctb, cph], Marco Sciaini [ctb, cph], Antônio Pedro Camargo [ctb, cph], C\u00e9dric Scherer [ctb, cph]",
"Repository": "RSPM"
},
"vroom": {