import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { ExternalLink, MoreHorizontal, Receipt } from "lucide-react";
import { Button, Menu, ActionIcon, Group, Text } from "@mantine/core";
import { BookingConfirmDialog } from "./BookingConfirmDialog";
import { OperationRescheduleModal } from "./OperationRescheduleModal";
import { useBookingActionDialog } from "./useBookingActionDialog";
import { useAuth } from "@/auth/useAuth";
import { FREIGHT_PERMS, hasPermission as hasFreightPermission } from "@/lib/permissions";
import {
isAllocateAction,
isClearanceNavAction,
isContractNavAction,
isRescheduleAction,
listRowHasActions,
type BookingActionContext,
} from "@/features/bookings/booking-actions.config";
import type { BookingListRow } from "@/types/booking";
interface BookingActionsMenuProps {
row: BookingListRow;
/** Compact table cell vs. larger detail toolbar */
variant?: "table" | "toolbar";
className?: string;
/** Suppresses table row navigation after menu/dialog close (click-through). */
onSuppressRowClick?: () => void;
onAllocateBooking?: () => void;
}
export function BookingActionsMenu({
row,
variant = "table",
onSuppressRowClick,
onAllocateBooking,
}: BookingActionsMenuProps) {
const navigate = useNavigate();
const { user } = useAuth();
const context: BookingActionContext = {
status: row.status,
paymentCurrency: row.paymentCurrency,
reference: row.reference,
schedulingStatus: row.schedulingStatus,
customsClearingEnabled: row.customsClearingEnabled,
consolidationPartnerId: row.consolidationPartnerId,
};
const flow = useBookingActionDialog(row.id, context);
const { actions, pendingAction, mutations } = flow;
// Day / train reschedule has its own modal (date + export train picker).
const [rescheduleOpen, setRescheduleOpen] = useState(false);
const goToContract = () =>
navigate(`/dashboard/booking-requests/${row.id}/contract`);
const goToClearanceTab = () =>
navigate(`/dashboard/booking-requests/${row.id}?tab=clearance`);
const goToAdditionalCharges = () =>
navigate(`/dashboard/booking-requests/${row.id}?tab=additional-charges`);
const canSeeAdditionalCharges = hasFreightPermission(
user,
FREIGHT_PERMS.additionalCharges.view,
);
const handleAction = (action: (typeof actions)[number]) => {
onSuppressRowClick?.();
if (isContractNavAction(action.id)) {
goToContract();
} else if (isClearanceNavAction(action.id)) {
goToClearanceTab();
} else if (isAllocateAction(action.id)) {
onAllocateBooking?.();
} else if (isRescheduleAction(action.id)) {
setRescheduleOpen(true);
} else {
flow.openAction(action);
}
};
const hasMenu = listRowHasActions(row, user);
// Row click already opens the detail page — no chevron affordance needed.
if (!hasMenu && variant === "table") {
return null;
}
// Toolbar: lay every action out as a button row.
if (variant === "toolbar" && actions.length > 0) {
return (
<>
{actions.map((action) => {
const Icon = action.icon;
const destructive = action.variant === "destructive";
return (
}
disabled={mutations.isPending}
onClick={() => handleAction(action)}
>
{action.label}
);
})}
{
onSuppressRowClick?.();
setRescheduleOpen(false);
}}
/>
>
);
}
return (
e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>
{
onSuppressRowClick?.();
setRescheduleOpen(false);
}}
/>
);
}
function ActionDialog({
flow,
pendingAction,
onSuppressRowClick,
consolidationPartnerId,
consolidationPartnerReference,
}: {
flow: ReturnType;
pendingAction: ReturnType["pendingAction"];
onSuppressRowClick?: () => void;
consolidationPartnerId?: string | null;
consolidationPartnerReference?: string | null;
}) {
return (
{
if (!open) onSuppressRowClick?.();
flow.setDialogOpen(open);
}}
action={pendingAction}
reference={flow.mergedContext.reference}
inputValue={flow.inputValue}
onInputChange={flow.setInputValue}
selectedFile={flow.selectedFile}
onFileChange={flow.setSelectedFile}
onConfirm={() => {
onSuppressRowClick?.();
flow.runAction();
}}
isPending={flow.mutations.isPending || flow.detailLoading}
confirmDisabled={flow.confirmDisabled}
// Only the four pairable decisions land on both halves; the rest stay
// per booking, so the warning must not appear for them.
pairedWithReference={
consolidationPartnerId &&
pendingAction &&
["accept", "cancel", "operationAccept", "requestChanges"].includes(
pendingAction.id,
)
? (consolidationPartnerReference ?? "its wagon partner")
: null
}
/>
);
}