fix: time slot dropdown group headers and dimming via boundary detection

VAutocomplete ignores interleaved fake header items — they were filtered
out before reaching the template. Replace with Approach A: keep only
real selectable items sorted by group, detect group boundaries in the
#item template by comparing adjacent groupName values, and render
VListSubheader before each new group.

- Remove _isGroupHeader from TimeSlotDropdownItem interface
- Rename groupTimeSlots → sortedItems (returns only selectable items)
- Add hasGroups computed for conditional header rendering
- Add isNewGroup(index) boundary detection in CreateShiftDialog
- Add scoped .time-slot-dimmed CSS class (opacity: 0.65)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 22:31:32 +02:00
parent 5bd028f408
commit 948965e664
3 changed files with 56 additions and 44 deletions

View File

@@ -36,7 +36,7 @@ const { data: eventDetail } = useEventDetail(orgIdRef, eventIdRef)
const sectionRef = computed(() => props.section ?? null)
// Determine dropdown scenario
const { scenario, showInfoTooltip, tooltipText, fetchParams, groupTimeSlots } = useTimeSlotDropdown(
const { scenario, showInfoTooltip, hasGroups, tooltipText, fetchParams, sortedItems } = useTimeSlotDropdown(
eventDetail,
sectionRef,
)
@@ -95,8 +95,8 @@ watch(
{ immediate: true },
)
// Group time slots for the dropdown
const flattenedTimeSlots = computed(() => {
// Build sorted dropdown items (no fake header items — groups are detected by boundary)
const dropdownItems = computed(() => {
// While loading, show the current shift's time slot so the dropdown doesn't flash a raw ULID
if (!timeSlots.value?.length) {
if (props.shift?.time_slot) {
@@ -106,7 +106,6 @@ const flattenedTimeSlots = computed(() => {
name: ts.name,
timeRange: `${ts.start_time} ${ts.end_time}`,
displayLabel: ts.name,
_isGroupHeader: false,
_isDimmed: false,
groupName: '',
}]
@@ -114,10 +113,16 @@ const flattenedTimeSlots = computed(() => {
return []
}
return groupTimeSlots(timeSlots.value)
return sortedItems(timeSlots.value)
})
const hasTimeSlots = computed(() => flattenedTimeSlots.value.some(i => !i._isGroupHeader))
const hasTimeSlots = computed(() => dropdownItems.value.length > 0)
// Detect group boundaries: true when the current item starts a new group
function isNewGroup(index: number): boolean {
if (index === 0) return true
return dropdownItems.value[index]?.groupName !== dropdownItems.value[index - 1]?.groupName
}
const statusOptions = [
{ title: 'Concept', value: 'draft' },
@@ -231,7 +236,7 @@ function onSubmit() {
<VAutocomplete
v-if="timeSlotsLoading || hasTimeSlots"
v-model="form.time_slot_id"
:items="flattenedTimeSlots.filter(i => !i._isGroupHeader)"
:items="dropdownItems"
item-title="displayLabel"
item-value="id"
label="Tijdslot"
@@ -251,12 +256,20 @@ function onSubmit() {
</InfoTooltip>
</template>
<template #item="{ props: itemProps, item }">
<template #item="{ props: itemProps, item, index }">
<!-- Group header: rendered when event_name changes -->
<VListSubheader
v-if="hasGroups && isNewGroup(index)"
:title="item.raw.groupName"
/>
<VListItem
v-if="!item.raw._isGroupHeader"
v-bind="itemProps"
:class="{ 'opacity-65': item.raw._isDimmed }"
:class="{ 'time-slot-dimmed': item.raw._isDimmed }"
>
<template #title>
{{ item.raw.name }}
</template>
<template #append>
<span class="text-caption text-medium-emphasis">
{{ item.raw.timeRange }}
@@ -412,3 +425,10 @@ function onSubmit() {
</VCard>
</VDialog>
</template>
<style scoped>
/* Dimmed festival-level time slots in sub-event dropdown (scenario A/D) */
:deep(.time-slot-dimmed) {
opacity: 0.65;
}
</style>