targo-frontend/src/modules/timesheets/composables/api/use-shift-api.ts

86 lines
3.4 KiB
TypeScript

import { unwrapAndClone } from "src/utils/unwrap-and-clone";
import { DATE_FORMAT_PATTERN, TIME_FORMAT_PATTERN } from "src/modules/timesheets/constants/shift.constants";
import { GenericApiError } from "src/modules/timesheets/models/expense.validation";
import { useShiftStore } from "src/stores/shift-store";
import { default_shift, type Shift, type UpsertShift } from "src/modules/timesheets/models/shift.models";
import { deepEqual } from "src/utils/deep-equal";
export const useShiftApi = () => {
const shift_store = useShiftStore();
const normalizeShiftPayload = (shift: Shift): Shift => {
const comment = shift.comment?.trim() || undefined;
return {
date: shift.date,
start_time: shift.start_time,
end_time: shift.end_time,
type: shift.type,
is_approved: false,
is_remote: shift.is_remote,
comment: comment,
};
};
const parseHHMM = (s: string): [number, number] => {
const m = /^(\d{2}):(\d{2})$/.exec(s);
if (!m) {
throw new GenericApiError({ 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 GenericApiError({ 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 = (shift: Shift, label: 'old_shift' | 'new_shift') => {
if (!TIME_FORMAT_PATTERN.test(shift.start_time) || !TIME_FORMAT_PATTERN.test(shift.end_time)) {
throw new GenericApiError({
status_code: 400,
message: `Invalid time format in ${label}. Expected HH:MM`,
context: { [label]: shift }
});
}
if (toMinutes(shift.end_time) <= toMinutes(shift.start_time)) {
throw new GenericApiError({
status_code: 400,
message: `Invalid time range in ${label}. The End time must be after the Start time`,
context: { [label]: shift }
});
}
};
const upsertOrDeleteShiftByEmployeeEmail = async (employee_email: string): Promise<void> => {
const flat_upsert_shift: UpsertShift = {
...(deepEqual(shift_store.initial_shift, default_shift) ? { old_shift: unwrapAndClone(shift_store.initial_shift) } : {}),
...(deepEqual(shift_store.current_shift, default_shift) ? { new_shift: unwrapAndClone(shift_store.current_shift) } : {}),
};
const normalized_upsert_shift: UpsertShift = {
...(flat_upsert_shift.old_shift ? { old_shift: normalizeShiftPayload(flat_upsert_shift.old_shift) } : {}),
...(flat_upsert_shift.new_shift ? { new_shift: normalizeShiftPayload(flat_upsert_shift.new_shift) } : {}),
};
if (normalized_upsert_shift.old_shift) validateShift(normalized_upsert_shift.old_shift, 'old_shift');
if (normalized_upsert_shift.new_shift) validateShift(normalized_upsert_shift.new_shift, 'new_shift');
await shift_store.upsertOrDeleteShiftByEmployeeEmail(employee_email, normalized_upsert_shift);
};
return {
upsertOrDeleteShiftByEmployeeEmail,
};
}