Time Changes

This commit is contained in:
2025-12-16 16:23:12 +01:00
parent 780b899d42
commit 6d8193296e
3 changed files with 534 additions and 559 deletions

View File

@@ -1,206 +1,170 @@
<script setup lang="ts">
import dayjs from "dayjs";
import { z } from 'zod'
import type { FormSubmitEvent } from '#ui/types'
import { useStaffTime } from '~/composables/useStaffTime'
const props = defineProps<{
modelValue: boolean;
entry?: any | null;
users: any[];
canSelectUser: boolean;
defaultUserId: string;
}>();
const props = defineProps({
modelValue: { type: Boolean, default: false },
entry: { type: Object, default: null },
defaultUserId: { type: String, default: null }
})
const emit = defineEmits(["update:modelValue", "saved"]);
const emit = defineEmits(['update:modelValue', 'saved'])
const { create, update } = useStaffTime();
// 💡 createEntry importieren
const { update, createEntry } = useStaffTime()
const { $dayjs } = useNuxtApp()
const toast = useToast()
const show = computed({
const loading = ref(false)
const types = [
{ label: 'Arbeitszeit', value: 'work' },
{ label: 'Pause', value: 'pause' },
{ label: 'Urlaub', value: 'vacation' },
{ label: 'Krankheit', value: 'sick' },
{ label: 'Feiertag', value: 'holiday' },
{ label: 'Sonstiges', value: 'other' }
]
const state = reactive({
start_date: '',
start_time: '',
end_date: '',
end_time: '',
type: 'work',
description: ''
})
const schema = z.object({
start_date: z.string().min(1, 'Datum erforderlich'),
start_time: z.string().min(1, 'Zeit erforderlich'),
type: z.string(),
description: z.string().optional()
})
const isOpen = computed({
get: () => props.modelValue,
set: (v: boolean) => emit("update:modelValue", v),
});
set: (value) => emit('update:modelValue', value)
})
// 🌈 Typen
const typeOptions = [
{ label: "Arbeitszeit", value: "work" },
{ label: "Urlaub", value: "vacation" },
{ label: "Krankheit", value: "sick" },
{ label: "Feiertag", value: "holiday" },
];
const toDateStr = (dateStr: string) => dateStr ? $dayjs(dateStr).format('YYYY-MM-DD') : ''
const toTimeStr = (dateStr: string) => dateStr ? $dayjs(dateStr).format('HH:mm') : ''
// Lokaler State
const local = reactive({
id: "",
user_id: "", // 👈 Mitarbeiter
description: "",
started_at: "",
stopped_at: "",
type: "work",
vacation_reason: "",
sick_reason: "",
});
watch(() => props.entry, (newVal) => {
if (newVal) {
// EDIT
state.start_date = toDateStr(newVal.started_at)
state.start_time = toTimeStr(newVal.started_at)
state.end_date = newVal.stopped_at ? toDateStr(newVal.stopped_at) : ''
state.end_time = newVal.stopped_at ? toTimeStr(newVal.stopped_at) : ''
state.type = newVal.type || 'work'
state.description = newVal.description || ''
} else {
// CREATE (Standardwerte: Heute)
const now = $dayjs()
state.start_date = now.format('YYYY-MM-DD')
state.start_time = now.format('HH:mm')
state.end_date = ''
state.end_time = ''
state.type = 'work'
state.description = ''
}
}, { immediate: true })
// 📡 ENTRY —> LOCAL
watch(
() => props.entry,
(val) => {
if (val) {
Object.assign(local, {
id: val.id,
user_id: val.user_id, // 👈 Mitarbeiter vorbelegen
description: val.description || "",
type: val.type || "work",
started_at:
val.type === "vacation"
? dayjs(val.started_at).format("YYYY-MM-DD")
: dayjs(val.started_at).format("YYYY-MM-DDTHH:mm"),
stopped_at:
val.type === "vacation"
? dayjs(val.stopped_at).format("YYYY-MM-DD")
: dayjs(val.stopped_at).format("YYYY-MM-DDTHH:mm"),
vacation_reason: val.vacation_reason || "",
sick_reason: val.sick_reason || "",
});
} else {
Object.assign(local, {
id: "",
user_id: props.defaultUserId, // 👈 Neuer Eintrag → aktueller Nutzer
description: "",
type: "work",
started_at: dayjs().format("YYYY-MM-DDTHH:mm"),
stopped_at: dayjs().add(1, "hour").format("YYYY-MM-DDTHH:mm"),
vacation_reason: "",
sick_reason: "",
});
}
},
{ immediate: true }
);
const loading = ref(false);
async function handleSubmit() {
loading.value = true;
async function onSubmit(event: FormSubmitEvent<any>) {
loading.value = true
try {
const payload: any = {
user_id: local.user_id, // 👈 immer senden
type: local.type,
};
// 1. Datum und Zeit kombinieren
const startIso = $dayjs(`${state.start_date} ${state.start_time}`).toISOString()
if (local.type === "vacation") {
payload.started_at = dayjs(local.started_at).startOf("day").toISOString();
payload.stopped_at = dayjs(local.stopped_at).endOf("day").toISOString();
payload.vacation_reason = local.vacation_reason;
} else {
payload.started_at = dayjs(local.started_at).toISOString();
payload.stopped_at = local.stopped_at
? dayjs(local.stopped_at).toISOString()
: null;
payload.description = local.description;
let endIso = null
if (state.end_date && state.end_time) {
endIso = $dayjs(`${state.end_date} ${state.end_time}`).toISOString()
if (local.type === "sick") {
payload.sick_reason = local.sick_reason;
if ($dayjs(endIso).isBefore($dayjs(startIso))) {
throw new Error("Endzeitpunkt muss nach dem Startzeitpunkt liegen.")
}
}
if (local.id) {
await update(local.id, payload);
if (props.entry) {
// 🟢 UPDATE (Bearbeiten)
await update(props.entry, {
start: startIso,
end: endIso,
type: state.type,
description: state.description
})
toast.add({ title: 'Eintrag aktualisiert', color: 'green' })
} else {
await create(payload);
// 🟢 CREATE (Neu Erstellen)
// 💡 HIER WAR DER FEHLER: Wir nutzen jetzt createEntry mit den Daten aus dem Formular
await createEntry({
start: startIso, // Die eingegebene Startzeit
end: endIso, // Die eingegebene Endzeit (oder null)
type: state.type,
description: state.description
})
toast.add({ title: 'Zeit manuell erfasst', color: 'green' })
}
emit("saved");
show.value = false;
emit('saved')
isOpen.value = false
} catch (error: any) {
toast.add({ title: 'Fehler', description: error.message, color: 'red' })
} finally {
loading.value = false;
loading.value = false
}
}
</script>
<template>
<UModal v-model="show" :ui="{ width: 'w-full sm:max-w-md' }" :key="local.id || 'new'">
<UCard>
<UModal v-model="isOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<template #header>
<div class="font-semibold text-lg">
{{ local.id ? "Zeit bearbeiten" : "Neue Zeit erfassen" }}
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
{{ entry ? 'Eintrag bearbeiten' : 'Neue Zeit erfassen' }}
</h3>
<UButton color="gray" variant="ghost" icon="i-heroicons-x-mark-20-solid" class="-my-1" @click="isOpen = false" />
</div>
</template>
<UForm @submit.prevent="handleSubmit" class="space-y-4">
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<!-- 👥 Mitarbeiter-Auswahl -->
<UFormGroup label="Mitarbeiter" v-if="props.canSelectUser">
<USelectMenu
v-model="local.user_id"
:options="props.users.map(u => ({
label: u.full_name || u.email,
value: u.user_id
}))"
placeholder="Mitarbeiter wählen"
option-attribute="label"
value-attribute="value"
/>
<UFormGroup label="Typ" name="type">
<USelectMenu v-model="state.type" :options="types" value-attribute="value" option-attribute="label" />
</UFormGroup>
<!-- TYPE -->
<UFormGroup label="Typ">
<USelect v-model="local.type" :options="typeOptions" />
<div class="grid grid-cols-2 gap-4">
<UFormGroup label="Start Datum" name="start_date">
<UInput type="date" v-model="state.start_date" />
</UFormGroup>
<UFormGroup label="Start Zeit" name="start_time">
<UInput type="time" v-model="state.start_time" />
</UFormGroup>
</div>
<div class="grid grid-cols-2 gap-4">
<UFormGroup label="Ende Datum" name="end_date">
<UInput type="date" v-model="state.end_date" />
</UFormGroup>
<UFormGroup label="Ende Zeit" name="end_time">
<UInput type="time" v-model="state.end_time" />
</UFormGroup>
</div>
<p class="text-xs text-gray-500 -mt-2">Leer lassen, wenn die Zeit noch läuft.</p>
<UFormGroup label="Beschreibung / Notiz" name="description">
<UTextarea v-model="state.description" placeholder="Was wurde gemacht?" />
</UFormGroup>
<!-- VACATION -->
<template v-if="local.type === 'vacation'">
<UFormGroup label="Urlaubsgrund">
<UInput v-model="local.vacation_reason" />
</UFormGroup>
<UFormGroup label="Start (Tag)">
<UInput v-model="local.started_at" type="date" />
</UFormGroup>
<UFormGroup label="Ende (Tag)">
<UInput v-model="local.stopped_at" type="date" />
</UFormGroup>
</template>
<!-- SICK -->
<template v-else-if="local.type === 'sick'">
<UFormGroup label="Krankheitsgrund">
<UInput v-model="local.sick_reason" />
</UFormGroup>
<UFormGroup label="Start (Tag)">
<UInput v-model="local.started_at" type="date" />
</UFormGroup>
<UFormGroup label="Ende (Tag)">
<UInput v-model="local.stopped_at" type="date" />
</UFormGroup>
</template>
<!-- WORK / OTHER -->
<template v-else>
<UFormGroup label="Beschreibung">
<UInput v-model="local.description" placeholder="Was wurde gemacht?" />
</UFormGroup>
<UFormGroup label="Startzeit">
<UInput v-model="local.started_at" type="datetime-local" />
</UFormGroup>
<UFormGroup label="Endzeit">
<UInput v-model="local.stopped_at" type="datetime-local" />
</UFormGroup>
</template>
<!-- ACTIONS -->
<div class="flex justify-end gap-2 mt-4">
<UButton color="gray" label="Abbrechen" @click="show = false" />
<UButton color="primary" :loading="loading" type="submit" label="Speichern" />
<div class="flex justify-end gap-2 pt-4">
<UButton label="Abbrechen" color="gray" variant="ghost" @click="isOpen = false" />
<UButton type="submit" label="Speichern" color="primary" :loading="loading" />
</div>
</UForm>
</UCard>
</UModal>
</template>
</template>