import { useMemo, useState } from "react"; import { Check, ShieldCheck } from "lucide-react"; import { Stack, Group, Text, Badge, Button, Box } from "@mantine/core"; import { BookingConfirmDialog } from "./BookingConfirmDialog"; import { useAuth } from "@/auth/useAuth"; import { formatApprovalProgress } from "@/features/bookings/approval-progress"; import { buildApproveActionForStep, canActOnApprovalStep, getNextPendingApprovalStep, } from "@/features/bookings/booking-actions.config"; import type { useBookingMutations } from "@/hooks/bookings/useBookings"; import type { BookingApprovalStep, BookingDetail } from "@/types/booking"; import { SectionCard } from "./detail/SectionCard"; type Mutations = ReturnType; interface ApprovalStepsCardProps { booking: BookingDetail; mutations: Mutations; } /** Approval chain with inline approve on the current pending step. */ export function ApprovalStepsCard({ booking, mutations }: ApprovalStepsCardProps) { const { user } = useAuth(); const [confirmOpen, setConfirmOpen] = useState(false); const [pendingStep, setPendingStep] = useState(null); const steps = useMemo( () => [...(booking.approvalSteps ?? [])].sort((a, b) => a.stepOrder - b.stepOrder), [booking.approvalSteps], ); const nextPending = getNextPendingApprovalStep(steps); const summary = formatApprovalProgress(booking.status, steps); const pendingAction = pendingStep ? buildApproveActionForStep(pendingStep) : null; const openApprove = (step: BookingApprovalStep) => { setPendingStep(step); setConfirmOpen(true); }; const closeApprove = () => { setConfirmOpen(false); setPendingStep(null); }; const runApprove = () => { if (!pendingStep) return; mutations.approveStep.mutate( { stepId: pendingStep.id, requiredRole: pendingStep.requiredRole }, { onSuccess: () => closeApprove() }, ); }; const subtitle = summary.detail || (nextPending ? `Next: ${nextPending.requiredRole} ยท step ${nextPending.stepOrder}` : steps.length ? "All steps complete" : "Accept submission to begin"); return ( <> {steps.filter((s) => s.status === "APPROVED").length}/{steps.length} } > {subtitle} {steps.length === 0 ? ( Use Accept for approval in staff actions to instantiate steps. ) : ( {steps.map((step) => ( ))} )} { if (!open) closeApprove(); else setConfirmOpen(true); }} action={pendingAction} reference={booking.reference} inputValue="" onInputChange={() => {}} onConfirm={runApprove} isPending={mutations.approveStep.isPending} /> ); } function StepRow({ step, steps, user, isNext, isPending, onApprove, }: { step: BookingApprovalStep; steps: BookingApprovalStep[]; user: ReturnType["user"]; isNext: boolean; isPending: boolean; onApprove: (step: BookingApprovalStep) => void; }) { const canApprove = canActOnApprovalStep(user, step, steps); const statusColor = step.status === "APPROVED" ? "green" : step.status === "REJECTED" ? "red" : isNext ? "green" : "gray"; return ( {step.stepOrder} {step.requiredRole} {step.remarks && ( {step.remarks} )} {canApprove && ( )} {step.status} ); }