mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 20:05:41 +00:00
122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
import { Download, Zap } from "lucide-react";
|
|
|
|
import type { BookingDetail } from "@/types/booking";
|
|
import { BookingActionsMenu } from "./BookingActionsMenu";
|
|
import { bookingSurface } from "./booking-ui.styles";
|
|
import { toBookingListRow } from "@/features/bookings/mapBookingListRow";
|
|
import type { useBookingMutations } from "@/hooks/bookings/useBookings";
|
|
import { Button } from "@edr/ui-common";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
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 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 (
|
|
<PanelShell title="Awaiting customer" description="No staff actions until resubmit.">
|
|
{booking.latestChangeRequestNote && (
|
|
<p className="rounded-lg border border-border/50 bg-muted/15 p-3 text-sm leading-relaxed backdrop-blur-sm">
|
|
{booking.latestChangeRequestNote}
|
|
</p>
|
|
)}
|
|
</PanelShell>
|
|
);
|
|
}
|
|
|
|
if (["DRAFT", "PENDING_CONSOLIDATION", "CONSOLIDATED"].includes(status)) {
|
|
return (
|
|
<PanelShell
|
|
title="No staff actions"
|
|
description="Monitor until the customer or system advances status."
|
|
muted
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<PanelShell
|
|
title="Staff actions"
|
|
description="Confirm each step before it is applied."
|
|
>
|
|
<BookingActionsMenu row={row} variant="toolbar" />
|
|
</PanelShell>
|
|
|
|
{status === "CONTRACT_READY" && (
|
|
<PanelShell title="Documents" description="Download generated contract.">
|
|
<Button
|
|
variant="outline"
|
|
className="gap-2 border-border/60 bg-background/60 backdrop-blur-sm hover:bg-background/80"
|
|
onClick={() =>
|
|
downloadBlob(
|
|
() => mutations.downloadContract(),
|
|
`contract-${booking.reference}.txt`,
|
|
)
|
|
}
|
|
>
|
|
<Download className="size-4" />
|
|
Download contract
|
|
</Button>
|
|
</PanelShell>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PanelShell({
|
|
title,
|
|
description,
|
|
children,
|
|
muted,
|
|
}: {
|
|
title: string;
|
|
description: string;
|
|
children: React.ReactNode;
|
|
muted?: boolean;
|
|
}) {
|
|
return (
|
|
<div className={cn(bookingSurface.sectionCard, !muted && "ring-0")}>
|
|
<div className={bookingSurface.sectionHeader}>
|
|
<div className={bookingSurface.sectionIcon}>
|
|
<Zap className="size-4" strokeWidth={1.75} />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-sm font-semibold text-foreground">{title}</h2>
|
|
<p className="text-xs text-muted-foreground">{description}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col gap-3 px-5 py-5">{children}</div>
|
|
</div>
|
|
);
|
|
}
|