mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
Persisted HEIC files and local files with an empty or octet-stream type were classified as "other", so they did not count toward the 10 photo / 3 video limit. Both classifiers now recognise them by extension. The Photos & Videos uploader and Extra Docs ignore a new selection while the previous one is still being screened, and the vendor portal drops an earlier pick whose video check finishes after a newer pick. Also removes ticket keys from source comments.
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
import {
|
|
MAX_PHOTOS_PER_WORK_ORDER,
|
|
MAX_VIDEOS_PER_WORK_ORDER,
|
|
MAX_VIDEO_DURATION_SECONDS,
|
|
resolveWorkOrderUploadKind,
|
|
workOrderFileSizeRejection,
|
|
type WorkOrderMediaKindCounts,
|
|
} from "@/domain/work-orders/utils/work-order-media-limits";
|
|
import { probeVideoDurationSeconds } from "@/lib/probe-video-duration";
|
|
|
|
export type WorkOrderUploadScreen = {
|
|
/** Photos and videos already on the work order, across every surface. */
|
|
existing: WorkOrderMediaKindCounts;
|
|
isTypeAllowed: (file: File) => boolean;
|
|
unsupportedTypeMessage: string;
|
|
};
|
|
|
|
export type WorkOrderUploadScreenResult = { accepted: File[]; rejections: string[] };
|
|
|
|
/**
|
|
* Browser pre-check shared by every dispatcher upload surface: type, size,
|
|
* per-work-order photo/video count (including earlier files in the same selection)
|
|
* and video duration. The server enforces the same contract; a video whose duration
|
|
* the browser cannot read is never blocked here.
|
|
*/
|
|
export async function screenWorkOrderUploads(
|
|
files: readonly File[],
|
|
{ existing, isTypeAllowed, unsupportedTypeMessage }: WorkOrderUploadScreen,
|
|
): Promise<WorkOrderUploadScreenResult> {
|
|
const counts = { ...existing };
|
|
const accepted: File[] = [];
|
|
const rejections: string[] = [];
|
|
for (const file of files) {
|
|
if (!isTypeAllowed(file)) {
|
|
rejections.push(unsupportedTypeMessage);
|
|
continue;
|
|
}
|
|
const sizeRejection = workOrderFileSizeRejection(file);
|
|
if (sizeRejection) {
|
|
rejections.push(sizeRejection);
|
|
continue;
|
|
}
|
|
const kind = resolveWorkOrderUploadKind(file);
|
|
if (kind === "photo" && counts.photos >= MAX_PHOTOS_PER_WORK_ORDER) {
|
|
rejections.push(`A work order can have at most ${MAX_PHOTOS_PER_WORK_ORDER} photos.`);
|
|
continue;
|
|
}
|
|
if (kind === "video" && counts.videos >= MAX_VIDEOS_PER_WORK_ORDER) {
|
|
rejections.push(`A work order can have at most ${MAX_VIDEOS_PER_WORK_ORDER} videos.`);
|
|
continue;
|
|
}
|
|
if (kind === "video") {
|
|
const duration = await probeVideoDurationSeconds(file);
|
|
if (duration != null && duration > MAX_VIDEO_DURATION_SECONDS) {
|
|
rejections.push(`Videos must be ${MAX_VIDEO_DURATION_SECONDS} seconds or shorter.`);
|
|
continue;
|
|
}
|
|
}
|
|
if (kind === "photo") counts.photos += 1;
|
|
if (kind === "video") counts.videos += 1;
|
|
accepted.push(file);
|
|
}
|
|
return { accepted, rejections };
|
|
}
|