968 lines
37 KiB
R
968 lines
37 KiB
R
# Präambel ####
|
||
|
||
AKZENT_FARBE = "#8B2635"
|
||
|
||
PFAD_DOWNLOAD_SKRIPT = "../API/get_data_vds33.R" # liefert: daten_vds33
|
||
PFAD_PSEUDONYM_SKRIPT = "../get_pseudo.R" # liefert: pseudo
|
||
|
||
VDS33_DISCLAIMER = paste0(
|
||
"Dies ist ein ipsatives Werteprofil ohne Normwerte und ohne klinischen Cutoff. ",
|
||
"Die Auswertung dient als Hilfsmittel für klinisches Fachpersonal und ersetzt keine ",
|
||
"eigenständige fachliche Einordnung. Die Interpretation obliegt der behandelnden Person."
|
||
)
|
||
|
||
# Farbskala fuer die Antwortstufen-Badges der 14 Items (Block C), analog zur
|
||
# gruen -> dunkelrot-Verlaufslogik in pg13r/app.R.
|
||
VDS33_STUFE_BADGE_FARBEN = c("0" = "#4CAF50", "1" = "#F48FB1", "2" = "#EF5350", "3" = "#B71C1C")
|
||
VDS33_STUFE_BADGE_TEXT_FARBEN = c("0" = "white", "1" = "#333333", "2" = "white", "3" = "white")
|
||
|
||
library(shiny)
|
||
library(dplyr)
|
||
library(ggplot2)
|
||
library(haven)
|
||
library(officer)
|
||
|
||
|
||
# Infrastruktur ####
|
||
|
||
APP_VERZEICHNIS = normalizePath(getwd())
|
||
|
||
absPath = function(pfad) {
|
||
if (grepl("^([A-Za-z]:[/\\\\]|/)", pfad)) return(pfad)
|
||
file.path(APP_VERZEICHNIS, pfad)
|
||
}
|
||
|
||
PFAD_DOWNLOAD_SKRIPT = normalizePath(absPath(PFAD_DOWNLOAD_SKRIPT), mustWork = FALSE)
|
||
PFAD_PSEUDONYM_SKRIPT = normalizePath(absPath(PFAD_PSEUDONYM_SKRIPT), mustWork = FALSE)
|
||
|
||
|
||
# Helper ####
|
||
|
||
# Sucht den Rohwert sowohl auf der Werte- als auch auf der Namen-Seite des
|
||
# labels-Attributs (formr exportiert je nach Feldtyp mal die uebliche Richtung
|
||
# NAME = Klartext / WERT = Code, mal vertauscht) und liefert den jeweils
|
||
# GEGENUEBERLIEGENDEN Text zurueck - Ansatz uebernommen aus vds32/app.R.
|
||
vds33_match_in_labels = function(labs, roh) {
|
||
roh_chr = as.character(roh)
|
||
idx = which(as.character(unclass(labs)) == roh_chr)
|
||
if (length(idx) > 0) return(list(text = names(labs)[idx[1]]))
|
||
idx = which(names(labs) == roh_chr)
|
||
if (length(idx) > 0) return(list(text = as.character(unclass(labs))[idx[1]]))
|
||
NULL
|
||
}
|
||
|
||
# Klartext eines 'mc'-Werts (Ja/Nein, Block-C-Antwortstufen) ueber das
|
||
# labels-Attribut der ORIGINAL-Spalte - nie hartkodierte Zahlenwerte
|
||
# annehmen (Abschnitt 4.2 der Spezifikation: Kodierungsrichtung unverifiziert).
|
||
vds33_resolve_label = function(original_spalte, wert) {
|
||
if (is.null(wert) || length(wert) == 0 || is.na(wert[1])) return(NA_character_)
|
||
roh = unclass(wert)[1]
|
||
labs = attr(original_spalte, "labels")
|
||
if (!is.null(labs) && length(labs) > 0) {
|
||
treffer = vds33_match_in_labels(labs, roh)
|
||
if (!is.null(treffer)) return(trimws(as.character(treffer$text)))
|
||
}
|
||
roh_chr = trimws(as.character(roh))
|
||
if (nchar(roh_chr) > 0) return(roh_chr)
|
||
NA_character_
|
||
}
|
||
|
||
# Block C (Abschnitt 4.6): Stufe 0-3 aus der fuehrenden Ziffer des
|
||
# Choice-Label-Texts ("0 = trifft nicht zu" usw.) statt aus dem Rohwert.
|
||
vds33_stufe_aus_label = function(label_text) {
|
||
if (is.na(label_text)) return(NA_integer_)
|
||
if (grepl("^\\s*[0-3]\\s*=", label_text)) {
|
||
return(as.integer(sub("^\\s*([0-3])\\s*=.*$", "\\1", label_text)))
|
||
}
|
||
stufe_num = suppressWarnings(as.integer(label_text))
|
||
if (!is.na(stufe_num) && stufe_num >= 0 && stufe_num <= 3) return(stufe_num)
|
||
NA_integer_
|
||
}
|
||
|
||
# Variablenlabel (Itemtext) der ORIGINAL-Spalte, mit Feldname als Fallback
|
||
# falls kein label-Attribut vorliegt.
|
||
vds33_get_var_label = function(original_spalte, feldname) {
|
||
lbl = attr(original_spalte, "label")
|
||
if (!is.null(lbl) && nchar(trimws(as.character(lbl))) > 0) return(trimws(as.character(lbl)))
|
||
feldname
|
||
}
|
||
|
||
# Freitextfeld lesen, leere/NA-Werte einheitlich als NA_character_.
|
||
vds33_text_feld = function(zeile, feldname) {
|
||
if (!(feldname %in% names(zeile))) return(NA_character_)
|
||
roh = zeile[[feldname]][1]
|
||
if (is.null(roh) || is.na(roh)) return(NA_character_)
|
||
txt = trimws(as.character(roh))
|
||
if (nchar(txt) == 0) NA_character_ else txt
|
||
}
|
||
|
||
# mc_multiple-Ankreuzliste (Abschnitt 4.2/9.1): Exportformat nicht
|
||
# verifiziert, daher robust auf Komma ODER Semikolon splitten und
|
||
# fuehrende/folgende Klammern/Anfuehrungszeichen entfernen.
|
||
vds33_parse_auswahl = function(roh) {
|
||
if (is.null(roh) || length(roh) == 0) return(character(0))
|
||
roh = roh[1]
|
||
if (is.na(roh)) return(character(0))
|
||
txt = trimws(as.character(unclass(roh)))
|
||
if (nchar(txt) == 0) return(character(0))
|
||
txt = gsub("^[\\[\"']+|[\\]\"']+$", "", txt)
|
||
teile = trimws(strsplit(txt, "[,;]")[[1]])
|
||
teile = gsub("^[\\[\"']+|[\\]\"']+$", "", teile)
|
||
teile[nchar(teile) > 0]
|
||
}
|
||
|
||
# select_one-Feld mit externer Choice-Liste (Abschnitt 9.2): formr
|
||
# exportiert solche Felder in dieser App-Serie meist als internen
|
||
# Choice-NAMEN (z.B. "w12", "wf3", "u5") direkt als String (verifiziert in
|
||
# vds24/app.R) - deshalb zuerst direkter Muster-Abgleich auf as.character(),
|
||
# erst bei Nichttreffer auf das labels-Attribut (dbl+lbl-Fall) ausweichen.
|
||
vds33_resolve_select_one_code = function(original_spalte, wert, muster) {
|
||
if (is.null(wert) || length(wert) == 0 || is.na(wert[1])) return(NA_character_)
|
||
roh = unclass(wert)[1]
|
||
roh_chr = trimws(as.character(roh))
|
||
if (grepl(muster, roh_chr)) return(roh_chr)
|
||
|
||
labs = attr(original_spalte, "labels")
|
||
if (!is.null(labs) && length(labs) > 0) {
|
||
treffer = vds33_match_in_labels(labs, roh)
|
||
if (!is.null(treffer)) {
|
||
kandidat = trimws(as.character(treffer$text))
|
||
if (grepl(muster, kandidat)) return(kandidat)
|
||
}
|
||
}
|
||
if (nchar(roh_chr) == 0) return(NA_character_)
|
||
roh_chr
|
||
}
|
||
|
||
make_vds33_profil_plot = function(pct) {
|
||
bereich_reihenfolge = c("wf1", "wf2", "wf3", "wf4", "wf5", "wf6", "wf7", "aw", "ew")
|
||
df = data.frame(
|
||
bereich = factor(bereich_kurz[bereich_reihenfolge], levels = bereich_kurz[bereich_reihenfolge]),
|
||
wert = unname(pct[bereich_reihenfolge]),
|
||
stringsAsFactors = FALSE
|
||
)
|
||
df_vorhanden = df[!is.na(df$wert), ]
|
||
|
||
ggplot(df, aes(x = bereich, y = wert, group = 1)) +
|
||
geom_line(data = df_vorhanden, color = AKZENT_FARBE, linewidth = 1) +
|
||
geom_point(data = df_vorhanden, color = AKZENT_FARBE, size = 3) +
|
||
scale_y_continuous(limits = c(0, 100), breaks = seq(0, 100, 25)) +
|
||
theme_minimal(base_size = 12) +
|
||
theme(
|
||
axis.title.x = element_blank(),
|
||
panel.grid.minor = element_blank(),
|
||
plot.margin = margin(t = 10, r = 15, b = 5, l = 5)
|
||
) +
|
||
labs(y = "Anteil angekreuzter Items (%)")
|
||
}
|
||
|
||
|
||
# Datenaufbereitung ####
|
||
|
||
item_bereich_map = data.frame(
|
||
item_nr = 1:59,
|
||
item_code = c(
|
||
"wf1_1","wf1_2","wf1_3","wf1_4","wf1_5","wf1_6","wf1_7",
|
||
"wf2_1","wf2_2","wf2_3","wf2_4","wf2_5","wf2_6","wf2_7",
|
||
"wf3_1","wf3_2","wf3_3","wf3_4","wf3_5","wf3_6","wf3_7",
|
||
"wf4_1","wf4_2","wf4_3","wf4_4","wf4_5","wf4_6","wf4_7",
|
||
"wf5_1","wf5_2","wf5_3","wf5_4","wf5_5","wf5_6","wf5_7",
|
||
"wf6_1","wf6_2","wf6_3","wf6_4","wf6_5","wf6_6",
|
||
"wf7_1","wf7_2","wf7_3","wf7_4","wf7_5","wf7_6","wf7_7",
|
||
"aw_1","aw_2","aw_3","aw_4","aw_5","aw_6","aw_7","aw_8","aw_9","aw_10","aw_11"
|
||
),
|
||
bereich_code = c(
|
||
rep("wf1",7), rep("wf2",7), rep("wf3",7), rep("wf4",7), rep("wf5",7),
|
||
rep("wf6",6), rep("wf7",7), rep("aw",11)
|
||
),
|
||
item_text = c(
|
||
"dass ich gegen äußere Widerstände zu meiner Meinung stehen kann",
|
||
"dass ich mich mit der Meinung anderer kritisch auseinandersetzen kann",
|
||
"dass ich mich jederzeit frei und selbstverantwortlich entscheiden kann",
|
||
"dass meine Handlungen mit meinen Einstellungen übereinstimmen",
|
||
"dass ich mich selbst akzeptieren kann",
|
||
"dass ich Schwierigkeiten nicht aus dem Weg gehe, nicht zu feige bin",
|
||
"dass ich meine Meinung jederzeit frei äußern kann",
|
||
"dass ich anderen überlegen bin",
|
||
"dass ich bessere Leistungen erbringen kann als meine Kollegen, Mitschüler etc.",
|
||
"dass ich einen angesehenen Beruf habe",
|
||
"dass ich leistungsfähig bin",
|
||
"dass ich in meinem Beruf erfolgreich bin",
|
||
"dass ich fehlerfrei arbeite",
|
||
"dass ich mich rasch auf neue Probleme und Situationen einstellen kann",
|
||
"dass ich ein ruhiges und behagliches Leben habe",
|
||
"dass ich sorglos leben kann",
|
||
"dass ich eine sichere Zukunft habe",
|
||
"dass ich materiellen Wohlstand habe",
|
||
"dass ich stabile wirtschaftliche Verhältnisse habe",
|
||
"dass ich ein konfliktfreies Leben habe",
|
||
"dass ich keine Feinde habe",
|
||
"mein Glaube an Gott",
|
||
"dass ich zu allen Menschen freundlich und nett bin",
|
||
"dass ich genügsam und bescheiden bin",
|
||
"dass ich von anderen Menschen nichts Schlechtes denke",
|
||
"dass ich meinen Mitmenschen gegenüber immer hilfsbereit bin",
|
||
"mein Glaube an ein Leben nach dem Tod",
|
||
"mein Glaube an eine göttliche Vorsehung",
|
||
"dass ich ein harmonisches Verhältnis zu meinem (Ehe-)Partner habe",
|
||
"dass ich stabile familiäre Verhältnisse habe",
|
||
"dass ich ein harmonisches Familienleben habe",
|
||
"dass ich in der Ehe/Partnerschaft treu bin",
|
||
"dass ich meine Eltern liebe und achte",
|
||
"dass ich Kinder habe",
|
||
"dass ich mich auf andere (besonders meinen Partner) verlassen kann",
|
||
"dass ich viele Freunde habe",
|
||
"dass ich in meinem sozialen Umfeld (Arbeitsplatz, Nachbarn etc.) beliebt bin",
|
||
"dass andere gut von mir denken",
|
||
"dass ich mich sozial engagiere",
|
||
"dass ich frei von Hemmungen bin",
|
||
"dass ich sicher im Umgang mit anderen bin (Selbstsicherheit)",
|
||
"dass ich ein interessantes, spannendes und ereignisreiches Leben habe",
|
||
"dass ich möglichst viel von der Welt gesehen habe",
|
||
"dass ich auf künstlerisch-kreativem Gebiet etwas Besonderes leiste",
|
||
"dass ich äußerlich attraktiv bin",
|
||
"dass ich frei und unabhängig bin",
|
||
"dass ich die Schönheiten der Natur und der Künste genießen kann",
|
||
"dass ich so viel wie möglich Freude und Vergnügen im Leben habe",
|
||
"dass Frieden in der Welt ist",
|
||
"dass ich gesund bin",
|
||
"dass ich körperlich fit bin",
|
||
"dass ich mich entsprechend meinen Möglichkeiten und Wünschen entfalten kann (Selbstverwirklichung)",
|
||
"dass ich meinem Leben einen Sinn gebe",
|
||
"dass ich das Gefühl habe, gebraucht zu werden",
|
||
"dass ich tolerant gegenüber Menschen bin, die anders sind",
|
||
"dass ich meine Pflichten gewissenhaft erfülle",
|
||
"dass ich Freude an der Arbeit habe",
|
||
"dass ich Bildung habe",
|
||
"dass ich die Menschen und das Leben verstehe (Weisheit)"
|
||
),
|
||
stringsAsFactors = FALSE
|
||
)
|
||
|
||
bereich_labels = c(
|
||
wf1 = "intellektuelle Freiheit – geistige Unabhängigkeit",
|
||
wf2 = "Überlegenheit, Leistung",
|
||
wf3 = "Sicherheit / materielle Sicherheit",
|
||
wf4 = "Glauben/Spiritualität",
|
||
wf5 = "Familie und Partnerschaft",
|
||
wf6 = "soziale Akzeptanz und Anerkennung",
|
||
wf7 = "etwas erleben und Schönes genießen",
|
||
aw = "allgemeine Werte",
|
||
ew = "weitere eigene (selbst genannte) Werte"
|
||
)
|
||
|
||
bereich_kurz = c(wf1 = "WF1", wf2 = "WF2", wf3 = "WF3", wf4 = "WF4", wf5 = "WF5",
|
||
wf6 = "WF6", wf7 = "WF7", aw = "AW", ew = "EW")
|
||
|
||
bereich_maxn = c(wf1 = 7, wf2 = 7, wf3 = 7, wf4 = 7, wf5 = 7, wf6 = 6, wf7 = 7, aw = 11, ew = 5)
|
||
|
||
VDS33_BEREICHE_OHNE_EW = c("wf1", "wf2", "wf3", "wf4", "wf5", "wf6", "wf7", "aw")
|
||
VDS33_BEREICHE_ALLE = c(VDS33_BEREICHE_OHNE_EW, "ew")
|
||
|
||
|
||
# UI ####
|
||
|
||
app_css = "
|
||
body { font-family: 'Segoe UI', Arial, sans-serif; background: #f5f5f5; }
|
||
.app-header {
|
||
background: #8B2635; color: white; padding: 18px 24px 14px;
|
||
margin-bottom: 20px; border-radius: 0 0 6px 6px;
|
||
}
|
||
.app-header h2 { margin: 0; font-size: 1.5rem; font-weight: 600; }
|
||
.app-header p { margin: 4px 0 0; opacity: 0.85; font-size: 0.9rem; }
|
||
.input-panel {
|
||
background: white; border-radius: 6px; padding: 16px 20px;
|
||
margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.12);
|
||
display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
|
||
}
|
||
.input-panel .form-group { margin-bottom: 0; }
|
||
.input-panel label { font-weight: 600; color: #333; }
|
||
.btn-laden {
|
||
background: #8B2635 !important; color: white !important;
|
||
border: none !important; border-radius: 4px !important;
|
||
padding: 8px 20px !important; font-weight: 600 !important; cursor: pointer;
|
||
}
|
||
.btn-laden:hover { background: #6d1e29 !important; }
|
||
.alert-fehler {
|
||
background: #FFEBEE; border-left: 5px solid #C62828;
|
||
padding: 12px 16px; border-radius: 4px; color: #B71C1C;
|
||
margin-bottom: 12px; font-weight: 500;
|
||
}
|
||
.alert-warnung {
|
||
background: #FFF3E0; border-left: 5px solid #E65100;
|
||
padding: 10px 16px; border-radius: 4px; color: #BF360C;
|
||
margin-bottom: 12px; font-size: 0.93em; font-weight: 500;
|
||
}
|
||
.abschnitt-karte {
|
||
background: white; border-radius: 6px; padding: 20px 24px;
|
||
margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.12);
|
||
}
|
||
.abschnitt-titel {
|
||
color: #8B2635; font-size: 1.15rem; font-weight: 700;
|
||
border-bottom: 2px solid #8B2635; padding-bottom: 8px; margin-bottom: 14px;
|
||
}
|
||
.abschnitt-untertitel {
|
||
color: #8B2635; font-size: 1.0rem; font-weight: 700; margin: 18px 0 10px;
|
||
}
|
||
.meta-block { margin-bottom: 10px; color: #555; font-size: 0.95em; }
|
||
.meta-block strong { color: #222; }
|
||
.ipsativ-hinweis {
|
||
background: #F5F5F5; border-left: 5px solid #9E9E9E;
|
||
padding: 10px 16px; border-radius: 4px; color: #555555;
|
||
margin-bottom: 16px; font-size: 0.88em; font-style: italic;
|
||
}
|
||
.tabelle-standard { width: 100%; border-collapse: collapse; font-size: 0.92em; }
|
||
.tabelle-standard th, .tabelle-standard td {
|
||
text-align: left; padding: 6px 10px; border-bottom: 1px solid #F0F0F0;
|
||
}
|
||
.tabelle-standard th {
|
||
color: #777; font-size: 0.8em; text-transform: uppercase;
|
||
letter-spacing: 0.03em; font-weight: 700;
|
||
}
|
||
.kontext-zeile {
|
||
display: flex; gap: 8px; align-items: baseline;
|
||
padding: 4px 0; color: #444; font-size: 0.93em;
|
||
}
|
||
.kontext-label { font-weight: 600; color: #333; min-width: 220px; }
|
||
.item-zeile {
|
||
display: flex; align-items: flex-start; gap: 10px;
|
||
padding: 7px 0; border-bottom: 1px solid #F0F0F0;
|
||
}
|
||
.item-zeile:last-child { border-bottom: none; }
|
||
.item-nr { font-weight: 600; color: #8B2635; min-width: 30px; flex-shrink: 0; }
|
||
.item-text { flex: 1; color: #333; font-size: 0.92em; }
|
||
.stufe-badge {
|
||
border-radius: 4px; padding: 2px 9px; font-weight: 700;
|
||
font-size: 0.82em; white-space: nowrap; display: inline-block; flex-shrink: 0;
|
||
background: #ECEFF1; color: #37474F;
|
||
}
|
||
.stufe-badge-0 { background: #4CAF50; color: white; }
|
||
.stufe-badge-1 { background: #F48FB1; color: #333333; }
|
||
.stufe-badge-2 { background: #EF5350; color: white; }
|
||
.stufe-badge-3 { background: #B71C1C; color: white; }
|
||
.legende-liste { margin-top: 12px; }
|
||
.legende-zeile {
|
||
display: flex; gap: 8px; padding: 2px 0; color: #444; font-size: 0.88em;
|
||
}
|
||
.legende-kurz { font-weight: 700; color: #8B2635; min-width: 34px; flex-shrink: 0; }
|
||
.legende-text { flex: 1; }
|
||
.freitext-zitat {
|
||
background: #FAFAFA; border-left: 3px solid #ccc; padding: 6px 12px;
|
||
margin: 4px 0 4px 30px; font-style: italic; color: #555; font-size: 0.88em;
|
||
}
|
||
.hinweis-klein { font-size: 0.85em; color: #777; font-style: italic; margin: 6px 0 12px; }
|
||
.hinweis-inkonsistenz { color: #B71C1C; font-style: italic; }
|
||
.disclaimer-zeile {
|
||
font-size: 0.82em; color: #777; font-style: italic;
|
||
margin-top: 10px; border-top: 1px solid rgba(0,0,0,.1); padding-top: 8px;
|
||
}
|
||
"
|
||
|
||
app_css = gsub("#8B2635", AKZENT_FARBE, app_css, fixed = TRUE)
|
||
|
||
ui = fluidPage(
|
||
tags$head(
|
||
tags$meta(charset = "UTF-8"),
|
||
tags$style(HTML(app_css))
|
||
),
|
||
|
||
div(class = "app-header",
|
||
tags$h2("VDS33 – Wertorientierung"),
|
||
tags$p("Ipsatives Werteprofil ohne Normwerte und ohne klinischen Cutoff | Prof. Dr. Dr. Serge Sulz")
|
||
),
|
||
|
||
div(class = "container-fluid",
|
||
|
||
div(class = "input-panel",
|
||
div(style = "min-width: 360px; white-space: nowrap;",
|
||
textInput("pseudonym",
|
||
label = tagList(
|
||
"Pseudonym",
|
||
tags$span(style = "font-weight: normal; font-style: italic; font-size: 0.78em; color: #888; margin-left: 4px; white-space: nowrap;",
|
||
"optional, hat Vorrang vor Chiffre")
|
||
),
|
||
placeholder = "optional", width = "340px")
|
||
),
|
||
div(style = "min-width: 200px;",
|
||
textInput("chiffre", label = "Patientenchiffre",
|
||
placeholder = "z.B. P000123", width = "100%")
|
||
),
|
||
actionButton("btn_suchen", "Auswerten", class = "btn btn-primary btn-laden"),
|
||
div(style = "margin-left: auto;",
|
||
downloadButton("download_word", "Word-Export (.docx)")
|
||
)
|
||
),
|
||
|
||
uiOutput("fehler_ui"),
|
||
uiOutput("warnung_ui"),
|
||
uiOutput("ergebnis_ui")
|
||
)
|
||
)
|
||
|
||
|
||
# Word-Export ####
|
||
|
||
erstelle_vds33_docx = function(erg) {
|
||
doc = read_docx()
|
||
|
||
fp_titel = fp_text(color = AKZENT_FARBE, bold = TRUE, font.size = 18)
|
||
fp_abschnitt = fp_text(color = AKZENT_FARBE, bold = TRUE, font.size = 13)
|
||
fp_label = fp_text(bold = TRUE, font.size = 11)
|
||
fp_normal = fp_text(font.size = 11)
|
||
fp_klein = fp_text(font.size = 9.5, italic = TRUE, color = "#666666")
|
||
fp_disclaimer = fp_text(font.size = 9, italic = TRUE, color = "#777777")
|
||
|
||
doc = body_add_fpar(doc, fpar(ftext("VDS33 – Wertorientierung", fp_titel)))
|
||
doc = body_add_fpar(doc, fpar(
|
||
ftext("Chiffre: ", fp_label),
|
||
ftext(erg$chiffre, fp_normal),
|
||
ftext(" Ausfülldatum: ", fp_label),
|
||
ftext(format(erg$ausfuelldatum, "%d.%m.%Y"), fp_normal)
|
||
))
|
||
if (!is.null(erg$mehrfach_warnung)) {
|
||
doc = body_add_fpar(doc, fpar(
|
||
ftext(erg$mehrfach_warnung, fp_text(font.size = 10, italic = TRUE, color = "#555555"))
|
||
))
|
||
}
|
||
doc = body_add_par(doc, "", style = "Normal")
|
||
|
||
profil_img = tempfile(fileext = ".png")
|
||
ggsave(profil_img, plot = make_vds33_profil_plot(erg$pct), width = 7, height = 3.6, dpi = 150, bg = "white")
|
||
doc = body_add_fpar(doc, fpar(ftext("Werteprofil", fp_abschnitt)))
|
||
doc = body_add_img(doc, src = profil_img, width = 6.2, height = 3.2)
|
||
|
||
for (b in VDS33_BEREICHE_ALLE) {
|
||
doc = body_add_fpar(doc, fpar(
|
||
ftext(paste0(bereich_kurz[[b]], " – "), fp_label),
|
||
ftext(bereich_labels[[b]], fp_klein)
|
||
))
|
||
}
|
||
doc = body_add_par(doc, "", style = "Normal")
|
||
|
||
for (t in erg$top3) {
|
||
doc = body_add_fpar(doc, fpar(
|
||
ftext(paste0(t$rang, ": "), fp_label),
|
||
ftext(t$text, fp_normal)
|
||
))
|
||
}
|
||
doc = body_add_par(doc, "", style = "Normal")
|
||
|
||
doc = body_add_fpar(doc, fpar(ftext("Wichtigste Einzelwerte", fp_abschnitt)))
|
||
for (r in erg$rang_wert) {
|
||
if (!isTRUE(r$vorhanden)) next
|
||
doc = body_add_fpar(doc, fpar(
|
||
ftext(paste0(r$rang, ". "), fp_label),
|
||
ftext(r$text, fp_normal),
|
||
ftext(paste0(" (", r$bereich_name, ")"), fp_klein)
|
||
))
|
||
if (isTRUE(r$inkonsistent)) {
|
||
doc = body_add_fpar(doc, fpar(ftext(
|
||
" Hinweis: Rangplatz verweist auf ein leeres eigenes-Werte-Freitextfeld.",
|
||
fp_text(font.size = 9, italic = TRUE, color = "#B71C1C"))))
|
||
}
|
||
}
|
||
doc = body_add_par(doc, "", style = "Normal")
|
||
|
||
doc = body_add_fpar(doc, fpar(ftext("Wichtigste Wertebereiche", fp_abschnitt)))
|
||
for (r in erg$rang_bereich) {
|
||
if (!isTRUE(r$vorhanden)) next
|
||
doc = body_add_fpar(doc, fpar(
|
||
ftext(paste0(r$rang, ". "), fp_label),
|
||
ftext(r$bereich_name, fp_normal)
|
||
))
|
||
}
|
||
doc = body_add_par(doc, "", style = "Normal")
|
||
|
||
doc = body_add_fpar(doc, fpar(ftext("Selbsteinschätzung je Bereich (Ja/Nein)", fp_abschnitt)))
|
||
for (b in VDS33_BEREICHE_ALLE) {
|
||
doc = body_add_fpar(doc, fpar(
|
||
ftext(paste0(bereich_labels[[b]], ": "), fp_label),
|
||
ftext(if (is.na(erg$janein[[b]])) "k. A." else erg$janein[[b]], fp_normal)
|
||
))
|
||
}
|
||
doc = body_add_par(doc, "", style = "Normal")
|
||
|
||
doc = body_add_fpar(doc, fpar(ftext("14 Items (Block C)", fp_abschnitt)))
|
||
for (it in erg$block_c) {
|
||
stufe_key = if (!is.na(it$stufe)) as.character(it$stufe) else NA_character_
|
||
fp_badge = if (!is.na(stufe_key)) fp_text(
|
||
color = VDS33_STUFE_BADGE_TEXT_FARBEN[[stufe_key]],
|
||
bold = TRUE,
|
||
shading.color = VDS33_STUFE_BADGE_FARBEN[[stufe_key]],
|
||
font.size = 10
|
||
) else fp_klein
|
||
badge_text = if (is.na(it$stufe)) " k. A. " else paste0(" ", it$stufe, " / 3 ")
|
||
doc = body_add_fpar(doc, fpar(
|
||
ftext(paste0(it$nr, ". ", it$item_text, " "), fp_normal),
|
||
ftext(badge_text, fp_badge)
|
||
))
|
||
}
|
||
doc = body_add_par(doc, "", style = "Normal")
|
||
|
||
doc = body_add_fpar(doc, fpar(ftext("Umgang mit diesen Themen (Block D)", fp_abschnitt)))
|
||
for (r in erg$d1) {
|
||
if (!isTRUE(r$vorhanden)) next
|
||
doc = body_add_fpar(doc, fpar(
|
||
ftext(paste0(r$rang, ". "), fp_label),
|
||
ftext(r$text, fp_normal)
|
||
))
|
||
}
|
||
if (!is.na(erg$d2_text)) {
|
||
doc = body_add_fpar(doc, fpar(ftext("Freitext (D.2): ", fp_label)))
|
||
doc = body_add_fpar(doc, fpar(ftext(erg$d2_text, fp_normal)))
|
||
}
|
||
if (!is.na(erg$d3_text)) {
|
||
doc = body_add_fpar(doc, fpar(ftext("Freitext (D.3): ", fp_label)))
|
||
doc = body_add_fpar(doc, fpar(ftext(erg$d3_text, fp_normal)))
|
||
}
|
||
doc = body_add_par(doc, "", style = "Normal")
|
||
|
||
doc = body_add_fpar(doc, fpar(ftext(VDS33_DISCLAIMER, fp_disclaimer)))
|
||
|
||
doc
|
||
}
|
||
|
||
|
||
# Server ####
|
||
|
||
server = function(input, output, session) {
|
||
|
||
observe({
|
||
query = parseQueryString(session$clientData$url_search)
|
||
if (!is.null(query$pseudonym) && nchar(trimws(query$pseudonym)) > 0) {
|
||
updateTextInput(session, "pseudonym", value = trimws(query$pseudonym))
|
||
}
|
||
})
|
||
|
||
observe({
|
||
query = parseQueryString(session$clientData$url_search)
|
||
if (!is.null(query$chiffre) && nchar(trimws(query$chiffre)) > 0) {
|
||
updateTextInput(session, "chiffre", value = toupper(trimws(query$chiffre)))
|
||
}
|
||
})
|
||
|
||
# Skripte werden NICHT beim App-Start gesourct, nur beim Klick.
|
||
ergebnis = eventReactive(input$btn_suchen, {
|
||
|
||
chiffre = toupper(trimws(input$chiffre))
|
||
|
||
if (nchar(trimws(input$pseudonym)) == 0 && nchar(chiffre) == 0) {
|
||
return(list(typ = "leere_eingabe", meldung = "Bitte Chiffre oder Pseudonym eingeben."))
|
||
}
|
||
if (nchar(trimws(input$pseudonym)) == 0 && !grepl("^[A-Z][0-9]{6}$", chiffre)) {
|
||
return(list(typ = "format_fehler", chiffre = chiffre))
|
||
}
|
||
|
||
if (!file.exists(PFAD_DOWNLOAD_SKRIPT)) {
|
||
return(list(typ = "skript_fehler",
|
||
meldung = paste0("Download-Skript nicht gefunden:\n", PFAD_DOWNLOAD_SKRIPT)))
|
||
}
|
||
if (!file.exists(PFAD_PSEUDONYM_SKRIPT)) {
|
||
return(list(typ = "skript_fehler",
|
||
meldung = paste0("Pseudonym-Skript nicht gefunden:\n", PFAD_PSEUDONYM_SKRIPT)))
|
||
}
|
||
|
||
ok_dl = tryCatch({
|
||
source(PFAD_DOWNLOAD_SKRIPT, local = FALSE)
|
||
list(ok = TRUE)
|
||
}, error = function(e) list(ok = FALSE, msg = e$message))
|
||
if (!ok_dl$ok) return(list(typ = "skript_fehler", meldung = ok_dl$msg))
|
||
|
||
db_ordner = local({
|
||
ordner = dirname(normalizePath(PFAD_PSEUDONYM_SKRIPT, mustWork = FALSE))
|
||
gefunden = NULL
|
||
for (i in 1:5) {
|
||
if (file.exists(file.path(ordner, "pseudonyme.db"))) { gefunden = ordner; break }
|
||
elternteil = dirname(ordner)
|
||
if (elternteil == ordner) break
|
||
ordner = elternteil
|
||
}
|
||
gefunden
|
||
})
|
||
if (is.null(db_ordner)) return(list(typ = "db_nicht_gefunden"))
|
||
|
||
alter_wd = getwd()
|
||
on.exit(setwd(alter_wd), add = TRUE)
|
||
setwd(db_ordner)
|
||
|
||
ok_ps = tryCatch({
|
||
source(PFAD_PSEUDONYM_SKRIPT, local = FALSE)
|
||
list(ok = TRUE)
|
||
}, error = function(e) list(ok = FALSE, msg = e$message))
|
||
if (!ok_ps$ok) return(list(typ = "skript_fehler", meldung = ok_ps$msg))
|
||
|
||
if (!exists("daten_vds33", envir = .GlobalEnv) || !exists("pseudo", envir = .GlobalEnv)) {
|
||
return(list(typ = "daten_fehlen"))
|
||
}
|
||
|
||
daten = get("daten_vds33", envir = .GlobalEnv)
|
||
pseudo_df = get("pseudo", envir = .GlobalEnv)
|
||
|
||
if (!("session" %in% names(daten))) {
|
||
return(list(typ = "daten_fehlen",
|
||
meldung = paste0(
|
||
"Erwartete Spalte 'session' nicht in 'daten_vds33' gefunden. ",
|
||
"Bitte Session-ID-Spaltenname vor Produktiveinsatz prüfen."
|
||
)))
|
||
}
|
||
|
||
if (nchar(trimws(input$pseudonym)) > 0) {
|
||
pw_treffer = pseudo_df[pseudo_df$pseudonym == trimws(input$pseudonym), ]
|
||
if (nrow(pw_treffer) > 0) chiffre = toupper(trimws(pw_treffer$chiffre[1]))
|
||
}
|
||
|
||
treffer_ps = pseudo_df[toupper(trimws(pseudo_df$chiffre)) == chiffre, ]
|
||
if (nrow(treffer_ps) == 0) return(list(typ = "chiffre_nicht_gefunden", chiffre = chiffre))
|
||
|
||
alle_session_ids = unique(treffer_ps$pseudonym)
|
||
if (nchar(trimws(input$pseudonym)) > 0) alle_session_ids = trimws(input$pseudonym)
|
||
|
||
treffer_daten = daten[daten$session %in% alle_session_ids, ]
|
||
if (nrow(treffer_daten) == 0) return(list(typ = "kein_treffer", chiffre = chiffre))
|
||
|
||
mehrfach_warnung = NULL
|
||
if (nrow(treffer_daten) > 1) {
|
||
n = nrow(treffer_daten)
|
||
zeitspalte = intersect(c("created", "ended"), names(treffer_daten))
|
||
if (length(zeitspalte) > 0) {
|
||
treffer_daten = treffer_daten[order(treffer_daten[[zeitspalte[1]]], decreasing = TRUE), ]
|
||
}
|
||
treffer_daten = treffer_daten[1, , drop = FALSE]
|
||
mehrfach_warnung = paste0(
|
||
"Mehrere Ausfüllungen gefunden (", n, " Einträge) — es wird die neueste angezeigt."
|
||
)
|
||
}
|
||
zeile = treffer_daten[1, , drop = FALSE]
|
||
|
||
ausfuelldatum = tryCatch({
|
||
kandidaten = c("created", "ended")
|
||
spalte = intersect(kandidaten, names(zeile))
|
||
if (length(spalte) > 0) {
|
||
roh = zeile[[spalte[1]]][1]
|
||
as.Date(as.POSIXct(as.character(roh)))
|
||
} else {
|
||
Sys.Date()
|
||
}
|
||
}, error = function(e) Sys.Date())
|
||
|
||
# Abschnitt 6, Schritt 1+2: Prozentwert je Wertebereich.
|
||
pct = setNames(rep(NA_real_, 9), VDS33_BEREICHE_ALLE)
|
||
for (b in VDS33_BEREICHE_OHNE_EW) {
|
||
feld = paste0("vds33_", b, "_auswahl")
|
||
codes = if (feld %in% names(zeile)) vds33_parse_auswahl(zeile[[feld]][1]) else character(0)
|
||
valide_codes = item_bereich_map$item_code[item_bereich_map$bereich_code == b]
|
||
valide = codes[codes %in% valide_codes]
|
||
pct[b] = 100 * length(valide) / bereich_maxn[[b]]
|
||
}
|
||
|
||
ew_texte = sapply(1:5, function(i) vds33_text_feld(zeile, paste0("vds33_ew_", i)))
|
||
ew_filled = sum(!is.na(ew_texte))
|
||
pct["ew"] = if (ew_filled > 0) 100 * ew_filled / 5 else NA_real_
|
||
|
||
# Abschnitt 6, Schritt 4: Top-3-Bereiche, level-gruppiert.
|
||
pct_valide = pct[!is.na(pct)]
|
||
levels_sortiert = sort(unique(pct_valide), decreasing = TRUE)
|
||
top_levels = head(levels_sortiert, 3)
|
||
rang_namen = c("Am wichtigsten", "Am zweitwichtigsten", "Am drittwichtigsten")
|
||
top3 = lapply(seq_along(top_levels), function(i) {
|
||
lvl = top_levels[i]
|
||
bereiche_auf_level = names(pct_valide)[pct_valide == lvl]
|
||
list(
|
||
rang = rang_namen[i],
|
||
level = lvl,
|
||
bereiche = bereiche_auf_level,
|
||
text = paste(bereich_labels[bereiche_auf_level], collapse = "; ")
|
||
)
|
||
})
|
||
|
||
# Abschnitt 6, Schritt 5: wichtigste Einzelwerte (Block B1).
|
||
rang_wert = lapply(1:5, function(i) {
|
||
feld = paste0("vds33_rang_wert_", i)
|
||
if (!(feld %in% names(zeile))) return(list(rang = i, vorhanden = FALSE))
|
||
code = vds33_resolve_select_one_code(daten[[feld]], zeile[[feld]], "^w[0-9]{1,2}$")
|
||
if (is.na(code)) return(list(rang = i, vorhanden = FALSE))
|
||
|
||
nr = suppressWarnings(as.integer(sub("^w", "", code)))
|
||
if (!is.na(nr) && nr >= 1 && nr <= 59) {
|
||
treffer = item_bereich_map[item_bereich_map$item_nr == nr, ]
|
||
return(list(
|
||
rang = i, code = code, vorhanden = TRUE, inkonsistent = FALSE,
|
||
bereich = treffer$bereich_code[1],
|
||
bereich_name = bereich_labels[[treffer$bereich_code[1]]],
|
||
text = treffer$item_text[1]
|
||
))
|
||
}
|
||
if (!is.na(nr) && nr >= 60 && nr <= 64) {
|
||
ew_feld = paste0("vds33_ew_", nr - 59)
|
||
ew_text = vds33_text_feld(zeile, ew_feld)
|
||
inkonsistent = is.na(ew_text)
|
||
return(list(
|
||
rang = i, code = code, vorhanden = TRUE, inkonsistent = inkonsistent,
|
||
bereich = "ew", bereich_name = bereich_labels[["ew"]],
|
||
text = if (inkonsistent) "(zugehöriges Freitextfeld ist leer)" else ew_text
|
||
))
|
||
}
|
||
list(
|
||
rang = i, code = code, vorhanden = TRUE, inkonsistent = TRUE,
|
||
bereich = NA_character_, bereich_name = "k. A.",
|
||
text = paste0("Unbekannter Code: ", code)
|
||
)
|
||
})
|
||
|
||
# Abschnitt 6, Schritt 6: wichtigste Wertebereiche (Block B2).
|
||
rang_bereich = lapply(1:3, function(i) {
|
||
feld = paste0("vds33_rang_bereich_", i)
|
||
if (!(feld %in% names(zeile))) return(list(rang = i, vorhanden = FALSE))
|
||
code = vds33_resolve_select_one_code(daten[[feld]], zeile[[feld]], "^(wf[1-7]|aw|ew)$")
|
||
if (is.na(code)) return(list(rang = i, vorhanden = FALSE))
|
||
bereich_name = if (code %in% names(bereich_labels)) bereich_labels[[code]] else code
|
||
list(rang = i, code = code, vorhanden = TRUE, bereich_name = bereich_name)
|
||
})
|
||
|
||
# Abschnitt 6, Schritt 7: Ja/Nein-Selbsteinschätzung je Bereich (deskriptiv).
|
||
janein = sapply(VDS33_BEREICHE_ALLE, function(b) {
|
||
feld = paste0("vds33_", b, "_janein")
|
||
if (!(feld %in% names(zeile))) return(NA_character_)
|
||
vds33_resolve_label(daten[[feld]], zeile[[feld]])
|
||
})
|
||
|
||
# Abschnitt 6, Schritt 8: Block C, 14 Items, rein deskriptiv.
|
||
block_c = lapply(1:14, function(i) {
|
||
feld = paste0("vds33_c", sprintf("%02d", i))
|
||
label_text = if (feld %in% names(zeile)) vds33_resolve_label(daten[[feld]], zeile[[feld]]) else NA_character_
|
||
list(
|
||
nr = i,
|
||
code = paste0("u", i),
|
||
item_text = if (feld %in% names(daten)) vds33_get_var_label(daten[[feld]], feld) else feld,
|
||
stufe = vds33_stufe_aus_label(label_text)
|
||
)
|
||
})
|
||
u_texte = setNames(sapply(block_c, function(x) x$item_text), sapply(block_c, function(x) x$code))
|
||
|
||
# Abschnitt 6, Schritt 9: Block D.
|
||
d1 = lapply(1:2, function(i) {
|
||
feld = paste0("vds33_d1_wahl_", i)
|
||
if (!(feld %in% names(zeile))) return(list(rang = i, vorhanden = FALSE))
|
||
code = vds33_resolve_select_one_code(daten[[feld]], zeile[[feld]], "^u[0-9]{1,2}$")
|
||
if (is.na(code)) return(list(rang = i, vorhanden = FALSE))
|
||
text = if (code %in% names(u_texte)) unname(u_texte[[code]]) else paste0("Unbekannter Code: ", code)
|
||
list(rang = i, code = code, vorhanden = TRUE, text = text)
|
||
})
|
||
d2_text = vds33_text_feld(zeile, "vds33_d2_text")
|
||
d3_text = vds33_text_feld(zeile, "vds33_d3_text")
|
||
|
||
list(
|
||
typ = "erfolg",
|
||
chiffre = chiffre,
|
||
ausfuelldatum = ausfuelldatum,
|
||
mehrfach_warnung = mehrfach_warnung,
|
||
pct = pct,
|
||
top3 = top3,
|
||
rang_wert = rang_wert,
|
||
rang_bereich = rang_bereich,
|
||
janein = janein,
|
||
block_c = block_c,
|
||
d1 = d1,
|
||
d2_text = d2_text,
|
||
d3_text = d3_text
|
||
)
|
||
})
|
||
|
||
vds33_fehlermeldung = function(d) {
|
||
switch(d$typ,
|
||
"leere_eingabe" = d$meldung,
|
||
"format_fehler" = paste0("Ungültige Chiffre '", d$chiffre, "'. Erwartet: ein Großbuchstabe + 6 Ziffern (z.B. P000123)."),
|
||
"skript_fehler" = paste0("Fehler beim Sourcen eines externen Skripts: ", d$meldung),
|
||
"db_nicht_gefunden" = "Die Datei 'pseudonyme.db' konnte in den übergeordneten Verzeichnissen nicht gefunden werden.",
|
||
"daten_fehlen" = if (!is.null(d$meldung)) d$meldung else "Nach dem Sourcen der Skripte fehlen die erwarteten Objekte 'daten_vds33' oder 'pseudo'.",
|
||
"chiffre_nicht_gefunden" = paste0("Chiffre '", d$chiffre, "' wurde in der Pseudonym-Datenbank nicht gefunden."),
|
||
"kein_treffer" = paste0("Kein VDS33-Datensatz für Chiffre '", d$chiffre, "' gefunden."),
|
||
"Unbekannter Fehler."
|
||
)
|
||
}
|
||
|
||
output$fehler_ui = renderUI({
|
||
req(input$btn_suchen)
|
||
d = ergebnis()
|
||
if (d$typ != "erfolg") div(class = "alert-fehler", vds33_fehlermeldung(d))
|
||
})
|
||
|
||
output$warnung_ui = renderUI({
|
||
req(input$btn_suchen)
|
||
d = ergebnis()
|
||
if (d$typ != "erfolg" || is.null(d$mehrfach_warnung)) return(NULL)
|
||
div(class = "alert-warnung", d$mehrfach_warnung)
|
||
})
|
||
|
||
output$ergebnis_ui = renderUI({
|
||
req(input$btn_suchen)
|
||
d = ergebnis()
|
||
if (d$typ != "erfolg") return(NULL)
|
||
|
||
top3_ui = lapply(d$top3, function(t) {
|
||
div(class = "kontext-zeile",
|
||
div(class = "kontext-label", paste0(t$rang, ":")),
|
||
div(t$text)
|
||
)
|
||
})
|
||
|
||
legende_ui = lapply(VDS33_BEREICHE_ALLE, function(b) {
|
||
div(class = "legende-zeile",
|
||
span(class = "legende-kurz", bereich_kurz[[b]]),
|
||
span(class = "legende-text", bereich_labels[[b]])
|
||
)
|
||
})
|
||
|
||
karte_profil = div(class = "abschnitt-karte",
|
||
div(class = "abschnitt-titel", "Werteprofil"),
|
||
div(class = "meta-block",
|
||
tags$strong("Chiffre: "), d$chiffre,
|
||
tags$span(" | ", style = "color:#ccc;"),
|
||
tags$strong("Ausfülldatum: "), format(d$ausfuelldatum, "%d.%m.%Y")
|
||
),
|
||
div(class = "ipsativ-hinweis",
|
||
"Rein ipsatives Werteprofil ohne Normwerte und ohne klinischen Cutoff. ",
|
||
"Dargestellt wird ausschließlich der relative Vergleich der 9 Wertebereiche zueinander."),
|
||
plotOutput("profil_plot", height = "280px"),
|
||
div(class = "legende-liste", legende_ui),
|
||
tags$hr(),
|
||
div(top3_ui)
|
||
)
|
||
|
||
rang_wert_ui = lapply(d$rang_wert, function(r) {
|
||
if (!isTRUE(r$vorhanden)) return(NULL)
|
||
tagList(
|
||
div(class = "item-zeile",
|
||
div(class = "item-nr", paste0(r$rang, ".")),
|
||
div(class = "item-text", r$text),
|
||
span(class = "stufe-badge", r$bereich_name)
|
||
),
|
||
if (isTRUE(r$inkonsistent)) div(class = "hinweis-inkonsistenz hinweis-klein",
|
||
"Hinweis: Rangplatz verweist auf ein leeres eigenes-Werte-Freitextfeld.")
|
||
)
|
||
})
|
||
|
||
karte_einzelwerte = div(class = "abschnitt-karte",
|
||
div(class = "abschnitt-titel", "Wichtigste Einzelwerte"),
|
||
div(rang_wert_ui)
|
||
)
|
||
|
||
rang_bereich_ui = lapply(d$rang_bereich, function(r) {
|
||
if (!isTRUE(r$vorhanden)) return(NULL)
|
||
div(class = "item-zeile",
|
||
div(class = "item-nr", paste0(r$rang, ".")),
|
||
div(class = "item-text", r$bereich_name)
|
||
)
|
||
})
|
||
|
||
karte_wertebereiche = div(class = "abschnitt-karte",
|
||
div(class = "abschnitt-titel", "Wichtigste Wertebereiche"),
|
||
div(rang_bereich_ui)
|
||
)
|
||
|
||
janein_zeilen = lapply(VDS33_BEREICHE_ALLE, function(b) {
|
||
tags$tr(
|
||
tags$td(bereich_labels[[b]]),
|
||
tags$td(if (is.na(d$janein[[b]])) "k. A." else d$janein[[b]])
|
||
)
|
||
})
|
||
|
||
karte_janein = div(class = "abschnitt-karte",
|
||
div(class = "abschnitt-titel", "Selbsteinschätzung je Bereich"),
|
||
div(class = "hinweis-klein",
|
||
"Rein deskriptiv - fließt nicht in die Prozentwerte des Werteprofils ein."),
|
||
tags$table(class = "tabelle-standard",
|
||
tags$thead(tags$tr(tags$th("Bereich"), tags$th("Insgesamt wichtig?"))),
|
||
tags$tbody(janein_zeilen)
|
||
)
|
||
)
|
||
|
||
block_c_ui = lapply(d$block_c, function(it) {
|
||
badge_klasse = if (is.na(it$stufe)) "stufe-badge" else paste0("stufe-badge stufe-badge-", it$stufe)
|
||
div(class = "item-zeile",
|
||
div(class = "item-nr", paste0(it$nr, ".")),
|
||
div(class = "item-text", it$item_text),
|
||
span(class = badge_klasse, if (is.na(it$stufe)) "k. A." else paste0(it$stufe, " / 3"))
|
||
)
|
||
})
|
||
|
||
karte_block_c = div(class = "abschnitt-karte",
|
||
div(class = "abschnitt-titel", "14 Items"),
|
||
div(class = "hinweis-klein", "Rein deskriptive Auflistung, keine Aggregation, kein Summenscore."),
|
||
div(block_c_ui)
|
||
)
|
||
|
||
d1_ui = lapply(d$d1, function(r) {
|
||
if (!isTRUE(r$vorhanden)) return(NULL)
|
||
div(class = "item-zeile",
|
||
div(class = "item-nr", paste0(r$rang, ".")),
|
||
div(class = "item-text", r$text)
|
||
)
|
||
})
|
||
|
||
karte_block_d = div(class = "abschnitt-karte",
|
||
div(class = "abschnitt-titel", "Umgang mit diesen Themen"),
|
||
div(d1_ui),
|
||
if (!is.na(d$d2_text)) tagList(
|
||
tags$div(class = "abschnitt-untertitel", "Freitext (D.2)"),
|
||
div(class = "freitext-zitat", style = "margin-left: 0;", d$d2_text)
|
||
),
|
||
if (!is.na(d$d3_text)) tagList(
|
||
tags$div(class = "abschnitt-untertitel", "Freitext (D.3)"),
|
||
div(class = "freitext-zitat", style = "margin-left: 0;", d$d3_text)
|
||
),
|
||
div(class = "disclaimer-zeile", VDS33_DISCLAIMER)
|
||
)
|
||
|
||
tagList(karte_profil, karte_einzelwerte, karte_wertebereiche, karte_janein, karte_block_c, karte_block_d)
|
||
})
|
||
|
||
output$profil_plot = renderPlot({
|
||
req(input$btn_suchen)
|
||
d = ergebnis()
|
||
req(d$typ == "erfolg")
|
||
make_vds33_profil_plot(d$pct)
|
||
}, bg = "transparent")
|
||
|
||
output$download_word = downloadHandler(
|
||
filename = function() {
|
||
d = tryCatch(ergebnis(), error = function(e) NULL)
|
||
erfolgreich = is.list(d) && identical(d$typ, "erfolg")
|
||
chiffre_esc = if (erfolgreich && nchar(d$chiffre) > 0) gsub("[^A-Za-z0-9_-]", "_", d$chiffre) else "export"
|
||
ausfuelldatum_fn = if (erfolgreich) {
|
||
tryCatch(format(as.Date(d$ausfuelldatum), "%Y%m%d"),
|
||
error = function(e) format(Sys.Date(), "%Y%m%d"))
|
||
} else {
|
||
format(Sys.Date(), "%Y%m%d")
|
||
}
|
||
paste0("VDS33_", chiffre_esc, "_", ausfuelldatum_fn, ".docx")
|
||
},
|
||
content = function(file) {
|
||
d = tryCatch(ergebnis(), error = function(e) NULL)
|
||
erfolgreich = is.list(d) && identical(d$typ, "erfolg")
|
||
if (!erfolgreich) {
|
||
doc = read_docx()
|
||
doc = body_add_par(doc,
|
||
"Kein Datensatz geladen. Bitte zuerst Chiffre oder Pseudonym eingeben und 'Auswerten' klicken.",
|
||
style = "Normal")
|
||
print(doc, target = file)
|
||
return()
|
||
}
|
||
doc = tryCatch(
|
||
erstelle_vds33_docx(d),
|
||
error = function(e) {
|
||
err_doc = read_docx()
|
||
body_add_par(err_doc,
|
||
paste0("Fehler beim Erstellen des Word-Dokuments: ", e$message),
|
||
style = "Normal")
|
||
}
|
||
)
|
||
print(doc, target = file)
|
||
}
|
||
)
|
||
}
|
||
|
||
|
||
# Start ####
|
||
|
||
shinyApp(ui, server)
|