Add External Link
Fix Plantafel
This commit is contained in:
@@ -3,6 +3,7 @@ import deLocale from "@fullcalendar/core/locales/de"
|
||||
import FullCalendar from "@fullcalendar/vue3"
|
||||
import interactionPlugin from "@fullcalendar/interaction"
|
||||
import resourceTimelinePlugin from "@fullcalendar/resource-timeline"
|
||||
import { parseDate } from "@internationalized/date"
|
||||
|
||||
const router = useRouter()
|
||||
const profileStore = useProfileStore()
|
||||
@@ -13,6 +14,10 @@ const { list: listStaffTimeSpans, createEntry, update: updateStaffTimeEntry } =
|
||||
const loading = ref(true)
|
||||
const savingAbsence = ref(false)
|
||||
const selectedType = ref("all")
|
||||
const calendarRef = ref(null)
|
||||
const calendarView = ref("resourceTimelineWeek")
|
||||
const calendarCurrentDate = ref($dayjs().format("YYYY-MM-DD"))
|
||||
const calendarTitle = ref("")
|
||||
const visibleRange = ref({
|
||||
from: $dayjs().startOf("month").format("YYYY-MM-DD"),
|
||||
to: $dayjs().endOf("month").format("YYYY-MM-DD")
|
||||
@@ -38,17 +43,72 @@ const setAbsenceDateToToday = (field) => {
|
||||
absenceForm[field] = $dayjs().format("YYYY-MM-DD")
|
||||
}
|
||||
|
||||
const startDateValue = computed({
|
||||
get: () => {
|
||||
if (!absenceForm.startDate) return null
|
||||
|
||||
try {
|
||||
return parseDate(absenceForm.startDate)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
set: (value) => {
|
||||
absenceForm.startDate = value ? value.toString() : ""
|
||||
}
|
||||
})
|
||||
|
||||
const endDateValue = computed({
|
||||
get: () => {
|
||||
if (!absenceForm.endDate) return null
|
||||
|
||||
try {
|
||||
return parseDate(absenceForm.endDate)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
set: (value) => {
|
||||
absenceForm.endDate = value ? value.toString() : ""
|
||||
}
|
||||
})
|
||||
|
||||
const resourceTypeOptions = [
|
||||
{ label: "Alle Ressourcen", value: "all" },
|
||||
{ label: "Profile", value: "Profile" },
|
||||
{ label: "Inventarartikel", value: "Inventarartikel" }
|
||||
]
|
||||
|
||||
const calendarViewOptions = [
|
||||
{ label: "Tag", value: "resourceTimelineDay" },
|
||||
{ label: "Woche", value: "resourceTimelineWeek" },
|
||||
{ label: "Monat", value: "resourceTimelineMonth" }
|
||||
]
|
||||
|
||||
const absenceTypeOptions = [
|
||||
{ label: "Urlaub", value: "vacation" },
|
||||
{ label: "Krank", value: "sick" }
|
||||
]
|
||||
|
||||
const calendarPickerValue = computed({
|
||||
get: () => {
|
||||
if (!calendarCurrentDate.value) return null
|
||||
|
||||
try {
|
||||
return parseDate(calendarCurrentDate.value)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
set: (value) => {
|
||||
calendarCurrentDate.value = value ? value.toString() : ""
|
||||
if (value) {
|
||||
const api = calendarRef.value?.getApi?.()
|
||||
api?.gotoDate(value.toString())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const profileOptions = computed(() =>
|
||||
profiles.value
|
||||
.filter((profile) => !profile.archived && profile.user_id)
|
||||
@@ -83,12 +143,9 @@ const calendarOptions = computed(() => ({
|
||||
schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
|
||||
locale: deLocale,
|
||||
plugins: [resourceTimelinePlugin, interactionPlugin],
|
||||
initialView: "resourceTimelineWeek",
|
||||
headerToolbar: {
|
||||
left: "prev,next today",
|
||||
center: "title",
|
||||
right: "resourceTimelineDay,resourceTimelineWeek,resourceTimelineMonth"
|
||||
},
|
||||
initialView: calendarView.value,
|
||||
initialDate: calendarCurrentDate.value,
|
||||
headerToolbar: false,
|
||||
resourceAreaWidth: "280px",
|
||||
resourceGroupField: "type",
|
||||
resourceOrder: "type,title",
|
||||
@@ -149,6 +206,10 @@ const calendarOptions = computed(() => ({
|
||||
const nextTo = $dayjs(info.end).subtract(1, "day").format("YYYY-MM-DD")
|
||||
const nextKey = `${nextFrom}:${nextTo}`
|
||||
|
||||
calendarView.value = info.view.type
|
||||
calendarCurrentDate.value = $dayjs(info.view.currentStart).format("YYYY-MM-DD")
|
||||
calendarTitle.value = info.view.title
|
||||
|
||||
if (nextKey === lastRangeKey.value) return
|
||||
|
||||
lastRangeKey.value = nextKey
|
||||
@@ -264,6 +325,31 @@ function resetAbsenceForm() {
|
||||
absenceForm.description = ""
|
||||
}
|
||||
|
||||
function getCalendarApi() {
|
||||
return calendarRef.value?.getApi?.()
|
||||
}
|
||||
|
||||
function changeCalendarView(view) {
|
||||
const api = getCalendarApi()
|
||||
if (!api || !view) return
|
||||
calendarView.value = view
|
||||
api.changeView(view)
|
||||
}
|
||||
|
||||
function moveCalendar(direction) {
|
||||
const api = getCalendarApi()
|
||||
if (!api) return
|
||||
|
||||
if (direction === "prev") api.prev()
|
||||
if (direction === "next") api.next()
|
||||
}
|
||||
|
||||
function moveCalendarToday() {
|
||||
const api = getCalendarApi()
|
||||
if (!api) return
|
||||
api.today()
|
||||
}
|
||||
|
||||
function openAbsenceModal(type = "vacation", preset = {}) {
|
||||
absenceForm.mode = preset.entry ? "edit" : "create"
|
||||
absenceForm.entry = preset.entry || null
|
||||
@@ -404,12 +490,49 @@ onMounted(() => {
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<USelectMenu
|
||||
v-model="selectedType"
|
||||
:options="resourceTypeOptions"
|
||||
value-attribute="value"
|
||||
option-attribute="label"
|
||||
:items="resourceTypeOptions"
|
||||
value-key="value"
|
||||
label-key="label"
|
||||
:clearable="false"
|
||||
class="min-w-[220px]"
|
||||
/>
|
||||
<USelectMenu
|
||||
v-model="calendarView"
|
||||
:items="calendarViewOptions"
|
||||
value-key="value"
|
||||
label-key="label"
|
||||
:clearable="false"
|
||||
class="min-w-[160px]"
|
||||
@update:model-value="changeCalendarView"
|
||||
/>
|
||||
<UPopover>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-heroicons-calendar-days"
|
||||
class="min-w-[180px] justify-between"
|
||||
>
|
||||
{{ calendarCurrentDate ? $dayjs(calendarCurrentDate).format("DD.MM.YYYY") : "Datum wählen" }}
|
||||
</UButton>
|
||||
|
||||
<template #content>
|
||||
<div class="p-2">
|
||||
<UCalendar v-model="calendarPickerValue" />
|
||||
<div class="flex justify-end border-t border-default pt-2">
|
||||
<UButton color="neutral" variant="ghost" size="sm" @click="moveCalendarToday">
|
||||
Heute
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
<div class="flex items-center gap-1">
|
||||
<UButton color="neutral" variant="ghost" icon="i-heroicons-chevron-left" @click="moveCalendar('prev')" />
|
||||
<UButton color="neutral" variant="ghost" icon="i-heroicons-chevron-right" @click="moveCalendar('next')" />
|
||||
</div>
|
||||
<span class="text-sm font-medium text-highlighted">
|
||||
{{ calendarTitle }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
@@ -449,6 +572,7 @@ onMounted(() => {
|
||||
|
||||
<FullCalendar
|
||||
v-else
|
||||
ref="calendarRef"
|
||||
:options="calendarOptions"
|
||||
/>
|
||||
</UDashboardPanelContent>
|
||||
@@ -475,33 +599,82 @@ onMounted(() => {
|
||||
<UFormField label="Profil">
|
||||
<USelectMenu
|
||||
v-model="absenceForm.userId"
|
||||
:options="profileOptions"
|
||||
value-attribute="value"
|
||||
option-attribute="label"
|
||||
searchable
|
||||
:items="profileOptions"
|
||||
value-key="value"
|
||||
label-key="label"
|
||||
class="w-full"
|
||||
:search-input="{ placeholder: 'Profil suchen...' }"
|
||||
:filter-fields="['label']"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Typ">
|
||||
<USelectMenu
|
||||
v-model="absenceForm.type"
|
||||
:options="absenceTypeOptions"
|
||||
value-attribute="value"
|
||||
option-attribute="label"
|
||||
:items="absenceTypeOptions"
|
||||
value-key="value"
|
||||
label-key="label"
|
||||
class="w-full"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<UFormField label="Start">
|
||||
<div class="flex items-center gap-2">
|
||||
<UInput v-model="absenceForm.startDate" type="date" class="flex-1" />
|
||||
<UButton color="gray" variant="soft" label="Heute" @click="setAbsenceDateToToday('startDate')" />
|
||||
<UPopover>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-heroicons-calendar-days"
|
||||
class="min-w-0 flex-1 justify-between"
|
||||
>
|
||||
<span class="truncate text-left">
|
||||
{{ absenceForm.startDate ? $dayjs(absenceForm.startDate).format("DD.MM.YYYY") : "Kein Datum" }}
|
||||
</span>
|
||||
</UButton>
|
||||
|
||||
<template #content>
|
||||
<div class="p-2">
|
||||
<UCalendar v-model="startDateValue" />
|
||||
<div class="flex justify-end border-t border-default pt-2">
|
||||
<UButton color="neutral" variant="ghost" size="sm" @click="setAbsenceDateToToday('startDate')">
|
||||
Heute
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<UButton color="neutral" variant="soft" label="Heute" @click="setAbsenceDateToToday('startDate')" />
|
||||
</div>
|
||||
</UFormField>
|
||||
<UFormField label="Ende">
|
||||
<div class="flex items-center gap-2">
|
||||
<UInput v-model="absenceForm.endDate" type="date" class="flex-1" />
|
||||
<UButton color="gray" variant="soft" label="Heute" @click="setAbsenceDateToToday('endDate')" />
|
||||
<UPopover>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-heroicons-calendar-days"
|
||||
class="min-w-0 flex-1 justify-between"
|
||||
>
|
||||
<span class="truncate text-left">
|
||||
{{ absenceForm.endDate ? $dayjs(absenceForm.endDate).format("DD.MM.YYYY") : "Kein Datum" }}
|
||||
</span>
|
||||
</UButton>
|
||||
|
||||
<template #content>
|
||||
<div class="p-2">
|
||||
<UCalendar v-model="endDateValue" />
|
||||
<div class="flex justify-end border-t border-default pt-2">
|
||||
<UButton color="neutral" variant="ghost" size="sm" @click="setAbsenceDateToToday('endDate')">
|
||||
Heute
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UPopover>
|
||||
|
||||
<UButton color="neutral" variant="soft" label="Heute" @click="setAbsenceDateToToday('endDate')" />
|
||||
</div>
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user