Zwischenstand

This commit is contained in:
2026-03-21 22:13:19 +01:00
parent b009ac845f
commit 68b2cbb0ee
64 changed files with 739 additions and 596 deletions

View File

@@ -179,7 +179,7 @@ onMounted(fetchProfile)
</div>
</div>
<UDivider label="Persönliche Daten" />
<USeparator label="Persönliche Daten" />
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
<UFormGroup label="Vorname">
@@ -211,7 +211,7 @@ onMounted(fetchProfile)
</UForm>
</UCard>
<UCard v-if="!pending && profile" class="mt-3">
<UDivider label="Vertragsinformationen" />
<USeparator label="Vertragsinformationen" />
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
<UFormGroup label="Vertragsart">
@@ -256,7 +256,7 @@ onMounted(fetchProfile)
</UCard>
<UCard v-if="!pending && profile" class="mt-3">
<UDivider label="Adresse & Standort" />
<USeparator label="Adresse & Standort" />
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
<UFormGroup label="Straße und Hausnummer">
@@ -285,7 +285,7 @@ onMounted(fetchProfile)
<UCard v-if="!pending && profile" class="mt-3">
<UDivider label="Wöchentliche Arbeitsstunden" />
<USeparator label="Wöchentliche Arbeitsstunden" />
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
@@ -313,7 +313,7 @@ onMounted(fetchProfile)
</UCard>
<UCard v-if="!pending && profile" class="mt-3">
<UDivider label="Sonstiges" />
<USeparator label="Sonstiges" />
<UForm :state="profile" @submit.prevent="saveProfile" class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4">
<UFormGroup label="Kleidergröße (Oberteil)">
<UInput v-model="profile.clothing_size_top" />

View File

@@ -39,7 +39,7 @@
</template>
</UDashboardNavbar>
<UTable
:rows="items"
:data="items"
:columns="normalizeTableColumns(columns)"
@select="(i) => navigateTo(`/staff/profiles/${i.id}`)"
>

View File

@@ -29,7 +29,16 @@ const platformIsNative = ref(false)
const selectedPresetRange = ref("Dieser Monat bis heute")
const selectedStartDay = ref("")
const selectedEndDay = ref("")
const openTab = ref(0)
const openTab = ref("0")
const presetRangeItems = [
'Dieser Monat bis heute',
'Diese Woche',
'Dieser Monat',
'Dieses Jahr',
'Letzte Woche',
'Letzter Monat',
'Letztes Jahr'
]
const showDocument = ref(false)
const uri = ref("")
@@ -131,7 +140,7 @@ async function loadWorkingTimeInfo() {
workingTimeInfo.value = data;
openTab.value = 0
openTab.value = "0"
}
// 📄 PDF generieren
@@ -172,12 +181,12 @@ async function saveFile() {
toast.add({title:"Auswertung erfolgreich gespeichert"})
fileSaved.value = true
} catch (error) {
toast.add({title:"Fehler beim Speichern der Auswertung", color: "rose"})
toast.add({title:"Fehler beim Speichern der Auswertung", color: "error"})
}
}
async function onTabChange(index: number) {
if (index === 1) await generateDocument()
async function onTabChange(index: string | number) {
if (String(index) === "1") await generateDocument()
}
// Initialisierung
@@ -204,52 +213,44 @@ await setupPage()
</template>
</UDashboardNavbar>
<UDashboardToolbar>
<UDashboardToolbar class="py-3">
<template #left>
<UFormGroup label="Zeitraum:">
<UFormField label="Zeitraum:">
<USelectMenu
:options="[
'Dieser Monat bis heute',
'Diese Woche',
'Dieser Monat',
'Dieses Jahr',
'Letzte Woche',
'Letzter Monat',
'Letztes Jahr'
]"
:items="presetRangeItems"
v-model="selectedPresetRange"
@change="changeRange"
/>
</UFormGroup>
</UFormField>
<UFormGroup label="Start:">
<UPopover :popper="{ placement: 'bottom-start' }">
<UFormField label="Start:">
<UPopover :content="{ side: 'bottom', align: 'start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="selectedStartDay ? $dayjs(selectedStartDay).format('DD.MM.YYYY') : 'Datum wählen'"
/>
<template #panel="{ close }">
<template #content>
<LazyDatePicker v-model="selectedStartDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
</UFormGroup>
</UFormField>
<UFormGroup label="Ende:">
<UPopover :popper="{ placement: 'bottom-start' }">
<UFormField label="Ende:">
<UPopover :content="{ side: 'bottom', align: 'start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="selectedEndDay ? $dayjs(selectedEndDay).format('DD.MM.YYYY') : 'Datum wählen'"
/>
<template #panel="{ close }">
<template #content>
<LazyDatePicker v-model="selectedEndDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
</UFormGroup>
</UFormField>
</template>
<template #right>
<UTooltip
:text="fileSaved ? 'Bericht bereits gespeichert' : 'Bericht speichern'"
v-if="openTab === 1 && uri"
v-if="openTab === '1' && uri"
>
<UButton
icon="i-mdi-content-save"
@@ -265,9 +266,9 @@ await setupPage()
<UTabs
:items="[{ label: 'Information' }, { label: 'Bericht' }]"
v-model="openTab"
@change="onTabChange"
@update:model-value="onTabChange"
>
<template #item="{ item }">
<template #content="{ item }">
<div v-if="item.label === 'Information'">
<UCard v-if="workingTimeInfo && workingTimeInfo.summary" class="my-5">
@@ -294,8 +295,8 @@ await setupPage()
<UDashboardPanel>
<UTable
v-if="workingTimeInfo"
:rows="workingTimeInfo.spans"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Anwesenheiten' }"
:data="workingTimeInfo.spans"
:empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Anwesenheiten' }"
:columns="normalizeTableColumns([
{ key: 'status', label: 'Status' },
{ key: 'startedAt', label: 'Start' },
@@ -303,30 +304,30 @@ await setupPage()
{ key: 'duration', label: 'Dauer' },
{ key: 'type', label: 'Typ' }
])"
@select="(row) => router.push(`/workingtimes/edit/${row.sourceEventIds[0]}`)"
:on-select="(row) => router.push(`/workingtimes/edit/${row.original.sourceEventIds[0]}`)"
>
<template #status-data="{row}">
<span v-if="row.status === 'approved'" class="text-primary-500">Genehmigt</span>
<span v-else-if="row.status === 'submitted'" class="text-cyan-500">Eingereicht</span>
<span v-else-if="row.status === 'factual'" class="text-gray-500">Faktisch</span>
<span v-else-if="row.status === 'draft'" class="text-red-500">Entwurf</span>
<span v-else>{{ row.status }}</span>
<template #status-cell="{ row }">
<span v-if="row.original.status === 'approved'" class="text-primary-500">Genehmigt</span>
<span v-else-if="row.original.status === 'submitted'" class="text-cyan-500">Eingereicht</span>
<span v-else-if="row.original.status === 'factual'" class="text-gray-500">Faktisch</span>
<span v-else-if="row.original.status === 'draft'" class="text-error-500">Entwurf</span>
<span v-else>{{ row.original.status }}</span>
</template>
<template #startedAt-data="{ row }">
{{ $dayjs(row.startedAt).format('HH:mm DD.MM.YY') }} Uhr
<template #startedAt-cell="{ row }">
{{ $dayjs(row.original.startedAt).format('HH:mm DD.MM.YY') }} Uhr
</template>
<template #endedAt-data="{ row }">
{{ $dayjs(row.endedAt).format('HH:mm DD.MM.YY') }} Uhr
<template #endedAt-cell="{ row }">
{{ $dayjs(row.original.endedAt).format('HH:mm DD.MM.YY') }} Uhr
</template>
<template #duration-data="{ row }">
{{ formatSpanDuration(row.startedAt, row.endedAt) }}
<template #duration-cell="{ row }">
{{ formatSpanDuration(row.original.startedAt, row.original.endedAt) }}
</template>
<template #type-data="{ row }">
{{ row.type.charAt(0).toUpperCase() + row.type.slice(1).replace('_', ' ') }}
<template #type-cell="{ row }">
{{ row.original.type.charAt(0).toUpperCase() + row.original.type.slice(1).replace('_', ' ') }}
</template>
</UTable>
</UDashboardPanel>
@@ -360,15 +361,7 @@ await setupPage()
<div class="p-4 space-y-4 border-b bg-white dark:bg-gray-900">
<USelectMenu
v-model="selectedPresetRange"
:options="[
'Dieser Monat bis heute',
'Diese Woche',
'Dieser Monat',
'Dieses Jahr',
'Letzte Woche',
'Letzter Monat',
'Letztes Jahr'
]"
:items="presetRangeItems"
@change="changeRange"
placeholder="Zeitraum wählen"
class="w-full"
@@ -377,13 +370,13 @@ await setupPage()
<div class="grid grid-cols-2 gap-3">
<div>
<p class="text-xs text-gray-500 mb-1">Start</p>
<UPopover :popper="{ placement: 'bottom-start' }">
<UPopover :content="{ side: 'bottom', align: 'start' }">
<UButton
icon="i-heroicons-calendar"
class="w-full"
:label="$dayjs(selectedStartDay).format('DD.MM.YYYY')"
/>
<template #panel>
<template #content>
<LazyDatePicker v-model="selectedStartDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
@@ -391,13 +384,13 @@ await setupPage()
<div>
<p class="text-xs text-gray-500 mb-1">Ende</p>
<UPopover :popper="{ placement: 'bottom-start' }">
<UPopover :content="{ side: 'bottom', align: 'start' }">
<UButton
icon="i-heroicons-calendar"
class="w-full"
:label="$dayjs(selectedEndDay).format('DD.MM.YYYY')"
/>
<template #panel>
<template #content>
<LazyDatePicker v-model="selectedEndDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
@@ -408,12 +401,11 @@ await setupPage()
<UTabs
:items="[{ label: 'Information' }, { label: 'Bericht' }]"
v-model="openTab"
@change="onTabChange"
@update:model-value="onTabChange"
class="mt-3 mx-3"
>
<template #item="{ item }">
<div v-if="item.label === 'Information'" class="space-y-4">
<template #content="{ item }">
<div v-if="item.label === 'Information'" class="space-y-4">
<UCard v-if="workingTimeInfo && workingTimeInfo.summary" class="mt-3">
<template #header>
@@ -459,7 +451,7 @@ await setupPage()
<div v-else-if="item.label === 'Bericht'">
<UButton
v-if="uri && !fileSaved"
v-if="openTab === '1' && uri && !fileSaved"
icon="i-mdi-content-save"
color="primary"
class="w-full mb-3"

View File

@@ -32,6 +32,10 @@ const rejectReason = ref("")
const users = ref([])
const selectedUser = ref(auth.user.id)
const canViewAll = computed(() => auth.permissions.includes('staff.time.read_all'))
const userItems = computed(() => users.value.map(u => ({
label: u.full_name || u.email,
value: u.user_id
})))
// DATA
const entries = ref([])
@@ -61,7 +65,7 @@ const typeLabel = {
const typeColor = {
work: "gray",
vacation: "yellow",
sick: "rose",
sick: "error",
holiday: "blue",
other: "gray"
}
@@ -130,7 +134,7 @@ async function confirmReject() {
showRejectModal.value = false
await load()
} catch (e) {
toast.add({ title: 'Fehler beim Ablehnen', description: e.message, color: 'red' })
toast.add({ title: 'Fehler beim Ablehnen', description: e.message, color: 'error' })
} finally {
loading.value = false
entryToReject.value = null
@@ -167,10 +171,10 @@ onMounted(async () => {
<div v-if="canViewAll" class="flex items-center gap-2">
<USelectMenu
v-model="selectedUser"
:options="users.map(u => ({ label: u.full_name || u.email, value: u.user_id }))"
:items="userItems"
placeholder="Benutzer auswählen"
value-attribute="value"
option-attribute="label"
value-key="value"
label-key="label"
class="min-w-[220px]"
:clearable="false"
/>
@@ -211,11 +215,11 @@ onMounted(async () => {
</div>
<template v-if="isViewingSelf">
<UButton v-if="active" color="red" icon="i-heroicons-stop" :loading="loading" label="Stoppen" @click="handleStop" />
<UButton v-if="active" color="error" icon="i-heroicons-stop" :loading="loading" label="Stoppen" @click="handleStop" />
<UButton v-else color="green" icon="i-heroicons-play" :loading="loading" label="Starten" @click="handleStart" />
</template>
<template v-else-if="active && canViewAll">
<UButton color="red" variant="soft" icon="i-heroicons-stop" :loading="loading" label="Mitarbeiter stoppen" @click="handleStop" />
<UButton color="error" variant="soft" icon="i-heroicons-stop" :loading="loading" label="Mitarbeiter stoppen" @click="handleStop" />
</template>
<UButton color="gray" variant="solid" icon="i-heroicons-plus" label="Erfassen" @click="() => { entryToEdit = null; showEditModal = true }" />
@@ -227,7 +231,7 @@ onMounted(async () => {
<UCard v-if="view === 'list'" :ui="{ body: { padding: 'p-0 sm:p-0' } }">
<UTable
:rows="entries"
:data="entries"
:columns="normalizeTableColumns([
{ key: 'actions', label: 'Aktionen', class: 'w-32' },
{ key: 'state', label: 'Status' },
@@ -237,49 +241,49 @@ onMounted(async () => {
{ key: 'type', label: 'Typ' },
{ key: 'description', label: 'Beschreibung' },
])"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Zeiten anzuzeigen' }"
:empty="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Zeiten anzuzeigen' }"
>
<template #state-data="{ row }">
<UBadge v-if="row.state === 'approved'" color="green" variant="subtle">Genehmigt</UBadge>
<UBadge v-else-if="row.state === 'submitted'" color="cyan" variant="subtle">Eingereicht</UBadge>
<UBadge v-else-if="row.state === 'rejected'" color="red" variant="subtle">Abgelehnt</UBadge>
<template #state-cell="{ row }">
<UBadge v-if="row.original.state === 'approved'" color="green" variant="subtle">Genehmigt</UBadge>
<UBadge v-else-if="row.original.state === 'submitted'" color="cyan" variant="subtle">Eingereicht</UBadge>
<UBadge v-else-if="row.original.state === 'rejected'" color="error" variant="subtle">Abgelehnt</UBadge>
<UBadge v-else color="gray" variant="subtle">Entwurf</UBadge>
</template>
<template #type-data="{ row }">
<UBadge :color="typeColor[row.type] || 'gray'" variant="soft">{{ typeLabel[row.type] || row.type }}</UBadge>
<template #type-cell="{ row }">
<UBadge :color="typeColor[row.original.type] || 'gray'" variant="soft">{{ typeLabel[row.original.type] || row.original.type }}</UBadge>
</template>
<template #started_at-data="{ row }">
<span v-if="['vacation','sick'].includes(row.type)">{{ useNuxtApp().$dayjs(row.started_at).format("DD.MM.YY") }}</span>
<span v-else>{{ useNuxtApp().$dayjs(row.started_at).format("DD.MM.YY HH:mm") }}</span>
<template #started_at-cell="{ row }">
<span v-if="['vacation','sick'].includes(row.original.type)">{{ useNuxtApp().$dayjs(row.original.started_at).format("DD.MM.YY") }}</span>
<span v-else>{{ useNuxtApp().$dayjs(row.original.started_at).format("DD.MM.YY HH:mm") }}</span>
</template>
<template #stopped_at-data="{ row }">
<span v-if="!row.stopped_at" class="text-primary-500 font-medium animate-pulse">läuft...</span>
<span v-else-if="['vacation','sick'].includes(row.type)">{{ useNuxtApp().$dayjs(row.stopped_at).format("DD.MM.YY") }}</span>
<span v-else>{{ useNuxtApp().$dayjs(row.stopped_at).format("DD.MM.YY HH:mm") }}</span>
<template #stopped_at-cell="{ row }">
<span v-if="!row.original.stopped_at" class="text-primary-500 font-medium animate-pulse">läuft...</span>
<span v-else-if="['vacation','sick'].includes(row.original.type)">{{ useNuxtApp().$dayjs(row.original.stopped_at).format("DD.MM.YY") }}</span>
<span v-else>{{ useNuxtApp().$dayjs(row.original.stopped_at).format("DD.MM.YY HH:mm") }}</span>
</template>
<template #duration_minutes-data="{ row }">
{{ row.duration_minutes ? useFormatDuration(row.duration_minutes) : "-" }}
<template #duration_minutes-cell="{ row }">
{{ row.original.duration_minutes ? useFormatDuration(row.original.duration_minutes) : "-" }}
</template>
<template #actions-data="{ row }">
<template #actions-cell="{ row }">
<div class="flex items-center gap-1">
<UTooltip text="Einreichen" v-if="(row.state === 'draft' || row.state === 'factual') && row.stopped_at">
<UButton size="xs" color="cyan" variant="ghost" icon="i-heroicons-paper-airplane" @click="handleSubmit(row)" :loading="loading" />
<UTooltip text="Einreichen" v-if="(row.original.state === 'draft' || row.original.state === 'factual') && row.original.stopped_at">
<UButton size="xs" color="cyan" variant="ghost" icon="i-heroicons-paper-airplane" @click="handleSubmit(row.original)" :loading="loading" />
</UTooltip>
<UTooltip text="Genehmigen" v-if="row.state === 'submitted' && canViewAll">
<UButton size="xs" color="green" variant="ghost" icon="i-heroicons-check" @click="handleApprove(row)" :loading="loading" />
<UTooltip text="Genehmigen" v-if="row.original.state === 'submitted' && canViewAll">
<UButton size="xs" color="green" variant="ghost" icon="i-heroicons-check" @click="handleApprove(row.original)" :loading="loading" />
</UTooltip>
<UTooltip text="Ablehnen" v-if="(row.state === 'submitted' || row.state === 'approved') && canViewAll">
<UButton size="xs" color="red" variant="ghost" icon="i-heroicons-x-mark" @click="openRejectModal(row)" :loading="loading" />
<UTooltip text="Ablehnen" v-if="(row.original.state === 'submitted' || row.original.state === 'approved') && canViewAll">
<UButton size="xs" color="error" variant="ghost" icon="i-heroicons-x-mark" @click="openRejectModal(row.original)" :loading="loading" />
</UTooltip>
<UTooltip text="Bearbeiten" v-if="['draft', 'factual', 'submitted'].includes(row.state)">
<UButton size="xs" color="gray" variant="ghost" icon="i-heroicons-pencil-square" @click="handleEdit(row)" />
<UTooltip text="Bearbeiten" v-if="['draft', 'factual', 'submitted'].includes(row.original.state)">
<UButton size="xs" color="gray" variant="ghost" icon="i-heroicons-pencil-square" @click="handleEdit(row.original)" />
</UTooltip>
</div>
</template>
<template #description-data="{ row }">
<span v-if="row.type === 'vacation'">{{row.vacation_reason}}</span>
<span v-else-if="row.type === 'sick'">{{row.sick_reason}}</span>
<span v-else>{{row.description}}</span>
<template #description-cell="{ row }">
<span v-if="row.original.type === 'vacation'">{{ row.original.vacation_reason }}</span>
<span v-else-if="row.original.type === 'sick'">{{ row.original.sick_reason }}</span>
<span v-else>{{ row.original.description }}</span>
</template>
</UTable>
</UCard>
@@ -334,7 +338,7 @@ onMounted(async () => {
<UBadge v-if="entry.state === 'approved'" color="green" size="xs" variant="solid">Genehmigt</UBadge>
<UBadge v-else-if="entry.state === 'submitted'" color="cyan" size="xs" variant="solid">Eingereicht</UBadge>
<UBadge v-else-if="entry.state === 'rejected'" color="red" size="xs" variant="solid">Abgelehnt</UBadge>
<UBadge v-else-if="entry.state === 'rejected'" color="error" size="xs" variant="solid">Abgelehnt</UBadge>
<UBadge v-else color="gray" size="xs" variant="subtle">Entwurf</UBadge>
</div>
</div>
@@ -363,7 +367,7 @@ onMounted(async () => {
<UButton
v-if="(entry.state === 'submitted' || entry.state === 'approved') && canViewAll"
size="xs" color="red" variant="ghost" icon="i-heroicons-x-mark" label="Ablehnen"
size="xs" color="error" variant="ghost" icon="i-heroicons-x-mark" label="Ablehnen"
@click="openRejectModal(entry)" :loading="loading"
/>
@@ -403,7 +407,7 @@ onMounted(async () => {
</div>
<div class="flex gap-2">
<template v-if="isViewingSelf">
<UButton v-if="active" color="red" icon="i-heroicons-stop" :loading="loading" @click="handleStop" />
<UButton v-if="active" color="error" icon="i-heroicons-stop" :loading="loading" @click="handleStop" />
<UButton v-else color="green" icon="i-heroicons-play" :loading="loading" @click="handleStart" />
</template>
</div>
@@ -422,7 +426,7 @@ onMounted(async () => {
</div>
<UBadge v-if="row.state === 'approved'" color="green">Genehmigt</UBadge>
<UBadge v-else-if="row.state === 'submitted'" color="cyan">Eingereicht</UBadge>
<UBadge v-else-if="row.state === 'rejected'" color="red">Abgelehnt</UBadge>
<UBadge v-else-if="row.state === 'rejected'" color="error">Abgelehnt</UBadge>
<UBadge v-else color="gray">Entwurf</UBadge>
</div>
<p class="text-sm text-gray-500 mt-1">Start: {{ useNuxtApp().$dayjs(row.started_at).format('DD.MM.YY HH:mm') }}</p>
@@ -445,29 +449,31 @@ onMounted(async () => {
/>
<UModal v-model:open="showRejectModal">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
Zeiteintrag ablehnen
</h3>
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="showRejectModal = false" />
<template #content>
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
Zeiteintrag ablehnen
</h3>
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="showRejectModal = false" />
</div>
</template>
<div class="space-y-4">
<p class="text-sm text-gray-500">
Der Eintrag wird als "Rejected" markiert und nicht mehr zur Arbeitszeit gezählt.
</p>
<UFormField label="Grund (optional)" name="reason">
<UTextarea v-model="rejectReason" placeholder="Falsche Buchung, Doppelt, etc." autofocus />
</UFormField>
</div>
</template>
<div class="space-y-4">
<p class="text-sm text-gray-500">
Der Eintrag wird als "Rejected" markiert und nicht mehr zur Arbeitszeit gezählt.
</p>
<UFormGroup label="Grund (optional)" name="reason">
<UTextarea v-model="rejectReason" placeholder="Falsche Buchung, Doppelt, etc." autofocus />
</UFormGroup>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<UButton color="gray" variant="soft" @click="showRejectModal = false">Abbrechen</UButton>
<UButton color="red" :loading="loading" @click="confirmReject">Bestätigen</UButton>
</div>
</template>
</UCard>
<template #footer>
<div class="flex justify-end gap-2">
<UButton color="gray" variant="soft" @click="showRejectModal = false">Abbrechen</UButton>
<UButton color="error" :loading="loading" @click="confirmReject">Bestätigen</UButton>
</div>
</template>
</UCard>
</template>
</UModal>
</template>