Files
bab-app/src/components/ResourceScheduleViewerComponent.vue
Patrick Toal d9cfa4ab56
All checks were successful
Build BAB Application Deployment Artifact / build (push) Successful in 2m1s
Convert type to interface
2024-04-29 08:37:15 -04:00

241 lines
6.2 KiB
Vue

<!-- 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>
<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"
>&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"
@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="displayName"
resource-width="32"
:interval-start="6"
:interval-count="18"
:interval-minutes="60"
cell-width="48"
style="--calendar-resources-width: 48px"
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>
</template>
<template #resource-label="{ scope: { resource } }">
<div class="col-12 .col-md-auto">
{{ resource.displayName }}
<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>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import {
QCalendarResource,
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';
import type { StatusTypes } from 'src/stores/schedule';
interface EventData {
event: object;
scope: {
timestamp: object;
columnindex: number;
activeDate: boolean;
droppable: boolean;
};
}
const durations = [1, 1.5, 2, 2.5, 3, 3.5, 4];
interface ResourceIntervalScope {
resource: Boat;
intervals: [];
timeStartPosX(start: TimestampOrNull): number;
timeDurationWidth(duration: number): number;
}
const statusLookup = {
confirmed: ['#14539a', 'white'],
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(
date.extractDate(selectedDate.value, 'YYYY-MM-DD'),
scope.resource.$id
);
return resourceEvents.map((event) => {
return {
left: scope.timeStartPosX(parseDate(event.start)),
width: scope.timeDurationWidth(
date.getDateDiff(event.end, event.start, 'minutes')
),
title: event.user,
status: event.status,
};
});
}
function getStyle(event: {
left: number;
width: number;
title: string;
status: StatusTypes;
}) {
return {
position: 'absolute',
background: event.status ? statusLookup[event.status][0] : 'white',
color: event.status ? statusLookup[event.status][1] : '#14539a',
left: `${event.left}px`,
width: `${event.width}px`,
height: '32px',
overflow: 'hidden',
};
}
const emit = defineEmits(['onClickTime', 'onUpdateDuration']);
function onPrev() {
calendar.value.prev();
}
function onNext() {
calendar.value.next();
}
function onClickDate(data: EventData) {
return data;
}
function onClickTime(data: EventData) {
// TODO: Add a duration picker, here.
emit('onClickTime', data);
}
function onUpdateDuration(value: EventData) {
emit('onUpdateDuration', value);
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
const 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>