chore: implementiere Logo-Upload-Zone mit Base64-Unterstützung, verbessere Vereinsverwaltung mit kompakten Feldern und nutzerspezifischen Uploadoptionen, optimiere Desktop-UX und Navigation
This commit is contained in:
+197
@@ -0,0 +1,197 @@
|
||||
package at.mocode.frontend.core.designsystem.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CalendarMonth
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.Month
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import kotlin.time.Clock
|
||||
|
||||
/**
|
||||
* Ein einfacher DatePicker-Dialog für Compose Desktop.
|
||||
* Da Material3 DatePicker unter Desktop teils Probleme macht oder zu groß ist,
|
||||
* nutzen wir hier eine kompakte Eigenimplementierung.
|
||||
*/
|
||||
@Composable
|
||||
fun MsDatePickerField(
|
||||
label: String,
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isError: Boolean = false,
|
||||
errorMessage: String? = null
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Box(modifier = modifier) {
|
||||
MsTextField(
|
||||
value = value,
|
||||
onValueChange = { /* Schreibgeschützt via Dialog */ },
|
||||
label = label,
|
||||
placeholder = "YYYY-MM-DD",
|
||||
readOnly = true,
|
||||
isError = isError,
|
||||
errorMessage = errorMessage,
|
||||
trailingIcon = Icons.Default.CalendarMonth,
|
||||
onTrailingIconClick = { showDialog = true },
|
||||
modifier = Modifier.clickable { showDialog = true }
|
||||
)
|
||||
|
||||
if (showDialog) {
|
||||
MsDatePickerDialog(
|
||||
initialDate = value,
|
||||
onDismiss = { showDialog = false },
|
||||
onDateSelected = {
|
||||
onValueChange(it)
|
||||
showDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MsDatePickerDialog(
|
||||
initialDate: String,
|
||||
onDismiss: () -> Unit,
|
||||
onDateSelected: (String) -> Unit
|
||||
) {
|
||||
val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||
val parsedDate = try {
|
||||
LocalDate.parse(initialDate)
|
||||
} catch (_: Exception) {
|
||||
now
|
||||
}
|
||||
|
||||
var currentMonth by remember { mutableStateOf(parsedDate.month) }
|
||||
var currentYear by remember { mutableStateOf(parsedDate.year) }
|
||||
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Card(
|
||||
modifier = Modifier.width(300.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
// Header: Monat/Jahr Auswahl
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(onClick = {
|
||||
if (currentMonth == Month.JANUARY) {
|
||||
currentMonth = Month.DECEMBER
|
||||
currentYear--
|
||||
} else {
|
||||
val months = Month.entries
|
||||
currentMonth = months[currentMonth.ordinal - 1]
|
||||
}
|
||||
}) {
|
||||
Text("<")
|
||||
}
|
||||
|
||||
Text(
|
||||
"${currentMonth.name} $currentYear",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
IconButton(onClick = {
|
||||
if (currentMonth == Month.DECEMBER) {
|
||||
currentMonth = Month.JANUARY
|
||||
currentYear++
|
||||
} else {
|
||||
val months = Month.entries
|
||||
currentMonth = months[currentMonth.ordinal + 1]
|
||||
}
|
||||
}) {
|
||||
Text(">")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
// Kalender-Grid
|
||||
val daysInMonth = getDaysInMonth(currentMonth, currentYear)
|
||||
val firstDayOfWeek = LocalDate(currentYear, currentMonth, 1).dayOfWeek.ordinal // 0=Monday
|
||||
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
listOf("Mo", "Di", "Mi", "Do", "Fr", "Sa", "So").forEach {
|
||||
Text(
|
||||
it,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = Color.Gray,
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val totalSlots = 42 // 6 Wochen
|
||||
Column {
|
||||
for (week in 0 until 6) {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
for (day in 0 until 7) {
|
||||
val slotIndex = week * 7 + day
|
||||
val dayNum = slotIndex - firstDayOfWeek + 1
|
||||
if (dayNum in 1..daysInMonth) {
|
||||
val isSelected = parsedDate.day == dayNum &&
|
||||
parsedDate.month == currentMonth &&
|
||||
parsedDate.year == currentYear
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.aspectRatio(1f)
|
||||
.padding(2.dp)
|
||||
.background(
|
||||
if (isSelected) MaterialTheme.colorScheme.primary
|
||||
else Color.Transparent,
|
||||
MaterialTheme.shapes.small
|
||||
)
|
||||
.clickable {
|
||||
val selected = LocalDate(currentYear, currentMonth, dayNum)
|
||||
onDateSelected(selected.toString())
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
dayNum.toString(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = if (isSelected) MaterialTheme.colorScheme.onPrimary
|
||||
else MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Spacer(Modifier.weight(1f).aspectRatio(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
TextButton(onClick = onDismiss) { Text("Abbrechen") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDaysInMonth(month: Month, year: Int): Int {
|
||||
return when (month) {
|
||||
Month.FEBRUARY -> if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) 29 else 28
|
||||
Month.APRIL, Month.JUNE, Month.SEPTEMBER, Month.NOVEMBER -> 30
|
||||
else -> 31
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user