feat(events): calendar popup respects the selected category filter #115

Merged
padreug merged 2 commits from feat/calendar-respect-categories into dev 2026-06-18 12:41:18 +00:00
6 changed files with 72 additions and 7 deletions

View file

@ -71,6 +71,8 @@ const messages: LocaleMessages = {
past: 'Past',
filters: 'Filters',
clearAll: 'Clear all',
filteringBy: 'Filtering by:',
removeCategory: 'Remove {category} filter',
},
categories: {
concert: 'Concert',

View file

@ -71,6 +71,8 @@ const messages: LocaleMessages = {
past: 'Pasado',
filters: 'Filtros',
clearAll: 'Limpiar todo',
filteringBy: 'Filtrando por:',
removeCategory: 'Quitar el filtro {category}',
},
categories: {
concert: 'Concierto',

View file

@ -71,6 +71,8 @@ const messages: LocaleMessages = {
past: 'Passé',
filters: 'Filtres',
clearAll: 'Tout effacer',
filteringBy: 'Filtré par :',
removeCategory: 'Retirer le filtre {category}',
},
categories: {
concert: 'Concert',

View file

@ -72,6 +72,8 @@ export interface LocaleMessages {
past: string
filters: string
clearAll: string
filteringBy: string
removeCategory: string
}
categories: Record<string, string>
detail: {

View file

@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import {
DialogRoot,
DialogPortal,
@ -10,8 +11,10 @@ import {
DialogClose,
} from 'reka-ui'
import { X } from 'lucide-vue-next'
import { Badge } from '@/components/ui/badge'
import EventCalendarView from './EventCalendarView.vue'
import type { Event } from '../types/event'
import type { EventCategory } from '../types/category'
// A date-picker popup: the month grid (with per-day event dots) in a
// dialog. Picking a day emits selectDate and closes. Reused by the feed
@ -21,23 +24,40 @@ import type { Event } from '../types/event'
// DialogContent) so it can use a light, blurred overlay instead of the
// usual opaque dark dim the feed stays visible, softly blurred, behind
// the frosted-glass panel.
const props = defineProps<{
const props = withDefaults(
defineProps<{
open: boolean
events: Event[]
title: string
description: string
}>()
// Active category filter mirrored from the feed. Rendered as
// deselectable chips so the user can see and loosen what's
// narrowing the calendar without closing it. Defaults to none for
// callers that don't filter by category (e.g. My Tickets).
selectedCategories?: EventCategory[]
}>(),
{
selectedCategories: () => [],
},
)
const emit = defineEmits<{
'update:open': [value: boolean]
selectDate: [date: Date]
'toggle-category': [category: EventCategory]
}>()
const { t } = useI18n()
const isOpen = computed({
get: () => props.open,
set: (v) => emit('update:open', v),
})
function categoryLabel(cat: EventCategory): string {
return t(`events.categories.${cat}`, cat)
}
function onSelectDate(date: Date) {
emit('selectDate', date)
isOpen.value = false
@ -62,6 +82,29 @@ function onSelectDate(date: Date) {
{{ description }}
</DialogDescription>
</div>
<!-- Active category filter only the selected categories, each
removable. Clicking deselects via the parent's toggle, which
reactively re-narrows the calendar dots without closing. -->
<div
v-if="selectedCategories.length"
class="flex flex-wrap items-center gap-1.5"
>
<span class="text-xs text-muted-foreground">
{{ t('events.filters.filteringBy') }}
</span>
<Badge
v-for="cat in selectedCategories"
:key="cat"
variant="secondary"
class="cursor-pointer gap-1 text-xs select-none hover:opacity-80 transition-opacity"
:aria-label="t('events.filters.removeCategory', { category: categoryLabel(cat) })"
@click="emit('toggle-category', cat)"
>
{{ categoryLabel(cat) }}
<X class="w-3 h-3" />
</Badge>
</div>
<EventCalendarView :events="events" picker-mode @select-date="onSelectDate" />
<DialogClose
class="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none"

View file

@ -61,6 +61,18 @@ const {
const filtersOpen = ref(false)
const calendarOpen = ref(false)
// Events feeding the calendar popup's per-day dots. Respects the active
// category filter (so the calendar reflects what the user is browsing),
// but not the temporal/day filters the calendar is for picking any
// date. No categories selected all events.
const calendarEvents = computed(() =>
selectedCategories.value.length
? allEvents.value.filter(
(e) => e.category && selectedCategories.value.includes(e.category),
)
: allEvents.value,
)
// Human label for the active day filter, shown as a removable chip.
const selectedDateLabel = computed(() =>
selectedDate.value
@ -255,10 +267,12 @@ onBeforeRouteLeave(() => {
day filters the feed to it and closes. -->
<EventCalendarPopup
v-model:open="calendarOpen"
:events="allEvents"
:events="calendarEvents"
:selected-categories="selectedCategories"
:title="t('events.nav.calendar', 'Calendar')"
:description="t('events.calendar.pickDay', 'Pick a day to see its events')"
@select-date="onSelectDate"
@toggle-category="toggleCategory"
/>
</div>
</template>