mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-04 17:12:06 +00:00
feat(work-orders): open Extra Docs through authorized content
This commit is contained in:
parent
edf4a51d4c
commit
47f78fec89
3 changed files with 64 additions and 7 deletions
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { Text } from "@/components/ui/text";
|
import { Text } from "@/components/ui/text";
|
||||||
|
import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api";
|
||||||
import type { UploadableMediaCategory } from "@/domain/work-orders/mappers/work-order-board-detail-mapper";
|
import type { UploadableMediaCategory } from "@/domain/work-orders/mappers/work-order-board-detail-mapper";
|
||||||
import type { FileCategory, MediaFile } from "@/domain/work-orders/types/work-order-media";
|
import type { FileCategory, MediaFile } from "@/domain/work-orders/types/work-order-media";
|
||||||
import {
|
import {
|
||||||
|
|
@ -43,21 +44,36 @@ type SlideOverExtrasTabProps = {
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
pending?: boolean;
|
pending?: boolean;
|
||||||
avetaRequired?: boolean;
|
avetaRequired?: boolean;
|
||||||
|
workOrderId?: string | number;
|
||||||
onUpload: (files: File[]) => void;
|
onUpload: (files: File[]) => void;
|
||||||
onRemove: (id: string) => void;
|
onRemove: (id: string) => void;
|
||||||
onCategorize?: (id: string, category: UploadableMediaCategory) => void;
|
onCategorize?: (id: string, category: UploadableMediaCategory) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let lastExtraDocObjectUrl: string | null = null;
|
||||||
|
|
||||||
|
async function openExtraDocContent(workOrderId: string | number, mediaId: string): Promise<void> {
|
||||||
|
const blob = await workOrdersApi.getMediaContent(workOrderId, mediaId);
|
||||||
|
if (lastExtraDocObjectUrl) {
|
||||||
|
URL.revokeObjectURL(lastExtraDocObjectUrl);
|
||||||
|
}
|
||||||
|
const objectUrl = URL.createObjectURL(blob);
|
||||||
|
lastExtraDocObjectUrl = objectUrl;
|
||||||
|
window.open(objectUrl, "_blank", "noopener,noreferrer");
|
||||||
|
}
|
||||||
|
|
||||||
function ExtraDocRow({
|
function ExtraDocRow({
|
||||||
file,
|
file,
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
pending,
|
pending,
|
||||||
|
workOrderId,
|
||||||
onRemove,
|
onRemove,
|
||||||
onCategorize,
|
onCategorize,
|
||||||
}: {
|
}: {
|
||||||
file: MediaFile;
|
file: MediaFile;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
pending: boolean;
|
pending: boolean;
|
||||||
|
workOrderId?: string | number;
|
||||||
onRemove: (id: string) => void;
|
onRemove: (id: string) => void;
|
||||||
onCategorize?: (id: string, category: UploadableMediaCategory) => void;
|
onCategorize?: (id: string, category: UploadableMediaCategory) => void;
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -75,15 +91,19 @@ function ExtraDocRow({
|
||||||
className="shrink-0"
|
className="shrink-0"
|
||||||
style={{ color: isAveta ? "var(--warning)" : "var(--color-text-muted)" }}
|
style={{ color: isAveta ? "var(--warning)" : "var(--color-text-muted)" }}
|
||||||
/>
|
/>
|
||||||
<a
|
<button
|
||||||
href={file.url}
|
type="button"
|
||||||
target="_blank"
|
onClick={() => {
|
||||||
rel="noreferrer"
|
if (workOrderId == null) return;
|
||||||
className="min-w-0 flex-1 truncate hover:underline"
|
void openExtraDocContent(workOrderId, file.id).catch(() => {
|
||||||
|
toast.error("Unable to open this document.");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="min-w-0 flex-1 truncate text-left hover:underline"
|
||||||
style={{ fontSize: 12, color: "var(--foreground)" }}
|
style={{ fontSize: 12, color: "var(--foreground)" }}
|
||||||
>
|
>
|
||||||
{file.name}
|
{file.name}
|
||||||
</a>
|
</button>
|
||||||
{showCategory ? (
|
{showCategory ? (
|
||||||
<select
|
<select
|
||||||
aria-label={`Category for ${file.name}`}
|
aria-label={`Category for ${file.name}`}
|
||||||
|
|
@ -134,6 +154,7 @@ export function SlideOverExtrasTab({
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
pending = false,
|
pending = false,
|
||||||
avetaRequired = false,
|
avetaRequired = false,
|
||||||
|
workOrderId,
|
||||||
onUpload,
|
onUpload,
|
||||||
onRemove,
|
onRemove,
|
||||||
onCategorize,
|
onCategorize,
|
||||||
|
|
@ -238,6 +259,7 @@ export function SlideOverExtrasTab({
|
||||||
file={file}
|
file={file}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
pending={pending}
|
pending={pending}
|
||||||
|
workOrderId={workOrderId}
|
||||||
onRemove={onRemove}
|
onRemove={onRemove}
|
||||||
onCategorize={onCategorize}
|
onCategorize={onCategorize}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,7 @@ export function WorkOrderSlideOverTabs({
|
||||||
>
|
>
|
||||||
<SlideOverExtrasTab
|
<SlideOverExtrasTab
|
||||||
uploads={state.extrasUploads}
|
uploads={state.extrasUploads}
|
||||||
|
workOrderId={infoSource?.id}
|
||||||
avetaRequired={resolveSlideOverAvetaRequired(
|
avetaRequired={resolveSlideOverAvetaRequired(
|
||||||
state.editing,
|
state.editing,
|
||||||
infoSource?.avetaRequired,
|
infoSource?.avetaRequired,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { fireEvent, screen } from "@testing-library/react";
|
import { fireEvent, screen } from "@testing-library/react";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { SlideOverExtrasTab } from "@/app/(protected)/workorders/_components/detail/tabs/slide-over-extras-tab";
|
import { SlideOverExtrasTab } from "@/app/(protected)/workorders/_components/detail/tabs/slide-over-extras-tab";
|
||||||
|
import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api";
|
||||||
import type { MediaFile } from "@/domain/work-orders/types/work-order-media";
|
import type { MediaFile } from "@/domain/work-orders/types/work-order-media";
|
||||||
import { renderWithProviders } from "@/test/test-utils";
|
import { renderWithProviders } from "@/test/test-utils";
|
||||||
|
|
||||||
|
|
@ -21,6 +22,10 @@ const avetaDoc: MediaFile = {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("SlideOverExtrasTab Aveta", () => {
|
describe("SlideOverExtrasTab Aveta", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
it("does not label the first extra document as Aveta when none is explicitly Aveta", () => {
|
it("does not label the first extra document as Aveta when none is explicitly Aveta", () => {
|
||||||
renderWithProviders(
|
renderWithProviders(
|
||||||
<SlideOverExtrasTab
|
<SlideOverExtrasTab
|
||||||
|
|
@ -72,6 +77,35 @@ describe("SlideOverExtrasTab Aveta", () => {
|
||||||
expect(screen.queryByText("safety-form.pdf")).toBeInTheDocument();
|
expect(screen.queryByText("safety-form.pdf")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("opens Extra Docs through authenticated content instead of a static href", async () => {
|
||||||
|
const blob = new Blob(["pdf"], { type: "application/pdf" });
|
||||||
|
const getMediaContent = vi.spyOn(workOrdersApi, "getMediaContent").mockResolvedValue(blob);
|
||||||
|
const createObjectURL = vi.fn(() => "blob:authorized");
|
||||||
|
vi.stubGlobal("URL", { ...URL, createObjectURL, revokeObjectURL: vi.fn() });
|
||||||
|
const open = vi.fn();
|
||||||
|
vi.stubGlobal("open", open);
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<SlideOverExtrasTab
|
||||||
|
uploads={[{ ...photo, url: "https://cdn.example.com/Assets/Documents/secret.pdf" }]}
|
||||||
|
workOrderId={10}
|
||||||
|
avetaRequired
|
||||||
|
isReadOnly
|
||||||
|
onUpload={vi.fn()}
|
||||||
|
onRemove={vi.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByRole("link")).not.toBeInTheDocument();
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "safety-form.pdf" }));
|
||||||
|
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(getMediaContent).toHaveBeenCalledWith(10, "m1");
|
||||||
|
});
|
||||||
|
expect(createObjectURL).toHaveBeenCalledWith(blob);
|
||||||
|
expect(open).toHaveBeenCalledWith("blob:authorized", "_blank", "noopener,noreferrer");
|
||||||
|
});
|
||||||
|
|
||||||
it("shows Extra Docs accept copy with PDF DOC and 50MB", () => {
|
it("shows Extra Docs accept copy with PDF DOC and 50MB", () => {
|
||||||
renderWithProviders(
|
renderWithProviders(
|
||||||
<SlideOverExtrasTab uploads={[]} isReadOnly={false} onUpload={vi.fn()} onRemove={vi.fn()} />,
|
<SlideOverExtrasTab uploads={[]} isReadOnly={false} onUpload={vi.fn()} onRemove={vi.fn()} />,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue