feat(frontend+billing): integrate billing UI and navigation into Turnier module

- **Navigation Updates:**
  - Added `AppScreen.Billing` route for participant billing linked to an event and tournament.

- **UI Additions:**
  - Introduced `BillingScreen` and `BillingViewModel` for participant account management and manual transactions.
  - Updated `TurnierAbrechnungTab` to include `BillingScreen` and enable account interaction.

- **Turnier Enhancements:**
  - Enhanced `NennungenTabContent` to support navigation to billing via a new interaction.
  - Added billing feature as a dependency to `turnier-feature`.

- **Billing Domain:**
  - Extended `Money` to include subtraction operation and improved formatting for negative amounts.
  - Added DTOs (`TeilnehmerKontoDto`, `BuchungDto`, `BuchungRequest`) for seamless data exchange with backend.

- **Test Improvements:**
  - Updated `PreviewTurnierAbrechnungTab` to include interactive billing placeholder.

- **Misc Updates:**
  - Enhanced breadcrumb navigation for billing in `DesktopMainLayout` for better user experience.
This commit is contained in:
2026-04-10 14:30:50 +02:00
parent a7e1872d10
commit 1ba4845f6c
13 changed files with 487 additions and 31 deletions
@@ -16,6 +16,9 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import at.mocode.frontend.core.designsystem.models.PlaceholderContent
import at.mocode.frontend.features.billing.presentation.BillingScreen
import at.mocode.frontend.features.billing.presentation.BillingViewModel
import org.koin.compose.koinInject
private val PrimaryBlue = Color(0xFF1E3A8A)
private val AccentBlue = Color(0xFF3B82F6)
@@ -30,7 +33,19 @@ private val OffenePostenRot = Color(0xFFDC2626)
* - Rechte Sidebar: Suche nach Reiter/Pferd, Zahlungsart, Buchen-Button
*/
@Composable
fun AbrechnungTabContent() {
fun AbrechnungTabContent(veranstaltungId: Long) {
val billingViewModel: BillingViewModel = koinInject()
BillingScreen(
viewModel = billingViewModel,
veranstaltungId = veranstaltungId,
onBack = {}
)
}
/* Alter Inhalt auskommentiert oder entfernt */
@Composable
private fun LegacyAbrechnungTabContent() {
var subTab by remember { mutableIntStateOf(0) }
var sidebarTab by remember { mutableIntStateOf(2) } // BUCHUNGEN default
val subTabs = listOf("BUCHUNGEN", "OFFENE POSTEN", "RECHNUNG")
@@ -103,8 +103,8 @@ fun TurnierDetailScreen(
Text("BEWERBE Tab (Anbindung in Arbeit)", modifier = Modifier.align(Alignment.Center))
}
3 -> ArtikelTabContent()
4 -> AbrechnungTabContent()
5 -> NennungenTabContent()
4 -> AbrechnungTabContent(veranstaltungId = veranstaltungId)
5 -> NennungenTabContent(onAbrechnungClick = { selectedTab = 4 })
6 -> StartlistenTabContent()
7 -> ErgebnislistenTabContent()
}
@@ -30,7 +30,7 @@ private val NennSelectedBg = Color(0xFFEFF6FF)
* - Rechts (360dp): Verkauf/Buchungen + Bewerbsübersicht
*/
@Composable
fun NennungenTabContent() {
fun NennungenTabContent(onAbrechnungClick: () -> Unit = {}) {
Row(modifier = Modifier.fillMaxSize()) {
// ── Linke Spalte: Suche + Tabelle ─────────────────────────────────────
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
@@ -48,7 +48,7 @@ fun NennungenTabContent() {
.fillMaxHeight()
.verticalScroll(rememberScrollState()),
) {
VerkaufBuchungenPanel()
VerkaufBuchungenPanel(onAbrechnungClick)
HorizontalDivider()
BewerbsuebersichtPanel()
}
@@ -170,9 +170,18 @@ private fun NennungStatusBadge(status: String) {
}
@Composable
private fun VerkaufBuchungenPanel() {
private fun VerkaufBuchungenPanel(onAbrechnungClick: () -> Unit = {}) {
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text("Verkauf / Buchungen", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("Verkauf / Buchungen", fontWeight = FontWeight.SemiBold, fontSize = 13.sp)
TextButton(onClick = onAbrechnungClick) {
Text("Zur Abrechnung", fontSize = 11.sp, color = NennBlue)
}
}
// Artikel-Buchungen
Card(elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)) {