Developing Booking Form

This commit is contained in:
2023-11-26 09:21:04 -05:00
parent 8200bcde52
commit a3cdbbfbbd
13 changed files with 423 additions and 172 deletions

View File

@@ -1,165 +1,181 @@
<template>
<div class="row justify-center">
<q-btn-group rounded>
<q-btn
color="primary"
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 class="row justify-center">
<div style="display: flex; max-width: 1024px; width: 100%">
<q-calendar-resource
ref="calendar"
v-model="selectedDate"
:model-resources="resources"
resource-key="id"
resource-label="name"
:interval-start="6"
:interval-count="12"
animated
bordered
@change="onChange"
@moved="onMoved"
@resource-expanded="onResourceExpanded"
@click-date="onClickDate"
@click-time="onClickTime"
@click-resource="onClickResource"
@click-head-resources="onClickHeadResources"
@click-interval="onClickInterval"
>
<template #resource-intervals="{ scope }">
<template v-for="(event, index) in getEvents(scope)" :key="index">
<q-badge
outline
color="primary"
:label="event.title"
:style="getStyle(event)"
/>
<q-page padding>
<q-card>
<q-form>
<q-input bottom-slots v-model="bookingForm.name" label="Name" readonly>
<template v-slot:prepend>
<q-icon name="person" />
</template>
</template>
</q-calendar-resource>
</div>
</div>
</q-input>
<q-select
v-model="bookingForm.boat"
:options="boats"
option-value="id"
option-label="name"
label="Boat"
>
<template v-slot:prepend>
<q-item-section avatar>
<q-img
v-if="bookingForm.boat?.iconsrc"
:src="bookingForm.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 defects. Select it to see details.
</q-tooltip>
</q-item-section>
</q-item>
</template>
</q-select>
<q-banner
rounded
class="bg-warning text-grey-10"
v-if="bookingForm.boat?.defects"
>
<template v-slot:avatar>
<q-icon name="warning" color="grey-10" />
</template>
This boat currently has the following defects:
<ol>
<li
v-for="defect in bookingForm.boat.defects"
:key="defect.description"
>
{{ defect.description }}
</li>
</ol>
</q-banner>
<q-input v-model="bookingForm.startDate" label="Check-Out" 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" 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
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>
<div>Booking Duration: {{ bookingDuration }} hours</div>
</q-form>
</q-card>
</q-page>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { QCalendarResource, today } from '@quasar/quasar-ui-qcalendar';
import { ref, computed } from 'vue';
import { useAuthStore } from 'src/stores/auth';
import { Boat, useBoatStore } from 'src/stores/boat';
import { date } from 'quasar';
const selectedDate = ref(today());
type Resource = {
id: string;
name: string;
expanded?: boolean;
children?: Resource[];
};
type Event = {
start: string;
title: string;
duration: number;
left?: number;
width?: number;
};
type ResourceIntervalScope = {
resource: Resource;
intervals: [];
timeStartPosX(start: string): number;
timeDurationWidth(duration: number): number;
};
const calendar = ref(null);
const resources = reactive<Resource[]>([
{ id: '1', name: 'ProjectX' },
{ id: '2', name: 'Take 5' },
{ id: '3', name: 'WeeBeestie' },
]);
const events = reactive<{ [key: string]: Event[] }>({
1: [
{ start: '06:00', title: 'John Smith', duration: 90 },
{ start: '12:00', title: 'Bob Barker', duration: 60 },
],
2: [
{ start: '08:00', title: 'Peter Parker', duration: 120 },
{ start: '11:00', title: 'Vince McMahon', duration: 240 },
],
3: [
{ start: '08:00', title: 'Heather Graham', duration: 240 },
{ start: '14:00', title: 'Lawrence Fishburne', duration: 60 },
],
const auth = useAuthStore();
const boats = useBoatStore().boats;
const startDateOrTime = ref(true);
const endDateOrTime = ref(true);
const bookingForm = ref({
name: auth.currentUser?.name,
boat: <Boat | undefined>undefined,
startDate: date.formatDate(new Date(), 'ddd MMM D, YYYY h:mm A'),
endDate: date.formatDate(
date.addToDate(new Date(), { hours: 2 }),
'ddd MMM D, YYYY h:mm A'
),
});
function getEvents(scope: ResourceIntervalScope) {
const resourceEvents = events[scope.resource.id];
return resourceEvents.map((event) => ({
left: scope.timeStartPosX(event.start),
width: scope.timeDurationWidth(event.duration),
title: event.title,
}));
}
const bookingDuration = computed(() => {
const diff = date.getDateDiff(
bookingForm.value.endDate,
bookingForm.value.startDate,
'hours'
);
return diff > 0 ? diff : 'Invalid.';
});
function getStyle(event: { left: number; width: number; title: string }) {
return {
position: 'absolute',
background: 'white',
left: `${event.left}px`,
width: `${event.width}px`,
height: '40px',
overflow: 'hidden',
};
}
const limitDate = (startDate: string) => {
return date.isBetweenDates(
startDate,
new Date(),
date.addToDate(new Date(), { days: 21 })
);
};
function onToday() {
calendar.value.moveToToday();
}
function onPrev() {
calendar.value.prev();
}
function onNext() {
calendar.value.next();
}
function onMoved(data) {
console.log('onMoved', data);
}
function onChange(data) {
console.log('onChange', data);
}
function onResourceExpanded(data) {
console.log('onResourceExpanded', data);
}
function onClickDate(data) {
console.log('onClickDate', data);
}
function onClickTime(data) {
console.log('onClickTime', data);
}
function onClickResource(data) {
console.log('onClickResource', data);
}
function onClickHeadResources(data) {
console.log('onClickHeadResources', data);
}
function onClickInterval(data) {
console.log('onClickInterval', data);
}
const minOpts = [0, 15, 30, 45, 60];
</script>

View File

@@ -0,0 +1,8 @@
<template>
<q-page padding>
<!-- content -->
</q-page>
</template>
<script setup lang="ts">
</script>

View File

@@ -1,5 +1,27 @@
<template>
<q-page padding> </q-page>
<q-page padding>
<q-item v-for="link in navlinks" :key="link.label">
<q-btn
:icon="link.icon"
color="primary"
size="1.25em"
:to="link.to"
:label="link.label"
rounded
class="full-width"
align="left"
/>
</q-item>
</q-page>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
const navlinks = [
{
icon: 'more_time',
to: '/schedule/book',
label: 'Create a Reservation',
},
{ icon: 'calendar_month', to: '/schedule/view', label: 'View Schedule' },
];
</script>