Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
<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>
{{ time.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">
<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" />
<NoteEditorModal
v-model="localNoteId"
:is-open="showNoteEditor"
:timeslot-id="timeslot.id"
@show="showNoteEditor = $event"
/>
</Teleport>
</template>
<script lang="ts" setup>
import { computed, ref, useAttrs } from 'vue'
import { useStore } from 'vuex'
import { parseISO } from 'date-fns'
import { useI18n } from '@/i18n'
import { TimeSlot } from '@/types'
import { useAPIObject } from '@/api'
import { Note, useNoteStore } from '@/stores/notes'
import { useImage } from '@/stores/images'
import { prettyDuration, usePretty } from '@/mixins/prettyDate'
import NoteEditorModal from './NoteEditorModal.vue'
import PlaylistModal from './PlaylistSelector.vue'
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.note_id)
const { obj: note, isLoading: isLoadingNote } = useAPIObject<Note>(noteStore, localNoteId)
const noteImage = useImage(computed(() => note.value?.image ?? null))
const time = computed(() => prettyDuration(props.timeslot.start, props.timeslot.end))
const playlist = computed<{ id: number; description: string } | null>(() =>
props.timeslot.playlist_id
? store.getters['playlists/getPlaylistById'](props.timeslot.playlist_id) ?? null
: null,
)
const rowClass = computed(() => {
const minutesInMs = time.value.minutes * 60 * 1000
const now = new Date()
const startDate = parseISO(props.timeslot.start)
const endDate = new Date(startDate.getTime() + minutesInMs)
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.schedule, props.timeslot.id)
}
</script>
<script lang="ts">
export default {
inheritAttrs: false,
compatConfig: {
MODE: 3,
},
}
</script>