diff --git a/apps/app/components.d.ts b/apps/app/components.d.ts index 4dae24b4..468101ce 100644 --- a/apps/app/components.d.ts +++ b/apps/app/components.d.ts @@ -10,6 +10,7 @@ declare module 'vue' { AddEditAddressDialog: typeof import('./src/components/dialogs/AddEditAddressDialog.vue')['default'] AddEditPermissionDialog: typeof import('./src/components/dialogs/AddEditPermissionDialog.vue')['default'] AddEditRoleDialog: typeof import('./src/components/dialogs/AddEditRoleDialog.vue')['default'] + AddPersonToCrowdListDialog: typeof import('./src/components/crowd-lists/AddPersonToCrowdListDialog.vue')['default'] AppAutocomplete: typeof import('./src/@core/components/app-form-elements/AppAutocomplete.vue')['default'] AppBarSearch: typeof import('./src/@core/components/AppBarSearch.vue')['default'] AppCardActions: typeof import('./src/@core/components/cards/AppCardActions.vue')['default'] @@ -37,6 +38,8 @@ declare module 'vue' { CreateShiftDialog: typeof import('./src/components/sections/CreateShiftDialog.vue')['default'] CreateSubEventDialog: typeof import('./src/components/events/CreateSubEventDialog.vue')['default'] CreateTimeSlotDialog: typeof import('./src/components/sections/CreateTimeSlotDialog.vue')['default'] + CrowdListDetailPanel: typeof import('./src/components/crowd-lists/CrowdListDetailPanel.vue')['default'] + CrowdListFormDialog: typeof import('./src/components/crowd-lists/CrowdListFormDialog.vue')['default'] CrowdTypesManager: typeof import('./src/components/organisations/CrowdTypesManager.vue')['default'] CustomCheckboxes: typeof import('./src/@core/components/app-form-elements/CustomCheckboxes.vue')['default'] CustomCheckboxesWithIcon: typeof import('./src/@core/components/app-form-elements/CustomCheckboxesWithIcon.vue')['default'] diff --git a/apps/app/src/components/events/EventTabsNav.vue b/apps/app/src/components/events/EventTabsNav.vue index 5e85bcc9..705e0773 100644 --- a/apps/app/src/components/events/EventTabsNav.vue +++ b/apps/app/src/components/events/EventTabsNav.vue @@ -54,6 +54,7 @@ const baseTabs = [ { label: 'Overzicht', icon: 'tabler-layout-dashboard', route: 'events-id' }, { label: 'Personen', icon: 'tabler-users', route: 'events-id-persons' }, { label: 'Publiekslijsten', icon: 'tabler-list', route: 'events-id-crowd-lists' }, + { label: 'Tijdsloten', icon: 'tabler-clock', route: 'events-id-time-slots' }, { label: 'Secties & Shifts', icon: 'tabler-layout-grid', route: 'events-id-sections' }, { label: 'Artiesten', icon: 'tabler-music', route: 'events-id-artists' }, { label: 'Briefings', icon: 'tabler-mail', route: 'events-id-briefings' }, @@ -71,7 +72,7 @@ const programmaonderdelenLabel = computed(() => { const tabs = computed(() => { if (!event.value?.is_festival) return baseTabs - // Festival tab order: Overzicht | Programmaonderdelen | Secties & Shifts | Personen | Publiekslijsten | Artiesten | Briefings | Instellingen + // Festival tab order: Overzicht | Programmaonderdelen | Tijdsloten | Secties & Shifts | Personen | Publiekslijsten | Artiesten | Briefings | Instellingen const festivalTab = { label: programmaonderdelenLabel.value, icon: 'tabler-calendar-event', @@ -81,12 +82,13 @@ const tabs = computed(() => { return [ baseTabs[0], // Overzicht festivalTab, - baseTabs[3], // Secties & Shifts + baseTabs[3], // Tijdsloten + baseTabs[4], // Secties & Shifts baseTabs[1], // Personen baseTabs[2], // Publiekslijsten - baseTabs[4], // Artiesten - baseTabs[5], // Briefings - baseTabs[6], // Instellingen + baseTabs[5], // Artiesten + baseTabs[6], // Briefings + baseTabs[7], // Instellingen ] }) diff --git a/apps/app/src/components/sections/CreateShiftDialog.vue b/apps/app/src/components/sections/CreateShiftDialog.vue index 3ff0154d..7ca0cce9 100644 --- a/apps/app/src/components/sections/CreateShiftDialog.vue +++ b/apps/app/src/components/sections/CreateShiftDialog.vue @@ -14,12 +14,11 @@ const props = withDefaults(defineProps<{ isSubEvent: false, }) -const emit = defineEmits<{ - openTimeSlots: [] -}>() - const modelValue = defineModel({ required: true }) +const router = useRouter() +const route = useRoute() + const eventIdRef = computed(() => props.eventId) const sectionIdRef = computed(() => props.sectionId) @@ -209,7 +208,7 @@ function onSubmit() { variant="text" color="primary" prepend-icon="tabler-clock" - @click="emit('openTimeSlots'); modelValue = false" + @click="modelValue = false; router.push({ name: 'events-id-time-slots', params: { id: (route.params as { id: string }).id } })" > Time Slots beheren diff --git a/apps/app/src/components/sections/SectionsShiftsPanel.vue b/apps/app/src/components/sections/SectionsShiftsPanel.vue index 44300826..9d747094 100644 --- a/apps/app/src/components/sections/SectionsShiftsPanel.vue +++ b/apps/app/src/components/sections/SectionsShiftsPanel.vue @@ -2,126 +2,18 @@ import draggable from 'vuedraggable' import { useSectionList, useDeleteSection, useReorderSections } from '@/composables/api/useSections' import { useShiftList, useDeleteShift } from '@/composables/api/useShifts' -import { useTimeSlotList, useDeleteTimeSlot } from '@/composables/api/useTimeSlots' -import { useSectionsUiStore } from '@/stores/useSectionsUiStore' import CreateSectionDialog from '@/components/sections/CreateSectionDialog.vue' import EditSectionDialog from '@/components/sections/EditSectionDialog.vue' -import CreateTimeSlotDialog from '@/components/sections/CreateTimeSlotDialog.vue' import CreateShiftDialog from '@/components/sections/CreateShiftDialog.vue' import AssignShiftDialog from '@/components/sections/AssignShiftDialog.vue' -import type { FestivalSection, Shift, ShiftStatus, TimeSlot, PersonType } from '@/types/section' -import type { EventItem } from '@/types/event' +import type { FestivalSection, Shift, ShiftStatus } from '@/types/section' -const props = withDefaults(defineProps<{ +const props = defineProps<{ eventId: string - children?: EventItem[] isSubEvent?: boolean -}>(), { - children: () => [], - isSubEvent: false, -}) +}>() const eventIdRef = computed(() => props.eventId) -const isSubEventRef = computed(() => props.isSubEvent) - -// --- Time Slots --- -const { data: timeSlots, isLoading: timeSlotsLoading } = useTimeSlotList(eventIdRef, { includeParent: isSubEventRef }) -const { mutate: deleteTimeSlotMutation, isPending: isDeletingTimeSlot } = useDeleteTimeSlot(eventIdRef) - -const personTypeLabel: Record = { - VOLUNTEER: 'Vrijwilliger', - CREW: 'Crew', - PRESS: 'Pers', - PHOTO: 'Fotograaf', - PARTNER: 'Partner', -} - -const personTypeColor: Record = { - VOLUNTEER: 'success', - CREW: 'primary', - PRESS: 'warning', - PHOTO: 'info', - PARTNER: 'secondary', -} - -// Group time slots by source when on a sub-event with parent time slots included -const hasFestivalTimeSlots = computed(() => - timeSlots.value?.some(ts => ts.source === 'festival') ?? false, -) - -const subEventTimeSlots = computed(() => - timeSlots.value?.filter(ts => ts.source !== 'festival') ?? [], -) - -const festivalTimeSlots = computed(() => - timeSlots.value?.filter(ts => ts.source === 'festival') ?? [], -) - -const timeSlotsSummary = computed(() => { - const slots = timeSlots.value - if (!slots?.length) return '' - - const count = slots.length - const dates = slots.map(ts => ts.date).sort() - const minDate = dates[0] - const maxDate = dates[dates.length - 1] - - const fmt = new Intl.DateTimeFormat('nl-NL', { day: 'numeric', month: 'short' }) - const fmtYear = new Intl.DateTimeFormat('nl-NL', { day: 'numeric', month: 'short', year: 'numeric' }) - - if (minDate === maxDate) { - return `${count} time slot${count > 1 ? 's' : ''} \u00b7 ${fmtYear.format(new Date(minDate))}` - } - - return `${count} time slots \u00b7 ${fmt.format(new Date(minDate))} \u2013 ${fmtYear.format(new Date(maxDate))}` -}) - -function formatTimeSlotDate(iso: string) { - if (!iso) return '' - return new Intl.DateTimeFormat('nl-NL', { weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric' }).format(new Date(iso)) -} - -function formatTime(time: string) { - return time?.slice(0, 5) ?? '' -} - -// --- Time slot context labels (Opbouw / Afbraak / Transitie) --- -const hasChildren = computed(() => props.children.length > 0) - -const childDateRange = computed(() => { - if (!hasChildren.value) return null - - const starts = props.children.map(c => c.start_date).sort() - const ends = props.children.map(c => c.end_date).sort() - - return { - firstStart: starts[0], - lastEnd: ends[ends.length - 1], - dates: props.children.map(c => ({ start: c.start_date, end: c.end_date })), - } -}) - -function getTimeSlotContext(ts: TimeSlot): { label: string; color: string } | null { - if (!childDateRange.value) return null - - const tsDate = ts.date - const { firstStart, lastEnd, dates } = childDateRange.value - - if (tsDate < firstStart) return { label: 'Opbouw', color: 'warning' } - if (tsDate > lastEnd) return { label: 'Afbraak', color: 'error' } - - // Check if this date falls between two sub-events (transition) - const sorted = [...dates].sort((a, b) => a.start.localeCompare(b.start)) - for (let i = 0; i < sorted.length - 1; i++) { - const currentEnd = sorted[i].end - const nextStart = sorted[i + 1].start - if (tsDate > currentEnd && tsDate < nextStart) { - return { label: 'Transitie', color: 'info' } - } - } - - return null -} // --- Section list --- const { data: sectionsQuery, isLoading: sectionsLoading } = useSectionList(eventIdRef) @@ -199,18 +91,14 @@ const shiftsByTimeSlot = computed(() => { }) // --- Dialogs --- -const sectionsUiStore = useSectionsUiStore() const isCreateSectionOpen = ref(false) const isEditSectionOpen = ref(false) -const isCreateTimeSlotOpen = ref(false) const isCreateShiftOpen = ref(false) const isAssignShiftOpen = ref(false) const editingShift = ref(null) const assigningShift = ref(null) -const editingTimeSlot = ref(null) -const duplicatingTimeSlot = ref(null) // Delete section const isDeleteSectionOpen = ref(false) @@ -234,43 +122,6 @@ function onDeleteSectionExecute() { }) } -// Delete time slot -const isDeleteTimeSlotOpen = ref(false) -const deletingTimeSlotId = ref(null) - -function onDeleteTimeSlotConfirm(ts: TimeSlot) { - deletingTimeSlotId.value = ts.id - isDeleteTimeSlotOpen.value = true -} - -function onDeleteTimeSlotExecute() { - if (!deletingTimeSlotId.value) return - deleteTimeSlotMutation(deletingTimeSlotId.value, { - onSuccess: () => { - isDeleteTimeSlotOpen.value = false - deletingTimeSlotId.value = null - }, - }) -} - -function onEditTimeSlot(ts: TimeSlot) { - editingTimeSlot.value = ts - duplicatingTimeSlot.value = null - isCreateTimeSlotOpen.value = true -} - -function onDuplicateTimeSlot(ts: TimeSlot) { - editingTimeSlot.value = null - duplicatingTimeSlot.value = ts - isCreateTimeSlotOpen.value = true -} - -function onAddTimeSlot() { - editingTimeSlot.value = null - duplicatingTimeSlot.value = null - isCreateTimeSlotOpen.value = true -} - // Delete shift const isDeleteShiftOpen = ref(false) const deletingShiftId = ref(null) @@ -309,15 +160,6 @@ function onEditSection() { isEditSectionOpen.value = true } -watch(isCreateTimeSlotOpen, (open) => { - if (!open) duplicatingTimeSlot.value = null -}) - -function onOpenTimeSlotsFromShiftDialog() { - isCreateShiftOpen.value = false - sectionsUiStore.expandTimeSlots() -} - // Status styling const statusColor: Record = { draft: 'default', @@ -380,282 +222,6 @@ function onSectionCreated(payload: { name: string; redirectedToParent: boolean;