More work on timeblocks
All checks were successful
Build BAB Application Deployment Artifact / build (push) Successful in 2m0s
All checks were successful
Build BAB Application Deployment Artifact / build (push) Successful in 2m0s
This commit is contained in:
@@ -3,108 +3,13 @@
|
||||
Use the calendar to pick a date. Select an available boat and timeslot
|
||||
below.
|
||||
</q-banner>
|
||||
<BoatScheduleTableComponent />
|
||||
<BoatScheduleTableComponent v-model="reservation" />
|
||||
</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';
|
||||
import BoatScheduleTableComponent from './boat/BoatScheduleTableComponent.vue';
|
||||
import { Reservation } from 'src/stores/schedule.types';
|
||||
|
||||
interface EventData {
|
||||
event: object;
|
||||
scope: {
|
||||
timestamp: object;
|
||||
columnindex: number;
|
||||
activeDate: boolean;
|
||||
droppable: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const calendar = ref();
|
||||
const scheduleStore = useScheduleStore();
|
||||
const selectedDate = ref(today());
|
||||
const selectedBoatTime = ref();
|
||||
|
||||
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);
|
||||
};
|
||||
const reservation = ref<Reservation | null>(null);
|
||||
</script>
|
||||
|
||||
@@ -14,18 +14,21 @@
|
||||
interval-start="06:00"
|
||||
:short-interval-label="true"
|
||||
v-model="selectedDate"
|
||||
:column-count="boats.length"
|
||||
@change="scrollToEvent()"
|
||||
:column-count="boatData.length"
|
||||
@change="changeEvent"
|
||||
v-touch-swipe.left.right="handleSwipe"
|
||||
>
|
||||
<template #head-day="{ scope }">
|
||||
<div style="text-align: center; font-weight: 800">
|
||||
{{ boats[scope.columnIndex].displayName }}
|
||||
{{ boatData[scope.columnIndex].displayName }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #day-body="{ scope }">
|
||||
<div v-for="block in blocklist" :key="block.id">
|
||||
<div
|
||||
v-for="block in boatData[scope.columnIndex].blocks"
|
||||
:key="block.id"
|
||||
>
|
||||
<div
|
||||
class="timeblock"
|
||||
:style="
|
||||
@@ -54,24 +57,26 @@ import {
|
||||
} from '@quasar/quasar-ui-qcalendar';
|
||||
import CalendarHeaderComponent from './CalendarHeaderComponent.vue';
|
||||
|
||||
import { ref, reactive, computed } from 'vue';
|
||||
import { useBoatStore } from 'src/stores/boat';
|
||||
import { Timeblock, useScheduleStore } from 'src/stores/schedule';
|
||||
import { TouchSwipeValue } from 'quasar';
|
||||
import { ref, computed } from 'vue';
|
||||
import { Boat, useBoatStore } from 'src/stores/boat';
|
||||
import { useScheduleStore } from 'src/stores/schedule';
|
||||
import { Reservation, Timeblock } from 'src/stores/schedule.types';
|
||||
|
||||
interface BoatData extends Boat {
|
||||
blocks?: Timeblock[];
|
||||
}
|
||||
|
||||
const scheduleStore = useScheduleStore();
|
||||
const boatStore = useBoatStore();
|
||||
const selectedBlock = ref<Timeblock | null>(null);
|
||||
const selectedDate = ref(today());
|
||||
const reservation = ref<Reservation | null>(null);
|
||||
|
||||
const blocklist = reactive<Timeblock[]>(
|
||||
scheduleStore.getTimeblocksForDate(parsed(today()) as Timestamp)
|
||||
);
|
||||
const boats = boatStore.boats;
|
||||
const boatData = ref<BoatData[]>(boatStore.boats);
|
||||
|
||||
const calendar = ref<QCalendarDay | null>(null);
|
||||
|
||||
function handleSwipe(event: Event) {
|
||||
function handleSwipe({ ...event }) {
|
||||
event.direction === 'right' ? calendar.value?.prev() : calendar.value?.next();
|
||||
}
|
||||
function blockStyles(
|
||||
@@ -84,16 +89,26 @@ function blockStyles(
|
||||
height: '',
|
||||
opacity: '',
|
||||
};
|
||||
if (timeStartPos && timeDurationHeight) {
|
||||
s.top = timeStartPos(block.start.time) + 'px';
|
||||
if (block && timeStartPos && timeDurationHeight) {
|
||||
s.top = timeStartPos(parsed(block.start)?.time || '00:00') + 'px';
|
||||
s.height =
|
||||
timeDurationHeight(
|
||||
diffTimestamp(block.start, block.end, false) / 1000 / 60
|
||||
) + 'px';
|
||||
}
|
||||
if (selectedBlock.value?.id === block.id) {
|
||||
s.opacity = '1.0';
|
||||
parseInt(
|
||||
timeDurationHeight(
|
||||
diffTimestamp(
|
||||
parsed(block.start) as Timestamp,
|
||||
parsed(block.end) as Timestamp,
|
||||
false
|
||||
) /
|
||||
1000 /
|
||||
60
|
||||
)
|
||||
) -
|
||||
1 +
|
||||
'px';
|
||||
}
|
||||
// if (selectedBlock.value?.id === block.id) {
|
||||
// s.opacity = '1.0';
|
||||
// }
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -109,38 +124,42 @@ function selectBlock(event: MouseEvent, scope: DayBodyScope, block: Timeblock) {
|
||||
selectedBlock.value = block;
|
||||
}
|
||||
|
||||
function scrollToEvent() {
|
||||
function changeEvent({ start }: { start: string }) {
|
||||
const newBlocks = scheduleStore.getTimeblocksForDate(start);
|
||||
boatData.value.map((b) => {
|
||||
return (b.blocks = newBlocks.filter((block) => block.boatId === b.$id));
|
||||
});
|
||||
setTimeout(() => calendar.value?.scrollToTime('09:00'), 10); // Should figure out why we need this setTimeout...
|
||||
}
|
||||
|
||||
const disabledBefore = computed(() => {
|
||||
let ts = parseTimestamp(today());
|
||||
ts = addToDate(ts, { day: -1 });
|
||||
return ts.date;
|
||||
const todayTs = parseTimestamp(today()) as Timestamp;
|
||||
return addToDate(todayTs, { day: -1 }).date;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
.boat-schedule-table-component
|
||||
display: flex
|
||||
height: 60vh
|
||||
max-height: 60vh
|
||||
.timeblock
|
||||
display: flex
|
||||
position: absolute
|
||||
justify-content: center
|
||||
align-items: center
|
||||
width: 100%
|
||||
opacity: 0.25
|
||||
margin: 0 1px
|
||||
width: 99%
|
||||
opacity: 0.5
|
||||
margin: 0px
|
||||
text-overflow: ellipsis
|
||||
overflow: hidden
|
||||
font-size: 0.8em
|
||||
cursor: pointer
|
||||
background: $primary
|
||||
color: white
|
||||
border: 2px solid black
|
||||
border: 1px solid black
|
||||
.selected
|
||||
opacity: 1 !important
|
||||
.q-calendar-day__interval--text
|
||||
font-size: 0.8em
|
||||
.q-calendar-day__day.q-current-day
|
||||
padding: 1px
|
||||
</style>
|
||||
|
||||
@@ -69,7 +69,7 @@ and rough engine performance.`,
|
||||
imgsrc: '/tmpimg/capri25.png',
|
||||
},
|
||||
{
|
||||
$id: '3',
|
||||
$id: '4',
|
||||
name: 'Just My Imagination',
|
||||
displayName: 'JMI',
|
||||
class: 'Capri 25',
|
||||
|
||||
@@ -1,40 +1,66 @@
|
||||
import { DateOptions, date } from 'quasar';
|
||||
import { Boat, useBoatStore } from '../boat';
|
||||
import type { StatusTypes, Timeblock, Reservation } from '../schedule.types';
|
||||
import { ID } from 'src/boot/appwrite';
|
||||
import {
|
||||
parseTimestamp,
|
||||
today,
|
||||
Timestamp,
|
||||
addToDate,
|
||||
} from '@quasar/quasar-ui-qcalendar';
|
||||
|
||||
export const weekdayBlocks = [
|
||||
{
|
||||
start: { time: '08:00', hour: 8, minute: 0 },
|
||||
end: { time: '12:00', hour: 12, minute: 0 },
|
||||
},
|
||||
{
|
||||
start: { time: '12:00', hour: 12, minute: 0 },
|
||||
end: { time: '16:00', hour: 16, minute: 0 },
|
||||
},
|
||||
{
|
||||
start: { time: '17:00', hour: 17, minute: 0 },
|
||||
end: { time: '21:00', hour: 21, minute: 0 },
|
||||
},
|
||||
] as Timeblock[];
|
||||
import type {
|
||||
StatusTypes,
|
||||
Reservation,
|
||||
TimeBlockTemplate,
|
||||
Timeblock,
|
||||
} from '../schedule.types';
|
||||
|
||||
export const weekendBlocks = [
|
||||
{
|
||||
start: { time: '07:00', hour: 7, minute: 0 },
|
||||
end: { time: '10:00', hour: 10, minute: 0 },
|
||||
},
|
||||
{
|
||||
start: { time: '10:00', hour: 10, minute: 0 },
|
||||
end: { time: '13:00', hour: 13, minute: 0 },
|
||||
},
|
||||
{
|
||||
start: { time: '13:00', hour: 13, minute: 0 },
|
||||
end: { time: '16:00', hour: 16, minute: 0 },
|
||||
},
|
||||
{
|
||||
start: { time: '16:00', hour: 16, minute: 0 },
|
||||
end: { time: '19:00', hour: 19, minute: 0 },
|
||||
},
|
||||
];
|
||||
export const templateA: TimeBlockTemplate = {
|
||||
id: '1',
|
||||
name: 'WeekdayBlocks',
|
||||
blocks: [
|
||||
['08:00', '12:00'],
|
||||
['12:00', '16:00'],
|
||||
['17:00', '21:00'],
|
||||
],
|
||||
};
|
||||
|
||||
export const templateB: TimeBlockTemplate = {
|
||||
id: '2',
|
||||
name: 'WeekendBlocks',
|
||||
blocks: [
|
||||
['07:00', '10:00'],
|
||||
['10:00', '13:00'],
|
||||
['13:00', '16:00'],
|
||||
['16:00', '19:00'],
|
||||
],
|
||||
};
|
||||
|
||||
export function getSampleTimeBlocks(): Timeblock[] {
|
||||
// Hard-code 30 days worth of blocks, for now. Make them random templates
|
||||
const boats = useBoatStore().boats;
|
||||
const result: Timeblock[] = [];
|
||||
const tsToday: Timestamp = parseTimestamp(today()) as Timestamp;
|
||||
|
||||
for (let i = 0; i <= 30; i++) {
|
||||
const template = Math.random() < 0.5 ? templateA : templateB;
|
||||
result.push(
|
||||
...boats
|
||||
.map((b): Timeblock[] => {
|
||||
return template.blocks.map((t): Timeblock => {
|
||||
return {
|
||||
id: 'id' + Math.random().toString(32).slice(2),
|
||||
boatId: b.$id,
|
||||
start: addToDate(tsToday, { day: i }).date + ' ' + t[0],
|
||||
end: addToDate(tsToday, { day: i }).date + ' ' + t[1],
|
||||
};
|
||||
});
|
||||
})
|
||||
.flat(2)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getSampleReservations(): Reservation[] {
|
||||
const sampleData = [
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { Boat } from './boat';
|
||||
import { Timestamp, parseDate } from '@quasar/quasar-ui-qcalendar';
|
||||
import {
|
||||
Timestamp,
|
||||
parseDate,
|
||||
parsed,
|
||||
compareDate,
|
||||
} from '@quasar/quasar-ui-qcalendar';
|
||||
|
||||
import { Reservation, Timeblock } from './schedule.types';
|
||||
import { weekdayBlocks, getSampleReservations } from './sampledata/schedule';
|
||||
import {
|
||||
getSampleReservations,
|
||||
getSampleTimeBlocks,
|
||||
} from './sampledata/schedule';
|
||||
|
||||
export const useScheduleStore = defineStore('schedule', () => {
|
||||
// TODO: Implement functions to dynamically pull this data.
|
||||
const reservations = ref<Reservation[]>(getSampleReservations());
|
||||
const timeblocks = weekdayBlocks;
|
||||
|
||||
const getTimeblocksForDate = (date: Timestamp): Timeblock[] => {
|
||||
return timeblocks.map((t) => {
|
||||
return {
|
||||
// Update all the start/end times with the passed in date
|
||||
...t,
|
||||
start: { ...date, ...t.start },
|
||||
end: { ...date, ...t.end },
|
||||
};
|
||||
});
|
||||
const getTimeblocksForDate = (date: string): Timeblock[] => {
|
||||
return getSampleTimeBlocks().filter((b) =>
|
||||
compareDate(parsed(b.start) as Timestamp, parsed(date) as Timestamp)
|
||||
);
|
||||
};
|
||||
|
||||
const getBoatReservations = (
|
||||
@@ -27,7 +29,6 @@ export const useScheduleStore = defineStore('schedule', () => {
|
||||
boat?: string
|
||||
): Reservation[] => {
|
||||
return reservations.value.filter((x) => {
|
||||
console.log(searchDate);
|
||||
return (
|
||||
((parseDate(x.start)?.date == searchDate.date ||
|
||||
parseDate(x.end)?.date == searchDate.date) && // Part of reservation falls on day
|
||||
@@ -38,6 +39,21 @@ export const useScheduleStore = defineStore('schedule', () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 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);
|
||||
// };
|
||||
const getConflictingReservations = (
|
||||
resource: Boat,
|
||||
start: Date,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Timestamp } from '@quasar/quasar-ui-qcalendar';
|
||||
import type { Boat } from './boat';
|
||||
|
||||
export type StatusTypes = 'tentative' | 'confirmed' | 'pending' | undefined;
|
||||
@@ -17,8 +16,17 @@ export interface Reservation {
|
||||
e.g.: Should there be any qcalendar stuff in this store? Or should we have just JS Date
|
||||
objects in here? */
|
||||
|
||||
export type timeTuple = [start: string, end: string];
|
||||
export interface Timeblock {
|
||||
start: Timestamp;
|
||||
end: Timestamp;
|
||||
id: string;
|
||||
boatId: string;
|
||||
start: string;
|
||||
end: string;
|
||||
selected?: false;
|
||||
}
|
||||
|
||||
export interface TimeBlockTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
blocks: timeTuple[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user