feat(work-orders): align Extra Docs accept and 50MB limits

This commit is contained in:
Arthur Bassi 2026-09-08 11:27:52 -03:00
parent 793230f0c2
commit 6c765e8b42
2 changed files with 69 additions and 13 deletions

View file

@ -1,9 +1,32 @@
import { isWorkOrderCompletionSurfaceReadOnly } from "@/domain/work-orders/utils/work-order-lock";
/** Backend RequestSizeLimit for media and completion-doc uploads. */
export const MAX_WORK_ORDER_MEDIA_BYTES = 30 * 1024 * 1024;
export const MAX_WORK_ORDER_MEDIA_BYTES = 50_000_000;
const ALLOWED_MIME_TYPES = new Set(["image/jpeg", "image/png", "video/mp4", "video/quicktime"]);
const MEDIA_MIME_TYPES = new Set(["image/jpeg", "image/png", "video/mp4", "video/quicktime"]);
const ALLOWED_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".mp4", ".mov"]);
const MEDIA_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".mp4", ".mov"]);
const EXTRA_DOC_MIME_TYPES = new Set([
...MEDIA_MIME_TYPES,
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
]);
const EXTRA_DOC_EXTENSIONS = new Set([...MEDIA_EXTENSIONS, ".pdf", ".doc", ".docx"]);
function fileExtension(file: File): string {
const name = file.name.toLowerCase();
const dot = name.lastIndexOf(".");
return dot >= 0 ? name.slice(dot) : "";
}
function isTypeAllowed(file: File, mimes: Set<string>, extensions: Set<string>): boolean {
const mimeOk = Boolean(file.type) && mimes.has(file.type.toLowerCase());
const extOk = extensions.has(fileExtension(file));
return mimeOk || extOk;
}
export function isWorkOrderFileTooLarge(
file: File,
@ -13,23 +36,24 @@ export function isWorkOrderFileTooLarge(
}
export function isWorkOrderMediaTypeAllowed(file: File): boolean {
const mimeOk = Boolean(file.type) && ALLOWED_MIME_TYPES.has(file.type.toLowerCase());
const name = file.name.toLowerCase();
const dot = name.lastIndexOf(".");
const ext = dot >= 0 ? name.slice(dot) : "";
const extOk = ALLOWED_EXTENSIONS.has(ext);
return mimeOk || extOk;
return isTypeAllowed(file, MEDIA_MIME_TYPES, MEDIA_EXTENSIONS);
}
export function isWorkOrderExtraDocTypeAllowed(file: File): boolean {
return isTypeAllowed(file, EXTRA_DOC_MIME_TYPES, EXTRA_DOC_EXTENSIONS);
}
export function workOrderMediaAcceptAttribute(): string {
return "image/jpeg,image/png,video/mp4,video/quicktime,.jpg,.jpeg,.png,.mp4,.mov";
}
export function formatMediaSizeLimit(maxBytes = MAX_WORK_ORDER_MEDIA_BYTES): string {
return `${Math.round(maxBytes / (1024 * 1024))} MB`;
export function workOrderExtraDocsAcceptAttribute(): string {
return `${workOrderMediaAcceptAttribute()},.pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document`;
}
import { isWorkOrderCompletionSurfaceReadOnly } from "@/domain/work-orders/utils/work-order-lock";
export function formatMediaSizeLimit(maxBytes = MAX_WORK_ORDER_MEDIA_BYTES): string {
return `${Math.round(maxBytes / 1_000_000)} MB`;
}
/** Completion-doc / completion-media lock: Canceled or Completed. Extra Docs use canMutateWorkOrderExtraDocs. */
export function isWorkOrderDocumentsReadOnly(status: string | null | undefined): boolean {

View file

@ -1,10 +1,14 @@
import { describe, expect, it } from "vitest";
import {
canDeleteWorkOrderMedia,
formatMediaSizeLimit,
isWorkOrderDocumentsReadOnly,
isWorkOrderExtraDocTypeAllowed,
isWorkOrderFileTooLarge,
isWorkOrderMediaTypeAllowed,
MAX_WORK_ORDER_MEDIA_BYTES,
workOrderExtraDocsAcceptAttribute,
workOrderMediaAcceptAttribute,
} from "@/domain/work-orders/utils/work-order-media-limits";
describe("work-order-media-limits", () => {
@ -20,7 +24,9 @@ describe("work-order-media-limits", () => {
expect(canDeleteWorkOrderMedia({ id: "0" })).toBe(false);
});
it("flags files over 30 MB", () => {
it("caps uploads at 50_000_000 bytes and formats decimal MB", () => {
expect(MAX_WORK_ORDER_MEDIA_BYTES).toBe(50_000_000);
expect(formatMediaSizeLimit()).toBe("50 MB");
expect(isWorkOrderFileTooLarge({ size: MAX_WORK_ORDER_MEDIA_BYTES + 1 } as File)).toBe(true);
expect(isWorkOrderFileTooLarge({ size: 10 } as File)).toBe(false);
});
@ -36,5 +42,31 @@ describe("work-order-media-limits", () => {
expect(isWorkOrderMediaTypeAllowed({ name: "a.pdf", type: "application/pdf" } as File)).toBe(
false,
);
expect(workOrderMediaAcceptAttribute()).not.toContain(".pdf");
});
it("allows Extra Docs PDF DOC DOCX plus media types", () => {
expect(isWorkOrderExtraDocTypeAllowed({ name: "a.pdf", type: "application/pdf" } as File)).toBe(
true,
);
expect(
isWorkOrderExtraDocTypeAllowed({ name: "a.doc", type: "application/msword" } as File),
).toBe(true);
expect(
isWorkOrderExtraDocTypeAllowed({
name: "a.docx",
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
} as File),
).toBe(true);
expect(isWorkOrderExtraDocTypeAllowed({ name: "a.jpg", type: "image/jpeg" } as File)).toBe(
true,
);
expect(isWorkOrderExtraDocTypeAllowed({ name: "a.gif", type: "image/gif" } as File)).toBe(
false,
);
const accept = workOrderExtraDocsAcceptAttribute();
expect(accept).toContain(".pdf");
expect(accept).toContain(".doc");
expect(accept).toContain(".docx");
});
});