// Phase 4 closure (PERF-M1 + P-H3): memoized dashboard chart panels. // // Pre-Phase-4 the four chart panels lived as inline JSX inside // DashboardPage's return statement. DashboardPage has 9 useQuery hooks // (health / summary / issuers / statusCounts / expirationTimeline / // jobTrends / issuanceRate / certs / jobs) and each refetch — including // the per-tab refocus refetches the Phase 2 work narrowed but didn't // eliminate for the live-tile cohort — forced React to re-evaluate every // chart's JSX subtree, including the Recharts ResponsiveContainer // reconciliation that the library uses under the hood (~10-50 ms each // for charts with non-trivial data). // // Post-Phase-4 each chart is its own React.memo-wrapped component. When // only `summary` updates, the four chart panels skip re-render entirely // because their `data` prop didn't change. When `jobTrends` updates, // only `JobTrendsLineChart` re-renders; the other three panels skip. // // React.memo's default equality is referential (Object.is). The parent // DashboardPage passes the query result's `.data` arrays directly — TanStack // Query returns a stable reference until the underlying data actually // changes (it caches via queryKey), so referential equality is the // correct check for this layer. No custom areEqual function needed. import { memo } from 'react'; import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, } from 'recharts'; // ─── Shared helpers ────────────────────────────────────── /** PascalCase → space-separated for display ("RenewalInProgress" → "Renewal In Progress"). */ const formatStatus = (s: string) => s.replace(/([a-z])([A-Z])/g, '$1 $2'); /** "2026-05-10" → "5/10" for compact x-axis labels. */ const formatShortDate = (dateStr: string) => { const d = new Date(dateStr + 'T00:00:00'); return `${d.getMonth() + 1}/${d.getDate()}`; }; interface TooltipPayloadEntry { color?: string; name?: string; value?: number | string; } interface CustomTooltipProps { active?: boolean; payload?: TooltipPayloadEntry[]; label?: string; } const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => { if (!active || !payload?.length) return null; return (
{label}
{payload.map((entry, i) => ({entry.name}: {typeof entry.value === 'number' && entry.name?.includes('rate') ? `${entry.value.toFixed(1)}%` : entry.value}
))}