import { Waypoints } from "lucide-react"; import { ResponsiveContainer, Sankey, Tooltip, type SankeyLinkProps } from "recharts"; import { Text } from "@mantine/core"; import type { IOverviewRevenueFlow } from "@/types/overview"; import { chartTooltipStyle } from "./chart-style"; import { SummaryCard } from "./SummaryCard"; import { DIRECTION_COLORS, FLOW_FALLBACK_COLOR, FLOW_LABELS, FREIGHT_TYPE_COLORS, } from "./flow-colors"; function formatEtb(amount: number) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "ETB", maximumFractionDigits: 0, }).format(amount); } interface SankeyNodeDatum { name: string; key: string; color: string; } /** Build recharts Sankey data: direction nodes on the left, freight types on the right. */ function toSankeyData(flows: IOverviewRevenueFlow[]) { const active = flows.filter((f) => f.amountEtb > 0); const nodes: SankeyNodeDatum[] = []; const indexByKey = new Map(); const nodeIndex = (key: string, color: string) => { const existing = indexByKey.get(key); if (existing != null) return existing; nodes.push({ name: FLOW_LABELS[key] ?? key, key, color }); indexByKey.set(key, nodes.length - 1); return nodes.length - 1; }; // Register directions first so they all land on the left column. for (const flow of active) { nodeIndex(flow.direction, DIRECTION_COLORS[flow.direction] ?? FLOW_FALLBACK_COLOR); } const links = active.map((flow) => ({ source: indexByKey.get(flow.direction)!, target: nodeIndex( flow.freightType, FREIGHT_TYPE_COLORS[flow.freightType] ?? FLOW_FALLBACK_COLOR, ), value: flow.amountEtb, })); return { nodes, links }; } function FlowNode({ x, y, width, height, index, payload, }: { x: number; y: number; width: number; height: number; index: number; payload: { name?: string; value?: number; color?: string }; }) { // Labels sit to the right of every bar: the right margin reserves room for // the last column, and the pale ribbons stay readable under the left one. return ( {payload.name} {formatEtb(payload.value ?? 0)} ); } /** Ribbon tinted by its source direction — the corridor keeps its color across the chart. */ function FlowLink({ sourceX, targetX, sourceY, targetY, sourceControlX, targetControlX, linkWidth, index, payload, }: SankeyLinkProps) { // Custom node fields (color) ride along on the layout node recharts hands back. const source = payload.source as { color?: string }; return ( ); } interface OverviewSankeyFlowProps { flows: IOverviewRevenueFlow[]; } /** * Where the money runs: ETB revenue as ribbons from trade direction to * freight type. Ribbon thickness is proportional to revenue, so the biggest * corridor is unmissable. */ export function OverviewSankeyFlow({ flows }: OverviewSankeyFlowProps) { const data = toSankeyData(flows); return ( {data.links.length === 0 ? ( No revenue in this period ) : ( formatEtb(Number(value))} contentStyle={chartTooltipStyle} /> )} ); }