diff --git a/frontend/src/components/pages/ReportingPage.js b/frontend/src/components/pages/ReportingPage.js index 0f3d282..142f4dc 100644 --- a/frontend/src/components/pages/ReportingPage.js +++ b/frontend/src/components/pages/ReportingPage.js @@ -385,6 +385,101 @@ function ActionCoverageDonut({ findings, activeSegment, onSegmentClick }) { ); } +// --------------------------------------------------------------------------- +// SVG Donut Chart — FP Workflow Status distribution +// --------------------------------------------------------------------------- +const FP_WORKFLOW_DEFS = [ + { key: 'Actionable', label: 'Actionable', color: '#F59E0B' }, + { key: 'Requested', label: 'Requested', color: '#0EA5E9' }, + { key: 'Reworked', label: 'Reworked', color: '#A855F7' }, + { key: 'Approved', label: 'Approved', color: '#22C55E' }, + { key: 'Rejected', label: 'Rejected', color: '#EF4444' }, + { key: 'Expired', label: 'Expired', color: '#64748B' }, + { key: 'Unknown', label: 'Unknown', color: '#334155' }, +]; + +function FPWorkflowDonut({ findings }) { + const SIZE = 180; + const CX = SIZE / 2; + const CY = SIZE / 2; + const OUTER = 72; + const INNER = 48; + + const counts = useMemo(() => { + const map = {}; + FP_WORKFLOW_DEFS.forEach(d => { map[d.key] = 0; }); + findings.forEach((f) => { + if (!f.workflow) return; + const state = f.workflow.state || ''; + const def = FP_WORKFLOW_DEFS.find(d => d.key.toLowerCase() === state.toLowerCase()); + const key = def ? def.key : 'Unknown'; + map[key] = (map[key] || 0) + 1; + }); + return map; + }, [findings]); + + const total = Object.values(counts).reduce((a, b) => a + b, 0); + + if (total === 0) { + return ( +
No FP workflows — click Sync to load
+