Many tweaks to booking form.
This commit is contained in:
@@ -1,69 +1,110 @@
|
||||
<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>
|
||||
<q-calendar-month
|
||||
ref="calendar"
|
||||
<q-card-section>
|
||||
<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%; 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
|
||||
ref="calendar"
|
||||
v-model="selectedDate"
|
||||
:disabled-before="disabledBefore"
|
||||
animated
|
||||
bordered
|
||||
mini-mode
|
||||
date-type="rounded"
|
||||
@change="onChange"
|
||||
@moved="onMoved"
|
||||
@click-date="onClickDate"
|
||||
/>
|
||||
</div></div
|
||||
></q-card-section>
|
||||
<q-calendar-resource
|
||||
v-model="selectedDate"
|
||||
:model-resources="boatStore.boats"
|
||||
resource-key="id"
|
||||
resource-label="name"
|
||||
:interval-start="6"
|
||||
:interval-count="18"
|
||||
cell-width="64"
|
||||
resource-min-height="40"
|
||||
animated
|
||||
bordered
|
||||
mini-mode
|
||||
date-type="rounded"
|
||||
@change="onChange"
|
||||
@moved="onMoved"
|
||||
@resource-expanded="onResourceExpanded"
|
||||
@click-date="onClickDate"
|
||||
/>
|
||||
<div style="float: right; display: flex; max-width: 1024px; width: 100%">
|
||||
<q-calendar-resource
|
||||
ref="calendar"
|
||||
v-model="selectedDate"
|
||||
:model-resources="boatStore.boats"
|
||||
resource-key="id"
|
||||
resource-label="name"
|
||||
:interval-start="6"
|
||||
:interval-count="18"
|
||||
cell-width="64"
|
||||
resource-min-height="40"
|
||||
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 :label="event.title" :style="getStyle(event)" />
|
||||
</template>
|
||||
@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 :label="event.title" :style="getStyle(event)" />
|
||||
</template>
|
||||
</q-calendar-resource>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #resource-label="{ scope: { resource } }">
|
||||
<div class="col-12">
|
||||
{{ resource.name }}
|
||||
<q-icon v-if="resource.defects" name="warning" color="warning" />
|
||||
</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>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -73,10 +114,16 @@ import {
|
||||
TimestampOrNull,
|
||||
today,
|
||||
parseDate,
|
||||
parseTimestamp,
|
||||
addToDate,
|
||||
Timestamp,
|
||||
} from '@quasar/quasar-ui-qcalendar';
|
||||
import { Boat, useBoatStore } from 'src/stores/boat';
|
||||
import { useScheduleStore } from 'src/stores/schedule';
|
||||
import { date } from 'quasar';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const durations = [1, 1.5, 2, 2.5, 3, 3.5, 4];
|
||||
|
||||
type ResourceIntervalScope = {
|
||||
resource: Boat;
|
||||
@@ -86,15 +133,37 @@ type ResourceIntervalScope = {
|
||||
};
|
||||
|
||||
const statusLookup = {
|
||||
tentative: ['#f2c037', 'white'],
|
||||
confirmed: ['#14539a', 'white'],
|
||||
pending: ['white', 'grey'],
|
||||
pending: ['#f2c037', 'white'],
|
||||
tentative: ['white', 'grey'],
|
||||
};
|
||||
|
||||
const calendar = ref();
|
||||
const boatStore = useBoatStore();
|
||||
const scheduleStore = useScheduleStore();
|
||||
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) {
|
||||
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() {
|
||||
calendar.value.prev();
|
||||
}
|
||||
@@ -143,16 +209,28 @@ function onNext() {
|
||||
calendar.value.next();
|
||||
}
|
||||
function onClickDate(data) {
|
||||
emit('onClickDate', data);
|
||||
return;
|
||||
}
|
||||
function onClickTime(data) {
|
||||
// TODO: Add a duration picker, here.
|
||||
emit('onClickTime', data);
|
||||
}
|
||||
function onCloseSection() {
|
||||
emit('onCloseSection');
|
||||
}
|
||||
function onUpdateDuration(value) {
|
||||
emit('onUpdateDuration', value);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const onClickInterval = () => {};
|
||||
const onClickHeadResources = onClickInterval;
|
||||
const onClickResource = onClickInterval;
|
||||
const onResourceExpanded = onClickInterval;
|
||||
const onMoved = onClickInterval;
|
||||
const onChange = onClickInterval;
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const onClickHeadResources = () => {};
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const onClickResource = () => {};
|
||||
// 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>
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user