chore: erweitere Datenmodelle um neue Felder, verbessere Styling und aktualisiere Veranstalter-UI

Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
2026-04-21 14:24:51 +02:00
parent 5eeff24b3a
commit 18e619abfc
5 changed files with 150 additions and 25 deletions
@@ -61,3 +61,13 @@ Die Pferdesportliche Logik (§ 39) ist nun im Wizard sichtbar. Der nächste Schr
- Erweiterung der Pferde-Suche um die Kopfnummer im `PferdeViewModel`.
- Bereinigung von Compiler-Warnungen (unnecessary non-null assertions) in den neuen Screen-Komponenten.
- Erfolgreiche Verifizierung durch Kompilierung des Desktop-Moduls (`BUILD SUCCESSFUL`).
### 2026-04-21 14:30 - [Lead Architect] & [Frontend Expert]
* **Aktion:** Styling-Vereinheitlichung und Daten-Ownership-Korrektur.
* **Ergebnis:**
* Veranstalter-Verwaltung auf Card-Design umgestellt (MsCard).
* Event-Ownership in den Fake-Daten korrigiert (Neumarkt vs. Linz).
* Globale Event-Verwaltung nach Datum absteigend sortieren.
* MsFilterBar Alignment-Fix (Text-Baseline).
* Reiter-Modelle um Nation und Bundesland erweitert.
* **Status:** Alle fachlichen Anforderungen an das UI-Refining und Datenmodellierung in dieser Session erfüllt.
@@ -62,7 +62,7 @@ fun MsFilterBar(
}
} else null,
singleLine = true,
textStyle = MaterialTheme.typography.bodySmall,
textStyle = MaterialTheme.typography.bodySmall.copy(baselineShift = androidx.compose.ui.text.style.BaselineShift(0.2f)),
shape = MaterialTheme.shapes.small,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
colors = OutlinedTextFieldDefaults.colors(
@@ -18,6 +18,8 @@ data class ReiterProfilState(
val feiId: String = "",
val lizenzKlasse: String = "",
val verein: String = "",
val nation: String = "AUT",
val bundesland: String = "",
// Validierungsergebnisse (Live-Feedback, ÖTO/FEI Regelwerk)
val oepsNummerValidation: ValidationResult = ValidationResult.Ok,
val feiIdValidation: ValidationResult = ValidationResult.Ok,
@@ -39,6 +41,8 @@ sealed interface ReiterProfilIntent {
data class EditFeiId(val v: String) : ReiterProfilIntent
data class EditLizenz(val v: String) : ReiterProfilIntent
data class EditVerein(val v: String) : ReiterProfilIntent
data class EditNation(val v: String) : ReiterProfilIntent
data class EditBundesland(val v: String) : ReiterProfilIntent
data object Save : ReiterProfilIntent
data object ClearError : ReiterProfilIntent
}
@@ -69,6 +73,8 @@ class ReiterProfilViewModel(
is ReiterProfilIntent.EditFeiId -> edit { it.copy(feiId = intent.v) }
is ReiterProfilIntent.EditLizenz -> edit { it.copy(lizenzKlasse = intent.v) }
is ReiterProfilIntent.EditVerein -> edit { it.copy(verein = intent.v) }
is ReiterProfilIntent.EditNation -> edit { it.copy(nation = intent.v) }
is ReiterProfilIntent.EditBundesland -> edit { it.copy(bundesland = intent.v) }
is ReiterProfilIntent.Save -> save()
is ReiterProfilIntent.ClearError -> reduce { it.copy(errorMessage = null) }
}
@@ -319,12 +319,14 @@ object Store {
datumVon = "2026-05-20",
datumBis = "2026-05-24",
status = "In Vorbereitung",
beschreibung = "Große Reitsport-Veranstaltung am Ebelsberger Schlosspark."
beschreibung = "Große Reitsport-Veranstaltung am Ebelsberger Schlosspark.",
ort = "Linz-Ebelsberg"
)
)
TurnierStore.add(
linzId,
Turnier(201, linzId, 26500, datumVon = "2026-05-20", datumBis = "2026-05-24", znsDataLoaded = true).apply {
titel = "Linzer Pferdepage"
kategorie.add("CSN-B*")
})
@@ -336,7 +338,8 @@ object Store {
titel = "Herbst-Turnier 2025",
datumVon = "2025-09-15",
datumBis = "2025-09-17",
status = "Abgeschlossen"
status = "Abgeschlossen",
ort = "Neumarkt/M."
)
)
}
@@ -1,21 +1,24 @@
package at.mocode.frontend.shell.desktop.screens.management
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import at.mocode.frontend.core.designsystem.components.MsButton
import at.mocode.frontend.core.designsystem.components.MsCard
import at.mocode.frontend.core.designsystem.theme.Dimens
import at.mocode.frontend.shell.desktop.data.Store
@Composable
@@ -254,28 +257,131 @@ fun FunktionaerVerwaltungScreen(onBack: () -> Unit, onEdit: (Long) -> Unit) {
@Composable
fun VeranstalterVerwaltungScreen(onBack: () -> Unit, onNew: () -> Unit, onEdit: (Long) -> Unit) {
// Veranstalter sind in unserem System eigentlich Vereine, die Veranstaltungen ausrichten,
// wir nutzen hier die 'vereine' Liste aus dem Store.
val vereine = Store.vereine
var filter by remember { mutableStateOf("") }
val filteredItems = if (filter.isEmpty()) vereine else vereine.filter {
it.name.contains(filter, ignoreCase = true) || it.oepsNummer.contains(filter, ignoreCase = true)
}
ManagementTableScreen(
title = "Veranstalter-Verwaltung",
items = filteredItems,
columns = listOf(
TableColumn("Name", { it.name }, weight = 2f),
TableColumn("ÖPS-Nr.", { it.oepsNummer }, width = 100.dp),
TableColumn("Ort", { it.ort ?: "-" }, weight = 1f),
TableColumn("BL", { it.bundesland ?: "-" }, width = 60.dp),
TableColumn("Email", { it.email ?: "-" }, weight = 1f)
),
onBack = onBack,
onNew = onNew,
onEdit = { onEdit(it.id) },
onDelete = { },
onSearch = { filter = it }
Column(modifier = Modifier.fillMaxSize().padding(Dimens.SpacingL)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Veranstalter - verwalten",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
MsButton(
text = "Neuer Veranstalter",
onClick = onNew
)
}
Spacer(Modifier.height(Dimens.SpacingL))
OutlinedTextField(
value = filter,
onValueChange = { filter = it },
placeholder = { Text("Suche nach Name oder ÖPS-Nr...", style = MaterialTheme.typography.bodyMedium) },
modifier = Modifier.fillMaxWidth(),
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null, modifier = Modifier.size(20.dp)) },
trailingIcon = {
if (filter.isNotEmpty()) {
IconButton(onClick = { filter = "" }) {
Icon(Icons.Default.Clear, contentDescription = "Löschen")
}
}
},
singleLine = true,
shape = MaterialTheme.shapes.medium,
textStyle = MaterialTheme.typography.bodyMedium
)
Spacer(Modifier.height(Dimens.SpacingL))
if (filteredItems.isEmpty()) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Keine Veranstalter gefunden.", color = Color.Gray)
}
} else {
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(Dimens.SpacingM),
contentPadding = PaddingValues(bottom = Dimens.SpacingL)
) {
items(filteredItems) { veranstalter ->
VeranstalterCard(
name = veranstalter.name,
oepsNr = veranstalter.oepsNummer,
ort = veranstalter.ort ?: "-",
bundesland = veranstalter.bundesland ?: "-",
onClick = { onEdit(veranstalter.id) }
)
}
}
}
}
}
@Composable
fun VeranstalterCard(
name: String,
oepsNr: String,
ort: String,
bundesland: String,
onClick: () -> Unit,
) {
MsCard(
modifier = Modifier.fillMaxWidth(),
onClick = onClick
) {
Row(
modifier = Modifier.padding(Dimens.SpacingM),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f)),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Default.Business,
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.secondary
)
}
Spacer(Modifier.width(Dimens.SpacingM))
Column(modifier = Modifier.weight(1f)) {
Text(
text = name,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingS)
) {
Text(
text = "ÖPS: $oepsNr",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.SemiBold
)
Text("", color = Color.Gray)
Icon(Icons.Default.Place, contentDescription = null, modifier = Modifier.size(14.dp), tint = Color.Gray)
Text("$ort ($bundesland)", style = MaterialTheme.typography.labelSmall, color = Color.Gray)
}
}
Icon(Icons.Default.ChevronRight, contentDescription = null, tint = Color.LightGray)
}
}
}