finished header and changed colors.

This commit is contained in:
oxidiert
2025-09-16 15:51:11 +02:00
parent 93d2fa4333
commit 77742275e6
3 changed files with 79 additions and 133 deletions

View File

@ -1,106 +0,0 @@
[
"client_code",
"questionnaire_1_demographic_information",
"questionnaire_1_demographic_information-coach_code",
"questionnaire_1_demographic_information-consent_instruction",
"questionnaire_1_demographic_information-no_consent_entered",
"questionnaire_1_demographic_information-accommodation",
"questionnaire_1_demographic_information-other_accommodation",
"questionnaire_1_demographic_information-client_code_entry_question",
"questionnaire_1_demographic_information-age",
"questionnaire_1_demographic_information-gender",
"questionnaire_1_demographic_information-country_of_origin",
"questionnaire_1_demographic_information-departure_country",
"questionnaire_1_demographic_information-since_in_germany",
"questionnaire_1_demographic_information-living_situation",
"questionnaire_1_demographic_information-number_family_members",
"questionnaire_1_demographic_information-languages_spoken",
"questionnaire_1_demographic_information-german_skills",
"questionnaire_1_demographic_information-school_years_total",
"questionnaire_1_demographic_information-school_years_origin",
"questionnaire_1_demographic_information-school_years_transit",
"questionnaire_1_demographic_information-school_years_germany",
"questionnaire_1_demographic_information-vocational_training",
"questionnaire_1_demographic_information-provisional_accommodation_since",
"questionnaire_2_rhs",
"questionnaire_2_rhs-coach_code",
"questionnaire_2_rhs-glass_explanation",
"questionnaire_2_rhs-q1_symptom",
"questionnaire_2_rhs-q2_symptom",
"questionnaire_2_rhs-q3_symptom",
"questionnaire_2_rhs-q4_symptom",
"questionnaire_2_rhs-q5_symptom",
"questionnaire_2_rhs-q6_symptom",
"questionnaire_2_rhs-q7_symptom",
"questionnaire_2_rhs-q8_symptom",
"questionnaire_2_rhs-q9_symptom",
"questionnaire_2_rhs-q10_reexperience_trauma",
"questionnaire_2_rhs-q11_physical_reaction",
"questionnaire_2_rhs-q12_emotional_numbness",
"questionnaire_2_rhs-q13_easily_startled",
"questionnaire_2_rhs-q14_intro",
"questionnaire_2_rhs-pain_rating_instruction",
"questionnaire_2_rhs-violence_question_1",
"questionnaire_2_rhs-times_happend",
"questionnaire_2_rhs-age_at_incident",
"questionnaire_2_rhs-conflict_since_arrival",
"questionnaire_2_rhs-times_happend2",
"questionnaire_2_rhs-age_at_incident2",
"questionnaire_2_rhs-asylum_procedure_since",
"questionnaire_3_integration_index",
"questionnaire_3_integration_index-coach_code",
"questionnaire_3_integration_index-feeling_connected_to_germany_question",
"questionnaire_3_integration_index-understanding_political_issues",
"questionnaire_3_integration_index-unexpected_expense_question",
"questionnaire_3_integration_index-dining_with_germans_question",
"questionnaire_3_integration_index-reading_german_articles_question",
"questionnaire_3_integration_index-visiting_doctor_question",
"questionnaire_3_integration_index-feeling_as_outsider_question",
"questionnaire_3_integration_index-recent_occupation_question",
"questionnaire_3_integration_index-discussing_politics_question",
"questionnaire_3_integration_index-contact_with_germans_question",
"questionnaire_3_integration_index-speaking_german_opinion_question",
"questionnaire_3_integration_index-job_search_question",
"questionnaire_4_consultation_results",
"questionnaire_4_consultation_results-coach_code",
"questionnaire_4_consultation_results-date_consultation_health_interview_result",
"questionnaire_4_consultation_results-consultation_decision",
"questionnaire_4_consultation_results-consent_conversation_in_6_months",
"questionnaire_4_consultation_results-participation_in_coaching",
"questionnaire_4_consultation_results-consent_coaching_given",
"questionnaire_4_consultation_results-consent_conversation_in_6_months",
"questionnaire_4_consultation_results-decision_after_reflection_period",
"questionnaire_4_consultation_results-professional_referral",
"questionnaire_4_consultation_results-confidentiality_agreement",
"questionnaire_4_consultation_results-health_insurance_card",
"questionnaire_4_consultation_results-consent_conversation_in_6_months",
"questionnaire_5_final_interview",
"questionnaire_5_final_interview-coach_code",
"questionnaire_5_final_interview-consent_followup_6_months",
"questionnaire_5_final_interview-date_final_interview",
"questionnaire_5_final_interview-amount_nat_appointments",
"questionnaire_5_final_interview-amount_session_flowers",
"questionnaire_5_final_interview-amount_session_stones",
"questionnaire_5_final_interview-termination_nat_coaching",
"questionnaire_5_final_interview-client_canceled_NAT",
"questionnaire_6_follow_up_survey",
"questionnaire_6_follow_up_survey-coach_code",
"questionnaire_6_follow_up_survey-follow_up",
"questionnaire_6_follow_up_survey-special_burden_question",
"questionnaire_6_follow_up_survey-glass_explanation",
"questionnaire_6_follow_up_survey-how_strong_past_month",
"questionnaire_6_follow_up_survey-q14_intro",
"questionnaire_6_follow_up_survey-pain_rating_instruction",
"questionnaire_6_follow_up_survey-feeling_connected_to_germany_question",
"questionnaire_6_follow_up_survey-understanding_political_issues",
"questionnaire_6_follow_up_survey-unexpected_expense_question",
"questionnaire_6_follow_up_survey-dining_with_germans_question",
"questionnaire_6_follow_up_survey-reading_german_articles_question",
"questionnaire_6_follow_up_survey-visiting_doctor_question",
"questionnaire_6_follow_up_survey-feeling_as_outsider_question",
"questionnaire_6_follow_up_survey-recent_occupation_question",
"questionnaire_6_follow_up_survey-discussing_politics_question",
"questionnaire_6_follow_up_survey-contact_with_germans_question",
"questionnaire_6_follow_up_survey-speaking_german_opinion_question",
"questionnaire_6_follow_up_survey-job_search_question"
]

Binary file not shown.

View File

@ -26,7 +26,7 @@ class DatabaseButtonHandler(
private val uiScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val tag = "DatabaseButtonHandler"
// Cache für geladene IDs aus assets/header_order.json
// Cache für geladene IDs aus assets/header_order.json oder header_order.xlsx
private var orderedIdsCache: List<String>? = null
fun setup() {
@ -106,7 +106,7 @@ class DatabaseButtonHandler(
/**
* Erzeugt eine Excel-Datei (ClientHeaders.xlsx) mit:
* - Zeile 1: Spalten-IDs (header_order.json)
* - Zeile 1: Spalten-IDs (header_order.json / header_order.xlsx)
* - Zeile 2: ENGLISCHE Fragen/Beschriftungen zu jeder ID (rein EN, keine IDs mehr)
* - Ab Zeile 3: pro Client die Werte ("Done"/"Not Done"/Antwort oder "None")
*/
@ -156,8 +156,7 @@ class DatabaseButtonHandler(
id in questionnaireIdSet -> if (statusMap[id] == true) "Done" else "Not Done"
else -> answerMap[id]?.takeIf { it.isNotBlank() } ?: "None"
}
// Für Export in EN lokalisieren; Done/Not Done/None bleiben unverändert.
val out = localizeForExportEn(id, raw)
val out = localizeForExportEn(id, raw) // Export immer EN (Done/Not Done/None bleiben)
row.createCell(c++).setCellValue(out)
}
}
@ -185,31 +184,24 @@ class DatabaseButtonHandler(
// Liefert einen englischen, lesbaren Fragetext/Titel zu einer ID (nur EN, keine IDs).
private suspend fun englishQuestionForId(id: String, questionnaireIdSet: Set<String>): String {
// 1) Spezielle Fälle
if (id == "client_code") return "Client code"
if (id in questionnaireIdSet && !id.contains('-')) return "Questionnaire status"
// 2) Versuch: LanguageManager EN für die ID
localizeEnglishNoBrackets(id)?.let { lm ->
if (!looksLikeId(lm, id)) return lm
}
// 3) Falls ID wie "...-field_name": nur den Feldteil humanisieren
val fieldPart = id.substringAfterLast('-', id)
localizeEnglishNoBrackets(fieldPart)?.let { lm ->
if (!looksLikeId(lm, fieldPart)) return lm
}
// 4) Humanisierte englische Fallbacks aus der ID (Titel-Case, Unterstriche → Leerzeichen)
val pretty = humanizeIdToEnglish(fieldPart)
if (pretty.isNotBlank()) return pretty
// 5) Letzter Fallback: „Question“
return "Question"
}
private fun looksLikeId(text: String, originalId: String): Boolean {
// sieht wie die ID aus (nahezu identisch oder nur Klammern/Formatierung)
val normText = text.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
val normId = originalId.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
return normText == normId
@ -217,11 +209,10 @@ class DatabaseButtonHandler(
private fun humanizeIdToEnglish(source: String): String {
val s = source
.replace(Regex("^questionnaire_\\d+_"), "") // Präfix entfernen
.replace(Regex("^questionnaire_\\d+_"), "")
.replace('_', ' ')
.trim()
if (s.isBlank()) return s
// Title Case
return s.split(Regex("\\s+")).joinToString(" ") { it.lowercase().replaceFirstChar { c -> c.titlecase() } }
}
@ -302,39 +293,50 @@ class DatabaseButtonHandler(
}
}
// Farben (Material-like)
val lightGreen = 0xFFC8E6C9.toInt() // beantwortet (nicht-Fragebogen)
val lightRed = 0xFFFFCDD2.toInt() // unbeantwortet (nicht-Fragebogen)
val doneGreen = 0xFF4CAF50.toInt() // Fragebogen: Done (BLEIBT)
val notRed = 0xFFF44336.toInt() // Fragebogen: Not Done (BLEIBT)
// Tabelle 2 (Header-Liste)
val orderedIds = loadOrderedIds()
orderedIds.forEachIndexed { idx, id ->
var rowBgColor: Int? = null
val darkGray = 0xFFBDBDBD.toInt()
val raw: String
val bgColorForCells: Int?
val cellBgForQuestionnaire: Int?
if (id == "client_code") {
// client_code bleibt unmarkiert
raw = clientCode
bgColorForCells = null
cellBgForQuestionnaire = null
} else if (id in questionnaireIdSet) {
// Fragebogenstatus: wie bisher grün/rot NUR für #, ID, Wert
raw = if (statusMap[id] == true) "Done" else "Not Done"
bgColorForCells = if (raw == "Done") 0xFF4CAF50.toInt() else 0xFFF44336.toInt()
cellBgForQuestionnaire = if (raw == "Done") doneGreen else notRed
} else {
// Normale Frage:
raw = answerMap[id]?.takeIf { it.isNotBlank() } ?: "None"
bgColorForCells = null
if (raw == "None") rowBgColor = darkGray
cellBgForQuestionnaire = null
// NEU: hellgrün wenn beantwortet, hellrot wenn None
rowBgColor = if (raw == "None") lightRed else lightGreen
}
val display = localizeHeaderValue(id, raw)
val cellBg = if (bgColorForCells != null)
mapOf(0 to bgColorForCells, 1 to bgColorForCells, 2 to bgColorForCells)
else emptyMap()
// Für Fragebögen: "#"(0), ID(1), Wert(2) farbig hinterlegen wie gehabt
val cellBgOverrides =
if (cellBgForQuestionnaire != null)
mapOf(0 to cellBgForQuestionnaire, 1 to cellBgForQuestionnaire, 2 to cellBgForQuestionnaire)
else emptyMap()
addRow(
table = tableOrdered,
cells = listOf((idx + 1).toString(), id, display),
colorOverrides = emptyMap(),
rowBgColor = rowBgColor,
cellBgOverrides = cellBg
rowBgColor = rowBgColor, // greift für normale Fragen (ganze Zeile inkl. #)
cellBgOverrides = cellBgOverrides // greift für Fragebögen (nur 3 Zellen)
)
}
}
@ -388,10 +390,20 @@ class DatabaseButtonHandler(
}
// ---------------------------
// assets/header_order.json laden (mit Cache)
// ordered_ids aus XLSX/JSON laden (mit Cache)
// ---------------------------
private fun loadOrderedIds(): List<String> {
orderedIdsCache?.let { return it }
val fromXlsx = runCatching { loadOrderedIdsFromExcel("header_order.xlsx") }
.onFailure { e -> Log.w(tag, "header_order.xlsx konnte nicht gelesen werden: ${e.message}") }
.getOrElse { emptyList() }
if (fromXlsx.isNotEmpty()) {
orderedIdsCache = fromXlsx
return fromXlsx
}
return try {
val stream = activity.assets.open("header_order.json")
val json = stream.readBytes().toString(Charset.forName("UTF-8"))
@ -400,12 +412,49 @@ class DatabaseButtonHandler(
orderedIdsCache = list
list
} catch (e: Exception) {
Log.e(tag, "header_order.json konnte nicht geladen werden: ${e.message}")
Toast.makeText(activity, "header_order.json fehlt oder ist ungültig", Toast.LENGTH_LONG).show()
Log.e(tag, "Weder header_order.xlsx noch header_order.json verfügbar/gültig: ${e.message}")
Toast.makeText(activity, "Keine Header-Vorlage gefunden", Toast.LENGTH_LONG).show()
emptyList()
}
}
private fun loadOrderedIdsFromExcel(assetFileName: String): List<String> {
activity.assets.open(assetFileName).use { input ->
XSSFWorkbook(input).use { wb ->
val sheet = wb.getSheetAt(0) ?: return emptyList()
val row = sheet.getRow(0) ?: return emptyList()
val first = row.firstCellNum.toInt()
val last = row.lastCellNum.toInt() // exklusiv
val out = mutableListOf<String>()
for (i in first until last) {
val cell = row.getCell(i) ?: continue
val value = getCellAsString(cell).trim()
if (value.isEmpty()) continue
if (i == first && value == "#") continue // „#“ in Spalte 0 ignorieren
out.add(value)
}
return out
}
}
}
private fun getCellAsString(cell: org.apache.poi.ss.usermodel.Cell): String =
when (cell.cellType) {
org.apache.poi.ss.usermodel.CellType.STRING -> cell.stringCellValue
org.apache.poi.ss.usermodel.CellType.NUMERIC ->
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell))
cell.dateCellValue.time.toString()
else {
val n = cell.numericCellValue
if (n % 1.0 == 0.0) n.toLong().toString() else n.toString()
}
org.apache.poi.ss.usermodel.CellType.BOOLEAN -> cell.booleanCellValue.toString()
org.apache.poi.ss.usermodel.CellType.FORMULA -> cell.richStringCellValue.string
else -> ""
}
// ---------------------------
// Hilfsfunktionen (Lokalisierung & UI)
// ---------------------------
@ -468,6 +517,9 @@ class DatabaseButtonHandler(
return raw
}
// ---------------------------
// UI-Helfer
// ---------------------------
private fun addHeaderRow(table: TableLayout, labels: List<String>) {
val row = TableRow(activity)
labels.forEach { label -> row.addView(makeHeaderCell(label)) }