Verfügbarkeitshinweise für Mitarbeiter und Plantafel-Details ergänzen
This commit is contained in:
2
backend/db/migrations/0034_profile_availability_note.sql
Normal file
2
backend/db/migrations/0034_profile_availability_note.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "auth_profiles"
|
||||||
|
ADD COLUMN "availability_note" text;
|
||||||
@@ -74,6 +74,7 @@ export const authProfiles = pgTable("auth_profiles", {
|
|||||||
contract_type: text("contract_type"),
|
contract_type: text("contract_type"),
|
||||||
position: text("position"),
|
position: text("position"),
|
||||||
qualification: text("qualification"),
|
qualification: text("qualification"),
|
||||||
|
availability_note: text("availability_note"),
|
||||||
|
|
||||||
address_street: text("address_street"),
|
address_street: text("address_street"),
|
||||||
address_zip: text("address_zip"),
|
address_zip: text("address_zip"),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import FullCalendar from "@fullcalendar/vue3"
|
|||||||
import interactionPlugin from "@fullcalendar/interaction"
|
import interactionPlugin from "@fullcalendar/interaction"
|
||||||
import resourceTimelinePlugin from "@fullcalendar/resource-timeline"
|
import resourceTimelinePlugin from "@fullcalendar/resource-timeline"
|
||||||
import { parseDate } from "@internationalized/date"
|
import { parseDate } from "@internationalized/date"
|
||||||
|
import { useDraggable } from "@vueuse/core"
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
@@ -31,6 +32,8 @@ const profiles = ref([])
|
|||||||
const inventoryitems = ref([])
|
const inventoryitems = ref([])
|
||||||
const savingQuickConfig = ref(false)
|
const savingQuickConfig = ref(false)
|
||||||
const isQuickConfigModalOpen = ref(false)
|
const isQuickConfigModalOpen = ref(false)
|
||||||
|
const quickConfigWindowEl = ref(null)
|
||||||
|
const profileDetailsWindowEl = ref(null)
|
||||||
const showQuickConfigEditor = ref(true)
|
const showQuickConfigEditor = ref(true)
|
||||||
const showQuickPresetManagement = ref(false)
|
const showQuickPresetManagement = ref(false)
|
||||||
const quickEntryConfig = reactive({
|
const quickEntryConfig = reactive({
|
||||||
@@ -49,6 +52,14 @@ const quickEntryColorOptions = [
|
|||||||
{ label: "Schwarz", value: "#111827" }
|
{ label: "Schwarz", value: "#111827" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const { style: quickConfigWindowStyle } = useDraggable(quickConfigWindowEl, {
|
||||||
|
initialValue: { x: 120, y: 100 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const { style: profileDetailsWindowStyle } = useDraggable(profileDetailsWindowEl, {
|
||||||
|
initialValue: { x: 220, y: 120 }
|
||||||
|
})
|
||||||
|
|
||||||
const isAbsenceModalOpen = ref(false)
|
const isAbsenceModalOpen = ref(false)
|
||||||
const isProfileDetailsModalOpen = ref(false)
|
const isProfileDetailsModalOpen = ref(false)
|
||||||
const loadingProfileVacation = ref(false)
|
const loadingProfileVacation = ref(false)
|
||||||
@@ -157,6 +168,20 @@ const tenantCalendarConfig = computed(() =>
|
|||||||
|| {}
|
|| {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const resolvedPlanningBoardConfig = computed(() => {
|
||||||
|
const config = tenantCalendarConfig.value?.planningBoard || {}
|
||||||
|
const normalizedStartTime = /^\d{2}:\d{2}$/.test(String(config.startTime || "")) ? String(config.startTime) : "06:00"
|
||||||
|
const normalizedEndTime = /^\d{2}:\d{2}$/.test(String(config.endTime || "")) ? String(config.endTime) : "21:00"
|
||||||
|
const normalizedSlotMinutes = Number(config.slotMinutes)
|
||||||
|
const allowedSlotMinutes = [15, 30, 60, 120, 180]
|
||||||
|
|
||||||
|
return {
|
||||||
|
startTime: normalizedStartTime,
|
||||||
|
endTime: normalizedEndTime,
|
||||||
|
slotMinutes: allowedSlotMinutes.includes(normalizedSlotMinutes) ? normalizedSlotMinutes : 180
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const resolvedQuickEntryConfig = computed(() => {
|
const resolvedQuickEntryConfig = computed(() => {
|
||||||
const config = tenantCalendarConfig.value?.quickEntry || {}
|
const config = tenantCalendarConfig.value?.quickEntry || {}
|
||||||
|
|
||||||
@@ -215,6 +240,9 @@ const selectedProfileRemainingVacationDays = computed(() => {
|
|||||||
|
|
||||||
return Number((selectedProfileAnnualLeaveDays.value - selectedProfileVacationDaysTaken.value).toFixed(2))
|
return Number((selectedProfileAnnualLeaveDays.value - selectedProfileVacationDaysTaken.value).toFixed(2))
|
||||||
})
|
})
|
||||||
|
const selectedProfileAvailabilityNote = computed(() =>
|
||||||
|
selectedProfile.value?.availability_note?.trim() || "Keine Verfügbarkeitshinweise hinterlegt"
|
||||||
|
)
|
||||||
|
|
||||||
const visibleResources = computed(() => {
|
const visibleResources = computed(() => {
|
||||||
if (selectedType.value === "all") return resources.value
|
if (selectedType.value === "all") return resources.value
|
||||||
@@ -294,16 +322,20 @@ const calendarOptions = computed(() => ({
|
|||||||
type: "resourceTimeline",
|
type: "resourceTimeline",
|
||||||
duration: { days: 1 },
|
duration: { days: 1 },
|
||||||
buttonText: "Tag",
|
buttonText: "Tag",
|
||||||
slotDuration: { hours: 1 }
|
slotDuration: { minutes: resolvedPlanningBoardConfig.value.slotMinutes },
|
||||||
|
snapDuration: { minutes: resolvedPlanningBoardConfig.value.slotMinutes },
|
||||||
|
slotMinTime: `${resolvedPlanningBoardConfig.value.startTime}:00`,
|
||||||
|
slotMaxTime: `${resolvedPlanningBoardConfig.value.endTime}:00`
|
||||||
},
|
},
|
||||||
resourceTimelineWeek: {
|
resourceTimelineWeek: {
|
||||||
type: "resourceTimeline",
|
type: "resourceTimeline",
|
||||||
duration: { days: 7 },
|
duration: { days: 7 },
|
||||||
buttonText: "Woche",
|
buttonText: "Woche",
|
||||||
slotDuration: { hours: 3 },
|
slotDuration: { minutes: resolvedPlanningBoardConfig.value.slotMinutes },
|
||||||
|
snapDuration: { minutes: resolvedPlanningBoardConfig.value.slotMinutes },
|
||||||
weekends: false,
|
weekends: false,
|
||||||
slotMinTime: "06:00:00",
|
slotMinTime: `${resolvedPlanningBoardConfig.value.startTime}:00`,
|
||||||
slotMaxTime: "21:00:00"
|
slotMaxTime: `${resolvedPlanningBoardConfig.value.endTime}:00`
|
||||||
},
|
},
|
||||||
resourceTimelineMonth: {
|
resourceTimelineMonth: {
|
||||||
type: "resourceTimeline",
|
type: "resourceTimeline",
|
||||||
@@ -991,121 +1023,62 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</UDashboardPanelContent>
|
</UDashboardPanelContent>
|
||||||
|
|
||||||
<UModal v-model:open="isQuickConfigModalOpen">
|
<div
|
||||||
<template #content>
|
v-if="isQuickConfigModalOpen"
|
||||||
<UCard class="mx-auto w-full max-w-2xl" :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
ref="quickConfigWindowEl"
|
||||||
<template #header>
|
:style="quickConfigWindowStyle"
|
||||||
<div class="flex items-center justify-between gap-4">
|
class="fixed z-[999] flex h-[720px] w-[980px] flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-2xl resize dark:border-gray-800 dark:bg-gray-900"
|
||||||
<div>
|
>
|
||||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
<div class="flex cursor-move items-center justify-between border-b border-gray-200 bg-gray-50 p-3 select-none dark:border-gray-800 dark:bg-gray-800/50">
|
||||||
Quick-Einträge
|
<div class="flex items-center gap-3">
|
||||||
</h3>
|
<UIcon name="i-heroicons-cog-6-tooth" class="text-gray-500" />
|
||||||
<p class="text-sm text-muted">
|
<div>
|
||||||
Name und Farbe für neu gezogene Quick-Einträge in der Plantafel.
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||||
</p>
|
Quick-Einträge
|
||||||
</div>
|
</h3>
|
||||||
<UButton
|
<p class="text-xs text-muted">
|
||||||
color="gray"
|
Name und Farbe für neu gezogene Quick-Einträge in der Plantafel.
|
||||||
variant="ghost"
|
</p>
|
||||||
icon="i-heroicons-x-mark-20-solid"
|
</div>
|
||||||
class="-my-1"
|
</div>
|
||||||
@click="isQuickConfigModalOpen = false"
|
<UButton
|
||||||
/>
|
color="gray"
|
||||||
</div>
|
variant="ghost"
|
||||||
</template>
|
icon="i-heroicons-x-mark-20-solid"
|
||||||
|
size="sm"
|
||||||
<div class="space-y-4 p-1">
|
@click="isQuickConfigModalOpen = false"
|
||||||
<div v-if="quickEntryPresets.length" class="space-y-2">
|
/>
|
||||||
<div class="text-xs font-medium text-muted">Vorlagen</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<button
|
|
||||||
v-for="preset in quickEntryPresets"
|
|
||||||
:key="preset.name"
|
|
||||||
type="button"
|
|
||||||
class="flex items-center gap-2 rounded border border-default px-2 py-1 text-xs transition hover:border-primary"
|
|
||||||
@click="applyQuickPreset(preset)"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="h-4 w-4 rounded-full border border-white/40"
|
|
||||||
:style="{ backgroundColor: preset.color }"
|
|
||||||
/>
|
|
||||||
{{ preset.name }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="flex-1 overflow-auto p-4">
|
||||||
|
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)]">
|
||||||
|
<div class="space-y-4">
|
||||||
<div class="rounded border border-default p-3">
|
<div class="rounded border border-default p-3">
|
||||||
<button
|
<div class="space-y-3">
|
||||||
type="button"
|
|
||||||
class="flex w-full items-center justify-between text-left"
|
|
||||||
@click="showQuickConfigEditor = !showQuickConfigEditor"
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-medium text-highlighted">Konfiguration</p>
|
<p class="text-sm font-medium text-highlighted">Vorlagen</p>
|
||||||
<p class="text-xs text-muted">Aktuellen Titel und Farbe für Quick-Einträge anpassen.</p>
|
<p class="text-xs text-muted">Gespeicherte Kombinationen direkt für neue Quick-Einträge anwenden.</p>
|
||||||
</div>
|
</div>
|
||||||
<UIcon
|
|
||||||
:name="showQuickConfigEditor ? 'i-heroicons-chevron-up' : 'i-heroicons-chevron-down'"
|
|
||||||
class="text-muted"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div v-if="showQuickConfigEditor" class="mt-4 space-y-3 border-t border-default pt-4">
|
<div v-if="quickEntryPresets.length" class="flex flex-wrap gap-2">
|
||||||
<UFormField label="Titel">
|
<button
|
||||||
<UInput v-model="quickEntryConfig.name" class="w-full" />
|
v-for="preset in quickEntryPresets"
|
||||||
</UFormField>
|
:key="preset.name"
|
||||||
|
type="button"
|
||||||
<UFormField label="Vorlagenname">
|
class="flex items-center gap-2 rounded border border-default px-2 py-1 text-xs transition hover:border-primary"
|
||||||
<UInput v-model="newQuickPresetName" class="w-full" />
|
@click="applyQuickPreset(preset)"
|
||||||
</UFormField>
|
>
|
||||||
|
<span
|
||||||
<UFormField label="Farbe">
|
class="h-4 w-4 rounded-full border border-white/40"
|
||||||
<div class="space-y-3">
|
:style="{ backgroundColor: preset.color }"
|
||||||
<div class="flex flex-wrap gap-2">
|
/>
|
||||||
<button
|
{{ preset.name }}
|
||||||
v-for="option in quickEntryColorOptions"
|
</button>
|
||||||
:key="option.value"
|
|
||||||
type="button"
|
|
||||||
class="flex items-center gap-2 rounded border px-2 py-1 text-xs transition"
|
|
||||||
:class="quickEntryConfig.color === option.value ? 'border-primary ring-1 ring-primary' : 'border-default'"
|
|
||||||
@click="quickEntryConfig.color = option.value"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="h-4 w-4 rounded-full border border-white/40"
|
|
||||||
:style="{ backgroundColor: option.value }"
|
|
||||||
/>
|
|
||||||
{{ option.label }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<input
|
|
||||||
v-model="quickEntryConfig.color"
|
|
||||||
type="color"
|
|
||||||
class="h-10 w-14 cursor-pointer rounded border border-default bg-transparent p-1"
|
|
||||||
>
|
|
||||||
<UInput v-model="quickEntryConfig.color" class="flex-1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</UFormField>
|
|
||||||
|
|
||||||
<div class="rounded border border-default p-3">
|
|
||||||
<div class="flex items-start justify-between gap-3">
|
|
||||||
<div>
|
|
||||||
<p class="text-xs text-muted">Vorschau</p>
|
|
||||||
<div class="mt-2 inline-flex rounded px-3 py-1 text-sm font-medium text-white" :style="{ backgroundColor: quickEntryConfig.color }">
|
|
||||||
{{ quickEntryConfig.name || "Quick-Eintrag" }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<UButton
|
|
||||||
color="primary"
|
|
||||||
:loading="savingQuickConfig"
|
|
||||||
@click="saveQuickPreset"
|
|
||||||
>
|
|
||||||
Vorlage speichern
|
|
||||||
</UButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p v-else class="text-sm text-muted">
|
||||||
|
Es sind noch keine Vorlagen gespeichert.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1155,9 +1128,88 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UCard>
|
|
||||||
</template>
|
<div class="rounded border border-default p-3">
|
||||||
</UModal>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex w-full items-center justify-between text-left"
|
||||||
|
@click="showQuickConfigEditor = !showQuickConfigEditor"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-highlighted">Konfiguration</p>
|
||||||
|
<p class="text-xs text-muted">Aktuellen Titel und Farbe für Quick-Einträge anpassen.</p>
|
||||||
|
</div>
|
||||||
|
<UIcon
|
||||||
|
:name="showQuickConfigEditor ? 'i-heroicons-chevron-up' : 'i-heroicons-chevron-down'"
|
||||||
|
class="text-muted"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div v-if="showQuickConfigEditor" class="mt-4 space-y-3 border-t border-default pt-4">
|
||||||
|
<UFormField label="Titel">
|
||||||
|
<UInput v-model="quickEntryConfig.name" class="w-full" />
|
||||||
|
</UFormField>
|
||||||
|
|
||||||
|
<UFormField label="Vorlagenname">
|
||||||
|
<UInput v-model="newQuickPresetName" class="w-full" />
|
||||||
|
</UFormField>
|
||||||
|
|
||||||
|
<UFormField label="Farbe">
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
v-for="option in quickEntryColorOptions"
|
||||||
|
:key="option.value"
|
||||||
|
type="button"
|
||||||
|
class="flex items-center gap-2 rounded border px-2 py-1 text-xs transition"
|
||||||
|
:class="quickEntryConfig.color === option.value ? 'border-primary ring-1 ring-primary' : 'border-default'"
|
||||||
|
@click="quickEntryConfig.color = option.value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="h-4 w-4 rounded-full border border-white/40"
|
||||||
|
:style="{ backgroundColor: option.value }"
|
||||||
|
/>
|
||||||
|
{{ option.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<input
|
||||||
|
v-model="quickEntryConfig.color"
|
||||||
|
type="color"
|
||||||
|
class="h-10 w-14 cursor-pointer rounded border border-default bg-transparent p-1"
|
||||||
|
>
|
||||||
|
<UInput v-model="quickEntryConfig.color" class="flex-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</UFormField>
|
||||||
|
|
||||||
|
<div class="rounded border border-default p-3">
|
||||||
|
<div class="flex items-start justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<p class="text-xs text-muted">Vorschau</p>
|
||||||
|
<div class="mt-2 inline-flex rounded px-3 py-1 text-sm font-medium text-white" :style="{ backgroundColor: quickEntryConfig.color }">
|
||||||
|
{{ quickEntryConfig.name || "Quick-Eintrag" }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UButton
|
||||||
|
color="primary"
|
||||||
|
:loading="savingQuickConfig"
|
||||||
|
@click="saveQuickPreset"
|
||||||
|
>
|
||||||
|
Vorlage speichern
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute bottom-0 right-0 h-4 w-4 cursor-se-resize opacity-50">
|
||||||
|
<UIcon name="i-heroicons-arrows-pointing-out" class="h-3 w-3 rotate-90 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<UModal v-model:open="isAbsenceModalOpen">
|
<UModal v-model:open="isAbsenceModalOpen">
|
||||||
<template #content>
|
<template #content>
|
||||||
@@ -1283,145 +1335,166 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
|
|
||||||
<UModal v-model:open="isProfileDetailsModalOpen">
|
<div
|
||||||
<template #content>
|
v-if="isProfileDetailsModalOpen"
|
||||||
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
ref="profileDetailsWindowEl"
|
||||||
<template #header>
|
:style="profileDetailsWindowStyle"
|
||||||
<div class="flex items-center justify-between gap-3">
|
class="fixed z-[999] flex h-[760px] w-[980px] flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-2xl resize dark:border-gray-800 dark:bg-gray-900"
|
||||||
<div>
|
>
|
||||||
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
<div class="flex cursor-move items-center justify-between border-b border-gray-200 bg-gray-50 p-3 select-none dark:border-gray-800 dark:bg-gray-800/50">
|
||||||
{{ getProfileLabel(selectedProfile) }}
|
<div class="flex items-center gap-3">
|
||||||
</h3>
|
<UIcon name="i-heroicons-user-circle" class="text-gray-500" />
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
<div>
|
||||||
Mitarbeiterdetails
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||||
</p>
|
{{ getProfileLabel(selectedProfile) }}
|
||||||
</div>
|
</h3>
|
||||||
<UButton
|
<p class="text-xs text-muted">
|
||||||
color="gray"
|
Mitarbeiterdetails
|
||||||
variant="ghost"
|
</p>
|
||||||
icon="i-heroicons-x-mark-20-solid"
|
</div>
|
||||||
class="-my-1"
|
</div>
|
||||||
@click="isProfileDetailsModalOpen = false"
|
<UButton
|
||||||
/>
|
color="gray"
|
||||||
</div>
|
variant="ghost"
|
||||||
</template>
|
icon="i-heroicons-x-mark-20-solid"
|
||||||
|
size="sm"
|
||||||
|
@click="isProfileDetailsModalOpen = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="selectedProfile" class="space-y-6">
|
<div v-if="selectedProfile" class="flex-1 overflow-auto p-4">
|
||||||
<div class="grid gap-4 md:grid-cols-2">
|
<div class="space-y-6">
|
||||||
<div class="rounded-lg border border-default p-4">
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
<p class="text-xs font-medium uppercase tracking-wide text-muted">Kontakt</p>
|
<div class="rounded-lg border border-default p-4">
|
||||||
<dl class="mt-3 space-y-3 text-sm">
|
<p class="text-xs font-medium uppercase tracking-wide text-muted">Kontakt</p>
|
||||||
<div class="flex items-start justify-between gap-4">
|
<dl class="mt-3 space-y-3 text-sm">
|
||||||
<dt class="text-muted">E-Mail</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.email || "Nicht hinterlegt" }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Mobil</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.mobile_tel || "Nicht hinterlegt" }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Festnetz</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.fixed_tel || "Nicht hinterlegt" }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Geburtstag</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ formatProfileDate(selectedProfile.birthday) }}</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-lg border border-default p-4">
|
|
||||||
<p class="text-xs font-medium uppercase tracking-wide text-muted">Beschäftigung</p>
|
|
||||||
<dl class="mt-3 space-y-3 text-sm">
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">MA-Nummer</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.employee_number || "Nicht hinterlegt" }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Position</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.position || "Nicht hinterlegt" }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Vertragsart</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.contract_type || "Nicht hinterlegt" }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Eintrittsdatum</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ formatProfileDate(selectedProfile.entry_date) }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Wochenstunden</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ formatProfileNumber(selectedProfile.weekly_working_hours) }}</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid gap-4 md:grid-cols-2">
|
|
||||||
<div class="rounded-lg border border-default p-4">
|
|
||||||
<p class="text-xs font-medium uppercase tracking-wide text-muted">Zuordnung</p>
|
|
||||||
<dl class="mt-3 space-y-3 text-sm">
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Niederlassung</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ selectedProfileBranchesLabel }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Teams</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ selectedProfileTeamsLabel }}</dd>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<dt class="text-muted">Status</dt>
|
|
||||||
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.active ? "Aktiv" : "Inaktiv" }}</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rounded-lg border border-default p-4">
|
|
||||||
<div class="flex items-start justify-between gap-4">
|
<div class="flex items-start justify-between gap-4">
|
||||||
<div>
|
<dt class="text-muted">E-Mail</dt>
|
||||||
<p class="text-xs font-medium uppercase tracking-wide text-muted">Urlaub {{ currentVacationYear }}</p>
|
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.email || "Nicht hinterlegt" }}</dd>
|
||||||
<p class="mt-1 text-sm text-muted">Anzeige auf Basis der aktuellen Zeitauswertung.</p>
|
|
||||||
</div>
|
|
||||||
<UBadge color="amber" variant="subtle">
|
|
||||||
Resturlaub
|
|
||||||
</UBadge>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
<div v-if="loadingProfileVacation" class="mt-4 space-y-2">
|
<dt class="text-muted">Mobil</dt>
|
||||||
<USkeleton class="h-6 w-full" />
|
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.mobile_tel || "Nicht hinterlegt" }}</dd>
|
||||||
<USkeleton class="h-6 w-4/5" />
|
|
||||||
<USkeleton class="h-6 w-3/5" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<dt class="text-muted">Festnetz</dt>
|
||||||
|
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.fixed_tel || "Nicht hinterlegt" }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<dt class="text-muted">Geburtstag</dt>
|
||||||
|
<dd class="text-right font-medium text-highlighted">{{ formatProfileDate(selectedProfile.birthday) }}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
<dl v-else class="mt-4 space-y-3 text-sm">
|
<div class="rounded-lg border border-default p-4">
|
||||||
<div class="flex items-start justify-between gap-4">
|
<p class="text-xs font-medium uppercase tracking-wide text-muted">Beschäftigung</p>
|
||||||
<dt class="text-muted">Urlaubsanspruch</dt>
|
<dl class="mt-3 space-y-3 text-sm">
|
||||||
<dd class="text-right font-medium text-highlighted">{{ formatProfileNumber(selectedProfileAnnualLeaveDays) }}</dd>
|
<div class="flex items-start justify-between gap-4">
|
||||||
</div>
|
<dt class="text-muted">MA-Nummer</dt>
|
||||||
<div class="flex items-start justify-between gap-4">
|
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.employee_number || "Nicht hinterlegt" }}</dd>
|
||||||
<dt class="text-muted">Bereits verplant/genehmigt</dt>
|
</div>
|
||||||
<dd class="text-right font-medium text-highlighted">{{ formatProfileNumber(selectedProfileVacationDaysTaken) }}</dd>
|
<div class="flex items-start justify-between gap-4">
|
||||||
</div>
|
<dt class="text-muted">Position</dt>
|
||||||
<div class="flex items-start justify-between gap-4">
|
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.position || "Nicht hinterlegt" }}</dd>
|
||||||
<dt class="text-muted">Resturlaub</dt>
|
</div>
|
||||||
<dd class="text-right text-base font-semibold text-highlighted">
|
<div class="flex items-start justify-between gap-4">
|
||||||
{{ selectedProfileRemainingVacationDays === null ? "Nicht hinterlegt" : formatProfileNumber(selectedProfileRemainingVacationDays) }}
|
<dt class="text-muted">Vertragsart</dt>
|
||||||
</dd>
|
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.contract_type || "Nicht hinterlegt" }}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
<div class="flex items-start justify-between gap-4">
|
||||||
</div>
|
<dt class="text-muted">Eintrittsdatum</dt>
|
||||||
|
<dd class="text-right font-medium text-highlighted">{{ formatProfileDate(selectedProfile.entry_date) }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<dt class="text-muted">Wochenstunden</dt>
|
||||||
|
<dd class="text-right font-medium text-highlighted">{{ formatProfileNumber(selectedProfile.weekly_working_hours) }}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
<div class="flex justify-end">
|
<div class="rounded-lg border border-default p-4">
|
||||||
<UButton color="gray" variant="soft" @click="isProfileDetailsModalOpen = false">
|
<p class="text-xs font-medium uppercase tracking-wide text-muted">Zuordnung</p>
|
||||||
Schließen
|
<dl class="mt-3 space-y-3 text-sm">
|
||||||
</UButton>
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<dt class="text-muted">Niederlassung</dt>
|
||||||
|
<dd class="text-right font-medium text-highlighted">{{ selectedProfileBranchesLabel }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<dt class="text-muted">Teams</dt>
|
||||||
|
<dd class="text-right font-medium text-highlighted">{{ selectedProfileTeamsLabel }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<dt class="text-muted">Status</dt>
|
||||||
|
<dd class="text-right font-medium text-highlighted">{{ selectedProfile.active ? "Aktiv" : "Inaktiv" }}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</UCard>
|
<div class="rounded-lg border border-default p-4">
|
||||||
</template>
|
<div class="flex items-start justify-between gap-4">
|
||||||
</UModal>
|
<div>
|
||||||
|
<p class="text-xs font-medium uppercase tracking-wide text-muted">Urlaub {{ currentVacationYear }}</p>
|
||||||
|
<p class="mt-1 text-sm text-muted">Anzeige auf Basis der aktuellen Zeitauswertung.</p>
|
||||||
|
</div>
|
||||||
|
<UBadge color="amber" variant="subtle">
|
||||||
|
Resturlaub
|
||||||
|
</UBadge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loadingProfileVacation" class="mt-4 space-y-2">
|
||||||
|
<USkeleton class="h-6 w-full" />
|
||||||
|
<USkeleton class="h-6 w-4/5" />
|
||||||
|
<USkeleton class="h-6 w-3/5" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl v-else class="mt-4 space-y-3 text-sm">
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<dt class="text-muted">Urlaubsanspruch</dt>
|
||||||
|
<dd class="text-right font-medium text-highlighted">{{ formatProfileNumber(selectedProfileAnnualLeaveDays) }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<dt class="text-muted">Bereits verplant/genehmigt</dt>
|
||||||
|
<dd class="text-right font-medium text-highlighted">{{ formatProfileNumber(selectedProfileVacationDaysTaken) }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<dt class="text-muted">Resturlaub</dt>
|
||||||
|
<dd class="text-right text-base font-semibold text-highlighted">
|
||||||
|
{{ selectedProfileRemainingVacationDays === null ? "Nicht hinterlegt" : formatProfileNumber(selectedProfileRemainingVacationDays) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-lg border border-default p-4">
|
||||||
|
<div class="flex items-start justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-xs font-medium uppercase tracking-wide text-muted">Verfügbarkeitshinweis</p>
|
||||||
|
<p class="mt-1 text-sm text-muted">Z. B. bevorzugte Einsatzzeiten oder bekannte Einschränkungen.</p>
|
||||||
|
</div>
|
||||||
|
<UBadge color="sky" variant="subtle">
|
||||||
|
Verfügbarkeit
|
||||||
|
</UBadge>
|
||||||
|
</div>
|
||||||
|
<p class="mt-4 whitespace-pre-wrap text-sm font-medium text-highlighted">
|
||||||
|
{{ selectedProfileAvailabilityNote }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end border-t border-gray-200 bg-gray-50 p-3 dark:border-gray-800 dark:bg-gray-800/30">
|
||||||
|
<UButton color="gray" variant="soft" @click="isProfileDetailsModalOpen = false">
|
||||||
|
Schließen
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute bottom-0 right-0 h-4 w-4 cursor-se-resize opacity-50">
|
||||||
|
<UIcon name="i-heroicons-arrows-pointing-out" class="h-3 w-3 rotate-90 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -548,6 +548,15 @@ onMounted(async () => {
|
|||||||
<UFormField label="Token-ID" class="w-full">
|
<UFormField label="Token-ID" class="w-full">
|
||||||
<UInput v-model="profile.token_id" class="w-full" />
|
<UInput v-model="profile.token_id" class="w-full" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
|
<UFormField label="Verfügbarkeitshinweis" class="w-full md:col-span-2">
|
||||||
|
<UTextarea
|
||||||
|
v-model="profile.availability_note"
|
||||||
|
class="w-full"
|
||||||
|
:rows="4"
|
||||||
|
placeholder="z. B. kann nur vormittags eingeplant werden, bevorzugt Außendienst, nicht dienstags verfügbar"
|
||||||
|
/>
|
||||||
|
</UFormField>
|
||||||
</UForm>
|
</UForm>
|
||||||
</UCard>
|
</UCard>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user