Add Reiter and Pferd edit dialogs, extend Masterdata repository with save and fetch methods, and integrate editors into Nennungen tab UI. Fix DI configuration and update previews.

This commit is contained in:
2026-04-12 15:56:06 +02:00
parent 4ca25b6417
commit f82d06f3e7
10 changed files with 280 additions and 2 deletions
@@ -37,7 +37,13 @@ data class Verein(
interface MasterdataRepository {
suspend fun searchReiter(query: String): Result<List<Reiter>>
suspend fun getReiter(id: String): Result<Reiter>
suspend fun saveReiter(reiter: Reiter): Result<Reiter>
suspend fun searchPferde(query: String): Result<List<Pferd>>
suspend fun getPferd(id: String): Result<Pferd>
suspend fun savePferd(pferd: Pferd): Result<Pferd>
suspend fun searchFunktionaere(query: String): Result<List<Funktionaer>>
suspend fun listVereine(): Result<List<Verein>>
suspend fun getVereinById(id: String): Result<Verein>
@@ -14,6 +14,9 @@ data class NennungenState(
val nennungen: List<Nennung> = emptyList(),
val searchResultsReiter: List<Reiter> = emptyList(),
val searchResultsPferde: List<Pferd> = emptyList(),
val searchResultsFunktionaere: List<Funktionaer> = emptyList(),
val selectedReiter: Reiter? = null,
val selectedPferd: Pferd? = null,
val errorMessage: String? = null
)
@@ -43,7 +46,10 @@ class NennungViewModel(
}
fun searchReiter(query: String) {
if (query.length < 2) return
if (query.length < 2) {
_state.value = _state.value.copy(searchResultsReiter = emptyList())
return
}
scope.launch {
masterdataRepo.searchReiter(query).onSuccess { list ->
_state.value = _state.value.copy(searchResultsReiter = list)
@@ -51,8 +57,24 @@ class NennungViewModel(
}
}
fun selectReiter(reiter: Reiter?) {
_state.value = _state.value.copy(selectedReiter = reiter)
}
fun saveReiter(reiter: Reiter) {
scope.launch {
masterdataRepo.saveReiter(reiter).onSuccess {
_state.value = _state.value.copy(selectedReiter = null)
// Evtl. Suchen/Listen aktualisieren
}
}
}
fun searchPferde(query: String) {
if (query.length < 2) return
if (query.length < 2) {
_state.value = _state.value.copy(searchResultsPferde = emptyList())
return
}
scope.launch {
masterdataRepo.searchPferde(query).onSuccess { list ->
_state.value = _state.value.copy(searchResultsPferde = list)
@@ -60,6 +82,30 @@ class NennungViewModel(
}
}
fun selectPferd(pferd: Pferd?) {
_state.value = _state.value.copy(selectedPferd = pferd)
}
fun savePferd(pferd: Pferd) {
scope.launch {
masterdataRepo.savePferd(pferd).onSuccess {
_state.value = _state.value.copy(selectedPferd = null)
}
}
}
fun searchFunktionaere(query: String) {
if (query.length < 2) {
_state.value = _state.value.copy(searchResultsFunktionaere = emptyList())
return
}
scope.launch {
masterdataRepo.searchFunktionaere(query).onSuccess { list ->
_state.value = _state.value.copy(searchResultsFunktionaere = list)
}
}
}
fun einreichen(bewerbId: String, abteilungId: String, reiterId: String, pferdId: String) {
_state.value = _state.value.copy(isLoading = true)
scope.launch {
@@ -22,6 +22,30 @@ class DefaultMasterdataRepository(
} else emptyList()
}
override suspend fun getReiter(id: String): Result<Reiter> = runCatching {
val response = client.get("${ApiRoutes.Masterdata.REITER}/$id")
if (response.status.isSuccess()) {
response.body<ReiterApiDto>().toDomain()
} else throw Exception("Reiter nicht gefunden")
}
override suspend fun saveReiter(reiter: Reiter): Result<Reiter> = runCatching {
val response = client.put("${ApiRoutes.Masterdata.REITER}/${reiter.id}") {
contentType(ContentType.Application.Json)
setBody(ReiterApiDto(
reiterId = reiter.id,
vorname = reiter.vorname,
nachname = reiter.nachname,
satznummer = reiter.satznummer,
vereinsName = reiter.verein,
feiId = reiter.feiId
))
}
if (response.status.isSuccess()) {
response.body<ReiterApiDto>().toDomain()
} else throw Exception("Fehler beim Speichern des Reiters")
}
override suspend fun searchPferde(query: String): Result<List<Pferd>> = runCatching {
val response = client.get("${ApiRoutes.Masterdata.PFERDE}/search") {
parameter("q", query)
@@ -31,6 +55,30 @@ class DefaultMasterdataRepository(
} else emptyList()
}
override suspend fun getPferd(id: String): Result<Pferd> = runCatching {
val response = client.get("${ApiRoutes.Masterdata.PFERDE}/$id")
if (response.status.isSuccess()) {
response.body<HorseApiDto>().toDomain()
} else throw Exception("Pferd nicht gefunden")
}
override suspend fun savePferd(pferd: Pferd): Result<Pferd> = runCatching {
val response = client.put("${ApiRoutes.Masterdata.PFERDE}/${pferd.id}") {
contentType(ContentType.Application.Json)
setBody(HorseApiDto(
pferdId = pferd.id,
pferdeName = pferd.name,
lebensnummer = pferd.lebensnummer,
geschlecht = "UNBEKANNT", // Fallback
geburtsjahr = pferd.geburtsjahr,
satznummer = pferd.oepsNummer
))
}
if (response.status.isSuccess()) {
response.body<HorseApiDto>().toDomain()
} else throw Exception("Fehler beim Speichern des Pferdes")
}
override suspend fun searchFunktionaere(query: String): Result<List<Funktionaer>> = runCatching {
val response = client.get("${ApiRoutes.Masterdata.FUNKTIONAERE}/search") {
parameter("q", query)
@@ -0,0 +1,97 @@
package at.mocode.turnier.feature.presentation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.unit.dp
import at.mocode.turnier.feature.domain.Pferd
import at.mocode.turnier.feature.domain.Reiter
@Composable
fun ReiterEditDialog(
reiter: Reiter,
onDismiss: () -> Unit,
onSave: (Reiter) -> Unit
) {
var vorname by remember { mutableStateOf(reiter.vorname) }
var nachname by remember { mutableStateOf(reiter.nachname) }
var oepsNummer by remember { mutableStateOf(reiter.oepsNummer ?: "") }
var verein by remember { mutableStateOf(reiter.verein ?: "") }
var feiId by remember { mutableStateOf(reiter.feiId ?: "") }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Reiter bearbeiten") },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(value = vorname, onValueChange = { vorname = it }, label = { Text("Vorname") })
OutlinedTextField(value = nachname, onValueChange = { nachname = it }, label = { Text("Nachname") })
OutlinedTextField(value = oepsNummer, onValueChange = { oepsNummer = it }, label = { Text("OEPS-Nr.") })
OutlinedTextField(value = verein, onValueChange = { verein = it }, label = { Text("Verein") })
OutlinedTextField(value = feiId, onValueChange = { feiId = it }, label = { Text("FEI-ID") })
}
},
confirmButton = {
Button(onClick = {
onSave(reiter.copy(
vorname = vorname,
nachname = nachname,
oepsNummer = oepsNummer,
satznummer = oepsNummer,
verein = verein,
feiId = feiId
))
}) {
Text("Speichern")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Abbrechen")
}
}
)
}
@Composable
fun PferdEditDialog(
pferd: Pferd,
onDismiss: () -> Unit,
onSave: (Pferd) -> Unit
) {
var name by remember { mutableStateOf(pferd.name) }
var lebensnummer by remember { mutableStateOf(pferd.lebensnummer) }
var oepsNummer by remember { mutableStateOf(pferd.oepsNummer ?: "") }
var geburtsjahr by remember { mutableStateOf(pferd.geburtsjahr?.toString() ?: "") }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Pferd bearbeiten") },
text = {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Name") })
OutlinedTextField(value = lebensnummer, onValueChange = { lebensnummer = it }, label = { Text("Lebensnummer") })
OutlinedTextField(value = oepsNummer, onValueChange = { oepsNummer = it }, label = { Text("OEPS-Nr.") })
OutlinedTextField(value = geburtsjahr, onValueChange = { geburtsjahr = it }, label = { Text("Geburtsjahr") })
}
},
confirmButton = {
Button(onClick = {
onSave(pferd.copy(
name = name,
lebensnummer = lebensnummer,
oepsNummer = oepsNummer,
geburtsjahr = geburtsjahr.toIntOrNull()
))
}) {
Text("Speichern")
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Abbrechen")
}
}
)
}
@@ -36,6 +36,22 @@ fun NennungenTabContent(
) {
val state by viewModel.state.collectAsState()
// --- Editoren ---
state.selectedReiter?.let { reiter ->
ReiterEditDialog(
reiter = reiter,
onDismiss = { viewModel.selectReiter(null) },
onSave = { viewModel.saveReiter(it) }
)
}
state.selectedPferd?.let { pferd ->
PferdEditDialog(
pferd = pferd,
onDismiss = { viewModel.selectPferd(null) },
onSave = { viewModel.savePferd(it) }
)
}
Row(modifier = Modifier.fillMaxSize()) {
// ── Linke Spalte: Suche + Tabelle ─────────────────────────────────────
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
@@ -44,6 +44,10 @@ fun DesktopApp() {
&& currentScreen !is AppScreen.VeranstalterDetail && currentScreen !is AppScreen.VeranstaltungKonfig
&& currentScreen !is AppScreen.VeranstaltungProfil && currentScreen !is AppScreen.TurnierDetail
&& currentScreen !is AppScreen.TurnierNeu
&& currentScreen !is AppScreen.ReiterVerwaltung
&& currentScreen !is AppScreen.PferdVerwaltung
&& currentScreen !is AppScreen.VereinVerwaltung
&& currentScreen !is AppScreen.StammdatenImport
) {
LaunchedEffect(Unit) {
// Standard: Start im Onboarding
@@ -17,6 +17,7 @@ import at.mocode.frontend.features.verein.di.vereinFeatureModule
import at.mocode.frontend.features.nennung.di.nennungFeatureModule
import at.mocode.frontend.features.pferde.di.pferdeModule
import at.mocode.frontend.features.reiter.di.reiterModule
import at.mocode.turnier.feature.di.turnierFeatureModule
import at.mocode.ping.feature.di.pingFeatureModule
import at.mocode.zns.feature.di.znsImportModule
import kotlinx.coroutines.runBlocking
@@ -41,6 +42,7 @@ fun main() = application {
pferdeModule,
reiterModule,
vereinFeatureModule,
turnierFeatureModule,
desktopModule,
)
}
@@ -117,6 +117,10 @@ fun PreviewTurnierOrganisationTab() {
val mockMasterdataRepo = object : MasterdataRepository {
override suspend fun searchReiter(query: String): Result<List<Reiter>> = Result.success(emptyList())
override suspend fun searchPferde(query: String): Result<List<Pferd>> = Result.success(emptyList())
override suspend fun getReiter(id: String): Result<Reiter> = Result.failure(NotImplementedError())
override suspend fun saveReiter(reiter: Reiter): Result<Reiter> = Result.success(reiter)
override suspend fun getPferd(id: String): Result<Pferd> = Result.failure(NotImplementedError())
override suspend fun savePferd(pferd: Pferd): Result<Pferd> = Result.success(pferd)
override suspend fun searchFunktionaere(query: String): Result<List<Funktionaer>> = Result.success(emptyList())
override suspend fun listVereine(): Result<List<Verein>> = Result.success(emptyList())
override suspend fun getVereinById(id: String): Result<Verein> = Result.failure(NotImplementedError())
@@ -185,6 +189,10 @@ fun PreviewTurnierNennungenTab() {
val mockMasterdataRepo = object : MasterdataRepository {
override suspend fun searchReiter(query: String): Result<List<Reiter>> = Result.success(emptyList())
override suspend fun searchPferde(query: String): Result<List<Pferd>> = Result.success(emptyList())
override suspend fun getReiter(id: String): Result<Reiter> = Result.failure(NotImplementedError())
override suspend fun saveReiter(reiter: Reiter): Result<Reiter> = Result.success(reiter)
override suspend fun getPferd(id: String): Result<Pferd> = Result.failure(NotImplementedError())
override suspend fun savePferd(pferd: Pferd): Result<Pferd> = Result.success(pferd)
override suspend fun searchFunktionaere(query: String): Result<List<Funktionaer>> = Result.success(emptyList())
override suspend fun listVereine(): Result<List<Verein>> = Result.success(emptyList())
override suspend fun getVereinById(id: String): Result<Verein> = Result.failure(NotImplementedError())