Files
bab-app/src/stores/reservation.ts
Patrick Toal a11b2a0568
All checks were successful
Build BAB Application Deployment Artifact / build (push) Successful in 2m32s
fix: reactivity bug with ListReservationsPage
2024-06-21 23:44:34 -04:00

286 lines
8.6 KiB
TypeScript

import { defineStore } from 'pinia';
import type { Reservation } from './schedule.types';
import { ComputedRef, computed, reactive } from 'vue';
import { AppwriteIds, databases } from 'src/boot/appwrite';
import { ID, Query } from 'appwrite';
import { date, useQuasar } from 'quasar';
import { Timestamp, parseDate, today } from '@quasar/quasar-ui-qcalendar';
import { LoadingTypes } from 'src/utils/misc';
import { useAuthStore } from './auth';
import { isPast } from 'src/utils/schedule';
import { useRealtimeStore } from './realtime';
export const useReservationStore = defineStore('reservation', () => {
const reservations = reactive<Map<string, Reservation>>(new Map());
const datesLoaded = reactive<Record<string, LoadingTypes>>({});
const userReservations = reactive<Map<string, Reservation>>(new Map());
const authStore = useAuthStore();
const $q = useQuasar();
const realtimeStore = useRealtimeStore();
realtimeStore.register(
`databases.${AppwriteIds.databaseId}.collections.${AppwriteIds.collection.reservation}.documents`,
(response) => {
const payload = response.payload as Reservation;
if (payload.$id) {
if (
response.events.includes(
'databases.*.collections.*.documents.*.delete'
)
) {
reservations.delete(payload.$id);
userReservations.delete(payload.$id);
} else {
reservations.set(payload.$id, payload);
if (payload.user === authStore.currentUser?.$id)
userReservations.set(payload.$id, payload);
}
}
}
);
// Fetch reservations for a specific date range
const fetchReservationsForDateRange = async (
start: string = today(),
end: string = start
) => {
const startDate = new Date(start < end ? start : end + 'T00:00');
const endDate = new Date(start < end ? end : start + '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.set(d.$id, d as Reservation)
);
setDateLoaded(startDate, endDate, 'loaded');
} catch (error) {
console.error('Failed to fetch reservations', error);
setDateLoaded(startDate, endDate, 'error');
}
};
const getReservationById = async (id: string) => {
try {
const response = await databases.getDocument(
AppwriteIds.databaseId,
AppwriteIds.collection.reservation,
id
);
return response as Reservation;
} catch (error) {
console.error('Failed to fetch reservation: ', error);
}
};
const createOrUpdateReservation = async (
reservation: Reservation
): Promise<Reservation> => {
let response;
try {
if (reservation.$id) {
response = await databases.updateDocument(
AppwriteIds.databaseId,
AppwriteIds.collection.reservation,
reservation.$id,
reservation
);
} else {
response = await databases.createDocument(
AppwriteIds.databaseId,
AppwriteIds.collection.reservation,
ID.unique(),
reservation
);
}
reservations.set(response.$id, response as Reservation);
userReservations.set(response.$id, response as Reservation);
console.info('Reservation booked: ', response);
return response as Reservation;
} catch (e) {
console.error('Error creating Reservation: ' + e);
throw e;
}
};
const deleteReservation = async (
reservation: string | Reservation | null | undefined
) => {
if (!reservation) return false;
const id = typeof reservation === 'string' ? reservation : reservation.$id;
if (!id) return false;
const status = $q.notify({
color: 'secondary',
textColor: 'white',
message: 'Deleting Reservation',
spinner: true,
closeBtn: 'Dismiss',
position: 'top',
timeout: 0,
group: false,
});
try {
await databases.deleteDocument(
AppwriteIds.databaseId,
AppwriteIds.collection.reservation,
id
);
reservations.delete(id);
userReservations.delete(id);
console.info(`Deleted reservation: ${id}`);
status({
color: 'warning',
message: 'Reservation Deleted',
spinner: false,
icon: 'delete',
timeout: 4000,
});
} catch (e) {
console.error('Error deleting reservation: ' + e);
status({
color: 'negative',
message: 'Failed to Delete Reservation',
spinner: false,
icon: 'error',
});
}
};
// 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[(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[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
): ComputedRef<Reservation[]> => {
if (!datesLoaded[searchDate]) {
fetchReservationsForDateRange(searchDate);
}
const dayStart = new Date(searchDate + 'T00:00');
const dayEnd = new Date(searchDate + 'T23:59');
return computed(() => {
return Array.from(reservations.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;
});
});
};
// Get conflicting reservations for a resource within a time range
const getConflictingReservations = (
resource: string,
start: Date,
end: Date
): Reservation[] => {
return Array.from(reservations.values()).filter(
(entry) =>
entry.resource === resource &&
new Date(entry.start) < end &&
new Date(entry.end) > start
);
};
// Check if a resource has time overlap
const isResourceTimeOverlapped = (
resource: string,
start: Date,
end: Date
): boolean => {
return getConflictingReservations(resource, start, end).length > 0;
};
// Check if a reservation overlaps with existing reservations
const isReservationOverlapped = (res: Reservation): boolean => {
return isResourceTimeOverlapped(
res.resource,
new Date(res.start),
new Date(res.end)
);
};
const fetchUserReservations = async () => {
if (!authStore.currentUser) return;
try {
const response = await databases.listDocuments(
AppwriteIds.databaseId,
AppwriteIds.collection.reservation,
[Query.equal('user', authStore.currentUser.$id)]
);
response.documents.forEach((d) =>
userReservations.set(d.$id, d as Reservation)
);
} catch (error) {
console.error('Failed to fetch reservations for user: ', error);
}
};
const sortedUserReservations = computed((): Reservation[] =>
[...userReservations.values()].sort(
(a, b) => new Date(b.start).getTime() - new Date(a.start).getTime()
)
);
const futureUserReservations = computed((): Reservation[] => {
if (!sortedUserReservations.value) return [];
return sortedUserReservations.value.filter((b) => !isPast(b.end));
});
const pastUserReservations = computed((): Reservation[] => {
if (!sortedUserReservations.value) return [];
return sortedUserReservations.value?.filter((b) => isPast(b.end));
});
return {
getReservationsByDate,
getReservationById,
createOrUpdateReservation,
deleteReservation,
fetchReservationsForDateRange,
isReservationOverlapped,
isResourceTimeOverlapped,
getConflictingReservations,
fetchUserReservations,
sortedUserReservations,
futureUserReservations,
pastUserReservations,
userReservations,
};
});