mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-03 20:13:15 +00:00
132 lines
4.2 KiB
TypeScript
132 lines
4.2 KiB
TypeScript
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<unknown>;
|
|
|
|
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<LocalUpload[]>([]);
|
|
|
|
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 };
|
|
}
|