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:
+2
@@ -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
|
||||
}
|
||||
|
||||
+12
@@ -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 {
|
||||
|
||||
+6
@@ -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("/")
|
||||
|
||||
+16
@@ -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)
|
||||
|
||||
+39
-39
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+202
-153
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user