feat(work-orders): open Extra Docs through authorized content

This commit is contained in:
Arthur Bassi 2026-09-08 11:33:21 -03:00
parent edf4a51d4c
commit 47f78fec89
3 changed files with 64 additions and 7 deletions

View file

@ -11,6 +11,7 @@ import {
} from "lucide-react";
import { toast } from "react-toastify";
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 { FileCategory, MediaFile } from "@/domain/work-orders/types/work-order-media";
import {
@ -43,21 +44,36 @@ type SlideOverExtrasTabProps = {
isReadOnly: boolean;
pending?: boolean;
avetaRequired?: boolean;
workOrderId?: string | number;
onUpload: (files: File[]) => void;
onRemove: (id: string) => 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({
file,
isReadOnly,
pending,
workOrderId,
onRemove,
onCategorize,
}: {
file: MediaFile;
isReadOnly: boolean;
pending: boolean;
workOrderId?: string | number;
onRemove: (id: string) => void;
onCategorize?: (id: string, category: UploadableMediaCategory) => void;
}) {
@ -75,15 +91,19 @@ function ExtraDocRow({
className="shrink-0"
style={{ color: isAveta ? "var(--warning)" : "var(--color-text-muted)" }}
/>
<a
href={file.url}
target="_blank"
rel="noreferrer"
className="min-w-0 flex-1 truncate hover:underline"
<button
type="button"
onClick={() => {
if (workOrderId == null) return;
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)" }}
>
{file.name}
</a>
</button>
{showCategory ? (
<select
aria-label={`Category for ${file.name}`}
@ -134,6 +154,7 @@ export function SlideOverExtrasTab({
isReadOnly,
pending = false,
avetaRequired = false,
workOrderId,
onUpload,
onRemove,
onCategorize,
@ -238,6 +259,7 @@ export function SlideOverExtrasTab({
file={file}
isReadOnly={isReadOnly}
pending={pending}
workOrderId={workOrderId}
onRemove={onRemove}
onCategorize={onCategorize}
/>

View file

@ -147,6 +147,7 @@ export function WorkOrderSlideOverTabs({
>
<SlideOverExtrasTab
uploads={state.extrasUploads}
workOrderId={infoSource?.id}
avetaRequired={resolveSlideOverAvetaRequired(
state.editing,
infoSource?.avetaRequired,

View file

@ -1,6 +1,7 @@
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 { workOrdersApi } from "@/domain/work-orders/api/work-orders-api";
import type { MediaFile } from "@/domain/work-orders/types/work-order-media";
import { renderWithProviders } from "@/test/test-utils";
@ -21,6 +22,10 @@ const avetaDoc: MediaFile = {
};
describe("SlideOverExtrasTab Aveta", () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it("does not label the first extra document as Aveta when none is explicitly Aveta", () => {
renderWithProviders(
<SlideOverExtrasTab
@ -72,6 +77,35 @@ describe("SlideOverExtrasTab Aveta", () => {
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", () => {
renderWithProviders(
<SlideOverExtrasTab uploads={[]} isReadOnly={false} onUpload={vi.fn()} onRemove={vi.fn()} />,