diff --git a/app/src/main/assets/header_order.json b/app/src/main/assets/header_order.json deleted file mode 100644 index 31c0c8e..0000000 --- a/app/src/main/assets/header_order.json +++ /dev/null @@ -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" -] diff --git a/app/src/main/assets/header_order.xlsx b/app/src/main/assets/header_order.xlsx new file mode 100644 index 0000000..207c3af Binary files /dev/null and b/app/src/main/assets/header_order.xlsx differ diff --git a/app/src/main/java/com/dano/test1/DatabaseButtonHandler.kt b/app/src/main/java/com/dano/test1/DatabaseButtonHandler.kt index 7c30611..2109144 100644 --- a/app/src/main/java/com/dano/test1/DatabaseButtonHandler.kt +++ b/app/src/main/java/com/dano/test1/DatabaseButtonHandler.kt @@ -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? = 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 { - // 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 { 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 { + 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() + + 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) { val row = TableRow(activity) labels.forEach { label -> row.addView(makeHeaderCell(label)) }