From 246962c14791583b9c122559d492a443f0446718 Mon Sep 17 00:00:00 2001
From: Konrad Mohrfeldt <km@roko.li>
Date: Wed, 24 Jul 2024 01:11:07 +0200
Subject: [PATCH] fix(Calendar): show loading indicator while program is
 processed

---
 src/Pages/Calendar.vue | 126 +++++++++++++++++++++--------------------
 1 file changed, 66 insertions(+), 60 deletions(-)

diff --git a/src/Pages/Calendar.vue b/src/Pages/Calendar.vue
index 14cbb59f..9fc7defe 100644
--- a/src/Pages/Calendar.vue
+++ b/src/Pages/Calendar.vue
@@ -95,7 +95,7 @@
 import { useErrorList } from '@rokoli/bnb/drf'
 import { computedAsync, createTemplatePromise } from '@vueuse/core'
 import { addDays, endOfDay, parseISO, startOfDay } from 'date-fns'
-import { ref, watchEffect } from 'vue'
+import { computed, ref, watchEffect } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 
 import { useI18n } from '@/i18n'
@@ -152,81 +152,87 @@ const conflictResponse = ref<ScheduleConflictResponse | undefined>()
 const {
   program,
   reload: reloadProgram,
-  isLoading: isUpdatingData,
+  isLoading: isLoadingProgram,
 } = useProgramSlots({
   start: () => startOfDay(currentStart.value),
   end: () => endOfDay(currentEnd.value),
 })
 const error = ref<Error | null>(null)
 const errorList = useErrorList(error)
+const isUpdatingData = computed(() => isLoadingProgram.value || isGeneratingEvents.value)
 
 const calendarEventsCache = new Map()
-const calendarEvents = computedAsync(async () => {
-  const result = []
+const isGeneratingEvents = ref(false)
+const calendarEvents = computedAsync(
+  async () => {
+    const result = []
 
-  // fetch relevant objects upfront
-  await Promise.allSettled([
-    showStore.retrieveMultiple(
-      program.value.map((e) => e.showId),
-      { useCached: true },
-    ),
-    timeslotStore.retrieveMultiple(
-      program.value.map((e) => e.timeslotId).filter((id) => id !== null) as number[],
-      { useCached: true },
-    ),
-    playlistStore.retrieveMultiple(
-      program.value.map((e) => e.playlistId).filter((id) => id !== null) as number[],
-      { useCached: true },
-    ),
-  ])
+    // fetch relevant objects upfront
+    await Promise.allSettled([
+      showStore.retrieveMultiple(
+        program.value.map((e) => e.showId),
+        { useCached: true },
+      ),
+      timeslotStore.retrieveMultiple(
+        program.value.map((e) => e.timeslotId).filter((id) => id !== null) as number[],
+        { useCached: true },
+      ),
+      playlistStore.retrieveMultiple(
+        program.value.map((e) => e.playlistId).filter((id) => id !== null) as number[],
+        { useCached: true },
+      ),
+    ])
 
-  for (const entry of program.value) {
-    const cacheKey = [entry.id, entry.timeslotId, entry.playlistId].join(':')
-    const cachedSlot = calendarEventsCache.get(cacheKey)
+    for (const entry of program.value) {
+      const cacheKey = [entry.id, entry.timeslotId, entry.playlistId].join(':')
+      const cachedSlot = calendarEventsCache.get(cacheKey)
 
-    if (cachedSlot) {
-      result.push(cachedSlot)
-      continue
-    }
+      if (cachedSlot) {
+        result.push(cachedSlot)
+        continue
+      }
 
-    const isEmpty = entry.playlistId === null
-    const emptyText = isEmpty ? `: ${t('calendar.empty')} âš ` : ''
-    const ts = entry.timeslotId
-      ? await timeslotStore.retrieve(entry.timeslotId, { useCached: true })
-      : null
-    const show = await showStore.retrieve(entry.showId, { useCached: true })
-    const durationMinutes =
-      calculateDurationSeconds(ts?.start ?? entry.start, ts?.end ?? entry.end, false) / 60
-    const isOwner =
-      show?.ownerIds?.includes?.(authStore?.steeringUser?.id as SteeringUser['id']) ?? false
-    const className = ['calendar-event', isOwner ? 'is-mine' : 'is-not-mine']
-    if (durationMinutes < 0) className.push('is-invalid')
-    if (entry.timeslotId) className.push('is-scheduled')
-    else className.push('is-generated')
-    if (entry.timeslotId === null || radioSettings.value?.program.fallback.showId === show?.id)
-      className.push('is-fallback')
-    else className.push('is-normal')
+      const isEmpty = entry.playlistId === null
+      const emptyText = isEmpty ? `: ${t('calendar.empty')} âš ` : ''
+      const ts = entry.timeslotId
+        ? await timeslotStore.retrieve(entry.timeslotId, { useCached: true })
+        : null
+      const show = await showStore.retrieve(entry.showId, { useCached: true })
+      const durationMinutes =
+        calculateDurationSeconds(ts?.start ?? entry.start, ts?.end ?? entry.end, false) / 60
+      const isOwner =
+        show?.ownerIds?.includes?.(authStore?.steeringUser?.id as SteeringUser['id']) ?? false
+      const className = ['calendar-event', isOwner ? 'is-mine' : 'is-not-mine']
+      if (durationMinutes < 0) className.push('is-invalid')
+      if (entry.timeslotId) className.push('is-scheduled')
+      else className.push('is-generated')
+      if (entry.timeslotId === null || radioSettings.value?.program.fallback.showId === show?.id)
+        className.push('is-fallback')
+      else className.push('is-normal')
 
-    const title = sanitizeHTML(show?.name ?? '') + emptyText
-    const slot = {
-      id: entry.id,
-      start: entry.start,
-      end: entry.end,
-      className: className.join(' '),
-      title,
-      display: entry.timeslotId === null ? 'block' : 'auto',
-      extendedProps: {
+      const title = sanitizeHTML(show?.name ?? '') + emptyText
+      const slot = {
+        id: entry.id,
+        start: entry.start,
+        end: entry.end,
+        className: className.join(' '),
         title,
-        id: entry.timeslotId,
-        durationMinutes: Math.abs(durationMinutes),
-      },
+        display: entry.timeslotId === null ? 'block' : 'auto',
+        extendedProps: {
+          title,
+          id: entry.timeslotId,
+          durationMinutes: Math.abs(durationMinutes),
+        },
+      }
+      calendarEventsCache.set(cacheKey, slot)
+      result.push(slot)
     }
-    calendarEventsCache.set(cacheKey, slot)
-    result.push(slot)
-  }
 
-  return result
-}, [])
+    return result
+  },
+  [],
+  { evaluating: isGeneratingEvents },
+)
 
 function syncDateRange({ start, end }: { start: Date; end: Date }) {
   currentStart.value = start
-- 
GitLab