feat: enhance ClearanceChargesSection with file viewing and downloading capabilities

This commit is contained in:
Marshal
2026-08-20 16:07:16 +00:00
parent 9e1d5ee9f2
commit 0d34b9f5e3
2 changed files with 44 additions and 5 deletions

View File

@@ -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.

View File

@@ -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<Freight.ClearanceCharge | null>(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 }) {
</Text>
</Group>
{/* GL's supporting document (port bill, receipt…) — what the
price is based on, so the customer can check before deciding. */}
{c.file && (
<Group
justify="space-between"
wrap="nowrap"
mt="sm"
style={{ border: "1px solid #EEF2F6", borderRadius: 10, padding: "8px 10px" }}
>
<Group gap={8} wrap="nowrap" style={{ minWidth: 0 }}>
<FileText size={16} color="#2E5B96" />
<Text fz="12.5px" c="#10202F" truncate>
{c.file.name}
</Text>
</Group>
<Group gap={6} wrap="nowrap">
{isViewable({ name: c.file.name, url: "" }) && (
<IconSquare
icon={<Eye size={15} />}
onClick={() =>
void fetchViewableFile(c.file!.id, c.file!.name).then(view)
}
/>
)}
<IconSquare
icon={<Download size={15} />}
onClick={() => void downloadStoredFile(c.file!.id, c.file!.name)}
/>
</Group>
</Group>
)}
{c.status === "REJECTED" && c.customerNote && (
<Alert color="red" variant="light" radius="md" p="xs" mt="sm">
<Text fz="12.5px">
@@ -246,6 +283,7 @@ export function ClearanceChargesSection({ bookingId }: { bookingId: string }) {
otp={pay.otp}
bill={pay.bill}
/>
{viewer}
</SectionCard>
);
}