chore: remove obsolete screens from meldestelle-desktop module
Build and Publish Docker Images / build-and-push (., backend/infrastructure/gateway/Dockerfile, api-gateway, api-gateway) (push) Failing after 2m56s
Build and Publish Docker Images / build-and-push (., backend/services/ping/Dockerfile, ping-service, ping-service) (push) Failing after 3m3s
Build and Publish Docker Images / build-and-push (., config/docker/caddy/web-app/Dockerfile, web-app, web-app) (push) Failing after 2m49s
Build and Publish Docker Images / build-and-push (., config/docker/keycloak/Dockerfile, keycloak, keycloak) (push) Successful in 2m13s

- Deleted unused screens including `AdminUebersichtScreen`, `AktorScreens`, `StammdatenImportScreen`, `TurnierDetailScreen`, and supporting components such as `PlaceholderContent`.
- Cleaned up references and placeholders to streamline module structure.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-26 15:08:38 +01:00
parent 1d393fdefe
commit c2b3b5889f
50 changed files with 5067 additions and 1016 deletions
@@ -1,9 +1,10 @@
/**
* Feature-Modul: ZNS-Stammdaten-Import (Desktop-only)
* Kapselt ViewModel, State und API-Kommunikation für den ZNS-Import.
* Kapselt ViewModel, State, API-Kommunikation und UI-Screen für den ZNS-Import.
*/
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
group = "at.mocode.clients"
@@ -13,8 +14,17 @@ kotlin {
jvm()
sourceSets {
jvmMain.dependencies {
implementation(projects.frontend.core.designSystem)
implementation(projects.frontend.core.network)
implementation(projects.frontend.core.auth)
implementation(compose.desktop.currentOs)
implementation(compose.foundation)
implementation(compose.runtime)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.materialIconsExtended)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.bundles.kmp.common)
implementation(libs.koin.core)
implementation(libs.ktor.client.core)
@@ -0,0 +1,257 @@
package at.mocode.zns.feature.presentation
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import at.mocode.zns.feature.ZnsImportViewModel
import org.koin.compose.viewmodel.koinViewModel
import javax.swing.JFileChooser
import javax.swing.filechooser.FileNameExtensionFilter
@Composable
fun StammdatenImportScreen(
viewModel: ZnsImportViewModel = koinViewModel(),
) {
val state = viewModel.state
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
// Titel
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Icon(Icons.Default.CloudUpload, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
Text("Stammdaten-Import (ZNS)", style = MaterialTheme.typography.headlineSmall)
}
HorizontalDivider()
// Datei-Auswahl
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text("Datei auswählen", style = MaterialTheme.typography.titleMedium)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxWidth(),
) {
OutlinedTextField(
value = state.selectedFilePath ?: "",
onValueChange = {},
readOnly = true,
placeholder = { Text("Keine Datei ausgewählt…") },
modifier = Modifier.weight(1f),
singleLine = true,
)
Button(
onClick = {
val path = pickZipFile()
if (path != null) viewModel.onFileSelected(path)
},
enabled = !state.isUploading && !(!state.isFinished && state.jobId != null),
) {
Icon(Icons.Default.FolderOpen, contentDescription = null)
Spacer(Modifier.width(4.dp))
Text("Durchsuchen")
}
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(
onClick = { viewModel.startImport() },
enabled = state.selectedFilePath != null && !state.isUploading && !(state.jobId != null && !state.isFinished),
) {
if (state.isUploading) {
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp,
color = MaterialTheme.colorScheme.onPrimary
)
Spacer(Modifier.width(8.dp))
} else {
Icon(Icons.Default.Upload, contentDescription = null)
Spacer(Modifier.width(4.dp))
}
Text("Import starten")
}
if (state.isFinished || state.errorMessage != null) {
OutlinedButton(onClick = { viewModel.reset() }) {
Icon(Icons.Default.Refresh, contentDescription = null)
Spacer(Modifier.width(4.dp))
Text("Zurücksetzen")
}
}
}
}
}
// Fehler-Banner
if (state.errorMessage != null) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer),
) {
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(Icons.Default.Error, contentDescription = null, tint = MaterialTheme.colorScheme.error)
Text(state.errorMessage!!, color = MaterialTheme.colorScheme.onErrorContainer)
}
}
}
// Fortschritt
if (state.jobId != null) {
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text("Status", style = MaterialTheme.typography.titleMedium)
StatusChip(state.jobStatus)
}
LinearProgressIndicator(
progress = { state.progress / 100f },
modifier = Modifier.fillMaxWidth().height(8.dp),
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
state.progressDetail.ifBlank { "Warte auf Server…" },
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
"${state.progress}%",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
if (state.isFinished && state.jobStatus == "ABGESCHLOSSEN") {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(
Icons.Default.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(16.dp)
)
Text(
"Import erfolgreich abgeschlossen.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary
)
}
}
}
}
}
// Fehler-Liste
if (state.errors.isNotEmpty()) {
Card(
modifier = Modifier.fillMaxWidth().weight(1f),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
Icon(
Icons.Default.Warning,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(18.dp)
)
Text(
"Import-Fehler (${state.errors.size})",
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.error,
)
}
HorizontalDivider()
LazyColumn(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
items(state.errors) { error ->
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface, RoundedCornerShape(4.dp))
.padding(horizontal = 8.dp, vertical = 4.dp),
) {
Text("", color = MaterialTheme.colorScheme.error)
Text(
error,
style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
)
}
}
}
}
}
}
}
}
@Composable
private fun StatusChip(status: String?) {
val (label, color) = when (status) {
"AUSSTEHEND" -> "Ausstehend" to MaterialTheme.colorScheme.outline
"ENTPACKEN" -> "Entpacken…" to MaterialTheme.colorScheme.tertiary
"LADE_VEREINE" -> "Lade Vereine…" to MaterialTheme.colorScheme.secondary
"LADE_REITER" -> "Lade Reiter…" to MaterialTheme.colorScheme.secondary
"LADE_PFERDE" -> "Lade Pferde…" to MaterialTheme.colorScheme.secondary
"LADE_RICHTER" -> "Lade Richter…" to MaterialTheme.colorScheme.secondary
"ABGESCHLOSSEN" -> "Abgeschlossen ✓" to MaterialTheme.colorScheme.primary
"FEHLER" -> "Fehler ✗" to MaterialTheme.colorScheme.error
else -> (status ?: "") to MaterialTheme.colorScheme.outline
}
Surface(
shape = RoundedCornerShape(12.dp),
color = color.copy(alpha = 0.15f),
) {
Text(
label,
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
style = MaterialTheme.typography.labelSmall,
color = color,
)
}
}
/** Öffnet einen nativen JFileChooser (JVM-only) und gibt den Pfad der gewählten ZIP zurück. */
private fun pickZipFile(): String? {
val chooser = JFileChooser()
chooser.dialogTitle = "ZNS.zip auswählen"
chooser.fileFilter = FileNameExtensionFilter("ZIP-Archiv (*.zip)", "zip")
chooser.isAcceptAllFileFilterUsed = false
val result = chooser.showOpenDialog(null)
return if (result == JFileChooser.APPROVE_OPTION) chooser.selectedFile.absolutePath else null
}