219 lines
5.8 KiB
Vue
219 lines
5.8 KiB
Vue
<script setup lang="ts">
|
|
import type { AdminTenant } from "~/composables/useAdmin"
|
|
|
|
const auth = useAuthStore()
|
|
const toast = useToast()
|
|
const router = useRouter()
|
|
const admin = useAdmin()
|
|
|
|
const loading = ref(true)
|
|
const creatingTenant = ref(false)
|
|
const importingTenant = ref(false)
|
|
const createTenantModalOpen = ref(false)
|
|
const importFileInput = ref<HTMLInputElement | null>(null)
|
|
const tenants = ref<AdminTenant[]>([])
|
|
const searchString = ref("")
|
|
|
|
const createTenantForm = ref({
|
|
name: "",
|
|
short: "",
|
|
})
|
|
|
|
const templateColumns = [
|
|
{ key: "name", label: "Tenant" },
|
|
{ key: "short", label: "Kürzel" },
|
|
{ key: "user_count", label: "Benutzer" },
|
|
]
|
|
|
|
const filteredRows = computed(() => {
|
|
const search = searchString.value.trim().toLowerCase()
|
|
if (!search) return tenants.value
|
|
|
|
return tenants.value.filter((tenant) =>
|
|
[tenant.name, tenant.short]
|
|
.filter(Boolean)
|
|
.some((value) => String(value).toLowerCase().includes(search))
|
|
)
|
|
})
|
|
|
|
const fetchTenants = async () => {
|
|
loading.value = true
|
|
|
|
try {
|
|
const overview = await admin.getOverview()
|
|
tenants.value = overview.tenants
|
|
} catch (err: any) {
|
|
console.error("[administration/tenants/index]", err)
|
|
toast.add({
|
|
title: "Tenants konnten nicht geladen werden",
|
|
description: err?.data?.error || err?.message || "Unbekannter Fehler",
|
|
color: "red",
|
|
})
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const createTenant = async () => {
|
|
if (creatingTenant.value) return
|
|
|
|
creatingTenant.value = true
|
|
|
|
try {
|
|
const response = await admin.createTenant(createTenantForm.value)
|
|
|
|
createTenantModalOpen.value = false
|
|
createTenantForm.value = {
|
|
name: "",
|
|
short: "",
|
|
}
|
|
|
|
await fetchTenants()
|
|
|
|
toast.add({
|
|
title: "Tenant angelegt",
|
|
description: "Standardordner und Datei-Tags wurden erstellt.",
|
|
color: "green",
|
|
})
|
|
|
|
if (response.tenant?.id) {
|
|
await router.push(`/administration/tenants/${response.tenant.id}`)
|
|
}
|
|
} catch (err: any) {
|
|
console.error("[administration/tenants/create]", err)
|
|
toast.add({
|
|
title: "Tenant konnte nicht angelegt werden",
|
|
description: err?.data?.error || err?.message || "Unbekannter Fehler",
|
|
color: "red",
|
|
})
|
|
} finally {
|
|
creatingTenant.value = false
|
|
}
|
|
}
|
|
|
|
const openImportFileDialog = () => {
|
|
importFileInput.value?.click()
|
|
}
|
|
|
|
const importTenantExport = async (event: Event) => {
|
|
const input = event.target as HTMLInputElement
|
|
const file = input.files?.[0]
|
|
if (!file || importingTenant.value) return
|
|
|
|
importingTenant.value = true
|
|
|
|
try {
|
|
const exportData = JSON.parse(await file.text())
|
|
const result = await admin.importTenant(exportData)
|
|
const rowCount = (result.tables || []).reduce((sum, table) => sum + table.rows, 0)
|
|
|
|
await fetchTenants()
|
|
|
|
toast.add({
|
|
title: "Mandantenimport abgeschlossen",
|
|
description: `Tenant ${result.tenantId}: ${rowCount} Datensätze und ${result.files?.restored || 0} Dateien verarbeitet.`,
|
|
color: "green",
|
|
})
|
|
|
|
if (result.tenantId) {
|
|
await router.push(`/administration/tenants/${result.tenantId}`)
|
|
}
|
|
} catch (err: any) {
|
|
console.error("[administration/tenants/import]", err)
|
|
toast.add({
|
|
title: "Mandant konnte nicht importiert werden",
|
|
description: err?.data?.error || err?.message || "Unbekannter Fehler",
|
|
color: "red",
|
|
})
|
|
} finally {
|
|
importingTenant.value = false
|
|
input.value = ""
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
if (!auth.user?.is_admin) {
|
|
await router.push("/")
|
|
return
|
|
}
|
|
|
|
await fetchTenants()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<UDashboardNavbar title="Administration: Tenants" :badge="filteredRows.length">
|
|
<template #right>
|
|
<UInput
|
|
v-model="searchString"
|
|
icon="i-heroicons-magnifying-glass"
|
|
placeholder="Tenants suchen"
|
|
class="hidden lg:block"
|
|
/>
|
|
<UButton icon="i-heroicons-plus" @click="createTenantModalOpen = true">
|
|
Tenant
|
|
</UButton>
|
|
<input
|
|
ref="importFileInput"
|
|
type="file"
|
|
accept="application/json,.json"
|
|
class="hidden"
|
|
@change="importTenantExport"
|
|
>
|
|
<UButton
|
|
icon="i-heroicons-arrow-up-tray"
|
|
color="warning"
|
|
variant="soft"
|
|
:loading="importingTenant"
|
|
@click="openImportFileDialog"
|
|
>
|
|
Import
|
|
</UButton>
|
|
</template>
|
|
</UDashboardNavbar>
|
|
|
|
<UTable
|
|
:data="filteredRows"
|
|
:columns="normalizeTableColumns(templateColumns)"
|
|
:loading="loading"
|
|
:on-select="(row) => router.push(`/administration/tenants/${row.original?.id || row.id}`)"
|
|
:empty="{ icon: 'i-heroicons-building-office-2', label: 'Keine Tenants gefunden' }"
|
|
/>
|
|
|
|
<UModal v-model:open="createTenantModalOpen">
|
|
<template #content>
|
|
<UCard>
|
|
<template #header>
|
|
<div class="text-lg font-semibold">Tenant anlegen</div>
|
|
</template>
|
|
|
|
<UForm :state="createTenantForm" class="space-y-4" @submit.prevent="createTenant">
|
|
<UFormField label="Name">
|
|
<UInput v-model="createTenantForm.name" />
|
|
</UFormField>
|
|
|
|
<UFormField label="Kürzel">
|
|
<UInput v-model="createTenantForm.short" />
|
|
</UFormField>
|
|
|
|
<UAlert
|
|
title="Seed-Daten"
|
|
description="Beim Anlegen werden Standard-Datei-Tags sowie Systemordner mit Jahresunterordnern erstellt."
|
|
color="primary"
|
|
variant="soft"
|
|
/>
|
|
|
|
<div class="flex justify-end gap-3 pt-2">
|
|
<UButton color="gray" variant="soft" @click="createTenantModalOpen = false">
|
|
Abbrechen
|
|
</UButton>
|
|
<UButton type="submit" color="primary" :loading="creatingTenant">
|
|
Tenant anlegen
|
|
</UButton>
|
|
</div>
|
|
</UForm>
|
|
</UCard>
|
|
</template>
|
|
</UModal>
|
|
</template>
|