All checks were successful
Build BAB Application Deployment Artifact / build (push) Successful in 2m33s
194 lines
4.6 KiB
Vue
194 lines
4.6 KiB
Vue
<template>
|
|
<q-card-section style="max-width: 320px">
|
|
<div class="text-caption">
|
|
Use the calendar to pick a date. Select an available boat and timeslot
|
|
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"
|
|
><</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"
|
|
:disabled-days="disabledDays()"
|
|
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"
|
|
v-model="selectedBoatTime"
|
|
>
|
|
<template v-slot:label="opt">
|
|
<div class="row items-center">
|
|
{{ opt.label }}
|
|
<span class="text-caption" 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,
|
|
parseTimestamp,
|
|
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 selectedBoatTime = ref();
|
|
|
|
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) {
|
|
//
|
|
}
|
|
}
|
|
|
|
const disabledDays = () => {
|
|
// Placeholder. This should actually compute days when boats aren't available.
|
|
const days = [];
|
|
const todayTs = parseTimestamp(today()) as Timestamp;
|
|
days.push(addToDate(todayTs, { day: 2 }).date);
|
|
return days;
|
|
};
|
|
|
|
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.id + ':' + x.start.time,
|
|
disable: conflicts.length > 0,
|
|
user: conflicts[0]?.user,
|
|
boat: boat,
|
|
timeblock: x,
|
|
};
|
|
});
|
|
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>
|