mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 04:12:09 +00:00
Merge pull request #244 from Sea-Haven-Industries/fix/ab/sh-383-mobile-video-upload
Increase work-order media upload limit to 200 MB
This commit is contained in:
commit
7e68fd50d7
8 changed files with 80 additions and 17 deletions
|
|
@ -11,6 +11,7 @@ import {
|
||||||
formatMediaSizeLimit,
|
formatMediaSizeLimit,
|
||||||
isWorkOrderDocumentsReadOnly,
|
isWorkOrderDocumentsReadOnly,
|
||||||
isWorkOrderFileTooLarge,
|
isWorkOrderFileTooLarge,
|
||||||
|
MAX_WORK_ORDER_COMPLETION_DOC_BYTES,
|
||||||
} from "@/domain/work-orders/utils/work-order-media-limits";
|
} from "@/domain/work-orders/utils/work-order-media-limits";
|
||||||
import { useCompDocDialog } from "@/app/(protected)/workorders/_hooks/use-comp-doc-dialog";
|
import { useCompDocDialog } from "@/app/(protected)/workorders/_hooks/use-comp-doc-dialog";
|
||||||
import { CompDocDialogFooter } from "./comp-doc-dialog-footer";
|
import { CompDocDialogFooter } from "./comp-doc-dialog-footer";
|
||||||
|
|
@ -35,8 +36,8 @@ type UseCompDocPdfUploadArgs = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function rejectCompDocPdf(file: File, rowVersion: string): string | null {
|
function rejectCompDocPdf(file: File, rowVersion: string): string | null {
|
||||||
if (isWorkOrderFileTooLarge(file)) {
|
if (isWorkOrderFileTooLarge(file, MAX_WORK_ORDER_COMPLETION_DOC_BYTES)) {
|
||||||
return `File exceeds the ${formatMediaSizeLimit()} limit.`;
|
return `File exceeds the ${formatMediaSizeLimit(MAX_WORK_ORDER_COMPLETION_DOC_BYTES)} limit.`;
|
||||||
}
|
}
|
||||||
if (file.type !== "application/pdf" && !file.name.toLowerCase().endsWith(".pdf")) {
|
if (file.type !== "application/pdf" && !file.name.toLowerCase().endsWith(".pdf")) {
|
||||||
return "Please upload a PDF file.";
|
return "Please upload a PDF file.";
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { fmtDocNow } from "@/domain/work-orders/utils/completion-doc-state";
|
||||||
import {
|
import {
|
||||||
formatMediaSizeLimit,
|
formatMediaSizeLimit,
|
||||||
isWorkOrderFileTooLarge,
|
isWorkOrderFileTooLarge,
|
||||||
|
MAX_WORK_ORDER_COMPLETION_DOC_BYTES,
|
||||||
} from "@/domain/work-orders/utils/work-order-media-limits";
|
} from "@/domain/work-orders/utils/work-order-media-limits";
|
||||||
|
|
||||||
type UseCompDocPdfActionsArgs = {
|
type UseCompDocPdfActionsArgs = {
|
||||||
|
|
@ -55,8 +56,10 @@ export function useCompDocPdfActions({
|
||||||
function handleUploadPdf(files: FileList | null) {
|
function handleUploadPdf(files: FileList | null) {
|
||||||
const file = files?.[0];
|
const file = files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
if (isWorkOrderFileTooLarge(file)) {
|
if (isWorkOrderFileTooLarge(file, MAX_WORK_ORDER_COMPLETION_DOC_BYTES)) {
|
||||||
toast.error(`File exceeds the ${formatMediaSizeLimit()} limit.`);
|
toast.error(
|
||||||
|
`File exceeds the ${formatMediaSizeLimit(MAX_WORK_ORDER_COMPLETION_DOC_BYTES)} limit.`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.type !== "application/pdf" && !file.name.toLowerCase().endsWith(".pdf")) {
|
if (file.type !== "application/pdf" && !file.name.toLowerCase().endsWith(".pdf")) {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { hasCompletionMediaGallery } from "@/domain/work-orders/utils/partition-
|
||||||
import {
|
import {
|
||||||
formatMediaSizeLimit,
|
formatMediaSizeLimit,
|
||||||
isWorkOrderFileTooLarge,
|
isWorkOrderFileTooLarge,
|
||||||
|
MAX_WORK_ORDER_COMPLETION_DOC_BYTES,
|
||||||
} from "@/domain/work-orders/utils/work-order-media-limits";
|
} from "@/domain/work-orders/utils/work-order-media-limits";
|
||||||
import { SlideOverCompletionActions } from "./slide-over-completion-actions";
|
import { SlideOverCompletionActions } from "./slide-over-completion-actions";
|
||||||
import { SlideOverCompletionMedia } from "./slide-over-completion-media";
|
import { SlideOverCompletionMedia } from "./slide-over-completion-media";
|
||||||
|
|
@ -71,8 +72,10 @@ export function SlideOverCompletionTab({
|
||||||
const handlePdfSelected = (files: FileList | null) => {
|
const handlePdfSelected = (files: FileList | null) => {
|
||||||
const file = files?.[0];
|
const file = files?.[0];
|
||||||
if (!file || !onUploadCompletionPdf) return;
|
if (!file || !onUploadCompletionPdf) return;
|
||||||
if (isWorkOrderFileTooLarge(file)) {
|
if (isWorkOrderFileTooLarge(file, MAX_WORK_ORDER_COMPLETION_DOC_BYTES)) {
|
||||||
toast.error(`File exceeds the ${formatMediaSizeLimit()} limit.`);
|
toast.error(
|
||||||
|
`File exceeds the ${formatMediaSizeLimit(MAX_WORK_ORDER_COMPLETION_DOC_BYTES)} limit.`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.type !== "application/pdf" && !file.name.toLowerCase().endsWith(".pdf")) {
|
if (file.type !== "application/pdf" && !file.name.toLowerCase().endsWith(".pdf")) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import { isWorkOrderCompletionSurfaceReadOnly } from "@/domain/work-orders/utils/work-order-lock";
|
import { isWorkOrderCompletionSurfaceReadOnly } from "@/domain/work-orders/utils/work-order-lock";
|
||||||
|
|
||||||
/** Backend RequestSizeLimit for media and completion-doc uploads. */
|
/** Backend RequestSizeLimit for media uploads and Extra Docs. */
|
||||||
export const MAX_WORK_ORDER_MEDIA_BYTES = 50_000_000;
|
export const MAX_WORK_ORDER_MEDIA_BYTES = 200_000_000;
|
||||||
|
|
||||||
|
/** Backend RequestSizeLimit for completion-document uploads. */
|
||||||
|
export const MAX_WORK_ORDER_COMPLETION_DOC_BYTES = 50_000_000;
|
||||||
|
|
||||||
const MEDIA_MIME_TYPES = new Set([
|
const MEDIA_MIME_TYPES = new Set([
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ describe("MediaUploadModal API wiring", () => {
|
||||||
});
|
});
|
||||||
expect(screen.getByText("Photos & Videos")).toBeInTheDocument();
|
expect(screen.getByText("Photos & Videos")).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText("Accepted: JPG, PNG, MP4, MOV · Max 50MB per file"),
|
screen.getByText("Accepted: JPG, PNG, MP4, MOV · Max 200MB per file"),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(screen.queryByText(/categorize after upload/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/categorize after upload/i)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
import { createRef } from "react";
|
import { createRef } from "react";
|
||||||
import { screen } from "@testing-library/react";
|
import { fireEvent, screen } from "@testing-library/react";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { MediaUploader } from "@/app/(protected)/workorders/_components/media/media-uploader";
|
||||||
import { MediaUploaderDropzone } from "@/app/(protected)/workorders/_components/media/media-uploader-dropzone";
|
import { MediaUploaderDropzone } from "@/app/(protected)/workorders/_components/media/media-uploader-dropzone";
|
||||||
import { workOrderMediaAcceptAttribute } from "@/domain/work-orders/utils/work-order-media-limits";
|
import {
|
||||||
|
MAX_WORK_ORDER_MEDIA_BYTES,
|
||||||
|
workOrderMediaAcceptAttribute,
|
||||||
|
} from "@/domain/work-orders/utils/work-order-media-limits";
|
||||||
import { renderWithProviders } from "@/test/test-utils";
|
import { renderWithProviders } from "@/test/test-utils";
|
||||||
|
|
||||||
|
vi.mock("react-toastify", () => ({ toast: { error: vi.fn() } }));
|
||||||
|
|
||||||
describe("MediaUploaderDropzone", () => {
|
describe("MediaUploaderDropzone", () => {
|
||||||
it("matches the AAP Photos accept copy and keeps it media-only", () => {
|
it("matches the AAP Photos accept copy and keeps it media-only", () => {
|
||||||
renderWithProviders(
|
renderWithProviders(
|
||||||
|
|
@ -16,7 +23,7 @@ describe("MediaUploaderDropzone", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getByText("Accepted: JPG, PNG, MP4, MOV · Max 50MB per file"),
|
screen.getByText("Accepted: JPG, PNG, MP4, MOV · Max 200MB per file"),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(screen.queryByText(/categorize after upload/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/categorize after upload/i)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(/PDF/)).not.toBeInTheDocument();
|
expect(screen.queryByText(/PDF/)).not.toBeInTheDocument();
|
||||||
|
|
@ -25,4 +32,19 @@ describe("MediaUploaderDropzone", () => {
|
||||||
expect(input?.getAttribute("accept")).toBe(workOrderMediaAcceptAttribute());
|
expect(input?.getAttribute("accept")).toBe(workOrderMediaAcceptAttribute());
|
||||||
expect(input?.getAttribute("accept")).not.toContain(".pdf");
|
expect(input?.getAttribute("accept")).not.toContain(".pdf");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects an oversized media file with the generic 200 MB toast", () => {
|
||||||
|
renderWithProviders(<MediaUploader uploads={[]} />);
|
||||||
|
|
||||||
|
const oversizedFile = {
|
||||||
|
name: "mobile-clip.MP4",
|
||||||
|
type: "application/octet-stream",
|
||||||
|
size: MAX_WORK_ORDER_MEDIA_BYTES + 1,
|
||||||
|
} as File;
|
||||||
|
const input = document.querySelector('input[type="file"]');
|
||||||
|
|
||||||
|
fireEvent.change(input as HTMLInputElement, { target: { files: [oversizedFile] } });
|
||||||
|
|
||||||
|
expect(toast.error).toHaveBeenCalledWith('"mobile-clip.MP4" exceeds the 200 MB limit.');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -140,13 +140,13 @@ describe("SlideOverExtrasTab Aveta", () => {
|
||||||
expect(getMediaContent).not.toHaveBeenCalled();
|
expect(getMediaContent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows Extra Docs accept copy with PDF DOC and 50MB", () => {
|
it("shows Extra Docs accept copy with PDF DOC and 200MB", () => {
|
||||||
renderWithProviders(
|
renderWithProviders(
|
||||||
<SlideOverExtrasTab uploads={[]} isReadOnly={false} onUpload={vi.fn()} onRemove={vi.fn()} />,
|
<SlideOverExtrasTab uploads={[]} isReadOnly={false} onUpload={vi.fn()} onRemove={vi.fn()} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getByText("Accepted: JPG, PNG, MP4, MOV, PDF, DOC · Max 50MB per file"),
|
screen.getByText("Accepted: JPG, PNG, MP4, MOV, PDF, DOC · Max 200MB per file"),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
const input = document.querySelector('input[type="file"]');
|
const input = document.querySelector('input[type="file"]');
|
||||||
expect(input?.getAttribute("accept")).toContain(".pdf");
|
expect(input?.getAttribute("accept")).toContain(".pdf");
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
isWorkOrderExtraDocTypeAllowed,
|
isWorkOrderExtraDocTypeAllowed,
|
||||||
isWorkOrderFileTooLarge,
|
isWorkOrderFileTooLarge,
|
||||||
isWorkOrderMediaTypeAllowed,
|
isWorkOrderMediaTypeAllowed,
|
||||||
|
MAX_WORK_ORDER_COMPLETION_DOC_BYTES,
|
||||||
MAX_WORK_ORDER_MEDIA_BYTES,
|
MAX_WORK_ORDER_MEDIA_BYTES,
|
||||||
workOrderExtraDocsAcceptAttribute,
|
workOrderExtraDocsAcceptAttribute,
|
||||||
workOrderMediaAcceptAttribute,
|
workOrderMediaAcceptAttribute,
|
||||||
|
|
@ -24,13 +25,31 @@ describe("work-order-media-limits", () => {
|
||||||
expect(canDeleteWorkOrderMedia({ id: "0" })).toBe(false);
|
expect(canDeleteWorkOrderMedia({ id: "0" })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("caps uploads at 50_000_000 bytes and formats decimal MB", () => {
|
it("caps uploads at 200_000_000 bytes and formats decimal MB", () => {
|
||||||
expect(MAX_WORK_ORDER_MEDIA_BYTES).toBe(50_000_000);
|
expect(MAX_WORK_ORDER_MEDIA_BYTES).toBe(200_000_000);
|
||||||
expect(formatMediaSizeLimit()).toBe("50 MB");
|
expect(formatMediaSizeLimit()).toBe("200 MB");
|
||||||
|
expect(isWorkOrderFileTooLarge({ size: MAX_WORK_ORDER_MEDIA_BYTES } as File)).toBe(false);
|
||||||
expect(isWorkOrderFileTooLarge({ size: MAX_WORK_ORDER_MEDIA_BYTES + 1 } as File)).toBe(true);
|
expect(isWorkOrderFileTooLarge({ size: MAX_WORK_ORDER_MEDIA_BYTES + 1 } as File)).toBe(true);
|
||||||
expect(isWorkOrderFileTooLarge({ size: 10 } as File)).toBe(false);
|
expect(isWorkOrderFileTooLarge({ size: 10 } as File)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("caps completion-document uploads at 50_000_000 bytes and formats the completion copy", () => {
|
||||||
|
expect(MAX_WORK_ORDER_COMPLETION_DOC_BYTES).toBe(50_000_000);
|
||||||
|
expect(formatMediaSizeLimit(MAX_WORK_ORDER_COMPLETION_DOC_BYTES)).toBe("50 MB");
|
||||||
|
expect(
|
||||||
|
isWorkOrderFileTooLarge(
|
||||||
|
{ size: MAX_WORK_ORDER_COMPLETION_DOC_BYTES } as File,
|
||||||
|
MAX_WORK_ORDER_COMPLETION_DOC_BYTES,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
isWorkOrderFileTooLarge(
|
||||||
|
{ size: MAX_WORK_ORDER_COMPLETION_DOC_BYTES + 1 } as File,
|
||||||
|
MAX_WORK_ORDER_COMPLETION_DOC_BYTES,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("allows only JPG PNG MP4 MOV by MIME or extension", () => {
|
it("allows only JPG PNG MP4 MOV by MIME or extension", () => {
|
||||||
expect(isWorkOrderMediaTypeAllowed({ name: "a.jpg", type: "image/jpeg" } as File)).toBe(true);
|
expect(isWorkOrderMediaTypeAllowed({ name: "a.jpg", type: "image/jpeg" } as File)).toBe(true);
|
||||||
expect(isWorkOrderMediaTypeAllowed({ name: "a.jpg", type: "image/jpg" } as File)).toBe(true);
|
expect(isWorkOrderMediaTypeAllowed({ name: "a.jpg", type: "image/jpg" } as File)).toBe(true);
|
||||||
|
|
@ -39,7 +58,19 @@ describe("work-order-media-limits", () => {
|
||||||
expect(isWorkOrderMediaTypeAllowed({ name: "a.mov", type: "video/quicktime" } as File)).toBe(
|
expect(isWorkOrderMediaTypeAllowed({ name: "a.mov", type: "video/quicktime" } as File)).toBe(
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
expect(isWorkOrderMediaTypeAllowed({ name: "a.MP4", type: "" } as File)).toBe(true);
|
||||||
|
expect(isWorkOrderMediaTypeAllowed({ name: "a.MOV", type: "" } as File)).toBe(true);
|
||||||
|
expect(isWorkOrderMediaTypeAllowed({ name: "a.mov", type: "video/quicktime" } as File)).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
isWorkOrderMediaTypeAllowed({ name: "a.mp4", type: "application/octet-stream" } as File),
|
||||||
|
).toBe(true);
|
||||||
expect(isWorkOrderMediaTypeAllowed({ name: "a.gif", type: "image/gif" } as File)).toBe(false);
|
expect(isWorkOrderMediaTypeAllowed({ name: "a.gif", type: "image/gif" } as File)).toBe(false);
|
||||||
|
expect(isWorkOrderMediaTypeAllowed({ name: "a.webm", type: "video/webm" } as File)).toBe(false);
|
||||||
|
expect(
|
||||||
|
isWorkOrderMediaTypeAllowed({ name: "a.txt", type: "application/octet-stream" } as File),
|
||||||
|
).toBe(false);
|
||||||
expect(isWorkOrderMediaTypeAllowed({ name: "a.pdf", type: "application/pdf" } as File)).toBe(
|
expect(isWorkOrderMediaTypeAllowed({ name: "a.pdf", type: "application/pdf" } as File)).toBe(
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue