feat(event-feature): enhance Veranstaltungs- & Turnier-Workflow
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Waiting to run
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Waiting to run
- Extended `Veranstaltung` domain model with new fields: `untertitel`, `logoUrl`, and `sponsoren`. - Refined navigation in `DesktopMainLayout.kt` to check turnier context and improve routing. - Overhauled `TurnierStammdatenTab` with enhanced interactivity: dynamic chip-based selectors for Spartens, Klassen, and Sponsors, as well as date pickers and ZNS import handling. - Implemented validations for date ranges and required fields. Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
+8
-1
@@ -1,6 +1,8 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -34,6 +36,11 @@ fun TurnierDetailScreen(
|
||||
) {
|
||||
var selectedTab by remember { mutableIntStateOf(0) }
|
||||
|
||||
// Temporäre Lösung bis zur echten Repository-Anbindung:
|
||||
// Da TurnierDetailScreen in einem anderen Modul liegt, übergeben wir
|
||||
// die Veranstaltungsinformationen eigentlich via ViewModel.
|
||||
// Hier nutzen wir vorerst koin oder Parameter.
|
||||
|
||||
val tabs = listOf(
|
||||
"STAMMDATEN",
|
||||
"ORGANISATION",
|
||||
|
||||
+262
-153
@@ -1,11 +1,11 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CloudDownload
|
||||
import androidx.compose.material.icons.filled.Usb
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import java.time.LocalDate
|
||||
|
||||
private val PrimaryBlue = Color(0xFF1E3A8A)
|
||||
private val AccentBlue = Color(0xFF3B82F6)
|
||||
@@ -26,214 +27,328 @@ private val AccentBlue = Color(0xFF3B82F6)
|
||||
* - Turnier-Beschreibung: Titel, Sub-Titel
|
||||
* - Sponsoren
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun StammdatenTabContent(turnierId: Long) {
|
||||
// In einer echten App würden wir diese Daten aus einem ViewModel laden.
|
||||
// Hier simulieren wir den State basierend auf den Anforderungen.
|
||||
|
||||
var turnierNr by remember { mutableStateOf("") }
|
||||
var typOto by remember { mutableStateOf(true) }
|
||||
var spracheDe by remember { mutableStateOf(true) }
|
||||
var sparteDressur by remember { mutableStateOf(false) }
|
||||
var sparteSpringen by remember { mutableStateOf(false) }
|
||||
var klasseC by remember { mutableStateOf(false) }
|
||||
var klasseB by remember { mutableStateOf(false) }
|
||||
var klasseA by remember { mutableStateOf(false) }
|
||||
var datumVon by remember { mutableStateOf("") }
|
||||
var datumBis by remember { mutableStateOf("") }
|
||||
var nrConfirmed by remember { mutableStateOf(false) }
|
||||
var znsDataLoaded by remember { mutableStateOf(false) }
|
||||
var typ by remember { mutableStateOf("ÖTO (National)") }
|
||||
|
||||
val sparten = remember { mutableStateListOf<String>() }
|
||||
val klassen = remember { mutableStateListOf<String>() }
|
||||
val kat = remember { mutableStateListOf<String>() }
|
||||
|
||||
var von by remember { mutableStateOf("") }
|
||||
var bis by remember { mutableStateOf("") }
|
||||
var ort by remember { mutableStateOf("") }
|
||||
|
||||
var titel by remember { mutableStateOf("") }
|
||||
var subTitel by remember { mutableStateOf("") }
|
||||
val sponsoren = remember { mutableStateListOf<String>() }
|
||||
|
||||
var showZnsDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// Hilfs-States für DatePicker
|
||||
var showDatePickerVon by remember { mutableStateOf(false) }
|
||||
var showDatePickerBis by remember { mutableStateOf(false) }
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.verticalScroll(scrollState)
|
||||
.padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
// ── Turnier-Konfiguration ────────────────────────────────────────────
|
||||
SectionCard(title = "Turnier-Konfiguration") {
|
||||
// ── Turnier-Konfiguration (Schritt 1 Logik) ───────────────────────────
|
||||
SectionCard(title = "Turnier-Konfiguration & ZNS") {
|
||||
FormRow("Turnier-Nr.:") {
|
||||
OutlinedTextField(
|
||||
value = turnierNr,
|
||||
onValueChange = { turnierNr = it },
|
||||
placeholder = { Text("z.B. 26128", fontSize = 13.sp) },
|
||||
modifier = Modifier.width(200.dp).height(48.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
OutlinedTextField(
|
||||
value = turnierNr,
|
||||
onValueChange = { if (it.length <= 5 && it.all { c -> c.isDigit() }) turnierNr = it },
|
||||
placeholder = { Text("5-stellig", fontSize = 13.sp) },
|
||||
modifier = Modifier.width(120.dp),
|
||||
singleLine = true,
|
||||
enabled = !nrConfirmed
|
||||
)
|
||||
if (!nrConfirmed) {
|
||||
Button(
|
||||
onClick = { nrConfirmed = true },
|
||||
enabled = turnierNr.length == 5,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue)
|
||||
) {
|
||||
Text("Bestätigen")
|
||||
}
|
||||
} else {
|
||||
InputChip(
|
||||
selected = true,
|
||||
onClick = { nrConfirmed = false },
|
||||
label = { Text("Bestätigt") },
|
||||
trailingIcon = { Icon(Icons.Default.Edit, 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FormRow("Typ:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(selected = typOto, onClick = { typOto = true })
|
||||
Text("OTO (National)", fontSize = 13.sp)
|
||||
RadioButton(selected = !typOto, onClick = { typOto = false })
|
||||
Text("FEI (International)", fontSize = 13.sp)
|
||||
FilterChip(
|
||||
selected = typ == "ÖTO (National)",
|
||||
onClick = { typ = "ÖTO (National)" },
|
||||
label = { Text("ÖTO (National)") }
|
||||
)
|
||||
FilterChip(
|
||||
selected = typ == "FEI (International)",
|
||||
onClick = { typ = "FEI (International)" },
|
||||
label = { Text("FEI (International)") }
|
||||
)
|
||||
}
|
||||
}
|
||||
FormRow("ZNS-Daten:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
|
||||
FormRow("ZNS-Stammdaten:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Button(
|
||||
onClick = {},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = AccentBlue),
|
||||
onClick = { showZnsDialog = true },
|
||||
colors = ButtonDefaults.buttonColors(containerColor = AccentBlue)
|
||||
) {
|
||||
Icon(Icons.Default.CloudDownload, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Import via Internet", fontSize = 13.sp)
|
||||
Icon(Icons.Default.CloudDownload, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Import via Internet")
|
||||
}
|
||||
OutlinedButton(onClick = {}) {
|
||||
Icon(Icons.Default.Usb, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Import via USB", fontSize = 13.sp)
|
||||
OutlinedButton(onClick = { showZnsDialog = true }) {
|
||||
Icon(Icons.Default.Usb, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Import via USB")
|
||||
}
|
||||
}
|
||||
Text(
|
||||
"Reiter-, Pferde-, Funktionärs- und Vereinsdaten vom OEPS Backend",
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFF6B7280),
|
||||
)
|
||||
}
|
||||
FormRow("Sprache:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(selected = spracheDe, onClick = { spracheDe = true })
|
||||
Text("Deutsch", fontSize = 13.sp)
|
||||
RadioButton(selected = !spracheDe, onClick = { spracheDe = false })
|
||||
Text("English", fontSize = 13.sp)
|
||||
|
||||
val znsStatusColor = if (znsDataLoaded) Color(0xFF2E7D32) else MaterialTheme.colorScheme.error
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(
|
||||
if (znsDataLoaded) Icons.Default.CheckCircle else Icons.Default.Error,
|
||||
contentDescription = null,
|
||||
tint = znsStatusColor,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Text(
|
||||
if (znsDataLoaded) "ZNS-Daten geladen" else "Keine ZNS-Daten geladen",
|
||||
color = znsStatusColor,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 13.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
FormRow("Sparten:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = sparteDressur, onCheckedChange = { sparteDressur = it })
|
||||
Text("Dressur", fontSize = 13.sp)
|
||||
Checkbox(checked = sparteSpringen, onCheckedChange = { sparteSpringen = it })
|
||||
Text("Springen", fontSize = 13.sp)
|
||||
}
|
||||
|
||||
// ── Sparten & Kategorien (Schritt 2 Logik) ───────────────────────────
|
||||
SectionCard(title = "Reglement & Sparten") {
|
||||
FormRow("Sparte:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
FilterChip(
|
||||
selected = sparten.contains("Dressur"),
|
||||
onClick = { if (sparten.contains("Dressur")) sparten.remove("Dressur") else sparten.add("Dressur") },
|
||||
label = { Text("Dressur") }
|
||||
)
|
||||
FilterChip(
|
||||
selected = sparten.contains("Springen"),
|
||||
onClick = { if (sparten.contains("Springen")) sparten.remove("Springen") else sparten.add("Springen") },
|
||||
label = { Text("Springen") }
|
||||
)
|
||||
}
|
||||
}
|
||||
FormRow("Klassen:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = klasseC, onCheckedChange = { klasseC = it })
|
||||
Text("C", fontSize = 13.sp)
|
||||
Checkbox(checked = klasseB, onCheckedChange = { klasseB = it })
|
||||
Text("B", fontSize = 13.sp)
|
||||
Checkbox(checked = klasseA, onCheckedChange = { klasseA = it })
|
||||
Text("A", fontSize = 13.sp)
|
||||
}
|
||||
}
|
||||
FormRow("Kategorien:") {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth().height(60.dp),
|
||||
color = Color(0xFFF3F4F6),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
"Bitte Sparte(n) auswählen",
|
||||
fontSize = 13.sp,
|
||||
color = Color(0xFF9CA3AF),
|
||||
|
||||
FormRow("Klasse:") {
|
||||
val klassenListe = listOf("C-NEU", "C", "B", "A", "L", "LM", "M", "S")
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
klassenListe.forEach { k ->
|
||||
FilterChip(
|
||||
selected = klassen.contains(k),
|
||||
onClick = { if (klassen.contains(k)) klassen.remove(k) else klassen.add(k) },
|
||||
label = { Text(k) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
FormRow("Datum:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
|
||||
FormRow("Kategorien:") {
|
||||
// Logik zur Generierung der Kategorien
|
||||
val suggested = mutableListOf<String>()
|
||||
sparten.forEach { s ->
|
||||
val prefix = if (s == "Dressur") "CDN" else "CSN"
|
||||
klassen.forEach { k ->
|
||||
suggested.add("$prefix-$k")
|
||||
suggested.add("${prefix}P-$k") // Pony Variante
|
||||
}
|
||||
}
|
||||
|
||||
if (suggested.isEmpty()) {
|
||||
Text("Bitte Sparte und Klasse wählen", color = Color.Gray, fontSize = 13.sp)
|
||||
} else {
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
suggested.forEach { c ->
|
||||
InputChip(
|
||||
selected = kat.contains(c),
|
||||
onClick = { if (kat.contains(c)) kat.remove(c) else kat.add(c) },
|
||||
label = { Text(c) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormRow("Zeitraum:") {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
OutlinedTextField(
|
||||
value = datumVon,
|
||||
onValueChange = { datumVon = it },
|
||||
placeholder = { Text("DD.MM.YYYY", fontSize = 12.sp) },
|
||||
modifier = Modifier.width(160.dp).height(48.dp),
|
||||
singleLine = true,
|
||||
value = von,
|
||||
onValueChange = {},
|
||||
label = { Text("Von") },
|
||||
modifier = Modifier.width(160.dp).clickable { showDatePickerVon = true },
|
||||
readOnly = true,
|
||||
trailingIcon = { Icon(Icons.Default.DateRange, null) }
|
||||
)
|
||||
Text("bis", fontSize = 13.sp)
|
||||
Text("bis")
|
||||
OutlinedTextField(
|
||||
value = datumBis,
|
||||
onValueChange = { datumBis = it },
|
||||
placeholder = { Text("DD.MM.YYYY", fontSize = 12.sp) },
|
||||
modifier = Modifier.width(160.dp).height(48.dp),
|
||||
singleLine = true,
|
||||
value = bis,
|
||||
onValueChange = {},
|
||||
label = { Text("Bis") },
|
||||
modifier = Modifier.width(160.dp).clickable { showDatePickerBis = true },
|
||||
readOnly = true,
|
||||
trailingIcon = { Icon(Icons.Default.DateRange, null) }
|
||||
)
|
||||
}
|
||||
Text("Hinweis: Muss innerhalb des Veranstaltungs-Zeitraums liegen.", fontSize = 11.sp, color = Color.Gray)
|
||||
}
|
||||
|
||||
FormRow("Ort:") {
|
||||
OutlinedTextField(
|
||||
value = ort,
|
||||
onValueChange = { ort = it },
|
||||
label = { Text("Austragungsort") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
supportingText = { Text("Muss mit Veranstaltungsort übereinstimmen.") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Turnier-Beschreibung ─────────────────────────────────────────────
|
||||
SectionCard(title = "Turnier-Beschreibung") {
|
||||
// ── Branding (Schritt 3 Logik) ───────────────────────────────────────
|
||||
SectionCard(title = "Turnier-Branding") {
|
||||
OutlinedTextField(
|
||||
value = titel,
|
||||
onValueChange = { titel = it },
|
||||
placeholder = { Text("z.B. Frühjahrs-Turnier 2026", fontSize = 13.sp) },
|
||||
label = { Text("Titel") },
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = subTitel,
|
||||
onValueChange = { subTitel = it },
|
||||
placeholder = { Text("z.B. KIDS CUP • PONY EINSTEIGER CUP OÖ", fontSize = 13.sp) },
|
||||
label = { Text("Sub-Titel") },
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
// ── Sponsoren ────────────────────────────────────────────────────────
|
||||
SectionCard(
|
||||
title = "Sponsoren",
|
||||
action = {
|
||||
TextButton(onClick = {}) {
|
||||
Text("+ Sponsor hinzufügen", color = AccentBlue, fontSize = 13.sp)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth().height(80.dp),
|
||||
color = Color(0xFFF9FAFB),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text("Noch keine Sponsoren hinzugefügt", fontSize = 13.sp, color = Color(0xFF6B7280))
|
||||
Spacer(Modifier.height(4.dp))
|
||||
TextButton(onClick = {}) {
|
||||
Text("+ Ersten Sponsor hinzufügen", color = AccentBlue, fontSize = 13.sp)
|
||||
}
|
||||
FormRow("Sponsoren:") {
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
sponsoren.forEach { s ->
|
||||
InputChip(
|
||||
selected = true,
|
||||
onClick = { sponsoren.remove(s) },
|
||||
label = { Text(s) },
|
||||
trailingIcon = { Icon(Icons.Default.Close, null, modifier = Modifier.size(14.dp)) }
|
||||
)
|
||||
}
|
||||
TextButton(onClick = { sponsoren.add("Neuer Sponsor") }) {
|
||||
Text("+ Hinzufügen")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Aktions-Buttons ──────────────────────────────────────────────────
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedButton(onClick = {}) { Text("Zurücksetzen") }
|
||||
Spacer(Modifier.width(8.dp))
|
||||
// ── Footer ──────────────────────────────────────────────────────────
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
Button(
|
||||
onClick = {},
|
||||
onClick = { /* Speichern */ },
|
||||
enabled = nrConfirmed && znsDataLoaded && kat.isNotEmpty() && von.isNotBlank() && titel.isNotBlank(),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = PrimaryBlue),
|
||||
) { Text("Speichern") }
|
||||
modifier = Modifier.padding(bottom = 24.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Save, null)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Änderungen speichern")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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; showZnsDialog = false }) { Text("Importieren") }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showZnsDialog = false }) { Text("Abbrechen") }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SectionCard(
|
||||
title: String,
|
||||
action: @Composable (() -> Unit)? = null,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(title, fontSize = 15.sp, fontWeight = FontWeight.SemiBold, color = PrimaryBlue)
|
||||
action?.invoke()
|
||||
}
|
||||
Column(Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Text(title, style = MaterialTheme.typography.titleLarge, color = PrimaryBlue, fontWeight = FontWeight.Bold)
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.surfaceVariant)
|
||||
content()
|
||||
}
|
||||
}
|
||||
@@ -241,20 +356,14 @@ private fun SectionCard(
|
||||
|
||||
@Composable
|
||||
private fun FormRow(label: String, content: @Composable ColumnScope.() -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 13.sp,
|
||||
label,
|
||||
modifier = Modifier.width(140.dp).padding(top = 12.dp),
|
||||
color = Color(0xFF374151),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user