fixing(client-module)

This commit is contained in:
stefan
2025-08-12 17:36:55 +02:00
parent a50b1b3822
commit 23b6708197
42 changed files with 198 additions and 3779 deletions
+19 -13
View File
@@ -1,25 +1,31 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("jvm")
alias(libs.plugins.compose.multiplatform)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlin.multiplatform)
}
dependencies {
// Greift explizit auf den "desktop" (JVM) Teil unseres KMP-Moduls zu.
implementation(projects.client.commonUi)
kotlin {
jvm("desktop")
// Stellt die Desktop-spezifischen Teile von Jetpack Compose bereit.
implementation(compose.desktop.currentOs)
// Stellt die Coroutine-Integration für die Swing-UI-Bibliothek bereit.
implementation(libs.kotlinx.coroutines.swing)
// --- Testing ---
testImplementation(projects.platform.platformTesting)
sourceSets {
val desktopMain by getting {
dependencies {
implementation(libs.compose.desktop.currentOs)
implementation(project(":client:common-ui"))
}
}
}
}
compose.desktop {
application {
mainClass = "at.mocode.client.desktop.MainKt"
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "MeldestellePro"
packageVersion = "1.0.0"
}
}
}
@@ -0,0 +1,10 @@
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import at.mocode.client.ui.App
fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "MeldestellePro") {
// Wir rufen hier exakt dieselbe geteilte App() Composable-Funktion auf.
App()
}
}
@@ -1,469 +0,0 @@
package at.mocode.client.desktop
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import at.mocode.client.common.BaseApp
import at.mocode.client.common.components.events.VeranstaltungsListe
import at.mocode.client.common.components.horses.PferdeListe
import at.mocode.client.common.components.masterdata.StammdatenListe
import at.mocode.client.web.screens.CreatePersonScreen
import at.mocode.client.web.screens.PersonListScreen
import at.mocode.core.domain.model.DatenQuelleE
import at.mocode.core.domain.model.PferdeGeschlechtE
import at.mocode.events.domain.model.Veranstaltung
import at.mocode.horses.domain.model.DomPferd
import at.mocode.masterdata.domain.model.LandDefinition
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDate
import kotlinx.serialization.json.Json
/**
* Main application composable for the desktop application.
* Implements a simple tab-based navigation between different screens.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun App() {
// State for navigation
var selectedTabIndex by remember { mutableStateOf(0) }
// Define tabs
val tabs = listOf(
TabItem("Dashboard", Icons.Default.Home),
TabItem("Veranstaltungen", Icons.Default.Event),
TabItem("Pferde", Icons.Default.Pets),
TabItem("Personen", Icons.Default.Person),
TabItem("Stammdaten", Icons.Default.Settings)
)
BaseApp {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Meldestelle - Reitersport Management") }
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
// Tab row for navigation
TabRow(
selectedTabIndex = selectedTabIndex
) {
tabs.forEachIndexed { index, tab ->
Tab(
selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index },
text = { Text(tab.title) },
icon = { Icon(tab.icon, contentDescription = tab.title) }
)
}
}
// Content based on selected tab
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
when (selectedTabIndex) {
0 -> DashboardScreen()
1 -> EventsScreen()
2 -> HorsesScreen()
3 -> PersonsScreen()
4 -> MasterDataScreen()
}
}
}
}
}
}
/**
* Data class representing a tab item
*/
data class TabItem(
val title: String,
val icon: androidx.compose.ui.graphics.vector.ImageVector
)
/**
* Dashboard screen showing an overview of the application
*/
@Composable
fun DashboardScreen() {
val coroutineScope = rememberCoroutineScope()
var pingResult by remember { mutableStateOf<String?>(null) }
var pingLoading by remember { mutableStateOf(false) }
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Willkommen bei Meldestelle",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = "Reitersport Management System",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 32.dp)
)
// Display ping result if available
pingResult?.let { result ->
Text(
text = "Ping Result: $result",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
}
// Quick access buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(
onClick = { /* TODO: Implement quick action */ }
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(Icons.Default.Add, contentDescription = "Neue Veranstaltung")
Spacer(modifier = Modifier.height(4.dp))
Text("Neue Veranstaltung")
}
}
Button(
onClick = { /* TODO: Implement quick action */ }
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(Icons.Default.Search, contentDescription = "Suche")
Spacer(modifier = Modifier.height(4.dp))
Text("Suche")
}
}
Button(
onClick = {
coroutineScope.launch {
pingLoading = true
try {
val pingClient = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
install(Auth) {
basic {
credentials {
BasicAuthCredentials(username = "admin", password = "admin")
}
}
}
}
val response: Map<String, String> = pingClient.get("http://localhost:8080/api/ping").body()
pingResult = response["status"] ?: "No status in response"
pingClient.close()
} catch (e: Exception) {
pingResult = "Error: ${e.message}"
} finally {
pingLoading = false
}
}
},
enabled = !pingLoading
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
if (pingLoading) Icons.Default.Refresh else Icons.Default.NetworkCheck,
contentDescription = "Ping Test"
)
Spacer(modifier = Modifier.height(4.dp))
Text(if (pingLoading) "Pinging..." else "Ping Test")
}
}
}
}
}
/**
* Events screen showing a list of events
*/
@Composable
fun EventsScreen() {
// Create some dummy event data for testing
val dummyEvents = remember {
listOf(
Veranstaltung(
name = "Reitturnier Wien",
ort = "Wien",
startDatum = LocalDate(2025, 8, 15),
endDatum = LocalDate(2025, 8, 17),
veranstalterVereinId = com.benasher44.uuid.uuid4(),
beschreibung = "Internationales Reitturnier mit Springprüfungen",
istAktiv = true,
istOeffentlich = true,
anmeldeschluss = LocalDate(2025, 8, 1),
maxTeilnehmer = 100
),
Veranstaltung(
name = "Dressurturnier Salzburg",
ort = "Salzburg",
startDatum = LocalDate(2025, 9, 5),
endDatum = LocalDate(2025, 9, 5),
veranstalterVereinId = com.benasher44.uuid.uuid4(),
beschreibung = "Dressurturnier für alle Altersklassen",
istAktiv = true,
istOeffentlich = true,
anmeldeschluss = LocalDate(2025, 8, 25),
maxTeilnehmer = 50
)
)
}
// Use the VeranstaltungsListe component to display the events
VeranstaltungsListe(
events = dummyEvents,
isLoading = false,
errorMessage = null
)
}
/**
* Horses screen showing a list of horses
*/
@Composable
fun HorsesScreen() {
// Create some dummy horse data for testing
val dummyHorses = remember {
listOf(
DomPferd(
pferdeName = "Maestoso Bella",
geschlecht = PferdeGeschlechtE.STUTE,
geburtsdatum = LocalDate(2018, 5, 12),
rasse = "Lipizzaner",
farbe = "Schimmel",
lebensnummer = "AT2018123456",
chipNummer = "276098100123456",
oepsNummer = "AT12345",
stockmass = 165,
istAktiv = true,
datenQuelle = DatenQuelleE.MANUELL
),
DomPferd(
pferdeName = "Donnerhall",
geschlecht = PferdeGeschlechtE.HENGST,
geburtsdatum = LocalDate(2020, 3, 24),
rasse = "Hannoveraner",
farbe = "Rappe",
lebensnummer = "DE2020654321",
passNummer = "DE98765",
feiNummer = "FEI10293847",
vaterName = "Dressage King",
mutterName = "Hannelore",
stockmass = 172,
istAktiv = true,
datenQuelle = DatenQuelleE.MANUELL
),
DomPferd(
pferdeName = "Lucky Star",
geschlecht = PferdeGeschlechtE.WALLACH,
geburtsdatum = LocalDate(2015, 7, 8),
rasse = "Haflinger",
farbe = "Fuchs",
chipNummer = "276098100654321",
istAktiv = true,
datenQuelle = DatenQuelleE.MANUELL
)
)
}
// Use the PferdeListe component to display the horses
PferdeListe(
horses = dummyHorses,
isLoading = false,
errorMessage = null,
onHorseClick = { /* Handle horse click */ }
)
}
/**
* Persons screen showing a list of persons
*/
@Composable
fun PersonsScreen() {
// State for navigation
var showCreatePerson by remember { mutableStateOf(false) }
// Create view models using AppDependencies
val personListViewModel = remember { at.mocode.client.web.di.AppDependencies.personListViewModel() }
val createPersonViewModel = remember { at.mocode.client.web.di.AppDependencies.createPersonViewModel() }
if (showCreatePerson) {
// Show create person screen
CreatePersonScreen(
viewModel = createPersonViewModel,
onNavigateBack = {
// When navigating back, refresh the person list if a person was created
if (createPersonViewModel.isSuccess) {
personListViewModel.refreshPersons()
}
showCreatePerson = false
}
)
} else {
// Show person list screen
PersonListScreen(
viewModel = personListViewModel,
onNavigateToCreatePerson = { showCreatePerson = true }
)
}
}
/**
* Master data screen showing master data like countries
*/
@Composable
fun MasterDataScreen() {
// Create some dummy country data for testing
val dummyCountries = remember {
listOf(
LandDefinition(
isoAlpha2Code = "AT",
isoAlpha3Code = "AUT",
isoNumerischerCode = "040",
nameDeutsch = "Österreich",
nameEnglisch = "Austria",
istEuMitglied = true,
istEwrMitglied = true,
istAktiv = true,
sortierReihenfolge = 1
),
LandDefinition(
isoAlpha2Code = "DE",
isoAlpha3Code = "DEU",
isoNumerischerCode = "276",
nameDeutsch = "Deutschland",
nameEnglisch = "Germany",
istEuMitglied = true,
istEwrMitglied = true,
istAktiv = true,
sortierReihenfolge = 2
),
LandDefinition(
isoAlpha2Code = "CH",
isoAlpha3Code = "CHE",
isoNumerischerCode = "756",
nameDeutsch = "Schweiz",
nameEnglisch = "Switzerland",
istEuMitglied = false,
istEwrMitglied = false,
istAktiv = true,
sortierReihenfolge = 3
),
LandDefinition(
isoAlpha2Code = "IT",
isoAlpha3Code = "ITA",
isoNumerischerCode = "380",
nameDeutsch = "Italien",
nameEnglisch = "Italy",
istEuMitglied = true,
istEwrMitglied = true,
istAktiv = true,
sortierReihenfolge = 4
),
LandDefinition(
isoAlpha2Code = "FR",
isoAlpha3Code = "FRA",
isoNumerischerCode = "250",
nameDeutsch = "Frankreich",
nameEnglisch = "France",
istEuMitglied = true,
istEwrMitglied = true,
istAktiv = true,
sortierReihenfolge = 5
)
)
}
// Use the StammdatenListe component to display the countries
StammdatenListe(
countries = dummyCountries,
isLoading = false,
errorMessage = null,
onCountryClick = { /* Handle country click */ }
)
}
/**
* A generic placeholder screen
*/
@Composable
fun PlaceholderScreen(
title: String,
description: String,
icon: androidx.compose.ui.graphics.vector.ImageVector
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
icon,
contentDescription = title,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = title,
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
Text(
text = description,
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 32.dp)
)
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = { /* TODO: Implement action */ }
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.Add, contentDescription = "Hinzufügen")
Spacer(modifier = Modifier.width(8.dp))
Text("Hinzufügen")
}
}
}
}
@@ -1,13 +0,0 @@
package at.mocode.client.desktop
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Meldestelle - Reitersport Management"
) {
App()
}
}