Add Zeitplan fields to domain and DTO models, implement UpdateZeitplan intent and API integration, and update ViewModel for Zeitplan state consistency.
This commit is contained in:
+14
-2
@@ -18,7 +18,13 @@ fun BewerbDto.toDomain(): Bewerb = Bewerb(
|
||||
name = name,
|
||||
sparte = sparte,
|
||||
klasse = klasse,
|
||||
nennungen = nennungen
|
||||
nennungen = nennungen,
|
||||
geplantesDatum = geplantesDatum,
|
||||
beginnZeit = beginnZeit,
|
||||
reitdauerMinuten = reitdauerMinuten,
|
||||
umbauMinuten = umbauMinuten,
|
||||
besichtigungMinuten = besichtigungMinuten,
|
||||
austragungsplatzId = austragungsplatzId,
|
||||
)
|
||||
|
||||
fun Bewerb.toDto(): BewerbDto = BewerbDto(
|
||||
@@ -29,7 +35,13 @@ fun Bewerb.toDto(): BewerbDto = BewerbDto(
|
||||
name = name,
|
||||
sparte = sparte,
|
||||
klasse = klasse,
|
||||
nennungen = nennungen
|
||||
nennungen = nennungen,
|
||||
geplantesDatum = geplantesDatum,
|
||||
beginnZeit = beginnZeit,
|
||||
reitdauerMinuten = reitdauerMinuten,
|
||||
umbauMinuten = umbauMinuten,
|
||||
besichtigungMinuten = besichtigungMinuten,
|
||||
austragungsplatzId = austragungsplatzId,
|
||||
)
|
||||
|
||||
fun AbteilungDto.toDomain(): Abteilung = Abteilung(id = id, bewerbId = bewerbId, name = name)
|
||||
|
||||
+6
@@ -18,6 +18,12 @@ data class BewerbDto(
|
||||
val sparte: String,
|
||||
val klasse: String,
|
||||
val nennungen: Int,
|
||||
val geplantesDatum: String? = null,
|
||||
val beginnZeit: String? = null,
|
||||
val reitdauerMinuten: Int? = null,
|
||||
val umbauMinuten: Int? = null,
|
||||
val besichtigungMinuten: Int? = null,
|
||||
val austragungsplatzId: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
+8
@@ -10,6 +10,13 @@ data class Bewerb(
|
||||
val klasse: String,
|
||||
val nennungen: Int,
|
||||
val warnungen: List<AbteilungsWarnung> = emptyList(),
|
||||
// Zeitplan-Felder
|
||||
val geplantesDatum: String? = null, // ISO-Format
|
||||
val beginnZeit: String? = null, // "HH:mm"
|
||||
val reitdauerMinuten: Int? = null,
|
||||
val umbauMinuten: Int? = null,
|
||||
val besichtigungMinuten: Int? = null,
|
||||
val austragungsplatzId: String? = null,
|
||||
)
|
||||
|
||||
data class AbteilungsWarnung(
|
||||
@@ -23,6 +30,7 @@ interface BewerbRepository {
|
||||
suspend fun getById(id: Long): Result<Bewerb>
|
||||
suspend fun create(model: Bewerb): Result<Bewerb>
|
||||
suspend fun update(id: Long, model: Bewerb): Result<Bewerb>
|
||||
suspend fun updateZeitplan(id: Long, datum: String?, beginn: String?, platzId: String?): Result<Bewerb>
|
||||
suspend fun delete(id: Long): Result<Unit>
|
||||
suspend fun importBewerbe(turnierId: Long, bewerbe: List<at.mocode.zns.parser.ZnsBewerb>): Result<Unit>
|
||||
}
|
||||
|
||||
+10
@@ -70,6 +70,7 @@ sealed interface BewerbIntent {
|
||||
data object StartNetworkScan : BewerbIntent
|
||||
data object StopNetworkScan : BewerbIntent
|
||||
data object RefreshDiscoveredNodes : BewerbIntent
|
||||
data class UpdateZeitplan(val id: Long, val beginnZeit: String?) : BewerbIntent
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +161,15 @@ class BewerbViewModel(
|
||||
is BewerbIntent.StartNetworkScan -> startScan()
|
||||
is BewerbIntent.StopNetworkScan -> stopScan()
|
||||
is BewerbIntent.RefreshDiscoveredNodes -> refreshNodes()
|
||||
is BewerbIntent.UpdateZeitplan -> updateZeitplan(intent.id, intent.beginnZeit)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateZeitplan(id: Long, beginn: String?) {
|
||||
scope.launch {
|
||||
repo.updateZeitplan(id, null, beginn, null).onSuccess {
|
||||
load() // Neu laden um Konsistenz zu prüfen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
@@ -72,6 +72,23 @@ class DefaultBewerbRepository(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateZeitplan(id: Long, datum: String?, beginn: String?, platzId: String?): Result<Bewerb> = runCatching {
|
||||
val response = client.patch("${ApiRoutes.API_PREFIX}/bewerbe/$id/zeitplan") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(mapOf(
|
||||
"geplantesDatum" to datum,
|
||||
"beginnZeit" to beginn,
|
||||
"austragungsplatzId" to platzId
|
||||
))
|
||||
}
|
||||
when {
|
||||
response.status.isSuccess() -> response.body<BewerbDto>().toDomain()
|
||||
response.status == HttpStatusCode.NotFound -> throw NotFound()
|
||||
response.status.value >= 500 -> throw ServerError()
|
||||
else -> throw HttpError(response.status.value)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long): Result<Unit> = runCatching {
|
||||
val response = client.delete("${ApiRoutes.API_PREFIX}/bewerbe/$id")
|
||||
when {
|
||||
|
||||
+7
-4
@@ -12,6 +12,9 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
/**
|
||||
* Detailansicht eines Turniers gemäß Vision_03.
|
||||
*
|
||||
@@ -61,6 +64,8 @@ fun TurnierDetailScreen(
|
||||
"ERGEBNISLISTEN",
|
||||
)
|
||||
|
||||
val bewerbViewModel: BewerbViewModel = koinInject { parametersOf(turnierId) }
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Horizontale Tab-Bar (direkt unter der TopBar)
|
||||
PrimaryScrollableTabRow(
|
||||
@@ -100,13 +105,11 @@ fun TurnierDetailScreen(
|
||||
veranstalterLogoUrl = veranstalterLogoUrl,
|
||||
)
|
||||
1 -> OrganisationTabContent()
|
||||
2 -> Box(modifier = Modifier.fillMaxSize()) {
|
||||
Text("BEWERBE Tab (Anbindung in Arbeit)", modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
2 -> BewerbeTabContent(viewModel = bewerbViewModel, turnierId = turnierId)
|
||||
3 -> ArtikelTabContent()
|
||||
4 -> AbrechnungTabContent(veranstaltungId = veranstaltungId)
|
||||
5 -> NennungenTabContent(onAbrechnungClick = { selectedTab = 4 })
|
||||
6 -> ZeitplanTabContent(turnierId = turnierId)
|
||||
6 -> ZeitplanTabContent(turnierId = turnierId, viewModel = bewerbViewModel)
|
||||
7 -> StartlistenTabContent()
|
||||
8 -> ErgebnislistenTabContent()
|
||||
}
|
||||
|
||||
+31
-6
@@ -38,9 +38,33 @@ private val MINUTE_HEIGHT = HOUR_HEIGHT / 60
|
||||
*/
|
||||
@Composable
|
||||
fun ZeitplanTabContent(
|
||||
turnierId: Long
|
||||
turnierId: Long,
|
||||
viewModel: BewerbViewModel
|
||||
) {
|
||||
var items by remember { mutableStateOf(sampleZeitplanItems()) }
|
||||
val state by viewModel.state.collectAsState()
|
||||
val items = state.filtered.map { bewerb ->
|
||||
val startMin = if (bewerb.beginnZeit != null) {
|
||||
val parts = bewerb.beginnZeit.split(":")
|
||||
parts[0].toInt() * 60 + parts[1].toInt()
|
||||
} else {
|
||||
7 * 60 // Default 07:00 wenn nichts gesetzt
|
||||
}
|
||||
|
||||
ZeitplanItemUi(
|
||||
id = bewerb.id,
|
||||
nummer = bewerb.tag.filter { it.isDigit() }.toIntOrNull() ?: 0,
|
||||
name = bewerb.name,
|
||||
startMinutes = startMin,
|
||||
durationMinutes = bewerb.reitdauerMinuten ?: 60,
|
||||
color = when (bewerb.sparte) {
|
||||
"DRESSUR" -> Color(0xFF1E3A8A)
|
||||
"SPRINGEN" -> Color(0xFF059669)
|
||||
else -> ZeitplanBlue
|
||||
},
|
||||
hasConflict = bewerb.warnungen.isNotEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().background(ZeitplanBg)) {
|
||||
@@ -63,13 +87,14 @@ fun ZeitplanTabContent(
|
||||
ZeitplanGitter()
|
||||
|
||||
// Bewerbe / Blöcke
|
||||
items.forEachIndexed { index, item ->
|
||||
items.forEach { item ->
|
||||
DraggableBewerbBox(
|
||||
item = item,
|
||||
onPositionChange = { newMinutes ->
|
||||
items = items.toMutableList().apply {
|
||||
this[index] = item.copy(startMinutes = newMinutes)
|
||||
}
|
||||
val h = newMinutes / 60
|
||||
val m = newMinutes % 60
|
||||
val timeStr = "${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}"
|
||||
viewModel.send(BewerbIntent.UpdateZeitplan(item.id, timeStr))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
+1
@@ -122,6 +122,7 @@ fun PreviewTurnierBewerbeTab() {
|
||||
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 updateZeitplan(id: Long, datum: String?, beginn: String?, platzId: String?): Result<Bewerb> = Result.failure(NotImplementedError())
|
||||
override suspend fun importBewerbe(turnierId: Long, bewerbe: List<ZnsBewerb>): Result<Unit> = Result.success(Unit)
|
||||
}
|
||||
val mockStartlistenRepo = object : StartlistenRepository {
|
||||
|
||||
Reference in New Issue
Block a user