import { useMemo, useState } from "react"; import type { MediaUploadRequest } from "@/app/(protected)/workorders/_components/media/media-uploader"; import { boardMediaItemsToMediaFiles } from "@/domain/work-orders/mappers/work-order-board-detail-mapper"; import type { MediaFile } from "@/domain/work-orders/types/work-order-media"; import type { WorkOrderBoardDetail } from "@/domain/work-orders/types/work-order-board-detail"; import { classifyUpload } from "@/domain/work-orders/utils/work-order-media-utils"; type LocalUpload = MediaFile & { sourceFile?: File; signal?: AbortSignal }; type UploadMutate = (input: { file: File; signal?: AbortSignal; onProgress?: (percent: number) => void; }) => Promise; function toLocalUpload(request: MediaUploadRequest): LocalUpload { const classified = classifyUpload(request.file); return { id: request.localId, name: request.file.name, fileType: classified.fileType, url: URL.createObjectURL(request.file), category: classified.category, purpose: null, progress: 0, uploadStatus: "uploading" as const, sourceFile: request.file, signal: request.signal, }; } function isSameAttempt(item: LocalUpload, request: MediaUploadRequest) { return item.id === request.localId && item.signal === request.signal; } export function useMediaUploadSession( detail: WorkOrderBoardDetail | undefined, uploadMutateAsync: UploadMutate, ) { const [localUploads, setLocalUploads] = useState([]); const serverUploads = useMemo( () => boardMediaItemsToMediaFiles(detail?.media ?? []), [detail?.media], ); const uploads = useMemo(() => { const serverIds = new Set(serverUploads.map((item) => item.id)); const pendingLocals = localUploads.filter( (item) => item.uploadStatus === "uploading" || item.uploadStatus === "failed", ); return [...serverUploads, ...pendingLocals.filter((item) => !serverIds.has(item.id))]; }, [serverUploads, localUploads]); function startUploads(requests: MediaUploadRequest[]) { setLocalUploads((prev) => { const next = [...prev]; for (const request of requests) { const entry = toLocalUpload(request); const index = next.findIndex((item) => item.id === request.localId); if (index >= 0) { const previous = next[index]; if (previous?.url.startsWith("blob:")) { URL.revokeObjectURL(previous.url); } next[index] = entry; } else { next.push(entry); } } return next; }); for (const request of requests) { void uploadMutateAsync({ file: request.file, signal: request.signal, onProgress: (percent) => { setLocalUploads((prev) => prev.map((item) => isSameAttempt(item, request) ? { ...item, progress: percent } : item, ), ); }, }) .then(() => { setLocalUploads((prev) => prev.filter((item) => !isSameAttempt(item, request))); }) .catch((error: Error) => { if (error.name === "AbortError") { setLocalUploads((prev) => prev.filter((item) => !isSameAttempt(item, request))); return; } setLocalUploads((prev) => prev.map((item) => isSameAttempt(item, request) ? { ...item, uploadStatus: "failed", errorMessage: error.message || "Upload failed", progress: undefined, } : item, ), ); }); } } function retryUpload(localId: string, signal: AbortSignal) { const target = localUploads.find((item) => item.id === localId); if (!target?.sourceFile) return; startUploads([ { localId, file: target.sourceFile, signal, onProgress: () => undefined, }, ]); } function removeLocal(id: string) { setLocalUploads((prev) => prev.filter((item) => item.id !== id)); } function isLocal(id: string) { return localUploads.some((item) => item.id === id); } return { uploads, startUploads, retryUpload, removeLocal, isLocal }; }