Compare commits

8 Commits

Author SHA1 Message Date
ea785887a1 Sorted out reactivity with storeToRefs
Some checks failed
Build BAB Application Deployment Artifact / build (push) Failing after 2m1s
2024-05-08 23:43:18 -04:00
b860e1d977 Add some checks 2024-05-08 17:23:23 -04:00
274d0193f7 Some timeblock stuff working 2024-05-08 13:32:10 -04:00
033993b1b8 Upgrade Quasar 2024-05-06 19:22:28 -04:00
2872fb867e Started work on Schedule Management 2024-05-06 17:22:11 -04:00
8e73650462 Clean up all kinds of typescript issues 2024-05-05 15:58:58 -04:00
634cff507c Converted some schedule to use backend 2024-05-04 23:17:05 -04:00
fa4d83e42d Cleanup linting messages. Also, break some things 2024-05-04 12:08:16 -04:00
33 changed files with 1126 additions and 417 deletions

4
appwrite.json Normal file
View File

@@ -0,0 +1,4 @@
{
"projectId": "65ede55a213134f2b688",
"projectName": ""
}

View File

@@ -14,14 +14,14 @@
},
"dependencies": {
"@quasar/extras": "^1.16.11",
"appwrite": "^13.0.0",
"appwrite": "^14.0.1",
"pinia": "^2.1.7",
"vue": "3",
"vue-router": "4"
},
"devDependencies": {
"@quasar/app-vite": "^1.7.4",
"@quasar/quasar-app-extension-qcalendar": "^4.0.0-beta.15",
"@quasar/app-vite": "^1.9.1",
"@quasar/quasar-app-extension-qcalendar": "^4.0.0-beta.16",
"@types/node": "^12.20.21",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
@@ -31,8 +31,10 @@
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-vue": "^9.0.0",
"prettier": "^2.5.1",
"quasar": "^2.15.2",
"typescript": "^4.5.4",
"quasar": "^2.16.0",
"typescript": "~5.3.0",
"vite-plugin-checker": "^0.6.4",
"vue-tsc": "^1.8.22",
"workbox-build": "^7.0.0",
"workbox-cacheable-response": "^7.0.0",
"workbox-core": "^7.0.0",

View File

@@ -9,7 +9,6 @@
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
const { configure } = require('quasar/wrappers');
const path = require('path');
module.exports = configure(function (/* ctx */) {
return {
@@ -73,9 +72,20 @@ module.exports = configure(function (/* ctx */) {
// extendViteConf (viteConf) {},
// viteVuePluginOptions: {},
// vitePlugins: [
// [ 'package-name', { ..options.. } ]
// ]
vitePlugins: [
[
'vite-plugin-checker',
{
vueTsc: {
tsconfigPath: 'tsconfig.vue-tsc.json',
},
eslint: {
lintCommand: 'eslint "./**/*.{js,ts,mjs,cjs,vue}"',
},
},
{ server: false },
],
],
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
@@ -104,7 +114,7 @@ module.exports = configure(function (/* ctx */) {
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
framework: {
config: {
autoImportComponentCase: 'combined', // or 'kebab' (default) or 'combined'
autoImportComponentCase: 'kebab', // or 'kebab' (default) or 'combined'
},
// iconSet: 'material-icons', // Quasar icon set

View File

@@ -2,10 +2,20 @@
<router-view />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
<script setup lang="ts">
import { defineComponent, onMounted } from 'vue';
import { useScheduleStore } from './stores/schedule';
import { useBoatStore } from './stores/boat';
import { useAuthStore } from './stores/auth';
export default defineComponent({
defineComponent({
name: 'OYS Borrow-a-Boat',
});
onMounted(async () => {
await useAuthStore().init();
await useScheduleStore().fetchTimeBlockTemplates();
await useScheduleStore().fetchTimeBlocks();
await useBoatStore().fetchBoats();
});
</script>

View File

@@ -13,9 +13,10 @@ const client = new Client();
// const appDatabaseId = '654ac5044d1c446feb71';
// Private self-hosted appwrite
client
.setEndpoint(process.env.APPWRITE_API_ENDPOINT)
.setProject(process.env.APPWRITE_API_PROJECT);
if (process.env.APPWRITE_API_ENDPOINT && process.env.APPWRITE_API_PROJECT)
client
.setEndpoint(process.env.APPWRITE_API_ENDPOINT)
.setProject(process.env.APPWRITE_API_PROJECT);
//TODO move this to config file
const AppwriteIds = {
@@ -24,7 +25,9 @@ const AppwriteIds = {
task: '65ee1cd5b550023fae4f',
taskTags: '65ee21d72d5c8007c34c',
skillTags: '66072582a74d94a4bd01',
boats: '66341910003e287cd71c',
boat: '66341910003e287cd71c',
timeBlock: '66361869002883fb4c4b',
timeBlockTemplate: '66361f480007fdd639af',
},
};

View File

@@ -1,64 +0,0 @@
<template>
<div>
<p>{{ title }}</p>
<ul>
<li v-for="todo in todos" :key="todo.id" @click="increment">
{{ todo.id }} - {{ todo.content }}
</li>
</ul>
<p>Count: {{ todoCount }} / {{ meta.totalCount }}</p>
<p>Active: {{ active ? 'yes' : 'no' }}</p>
<p>Clicks on todos: {{ clickCount }}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent,
PropType,
computed,
ref,
toRef,
Ref,
} from 'vue';
import { Todo, Meta } from './models';
function useClickCount() {
const clickCount = ref(0);
function increment() {
clickCount.value += 1
return clickCount.value;
}
return { clickCount, increment };
}
function useDisplayTodo(todos: Ref<Todo[]>) {
const todoCount = computed(() => todos.value.length);
return { todoCount };
}
export default defineComponent({
name: 'ExampleComponent',
props: {
title: {
type: String,
required: true
},
todos: {
type: Array as PropType<Todo[]>,
default: () => []
},
meta: {
type: Object as PropType<Meta>,
required: true
},
active: {
type: Boolean
}
},
setup (props) {
return { ...useClickCount(), ...useDisplayTodo(toRef(props, 'todos')) };
},
});
</script>

View File

@@ -116,7 +116,7 @@ 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';
import type { StatusTypes } from 'src/stores/schedule.types';
interface EventData {
event: object;
@@ -172,7 +172,7 @@ function monthFormatter() {
function getEvents(scope: ResourceIntervalScope) {
const resourceEvents = scheduleStore.getBoatReservations(
date.extractDate(selectedDate.value, 'YYYY-MM-DD'),
parseDate(date.extractDate(selectedDate.value, 'YYYY-MM-DD')) as Timestamp,
scope.resource.$id
);

View File

@@ -1,23 +1,26 @@
<template>
<q-card v-for="boat in boats" :key="boat.id" flat class="mobile-card">
<q-card-section>
<q-img :src="boat.imgSrc" :fit="'scale-down'">
<div class="row absolute-top">
<div class="col text-h6 text-left">{{ boat.name }}</div>
<div class="col text-right">{{ boat.class }}</div>
</div>
</q-img>
</q-card-section>
<div v-if="boats">
<q-card v-for="boat in boats" :key="boat.id" flat class="mobile-card">
<q-card-section>
<q-img :src="boat.imgSrc" :fit="'scale-down'">
<div class="row absolute-top">
<div class="col text-h6 text-left">{{ boat.name }}</div>
<div class="col text-right">{{ boat.class }}</div>
</div>
</q-img>
</q-card-section>
<q-separator />
<q-separator />
<q-card-actions align="evenly">
<q-btn flat>Info</q-btn>
<q-btn flat>Book</q-btn>
<q-btn flat>Check-Out</q-btn>
<q-btn flat>Check-In</q-btn>
</q-card-actions>
</q-card>
<q-card-actions align="evenly">
<q-btn flat>Info</q-btn>
<q-btn flat>Book</q-btn>
<q-btn flat>Check-Out</q-btn>
<q-btn flat>Check-In</q-btn>
</q-card-actions>
</q-card>
</div>
<div v-else><q-card>Sorry, no boats to show you!</q-card></div>
</template>
<script setup lang="ts">

View File

@@ -0,0 +1,19 @@
<template>
<div class="row justify-center">
<div class="q-pa-md q-gutter-sm row">
<q-btn no-caps class="button" style="margin: 2px" @click="$emit('today')">
Today
</q-btn>
<q-btn no-caps class="button" style="margin: 2px" @click="$emit('prev')">
&lt; Prev
</q-btn>
<q-btn no-caps class="button" style="margin: 2px" @click="$emit('next')">
Next &gt;
</q-btn>
</div>
</div>
</template>
<script setup lang="ts">
defineEmits(['today', 'prev', 'next']);
</script>

View File

@@ -0,0 +1,129 @@
<template>
<q-card>
<q-card-section horizontal>
<div>
<q-input
label="Template name"
filled
dense
v-model="template.name"
v-if="editable"
/>
<q-list dense>
<q-item v-for="item in template.timeTuples" :key="item[0]">
<q-input
dense
:filled="editable"
v-model="item[0]"
type="time"
label="Start"
:readonly="!editable"
/>
<q-input
:filled="editable"
dense
v-model="item[1]"
type="time"
label="End"
:readonly="!editable"
/> </q-item></q-list
><q-btn
v-if="editable"
dense
color="primary"
size="sm"
label="Add interval"
@click="template.timeTuples.push(['00:00', '00:00'])"
/>
</div>
<q-card-actions align="right" vertical>
<q-btn
v-if="!editable"
color="primary"
icon="edit"
label="Edit"
@click="editable = !editable"
/>
<q-btn
v-if="editable"
color="primary"
icon="save"
label="Save"
@click="saveTemplate($event, template)"
/>
<q-btn
v-if="editable"
color="secondary"
icon="cancel"
label="Cancel"
@click="revert"
/>
<q-btn
color="negative"
icon="delete"
label="Delete"
@click="deleteTemplate($event, template)"
/>
</q-card-actions>
</q-card-section>
</q-card>
<q-dialog v-model="alert">
<q-card>
<q-card-section>
<div class="text-h6">Overlapped blocks!</div>
</q-card-section>
<q-card-section class="q-pt-none">
<q-chip
square
icon="schedule"
v-for="item in overlapped"
:key="item.start"
>
{{ item.start }}-{{ item.end }}</q-chip
>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="OK" color="primary" v-close-popup />
</q-card-actions> </q-card
></q-dialog>
</template>
<script setup lang="ts">
import { timeTuplesOverlapped, useScheduleStore } from 'src/stores/schedule';
import { TimeBlockTemplate } from 'src/stores/schedule.types';
import { ref } from 'vue';
const editable = ref(false);
const alert = ref(false);
const overlapped = ref();
const scheduleStore = useScheduleStore();
const template = defineModel<TimeBlockTemplate>({ required: true });
const revert = () => {
editable.value = false;
};
const deleteTemplate = (
event: Event,
template: TimeBlockTemplate | undefined
) => {
if (template?.$id) scheduleStore.deleteTimeBlockTemplate(template.$id);
};
// const edit = () => {
// // TODO: Make it so that editing the template does not affect the store. Need to be able to "cancel" editing, and revert to original data
// };
const saveTemplate = (evt: Event, template: TimeBlockTemplate | undefined) => {
if (!template) return false;
overlapped.value = timeTuplesOverlapped(template.timeTuples);
if (overlapped.value.length > 0) {
alert.value = true;
} else {
if (template.$id) {
console.log(template.$id);
scheduleStore.updateTimeBlockTemplate(template, template.$id);
} else {
scheduleStore.createTimeBlockTemplate(template);
}
editable.value = false;
}
};
</script>

View File

@@ -14,36 +14,32 @@
interval-start="06:00"
:short-interval-label="true"
v-model="selectedDate"
:column-count="boatData.length"
@change="changeEvent"
:column-count="boats.length"
v-touch-swipe.left.right="handleSwipe"
>
<template #head-day="{ scope }">
<div style="text-align: center; font-weight: 800">
{{ boatData[scope.columnIndex].displayName }}
{{ getBoatDisplayName(scope) }}
</div>
</template>
<template #day-body="{ scope }">
<div
v-for="block in boatData[scope.columnIndex].blocks"
:key="block.id"
>
<div v-for="block in getBoatBlocks(scope)" :key="block.$id">
<div
class="timeblock"
:class="selectedBlock?.id === block.id ? 'selected' : ''"
:class="selectedBlock?.$id === block.$id ? 'selected' : ''"
:style="
blockStyles(block, scope.timeStartPos, scope.timeDurationHeight)
"
:id="block.id"
@click="selectBlock($event, scope, block)"
>
{{ boatData[scope.columnIndex].name }}<br />
{{ selectedBlock?.id === block.id ? 'Selected' : 'Available' }}
{{ boats[scope.columnIndex].name }}<br />
{{ selectedBlock?.$id === block.$id ? 'Selected' : 'Available' }}
</div>
</div>
<div
v-for="r in boatData[scope.columnIndex].reservations"
<!-- <div
v-for="r in boats[scope.columnIndex].reservations"
:key="r.id"
>
<div
@@ -58,7 +54,7 @@
>
{{ r.user }}
</div>
</div>
</div> -->
</template>
</QCalendarDay>
</div>
@@ -71,61 +67,58 @@ import {
Timestamp,
diffTimestamp,
today,
parsed,
parseTimestamp,
parseDate,
addToDate,
makeDateTime,
} from '@quasar/quasar-ui-qcalendar';
import CalendarHeaderComponent from './CalendarHeaderComponent.vue';
import { ref, computed } from 'vue';
import { Boat, useBoatStore } from 'src/stores/boat';
import { useBoatStore } from 'src/stores/boat';
import { useScheduleStore } from 'src/stores/schedule';
import { Reservation, Timeblock } from 'src/stores/schedule.types';
import { date } from 'quasar';
interface BoatData extends Boat {
blocks?: Timeblock[];
reservations?: Reservation[];
}
import { TimeBlock } from 'src/stores/schedule.types';
import { storeToRefs } from 'pinia';
const scheduleStore = useScheduleStore();
const boatStore = useBoatStore();
const selectedBlock = defineModel<Timeblock | null>();
const { boats } = storeToRefs(useBoatStore());
const selectedBlock = defineModel<TimeBlock | null>();
const selectedDate = ref(today());
const boatData = ref<BoatData[]>(boatStore.boats);
const calendar = ref<QCalendarDay | null>(null);
function handleSwipe({ ...event }) {
event.direction === 'right' ? calendar.value?.prev() : calendar.value?.next();
}
function reservationStyles(
reservation: Reservation,
// function reservationStyles(
// reservation: Reservation,
// timeStartPos: (t: string) => string,
// timeDurationHeight: (d: number) => string
// ) {
// return genericBlockStyle(
// parseDate(reservation.start) as Timestamp,
// parseDate(reservation.end) as Timestamp,
// timeStartPos,
// timeDurationHeight
// );
// }
function blockStyles(
block: TimeBlock,
timeStartPos: (t: string) => string,
timeDurationHeight: (d: number) => string
) {
return genericBlockStyle(
parseDate(reservation.start) as Timestamp,
parseDate(reservation.end) as Timestamp,
parseDate(new Date(block.start)) as Timestamp,
parseDate(new Date(block.end)) as Timestamp,
timeStartPos,
timeDurationHeight
);
}
function blockStyles(
block: Timeblock,
timeStartPos: (t: string) => string,
timeDurationHeight: (d: number) => string
) {
return genericBlockStyle(
parsed(block.start) as Timestamp,
parsed(block.end) as Timestamp,
timeStartPos,
timeDurationHeight
);
function getBoatDisplayName(scope: DayBodyScope) {
return boats && boats.value[scope.columnIndex]
? boats.value[scope.columnIndex].displayName
: '';
}
function genericBlockStyle(
@@ -161,42 +154,61 @@ interface DayBodyScope {
timestamp: Timestamp;
}
function selectBlock(event: MouseEvent, scope: DayBodyScope, block: Timeblock) {
function selectBlock(event: MouseEvent, scope: DayBodyScope, block: TimeBlock) {
// TODO: Disable blocks before today with updateDisabled and/or comparison
selectedBlock.value === block
? (selectedBlock.value = null)
: (selectedBlock.value = block);
}
function changeEvent({ start }: { start: string }) {
const newBlocks = scheduleStore.getTimeblocksForDate(start);
const reservations = scheduleStore.getBoatReservations(
parsed(start) as Timestamp
);
boatData.value.map((boat) => {
boat.reservations = reservations.filter(
(reservation) => reservation.resource === boat
);
boat.blocks = newBlocks.filter(
(block) =>
block.boatId === boat.$id &&
boat.reservations?.filter(
(r) =>
r.start <
date.addToDate(makeDateTime(parsed(block.end) as Timestamp), {
hours: 4,
}) &&
r.end >
date.addToDate(makeDateTime(parsed(block.start) as Timestamp), {
hours: 4,
})
).length == 0
);
});
setTimeout(() => calendar.value?.scrollToTime('09:00'), 100); // Should figure out why we need this setTimeout...
interface BoatBlocks {
[key: string]: TimeBlock[];
}
const boatBlocks = computed((): BoatBlocks => {
return scheduleStore
.getTimeBlocksForDate(selectedDate.value)
.reduce((result, tb) => {
if (!result[tb.boatId]) result[tb.boatId] = [];
result[tb.boatId].push(tb);
return result;
}, <BoatBlocks>{});
});
function getBoatBlocks(scope: DayBodyScope): TimeBlock[] {
return boats.value[scope.columnIndex]
? boatBlocks.value[boats.value[scope.columnIndex].$id]
: [];
}
// function changeEvent({ start }: { start: string }) {
// const newBlocks = scheduleStore.getTimeBlocksForDate(start);
// const reservations = scheduleStore.getBoatReservations(
// parsed(start) as Timestamp
// );
// boats.value.map((boat) => {
// boat.reservations = reservations.filter(
// (reservation) => reservation.resource === boat
// );
// boat.blocks = newBlocks.filter(
// (block) =>
// block.boatId === boat.$id &&
// boat.reservations?.filter(
// (r: Reservation) =>
// r.start <
// date.addToDate(makeDateTime(parsed(block.end) as Timestamp), {
// hours: 4,
// }) &&
// r.end >
// date.addToDate(makeDateTime(parsed(block.start) as Timestamp), {
// hours: 4,
// })
// ).length == 0
// );
// });
// setTimeout(() => calendar.value?.scrollToTime('09:00'), 100); // Should figure out why we need this setTimeout...
// }
const disabledBefore = computed(() => {
const todayTs = parseTimestamp(today()) as Timestamp;
return addToDate(todayTs, { day: -1 }).date;

View File

@@ -57,7 +57,7 @@ import { ref, reactive, computed } from 'vue';
const selectedDate = defineModel<string>();
const weekdays = reactive([0, 1, 2, 3, 4, 5, 6]),
const weekdays = reactive([1, 2, 3, 4, 5, 6, 0]),
locale = ref('en-CA'),
monthFormatter = monthFormatterFunc(),
dayFormatter = dayFormatterFunc(),
@@ -124,8 +124,14 @@ function dayClass(day: Timestamp) {
}
function monthFormatterFunc() {
const longOptions = { timeZone: 'UTC', month: 'long' };
const shortOptions = { timeZone: 'UTC', month: 'short' };
const longOptions: Intl.DateTimeFormatOptions = {
timeZone: 'UTC',
month: 'long',
};
const shortOptions: Intl.DateTimeFormatOptions = {
timeZone: 'UTC',
month: 'short',
};
return createNativeLocaleFormatter(locale.value, (_tms, short) =>
short ? shortOptions : longOptions
@@ -133,17 +139,28 @@ function monthFormatterFunc() {
}
function weekdayFormatterFunc() {
const longOptions = { timeZone: 'UTC', weekday: 'long' };
const shortOptions = { timeZone: 'UTC', weekday: 'short' };
const longOptions: Intl.DateTimeFormatOptions = {
timeZone: 'UTC',
weekday: 'long',
};
const shortOptions: Intl.DateTimeFormatOptions = {
timeZone: 'UTC',
weekday: 'short',
};
return createNativeLocaleFormatter(locale.value, (_tms, short) =>
short ? shortOptions : longOptions
);
}
function dayFormatterFunc() {
const longOptions = { timeZone: 'UTC', day: '2-digit' };
const shortOptions = { timeZone: 'UTC', day: 'numeric' };
const longOptions: Intl.DateTimeFormatOptions = {
timeZone: 'UTC',
day: '2-digit',
};
const shortOptions: Intl.DateTimeFormatOptions = {
timeZone: 'UTC',
day: 'numeric',
};
return createNativeLocaleFormatter(locale.value, (_tms, short) =>
short ? shortOptions : longOptions

View File

@@ -20,5 +20,5 @@
import { defineProps } from 'vue';
import type { Task } from 'src/stores/task';
const props = defineProps<{ task: Task }>();
defineProps<{ task: Task }>();
</script>

View File

@@ -162,7 +162,7 @@ import { useRouter } from 'vue-router';
import { useTaskStore, TASKSTATUS } from 'src/stores/task';
import type { TaskTag, SkillTag, Task } from 'src/stores/task';
import { date } from 'quasar';
import { Boat, useBoatStore } from 'src/stores/boat';
import { useBoatStore } from 'src/stores/boat';
const props = defineProps<{ taskId?: string }>();
const taskStore = useTaskStore();
@@ -187,7 +187,7 @@ const targetTask = taskId && taskStore.tasks.find((t) => t.$id === taskId);
const modifiedTask = reactive(targetTask ? targetTask : defaultTask);
let tasks = taskStore.tasks;
const boatList = ref<Boat[]>(useBoatStore().boats);
const boatList = useBoatStore().boats;
const skillTagOptions = ref<SkillTag[]>(taskStore.skillTags);
const taskTagOptions = ref<TaskTag[]>(taskStore.taskTags);

View File

@@ -9,9 +9,8 @@
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
import type { Task } from 'src/stores/task';
import TaskCardComponent from './TaskCardComponent.vue';
const props = defineProps<{ tasks: Task[] }>();
defineProps<{ tasks: Task[] }>();
</script>

View File

@@ -133,7 +133,7 @@
<q-separator />
<q-list dense>
<q-item
v-for="col in props.cols.filter((col) => col.name !== 'desc')"
v-for="col in props.cols.filter((col:Boat) => col.name !== 'desc')"
:key="col.name"
>
<q-item-section>
@@ -215,10 +215,8 @@
import { computed, defineProps, ref } from 'vue';
import { useTaskStore, Task, SkillTag, TaskTag } from 'src/stores/task';
import { QTableProps, date, useQuasar } from 'quasar';
import { useBoatStore } from 'src/stores/boat';
import { useRouter } from 'vue-router';
import { Boat, useBoatStore } from 'src/stores/boat';
const router = useRouter();
const selected = ref([]);
const loading = ref(false); // Placeholder
const fabShow = ref(false);
@@ -301,44 +299,51 @@ const columns = <QTableProps['columns']>[
{ name: 'actions', align: 'center', label: 'Actions', field: '$id' },
];
const props = defineProps<{ tasks: Task[] }>();
defineProps<{ tasks: Task[] }>();
const taskStore = useTaskStore();
const $q = useQuasar();
const searchFilter = ref({
interface SearchObject {
title: string;
skillTags: SkillTag[];
taskTags: TaskTag[];
}
const searchFilter = ref<SearchObject>({
title: '',
skillTags: <SkillTag[]>[],
taskTags: <TaskTag[]>[],
skillTags: [],
taskTags: [],
});
const skillTagOptions = ref<SkillTag[]>(taskStore.skillTags);
const taskTagOptions = ref<TaskTag[]>(taskStore.taskTags);
function onRowClick(evt: Event, row: Task) {
router.push({ name: 'edit-task', params: { id: row.$id } });
}
// function onRowClick(evt: Event, row: Task) {
// router.push({ name: 'edit-task', params: { id: row.$id } });
// }
// TODO: Implement server side search
const filterRows = computed(
() => (rows: readonly Task[], terms: any, cols: any, cellValueFn: any) => {
let result = rows;
result = rows.filter((row) =>
terms.title
? row.title.toLowerCase().includes(terms.title.toLowerCase())
: true
);
result = result.filter((row) =>
terms.skillTags && terms.skillTags.length > 0
? row.required_skills.some((req_skill) =>
terms.skillTags.map((t) => t.$id).includes(req_skill)
)
: true
);
result = result.filter((row) =>
terms.taskTags && terms.taskTags.length > 0
? row.tags.some((tag) => terms.taskTags.map((t) => t.$id).includes(tag))
: true
);
return result;
() => (rows: readonly Task[], terms: SearchObject) => {
return rows
.filter((row) =>
terms.title
? row.title.toLowerCase().includes(terms.title.toLowerCase())
: true
)
.filter((row) =>
terms.skillTags && terms.skillTags.length > 0
? row.required_skills.some((req_skill) =>
terms.skillTags.map((t) => t.$id).includes(req_skill)
)
: true
)
.filter((row) =>
terms.taskTags && terms.taskTags.length > 0
? row.tags.some((tag) =>
terms.taskTags.map((t) => t.$id).includes(tag)
)
: true
);
}
);

View File

@@ -11,5 +11,7 @@ import { ref } from 'vue';
import { useBoatStore } from 'src/stores/boat';
import ToolbarComponent from 'src/components/ToolbarComponent.vue';
const boatStore = useBoatStore();
boatStore.fetchBoats();
const boats = ref(useBoatStore().boats);
</script>

View File

@@ -8,32 +8,25 @@
<q-avatar icon="person" />
</q-item-section>
<q-item-section>
Ricky Gervais
{{ authStore.currentUser?.name }}
<q-item-label caption>Name</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section avatar>
<q-avatar icon="numbers" />
</q-item-section>
<q-item-section>
123456
<q-item-label caption>Member ID</q-item-label>
</q-item-section>
</q-item>
<q-separator />
<q-item>
<q-item-section>
<q-item-label overline>Certifications</q-item-label>
<q-chip square icon="verified" color="green" text-color="white"
>J/27</q-chip
>
<q-chip square icon="verified" color="blue" text-color="white"
>Capri25</q-chip
>
<q-chip square icon="verified" color="red" text-color="white"
>Night</q-chip
>
<div>
<q-chip square icon="verified" color="green" text-color="white"
>J/27</q-chip
>
<q-chip square icon="verified" color="blue" text-color="white"
>Capri25</q-chip
>
<q-chip square icon="verified" color="grey-9" text-color="white"
>Night</q-chip
>
</div>
</q-item-section>
</q-item>
</q-list>
@@ -42,4 +35,7 @@
<script setup lang="ts">
import ToolbarComponent from 'src/components/ToolbarComponent.vue';
import { useAuthStore } from 'src/stores/auth';
const authStore = useAuthStore();
</script>

View File

@@ -30,7 +30,7 @@
rounded
class="bg-warning text-grey-10"
style="max-width: 95vw; margin: auto"
v-if="bookingForm.boat?.defects"
v-if="bookingForm.boat && bookingForm.boat.defects.length > 0"
>
<template v-slot:avatar>
<q-icon name="warning" color="grey-10" />
@@ -63,7 +63,7 @@
><q-banner v-if="bookingForm.boat"
>Passengers:
{{ bookingForm.members.length + bookingForm.guests.length }} /
{{ bookingForm.boat.booking?.maxPassengers }}</q-banner
{{ bookingForm.boat.maxPassengers }}</q-banner
>
<q-item
class="q-my-sm"
@@ -102,7 +102,7 @@ import { useAuthStore } from 'src/stores/auth';
import { Boat, useBoatStore } from 'src/stores/boat';
import { date } from 'quasar';
import { useScheduleStore } from 'src/stores/schedule';
import { Timeblock } from 'src/stores/schedule.types';
import { TimeBlock } from 'src/stores/schedule.types';
import BoatScheduleTableComponent from 'src/components/scheduling/boat/BoatScheduleTableComponent.vue';
interface BookingForm {
@@ -119,7 +119,7 @@ const auth = useAuthStore();
const dateFormat = 'MMM D, YYYY h:mm A';
const resourceView = ref(true);
const scheduleStore = useScheduleStore();
const timeblock = ref<Timeblock>();
const timeblock = ref<TimeBlock>();
const bookingForm = ref<BookingForm>({
bookingId: scheduleStore.getNewId(),
name: auth.currentUser?.name,
@@ -131,7 +131,6 @@ const bookingForm = ref<BookingForm>({
});
watch(timeblock, (tb_new) => {
console.log('Hi');
bookingForm.value.boat = useBoatStore().boats.find(
(b) => b.$id === tb_new?.boatId
);

View File

@@ -50,25 +50,17 @@
</template>
<script setup lang="ts">
import { Reservation, useScheduleStore } from 'src/stores/schedule';
import { useScheduleStore } from 'src/stores/schedule';
import { Reservation } from 'src/stores/schedule.types';
import { ref } from 'vue';
const scheduleStore = useScheduleStore();
import {
TimestampOrNull,
makeDateTime,
makeDate,
parseDate,
today,
} from '@quasar/quasar-ui-qcalendar';
import { TimestampOrNull, parseDate, today } from '@quasar/quasar-ui-qcalendar';
import { QCalendarDay } from '@quasar/quasar-ui-qcalendar';
import { date } from 'quasar';
import { Timestamp } from '@quasar/quasar-ui-qcalendar';
const selectedDate = ref(today());
// Use ref to get a reference to the QCalendarDay component
const calendarRef = ref(QCalendarDay);
// Method declarations
function slotStyle(
@@ -93,32 +85,22 @@ function slotStyle(
function reservationEvents(timestamp: Timestamp) {
return scheduleStore.getBoatReservations(timestamp);
}
function onToday() {
calendarRef.value.moveToToday();
}
function onPrev() {
calendarRef.value.prev();
}
function onNext() {
calendarRef.value.next();
}
function onMoved(data) {
function onMoved(data: Event) {
console.log('onMoved', data);
}
function onChange(data) {
function onChange(data: Event) {
console.log('onChange', data);
}
function onClickDate(data) {
function onClickDate(data: Event) {
console.log('onClickDate', data);
}
function onClickTime(data) {
function onClickTime(data: Event) {
console.log('onClickTime', data);
}
function onClickInterval(data) {
function onClickInterval(data: Event) {
console.log('onClickInterval', data);
}
function onClickHeadDay(data) {
function onClickHeadDay(data: Event) {
console.log('onClickHeadDay', data);
}
</script>

View File

@@ -0,0 +1,188 @@
<template>
<div class="fit row wrap justify-start items-start content-start">
<div class="col-9 q-pa-md">
<div class="scheduler">
<NavigationBar @next="onNext" @today="onToday" @prev="onPrev" />
<q-calendar-scheduler
ref="calendar"
v-model="selectedDate"
v-model:model-resources="boats"
resource-key="$id"
resource-label="name"
view="week"
:weekdays="[1, 2, 3, 4, 5, 6, 0]"
hoverable
animated
bordered
:drag-enter-func="onDragEnter"
:drag-over-func="onDragOver"
:drag-leave-func="onDragLeave"
:drop-func="onDrop"
:day-min-height="50"
:day-height="0"
>
<template #day="{ scope }">
<div
v-if="getTimeBlocks(scope.timestamp, scope.resource)"
style="
display: flex;
flex: 1 0 auto;
flex-wrap: wrap;
justify-content: space-evenly;
align-items: center;
font-size: 12px;
"
>
<template
v-for="event in getTimeBlocks(scope.timestamp, scope.resource)"
:key="event.id"
>
<q-chip clickable square icon="schedule">
{{ date.formatDate(event.start, 'HH:mm') }} -
{{ date.formatDate(event.end, 'HH:mm') }}</q-chip
>
</template>
</div>
</template>
</q-calendar-scheduler>
</div>
</div>
<div class="col-3 q-pa-md">
<q-list padding bordered class="rounded-borders">
<q-item>
<q-item-section>
<q-item-label overline>Availability Templates</q-item-label>
<q-item-label caption
>Drag and drop a template to a boat / date to create booking
availability</q-item-label
>
</q-item-section>
</q-item>
<q-card-actions align="right">
<q-btn label="Add Template" color="primary" @click="addTemplate" />
</q-card-actions>
<q-separator spaced />
<q-expansion-item
v-for="template in timeblockTemplates"
:key="template.$id"
dense
dense-toggle
expand-separator
:label="template.name"
style="font-size: 0.8em"
draggable="true"
@dragstart="onDragStart($event, template)"
>
<TimeBlockTemplateComponent
:model-value="template"
/> </q-expansion-item
></q-list>
</div>
</div>
</template>
<script setup lang="ts">
import {
QCalendarScheduler,
Timestamp,
today,
} from '@quasar/quasar-ui-qcalendar';
import { Boat, useBoatStore } from 'src/stores/boat';
import { buildTimeBlock, useScheduleStore } from 'src/stores/schedule';
import { onMounted, ref } from 'vue';
import type { TimeBlockTemplate, TimeTuple } from 'src/stores/schedule.types';
import { date } from 'quasar';
import TimeBlockTemplateComponent from 'src/components/scheduling/TimeBlockTemplateComponent.vue';
import NavigationBar from 'src/components/scheduling/NavigationBar.vue';
import { storeToRefs } from 'pinia';
const selectedDate = ref(today());
const { fetchBoats } = useBoatStore();
const { getTimeBlocks, fetchTimeBlocks, fetchTimeBlockTemplates } =
useScheduleStore();
const { boats } = storeToRefs(useBoatStore());
const { timeblockTemplates } = storeToRefs(useScheduleStore());
const calendar = ref();
onMounted(async () => {
await fetchBoats();
await fetchTimeBlocks();
await fetchTimeBlockTemplates();
});
function addTemplate() {
timeblockTemplates.value.push({ name: 'New Template', timeTuples: [] });
}
function createTimeBlock(boat: Boat, templateId: string, date: string) {
const timeBlock = timeblockTemplates.value.find((t) => t.$id === templateId);
timeBlock?.timeTuples.map((tb: TimeTuple) =>
useScheduleStore().createTimeBlock(buildTimeBlock(boat, tb, date))
);
}
function onDragStart(e: DragEvent, template: TimeBlockTemplate) {
if (e.dataTransfer) {
console.log('Drag start: ', e);
e.dataTransfer.dropEffect = 'copy';
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('ID', template.$id || '');
}
}
function onDragEnter(e: DragEvent, type: string) {
console.log('onDragEnter', e, type);
if (type === 'day' || type === 'head-day') {
e.preventDefault();
if (e.target instanceof HTMLDivElement)
e.target.classList.add('bg-secondary');
}
}
function onDragOver(e: DragEvent, type: string) {
console.log('onDragOver');
if (type === 'day' || type === 'head-day') {
e.preventDefault();
return true;
}
}
function onDragLeave(e: DragEvent, type: string) {
console.log('onDragLeave');
if (type === 'day' || type === 'head-day') {
e.preventDefault();
if (e.target instanceof HTMLDivElement)
e.target.classList.remove('bg-secondary');
console.log(e.target);
return false;
}
}
function onDrop(
e: DragEvent,
type: string,
scope: { resource: Boat; timestamp: Timestamp }
) {
console.log('onDrop', e, type, scope);
if ((type === 'day' || type === 'head-day') && e.dataTransfer) {
const templateId = e.dataTransfer.getData('ID');
const date = scope.timestamp.date;
if (type === 'head-day') {
boats.value.map((r) => createTimeBlock(r, templateId, date));
} else {
createTimeBlock(scope.resource, templateId, date);
}
}
if (e.target instanceof HTMLDivElement)
e.target.classList.remove('bg-secondary');
return false;
}
function onToday() {
calendar.value.moveToToday();
}
function onPrev() {
calendar.value.prev();
}
function onNext() {
calendar.value.next();
}
</script>

View File

@@ -3,7 +3,7 @@
<q-item v-for="link in navlinks" :key="link.label">
<q-btn
:icon="link.icon"
color="primary"
:color="link.color"
size="1.25em"
:to="link.to"
:label="link.label"
@@ -21,7 +21,19 @@ const navlinks = [
icon: 'more_time',
to: '/schedule/book',
label: 'Create a Reservation',
color: 'primary',
},
{
icon: 'calendar_month',
to: '/schedule/view',
label: 'View Schedule',
color: 'primary',
},
{
icon: 'edit_calendar',
to: '/schedule/manage',
label: 'Manage Calendar',
color: 'accent',
},
{ icon: 'calendar_month', to: '/schedule/view', label: 'View Schedule' },
];
</script>

View File

@@ -38,9 +38,6 @@ export default route(function (/* { store, ssrContext } */) {
Router.beforeEach((to) => {
const auth = useAuthStore();
if (!auth.ready) {
return false;
}
if (auth.currentUser) {
return to.meta.accountRoute ? { name: 'index' } : true;
} else {

View File

@@ -40,6 +40,12 @@ const routes: RouteRecordRaw[] = [
component: () => import('src/pages/schedule/BoatScheduleView.vue'),
name: 'boat-schedule',
},
{
path: 'manage',
component: () => import('src/pages/schedule/ManageCalendar.vue'),
name: 'manage-schedule',
meta: { requiresScheduleAdmin: true },
},
],
},
{
@@ -96,6 +102,7 @@ const routes: RouteRecordRaw[] = [
{
path: '/admin',
component: () => import('layouts/AdminLayout.vue'),
meta: { requiresAdmin: true },
children: [
{
path: '/user',

View File

@@ -1,11 +1,10 @@
import { defineStore } from 'pinia';
import { ID, account } from 'boot/appwrite';
import type { Models } from 'appwrite';
import { OAuthProvider, type Models } from 'appwrite';
import { ref } from 'vue';
export const useAuthStore = defineStore('auth', () => {
const currentUser = ref<Models.User<Models.Preferences> | null>(null);
const ready = ref(false);
async function init() {
try {
@@ -13,7 +12,6 @@ export const useAuthStore = defineStore('auth', () => {
} catch {
currentUser.value = null;
}
ready.value = true;
}
async function register(email: string, password: string) {
@@ -21,12 +19,12 @@ export const useAuthStore = defineStore('auth', () => {
return await login(email, password);
}
async function login(email: string, password: string) {
await account.createEmailSession(email, password);
await account.createEmailPasswordSession(email, password);
currentUser.value = await account.get();
}
async function googleLogin() {
account.createOAuth2Session(
'google',
OAuthProvider.Google,
'https://bab.toal.ca/',
'https://bab.toal.ca/#/login'
);
@@ -37,5 +35,5 @@ export const useAuthStore = defineStore('auth', () => {
return account.deleteSession('current').then((currentUser.value = null));
}
return { currentUser, register, login, googleLogin, logout, init, ready };
return { currentUser, register, login, googleLogin, logout, init };
});

View File

@@ -1,8 +1,11 @@
import { Models } from 'appwrite';
import { defineStore } from 'pinia';
import { AppwriteIds, databases } from 'src/boot/appwrite';
import { ref } from 'vue';
// const boatSource = null;
export interface Boat {
export interface Boat extends Models.Document {
$id: string;
name: string;
displayName?: string;
@@ -10,10 +13,10 @@ export interface Boat {
year?: number;
imgSrc?: string;
iconSrc?: string;
bookingAvailable?: boolean;
bookingAvailable: boolean;
requiredCerts: string[];
maxPassengers: number;
defects?: {
defects: {
type: string;
severity: string;
description: string;
@@ -21,81 +24,20 @@ export interface Boat {
}[];
}
const getSampleData = () => [
{
$id: '1',
name: 'ProjectX',
displayName: 'PX',
class: 'J/27',
year: 1981,
imgSrc: '/tmpimg/j27.png',
iconSrc: '/tmpimg/projectx_avatar256.png',
bookingAvailable: true,
maxPassengers: 8,
requiredCerts: [],
defects: [
{
type: 'engine',
severity: 'moderate',
description: 'Fuel line leaks at engine fitting.',
detail: `The gasket in the end of the fuel hose is damaged, and does not properly seal.
This will cause fuel to leak, and will allow air into the fuel chamber, causing a lean mixture,
and rough engine performance.`,
},
{
type: 'rigging',
severity: 'moderate',
description: 'Tiller extension is broken.',
detail:
'The tiller extension swivel is broken, and will not attach to the tiller.',
},
],
},
{
$id: '2',
name: 'Take5',
displayName: 'T5',
class: 'J/27',
year: 1985,
imgSrc: '/tmpimg/j27.png',
iconsrc: '/tmpimg/take5_avatar32.png',
bookingAvailable: true,
maxPassengers: 8,
requiredCerts: [],
},
{
$id: '3',
name: 'WeeBeestie',
displayName: 'WB',
class: 'Capri 25',
year: 1989,
imgSrc: '/tmpimg/capri25.png',
bookingAvailable: true,
maxPassengers: 6,
requiredCerts: [],
},
{
$id: '4',
name: 'Just My Imagination',
displayName: 'JMI',
class: 'Sirius 28',
year: 1989,
imgSrc: '/tmpimg/JMI.jpg',
bookingAvailable: true,
maxPassengers: 8,
requiredCerts: [],
},
];
export const useBoatStore = defineStore('boat', () => {
const boats = ref<Boat[]>([]);
export const useBoatStore = defineStore('boat', {
state: () => ({
boats: getSampleData(),
}),
async function fetchBoats() {
try {
const response = await databases.listDocuments(
AppwriteIds.databaseId,
AppwriteIds.collection.boat
);
boats.value = response.documents as Boat[];
} catch (error) {
console.error('Failed to fetch boats', error);
}
}
getters: {},
actions: {
// update () {
// }
},
return { boats, fetchBoats };
});

View File

@@ -0,0 +1,65 @@
export const getSampleData = () => [
{
$id: '1',
name: 'ProjectX',
displayName: 'PX',
class: 'J/27',
year: 1981,
imgSrc: '/tmpimg/j27.png',
iconSrc: '/tmpimg/projectx_avatar256.png',
bookingAvailable: true,
maxPassengers: 8,
requiredCerts: [],
defects: [
{
type: 'engine',
severity: 'moderate',
description: 'Fuel line leaks at engine fitting.',
detail: `The gasket in the end of the fuel hose is damaged, and does not properly seal.
This will cause fuel to leak, and will allow air into the fuel chamber, causing a lean mixture,
and rough engine performance.`,
},
{
type: 'rigging',
severity: 'moderate',
description: 'Tiller extension is broken.',
detail:
'The tiller extension swivel is broken, and will not attach to the tiller.',
},
],
},
{
$id: '2',
name: 'Take5',
displayName: 'T5',
class: 'J/27',
year: 1985,
imgSrc: '/tmpimg/j27.png',
iconsrc: '/tmpimg/take5_avatar32.png',
bookingAvailable: true,
maxPassengers: 8,
requiredCerts: [],
},
{
$id: '3',
name: 'WeeBeestie',
displayName: 'WB',
class: 'Capri 25',
year: 1989,
imgSrc: '/tmpimg/capri25.png',
bookingAvailable: true,
maxPassengers: 6,
requiredCerts: [],
},
{
$id: '4',
name: 'Just My Imagination',
displayName: 'JMI',
class: 'Sirius 28',
year: 1989,
imgSrc: '/tmpimg/JMI.jpg',
bookingAvailable: true,
maxPassengers: 8,
requiredCerts: [],
},
];

View File

@@ -11,13 +11,14 @@ import type {
StatusTypes,
Reservation,
TimeBlockTemplate,
Timeblock,
TimeBlock,
TimeTuple,
} from '../schedule.types';
export const templateA: TimeBlockTemplate = {
id: '1',
name: 'WeekdayBlocks',
blocks: [
timeTuples: [
['08:00', '12:00'],
['12:00', '16:00'],
['17:00', '21:00'],
@@ -27,7 +28,7 @@ export const templateA: TimeBlockTemplate = {
export const templateB: TimeBlockTemplate = {
id: '2',
name: 'WeekendBlocks',
blocks: [
timeTuples: [
['07:00', '10:00'],
['10:00', '13:00'],
['13:00', '16:00'],
@@ -35,20 +36,20 @@ export const templateB: TimeBlockTemplate = {
],
};
export function getSampleTimeBlocks(): Timeblock[] {
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 result: TimeBlock[] = [];
const tsToday: Timestamp = parseTimestamp(today()) as Timestamp;
for (let i = 0; i <= 30; i++) {
const template = templateB;
result.push(
...boats
.map((b): Timeblock[] => {
return template.blocks.map((t): Timeblock => {
.map((b): TimeBlock[] => {
return template.blocks.map((t: TimeTuple): TimeBlock => {
return {
id: 'id' + Math.random().toString(32).slice(2),
$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],

View File

@@ -8,21 +8,92 @@ import {
compareDate,
} from '@quasar/quasar-ui-qcalendar';
import { Reservation, Timeblock } from './schedule.types';
import {
getSampleReservations,
getSampleTimeBlocks,
} from './sampledata/schedule';
Reservation,
TimeBlockTemplate,
TimeTuple,
TimeBlock,
} from './schedule.types';
import { AppwriteIds, databases } from 'src/boot/appwrite';
import { ID, Models } from 'appwrite';
export type Interval = {
start: string;
end: string;
};
export function arrayToTimeTuples(arr: string[]) {
const timeTuples: TimeTuple[] = [];
for (let i = 0; i < arr.length; i += 2) {
timeTuples.push([arr[i], arr[i + 1]]);
}
return timeTuples;
}
export function timeTuplesOverlapped(tuples: TimeTuple[]): Interval[] {
return blocksOverlapped(
tuples.map((tuples) => {
return {
start: '01/01/2001 ' + tuples[0],
end: '01/01/2001 ' + tuples[1],
};
})
).map((t) => {
return { start: t.start.split(' ')[1], end: t.end.split(' ')[1] };
});
}
export function blocksOverlapped(blocks: TimeBlock[] | Interval[]): Interval[] {
return Array.from(
new Set(
blocks
.sort((a, b) => Date.parse(a.start) - Date.parse(b.start))
.reduce((acc: Interval[], block, i, arr) => {
if (i > 0 && block.start < arr[i - 1].end)
acc.push(arr[i - 1], block);
return acc;
}, [])
)
);
}
export function buildTimeBlock(
resource: Boat,
time: TimeTuple,
blockDate: string
): TimeBlock {
/* When the time zone offset is absent, date-only forms are interpreted
as a UTC time and date-time forms are interpreted as local time. */
const result = {
boatId: resource.$id,
start: new Date(blockDate + 'T' + time[0]).toISOString(),
end: new Date(blockDate + 'T' + time[1]).toISOString(),
};
return result;
}
export const useScheduleStore = defineStore('schedule', () => {
// TODO: Implement functions to dynamically pull this data.
const reservations = ref<Reservation[]>(getSampleReservations());
const timeblocks = ref<Timeblock[]>(getSampleTimeBlocks());
const reservations = ref<Reservation[]>([]);
const timeblocks = ref<TimeBlock[]>([]);
const timeblockTemplates = ref<TimeBlockTemplate[]>([]);
const getTimeblocksForDate = (date: string): Timeblock[] => {
return timeblocks.value.filter((b) =>
compareDate(parsed(b.start) as Timestamp, parsed(date) as Timestamp)
);
const getTimeBlocks = (date: Timestamp, boat: Boat): TimeBlock[] => {
return timeblocks.value.filter((block) => {
return (
compareDate(parseDate(new Date(block.start)) as Timestamp, date) &&
block.boatId === boat.$id
);
});
};
const getTimeBlocksForDate = (date: string): TimeBlock[] => {
// TODO: This needs to actually make sure we have the dates we need, stay in sync, etc.
return timeblocks.value.filter((b) => {
return compareDate(
parseDate(new Date(b.start)) as Timestamp,
parsed(date) as Timestamp
);
});
};
const getBoatReservations = (
@@ -40,7 +111,37 @@ export const useScheduleStore = defineStore('schedule', () => {
});
};
// const getConflicts = (timeblock: Timeblock, boat: Boat) => {
async function fetchTimeBlocks() {
try {
const response = await databases.listDocuments(
AppwriteIds.databaseId,
AppwriteIds.collection.timeBlock
);
timeblocks.value = response.documents as TimeBlock[];
} catch (error) {
console.error('Failed to fetch timeblocks', error);
}
}
async function fetchTimeBlockTemplates() {
try {
const response = await databases.listDocuments(
AppwriteIds.databaseId,
AppwriteIds.collection.timeBlockTemplate
);
timeblockTemplates.value = response.documents.map(
(d: Models.Document): TimeBlockTemplate => {
return {
...d,
timeTuples: arrayToTimeTuples(d.timeTuple),
} as TimeBlockTemplate;
}
);
} catch (error) {
console.error('Failed to fetch timeblock templates', error);
}
}
// const getConflicts = (timeblock: TimeBlock, boat: Boat) => {
// const start = date.buildDate({
// hour: timeblock.start.hour,
// minute: timeblock.start.minute,
@@ -55,6 +156,7 @@ export const useScheduleStore = defineStore('schedule', () => {
// });
// return scheduleStore.getConflictingReservations(boat, start, end);
// };
const getConflictingReservations = (
resource: Boat,
start: Date,
@@ -98,13 +200,85 @@ export const useScheduleStore = defineStore('schedule', () => {
: reservations.value.push(reservation);
};
const createTimeBlock = async (block: TimeBlock) => {
try {
const response = await databases.createDocument(
AppwriteIds.databaseId,
AppwriteIds.collection.timeBlock,
ID.unique(),
block
);
timeblocks.value.push(response as TimeBlock);
} catch (e) {
console.error('Error creating TimeBlock: ' + e);
}
};
const createTimeBlockTemplate = async (template: TimeBlockTemplate) => {
try {
const response = await databases.createDocument(
AppwriteIds.databaseId,
AppwriteIds.collection.timeBlockTemplate,
ID.unique(),
{ name: template.name, timeTuple: template.timeTuples.flat(2) }
);
timeblockTemplates.value.push(response as TimeBlockTemplate);
} catch (e) {
console.error('Error updating TimeBlockTemplate: ' + e);
}
};
const deleteTimeBlockTemplate = async (id: string) => {
try {
await databases.deleteDocument(
AppwriteIds.databaseId,
AppwriteIds.collection.timeBlockTemplate,
id
);
timeblockTemplates.value = timeblockTemplates.value.filter(
(template) => template.$id !== id
);
} catch (e) {
console.error('Error deleting TimeBlockTemplate: ' + e);
}
};
const updateTimeBlockTemplate = async (
template: TimeBlockTemplate,
id: string
) => {
try {
const response = await databases.updateDocument(
AppwriteIds.databaseId,
AppwriteIds.collection.timeBlockTemplate,
id,
{
name: template.name,
timeTuple: template.timeTuples.flat(2),
}
);
timeblockTemplates.value = timeblockTemplates.value.map((b) =>
b.$id !== id ? b : (response as TimeBlockTemplate)
);
} catch (e) {
console.error('Error updating TimeBlockTemplate: ' + e);
}
};
return {
reservations,
timeblocks,
timeblockTemplates,
getBoatReservations,
getConflictingReservations,
getTimeblocksForDate,
getTimeBlocksForDate,
getTimeBlocks,
fetchTimeBlocks,
fetchTimeBlockTemplates,
getNewId,
addOrCreateReservation,
createTimeBlock,
createTimeBlockTemplate,
deleteTimeBlockTemplate,
updateTimeBlockTemplate,
isReservationOverlapped,
isResourceTimeOverlapped,
};

View File

@@ -1,3 +1,4 @@
import { Models } from 'appwrite';
import type { Boat } from './boat';
export type StatusTypes = 'tentative' | 'confirmed' | 'pending' | undefined;
@@ -16,17 +17,15 @@ 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 {
id: string;
export type TimeTuple = [start: string, end: string];
export type TimeBlock = Partial<Models.Document> & {
boatId: string;
start: string;
end: string;
selected?: false;
}
};
export interface TimeBlockTemplate {
id: string;
export type TimeBlockTemplate = Partial<Models.Document> & {
name: string;
blocks: timeTuple[];
}
timeTuples: TimeTuple[];
};

View File

@@ -86,7 +86,7 @@ export const useTaskStore = defineStore('tasks', {
return;
}
try {
const response = await databases.deleteDocument(
await databases.deleteDocument(
AppwriteIds.databaseId,
AppwriteIds.collection.task,
docId

6
tsconfig.vue-tsc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"skipLibCheck": true
}
}

260
yarn.lock
View File

@@ -19,7 +19,7 @@
jsonpointer "^5.0.0"
leven "^3.1.0"
"@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2":
"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2":
version "7.24.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae"
integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==
@@ -1077,13 +1077,13 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@quasar/app-vite@^1.7.4":
version "1.8.5"
resolved "https://registry.yarnpkg.com/@quasar/app-vite/-/app-vite-1.8.5.tgz#f4345be5f22c9a5309ec98b40b5ca56e2e376f90"
integrity sha512-OB5nU9qKIl3p7Ton9fLWkSQTv1I/7slfQl8izhZPPJZRY755Jn4Kz1exYUoEgJJ4cLSaUI/cpnVOL59pw53NEg==
"@quasar/app-vite@^1.9.1":
version "1.9.1"
resolved "https://registry.yarnpkg.com/@quasar/app-vite/-/app-vite-1.9.1.tgz#eb5a5e3fbc4bccf866c5555513df1fd986cb497d"
integrity sha512-IC50irZQ3kPhyhdjG15+GRav4KOMN82uesApIg91HlxdMrLNw4FJrFbwVsRgJfFjS1dT1h2qK3bhYICb8goECg==
dependencies:
"@quasar/render-ssr-error" "^1.0.3"
"@quasar/vite-plugin" "^1.3.3"
"@quasar/vite-plugin" "^1.7.0"
"@rollup/pluginutils" "^4.1.2"
"@types/chrome" "^0.0.208"
"@types/compression" "^1.7.2"
@@ -1122,14 +1122,14 @@
resolved "https://registry.yarnpkg.com/@quasar/extras/-/extras-1.16.11.tgz#84b1efb9097a6e58c3ebfdd5da83ac658056a35c"
integrity sha512-sbTBHOA+Hi7ah0P6qSm+xfRXqwJ94ct3NKA3Lkq3iNPYuHD7VXbSWtP2eA7Cu9Fd0WjVoPbngf6yFGg46U3IfQ==
"@quasar/quasar-app-extension-qcalendar@^4.0.0-beta.15":
version "4.0.0-beta.15"
resolved "https://registry.yarnpkg.com/@quasar/quasar-app-extension-qcalendar/-/quasar-app-extension-qcalendar-4.0.0-beta.15.tgz#1e85626a104c3a33083b7237f50ccf5f9048926a"
integrity sha512-i6hQkcP70LXLfVMPZMKQjSg3681gjZmASV3vq6ULzc0LhtBiPneLdVNNtH2itkWxAmaUj+1heQDI5Pa0F7VKLQ==
"@quasar/quasar-app-extension-qcalendar@^4.0.0-beta.16":
version "4.0.0-beta.16"
resolved "https://registry.yarnpkg.com/@quasar/quasar-app-extension-qcalendar/-/quasar-app-extension-qcalendar-4.0.0-beta.16.tgz#5b0bdfb04db59cf6017892ff91563dd9759c7bb5"
integrity sha512-Rj3KKjPFrE13cswlZAPcqdqi1YH9CeHMpWIw8xsNqdLhCoaRhMGbRas9fvHFLJOXpnsDaVwWINNgN/bBUyn99w==
dependencies:
"@quasar/quasar-ui-qcalendar" "^4.0.0-beta.15"
"@quasar/quasar-ui-qcalendar" "^4.0.0-beta.16"
"@quasar/quasar-ui-qcalendar@^4.0.0-beta.15":
"@quasar/quasar-ui-qcalendar@^4.0.0-beta.16":
version "4.0.0-beta.16"
resolved "https://registry.yarnpkg.com/@quasar/quasar-ui-qcalendar/-/quasar-ui-qcalendar-4.0.0-beta.16.tgz#90dca0962f1fe1068361f387893df6c5da7522e2"
integrity sha512-KVbFJD1HQp91tiklv+6XsG7bq8FKK6mhhnoVzmjgoyhUAEb9csfbDPbpegy1/FzXy3o0wITe6mmRZ8nbaiMEZg==
@@ -1141,10 +1141,10 @@
dependencies:
stack-trace "^1.0.0-pre2"
"@quasar/vite-plugin@^1.3.3":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@quasar/vite-plugin/-/vite-plugin-1.6.0.tgz#3b8f82656b14782fafe66b30dfac0775b87ab9dd"
integrity sha512-LmbV76G1CwWZbrEQhqyZpkRQTJyO3xpW55aXY1zWN+JhyUeG77CcMCEWteBVnJ6I6ehUPFDC9ONd2+WlwH6rNQ==
"@quasar/vite-plugin@^1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@quasar/vite-plugin/-/vite-plugin-1.7.0.tgz#8873391ed7f69677948180f6eb14aa0821747478"
integrity sha512-ia4w1n4DuPYm92MQLPNpMqLJID1WGGRyVGxkVeg8V+V25Vh3p9QBo++iuXR4sW/bCmzzx66Ko6VStsr1zp90GQ==
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
@@ -1464,6 +1464,28 @@
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.4.tgz#966a6279060eb2d9d1a02ea1a331af071afdcf9e"
integrity sha512-IfFNbtkbIm36O9KB8QodlwwYvTEsJb4Lll4c2IwB3VHc2gie2mSPtSzL0eYay7X2jd/2WX02FjSGTWR6OPr/zg==
"@volar/language-core@1.11.1", "@volar/language-core@~1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.11.1.tgz#ecdf12ea8dc35fb8549e517991abcbf449a5ad4f"
integrity sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==
dependencies:
"@volar/source-map" "1.11.1"
"@volar/source-map@1.11.1", "@volar/source-map@~1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.11.1.tgz#535b0328d9e2b7a91dff846cab4058e191f4452f"
integrity sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==
dependencies:
muggle-string "^0.3.1"
"@volar/typescript@~1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.11.1.tgz#ba86c6f326d88e249c7f5cfe4b765be3946fd627"
integrity sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==
dependencies:
"@volar/language-core" "1.11.1"
path-browserify "^1.0.1"
"@vue/compiler-core@3.4.25":
version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.25.tgz#691f59ee5014f6f2a2488fd4465f892e1e82f729"
@@ -1475,6 +1497,17 @@
estree-walker "^2.0.2"
source-map-js "^1.2.0"
"@vue/compiler-core@3.4.26":
version "3.4.26"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.4.26.tgz#d507886520e83a6f8339ed55ed0b2b5d84b44b73"
integrity sha512-N9Vil6Hvw7NaiyFUFBPXrAyETIGlQ8KcFMkyk6hW1Cl6NvoqvP+Y8p1Eqvx+UdqsnrnI9+HMUEJegzia3mhXmQ==
dependencies:
"@babel/parser" "^7.24.4"
"@vue/shared" "3.4.26"
entities "^4.5.0"
estree-walker "^2.0.2"
source-map-js "^1.2.0"
"@vue/compiler-dom@3.4.25":
version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.25.tgz#b367e0c84e11d9e9f70beabdd6f6b2277fde375f"
@@ -1483,6 +1516,14 @@
"@vue/compiler-core" "3.4.25"
"@vue/shared" "3.4.25"
"@vue/compiler-dom@^3.3.0":
version "3.4.26"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.4.26.tgz#acc7b788b48152d087d4bb9e655b795e3dbec554"
integrity sha512-4CWbR5vR9fMg23YqFOhr6t6WB1Fjt62d6xdFPyj8pxrYub7d+OgZaObMsoxaF9yBUHPMiPFK303v61PwAuGvZA==
dependencies:
"@vue/compiler-core" "3.4.26"
"@vue/shared" "3.4.26"
"@vue/compiler-sfc@3.4.25":
version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.4.25.tgz#ceab148f81571c8b251e8a8b75a9972addf1db8b"
@@ -1511,6 +1552,21 @@
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.1.tgz#7c14346383751d9f6ad4bea0963245b30220ef83"
integrity sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==
"@vue/language-core@1.8.27":
version "1.8.27"
resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.27.tgz#2ca6892cb524e024a44e554e4c55d7a23e72263f"
integrity sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==
dependencies:
"@volar/language-core" "~1.11.1"
"@volar/source-map" "~1.11.1"
"@vue/compiler-dom" "^3.3.0"
"@vue/shared" "^3.3.0"
computeds "^0.0.1"
minimatch "^9.0.3"
muggle-string "^0.3.1"
path-browserify "^1.0.1"
vue-template-compiler "^2.7.14"
"@vue/reactivity@3.4.25":
version "3.4.25"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.4.25.tgz#74983b146e06ce3341d15382669350125375d36f"
@@ -1548,6 +1604,11 @@
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.25.tgz#243ba8543e7401751e0ca319f75a80f153edd273"
integrity sha512-k0yappJ77g2+KNrIaF0FFnzwLvUBLUYr8VOwz+/6vLsmItFp51AcxLL7Ey3iPd7BIRyWPOcqUjMnm7OkahXllA==
"@vue/shared@3.4.26", "@vue/shared@^3.3.0":
version "3.4.26"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.26.tgz#f17854fb1faf889854aed4b23b60e86a8cab6403"
integrity sha512-Fg4zwR0GNnjzodMt3KRy2AWGMKQXByl56+4HjN87soxLNU9P5xcJkstAlIeEF3cU6UYOzmJl1tV0dVPGIljCnQ==
accepts@~1.3.5, accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
@@ -1586,7 +1647,7 @@ ajv@^8.0.1, ajv@^8.6.0:
require-from-string "^2.0.2"
uri-js "^4.2.2"
ansi-escapes@^4.2.1:
ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
@@ -1620,10 +1681,10 @@ anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
appwrite@^13.0.0:
version "13.0.2"
resolved "https://registry.yarnpkg.com/appwrite/-/appwrite-13.0.2.tgz#225f38225a012bb7dc2a70ea777fae363f9188fa"
integrity sha512-ISkUXO8pojDWGx5XqknCwwikgAQye4Ni4FL+Ns8Hg42rXeyehLlmvHGjFOmpS+odT6nsWYUaXzVjV4SZuDorog==
appwrite@^14.0.1:
version "14.0.1"
resolved "https://registry.yarnpkg.com/appwrite/-/appwrite-14.0.1.tgz#8a7e653597b370f0b9472c007e29ca0be8af182a"
integrity sha512-ORlvfqVif/2K3qKGgGiGfMP33Zwm+xxB1fIC4Lm3sojOkDd8u8YvgKQO0Meq5UXb8Dc0Rl66Z7qlGBAfRQ04bA==
dependencies:
cross-fetch "3.1.5"
isomorphic-form-data "2.0.0"
@@ -1936,7 +1997,7 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1, chokidar@^3.5.3:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@@ -2044,6 +2105,11 @@ commander@^2.20.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^8.0.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
common-tags@^1.8.0:
version "1.8.2"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6"
@@ -2079,6 +2145,11 @@ compression@^1.7.4:
safe-buffer "5.1.2"
vary "~1.1.2"
computeds@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/computeds/-/computeds-0.0.1.tgz#215b08a4ba3e08a11ff6eee5d6d8d7166a97ce2e"
integrity sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -2194,6 +2265,11 @@ data-view-byte-offset@^1.0.0:
es-errors "^1.3.0"
is-data-view "^1.0.1"
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -2904,7 +2980,7 @@ fast-glob@3.2.12:
merge2 "^1.3.0"
micromatch "^4.0.4"
fast-glob@^3.2.9:
fast-glob@^3.2.7, fast-glob@^3.2.9:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
@@ -3240,6 +3316,11 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2:
dependencies:
function-bind "^1.1.2"
he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
html-minifier-terser@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz#18752e23a2f0ed4b0f550f217bb41693e975b942"
@@ -3846,7 +3927,7 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -3860,6 +3941,13 @@ minimatch@^5.0.1, minimatch@^5.1.0:
dependencies:
brace-expansion "^2.0.1"
minimatch@^9.0.3:
version "9.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51"
integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==
dependencies:
brace-expansion "^2.0.1"
minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
@@ -3880,6 +3968,11 @@ ms@2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
muggle-string@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.3.1.tgz#e524312eb1728c63dd0b2ac49e3282e6ed85963a"
integrity sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==
mute-stream@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
@@ -3935,6 +4028,13 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
dependencies:
path-key "^3.0.0"
nth-check@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
@@ -4071,6 +4171,11 @@ pascal-case@^3.1.2:
no-case "^3.0.4"
tslib "^2.0.3"
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -4081,7 +4186,7 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
path-key@^3.1.0:
path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
@@ -4186,10 +4291,10 @@ qs@6.11.0:
dependencies:
side-channel "^1.0.4"
quasar@^2.15.2:
version "2.15.4"
resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.15.4.tgz#764bd886671f98d75f682b1df917adaf7dc4a849"
integrity sha512-6Rtj0KrsVA0IV9zMZ6R7U7hOpwLS/6E06hsISVHRPn21KEm3XAwHdvy9xWz5kwqWraHRlcisFSDu/KPL4VQK1w==
quasar@^2.16.0:
version "2.16.0"
resolved "https://registry.yarnpkg.com/quasar/-/quasar-2.16.0.tgz#c168a3a135fb67c39bd1e8e5fa82880a7dd2a412"
integrity sha512-j0MSuGuIAOQdtg/zEn/7jMIZjqS00Kp4t4h/0+HCqEkf6mxtwOJoaC7s0rIC+6AVYIErCTiXrp7Hmkt32Hom1w==
queue-microtask@^1.2.2:
version "1.2.3"
@@ -4457,7 +4562,7 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.6.0:
semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.5.0, semver@^7.5.4, semver@^7.6.0:
version "7.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
@@ -4796,6 +4901,11 @@ through@^2.3.6:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
tiny-invariant@^1.1.0:
version "1.3.3"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -4923,10 +5033,10 @@ typed-array-length@^1.0.6:
is-typed-array "^1.1.13"
possible-typed-array-names "^1.0.0"
typescript@^4.5.4:
version "4.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
typescript@~5.3.0:
version "5.3.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
unbox-primitive@^1.0.2:
version "1.0.2"
@@ -5018,6 +5128,27 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
vite-plugin-checker@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.6.4.tgz#aca186ab605aa15bd2c5dd9cc6d7c8fdcbe214ec"
integrity sha512-2zKHH5oxr+ye43nReRbC2fny1nyARwhxdm0uNYp/ERy4YvU9iZpNOsueoi/luXw5gnpqRSvjcEPxXbS153O2wA==
dependencies:
"@babel/code-frame" "^7.12.13"
ansi-escapes "^4.3.0"
chalk "^4.1.1"
chokidar "^3.5.1"
commander "^8.0.0"
fast-glob "^3.2.7"
fs-extra "^11.1.0"
npm-run-path "^4.0.1"
semver "^7.5.0"
strip-ansi "^6.0.0"
tiny-invariant "^1.1.0"
vscode-languageclient "^7.0.0"
vscode-languageserver "^7.0.0"
vscode-languageserver-textdocument "^1.0.1"
vscode-uri "^3.0.2"
vite@^2.9.13:
version "2.9.18"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.18.tgz#74e2a83b29da81e602dac4c293312cc575f091c7"
@@ -5030,6 +5161,50 @@ vite@^2.9.13:
optionalDependencies:
fsevents "~2.3.2"
vscode-jsonrpc@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e"
integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==
vscode-languageclient@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2"
integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==
dependencies:
minimatch "^3.0.4"
semver "^7.3.4"
vscode-languageserver-protocol "3.16.0"
vscode-languageserver-protocol@3.16.0:
version "3.16.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821"
integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==
dependencies:
vscode-jsonrpc "6.0.0"
vscode-languageserver-types "3.16.0"
vscode-languageserver-textdocument@^1.0.1:
version "1.0.11"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz#0822a000e7d4dc083312580d7575fe9e3ba2e2bf"
integrity sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==
vscode-languageserver-types@3.16.0:
version "3.16.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247"
integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==
vscode-languageserver@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0"
integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==
dependencies:
vscode-languageserver-protocol "3.16.0"
vscode-uri@^3.0.2:
version "3.0.8"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
vue-demi@>=0.14.5:
version "0.14.7"
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.7.tgz#8317536b3ef74c5b09f268f7782e70194567d8f2"
@@ -5055,6 +5230,23 @@ vue-router@4:
dependencies:
"@vue/devtools-api" "^6.5.1"
vue-template-compiler@^2.7.14:
version "2.7.16"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz#c81b2d47753264c77ac03b9966a46637482bb03b"
integrity sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==
dependencies:
de-indent "^1.0.2"
he "^1.2.0"
vue-tsc@^1.8.22:
version "1.8.27"
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.27.tgz#feb2bb1eef9be28017bb9e95e2bbd1ebdd48481c"
integrity sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==
dependencies:
"@volar/typescript" "~1.11.1"
"@vue/language-core" "1.8.27"
semver "^7.5.4"
vue@3:
version "3.4.25"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.4.25.tgz#e59d4ed36389647b52ff2fd7aa84bb6691f4205b"