Files
edr-platform/apps/edr-freight-web/backoffice/src/components/bookings/BookingActionsToolbar.tsx

140 lines
4.2 KiB
TypeScript

import { useState } from "react";
import { Download, Zap, FileText, Clock } from "lucide-react";
import { Stack, Text, Button } from "@mantine/core";
import { AllocateBookingWizard } from "@/components/trainScheduling/AllocateBookingWizard";
import type { BookingDetail } from "@/types/booking";
import { BookingActionsMenu } from "./BookingActionsMenu";
import { SectionCard } from "./detail/SectionCard";
import { toBookingListRow } from "@/features/bookings/mapBookingListRow";
import { canAllocateBooking } from "@/features/bookings/booking-actions.config";
import type { useBookingMutations } from "@/hooks/bookings/useBookings";
type Mutations = ReturnType<typeof useBookingMutations>;
interface BookingActionsToolbarProps {
booking: BookingDetail;
mutations: Mutations;
}
/** Detail-page actions: primary toolbar + downloads. */
export function BookingActionsToolbar({ booking, mutations }: BookingActionsToolbarProps) {
const row = toBookingListRow(booking);
const { status } = booking;
const [allocateOpen, setAllocateOpen] = useState(false);
const downloadBlob = async (fn: () => Promise<Blob>, filename: string) => {
const blob = await fn();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
};
if (status === "REJECTED" || status === "CANCELLED" || status === "COMPLETED") {
return null;
}
if (status === "CHANGES_REQUESTED") {
return (
<SectionCard icon={Zap} title="Awaiting customer">
<Stack gap="sm">
<Text size="sm" c="dimmed">
No staff actions until resubmit.
</Text>
{booking.latestChangeRequestNote && (
<Text
size="sm"
p="sm"
style={{
borderRadius: 8,
border: "1px solid var(--mantine-color-gray-2)",
background: "var(--mantine-color-gray-0)",
lineHeight: 1.5,
}}
>
{booking.latestChangeRequestNote}
</Text>
)}
</Stack>
</SectionCard>
);
}
if (["DRAFT", "PENDING_CONSOLIDATION", "CONSOLIDATED"].includes(status)) {
return (
<SectionCard icon={Zap} title="No staff actions">
<Text size="sm" c="dimmed">
Monitor until the customer or system advances status.
</Text>
</SectionCard>
);
}
if (
["FULLY_EXECUTED", "PNR_GENERATED", "PAYMENT_VERIFICATION_IN_PROGRESS"].includes(
status,
)
) {
return (
<Stack gap="lg">
<SectionCard icon={Clock} title="Awaiting customer payment">
<Stack gap="sm">
<Text size="sm" c="dimmed">
Payment is completed by the customer. The booking status updates
automatically once payment is confirmed, then moves to Operations.
</Text>
{status === "FULLY_EXECUTED" && (
<BookingActionsMenu row={row} variant="toolbar" />
)}
</Stack>
</SectionCard>
</Stack>
);
}
return (
<Stack gap="lg">
<SectionCard icon={Zap} title="Staff actions">
<Stack gap="sm">
<Text size="xs" c="dimmed">
Confirm each step before it is applied.
</Text>
<BookingActionsMenu
row={row}
variant="toolbar"
onAllocateBooking={() => setAllocateOpen(true)}
/>
</Stack>
</SectionCard>
{status === "CONTRACT_READY" && (
<SectionCard icon={FileText} title="Documents">
<Button
variant="default"
leftSection={<Download size={16} />}
onClick={() =>
downloadBlob(
() => mutations.downloadContract(),
`contract-${booking.reference}.txt`,
)
}
>
Download contract
</Button>
</SectionCard>
)}
{canAllocateBooking(booking) ? (
<AllocateBookingWizard
booking={booking}
opened={allocateOpen}
onClose={() => setAllocateOpen(false)}
/>
) : null}
</Stack>
);
}