feat(frontend): Struktur und Kommentare verfeinert, Mail-Service-Konfiguration erweitert

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-04-15 11:49:26 +02:00
parent 8c804832d8
commit d0b756694b
13 changed files with 682 additions and 284 deletions
@@ -1,6 +1,8 @@
package at.mocode.frontend.core.network
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
expect object PlatformConfig {
fun resolveApiBaseUrl(): String
fun resolveMailServiceUrl(): String
fun resolveKeycloakUrl(): String
}
@@ -36,6 +36,18 @@ actual object PlatformConfig {
return fallbackUrl
}
actual fun resolveMailServiceUrl(): String {
val fromGlobal = try {
(globalScope.MAIL_SERVICE_URL as? String)?.trim().orEmpty()
} catch (_: dynamic) {
""
}
if (fromGlobal.isNotEmpty()) {
return fromGlobal.removeSuffix("/")
}
return "http://localhost:8085"
}
actual fun resolveKeycloakUrl(): String {
// 1) Prefer a global JS variable (injected by main.kt via AppConfig)
val fromGlobal = try {
@@ -10,6 +10,12 @@ actual object PlatformConfig {
return "http://localhost:8081"
}
actual fun resolveMailServiceUrl(): String {
val env = System.getenv("MAIL_SERVICE_URL")?.trim().orEmpty()
if (env.isNotEmpty()) return env.removeSuffix("/")
return "http://localhost:8085"
}
actual fun resolveKeycloakUrl(): String {
val env = System.getenv("KEYCLOAK_URL")?.trim().orEmpty()
if (env.isNotEmpty()) return env.removeSuffix("/")
@@ -6,6 +6,12 @@ package at.mocode.frontend.core.network
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
actual object PlatformConfig {
actual fun resolveMailServiceUrl(): String {
val fromGlobal = getGlobalMailServiceUrl()
if (fromGlobal.isNotEmpty()) return fromGlobal.removeSuffix("/")
return "http://localhost:8085"
}
actual fun resolveKeycloakUrl(): String {
val fromGlobal = getGlobalKeycloakUrl()
if (fromGlobal.isNotEmpty()) return fromGlobal.removeSuffix("/")
@@ -47,6 +53,16 @@ private fun getGlobalApiBaseUrl(): String = js(
"""
)
@OptIn(ExperimentalWasmJsInterop::class)
private fun getGlobalMailServiceUrl(): String = js(
"""
(function() {
var global = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {}));
return (global.MAIL_SERVICE_URL && typeof global.MAIL_SERVICE_URL === 'string') ? global.MAIL_SERVICE_URL : "";
})()
"""
)
@OptIn(ExperimentalWasmJsInterop::class)
private fun getGlobalKeycloakUrl(): String = js(
"""
@@ -41,6 +41,7 @@ kotlin {
commonMain.dependencies {
implementation(projects.frontend.core.designSystem)
implementation(projects.frontend.core.domain)
implementation(projects.frontend.core.network)
implementation(libs.kotlinx.datetime)
implementation(compose.foundation)
@@ -1,52 +1,52 @@
package at.mocode.frontend.features.nennung.domain
import at.mocode.frontend.core.network.PlatformConfig
import at.mocode.frontend.features.nennung.presentation.web.NennungPayload
import io.ktor.client.HttpClient
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.serialization.Serializable
@Serializable
data class NennungApiRequest(
val turnierNr: String,
val vorname: String,
val nachname: String,
val lizenz: String,
val pferdName: String,
val pferdAlter: String,
val email: String,
val telefon: String?,
val bewerbe: String,
val bemerkungen: String?
val turnierNr: String,
val vorname: String,
val nachname: String,
val lizenz: String,
val pferdName: String,
val pferdAlter: String,
val email: String,
val telefon: String?,
val bewerbe: String,
val bemerkungen: String?
)
class NennungRemoteRepository(private val client: HttpClient) {
suspend fun sendeNennung(turnierNr: String, payload: NennungPayload): Result<Unit> {
return try {
val request = NennungApiRequest(
turnierNr = turnierNr,
vorname = payload.vorname,
nachname = payload.nachname,
lizenz = payload.lizenz,
pferdName = payload.pferdName,
pferdAlter = payload.pferdAlter,
email = payload.email,
telefon = payload.telefon,
bewerbe = payload.bewerbe.joinToString(", ") { it.nr.toString() },
bemerkungen = payload.bemerkungen
)
private val mailServiceUrl = PlatformConfig.resolveMailServiceUrl()
// Wir senden direkt an den mail-service (Port 8085)
// In einer Prod-Umgebung würde dies über das Gateway laufen.
client.post("http://localhost:8085/api/mail/nennung") {
contentType(ContentType.Application.Json)
setBody(request)
}
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
suspend fun sendeNennung(turnierNr: String, payload: NennungPayload): Result<Unit> {
return try {
val request = NennungApiRequest(
turnierNr = turnierNr,
vorname = payload.vorname,
nachname = payload.nachname,
lizenz = payload.lizenz,
pferdName = payload.pferdName,
pferdAlter = payload.pferdAlter,
email = payload.email,
telefon = payload.telefon,
bewerbe = payload.bewerbe.joinToString(", ") { it.nr.toString() },
bemerkungen = payload.bemerkungen
)
// Wir senden an den mail-service (URL dynamisch aufgelöst)
client.post("$mailServiceUrl/api/mail/nennung") {
contentType(ContentType.Application.Json)
setBody(request)
}
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
}
@@ -49,7 +49,8 @@ fun StammdatenTabContent(
var znsDataLoaded by remember { mutableStateOf(false) }
var znsPayloadVersion by remember { mutableStateOf<String?>(null) }
var znsImportedAt by remember { mutableStateOf<String?>(null) }
val znsImportHistory = remember { mutableStateListOf<Triple<String, String, Boolean>>() } // (source, payloadVersion, ok)
val znsImportHistory =
remember { mutableStateListOf<Triple<String, String, Boolean>>() } // (source, payloadVersion, ok)
var typ by remember { mutableStateOf("ÖTO (National)") }
val sparten = remember { mutableStateListOf<String>() }
@@ -63,10 +64,10 @@ fun StammdatenTabContent(
var titel by remember { mutableStateOf("") }
var subTitel by remember { mutableStateOf("") }
// Initialisierung aus Mock-Store (StoreV2/TurnierStoreV2) falls vorhanden
// Initialisierung aus Mock-Store (`StoreV2/TurnierStoreV2`) falls vorhanden
LaunchedEffect(turnierId) {
// Da wir in einem anderen Modul sind, können wir nicht direkt auf StoreV2 zugreifen
// ohne die Abhängigkeit zu haben. In einer echten Architektur käme dies über das Repository.
// Da wir in einem anderen Modul sind, können wir nicht direkt auf StoreV2 zugreifen,
// ohne die Abhängigkeit zu haben. In einer echten Architektur kommt dies über das Repository.
// Aber für die Demo/Fakten-Präsentation im Desktop-Shell-Kontext:
try {
val clazz = Class.forName("at.mocode.desktop.v2.TurnierStoreV2")
@@ -76,37 +77,39 @@ fun StammdatenTabContent(
val idField = t!!::class.java.getDeclaredField("turnierNr")
idField.isAccessible = true
idField.get(t).toString() == turnierId.toString() ||
t.hashCode().toLong() == turnierId // Fallback falls ID anders gemappt ist
t.hashCode().toLong() == turnierId // Fallback, falls die ID anders gemappt ist
}
if (turnier != null) {
val tClass = turnier::class.java
when {
turnier != null -> {
val tClass = turnier::class.java
val nrField = tClass.getDeclaredField("turnierNr")
nrField.isAccessible = true
turnierNr = nrField.get(turnier).toString()
nrConfirmed = true
val nrField = tClass.getDeclaredField("turnierNr")
nrField.isAccessible = true
turnierNr = nrField.get(turnier).toString()
nrConfirmed = true
val titelField = tClass.getDeclaredField("titel")
titelField.isAccessible = true
titel = titelField.get(turnier) as String
val titelField = tClass.getDeclaredField("titel")
titelField.isAccessible = true
titel = titelField.get(turnier) as String
val subField = tClass.getDeclaredField("subTitel")
subField.isAccessible = true
subTitel = subField.get(turnier) as String
val subField = tClass.getDeclaredField("subTitel")
subField.isAccessible = true
subTitel = subField.get(turnier) as String
val katField = tClass.getDeclaredField("kategorie")
katField.isAccessible = true
val kats = katField.get(turnier) as? List<String>
kats?.let { kat.addAll(it) }
val katField = tClass.getDeclaredField("kategorie")
katField.isAccessible = true
val kats = katField.get(turnier) as? List<String>
kats?.let { kat.addAll(it) }
val typField = tClass.getDeclaredField("typ")
typField.isAccessible = true
typ = typField.get(turnier) as String
val typField = tClass.getDeclaredField("typ")
typField.isAccessible = true
typ = typField.get(turnier) as String
val znsField = tClass.getDeclaredField("znsDataLoaded")
znsField.isAccessible = true
znsDataLoaded = znsField.get(turnier) as Boolean
val znsField = tClass.getDeclaredField("znsDataLoaded")
znsField.isAccessible = true
znsDataLoaded = znsField.get(turnier) as Boolean
}
}
} catch (_: Exception) {
// Reflection fehlgeschlagen oder Store nicht erreichbar -> Fallback auf leere Felder
@@ -118,7 +121,7 @@ fun StammdatenTabContent(
var showZnsDialog by remember { mutableStateOf(false) }
var showZnsLog by remember { mutableStateOf(false) }
// Hilfs-States für DatePicker
// Hilf's-States für DatePicker
var showDatePickerVon by remember { mutableStateOf(false) }
var showDatePickerBis by remember { mutableStateOf(false) }
@@ -143,29 +146,35 @@ fun StammdatenTabContent(
singleLine = true,
enabled = !nrConfirmed
)
if (!nrConfirmed) {
Button(
onClick = { showNrConfirm = true },
enabled = turnierNr.length == 5,
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue)
) {
Text("Bestätigen")
when {
!nrConfirmed -> {
Button(
onClick = { showNrConfirm = true },
enabled = turnierNr.length == 5,
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue)
) {
Text("Bestätigen")
}
}
else -> {
InputChip(
selected = true,
onClick = { },
label = { Text("Bestätigt") },
trailingIcon = { Icon(Icons.Default.Check, contentDescription = null, modifier = Modifier.size(16.dp)) }
)
}
} else {
InputChip(
selected = true,
onClick = { },
label = { Text("Bestätigt") },
trailingIcon = { Icon(Icons.Default.Check, contentDescription = null, modifier = Modifier.size(16.dp)) }
)
}
}
if (turnierNr.length == 5 && !nrConfirmed) {
Text(
"Bitte Turnier-Nummer bestätigen um fortzufahren.",
color = MaterialTheme.colorScheme.error,
fontSize = 11.sp
)
when (turnierNr.length) {
5 if !nrConfirmed -> {
Text(
"Bitte Turnier-Nummer bestätigen um fortzufahren.",
color = MaterialTheme.colorScheme.error,
fontSize = 11.sp
)
}
}
}
@@ -190,8 +199,7 @@ fun StammdatenTabContent(
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
Button(
onClick = { showZnsDialog = true },
colors = ButtonDefaults.buttonColors(containerColor = AccentBlue)
, enabled = nrConfirmed
colors = ButtonDefaults.buttonColors(containerColor = AccentBlue), enabled = nrConfirmed
) {
Icon(Icons.Default.CloudDownload, contentDescription = null, modifier = Modifier.size(18.dp))
Spacer(Modifier.width(8.dp))
@@ -254,7 +262,7 @@ fun StammdatenTabContent(
}
FormRow("Klasse:") {
val klassenListe = listOf("C-NEU", "C", "B", "A", "L", "LM", "M", "S")
val klassenListe = listOf("C-NEU", "C", "B", "A")
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
klassenListe.forEach { k ->
FilterChip(
@@ -278,32 +286,37 @@ fun StammdatenTabContent(
}
}
if (suggested.isEmpty()) {
Text("Bitte Sparte und Klasse wählen", color = Color.Gray, fontSize = 13.sp)
} else {
// Gruppiere nach Sparte (CDN/CSN)
val grouped = suggested.groupBy { if (it.startsWith("CDN")) "Dressur" else "Springen" }
grouped.forEach { (gruppe, eintraege) ->
Text(gruppe, fontWeight = FontWeight.SemiBold, color = PrimaryBlue)
Spacer(Modifier.height(4.dp))
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
eintraege.sorted().forEach { c ->
InputChip(
selected = kat.contains(c),
onClick = { if (kat.contains(c)) kat.remove(c) else kat.add(c) },
enabled = nrConfirmed,
label = { Text(c) }
)
when {
suggested.isEmpty() -> {
Text("Bitte Sparte und Klasse wählen", color = Color.Gray, fontSize = 13.sp)
}
else -> {
// Gruppiere nach Sparte (CDN/CSN)
val grouped = suggested.groupBy { if (it.startsWith("CDN")) "Dressur" else "Springen" }
grouped.forEach { (gruppe, eintraege) ->
Text(gruppe, fontWeight = FontWeight.SemiBold, color = PrimaryBlue)
Spacer(Modifier.height(4.dp))
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
eintraege.sorted().forEach { c ->
InputChip(
selected = kat.contains(c),
onClick = { if (kat.contains(c)) kat.remove(c) else kat.add(c) },
enabled = nrConfirmed,
label = { Text(c) }
)
}
}
Spacer(Modifier.height(8.dp))
}
Spacer(Modifier.height(8.dp))
}
}
}
FormRow("Zeitraum:") {
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
val vonMod = if (nrConfirmed) Modifier.width(160.dp).clickable { showDatePickerVon = true } else Modifier.width(160.dp)
val vonMod =
if (nrConfirmed) Modifier.width(160.dp).clickable { showDatePickerVon = true } else Modifier.width(160.dp)
OutlinedTextField(
value = von,
onValueChange = {},
@@ -314,7 +327,8 @@ fun StammdatenTabContent(
trailingIcon = { Icon(Icons.Default.DateRange, null) }
)
Text("bis")
val bisMod = if (nrConfirmed) Modifier.width(160.dp).clickable { showDatePickerBis = true } else Modifier.width(160.dp)
val bisMod =
if (nrConfirmed) Modifier.width(160.dp).clickable { showDatePickerBis = true } else Modifier.width(160.dp)
OutlinedTextField(
value = bis,
onValueChange = {},
@@ -325,7 +339,8 @@ fun StammdatenTabContent(
trailingIcon = { Icon(Icons.Default.DateRange, null) }
)
}
val rangeText = if (eventVon != null && eventBis != null) "Muss zwischen $eventVon $eventBis liegen." else "Muss innerhalb des Veranstaltungs-Zeitraums liegen."
val rangeText =
if (eventVon != null && eventBis != null) "Muss zwischen $eventVon $eventBis liegen." else "Muss innerhalb des Veranstaltungs-Zeitraums liegen."
Text(rangeText, fontSize = 11.sp, color = Color.Gray)
}
}
@@ -335,7 +350,8 @@ fun StammdatenTabContent(
// Default-Titel-Vorschlag: [Kategorien] [Verein-Ort] [Bundesland]
val defaultTitle = remember(kat.size, veranstalterOrt, veranstalterBundesland) {
val cats = if (kat.isEmpty()) "" else kat.sorted().joinToString(" ")
listOfNotNull(cats.ifBlank { null },
listOfNotNull(
cats.ifBlank { null },
listOfNotNull(veranstalterOrt, veranstalterBundesland).filter { it.isNotBlank() }.joinToString(" ")
.takeIf { it.isNotBlank() }
).joinToString(" ")
@@ -367,7 +383,12 @@ fun StammdatenTabContent(
supportingText = {
if (eventOrt != null && ort.isNotBlank() && ort.trim() != eventOrt.trim()) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.Warning, contentDescription = null, tint = Color(0xFFF59E0B), modifier = Modifier.size(14.dp))
Icon(
Icons.Default.Warning,
contentDescription = null,
tint = Color(0xFFF59E0B),
modifier = Modifier.size(14.dp)
)
Spacer(Modifier.width(4.dp))
Text("Abweichung zum Veranstaltungsort ($eventOrt) bitte prüfen.", color = Color(0xFFF59E0B))
}
@@ -409,14 +430,26 @@ fun StammdatenTabContent(
}
// ── Footer ──────────────────────────────────────────────────────────
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// Save-Enable-Matrix (kleine Checkliste)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
AssistChip(onClick = {}, label = { Text("Nr bestätigt") }, leadingIcon = {
Icon(if (nrConfirmed) Icons.Default.Check else Icons.Default.Close, null, tint = if (nrConfirmed) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error)
Icon(
if (nrConfirmed) Icons.Default.Check else Icons.Default.Close,
null,
tint = if (nrConfirmed) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
)
})
AssistChip(onClick = {}, label = { Text("ZNS geladen") }, leadingIcon = {
Icon(if (znsDataLoaded) Icons.Default.Check else Icons.Default.Close, null, tint = if (znsDataLoaded) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error)
Icon(
if (znsDataLoaded) Icons.Default.Check else Icons.Default.Close,
null,
tint = if (znsDataLoaded) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
)
})
val dateOk = remember(von, bis, eventVon, eventBis) {
try {
@@ -427,10 +460,16 @@ fun StammdatenTabContent(
val tB = if (bis.isBlank()) tV else LocalDate.parse(bis)
!tV.isBefore(evV) && !tB.isAfter(evB) && !tB.isBefore(tV)
}
} catch (_: Exception) { false }
} catch (_: Exception) {
false
}
}
AssistChip(onClick = {}, label = { Text("Datum gültig") }, leadingIcon = {
Icon(if (dateOk) Icons.Default.Check else Icons.Default.Close, null, tint = if (dateOk) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error)
Icon(
if (dateOk) Icons.Default.Check else Icons.Default.Close,
null,
tint = if (dateOk) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
)
})
}
@@ -446,7 +485,9 @@ fun StammdatenTabContent(
val tB = if (bis.isBlank()) tV else LocalDate.parse(bis)
!tV.isBefore(evV) && !tB.isAfter(evB) && !tB.isBefore(tV)
}
} catch (_: Exception) { false }
} catch (_: Exception) {
false
}
base && dateValid
},
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
@@ -460,88 +501,96 @@ fun StammdatenTabContent(
}
// Dialog-Simulationen
if (showZnsDialog) {
AlertDialog(
onDismissRequest = { showZnsDialog = false },
title = { Text("ZNS Import") },
text = { Text("Simuliere ZNS-Stammdaten Import für Turnier #$turnierNr...") },
confirmButton = {
TextButton(onClick = {
znsDataLoaded = true
znsPayloadVersion = "v2.4"
znsImportedAt = java.time.Instant.now().toString()
znsImportHistory.add(Triple("Internet/USB", znsPayloadVersion!!, true))
showZnsDialog = false
}) { Text("Importieren") }
},
dismissButton = {
TextButton(onClick = { showZnsDialog = false }) { Text("Abbrechen") }
}
)
when {
showZnsDialog -> {
AlertDialog(
onDismissRequest = { showZnsDialog = false },
title = { Text("ZNS Import") },
text = { Text("Simuliere ZNS-Stammdaten Import für Turnier #$turnierNr...") },
confirmButton = {
TextButton(onClick = {
znsDataLoaded = true
znsPayloadVersion = "v2.4"
znsImportedAt = java.time.Instant.now().toString()
znsImportHistory.add(Triple("Internet/USB", znsPayloadVersion!!, true))
showZnsDialog = false
}) { Text("Importieren") }
},
dismissButton = {
TextButton(onClick = { showZnsDialog = false }) { Text("Abbrechen") }
}
)
}
}
if (showNrConfirm) {
AlertDialog(
onDismissRequest = { showNrConfirm = false },
title = { Text("Turnier-Nummer bestätigen?") },
text = { Text("Die Turnier-Nr. ist nach der Bestätigung nicht mehr änderbar.") },
confirmButton = {
TextButton(onClick = { nrConfirmed = true; showNrConfirm = false }) { Text("Ja, bestätigen") }
},
dismissButton = {
TextButton(onClick = { showNrConfirm = false }) { Text("Abbrechen") }
}
)
when {
showNrConfirm -> {
AlertDialog(
onDismissRequest = { showNrConfirm = false },
title = { Text("Turnier-Nummer bestätigen?") },
text = { Text("Die Turnier-Nr. ist nach der Bestätigung nicht mehr änderbar.") },
confirmButton = {
TextButton(onClick = { nrConfirmed = true; showNrConfirm = false }) { Text("Ja, bestätigen") }
},
dismissButton = {
TextButton(onClick = { showNrConfirm = false }) { Text("Abbrechen") }
}
)
}
}
if (showZnsLog) {
AlertDialog(
onDismissRequest = { showZnsLog = false },
title = { Text("ZNS Import-Log (letzte 5)") },
text = {
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
if (znsImportHistory.isEmpty()) {
Text("Keine Einträge vorhanden.", color = Color.Gray)
} else {
znsImportHistory.takeLast(5).asReversed().forEach { (src, ver, ok) ->
val c = if (ok) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
Text("$src Version $ver ${if (ok) "OK" else "Fehler"}", color = c, fontSize = 13.sp)
when {
showZnsLog -> {
AlertDialog(
onDismissRequest = { showZnsLog = false },
title = { Text("ZNS Import-Log (letzte 5)") },
text = {
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
if (znsImportHistory.isEmpty()) {
Text("Keine Einträge vorhanden.", color = Color.Gray)
} else {
znsImportHistory.takeLast(5).asReversed().forEach { (src, ver, ok) ->
val c = if (ok) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
Text("$src Version $ver ${if (ok) "OK" else "Fehler"}", color = c, fontSize = 13.sp)
}
}
}
},
confirmButton = { TextButton(onClick = { showZnsLog = false }) { Text("Schließen") } }
)
}
}
when {
showDatePickerVon -> {
val state = rememberDatePickerState()
DatePickerDialog(
onDismissRequest = { showDatePickerVon = false },
confirmButton = {
TextButton(onClick = {
state.selectedDateMillis?.let {
von = LocalDate.ofEpochDay(it / (24 * 60 * 60 * 1000)).toString()
}
showDatePickerVon = false
}) { Text("OK") }
}
},
confirmButton = { TextButton(onClick = { showZnsLog = false }) { Text("Schließen") } }
)
}
) { DatePicker(state) }
}
if (showDatePickerVon) {
val state = rememberDatePickerState()
DatePickerDialog(
onDismissRequest = { showDatePickerVon = false },
confirmButton = {
TextButton(onClick = {
state.selectedDateMillis?.let {
von = LocalDate.ofEpochDay(it / (24 * 60 * 60 * 1000)).toString()
}
showDatePickerVon = false
}) { Text("OK") }
}
) { DatePicker(state) }
}
if (showDatePickerBis) {
val state = rememberDatePickerState()
DatePickerDialog(
onDismissRequest = { showDatePickerBis = false },
confirmButton = {
TextButton(onClick = {
state.selectedDateMillis?.let {
bis = LocalDate.ofEpochDay(it / (24 * 60 * 60 * 1000)).toString()
}
showDatePickerBis = false
}) { Text("OK") }
}
) { DatePicker(state) }
showDatePickerBis -> {
val state = rememberDatePickerState()
DatePickerDialog(
onDismissRequest = { showDatePickerBis = false },
confirmButton = {
TextButton(onClick = {
state.selectedDateMillis?.let {
bis = LocalDate.ofEpochDay(it / (24 * 60 * 60 * 1000)).toString()
}
showDatePickerBis = false
}) { Text("OK") }
}
) { DatePicker(state) }
}
}
}