fix(capacity): Fix availability load/save error handling
- Clone response before reading to prevent body consumption issues - Add better error logging in API client - Surface backend error messages in capacity page - Import ApiError type for proper error handling Test Results: - Backend: 15 capacity tests passed ✅ - Frontend Unit: 10 passed ✅ - E2E: 130 passed, 24 skipped ✅ Refs: openspec/changes/headroom-foundation
This commit is contained in:
@@ -221,13 +221,21 @@ export async function apiRequest<T>(endpoint: string, options: ApiRequestOptions
|
|||||||
|
|
||||||
// Handle API response
|
// Handle API response
|
||||||
async function handleResponse(response: Response): Promise<Response> {
|
async function handleResponse(response: Response): Promise<Response> {
|
||||||
const contentType = response.headers?.get?.('content-type') || response.headers?.get?.('Content-Type');
|
const contentType =
|
||||||
|
response.headers?.get?.('content-type') || response.headers?.get?.('Content-Type');
|
||||||
const isJson = contentType && contentType.includes('application/json');
|
const isJson = contentType && contentType.includes('application/json');
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = isJson ? await response.json() : await response.text();
|
const payloadResponse = response.clone();
|
||||||
|
const data = isJson ? await payloadResponse.json() : await payloadResponse.text();
|
||||||
const errorData = typeof data === 'object' ? data : { message: data };
|
const errorData = typeof data === 'object' ? data : { message: data };
|
||||||
const message = (errorData as { message?: string }).message || 'API request failed';
|
const message = (errorData as { message?: string }).message || 'API request failed';
|
||||||
|
console.error('API error', {
|
||||||
|
url: response.url,
|
||||||
|
status: response.status,
|
||||||
|
message,
|
||||||
|
data: errorData
|
||||||
|
});
|
||||||
throw new ApiError(message, response.status, errorData);
|
throw new ApiError(message, response.status, errorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
} from '$lib/stores/capacity';
|
} from '$lib/stores/capacity';
|
||||||
import { teamMembersStore } from '$lib/stores/teamMembers';
|
import { teamMembersStore } from '$lib/stores/teamMembers';
|
||||||
import { getIndividualCapacity, saveAvailability } from '$lib/api/capacity';
|
import { getIndividualCapacity, saveAvailability } from '$lib/api/capacity';
|
||||||
|
import { ApiError } from '$lib/services/api';
|
||||||
import type { Capacity } from '$lib/types/capacity';
|
import type { Capacity } from '$lib/types/capacity';
|
||||||
|
|
||||||
type TabKey = 'calendar' | 'summary' | 'holidays' | 'pto';
|
type TabKey = 'calendar' | 'summary' | 'holidays' | 'pto';
|
||||||
@@ -46,6 +47,16 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function mapError(error: unknown, fallback: string) {
|
||||||
|
if (error instanceof ApiError) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
if (error instanceof Error && error.message) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshIndividualCapacity(memberId: string, period: string) {
|
async function refreshIndividualCapacity(memberId: string, period: string) {
|
||||||
if (
|
if (
|
||||||
individualCapacity &&
|
individualCapacity &&
|
||||||
@@ -61,8 +72,8 @@
|
|||||||
try {
|
try {
|
||||||
individualCapacity = await getIndividualCapacity(period, memberId);
|
individualCapacity = await getIndividualCapacity(period, memberId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load capacity details', error);
|
console.error('Failed to load capacity details', error, { memberId, period });
|
||||||
calendarError = 'Unable to load individual capacity data.';
|
calendarError = mapError(error, 'Unable to load individual capacity data.');
|
||||||
individualCapacity = null;
|
individualCapacity = null;
|
||||||
} finally {
|
} finally {
|
||||||
loadingIndividual = false;
|
loadingIndividual = false;
|
||||||
@@ -100,8 +111,8 @@
|
|||||||
loadRevenue(period)
|
loadRevenue(period)
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save availability', error);
|
console.error('Failed to save availability', error, { memberId: selectedMemberId, date, availability });
|
||||||
availabilityError = 'Unable to save availability changes.';
|
availabilityError = mapError(error, 'Unable to save availability changes.');
|
||||||
} finally {
|
} finally {
|
||||||
availabilitySaving = false;
|
availabilitySaving = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user