chore: enhance Stammdaten-Verwaltung and refine desktop UX across multiple features, fix typo in settings.json, enable WASM builds, and add Master-Detail layout for Funktionäre
Desktop CI — Headless Tests & Build / Compose Desktop — Tests (headless) & Build (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Has been cancelled
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Has been cancelled

This commit is contained in:
2026-04-20 02:49:30 +02:00
parent d4aeba4666
commit 345c329350
14 changed files with 748 additions and 123 deletions
@@ -1,9 +1,9 @@
package at.mocode.frontend.features.reiter.presentation
import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import at.mocode.frontend.core.designsystem.components.*
@@ -23,7 +23,8 @@ fun ReiterScreen(
ReiterListContent(
uiState = uiState,
onSearchChange = viewModel::onSearchQueryChange,
onReiterSelected = viewModel::selectReiter
onReiterSelected = viewModel::selectReiter,
onAddNew = { viewModel.addNewReiter() }
)
},
detail = {
@@ -43,6 +44,11 @@ fun ReiterScreen(
onSave = viewModel::onSave,
onCancel = viewModel::onCancel
)
} else if (uiState.selectedReiter != null) {
ReiterCard(
reiter = uiState.selectedReiter,
onEdit = { viewModel.selectReiter(uiState.selectedReiter) }
)
} else {
PlaceholderContent(
title = "Kein Reiter ausgewählt",
@@ -57,13 +63,20 @@ fun ReiterScreen(
private fun ReiterListContent(
uiState: ReiterUiState,
onSearchChange: (String) -> Unit,
onReiterSelected: (Reiter) -> Unit
onReiterSelected: (Reiter) -> Unit,
onAddNew: () -> Unit
) {
Column(modifier = Modifier.fillMaxSize()) {
MsFilterBar(
searchQuery = uiState.searchQuery,
onSearchQueryChange = onSearchChange,
resultCount = uiState.searchResults.size
resultCount = uiState.searchResults.size,
actions = {
MsButton(
text = "Reiter anlegen",
onClick = onAddNew
)
}
)
Spacer(Modifier.height(8.dp))
@@ -72,14 +85,9 @@ private fun ReiterListContent(
items = uiState.searchResults,
columns = listOf(
MsColumnDefinition(
title = "Vorname",
weight = 1f,
cellRenderer = { Text(it.vorname, style = MaterialTheme.typography.bodySmall) }
),
MsColumnDefinition(
title = "Nachname",
weight = 1f,
cellRenderer = { Text(it.nachname, style = MaterialTheme.typography.bodySmall) }
title = "Name",
weight = 1.5f,
cellRenderer = { Text(it.name, style = MaterialTheme.typography.bodySmall) }
),
MsColumnDefinition(
title = "Lizenz",
@@ -103,6 +111,107 @@ private fun ReiterListContent(
}
}
@Composable
fun ReiterCard(
reiter: Reiter,
onEdit: () -> Unit
) {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Card(
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
)
) {
Column(modifier = Modifier.padding(24.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Surface(
modifier = Modifier.size(48.dp),
shape = MaterialTheme.shapes.medium,
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)
) {
Box(contentAlignment = Alignment.Center) {
Text(
text = (reiter.vorname.take(1) + reiter.nachname.take(1)).uppercase(),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary
)
}
}
Spacer(Modifier.width(16.dp))
Column {
Text(
reiter.name,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface
)
Text(
"ÖPS-Nr: ${reiter.oepsNummer ?: "-"}",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
MsStatusBadge(
text = reiter.status.label,
containerColor = reiter.status.color.copy(alpha = 0.1f),
contentColor = reiter.status.color
)
}
Spacer(Modifier.height(24.dp))
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
Spacer(Modifier.height(24.dp))
Row(modifier = Modifier.fillMaxWidth()) {
ReiterDetailItem(label = "Lizenz", value = reiter.lizenz.label, modifier = Modifier.weight(1f))
ReiterDetailItem(label = "Hauptsparte", value = reiter.sparte.label, modifier = Modifier.weight(1f))
}
Spacer(Modifier.height(16.dp))
Row(modifier = Modifier.fillMaxWidth()) {
ReiterDetailItem(label = "E-Mail", value = reiter.email ?: "-", modifier = Modifier.weight(1f))
ReiterDetailItem(label = "Telefon", value = reiter.telefon ?: "-", modifier = Modifier.weight(1f))
}
Spacer(Modifier.height(16.dp))
Row(modifier = Modifier.fillMaxWidth()) {
ReiterDetailItem(label = "Verein", value = reiter.verein ?: "-", modifier = Modifier.weight(1f))
ReiterDetailItem(label = "FEI-ID", value = reiter.feiId ?: "-", modifier = Modifier.weight(1f))
}
Spacer(Modifier.height(32.dp))
MsButton(
text = "Reiterdaten bearbeiten",
onClick = onEdit,
fullWidth = true
)
}
}
}
}
@Composable
private fun ReiterDetailItem(label: String, value: String, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Text(label, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
Text(value, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurface)
}
}
@Composable
private fun ReiterEditorContent(
uiState: ReiterUiState,
@@ -133,13 +242,15 @@ private fun ReiterEditorContent(
value = uiState.editVorname,
onValueChange = onVornameChange,
label = "Vorname",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
MsTextField(
value = uiState.editName,
onValueChange = onNachnameChange,
label = "Nachname",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
}
@@ -150,13 +261,15 @@ private fun ReiterEditorContent(
value = uiState.editFeiId,
onValueChange = onFeiIdChange,
label = "FEI ID",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
MsTextField(
value = uiState.editOepsNummer,
onValueChange = onOepsNummerChange,
label = "ÖPS Nummer",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
}
@@ -167,13 +280,15 @@ private fun ReiterEditorContent(
value = uiState.editGeburtsdatum,
onValueChange = onGeburtsdatumChange,
label = "Geburtsdatum",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
MsTextField(
value = uiState.editVerein,
onValueChange = onVereinChange,
label = "Verein",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
}
@@ -184,13 +299,15 @@ private fun ReiterEditorContent(
value = uiState.editEmail,
onValueChange = onEmailChange,
label = "E-Mail",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
MsTextField(
value = uiState.editTelefon,
onValueChange = onTelefonChange,
label = "Telefon",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
}
@@ -18,6 +18,7 @@ data class ReiterUiState(
val selectedReiter: Reiter? = null,
val isEditing: Boolean = false,
val isLoading: Boolean = false,
val editId: String = "",
val editName: String = "",
val editVorname: String = "",
val editLizenz: LizenzKlasse = LizenzKlasse.KEINE,
@@ -65,6 +66,7 @@ open class ReiterViewModel(initialLoad: Boolean = true) : ViewModel() {
uiState = uiState.copy(
selectedReiter = reiter,
isEditing = true,
editId = reiter.id,
editVorname = reiter.vorname,
editName = reiter.nachname,
editLizenz = reiter.lizenz,
@@ -79,6 +81,25 @@ open class ReiterViewModel(initialLoad: Boolean = true) : ViewModel() {
)
}
fun addNewReiter() {
uiState = uiState.copy(
selectedReiter = null,
isEditing = true,
editId = "",
editVorname = "",
editName = "",
editLizenz = LizenzKlasse.KEINE,
editSparte = Sparte.KEINE,
editStatus = ReiterStatus.AKTIV,
editFeiId = "",
editOepsNummer = "",
editGeburtsdatum = "",
editEmail = "",
editTelefon = "",
editVerein = ""
)
}
fun onEditFeiIdChange(value: String) { uiState = uiState.copy(editFeiId = value) }
fun onEditOepsNummerChange(value: String) { uiState = uiState.copy(editOepsNummer = value) }
fun onEditGeburtsdatumChange(value: String) { uiState = uiState.copy(editGeburtsdatum = value) }