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,11 +1,14 @@
package at.mocode.frontend.features.pferde.presentation
import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Pets
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import at.mocode.frontend.core.designsystem.components.*
import at.mocode.frontend.core.designsystem.models.PlaceholderContent
@@ -24,7 +27,8 @@ fun PferdeScreen(
PferdeListContent(
uiState = uiState,
onSearchChange = viewModel::onSearchQueryChange,
onPferdSelected = viewModel::selectPferd
onPferdSelected = viewModel::selectPferd,
onAddNew = { viewModel.addNewPferd() }
)
},
detail = {
@@ -43,6 +47,11 @@ fun PferdeScreen(
onSave = viewModel::onSave,
onCancel = viewModel::onCancel
)
} else if (uiState.selectedPferd != null) {
PferdCard(
pferd = uiState.selectedPferd,
onEdit = { viewModel.selectPferd(uiState.selectedPferd) }
)
} else {
PlaceholderContent(
title = "Kein Pferd ausgewählt",
@@ -57,13 +66,21 @@ fun PferdeScreen(
private fun PferdeListContent(
uiState: PferdeUiState,
onSearchChange: (String) -> Unit,
onPferdSelected: (Pferd) -> Unit
onPferdSelected: (Pferd) -> Unit,
onAddNew: () -> Unit
) {
Column(modifier = Modifier.fillMaxSize()) {
MsFilterBar(
searchQuery = uiState.searchQuery,
onSearchQueryChange = onSearchChange,
resultCount = uiState.searchResults.size
resultCount = uiState.searchResults.size,
actions = {
MsButton(
onClick = onAddNew,
text = "Pferd anlegen",
icon = Icons.Default.Add
)
}
)
Spacer(Modifier.height(8.dp))
@@ -77,9 +94,9 @@ private fun PferdeListContent(
cellRenderer = { Text(it.name, style = MaterialTheme.typography.bodySmall) }
),
MsColumnDefinition(
title = "Lebensnummer",
width = 150.dp,
cellRenderer = { Text(it.lebensnummer, style = MaterialTheme.typography.bodySmall) }
title = "ÖPS-Nr.",
width = 100.dp,
cellRenderer = { Text(it.oepsNummer ?: "-", style = MaterialTheme.typography.bodySmall) }
),
MsColumnDefinition(
title = "Status",
@@ -93,11 +110,114 @@ private fun PferdeListContent(
}
)
),
onRowClick = onPferdSelected
onRowClick = onPferdSelected,
selectedItem = uiState.selectedPferd
)
}
}
@Composable
fun PferdCard(
pferd: Pferd,
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) {
Icon(
Icons.Default.Pets,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp)
)
}
}
Spacer(Modifier.width(16.dp))
Column {
Text(
pferd.name,
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold
)
Text(
pferd.lebensnummer,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
MsStatusBadge(
text = pferd.status.label,
containerColor = pferd.status.color.copy(alpha = 0.1f),
contentColor = pferd.status.color
)
}
Spacer(Modifier.height(24.dp))
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
Spacer(Modifier.height(24.dp))
Row(modifier = Modifier.fillMaxWidth()) {
DetailItem(label = "ÖPS-Nr.", value = pferd.oepsNummer ?: "-", modifier = Modifier.weight(1f))
DetailItem(label = "FEI-ID", value = pferd.feiId ?: "-", modifier = Modifier.weight(1f))
}
Spacer(Modifier.height(16.dp))
Row(modifier = Modifier.fillMaxWidth()) {
DetailItem(label = "Geschlecht", value = pferd.geschlecht.label, modifier = Modifier.weight(1f))
DetailItem(label = "Farbe", value = pferd.farbe, modifier = Modifier.weight(1f))
}
Spacer(Modifier.height(16.dp))
Row(modifier = Modifier.fillMaxWidth()) {
DetailItem(label = "Geburtsjahr", value = pferd.geburtsjahr?.toString() ?: "-", modifier = Modifier.weight(1f))
DetailItem(label = "Besitzer", value = pferd.besitzer ?: "-", modifier = Modifier.weight(1f))
}
Spacer(Modifier.height(32.dp))
MsButton(
onClick = onEdit,
text = "Pferdedaten bearbeiten",
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
@Composable
private fun DetailItem(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, fontWeight = FontWeight.Medium)
}
}
@Composable
private fun PferdeEditorContent(
uiState: PferdeUiState,
@@ -127,13 +247,15 @@ private fun PferdeEditorContent(
value = uiState.editName,
onValueChange = onNameChange,
label = "Name",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
MsTextField(
value = uiState.editLebensnummer,
onValueChange = onLebensnummerChange,
label = "Lebensnummer",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
}
@@ -144,13 +266,15 @@ private fun PferdeEditorContent(
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
)
}
@@ -169,7 +293,8 @@ private fun PferdeEditorContent(
value = uiState.editFarbe,
onValueChange = onFarbeChange,
label = "Farbe",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
}
@@ -180,13 +305,15 @@ private fun PferdeEditorContent(
value = uiState.editGeburtsjahr,
onValueChange = onGeburtsjahrChange,
label = "Geburtsjahr",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
MsTextField(
value = uiState.editBesitzer,
onValueChange = onBesitzerChange,
label = "Besitzer",
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
compact = true
)
}
@@ -17,6 +17,7 @@ data class PferdeUiState(
val selectedPferd: Pferd? = null,
val isEditing: Boolean = false,
val isLoading: Boolean = false,
val editId: String = "",
val editName: String = "",
val editLebensnummer: String = "",
val editGeschlecht: Geschlecht = Geschlecht.WALLACH,
@@ -59,6 +60,7 @@ open class PferdeViewModel(initialLoad: Boolean = true) : ViewModel() {
uiState = uiState.copy(
selectedPferd = pferd,
isEditing = true,
editId = pferd.id,
editName = pferd.name,
editLebensnummer = pferd.lebensnummer,
editGeschlecht = pferd.geschlecht,
@@ -71,6 +73,23 @@ open class PferdeViewModel(initialLoad: Boolean = true) : ViewModel() {
)
}
fun addNewPferd() {
uiState = uiState.copy(
selectedPferd = null,
isEditing = true,
editId = "",
editName = "",
editLebensnummer = "",
editGeschlecht = Geschlecht.WALLACH,
editFarbe = "",
editGeburtsjahr = "",
editStatus = PferdeStatus.AKTIV,
editFeiId = "",
editOepsNummer = "",
editBesitzer = ""
)
}
fun onEditFeiIdChange(value: String) {
uiState = uiState.copy(editFeiId = value)
}