From a322212ef5929ed49a004bf200d399f5426d435e Mon Sep 17 00:00:00 2001 From: M_Viper Date: Mon, 26 Oct 2026 21:31:35 +0000 Subject: [PATCH] Upload via GUI (4909 Dateien) --- .../de/deskhub/staff/data/TicketRepository.kt | 6 ++-- .../java/de/deskhub/staff/ui/DeskHubApp.kt | 30 ++++++++++++++----- .../de/deskhub/staff/ui/DeskHubViewModel.kt | 7 +++-- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/mobile/src/main/java/de/deskhub/staff/data/TicketRepository.kt b/mobile/src/main/java/de/deskhub/staff/data/TicketRepository.kt index 2ce656eb..cbb79dcc 100644 --- a/mobile/src/main/java/de/deskhub/staff/data/TicketRepository.kt +++ b/mobile/src/main/java/de/deskhub/staff/data/TicketRepository.kt @@ -227,9 +227,11 @@ class TicketRepository( status: String?, assignedStaffId: Int?, updateAssignment: Boolean, + resolutionCode: String? = null, ): TicketSummary { val request = linkedMapOf("number" to number) status?.let { request["status"] = it } + resolutionCode?.let { request["resolutionCode"] = it } if (updateAssignment) request["assignedStaffId"] = assignedStaffId return requireApi().updateTicket(request).ticket.toDomain() } @@ -264,8 +266,8 @@ class TicketRepository( ) } - suspend fun addMessage(number: String, type: String, message: String, setStatus: String?, signature: String? = null) { - requireApi().addMessage(MessageRequest(number, type, message, setStatus, signature)) + suspend fun addMessage(number: String, type: String, message: String, setStatus: String?, signature: String? = null, resolutionCode: String? = null) { + requireApi().addMessage(MessageRequest(number, type, message, setStatus, signature, resolutionCode)) } suspend fun cannedResponses(): List = diff --git a/mobile/src/main/java/de/deskhub/staff/ui/DeskHubApp.kt b/mobile/src/main/java/de/deskhub/staff/ui/DeskHubApp.kt index 208a52ff..402493d6 100644 --- a/mobile/src/main/java/de/deskhub/staff/ui/DeskHubApp.kt +++ b/mobile/src/main/java/de/deskhub/staff/ui/DeskHubApp.kt @@ -5365,8 +5365,8 @@ private fun TicketDetailScreen( state: DeskHubState, onBack: () -> Unit, onRetry: () -> Unit, - onUpdate: (String, Int?) -> Unit, - onMessage: (String, String, String?, String?, () -> Unit) -> Unit, + onUpdate: (String, Int?, String?) -> Unit, + onMessage: (String, String, String?, String?, String?, () -> Unit) -> Unit, onSpamAction: (Boolean) -> Unit, onSnooze: (String?) -> Unit = {}, onFollow: (Boolean) -> Unit = {}, @@ -5421,8 +5421,8 @@ private fun TicketDetailContent( cannedResponses: List, mutationLoading: Boolean, mutationError: String?, - onUpdate: (String, Int?) -> Unit, - onMessage: (String, String, String?, String?, () -> Unit) -> Unit, + onUpdate: (String, Int?, String?) -> Unit, + onMessage: (String, String, String?, String?, String?, () -> Unit) -> Unit, onSpamAction: (Boolean) -> Unit, onSnooze: (String?) -> Unit = {}, onFollow: (Boolean) -> Unit = {}, @@ -5437,6 +5437,8 @@ private fun TicketDetailContent( var messageType by rememberSaveable(ticket.summary.number) { mutableStateOf("reply") } var message by rememberSaveable(ticket.summary.number) { mutableStateOf("") } var replyStatus by rememberSaveable(ticket.summary.number) { mutableStateOf("answered") } + var resolutionCode by rememberSaveable(ticket.summary.number) { mutableStateOf("") } + val resolutionChoices = listOf("fixed" to "Fehler behoben", "guidance" to "Anleitung", "duplicate" to "Doppelt", "not_reproducible" to "Nicht reproduzierbar", "no_action" to "Keine Maßnahme", "spam" to "Spam", "automated" to "Automatisch", "other" to "Sonstiges") // Signatur beim Antworten: "own", "dept" oder "none" (Voreinstellung vom Server) var signatureChoice by rememberSaveable(ticket.summary.number) { mutableStateOf(ticket.replySignatures?.default ?: "none") } // Textbausteine: nur die der Abteilung dieses Tickets und die für alle Abteilungen (ältere Server liefern alle) @@ -5726,14 +5728,21 @@ private fun TicketDetailContent( } } Button( - onClick = { onUpdate(selectedStatus, selectedAgent) }, + onClick = { onUpdate(selectedStatus, selectedAgent, resolutionCode.takeIf { selectedStatus in setOf("resolved", "closed") }) }, modifier = Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.medium, enabled = !mutationLoading && - (selectedStatus != ticket.summary.status || selectedAgent != ticket.summary.assignedStaff?.id), + (selectedStatus != ticket.summary.status || selectedAgent != ticket.summary.assignedStaff?.id) && + (selectedStatus !in setOf("resolved", "closed") || selectedStatus == ticket.summary.status || resolutionCode.isNotBlank()), ) { Text(if (mutationLoading) "Speichert …" else "Änderungen speichern") } + if (selectedStatus in setOf("resolved", "closed")) { + var reasonExpanded by remember { mutableStateOf(false) } + SelectionField(label = "Abschlussgrund", value = resolutionChoices.firstOrNull { it.first == resolutionCode }?.second ?: "Bitte auswählen", expanded = reasonExpanded, onExpand = { reasonExpanded = true }, onDismiss = { reasonExpanded = false }) { + resolutionChoices.forEach { (code, label) -> DropdownMenuItem(text = { Text(label) }, onClick = { resolutionCode = code; reasonExpanded = false }) } + } + } } } } @@ -5886,6 +5895,12 @@ private fun TicketDetailContent( ) } } + if (replyStatus in setOf("resolved", "closed")) { + var reasonExpanded by remember { mutableStateOf(false) } + SelectionField(label = "Abschlussgrund", value = resolutionChoices.firstOrNull { it.first == resolutionCode }?.second ?: "Bitte auswählen", expanded = reasonExpanded, onExpand = { reasonExpanded = true }, onDismiss = { reasonExpanded = false }) { + resolutionChoices.forEach { (code, label) -> DropdownMenuItem(text = { Text(label) }, onClick = { resolutionCode = code; reasonExpanded = false }) } + } + } } mutationError?.let { ErrorText(it) } Button( @@ -5895,13 +5910,14 @@ private fun TicketDetailContent( message, replyStatus.takeIf { messageType == "reply" }, signatureChoice.takeIf { messageType == "reply" && ticket.replySignatures != null }, + resolutionCode.takeIf { messageType == "reply" && replyStatus in setOf("resolved", "closed") }, ) { message = "" } }, modifier = Modifier .fillMaxWidth() .height(48.dp), shape = MaterialTheme.shapes.medium, - enabled = !mutationLoading && message.isNotBlank() && + enabled = !mutationLoading && message.isNotBlank() && (messageType != "reply" || replyStatus !in setOf("resolved", "closed") || resolutionCode.isNotBlank()) && ((messageType == "reply" && canReply && !chatLockedByOther) || (messageType == "note" && canNote)), ) { if (!mutationLoading) { diff --git a/mobile/src/main/java/de/deskhub/staff/ui/DeskHubViewModel.kt b/mobile/src/main/java/de/deskhub/staff/ui/DeskHubViewModel.kt index a1836ee4..72b77b6a 100644 --- a/mobile/src/main/java/de/deskhub/staff/ui/DeskHubViewModel.kt +++ b/mobile/src/main/java/de/deskhub/staff/ui/DeskHubViewModel.kt @@ -530,7 +530,7 @@ class DeskHubViewModel( } } - fun updateTicket(status: String, assignedStaffId: Int?) { + fun updateTicket(status: String, assignedStaffId: Int?, resolutionCode: String?) { val ticket = state.value.selectedTicket ?: return viewModelScope.launch { mutableState.update { it.copy(mutationLoading = true, mutationError = null) } @@ -541,6 +541,7 @@ class DeskHubViewModel( status = status.takeIf { it != ticket.summary.status }, assignedStaffId = assignedStaffId, updateAssignment = updateAssignment, + resolutionCode = resolutionCode, ) }.onSuccess { mutableState.update { it.copy(mutationLoading = false) } @@ -720,11 +721,11 @@ class DeskHubViewModel( } } - fun addMessage(type: String, message: String, setStatus: String?, signature: String?, onSuccess: () -> Unit) { + fun addMessage(type: String, message: String, setStatus: String?, signature: String?, resolutionCode: String?, onSuccess: () -> Unit) { val number = state.value.selectedTicket?.summary?.number ?: return viewModelScope.launch { mutableState.update { it.copy(mutationLoading = true, mutationError = null) } - runCatching { repository.addMessage(number, type, message, setStatus, signature) } + runCatching { repository.addMessage(number, type, message, setStatus, signature, resolutionCode) } .onSuccess { mutableState.update { it.copy(mutationLoading = false) } onSuccess()