refactor: everything to nuxt.js

This commit is contained in:
2026-03-19 14:30:36 -04:00
parent 6e1f58cd8e
commit bb3042014e
159 changed files with 6786 additions and 11198 deletions

41
app/utils/appwrite.ts Normal file
View File

@@ -0,0 +1,41 @@
import { Client, Account, Databases, Functions, ID, Teams } from 'appwrite';
const client = new Client();
function initAppwriteClient(endpoint: string, projectId: string) {
client.setEndpoint(endpoint).setProject(projectId);
}
type AppwriteIDConfig = {
databaseId: string;
collection: {
boat: string;
reservation: string;
interval: string;
intervalTemplate: string;
// task, taskTags, skillTags — parked; collections not yet created in bab_prod
};
function: {
userinfo: string;
};
};
const AppwriteIds: AppwriteIDConfig = {
databaseId: 'bab_prod',
collection: {
boat: 'boat',
reservation: 'reservation',
interval: 'interval',
intervalTemplate: 'intervalTemplate',
},
function: {
userinfo: 'userinfo',
},
};
const account = new Account(client);
const databases = new Databases(client);
const functions = new Functions(client);
const teams = new Teams(client);
export { client, account, databases, functions, teams, ID, AppwriteIds, initAppwriteClient };

20
app/utils/boat.types.ts Normal file
View File

@@ -0,0 +1,20 @@
import type { Models } from 'appwrite';
export interface Boat extends Models.Document {
$id: string;
name: string;
displayName?: string;
class?: string;
year?: number;
imgSrc?: string;
iconSrc?: string;
bookingAvailable: boolean;
requiredCerts: string[];
maxPassengers: number;
defects: {
type: string;
severity: string;
description: string;
detail?: string;
}[];
}

7
app/utils/misc.ts Normal file
View File

@@ -0,0 +1,7 @@
export function getNewId(): string {
return [...Array(20)]
.map(() => Math.floor(Math.random() * 16).toString(16))
.join('');
}
export type LoadingTypes = 'loaded' | 'pending' | 'error' | undefined;

112
app/utils/navlinks.ts Normal file
View File

@@ -0,0 +1,112 @@
import { useAuthStore } from '~/stores/auth';
export type Link = {
name: string;
to?: string;
icon: string;
front_links?: boolean;
enabled?: boolean;
color?: string;
sublinks?: Link[];
requiredRoles?: string[];
};
export const links: Link[] = [
{
name: 'Home',
to: '/',
icon: 'home',
front_links: false,
enabled: true,
},
{
name: 'Profile',
to: '/profile',
icon: 'account_circle',
front_links: false,
enabled: true,
},
{
name: 'Boats',
to: '/boat',
icon: 'sailing',
front_links: true,
enabled: true,
},
{
name: 'Schedule',
to: '/schedule',
icon: 'calendar_month',
front_links: true,
enabled: true,
sublinks: [
{ name: 'My View', to: '/schedule/list', icon: 'list', front_links: false, enabled: true },
{ name: 'Book', to: '/schedule/book', icon: 'more_time', front_links: false, enabled: true },
{ name: 'Calendar', to: '/schedule/view', icon: 'calendar_month', front_links: false, enabled: true },
],
},
{
name: 'Certifications',
to: '/certification',
icon: 'verified',
front_links: true,
enabled: false,
},
{
name: 'Checklists',
to: '/checklist',
icon: 'checklist',
front_links: true,
enabled: false,
},
{
name: 'Reference',
to: '/reference',
icon: 'info_outline',
front_links: true,
enabled: false,
},
{
name: 'Manage',
icon: 'tune',
enabled: true,
requiredRoles: ['Schedule Admins'],
color: 'negative',
sublinks: [
{
name: 'Schedule',
to: '/schedule/manage',
icon: 'edit_calendar',
front_links: false,
enabled: true,
color: 'accent',
requiredRoles: ['Schedule Admins'],
},
],
},
];
export function useNavLinks() {
const authStore = useAuthStore();
function hasRole(roles: string[] | undefined) {
if (roles === undefined) return true;
return authStore.hasRequiredRole(roles);
}
const enabledLinks = links
.filter((link) => link.enabled)
.map((link) => {
if (link.sublinks) {
return {
...link,
sublinks: link.sublinks.filter(
(sublink) => sublink.enabled && hasRole(sublink.requiredRoles)
),
};
}
return link;
});
return { enabledLinks };
}

75
app/utils/schedule.ts Normal file
View File

@@ -0,0 +1,75 @@
import { date } from 'quasar';
import type { Boat } from '~/utils/boat.types';
import type { Interval, IntervalTemplate, TimeTuple } from '~/utils/schedule.types';
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 intervalsOverlapped(
tuples.map((tuples) => {
return {
resource: '',
start: '01/01/2001 ' + tuples[0],
end: '01/01/2001 ' + tuples[1],
};
})
).map((t) => {
return { ...t, start: t.start.split(' ')[1]!, end: t.end.split(' ')[1]! };
});
}
export function intervalsOverlapped(blocks: 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 copyTimeTuples(tuples: TimeTuple[]): TimeTuple[] {
return tuples.map((t) => Object.assign([], t));
}
export function copyIntervalTemplate(template: IntervalTemplate): IntervalTemplate {
return {
...template,
timeTuples: copyTimeTuples(template.timeTuples || []),
};
}
export function buildInterval(resource: Boat, time: TimeTuple, blockDate: string): Interval {
return {
resource: resource.$id,
start: new Date(blockDate + 'T' + time[0]).toISOString(),
end: new Date(blockDate + 'T' + time[1]).toISOString(),
};
}
export const isPast = (itemDate: Date | string): boolean => {
if (!(itemDate instanceof Date)) {
itemDate = new Date(itemDate);
}
return itemDate < new Date();
};
export function formatDate(inputDate: string | undefined): string {
if (!inputDate) return '';
return date.formatDate(new Date(inputDate), 'ddd MMM Do hh:mm A');
}
export function formatTime(inputDate: string | undefined): string {
if (!inputDate) return '';
return date.formatDate(new Date(inputDate), 'hh:mm A');
}

View File

@@ -0,0 +1,28 @@
import type { Models } from 'appwrite';
export type StatusTypes = 'tentative' | 'confirmed' | 'pending' | undefined;
export type Reservation = Interval & {
user: string;
status?: StatusTypes;
reason: string;
comment: string;
members?: string[];
guests?: string[];
};
// 24 hrs in advance only 2 weekday, and 1 weekend slot
// Within 24 hrs, any available slot
export type TimeTuple = [start: string, end: string];
export type Interval = Partial<Models.Document> & {
resource: string;
start: string;
end: string;
user?: string;
};
export type IntervalTemplate = Partial<Models.Document> & {
name: string;
timeTuples: TimeTuple[];
};

1
app/utils/version.ts Normal file
View File

@@ -0,0 +1 @@
export const APP_VERSION = '0.0.0';