<template> <div> <b-modal ref="modalEmissionManagerEdit" :title="$t('scheduleEditor.titleEdit', { show: sanitizeHTML(selectedShow.name) })" size="lg" > <server-errors :errors="serverErrors" /> <div v-if="timeslot && loaded.schedule"> <p class="tw-mb-0" v-html=" $t('scheduleEditor.timeslotRuns', { firstDate: prettyDate(timeslot.start), startTime: prettyTime(timeslot.start), endTime: prettyTime(timeslot.end), }) " /> <div v-if="scheduleRRule && scheduleRRule.count === 1"> <p> {{ $t('scheduleEditor.singleEmission') }} <span v-if="!loaded.scheduleTimeslots"> <br /> <img src="/assets/radio.gif" :alt="$t('loading')" /> </span> <span v-else> <span v-if="scheduleTimeslots.length > 1" v-html=" '<br>' + $t('scheduleEditor.coexistingTimeslot', { firstDate: timeslot.start === scheduleTimeslots[0].start ? prettyDate(scheduleTimeslots[1].start) : prettyDate(scheduleTimeslots[0].start), startTime: timeslot.start === scheduleTimeslots[0].start ? prettyTime(scheduleTimeslots[1].start) : prettyTime(scheduleTimeslots[0].start), endTime: timeslot.start === scheduleTimeslots[0].start ? prettyTime(scheduleTimeslots[1].end) : prettyTime(scheduleTimeslots[0].end), }) " /> </span> </p> </div> <div v-else-if="scheduleRRule"> <SafeHTML as="p" sanitize-preset="safe-html" :html=" $t( schedule.lastDate ? 'scheduleEditor.recurringSchedule' : 'scheduleEditor.recurringScheduleNoEnd', { rrule: scheduleRRule.name, lastDate: schedule.lastDate ? prettyDate(schedule.lastDate) : '', }, ) " /> <b-table id="emission-table" striped :per-page="perPage" :current-page="currentPage" :fields="[ { key: 'start', label: $t('scheduleEditor.start') }, { key: 'end', label: $t('scheduleEditor.end') }, ]" :items="scheduleTimeslots" :busy="!loaded.scheduleTimeslots" :tbody-tr-class="trClass" > <template #cell(start)="data"> {{ prettyDateTime(data.item.start) }} </template> <template #cell(end)="data"> {{ prettyDateTime(data.item.end) }} </template> </b-table> <div class="tw-w-full tw-flex tw-justify-end"> <b-pagination v-model="currentPage" :total-rows="scheduleTimeslots.length" :per-page="perPage" aria-controls="emission-table" /> </div> </div> </div> <div v-else> <img src="/assets/radio.gif" :alt="$t('loading')" /> </div> <hr /> <h4>{{ $t('scheduleEditor.addRepetition') }}</h4> <b-row> <b-col cols="6"> <label class="tw-leading-loose"> {{ $t('scheduleEditor.whenToRepeat') }} <b-form-select v-model="repetitionRule" :options="repetitionOptions" /> </label> <b-checkbox v-if="repetitionRule !== 4" v-model="useSameTime"> {{ $t('scheduleEditor.useSameTime') }} </b-checkbox> </b-col> <b-col v-if="!useSameTime" cols="6"> <label class="tw-leading-loose"> {{ $t('scheduleEditor.repeatAt') }} <b-form-input v-model="repetitionTime" type="time" /> </label> </b-col> </b-row> <b-row v-if="repetitionRule === 3" class="tw-mt-4"> <b-col cols="6"> <label class="tw-leading-loose"> {{ $t('scheduleEditor.addNoOfDays') }} <b-form-input v-model="addNoOfDays" type="number" /> </label> <b-checkbox v-model="onlyBusinessDays"> {{ $t('scheduleEditor.onlyBusinessDays') }} </b-checkbox> </b-col> </b-row> <b-row class="my-4"> <b-col> <b-button variant="primary" size="sm" @click="createRepetitionSchedule"> {{ $t('scheduleEditor.addRepetition') }} </b-button> </b-col> </b-row> <template #modal-footer> <div v-if="loaded.scheduleTimeslots" class="tw-w-full tw-flex tw-justify-between tw-items-center" > <div class="tw-space-x-2"> <template v-if="isInTheFuture(timeslot)"> <b-button variant="danger" size="sm" @click="deleteFullSchedule(schedule.id)"> <span v-if="scheduleTimeslots.length === 1">{{ $t('scheduleEditor.delete.delete') }}</span> <span v-else-if="scheduleRRule?.count === 1">{{ $t('scheduleEditor.delete.both') }}</span> <span v-else>{{ $t('scheduleEditor.delete.scheduleTimeslots') }}</span> </b-button> <b-button v-if="scheduleRRule?.count !== 1 && scheduleTimeslots.length > 1" variant="danger" size="sm" @click="deleteSingleTimeslot(schedule.id, timeslot.id)" > {{ $t('scheduleEditor.delete.timeslot') }} </b-button> </template> <b-button v-if="scheduleRRule?.count !== 1 && scheduleTimeslots.length > 1" variant="danger" size="sm" @click="deleteAllFutureTimeslots(schedule.id, timeslot.id)" > {{ $t('scheduleEditor.delete.allTimeslots') }} </b-button> </div> </div> <div v-else> <img src="/assets/radio.gif" :alt="$t('loading')" /> </div> </template> </b-modal> <b-modal ref="modalEmissionManagerDeleteTimeslots" :title="$t('scheduleEditor.delete.timeslotsTitle')" size="lg" centered hide-footer no-close-on-esc no-close-on-backdrop > <div> <img src="/assets/radio.gif" :alt="$t('loading')" /> <b-progress :value="deletion.count" :max="deletion.amount" variant="primary" animated /> </div> </b-modal> </div> </template> <script> import { mapStores } from 'pinia' import { mapGetters } from 'vuex' import prettyDate, { formatSeconds, hmsToSeconds } from '../../mixins/prettyDate' import ServerErrors from '@/components/ServerErrors.vue' import { sanitizeHTML } from '@/util' import { useRRuleStore } from '@/stores/rrules' import SafeHTML from '@/components/generic/SafeHTML.js' export default { components: { SafeHTML, ServerErrors }, mixins: [prettyDate], emits: ['conflict', 'update'], data() { return { currentPage: 1, perPage: 5, timeslot: null, deletion: { amount: 0, count: 0, }, useSameTime: true, onlyBusinessDays: false, addNoOfDays: 1, repetitionRule: 1, repetitionTime: '', serverErrors: [], } }, computed: { loaded() { return { shows: this.$store.state.shows.loaded.shows, schedule: this.$store.state.shows.loaded.schedule, scheduleTimeslots: this.$store.state.shows.loaded.scheduleTimeslots, } }, repetitionOptions() { return [ { value: 1, text: this.$t('scheduleEditor.repetition.followingDay') }, { value: 2, text: this.$t('scheduleEditor.repetition.followingBusinessDay') }, { value: 3, text: this.$t('scheduleEditor.repetition.numberOfDaysLater') }, ] }, ...mapGetters({ selectedShow: 'shows/selectedShow', schedule: 'shows/schedule', scheduleTimeslots: 'shows/scheduleTimeslots', }), ...mapStores(useRRuleStore), scheduleRRule() { return this.rrulesStore.itemMap.get(this.schedule.rruleId) }, }, methods: { sanitizeHTML, trClass(item, type) { if (!item || type !== 'row') { return '' } return item.id === this.timeslot.id ? 'table-info' : '' }, isInTheFuture(timeslot) { const start = new Date(timeslot.start) const now = new Date() return start > now }, async createRepetitionSchedule() { const { onlyBusinessDays, addNoOfDays } = this.getRepetitionParameters() const { firstDate, rruleId, lastDate, defaultPlaylistId, automationId, byWeekday } = this.schedule let { startTime, endTime } = this.schedule if (this.repetitionTime.length > 0) { const newStartTime = hmsToSeconds(this.repetitionTime) const oldStartTime = hmsToSeconds(startTime) const oldEndTime = hmsToSeconds(endTime) startTime = formatSeconds(newStartTime, true) endTime = formatSeconds(newStartTime + (oldEndTime - oldStartTime), true) } const newSchedule = { schedule: { firstDate, startTime, endTime, rruleId, lastDate, defaultPlaylistId, automationId, byWeekday, isRepetition: true, addBusinessDaysOnly: onlyBusinessDays, addDaysNo: parseInt(addNoOfDays, 10), }, } this.serverErrors = [] try { await this.$store.dispatch('shows/submitSchedule', { showId: this.selectedShow.id, schedule: newSchedule, }) this.$emit('update') } catch (e) { if (e.response?.status === 409) { this.$log.debug('Timeslot conflict. Switching to resolve mode.') this.$emit('conflict', e.response.data) } else { this.serverErrors = e.errors ?? [] } } finally { this.$refs.modalEmissionManagerEdit.hide() } }, deleteFullSchedule(id) { this.$store.dispatch('shows/deleteSchedule', { showId: this.selectedShow.id, scheduleId: id, callback: () => { this.$emit('update') this.$refs.modalEmissionManagerEdit.hide() }, }) }, deleteSingleTimeslot(scheduleId, timeslotId) { this.$store.dispatch('shows/deleteTimeslot', { showId: this.selectedShow.id, scheduleId: scheduleId, timeslotId: timeslotId, callback: () => { this.$emit('update') this.$refs.modalEmissionManagerEdit.hide() }, }) }, deleteAllFutureTimeslots(scheduleId) { const startDate = new Date(this.timeslot.start) const toDelete = [] for (const slot of this.scheduleTimeslots) { if (new Date(slot.start) >= startDate) { toDelete.push(slot.id) } } if (toDelete.length === this.scheduleTimeslots.length) { this.$log.debug('deleting full schedule') this.deleteFullSchedule(scheduleId) } else { this.deletion.amount = toDelete.length this.deletion.count = 0 this.$refs.modalEmissionManagerDeleteTimeslots.show() for (const i in toDelete) { this.$log.debug('Deleting timeslot', toDelete[i]) this.$store.dispatch('shows/deleteTimeslot', { showId: this.selectedShow.id, scheduleId: scheduleId, timeslotId: toDelete[i], callback: () => { this.deletion.count++ this.$log.debug('deleted ' + this.deletion.count + ' timeslots') if (this.deletion.count === this.deletion.amount) { this.$emit('update') this.$refs.modalEmissionManagerDeleteTimeslots.hide() this.$refs.modalEmissionManagerEdit.hide() } }, }) } } }, loadSchedule(scheduleId) { this.$store.dispatch('shows/fetchSchedule', { showId: this.selectedShow.id, scheduleId, callback: () => { this.loadScheduleTimeslots(scheduleId) }, }) }, loadScheduleTimeslots(scheduleId) { this.$store.dispatch('shows/fetchTimeslots', { scheduleId, start: this.schedule.firstDate, end: this.schedule.lastDate, }) }, getRepetitionParameters() { if (this.repetitionRule == 1) { return { onlyBusinessDays: false, addNoOfDays: 1, } } if (this.repetitionRule == 2) { return { onlyBusinessDays: true, addNoOfDays: 1, } } return { onlyBusinessDays: this.onlyBusinessDays, addNoOfDays: this.addNoOfDays, } }, // initialise a new schedule and open the modal open(timeslot) { const timeslotIndex = this.scheduleTimeslots.findIndex((item) => item.id === timeslot.id) let timeslotPage = Math.ceil(timeslotIndex / this.perPage) const timeslotIsFirstEntryOnPage = timeslotIndex === timeslotPage * this.perPage // The math above is slightly off for every first entry on the page // So we add 1 to adjust the page to the correct one if (timeslotIsFirstEntryOnPage) { timeslotPage += 1 } this.timeslot = timeslot this.currentPage = timeslotPage this.$refs.modalEmissionManagerEdit.show() this.loadSchedule(timeslot.scheduleId) }, }, } </script>