import { api } from "src/boot/axios"; import { isProxy, toRaw } from "vue"; /* eslint-disable */ export interface ShiftPayload { start_time: string; end_time: string; type: string; is_remote: boolean; comment?: string; } export interface UpsertShiftsBody { old_shift?: ShiftPayload; new_shift?: ShiftPayload; } export type UpsertAction = 'created' | 'updated' | 'deleted'; export interface DayShift { start_time: string; end_time: string; type: string; is_remote: boolean; comment?: string | null; } export interface UpsertShiftsResponse { action: UpsertAction; day: DayShift[]; } export const TIME_FORMAT_PATTERN = /^([01]\d|2[0-3]):([0-5]\d)$/; export const DATE_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}$/; export const COMMENT_MAX_LENGTH = 512 as const; //normalize payload to match backend data export const normalize_comment = (input?: string): string | undefined => { if ( typeof input === 'undefined' || input === null) return undefined; const trimmed = String(input).trim(); return trimmed.length ? trimmed : undefined; } export const normalize_type = (input: string): string => (input ?? '').trim().toUpperCase(); export const normalize_payload = (payload: ShiftPayload): ShiftPayload => { const comment = normalize_comment(payload.comment); return { start_time: payload.start_time, end_time: payload.end_time, type: normalize_type(payload.type), is_remote: Boolean(payload.is_remote), ...(comment !== undefined ? { comment } : {}), }; }; const toPlain = (obj: T): T => { const raw = isProxy(obj) ? toRaw(obj): obj; if(typeof (globalThis as any).structuredClone === 'function') { return (globalThis as any).structuredClone(raw); } return JSON.parse(JSON.stringify(raw)); } //error handling export interface ApiErrorPayload { status_code: number; error_code?: string; message?: string; context?: Record; } export class UpsertShiftsError extends Error { status_code: number; error_code?: string | undefined; context?: Record | undefined; constructor(payload: ApiErrorPayload) { super(payload.message || 'Request failed'); this.name = 'UpsertShiftsError'; this.status_code = payload.status_code; this.error_code = payload.error_code; this.context = payload.context; } } const parseHHMM = (s:string): [number, number] => { const m = /^(\d{2}):(\d{2})$/.exec(s); if(!m) { throw new UpsertShiftsError({ status_code: 400, message: `Invalid time format: ${s}. Expected HH:MM.`}); } const h = Number(m[1]); const min = Number(m[2]); if(Number.isNaN(h) || Number.isNaN(min) || h < 0 || h> 23 || min < 0 || min > 59) { throw new UpsertShiftsError({ status_code: 400, message: `Invalid time value: ${s}.`}) } return [h, min]; } const toMinutes = (hhmm: string): number => { const [h,m] = parseHHMM(hhmm); return h * 60 + m; } const validateShift = (payload: ShiftPayload, label: 'old_shift'|'new_shift') => { if(!TIME_FORMAT_PATTERN.test(payload.start_time) || !TIME_FORMAT_PATTERN.test(payload.end_time)) { throw new UpsertShiftsError({ status_code: 400, message: `Invalid time format in ${label}. Expected HH:MM`, context: { [label]: payload } }); } if(toMinutes(payload.end_time) <= toMinutes(payload.start_time)) { throw new UpsertShiftsError({ status_code: 400, message: `Invalid time range in ${label}. The End time must be after the Start time`, context: { [label]: payload} }); } } export const upsert_shifts_by_date = async ( email: string, date: string, body: UpsertShiftsBody, ): Promise => { if (!DATE_FORMAT_PATTERN.test(date)){ throw new UpsertShiftsError({ status_code: 400, message: 'Invalid date format, expected YYYY-MM-DD', }); } const flatBody: UpsertShiftsBody = { ...(body.old_shift ? { old_shift: toPlain(body.old_shift) }: {}), ...(body.new_shift ? { new_shift: toPlain(body.new_shift) }: {}), }; const normalized: UpsertShiftsBody = { ...(flatBody.old_shift ? { old_shift: normalize_payload(flatBody.old_shift) } : {}), ...(flatBody.new_shift ? { new_shift: normalize_payload(flatBody.new_shift) } : {}), }; if(normalized.old_shift) validateShift(normalized.old_shift, 'old_shift'); if(normalized.new_shift) validateShift(normalized.new_shift, 'new_shift'); const encoded_email = encodeURIComponent(email); const encoded_date = encodeURIComponent(date); //error handling to be used with notify in case of bad input try { const { data } = await api.put( `/shifts/upsert/${encoded_email}/${encoded_date}`, normalized, { headers: {'content-type': 'application/json'}} ); return data; } catch (err: any) { const status_code: number = err?.response?.status ?? 500; const data = err?.response?.data ?? {}; const payload: ApiErrorPayload = { status_code, error_code: data.error_code, message: data.message || data.error || err.message, context: data.context, }; throw new UpsertShiftsError(payload); } };