refactor(desktop, core): Onboarding zu DeviceInitialization umbenannt, Navigation und Screens angepasst
Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
+65
-37
@@ -1,11 +1,13 @@
|
||||
package at.mocode.turnier.feature.presentation
|
||||
|
||||
import at.mocode.frontend.core.network.discovery.DiscoveredService
|
||||
import at.mocode.frontend.core.network.sync.DataChangedEvent
|
||||
import at.mocode.frontend.core.network.sync.PingEvent
|
||||
import at.mocode.frontend.core.network.sync.SyncManager
|
||||
import at.mocode.frontend.core.network.sync.*
|
||||
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.domain.model.StartlistenZeile
|
||||
import at.mocode.zns.parser.ZnsBewerb
|
||||
import at.mocode.zns.parser.ZnsBewerbParser
|
||||
import at.mocode.zns.parser.ZnsNennung
|
||||
@@ -17,7 +19,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import at.mocode.turnier.feature.domain.model.StartlistenZeile
|
||||
|
||||
typealias BewerbListItem = Bewerb
|
||||
|
||||
@@ -112,9 +113,11 @@ class BewerbViewModel(
|
||||
load() // Bei relevanten Änderungen neu laden
|
||||
}
|
||||
}
|
||||
|
||||
is PingEvent -> {
|
||||
// Optional: Heartbeat loggen oder Status anzeigen
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@@ -123,9 +126,11 @@ class BewerbViewModel(
|
||||
// Auch verbundene Peers beobachten
|
||||
scope.launch {
|
||||
manager.getConnectedPeers().collect { peers ->
|
||||
reduce { it.copy(discoveredNodes = peers.map { p ->
|
||||
DiscoveredService("P2P", p, 0)
|
||||
}) }
|
||||
reduce {
|
||||
it.copy(discoveredNodes = peers.map { p ->
|
||||
DiscoveredService("P2P", p, 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,38 +143,46 @@ class BewerbViewModel(
|
||||
is BewerbIntent.Select -> {
|
||||
reduce { it.copy(selectedId = intent.id) }
|
||||
if (intent.id != null) {
|
||||
loadErgebnisse()
|
||||
loadErgebnisse()
|
||||
}
|
||||
}
|
||||
|
||||
is BewerbIntent.ClearError -> reduce { it.copy(errorMessage = null) }
|
||||
|
||||
is BewerbIntent.OpenDialog -> {
|
||||
dialogVm.send(BewerbAnlegenIntent.Open)
|
||||
syncDialogState()
|
||||
}
|
||||
|
||||
is BewerbIntent.CloseDialog -> {
|
||||
dialogVm.send(BewerbAnlegenIntent.Close)
|
||||
syncDialogState()
|
||||
}
|
||||
|
||||
is BewerbIntent.SetBewerbsTyp -> {
|
||||
dialogVm.send(BewerbAnlegenIntent.SetBewerbsTyp(intent.typ))
|
||||
syncDialogState()
|
||||
}
|
||||
|
||||
is BewerbIntent.SetAbteilungsTyp -> {
|
||||
dialogVm.send(BewerbAnlegenIntent.SetAbteilungsTyp(intent.typ))
|
||||
syncDialogState()
|
||||
}
|
||||
|
||||
is BewerbIntent.OpenImportDialog -> _state.value = _state.value.copy(showImportDialog = true)
|
||||
is BewerbIntent.CloseImportDialog -> _state.value = _state.value.copy(showImportDialog = false, importPreview = emptyList(), nennungenPreview = emptyList())
|
||||
is BewerbIntent.CloseImportDialog -> _state.value =
|
||||
_state.value.copy(showImportDialog = false, importPreview = emptyList(), nennungenPreview = emptyList())
|
||||
|
||||
is BewerbIntent.ProcessImportFile -> {
|
||||
val bewerbe = intent.lines.mapNotNull { ZnsBewerbParser.parse(it) }
|
||||
val nennungen = intent.lines.mapNotNull { ZnsNennungParser.parse(it) }
|
||||
_state.value = _state.value.copy(importPreview = bewerbe, nennungenPreview = nennungen)
|
||||
}
|
||||
|
||||
is BewerbIntent.ConfirmImport -> {
|
||||
confirmImport()
|
||||
}
|
||||
|
||||
is BewerbIntent.GenerateStartliste -> generateStartliste()
|
||||
is BewerbIntent.CloseStartlistePreview -> reduce { it.copy(showStartlistePreview = false) }
|
||||
is BewerbIntent.StartNetworkScan -> startScan()
|
||||
@@ -183,38 +196,41 @@ class BewerbViewModel(
|
||||
is BewerbIntent.OpenErgebnisEdit -> {
|
||||
val bewerbId = state.value.selectedId?.toString() ?: ""
|
||||
reduce {
|
||||
it.copy(
|
||||
selectedZeile = intent.zeile,
|
||||
editingErgebnis = at.mocode.turnier.feature.domain.Ergebnis(
|
||||
nennungId = intent.zeile.nennungId,
|
||||
bewerbId = bewerbId
|
||||
)
|
||||
it.copy(
|
||||
selectedZeile = intent.zeile,
|
||||
editingErgebnis = at.mocode.turnier.feature.domain.Ergebnis(
|
||||
nennungId = intent.zeile.nennungId,
|
||||
bewerbId = bewerbId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is BewerbIntent.CloseErgebnisEdit -> reduce { it.copy(editingErgebnis = null, selectedZeile = null) }
|
||||
is BewerbIntent.SaveErgebnis -> {
|
||||
scope.launch {
|
||||
ergebnisRepo.save(intent.ergebnis).onSuccess {
|
||||
reduce { it.copy(editingErgebnis = null, selectedZeile = null) }
|
||||
loadErgebnisse()
|
||||
}
|
||||
ergebnisRepo.save(intent.ergebnis).onSuccess {
|
||||
reduce { it.copy(editingErgebnis = null, selectedZeile = null) }
|
||||
loadErgebnisse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BewerbIntent.CalculatePlatzierung -> {
|
||||
val selectedId = state.value.selectedId ?: return@send
|
||||
val selectedId = state.value.selectedId ?: return
|
||||
scope.launch {
|
||||
ergebnisRepo.calculatePlatzierung(selectedId.toString()).onSuccess {
|
||||
loadErgebnisse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is BewerbIntent.ExportErgebnislistePdf -> {
|
||||
val selectedId = state.value.selectedId ?: return@send
|
||||
val selectedId = state.value.selectedId ?: return
|
||||
scope.launch {
|
||||
ergebnisRepo.exportPdf(selectedId.toString()).onSuccess { bytes ->
|
||||
// In einer echten Desktop-App würde man hier einen File-Saver öffnen
|
||||
// Für den MVP loggen wir nur den Erfolg.
|
||||
// In einer echten Desktop-App würde man hier einen File-Saver öffnen.
|
||||
// Für den MVP loggen wir nur den Erfolg ein.
|
||||
println("PDF Export erfolgreich: ${bytes.size} bytes")
|
||||
}
|
||||
}
|
||||
@@ -225,9 +241,9 @@ class BewerbViewModel(
|
||||
private fun loadErgebnisse() {
|
||||
val bewerbId = state.value.selectedId ?: return
|
||||
scope.launch {
|
||||
ergebnisRepo.getForBewerb(bewerbId.toString()).onSuccess { list ->
|
||||
reduce { it.copy(ergebnisse = list) }
|
||||
}
|
||||
ergebnisRepo.getForBewerb(bewerbId.toString()).onSuccess { list ->
|
||||
reduce { it.copy(ergebnisse = list) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +264,12 @@ class BewerbViewModel(
|
||||
repo.getAuditLog(id).onSuccess { log ->
|
||||
_state.update { it.copy(auditLog = log, isAuditLoading = false) }
|
||||
}.onFailure { t ->
|
||||
_state.update { it.copy(isAuditLoading = false, errorMessage = "Audit-Log konnte nicht geladen werden: ${t.message}") }
|
||||
_state.update {
|
||||
it.copy(
|
||||
isAuditLoading = false,
|
||||
errorMessage = "Audit-Log konnte nicht geladen werden: ${t.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,7 +277,7 @@ class BewerbViewModel(
|
||||
private fun updateZeitplan(id: Long, beginn: String?) {
|
||||
scope.launch {
|
||||
repo.updateZeitplan(id, null, beginn, null).onSuccess {
|
||||
load() // Neu laden um Konsistenz zu prüfen
|
||||
load() // Neu laden, um Konsistenz zu prüfen
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,13 +285,15 @@ class BewerbViewModel(
|
||||
private fun startScan() {
|
||||
syncManager?.start(8080)
|
||||
_state.update { it.copy(isScanning = true) }
|
||||
// Nach dem Start des Servers ein Ping-Event broadcasten um Präsenz zu zeigen
|
||||
syncManager?.broadcastEvent(PingEvent(
|
||||
eventId = turnierId.toString(),
|
||||
sequenceNumber = 0,
|
||||
originNodeId = "Client-${(1000..9999).random()}",
|
||||
createdAt = 0 // In commonMain ohne Clock-Lib erst mal 0
|
||||
))
|
||||
// Nach dem Start des Servers ein ConnectivityCheck-Event Broadcasting, um Präsenz zu zeigen
|
||||
syncManager?.broadcastEvent(
|
||||
PingEvent(
|
||||
eventId = turnierId.toString(),
|
||||
sequenceNumber = 0,
|
||||
originNodeId = "Client-${(1000..9999).random()}",
|
||||
createdAt = 0 // In commonMain ohne Clock-Lib erst mal 0
|
||||
)
|
||||
)
|
||||
refreshNodes()
|
||||
}
|
||||
|
||||
@@ -318,7 +341,12 @@ class BewerbViewModel(
|
||||
reduce { it.copy(showImportDialog = false, importPreview = emptyList()) }
|
||||
load()
|
||||
} else {
|
||||
reduce { it.copy(isLoading = false, errorMessage = "Import fehlgeschlagen: ${result.exceptionOrNull()?.message}") }
|
||||
reduce {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
errorMessage = "Import fehlgeschlagen: ${result.exceptionOrNull()?.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,9 +376,9 @@ class BewerbViewModel(
|
||||
val q = query.trim()
|
||||
return list.filter {
|
||||
it.name.contains(q, ignoreCase = true) ||
|
||||
it.sparte.contains(q, ignoreCase = true) ||
|
||||
it.klasse.contains(q, ignoreCase = true) ||
|
||||
it.tag.contains(q, ignoreCase = true)
|
||||
it.sparte.contains(q, ignoreCase = true) ||
|
||||
it.klasse.contains(q, ignoreCase = true) ||
|
||||
it.tag.contains(q, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-9
@@ -26,13 +26,13 @@ private val NennSelectedBg = Color(0xFFEFF6FF)
|
||||
* NENNUNGEN-Tab gemäß Vision_03.
|
||||
*
|
||||
* Layout: 2-spaltig
|
||||
* - Links (flex): Pferd+Reiter-Suche + Nennungs-Tabelle
|
||||
* - Rechts (360dp): Verkauf/Buchungen + Bewerbsübersicht
|
||||
* - Links (flex): Pferd+Reiter-Suche + Nennungs-Tabelle
|
||||
* - Rechts (360dp): Verkauf/Buchungen + Bewerbsübersicht
|
||||
*/
|
||||
@Composable
|
||||
fun NennungenTabContent(
|
||||
viewModel: TurnierNennungViewModel,
|
||||
onAbrechnungClick: () -> Unit = {}
|
||||
viewModel: TurnierNennungViewModel,
|
||||
onAbrechnungClick: () -> Unit = {}
|
||||
) {
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
@@ -55,7 +55,7 @@ fun NennungenTabContent(
|
||||
Row(modifier = Modifier.fillMaxSize()) {
|
||||
// ── Linke Spalte: Suche + Tabelle ─────────────────────────────────────
|
||||
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
|
||||
NennungenSuchePanel(viewModel, state)
|
||||
NennungenSuchePanel(viewModel)
|
||||
HorizontalDivider()
|
||||
NennungenTabelle(viewModel, state)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ fun NennungenTabContent(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NennungenSuchePanel(viewModel: TurnierNennungViewModel, state: NennungenState) {
|
||||
private fun NennungenSuchePanel(viewModel: TurnierNennungViewModel) {
|
||||
var pferdQuery by remember { mutableStateOf("") }
|
||||
var reiterQuery by remember { mutableStateOf("") }
|
||||
|
||||
@@ -146,7 +146,7 @@ private fun NennungenTabelle(viewModel: TurnierNennungViewModel, state: Nennunge
|
||||
Text("Keine Nennungen vorhanden", fontSize = 14.sp, color = Color(0xFF6B7280))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
"Suchen Sie nach Pferd und Reiter, um eine Nennung hinzuzufügen.",
|
||||
"Suchen Sie nach Pferd und Reiter, um eine EntryManagement hinzuzufügen.",
|
||||
fontSize = 12.sp,
|
||||
color = Color(0xFF9CA3AF)
|
||||
)
|
||||
@@ -287,5 +287,3 @@ private data class NennungUiModel(
|
||||
val bewerb: String,
|
||||
val status: String,
|
||||
)
|
||||
|
||||
private fun sampleNennungen(): List<NennungUiModel> = emptyList()
|
||||
|
||||
Reference in New Issue
Block a user