import { API_PATHS } from "@/api/api-paths"; import { ApiError, mapHttpStatusToMessage } from "@/api/api-error"; import { api, apiGet, apiPost } from "@/api/api"; import { handleApiResponse } from "@/api/handle-api-response"; import { BoardPatchConflictError, BoardPatchValidationError, } from "@/domain/work-orders/errors/board-patch-errors"; import { buildCompletionDocFormData, buildMediaFormData, mapBoardDetailComment, mapBoardDetailMediaItem, mapCompletion, type UploadableMediaCategory, } from "@/domain/work-orders/mappers/work-order-board-detail-mapper"; import type { WorkOrderBoardCompletion, WorkOrderBoardMediaItem, } from "@/domain/work-orders/types/work-order-board-detail"; import type { WorkOrderComment } from "@/domain/work-orders/types/work-order"; async function readResponseJson(response: Response): Promise { try { return await response.json(); } catch { return undefined; } } function throwDocumentHttpError(response: Response, data: unknown): never { const record = typeof data === "object" && data !== null ? (data as Record) : {}; if (response.status === 409) { throw new BoardPatchConflictError( typeof record.message === "string" ? record.message : "The record was modified by another user. Refresh and retry.", null, ); } if (response.status === 422) { throw new BoardPatchValidationError( typeof record.code === "string" ? record.code : "ValidationError", typeof record.message === "string" ? record.message : mapHttpStatusToMessage(422, data), ); } throw new ApiError(mapHttpStatusToMessage(response.status, data), response.status); } export const workOrderBoardDocumentsApi = { listComments: async (workOrderId: string | number): Promise => { const data = await apiGet(API_PATHS.workOrder.comments(workOrderId)); const items = handleApiResponse(data); return Array.isArray(items) ? items.map(mapBoardDetailComment) : []; }, createComment: async (workOrderId: string | number, text: string): Promise => { const data = await apiPost(API_PATHS.workOrder.comments(workOrderId), { text }); return mapBoardDetailComment(handleApiResponse(data)); }, updateComment: async ( workOrderId: string | number, commentId: string | number, text: string, ): Promise => { const response = await api.patch(API_PATHS.workOrder.commentItem(workOrderId, commentId), { json: { text }, throwHttpErrors: false, }); const data = await readResponseJson(response); if (response.ok) { if (response.status === 204 || data == null) { return mapBoardDetailComment({ id: commentId, text }); } return mapBoardDetailComment(handleApiResponse(data) ?? data); } throwDocumentHttpError(response, data); }, /** @deprecated Prefer createComment — modern board endpoint. */ addComment: async ( workOrderId: string | number, text: string, _commentType?: string, ): Promise => { return workOrderBoardDocumentsApi.createComment(workOrderId, text); }, listMedia: async (workOrderId: string | number): Promise => { const data = await apiGet(API_PATHS.workOrder.media(workOrderId)); const items = handleApiResponse(data); return Array.isArray(items) ? items.map(mapBoardDetailMediaItem) : []; }, uploadMedia: async ( workOrderId: string | number, file: File, category?: UploadableMediaCategory, options?: { signal?: AbortSignal; onProgress?: (percent: number) => void }, ): Promise => { const formData = buildMediaFormData(file, category); const response = await api.post(API_PATHS.workOrder.media(workOrderId), { body: formData, throwHttpErrors: false, signal: options?.signal, onUploadProgress: options?.onProgress ? (progress) => { options.onProgress?.(Math.round(progress.percent * 100)); } : undefined, }); const data = await readResponseJson(response); if (response.ok) { return mapBoardDetailMediaItem(handleApiResponse(data) ?? data); } throwDocumentHttpError(response, data); }, updateMediaCategory: async ( workOrderId: string | number, mediaId: string | number, category: UploadableMediaCategory, ): Promise => { const formData = new FormData(); formData.append("category", category); const response = await api.patch(API_PATHS.workOrder.mediaItem(workOrderId, mediaId), { body: formData, throwHttpErrors: false, }); const data = await readResponseJson(response); if (response.ok) { return mapBoardDetailMediaItem(handleApiResponse(data) ?? data); } throwDocumentHttpError(response, data); }, deleteMedia: async (workOrderId: string | number, mediaId: string | number): Promise => { const numericId = typeof mediaId === "number" ? mediaId : Number(mediaId); if (!Number.isFinite(numericId) || numericId <= 0) { throw new BoardPatchValidationError( "InvalidMedia", "Legacy media cannot be deleted via this endpoint.", ); } const response = await api.delete(API_PATHS.workOrder.mediaItem(workOrderId, numericId), { throwHttpErrors: false, }); if (response.status === 204) { return; } const data = await readResponseJson(response); if (response.ok) { return; } throwDocumentHttpError(response, data); }, uploadCompletionDoc: async ( workOrderId: string | number, params: { file: File; workOrderVersion: string; signOffName?: string; signOffSignature?: string; }, ): Promise => { if (!params.workOrderVersion) { throw new BoardPatchValidationError( "WorkOrderVersionRequired", "workOrderVersion is required to upload a completion document.", ); } const formData = buildCompletionDocFormData(params); const response = await api.post(API_PATHS.workOrder.completionDoc(workOrderId), { body: formData, throwHttpErrors: false, }); const data = await readResponseJson(response); if (response.ok) { return mapCompletion(handleApiResponse(data) ?? data); } throwDocumentHttpError(response, data); }, };