überarbeiten der datenbank anzeige

This commit is contained in:
oxidiert
2025-09-23 15:17:34 +02:00
parent 8dc9be20a4
commit 91f6f77b73
3 changed files with 58 additions and 65 deletions

View File

@ -19,7 +19,6 @@ class DatabaseButtonHandler(
private val uiScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val uiScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val tag = "DatabaseButtonHandler" private val tag = "DatabaseButtonHandler"
// Services (wie bei dir)
private val headerRepo = HeaderOrderRepository(activity) private val headerRepo = HeaderOrderRepository(activity)
private val exporter = ExcelExportService(activity, headerRepo) private val exporter = ExcelExportService(activity, headerRepo)
@ -34,16 +33,15 @@ class DatabaseButtonHandler(
private fun openDatabaseScreen() { private fun openDatabaseScreen() {
activity.setContentView(R.layout.database_screen) activity.setContentView(R.layout.database_screen)
val lang = safeLang() // ✨ val lang = safeLang()
val titleTv: TextView = requireView(R.id.title, "title") // ✨ val titleTv: TextView = requireView(R.id.title, "title")
val table: TableLayout = requireView(R.id.tableClients, "tableClients") val table: TableLayout = requireView(R.id.tableClients, "tableClients")
val progress: ProgressBar = requireView(R.id.progressBar, "progressBar") val progress: ProgressBar = requireView(R.id.progressBar, "progressBar")
val emptyView: TextView = requireView(R.id.emptyView, "emptyView") val emptyView: TextView = requireView(R.id.emptyView, "emptyView")
val backButton: Button = requireView(R.id.backButton, "backButton") val backButton: Button = requireView(R.id.backButton, "backButton")
val btnDownloadHeader: Button = requireView(R.id.btnDownloadHeader, "btnDownloadHeader") val btnDownloadHeader: Button = requireView(R.id.btnDownloadHeader, "btnDownloadHeader")
// ✨ statische Texte lokalisieren
titleTv.text = t(lang, "database_clients_title") ?: "Datenbank Clients" titleTv.text = t(lang, "database_clients_title") ?: "Datenbank Clients"
emptyView.text = t(lang, "no_clients_available") ?: "Keine Clients vorhanden." emptyView.text = t(lang, "no_clients_available") ?: "Keine Clients vorhanden."
backButton.text = t(lang, "previous") ?: "Zurück" backButton.text = t(lang, "previous") ?: "Zurück"
@ -56,7 +54,6 @@ class DatabaseButtonHandler(
emptyView.visibility = View.GONE emptyView.visibility = View.GONE
table.removeAllViews() table.removeAllViews()
// ✨ Spaltenkopf lokalisieren
addHeaderRow(table, listOf("#", t(lang, "client_code") ?: "Client-Code")) addHeaderRow(table, listOf("#", t(lang, "client_code") ?: "Client-Code"))
uiScope.launch { uiScope.launch {
@ -91,7 +88,7 @@ class DatabaseButtonHandler(
val savedUri = exporter.exportHeadersForAllClients() val savedUri = exporter.exportHeadersForAllClients()
progress.visibility = View.GONE progress.visibility = View.GONE
val lang = safeLang() // ✨ val lang = safeLang()
if (savedUri != null) { if (savedUri != null) {
Toast.makeText( Toast.makeText(
activity, activity,
@ -115,7 +112,7 @@ class DatabaseButtonHandler(
private fun openClientOverviewScreen(clientCode: String) { private fun openClientOverviewScreen(clientCode: String) {
activity.setContentView(R.layout.client_overview_screen) activity.setContentView(R.layout.client_overview_screen)
val lang = safeLang() // ✨ val lang = safeLang()
val title: TextView = requireView(R.id.titleClientOverview, "titleClientOverview") val title: TextView = requireView(R.id.titleClientOverview, "titleClientOverview")
val tableQ: TableLayout = requireView(R.id.tableQuestionnaires, "tableQuestionnaires") val tableQ: TableLayout = requireView(R.id.tableQuestionnaires, "tableQuestionnaires")
@ -126,7 +123,6 @@ class DatabaseButtonHandler(
val headerLabel: TextView = requireView(R.id.headerLabel, "headerLabel") val headerLabel: TextView = requireView(R.id.headerLabel, "headerLabel")
val tableOrdered: TableLayout = requireView(R.id.tableOrdered, "tableOrdered") val tableOrdered: TableLayout = requireView(R.id.tableOrdered, "tableOrdered")
// ✨
title.text = "${t(lang, "client") ?: "Client"}: $clientCode ${t(lang, "questionnaires") ?: "Fragebögen"}" title.text = "${t(lang, "client") ?: "Client"}: $clientCode ${t(lang, "questionnaires") ?: "Fragebögen"}"
headerLabel.text = t(lang, "headers") ?: "header" headerLabel.text = t(lang, "headers") ?: "header"
backButton.text = t(lang, "previous") ?: "Zurück" backButton.text = t(lang, "previous") ?: "Zurück"
@ -137,7 +133,6 @@ class DatabaseButtonHandler(
tableQ.removeAllViews() tableQ.removeAllViews()
tableOrdered.removeAllViews() tableOrdered.removeAllViews()
// ✨ Tabellenköpfe lokalisieren
addHeaderRow( addHeaderRow(
tableQ, tableQ,
listOf("#", t(lang, "questionnaire_id") ?: "Fragebogen-ID", t(lang, "status") ?: "Status") listOf("#", t(lang, "questionnaire_id") ?: "Fragebogen-ID", t(lang, "status") ?: "Status")
@ -149,46 +144,53 @@ class DatabaseButtonHandler(
uiScope.launch { uiScope.launch {
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
val allQuestionnaires = MyApp.database.questionnaireDao().getAll() val allQuestionnairesDb = MyApp.database.questionnaireDao().getAll()
val completedForClient = MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode) val completedForClient = MyApp.database.completedQuestionnaireDao().getAllForClient(clientCode)
val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode) val allAnswersForClient = MyApp.database.answerDao().getAnswersForClient(clientCode)
Triple(allQuestionnaires, completedForClient, allAnswersForClient) Triple(allQuestionnairesDb, completedForClient, allAnswersForClient)
} }
// IDs aus der JSON-Reihenfolge lesen (alle, die es geben soll)
val idsFromAssets: List<String> = loadQuestionnaireIdsFromAssets()
progress.visibility = View.GONE progress.visibility = View.GONE
val allQuestionnaires: List<Questionnaire> = result.first val dbQuestionnaires: List<Questionnaire> = result.first
val completedForClient = result.second val completedForClient = result.second
val allAnswersForClient = result.third val allAnswersForClient = result.third
if (allQuestionnaires.isEmpty()) { // Vereinigung: alles was in JSON steht + alles was in der DB existiert
val allIds: List<String> =
(idsFromAssets + dbQuestionnaires.map { it.id }).distinct()
if (allIds.isEmpty()) {
emptyView.text = t(lang, "no_questionnaires") ?: "Keine Fragebögen vorhanden." emptyView.text = t(lang, "no_questionnaires") ?: "Keine Fragebögen vorhanden."
emptyView.visibility = View.VISIBLE emptyView.visibility = View.VISIBLE
} }
val statusMap = completedForClient.associate { it.questionnaireId to it.isDone } val statusMap = completedForClient.associate { it.questionnaireId to it.isDone }
val questionnaireIdSet = allQuestionnaires.map { it.id }.toSet() val questionnaireIdSet = allIds.toSet() // für die zweite Tabelle
val answerMap = allAnswersForClient.associate { it.questionId to it.answerValue } val answerMap = allAnswersForClient.associate { it.questionId to it.answerValue }
// Tabelle 1 (Status) // Tabelle 1 (Status) JETZT mit allen IDs
allQuestionnaires allIds
.sortedWith(compareBy({ extractQuestionnaireNumber(it.id) ?: Int.MAX_VALUE }, { it.id })) .sortedWith(compareBy({ extractQuestionnaireNumber(it) ?: Int.MAX_VALUE }, { it }))
.forEachIndexed { idx, q -> .forEachIndexed { idx, qid ->
val isDone = statusMap[q.id] ?: false val isDone = statusMap[qid] ?: false
val statusText = if (isDone) "" else "" val statusText = if (isDone) "" else ""
val statusTextColor = if (isDone) 0xFF4CAF50.toInt() else 0xFFF44336.toInt() val statusTextColor = if (isDone) 0xFF4CAF50.toInt() else 0xFFF44336.toInt()
if (isDone) { if (isDone) {
addClickableRow( addClickableRow(
table = tableQ, table = tableQ,
cells = listOf((idx + 1).toString(), q.id, statusText), cells = listOf((idx + 1).toString(), qid, statusText),
onClick = { openQuestionnaireDetailScreen(clientCode, q.id) }, onClick = { openQuestionnaireDetailScreen(clientCode, qid) },
colorOverrides = mapOf(2 to statusTextColor) colorOverrides = mapOf(2 to statusTextColor)
) )
} else { } else {
addDisabledRow( addDisabledRow(
table = tableQ, table = tableQ,
cells = listOf((idx + 1).toString(), q.id, statusText), cells = listOf((idx + 1).toString(), qid, statusText),
colorOverrides = mapOf(2 to statusTextColor) colorOverrides = mapOf(2 to statusTextColor)
) )
} }
@ -219,7 +221,7 @@ class DatabaseButtonHandler(
rowBgColor = if (raw == "None") lightRed else lightGreen rowBgColor = if (raw == "None") lightRed else lightGreen
} }
val display = localizeHeaderValue(id, raw, lang) // ✨ val display = localizeHeaderValue(id, raw, lang)
val cellBgOverrides = val cellBgOverrides =
if (cellBgForQuestionnaire != null) if (cellBgForQuestionnaire != null)
@ -242,7 +244,7 @@ class DatabaseButtonHandler(
private fun openQuestionnaireDetailScreen(clientCode: String, questionnaireId: String) { private fun openQuestionnaireDetailScreen(clientCode: String, questionnaireId: String) {
activity.setContentView(R.layout.questionnaire_detail_screen) activity.setContentView(R.layout.questionnaire_detail_screen)
val lang = safeLang() // ✨ val lang = safeLang()
val title: TextView = requireView(R.id.titleQuestionnaireDetail, "titleQuestionnaireDetail") val title: TextView = requireView(R.id.titleQuestionnaireDetail, "titleQuestionnaireDetail")
val table: TableLayout = requireView(R.id.tableQA, "tableQA") val table: TableLayout = requireView(R.id.tableQA, "tableQA")
@ -250,7 +252,6 @@ class DatabaseButtonHandler(
val emptyView: TextView = requireView(R.id.emptyViewQA, "emptyViewQA") val emptyView: TextView = requireView(R.id.emptyViewQA, "emptyViewQA")
val backButton: Button = requireView(R.id.backButtonQA, "backButtonQA") val backButton: Button = requireView(R.id.backButtonQA, "backButtonQA")
// ✨ Titel + Back + Empty übersetzen
title.text = "${t(lang, "client") ?: "Client"}: $clientCode ${t(lang, "questionnaire") ?: "Fragebogen"}: $questionnaireId" title.text = "${t(lang, "client") ?: "Client"}: $clientCode ${t(lang, "questionnaire") ?: "Fragebogen"}: $questionnaireId"
backButton.text = t(lang, "previous") ?: "Zurück" backButton.text = t(lang, "previous") ?: "Zurück"
emptyView.text = t(lang, "no_questions_available") ?: "Keine Fragen vorhanden." emptyView.text = t(lang, "no_questions_available") ?: "Keine Fragen vorhanden."
@ -260,7 +261,6 @@ class DatabaseButtonHandler(
emptyView.visibility = View.GONE emptyView.visibility = View.GONE
table.removeAllViews() table.removeAllViews()
// ✨ Kopf: # | Frage | Antwort
addHeaderRow(table, listOf("#", t(lang, "question") ?: "Frage", t(lang, "answer") ?: "Antwort")) addHeaderRow(table, listOf("#", t(lang, "question") ?: "Frage", t(lang, "answer") ?: "Antwort"))
uiScope.launch { uiScope.launch {
@ -281,17 +281,17 @@ class DatabaseButtonHandler(
val answerMap = answersForClient.associate { it.questionId to it.answerValue } val answerMap = answersForClient.associate { it.questionId to it.answerValue }
questions.forEachIndexed { idx, q: Question -> questions.forEachIndexed { idx, q: Question ->
val baseId = q.questionId.substringAfterLast('-', q.questionId) // z.B. consent_signed, gender_male val baseId = q.questionId.substringAfterLast('-', q.questionId)
val qText = localizeQuestionLabel(q.questionId, q.question, lang) // ✨ val qText = localizeQuestionLabel(q.questionId, q.question, lang)
val raw = answerMap[q.questionId]?.takeIf { it.isNotBlank() } ?: "" val raw = answerMap[q.questionId]?.takeIf { it.isNotBlank() } ?: ""
val aText = localizeAnswerValue(baseId, raw, lang) // ✨ val aText = localizeAnswerValue(baseId, raw, lang)
addRow(table, listOf((idx + 1).toString(), qText, aText)) addRow(table, listOf((idx + 1).toString(), qText, aText))
} }
} }
} }
// --------------------------- // ---------------------------
// Lokalisierungshilfen (Anzeige) // Hilfen
// --------------------------- // ---------------------------
private fun safeLang(): String = try { languageIDProvider() } catch (_: Exception) { "GERMAN" } private fun safeLang(): String = try { languageIDProvider() } catch (_: Exception) { "GERMAN" }
@ -307,38 +307,27 @@ class DatabaseButtonHandler(
return if (out.equals(key, true) || out.isBlank()) null else out return if (out.equals(key, true) || out.isBlank()) null else out
} }
// ✨ Frage-Label für die UI
private fun localizeQuestionLabel(questionId: String, fallbackQuestionText: String?, lang: String): String { private fun localizeQuestionLabel(questionId: String, fallbackQuestionText: String?, lang: String): String {
val field = questionId.substringAfterLast('-', questionId) val field = questionId.substringAfterLast('-', questionId)
t(lang, field)?.let { return it } // z.B. consent_instruction t(lang, field)?.let { return it }
t(lang, questionId)?.let { return it } // kompletter key t(lang, questionId)?.let { return it }
fallbackQuestionText?.takeIf { it.isNotBlank() }?.let { return it } fallbackQuestionText?.takeIf { it.isNotBlank() }?.let { return it }
return field.replace('_', ' ').replaceFirstChar { it.titlecase() } return field.replace('_', ' ').replaceFirstChar { it.titlecase() }
} }
// ✨ Antwort-Wert für die UI
private fun localizeAnswerValue(fieldId: String, raw: String, lang: String): String { private fun localizeAnswerValue(fieldId: String, raw: String, lang: String): String {
// Zahlen/Datum/Strich beibehalten
if (raw == "") return raw if (raw == "") return raw
if (raw.matches(Regex("^\\d{1,4}([./-]\\d{1,2}([./-]\\d{1,4})?)?\$"))) return raw if (raw.matches(Regex("^\\d{1,4}([./-]\\d{1,2}([./-]\\d{1,4})?)?\$"))) return raw
// 1) direkter Key
t(lang, raw)?.let { return it } t(lang, raw)?.let { return it }
// 2) normalisiert
val norm = raw.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_') val norm = raw.lowercase().replace(Regex("[^a-z0-9]+"), "_").trim('_')
if (norm.isNotBlank()) t(lang, norm)?.let { return it } if (norm.isNotBlank()) t(lang, norm)?.let { return it }
// 3) kombiniere Feld + Wert (häufiges Muster)
if (norm.isNotBlank()) { if (norm.isNotBlank()) {
t(lang, "${fieldId}_$norm")?.let { return it } t(lang, "${fieldId}_$norm")?.let { return it }
t(lang, "${fieldId}-${norm}")?.let { return it } t(lang, "${fieldId}-${norm}")?.let { return it }
} }
// Klammern entfernen, falls aus LanguageManager so geliefert
return stripBrackets(raw) return stripBrackets(raw)
} }
// ---------------------------
// Anzeige-Helfer
// ---------------------------
private fun addHeaderRow(table: TableLayout, labels: List<String>) { private fun addHeaderRow(table: TableLayout, labels: List<String>) {
val row = TableRow(activity) val row = TableRow(activity)
labels.forEach { label -> row.addView(makeHeaderCell(label)) } labels.forEach { label -> row.addView(makeHeaderCell(label)) }
@ -442,7 +431,6 @@ class DatabaseButtonHandler(
return m?.groupValues?.get(1)?.toIntOrNull() return m?.groupValues?.get(1)?.toIntOrNull()
} }
// ✨ alte localizeHeaderValue erweitert: nimmt jetzt lang mit
private fun localizeHeaderValue(id: String, raw: String, lang: String): String { private fun localizeHeaderValue(id: String, raw: String, lang: String): String {
if (id == "client_code") return raw if (id == "client_code") return raw
@ -457,4 +445,17 @@ class DatabaseButtonHandler(
for (k in candidates) t(lang, k)?.let { return it } for (k in candidates) t(lang, k)?.let { return it }
return stripBrackets(raw) return stripBrackets(raw)
} }
/** Lädt alle Fragebogen-IDs aus questionnaire_order.json (file ohne .json). */
private fun loadQuestionnaireIdsFromAssets(): List<String> = try {
val input = activity.assets.open("questionnaire_order.json")
val json = input.bufferedReader().use { it.readText() }
val arr = JSONArray(json)
(0 until arr.length()).mapNotNull { i ->
val obj = arr.optJSONObject(i)
obj?.optString("file")?.removeSuffix(".json")
}
} catch (_: Exception) {
emptyList()
}
} }

View File

@ -649,8 +649,8 @@ object LanguageManager {
"please_client_code" to "Please enter client code", "please_client_code" to "Please enter client code",
"no_profile" to "This client is not yet part of the database", "no_profile" to "This client is not yet part of the database",
"questionnaires_finished" to "All questionnaires have been completed for this client!", "questionnaires_finished" to "All questionnaires have been completed for this client!",
"edit" to "Bearbeiten", "edit" to "Edit",
"save" to "Speichern", "save" to "Save",
"upload" to "Upload", "upload" to "Upload",
"download" to "Download", "download" to "Download",
"database" to "Database", "database" to "Database",

View File

@ -15,26 +15,19 @@
android:textSize="20sp" android:textSize="20sp"
android:paddingBottom="8dp" /> android:paddingBottom="8dp" />
<!-- Tabelle 1: Fragebögen (✓/✗) --> <!-- OBERTEIL: Alle Fragebögen vollständig (kein Weight, keine vertikale ScrollView) -->
<HorizontalScrollView <HorizontalScrollView
android:id="@+id/qsScroll"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_weight="1"
android:fillViewport="true" android:fillViewport="true"
android:scrollbars="horizontal"> android:scrollbars="horizontal">
<ScrollView <TableLayout
android:id="@+id/tableQuestionnaires"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:fillViewport="true" android:stretchColumns="1" />
android:scrollbars="vertical">
<TableLayout
android:id="@+id/tableQuestionnaires"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:stretchColumns="1" />
</ScrollView>
</HorizontalScrollView> </HorizontalScrollView>
<TextView <TextView
@ -54,7 +47,7 @@
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingBottom="8dp" /> android:paddingBottom="8dp" />
<!-- HEADER + Tabelle 2: geordnete IDs/Werte --> <!-- HEADER + Tabelle 2: Scrollbar bekommt den restlichen Platz -->
<TextView <TextView
android:id="@+id/headerLabel" android:id="@+id/headerLabel"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -89,7 +82,6 @@
<Button <Button
android:id="@+id/backButtonClient" android:id="@+id/backButtonClient"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content" />
android:text="Zurück" />
</LinearLayout> </LinearLayout>