import { useEffect, useRef, useState } from 'react'; import { type DistributionPreview } from '@/types'; interface Props { distribution: DistributionPreview; bucketRefs: React.RefObject | null>; containerRef: React.RefObject; incomeRef: React.RefObject; } interface LinePosition { bucketId: string; amount: number; bucketY: number; bucketRight: number; incomeLeft: number; incomeY: number; } const centsToDollars = (cents: number): string => `$${(cents / 100).toFixed(0)}`; export default function DistributionLines({ distribution, bucketRefs, containerRef, incomeRef }: Props) { const [positions, setPositions] = useState([]); const svgRef = useRef(null); useEffect(() => { const measure = () => { const container = containerRef.current; const income = incomeRef.current; if (!container || !income) return; const containerRect = container.getBoundingClientRect(); const incomeRect = income.getBoundingClientRect(); const incomeLeft = incomeRect.left - containerRect.left; const incomeCenterY = incomeRect.top + incomeRect.height / 2 - containerRect.top; const lines: LinePosition[] = []; for (const alloc of distribution.allocations) { const el = bucketRefs.current?.get(alloc.bucket_id); if (!el) continue; const bucketRect = el.getBoundingClientRect(); const bucketRight = bucketRect.right - containerRect.left; const bucketY = bucketRect.top + bucketRect.height / 2 - containerRect.top; lines.push({ bucketId: alloc.bucket_id, amount: alloc.allocated_amount, bucketY, bucketRight, incomeLeft, incomeY: incomeCenterY, }); } setPositions(lines); }; requestAnimationFrame(measure); const observer = new ResizeObserver(measure); if (containerRef.current) observer.observe(containerRef.current); return () => observer.disconnect(); }, [distribution, bucketRefs, containerRef, incomeRef]); if (positions.length === 0) return null; // Trunk X position: midpoint between rightmost bucket and income panel const trunkX = positions.length > 0 ? (Math.max(...positions.map(p => p.bucketRight)) + positions[0].incomeLeft) / 2 : 0; const incomeY = positions[0]?.incomeY ?? 0; const allYs = [...positions.map(p => p.bucketY), incomeY]; const trunkTop = Math.min(...allYs); const trunkBottom = Math.max(...allYs); return ( {/* Vertical trunk from income to span all bucket Y positions */} {/* Horizontal line from trunk to income panel */} {/* Branch lines from trunk to each bucket */} {positions.map((pos) => { const isZero = pos.amount === 0; const opacity = isZero ? 0.2 : 0.8; const label = centsToDollars(pos.amount); return ( {/* Horizontal branch: trunk → bucket */} {/* Arrow tip */} {/* Amount label */} {label} ); })} ); }