Remodel for Mobile
This commit is contained in:
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user