Add CCP Metrics page with multi-vertical VCL upload and cross-org reporting
New feature: multi-file per-vertical compliance xlsx upload with scoped
resolution logic, executive-level aggregated reporting, and drill-down
by vertical and metric. Supports daily upload cadence and batch commit.
Backend:
- Migration: add vertical column to compliance_items/uploads, create
vcl_multi_vertical_summary table
- New route module: routes/vclMultiVertical.js with preview, commit,
stats, trend, metric drill-down, device list, and burndown endpoints
- New helpers: parseVerticalFilename(), computeVerticalBurndown()
- Vertical-scoped resolution: uploading one vertical never resolves
items from other verticals
Frontend:
- CCPMetricsPage with stats bar, trend chart, donut, vertical table
- Drill-down: vertical -> metrics by category -> device list
- Per-vertical burndown forecast chart
- MultiVerticalUploadModal: multi-file drag-drop, batch preview, commit
- Nav entry: CCP Metrics (Building2 icon)
Docs:
- Design brief for stakeholder meeting (docs/vcl-multi-vertical-design-brief.md)
2026-05-14 09:49:59 -06:00
import React , { useState , useEffect , useCallback } from 'react' ;
2026-05-14 10:00:00 -06:00
import { Upload , Building2 , ChevronLeft , Loader , AlertCircle , BarChart3 } from 'lucide-react' ;
Add CCP Metrics page with multi-vertical VCL upload and cross-org reporting
New feature: multi-file per-vertical compliance xlsx upload with scoped
resolution logic, executive-level aggregated reporting, and drill-down
by vertical and metric. Supports daily upload cadence and batch commit.
Backend:
- Migration: add vertical column to compliance_items/uploads, create
vcl_multi_vertical_summary table
- New route module: routes/vclMultiVertical.js with preview, commit,
stats, trend, metric drill-down, device list, and burndown endpoints
- New helpers: parseVerticalFilename(), computeVerticalBurndown()
- Vertical-scoped resolution: uploading one vertical never resolves
items from other verticals
Frontend:
- CCPMetricsPage with stats bar, trend chart, donut, vertical table
- Drill-down: vertical -> metrics by category -> device list
- Per-vertical burndown forecast chart
- MultiVerticalUploadModal: multi-file drag-drop, batch preview, commit
- Nav entry: CCP Metrics (Building2 icon)
Docs:
- Design brief for stakeholder meeting (docs/vcl-multi-vertical-design-brief.md)
2026-05-14 09:49:59 -06:00
import { useAuth } from '../../contexts/AuthContext' ;
import { PieChart , Pie , Cell , ComposedChart , Bar , Line , XAxis , YAxis , CartesianGrid , Tooltip , Legend , ReferenceLine , ResponsiveContainer } from 'recharts' ;
import MultiVerticalUploadModal from './MultiVerticalUploadModal' ;
const API _BASE = process . env . REACT _APP _API _BASE || 'http://localhost:3001/api' ;
const TEAL = '#14B8A6' ;
const PURPLE = '#A78BFA' ;
// ---------------------------------------------------------------------------
// Styles
// ---------------------------------------------------------------------------
const PAGE _STYLE = {
padding : '1.5rem 2rem' ,
minHeight : '100vh' ,
fontFamily : "'JetBrains Mono', 'Fira Code', monospace" ,
} ;
const CARD _STYLE = {
background : 'rgba(15, 23, 42, 0.6)' ,
border : '1px solid rgba(167, 139, 250, 0.2)' ,
borderRadius : '0.75rem' ,
padding : '1.25rem' ,
} ;
const STAT _CARD _STYLE = {
... CARD _STYLE ,
textAlign : 'center' ,
flex : 1 ,
minWidth : '140px' ,
} ;
const TABLE _STYLE = {
width : '100%' ,
borderCollapse : 'collapse' ,
fontSize : '0.8rem' ,
} ;
const TH _STYLE = {
padding : '0.75rem 1rem' ,
textAlign : 'left' ,
color : '#94A3B8' ,
fontWeight : '600' ,
fontSize : '0.7rem' ,
textTransform : 'uppercase' ,
letterSpacing : '0.05em' ,
borderBottom : '1px solid rgba(255,255,255,0.08)' ,
} ;
const TD _STYLE = {
padding : '0.75rem 1rem' ,
color : '#E2E8F0' ,
borderBottom : '1px solid rgba(255,255,255,0.04)' ,
} ;
// ---------------------------------------------------------------------------
// Stats Bar
// ---------------------------------------------------------------------------
function StatsBar ( { stats } ) {
if ( ! stats ) return null ;
const items = [
{ label : 'Total Devices' , value : stats . total _devices . toLocaleString ( ) , color : '#94A3B8' } ,
{ label : 'Compliant' , value : stats . compliant . toLocaleString ( ) , color : '#10B981' } ,
{ label : 'Non-Compliant' , value : stats . non _compliant . toLocaleString ( ) , color : '#EF4444' } ,
{ label : 'Current %' , value : ` ${ stats . compliance _pct } % ` , color : stats . compliance _pct >= stats . target _pct ? '#10B981' : '#F59E0B' } ,
{ label : 'Target %' , value : ` ${ stats . target _pct } % ` , color : PURPLE } ,
] ;
return (
< div style = { { display : 'flex' , gap : '1rem' , flexWrap : 'wrap' , marginBottom : '1.5rem' } } >
{ items . map ( ( { label , value , color } ) => (
< div key = { label } style = { STAT _CARD _STYLE } >
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , letterSpacing : '0.08em' , marginBottom : '0.5rem' } } > { label } < / d i v >
< div style = { { fontSize : '1.5rem' , fontWeight : '700' , color } } > { value } < / d i v >
< / d i v >
) ) }
< / d i v >
) ;
}
// ---------------------------------------------------------------------------
// Donut Chart
// ---------------------------------------------------------------------------
function DonutChart ( { donut } ) {
if ( ! donut ) return null ;
const data = [
{ name : 'Blocked' , count : donut . blocked . count , color : '#EF4444' } ,
{ name : 'In-Progress' , count : donut . in _progress . count , color : '#F59E0B' } ,
] ;
const total = donut . blocked . count + donut . in _progress . count ;
return (
< div style = { { ... CARD _STYLE , flex : 1 } } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '1rem' } } >
Non - Compliant Status
< / d i v >
< div style = { { display : 'flex' , alignItems : 'center' , justifyContent : 'center' } } >
< ResponsiveContainer width = { 200 } height = { 200 } >
< PieChart >
< Pie data = { data } innerRadius = { 55 } outerRadius = { 80 } dataKey = "count" nameKey = "name" >
{ data . map ( ( entry , i ) => < Cell key = { i } fill = { entry . color } / > ) }
< / P i e >
< Tooltip contentStyle = { { background : '#1E293B' , border : '1px solid #334155' , borderRadius : '0.5rem' , fontSize : '0.75rem' } } / >
< Legend wrapperStyle = { { fontSize : '0.7rem' } } / >
< / P i e C h a r t >
< / R e s p o n s i v e C o n t a i n e r >
< div style = { { textAlign : 'center' , marginLeft : '1rem' } } >
< div style = { { fontSize : '2rem' , fontWeight : '700' , color : '#E2E8F0' } } > { total } < / d i v >
< div style = { { fontSize : '0.65rem' , color : '#64748B' } } > Total Non - Compliant < / d i v >
< div style = { { marginTop : '0.75rem' , fontSize : '0.7rem' } } >
< div style = { { color : '#EF4444' } } > Blocked : { donut . blocked . count } ( { donut . blocked . pct } % ) < / d i v >
< div style = { { color : '#F59E0B' } } > In - Progress : { donut . in _progress . count } ( { donut . in _progress . pct } % ) < / d i v >
< / d i v >
< / d i v >
< / d i v >
< / d i v >
) ;
}
// ---------------------------------------------------------------------------
// Trend Chart
// ---------------------------------------------------------------------------
function TrendChart ( { months } ) {
if ( ! months || months . length === 0 ) return (
< div style = { { ... CARD _STYLE , flex : 2 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , color : '#64748B' , fontSize : '0.8rem' } } >
No trend data yet . Upload compliance data to generate trends .
< / d i v >
) ;
return (
< div style = { { ... CARD _STYLE , flex : 2 } } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '1rem' } } >
Compliance Trend
< / d i v >
< ResponsiveContainer width = "100%" height = { 220 } >
< ComposedChart data = { months } >
< CartesianGrid stroke = "rgba(255,255,255,0.05)" strokeDasharray = "3 3" / >
< XAxis dataKey = "month" tick = { { fontSize : 10 , fill : '#64748B' } } / >
< YAxis yAxisId = "count" tick = { { fontSize : 10 , fill : '#64748B' } } / >
< YAxis yAxisId = "pct" orientation = "right" domain = { [ 0 , 100 ] } unit = "%" tick = { { fontSize : 10 , fill : '#64748B' } } / >
< Tooltip contentStyle = { { background : '#1E293B' , border : '1px solid #334155' , borderRadius : '0.5rem' , fontSize : '0.75rem' } } / >
< Bar yAxisId = "count" dataKey = "compliant_count" fill = "#10B981" fillOpacity = { 0.6 } name = "Compliant Devices" / >
< Line yAxisId = "pct" dataKey = "compliance_pct" stroke = { TEAL } strokeWidth = { 2 } dot = { { r : 3 } } name = "Actual %" / >
< Line yAxisId = "pct" dataKey = "forecast_pct" stroke = { TEAL } strokeWidth = { 2 } strokeDasharray = "5 3" dot = { false } name = "Forecast %" / >
< ReferenceLine yAxisId = "pct" y = { months [ 0 ] ? . target _pct || 95 } stroke = "#F59E0B" strokeDasharray = "4 4" label = { { value : 'Target' , fill : '#F59E0B' , fontSize : 10 } } / >
< / C o m p o s e d C h a r t >
< / R e s p o n s i v e C o n t a i n e r >
< / d i v >
) ;
}
// ---------------------------------------------------------------------------
// Vertical Breakdown Table
// ---------------------------------------------------------------------------
function VerticalTable ( { breakdown , onSelectVertical } ) {
if ( ! breakdown || breakdown . length === 0 ) return null ;
return (
< div style = { { ... CARD _STYLE , marginTop : '1.5rem' } } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '1rem' } } >
Vertical Breakdown
< / d i v >
< div style = { { overflowX : 'auto' } } >
< table style = { TABLE _STYLE } >
< thead >
< tr >
< th style = { TH _STYLE } > Vertical < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Total < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Compliant < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Non - Compliant < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Compliance % < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Blockers < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Last Upload < / t h >
< / t r >
< / t h e a d >
< tbody >
{ breakdown . map ( v => {
const pctColor = v . compliance _pct >= 95 ? '#10B981' : v . compliance _pct >= 80 ? '#F59E0B' : '#EF4444' ;
return (
< tr
key = { v . vertical }
onClick = { ( ) => onSelectVertical ( v . vertical ) }
style = { { cursor : 'pointer' , transition : 'background 0.15s' } }
onMouseEnter = { e => e . currentTarget . style . background = 'rgba(167, 139, 250, 0.08)' }
onMouseLeave = { e => e . currentTarget . style . background = 'transparent' }
>
< td style = { { ... TD _STYLE , fontWeight : '600' , color : PURPLE } } > { v . vertical } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' } } > { v . total _devices . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#10B981' } } > { v . compliant . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#EF4444' } } > { v . non _compliant . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , fontWeight : '700' , color : pctColor } } > { v . compliance _pct } % < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : v . blockers > 0 ? '#EF4444' : '#64748B' } } > { v . blockers } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#64748B' , fontSize : '0.7rem' } } > { v . last _upload || '—' } < / t d >
< / t r >
) ;
} ) }
< / t b o d y >
< / t a b l e >
< / d i v >
< / d i v >
) ;
}
// ---------------------------------------------------------------------------
// Vertical Detail View (metric drill-down)
// ---------------------------------------------------------------------------
function VerticalDetailView ( { vertical , onBack , onSelectMetric } ) {
const [ metrics , setMetrics ] = useState ( null ) ;
const [ categories , setCategories ] = useState ( null ) ;
const [ burndown , setBurndown ] = useState ( null ) ;
const [ loading , setLoading ] = useState ( true ) ;
2026-05-14 10:00:51 -06:00
// ⚠️ CONVENTION: Missing error state — .catch() silently swallows errors without displaying them to the user. Add an error state and render an error message (see main CCPMetricsPage pattern).
Add CCP Metrics page with multi-vertical VCL upload and cross-org reporting
New feature: multi-file per-vertical compliance xlsx upload with scoped
resolution logic, executive-level aggregated reporting, and drill-down
by vertical and metric. Supports daily upload cadence and batch commit.
Backend:
- Migration: add vertical column to compliance_items/uploads, create
vcl_multi_vertical_summary table
- New route module: routes/vclMultiVertical.js with preview, commit,
stats, trend, metric drill-down, device list, and burndown endpoints
- New helpers: parseVerticalFilename(), computeVerticalBurndown()
- Vertical-scoped resolution: uploading one vertical never resolves
items from other verticals
Frontend:
- CCPMetricsPage with stats bar, trend chart, donut, vertical table
- Drill-down: vertical -> metrics by category -> device list
- Per-vertical burndown forecast chart
- MultiVerticalUploadModal: multi-file drag-drop, batch preview, commit
- Nav entry: CCP Metrics (Building2 icon)
Docs:
- Design brief for stakeholder meeting (docs/vcl-multi-vertical-design-brief.md)
2026-05-14 09:49:59 -06:00
useEffect ( ( ) => {
setLoading ( true ) ;
Promise . all ( [
fetch ( ` ${ API _BASE } /compliance/vcl-multi/vertical/ ${ encodeURIComponent ( vertical ) } /metrics ` , { credentials : 'include' } ) . then ( r => r . json ( ) ) ,
fetch ( ` ${ API _BASE } /compliance/vcl-multi/vertical/ ${ encodeURIComponent ( vertical ) } /burndown ` , { credentials : 'include' } ) . then ( r => r . json ( ) ) ,
] ) . then ( ( [ metricsData , burndownData ] ) => {
setMetrics ( metricsData . metrics || [ ] ) ;
setCategories ( metricsData . categories || [ ] ) ;
setBurndown ( burndownData ) ;
setLoading ( false ) ;
} ) . catch ( ( ) => setLoading ( false ) ) ;
} , [ vertical ] ) ;
if ( loading ) return < div style = { { padding : '2rem' , textAlign : 'center' , color : '#64748B' } } > < Loader style = { { animation : 'spin 1s linear infinite' } } / > Loading ... < / d i v > ;
return (
< div >
< button
onClick = { onBack }
style = { { background : 'none' , border : 'none' , color : PURPLE , cursor : 'pointer' , display : 'flex' , alignItems : 'center' , gap : '0.5rem' , fontSize : '0.8rem' , marginBottom : '1.5rem' , padding : 0 } }
>
< ChevronLeft style = { { width : '16px' , height : '16px' } } / > Back to Overview
< / b u t t o n >
< h2 style = { { fontSize : '1.2rem' , color : '#E2E8F0' , marginBottom : '1rem' , fontWeight : '700' } } >
< Building2 style = { { width : '20px' , height : '20px' , display : 'inline' , marginRight : '0.5rem' , color : PURPLE } } / >
{ vertical }
< / h 2 >
{ /* Burndown summary */ }
{ burndown && (
< div style = { { display : 'flex' , gap : '1rem' , marginBottom : '1.5rem' , flexWrap : 'wrap' } } >
< div style = { STAT _CARD _STYLE } >
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , marginBottom : '0.5rem' } } > Non - Compliant < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#EF4444' } } > { burndown . total } < / d i v >
< / d i v >
< div style = { STAT _CARD _STYLE } >
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , marginBottom : '0.5rem' } } > With Dates < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#F59E0B' } } > { burndown . with _dates } < / d i v >
< / d i v >
< div style = { STAT _CARD _STYLE } >
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , marginBottom : '0.5rem' } } > Blockers < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#EF4444' } } > { burndown . blockers } < / d i v >
< / d i v >
{ burndown . projected _clear _date && (
< div style = { STAT _CARD _STYLE } >
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , marginBottom : '0.5rem' } } > Projected Clear < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#10B981' } } > { burndown . projected _clear _date } < / d i v >
< / d i v >
) }
< / d i v >
) }
{ /* Burndown chart */ }
{ burndown && burndown . monthly && Object . keys ( burndown . monthly ) . length > 0 && (
< div style = { { ... CARD _STYLE , marginBottom : '1.5rem' } } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '1rem' } } >
Burndown Forecast
< / d i v >
< ResponsiveContainer width = "100%" height = { 180 } >
< ComposedChart data = { Object . entries ( burndown . monthly ) . sort ( ( [ a ] , [ b ] ) => a . localeCompare ( b ) ) . map ( ( [ month , count ] ) => ( { month , count } ) ) } >
< CartesianGrid stroke = "rgba(255,255,255,0.05)" strokeDasharray = "3 3" / >
< XAxis dataKey = "month" tick = { { fontSize : 10 , fill : '#64748B' } } / >
< YAxis tick = { { fontSize : 10 , fill : '#64748B' } } / >
< Tooltip contentStyle = { { background : '#1E293B' , border : '1px solid #334155' , borderRadius : '0.5rem' , fontSize : '0.75rem' } } / >
< Bar dataKey = "count" fill = { PURPLE } fillOpacity = { 0.7 } name = "Remediations" / >
< / C o m p o s e d C h a r t >
< / R e s p o n s i v e C o n t a i n e r >
< / d i v >
) }
{ /* Category summary */ }
{ categories && categories . length > 0 && (
< div style = { { ... CARD _STYLE , marginBottom : '1.5rem' } } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '1rem' } } >
By Category
< / d i v >
< div style = { { display : 'flex' , gap : '0.75rem' , flexWrap : 'wrap' } } >
{ categories . map ( c => (
< div key = { c . category } style = { { ... CARD _STYLE , padding : '0.75rem 1rem' , minWidth : '160px' } } >
< div style = { { fontSize : '0.65rem' , color : '#94A3B8' , marginBottom : '0.25rem' } } > { c . category } < / d i v >
< div style = { { fontSize : '1.1rem' , fontWeight : '700' , color : c . compliance _pct >= 95 ? '#10B981' : c . compliance _pct >= 80 ? '#F59E0B' : '#EF4444' } } >
{ c . compliance _pct } %
< / d i v >
< div style = { { fontSize : '0.6rem' , color : '#64748B' } } > { c . non _compliant } non - compliant < / d i v >
< / d i v >
) ) }
< / d i v >
< / d i v >
) }
{ /* Metrics table */ }
< div style = { CARD _STYLE } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '1rem' } } >
Metrics
< / d i v >
< table style = { TABLE _STYLE } >
< thead >
< tr >
< th style = { TH _STYLE } > Metric < / t h >
< th style = { TH _STYLE } > Description < / t h >
< th style = { TH _STYLE } > Category < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Compliant < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Non - Compliant < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Total < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > % < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Target < / t h >
< / t r >
< / t h e a d >
< tbody >
{ metrics && metrics . map ( ( m , i ) => {
const pctColor = m . compliance _pct >= m . target ? '#10B981' : m . compliance _pct >= ( m . target * 0.85 ) ? '#F59E0B' : '#EF4444' ;
return (
< tr
key = { i }
onClick = { ( ) => onSelectMetric ( m . metric _id ) }
style = { { cursor : 'pointer' , transition : 'background 0.15s' } }
onMouseEnter = { e => e . currentTarget . style . background = 'rgba(167, 139, 250, 0.06)' }
onMouseLeave = { e => e . currentTarget . style . background = 'transparent' }
>
< td style = { { ... TD _STYLE , fontWeight : '600' , color : PURPLE } } > { m . metric _id } < / t d >
< td style = { { ... TD _STYLE , fontSize : '0.7rem' , color : '#94A3B8' , maxWidth : '250px' , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' } } > { m . metric _desc } < / t d >
< td style = { { ... TD _STYLE , fontSize : '0.7rem' } } > { m . category } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#10B981' } } > { m . compliant } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#EF4444' } } > { m . non _compliant } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' } } > { m . total } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , fontWeight : '700' , color : pctColor } } > { Number ( m . compliance _pct ) . toFixed ( 1 ) } % < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#64748B' } } > { Number ( m . target ) . toFixed ( 0 ) } % < / t d >
< / t r >
) ;
} ) }
< / t b o d y >
< / t a b l e >
< / d i v >
< / d i v >
) ;
}
// ---------------------------------------------------------------------------
// Metric Device List (deepest drill-down)
// ---------------------------------------------------------------------------
function MetricDeviceList ( { vertical , metricId , onBack } ) {
const [ devices , setDevices ] = useState ( null ) ;
const [ loading , setLoading ] = useState ( true ) ;
2026-05-14 10:00:51 -06:00
// ⚠️ CONVENTION: Missing error state — .catch() silently swallows errors without displaying them to the user. Add an error state and render an error message (see main CCPMetricsPage pattern).
Add CCP Metrics page with multi-vertical VCL upload and cross-org reporting
New feature: multi-file per-vertical compliance xlsx upload with scoped
resolution logic, executive-level aggregated reporting, and drill-down
by vertical and metric. Supports daily upload cadence and batch commit.
Backend:
- Migration: add vertical column to compliance_items/uploads, create
vcl_multi_vertical_summary table
- New route module: routes/vclMultiVertical.js with preview, commit,
stats, trend, metric drill-down, device list, and burndown endpoints
- New helpers: parseVerticalFilename(), computeVerticalBurndown()
- Vertical-scoped resolution: uploading one vertical never resolves
items from other verticals
Frontend:
- CCPMetricsPage with stats bar, trend chart, donut, vertical table
- Drill-down: vertical -> metrics by category -> device list
- Per-vertical burndown forecast chart
- MultiVerticalUploadModal: multi-file drag-drop, batch preview, commit
- Nav entry: CCP Metrics (Building2 icon)
Docs:
- Design brief for stakeholder meeting (docs/vcl-multi-vertical-design-brief.md)
2026-05-14 09:49:59 -06:00
useEffect ( ( ) => {
setLoading ( true ) ;
fetch ( ` ${ API _BASE } /compliance/vcl-multi/vertical/ ${ encodeURIComponent ( vertical ) } /metric/ ${ encodeURIComponent ( metricId ) } /devices ` , { credentials : 'include' } )
. then ( r => r . json ( ) )
. then ( data => { setDevices ( data . devices || [ ] ) ; setLoading ( false ) ; } )
. catch ( ( ) => setLoading ( false ) ) ;
} , [ vertical , metricId ] ) ;
if ( loading ) return < div style = { { padding : '2rem' , textAlign : 'center' , color : '#64748B' } } > < Loader style = { { animation : 'spin 1s linear infinite' } } / > Loading ... < / d i v > ;
return (
< div >
< button
onClick = { onBack }
style = { { background : 'none' , border : 'none' , color : PURPLE , cursor : 'pointer' , display : 'flex' , alignItems : 'center' , gap : '0.5rem' , fontSize : '0.8rem' , marginBottom : '1.5rem' , padding : 0 } }
>
< ChevronLeft style = { { width : '16px' , height : '16px' } } / > Back to Metrics
< / b u t t o n >
< h3 style = { { fontSize : '1rem' , color : '#E2E8F0' , marginBottom : '1rem' } } >
{ vertical } / Metric { metricId } — { devices ? devices . length : 0 } non - compliant devices
< / h 3 >
< div style = { CARD _STYLE } >
< table style = { TABLE _STYLE } >
< thead >
< tr >
< th style = { TH _STYLE } > Hostname < / t h >
< th style = { TH _STYLE } > IP Address < / t h >
< th style = { TH _STYLE } > Type < / t h >
< th style = { TH _STYLE } > Team < / t h >
< th style = { { ... TH _STYLE , textAlign : 'right' } } > Seen Count < / t h >
< th style = { TH _STYLE } > First Seen < / t h >
< th style = { TH _STYLE } > Last Seen < / t h >
< th style = { TH _STYLE } > Resolution Date < / t h >
< th style = { TH _STYLE } > Remediation Plan < / t h >
< / t r >
< / t h e a d >
< tbody >
{ devices && devices . map ( ( d , i ) => (
< tr key = { i } >
< td style = { { ... TD _STYLE , fontWeight : '600' , color : '#E2E8F0' } } > { d . hostname } < / t d >
< td style = { { ... TD _STYLE , color : '#94A3B8' } } > { d . ip _address || '—' } < / t d >
< td style = { { ... TD _STYLE , color : '#94A3B8' } } > { d . device _type || '—' } < / t d >
< td style = { { ... TD _STYLE , color : '#94A3B8' } } > { d . team || '—' } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' } } > { d . seen _count } < / t d >
< td style = { { ... TD _STYLE , color : '#64748B' , fontSize : '0.7rem' } } > { d . first _seen || '—' } < / t d >
< td style = { { ... TD _STYLE , color : '#64748B' , fontSize : '0.7rem' } } > { d . last _seen || '—' } < / t d >
< td style = { { ... TD _STYLE , color : d . resolution _date ? '#F59E0B' : '#64748B' , fontSize : '0.7rem' } } > { d . resolution _date || 'Not set' } < / t d >
< td style = { { ... TD _STYLE , color : '#94A3B8' , fontSize : '0.7rem' , maxWidth : '200px' , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' } } > { d . remediation _plan || '—' } < / t d >
< / t r >
) ) }
{ devices && devices . length === 0 && (
< tr > < td colSpan = { 9 } style = { { ... TD _STYLE , textAlign : 'center' , color : '#64748B' } } > No devices found < / t d > < / t r >
) }
< / t b o d y >
< / t a b l e >
< / d i v >
< / d i v >
) ;
}
// ---------------------------------------------------------------------------
// Main Page Component
// ---------------------------------------------------------------------------
export default function CCPMetricsPage ( ) {
const { isAdmin , isEditor } = useAuth ( ) ;
const [ stats , setStats ] = useState ( null ) ;
const [ trend , setTrend ] = useState ( null ) ;
const [ loading , setLoading ] = useState ( true ) ;
const [ error , setError ] = useState ( null ) ;
const [ showUpload , setShowUpload ] = useState ( false ) ;
// Drill-down state
const [ selectedVertical , setSelectedVertical ] = useState ( null ) ;
const [ selectedMetric , setSelectedMetric ] = useState ( null ) ;
const fetchData = useCallback ( ( ) => {
setLoading ( true ) ;
setError ( null ) ;
Promise . all ( [
fetch ( ` ${ API _BASE } /compliance/vcl-multi/stats ` , { credentials : 'include' } ) . then ( r => { if ( ! r . ok ) throw new Error ( 'Failed to load stats' ) ; return r . json ( ) ; } ) ,
fetch ( ` ${ API _BASE } /compliance/vcl-multi/trend ` , { credentials : 'include' } ) . then ( r => { if ( ! r . ok ) throw new Error ( 'Failed to load trend' ) ; return r . json ( ) ; } ) ,
] ) . then ( ( [ statsData , trendData ] ) => {
setStats ( statsData ) ;
setTrend ( trendData ) ;
setLoading ( false ) ;
} ) . catch ( err => {
setError ( err . message ) ;
setLoading ( false ) ;
} ) ;
} , [ ] ) ;
useEffect ( ( ) => { fetchData ( ) ; } , [ fetchData ] ) ;
const handleUploadComplete = ( ) => {
setShowUpload ( false ) ;
fetchData ( ) ;
} ;
// Render drill-down views
if ( selectedMetric && selectedVertical ) {
return (
< div style = { PAGE _STYLE } >
< MetricDeviceList
vertical = { selectedVertical }
metricId = { selectedMetric }
onBack = { ( ) => setSelectedMetric ( null ) }
/ >
< / d i v >
) ;
}
if ( selectedVertical ) {
return (
< div style = { PAGE _STYLE } >
< VerticalDetailView
vertical = { selectedVertical }
onBack = { ( ) => setSelectedVertical ( null ) }
onSelectMetric = { ( metricId ) => setSelectedMetric ( metricId ) }
/ >
< / d i v >
) ;
}
// Main overview
return (
< div style = { PAGE _STYLE } >
{ /* Header */ }
< div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' , marginBottom : '1.5rem' } } >
< div >
< h1 style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#E2E8F0' , margin : 0 , display : 'flex' , alignItems : 'center' , gap : '0.75rem' } } >
< BarChart3 style = { { width : '24px' , height : '24px' , color : PURPLE } } / >
CCP Metrics — Multi - Vertical VCL
< / h 1 >
< p style = { { fontSize : '0.7rem' , color : '#64748B' , margin : '0.25rem 0 0 0' } } >
Cross - organizational compliance posture across all verticals
< / p >
< / d i v >
{ ( isAdmin ( ) || isEditor ( ) ) && (
< button
onClick = { ( ) => setShowUpload ( true ) }
style = { {
display : 'flex' , alignItems : 'center' , gap : '0.5rem' ,
padding : '0.6rem 1.2rem' ,
background : ` ${ PURPLE } 20 ` ,
border : ` 1px solid ${ PURPLE } 60 ` ,
borderRadius : '0.5rem' ,
color : PURPLE ,
fontSize : '0.75rem' ,
fontWeight : '600' ,
cursor : 'pointer' ,
transition : 'background 0.15s' ,
} }
onMouseEnter = { e => e . currentTarget . style . background = ` ${ PURPLE } 35 ` }
onMouseLeave = { e => e . currentTarget . style . background = ` ${ PURPLE } 20 ` }
>
< Upload style = { { width : '14px' , height : '14px' } } / >
Upload Verticals
< / b u t t o n >
) }
< / d i v >
{ /* Loading / Error states */ }
{ loading && (
< div style = { { textAlign : 'center' , padding : '3rem' , color : '#64748B' } } >
< Loader style = { { width : '24px' , height : '24px' , animation : 'spin 1s linear infinite' , marginBottom : '0.5rem' } } / >
< div style = { { fontSize : '0.8rem' } } > Loading compliance data ... < / d i v >
< / d i v >
) }
{ error && (
< div style = { { ... CARD _STYLE , borderColor : 'rgba(239, 68, 68, 0.3)' , display : 'flex' , alignItems : 'center' , gap : '0.75rem' , padding : '1rem' } } >
< AlertCircle style = { { width : '18px' , height : '18px' , color : '#EF4444' , flexShrink : 0 } } / >
< span style = { { color : '#EF4444' , fontSize : '0.8rem' } } > { error } < / s p a n >
< / d i v >
) }
{ ! loading && ! error && stats && (
< >
{ /* Stats bar */ }
< StatsBar stats = { stats . stats } / >
{ /* Charts row */ }
< div style = { { display : 'flex' , gap : '1.5rem' , marginBottom : '1.5rem' , flexWrap : 'wrap' } } >
< TrendChart months = { trend ? . months } / >
< DonutChart donut = { stats . donut } / >
< / d i v >
{ /* Vertical breakdown table */ }
< VerticalTable
breakdown = { stats . vertical _breakdown }
onSelectVertical = { setSelectedVertical }
/ >
{ /* Last upload info */ }
{ stats . last _upload _date && (
< div style = { { marginTop : '1rem' , fontSize : '0.65rem' , color : '#475569' , textAlign : 'right' } } >
Last upload : { stats . last _upload _date }
< / d i v >
) }
< / >
) }
{ ! loading && ! error && ( ! stats || ! stats . vertical _breakdown || stats . vertical _breakdown . length === 0 ) && (
< div style = { { ... CARD _STYLE , textAlign : 'center' , padding : '3rem' , marginTop : '2rem' } } >
< Building2 style = { { width : '48px' , height : '48px' , color : '#334155' , margin : '0 auto 1rem' } } / >
< div style = { { fontSize : '1rem' , color : '#94A3B8' , marginBottom : '0.5rem' } } > No multi - vertical data yet < / d i v >
< div style = { { fontSize : '0.75rem' , color : '#64748B' , marginBottom : '1.5rem' } } >
Upload per - vertical compliance xlsx files to generate cross - organizational reports .
< / d i v >
{ ( isAdmin ( ) || isEditor ( ) ) && (
< button
onClick = { ( ) => setShowUpload ( true ) }
style = { {
padding : '0.6rem 1.5rem' ,
background : PURPLE ,
border : 'none' ,
borderRadius : '0.5rem' ,
color : '#FFF' ,
fontSize : '0.8rem' ,
fontWeight : '600' ,
cursor : 'pointer' ,
} }
>
Upload Verticals
< / b u t t o n >
) }
< / d i v >
) }
{ /* Upload Modal */ }
{ showUpload && (
< MultiVerticalUploadModal
onClose = { ( ) => setShowUpload ( false ) }
onUploadComplete = { handleUploadComplete }
/ >
) }
< / d i v >
) ;
}