aboutsummaryrefslogtreecommitdiffstats
path: root/web/components/EventListing.vue
diff options
context:
space:
mode:
Diffstat (limited to 'web/components/EventListing.vue')
-rw-r--r--web/components/EventListing.vue179
1 files changed, 179 insertions, 0 deletions
diff --git a/web/components/EventListing.vue b/web/components/EventListing.vue
new file mode 100644
index 0000000..5c04189
--- /dev/null
+++ b/web/components/EventListing.vue
@@ -0,0 +1,179 @@
+<script setup lang="ts">
+import { StarIcon } from 'lucide-vue-next';
+import { format, formatDistanceToNow } from 'date-fns';
+import { type Event as ScheduledEvent } from '~/stores/schedule';
+import Spinner from './Spinner.vue';
+
+const { event, showRelativeTime } = defineProps<{
+ event: ScheduledEvent;
+ showRelativeTime?: boolean;
+}>();
+
+const selectedEventStore = useSelectedEventStore();
+const favouritesStore = useFavouritesStore();
+const errorStore = useErrorStore();
+const config = useRuntimeConfig();
+
+const addingToFavourite = ref(false);
+const relativeTime = ref();
+const timer = ref();
+
+const updateRelativeTime = () => {
+ if (event.start < new Date() && event.end > new Date()) {
+ relativeTime.value = 'now';
+ } else {
+ relativeTime.value = `in ${formatDistanceToNow(event.start)}`
+ }
+};
+
+onMounted(() => {
+ if (showRelativeTime) {
+ updateRelativeTime();
+ timer.value = setInterval(updateRelativeTime, 1000);
+ }
+});
+
+onUnmounted(() => {
+ if (timer.value) {
+ clearInterval(timer.value);
+ }
+});
+
+const addFavourite = async () => {
+ addingToFavourite.value = true;
+
+ try {
+ const res = await $fetch(config.public.baseURL + '/favourites', {
+ method: 'POST',
+ body: JSON.stringify({
+ eventGuid: event.guid,
+ eventId: event.id,
+ }),
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ favouritesStore.addFavourite({
+ id: (res as any).data.id,
+ eventId: event.id,
+ eventGuid: event.guid,
+ });
+ } catch(e: any) {
+ errorStore.setError(e.data.message ?? 'An unknown error occurred');
+ } finally {
+ addingToFavourite.value = false;
+ }
+};
+
+const removeFavourite = async () => {
+ addingToFavourite.value = true;
+
+ try {
+ await $fetch(config.public.baseURL + '/favourites', {
+ method: 'DELETE',
+ body: JSON.stringify({
+ eventGuid: event.guid,
+ eventId: event.id,
+ }),
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ favouritesStore.removeFavourite({
+ eventId: event.id,
+ eventGuid: event.guid,
+ });
+ } catch(e: any) {
+ errorStore.setError(e.data.message ?? 'An unknown error occurred');
+ } finally {
+ addingToFavourite.value = false;
+ }
+
+};
+</script>
+
+<template>
+ <div class="event">
+ <div class="event-details" @click="selectedEventStore.setSelectedEvent(event)">
+ <span class="event-info">
+ <span>{{ format(event.start, "kk:mm") }} - {{ format(event.end, "kk:mm") }},</span> <span>{{ event.room }}</span> <span v-if="showRelativeTime">-</span> <span v-if="showRelativeTime" class="relative-time">{{ relativeTime }}</span>
+ </span>
+ <span class="event-title">{{ event.title }}</span>
+ <span class="event-speaker">{{ event.persons.map(p => p.name).join(", ") }}</span>
+ <span class="event-track">{{ event.track.name }}</span>
+ </div>
+ <template v-if="!addingToFavourite" class="event-button">
+ <StarIcon v-if="favouritesStore.isFavourite(event)" color="var(--color-favourite)" fill="var(--color-favourite)" class="event-button" @click="removeFavourite" />
+ <StarIcon v-else color="var(--color-text-muted)" class="event-button" @click="addFavourite" />
+ </template>
+ <template v-else>
+ <Spinner color="var(--color-text-muted)" class="event-button-loading" />
+ </template>
+ </div>
+</template>
+
+<style scoped>
+.event-details {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-direction: column;
+ line-height: 1.3;
+ cursor: pointer;
+ flex-grow: 1;
+ padding: 0.825rem 1rem;
+}
+
+.event-details:hover {
+ background-color: var(--color-hover);
+}
+
+.event {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ position: relative;
+ left: -1rem;
+ width: calc(100% + 2 * 1rem);
+}
+
+.event-title {
+ font-size: var(--text-large);
+ font-weight: 600;
+ color: var(--color-primary);
+ margin: 0;
+}
+
+.event-speaker, .event-track {
+ font-size: var(--text-small);
+ color: var(--color-text-muted);
+}
+
+.event-info {
+ font-size: var(--text-normal);
+ color: var(--color-text);
+ margin: 0;
+}
+
+.event-button, .event-button-loading {
+ height: 1.5rem;
+ width: 1.5rem;
+ padding: 0 1rem;
+ flex-shrink: 0;
+}
+
+.event-button {
+ cursor: pointer;
+}
+
+.event-button-loading {
+ cursor: progress;
+}
+
+.relative-time {
+ color: var(--color-text-success);
+}
+
+</style> \ No newline at end of file