Change timesheet UI to better fit current app model and avoid adding extra clicks and interactions to add new shifts and expenses. Also refactoring calls to backend to be more efficient and use recently-finalized OIDC implementation and integration.
79 lines
2.7 KiB
TypeScript
79 lines
2.7 KiB
TypeScript
import { type Expense, EXPENSE_TYPE, type ExpenseType } from "src/modules/timesheets/models/expense.models";
|
|
import type { Normalizer } from "src/utils/normalize-object";
|
|
|
|
export interface ApiErrorPayload {
|
|
status_code: number;
|
|
error_code?: string;
|
|
message?: string;
|
|
context?: Record<string, unknown>;
|
|
};
|
|
|
|
export abstract class ApiError extends Error {
|
|
status_code: number;
|
|
error_code?: string;
|
|
context?: Record<string, unknown>;
|
|
|
|
constructor(payload: ApiErrorPayload, defaultMessage: string) {
|
|
super(payload.message || defaultMessage);
|
|
this.status_code = payload.status_code;
|
|
this.error_code = payload.error_code ?? "unknown";
|
|
this.context = payload.context ?? {'unknown': 'unknown error has occured', };
|
|
}
|
|
};
|
|
|
|
export class GenericApiError extends ApiError {
|
|
constructor(payload: ApiErrorPayload) {
|
|
super(payload, 'Encountered an error processing request');
|
|
this.name = 'GenericApiError';
|
|
}
|
|
};
|
|
|
|
export class ExpensesValidationError extends ApiError {
|
|
constructor(payload: ApiErrorPayload) {
|
|
super(payload, 'Invalid expense payload');
|
|
this.name = 'ExpensesValidationError';
|
|
}
|
|
};
|
|
|
|
export class ExpensesApiError extends ApiError {
|
|
constructor(payload: ApiErrorPayload) {
|
|
super(payload, 'Request failed');
|
|
this.name = 'ExpensesApiError';
|
|
}
|
|
};
|
|
|
|
export const expense_validation_schema: Normalizer<Expense> = {
|
|
id: v => typeof v === 'number' ? v : -1,
|
|
date: v => typeof v === 'string' ? v.trim() : '1970-01-01',
|
|
type: v => EXPENSE_TYPE.includes(v as ExpenseType) ? v as ExpenseType : "EXPENSES",
|
|
amount: v => typeof v === "number" ? v : -1,
|
|
mileage: v => typeof v === "number" ? v : undefined,
|
|
comment: v => typeof v === 'string' ? v.trim() : '',
|
|
supervisor_comment: v => typeof v === 'string' ? v.trim() : '',
|
|
is_approved: v => !!v,
|
|
};
|
|
|
|
export function toExpensesError(err: unknown): ExpensesValidationError | ExpensesApiError {
|
|
if (err instanceof ExpensesValidationError || err instanceof ExpensesApiError) {
|
|
return err;
|
|
}
|
|
|
|
if (typeof err === 'object' && err !== null && 'status_code' in err) {
|
|
const payload = err as ApiErrorPayload;
|
|
|
|
// Don't know how to differentiate both types of errors, can be updated here
|
|
if (payload.error_code?.startsWith('API_')) {
|
|
return new ExpensesApiError(payload);
|
|
}
|
|
|
|
return new ExpensesValidationError(payload);
|
|
}
|
|
|
|
// Fallback with ValidationError as default
|
|
return new ExpensesValidationError({
|
|
status_code: 500,
|
|
message: err instanceof Error ? err.message : 'Unknown error',
|
|
context: { original: err }
|
|
});
|
|
}
|