All checks were successful
Build BAB Application Deployment Artifact / build (push) Successful in 2m28s
292 lines
8.6 KiB
TypeScript
292 lines
8.6 KiB
TypeScript
import { defineStore } from 'pinia';
|
|
import type { Reservation } from './schedule.types';
|
|
import { computed, ref, watch } 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';
|
|
|
|
export const useReservationStore = defineStore('reservation', () => {
|
|
const reservations = ref<Map<string, Reservation>>(new Map());
|
|
const datesLoaded = ref<Record<string, LoadingTypes>>({});
|
|
const userReservations = ref<Map<string, Reservation>>(new Map());
|
|
// TODO: Come up with a better way of storing reservations by date & reservations for user
|
|
const authStore = useAuthStore();
|
|
const $q = useQuasar();
|
|
|
|
// 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.value.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.value.set(response.$id, response as Reservation);
|
|
userReservations.value.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;
|
|
let id;
|
|
if (typeof reservation === 'string') {
|
|
id = reservation;
|
|
} else if ('$id' in reservation && typeof reservation.$id === 'string') {
|
|
id = reservation.$id;
|
|
} else {
|
|
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.value.delete(id);
|
|
userReservations.value.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.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[] => {
|
|
return Array.from(reservations.value.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.value.set(d.$id, d as Reservation)
|
|
);
|
|
} catch (error) {
|
|
console.error('Failed to fetch reservations for user: ', error);
|
|
}
|
|
};
|
|
|
|
const sortedUserReservations = computed((): Reservation[] =>
|
|
[...userReservations.value?.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));
|
|
});
|
|
|
|
// Ensure reactivity for computed properties when Map is modified
|
|
watch(
|
|
reservations,
|
|
() => {
|
|
sortedUserReservations.value;
|
|
futureUserReservations.value;
|
|
pastUserReservations.value;
|
|
},
|
|
{ deep: true }
|
|
);
|
|
|
|
watch(
|
|
userReservations,
|
|
() => {
|
|
sortedUserReservations.value;
|
|
futureUserReservations.value;
|
|
pastUserReservations.value;
|
|
},
|
|
{ deep: true }
|
|
);
|
|
|
|
return {
|
|
getReservationsByDate,
|
|
getReservationById,
|
|
createOrUpdateReservation,
|
|
deleteReservation,
|
|
fetchReservationsForDateRange,
|
|
isReservationOverlapped,
|
|
isResourceTimeOverlapped,
|
|
getConflictingReservations,
|
|
fetchUserReservations,
|
|
sortedUserReservations,
|
|
futureUserReservations,
|
|
pastUserReservations,
|
|
userReservations,
|
|
};
|
|
});
|