diff --git a/src/App.vue b/src/App.vue index f1d212e..ea4a4fa 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,10 +4,7 @@ diff --git a/src/components/scheduling/boat/BoatScheduleTableComponent.vue b/src/components/scheduling/boat/BoatScheduleTableComponent.vue index b73e4e3..cc6dbea 100644 --- a/src/components/scheduling/boat/BoatScheduleTableComponent.vue +++ b/src/components/scheduling/boat/BoatScheduleTableComponent.vue @@ -70,11 +70,10 @@ import { parseTimestamp, parseDate, addToDate, - getDate, } from '@quasar/quasar-ui-qcalendar'; import CalendarHeaderComponent from './CalendarHeaderComponent.vue'; -import { ref, computed } from 'vue'; +import { ref, computed, onMounted } from 'vue'; import { useBoatStore } from 'src/stores/boat'; import { useScheduleStore } from 'src/stores/schedule'; import { useAuthStore } from 'src/stores/auth'; @@ -90,6 +89,12 @@ const selectedDate = ref(today()); const calendar = ref(null); +onMounted(async () => { + await useBoatStore().fetchBoats(); + await scheduleStore.fetchIntervals(); + await scheduleStore.fetchIntervalTemplates(); +}); + function handleSwipe({ ...event }) { event.direction === 'right' ? calendar.value?.prev() : calendar.value?.next(); } @@ -169,18 +174,24 @@ function selectBlock(event: MouseEvent, scope: DayBodyScope, block: Interval) { : (selectedBlock.value = block); } -interface BoatBlocks { - [key: string]: Interval[]; -} - -const boatBlocks = computed((): BoatBlocks => { +const boatBlocks = computed((): Record => { return scheduleStore .getIntervalsForDate(selectedDate.value) .reduce((result, tb) => { if (!result[tb.boatId]) result[tb.boatId] = []; result[tb.boatId].push(tb); return result; - }, {}); + }, >{}); +}); + +const boatReservations = computed((): Record => { + return reservationStore + .getReservationsByDate(selectedDate.value) + .reduce((result, reservation) => { + if (!result[reservation.resource]) result[reservation.resource] = []; + result[reservation.resource].push(reservation); + return result; + }, >{}); }); function getBoatBlocks(scope: DayBodyScope): Interval[] { @@ -189,9 +200,7 @@ function getBoatBlocks(scope: DayBodyScope): Interval[] { } function getBoatReservations(scope: DayBodyScope): Reservation[] { const boat = boats.value[scope.columnIndex]; - return boat - ? reservationStore.getReservationsByDate(getDate(scope.timestamp), boat.$id) - : []; + return boat ? boatReservations.value[boat.$id] : []; } // function changeEvent({ start }: { start: string }) { diff --git a/src/stores/reservation.ts b/src/stores/reservation.ts index 801a562..d6021b8 100644 --- a/src/stores/reservation.ts +++ b/src/stores/reservation.ts @@ -1,29 +1,109 @@ import { defineStore } from 'pinia'; import type { Reservation } from './schedule.types'; -import { ref } from 'vue'; +import { computed, ref } from 'vue'; import { AppwriteIds, databases } from 'src/boot/appwrite'; import { Query } from 'appwrite'; import { date } from 'quasar'; -import { Timestamp, parseDate } from '@quasar/quasar-ui-qcalendar'; +import { Timestamp, parseDate, today } from '@quasar/quasar-ui-qcalendar'; export const useReservationStore = defineStore('reservation', () => { + type LoadingTypes = 'loaded' | 'pending' | undefined; const reservations = ref>(new Map()); - const datesLoaded = ref>({}); + const datesLoaded = ref>({}); + // Fetch reservations for a specific date range + const fetchReservationsForDateRange = async ( + start: string = today(), + end: string = start + ) => { + const startDate = new Date(start + 'T00:00'); + const endDate = new Date(end + 'T23:59'); + if (getUnloadedDates(startDate, endDate).length === 0) return; + + setDateLoaded(startDate, endDate, 'pending'); + + try { + const response = await databases.listDocuments( + AppwriteIds.databaseId, + AppwriteIds.collection.reservation, + [ + Query.greaterThanEqual('end', startDate.toISOString()), + Query.lessThanEqual('start', endDate.toISOString()), + ] + ); + + response.documents.forEach((d) => + reservations.value.set(d.$id, d as Reservation) + ); + setDateLoaded(startDate, endDate, 'loaded'); + } catch (error) { + console.error('Failed to fetch reservations', error); + setDateLoaded(startDate, endDate, undefined); + } + }; + + // Set the loading state for dates + const setDateLoaded = (start: Date, end: Date, state: LoadingTypes) => { + if (start > end) return []; + let curDate = start; + while (curDate < end) { + datesLoaded.value[(parseDate(curDate) as Timestamp).date] = state; + curDate = date.addToDate(curDate, { days: 1 }); + } + }; + + const getUnloadedDates = (start: Date, end: Date): string[] => { + if (start > end) return []; + let curDate = start; + const unloaded = []; + while (curDate < end) { + const parsedDate = (parseDate(curDate) as Timestamp).date; + if (datesLoaded.value[parsedDate] === undefined) + unloaded.push(parsedDate); + curDate = date.addToDate(curDate, { days: 1 }); + } + return unloaded; + }; + + // Get reservations by date and optionally filter by boat + const getReservationsByDate = ( + searchDate: string, + boat?: string + ): Reservation[] => { + if (!datesLoaded.value[searchDate]) { + fetchReservationsForDateRange(searchDate); + } + const dayStart = new Date(searchDate + 'T00:00'); + const dayEnd = new Date(searchDate + 'T23:59'); + + return computed(() => { + return Array.from(reservations.value.values()).filter((reservation) => { + const reservationStart = new Date(reservation.start); + const reservationEnd = new Date(reservation.end); + + const isWithinDay = + reservationStart < dayEnd && reservationEnd > dayStart; + const matchesBoat = boat ? boat === reservation.resource : true; + return isWithinDay && matchesBoat; + }); + }).value; + }; + + // Get conflicting reservations for a resource within a time range const getConflictingReservations = ( resource: string, start: Date, end: Date ): Reservation[] => { - const overlapped = Array.from(reservations.value.values()).filter( - (entry: Reservation) => - entry.resource == resource && + return Array.from(reservations.value.values()).filter( + (entry) => + entry.resource === resource && new Date(entry.start) < end && new Date(entry.end) > start ); - return overlapped; }; + // Check if a resource has time overlap const isResourceTimeOverlapped = ( resource: string, start: Date, @@ -32,6 +112,7 @@ export const useReservationStore = defineStore('reservation', () => { return getConflictingReservations(resource, start, end).length > 0; }; + // Check if a reservation overlaps with existing reservations const isReservationOverlapped = (res: Reservation): boolean => { return isResourceTimeOverlapped( res.resource, @@ -40,75 +121,9 @@ export const useReservationStore = defineStore('reservation', () => { ); }; - function setDateLoaded(start: Date, end: Date, state: boolean) { - let curDate = start; - while (curDate < end) { - datesLoaded.value[(parseDate(curDate) as Timestamp).date] = state; - curDate = date.addToDate(curDate, { days: 1 }); - } - } - async function fetchReservationsForDateRange(start: string, end?: string) { - const startDate = new Date(start + 'T00:00'); - const endDate = new Date(end || start + 'T23:59'); - - datesLoaded.value[start] = false; - - if (end) { - setDateLoaded(startDate, endDate, false); - } - - try { - databases - .listDocuments( - AppwriteIds.databaseId, - AppwriteIds.collection.reservation, - [ - Query.and([ - Query.greaterThanEqual('end', startDate.toISOString()), - Query.lessThanEqual('start', endDate.toISOString()), - ]), - ] - ) - .then((response) => { - response.documents.forEach((d) => - reservations.value.set(d.$id, d as Reservation) - ); - setDateLoaded(startDate, endDate, true); - }); - } catch (error) { - console.error('Failed to fetch reservations', error); - } - } - - async function fetchReservations() { - return; - // fetchReservationsForDateRange( - // today(), - // date.addToDate(today(), { days: 7 }).toLocaleDateString() - // ); - } - - const getReservationsByDate = ( - searchDate: string, - boat?: string - ): Reservation[] => { - if (!datesLoaded.value[searchDate]) { - fetchReservationsForDateRange(searchDate); - } - const result = Array.from(reservations.value.values()).filter((x) => { - return new Date(x.start) < new Date(searchDate + 'T' + '23:59') && - new Date(x.end) > new Date(searchDate + 'T' + '00:00') && // Part of reservation falls on day - boat - ? boat === x.boat - : true; // A specific boat has been passed, and matches - }); - console.log(result); - return result; - }; - return { getReservationsByDate, - fetchReservations, + fetchReservationsForDateRange, isReservationOverlapped, isResourceTimeOverlapped, getConflictingReservations, diff --git a/src/stores/schedule.ts b/src/stores/schedule.ts index 7415a36..f97ef64 100644 --- a/src/stores/schedule.ts +++ b/src/stores/schedule.ts @@ -96,6 +96,7 @@ export const useScheduleStore = defineStore('schedule', () => { const getIntervalsForDate = (date: string): Interval[] => { // TODO: This needs to actually make sure we have the dates we need, stay in sync, etc. + return intervals.value.filter((b) => { return compareDate( parseDate(new Date(b.start)) as Timestamp,