diff --git a/src/components/shows/Schedules.vue b/src/components/shows/Schedules.vue index 0fa47e3875c8cd1d8b2cb2eadae134de583496b2..e8192d3d1df622fe86afe3c973f28e9b5c402d63 100644 --- a/src/components/shows/Schedules.vue +++ b/src/components/shows/Schedules.vue @@ -1,77 +1,120 @@ <template> - <div - v-if="relevantSchedules.length > 0" - :class="{ - 'show-schedules': true, - expandable: isExpandable, - collapsed: isExpandable && isCollapsed, - }" - @click="isCollapsed = !isCollapsed" - > - <table class="table b-table table-striped border"> - <thead> - <tr> - <th class="top-header"> - {{ uppercaseFirst(t('showSchedules.schedule')) }} - </th> - <th class="top-header text-right font-weight-normal"> - {{ relevantSchedules.length }} - {{ - relevantSchedules.length === 1 - ? t('showSchedules.schedule') - : t('showSchedules.schedules') - }} - </th> - </tr> - </thead> - - <tbody> - <tr v-for="schedule in relevantSchedules" :key="schedule.id"> - <td> - {{ renderRruleForSchedule(schedule) }} - </td> - <td class="text-right"> - <template v-if="schedule.firstDate === schedule.lastDate"> - {{ prettyDate(parseISO(schedule.firstDate)) }} <br /> - </template> - - {{ prettyHours(schedule.startTime) }} - {{ prettyHours(schedule.endTime) }} - </td> - </tr> - </tbody> - </table> - - <div v-if="isExpandable" class="collapser"> - {{ t('showSchedules.collapse') }} - </div> - </div> - - <div v-else class="border p-4 mb-4"> - {{ t('showSchedules.noSchedulesAvailable') }} - </div> + <section> + <header class="tw-flex tw-items-center tw-gap-6 tw-mt-12 tw-mb-4"> + <SectionTitle>{{ t('showSchedules.title') }}</SectionTitle> + + <button + v-if="collapseSchedules" + type="button" + class="btn btn-default" + aria-controls="schedule-collapsable" + :aria-expanded="!isCollapsed" + @click="isCollapsed = !isCollapsed" + > + {{ t(isCollapsed ? 'showSchedules.showAll' : 'showSchedules.hide') }} + </button> + </header> + + <Collapse + id="schedule-collapsable" + v-model:is-collapsed="isCollapsed" + class="tw-mb-12" + peek="10rem" + > + <div class="aura-table-wrapper tw-mb-0"> + <table class="aura-table"> + <thead> + <tr> + <th scope="col"> + {{ t('showSchedules.rhythm') }} + </th> + <th scope="col"> + {{ t('showSchedules.firstBroadcast') }} + </th> + <th scope="col"> + {{ t('showSchedules.lastBroadcast') }} + </th> + <th scope="col" class="tw-text-right"> + {{ t('showSchedules.times') }} + </th> + </tr> + </thead> + + <tbody> + <tr + v-for="schedule in relevantSchedules" + :key="schedule.id" + :aria-label=" + t( + schedule.lastDate + ? 'showSchedules.scheduleDescriptionFinite' + : 'showSchedules.scheduleDescription', + { + rhythm: renderRruleForSchedule(schedule), + startDate: prettyDate(parseISO(schedule.firstDate)), + endDate: schedule.lastDate ? prettyDate(parseISO(schedule.lastDate)) : '', + startTime: prettyHours(schedule.startTime), + endTime: prettyHours(schedule.endTime), + }, + ) + " + > + <th scope="row"> + {{ renderRruleForSchedule(schedule) }} + </th> + <td :colspan="schedule.firstDate === schedule.lastDate ? 2 : 1"> + {{ prettyDate(parseISO(schedule.firstDate)) }} + </td> + <td v-if="schedule.firstDate !== schedule.lastDate"> + <template v-if="schedule.lastDate"> + {{ prettyDate(parseISO(schedule.lastDate)) }} + </template> + <template v-else> offen </template> + </td> + <td class="tw-text-right"> + {{ prettyHours(schedule.startTime) }} - {{ prettyHours(schedule.endTime) }} + </td> + </tr> + <tr v-if="relevantSchedules.length === 0"> + <td colspan="2"> + <p>{{ t('showSchedules.noSchedulesAvailable') }}</p> + </td> + </tr> + </tbody> + </table> + </div> + </Collapse> + </section> </template> -<script setup> +<script lang="ts" setup> import { parseISO } from 'date-fns' +import { computed, ref, watch } from 'vue' import { useStore } from 'vuex' + import { useRRule } from '@/mixins/rrules' import { usePretty } from '@/mixins/prettyDate' import { lowercaseFirst, uppercaseFirst, useSelectedShow } from '@/utilities' -import { computed, ref, watch } from 'vue' import { useI18n } from '@/i18n' +import SectionTitle from '@/components/generic/SectionTitle.vue' +import Collapse from '@/components/generic/Collapse.vue' +import { Schedule } from '@/types' + +defineOptions({ + compatConfig: { MODE: 3 }, +}) const { t } = useI18n() const { rruleRender } = useRRule() const { prettyWeekday, prettyDate, prettyHours } = usePretty() const store = useStore() const selectedShow = useSelectedShow() -const schedules = computed(() => store.state.shows.schedules) +const schedules = computed<Schedule[]>(() => store.state.shows.schedules) const relevantSchedules = computed(() => schedules.value.filter((s) => !isPossiblyPastSchedule(s))) -const isExpandable = computed(() => relevantSchedules.value.length > 2) - -const isCollapsed = ref(true) +const collapseSchedules = computed(() => relevantSchedules.value.length > 2) +const isCollapsed = ref(!collapseSchedules.value) -function isPossiblyPastSchedule(schedule) { +function isPossiblyPastSchedule(schedule: Schedule) { // Recurrence rules get very complex very fast, so we only give // a definitive answer if a lastDate is set for this schedule. if (!schedule.lastDate) { @@ -89,7 +132,7 @@ function updateSchedules() { } } -function renderRruleForSchedule(schedule) { +function renderRruleForSchedule(schedule: Schedule) { if (schedule.rruleId < 3) { return uppercaseFirst(rruleRender(schedule.rruleId)) } @@ -103,57 +146,3 @@ function renderRruleForSchedule(schedule) { // TODO[#127]: this belongs in the store watch(selectedShow, updateSchedules, { immediate: true }) </script> - -<script> -export default { - compatConfig: { - MODE: 3, - }, -} -</script> - -<style scoped> -.show-schedules { - box-sizing: border-box; - width: 100%; - margin-bottom: 0.5rem; - position: relative; - overflow-y: hidden; -} - -.show-schedules:hover { - cursor: pointer; -} - -.show-schedules.expandable { - margin-bottom: 1rem; -} - -.show-schedules.collapsed { - height: 10rem; -} - -.show-schedules.collapsed::after { - content: 'click to expand'; - display: flex; - justify-content: center; - align-items: center; - background: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 9) 66%); - height: 4rem; - width: 100%; - z-index: 1; - bottom: 0; - position: absolute; -} - -.show-schedules.collapsed::after, -.collapser { - text-align: center; - color: var(--info); -} - -.show-schedules:hover:after, -.show-schedules:hover .collapser { - text-decoration: underline; -} -</style> diff --git a/src/i18n/de.js b/src/i18n/de.js index 30d6ad63f90e09a76eb248968e0939902fc15b6f..835934a6ca4ad35092c722537be76a1e817d5797 100644 --- a/src/i18n/de.js +++ b/src/i18n/de.js @@ -208,11 +208,18 @@ export default { }, showSchedules: { - schedule: 'Programm', - schedules: 'Programme', - noSchedulesAvailable: 'Es gibt für diese Sendereihe noch kein Ausstrahlungs-Schema', - - collapse: 'Zum Verkleinern klicken', + title: 'Ausstrahlungsschema', + times: 'Sendezeiten', + rhythm: 'Rhythmus', + firstBroadcast: 'Erste Ausstrahlung', + lastBroadcast: 'Letzte Ausstrahlung', + noSchedulesAvailable: 'Es gibt für diese Sendereihe noch kein Ausstrahlungsschema', + showAll: 'Alles anzeigen', + hide: 'Ausblenden', + scheduleDescription: + '%{rhythm} ab %{startDate} jeweils von %{startTime} Uhr bis %{endTime} Uhr.', + scheduleDescriptionFinite: + '%{rhythm} ab %{startDate} bis zum %{endDate} jeweils von %{startTime} Uhr bis %{endTime} Uhr.', }, showTimeslots: { diff --git a/src/i18n/en.js b/src/i18n/en.js index e593d53f84f11634e88668b434fce58e604f6c55..c7a490fab01410e6aa2edd2294d9889106fc80bb 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -208,11 +208,18 @@ export default { }, showSchedules: { - schedule: 'schedule', - schedules: 'schedules', + title: 'Schedules', + times: 'Airtime', + rhythm: 'Rhythm', + firstBroadcast: 'First airing', + lastBroadcast: 'Last airing', noSchedulesAvailable: 'There are currently no schedules for this show', - - collapse: 'click to collapse', + showAll: 'Show All', + hide: 'Hide', + scheduleDescription: + '%{rhythm} beginning %{startDate} from %{startTime} to %{endTime} respectively.', + scheduleDescriptionFinite: + '%{rhythm} beginning %{startDate} through %{endDate} from %{startTime} to %{endTime} respectively.', }, showTimeslots: { diff --git a/src/types.ts b/src/types.ts index cf8d029f97dcc695bdbd1da34b0ad5d3ac2ceeb3..a14dc1129546de0aed76f874f5c232bb3490c973 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,3 +19,4 @@ export type PaginationData = { export type TimeSlot = Required<steeringComponents['schemas']['TimeSlot']> export type Show = Required<steeringComponents['schemas']['Show']> export type Playlist = Required<tankComponents['schemas']['store.Playlist']> +export type Schedule = Required<steeringComponents['schemas']['Schedule']>