Remodel for Mobile

This commit is contained in:
2025-11-20 12:38:38 +01:00
parent 5f6df7c69d
commit 24f576aeaa
13 changed files with 1226 additions and 595 deletions

View File

@@ -9,6 +9,8 @@ const workingtimes = ref([])
const absencerequests = ref([])
const workingTimeInfo = ref(null)
const platformIsNative = ref(useCapacitor().getIsNative())
const selectedPresetRange = ref("Dieser Monat bis heute")
const selectedStartDay = ref("")
const selectedEndDay = ref("")
@@ -63,6 +65,7 @@ async function setupPage() {
profile.value = (await useNuxtApp().$api("/api/tenant/profiles")).data.find(i => i.user_id === route.params.id)
console.log(profile.value)
setPageLayout(platformIsNative.value ? 'mobile' : 'default')
}
@@ -119,29 +122,30 @@ changeRange()
</script>
<template>
<UDashboardNavbar :ui="{ center: 'flex items-stretch gap-1.5 min-w-0' }">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push('/staff/time')"
>
Anwesenheiten
</UButton>
</template>
<template v-if="!platformIsNative">
<UDashboardNavbar :ui="{ center: 'flex items-stretch gap-1.5 min-w-0' }">
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="outline"
@click="router.push('/staff/time')"
>
Anwesenheiten
</UButton>
</template>
<template #center>
<h1 class="text-xl font-medium truncate">
Auswertung Anwesenheiten: {{ profile?.full_name || '' }}
</h1>
</template>
</UDashboardNavbar>
<template #center>
<h1 class="text-xl font-medium truncate">
Auswertung Anwesenheiten: {{ profile?.full_name || '' }}
</h1>
</template>
</UDashboardNavbar>
<UDashboardToolbar>
<template #left>
<UFormGroup label="Zeitraum:">
<USelectMenu
:options="[
<UDashboardToolbar>
<template #left>
<UFormGroup label="Zeitraum:">
<USelectMenu
:options="[
'Dieser Monat bis heute',
'Diese Woche',
'Dieser Monat',
@@ -150,114 +154,265 @@ changeRange()
'Letzter Monat',
'Letztes Jahr'
]"
v-model="selectedPresetRange"
@change="changeRange"
/>
</UFormGroup>
<UFormGroup label="Start:">
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="selectedStartDay ? $dayjs(selectedStartDay).format('DD.MM.YYYY') : 'Datum wählen'"
v-model="selectedPresetRange"
@change="changeRange"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="selectedStartDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
</UFormGroup>
</UFormGroup>
<UFormGroup label="Ende:">
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="selectedEndDay ? $dayjs(selectedEndDay).format('DD.MM.YYYY') : 'Datum wählen'"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="selectedEndDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
</UFormGroup>
</template>
<template #right>
<UTooltip
:text="fileSaved ? 'Bericht bereits gespeichert' : 'Bericht speichern'"
v-if="openTab === 1 && uri"
>
<UButton
icon="i-mdi-content-save"
:disabled="fileSaved"
@click="saveFile"
>Bericht</UButton>
</UTooltip>
</template>
</UDashboardToolbar>
<UDashboardPanelContent>
<UTabs
:items="[{ label: 'Information' }, { label: 'Bericht' }]"
v-model="openTab"
@change="onTabChange"
>
<template #item="{ item }">
<div v-if="item.label === 'Information'">
<UCard v-if="workingTimeInfo" class="my-5">
<template #header>
<h3 class="text-base font-semibold">Zusammenfassung</h3>
<UFormGroup label="Start:">
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="selectedStartDay ? $dayjs(selectedStartDay).format('DD.MM.YYYY') : 'Datum wählen'"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="selectedStartDay" @close="loadWorkingTimeInfo" />
</template>
<div class="grid grid-cols-2 gap-3 text-sm">
<p>Eingereicht: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesEingereicht) }}</b></p>
<p>Genehmigt: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesApproved) }}</b></p>
<p>Feiertagsausgleich: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesRecreationDays) }}</b> / {{ workingTimeInfo.sumRecreationDays }} Tage</p>
<p>Urlaubs-/Berufsschulausgleich: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesVacationDays) }}</b> / {{ workingTimeInfo.sumVacationDays }} Tage</p>
<p>Krankheitsausgleich: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesSickDays) }}</b> / {{ workingTimeInfo.sumSickDays }} Tage</p>
<p>Soll-Stunden: <b>{{ formatMinutesToHHMM(workingTimeInfo.timeSpanWorkingMinutes) }}</b></p>
<p class="col-span-2">
Inoffizielles Saldo: <b>{{ (workingTimeInfo.saldoInOfficial >= 0 ? '+' : '-') + formatMinutesToHHMM(Math.abs(workingTimeInfo.saldoInOfficial)) }}</b>
</p>
<p class="col-span-2">
Saldo: <b>{{ (workingTimeInfo.saldo >= 0 ? '+' : '-') + formatMinutesToHHMM(Math.abs(workingTimeInfo.saldo)) }}</b>
</p>
</div>
</UCard>
</UPopover>
</UFormGroup>
<UDashboardPanel>
<UTable
v-if="workingTimeInfo"
:rows="workingTimeInfo.times"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Anwesenheiten' }"
:columns="[
<UFormGroup label="Ende:">
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="selectedEndDay ? $dayjs(selectedEndDay).format('DD.MM.YYYY') : 'Datum wählen'"
/>
<template #panel="{ close }">
<LazyDatePicker v-model="selectedEndDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
</UFormGroup>
</template>
<template #right>
<UTooltip
:text="fileSaved ? 'Bericht bereits gespeichert' : 'Bericht speichern'"
v-if="openTab === 1 && uri"
>
<UButton
icon="i-mdi-content-save"
:disabled="fileSaved"
@click="saveFile"
>Bericht</UButton>
</UTooltip>
</template>
</UDashboardToolbar>
<UDashboardPanelContent>
<UTabs
:items="[{ label: 'Information' }, { label: 'Bericht' }]"
v-model="openTab"
@change="onTabChange"
>
<template #item="{ item }">
<div v-if="item.label === 'Information'">
<UCard v-if="workingTimeInfo" class="my-5">
<template #header>
<h3 class="text-base font-semibold">Zusammenfassung</h3>
</template>
<div class="grid grid-cols-2 gap-3 text-sm">
<p>Eingereicht: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesEingereicht) }}</b></p>
<p>Genehmigt: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesApproved) }}</b></p>
<p>Feiertagsausgleich: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesRecreationDays) }}</b> / {{ workingTimeInfo.sumRecreationDays }} Tage</p>
<p>Urlaubs-/Berufsschulausgleich: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesVacationDays) }}</b> / {{ workingTimeInfo.sumVacationDays }} Tage</p>
<p>Krankheitsausgleich: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesSickDays) }}</b> / {{ workingTimeInfo.sumSickDays }} Tage</p>
<p>Soll-Stunden: <b>{{ formatMinutesToHHMM(workingTimeInfo.timeSpanWorkingMinutes) }}</b></p>
<p class="col-span-2">
Inoffizielles Saldo: <b>{{ (workingTimeInfo.saldoInOfficial >= 0 ? '+' : '-') + formatMinutesToHHMM(Math.abs(workingTimeInfo.saldoInOfficial)) }}</b>
</p>
<p class="col-span-2">
Saldo: <b>{{ (workingTimeInfo.saldo >= 0 ? '+' : '-') + formatMinutesToHHMM(Math.abs(workingTimeInfo.saldo)) }}</b>
</p>
</div>
</UCard>
<UDashboardPanel>
<UTable
v-if="workingTimeInfo"
:rows="workingTimeInfo.times"
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Keine Anwesenheiten' }"
:columns="[
{ key: 'state', label: 'Status' },
{ key: 'start', label: 'Start' },
{ key: 'end', label: 'Ende' },
{ key: 'duration', label: 'Dauer' },
{ key: 'description', label: 'Beschreibung' }
]"
@select="(row) => router.push(`/workingtimes/edit/${row.id}`)"
>
<template #state-data="{row}">
<span v-if="row.state === 'approved'" class="text-primary-500">Genehmigt</span>
<span v-else-if="row.state === 'submitted'" class="text-cyan-500">Eingereicht</span>
<span v-else-if="row.state === 'draft'" class="text-red-500">Entwurf</span>
</template>
@select="(row) => router.push(`/workingtimes/edit/${row.id}`)"
>
<template #state-data="{row}">
<span v-if="row.state === 'approved'" class="text-primary-500">Genehmigt</span>
<span v-else-if="row.state === 'submitted'" class="text-cyan-500">Eingereicht</span>
<span v-else-if="row.state === 'draft'" class="text-red-500">Entwurf</span>
</template>
<template #start-data="{ row }">
{{ $dayjs(row.started_at).format('HH:mm DD.MM.YY') }} Uhr
</template>
<template #start-data="{ row }">
{{ $dayjs(row.started_at).format('HH:mm DD.MM.YY') }} Uhr
</template>
<template #end-data="{ row }">
{{ $dayjs(row.stopped_at).format('HH:mm DD.MM.YY') }} Uhr
</template>
<template #end-data="{ row }">
{{ $dayjs(row.stopped_at).format('HH:mm DD.MM.YY') }} Uhr
</template>
<template #duration-data="{ row }">
{{ useFormatDuration(row.duration_minutes) }}
</template>
</UTable>
</UDashboardPanel>
<template #duration-data="{ row }">
{{ useFormatDuration(row.duration_minutes) }}
</template>
</UTable>
</UDashboardPanel>
</div>
<div v-else-if="item.label === 'Bericht'">
<PDFViewer
v-if="showDocument"
:uri="uri"
location="show_time_evaluation"
/>
</div>
</template>
</UTabs>
</UDashboardPanelContent>
</template>
<!-- ====================== -->
<!-- 📱 MOBILE ANSICHT -->
<!-- ====================== -->
<template v-else>
<!-- 🔙 Navigation -->
<UDashboardNavbar title="Auswertung">
<template #toggle><div></div></template>
<template #left>
<UButton
icon="i-heroicons-chevron-left"
variant="ghost"
@click="router.push('/staff/time')"
/>
</template>
</UDashboardNavbar>
<!-- 📌 Mobile Zeitraumwahl -->
<div class="p-4 space-y-4 border-b bg-white dark:bg-gray-900">
<!-- Predefined Ranges -->
<USelectMenu
v-model="selectedPresetRange"
:options="[
'Dieser Monat bis heute',
'Diese Woche',
'Dieser Monat',
'Dieses Jahr',
'Letzte Woche',
'Letzter Monat',
'Letztes Jahr'
]"
@change="changeRange"
placeholder="Zeitraum wählen"
class="w-full"
/>
<!-- Start/End Datum -->
<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' }">
<UButton
icon="i-heroicons-calendar"
class="w-full"
:label="$dayjs(selectedStartDay).format('DD.MM.YYYY')"
/>
<template #panel>
<LazyDatePicker v-model="selectedStartDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
</div>
<div>
<p class="text-xs text-gray-500 mb-1">Ende</p>
<UPopover :popper="{ placement: 'bottom-start' }">
<UButton
icon="i-heroicons-calendar"
class="w-full"
:label="$dayjs(selectedEndDay).format('DD.MM.YYYY')"
/>
<template #panel>
<LazyDatePicker v-model="selectedEndDay" @close="loadWorkingTimeInfo" />
</template>
</UPopover>
</div>
</div>
</div>
<!-- 📑 Mobile Tabs -->
<UTabs
:items="[{ label: 'Information' }, { label: 'Bericht' }]"
v-model="openTab"
@change="onTabChange"
class="mt-3 mx-3"
>
<template #item="{ item }">
<!-- ====================== -->
<!-- TAB 1 INFORMATION -->
<!-- ====================== -->
<div v-if="item.label === 'Information'" class="space-y-4">
<!-- Summary Card -->
<UCard v-if="workingTimeInfo" class="mt-3">
<template #header>
<h3 class="text-base font-semibold">Zusammenfassung</h3>
</template>
<div class="space-y-2 text-sm">
<p>Eingereicht: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesEingereicht) }}</b></p>
<p>Genehmigt: <b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesApproved) }}</b></p>
<p>
Feiertagsausgleich:
<b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesRecreationDays) }}</b>
/ {{ workingTimeInfo.sumRecreationDays }} Tage
</p>
<p>
Urlaubs-/Berufsschule:
<b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesVacationDays) }}</b>
/ {{ workingTimeInfo.sumVacationDays }} Tage
</p>
<p>
Krankheitsausgleich:
<b>{{ formatMinutesToHHMM(workingTimeInfo.sumWorkingMinutesSickDays) }}</b>
/ {{ workingTimeInfo.sumSickDays }} Tage
</p>
<p>Soll: <b>{{ formatMinutesToHHMM(workingTimeInfo.timeSpanWorkingMinutes) }}</b></p>
<p>
Inoffizielles Saldo:
<b>{{ (workingTimeInfo.saldoInOfficial >= 0 ? '+' : '-') + formatMinutesToHHMM(Math.abs(workingTimeInfo.saldoInOfficial)) }}</b>
</p>
<p>
Saldo:
<b>{{ (workingTimeInfo.saldo >= 0 ? '+' : '-') + formatMinutesToHHMM(Math.abs(workingTimeInfo.saldo)) }}</b>
</p>
</div>
</UCard>
</div>
<!-- ====================== -->
<!-- TAB 2 BERICHT -->
<!-- ====================== -->
<div v-else-if="item.label === 'Bericht'">
<UButton
v-if="uri && !fileSaved"
icon="i-mdi-content-save"
color="primary"
class="w-full mb-3"
@click="saveFile"
>
Bericht speichern
</UButton>
<PDFViewer
v-if="showDocument"
:uri="uri"
@@ -266,5 +421,7 @@ changeRange()
</div>
</template>
</UTabs>
</UDashboardPanelContent>
</template>
</template>

View File

@@ -1,20 +1,28 @@
<script setup lang="ts">
import { useStaffTime } from '~/composables/useStaffTime'
import { useAuthStore } from '~/stores/auth'
import FloatingActionButton from "~/components/mobile/FloatingActionButton.vue";
const { list, start, stop, submit,approve } = useStaffTime()
definePageMeta({
layout: "default",
})
const { list, start, stop, submit, approve } = useStaffTime()
const auth = useAuthStore()
const router = useRouter()
// MOBILE DETECTION
const platformIsNative = useCapacitor().getIsNative()
// LIST + ACTIVE
const entries = ref([])
const active = computed(() => entries.value.find(e => !e.stopped_at && e.user_id === auth.user.id))
const loading = ref(false)
const showModal = ref(false)
const editEntry = ref(null)
// 👥 Nutzer-Filter (nur für Berechtigte)
const users = ref([])
const selectedUser = ref<string | null>(null)
const selectedUser = ref(platformIsNative ? auth.user.id : null)
const canViewAll = computed(() => auth.permissions.includes('staff.time.read_all'))
@@ -25,15 +33,18 @@ async function loadUsers() {
users.value = res
}
// LOAD ENTRIES (only own entries on mobile)
async function load() {
entries.value = await list(
canViewAll.value && selectedUser.value ? { user_id: selectedUser.value } : undefined
)
}
async function handleStart() {
loading.value = true
await start('Arbeitszeit gestartet')
await start("Arbeitszeit gestartet")
await load()
loading.value = false
}
@@ -61,160 +72,291 @@ async function handleApprove(entry: any) {
await load()
}
onMounted(async () => {
await loadUsers()
await load()
await loadUsers()
setPageLayout(platformIsNative ? 'mobile' : 'default')
})
</script>
<template>
<UDashboardNavbar title="Zeiterfassung" :badge="entries.length" />
<!-- ============================= -->
<!-- DESKTOP VERSION -->
<!-- ============================= -->
<template v-if="!platformIsNative">
<UDashboardNavbar title="Zeiterfassung" :badge="entries.length" />
<!-- TOOLBAR -->
<UDashboardToolbar>
<template #left>
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-clock" class="text-primary-500" />
<span v-if="active" class="text-primary-600 font-medium">
Läuft seit {{ useNuxtApp().$dayjs(active.started_at).format('HH:mm') }}
</span>
<span v-else class="text-gray-500">Keine aktive Zeit</span>
</div>
</template>
<UDashboardToolbar>
<template #left>
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-clock" class="text-primary-500" />
<span v-if="active" class="text-primary-600 font-medium">
Läuft seit {{ useNuxtApp().$dayjs(active.started_at).format('HH:mm') }}
</span>
<span v-else class="text-gray-500">Keine aktive Zeit</span>
</div>
</template>
<template #right>
<UButton
v-if="active"
color="red"
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"
/>
<UButton
color="primary"
icon="i-heroicons-plus"
label="Zeit"
@click="() => { editEntry = null; showModal = true }"
/>
</template>
</UDashboardToolbar>
<UDashboardToolbar>
<template #left>
<!-- 👥 User-Filter (nur bei Berechtigung) -->
<div v-if="canViewAll" class="flex items-center gap-2">
<USelectMenu
v-model="selectedUser"
:options="[
<template #right>
<UButton
v-if="active"
color="red"
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"
/>
<UButton
color="primary"
icon="i-heroicons-plus"
label="Zeit"
@click="() => { editEntry = null; showModal = true }"
/>
</template>
</UDashboardToolbar>
<UDashboardToolbar>
<template #left>
<!-- 👥 User-Filter (nur bei Berechtigung) -->
<div v-if="canViewAll" class="flex items-center gap-2">
<USelectMenu
v-model="selectedUser"
:options="[
{ label: 'Alle Benutzer', value: null },
...users.map(u => ({ label: u.full_name || u.email, value: u.user_id }))
]"
placeholder="Benutzer auswählen"
value-attribute="value"
option-attribute="label"
class="min-w-[220px]"
@change="load"
/>
<!-- 🔹 Button zur Auswertung -->
<UTooltip
:text="selectedUser ? 'Anwesenheiten des Mitarbeiters auswerten' : 'Mitarbeiter für die Auswertung auswählen'"
>
<UButton
:disabled="!selectedUser"
color="gray"
icon="i-heroicons-chart-bar"
label="Auswertung"
variant="soft"
@click="router.push(`/staff/time/${selectedUser}/evaluate`)"
placeholder="Benutzer auswählen"
value-attribute="value"
option-attribute="label"
class="min-w-[220px]"
@change="load"
/>
</UTooltip>
<!-- 🔹 Button zur Auswertung -->
<UTooltip
:text="selectedUser ? 'Anwesenheiten des Mitarbeiters auswerten' : 'Mitarbeiter für die Auswertung auswählen'"
>
<UButton
:disabled="!selectedUser"
color="gray"
icon="i-heroicons-chart-bar"
label="Auswertung"
variant="soft"
@click="router.push(`/staff/time/${selectedUser}/evaluate`)"
/>
</UTooltip>
</div>
</template>
</UDashboardToolbar>
<UDashboardPanelContent>
<UTable
:rows="entries"
:columns="[
{ key: 'actions', label: '' },
{ key: 'state', label: 'Status' },
{ key: 'started_at', label: 'Start' },
{ key: 'stopped_at', label: 'Ende' },
{ key: 'duration_minutes', label: 'Dauer' },
{ key: 'description', label: 'Beschreibung' },
]"
>
<template #state-data="{ row }">
<span v-if="row.state === 'approved'" class="text-primary-500">Genehmigt</span>
<span v-else-if="row.state === 'submitted'" class="text-cyan-500">Eingereicht</span>
<span v-else-if="row.state === 'draft'" class="text-red-500">Entwurf</span>
</template>
<template #started_at-data="{ row }">
{{ useNuxtApp().$dayjs(row.started_at).format("DD.MM.YY HH:mm") }}
</template>
<template #stopped_at-data="{ row }">
<span v-if="row.stopped_at">
{{ useNuxtApp().$dayjs(row.stopped_at).format("DD.MM.YY HH:mm") }}
</span>
<span v-else class="text-primary-500 font-medium">läuft...</span>
</template>
<template #duration_minutes-data="{ row }">
{{ row.duration_minutes ? useFormatDuration(row.duration_minutes) : "-" }}
</template>
<template #actions-data="{ row }">
<UTooltip text="Zeit genehmigen" v-if="row.state === 'submitted'">
<UButton
variant="ghost"
icon="i-heroicons-check-circle"
@click="handleApprove(row)"
/>
</UTooltip>
<UTooltip text="Zeit einreichen" v-if="row.state === 'draft'">
<UButton
variant="ghost"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="handleSubmit(row)"
/>
</UTooltip>
<UTooltip text="Zeit bearbeiten" v-if="row.state === 'draft'">
<UButton
variant="ghost"
icon="i-heroicons-pencil-square"
@click="handleEdit(row)"
/>
</UTooltip>
</template>
</UTable>
</UDashboardPanelContent>
</template>
<!-- ============================= -->
<!-- MOBILE VERSION -->
<!-- ============================= -->
<template v-else>
<UDashboardNavbar title="Zeiterfassung" />
<div class="relative flex flex-col h-[100dvh] overflow-hidden">
<!-- 🔥 FIXED ACTIVE TIMER -->
<div class="p-4 bg-white dark:bg-gray-900 border-b sticky top-0 z-20 shadow-sm">
<UCard class="p-3">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">Aktive Zeit</p>
<p v-if="active" class="text-primary-600 font-semibold">
Läuft seit {{ useNuxtApp().$dayjs(active.started_at).format('HH:mm') }}
</p>
<p v-else class="text-gray-600">Keine aktive Zeit</p>
</div>
<UButton
v-if="active"
color="red"
icon="i-heroicons-stop"
:loading="loading"
@click="handleStop"
/>
<UButton
v-else
color="green"
icon="i-heroicons-play"
:loading="loading"
@click="handleStart"
/>
</div>
</UCard>
</div>
</template>
</UDashboardToolbar>
<!-- TABELLE -->
<UDashboardPanelContent>
<UTable
:rows="entries"
:columns="[
{ key: 'actions', label: '' },
{ key: 'state', label: 'Status' },
{ key: 'started_at', label: 'Start' },
{ key: 'stopped_at', label: 'Ende' },
{ key: 'duration_minutes', label: 'Dauer' },
{ key: 'description', label: 'Beschreibung' },
...(canViewAll ? [{ key: 'user_name', label: 'Benutzer' }] : []),
<div class="px-3 mt-3">
<UButton
color="gray"
icon="i-heroicons-chart-bar"
label="Eigene Auswertung"
class="w-full"
variant="soft"
@click="router.push(`/staff/time/${auth.user.id}/evaluate`)"
/>
</div>
]"
>
<template #state-data="{row}">
<span v-if="row.state === 'approved'" class="text-primary-500">Genehmigt</span>
<span v-else-if="row.state === 'submitted'" class="text-cyan-500">Eingereicht</span>
<span v-else-if="row.state === 'draft'" class="text-red-500">Entwurf</span>
</template>
<template #started_at-data="{ row }">
{{ useNuxtApp().$dayjs(row.started_at).format('DD.MM.YY HH:mm') }}
</template>
<template #stopped_at-data="{ row }">
<span v-if="row.stopped_at">
{{ useNuxtApp().$dayjs(row.stopped_at).format('DD.MM.YY HH:mm') }}
</span>
<span v-else class="text-primary-500 font-medium">läuft...</span>
</template>
<template #duration_minutes-data="{ row }">
{{ row.duration_minutes ? useFormatDuration(row.duration_minutes) : '-' }}
</template>
<template #user_name-data="{ row }">
{{ row.user_id ? users.find(i => i.user_id === row.user_id).full_name : '-' }}
</template>
<template #actions-data="{ row }">
<UTooltip
text="Zeit genehmigen"
v-if="row.state === 'submitted'"
<!-- 📜 SCROLLABLE CONTENT -->
<UDashboardPanelContent class="flex-1 overflow-y-auto p-3 space-y-4 pb-24">
<!-- ZEIT-CARDS -->
<UCard
v-for="row in entries"
:key="row.id"
class="p-4 border rounded-xl active:scale-[0.98] transition cursor-pointer"
@click="handleEdit(row)"
>
<UButton
variant="ghost"
icon="i-heroicons-check-circle"
@click="handleApprove(row)"
<div class="flex justify-between items-center">
<p class="font-semibold">
{{ row.description || 'Keine Beschreibung' }}
</p>
/>
</UTooltip>
<UTooltip
text="Zeit einreichen"
v-if="row.state === 'draft'"
>
<UButton
variant="ghost"
icon="i-heroicons-arrow-right-end-on-rectangle"
@click="handleSubmit(row)"
<UBadge
:color="{
approved: 'primary',
submitted: 'cyan',
draft: 'red'
}[row.state]"
>
{{
{
approved: 'Genehmigt',
submitted: 'Eingereicht',
draft: 'Entwurf'
}[row.state] || row.state
}}
</UBadge>
/>
</UTooltip>
<UTooltip
text="Zeit bearbeiten"
v-if="row.state === 'draft'"
>
<UButton
variant="ghost"
icon="i-heroicons-pencil-square"
@click="handleEdit(row)"
/>
</UTooltip>
</div>
<p class="text-sm text-gray-500 mt-1">
Start: {{ useNuxtApp().$dayjs(row.started_at).format('DD.MM.YY HH:mm') }}
</p>
</template>
</UTable>
</UDashboardPanelContent>
<p class="text-sm text-gray-500">
Ende:
<span v-if="row.stopped_at">
{{ useNuxtApp().$dayjs(row.stopped_at).format('DD.MM.YY HH:mm') }}
</span>
<span v-else class="text-primary-500 font-medium">läuft...</span>
</p>
<p class="text-sm text-gray-500">
Dauer:
{{ row.duration_minutes ? useFormatDuration(row.duration_minutes) : '-' }}
</p>
<!-- ACTION-BUTTONS -->
<div class="flex gap-2 mt-3">
<UButton
v-if="row.state === 'draft'"
color="gray"
icon="i-heroicons-arrow-right-end-on-rectangle"
label="Einreichen"
variant="soft"
@click.stop="handleSubmit(row)"
/>
<!-- <UButton
v-if="row.state === 'submitted'"
color="primary"
icon="i-heroicons-check"
label="Genehmigen"
variant="soft"
@click.stop="handleApprove(row)"
/>-->
</div>
</UCard>
</UDashboardPanelContent>
<!-- FLOATING ACTION BUTTON -->
<FloatingActionButton
icon="i-heroicons-plus"
class="!fixed bottom-6 right-6 z-50"
color="primary"
@click="() => { editEntry = null; showModal = true }"
/>
</div>
</template>
<!-- MODAL -->
<StaffTimeEntryModal v-model="showModal" :entry="editEntry" @saved="load" />