Zwischenstand
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user