Merge pull request 'feat(events): calendar popup respects the selected category filter' (#115) from feat/calendar-respect-categories into dev
Reviewed-on: #115
This commit is contained in:
commit
83ea3e609c
6 changed files with 72 additions and 7 deletions
|
|
@ -71,6 +71,8 @@ const messages: LocaleMessages = {
|
||||||
past: 'Past',
|
past: 'Past',
|
||||||
filters: 'Filters',
|
filters: 'Filters',
|
||||||
clearAll: 'Clear all',
|
clearAll: 'Clear all',
|
||||||
|
filteringBy: 'Filtering by:',
|
||||||
|
removeCategory: 'Remove {category} filter',
|
||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
concert: 'Concert',
|
concert: 'Concert',
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@ const messages: LocaleMessages = {
|
||||||
past: 'Pasado',
|
past: 'Pasado',
|
||||||
filters: 'Filtros',
|
filters: 'Filtros',
|
||||||
clearAll: 'Limpiar todo',
|
clearAll: 'Limpiar todo',
|
||||||
|
filteringBy: 'Filtrando por:',
|
||||||
|
removeCategory: 'Quitar el filtro {category}',
|
||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
concert: 'Concierto',
|
concert: 'Concierto',
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@ const messages: LocaleMessages = {
|
||||||
past: 'Passé',
|
past: 'Passé',
|
||||||
filters: 'Filtres',
|
filters: 'Filtres',
|
||||||
clearAll: 'Tout effacer',
|
clearAll: 'Tout effacer',
|
||||||
|
filteringBy: 'Filtré par :',
|
||||||
|
removeCategory: 'Retirer le filtre {category}',
|
||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
concert: 'Concert',
|
concert: 'Concert',
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,8 @@ export interface LocaleMessages {
|
||||||
past: string
|
past: string
|
||||||
filters: string
|
filters: string
|
||||||
clearAll: string
|
clearAll: string
|
||||||
|
filteringBy: string
|
||||||
|
removeCategory: string
|
||||||
}
|
}
|
||||||
categories: Record<string, string>
|
categories: Record<string, string>
|
||||||
detail: {
|
detail: {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import {
|
import {
|
||||||
DialogRoot,
|
DialogRoot,
|
||||||
DialogPortal,
|
DialogPortal,
|
||||||
|
|
@ -10,8 +11,10 @@ import {
|
||||||
DialogClose,
|
DialogClose,
|
||||||
} from 'reka-ui'
|
} from 'reka-ui'
|
||||||
import { X } from 'lucide-vue-next'
|
import { X } from 'lucide-vue-next'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
import EventCalendarView from './EventCalendarView.vue'
|
import EventCalendarView from './EventCalendarView.vue'
|
||||||
import type { Event } from '../types/event'
|
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
|
// 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
|
// 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
|
// DialogContent) so it can use a light, blurred overlay instead of the
|
||||||
// usual opaque dark dim — the feed stays visible, softly blurred, behind
|
// usual opaque dark dim — the feed stays visible, softly blurred, behind
|
||||||
// the frosted-glass panel.
|
// the frosted-glass panel.
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
open: boolean
|
open: boolean
|
||||||
events: Event[]
|
events: Event[]
|
||||||
title: string
|
title: string
|
||||||
description: 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<{
|
const emit = defineEmits<{
|
||||||
'update:open': [value: boolean]
|
'update:open': [value: boolean]
|
||||||
selectDate: [date: Date]
|
selectDate: [date: Date]
|
||||||
|
'toggle-category': [category: EventCategory]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const isOpen = computed({
|
const isOpen = computed({
|
||||||
get: () => props.open,
|
get: () => props.open,
|
||||||
set: (v) => emit('update:open', v),
|
set: (v) => emit('update:open', v),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function categoryLabel(cat: EventCategory): string {
|
||||||
|
return t(`events.categories.${cat}`, cat)
|
||||||
|
}
|
||||||
|
|
||||||
function onSelectDate(date: Date) {
|
function onSelectDate(date: Date) {
|
||||||
emit('selectDate', date)
|
emit('selectDate', date)
|
||||||
isOpen.value = false
|
isOpen.value = false
|
||||||
|
|
@ -62,6 +82,29 @@ function onSelectDate(date: Date) {
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</div>
|
</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" />
|
<EventCalendarView :events="events" picker-mode @select-date="onSelectDate" />
|
||||||
<DialogClose
|
<DialogClose
|
||||||
class="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none"
|
class="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none"
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,18 @@ const {
|
||||||
const filtersOpen = ref(false)
|
const filtersOpen = ref(false)
|
||||||
const calendarOpen = 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.
|
// Human label for the active day filter, shown as a removable chip.
|
||||||
const selectedDateLabel = computed(() =>
|
const selectedDateLabel = computed(() =>
|
||||||
selectedDate.value
|
selectedDate.value
|
||||||
|
|
@ -255,10 +267,12 @@ onBeforeRouteLeave(() => {
|
||||||
day filters the feed to it and closes. -->
|
day filters the feed to it and closes. -->
|
||||||
<EventCalendarPopup
|
<EventCalendarPopup
|
||||||
v-model:open="calendarOpen"
|
v-model:open="calendarOpen"
|
||||||
:events="allEvents"
|
:events="calendarEvents"
|
||||||
|
:selected-categories="selectedCategories"
|
||||||
:title="t('events.nav.calendar', 'Calendar')"
|
:title="t('events.nav.calendar', 'Calendar')"
|
||||||
:description="t('events.calendar.pickDay', 'Pick a day to see its events')"
|
:description="t('events.calendar.pickDay', 'Pick a day to see its events')"
|
||||||
@select-date="onSelectDate"
|
@select-date="onSelectDate"
|
||||||
|
@toggle-category="toggleCategory"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue