feat(admin): archive users and their profiles permanently
This commit is contained in:
@@ -11,6 +11,10 @@ const userId = route.params.id as string
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const resettingPassword = ref(false)
|
||||
const archiving = ref(false)
|
||||
const archiveModalOpen = ref(false)
|
||||
const profileStore = useProfileStore()
|
||||
const isOwnUser = computed(() => userId === (auth.user?.id || auth.user?.user_id))
|
||||
const resetPasswordModalOpen = ref(false)
|
||||
const resetPasswordInput = ref("")
|
||||
const resetPasswordResult = ref("")
|
||||
@@ -102,7 +106,7 @@ const fetchUser = async () => {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const overview = await admin.getOverview()
|
||||
const overview = await admin.getOverview(true)
|
||||
roles.value = overview.roles
|
||||
tenants.value = overview.tenants
|
||||
unassignedProfiles.value = overview.unassignedProfiles
|
||||
@@ -136,7 +140,7 @@ const fetchUser = async () => {
|
||||
}
|
||||
|
||||
const saveUser = async () => {
|
||||
if (!userForm.value || saving.value) return
|
||||
if (!userForm.value || userForm.value.archived_at || saving.value || archiving.value) return
|
||||
|
||||
saving.value = true
|
||||
normalizeUserAssignments()
|
||||
@@ -172,8 +176,24 @@ const saveUser = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const archiveUser = async () => {
|
||||
if (!userForm.value || userForm.value.archived_at || archiving.value || isOwnUser.value) return
|
||||
archiving.value = true
|
||||
try {
|
||||
await admin.archiveUser(userForm.value.id)
|
||||
archiveModalOpen.value = false
|
||||
await fetchUser()
|
||||
if (auth.activeTenant) await profileStore.fetchData()
|
||||
toast.add({ title: "Benutzer dauerhaft archiviert", description: "Alle zugehörigen Profile wurden archiviert und ausgeblendet.", color: "success" })
|
||||
} catch (err: any) {
|
||||
toast.add({ title: "Benutzer konnte nicht archiviert werden", description: err?.data?.error || err?.message || "Unbekannter Fehler", color: "error" })
|
||||
} finally {
|
||||
archiving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetPassword = async () => {
|
||||
if (!userForm.value || resettingPassword.value) return
|
||||
if (!userForm.value || userForm.value.archived_at || resettingPassword.value || archiving.value) return
|
||||
|
||||
const password = resetPasswordInput.value.trim()
|
||||
if (password && password.length < 8) {
|
||||
@@ -248,21 +268,40 @@ onMounted(async () => {
|
||||
</UButton>
|
||||
</template>
|
||||
<template #right>
|
||||
<UButton
|
||||
v-if="userForm && !userForm.archived_at && !isOwnUser"
|
||||
color="error"
|
||||
variant="soft"
|
||||
icon="i-heroicons-archive-box"
|
||||
:disabled="saving || resettingPassword || archiving"
|
||||
@click="archiveModalOpen = true"
|
||||
>
|
||||
Dauerhaft archivieren
|
||||
</UButton>
|
||||
<UButton
|
||||
color="warning"
|
||||
variant="soft"
|
||||
icon="i-heroicons-key"
|
||||
:disabled="loading || !userForm || !!userForm.archived_at || archiving || saving"
|
||||
@click="openResetPasswordModal"
|
||||
>
|
||||
Passwort zurücksetzen
|
||||
</UButton>
|
||||
<UButton color="primary" :loading="saving" @click="saveUser">
|
||||
<UButton color="primary" :loading="saving" :disabled="loading || !userForm || !!userForm.archived_at || archiving || resettingPassword" @click="saveUser">
|
||||
Speichern
|
||||
</UButton>
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardPanelContent>
|
||||
<UAlert
|
||||
v-if="userForm?.archived_at"
|
||||
title="Benutzer dauerhaft archiviert"
|
||||
:description="`Archiviert am ${new Date(userForm.archived_at).toLocaleString('de-DE')}. Die Anmeldung ist gesperrt. Alle zugehörigen Profile sind archiviert und ausgeblendet.`"
|
||||
color="warning"
|
||||
variant="soft"
|
||||
class="mb-4"
|
||||
/>
|
||||
<UCard v-if="!loading && userForm">
|
||||
<div class="flex items-start justify-between gap-4 mb-6">
|
||||
<div>
|
||||
@@ -273,7 +312,7 @@ onMounted(async () => {
|
||||
|
||||
<USeparator label="Benutzer" />
|
||||
|
||||
<UForm :state="userForm" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<UForm :disabled="!!userForm.archived_at || archiving" :state="userForm" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
|
||||
<UFormField label="E-Mail">
|
||||
<UInput v-model="userForm.email" />
|
||||
</UFormField>
|
||||
@@ -341,6 +380,7 @@ onMounted(async () => {
|
||||
|
||||
<UFormField label="Rolle">
|
||||
<USelectMenu
|
||||
:disabled="!!userForm.archived_at || archiving"
|
||||
:model-value="getRoleForTenant(tenantId)"
|
||||
:items="getRoleOptionsForTenant(tenantId)"
|
||||
value-key="value"
|
||||
@@ -352,6 +392,7 @@ onMounted(async () => {
|
||||
|
||||
<UFormField label="Freies Profil">
|
||||
<USelectMenu
|
||||
:disabled="!!userForm.archived_at || archiving"
|
||||
:model-value="getProfileAssignmentForTenant(tenantId)"
|
||||
:items="[
|
||||
{ label: 'Neues Profil erzeugen', value: null },
|
||||
@@ -390,7 +431,7 @@ onMounted(async () => {
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
>
|
||||
{{ profile.full_name || `${profile.first_name} ${profile.last_name}` }} · Tenant {{ profile.tenant_id }}
|
||||
{{ profile.full_name || `${profile.first_name} ${profile.last_name}` }} · Tenant {{ profile.tenant_id }}{{ profile.archived_at ? " · Archiviert" : "" }}
|
||||
</UBadge>
|
||||
</div>
|
||||
</UCard>
|
||||
@@ -398,6 +439,27 @@ onMounted(async () => {
|
||||
<USkeleton v-if="loading" class="h-80" />
|
||||
</UDashboardPanelContent>
|
||||
|
||||
<UModal v-model:open="archiveModalOpen" :dismissible="!archiving">
|
||||
<template #content>
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="text-lg font-semibold">Benutzer dauerhaft archivieren</div>
|
||||
</template>
|
||||
<div class="space-y-3 text-sm">
|
||||
<p><strong>{{ userForm?.display_name }}</strong> ({{ userForm?.email }}) wird dauerhaft archiviert.</p>
|
||||
<p>Die Anmeldung wird gesperrt, bestehende Sitzungen und API-Schlüssel werden ungültig. Alle {{ userForm?.profiles.length || 0 }} zugehörigen Profile werden in sämtlichen Tenants archiviert und aus Listen und Auswahllisten ausgeblendet.</p>
|
||||
<p>Bestehende Dokumente, Zeiterfassungen und Zuordnungen bleiben erhalten. Die Archivierung kann nicht rückgängig gemacht werden.</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-3">
|
||||
<UButton variant="soft" color="neutral" :disabled="archiving" @click="archiveModalOpen = false">Abbrechen</UButton>
|
||||
<UButton color="error" icon="i-heroicons-archive-box" :loading="archiving" @click="archiveUser">Dauerhaft archivieren</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
<UModal v-model:open="resetPasswordModalOpen">
|
||||
<template #content>
|
||||
<UCard>
|
||||
|
||||
Reference in New Issue
Block a user