diff --git a/frontend/src/components/pages/ReportingPage.js b/frontend/src/components/pages/ReportingPage.js index 69d1e4f..e956ab4 100644 --- a/frontend/src/components/pages/ReportingPage.js +++ b/frontend/src/components/pages/ReportingPage.js @@ -264,6 +264,101 @@ function StatusDonut({ open, closed, loading }) { ); } +// --------------------------------------------------------------------------- +// SVG Donut Chart — FP# workflow state distribution +// --------------------------------------------------------------------------- +const WF_STATE_DEFS = [ + { key: 'expired', label: 'Expired', color: '#EF4444' }, + { key: 'rejected', label: 'Rejected', color: '#F87171' }, + { key: 'reworked', label: 'Reworked', color: '#F59E0B' }, + { key: 'actionable', label: 'Actionable', color: '#FCD34D' }, + { key: 'requested', label: 'Requested', color: '#0EA5E9' }, + { key: 'approved', label: 'Approved', color: '#10B981' }, + { key: 'none', label: 'No FP#', color: '#334155' }, +]; + +function WorkflowDonut({ findings }) { + const SIZE = 180; + const CX = SIZE / 2; + const CY = SIZE / 2; + const OUTER = 72; + const INNER = 48; + + const counts = useMemo(() => { + const map = Object.fromEntries(WF_STATE_DEFS.map((d) => [d.key, 0])); + findings.forEach((f) => { + const state = (f.workflow?.state || '').toLowerCase(); + if (state && state in map) map[state]++; + else map.none++; + }); + return map; + }, [findings]); + + const total = Object.values(counts).reduce((a, b) => a + b, 0); + + if (total === 0) { + return ( +
No data — click Sync to load
+