From 3a4eeee61ba5dc531206739f42aa03cc3dfc6269 Mon Sep 17 00:00:00 2001 From: Marshal Date: Thu, 20 Aug 2026 16:07:16 +0000 Subject: [PATCH] feat: enhance ClearanceChargesSection with file viewing and downloading capabilities --- .../contracts/ClearanceChargesTab.tsx | 9 +++-- .../components/ClearanceChargesSection.tsx | 40 ++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/ClearanceChargesTab.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/ClearanceChargesTab.tsx index 4a23ab8e7..2b612f09c 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/ClearanceChargesTab.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/ClearanceChargesTab.tsx @@ -102,8 +102,9 @@ export function ClearanceChargesTab({ onError, }); const bill = useMutation({ - mutationFn: (p: BillInput & { chargeId: string }) => - bookingsService.billClearanceCharge(bookingId, p.chargeId, p), + // Body must be exactly the DTO — the API rejects unknown keys like chargeId. + mutationFn: ({ chargeId, ...payload }: BillInput & { chargeId: string }) => + bookingsService.billClearanceCharge(bookingId, chargeId, payload), onSuccess: (next) => { toast.success("Charge amount saved"); refresh(next); @@ -120,8 +121,8 @@ export function ClearanceChargesTab({ onError, }); const createMisc = useMutation({ - mutationFn: (p: BillInput & { file: File }) => - bookingsService.createMiscellaneousCharge(bookingId, p.file, p), + mutationFn: ({ file, ...payload }: BillInput & { file: File }) => + bookingsService.createMiscellaneousCharge(bookingId, file, payload), onSuccess: (next) => { toast.success("Miscellaneous charge created"); // Remount the form so the next charge starts from an empty one. diff --git a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/ClearanceChargesSection.tsx b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/ClearanceChargesSection.tsx index 2e40346ce..2977504f0 100644 --- a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/ClearanceChargesSection.tsx +++ b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/ClearanceChargesSection.tsx @@ -1,16 +1,20 @@ import { useState } from "react"; import { Alert, Box, Button, Group, Stack, Text, Textarea } from "@mantine/core"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { Check, CreditCard, Receipt, X } from "lucide-react"; +import { Check, CreditCard, Download, Eye, FileText, Receipt, X } from "lucide-react"; import { Link } from "react-router-dom"; import toast from "react-hot-toast"; import type { Freight } from "@edr/types"; +import { isViewable } from "@edr/ui-common"; +import { useFileViewer } from "@/hooks/useFileViewer"; import { useInvoicePayment } from "@/hooks/useInvoicePayment"; import { bookingsService } from "@/services/bookings.service"; +import { downloadStoredFile, fetchViewableFile } from "@/services/files.service"; import { formatAmount } from "../utils"; +import { IconSquare } from "./Documents"; import { PaymentMethodModal } from "./PaymentMethodModal"; import { CardTitle, SectionCard } from "./layout"; @@ -50,6 +54,7 @@ export function ClearanceChargesSection({ bookingId }: { bookingId: string }) { const [note, setNote] = useState(""); const [payCharge, setPayCharge] = useState(null); const pay = useInvoicePayment(); + const { view, viewer } = useFileViewer(); const onError = (e: unknown) => toast.error(e instanceof Error ? e.message : "Could not update the charge"); @@ -132,6 +137,38 @@ export function ClearanceChargesSection({ bookingId }: { bookingId: string }) { + {/* GL's supporting document (port bill, receipt…) — what the + price is based on, so the customer can check before deciding. */} + {c.file && ( + + + + + {c.file.name} + + + + {isViewable({ name: c.file.name, url: "" }) && ( + } + onClick={() => + void fetchViewableFile(c.file!.id, c.file!.name).then(view) + } + /> + )} + } + onClick={() => void downloadStoredFile(c.file!.id, c.file!.name)} + /> + + + )} + {c.status === "REJECTED" && c.customerNote && ( @@ -246,6 +283,7 @@ export function ClearanceChargesSection({ bookingId }: { bookingId: string }) { otp={pay.otp} bill={pay.bill} /> + {viewer} ); }