This commit is contained in:
@@ -38,7 +38,6 @@ import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.BottomNavigation
|
||||
import androidx.compose.material.BottomNavigationItem
|
||||
import androidx.compose.material.Button
|
||||
@@ -595,7 +594,7 @@ private fun MobileFeedbackDialog(
|
||||
var sending by remember { mutableStateOf(false) }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { if (!sending) onDismiss() },
|
||||
title = { Text("Feedback senden") },
|
||||
text = {
|
||||
@@ -1901,7 +1900,7 @@ private fun DiaryListScreen(
|
||||
}
|
||||
|
||||
if (showNewDateDialog) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = {
|
||||
if (!diaryState.isLoading) {
|
||||
showNewDateDialog = false
|
||||
@@ -2566,7 +2565,7 @@ private fun DiaryDetailScreen(
|
||||
|
||||
ordersForMemberId?.let { memberId ->
|
||||
val member = activeMembers.firstOrNull { it.id == memberId }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = {
|
||||
ordersForMemberId = null
|
||||
ordersList = emptyList()
|
||||
@@ -2610,7 +2609,7 @@ private fun DiaryDetailScreen(
|
||||
|
||||
statsForMemberId?.let { memberId ->
|
||||
val member = activeMembers.firstOrNull { it.id == memberId }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = {
|
||||
statsForMemberId = null
|
||||
statsList = emptyList()
|
||||
@@ -2792,7 +2791,7 @@ private fun DiaryDetailScreen(
|
||||
!isExcused &&
|
||||
((m.hasImage == true) || (m.imageUrl != null) || (m.primaryImageId != null) || (m.images.isNotEmpty()))
|
||||
}
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { showMembersGalleryDialog = false },
|
||||
text = {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
@@ -3349,7 +3348,7 @@ private fun DiaryDetailScreen(
|
||||
|
||||
memberNotesSheetMember?.let { sheetMember ->
|
||||
val notesScroll = rememberScrollState()
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = {
|
||||
memberNotesSheetMember = null
|
||||
memberNotesError = null
|
||||
@@ -4480,7 +4479,7 @@ private fun DiaryDetailScreen(
|
||||
val presentParticipantRows = participants
|
||||
.filter { it.isPresentParticipant() }
|
||||
.sortedBy { p -> activeMembers.find { it.id == p.memberId }?.fullName()?.lowercase() ?: "" }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { if (!assignParticipantsBusy) assigningParticipantsItem = null },
|
||||
title = { Text(tr("diary.assignParticipants", "Teilnehmer zuordnen")) },
|
||||
text = {
|
||||
@@ -4584,7 +4583,7 @@ private fun DiaryDetailScreen(
|
||||
|
||||
assigningPlanItem?.let { assignItem ->
|
||||
var assignGroupMenu by remember { mutableStateOf(false) }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { if (!planMutating) assigningPlanItem = null },
|
||||
title = { Text(tr("diary.planAssignGroup", "Zuordnen")) },
|
||||
text = {
|
||||
@@ -4659,7 +4658,7 @@ private fun DiaryDetailScreen(
|
||||
|
||||
editingPlanItem?.let { editItem ->
|
||||
var editGroupMenu by remember { mutableStateOf(false) }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { if (!planMutating) editingPlanItem = null },
|
||||
title = { Text(tr("diary.editPlanItem", "Eintrag bearbeiten")) },
|
||||
text = {
|
||||
@@ -4911,7 +4910,7 @@ private fun DiaryDetailScreen(
|
||||
}
|
||||
planImageViewerUrl?.let { u ->
|
||||
val auth = dependencies.diaryAuthHeaders()
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { planImageViewerUrl = null },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { planImageViewerUrl = null }) {
|
||||
@@ -5111,6 +5110,22 @@ private fun MembersScreen(
|
||||
var membersActionNote by remember { mutableStateOf<String?>(null) }
|
||||
var pendingBulkForms by remember { mutableStateOf(false) }
|
||||
var pendingBulkTestOff by remember { mutableStateOf(false) }
|
||||
var pendingDeactivateMember by remember { mutableStateOf<Member?>(null) }
|
||||
var clickTtPendingMemberIds by remember { mutableStateOf<Set<Int>>(emptySet()) }
|
||||
var ordersForMember by remember { mutableStateOf<Member?>(null) }
|
||||
var ordersList by remember { mutableStateOf<List<de.tsschulz.tt_tagebuch.shared.api.models.MemberOrderDto>>(emptyList()) }
|
||||
var ordersLoading by remember { mutableStateOf(false) }
|
||||
var ordersError by remember { mutableStateOf<String?>(null) }
|
||||
var statsForMember by remember { mutableStateOf<Member?>(null) }
|
||||
var statsList by remember { mutableStateOf<List<MemberActivityStatDto>>(emptyList()) }
|
||||
var statsLastParticipations by remember { mutableStateOf<List<MemberLastParticipationDto>>(emptyList()) }
|
||||
var statsLoading by remember { mutableStateOf(false) }
|
||||
var statsError by remember { mutableStateOf<String?>(null) }
|
||||
var notesForMember by remember { mutableStateOf<Member?>(null) }
|
||||
var notesList by remember { mutableStateOf<List<DiaryMemberNoteDto>>(emptyList()) }
|
||||
var notesDraft by rememberSaveable { mutableStateOf("") }
|
||||
var notesLoading by remember { mutableStateOf(false) }
|
||||
var notesError by remember { mutableStateOf<String?>(null) }
|
||||
var seasonStartYear by rememberSaveable { mutableStateOf(getSeasonStartYearFromDateToday()) }
|
||||
var seasonMenuOpen by remember { mutableStateOf(false) }
|
||||
var selectedAgeGroup by rememberSaveable { mutableStateOf("") }
|
||||
@@ -5299,10 +5314,86 @@ private fun MembersScreen(
|
||||
val filteredEmailsPlain = remember(filteredMembers) {
|
||||
filteredMembers.flatMap { extractEmailAddressesFromMember(it) }.distinct()
|
||||
}
|
||||
fun loadMemberOrders(member: Member) {
|
||||
ordersForMember = member
|
||||
ordersLoading = true
|
||||
ordersError = null
|
||||
ordersList = emptyList()
|
||||
dependencies.applicationScope.launch {
|
||||
try {
|
||||
ordersList = dependencies.memberOrdersApi.listForMember(clubId, member.id).orders
|
||||
} catch (t: Throwable) {
|
||||
ordersError = t.message
|
||||
} finally {
|
||||
ordersLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
fun loadMemberStats(member: Member) {
|
||||
statsForMember = member
|
||||
statsLoading = true
|
||||
statsError = null
|
||||
statsList = emptyList()
|
||||
statsLastParticipations = emptyList()
|
||||
dependencies.applicationScope.launch {
|
||||
try {
|
||||
coroutineScope {
|
||||
val allStats = async { dependencies.membersManager.memberActivityStats(clubId, member.id, "all") }
|
||||
val recent = async { dependencies.membersManager.memberLastParticipations(clubId, member.id, 8) }
|
||||
statsList = allStats.await()
|
||||
statsLastParticipations = recent.await()
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
statsError = t.message
|
||||
} finally {
|
||||
statsLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
fun loadMemberNotes(member: Member) {
|
||||
notesForMember = member
|
||||
notesDraft = ""
|
||||
notesLoading = true
|
||||
notesError = null
|
||||
notesList = emptyList()
|
||||
dependencies.applicationScope.launch {
|
||||
try {
|
||||
notesList = dependencies.membersManager.listMemberNotes(clubId, member.id)
|
||||
} catch (t: Throwable) {
|
||||
notesError = t.message
|
||||
} finally {
|
||||
notesLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
fun quickMarkForm(member: Member) {
|
||||
if (!canWriteMembers) return
|
||||
dependencies.applicationScope.launch {
|
||||
val r = runCatching { dependencies.membersManager.quickUpdateMemberFormHandedOver(clubId, member.id) }
|
||||
r.onSuccess { membersActionNote = it.message ?: trStr("members.formMarkedAsHandedOver", "Formularstatus aktualisiert.") }
|
||||
.onFailure { membersActionNote = it.message ?: trStr("members.errorMarkingForm", "Formularstatus konnte nicht geändert werden.") }
|
||||
dependencies.membersManager.loadMembers(clubId)
|
||||
}
|
||||
}
|
||||
fun requestClickTt(member: Member) {
|
||||
if (!canWriteMembers || clickTtPendingMemberIds.contains(member.id)) return
|
||||
clickTtPendingMemberIds = clickTtPendingMemberIds + member.id
|
||||
dependencies.applicationScope.launch {
|
||||
try {
|
||||
val r = dependencies.membersManager.requestClickTtRegistration(clubId, member.id)
|
||||
membersActionNote = r.message ?: r.error ?: trStr("members.clickTtRequestSuccess", "Click-TT-Antrag angestoßen.")
|
||||
dependencies.membersManager.loadMembers(clubId)
|
||||
} catch (t: Throwable) {
|
||||
membersActionNote = t.message ?: trStr("members.clickTtRequestError", "Click-TT-Antrag konnte nicht gestartet werden.")
|
||||
} finally {
|
||||
clickTtPendingMemberIds = clickTtPendingMemberIds - member.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingBulkForms && canWriteMembers) {
|
||||
val targets = filteredMembers.filter { it.memberFormHandedOver != true }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { pendingBulkForms = false },
|
||||
title = { Text(tr("members.bulkFormsTitle", "Formulare übergeben")) },
|
||||
text = {
|
||||
@@ -5347,7 +5438,7 @@ private fun MembersScreen(
|
||||
}
|
||||
if (pendingBulkTestOff && canWriteMembers) {
|
||||
val targets = filteredMembers.filter { it.testMembership == true }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { pendingBulkTestOff = false },
|
||||
title = { Text(tr("members.bulkTestOffTitle", "Testmitgliedschaften beenden")) },
|
||||
text = {
|
||||
@@ -5391,6 +5482,151 @@ private fun MembersScreen(
|
||||
)
|
||||
}
|
||||
|
||||
pendingDeactivateMember?.let { member ->
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { pendingDeactivateMember = null },
|
||||
title = { Text(tr("members.deactivateMember", "Mitglied deaktivieren")) },
|
||||
text = { Text(trStr("members.deactivateMemberConfirm", "%s deaktivieren?").replace("%s", member.fullName()).replace("{name}", member.fullName())) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
pendingDeactivateMember = null
|
||||
dependencies.applicationScope.launch {
|
||||
val r = runCatching { dependencies.membersManager.quickDeactivateMember(clubId, member.id) }
|
||||
r.onSuccess { membersActionNote = it.message ?: trStr("members.memberDeactivated", "Mitglied deaktiviert.") }
|
||||
.onFailure { membersActionNote = it.message ?: trStr("members.errorDeactivatingMember", "Mitglied konnte nicht deaktiviert werden.") }
|
||||
dependencies.membersManager.loadMembers(clubId)
|
||||
}
|
||||
},
|
||||
) { Text(tr("mobile.ok", "OK")) }
|
||||
},
|
||||
dismissButton = { TextButton(onClick = { pendingDeactivateMember = null }) { Text(tr("mobile.cancel", "Abbrechen")) } },
|
||||
)
|
||||
}
|
||||
ordersForMember?.let { member ->
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { ordersForMember = null; ordersList = emptyList(); ordersError = null },
|
||||
title = { Text(trStr("orders.memberTitle", "Bestellungen: {name}").replace("{name}", member.fullName())) },
|
||||
text = {
|
||||
Column(modifier = Modifier.fillMaxWidth().heightIn(max = 420.dp).verticalScroll(rememberScrollState())) {
|
||||
when {
|
||||
ordersLoading -> CircularProgressIndicator(modifier = Modifier.padding(24.dp).align(Alignment.CenterHorizontally))
|
||||
ordersError != null -> Text(ordersError.orEmpty(), color = MaterialTheme.colors.error)
|
||||
ordersList.isEmpty() -> Text(tr("orders.noOrdersMember", "Für dieses Mitglied gibt es noch keine Bestellungen."))
|
||||
else -> ordersList.forEach { order ->
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp)) {
|
||||
Text(order.item, fontWeight = FontWeight.SemiBold)
|
||||
Text(order.status, style = MaterialTheme.typography.caption)
|
||||
order.cost?.let { Text("${trStr("orders.cost", "Kosten")}: $it", style = MaterialTheme.typography.caption) }
|
||||
Divider(modifier = Modifier.padding(top = 6.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = { TextButton(onClick = { ordersForMember = null; ordersList = emptyList() }) { Text(tr("common.close", "Schließen")) } },
|
||||
)
|
||||
}
|
||||
statsForMember?.let { member ->
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { statsForMember = null; statsList = emptyList(); statsLastParticipations = emptyList(); statsError = null },
|
||||
title = { Text(tr("members.exercises", "Übungen") + ": " + member.fullName()) },
|
||||
text = {
|
||||
Column(modifier = Modifier.fillMaxWidth().heightIn(max = 460.dp).verticalScroll(rememberScrollState())) {
|
||||
when {
|
||||
statsLoading -> CircularProgressIndicator(modifier = Modifier.padding(24.dp).align(Alignment.CenterHorizontally))
|
||||
statsError != null -> Text(statsError.orEmpty(), color = MaterialTheme.colors.error)
|
||||
statsList.isEmpty() && statsLastParticipations.isEmpty() -> Text(trStr("memberActivities.noActivities", "Keine Übungen im gewählten Zeitraum gefunden."))
|
||||
else -> {
|
||||
if (statsList.isNotEmpty()) {
|
||||
Text(tr("members.activityStats", "Aktivität (Statistik)"), fontWeight = FontWeight.SemiBold)
|
||||
statsList.take(20).forEach { row ->
|
||||
Text("${row.name ?: row.code ?: "?"} - ${row.count}x", style = MaterialTheme.typography.body2, modifier = Modifier.padding(top = 3.dp))
|
||||
}
|
||||
}
|
||||
if (statsLastParticipations.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Text(tr("members.lastParticipations", "Letzte Teilnahmen"), fontWeight = FontWeight.SemiBold)
|
||||
statsLastParticipations.forEach { part ->
|
||||
Text("${displayActivityDate(part.date)} ${part.activityName ?: part.activityFullName ?: ""}", style = MaterialTheme.typography.body2, modifier = Modifier.padding(top = 3.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = { TextButton(onClick = { statsForMember = null; statsList = emptyList(); statsLastParticipations = emptyList() }) { Text(tr("common.close", "Schließen")) } },
|
||||
)
|
||||
}
|
||||
notesForMember?.let { member ->
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { notesForMember = null; notesList = emptyList(); notesDraft = ""; notesError = null },
|
||||
title = { Text(tr("members.notes", "Notizen") + ": " + member.fullName()) },
|
||||
text = {
|
||||
Column(modifier = Modifier.fillMaxWidth().heightIn(max = 460.dp).verticalScroll(rememberScrollState())) {
|
||||
notesError?.let { Text(it, color = MaterialTheme.colors.error, modifier = Modifier.padding(bottom = 6.dp)) }
|
||||
if (notesLoading) {
|
||||
CircularProgressIndicator(modifier = Modifier.padding(24.dp).align(Alignment.CenterHorizontally))
|
||||
} else if (notesList.isEmpty()) {
|
||||
Text(trStr("memberNotes.noNotes", "Keine Notizen vorhanden."), style = MaterialTheme.typography.caption)
|
||||
} else {
|
||||
notesList.forEach { note ->
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), verticalAlignment = Alignment.Top) {
|
||||
Text(note.content.orEmpty(), modifier = Modifier.weight(1f), style = MaterialTheme.typography.body2)
|
||||
if (canWriteMembers) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
notesLoading = true
|
||||
dependencies.applicationScope.launch {
|
||||
try {
|
||||
notesList = dependencies.membersManager.deleteMemberNote(clubId, note.id)
|
||||
} catch (t: Throwable) {
|
||||
notesError = t.message
|
||||
} finally {
|
||||
notesLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
) { Text(tr("common.delete", "Löschen"), style = MaterialTheme.typography.caption) }
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
if (canWriteMembers) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = notesDraft,
|
||||
onValueChange = { notesDraft = it },
|
||||
label = { Text(tr("mobile.newNote", "Neue Notiz")) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = !notesLoading,
|
||||
)
|
||||
TextButton(
|
||||
onClick = {
|
||||
val text = notesDraft.trim()
|
||||
if (text.isEmpty()) return@TextButton
|
||||
notesLoading = true
|
||||
dependencies.applicationScope.launch {
|
||||
try {
|
||||
notesList = dependencies.membersManager.addMemberNote(clubId, member.id, text)
|
||||
notesDraft = ""
|
||||
} catch (t: Throwable) {
|
||||
notesError = t.message
|
||||
} finally {
|
||||
notesLoading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = notesDraft.isNotBlank() && !notesLoading,
|
||||
) { Text(tr("memberNotes.add", "Hinzufügen")) }
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = { TextButton(onClick = { notesForMember = null; notesList = emptyList(); notesDraft = "" }) { Text(tr("common.close", "Schließen")) } },
|
||||
)
|
||||
}
|
||||
|
||||
val membersBrowseScroll = rememberScrollState()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -5891,7 +6127,7 @@ private fun MembersScreen(
|
||||
EmptyText(if (query.isBlank()) tr("mobile.noMembers", "Keine Mitglieder gefunden") else tr("mobile.noResults", "Keine Treffer"))
|
||||
}
|
||||
val tableScroll = rememberScrollState()
|
||||
val tableWidth = if (showTrainingParticipationsColumn) 1030.dp else 930.dp
|
||||
val tableWidth = if (showTrainingParticipationsColumn) 1180.dp else 1080.dp
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -5910,7 +6146,7 @@ private fun MembersScreen(
|
||||
if (showTrainingParticipationsColumn) {
|
||||
Text(tr("members.trainingParticipations", "Trainings-Teilnahmen"), modifier = Modifier.width(100.dp), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.caption)
|
||||
}
|
||||
Text(tr("members.actions", "Aktionen"), modifier = Modifier.width(120.dp), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.caption)
|
||||
Text(tr("members.actions", "Aktionen"), modifier = Modifier.width(270.dp), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.caption)
|
||||
}
|
||||
Divider()
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
@@ -5959,9 +6195,48 @@ private fun MembersScreen(
|
||||
if (showTrainingParticipationsColumn) {
|
||||
Text((member.trainingParticipations ?: 0).toString(), modifier = Modifier.width(100.dp), style = MaterialTheme.typography.caption)
|
||||
}
|
||||
Row(modifier = Modifier.width(120.dp), horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
TextButton(onClick = { stack = MembersStackRoute.Detail(member.id) }, modifier = Modifier.heightIn(min = 28.dp)) {
|
||||
Text(tr("mobile.detail", "Details"), style = MaterialTheme.typography.caption)
|
||||
Row(modifier = Modifier.width(270.dp), horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
if (member.memberFormHandedOver != true) {
|
||||
IconButton(
|
||||
onClick = { quickMarkForm(member) },
|
||||
enabled = canWriteMembers,
|
||||
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.markFormReceived", "Formular erhalten") },
|
||||
) { Text("✓", color = Color(0xFF2E7D32), fontWeight = FontWeight.Bold) }
|
||||
}
|
||||
if (member.active && member.ttr == null && member.qttr == null && member.clickTtApplicationSubmitted != true) {
|
||||
IconButton(
|
||||
onClick = { requestClickTt(member) },
|
||||
enabled = canWriteMembers && !clickTtPendingMemberIds.contains(member.id),
|
||||
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.clickTtRequestAction", "Click-TT-Antrag") },
|
||||
) { Text(if (clickTtPendingMemberIds.contains(member.id)) "…" else "🏓", fontSize = 16.sp) }
|
||||
}
|
||||
IconButton(
|
||||
onClick = { loadMemberOrders(member) },
|
||||
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("orders.title", "Bestellungen") },
|
||||
) { Text("📦", fontSize = 16.sp) }
|
||||
IconButton(
|
||||
onClick = { stack = MembersStackRoute.Edit(member.id) },
|
||||
enabled = canWriteMembers,
|
||||
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.editMember", "Bearbeiten") },
|
||||
) { Text("✎", fontSize = 16.sp) }
|
||||
IconButton(
|
||||
onClick = { loadMemberNotes(member) },
|
||||
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.notes", "Notizen") },
|
||||
) { Text("📝", fontSize = 16.sp) }
|
||||
IconButton(
|
||||
onClick = { loadMemberStats(member) },
|
||||
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.exercises", "Übungen") },
|
||||
) { Text("🏃", fontSize = 16.sp) }
|
||||
IconButton(
|
||||
onClick = { stack = MembersStackRoute.Detail(member.id) },
|
||||
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("mobile.detail", "Details") },
|
||||
) { Icon(Icons.Filled.Visibility, contentDescription = null, modifier = Modifier.size(18.dp)) }
|
||||
if (member.active) {
|
||||
IconButton(
|
||||
onClick = { pendingDeactivateMember = member },
|
||||
enabled = canWriteMembers,
|
||||
modifier = Modifier.size(36.dp).semantics { contentDescription = trStr("members.deactivateMember", "Mitglied deaktivieren") },
|
||||
) { Text("⊖", color = MaterialTheme.colors.error, fontWeight = FontWeight.Bold) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6967,7 +7242,7 @@ private fun DeleteAccountDialog(
|
||||
) {
|
||||
var password by rememberSaveable { mutableStateOf("") }
|
||||
var error by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Eigenes Konto löschen") },
|
||||
text = {
|
||||
@@ -7671,7 +7946,7 @@ private fun DiaryDeleteConfirmDialogs(
|
||||
onGroupError: (String?) -> Unit,
|
||||
) {
|
||||
if (showDateConfirm) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismissDate,
|
||||
title = { Text(tr("diary.confirmDelete", "Trainingstag loeschen")) },
|
||||
text = { Text(tr("diary.confirmDeleteDateDetails", "Der leere Trainingstag wird unwiderruflich geloescht.")) },
|
||||
@@ -7691,7 +7966,7 @@ private fun DiaryDeleteConfirmDialogs(
|
||||
)
|
||||
}
|
||||
groupCandidate?.let { group ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismissGroup,
|
||||
title = { Text(tr("diary.deleteGroup", "Trainingsgruppe loeschen")) },
|
||||
text = { Text("${group.name ?: "Gruppe ${group.id}"} wirklich loeschen?") },
|
||||
@@ -7735,7 +8010,7 @@ private fun DiaryQuickAddMemberDialog(
|
||||
var gender by rememberSaveable { mutableStateOf("unknown") }
|
||||
var busy by remember { mutableStateOf(false) }
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { if (!busy) onDismiss() },
|
||||
title = { Text(tr("diary.quickAdd", "Testmitglied schnell hinzufuegen")) },
|
||||
text = {
|
||||
@@ -7889,7 +8164,7 @@ private fun DiaryPlanSupplementDialogs(
|
||||
editingNestedItem?.let { nested ->
|
||||
var duration by remember(nested.id) { mutableStateOf(nested.duration?.toString().orEmpty()) }
|
||||
var durationText by remember(nested.id) { mutableStateOf(nested.durationText.orEmpty()) }
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismissEditNested,
|
||||
title = { Text(tr("diary.editGroupActivity", "Gruppen-Aktivitaet bearbeiten")) },
|
||||
text = {
|
||||
|
||||
@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -967,7 +966,7 @@ private fun BillingClubScreen(dependencies: AppDependencies, onBack: () -> Unit)
|
||||
}
|
||||
|
||||
deleteRunTarget?.let { target ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { deleteRunTarget = null },
|
||||
title = { Text(tr("billing.deleteBilling", "Löschen")) },
|
||||
text = { Text(tr("billing.deleteConfirm", "Abrechnungslauf wirklich löschen?")) },
|
||||
@@ -986,7 +985,7 @@ private fun BillingClubScreen(dependencies: AppDependencies, onBack: () -> Unit)
|
||||
)
|
||||
}
|
||||
deleteTemplateTarget?.let { target ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { deleteTemplateTarget = null },
|
||||
title = { Text(tr("billing.deleteTemplate", "Vorlage löschen")) },
|
||||
text = { Text(tr("billing.deleteTemplateConfirm", "Vorlage wirklich löschen?")) },
|
||||
|
||||
@@ -15,7 +15,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -346,7 +345,7 @@ private fun ClubAdminPermissionsScreen(dependencies: AppDependencies, onBack: ()
|
||||
|
||||
val dialogMember = customizeFor
|
||||
if (dialogMember != null) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { if (!customizeSaving) customizeFor = null },
|
||||
title = { Text(dialogMember.user?.email ?: "User ${dialogMember.userId}") },
|
||||
text = {
|
||||
@@ -538,7 +537,7 @@ private fun ClubAdminLogsScreen(dependencies: AppDependencies, onBack: () -> Uni
|
||||
}
|
||||
|
||||
if (detailId != null) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { detailId = null },
|
||||
title = { Text(tr("mobile.logDetail", "Log-Detail")) },
|
||||
text = {
|
||||
|
||||
@@ -14,7 +14,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -723,7 +722,7 @@ private fun ClubTrainingGroupsTabContent(
|
||||
}
|
||||
|
||||
if (editing != null) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { editing = null },
|
||||
title = { Text(tr("trainingGroupsTab.editGroup", "Gruppe bearbeiten")) },
|
||||
text = {
|
||||
@@ -758,7 +757,7 @@ private fun ClubTrainingGroupsTabContent(
|
||||
|
||||
if (deleteTarget != null) {
|
||||
val dg = deleteTarget!!
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { deleteTarget = null },
|
||||
title = { Text(tr("trainingGroupsTab.delete", "Löschen")) },
|
||||
text = {
|
||||
@@ -959,7 +958,7 @@ private fun ClubTrainingTimesTabContent(
|
||||
|
||||
if (editing != null) {
|
||||
val et = editing!!
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { editing = null },
|
||||
title = { Text(tr("trainingTimesTab.editTime", "Trainingszeit bearbeiten")) },
|
||||
text = {
|
||||
@@ -1024,7 +1023,7 @@ private fun ClubTrainingTimesTabContent(
|
||||
|
||||
if (deleteTimeId != null) {
|
||||
val tid = deleteTimeId!!
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { deleteTimeId = null },
|
||||
title = { Text(tr("trainingTimesTab.delete", "Löschen")) },
|
||||
text = { Text("Diese Trainingszeit wirklich löschen?") },
|
||||
|
||||
@@ -15,7 +15,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -208,7 +207,7 @@ private fun PredefinedActivityEditorDialog(
|
||||
var err by remember { mutableStateOf<String?>(null) }
|
||||
var busy by remember { mutableStateOf(false) }
|
||||
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(if (isNew) resolve("mobile.newPredefined", "Neue Aktivität") else resolve("mobile.editPredefined", "Bearbeiten")) },
|
||||
text = {
|
||||
|
||||
@@ -16,7 +16,6 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
@@ -106,7 +105,7 @@ internal fun TournamentEditorClassesTab(
|
||||
var distributedMatches by remember { mutableStateOf<List<TournamentMatchDto>>(emptyList()) }
|
||||
var distributedMessage by remember { mutableStateOf<String?>(null) }
|
||||
if (showAdd) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { showAdd = false },
|
||||
title = { Text(tr("tournaments.addClass", "Klasse anlegen")) },
|
||||
text = {
|
||||
@@ -185,7 +184,7 @@ internal fun TournamentEditorClassesTab(
|
||||
}
|
||||
|
||||
if (showDistributedDialog) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { showDistributedDialog = false },
|
||||
title = { Text(tr("tournaments.distributeTablesResult", "Tischverteilung")) },
|
||||
text = {
|
||||
@@ -512,7 +511,7 @@ internal fun TournamentEditorParticipantsTab(
|
||||
if (showExternal) {
|
||||
OutlinedButton(onClick = { extDialog = true }) { Text(tr("tournaments.addExternalParticipant", "Extern hinzufügen")) }
|
||||
if (extDialog) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { extDialog = false },
|
||||
title = { Text(tr("tournaments.addExternalParticipant", "Extern hinzufügen")) },
|
||||
text = {
|
||||
@@ -940,7 +939,7 @@ internal fun TournamentEditorMatchesTab(
|
||||
// Confirmation dialog for deleting a set
|
||||
if (confirmDelete != null) {
|
||||
val (matchIdToDelete, setToDelete) = confirmDelete!!
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { confirmDelete = null },
|
||||
title = { Text(tr("tournaments.confirmDeleteSetTitle", "Satz löschen")) },
|
||||
text = { Text(tr("tournaments.confirmDeleteSet", "Soll dieser Satz wirklich gelöscht werden?")) },
|
||||
@@ -970,7 +969,7 @@ internal fun TournamentEditorMatchesTab(
|
||||
// Edit set dialog
|
||||
if (editingSet != null) {
|
||||
val (matchIdToEdit, setToEdit) = editingSet!!
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { editingSet = null },
|
||||
title = { Text(tr("tournaments.editSetTitle", "Satz bearbeiten")) },
|
||||
text = {
|
||||
@@ -1035,7 +1034,7 @@ internal fun TournamentEditorMatchesTab(
|
||||
)
|
||||
}
|
||||
if (showDistributedDialog) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { showDistributedDialog = false },
|
||||
title = { Text(tr("tournaments.distributeTablesResult", "Tischverteilung")) },
|
||||
text = {
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Divider
|
||||
|
||||
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Divider
|
||||
@@ -148,7 +147,7 @@ fun InternalTournamentStatsDialog(
|
||||
val scroll = rememberScrollState()
|
||||
val s = stats
|
||||
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(tr("tournaments.internalStatsTitle", "Interne Turnier-Statistik (Einzel)")) },
|
||||
text = {
|
||||
|
||||
@@ -27,7 +27,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -491,7 +490,7 @@ fun MemberGroupPortraitCropRoute(
|
||||
}
|
||||
|
||||
if (memberPickerOpen) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { memberPickerOpen = false },
|
||||
title = { Text(s("members.groupCropPickMember", "Mitglied wählen")) },
|
||||
text = {
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -664,7 +663,7 @@ internal fun OfficialTournamentsWorkspaceScreen(
|
||||
}
|
||||
|
||||
if (showInfoDialog && infoMessage != null) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { showInfoDialog = false },
|
||||
title = { Text(infoTitle) },
|
||||
text = { Text(infoMessage!!) },
|
||||
@@ -681,7 +680,7 @@ internal fun OfficialTournamentsWorkspaceScreen(
|
||||
"reset" -> participantsRows(pBatch, mapSnapshot(), "participated") { memberNameById(it) }
|
||||
else -> emptyList()
|
||||
}
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { batchAction = null },
|
||||
title = { Text(tr("officialTournaments.batchTitle", "Sammelaktion")) },
|
||||
text = { Text(tr("officialTournaments.batchConfirm", "{n} Einträge wirklich ausführen?").replace("{n}", rows.size.toString())) },
|
||||
@@ -736,7 +735,7 @@ internal fun OfficialTournamentsWorkspaceScreen(
|
||||
}
|
||||
|
||||
deleteTarget?.let { target ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { deleteTarget = null },
|
||||
title = { Text(tr("officialTournaments.deleteTournamentTitle", "Turnier löschen")) },
|
||||
text = { Text("${target.title ?: ""} (ID ${target.id})") },
|
||||
|
||||
@@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -300,7 +299,7 @@ private fun MyTischtennisAccountScreen(dependencies: AppDependencies, onBack: ()
|
||||
}
|
||||
|
||||
if (confirmUnlink) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { confirmUnlink = false },
|
||||
title = { Text(tr("myTischtennisAccount.unlinkAccountTitle", "Account trennen")) },
|
||||
text = { Text(tr("myTischtennisAccount.unlinkAccountConfirm", "Wirklich trennen?")) },
|
||||
@@ -359,7 +358,7 @@ private fun MyTischtennisEditorDialog(
|
||||
var err by remember { mutableStateOf<String?>(null) }
|
||||
var busy by remember { mutableStateOf(false) }
|
||||
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
@@ -578,7 +577,7 @@ private fun ClickTtAccountScreen(dependencies: AppDependencies, onBack: () -> Un
|
||||
}
|
||||
|
||||
if (confirmDelete) {
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { confirmDelete = false },
|
||||
title = { Text(ct("mobile.clickTt.deleteTitle", "Account löschen")) },
|
||||
text = { Text(ct("mobile.clickTt.deleteConfirm", "Wirklich löschen?")) },
|
||||
@@ -623,7 +622,7 @@ private fun ClickTtEditorDialog(
|
||||
var err by remember { mutableStateOf<String?>(null) }
|
||||
var busy by remember { mutableStateOf(false) }
|
||||
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
|
||||
@@ -21,7 +21,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -43,9 +42,11 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import de.tsschulz.tt_tagebuch.app.AppDependencies
|
||||
import de.tsschulz.tt_tagebuch.app.stats.TrainingStatsDerived
|
||||
import de.tsschulz.tt_tagebuch.shared.api.models.ScheduleMatchDto
|
||||
@@ -348,7 +349,7 @@ internal fun ScheduleScreen(dependencies: AppDependencies, friendlyOnly: Boolean
|
||||
}
|
||||
|
||||
detailMatch?.let { m ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { detailMatch = null },
|
||||
title = { Text("${m.homeTeam?.name ?: "?"} : ${m.guestTeam?.name ?: "?"}") },
|
||||
text = {
|
||||
@@ -517,23 +518,42 @@ internal fun ScheduleScreen(dependencies: AppDependencies, friendlyOnly: Boolean
|
||||
LaunchedEffect(m.id, clubId) {
|
||||
loadPlayerDialogMembersFor(m)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = { if (!playerSaving) playerMatch = null },
|
||||
title = { Text(tr("schedule.playerSelectionTitle", "Spieler")) },
|
||||
text = {
|
||||
Column(modifier = Modifier.heightIn(max = 400.dp)) {
|
||||
playerError?.let { Text(it, color = MaterialTheme.colors.error) }
|
||||
val dialogHeight = (LocalConfiguration.current.screenHeightDp * 0.82f).dp
|
||||
Dialog(onDismissRequest = { if (!playerSaving) playerMatch = null }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.widthIn(max = 560.dp)
|
||||
.height(dialogHeight),
|
||||
elevation = 8.dp,
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
|
||||
Text(
|
||||
tr("schedule.playerSelectionTitle", "Spieler"),
|
||||
style = MaterialTheme.typography.h6,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
playerError?.let {
|
||||
Text(it, color = MaterialTheme.colors.error, modifier = Modifier.padding(bottom = 8.dp))
|
||||
}
|
||||
val memberList = playerDialogMembers
|
||||
if (membersState.isLoading && memberList.isEmpty()) {
|
||||
CircularProgressIndicator(modifier = Modifier.padding(16.dp))
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
} else {
|
||||
val scroll = rememberScrollState()
|
||||
Column(Modifier.verticalScroll(scroll)) {
|
||||
memberList.forEach { mem ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
||||
) {
|
||||
items(memberList, key = { it.id }) { mem ->
|
||||
val id = mem.id
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp),
|
||||
) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text("${mem.firstName} ${mem.lastName}".trim(), maxLines = 1)
|
||||
@@ -564,39 +584,42 @@ internal fun ScheduleScreen(dependencies: AppDependencies, friendlyOnly: Boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = !playerSaving && !isFriendlyMatchLocked(m),
|
||||
onClick = {
|
||||
scope.launch {
|
||||
playerSaving = true
|
||||
playerError = null
|
||||
runCatching {
|
||||
val visibleIds = playerDialogMembers.map { it.id }.toSet()
|
||||
fun mergeVisible(existing: List<Int>, selected: List<Int>): List<Int> =
|
||||
(existing.filter { it !in visibleIds } + selected.filter { it in visibleIds }).distinct()
|
||||
dependencies.scheduleManager.updateMatchPlayersForMatch(
|
||||
clubId = clubId,
|
||||
match = m,
|
||||
ready = mergeVisible(m.playersReady, readyIds),
|
||||
planned = mergeVisible(m.playersPlanned, plannedIds),
|
||||
played = mergeVisible(m.playersPlayed, playedIds),
|
||||
)
|
||||
playerMatch = null
|
||||
}.onFailure { playerError = it.message ?: tr("schedule.errorSavingPlayerSelection", "Speichern fehlgeschlagen") }
|
||||
playerSaving = false
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TextButton(enabled = !playerSaving, onClick = { playerMatch = null }) {
|
||||
Text(tr("common.cancel", "Abbrechen"))
|
||||
}
|
||||
},
|
||||
) { Text(tr("common.save", "Speichern")) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(enabled = !playerSaving, onClick = { playerMatch = null }) {
|
||||
Text(tr("common.cancel", "Abbrechen"))
|
||||
TextButton(
|
||||
enabled = !playerSaving && !isFriendlyMatchLocked(m),
|
||||
onClick = {
|
||||
scope.launch {
|
||||
playerSaving = true
|
||||
playerError = null
|
||||
runCatching {
|
||||
val visibleIds = playerDialogMembers.map { it.id }.toSet()
|
||||
fun mergeVisible(existing: List<Int>, selected: List<Int>): List<Int> =
|
||||
(existing.filter { it !in visibleIds } + selected.filter { it in visibleIds }).distinct()
|
||||
dependencies.scheduleManager.updateMatchPlayersForMatch(
|
||||
clubId = clubId,
|
||||
match = m,
|
||||
ready = mergeVisible(m.playersReady, readyIds),
|
||||
planned = mergeVisible(m.playersPlanned, plannedIds),
|
||||
played = mergeVisible(m.playersPlayed, playedIds),
|
||||
)
|
||||
playerMatch = null
|
||||
}.onFailure { playerError = it.message ?: tr("schedule.errorSavingPlayerSelection", "Speichern fehlgeschlagen") }
|
||||
playerSaving = false
|
||||
}
|
||||
},
|
||||
) { Text(if (playerSaving) "…" else tr("common.save", "Speichern")) }
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,7 +662,7 @@ private fun FriendlyMatchEditDialog(
|
||||
return normalized
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(if (match == null) "Freundschaftsspiel anlegen" else "Freundschaftsspiel bearbeiten") },
|
||||
text = {
|
||||
@@ -812,7 +835,7 @@ private fun FriendlyResultDialog(
|
||||
resultDetails = rows,
|
||||
)
|
||||
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Ergebniseingabe") },
|
||||
text = {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package de.tsschulz.tt_tagebuch.app.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
|
||||
@Composable
|
||||
fun StableAlertDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
title: @Composable (() -> Unit)? = null,
|
||||
text: @Composable (() -> Unit)? = null,
|
||||
confirmButton: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
dismissButton: @Composable (() -> Unit)? = null,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
) {
|
||||
StableAlertDialogFrame(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = title,
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
properties = properties,
|
||||
buttons = {
|
||||
dismissButton?.invoke()
|
||||
confirmButton()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StableAlertDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
buttons: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
title: @Composable (() -> Unit)? = null,
|
||||
text: @Composable (() -> Unit)? = null,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
) {
|
||||
StableAlertDialogFrame(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = title,
|
||||
text = text,
|
||||
modifier = modifier,
|
||||
properties = properties,
|
||||
buttons = buttons,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StableAlertDialogFrame(
|
||||
onDismissRequest: () -> Unit,
|
||||
title: @Composable (() -> Unit)?,
|
||||
text: @Composable (() -> Unit)?,
|
||||
modifier: Modifier,
|
||||
properties: DialogProperties,
|
||||
buttons: @Composable () -> Unit,
|
||||
) {
|
||||
val maxHeight = (LocalConfiguration.current.screenHeightDp * 0.86f).dp
|
||||
Dialog(onDismissRequest = onDismissRequest, properties = properties) {
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.widthIn(max = 560.dp)
|
||||
.heightIn(max = maxHeight),
|
||||
elevation = 8.dp,
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
if (title != null) {
|
||||
androidx.compose.material.ProvideTextStyle(
|
||||
MaterialTheme.typography.h6.copy(fontWeight = FontWeight.SemiBold),
|
||||
) {
|
||||
title()
|
||||
}
|
||||
}
|
||||
if (text != null) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = if (title != null) 12.dp else 0.dp)
|
||||
.heightIn(max = maxHeight - 112.dp),
|
||||
) {
|
||||
text()
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 12.dp),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
buttons()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -697,7 +696,7 @@ internal fun TeamEditorScreen(
|
||||
}
|
||||
|
||||
info?.let { msg ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { info = null },
|
||||
title = { Text(t("common.info", "Hinweis")) },
|
||||
text = { Text(msg) },
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
@@ -362,7 +361,7 @@ internal fun TeamManagementScreen(
|
||||
}
|
||||
|
||||
deleteTarget?.let { target ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { deleteTarget = null },
|
||||
title = { Text(getMobileString("mobile.teamDeleteTitle", "Mannschaft löschen?")) },
|
||||
text = { Text(target.name.ifBlank { "#${target.id}" }) },
|
||||
@@ -395,7 +394,7 @@ internal fun TeamManagementScreen(
|
||||
}
|
||||
|
||||
infoMessage?.let { msg ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { infoMessage = null },
|
||||
title = { Text(getMobileString("common.error", "Fehler")) },
|
||||
text = { Text(msg) },
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Divider
|
||||
@@ -475,7 +474,7 @@ internal fun TeamPlanningScreen(
|
||||
}
|
||||
|
||||
deleteTeamId?.let { tid ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { deleteTeamId = null },
|
||||
title = { Text(t("mobile.teamDeleteTitle", "Mannschaft löschen?")) },
|
||||
text = { Text(teams.find { it.id == tid }?.name.orEmpty()) },
|
||||
@@ -497,7 +496,7 @@ internal fun TeamPlanningScreen(
|
||||
}
|
||||
|
||||
info?.let { msg ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { info = null },
|
||||
title = { Text(t("common.error", "Fehler")) },
|
||||
text = { Text(msg) },
|
||||
|
||||
@@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Divider
|
||||
@@ -668,7 +667,7 @@ internal fun TrainingStatsScreen(dependencies: AppDependencies) {
|
||||
}
|
||||
|
||||
detailMember?.let { member ->
|
||||
AlertDialog(
|
||||
StableAlertDialog(
|
||||
onDismissRequest = { detailMember = null },
|
||||
title = {
|
||||
Text(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[versions]
|
||||
# composeApp (Play Store / „Über die App“-Build)
|
||||
appVersionCode = "25"
|
||||
appVersionName = "1.7.5"
|
||||
appVersionCode = "26"
|
||||
appVersionName = "1.7.6"
|
||||
agp = "9.2.1"
|
||||
android-compileSdk = "35"
|
||||
android-minSdk = "24"
|
||||
|
||||
@@ -9,6 +9,7 @@ import de.tsschulz.tt_tagebuch.shared.api.models.MemberSetBody
|
||||
import de.tsschulz.tt_tagebuch.shared.api.models.MemberTransferRunBody
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.request.post
|
||||
@@ -18,8 +19,16 @@ import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.contentType
|
||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
@Serializable
|
||||
data class MemberNoteMutationBody(
|
||||
val clubId: Int,
|
||||
val memberId: Int? = null,
|
||||
val content: String? = null,
|
||||
)
|
||||
|
||||
class MembersApi(
|
||||
private val client: AuthedHttpClient,
|
||||
) {
|
||||
@@ -58,6 +67,32 @@ class MembersApi(
|
||||
return client.http.post("/api/clubmembers/quick-update-member-form/$clubId/$memberId").body()
|
||||
}
|
||||
|
||||
suspend fun quickDeactivateMember(clubId: Int, memberId: Int): MemberQuickMutationResponse {
|
||||
return client.http.post("/api/clubmembers/quick-deactivate/$clubId/$memberId").body()
|
||||
}
|
||||
|
||||
suspend fun requestClickTtRegistration(clubId: Int, memberId: Int): MemberQuickMutationResponse {
|
||||
return client.http.post("/api/clubmembers/clicktt-registration/$clubId/$memberId").body()
|
||||
}
|
||||
|
||||
suspend fun listMemberNotes(clubId: Int, memberId: Int): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> {
|
||||
return client.http.get("/api/member-notes/$memberId") {
|
||||
parameter("clubId", clubId)
|
||||
}.body()
|
||||
}
|
||||
|
||||
suspend fun addMemberNote(clubId: Int, memberId: Int, content: String): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> {
|
||||
return client.http.post("/api/member-notes") {
|
||||
setBody(MemberNoteMutationBody(clubId = clubId, memberId = memberId, content = content))
|
||||
}.body()
|
||||
}
|
||||
|
||||
suspend fun deleteMemberNote(clubId: Int, noteId: Int): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> {
|
||||
return client.http.delete("/api/member-notes/$noteId") {
|
||||
setBody(MemberNoteMutationBody(clubId = clubId))
|
||||
}.body()
|
||||
}
|
||||
|
||||
suspend fun transferMembers(clubId: Int, body: MemberTransferRunBody): JsonObject {
|
||||
return client.http.post("/api/clubmembers/transfer/$clubId") {
|
||||
setBody(body)
|
||||
|
||||
@@ -21,6 +21,7 @@ data class Member(
|
||||
val testMembership: Boolean? = null,
|
||||
val picsInInternetAllowed: Boolean? = null,
|
||||
val memberFormHandedOver: Boolean? = null,
|
||||
val clickTtApplicationSubmitted: Boolean? = null,
|
||||
val adultReleaseApproved: Boolean? = null,
|
||||
val adultReserveApproved: Boolean? = null,
|
||||
val lastTraining: String? = null,
|
||||
|
||||
@@ -77,6 +77,21 @@ class MembersManager(
|
||||
suspend fun quickUpdateMemberFormHandedOver(clubId: Int, memberId: Int): MemberQuickMutationResponse =
|
||||
membersApi.quickUpdateMemberFormHandedOver(clubId, memberId)
|
||||
|
||||
suspend fun quickDeactivateMember(clubId: Int, memberId: Int): MemberQuickMutationResponse =
|
||||
membersApi.quickDeactivateMember(clubId, memberId)
|
||||
|
||||
suspend fun requestClickTtRegistration(clubId: Int, memberId: Int): MemberQuickMutationResponse =
|
||||
membersApi.requestClickTtRegistration(clubId, memberId)
|
||||
|
||||
suspend fun listMemberNotes(clubId: Int, memberId: Int): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> =
|
||||
membersApi.listMemberNotes(clubId, memberId)
|
||||
|
||||
suspend fun addMemberNote(clubId: Int, memberId: Int, content: String): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> =
|
||||
membersApi.addMemberNote(clubId, memberId, content)
|
||||
|
||||
suspend fun deleteMemberNote(clubId: Int, noteId: Int): List<de.tsschulz.tt_tagebuch.shared.api.models.DiaryMemberNoteDto> =
|
||||
membersApi.deleteMemberNote(clubId, noteId)
|
||||
|
||||
suspend fun transferMembers(clubId: Int, body: MemberTransferRunBody): JsonObject =
|
||||
membersApi.transferMembers(clubId, body)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user