- Add `ApiRoutes` for central backend routing configuration. - Implement `DefaultVeranstalterRepository` and `DefaultTurnierRepository` with Ktor clients. - Add domain models (`Turnier`, `Bewerb`, `Abteilung`, `Veranstalter`) and respective repository interfaces. - Replace fake VeranstalterRepository with real implementation. - Update DI with `veranstalterModule` and HTTP client injection. - Simplify TokenProvider and update HttpClient setup (timeouts, retries, logging). - Mark roadmap tasks B-2 as partially complete.
9.5 KiB
🎨 [Frontend Expert] — Schritt-für-Schritt Roadmap
Stand: 3. April 2026 Rolle: KMP, Compose Desktop, State-Management, MVVM/UDF, Backend-Anbindung
🔴 Sprint A — Sofort (diese Woche)
-
A-1 | ViewModel-Architektur definieren und Referenz-Implementierung umsetzen
- MVVM mit UDF (Unidirectional Data Flow) als verbindliches Muster festlegen
Intent- undState-Klassen-Struktur definieren (Vorlage für alle anderen ViewModels)VeranstalterViewModelals vollständige Referenz-Implementierung umsetzenState-Klasse definierenIntent-Klasse (Sealed Class) definieren- Business-Logik aus Composables herausziehen (keine
StoreV2-Aufrufe mehr direkt inonSaved) - Lokalen
remember-State durch ViewModel-State ersetzen
- Ergebnis als Muster-Dokument in
docs/06_Frontend/ablegen
Referenzen:
- docs/06_Frontend/MVVM_UDF_Pattern.md (Regeln, Vorlage, Referenz-Code)
- frontend/features/veranstalter-feature/src/commonMain/.../VeranstalterViewModel.kt
- frontend/features/veranstalter-feature/src/jvmMain/kotlin/at/mocode/frontend/features/veranstalter/data/remote/DefaultVeranstalterRepository.kt
- frontend/features/veranstalter-feature/src/jvmMain/.../VeranstalterAuswahlScreen.kt (nutzt ViewModel/Intents)
-
A-2 | Abteilungs-Logik im Bewerb-Dialog berücksichtigen
- Dialog enthält Abteilungs-Auswahl als Teil des „Bewerb anlegen“-Flows (im selben Modal)
- CSN-C-NEU: Automatischer Vorschlag der Pflicht-Teilung mit 4 Abteilungen:
- Ohne Lizenz · R1
- Ohne Lizenz · R2+
- Mit Lizenz · R1
- Mit Lizenz · R2+
- Beim Auto-Vorschlag Default-Setzung des Abteilungs-Typs auf
SEPARATE_SIEGEREHRUNG - Manuelle Umschaltung des Abteilungs-Typs möglich:
SEPARATE_SIEGEREHRUNGoderORGANISATORISCH - UX: Bei erkanntem Typ „CSN-C-NEU“ wird ein AssistChip „Pflicht-Teilung vorgeschlagen“ angezeigt
Akzeptanzkriterien:
- Der „Bewerb anlegen“-Dialog zeigt ein Eingabefeld „Bewerbs-Typ“ und eine Auswahl für den Abteilungs-Typ (zwei Chips)
- Bei Eingabe „CSN-C-NEU“ wird automatisch die oben definierte 4er-Teilung in der Abteilungs-Liste angezeigt
- Die Auto-Teilung kann angezeigt werden, ohne dass der Dialog neu geöffnet werden muss (Live-Reaktion auf Eingabe)
- Der gesetzte Abteilungs-Typ ist im State sichtbar und wird vom Dialog korrekt reflektiert
- Kein Vorschlag für andere Typen; Liste bleibt leer bis manuell hinzugefügt/implementiert (aktuell out-of-scope)
Referenzen (konkret):
- frontend/features/turnier-feature/src/commonMain/kotlin/at/mocode/turnier/feature/presentation/BewerbAnlegenViewModel.kt
BewerbAnlegenState,BewerbAnlegenIntent,applySuggestion()(Auto-Vorschlag + Default-AbteilungsTyp)
- frontend/features/turnier-feature/src/jvmMain/kotlin/at/mocode/turnier/feature/presentation/TurnierBewerbeTab.kt
BewerbAnlegenDialog(...): Eingabe „Bewerbs-Typ“, AssistChip, Auswahl Abteilungs-Typ, Anzeige der vorgeschlagenen Abteilungen
🟠 Sprint B — Kurzfristig (nächste Woche)
-
B-1 | ViewModels für alle V3-Screens umsetzen
TurnierViewModelBewerbViewModel(inkl. Abteilungs-Logik via Dialog-VM)PferdProfilViewModelReiterProfilViewModelVereinsViewModelFunktionaerViewModelAbteilungViewModel(Startliste, Ergebnisse)
-
B-2 | Ktor-Clients und Repositories für Backend-Anbindung vorbereiten (V3-ready)
-
KMP-Ktor-Client zentral konfigurieren (BaseURL, Auth, Timeout, JSON, Logging)
- BaseURL per
PlatformConfig.resolveApiBaseUrl()(SSoT; JS:globalThis.API_BASE_URL/window.location.origin, JVM:.env/Systemprop) → frontend/core/network - Auth: Bearer Token über Interceptor; Token-Quelle: core/auth (
AuthApiClient) bzw. Session-Store → HeaderAuthorization: Bearer <token> - Timeouts: connect = 5s, request = 15s, socket = 30s (prod); dev je 2× höher; Retry-Policy max 2 Versuche bei 5xx/Network
- JSON:
kotlinx.serializationmitignoreUnknownKeys=true,explicitNulls=false,coerceInputValues=true - Logging:
LogLevel.HEADERSin dev,LogLevel.NONEin prod; PII nie loggen - Engines: JVM=CIO, JS=fetch (ktor-client-js), WASM=js (vorbereitet)
- BaseURL per
-
Repository-Schnittstellen je Domäne definieren (Mock ↔ Real austauschbar)
- Pakete/Orte (commonMain):
at.mocode.frontend.features.veranstalter.domain.VeranstalterRepositoryat.mocode.turnier.feature.domain.TurnierRepositoryat.mocode.turnier.feature.domain.BewerbRepositoryat.mocode.turnier.feature.domain.AbteilungRepository
- Operationen V3-Minimum:
list,getById,create,update,delete(suspend) - Rückgabetypen: Domain-Modelle (nicht DTOs); Fehler als
Either<DomainError, T>oderResult<T>(einheitlich festlegen)
- Pakete/Orte (commonMain):
-
HTTP-Clients + DTOs + Mapper (jvmMain/jsMain)
- DTOs pro Feature in
.../data/remote/dtomit@Serializable(Veranstalter) - Mapper:
Dto ↔ Domainin.../data/mapper(reine Funktionen) (Veranstalter) - Client-Implementierungen in
.../data/remote/Default*Repositorymit Ktor (Veranstalter) - Fehlerbehandlung: Mapping HTTP 401→
AuthError.Expired, 403→AuthError.Forbidden, 404→NotFound, 409→Conflict, 5xx→ServerError
- DTOs pro Feature in
-
Koin-DI-Module
core/network:HttpClient-Factory alssingle { provideHttpClient(env) }- Feature-Module binden
Repository-Interfaces auf Default-Impl AuthApiClient(core/auth) integrieren, Token-Provider injizierbar (z. B.() -> String?)
-
Backend-Endpunkte verdrahten (gemäß contracts/ oder Backend-Services)
- Veranstalter: GET
/api/v3/veranstalter, POST/api/v3/veranstalter... - Turniere: GET
/api/v3/turniere, ... - Bewerbe: GET
/api/v3/turniere/{id}/bewerbe, ... - Abteilungen: GET
/api/v3/bewerbe/{id}/abteilungen, ... - Versionierung: Präfix
/api/v3zentral inApiRoutes
- Veranstalter: GET
-
Migration:
StoreV2schrittweise ablösen- ViewModels von
StoreV2auf Repositories umschalten (Feature für Feature) - Parallelbetrieb per Toggle:
useRealBackend=true/false(Konfig/DI) - Entfernen von
StoreV2, sobald Feature vollständig migriert und stabil
- ViewModels von
-
Qualität & DX
- Akzeptanztests per Fake-Server (Mock Engine) gegen Repos (happy + error paths)
- Network-Error-UX: Einheitliche Fehlermeldungen/Retry in ViewModels (UDF)
- Dokumentation in
docs/06_Frontend/Networking.md(Beispiele, Guidelines)
Referenzen (bestehend):
- frontend/core/network/src/commonMain/.../PlatformConfig.kt (expect) und js/jvm actuals
- frontend/core/auth/src/commonMain/.../AuthApiClient.kt (Keycloak/PKCE, Token-Erhalt)
- frontend/core/network/build.gradle.kts (Ktor- und Engine-Dependencies)
- frontend/core/network/src/commonMain/.../NetworkModule.kt (HttpClient-Setup, Retry/Timeout, Token-Inject)
- frontend/features/veranstalter-feature/src/jvmMain/kotlin/at/mocode/frontend/features/veranstalter/data/remote/DefaultVeranstalterRepository.kt
Akzeptanzkriterien (B-2 abgeschlossen):
HttpClient-Factory vorhanden, konfiguriert und via Koin injizierbar- Repository-Interfaces existieren in commonMain, mit Domain-Typen und suspend-APIs (Veranstalter, Turnier vorbereitet)
- Mindestens
VeranstalterRepositorynutzt echten Backend-Client und liefert Daten - Fehler werden einheitlich modelliert und bis ins ViewModel propagiert
- Ein Feature-ViewModel (z. B. Veranstalter) läuft ohne
StoreV2
-
-
B-3 | Validierungs-Live-Feedback in Edit-Dialogen
- Spezifikation von 📜 Rulebook Expert (Sprint A-5) als Basis nutzen
- OEPS-Nummer: Inline-Validierung beim Tippen
- FEI-ID: Inline-Validierung beim Tippen
- Lizenzklasse × Bewerbs-Klasse: Warnung wenn nicht erlaubt
- Altersklasse Pferd: Warnung wenn nicht kompatibel
-
B-4 | Kassa-Screen: Veranstaltungs-Kassa implementieren
- Gesamt-Saldo-Ansicht (Salden aus allen Turnieren der Veranstaltung)
- Turnier-übergreifender Zahlvorgang (eine Zahlung, mehrere Rechnungen)
- Rechnungsvorschau je Turnier
🟡 Sprint C — Mittelfristig (in 2 Wochen)
-
C-1 | Mock-Store (
StoreV2) vollständig ablösen- Alle verbleibenden
StoreV2-Referenzen durch echte Repositories ersetzen StoreV2nach vollständiger Ablösung entfernen oder als@Deprecatedmarkieren
- Alle verbleibenden
-
C-2 | LAN-Sync-UI vorbereiten (nach ADR von Architect)
- Verbindungsstatus-Anzeige (Online/Offline/LAN)
- Sync-Trigger manuell und automatisch
⏸️ USB-Stick Fallback (Export/Import UI) — Separate Besprechung zu einem späteren Zeitpunkt
📌 Abhängigkeiten
| Warte auf | Von wem |
|---|---|
| Domänen-Modell final (Abteilung) | 🏗️ Architect |
| CRUD-Endpunkte | 👷 Backend |
| Validierungs-Spezifikation | 📜 Rulebook Expert |
| Wireframes Edit-Formulare | 🖌️ UI/UX Designer |
| Meine Aufgabe | Ermöglicht wem |
|---|---|
| ViewModel-Referenz (A-1) | Alle anderen ViewModels folgen diesem Muster |
| Ktor-Repositories (B-2) | Ablösung von StoreV2, echte Daten im Frontend |