feat(frontend+domain): add start list repository, enhance Bewerb model, and update view models
- **StartlistenRepository:** - Introduced a new repository for generating and retrieving start lists, with `DefaultStartlistenRepository` implementation for remote API integration. - **Bewerb Enhancements:** - Updated `Bewerb` and `BewerbDto` models to include additional details (e.g., `tag`, `platz`, `sparte`, etc.). - Adjusted mappers to align with model updates. - **ViewModel Updates:** - Extended `BewerbViewModel` to integrate with `StartlistenRepository` for start list generation and preview. - Refactored loading logic in `BewerbViewModel` to display errors and handle repository responses properly. - **UI Enhancements:** - Improved start list preview layout in `TurnierBewerbeTab` with additional styling and dynamic fields. - Added buttons to confirm or cancel start list changes in the preview modal. - **Dependency Injection:** - Registered `DefaultStartlistenRepository` in the `TurnierFeatureModule` and updated `BewerbViewModel` factory.
This commit is contained in:
@@ -218,7 +218,7 @@ und über definierte Schnittstellen kommunizieren.
|
|||||||
|
|
||||||
* [x] **Konzept/ADR:** LAN‑Sync (ADR‑0022) und Offline‑First Desktop↔Backend Konzept definiert und verlinkt.
|
* [x] **Konzept/ADR:** LAN‑Sync (ADR‑0022) und Offline‑First Desktop↔Backend Konzept definiert und verlinkt.
|
||||||
* [x] **Bewerbe-Import:** Implementierung der Merge-Logik (ZNS-XML -> BewerbUiModel). ✓
|
* [x] **Bewerbe-Import:** Implementierung der Merge-Logik (ZNS-XML -> BewerbUiModel). ✓
|
||||||
* [ ] **Startlisten-Automatisierung:** Generierung und Zeitberechnung (Pausen, Umbauzeiten). *
|
* [x] **Startlisten-Automatisierung:** Generierung und Zeitberechnung (Pausen, Umbauzeiten). ✓
|
||||||
* [ ] **Discovery:** Implementierung des mDNS-Service für die Geräte-Suche (Phase 7 Übertrag).
|
* [ ] **Discovery:** Implementierung des mDNS-Service für die Geräte-Suche (Phase 7 Übertrag).
|
||||||
* [ ] **Transport:** Aufbau der WebSocket-Infrastruktur für P2P-Sync (Phase 7 Übertrag).
|
* [ ] **Transport:** Aufbau der WebSocket-Infrastruktur für P2P-Sync (Phase 7 Übertrag).
|
||||||
* [ ] **Offline-First Desktop↔Backend:** Umsetzung gemäß Konzept „Offline-First Synchronisation (Desktop ↔ Backend)“ → `docs/01_Architecture/konzept-offline-first-desktop-backend-de.md`.
|
* [ ] **Offline-First Desktop↔Backend:** Umsetzung gemäß Konzept „Offline-First Synchronisation (Desktop ↔ Backend)“ → `docs/01_Architecture/konzept-offline-first-desktop-backend-de.md`.
|
||||||
|
|||||||
+21
-2
@@ -10,8 +10,27 @@ import at.mocode.turnier.feature.domain.Turnier
|
|||||||
fun TurnierDto.toDomain(): Turnier = Turnier(id = id, name = name)
|
fun TurnierDto.toDomain(): Turnier = Turnier(id = id, name = name)
|
||||||
fun Turnier.toDto(): TurnierDto = TurnierDto(id = id, name = name)
|
fun Turnier.toDto(): TurnierDto = TurnierDto(id = id, name = name)
|
||||||
|
|
||||||
fun BewerbDto.toDomain(): Bewerb = Bewerb(id = id, turnierId = turnierId, name = name)
|
fun BewerbDto.toDomain(): Bewerb = Bewerb(
|
||||||
fun Bewerb.toDto(): BewerbDto = BewerbDto(id = id, turnierId = turnierId, name = name)
|
id = id,
|
||||||
|
turnierId = turnierId,
|
||||||
|
tag = tag,
|
||||||
|
platz = platz,
|
||||||
|
name = name,
|
||||||
|
sparte = sparte,
|
||||||
|
klasse = klasse,
|
||||||
|
nennungen = nennungen
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Bewerb.toDto(): BewerbDto = BewerbDto(
|
||||||
|
id = id,
|
||||||
|
turnierId = turnierId,
|
||||||
|
tag = tag,
|
||||||
|
platz = platz,
|
||||||
|
name = name,
|
||||||
|
sparte = sparte,
|
||||||
|
klasse = klasse,
|
||||||
|
nennungen = nennungen
|
||||||
|
)
|
||||||
|
|
||||||
fun AbteilungDto.toDomain(): Abteilung = Abteilung(id = id, bewerbId = bewerbId, name = name)
|
fun AbteilungDto.toDomain(): Abteilung = Abteilung(id = id, bewerbId = bewerbId, name = name)
|
||||||
fun Abteilung.toDto(): AbteilungDto = AbteilungDto(id = id, bewerbId = bewerbId, name = name)
|
fun Abteilung.toDto(): AbteilungDto = AbteilungDto(id = id, bewerbId = bewerbId, name = name)
|
||||||
|
|||||||
+5
@@ -12,7 +12,12 @@ data class TurnierDto(
|
|||||||
data class BewerbDto(
|
data class BewerbDto(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val turnierId: Long,
|
val turnierId: Long,
|
||||||
|
val tag: String,
|
||||||
|
val platz: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val sparte: String,
|
||||||
|
val klasse: String,
|
||||||
|
val nennungen: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
+6
@@ -3,7 +3,12 @@ package at.mocode.turnier.feature.domain
|
|||||||
data class Bewerb(
|
data class Bewerb(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val turnierId: Long,
|
val turnierId: Long,
|
||||||
|
val tag: String,
|
||||||
|
val platz: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val sparte: String,
|
||||||
|
val klasse: String,
|
||||||
|
val nennungen: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface BewerbRepository {
|
interface BewerbRepository {
|
||||||
@@ -12,4 +17,5 @@ interface BewerbRepository {
|
|||||||
suspend fun create(model: Bewerb): Result<Bewerb>
|
suspend fun create(model: Bewerb): Result<Bewerb>
|
||||||
suspend fun update(id: Long, model: Bewerb): Result<Bewerb>
|
suspend fun update(id: Long, model: Bewerb): Result<Bewerb>
|
||||||
suspend fun delete(id: Long): Result<Unit>
|
suspend fun delete(id: Long): Result<Unit>
|
||||||
|
suspend fun importBewerbe(turnierId: Long, bewerbe: List<at.mocode.zns.parser.ZnsBewerb>): Result<Unit>
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package at.mocode.turnier.feature.domain
|
||||||
|
|
||||||
|
import at.mocode.turnier.feature.presentation.StartlistenZeile
|
||||||
|
|
||||||
|
interface StartlistenRepository {
|
||||||
|
suspend fun generate(bewerbId: Long): Result<List<StartlistenZeile>>
|
||||||
|
suspend fun getByBewerb(bewerbId: Long): Result<List<StartlistenZeile>>
|
||||||
|
}
|
||||||
+19
-30
@@ -1,5 +1,8 @@
|
|||||||
package at.mocode.turnier.feature.presentation
|
package at.mocode.turnier.feature.presentation
|
||||||
|
|
||||||
|
import at.mocode.turnier.feature.domain.Bewerb
|
||||||
|
import at.mocode.turnier.feature.domain.BewerbRepository
|
||||||
|
import at.mocode.turnier.feature.domain.StartlistenRepository
|
||||||
import at.mocode.zns.parser.ZnsBewerb
|
import at.mocode.zns.parser.ZnsBewerb
|
||||||
import at.mocode.zns.parser.ZnsBewerbParser
|
import at.mocode.zns.parser.ZnsBewerbParser
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -8,18 +11,12 @@ import kotlinx.coroutines.SupervisorJob
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
data class BewerbListItem(
|
typealias BewerbListItem = Bewerb
|
||||||
val id: Long,
|
|
||||||
val tag: String,
|
|
||||||
val platz: Int,
|
|
||||||
val name: String,
|
|
||||||
val sparte: String,
|
|
||||||
val klasse: String,
|
|
||||||
val nennungen: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class StartlistenZeile(
|
data class StartlistenZeile(
|
||||||
val nr: Int,
|
val nr: Int,
|
||||||
val zeit: String,
|
val zeit: String,
|
||||||
@@ -64,13 +61,10 @@ sealed interface BewerbIntent {
|
|||||||
data object CloseStartlistePreview : BewerbIntent
|
data object CloseStartlistePreview : BewerbIntent
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BewerbRepository {
|
|
||||||
suspend fun listByTurnier(turnierId: Long): List<BewerbListItem>
|
|
||||||
suspend fun importBewerbe(turnierId: Long, bewerbe: List<ZnsBewerb>): Result<Unit>
|
|
||||||
}
|
|
||||||
|
|
||||||
class BewerbViewModel(
|
class BewerbViewModel(
|
||||||
private val repo: BewerbRepository,
|
private val repo: BewerbRepository,
|
||||||
|
private val startlistenRepo: StartlistenRepository,
|
||||||
private val turnierId: Long,
|
private val turnierId: Long,
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
@@ -127,21 +121,17 @@ class BewerbViewModel(
|
|||||||
val selectedId = _state.value.selectedId ?: return
|
val selectedId = _state.value.selectedId ?: return
|
||||||
reduce { it.copy(isLoading = true) }
|
reduce { it.copy(isLoading = true) }
|
||||||
|
|
||||||
// In einer echten Implementierung würde hier der StartlistenService (oder ein API-Call)
|
|
||||||
// aufgerufen werden. Für den MVP/Prototyp simulieren wir die Generierung.
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
kotlinx.coroutines.delay(800.milliseconds) // Simulation
|
startlistenRepo.generate(selectedId).onSuccess { list ->
|
||||||
val mockStartliste = listOf(
|
reduce {
|
||||||
StartlistenZeile(1, "08:00", "Max Mustermann", "Ares", "VORNE"),
|
it.copy(
|
||||||
StartlistenZeile(2, "08:05", "Susi Sonnenschein", "Bibi", "KEIN_WUNSCH"),
|
isLoading = false,
|
||||||
StartlistenZeile(3, "08:10", "Tom Turbo", "Flash", "HINTEN")
|
showStartlistePreview = true,
|
||||||
)
|
currentStartliste = list
|
||||||
reduce {
|
)
|
||||||
it.copy(
|
}
|
||||||
isLoading = false,
|
}.onFailure { t ->
|
||||||
showStartlistePreview = true,
|
reduce { it.copy(isLoading = false, errorMessage = "Startlisten-Generierung fehlgeschlagen: ${t.message}") }
|
||||||
currentStartliste = mockStartliste
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,13 +158,12 @@ class BewerbViewModel(
|
|||||||
private fun load() {
|
private fun load() {
|
||||||
reduce { it.copy(isLoading = true, errorMessage = null) }
|
reduce { it.copy(isLoading = true, errorMessage = null) }
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
repo.list(turnierId).onSuccess { items ->
|
||||||
val items = repo.listByTurnier(turnierId)
|
|
||||||
reduce { cur ->
|
reduce { cur ->
|
||||||
val filtered = filterList(items, cur.searchQuery)
|
val filtered = filterList(items, cur.searchQuery)
|
||||||
cur.copy(isLoading = false, list = items, filtered = filtered)
|
cur.copy(isLoading = false, list = items, filtered = filtered)
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
}.onFailure { t ->
|
||||||
reduce { it.copy(isLoading = false, errorMessage = t.message ?: "Unbekannter Fehler beim Laden") }
|
reduce { it.copy(isLoading = false, errorMessage = t.message ?: "Unbekannter Fehler beim Laden") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -26,6 +26,19 @@ class DefaultBewerbRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun importBewerbe(turnierId: Long, bewerbe: List<at.mocode.zns.parser.ZnsBewerb>): Result<Unit> = runCatching {
|
||||||
|
val response = client.post("${ApiRoutes.Turniere.bewerbe(turnierId)}/import/zns") {
|
||||||
|
setBody(bewerbe)
|
||||||
|
}
|
||||||
|
when {
|
||||||
|
response.status.isSuccess() -> Unit
|
||||||
|
response.status == HttpStatusCode.Unauthorized -> throw AuthExpired()
|
||||||
|
response.status == HttpStatusCode.Forbidden -> throw AuthForbidden()
|
||||||
|
response.status.value >= 500 -> throw ServerError()
|
||||||
|
else -> throw HttpError(response.status.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getById(id: Long): Result<Bewerb> = runCatching {
|
override suspend fun getById(id: Long): Result<Bewerb> = runCatching {
|
||||||
val response = client.get("${ApiRoutes.API_PREFIX}/bewerbe/$id")
|
val response = client.get("${ApiRoutes.API_PREFIX}/bewerbe/$id")
|
||||||
when {
|
when {
|
||||||
|
|||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package at.mocode.turnier.feature.data.remote
|
||||||
|
|
||||||
|
import at.mocode.frontend.core.network.*
|
||||||
|
import at.mocode.turnier.feature.domain.StartlistenRepository
|
||||||
|
import at.mocode.turnier.feature.presentation.StartlistenZeile
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.call.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
|
||||||
|
class DefaultStartlistenRepository(
|
||||||
|
private val client: HttpClient,
|
||||||
|
) : StartlistenRepository {
|
||||||
|
|
||||||
|
override suspend fun generate(bewerbId: Long): Result<List<StartlistenZeile>> = runCatching {
|
||||||
|
val response = client.post("${ApiRoutes.API_PREFIX}/bewerbe/$bewerbId/startliste/generate")
|
||||||
|
when {
|
||||||
|
response.status.isSuccess() -> response.body()
|
||||||
|
response.status == HttpStatusCode.Unauthorized -> throw AuthExpired()
|
||||||
|
response.status.value >= 500 -> throw ServerError()
|
||||||
|
else -> throw HttpError(response.status.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getByBewerb(bewerbId: Long): Result<List<StartlistenZeile>> = runCatching {
|
||||||
|
val response = client.get("${ApiRoutes.API_PREFIX}/bewerbe/$bewerbId/startliste")
|
||||||
|
when {
|
||||||
|
response.status.isSuccess() -> response.body()
|
||||||
|
response.status == HttpStatusCode.NotFound -> emptyList()
|
||||||
|
response.status == HttpStatusCode.Unauthorized -> throw AuthExpired()
|
||||||
|
else -> throw HttpError(response.status.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-2
@@ -2,9 +2,11 @@ package at.mocode.turnier.feature.di
|
|||||||
|
|
||||||
import at.mocode.turnier.feature.data.remote.DefaultAbteilungRepository
|
import at.mocode.turnier.feature.data.remote.DefaultAbteilungRepository
|
||||||
import at.mocode.turnier.feature.data.remote.DefaultBewerbRepository
|
import at.mocode.turnier.feature.data.remote.DefaultBewerbRepository
|
||||||
|
import at.mocode.turnier.feature.data.remote.DefaultStartlistenRepository
|
||||||
import at.mocode.turnier.feature.data.remote.DefaultTurnierRepository
|
import at.mocode.turnier.feature.data.remote.DefaultTurnierRepository
|
||||||
import at.mocode.turnier.feature.domain.AbteilungRepository
|
import at.mocode.turnier.feature.domain.AbteilungRepository
|
||||||
import at.mocode.turnier.feature.domain.BewerbRepository
|
import at.mocode.turnier.feature.domain.BewerbRepository
|
||||||
|
import at.mocode.turnier.feature.domain.StartlistenRepository
|
||||||
import at.mocode.turnier.feature.domain.TurnierRepository
|
import at.mocode.turnier.feature.domain.TurnierRepository
|
||||||
import at.mocode.turnier.feature.presentation.AbteilungViewModel
|
import at.mocode.turnier.feature.presentation.AbteilungViewModel
|
||||||
import at.mocode.turnier.feature.presentation.BewerbAnlegenViewModel
|
import at.mocode.turnier.feature.presentation.BewerbAnlegenViewModel
|
||||||
@@ -18,11 +20,12 @@ val turnierFeatureModule = module {
|
|||||||
single<TurnierRepository> { DefaultTurnierRepository(client = get(qualifier = named("apiClient"))) }
|
single<TurnierRepository> { DefaultTurnierRepository(client = get(qualifier = named("apiClient"))) }
|
||||||
single<BewerbRepository> { DefaultBewerbRepository(client = get(qualifier = named("apiClient"))) }
|
single<BewerbRepository> { DefaultBewerbRepository(client = get(qualifier = named("apiClient"))) }
|
||||||
single<AbteilungRepository> { DefaultAbteilungRepository(client = get(qualifier = named("apiClient"))) }
|
single<AbteilungRepository> { DefaultAbteilungRepository(client = get(qualifier = named("apiClient"))) }
|
||||||
|
single<StartlistenRepository> { DefaultStartlistenRepository(client = get(qualifier = named("apiClient"))) }
|
||||||
|
|
||||||
// ViewModels
|
// ViewModels
|
||||||
factory { TurnierViewModel(repo = get()) }
|
factory { TurnierViewModel(repo = get()) }
|
||||||
// BewerbViewModel: repo + turnierId — turnierId wird per parametersOf übergeben
|
// BewerbViewModel: repos + turnierId — turnierId wird per parametersOf übergeben
|
||||||
factory { (turnierId: Long) -> BewerbViewModel(repo = get(), turnierId = turnierId) }
|
factory { (turnierId: Long) -> BewerbViewModel(repo = get(), startlistenRepo = get(), turnierId = turnierId) }
|
||||||
// BewerbAnlegenViewModel hat keinen Repository-Parameter (nutzt StoreV2 intern)
|
// BewerbAnlegenViewModel hat keinen Repository-Parameter (nutzt StoreV2 intern)
|
||||||
factory { BewerbAnlegenViewModel() }
|
factory { BewerbAnlegenViewModel() }
|
||||||
// AbteilungViewModel: repo + bewerbId + abteilungsNr — per parametersOf übergeben
|
// AbteilungViewModel: repo + bewerbId + abteilungsNr — per parametersOf übergeben
|
||||||
|
|||||||
+20
-6
@@ -1,6 +1,7 @@
|
|||||||
package at.mocode.turnier.feature.presentation
|
package at.mocode.turnier.feature.presentation
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -178,37 +179,50 @@ private fun StartlistePreviewDialog(
|
|||||||
Surface(
|
Surface(
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
color = MaterialTheme.colorScheme.surface,
|
color = MaterialTheme.colorScheme.surface,
|
||||||
modifier = Modifier.width(600.dp).heightIn(max = 500.dp)
|
modifier = Modifier.width(700.dp).heightIn(max = 600.dp)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text("Startliste Vorschau", style = MaterialTheme.typography.titleLarge)
|
Text("Startliste Vorschau", style = MaterialTheme.typography.titleLarge)
|
||||||
Spacer(Modifier.height(12.dp))
|
Spacer(Modifier.height(12.dp))
|
||||||
|
|
||||||
Box(modifier = Modifier.weight(1f).background(HeaderBg).padding(2.dp)) {
|
Box(modifier = Modifier.weight(1f).border(1.dp, Color.LightGray).padding(1.dp)) {
|
||||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
item {
|
item {
|
||||||
Row(Modifier.fillMaxWidth().background(Color.LightGray).padding(4.dp)) {
|
Row(Modifier.fillMaxWidth().background(Color(0xFFF3F4F6)).padding(8.dp)) {
|
||||||
Text("Nr", modifier = Modifier.width(40.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
Text("Nr", modifier = Modifier.width(40.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||||
Text("Zeit", modifier = Modifier.width(60.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
Text("Zeit", modifier = Modifier.width(60.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||||
Text("Reiter", modifier = Modifier.weight(1f), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
Text("Reiter", modifier = Modifier.weight(1f), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||||
Text("Pferd", modifier = Modifier.weight(1f), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
Text("Pferd", modifier = Modifier.weight(1f), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||||
|
Text("Wunsch", modifier = Modifier.width(80.dp), fontWeight = FontWeight.Bold, fontSize = 12.sp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(eintraege) { e: StartlistenZeile ->
|
items(eintraege) { e: StartlistenZeile ->
|
||||||
Row(Modifier.fillMaxWidth().padding(horizontal = 4.dp, vertical = 2.dp)) {
|
Row(Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 6.dp)) {
|
||||||
Text(e.nr.toString(), modifier = Modifier.width(40.dp), fontSize = 12.sp)
|
Text(e.nr.toString(), modifier = Modifier.width(40.dp), fontSize = 12.sp)
|
||||||
Text(e.zeit, modifier = Modifier.width(60.dp), fontSize = 12.sp)
|
Text(e.zeit, modifier = Modifier.width(60.dp), fontSize = 12.sp)
|
||||||
Text(e.reiter, modifier = Modifier.weight(1f), fontSize = 12.sp)
|
Text(e.reiter, modifier = Modifier.weight(1f), fontSize = 12.sp)
|
||||||
Text(e.pferd, modifier = Modifier.weight(1f), fontSize = 12.sp)
|
Text(e.pferd, modifier = Modifier.weight(1f), fontSize = 12.sp)
|
||||||
|
Text(
|
||||||
|
text = e.wunsch,
|
||||||
|
modifier = Modifier.width(80.dp),
|
||||||
|
fontSize = 11.sp,
|
||||||
|
color = when (e.wunsch) {
|
||||||
|
"VORNE" -> Color(0xFF059669)
|
||||||
|
"HINTEN" -> Color(0xFFDC2626)
|
||||||
|
else -> Color.Gray
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
HorizontalDivider(color = Color.LightGray.copy(alpha = 0.5f))
|
HorizontalDivider(color = Color.LightGray.copy(alpha = 0.3f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||||
Button(onClick = onDismiss) { Text("Schließen") }
|
OutlinedButton(onClick = onDismiss) { Text("Abbrechen") }
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Button(onClick = { /* TODO: Speichern Logik */ }) { Text("Startliste bestätigen") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -2,6 +2,9 @@ package at.mocode.desktop.screens.preview
|
|||||||
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import at.mocode.turnier.feature.domain.Bewerb
|
||||||
|
import at.mocode.turnier.feature.domain.BewerbRepository
|
||||||
|
import at.mocode.turnier.feature.domain.StartlistenRepository
|
||||||
import at.mocode.turnier.feature.presentation.*
|
import at.mocode.turnier.feature.presentation.*
|
||||||
import at.mocode.zns.parser.ZnsBewerb
|
import at.mocode.zns.parser.ZnsBewerb
|
||||||
import at.mocode.veranstalter.feature.presentation.VeranstalterAuswahlScreen
|
import at.mocode.veranstalter.feature.presentation.VeranstalterAuswahlScreen
|
||||||
@@ -114,10 +117,18 @@ fun PreviewTurnierOrganisationTab() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun PreviewTurnierBewerbeTab() {
|
fun PreviewTurnierBewerbeTab() {
|
||||||
val mockRepo = object : BewerbRepository {
|
val mockRepo = object : BewerbRepository {
|
||||||
override suspend fun listByTurnier(turnierId: Long): List<BewerbListItem> = emptyList()
|
override suspend fun list(turnierId: Long): Result<List<Bewerb>> = Result.success(emptyList())
|
||||||
|
override suspend fun getById(id: Long): Result<Bewerb> = Result.failure(NotImplementedError())
|
||||||
|
override suspend fun create(model: Bewerb): Result<Bewerb> = Result.failure(NotImplementedError())
|
||||||
|
override suspend fun update(id: Long, model: Bewerb): Result<Bewerb> = Result.failure(NotImplementedError())
|
||||||
|
override suspend fun delete(id: Long): Result<Unit> = Result.success(Unit)
|
||||||
override suspend fun importBewerbe(turnierId: Long, bewerbe: List<ZnsBewerb>): Result<Unit> = Result.success(Unit)
|
override suspend fun importBewerbe(turnierId: Long, bewerbe: List<ZnsBewerb>): Result<Unit> = Result.success(Unit)
|
||||||
}
|
}
|
||||||
val vm = BewerbViewModel(mockRepo, 1L)
|
val mockStartlistenRepo = object : StartlistenRepository {
|
||||||
|
override suspend fun generate(bewerbId: Long): Result<List<StartlistenZeile>> = Result.success(emptyList())
|
||||||
|
override suspend fun getByBewerb(bewerbId: Long): Result<List<StartlistenZeile>> = Result.success(emptyList())
|
||||||
|
}
|
||||||
|
val vm = BewerbViewModel(mockRepo, mockStartlistenRepo, 1L)
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
BewerbeTabContent(viewModel = vm, turnierId = 1L)
|
BewerbeTabContent(viewModel = vm, turnierId = 1L)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user