Many tweaks to booking form.
This commit is contained in:
@@ -1,32 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="row justify-center">
|
<q-card-section>
|
||||||
<q-btn-group rounded>
|
<div class="text-caption text-justify">
|
||||||
<q-btn
|
Use the calendar to pick a date. Tap a box in the grid for the boat and
|
||||||
color="primary"
|
start time. Select the duration below.
|
||||||
rounded
|
|
||||||
icon="keyboard_arrow_left"
|
|
||||||
label="Prev"
|
|
||||||
@click="onPrev"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
color="primary"
|
|
||||||
rounded
|
|
||||||
icon="today"
|
|
||||||
label="Today"
|
|
||||||
@click="onToday"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
color="primary"
|
|
||||||
rounded
|
|
||||||
icon-right="keyboard_arrow_right"
|
|
||||||
label="Next"
|
|
||||||
@click="onNext"
|
|
||||||
/>
|
|
||||||
</q-btn-group>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div style="width: 100%; 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"
|
||||||
|
><</span
|
||||||
|
>
|
||||||
|
{{ formattedMonth }}
|
||||||
|
<span
|
||||||
|
class="q-button"
|
||||||
|
style="cursor: pointer; user-select: none"
|
||||||
|
@click="onNext"
|
||||||
|
>></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
|
<q-calendar-month
|
||||||
ref="calendar"
|
ref="calendar"
|
||||||
v-model="selectedDate"
|
v-model="selectedDate"
|
||||||
|
:disabled-before="disabledBefore"
|
||||||
animated
|
animated
|
||||||
bordered
|
bordered
|
||||||
mini-mode
|
mini-mode
|
||||||
@@ -35,9 +49,9 @@
|
|||||||
@moved="onMoved"
|
@moved="onMoved"
|
||||||
@click-date="onClickDate"
|
@click-date="onClickDate"
|
||||||
/>
|
/>
|
||||||
<div style="float: right; display: flex; max-width: 1024px; width: 100%">
|
</div></div
|
||||||
|
></q-card-section>
|
||||||
<q-calendar-resource
|
<q-calendar-resource
|
||||||
ref="calendar"
|
|
||||||
v-model="selectedDate"
|
v-model="selectedDate"
|
||||||
:model-resources="boatStore.boats"
|
:model-resources="boatStore.boats"
|
||||||
resource-key="id"
|
resource-key="id"
|
||||||
@@ -62,8 +76,35 @@
|
|||||||
<q-badge outline :label="event.title" :style="getStyle(event)" />
|
<q-badge outline :label="event.title" :style="getStyle(event)" />
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</q-calendar-resource>
|
|
||||||
|
<template #resource-label="{ scope: { resource } }">
|
||||||
|
<div class="col-12">
|
||||||
|
{{ resource.name }}
|
||||||
|
<q-icon v-if="resource.defects" name="warning" color="warning" />
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-calendar-resource>
|
||||||
|
|
||||||
|
<q-card-section>
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
v-model="duration"
|
||||||
|
:options="durations"
|
||||||
|
dense
|
||||||
|
@update:model-value="onUpdateDuration"
|
||||||
|
label="Duration (hours)"
|
||||||
|
stack-label
|
||||||
|
><template v-slot:append><q-icon name="timelapse" /></template></q-select
|
||||||
|
></q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
class="full-width"
|
||||||
|
icon="keyboard_arrow_down"
|
||||||
|
icon-right="keyboard_arrow_down"
|
||||||
|
label="Next: Crew & Passengers"
|
||||||
|
@click="onCloseSection"
|
||||||
|
/></q-card-section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -73,10 +114,16 @@ import {
|
|||||||
TimestampOrNull,
|
TimestampOrNull,
|
||||||
today,
|
today,
|
||||||
parseDate,
|
parseDate,
|
||||||
|
parseTimestamp,
|
||||||
|
addToDate,
|
||||||
|
Timestamp,
|
||||||
} from '@quasar/quasar-ui-qcalendar';
|
} from '@quasar/quasar-ui-qcalendar';
|
||||||
import { Boat, useBoatStore } from 'src/stores/boat';
|
import { Boat, useBoatStore } from 'src/stores/boat';
|
||||||
import { useScheduleStore } from 'src/stores/schedule';
|
import { useScheduleStore } from 'src/stores/schedule';
|
||||||
import { date } from 'quasar';
|
import { date } from 'quasar';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const durations = [1, 1.5, 2, 2.5, 3, 3.5, 4];
|
||||||
|
|
||||||
type ResourceIntervalScope = {
|
type ResourceIntervalScope = {
|
||||||
resource: Boat;
|
resource: Boat;
|
||||||
@@ -86,15 +133,37 @@ type ResourceIntervalScope = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const statusLookup = {
|
const statusLookup = {
|
||||||
tentative: ['#f2c037', 'white'],
|
|
||||||
confirmed: ['#14539a', 'white'],
|
confirmed: ['#14539a', 'white'],
|
||||||
pending: ['white', 'grey'],
|
pending: ['#f2c037', 'white'],
|
||||||
|
tentative: ['white', 'grey'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const calendar = ref();
|
const calendar = ref();
|
||||||
const boatStore = useBoatStore();
|
const boatStore = useBoatStore();
|
||||||
const scheduleStore = useScheduleStore();
|
const scheduleStore = useScheduleStore();
|
||||||
const selectedDate = ref(today());
|
const selectedDate = ref(today());
|
||||||
|
const duration = ref(1);
|
||||||
|
|
||||||
|
const formattedMonth = computed(() => {
|
||||||
|
const date = new Date(selectedDate.value);
|
||||||
|
|
||||||
|
return monthFormatter()?.format(date);
|
||||||
|
});
|
||||||
|
const disabledBefore = computed(() => {
|
||||||
|
const todayTs = parseTimestamp(today()) as Timestamp;
|
||||||
|
return addToDate(todayTs, { day: -1 }).date;
|
||||||
|
});
|
||||||
|
|
||||||
|
function monthFormatter() {
|
||||||
|
try {
|
||||||
|
return new Intl.DateTimeFormat('en-CA' || undefined, {
|
||||||
|
month: 'long',
|
||||||
|
timeZone: 'UTC',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getEvents(scope: ResourceIntervalScope) {
|
function getEvents(scope: ResourceIntervalScope) {
|
||||||
const resourceEvents = scheduleStore.getBoatReservations(
|
const resourceEvents = scheduleStore.getBoatReservations(
|
||||||
@@ -131,11 +200,8 @@ function getStyle(event: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits(['onClickDate', 'onClickTime']);
|
const emit = defineEmits(['onClickTime', 'onCloseSection', 'onUpdateDuration']);
|
||||||
|
|
||||||
function onToday() {
|
|
||||||
calendar.value.moveToToday();
|
|
||||||
}
|
|
||||||
function onPrev() {
|
function onPrev() {
|
||||||
calendar.value.prev();
|
calendar.value.prev();
|
||||||
}
|
}
|
||||||
@@ -143,16 +209,28 @@ function onNext() {
|
|||||||
calendar.value.next();
|
calendar.value.next();
|
||||||
}
|
}
|
||||||
function onClickDate(data) {
|
function onClickDate(data) {
|
||||||
emit('onClickDate', data);
|
return;
|
||||||
}
|
}
|
||||||
function onClickTime(data) {
|
function onClickTime(data) {
|
||||||
|
// TODO: Add a duration picker, here.
|
||||||
emit('onClickTime', data);
|
emit('onClickTime', data);
|
||||||
}
|
}
|
||||||
|
function onCloseSection() {
|
||||||
|
emit('onCloseSection');
|
||||||
|
}
|
||||||
|
function onUpdateDuration(value) {
|
||||||
|
emit('onUpdateDuration', value);
|
||||||
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
const onClickInterval = () => {};
|
const onClickInterval = () => {};
|
||||||
const onClickHeadResources = onClickInterval;
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
const onClickResource = onClickInterval;
|
const onClickHeadResources = () => {};
|
||||||
const onResourceExpanded = onClickInterval;
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
const onMoved = onClickInterval;
|
const onClickResource = () => {};
|
||||||
const onChange = onClickInterval;
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
const onResourceExpanded = () => {};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
const onMoved = () => {};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
const onChange = () => {};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
40
src/components/boat/BoatPickerComponent.vue
Normal file
40
src/components/boat/BoatPickerComponent.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<q-select
|
||||||
|
v-model="boat"
|
||||||
|
:options="boats"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
label="Boat"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-img v-if="boat?.iconsrc" :src="boat?.iconsrc" />
|
||||||
|
<q-icon v-else name="sailing" />
|
||||||
|
</q-item-section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-img :src="scope.opt.iconsrc" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.name }}</q-item-label>
|
||||||
|
<q-item-label caption>{{ scope.opt.class }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar v-if="scope.opt.defects">
|
||||||
|
<q-icon name="warning" color="warning" />
|
||||||
|
<q-tooltip class="bg-amber text-black shadow-7"
|
||||||
|
>This boat has notices. Select it to see details.
|
||||||
|
</q-tooltip>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Boat, useBoatStore } from 'src/stores/boat';
|
||||||
|
const boats = useBoatStore().boats;
|
||||||
|
const boat = <Boat | undefined>undefined;
|
||||||
|
</script>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import BoatPreviewComponent from 'src/components/BoatPreviewComponent.vue';
|
import BoatPreviewComponent from 'src/components/boat/BoatPreviewComponent.vue';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useBoatStore } from 'src/stores/boat';
|
import { useBoatStore } from 'src/stores/boat';
|
||||||
import ToolbarComponent from 'src/components/ToolbarComponent.vue';
|
import ToolbarComponent from 'src/components/ToolbarComponent.vue';
|
||||||
|
|||||||
@@ -1,47 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page padding>
|
<q-page padding>
|
||||||
<q-card>
|
<q-list>
|
||||||
<q-form @submit="onSubmit" @reset="onReset" class="q-gutter-md">
|
<q-form @submit="onSubmit" @reset="onReset" class="q-gutter-md">
|
||||||
<q-input bottom-slots v-model="bookingForm.name" label="Name" readonly>
|
<q-input
|
||||||
|
bottom-slots
|
||||||
|
v-model="bookingForm.name"
|
||||||
|
label="Creating reservation for"
|
||||||
|
readonly
|
||||||
|
>
|
||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<q-icon name="person" />
|
<q-icon name="person" />
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
<q-select
|
<q-expansion-item
|
||||||
v-model="bookingForm.boat"
|
expand-separator
|
||||||
:options="boats"
|
v-model="resourceView"
|
||||||
option-value="id"
|
icon="calendar_month"
|
||||||
option-label="name"
|
label="Boat and Time"
|
||||||
label="Boat"
|
default-opened
|
||||||
|
:caption="bookingSummary"
|
||||||
>
|
>
|
||||||
<template v-slot:prepend>
|
<q-separator />
|
||||||
<q-item-section avatar>
|
<resource-schedule-viewer-component
|
||||||
<q-img
|
@on-click-time="onClickTime"
|
||||||
v-if="bookingForm.boat?.iconsrc"
|
@on-close-section="() => (resourceView = !resourceView)"
|
||||||
:src="bookingForm.boat?.iconsrc"
|
@on-update-duration="
|
||||||
/>
|
(value) => {
|
||||||
<q-icon v-else name="sailing" />
|
bookingForm.duration = value;
|
||||||
</q-item-section>
|
}
|
||||||
</template>
|
"
|
||||||
|
/></q-expansion-item>
|
||||||
|
|
||||||
<template v-slot:option="scope">
|
|
||||||
<q-item v-bind="scope.itemProps">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-img :src="scope.opt.iconsrc" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ scope.opt.name }}</q-item-label>
|
|
||||||
<q-item-label caption>{{ scope.opt.class }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar v-if="scope.opt.defects">
|
|
||||||
<q-icon name="warning" color="warning" />
|
|
||||||
<q-tooltip class="bg-amber text-black shadow-7"
|
|
||||||
>This boat has defects. Select it to see details.
|
|
||||||
</q-tooltip>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
<q-banner
|
<q-banner
|
||||||
rounded
|
rounded
|
||||||
class="bg-warning text-grey-10"
|
class="bg-warning text-grey-10"
|
||||||
@@ -50,7 +39,7 @@
|
|||||||
<template v-slot:avatar>
|
<template v-slot:avatar>
|
||||||
<q-icon name="warning" color="grey-10" />
|
<q-icon name="warning" color="grey-10" />
|
||||||
</template>
|
</template>
|
||||||
This boat currently has the following defects:
|
This boat currently has the following notices:
|
||||||
<ol>
|
<ol>
|
||||||
<li
|
<li
|
||||||
v-for="defect in bookingForm.boat.defects"
|
v-for="defect in bookingForm.boat.defects"
|
||||||
@@ -60,145 +49,65 @@
|
|||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</q-banner>
|
</q-banner>
|
||||||
<q-btn
|
<q-expansion-item
|
||||||
label="View Schedule"
|
expand-separator
|
||||||
color="primary"
|
icon="people"
|
||||||
flat
|
label="Crew and Passengers"
|
||||||
@click="showSchedule = true"
|
default-opened
|
||||||
>
|
|
||||||
<q-dialog
|
|
||||||
v-model="showSchedule"
|
|
||||||
full-width
|
|
||||||
cover
|
|
||||||
transition-show="scale"
|
|
||||||
transition-hide="scale"
|
|
||||||
>
|
|
||||||
<q-card>
|
|
||||||
<resource-schedule-viewer-component
|
|
||||||
@on-click-date="onClickDate"
|
|
||||||
@on-click-time="onClickTime"
|
|
||||||
/>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</q-btn>
|
|
||||||
<q-input
|
|
||||||
v-model="bookingForm.startDate"
|
|
||||||
label="Check-Out"
|
|
||||||
label-color="accent"
|
|
||||||
readonly
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<q-icon name="event" class="cursor-pointer"> </q-icon>
|
|
||||||
</template>
|
|
||||||
<q-popup-proxy
|
|
||||||
cover
|
|
||||||
transition-show="scale"
|
|
||||||
transition-hide="scale"
|
|
||||||
@before-show="startDateOrTime = true"
|
|
||||||
>
|
|
||||||
<q-date
|
|
||||||
v-model="bookingForm.startDate"
|
|
||||||
mask="ddd MMM D, YYYY h:mm A"
|
|
||||||
:options="limitDate"
|
|
||||||
v-if="startDateOrTime"
|
|
||||||
>
|
|
||||||
<div class="row items-center justify-end">
|
|
||||||
<q-btn
|
|
||||||
label="Next"
|
|
||||||
color="primary"
|
|
||||||
flat
|
|
||||||
@click="startDateOrTime = false"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</q-date>
|
|
||||||
<q-time
|
|
||||||
v-model="bookingForm.startDate"
|
|
||||||
mask="ddd MMM D, YYYY h:mm A"
|
|
||||||
:minute-options="minOpts"
|
|
||||||
v-else
|
|
||||||
>
|
|
||||||
<div class="row items-center justify-end">
|
|
||||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
|
||||||
</div>
|
|
||||||
</q-time>
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-input>
|
|
||||||
<q-input
|
|
||||||
v-model="bookingForm.endDate"
|
|
||||||
label="Check-In"
|
|
||||||
label-color="accent"
|
|
||||||
readonly
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<q-icon name="event" class="cursor-pointer"> </q-icon>
|
|
||||||
</template>
|
|
||||||
<q-popup-proxy
|
|
||||||
cover
|
|
||||||
transition-show="scale"
|
|
||||||
transition-hide="scale"
|
|
||||||
@before-show="endDateOrTime = true"
|
|
||||||
>
|
|
||||||
<q-date
|
|
||||||
v-model="bookingForm.startDate"
|
|
||||||
mask="ddd MMM D, YYYY h:mm A"
|
|
||||||
:options="limitDate"
|
|
||||||
v-if="endDateOrTime"
|
|
||||||
>
|
|
||||||
<div class="row items-center justify-end">
|
|
||||||
<q-btn v-close-popup label="Cancel" color="accent" flat />
|
|
||||||
<q-btn
|
|
||||||
label="Next"
|
|
||||||
color="primary"
|
|
||||||
flat
|
|
||||||
@click="endDateOrTime = !endDateOrTime"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</q-date>
|
|
||||||
<q-time
|
|
||||||
v-model="bookingForm.endDate"
|
|
||||||
mask="ddd MMM D, YYYY h:mm A"
|
|
||||||
:minute-options="minOpts"
|
|
||||||
v-else
|
|
||||||
>
|
|
||||||
<div class="row items-center justify-end">
|
|
||||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
|
||||||
</div>
|
|
||||||
</q-time>
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-input>
|
|
||||||
<q-chip icon="timelapse"
|
|
||||||
>Booking Duration: {{ bookingDuration }}</q-chip
|
|
||||||
>
|
>
|
||||||
|
<q-separator />
|
||||||
|
</q-expansion-item>
|
||||||
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-btn label="Submit" type="submit" color="primary" />
|
<q-btn label="Submit" type="submit" color="primary" />
|
||||||
</q-item-section>
|
</q-item-section> </q-form
|
||||||
</q-form>
|
></q-list>
|
||||||
</q-card>
|
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
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 { date } from 'quasar';
|
import { Dialog, date } from 'quasar';
|
||||||
import ResourceScheduleViewerComponent from 'src/components/ResourceScheduleViewerComponent.vue';
|
import ResourceScheduleViewerComponent from 'src/components/ResourceScheduleViewerComponent.vue';
|
||||||
import { makeDateTime } from '@quasar/quasar-ui-qcalendar';
|
import { makeDateTime } from '@quasar/quasar-ui-qcalendar';
|
||||||
|
import { useScheduleStore, Reservation } from 'src/stores/schedule';
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const boats = useBoatStore().boats;
|
|
||||||
const dateFormat = 'ddd MMM D, YYYY h:mm A';
|
const dateFormat = 'ddd MMM D, YYYY h:mm A';
|
||||||
const startDateOrTime = ref(true);
|
const resourceView = ref(true);
|
||||||
const endDateOrTime = ref(true);
|
const scheduleStore = useScheduleStore();
|
||||||
const showSchedule = ref(false);
|
const bookingForm = reactive({
|
||||||
const bookingForm = ref({
|
bookingId: scheduleStore.getNewId(),
|
||||||
name: auth.currentUser?.name,
|
name: auth.currentUser?.name,
|
||||||
boat: <Boat | undefined>undefined,
|
boat: <Boat | undefined>undefined,
|
||||||
startDate: date.formatDate(new Date(), dateFormat),
|
startDate: date.formatDate(new Date(), dateFormat),
|
||||||
endDate: date.formatDate(
|
endDate: computed(() =>
|
||||||
date.addToDate(new Date(), { hours: 2 }),
|
date.formatDate(
|
||||||
|
date.addToDate(bookingForm.startDate, {
|
||||||
|
hours: bookingForm.duration,
|
||||||
|
}),
|
||||||
dateFormat
|
dateFormat
|
||||||
|
)
|
||||||
),
|
),
|
||||||
|
duration: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(bookingForm, (b, a) => {
|
||||||
|
const newRes = <Reservation>{
|
||||||
|
id: b.bookingId,
|
||||||
|
user: b.name,
|
||||||
|
resource: b.boat,
|
||||||
|
start: date.extractDate(b.startDate, dateFormat),
|
||||||
|
end: date.extractDate(b.endDate, dateFormat),
|
||||||
|
reservationDate: new Date(),
|
||||||
|
status: 'tentative',
|
||||||
|
};
|
||||||
|
//TODO: Turn this into a validator.
|
||||||
|
scheduleStore.isOverlapped(newRes)
|
||||||
|
? Dialog.create({ message: 'This booking overlaps another!' })
|
||||||
|
: scheduleStore.addOrCreateReservation(newRes);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
@@ -209,22 +118,18 @@ const onSubmit = () => {
|
|||||||
// TODO
|
// TODO
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickDate = (data) => {
|
|
||||||
console.log(data);
|
|
||||||
};
|
|
||||||
const onClickTime = (data) => {
|
const onClickTime = (data) => {
|
||||||
bookingForm.value.boat = data.scope.resource;
|
bookingForm.boat = data.scope.resource;
|
||||||
bookingForm.value.startDate = date.formatDate(
|
bookingForm.startDate = date.formatDate(
|
||||||
date.addToDate(makeDateTime(data.scope.timestamp), { hours: 5 }), // A terrible hack to convert back to EST. TODO: FIX!!!!
|
date.addToDate(makeDateTime(data.scope.timestamp), { hours: 5 }), // A terrible hack to convert back to EST. TODO: FIX!!!!
|
||||||
dateFormat
|
dateFormat
|
||||||
);
|
);
|
||||||
showSchedule.value = false;
|
console.log(bookingForm.startDate);
|
||||||
console.log(bookingForm.value.startDate);
|
|
||||||
};
|
};
|
||||||
const bookingDuration = computed(() => {
|
const bookingDuration = computed(() => {
|
||||||
const diff = date.getDateDiff(
|
const diff = date.getDateDiff(
|
||||||
bookingForm.value.endDate,
|
bookingForm.endDate,
|
||||||
bookingForm.value.startDate,
|
bookingForm.startDate,
|
||||||
'minutes'
|
'minutes'
|
||||||
);
|
);
|
||||||
return diff <= 0
|
return diff <= 0
|
||||||
@@ -233,6 +138,12 @@ const bookingDuration = computed(() => {
|
|||||||
(diff % 60 > 0 ? ' ' + (diff % 60) + ' minutes' : '');
|
(diff % 60 > 0 ? ' ' + (diff % 60) + ' minutes' : '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const bookingSummary = computed(() => {
|
||||||
|
return bookingForm.boat && bookingForm.startDate && bookingForm.endDate
|
||||||
|
? `${bookingForm.boat.name} @ ${bookingForm.startDate} for ${bookingDuration.value}`
|
||||||
|
: '';
|
||||||
|
});
|
||||||
|
|
||||||
const limitDate = (startDate: string) => {
|
const limitDate = (startDate: string) => {
|
||||||
return date.isBetweenDates(
|
return date.isBetweenDates(
|
||||||
startDate,
|
startDate,
|
||||||
@@ -241,6 +152,4 @@ const limitDate = (startDate: string) => {
|
|||||||
{ inclusiveFrom: true, inclusiveTo: true, onlyDate: true }
|
{ inclusiveFrom: true, inclusiveTo: true, onlyDate: true }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const minOpts = [0, 15, 30, 45, 60];
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { date } from 'quasar';
|
|||||||
import { DateOptions } from 'quasar';
|
import { DateOptions } from 'quasar';
|
||||||
|
|
||||||
export interface Reservation {
|
export interface Reservation {
|
||||||
|
id: number;
|
||||||
user: string;
|
user: string;
|
||||||
start: Date;
|
start: Date;
|
||||||
end: Date;
|
end: Date;
|
||||||
@@ -16,6 +17,7 @@ export interface Reservation {
|
|||||||
function getSampleData(): Reservation[] {
|
function getSampleData(): Reservation[] {
|
||||||
const sampleData = [
|
const sampleData = [
|
||||||
{
|
{
|
||||||
|
id: 1,
|
||||||
user: 'John Smith',
|
user: 'John Smith',
|
||||||
start: '12:00',
|
start: '12:00',
|
||||||
end: '14:00',
|
end: '14:00',
|
||||||
@@ -23,6 +25,7 @@ function getSampleData(): Reservation[] {
|
|||||||
status: 'confirmed',
|
status: 'confirmed',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 2,
|
||||||
user: 'Bob Barker',
|
user: 'Bob Barker',
|
||||||
start: '18:00',
|
start: '18:00',
|
||||||
end: '20:00',
|
end: '20:00',
|
||||||
@@ -30,6 +33,7 @@ function getSampleData(): Reservation[] {
|
|||||||
status: 'confirmed',
|
status: 'confirmed',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 3,
|
||||||
user: 'Peter Parker',
|
user: 'Peter Parker',
|
||||||
start: '8:00',
|
start: '8:00',
|
||||||
end: '10:00',
|
end: '10:00',
|
||||||
@@ -37,6 +41,7 @@ function getSampleData(): Reservation[] {
|
|||||||
status: 'tentative',
|
status: 'tentative',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 4,
|
||||||
user: 'Vince McMahon',
|
user: 'Vince McMahon',
|
||||||
start: '13:00',
|
start: '13:00',
|
||||||
end: '17:00',
|
end: '17:00',
|
||||||
@@ -44,13 +49,20 @@ function getSampleData(): Reservation[] {
|
|||||||
status: 'pending',
|
status: 'pending',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 5,
|
||||||
user: 'Heather Graham',
|
user: 'Heather Graham',
|
||||||
start: '06:00',
|
start: '06:00',
|
||||||
end: '09:00',
|
end: '09:00',
|
||||||
boat: 3,
|
boat: 3,
|
||||||
status: 'confirmed',
|
status: 'confirmed',
|
||||||
},
|
},
|
||||||
{ user: 'Lawrence Fishburne', start: '18:00', end: '20:00', boat: 3 },
|
{
|
||||||
|
id: 6,
|
||||||
|
user: 'Lawrence Fishburne',
|
||||||
|
start: '18:00',
|
||||||
|
end: '20:00',
|
||||||
|
boat: 3,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const boatStore = useBoatStore();
|
const boatStore = useBoatStore();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -64,6 +76,7 @@ function getSampleData(): Reservation[] {
|
|||||||
return sampleData.map((entry): Reservation => {
|
return sampleData.map((entry): Reservation => {
|
||||||
const boat = <Boat>boatStore.boats.find((b) => b.id == entry.boat);
|
const boat = <Boat>boatStore.boats.find((b) => b.id == entry.boat);
|
||||||
return {
|
return {
|
||||||
|
id: entry.id,
|
||||||
user: entry.user,
|
user: entry.user,
|
||||||
start: date.adjustDate(now, makeOpts(splitTime(entry.start))),
|
start: date.adjustDate(now, makeOpts(splitTime(entry.start))),
|
||||||
end: date.adjustDate(now, makeOpts(splitTime(entry.end))),
|
end: date.adjustDate(now, makeOpts(splitTime(entry.end))),
|
||||||
@@ -80,7 +93,6 @@ export const useScheduleStore = defineStore('schedule', () => {
|
|||||||
boat: number | string,
|
boat: number | string,
|
||||||
curDate: Date
|
curDate: Date
|
||||||
): Reservation[] => {
|
): Reservation[] => {
|
||||||
console.log(reservations.value);
|
|
||||||
return reservations.value.filter((x) => {
|
return reservations.value.filter((x) => {
|
||||||
return (
|
return (
|
||||||
(x.start.getDate() == curDate.getDate() ||
|
(x.start.getDate() == curDate.getDate() ||
|
||||||
@@ -91,5 +103,36 @@ export const useScheduleStore = defineStore('schedule', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return { reservations, getBoatReservations };
|
|
||||||
|
const isOverlapped = (res: Reservation) => {
|
||||||
|
const lapped = reservations.value.filter(
|
||||||
|
(entry: Reservation) =>
|
||||||
|
entry.resource == res.resource &&
|
||||||
|
((entry.start <= res.start && entry.end > res.start) ||
|
||||||
|
(entry.end >= res.end && entry.start <= res.end))
|
||||||
|
);
|
||||||
|
return lapped.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNewId = () => {
|
||||||
|
// Trivial placeholder
|
||||||
|
return Math.max(...reservations.value.map((item) => item.id)) + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addOrCreateReservation = (reservation: Reservation) => {
|
||||||
|
const index = reservations.value.findIndex(
|
||||||
|
(res) => res.id == reservation.id
|
||||||
|
);
|
||||||
|
index != -1
|
||||||
|
? (reservations.value[index] = reservation)
|
||||||
|
: reservations.value.push(reservation);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
reservations,
|
||||||
|
getBoatReservations,
|
||||||
|
getNewId,
|
||||||
|
addOrCreateReservation,
|
||||||
|
isOverlapped,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user