Files
FEDEO/frontend/pages/administration/users/index.vue
florianfederspiel 6dcd8b1863 KI-AGENT: Tabellen-Empty-States ohne JSON rendern
Ersetzt ungültige UTable-Empty-Props durch einen gemeinsamen Empty-State-Slot, damit leere Tabellen keine Objekt-/JSON-Ausgabe mehr anzeigen.
2026-05-19 18:36:54 +02:00

208 lines
5.6 KiB
Vue

<script setup lang="ts">
import type { AdminUser } from "~/composables/useAdmin"
const auth = useAuthStore()
const toast = useToast()
const router = useRouter()
const admin = useAdmin()
const loading = ref(true)
const creatingUser = ref(false)
const createUserModalOpen = ref(false)
const createdUserPassword = ref("")
const users = ref<AdminUser[]>([])
const searchString = ref("")
const createUserForm = ref({
email: "",
password: "",
first_name: "",
last_name: "",
is_admin: false,
multiTenant: true,
})
const templateColumns = [
{ key: "display_name", label: "Benutzer" },
{ key: "email", label: "E-Mail" },
{ key: "tenant_count", label: "Tenants" },
{ key: "is_admin", label: "Admin" },
]
const filteredRows = computed(() => {
const search = searchString.value.trim().toLowerCase()
const rows = users.value.map((user) => ({
...user,
tenant_count: user.tenant_ids.length,
is_admin: user.is_admin ? "Ja" : "Nein",
}))
if (!search) return rows
return rows.filter((row) =>
[row.display_name, row.email]
.filter(Boolean)
.some((value) => String(value).toLowerCase().includes(search))
)
})
const fetchUsers = async () => {
loading.value = true
try {
const overview = await admin.getOverview()
users.value = overview.users
} catch (err: any) {
console.error("[administration/users/index]", err)
toast.add({
title: "Benutzer konnten nicht geladen werden",
description: err?.data?.error || err?.message || "Unbekannter Fehler",
color: "red",
})
} finally {
loading.value = false
}
}
const createUser = async () => {
if (creatingUser.value) return
creatingUser.value = true
try {
const response = await admin.createUser(createUserForm.value)
createdUserPassword.value = response.initialPassword || ""
createUserModalOpen.value = false
createUserForm.value = {
email: "",
password: "",
first_name: "",
last_name: "",
is_admin: false,
multiTenant: true,
}
await fetchUsers()
toast.add({
title: "Benutzer angelegt",
description: createdUserPassword.value ? `Initialpasswort: ${createdUserPassword.value}` : undefined,
color: "green",
})
if (response.user?.id) {
await router.push(`/administration/users/${response.user.id}`)
}
} catch (err: any) {
console.error("[administration/users/create]", err)
toast.add({
title: "Benutzer konnte nicht angelegt werden",
description: err?.data?.error || err?.message || "Unbekannter Fehler",
color: "red",
})
} finally {
creatingUser.value = false
}
}
onMounted(async () => {
if (!auth.user?.is_admin) {
await router.push("/")
return
}
await fetchUsers()
})
</script>
<template>
<UDashboardNavbar title="Administration: Benutzer" :badge="filteredRows.length">
<template #right>
<UInput
v-model="searchString"
icon="i-heroicons-magnifying-glass"
placeholder="Benutzer suchen"
class="hidden lg:block"
/>
<UButton icon="i-heroicons-plus" @click="createUserModalOpen = true">
Benutzer
</UButton>
</template>
</UDashboardNavbar>
<UTable
:data="filteredRows"
:columns="normalizeTableColumns(templateColumns)"
:loading="loading"
:on-select="(row) => router.push(`/administration/users/${row.original?.id || row.id}`)"
>
<template #empty>
<TableEmptyState label="Keine Benutzer gefunden" icon="i-heroicons-users" />
</template>
</UTable>
<UModal v-model:open="createUserModalOpen">
<template #content>
<UCard>
<template #header>
<div class="text-lg font-semibold">Benutzer anlegen</div>
</template>
<UForm :state="createUserForm" class="space-y-4" @submit.prevent="createUser">
<UFormField label="E-Mail">
<UInput v-model="createUserForm.email" type="email" />
</UFormField>
<UFormField label="Initialpasswort">
<UInput v-model="createUserForm.password" placeholder="Leer lassen für automatisches Passwort" />
</UFormField>
<UFormField label="Vorname für neues Profil">
<UInput v-model="createUserForm.first_name" />
</UFormField>
<UFormField label="Nachname für neues Profil">
<UInput v-model="createUserForm.last_name" />
</UFormField>
<UFormField label="Administrative Freigabe">
<div class="flex items-center gap-3 h-10">
<USwitch v-model="createUserForm.is_admin" />
<span class="text-sm text-gray-600">Benutzer darf die Administration öffnen</span>
</div>
</UFormField>
<UFormField label="Multi-Tenant">
<div class="flex items-center gap-3 h-10">
<USwitch v-model="createUserForm.multiTenant" />
<span class="text-sm text-gray-600">Mehrere Tenant-Zuordnungen erlauben</span>
</div>
</UFormField>
<div class="flex justify-end gap-3 pt-2">
<UButton color="gray" variant="soft" @click="createUserModalOpen = false">
Abbrechen
</UButton>
<UButton type="submit" color="primary" :loading="creatingUser">
Benutzer anlegen
</UButton>
</div>
</UForm>
</UCard>
</template>
</UModal>
<div class="mx-5 mb-5">
<UAlert
v-if="createdUserPassword"
title="Initialpasswort für neuen Benutzer"
:description="createdUserPassword"
color="amber"
variant="soft"
close-button
@close="createdUserPassword = ''"
/>
</div>
</template>