Upload via GUI (2911 Dateien)
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -64,6 +64,7 @@ import de.deskhub.staff.domain.model.ActivityUpdate
|
||||
import de.deskhub.staff.domain.model.AuditLogEntry
|
||||
import de.deskhub.staff.domain.model.AuditLogPage
|
||||
import de.deskhub.staff.domain.model.BanEntry
|
||||
import de.deskhub.staff.domain.model.PermissionGroup
|
||||
import de.deskhub.staff.domain.model.BusinessHoliday
|
||||
import de.deskhub.staff.domain.model.BusinessHour
|
||||
import de.deskhub.staff.domain.model.BusinessHoursData
|
||||
@@ -257,6 +258,20 @@ class TicketRepository(
|
||||
suspend fun setFollowing(number: String, following: Boolean): TicketSummary =
|
||||
requireApi().updateTicket(linkedMapOf("number" to number, "following" to following)).ticket.toDomain()
|
||||
|
||||
/** E-Mail-Abruf von Hand: Ergebnis (ohne Probleme?, fertiger Text). */
|
||||
suspend fun fetchMailNow(): Pair<Boolean, String> {
|
||||
val r = requireApi().mailFetch()
|
||||
return Pair(r.ok || r.level != "error", r.message)
|
||||
}
|
||||
|
||||
/** Anhänge, Bilder und Formatierung der Original-E-Mail nachladen: fertiger Text. */
|
||||
suspend fun reloadMailAttachments(number: String): String {
|
||||
val r = requireApi().ticketMailReload(number)
|
||||
val n = r.added + r.inlineImages
|
||||
val base = if (n > 0 || r.formatting) "Anhänge und Bilder aus der E-Mail nachgeladen." else "Es gab nichts Neues zu laden."
|
||||
return if (r.failed.isEmpty()) base else base + " " + r.failed.joinToString(" | ")
|
||||
}
|
||||
|
||||
suspend fun setTicketSpam(number: String, restore: Boolean): SpamActionResult {
|
||||
val response = requireApi().ticketSpam(number, if (restore) "restore" else "mark")
|
||||
return SpamActionResult(
|
||||
@@ -588,6 +603,8 @@ class TicketRepository(
|
||||
permissionKeys = r.permissionKeys,
|
||||
defaultAgentPermissions = r.defaultAgentPermissions,
|
||||
roles = r.items.map(RoleDto::toDomain),
|
||||
permissionLabels = r.permissionLabels,
|
||||
permissionGroups = r.permissionGroups.map { PermissionGroup(it.title, it.keys) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -832,7 +849,7 @@ private fun PriorityFullDto.toDomain() = PriorityFull(id, name, color, responseH
|
||||
private fun HelpTopicDto.toDomain() = HelpTopic(id, name, departmentId, departmentName, priorityId, priorityName, priorityColor, isActive, sortOrder, isPublic, parentId, slaPlanId, path.ifBlank { name }, notes, updatedAt)
|
||||
private fun OrganizationDto.toDomain() = Organization(id, name, notes, memberCount)
|
||||
private fun TeamDto.toDomain() = Team(id, name, leadId, leadName, notes, isActive, memberCount, memberIds)
|
||||
private fun RoleDto.toDomain() = Role(id, name, permissions, staffCount)
|
||||
private fun RoleDto.toDomain() = Role(id, name, permissions, staffCount, effectivePermissions)
|
||||
private fun StaffAdminDto.toDomain() = StaffAdmin(
|
||||
id, name, email, role, roleId, departmentId, departmentName, isActive, signature,
|
||||
extraDepartments.map { StaffExtraDept(it.departmentId, it.notify, it.roleId) },
|
||||
|
||||
@@ -99,6 +99,9 @@ import de.deskhub.staff.data.remote.dto.UploadImageResponse
|
||||
import de.deskhub.staff.data.remote.dto.TicketResponse
|
||||
import de.deskhub.staff.data.remote.dto.TicketSpamRequest
|
||||
import de.deskhub.staff.data.remote.dto.TicketSpamResponse
|
||||
import de.deskhub.staff.data.remote.dto.MailFetchResponse
|
||||
import de.deskhub.staff.data.remote.dto.MailReloadRequest
|
||||
import de.deskhub.staff.data.remote.dto.MailReloadResponse
|
||||
import de.deskhub.staff.data.remote.dto.TicketSummaryResponse
|
||||
import de.deskhub.staff.data.remote.dto.TimeEntriesResponse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -196,6 +199,12 @@ class DeskHubApi(
|
||||
suspend fun addMessage(request: MessageRequest): MessageResponse =
|
||||
execute("ticket_message.php", "POST", request, MessageResponse::class.java)
|
||||
|
||||
suspend fun mailFetch(): MailFetchResponse =
|
||||
execute("mail_fetch.php", "POST", emptyMap<String, String>(), MailFetchResponse::class.java)
|
||||
|
||||
suspend fun ticketMailReload(number: String): MailReloadResponse =
|
||||
execute("ticket_mail_reload.php", "POST", MailReloadRequest(number = number), MailReloadResponse::class.java)
|
||||
|
||||
suspend fun ticketSpam(number: String, action: String): TicketSpamResponse =
|
||||
execute("ticket_spam.php", "POST", TicketSpamRequest(number = number, action = action), TicketSpamResponse::class.java)
|
||||
|
||||
|
||||
@@ -200,6 +200,31 @@ data class CustomFieldValueDto(
|
||||
val value: String = "",
|
||||
)
|
||||
|
||||
/** E-Mail-Abruf von Hand (Antwort der Schnittstelle mail_fetch.php). */
|
||||
data class MailFetchResponse(
|
||||
val ok: Boolean = false,
|
||||
val level: String = "info",
|
||||
val message: String = "",
|
||||
val processed: Int = 0,
|
||||
val newTickets: Int = 0,
|
||||
val replies: Int = 0,
|
||||
val available: Boolean = false,
|
||||
)
|
||||
|
||||
/** Anhänge, eingebettete Bilder und Formatierung der Original-E-Mail nachladen (ticket_mail_reload.php). */
|
||||
data class MailReloadRequest(
|
||||
val number: String,
|
||||
)
|
||||
|
||||
data class MailReloadResponse(
|
||||
val added: Int = 0,
|
||||
val inlineImages: Int = 0,
|
||||
val formatting: Boolean = false,
|
||||
val skipped: Int = 0,
|
||||
val alreadyThere: Int = 0,
|
||||
val failed: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
data class TicketSpamRequest(
|
||||
val number: String,
|
||||
val action: String,
|
||||
@@ -817,11 +842,20 @@ data class RoleDto(
|
||||
val id: Int = 0,
|
||||
val name: String = "",
|
||||
val permissions: Map<String, Boolean> = emptyMap(),
|
||||
/** Wirksame Rechte (fehlende Rechte der Rolle mit Standardwert aufgefüllt); ältere Server liefern das nicht. */
|
||||
val effectivePermissions: Map<String, Boolean>? = null,
|
||||
val staffCount: Int = 0,
|
||||
)
|
||||
|
||||
data class PermissionGroupDto(
|
||||
val title: String = "",
|
||||
val keys: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
data class RoleListResponse(
|
||||
val permissionKeys: List<String> = emptyList(),
|
||||
val permissionLabels: Map<String, String> = emptyMap(),
|
||||
val permissionGroups: List<PermissionGroupDto> = emptyList(),
|
||||
val defaultAgentPermissions: Map<String, Boolean> = emptyMap(),
|
||||
val items: List<RoleDto> = emptyList(),
|
||||
)
|
||||
|
||||
@@ -456,12 +456,17 @@ data class Role(
|
||||
val name: String,
|
||||
val permissions: Map<String, Boolean>,
|
||||
val staffCount: Int,
|
||||
val effectivePermissions: Map<String, Boolean>? = null,
|
||||
)
|
||||
|
||||
data class PermissionGroup(val title: String, val keys: List<String>)
|
||||
|
||||
data class RoleData(
|
||||
val permissionKeys: List<String>,
|
||||
val defaultAgentPermissions: Map<String, Boolean>,
|
||||
val roles: List<Role>,
|
||||
val permissionLabels: Map<String, String> = emptyMap(),
|
||||
val permissionGroups: List<PermissionGroup> = emptyList(),
|
||||
)
|
||||
|
||||
// ─── Admin: Mitarbeiter ──────────────────────────────────────────────────────
|
||||
|
||||
@@ -289,6 +289,7 @@ private fun StaffNavigation(state: DeskHubState, viewModel: DeskHubViewModel) {
|
||||
navController.navigate("detail")
|
||||
},
|
||||
onLogout = viewModel::logout,
|
||||
onFetchMail = viewModel::fetchMailNow,
|
||||
onNewTicket = {
|
||||
viewModel.startNewTicket()
|
||||
navController.navigate("newTicket")
|
||||
@@ -394,6 +395,7 @@ private fun StaffNavigation(state: DeskHubState, viewModel: DeskHubViewModel) {
|
||||
onUpdate = viewModel::updateTicket,
|
||||
onMessage = viewModel::addMessage,
|
||||
onSpamAction = viewModel::setTicketSpam,
|
||||
onReloadMailAttachments = viewModel::reloadMailAttachments,
|
||||
onSnooze = viewModel::snoozeTicket,
|
||||
onFollow = viewModel::setFollowing,
|
||||
onAddCc = viewModel::addCollaborator,
|
||||
@@ -452,7 +454,7 @@ private fun StaffNavigation(state: DeskHubState, viewModel: DeskHubViewModel) {
|
||||
onBack = { navController.popBackStack() },
|
||||
onLoad = viewModel::loadAvailability,
|
||||
onSetStatus = viewModel::setMyAvailability,
|
||||
isAdmin = state.staff?.role == "admin",
|
||||
isAdmin = state.can("manage_availability"),
|
||||
onSetMemberStatus = viewModel::setMemberAvailability,
|
||||
)
|
||||
}
|
||||
@@ -908,6 +910,7 @@ private fun TicketListScreen(
|
||||
onLoadViews: () -> Unit,
|
||||
onTicket: (String) -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
onFetchMail: () -> Unit,
|
||||
onNewTicket: () -> Unit,
|
||||
) {
|
||||
LaunchedEffect(Unit) { onLoadViews() }
|
||||
@@ -938,6 +941,7 @@ private fun TicketListScreen(
|
||||
Text("Tickets", style = MaterialTheme.typography.headlineMedium)
|
||||
Text("${state.ticketsTotal} insgesamt", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
if (state.can("fetch_mail")) androidx.compose.material3.TextButton(onClick = onFetchMail, enabled = !state.mutationLoading) { Text("📥 E-Mails abrufen") }
|
||||
IconButton(onClick = onRefresh) { Icon(Icons.Filled.Refresh, contentDescription = "Aktualisieren") }
|
||||
}
|
||||
TicketFilterBar(
|
||||
@@ -951,6 +955,7 @@ private fun TicketListScreen(
|
||||
onDeleteView = onDeleteView,
|
||||
)
|
||||
state.mutationError?.let { ErrorText(it, Modifier.padding(horizontal = 16.dp)) }
|
||||
state.mutationInfo?.let { Text(it, modifier = Modifier.padding(horizontal = 20.dp, vertical = 4.dp), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) }
|
||||
when {
|
||||
state.ticketsLoading && state.tickets.isEmpty() -> LoadingState("Tickets werden geladen …")
|
||||
state.ticketsError != null && state.tickets.isEmpty() -> ErrorState(state.ticketsError, onRefresh)
|
||||
@@ -1059,7 +1064,7 @@ private fun TicketFilterBar(
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Speichert die aktuellen Filter.", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
OutlinedTextField(value = viewName, onValueChange = { viewName = it }, label = { Text("Name") }, singleLine = true, shape = MaterialTheme.shapes.medium)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (metadata?.staff?.permissions?.get("share_views") == true) Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = viewShared, onCheckedChange = { viewShared = it })
|
||||
Text("Für alle Mitarbeiter sichtbar")
|
||||
}
|
||||
@@ -1286,7 +1291,7 @@ private fun TicketFilterBar(
|
||||
FilterChip(
|
||||
selected = filters.snoozed,
|
||||
onClick = { onFiltersChanged(filters.copy(snoozed = !filters.snoozed)); onSearch() },
|
||||
label = { Text("💤 Wiedervorlage") },
|
||||
label = { Text("Wiedervorlage") },
|
||||
shape = CircleShape,
|
||||
)
|
||||
}
|
||||
@@ -4110,13 +4115,41 @@ private val AdminSectionPermissionLabels = linkedMapOf(
|
||||
"manage_business_hours" to "Geschäftszeiten verwalten",
|
||||
)
|
||||
|
||||
private val RolePermissionLabels = TicketPermissionLabels + AdminSectionPermissionLabels
|
||||
/** Rechte, die mit Server 1.0.85 dazugekommen sind (Beschriftungen für ältere Server ohne „permissionLabels“). */
|
||||
private val NewPermissionLabels = linkedMapOf(
|
||||
"merge_split_ticket" to "Tickets zusammenführen und teilen",
|
||||
"link_tickets" to "Tickets verknüpfen",
|
||||
"view_customers" to "Kundenprofile ansehen",
|
||||
"manage_customers" to "Kundenverzeichnis verwalten",
|
||||
"manage_banlist" to "Sperrliste und Absender sperren",
|
||||
"schedule_reports" to "Berichte per E-Mail versenden",
|
||||
"export_tickets" to "Tickets als CSV exportieren",
|
||||
"fetch_mail" to "E-Mails jetzt abrufen",
|
||||
"view_mail_queue" to "Mail-Warteschlange verwalten",
|
||||
"manage_ticket_templates" to "Ticket-Vorlagen verwalten",
|
||||
"manage_site_pages" to "Seiten im Kundenportal verwalten",
|
||||
"manage_languages" to "Sprachen verwalten",
|
||||
"manage_status_page" to "Statusseite verwalten",
|
||||
"share_views" to "Ansichten mit dem Team teilen",
|
||||
"manage_custom_fields" to "Ticket-Felder und Listen verwalten",
|
||||
"manage_automation" to "Ticket-Regeln und wiederkehrende Tickets verwalten",
|
||||
"manage_problems" to "Problemmanagement",
|
||||
"manage_availability" to "Status und Schichten der Kollegen ändern",
|
||||
"view_login_guard" to "Anmelde-Wächter ansehen",
|
||||
"view_help" to "Hilfe und Handbuch"
|
||||
)
|
||||
|
||||
private val RolePermissionLabels = TicketPermissionLabels + AdminSectionPermissionLabels + NewPermissionLabels
|
||||
|
||||
/** Hat der angemeldete Mitarbeiter dieses Recht? Echte Administratoren haben alle. */
|
||||
private fun DeskHubState.can(permission: String): Boolean = staff?.role == "admin" || staff?.permissions?.get(permission) == true
|
||||
|
||||
/** Ist true für echte Admins UND für Agenten, denen eine der obigen Berechtigungen zugewiesen wurde. */
|
||||
private fun Staff?.hasAnyAdminAccess(): Boolean {
|
||||
if (this == null) return false
|
||||
if (role == "admin") return true
|
||||
return AdminSectionPermissionLabels.keys.any { permissions[it] == true } || permissions["view_reports"] == true
|
||||
return AdminSectionPermissionLabels.keys.any { permissions[it] == true } || permissions["view_reports"] == true ||
|
||||
permissions["manage_automation"] == true || permissions["manage_banlist"] == true || permissions["manage_availability"] == true
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -4149,32 +4182,27 @@ private fun AdminRolesScreen(
|
||||
showForm -> {
|
||||
val role = data.roles.firstOrNull { it.id == editingId }
|
||||
var name by rememberSaveable(role?.id) { mutableStateOf(role?.name ?: "") }
|
||||
var perms by rememberSaveable(role?.id) { mutableStateOf(role?.permissions ?: emptyMap()) }
|
||||
var perms by rememberSaveable(role?.id) { mutableStateOf(role?.effectivePermissions ?: role?.permissions ?: data.defaultAgentPermissions) }
|
||||
val permLabels = RolePermissionLabels + data.permissionLabels
|
||||
val permGroups: List<Pair<String, List<String>>> = if (data.permissionGroups.isNotEmpty()) data.permissionGroups.map { it.title to it.keys }
|
||||
else listOf("Ticket-Berechtigungen" to TicketPermissionLabels.keys.toList(), "Adminportal-Bereiche (\"Co-Admin\")" to AdminSectionPermissionLabels.keys.toList())
|
||||
Column(
|
||||
modifier = Modifier.padding(padding).fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text(if (role == null) "Neue Rolle" else "Rolle bearbeiten", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
|
||||
OutlinedTextField(value = name, onValueChange = { name = it }, modifier = Modifier.fillMaxWidth(), label = { Text("Name") }, singleLine = true, shape = MaterialTheme.shapes.medium)
|
||||
Text("Ticket-Berechtigungen", style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.SemiBold)
|
||||
TicketPermissionLabels.forEach { (key, label) ->
|
||||
permGroups.forEach { (groupTitle, keys) ->
|
||||
Text(groupTitle, style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.SemiBold)
|
||||
keys.forEach { key ->
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||
Checkbox(
|
||||
checked = perms[key] == true,
|
||||
onCheckedChange = { checked -> perms = perms + (key to checked) },
|
||||
)
|
||||
Text(label, style = MaterialTheme.typography.bodyMedium)
|
||||
Text(permLabels[key] ?: key, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
Text("Adminportal-Bereiche (\"Co-Admin\")", style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.SemiBold)
|
||||
AdminSectionPermissionLabels.forEach { (key, label) ->
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||
Checkbox(
|
||||
checked = perms[key] == true,
|
||||
onCheckedChange = { checked -> perms = perms + (key to checked) },
|
||||
)
|
||||
Text(label, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
state.rolesMutationError?.let { ErrorText(it) }
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
@@ -4201,7 +4229,7 @@ private fun RoleRowAdmin(role: Role, onEdit: () -> Unit, onDelete: () -> Unit) {
|
||||
Column(Modifier.padding(14.dp)) {
|
||||
Text(role.name, fontWeight = FontWeight.SemiBold)
|
||||
Text("${role.staffCount} Mitarbeiter", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
val granted = role.permissions.filterValues { it }.keys.mapNotNull { RolePermissionLabels[it] }
|
||||
val granted = (role.effectivePermissions ?: role.permissions).filterValues { it }.keys.mapNotNull { RolePermissionLabels[it] }
|
||||
Text(
|
||||
if (granted.isEmpty()) "Keine Berechtigungen" else granted.joinToString(", "),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
@@ -5368,6 +5396,7 @@ private fun TicketDetailScreen(
|
||||
onUpdate: (String, Int?, String?) -> Unit,
|
||||
onMessage: (String, String, String?, String?, String?, () -> Unit) -> Unit,
|
||||
onSpamAction: (Boolean) -> Unit,
|
||||
onReloadMailAttachments: () -> Unit,
|
||||
onSnooze: (String?) -> Unit = {},
|
||||
onFollow: (Boolean) -> Unit = {},
|
||||
onAddCc: (String) -> Unit = {},
|
||||
@@ -5402,6 +5431,8 @@ private fun TicketDetailScreen(
|
||||
onUpdate = onUpdate,
|
||||
onMessage = onMessage,
|
||||
onSpamAction = onSpamAction,
|
||||
onReloadMailAttachments = onReloadMailAttachments,
|
||||
mutationInfo = state.mutationInfo,
|
||||
onSnooze = onSnooze,
|
||||
onFollow = onFollow,
|
||||
onAddCc = onAddCc,
|
||||
@@ -5421,9 +5452,11 @@ private fun TicketDetailContent(
|
||||
cannedResponses: List<de.deskhub.staff.domain.model.CannedResponse>,
|
||||
mutationLoading: Boolean,
|
||||
mutationError: String?,
|
||||
mutationInfo: String?,
|
||||
onUpdate: (String, Int?, String?) -> Unit,
|
||||
onMessage: (String, String, String?, String?, String?, () -> Unit) -> Unit,
|
||||
onSpamAction: (Boolean) -> Unit,
|
||||
onReloadMailAttachments: () -> Unit = {},
|
||||
onSnooze: (String?) -> Unit = {},
|
||||
onFollow: (Boolean) -> Unit = {},
|
||||
onAddCc: (String) -> Unit = {},
|
||||
@@ -5574,6 +5607,14 @@ private fun TicketDetailContent(
|
||||
}
|
||||
}
|
||||
if (ticket.source == "email") {
|
||||
item {
|
||||
OutlinedButton(
|
||||
onClick = onReloadMailAttachments,
|
||||
enabled = !mutationLoading,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text("📎 Anhänge und Bilder aus E-Mail laden") }
|
||||
mutationInfo?.let { Text(it, modifier = Modifier.padding(top = 6.dp), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) }
|
||||
}
|
||||
item {
|
||||
if (!ticket.summary.isSpam) {
|
||||
OutlinedButton(
|
||||
|
||||
@@ -111,6 +111,7 @@ data class DeskHubState(
|
||||
val detailError: String? = null,
|
||||
val mutationLoading: Boolean = false,
|
||||
val mutationError: String? = null,
|
||||
val mutationInfo: String? = null,
|
||||
val dashboard: DashboardStats? = null,
|
||||
val dashboardLoading: Boolean = false,
|
||||
val dashboardError: String? = null,
|
||||
@@ -696,6 +697,43 @@ class DeskHubViewModel(
|
||||
|
||||
fun statusClearMessage() = updateStatusPage { it.copy(message = null, error = null) }
|
||||
|
||||
/** E-Mail-Abruf von Hand starten (Knopf in der Ticketliste). */
|
||||
fun fetchMailNow() {
|
||||
viewModelScope.launch {
|
||||
mutableState.update { it.copy(mutationLoading = true, mutationError = null, mutationInfo = null) }
|
||||
runCatching { repository.fetchMailNow() }
|
||||
.onSuccess { (ok, message) ->
|
||||
mutableState.update {
|
||||
if (ok) it.copy(mutationLoading = false, mutationInfo = message) else it.copy(mutationLoading = false, mutationError = message)
|
||||
}
|
||||
loadTickets(reset = true)
|
||||
kotlinx.coroutines.delay(8000)
|
||||
mutableState.update { it.copy(mutationInfo = null) }
|
||||
}
|
||||
.onFailure { error ->
|
||||
mutableState.update { it.copy(mutationLoading = false, mutationError = error.userMessage()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Lädt Anhänge, eingebettete Bilder und die Formatierung der Original-E-Mail des geöffneten Tickets nach. */
|
||||
fun reloadMailAttachments() {
|
||||
val ticket = state.value.selectedTicket ?: return
|
||||
viewModelScope.launch {
|
||||
mutableState.update { it.copy(mutationLoading = true, mutationError = null, mutationInfo = null) }
|
||||
runCatching { repository.reloadMailAttachments(ticket.summary.number) }
|
||||
.onSuccess { message ->
|
||||
mutableState.update { it.copy(mutationLoading = false, mutationInfo = message) }
|
||||
openTicket(ticket.summary.number)
|
||||
kotlinx.coroutines.delay(8000)
|
||||
mutableState.update { it.copy(mutationInfo = null) }
|
||||
}
|
||||
.onFailure { error ->
|
||||
mutableState.update { it.copy(mutationLoading = false, mutationError = error.userMessage()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Markiert das aktuell geöffnete Ticket als Spam, oder macht das (restore = true) rückgängig. */
|
||||
fun setTicketSpam(restore: Boolean) {
|
||||
val ticket = state.value.selectedTicket ?: return
|
||||
|
||||
@@ -111,7 +111,7 @@ fun MoreScreen(staff: Staff?, onNavigate: (String) -> Unit, onLogout: () -> Unit
|
||||
|
||||
val personal = listOf(
|
||||
MoreEntry(Icons.AutoMirrored.Filled.Assignment, "Aufgaben", "To-dos für dich und dein Team", "adminTasks"),
|
||||
MoreEntry(Icons.Filled.Circle, if (isFullAdmin) "Status der Mitarbeiter" else "Mein Status", "Verfügbarkeit und Team-Übersicht", "adminAvailability"),
|
||||
MoreEntry(Icons.Filled.Circle, if (allowed("manage_availability")) "Status der Mitarbeiter" else "Mein Status", "Verfügbarkeit und Team-Übersicht", "adminAvailability"),
|
||||
MoreEntry(Icons.Filled.Notifications, "Benachrichtigungen", "Popups und stille Zeiten", "pushPrefs"),
|
||||
)
|
||||
val manage = buildList {
|
||||
@@ -135,8 +135,8 @@ fun MoreScreen(staff: Staff?, onNavigate: (String) -> Unit, onLogout: () -> Unit
|
||||
if (allowed("view_time_entries")) add(MoreEntry(Icons.Filled.AccessTime, "Zeiterfassung", "Zeiten je Mitarbeiter", "adminTimeEntries"))
|
||||
if (allowed("manage_escalations")) add(MoreEntry(Icons.AutoMirrored.Filled.Rule, "Eskalationsregeln", "Regeln für überfällige Tickets", "adminEscalation"))
|
||||
if (allowed("manage_business_hours")) add(MoreEntry(Icons.Filled.Schedule, "Geschäftszeiten", "SLA-Zeitfenster, Feiertage", "adminBusinessHours"))
|
||||
if (isFullAdmin) add(MoreEntry(Icons.Filled.Bolt, "Automatisierung", "Ticket-Regeln, Auto-Antworten", "adminTicketRules"))
|
||||
if (isFullAdmin) add(MoreEntry(Icons.Filled.Block, "Sperrliste", "Absender sperren", "adminBanlist"))
|
||||
if (allowed("manage_automation")) add(MoreEntry(Icons.Filled.Bolt, "Automatisierung", "Ticket-Regeln, Auto-Antworten", "adminTicketRules"))
|
||||
if (allowed("manage_banlist")) add(MoreEntry(Icons.Filled.Block, "Sperrliste", "Absender sperren", "adminBanlist"))
|
||||
add(MoreEntry(Icons.Filled.Flag, "Statusseite", "Dienste und Störungen für Kunden", "adminStatus"))
|
||||
if (allowed("view_audit_log")) add(MoreEntry(Icons.Filled.History, "Admin-Protokoll", "Änderungen und Mailfehler", "adminAuditLog"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user