refactor: everything to nuxt.js
This commit is contained in:
168
app/components/ResourceScheduleViewerComponent.vue
Normal file
168
app/components/ResourceScheduleViewerComponent.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<!-- Abandoned: superseded by block-based booking. Retained for future reference. -->
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import type { TimestampOrNull, Timestamp } from '@quasar/quasar-ui-qcalendar';
|
||||
import {
|
||||
QCalendarResource,
|
||||
QCalendarMonth,
|
||||
today,
|
||||
parseTimestamp,
|
||||
addToDate,
|
||||
parsed,
|
||||
} from '@quasar/quasar-ui-qcalendar';
|
||||
import { useBoatStore } from '~/stores/boat';
|
||||
import type { Boat } from '~/utils/boat.types';
|
||||
import { useReservationStore } from '~/stores/reservation';
|
||||
import { date } from 'quasar';
|
||||
import { computed } from 'vue';
|
||||
import type { StatusTypes } from '~/utils/schedule.types';
|
||||
import { useIntervalStore } from '~/stores/interval';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
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 reservationStore = useReservationStore();
|
||||
const { selectedDate } = storeToRefs(useIntervalStore());
|
||||
const duration = ref(1);
|
||||
|
||||
const formattedMonth = computed(() => {
|
||||
const d = new Date(selectedDate.value);
|
||||
return monthFormatter()?.format(d);
|
||||
});
|
||||
|
||||
const disabledBefore = computed(() => {
|
||||
const todayTs = parseTimestamp(today()) as Timestamp;
|
||||
return addToDate(todayTs, { day: -1 }).date;
|
||||
});
|
||||
|
||||
function monthFormatter() {
|
||||
try {
|
||||
return new Intl.DateTimeFormat('en-CA', { month: 'long', timeZone: 'UTC' });
|
||||
} catch { /* */ }
|
||||
}
|
||||
|
||||
function getEvents(scope: ResourceIntervalScope) {
|
||||
const resourceEvents = reservationStore.getReservationsByDate(selectedDate.value, scope.resource.$id);
|
||||
return resourceEvents.value.map((event) => ({
|
||||
left: scope.timeStartPosX(parsed(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) { emit('onClickTime', data); }
|
||||
function onUpdateDuration(value: EventData) { emit('onUpdateDuration', value); }
|
||||
const onClickInterval = () => {};
|
||||
const onClickHeadResources = () => {};
|
||||
const onClickResource = () => {};
|
||||
const onResourceExpanded = () => {};
|
||||
const onMoved = () => {};
|
||||
const onChange = () => {};
|
||||
</script>
|
||||
|
||||
<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.
|
||||
</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="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>
|
||||
Reference in New Issue
Block a user