fix(capacity): stabilize PTO flows and calendar consistency

Make PTO creation immediately approved, add PTO deletion, and ensure cache invalidation updates individual/team/revenue capacity consistently.

Harden holiday duplicate handling (422), support PTO-day availability overrides without disabling edits, and align tests plus OpenSpec artifacts with the new behavior.
This commit is contained in:
2026-02-19 22:47:39 -05:00
parent 0a9fdd248b
commit b821713cc7
21 changed files with 1081 additions and 128 deletions

View File

@@ -8,6 +8,7 @@
import { unwrapResponse } from '$lib/api/client';
const API_BASE_URL = import.meta.env.VITE_API_URL ?? '/api';
export const GENERIC_SERVER_ERROR_MESSAGE = 'An unexpected server error occurred. Please try again.';
// Token storage keys
const ACCESS_TOKEN_KEY = 'headroom_access_token';
@@ -76,6 +77,22 @@ export class ApiError extends Error {
}
}
export function sanitizeApiErrorMessage(message?: string): string {
if (!message) {
return GENERIC_SERVER_ERROR_MESSAGE;
}
const normalized = message.toLowerCase();
const containsHtml = ['<!doctype', '<html', '<body', '<head'].some((fragment) =>
normalized.includes(fragment)
);
const containsSql = ['sqlstate', 'illuminate\\', 'stack trace'].some((fragment) =>
normalized.includes(fragment)
);
return containsHtml || containsSql ? GENERIC_SERVER_ERROR_MESSAGE : message;
}
// Queue for requests waiting for token refresh
let isRefreshing = false;
let refreshSubscribers: Array<(token: string) => void> = [];
@@ -131,6 +148,7 @@ export async function apiRequest<T>(endpoint: string, options: ApiRequestOptions
// Prepare headers
const headers: Record<string, string> = {
Accept: 'application/json',
'Content-Type': 'application/json',
...options.headers,
};
@@ -229,7 +247,8 @@ async function handleResponse(response: Response): Promise<Response> {
const payloadResponse = response.clone();
const data = isJson ? await payloadResponse.json() : await payloadResponse.text();
const errorData = typeof data === 'object' ? data : { message: data };
const message = (errorData as { message?: string }).message || 'API request failed';
const rawMessage = (errorData as { message?: string }).message || 'API request failed';
const message = sanitizeApiErrorMessage(rawMessage);
console.error('API error', {
url: response.url,
status: response.status,