<template> <ASection :title="t('showTimeslots.title')"> <template #header> <TimeSlotFilter v-model:start-date="formattedLocalStartTime" v-model:direction="direction" class="tw-flex tw-gap-3 tw-items-center tw-ml-auto" /> </template> <div class="aura-table-wrapper"> <table ref="tableEl" class="aura-table"> <thead class="tw-sticky tw-top-0 tw-z-10 tw-bg-white"> <tr> <th>{{ t('emissionTable.title') }}</th> <th>{{ t('emissionTable.start') }}</th> <th>{{ t('emissionTable.duration') }}</th> <th>{{ t('emissionTable.playlist') }}</th> <th class="tw-p-0" /> </tr> </thead> <tbody> <template v-for="timeslot in result.items" :key="timeslot.id"> <TimeSlotRow class="tw-transition hover:tw-bg-gray-50" :timeslot="timeslot" :is-next-up="nextBroadcastedTimeslot?.id === timeslot.id" :is-on-air="onAirTimeslot?.id === timeslot.id" /> </template> <template v-if="!hasTimeslots"> <tr> <td colspan="4"> <p class="tw-text-center tw-p-3 tw-m-0"> {{ t('showTimeslots.noTimeslotsScheduled') }} </p> </td> </tr> </template> </tbody> </table> <div class="tw-flex tw-flex-wrap tw-gap-3 tw-items-center tw-px-6 tw-mt-3 tw-py-3 tw-border-0 tw-border-t tw-border-solid tw-border-gray-200 empty:tw-hidden" > <PaginationRange v-if="result.count > 0" :pagination-data="result" /> <FormGroup v-slot="attrs" class="tw-ml-auto tw-m-0 last:tw-mr-0"> <label class="tw-flex tw-items-center tw-gap-3 tw-m-0"> <span>{{ t('showTimeslots.numberOfSlots') }}</span> <input v-model.lazy="limit" v-bind="attrs" type="number" min="1" step="1" class="tw-w-[80px] tw-self-center" /> </label> </FormGroup> <Pagination v-model="page" :items-per-page="result.itemsPerPage" :count="result.count" /> </div> </div> </ASection> </template> <script lang="ts" setup> import { usePaginatedList } from '@rokoli/bnb/drf' import { useNow, useStorage } from '@vueuse/core' import { formatISO, roundToNearestMinutes } from 'date-fns' import { computed, ref, watch, watchEffect } from 'vue' import { useI18n } from '@/i18n' import { Show } from '@/types' import { useFormattedISODate, useQuery } from '@/util' import { useTimeSlotStore } from '@/stores/timeslots' import TimeSlotFilter from './TimeSlotFilter.vue' import TimeSlotRow from '@/components/shows/TimeSlotRow.vue' import Pagination from '@/components/generic/Pagination.vue' import PaginationRange from '@/components/generic/PaginationRange.vue' import ASection from '@/components/generic/ASection.vue' import FormGroup from '@/components/generic/FormGroup.vue' const DEFAULT_TIMESLOT_LIMIT = parseInt( import.meta.env.VUE_APP_TIMESLOT_FILTER_DEFAULT_NUMSLOTS ?? 5, ) defineOptions({ compatConfig: { MODE: 3 }, ATTR_FALSE_VALUE: false, }) const props = defineProps<{ show: Show }>() const { t } = useI18n() const timeslotStore = useTimeSlotStore() const now = useNow({ interval: 60_000 }) const page = ref(1) const direction = ref<'future' | 'past'>('future') const limit = useStorage('aura:timeslotList:timeslotsPerPage', DEFAULT_TIMESLOT_LIMIT) const startTime = ref(roundToNearestMinutes(new Date())) const formattedStartTime = useFormattedISODate(startTime) const formattedLocalStartTime = useFormattedISODate(startTime, now, { stripOffset: true }) const tableEl = ref<HTMLTableElement>() const hasPageBeenModified = ref(false) const { result } = usePaginatedList(timeslotStore.listIsolated, page, limit, { events: timeslotStore.events, query: useQuery(() => ({ showIds: props.show.id, endsAfter: direction.value === 'future' ? formattedStartTime.value : undefined, endsBefore: direction.value === 'past' ? formattedStartTime.value : undefined, order: direction.value === 'future' ? 'start' : '-start', })), }) const hasTimeslots = computed(() => result.value.count > 0) const onAirTimeslot = computed(() => { const _now = formatISO(now.value) return result.value.items.find((t) => t.start <= _now && _now < t.end) ?? null }) const currentResultTime = ref(new Date()) const { result: currentResult } = usePaginatedList(timeslotStore.listIsolated, 1, 1, { events: timeslotStore.events, query: useQuery(() => ({ showIds: props.show.id, startsAfter: formatISO(currentResultTime.value), order: 'start', })), }) const nextBroadcastedTimeslot = computed(() => currentResult.value.items[0] ?? null) watch([onAirTimeslot, useNow({ interval: 15 * 60_000 })], () => { // reload from usePaginatedList doesn’t cut it, because we actually need to change the query currentResultTime.value = new Date() }) watchEffect(() => { if (page.value !== 1) { hasPageBeenModified.value = true } if (hasPageBeenModified.value && tableEl.value) { tableEl.value.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' }) } }) </script>