<template> <tr :class="rowClass" v-bind="attrs"> <td> <div class="tw-w-12 tw-aspect-square rounded-lg tw-bg-gray-400 tw-overflow-hidden"> <img v-if="noteImage" class="tw-object-cover tw-h-full tw-w-full tw-max-w-full tw-max-h-full" :src="noteImage.image" sizes="100px" :srcset=" noteImage.thumbnails .filter(({ width, height }) => width === height) .map(({ width, url }) => `${url} ${width}w`) .join(',') " alt="" /> </div> </td> <td> <template v-if="!isLoadingNote"> <template v-if="note"> {{ note.title }} </template> <span v-else class="tw-text-sm tw-text-gray-500"> {{ t('noneSetMasculine') }} </span> </template> </td> <td> {{ prettyDateTime(timeslot.start) }} </td> <td> {{ secondsToDurationString(duration) }} </td> <td> <template v-if="playlist"> <template v-if="playlist.description?.trim()">{{ playlist.description }}</template> <template v-else>{{ playlist.id }}</template> </template> <span v-else class="tw-text-sm tw-text-gray-500"> {{ t('noneSetFeminine') }} </span> </td> <td> <div class="tw-flex tw-gap-2 tw-justify-end"> <button type="button" class="btn btn-sm btn-default" :title="t('showTimeslots.editDescription')" @click="editNote" > <icon-system-uicons-pen /> </button> <button type="button" class="btn btn-sm btn-default" :title="t('showTimeslots.editPlaylist')" @click="editPlaylist" > <icon-ph-playlist-light /> </button> </div> </td> </tr> <Teleport to="body"> <PlaylistModal ref="playlistModal" :timeslot="timeslot" /> <NoteEditorModal v-model="localNoteId" :is-open="showNoteEditor" :timeslot="timeslot" @show="showNoteEditor = $event" /> </Teleport> </template> <script lang="ts" setup> import { useObjectFromStore } from '@rokoli/bnb/drf' import { parseISO } from 'date-fns' import { computed, ref, useAttrs } from 'vue' import { useStore } from 'vuex' import { useI18n } from '@/i18n' import { TimeSlot } from '@/types' import { useNoteStore } from '@/stores/notes' import { useImage } from '@/stores/images' import { usePretty } from '@/mixins/prettyDate' import NoteEditorModal from './NoteEditorModal.vue' import PlaylistModal from './PlaylistSelector.vue' import { calculateDurationSeconds, secondsToDurationString } from '@/util' defineOptions({ compatConfig: { MODE: 3 }, inheritAttrs: false, ATTR_FALSE_VALUE: false, }) const props = defineProps<{ timeslot: TimeSlot }>() const attrs = useAttrs() const { t } = useI18n() const { prettyDateTime } = usePretty() const store = useStore() const noteStore = useNoteStore() // TODO: once the timeslot store is migrated to pinia we actually want to trigger // an API update for this timeslot. const localNoteId = ref(props.timeslot.noteId) const { obj: note, isLoading: isLoadingNote } = useObjectFromStore(localNoteId, noteStore) const noteImage = useImage(computed(() => note.value?.imageId ?? null)) const duration = computed(() => calculateDurationSeconds(props.timeslot.start, props.timeslot.end)) const playlist = computed<{ id: number; description: string } | null>(() => props.timeslot.playlistId ? store.getters['playlists/getPlaylistById'](props.timeslot.playlistId) ?? null : null, ) const rowClass = computed(() => { const now = new Date() const startDate = parseISO(props.timeslot.start) const endDate = new Date(startDate.getTime() + duration.value * 1000) return { 'tw-opacity-50': now > endDate, } }) const playlistModal = ref() const showNoteEditor = ref(false) function editNote() { showNoteEditor.value = true } function editPlaylist() { playlistModal.value.open(props.timeslot.scheduleId) } </script>