Projekttyp-Verwaltung reparieren
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 46s
Build and Push Docker Images / build-frontend (push) Successful in 1m12s
Build and Push Docker Images / build-website (push) Successful in 23s
Build and Push Docker Images / build-central-services-api (push) Successful in 22s
Build and Push Docker Images / build-central-services-admin (push) Successful in 21s
Build and Push Docker Images / build-docs (push) Successful in 21s

This commit is contained in:
2026-09-09 12:56:32 +02:00
parent df2308d407
commit 4e05906556
4 changed files with 373 additions and 265 deletions

View File

@@ -28,6 +28,7 @@ const columnMap: Record<string, any> = {
customerinventoryitems: historyitems.customerinventoryitem, customerinventoryitems: historyitems.customerinventoryitem,
memberrelations: historyitems.memberrelation, memberrelations: historyitems.memberrelation,
outgoingsepamandates: historyitems.outgoingsepamandate, outgoingsepamandates: historyitems.outgoingsepamandate,
projecttypes: historyitems.projecttype,
}; };
const insertFieldMap: Record<string, string> = { const insertFieldMap: Record<string, string> = {
@@ -55,6 +56,7 @@ const insertFieldMap: Record<string, string> = {
customerinventoryitems: "customerinventoryitem", customerinventoryitems: "customerinventoryitem",
memberrelations: "memberrelation", memberrelations: "memberrelation",
outgoingsepamandates: "outgoingsepamandate", outgoingsepamandates: "outgoingsepamandate",
projecttypes: "projecttype",
} }
const parseId = (value: string) => { const parseId = (value: string) => {

View File

@@ -35,6 +35,7 @@ const HISTORY_ENTITY_LABELS: Record<string, string> = {
memberrelations: "Mitgliedsverhältnisse", memberrelations: "Mitgliedsverhältnisse",
teams: "Teams", teams: "Teams",
outgoingsepamandates: "Ausgehende SEPA-Mandate", outgoingsepamandates: "Ausgehende SEPA-Mandate",
projecttypes: "Projekttypen",
} }
export function getHistoryEntityLabel(entity: string) { export function getHistoryEntityLabel(entity: string) {
@@ -96,6 +97,7 @@ export async function insertHistoryItem(
files: "file", files: "file",
memberrelations: "memberrelation", memberrelations: "memberrelation",
outgoingsepamandates: "outgoingsepamandate", outgoingsepamandates: "outgoingsepamandate",
projecttypes: "projecttype",
} }
const fkColumn = columnMap[params.entity] const fkColumn = columnMap[params.entity]

View File

@@ -1,314 +1,392 @@
<script setup> <script setup>
import { v4 as uuidv4 } from 'uuid'
import { v4 as uuidv4 } from 'uuid'; const defaultPhases = [
{ icon: 'i-heroicons-clipboard-document', label: 'Erstkontakt' },
{ icon: 'i-heroicons-wrench-screwdriver', label: 'Umsetzung' },
{ icon: 'i-heroicons-document-text', label: 'Rechnungsstellung' },
{ icon: 'i-heroicons-check', label: 'Abgeschlossen' }
]
const quickActionOptions = [
{ label: '+ Angebot', title: 'Angebot erstellen', link: '/createDocument/edit/?type=quotes' },
{ label: '+ Kostenschätzung', title: 'Kostenschätzung erstellen', link: '/createDocument/edit/?type=costEstimates' },
{ label: '+ Auftrag', title: 'Auftrag erstellen', link: '/createDocument/edit/?type=confirmationOrders' },
{ label: '+ Lieferschein', title: 'Lieferschein erstellen', link: '/createDocument/edit/?type=deliveryNotes' },
{ label: '+ Packschein', title: 'Packschein erstellen', link: '/createDocument/edit/?type=packingSlips' },
{ label: '+ Rechnung', title: 'Rechnung erstellen', link: '/createDocument/edit/?type=invoices' },
{ label: '+ Aufgabe', title: 'Aufgabe erstellen', link: '/tasks?mode=create' },
{ label: '+ Termin', title: 'Termin erstellen', link: '/standardEntity/events/create' }
]
const createPhase = (phase = {}, index = -1) => ({
defineShortcuts({ key: phase.key || uuidv4(),
'backspace': () => { icon: phase.icon || '',
router.push("/projecttypes") label: phase.label || '',
}, optional: Boolean(phase.optional),
'arrowleft': () => { description: phase.description || '',
const currentIndex = Number(openTab.value) quickactions: Array.isArray(phase.quickactions)
if(currentIndex > 0){ ? phase.quickactions.map(action => ({ ...action }))
openTab.value = String(currentIndex - 1) : [],
} active: index === 0
}, })
'arrowright': () => {
const currentIndex = Number(openTab.value) const createEmptyItem = () => ({
if(currentIndex < 3) { name: '',
openTab.value = String(currentIndex + 1) initialPhases: defaultPhases.map(createPhase)
}
},
}) })
const openTab = ref("0")
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const toast = useToast() const toast = useToast()
const entities = useEntities('projecttypes')
const mode = ref(route.params.mode || "show") const mode = computed(() => String(route.params.mode || 'show'))
const itemInfo = ref({ const itemInfo = ref(createEmptyItem())
name: "", const loading = ref(false)
initialPhases: [{ "key": "f31f6fcb-34d5-41a0-9b8f-6c85062f19be", "icon": "i-heroicons-clipboard-document", "label": "Erstkontakt", "active": true, "quickactions": [] }, { "key": "41995d1f-78fa-448b-b6ea-e206645ffb89", "icon": "i-heroicons-wrench-screwdriver", "label": "Umsetzung", "quickactions": [] }, { "key": "267e78ac-9eab-4736-b9c8-4b94c1724494", "icon": "i-heroicons-document-text", "label": "Rechnungsstellung", "quickactions": [] }, { "key": "038df888-53f2-4985-b08e-776ab82df5d3", "icon": "i-heroicons-check", "label": "Abgeschlossen", "quickactions": [] } ] const saving = ref(false)
}) const loadError = ref('')
const oldItemInfo = ref({})
const openQuickActionModal = ref(false) const openQuickActionModal = ref(false)
const selectedKeyForQuickAction = ref("") const selectedPhaseKey = ref(null)
const openArchiveModal = ref(false)
const setKeys = () => { const isEditMode = computed(() => mode.value === 'edit' || mode.value === 'create')
itemInfo.value.initialPhases = itemInfo.value.initialPhases.map(i => { const canSave = computed(() => {
return { return itemInfo.value.name.trim().length > 0
...i, && itemInfo.value.initialPhases.length > 0
key: uuidv4(), && itemInfo.value.initialPhases.every(phase => phase.label.trim().length > 0)
quickactions: i.quickactions || []
}
}) })
itemInfo.value.initialPhases[0].active = true const normalizeItem = (item) => ({
...item,
name: item?.name || '',
initialPhases: Array.isArray(item?.initialPhases)
? item.initialPhases.map(createPhase)
: []
})
const loadItem = async () => {
loadError.value = ''
openQuickActionModal.value = false
openArchiveModal.value = false
if (mode.value === 'create') {
itemInfo.value = createEmptyItem()
return
} }
const setupPage = async() => { if (!route.params.id) {
loadError.value = 'Es wurde kein Projekttyp ausgewählt.'
if(mode.value === "show" ){ return
itemInfo.value = await useEntities("projecttypes").selectSingle(route.params.id,"*")
} else if (mode.value === "edit") {
itemInfo.value = await useEntities("projecttypes").selectSingle(route.params.id,"*")
} }
if(mode.value === "create") { loading.value = true
let query = route.query try {
const item = await entities.selectSingle(route.params.id, '*')
if (!item) {
loadError.value = 'Der Projekttyp wurde nicht gefunden.'
return
}
itemInfo.value = normalizeItem(item)
} catch (error) {
console.error('Projekttyp konnte nicht geladen werden', error)
loadError.value = 'Der Projekttyp konnte nicht geladen werden.'
} finally {
loading.value = false
} }
if(itemInfo.value) oldItemInfo.value = JSON.parse(JSON.stringify(itemInfo.value))
setKeys()
} }
setupPage() watch(
() => [route.params.mode, route.params.id],
loadItem,
{ immediate: true }
)
defineShortcuts({
backspace: () => router.push('/projecttypes')
})
const addPhase = () => { const addPhase = () => {
itemInfo.value.initialPhases.push({label: '', icon: ''}), itemInfo.value.initialPhases.push(createPhase())
setKeys
} }
const removePhase = (phaseKey) => {
itemInfo.value.initialPhases = itemInfo.value.initialPhases
.filter(phase => phase.key !== phaseKey)
.map(createPhase)
}
const openQuickActions = (phaseKey) => {
selectedPhaseKey.value = phaseKey
openQuickActionModal.value = true
}
const addQuickAction = (action) => {
const phase = itemInfo.value.initialPhases.find(item => item.key === selectedPhaseKey.value)
if (!phase) return
if (!phase.quickactions.some(item => item.link === action.link)) {
phase.quickactions.push({ label: action.label, link: action.link })
}
openQuickActionModal.value = false
}
const removeQuickAction = (phase, actionIndex) => {
phase.quickactions.splice(actionIndex, 1)
}
const payload = () => ({
name: itemInfo.value.name.trim(),
initialPhases: itemInfo.value.initialPhases.map((phase, index) => ({
...createPhase(phase, index),
label: phase.label.trim(),
description: phase.description.trim()
}))
})
const save = async () => {
if (!canSave.value || saving.value) return
saving.value = true
try {
if (mode.value === 'create') {
await entities.create(payload())
} else {
await entities.update(itemInfo.value.id, payload())
}
} catch (error) {
console.error('Projekttyp konnte nicht gespeichert werden', error)
toast.add({ title: 'Projekttyp konnte nicht gespeichert werden', color: 'error' })
} finally {
saving.value = false
}
}
const archiveItem = async () => {
if (!itemInfo.value.id || saving.value) return
saving.value = true
try {
await entities.archive(itemInfo.value.id)
toast.add({ title: 'Projekttyp archiviert' })
} catch (error) {
console.error('Projekttyp konnte nicht archiviert werden', error)
toast.add({ title: 'Projekttyp konnte nicht archiviert werden', color: 'error' })
} finally {
saving.value = false
openArchiveModal.value = false
}
}
</script> </script>
<template> <template>
<UDashboardNavbar :title="itemInfo ? itemInfo.name : (mode === 'create' ? 'Projekt erstellen' : 'Projekt bearbeiten')"> <UDashboardNavbar :title="itemInfo.name || (mode === 'create' ? 'Projekttyp erstellen' : 'Projekttyp')">
<template #left> <template #left>
<UButton <UButton icon="i-heroicons-chevron-left" variant="outline" @click="router.push('/projecttypes')">
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push(`/projecttypes`)"
>
Projekttypen Projekttypen
</UButton> </UButton>
</template> </template>
<template #center>
<h1
v-if="itemInfo"
class="text-xl font-medium"
>{{itemInfo.name ? `Projekttyp: ${itemInfo.name}` : (mode === 'create' ? 'Projekttyp erstellen' : 'Projekttyp bearbeiten')}}</h1>
</template>
<template #right> <template #right>
<UButton <UButton v-if="isEditMode" :loading="saving" :disabled="!canSave" @click="save">
v-if="mode === 'edit'" {{ mode === 'create' ? 'Erstellen' : 'Speichern' }}
@click="useEntities('projecttypes').update(itemInfo.id, itemInfo)"
>
Speichern
</UButton> </UButton>
<UButton <UButton
v-else-if="mode === 'create'" v-if="isEditMode"
@click="useEntities('projecttypes').create( itemInfo)" color="neutral"
> variant="outline"
Erstellen @click="router.push(itemInfo.id ? `/projecttypes/show/${itemInfo.id}` : '/projecttypes')"
</UButton>
<UButton
@click="router.push(itemInfo.id ? `/projecttypes/show/${itemInfo.id}` : `/projecttypes`)"
color="red"
class="ml-2"
v-if="mode === 'edit' || mode === 'create'"
> >
Abbrechen Abbrechen
</UButton> </UButton>
<UButton <template v-if="mode === 'show' && itemInfo.id">
v-if="mode === 'show'" <UButton variant="outline" @click="router.push(`/projecttypes/edit/${itemInfo.id}`)">
@click="router.push(`/projecttypes/edit/${itemInfo.id}`)"
>
Bearbeiten Bearbeiten
</UButton> </UButton>
<UButton color="error" variant="outline" @click="openArchiveModal = true">
Archivieren
</UButton>
</template>
</template> </template>
</UDashboardNavbar> </UDashboardNavbar>
<UDashboardPanelContent> <UDashboardPanelContent>
<UTabs <div v-if="loading" class="flex justify-center py-12">
:items="[{label: 'Informationen'}]" <UIcon name="i-heroicons-arrow-path" class="size-7 animate-spin" />
v-if="itemInfo.id && mode == 'show'"
v-model="openTab"
>
<template #content="{ item }">
<div v-if="item.label === 'Informationen'" class="flex flex-row">
<div class="w-1/2 mr-3">
<UCard class="mt-5">
{{itemInfo}}
</UCard>
</div> </div>
<div class="w-1/2">
<UCard class="mt-5"> <UAlert
v-else-if="loadError"
color="error"
variant="soft"
icon="i-heroicons-exclamation-triangle"
title="Fehler beim Laden"
:description="loadError"
/>
<div v-else-if="mode === 'show'" class="grid gap-5 lg:grid-cols-2">
<UCard>
<template #header>
<h2 class="text-lg font-medium">Phasenvorlage</h2>
</template>
<div v-if="itemInfo.initialPhases.length" class="space-y-3">
<div
v-for="(phase, index) in itemInfo.initialPhases"
:key="phase.key"
class="rounded-lg border border-default p-4"
>
<div class="flex items-center gap-3">
<UIcon :name="phase.icon || 'i-heroicons-clipboard-document'" class="size-5" />
<span class="font-medium">{{ phase.label }}</span>
<UBadge v-if="index === 0" variant="soft">Startphase</UBadge>
<UBadge v-if="phase.optional" color="neutral" variant="soft">Optional</UBadge>
</div>
<p v-if="phase.description" class="mt-2 text-sm text-muted">{{ phase.description }}</p>
<div v-if="phase.quickactions.length" class="mt-3 flex flex-wrap gap-2">
<UBadge v-for="action in phase.quickactions" :key="action.link" color="neutral" variant="outline">
{{ action.label }}
</UBadge>
</div>
</div>
</div>
<TableEmptyState v-else label="Keine Phasen hinterlegt" />
</UCard>
<UCard>
<HistoryDisplay <HistoryDisplay
type="project" v-if="itemInfo.id"
v-if="itemInfo" type="projecttypes"
:element-id="itemInfo.id" :element-id="String(itemInfo.id)"
render-headline render-headline
/> />
</UCard> </UCard>
</div> </div>
<div v-else-if="isEditMode" class="space-y-5">
<UAlert
v-if="mode === 'edit'"
color="warning"
variant="outline"
description="Änderungen an diesem Projekttyp gelten nur für neu erstellte Projekte. Bestehende Projekte bleiben unverändert."
/>
<UCard>
<UFormField label="Name" required>
<UInput v-model="itemInfo.name" class="w-full" placeholder="Name des Projekttyps" />
</UFormField>
</UCard>
<UCard>
<template #header>
<div class="flex items-center justify-between gap-3">
<div>
<h2 class="text-lg font-medium">Initiale Phasen</h2>
<p class="text-sm text-muted">Diese Phasen werden in neue Projekte übernommen.</p>
</div>
<UButton icon="i-heroicons-plus" @click="addPhase">Phase hinzufügen</UButton>
</div> </div>
</template> </template>
</UTabs>
<UForm v-else-if="mode === 'edit' || mode === 'create'">
<UAlert
color="error"
variant="outline"
class="mb-5"
v-if="mode === 'edit'"
description="Achtung Änderungen an diesem Projekttypen betreffen nur Projekte die damit neu erstellt werden. Bestehende Projekte bleiben unverändert."
/>
<UFormField
label="Name:"
>
<UInput
v-model="itemInfo.name"
/>
</UFormField>
<USeparator class="mt-5">
Initiale Phasen
</USeparator>
<UButton
class="mt-3"
@click="addPhase"
>
+ Phase
</UButton>
<table class="mt-3">
<thead>
<tr>
<th></th>
<th class="text-left"><span class="ml-2">Name</span></th>
<th class="text-left"><span class="ml-2">Icon</span></th>
<th class="text-left"><span class="ml-2">Optional</span></th>
<th class="text-left"><span class="ml-2">Beschreibung</span></th>
<th class="text-left"><span class="ml-2">Schnellaktionen</span></th>
<th></th>
</tr>
</thead>
<draggable <draggable
v-model="itemInfo.initialPhases" v-model="itemInfo.initialPhases"
handle=".handle" handle=".phase-handle"
tag="tbody" item-key="key"
itemKey="pos" class="space-y-3"
@end="setKeys"
> >
<template #item="{ element: phase, index }">
<div class="rounded-lg border border-default p-4">
<div class="mb-4 flex items-center gap-2">
<UIcon name="i-mdi-menu" class="phase-handle size-5 cursor-grab text-muted" />
<span class="font-medium">Phase {{ index + 1 }}</span>
<UBadge v-if="index === 0" variant="soft">Startphase</UBadge>
<UButton
class="ml-auto"
icon="i-heroicons-trash"
color="error"
variant="ghost"
:disabled="itemInfo.initialPhases.length === 1"
aria-label="Phase entfernen"
@click="removePhase(phase.key)"
/>
</div>
<template #item="{element: phase}"> <div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<tr> <UFormField label="Name" required>
<td> <UInput v-model="phase.label" class="w-full" placeholder="Phasenname" />
<UIcon </UFormField>
class="handle" <UFormField label="Icon">
name="i-mdi-menu" <UInput v-model="phase.icon" class="w-full" placeholder="i-heroicons-…" />
/> </UFormField>
</td> <UFormField label="Beschreibung" class="xl:col-span-2">
<td> <UInput v-model="phase.description" class="w-full" placeholder="Optionale Beschreibung" />
<UInput </UFormField>
class="my-2 ml-2" </div>
v-model="phase.label"
placeholder="Name" <div class="mt-4 flex flex-wrap items-center gap-2">
/> <UCheckbox v-model="phase.optional" label="Optionale Phase" :disabled="index === 0" />
</td> <UButton class="ml-auto" variant="outline" icon="i-heroicons-bolt" @click="openQuickActions(phase.key)">
<td> Schnellaktion hinzufügen
<UInput
class="my-2 ml-2"
v-model="phase.icon"
placeholder="Icon"
/>
</td>
<td>
<UCheckbox
class="my-2 ml-2"
v-model="phase.optional"
/>
</td>
<td>
<UInput
class="my-2 ml-2"
v-model="phase.description"
placeholder="Beschreibung"
/>
</td>
<td>
<UButton
class="my-2 ml-2"
variant="outline"
@click="openQuickActionModal = true,
selectedKeyForQuickAction = phase.key"
>+ Schnellaktion</UButton>
<UButton
@click="phase.quickactions = phase.quickactions.filter(i => i.label !== button.label)"
v-for="button in phase.quickactions"
class="ml-1"
>
{{ button.label }}
</UButton> </UButton>
<UButton
v-for="(action, actionIndex) in phase.quickactions"
:key="`${action.link}-${actionIndex}`"
color="neutral"
variant="soft"
trailing-icon="i-heroicons-x-mark"
@click="removeQuickAction(phase, actionIndex)"
>
{{ action.label }}
</UButton>
</div>
</div>
</template>
</draggable>
<UModal v-model:open="openQuickActionModal"> <p v-if="!canSave" class="mt-4 text-sm text-error">
Name und Phasennamen müssen ausgefüllt sein; mindestens eine Phase ist erforderlich.
</p>
</UCard>
</div>
</UDashboardPanelContent>
<UModal v-model:open="openQuickActionModal" title="Schnellaktion hinzufügen">
<template #content> <template #content>
<UCard> <UCard>
<template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white"> <h3 class="font-semibold">Schnellaktion hinzufügen</h3>
Schnellaktion hinzufügen <UButton icon="i-heroicons-x-mark" color="neutral" variant="ghost" @click="openQuickActionModal = false" />
</h3>
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="openQuickActionModal = false" />
</div> </div>
<div class="flex flex-col"> </template>
<div class="grid gap-2 sm:grid-cols-2">
<UButton <UButton
class="my-1" v-for="action in quickActionOptions"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Angebot',link:'/createDocument/edit/?type=quotes'})">Angebot Erstellen</UButton> :key="action.link"
<UButton variant="outline"
class="my-1" @click="addQuickAction(action)"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Kostenschätzung',link:'/createDocument/edit/?type=costEstimates'})">Kostenschätzung Erstellen</UButton> >
<UButton {{ action.title }}
class="my-1" </UButton>
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Auftrag',link:'/createDocument/edit/?type=confirmationOrders'})">Auftrag Erstellen</UButton>
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Lieferschein',link:'/createDocument/edit/?type=deliveryNotes'})">Lieferschein Erstellen</UButton>
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Packschein',link:'/createDocument/edit/?type=packingSlips'})">Packschein Erstellen</UButton>
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Rechnung',link:'/createDocument/edit/?type=invoices'})">Rechnung Erstellen</UButton>
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Aufgabe',link:'/tasks?mode=create'})">Aufgabe Erstellen</UButton>
<UButton
class="my-1"
@click="itemInfo.initialPhases[itemInfo.initialPhases.findIndex(i=> i.key === selectedKeyForQuickAction)].quickactions.push({label:'+ Termin',link:'/standardEntity/events/create'})">Termin Erstellen</UButton>
</div> </div>
</UCard> </UCard>
</template> </template>
</UModal> </UModal>
</td>
<td> <UModal v-model:open="openArchiveModal" title="Projekttyp archivieren">
<UButton <template #content>
class="my-2 ml-2" <UCard>
variant="outline" <template #header>
color="error" <h3 class="font-semibold">Projekttyp archivieren?</h3>
@click="itemInfo.initialPhases = itemInfo.initialPhases.filter(i => i !== phase)"
>X</UButton>
</td>
</tr>
</template> </template>
</draggable> <p>{{ itemInfo.name }} wird aus der Projekttyp-Auswahl ausgeblendet.</p>
</table> <template #footer>
<div class="flex justify-end gap-2">
</UForm> <UButton color="neutral" variant="outline" @click="openArchiveModal = false">Abbrechen</UButton>
</UDashboardPanelContent> <UButton color="error" :loading="saving" @click="archiveItem">Archivieren</UButton>
</div>
</template>
</UCard>
</template>
</UModal>
</template> </template>
<style scoped>
</style>

View File

@@ -3,17 +3,16 @@
defineShortcuts({ defineShortcuts({
'/': () => { '/': () => {
//console.log(searchinput) document.getElementById("searchinput")?.focus()
//searchinput.value.focus()
document.getElementById("searchinput").focus()
}, },
'+': () => { '+': () => {
router.push("/projects/create") router.push("/projecttypes/create")
}, },
'Enter': { 'Enter': {
usingInput: true, usingInput: true,
handler: () => { handler: () => {
router.push(`/projecttypes/show/${filteredRows.value[selectedItem.value].id}`) const selected = filteredRows.value[selectedItem.value]
if (selected) router.push(`/projecttypes/show/${selected.id}`)
} }
}, },
'arrowdown': () => { 'arrowdown': () => {
@@ -38,10 +37,21 @@ const tempStore = useTempStore()
const items = ref([]) const items = ref([])
const selectedItem = ref(0) const selectedItem = ref(0)
const loading = ref(false)
const loadError = ref('')
const setup = async () => { const setup = async () => {
items.value = await useEntities("projecttypes").select() loading.value = true
loadError.value = ''
try {
items.value = await useEntities("projecttypes").select('*', 'name', true)
} catch (error) {
console.error('Projekttypen konnten nicht geladen werden', error)
loadError.value = 'Die Projekttypen konnten nicht geladen werden.'
} finally {
loading.value = false
}
} }
setup() setup()
@@ -62,6 +72,11 @@ const filteredRows = computed(() => {
return useListFilter(searchString.value, items.value) return useListFilter(searchString.value, items.value)
}) })
const openProjecttype = (row) => {
const item = row?.original || row
if (item?.id) router.push(`/projecttypes/show/${item.id}`)
}
</script> </script>
<template> <template>
@@ -87,11 +102,13 @@ const filteredRows = computed(() => {
</UDashboardNavbar> </UDashboardNavbar>
<UTable <UTable
v-if="!loadError"
:data="filteredRows" :data="filteredRows"
:columns="normalizeTableColumns(columns)" :columns="normalizeTableColumns(columns)"
class="w-full" class="w-full"
:ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }" :ui="{ divide: 'divide-gray-200 dark:divide-gray-800' }"
:on-select="(i) => router.push(`/projecttypes/show/${i.id}`) " :loading="loading"
:on-select="openProjecttype"
> >
<template #name-cell="{row}"> <template #name-cell="{row}">
<span class="text-primary-500 font-bold" v-if="row.original === filteredRows[selectedItem]">{{ row.original.name }}</span> <span class="text-primary-500 font-bold" v-if="row.original === filteredRows[selectedItem]">{{ row.original.name }}</span>
@@ -101,6 +118,15 @@ const filteredRows = computed(() => {
<TableEmptyState label="Keine Projekttypen anzuzeigen" /> <TableEmptyState label="Keine Projekttypen anzuzeigen" />
</template> </template>
</UTable> </UTable>
<UAlert
v-else
class="m-5"
color="error"
variant="soft"
icon="i-heroicons-exclamation-triangle"
title="Fehler beim Laden"
:description="loadError"
/>
</template> </template>
<style scoped> <style scoped>