Adapting to time blocks for bookings
Some checks failed
Build BAB Application Deployment Artifact / build (push) Failing after 2m11s

This commit is contained in:
2023-12-23 11:39:54 -05:00
parent 489cc2646b
commit 66e2169f45
4 changed files with 246 additions and 32 deletions

View File

@@ -1,3 +1,4 @@
<!-- This has been abandoned for now. Going to block-based booking. Will probably need the schedule viewer functionality at some point in the future, though -->
<template> <template>
<q-card-section> <q-card-section>
<div class="text-caption text-justify"> <div class="text-caption text-justify">

View File

@@ -0,0 +1,183 @@
<template>
<q-card-section style="max-width: 320px">
<div class="text-caption text-justify">
Use the calendar to pick a date. Tap a box in the grid for the boat and
start time. Select the duration below.
</div>
<div
style="
width: 100%;
max-width: 320px;
display: flex;
justify-content: center;
"
>
<div
style="
width: 50%;
max-width: 350px;
display: flex;
justify-content: space-between;
"
>
<span
class="q-button"
style="cursor: pointer; user-select: none"
@click="onPrev"
>&lt;</span
>
{{ formattedMonth }}
<span
class="q-button"
style="cursor: pointer; user-select: none"
@click="onNext"
>&gt;</span
>
</div>
</div>
<div
style="
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
"
>
<div style="display: flex; width: 100%">
<q-calendar-month
ref="calendar"
v-model="selectedDate"
:disabled-before="disabledBefore"
animated
bordered
mini-mode
date-type="rounded"
@click-date="onClickDate"
@change="onChange"
/>
</div></div
></q-card-section>
<q-card-section style="max-width: 320px">
<div v-for="boat in boatStore.boats" :key="boat.name">
<q-item-label header>{{ boat.name }}</q-item-label>
<q-item>
<q-item-section>
<q-option-group
:options="boatoptions(boat)"
type="radio"
:model-value="selectedTime"
>
<template v-slot:label="opt">
<div class="row items-center">
{{ opt.label }} &nbsp;
<span v-if="opt.disable">Reserved by {{ opt.user }}</span>
</div></template
>
</q-option-group>
</q-item-section>
</q-item>
</div>
</q-card-section>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import {
today,
parsed,
addToDate,
Timestamp,
} from '@quasar/quasar-ui-qcalendar';
import { Boat, useBoatStore } from 'src/stores/boat';
import { useScheduleStore, Timeblock } from 'src/stores/schedule';
import { computed } from 'vue';
import { date } from 'quasar';
type EventData = {
event: object;
scope: {
timestamp: object;
columnindex: number;
activeDate: boolean;
droppable: boolean;
};
};
const calendar = ref();
const boatStore = useBoatStore();
const scheduleStore = useScheduleStore();
const selectedDate = ref(today());
const formattedMonth = computed(() => {
const date = new Date(selectedDate.value);
return monthFormatter()?.format(date);
});
const disabledBefore = computed(() => {
const todayTs = parsed(today()) as Timestamp;
return addToDate(todayTs, { day: -1 }).date;
});
const selectedTime = computed(() => {
return boatStore.boats[0] + ':09:00';
});
function monthFormatter() {
try {
return new Intl.DateTimeFormat('en-CA' || undefined, {
month: 'long',
timeZone: 'UTC',
});
} catch (e) {
//
}
}
const boatoptions = (boat: Boat) => {
const options = useScheduleStore()
.getTimeblocksForDate(date.extractDate(selectedDate.value, 'YYYY-MM-DD'))
.map((x: Timeblock) => {
const conflicts = getConflicts(x, boat);
return {
label: x.start.time + ' to ' + x.end.time,
value: boat.name + ':' + x.start.time,
disable: conflicts.length > 0,
user: conflicts[0]?.user,
};
});
return options;
};
const emit = defineEmits(['onClickTime', 'onUpdateDuration']);
function onPrev() {
calendar.value.prev();
}
function onNext() {
calendar.value.next();
}
function onClickDate(data: EventData) {
return data;
}
function onChange(data: EventData) {
return data;
}
const getConflicts = (timeblock: Timeblock, boat: Boat) => {
const start = date.buildDate({
hour: timeblock.start.hour,
minute: timeblock.start.minute,
second: 0,
millisecond: 0,
});
const end = date.buildDate({
hour: timeblock.end.hour,
minute: timeblock.end.minute,
second: 0,
millisecond: 0,
});
return scheduleStore.getConflictingReservations(boat, start, end);
};
</script>

View File

@@ -21,14 +21,8 @@
:caption="bookingSummary" :caption="bookingSummary"
> >
<q-separator /> <q-separator />
<resource-schedule-viewer-component <boat-selection />
@on-click-time="onClickTime"
@on-update-duration="
(value) => {
bookingForm.duration = value;
}
"
/>
<q-banner <q-banner
rounded rounded
class="bg-warning text-grey-10" class="bg-warning text-grey-10"
@@ -78,7 +72,7 @@ import { reactive, ref, computed, watch } from 'vue';
import { useAuthStore } from 'src/stores/auth'; import { useAuthStore } from 'src/stores/auth';
import { Boat, useBoatStore } from 'src/stores/boat'; import { Boat, useBoatStore } from 'src/stores/boat';
import { Dialog, date } from 'quasar'; import { Dialog, date } from 'quasar';
import ResourceScheduleViewerComponent from 'src/components/ResourceScheduleViewerComponent.vue'; import BoatSelection from 'src/components/scheduling/BoatSelection.vue';
import { makeDateTime } from '@quasar/quasar-ui-qcalendar'; import { makeDateTime } from '@quasar/quasar-ui-qcalendar';
import { useScheduleStore, Reservation } from 'src/stores/schedule'; import { useScheduleStore, Reservation } from 'src/stores/schedule';
@@ -113,7 +107,7 @@ watch(bookingForm, (b, a) => {
status: 'tentative', status: 'tentative',
}; };
//TODO: Turn this into a validator. //TODO: Turn this into a validator.
scheduleStore.isOverlapped(newRes) scheduleStore.isReservationOverlapped(newRes)
? Dialog.create({ message: 'This booking overlaps another!' }) ? Dialog.create({ message: 'This booking overlaps another!' })
: scheduleStore.addOrCreateReservation(newRes); : scheduleStore.addOrCreateReservation(newRes);
}); });

View File

@@ -8,6 +8,7 @@ import {
parseTimestamp, parseTimestamp,
TimestampArray, TimestampArray,
} from '@quasar/quasar-ui-qcalendar'; } from '@quasar/quasar-ui-qcalendar';
import { timeStamp } from 'console';
export type StatusTypes = 'tentative' | 'confirmed' | 'pending' | undefined; export type StatusTypes = 'tentative' | 'confirmed' | 'pending' | undefined;
export type Reservation = { export type Reservation = {
@@ -26,10 +27,22 @@ export type Timeblock = {
}; };
const sampleBlocks = [ const sampleBlocks = [
{ start: { hour: 9, minute: 0 }, end: { hour: 12, minute: 0 } }, {
{ start: { hour: 12, minute: 0 }, end: { hour: 15, minute: 0 } }, start: { time: '09:00', hour: 9, minute: 0, hasDay: false, hasTime: true },
{ start: { hour: 15, minute: 0 }, end: { hour: 18, minute: 0 } }, end: { time: '12:00', hour: 12, minute: 0, hasDay: false, hasTime: true },
{ start: { hour: 18, minute: 0 }, end: { hour: 21, minute: 0 } }, },
{
start: { time: '12:00', hour: 12, minute: 0, hasDay: false, hasTime: true },
end: { time: '15:00', hour: 15, minute: 0, hasDay: false, hasTime: true },
},
{
start: { time: '15:00', hour: 15, minute: 0, hasDay: false, hasTime: true },
end: { time: '18:00', hour: 18, minute: 0, hasDay: false, hasTime: true },
},
{
start: { time: '18:00', hour: 18, minute: 0, hasDay: false, hasTime: true },
end: { time: '21:00', hour: 21, minute: 0, hasDay: false, hasTime: true },
},
] as Timeblock[]; ] as Timeblock[];
function getSampleReservations(): Reservation[] { function getSampleReservations(): Reservation[] {
@@ -38,7 +51,7 @@ function getSampleReservations(): Reservation[] {
id: 1, id: 1,
user: 'John Smith', user: 'John Smith',
start: '12:00', start: '12:00',
end: '14:00', end: '15:00',
boat: 1, boat: 1,
status: 'confirmed', status: 'confirmed',
}, },
@@ -46,31 +59,31 @@ function getSampleReservations(): Reservation[] {
id: 2, id: 2,
user: 'Bob Barker', user: 'Bob Barker',
start: '18:00', start: '18:00',
end: '20:00', end: '21:00',
boat: 1, boat: 1,
status: 'confirmed', status: 'confirmed',
}, },
{ {
id: 3, id: 3,
user: 'Peter Parker', user: 'Peter Parker',
start: '8:00', start: '9:00',
end: '10:00', end: '12:00',
boat: 2, boat: 2,
status: 'tentative', status: 'tentative',
}, },
{ {
id: 4, id: 4,
user: 'Vince McMahon', user: 'Vince McMahon',
start: '13:00', start: '15:00',
end: '17:00', end: '18:00',
boat: 2, boat: 2,
status: 'pending', status: 'pending',
}, },
{ {
id: 5, id: 5,
user: 'Heather Graham', user: 'Heather Graham',
start: '06:00', start: '09:00',
end: '09:00', end: '12:00',
boat: 3, boat: 3,
status: 'confirmed', status: 'confirmed',
}, },
@@ -78,7 +91,7 @@ function getSampleReservations(): Reservation[] {
id: 6, id: 6,
user: 'Lawrence Fishburne', user: 'Lawrence Fishburne',
start: '18:00', start: '18:00',
end: '20:00', end: '21:00',
boat: 3, boat: 3,
}, },
]; ];
@@ -88,7 +101,12 @@ function getSampleReservations(): Reservation[] {
return x.split(':'); return x.split(':');
}; };
const makeOpts = (x: string[]): DateOptions => { const makeOpts = (x: string[]): DateOptions => {
return { hour: parseInt(x[0]), minute: parseInt(x[1]) }; return {
hour: parseInt(x[0]),
minute: parseInt(x[1]),
seconds: 0,
milliseconds: 0,
};
}; };
return sampleData.map((entry): Reservation => { return sampleData.map((entry): Reservation => {
@@ -128,15 +146,30 @@ export const useScheduleStore = defineStore('schedule', () => {
}); });
}; };
const isOverlapped = (res: Reservation) => { const getConflictingReservations = (
const lapped = reservations.value.filter( resource: Boat,
start: Date,
end: Date
): Reservation[] => {
const overlapped = reservations.value.filter(
(entry: Reservation) => (entry: Reservation) =>
entry.id != res.id && entry.resource.id == resource.id &&
entry.resource == res.resource && entry.start < end &&
((entry.start <= res.start && entry.end > res.start) || entry.end > start
(entry.end >= res.end && entry.start <= res.end))
); );
return lapped.length > 0; return overlapped;
};
const isResourceTimeOverlapped = (
resource: Boat,
start: Date,
end: Date
): boolean => {
return getConflictingReservations(resource, start, end).length > 0;
};
const isReservationOverlapped = (res: Reservation): boolean => {
return isResourceTimeOverlapped(res.resource, res.start, res.end);
}; };
const getNewId = () => { const getNewId = () => {
@@ -156,8 +189,11 @@ export const useScheduleStore = defineStore('schedule', () => {
return { return {
reservations, reservations,
getBoatReservations, getBoatReservations,
getConflictingReservations,
getTimeblocksForDate,
getNewId, getNewId,
addOrCreateReservation, addOrCreateReservation,
isOverlapped, isReservationOverlapped,
isResourceTimeOverlapped,
}; };
}); });