diff --git a/frontend/package/components/ShipmentTimeline/ShipmentTimeline.tsx b/frontend/package/components/ShipmentTimeline/ShipmentTimeline.tsx new file mode 100644 index 00000000..afd2ed01 --- /dev/null +++ b/frontend/package/components/ShipmentTimeline/ShipmentTimeline.tsx @@ -0,0 +1,188 @@ +'use client'; + +import { cn } from '@/lib/utils'; +import type { ShipmentStatusHistory, ShipmentStatus } from '@/types/shipment.types'; + +interface TimelineStep { + status: ShipmentStatus; + label: string; + timestamp?: string | null; + actor?: string; + reason?: string | null; +} + +const STEPS: { status: ShipmentStatus; label: string }[] = [ + { status: 'pending' as ShipmentStatus, label: 'Created' }, + { status: 'accepted' as ShipmentStatus, label: 'Accepted' }, + { status: 'in_transit' as ShipmentStatus, label: 'In Transit' }, + { status: 'delivered' as ShipmentStatus, label: 'Delivered' }, + { status: 'completed' as ShipmentStatus, label: 'Completed' }, +]; + +const TERMINAL_STATUSES: ShipmentStatus[] = [ + 'cancelled' as ShipmentStatus, + 'disputed' as ShipmentStatus, +]; + +export interface ShipmentTimelineProps { + history: ShipmentStatusHistory[]; + currentStatus?: ShipmentStatus | null; +} + +export function ShipmentTimeline({ history, currentStatus }: ShipmentTimelineProps) { + const isTerminal = currentStatus && TERMINAL_STATUSES.includes(currentStatus); + + // Build steps from history + const completedStatuses = new Set(history.map((h) => h.toStatus)); + const lastCompleted = history.length > 0 ? history[history.length - 1] : null; + + // If terminal, show a special indicator + if (isTerminal && currentStatus) { + const terminalLabel = + currentStatus === 'cancelled' ? 'Cancelled' : 'Disputed'; + const terminalColor = + currentStatus === 'cancelled' + ? 'bg-amber-500' + : 'bg-red-500'; + + return ( +
+
    + {history.map((entry) => ( +
  1. + +

    + {entry.toStatus.replace('_', ' ').replace(/\b\w/g, (l) => l.toUpperCase())} +

    + + {entry.reason && ( +

    + “{entry.reason}” +

    + )} +
  2. + ))} +
  3. + +

    + {terminalLabel} +

    + {lastCompleted?.changedAt && ( + + )} +
  4. +
+
+ ); + } + + // Determine current step index + const currentIndex = currentStatus + ? STEPS.findIndex((s) => s.status === currentStatus) + : -1; + + return ( +
+
    + {STEPS.map((step, i) => { + const isCompleted = i < currentIndex; + const isCurrent = i === currentIndex; + const historyEntry = history.find((h) => h.toStatus === step.status); + + return ( +
  1. + +
    +

    + {step.label} + {isCurrent && ( + + Active + + )} +

    + {historyEntry && ( + <> + + {historyEntry.changedBy && ( +

    + by {historyEntry.changedBy.firstName} {historyEntry.changedBy.lastName} +

    + )} + + )} + {historyEntry?.reason && ( +

    + “{historyEntry.reason}” +

    + )} +
    +
  2. + ); + })} +
+
+ ); +} diff --git a/frontend/package/components/ShipmentTimeline/index.ts b/frontend/package/components/ShipmentTimeline/index.ts new file mode 100644 index 00000000..e8ef08ce --- /dev/null +++ b/frontend/package/components/ShipmentTimeline/index.ts @@ -0,0 +1,2 @@ +export { ShipmentTimeline } from './ShipmentTimeline'; +export type { ShipmentTimelineProps } from './ShipmentTimeline';