feat(veranstaltung): UI-Refactoring und Validierung für Veranstaltungsverwaltung hinzugefügt
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
type: Journal
|
||||||
|
status: ACTIVE
|
||||||
|
owner: Curator
|
||||||
|
created: 2026-04-16
|
||||||
|
---
|
||||||
|
|
||||||
|
# Journal — 16. April 2026 (Veranstaltungs-Verwaltung Refactoring)
|
||||||
|
|
||||||
|
## 🎯 Ziel & Entscheidung
|
||||||
|
|
||||||
|
Überarbeitung der **Veranstaltungs-Verwaltung** gemäß der neuen UI-Vision (High-Density & Desktop-First).
|
||||||
|
Ziel war es, die Navigation effizienter zu gestalten (Double-Click Navigation) und den Wizard für die Neuanlage
|
||||||
|
funktional auszubauen (Stammdaten-Validierung).
|
||||||
|
|
||||||
|
## 🎨 UI/UX Änderungen
|
||||||
|
|
||||||
|
- **VeranstaltungenScreen:**
|
||||||
|
- Titel auf "Veranstaltungen - verwalten" aktualisiert (Vorgabe: Bindestrich + Kleinschreibung des Verbs).
|
||||||
|
- Entfernung der redundanten Navigations-Buttons (Reiter, Verein, ZNS-Importer) im Header zur Reduzierung der
|
||||||
|
kognitiven Last.
|
||||||
|
- Einführung der `VeranstaltungCard` mit Logo-Platzhalter und Hover-Feedback.
|
||||||
|
- Implementierung von **Double-Click Navigation** zum Öffnen einer Veranstaltung.
|
||||||
|
- Radikale Entschlackung: Platzhalter wurden durch eine saubere Liste/Grid-Logik ersetzt.
|
||||||
|
- Integration des primären Action-Buttons "+ Neue Veranstaltung" im Header.
|
||||||
|
|
||||||
|
- **VeranstaltungNeuScreen (Wizard):**
|
||||||
|
- Umstellung auf einen tab-basierten Workflow (Stammdaten | Organisation | Preisliste).
|
||||||
|
- Implementierung des Stammdaten-Formulars (A-Satz) mit Pflichtfeld-Validierung (Name, Ort, Datum).
|
||||||
|
- Integration der `MsTextField` und `MsButton` Komponenten aus dem Design-System.
|
||||||
|
- Vorbereitung für ZNS-Import Integration.
|
||||||
|
|
||||||
|
## 🏗️ Technische Details
|
||||||
|
|
||||||
|
- **State Management:** Nutzung von `remember` und `mutableStateOf` für die Formular-Validierung im Screen.
|
||||||
|
- **Modelle:** Einführung von `VeranstaltungSimpleUiModel` zur Entkopplung von Domain-Modellen in der UI.
|
||||||
|
- **Komponenten:** Nutzung von `combinedClickable` für Desktop-spezifische Interaktionen.
|
||||||
|
|
||||||
|
## 🔗 Relevante Dateien
|
||||||
|
|
||||||
|
-
|
||||||
|
`frontend/features/veranstaltung-feature/src/jvmMain/kotlin/at/mocode/veranstaltung/feature/presentation/VeranstaltungenScreen.kt`
|
||||||
|
- `frontend/shells/meldestelle-desktop/src/jvmMain/kotlin/at/mocode/desktop/v2/VeranstaltungScreens.kt` (Zentrale
|
||||||
|
Desktop-Ansicht)
|
||||||
|
-
|
||||||
|
`frontend/features/veranstaltung-feature/src/jvmMain/kotlin/at/mocode/veranstaltung/feature/presentation/VeranstaltungNeuScreen.kt`
|
||||||
+131
-19
@@ -1,45 +1,73 @@
|
|||||||
package at.mocode.veranstaltung.feature.presentation
|
package at.mocode.veranstaltung.feature.presentation
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
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.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import at.mocode.frontend.core.designsystem.models.PlaceholderContent
|
import at.mocode.frontend.core.designsystem.components.MsButton
|
||||||
|
import at.mocode.frontend.core.designsystem.components.MsTextField
|
||||||
|
import at.mocode.frontend.core.designsystem.theme.Dimens
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formular zum Anlegen einer neuen Veranstaltung (Vision_03: /veranstaltung/neu).
|
* Formular zum Anlegen einer neuen Veranstaltung (Vision_03: /veranstaltung/neu).
|
||||||
* Tabs: Veranstaltung-Übersicht | Stammdaten (A-Satz) | Organisation | Preisliste
|
* Tabs: Stammdaten (A-Satz) | Organisation | Preisliste
|
||||||
* TODO: Echte Formular-Felder und Persistenz (Phase 4/5).
|
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun VeranstaltungNeuScreen(
|
fun VeranstaltungNeuScreen(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSave: () -> Unit,
|
onSave: () -> Unit,
|
||||||
) {
|
) {
|
||||||
var selectedTab by remember { mutableIntStateOf(1) } // Stammdaten ist Standard-Tab
|
var selectedTab by remember { mutableIntStateOf(0) } // Stammdaten ist Standard-Tab
|
||||||
val tabs = listOf("Übersicht", "Stammdaten (A-Satz)", "Organisation", "Preisliste")
|
val tabs = listOf("Stammdaten (A-Satz)", "Organisation", "Preisliste")
|
||||||
|
|
||||||
|
// Formular-State für Stammdaten
|
||||||
|
var name by remember { mutableStateOf("") }
|
||||||
|
var ort by remember { mutableStateOf("") }
|
||||||
|
var startDatum by remember { mutableStateOf("") }
|
||||||
|
var endDatum by remember { mutableStateOf("") }
|
||||||
|
var veranstalter by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
// Validierung
|
||||||
|
val isNameValid = name.isNotBlank()
|
||||||
|
val isOrtValid = ort.isNotBlank()
|
||||||
|
val isStartDatumValid = startDatum.length >= 8 // Einfache Prüfung für DD.MM.YY
|
||||||
|
val isFormValid = isNameValid && isOrtValid && isStartDatumValid
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
// Toolbar
|
// Toolbar (Header)
|
||||||
Row(
|
Surface(
|
||||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
shadowElevation = 2.dp,
|
||||||
|
color = MaterialTheme.colorScheme.surface
|
||||||
) {
|
) {
|
||||||
Row {
|
Row(
|
||||||
|
modifier = Modifier.padding(Dimens.SpacingM),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Zurück")
|
||||||
}
|
}
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(Dimens.SpacingS))
|
||||||
Text(
|
Text(
|
||||||
text = "Neue Veranstaltung",
|
text = "Neue Veranstaltung anlegen",
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
modifier = Modifier.alignByBaseline(),
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MsButton(
|
||||||
|
text = "Veranstaltung speichern",
|
||||||
|
onClick = onSave,
|
||||||
|
enabled = isFormValid
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Button(onClick = onSave) { Text("Speichern") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryTabRow(selectedTabIndex = selectedTab) {
|
PrimaryTabRow(selectedTabIndex = selectedTab) {
|
||||||
@@ -52,12 +80,96 @@ fun VeranstaltungNeuScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(Dimens.SpacingL)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
when (selectedTab) {
|
when (selectedTab) {
|
||||||
0 -> PlaceholderContent("Veranstaltung – Übersicht", "Wird nach dem Speichern befüllt.")
|
0 -> { // Stammdaten
|
||||||
1 -> PlaceholderContent("Stammdaten (A-Satz)", "Felder: Bezeichnung, Datum, Ort, Veranstalter …")
|
Column(
|
||||||
2 -> PlaceholderContent("Organisation", "Felder: Richter, Parcourschef, Tierarzt …")
|
modifier = Modifier.widthIn(max = 600.dp),
|
||||||
3 -> PlaceholderContent("Preisliste", "Nenngebühren pro Bewerb/Sparte …")
|
verticalArrangement = Arrangement.spacedBy(Dimens.SpacingM)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Allgemeine Informationen (A-Satz)",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
|
||||||
|
MsTextField(
|
||||||
|
value = name,
|
||||||
|
onValueChange = { name = it },
|
||||||
|
label = "Name der Veranstaltung (z.B. Pfingstturnier)",
|
||||||
|
placeholder = "Name eingeben",
|
||||||
|
isError = name.isBlank(),
|
||||||
|
errorMessage = if (name.isBlank()) "Name ist erforderlich" else null
|
||||||
|
)
|
||||||
|
|
||||||
|
MsTextField(
|
||||||
|
value = veranstalter,
|
||||||
|
onValueChange = { veranstalter = it },
|
||||||
|
label = "Veranstalter (Verein)",
|
||||||
|
placeholder = "Verein suchen oder eingeben"
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)) {
|
||||||
|
MsTextField(
|
||||||
|
value = ort,
|
||||||
|
onValueChange = { ort = it },
|
||||||
|
label = "Ort",
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
isError = ort.isBlank()
|
||||||
|
)
|
||||||
|
MsTextField(
|
||||||
|
value = "Österreich",
|
||||||
|
onValueChange = {},
|
||||||
|
label = "Land",
|
||||||
|
modifier = Modifier.weight(0.5f),
|
||||||
|
readOnly = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(Dimens.SpacingM)) {
|
||||||
|
MsTextField(
|
||||||
|
value = startDatum,
|
||||||
|
onValueChange = { startDatum = it },
|
||||||
|
label = "Startdatum (TT.MM.JJJJ)",
|
||||||
|
placeholder = "24.04.2026",
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
MsTextField(
|
||||||
|
value = endDatum,
|
||||||
|
onValueChange = { endDatum = it },
|
||||||
|
label = "Enddatum (TT.MM.JJJJ)",
|
||||||
|
placeholder = "26.04.2026",
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"Hinweis: Diese Daten bilden die Basis für den ZNS-Import und die Abrechnung.",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1 -> { // Organisation
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(Dimens.SpacingM)) {
|
||||||
|
Text("Funktionäre & Verantwortliche", style = MaterialTheme.typography.titleLarge)
|
||||||
|
Text("Hier werden Richter, Parcourschefs und Tierärzte zugewiesen.")
|
||||||
|
// Später: MsSearchableSelect für Funktionäre
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
2 -> { // Preisliste
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(Dimens.SpacingM)) {
|
||||||
|
Text("Gebühren & Preisliste", style = MaterialTheme.typography.titleLarge)
|
||||||
|
Text("Definition der Nenngebühren und Pauschalen gemäß ÖTO.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+138
-18
@@ -1,51 +1,171 @@
|
|||||||
package at.mocode.veranstaltung.feature.presentation
|
package at.mocode.veranstaltung.feature.presentation
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Event
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import at.mocode.frontend.core.designsystem.models.PlaceholderContent
|
import at.mocode.frontend.core.designsystem.components.MsButton
|
||||||
|
import at.mocode.frontend.core.designsystem.components.MsCard
|
||||||
|
import at.mocode.frontend.core.designsystem.theme.Dimens
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI-Modell für die Anzeige einer Veranstaltung in der Liste.
|
||||||
|
*/
|
||||||
|
data class VeranstaltungSimpleUiModel(
|
||||||
|
val id: Long,
|
||||||
|
val name: String,
|
||||||
|
val untertitel: String?,
|
||||||
|
val ort: String,
|
||||||
|
val datum: String,
|
||||||
|
val logoUrl: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Veranstaltungs-Übersicht (Drawer-Einstieg gemäß Vision_03).
|
* Veranstaltungs-Übersicht (Drawer-Einstieg gemäß Vision_03).
|
||||||
* Zeigt Liste aller Veranstaltungen + Button "Neue Veranstaltung".
|
* Zeigt Liste aller Veranstaltungen + Button "Neue Veranstaltung".
|
||||||
* TODO: Echte Daten aus dem event-management-context laden (Phase 4/5).
|
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun VeranstaltungenScreen(
|
fun VeranstaltungenScreen(
|
||||||
onVeranstaltungNeu: () -> Unit,
|
onVeranstaltungNeu: () -> Unit,
|
||||||
onVeranstaltungOeffnen: (Long) -> Unit,
|
onVeranstaltungOeffnen: (Long) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.fillMaxSize().padding(24.dp)) {
|
// Später: Echte Daten aus dem ViewModel laden
|
||||||
|
val veranstaltungen = remember {
|
||||||
|
mutableStateListOf(
|
||||||
|
VeranstaltungSimpleUiModel(
|
||||||
|
id = 1L,
|
||||||
|
name = "Springturnier Neumarkt",
|
||||||
|
untertitel = "CSN-B* | 24. - 26. April 2026",
|
||||||
|
ort = "Neumarkt am Wallersee",
|
||||||
|
datum = "24.04.2026 - 26.04.2026"
|
||||||
|
),
|
||||||
|
VeranstaltungSimpleUiModel(
|
||||||
|
id = 2L,
|
||||||
|
name = "Dressurtage Lamprechtshausen",
|
||||||
|
untertitel = "CDN-A* | 01. - 03. Mai 2026",
|
||||||
|
ort = "Lamprechtshausen",
|
||||||
|
datum = "01.05.2026 - 03.05.2026"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxSize().padding(Dimens.SpacingL)) {
|
||||||
|
// Header: Titel + Action
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Veranstaltungen",
|
text = "Veranstaltungen - verwalten",
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
MsButton(
|
||||||
|
text = "Neue Veranstaltung",
|
||||||
|
onClick = onVeranstaltungNeu
|
||||||
|
// icon = Icons.Default.Add // MsButton unterstützt noch kein Icon im Parameter
|
||||||
)
|
)
|
||||||
Button(onClick = onVeranstaltungNeu) {
|
|
||||||
Icon(Icons.Default.Add, contentDescription = null)
|
|
||||||
Spacer(Modifier.width(8.dp))
|
|
||||||
Text("Neue Veranstaltung")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(Modifier.height(Dimens.SpacingL))
|
||||||
|
|
||||||
// Platzhalter – wird durch echte Daten ersetzt
|
if (veranstaltungen.isEmpty()) {
|
||||||
PlaceholderContent(
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
title = "Noch keine Veranstaltungen",
|
Text(
|
||||||
subtitle = "Lege eine neue Veranstaltung an, um zu beginnen.",
|
"Keine Veranstaltungen gefunden. Legen Sie eine neue an.",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Dimens.SpacingM)
|
||||||
|
) {
|
||||||
|
items(veranstaltungen) { event ->
|
||||||
|
VeranstaltungCard(
|
||||||
|
event = event,
|
||||||
|
onDoubleClick = { onVeranstaltungOeffnen(event.id) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun VeranstaltungCard(
|
||||||
|
event: VeranstaltungSimpleUiModel,
|
||||||
|
onDoubleClick: () -> Unit
|
||||||
|
) {
|
||||||
|
MsCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = { /* Einfacher Klick für Selektion, falls gewünscht */ },
|
||||||
|
onDoubleClick = onDoubleClick
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(Dimens.SpacingS),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Platzhalter für Logo
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
imageVector = Icons.Default.Event,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.width(Dimens.SpacingM))
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = event.name,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
if (!event.untertitel.isNullOrBlank()) {
|
||||||
|
Text(
|
||||||
|
text = event.untertitel,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "${event.ort} | ${event.datum}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ package at.mocode.desktop.screens.onboarding
|
|||||||
/**
|
/**
|
||||||
* Validierungslogik für den Onboarding-Wizard.
|
* Validierungslogik für den Onboarding-Wizard.
|
||||||
*
|
*
|
||||||
* Extrahiert aus [OnboardingScreen] für isolierte Unit-Tests (B-2).
|
* Extrahiert aus `OnboardingScreen` für isolierte Unit-Tests (B-2).
|
||||||
* Regeln gemäß Onboarding-Spezifikation:
|
* Regeln gemäß Onboarding-Spezifikation:
|
||||||
* - Gerätename: mindestens 3 Zeichen (nach trim)
|
* - Gerätename: mindestens 3 Zeichen (nach trim)
|
||||||
* - Sicherheitsschlüssel: mindestens 8 Zeichen (nach trim)
|
* - Sicherheitsschlüssel: mindestens 8 Zeichen (nach trim)
|
||||||
|
|||||||
+1
-39
@@ -57,51 +57,13 @@ fun VeranstaltungVerwaltungV2(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
// Navigation Toolbar (Top)
|
|
||||||
Row(
|
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
AssistChip(
|
|
||||||
onClick = onNavigateToPferde,
|
|
||||||
label = { Text("Pferde") },
|
|
||||||
leadingIcon = { Icon(Icons.Default.Pets, null) })
|
|
||||||
AssistChip(
|
|
||||||
onClick = onNavigateToReiter,
|
|
||||||
label = { Text("Reiter") },
|
|
||||||
leadingIcon = { Icon(Icons.Default.Person, null) })
|
|
||||||
AssistChip(
|
|
||||||
onClick = onNavigateToVereine,
|
|
||||||
label = { Text("Vereine") },
|
|
||||||
leadingIcon = { Icon(Icons.Default.Home, null) })
|
|
||||||
AssistChip(
|
|
||||||
onClick = onNavigateToFunktionaere,
|
|
||||||
label = { Text("Funktionäre") },
|
|
||||||
leadingIcon = { Icon(Icons.Default.Badge, null) })
|
|
||||||
AssistChip(
|
|
||||||
onClick = onNavigateToVeranstalter,
|
|
||||||
label = { Text("Veranstalter") },
|
|
||||||
leadingIcon = { Icon(Icons.Default.Business, null) })
|
|
||||||
VerticalDivider(Modifier.height(32.dp).padding(horizontal = 4.dp))
|
|
||||||
AssistChip(
|
|
||||||
onClick = onNavigateToZnsImport,
|
|
||||||
label = { Text("ZNS Importer") },
|
|
||||||
leadingIcon = { Icon(Icons.Default.CloudDownload, null) },
|
|
||||||
colors = AssistChipDefaults.assistChipColors(
|
|
||||||
labelColor = MaterialTheme.colorScheme.primary,
|
|
||||||
leadingIconContentColor = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text("Veranstaltung-Verwaltung", style = MaterialTheme.typography.headlineMedium)
|
Text("Veranstaltungen - verwalten", style = MaterialTheme.typography.headlineMedium)
|
||||||
Button(onClick = onNewVeranstaltung) {
|
Button(onClick = onNewVeranstaltung) {
|
||||||
Icon(Icons.Default.Add, contentDescription = null)
|
Icon(Icons.Default.Add, contentDescription = null)
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
|
|||||||
Reference in New Issue
Block a user