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 11:54:58 -06:00
import { Upload , Building2 , ChevronLeft , Loader , AlertCircle , BarChart3 , Settings , Trash2 , RotateCcw } 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' ;
2026-05-15 17:08:55 -06:00
import { PieChart , Pie , Cell , ComposedChart , Bar , BarChart , Line , XAxis , YAxis , CartesianGrid , Tooltip , Legend , ReferenceLine , ResponsiveContainer } from 'recharts' ;
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 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
// ---------------------------------------------------------------------------
2026-05-14 15:24:10 -06:00
function StatsBar ( { stats , onNonCompliantClick , ncExpanded } ) {
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
if ( ! stats ) return null ;
const items = [
{ label : 'Total Devices' , value : stats . total _devices . toLocaleString ( ) , color : '#94A3B8' } ,
{ label : 'Compliant' , value : stats . compliant . toLocaleString ( ) , color : '#10B981' } ,
2026-05-14 15:24:10 -06:00
{ label : 'Non-Compliant' , value : stats . non _compliant . toLocaleString ( ) , color : '#EF4444' , clickable : true } ,
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
{ 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' } } >
2026-05-14 15:24:10 -06:00
{ items . map ( ( { label , value , color , clickable } ) => (
< div
key = { label }
onClick = { clickable ? onNonCompliantClick : undefined }
style = { {
... STAT _CARD _STYLE ,
cursor : clickable ? 'pointer' : 'default' ,
border : clickable && ncExpanded
? '1px solid rgba(239, 68, 68, 0.6)'
: STAT _CARD _STYLE . border ,
background : clickable && ncExpanded
? 'rgba(239, 68, 68, 0.08)'
: STAT _CARD _STYLE . background ,
transition : 'all 0.15s' ,
} }
onMouseEnter = { clickable ? e => { e . currentTarget . style . borderColor = 'rgba(239, 68, 68, 0.5)' ; } : undefined }
onMouseLeave = { clickable ? e => { if ( ! ncExpanded ) e . currentTarget . style . borderColor = 'rgba(167, 139, 250, 0.2)' ; } : undefined }
>
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , letterSpacing : '0.08em' , marginBottom : '0.5rem' } } >
{ label } { clickable && < span style = { { marginLeft : '0.4rem' , fontSize : '0.6rem' , color : '#64748B' } } > { ncExpanded ? '▾' : '▸' } < / s p a n > }
< / d i v >
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
< div style = { { fontSize : '1.5rem' , fontWeight : '700' , color } } > { value } < / d i v >
< / d i v >
) ) }
< / d i v >
) ;
}
2026-05-14 15:24:10 -06:00
// ---------------------------------------------------------------------------
// Metric Breakdown Panel (shown when Non-Compliant is clicked)
// ---------------------------------------------------------------------------
2026-05-14 15:29:20 -06:00
function MetricBreakdownPanel ( { metrics } ) {
const [ showAll , setShowAll ] = useState ( false ) ;
2026-05-14 15:24:10 -06:00
if ( ! metrics || metrics . length === 0 ) return null ;
// Only show metrics with non_compliant > 0
const ncMetrics = metrics . filter ( m => m . non _compliant > 0 ) ;
if ( ncMetrics . length === 0 ) return null ;
2026-05-14 15:29:20 -06:00
const TOP _COUNT = 8 ;
const displayMetrics = showAll ? ncMetrics : ncMetrics . slice ( 0 , TOP _COUNT ) ;
const hasMore = ncMetrics . length > TOP _COUNT ;
2026-05-14 15:24:10 -06:00
return (
< div style = { { ... CARD _STYLE , marginBottom : '1.5rem' } } >
2026-05-14 15:29:20 -06:00
< div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' , marginBottom : '0.75rem' } } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' } } >
Non - Compliant by Metric
< / d i v >
{ hasMore && (
< button
onClick = { ( ) => setShowAll ( ! showAll ) }
style = { { background : 'none' , border : 'none' , color : PURPLE , fontSize : '0.7rem' , cursor : 'pointer' , padding : '0.2rem 0.5rem' } }
>
{ showAll ? 'Show top 8' : ` Show all ${ ncMetrics . length } ` }
< / b u t t o n >
) }
2026-05-14 15:24:10 -06:00
< / d i v >
2026-05-14 15:29:20 -06:00
< div style = { { display : 'grid' , gridTemplateColumns : 'repeat(auto-fill, minmax(130px, 1fr))' , gap : '0.5rem' } } >
{ displayMetrics . map ( m => {
2026-05-14 15:24:10 -06:00
const pct = m . total > 0 ? ( m . compliant / m . total ) : 0 ;
const target = Number ( m . target || 0 ) ;
2026-05-14 15:30:43 -06:00
const pctColor = pct >= target ? '#10B981' : pct >= target * 0.85 ? '#F59E0B' : '#EF4444' ;
2026-05-14 15:24:10 -06:00
return (
2026-05-14 15:29:20 -06:00
< div
2026-05-14 15:24:10 -06:00
key = { m . metric _id }
style = { {
2026-05-14 15:29:20 -06:00
background : 'rgba(15, 23, 42, 0.7)' ,
2026-05-14 15:30:43 -06:00
border : ` 1px solid ${ pctColor } 30 ` ,
2026-05-14 15:29:20 -06:00
borderRadius : '0.4rem' ,
padding : '0.6rem 0.75rem' ,
2026-05-14 15:24:10 -06:00
} }
>
2026-05-14 15:29:20 -06:00
< div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'baseline' , marginBottom : '0.3rem' } } >
< span style = { { fontFamily : 'monospace' , fontSize : '0.8rem' , fontWeight : '700' , color : '#E2E8F0' } } > { m . metric _id } < / s p a n >
2026-05-14 15:30:43 -06:00
< span style = { { fontSize : '0.55rem' , fontFamily : 'monospace' , color : pctColor } } > { ( pct * 100 ) . toFixed ( 0 ) } % < / s p a n >
2026-05-14 15:24:10 -06:00
< / d i v >
2026-05-14 15:30:43 -06:00
< div style = { { fontSize : '1rem' , fontWeight : '700' , color : '#EF4444' } } > { m . non _compliant . toLocaleString ( ) } < / d i v >
2026-05-14 15:29:20 -06:00
< / d i v >
2026-05-14 15:24:10 -06:00
) ;
} ) }
< / d i v >
< / d i v >
) ;
}
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
// ---------------------------------------------------------------------------
// 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 >
) ;
}
2026-05-15 17:08:55 -06:00
// ---------------------------------------------------------------------------
// Aggregated Burndown Chart
// ---------------------------------------------------------------------------
function AggregatedBurndownChart ( { data , loading , error } ) {
if ( loading ) {
return (
< div style = { { ... CARD _STYLE , marginBottom : '1.5rem' , textAlign : 'center' , padding : '2rem' } } >
< Loader style = { { animation : 'spin 1s linear infinite' , width : '20px' , height : '20px' , color : '#64748B' } } / >
< div style = { { fontSize : '0.75rem' , color : '#64748B' , marginTop : '0.5rem' } } > Loading ... < / d i v >
< / d i v >
) ;
}
if ( error ) {
return (
< div style = { { ... CARD _STYLE , marginBottom : '1.5rem' , padding : '1.25rem' } } >
< div style = { { fontSize : '0.75rem' , color : '#EF4444' , fontFamily : 'monospace' } } >
Error loading burndown data : { error }
< / d i v >
< / d i v >
) ;
}
if ( ! data ) return null ;
// Empty state: no non-compliant devices
if ( data . total _non _compliant === 0 ) {
return (
< div style = { { ... CARD _STYLE , marginBottom : '1.5rem' , textAlign : 'center' , padding : '2rem' } } >
< div style = { { fontSize : '0.8rem' , color : '#10B981' } } > No non - compliant devices across any vertical . < / d i v >
< / d i v >
) ;
}
// All blockers: no monthly forecast
const monthlyKeys = Object . keys ( data . monthly _forecast || { } ) ;
const hasMonthlyData = monthlyKeys . length > 0 ;
// Prepare chart data
const monthlyData = monthlyKeys
. sort ( )
. map ( month => ( { month , count : data . monthly _forecast [ month ] } ) ) ;
return (
< div style = { { ... CARD _STYLE , marginBottom : '1.5rem' } } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '1rem' } } >
Aggregated Burndown Forecast
< / d i v >
{ /* Summary header */ }
< div style = { { display : 'flex' , gap : '1.5rem' , marginBottom : '1.25rem' , flexWrap : 'wrap' } } >
< div >
< div style = { { fontSize : '0.6rem' , color : '#64748B' , textTransform : 'uppercase' } } > Non - Compliant < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#EF4444' } } > { data . total _non _compliant . toLocaleString ( ) } < / d i v >
< / d i v >
< div >
< div style = { { fontSize : '0.6rem' , color : '#64748B' , textTransform : 'uppercase' } } > Blockers < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#EF4444' } } > { data . blockers . toLocaleString ( ) } < / d i v >
< / d i v >
< div >
< div style = { { fontSize : '0.6rem' , color : '#64748B' , textTransform : 'uppercase' } } > In - Progress < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#F59E0B' } } > { data . with _dates . toLocaleString ( ) } < / d i v >
< / d i v >
{ data . projected _clear _date && (
< div >
< div style = { { fontSize : '0.6rem' , color : '#64748B' , textTransform : 'uppercase' } } > Projected Clear < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#10B981' } } > { data . projected _clear _date } < / d i v >
< / d i v >
) }
< / d i v >
{ /* Chart or blocker message */ }
{ hasMonthlyData ? (
< ResponsiveContainer width = "100%" height = { 200 } >
< BarChart data = { monthlyData } >
< 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 = "#A78BFA" fillOpacity = { 0.7 } name = "Projected Remediations" / >
< / B a r 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 = { { padding : '1.5rem' , textAlign : 'center' , color : '#F59E0B' , fontSize : '0.8rem' } } >
All { data . blockers . toLocaleString ( ) } non - compliant devices lack remediation dates .
< / d i v >
) }
{ /* Per-vertical contribution table */ }
{ data . by _vertical && data . by _vertical . length > 0 && (
< div style = { { marginTop : '1rem' } } >
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '0.5rem' } } >
By Vertical
< / d i v >
< table style = { { ... TABLE _STYLE , fontSize : '0.7rem' } } >
< thead >
< tr >
< th style = { { ... TH _STYLE , fontSize : '0.6rem' } } > Vertical < / t h >
< th style = { { ... TH _STYLE , fontSize : '0.6rem' , textAlign : 'right' } } > Total < / t h >
< th style = { { ... TH _STYLE , fontSize : '0.6rem' , textAlign : 'right' } } > Blockers < / t h >
< th style = { { ... TH _STYLE , fontSize : '0.6rem' , textAlign : 'right' } } > With Dates < / t h >
< / t r >
< / t h e a d >
< tbody >
{ data . by _vertical . map ( v => (
< tr key = { v . vertical } >
< td style = { { ... TD _STYLE , color : PURPLE , fontWeight : '600' , padding : '0.5rem 1rem' } } > { v . vertical } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , padding : '0.5rem 1rem' } } > { v . total . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : v . blockers > 0 ? '#EF4444' : '#64748B' , padding : '0.5rem 1rem' } } > { v . blockers . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#F59E0B' , padding : '0.5rem 1rem' } } > { v . with _dates . toLocaleString ( ) } < / t d >
< / t r >
) ) }
< / t b o d y >
< / t a b l e >
< / d i v >
) }
< / d i v >
) ;
}
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
// ---------------------------------------------------------------------------
// 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 ) ;
2026-05-14 12:27:46 -06:00
const [ teams , setTeams ] = useState ( [ ] ) ;
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
const [ burndown , setBurndown ] = useState ( null ) ;
const [ loading , setLoading ] = useState ( true ) ;
2026-05-14 12:27:46 -06:00
const [ expandedMetrics , setExpandedMetrics ] = useState ( new Set ( ) ) ;
const [ teamFilter , setTeamFilter ] = useState ( '' ) ; // '' = all teams (rollup view)
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 || [ ] ) ;
2026-05-14 12:27:46 -06:00
setTeams ( metricsData . teams || [ ] ) ;
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
setBurndown ( burndownData ) ;
setLoading ( false ) ;
} ) . catch ( ( ) => setLoading ( false ) ) ;
} , [ vertical ] ) ;
2026-05-14 12:27:46 -06:00
const toggleMetricExpand = ( metricId ) => {
setExpandedMetrics ( prev => {
const next = new Set ( prev ) ;
if ( next . has ( metricId ) ) next . delete ( metricId ) ;
else next . add ( metricId ) ;
return next ;
} ) ;
} ;
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
if ( loading ) return < div style = { { padding : '2rem' , textAlign : 'center' , color : '#64748B' } } > < Loader style = { { animation : 'spin 1s linear infinite' } } / > Loading ... < / d i v > ;
2026-05-14 12:27:46 -06:00
// Filter metrics by team if a team filter is active
const displayMetrics = teamFilter
? metrics . filter ( m => m . sub _teams && m . sub _teams . some ( st => st . team === teamFilter ) )
: metrics ;
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
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 >
) }
2026-05-14 12:27:46 -06:00
{ /* Team filter */ }
{ teams . length > 0 && (
< div style = { { ... CARD _STYLE , marginBottom : '1.5rem' , padding : '0.75rem 1.25rem' } } >
< div style = { { display : 'flex' , alignItems : 'center' , gap : '1rem' , flexWrap : 'wrap' } } >
< span style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' } } > Filter by Team : < / s p a n >
< button
onClick = { ( ) => setTeamFilter ( '' ) }
style = { {
padding : '0.3rem 0.7rem' ,
background : ! teamFilter ? PURPLE : 'transparent' ,
border : ` 1px solid ${ ! teamFilter ? PURPLE : 'rgba(255,255,255,0.15)' } ` ,
borderRadius : '0.375rem' ,
color : ! teamFilter ? '#FFF' : '#94A3B8' ,
fontSize : '0.7rem' ,
cursor : 'pointer' ,
fontWeight : ! teamFilter ? '600' : '400' ,
} }
>
All ( Rollup )
< / b u t t o n >
{ teams . map ( t => (
< button
key = { t }
onClick = { ( ) => setTeamFilter ( t === teamFilter ? '' : t ) }
style = { {
padding : '0.3rem 0.7rem' ,
background : teamFilter === t ? TEAL : 'transparent' ,
border : ` 1px solid ${ teamFilter === t ? TEAL : 'rgba(255,255,255,0.15)' } ` ,
borderRadius : '0.375rem' ,
color : teamFilter === t ? '#FFF' : '#94A3B8' ,
fontSize : '0.7rem' ,
cursor : 'pointer' ,
fontWeight : teamFilter === t ? '600' : '400' ,
} }
>
{ t }
< / b u t t o n >
) ) }
< / d i v >
< / d i v >
) }
{ /* Metrics table with expandable sub-team rows */ }
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
< div style = { CARD _STYLE } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '1rem' } } >
2026-05-14 12:27:46 -06:00
Metrics { teamFilter && < span style = { { color : TEAL , fontWeight : '600' } } > — { teamFilter } < / s p a n > }
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
< / d i v >
< table style = { TABLE _STYLE } >
< thead >
< tr >
2026-05-14 12:27:46 -06:00
< th style = { { ... TH _STYLE , width : '30px' } } > < / t h >
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
< th style = { TH _STYLE } > Metric < / t h >
< th style = { TH _STYLE } > Description < / t h >
< th style = { TH _STYLE } > Category < / t h >
2026-05-14 12:27:46 -06:00
< th style = { TH _STYLE } > { teamFilter ? 'Team' : '' } < / t h >
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
< 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 >
2026-05-14 12:27:46 -06:00
{ displayMetrics && displayMetrics . map ( ( m ) => {
const hasSubTeams = m . sub _teams && m . sub _teams . length > 0 ;
const isExpanded = expandedMetrics . has ( m . metric _id ) ;
// If team filter is active, show the filtered team's data instead of rollup
const displayRow = teamFilter && m . sub _teams
? m . sub _teams . find ( st => st . team === teamFilter ) || m
: m ;
const displayPct = Number ( displayRow . compliance _pct || 0 ) ;
const targetVal = Number ( m . target || 0 ) ;
const pctColor = displayPct >= targetVal ? '#10B981' : displayPct >= ( targetVal * 0.85 ) ? '#F59E0B' : '#EF4444' ;
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
return (
2026-05-14 12:27:46 -06:00
< React . Fragment key = { m . metric _id } >
{ /* Primary metric row */ }
< tr
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 , padding : '0.5rem' , textAlign : 'center' } } >
{ hasSubTeams && ! teamFilter && (
< button
onClick = { ( e ) => { e . stopPropagation ( ) ; toggleMetricExpand ( m . metric _id ) ; } }
style = { { background : 'none' , border : 'none' , color : '#64748B' , cursor : 'pointer' , fontSize : '0.75rem' , padding : '0.2rem' } }
title = { isExpanded ? 'Collapse sub-teams' : 'Expand sub-teams' }
>
{ isExpanded ? '▾' : '▸' }
< / b u t t o n >
) }
< / t d >
< td
style = { { ... TD _STYLE , fontWeight : '600' , color : PURPLE } }
2026-05-14 14:53:41 -06:00
onClick = { ( ) => onSelectMetric ( m . metric _id , m ) }
2026-05-14 12:27:46 -06:00
>
{ 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 , fontSize : '0.7rem' , color : TEAL } } > { teamFilter || '' } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#10B981' } } > { ( displayRow . compliant || 0 ) . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#EF4444' } } > { ( displayRow . non _compliant || 0 ) . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' } } > { ( displayRow . total || 0 ) . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , fontWeight : '700' , color : pctColor } } > { ( displayPct * 100 ) . toFixed ( 1 ) } % < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#64748B' } } > { ( targetVal * 100 ) . toFixed ( 0 ) } % < / t d >
< / t r >
{ /* Expanded sub-team rows */ }
{ isExpanded && ! teamFilter && hasSubTeams && m . sub _teams . map ( st => {
const stPct = Number ( st . compliance _pct || 0 ) ;
const stPctColor = stPct >= targetVal ? '#10B981' : stPct >= ( targetVal * 0.85 ) ? '#F59E0B' : '#EF4444' ;
return (
< tr key = { ` ${ m . metric _id } - ${ st . team } ` } style = { { background : 'rgba(20, 184, 166, 0.03)' } } >
< td style = { { ... TD _STYLE , padding : '0.4rem' } } > < / t d >
< td style = { { ... TD _STYLE , paddingLeft : '1.5rem' , fontSize : '0.7rem' , color : '#64748B' } } > └ < / t d >
< td style = { { ... TD _STYLE , fontSize : '0.7rem' , color : '#94A3B8' } } > < / t d >
< td style = { { ... TD _STYLE , fontSize : '0.7rem' } } > < / t d >
< td style = { { ... TD _STYLE , fontSize : '0.75rem' , color : TEAL , fontWeight : '500' } } > { st . team } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#10B981' , fontSize : '0.75rem' } } > { ( st . compliant || 0 ) . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#EF4444' , fontSize : '0.75rem' } } > { ( st . non _compliant || 0 ) . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , fontSize : '0.75rem' } } > { ( st . total || 0 ) . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , fontWeight : '600' , color : stPctColor , fontSize : '0.75rem' } } > { ( stPct * 100 ) . toFixed ( 1 ) } % < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#64748B' , fontSize : '0.75rem' } } > < / t d >
< / t r >
) ;
} ) }
< / R e a c t . F r a g m e n t >
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
) ;
} ) }
< / t b o d y >
< / t a b l e >
< / d i v >
< / d i v >
) ;
}
// ---------------------------------------------------------------------------
2026-05-14 14:53:41 -06:00
// Metric Sub-Team View (intermediate drill-down: metric → sub-teams)
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
// ---------------------------------------------------------------------------
2026-05-14 14:53:41 -06:00
function MetricSubTeamView ( { vertical , metricId , metricData , onBack , onSelectTeam } ) {
// metricData contains the metric's sub_teams from the parent view
const pctColor = ( pct , target ) => {
const p = Number ( pct || 0 ) ;
const t = Number ( target || 0 ) ;
return p >= t ? '#10B981' : p >= t * 0.85 ? '#F59E0B' : '#EF4444' ;
} ;
const targetVal = Number ( metricData ? . target || 0 ) ;
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 : '1.1rem' , color : '#E2E8F0' , marginBottom : '0.25rem' , fontWeight : '700' } } >
{ vertical } / Metric { metricId }
< / h 3 >
{ metricData ? . metric _desc && (
< p style = { { fontSize : '0.75rem' , color : '#94A3B8' , margin : '0 0 1.5rem 0' } } > { metricData . metric _desc } < / p >
) }
{ /* Metric rollup stats */ }
< 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' } } > Total < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#E2E8F0' } } > { ( metricData ? . total || 0 ) . toLocaleString ( ) } < / d i v >
< / d i v >
< div style = { STAT _CARD _STYLE } >
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , marginBottom : '0.5rem' } } > Compliant < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : '#10B981' } } > { ( metricData ? . compliant || 0 ) . toLocaleString ( ) } < / d i v >
< / d i v >
< 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' } } > { ( metricData ? . non _compliant || 0 ) . toLocaleString ( ) } < / d i v >
< / d i v >
< div style = { STAT _CARD _STYLE } >
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , marginBottom : '0.5rem' } } > Compliance < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : pctColor ( metricData ? . compliance _pct , metricData ? . target ) } } >
{ ( Number ( metricData ? . compliance _pct || 0 ) * 100 ) . toFixed ( 1 ) } %
< / d i v >
< / d i v >
< div style = { STAT _CARD _STYLE } >
< div style = { { fontSize : '0.65rem' , color : '#64748B' , textTransform : 'uppercase' , marginBottom : '0.5rem' } } > Target < / d i v >
< div style = { { fontSize : '1.3rem' , fontWeight : '700' , color : PURPLE } } > { ( targetVal * 100 ) . toFixed ( 0 ) } % < / d i v >
< / d i v >
< / d i v >
{ /* Sub-team breakdown table */ }
< div style = { CARD _STYLE } >
< div style = { { fontSize : '0.7rem' , color : '#94A3B8' , textTransform : 'uppercase' , letterSpacing : '0.05em' , marginBottom : '1rem' } } >
Sub - Team Breakdown
< / d i v >
{ metricData ? . sub _teams && metricData . sub _teams . length > 0 ? (
< table style = { TABLE _STYLE } >
< thead >
< tr >
< th style = { TH _STYLE } > Team < / 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' } } > Compliance % < / t h >
< / t r >
< / t h e a d >
< tbody >
{ metricData . sub _teams . map ( st => {
const stPct = Number ( st . compliance _pct || 0 ) ;
const stPctColor = pctColor ( st . compliance _pct , metricData . target ) ;
return (
< tr
key = { st . team }
onClick = { ( ) => onSelectTeam ( st . team ) }
style = { { cursor : 'pointer' , transition : 'background 0.15s' } }
onMouseEnter = { e => e . currentTarget . style . background = 'rgba(20, 184, 166, 0.08)' }
onMouseLeave = { e => e . currentTarget . style . background = 'transparent' }
>
< td style = { { ... TD _STYLE , fontWeight : '600' , color : TEAL } } > { st . team } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#10B981' } } > { ( st . compliant || 0 ) . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , color : '#EF4444' } } > { ( st . non _compliant || 0 ) . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' } } > { ( st . total || 0 ) . toLocaleString ( ) } < / t d >
< td style = { { ... TD _STYLE , textAlign : 'right' , fontWeight : '700' , color : stPctColor } } > { ( stPct * 100 ) . toFixed ( 1 ) } % < / t d >
< / t r >
) ;
} ) }
< / t b o d y >
< / t a b l e >
) : (
< div style = { { color : '#64748B' , fontSize : '0.8rem' , padding : '1rem' , textAlign : 'center' } } >
No sub - team breakdown available for this metric .
< div style = { { marginTop : '0.75rem' } } >
< button
onClick = { ( ) => onSelectTeam ( null ) }
style = { { padding : '0.5rem 1rem' , background : ` ${ PURPLE } 20 ` , border : ` 1px solid ${ PURPLE } 60 ` , borderRadius : '0.5rem' , color : PURPLE , fontSize : '0.75rem' , cursor : 'pointer' , fontWeight : '600' } }
>
View All Devices
< / b u t t o n >
< / d i v >
< / d i v >
) }
< / d i v >
< / d i v >
) ;
}
// ---------------------------------------------------------------------------
// Metric Device List (deepest drill-down — filtered by team)
// ---------------------------------------------------------------------------
function MetricDeviceList ( { vertical , metricId , team , onBack } ) {
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
const [ devices , setDevices ] = useState ( null ) ;
const [ loading , setLoading ] = useState ( true ) ;
2026-05-14 14:53:41 -06:00
// ⚠️ CONVENTION: Missing error state — .catch() below silently swallows errors without displaying them to the user. Add an error state and render an error message.
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 ) ;
2026-05-14 14:53:41 -06:00
let url = ` ${ API _BASE } /compliance/vcl-multi/vertical/ ${ encodeURIComponent ( vertical ) } /metric/ ${ encodeURIComponent ( metricId ) } /devices ` ;
if ( team ) url += ` ?team= ${ encodeURIComponent ( team ) } ` ;
fetch ( url , { credentials : 'include' } )
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
. then ( r => r . json ( ) )
. then ( data => { setDevices ( data . devices || [ ] ) ; setLoading ( false ) ; } )
. catch ( ( ) => setLoading ( false ) ) ;
2026-05-14 14:53:41 -06:00
} , [ vertical , metricId , team ] ) ;
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
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 } }
>
2026-05-14 14:53:41 -06:00
< ChevronLeft style = { { width : '16px' , height : '16px' } } / > Back to Sub - Teams
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
< / b u t t o n >
< h3 style = { { fontSize : '1rem' , color : '#E2E8F0' , marginBottom : '1rem' } } >
2026-05-14 14:53:41 -06:00
{ vertical } / Metric { metricId } { team ? ` / ${ team } ` : '' } — { devices ? devices . length : 0 } non - compliant devices
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
< / 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 >
) ;
}
2026-05-14 11:54:58 -06:00
// ---------------------------------------------------------------------------
// Data Management Panel
// ---------------------------------------------------------------------------
function DataManagementPanel ( { onClose , onDataChanged } ) {
const [ uploads , setUploads ] = useState ( [ ] ) ;
const [ loading , setLoading ] = useState ( true ) ;
const [ actionLoading , setActionLoading ] = useState ( null ) ;
const [ confirmAction , setConfirmAction ] = useState ( null ) ; // { type, label, action }
const [ message , setMessage ] = useState ( null ) ;
useEffect ( ( ) => {
fetchUploads ( ) ;
} , [ ] ) ;
const fetchUploads = ( ) => {
setLoading ( true ) ;
fetch ( ` ${ API _BASE } /compliance/vcl-multi/uploads ` , { credentials : 'include' } )
. then ( r => r . json ( ) )
. then ( data => { setUploads ( data . uploads || [ ] ) ; setLoading ( false ) ; } )
. catch ( ( ) => setLoading ( false ) ) ;
} ;
const handleDeleteVertical = async ( vertical ) => {
setActionLoading ( vertical ) ;
setMessage ( null ) ;
try {
const res = await fetch ( ` ${ API _BASE } /compliance/vcl-multi/vertical/ ${ encodeURIComponent ( vertical ) } ` , {
method : 'DELETE' , credentials : 'include' ,
} ) ;
const data = await res . json ( ) ;
if ( ! res . ok ) { setMessage ( { type : 'error' , text : data . error } ) ; }
else { setMessage ( { type : 'success' , text : data . message } ) ; fetchUploads ( ) ; onDataChanged ( ) ; }
} catch ( err ) { setMessage ( { type : 'error' , text : err . message } ) ; }
setActionLoading ( null ) ;
setConfirmAction ( null ) ;
} ;
const handleRollbackUpload = async ( uploadId ) => {
setActionLoading ( uploadId ) ;
setMessage ( null ) ;
try {
const res = await fetch ( ` ${ API _BASE } /compliance/vcl-multi/upload/ ${ uploadId } ` , {
method : 'DELETE' , credentials : 'include' ,
} ) ;
const data = await res . json ( ) ;
if ( ! res . ok ) { setMessage ( { type : 'error' , text : data . error } ) ; }
else { setMessage ( { type : 'success' , text : data . message } ) ; fetchUploads ( ) ; onDataChanged ( ) ; }
} catch ( err ) { setMessage ( { type : 'error' , text : err . message } ) ; }
setActionLoading ( null ) ;
setConfirmAction ( null ) ;
} ;
const handleDeleteAll = async ( ) => {
setActionLoading ( 'all' ) ;
setMessage ( null ) ;
try {
const res = await fetch ( ` ${ API _BASE } /compliance/vcl-multi/all ` , {
method : 'DELETE' , credentials : 'include' ,
} ) ;
const data = await res . json ( ) ;
if ( ! res . ok ) { setMessage ( { type : 'error' , text : data . error } ) ; }
else { setMessage ( { type : 'success' , text : data . message } ) ; fetchUploads ( ) ; onDataChanged ( ) ; }
} catch ( err ) { setMessage ( { type : 'error' , text : err . message } ) ; }
setActionLoading ( null ) ;
setConfirmAction ( null ) ;
} ;
// Group uploads by vertical
const verticalGroups = { } ;
for ( const u of uploads ) {
if ( ! verticalGroups [ u . vertical ] ) verticalGroups [ u . vertical ] = [ ] ;
verticalGroups [ u . vertical ] . push ( u ) ;
}
return (
< div style = { { position : 'fixed' , inset : 0 , background : 'rgba(0,0,0,0.7)' , backdropFilter : 'blur(4px)' , zIndex : 100 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' } } onClick = { onClose } >
< div style = { { background : 'linear-gradient(180deg, #0F1A2E 0%, #0A1628 100%)' , border : '1px solid rgba(239, 68, 68, 0.3)' , borderRadius : '1rem' , width : '90%' , maxWidth : '800px' , maxHeight : '80vh' , overflow : 'auto' , padding : '2rem' } } onClick = { e => e . stopPropagation ( ) } >
< div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' , marginBottom : '1.5rem' } } >
< h2 style = { { fontSize : '1.1rem' , fontWeight : '700' , color : '#E2E8F0' , margin : 0 } } > Manage Data < / h 2 >
{ /* ⚠️ CONVENTION: Use lucide-react <X /> icon instead of raw Unicode character */ }
< button onClick = { onClose } style = { { background : 'none' , border : 'none' , color : '#64748B' , cursor : 'pointer' } } > ✕ < / b u t t o n >
< / d i v >
{ /* Message */ }
{ message && (
< div style = { { marginBottom : '1rem' , padding : '0.75rem' , borderRadius : '0.5rem' , background : message . type === 'error' ? 'rgba(239,68,68,0.1)' : 'rgba(16,185,129,0.1)' , border : ` 1px solid ${ message . type === 'error' ? 'rgba(239,68,68,0.3)' : 'rgba(16,185,129,0.3)' } ` , fontSize : '0.75rem' , color : message . type === 'error' ? '#F87171' : '#6EE7B7' } } >
{ message . text }
< / d i v >
) }
{ /* Confirm dialog */ }
{ confirmAction && (
< div style = { { marginBottom : '1rem' , padding : '1rem' , borderRadius : '0.5rem' , background : 'rgba(239,68,68,0.1)' , border : '1px solid rgba(239,68,68,0.4)' } } >
< div style = { { fontSize : '0.8rem' , color : '#F87171' , marginBottom : '0.75rem' } } >
{ confirmAction . label }
< / d i v >
< div style = { { display : 'flex' , gap : '0.75rem' } } >
< button onClick = { ( ) => setConfirmAction ( null ) } style = { { padding : '0.4rem 1rem' , background : 'transparent' , border : '1px solid rgba(255,255,255,0.2)' , borderRadius : '0.375rem' , color : '#94A3B8' , fontSize : '0.75rem' , cursor : 'pointer' } } > Cancel < / b u t t o n >
< button onClick = { confirmAction . action } style = { { padding : '0.4rem 1rem' , background : '#EF4444' , border : 'none' , borderRadius : '0.375rem' , color : '#FFF' , fontSize : '0.75rem' , fontWeight : '600' , cursor : 'pointer' } } > Confirm Delete < / b u t t o n >
< / d i v >
< / d i v >
) }
{ /* Delete All button */ }
< div style = { { marginBottom : '1.5rem' , paddingBottom : '1rem' , borderBottom : '1px solid rgba(255,255,255,0.06)' } } >
< button
onClick = { ( ) => setConfirmAction ( { label : 'Delete ALL multi-vertical data? This cannot be undone.' , action : handleDeleteAll } ) }
disabled = { uploads . length === 0 || actionLoading }
style = { { display : 'flex' , alignItems : 'center' , gap : '0.5rem' , padding : '0.5rem 1rem' , background : 'rgba(239,68,68,0.1)' , border : '1px solid rgba(239,68,68,0.3)' , borderRadius : '0.5rem' , color : '#EF4444' , fontSize : '0.75rem' , cursor : uploads . length === 0 ? 'not-allowed' : 'pointer' , opacity : uploads . length === 0 ? 0.5 : 1 } }
>
< Trash2 style = { { width : '13px' , height : '13px' } } / >
Reset All Data
< / b u t t o n >
< / d i v >
{ loading && < div style = { { textAlign : 'center' , color : '#64748B' , fontSize : '0.8rem' , padding : '2rem' } } > Loading uploads ... < / d i v > }
{ ! loading && uploads . length === 0 && (
< div style = { { textAlign : 'center' , color : '#64748B' , fontSize : '0.8rem' , padding : '2rem' } } > No uploads yet . < / d i v >
) }
{ /* Per-vertical sections */ }
{ ! loading && Object . entries ( verticalGroups ) . map ( ( [ vertical , vUploads ] ) => (
< div key = { vertical } style = { { marginBottom : '1.5rem' } } >
< div style = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' , marginBottom : '0.5rem' } } >
< span style = { { fontSize : '0.8rem' , fontWeight : '600' , color : PURPLE } } > { vertical } < / s p a n >
< button
onClick = { ( ) => setConfirmAction ( { label : ` Delete all data for " ${ vertical } "? This removes all uploads and items for this vertical. ` , action : ( ) => handleDeleteVertical ( vertical ) } ) }
disabled = { actionLoading }
style = { { display : 'flex' , alignItems : 'center' , gap : '0.3rem' , padding : '0.3rem 0.6rem' , background : 'none' , border : '1px solid rgba(239,68,68,0.3)' , borderRadius : '0.375rem' , color : '#EF4444' , fontSize : '0.65rem' , cursor : 'pointer' } }
>
< Trash2 style = { { width : '11px' , height : '11px' } } / > Delete Vertical
< / b u t t o n >
< / d i v >
{ vUploads . slice ( 0 , 5 ) . map ( ( u , i ) => (
< div key = { u . id } style = { { display : 'flex' , alignItems : 'center' , gap : '0.75rem' , padding : '0.5rem 0.75rem' , background : 'rgba(15,23,42,0.5)' , borderRadius : '0.375rem' , marginBottom : '0.25rem' , fontSize : '0.7rem' } } >
< span style = { { color : '#94A3B8' , flex : 1 } } > { u . filename } < / s p a n >
< span style = { { color : '#64748B' } } > { u . report _date || '—' } < / s p a n >
< span style = { { color : '#64748B' , fontSize : '0.6rem' } } > + { u . new _count || 0 } / - { u . resolved _count || 0 } < / s p a n >
{ i === 0 && (
< button
onClick = { ( ) => setConfirmAction ( { label : ` Rollback " ${ u . filename } "? New items will be deleted and resolved items reactivated. ` , action : ( ) => handleRollbackUpload ( u . id ) } ) }
disabled = { actionLoading }
style = { { display : 'flex' , alignItems : 'center' , gap : '0.25rem' , padding : '0.2rem 0.5rem' , background : 'none' , border : '1px solid rgba(245,158,11,0.4)' , borderRadius : '0.25rem' , color : '#F59E0B' , fontSize : '0.6rem' , cursor : 'pointer' } }
>
< RotateCcw style = { { width : '10px' , height : '10px' } } / > Rollback
< / b u t t o n >
) }
< / d i v >
) ) }
{ vUploads . length > 5 && (
< div style = { { fontSize : '0.6rem' , color : '#64748B' , paddingLeft : '0.75rem' } } > ... and { vUploads . length - 5 } more < / d i v >
) }
< / d i v >
) ) }
< / d i v >
< / d i v >
) ;
}
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
// ---------------------------------------------------------------------------
// Main Page Component
// ---------------------------------------------------------------------------
export default function CCPMetricsPage ( ) {
const { isAdmin , isEditor } = useAuth ( ) ;
const [ stats , setStats ] = useState ( null ) ;
const [ trend , setTrend ] = useState ( null ) ;
2026-05-15 17:08:55 -06:00
const [ burndownData , setBurndownData ] = useState ( null ) ;
const [ burndownLoading , setBurndownLoading ] = useState ( true ) ;
const [ burndownError , setBurndownError ] = useState ( null ) ;
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
const [ loading , setLoading ] = useState ( true ) ;
const [ error , setError ] = useState ( null ) ;
const [ showUpload , setShowUpload ] = useState ( false ) ;
2026-05-14 11:54:58 -06:00
const [ showManage , setShowManage ] = useState ( false ) ;
2026-05-14 15:24:10 -06:00
const [ showMetricBreakdown , setShowMetricBreakdown ] = useState ( false ) ;
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
// Drill-down state
const [ selectedVertical , setSelectedVertical ] = useState ( null ) ;
const [ selectedMetric , setSelectedMetric ] = useState ( null ) ;
2026-05-14 14:53:41 -06:00
const [ selectedMetricData , setSelectedMetricData ] = useState ( null ) ;
const [ selectedTeam , setSelectedTeam ] = useState ( null ) ;
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
const fetchData = useCallback ( ( ) => {
setLoading ( true ) ;
setError ( null ) ;
2026-05-15 17:08:55 -06:00
setBurndownLoading ( true ) ;
setBurndownError ( null ) ;
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
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 ) ;
} ) ;
2026-05-15 17:08:55 -06:00
// Fetch burndown independently so a failure doesn't block the rest of the page
fetch ( ` ${ API _BASE } /compliance/vcl-multi/burndown ` , { credentials : 'include' } )
. then ( r => { if ( ! r . ok ) throw new Error ( 'Failed to load burndown' ) ; return r . json ( ) ; } )
. then ( data => { setBurndownData ( data ) ; setBurndownLoading ( false ) ; } )
. catch ( err => { setBurndownError ( err . message ) ; setBurndownLoading ( false ) ; } ) ;
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 ( ( ) => { fetchData ( ) ; } , [ fetchData ] ) ;
const handleUploadComplete = ( ) => {
setShowUpload ( false ) ;
fetchData ( ) ;
} ;
// Render drill-down views
2026-05-14 14:53:41 -06:00
if ( selectedTeam !== null && selectedMetric && selectedVertical ) {
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
return (
< div style = { PAGE _STYLE } >
< MetricDeviceList
vertical = { selectedVertical }
metricId = { selectedMetric }
2026-05-14 14:53:41 -06:00
team = { selectedTeam }
onBack = { ( ) => setSelectedTeam ( null ) }
/ >
< / d i v >
) ;
}
if ( selectedMetric && selectedVertical ) {
return (
< div style = { PAGE _STYLE } >
< MetricSubTeamView
vertical = { selectedVertical }
metricId = { selectedMetric }
metricData = { selectedMetricData }
onBack = { ( ) => { setSelectedMetric ( null ) ; setSelectedMetricData ( null ) ; } }
onSelectTeam = { ( team ) => setSelectedTeam ( team ) }
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
/ >
< / d i v >
) ;
}
if ( selectedVertical ) {
return (
< div style = { PAGE _STYLE } >
< VerticalDetailView
vertical = { selectedVertical }
onBack = { ( ) => setSelectedVertical ( null ) }
2026-05-14 14:53:41 -06:00
onSelectMetric = { ( metricId , metricData ) => { setSelectedMetric ( metricId ) ; setSelectedMetricData ( metricData ) ; } }
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
/ >
< / 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 ( ) ) && (
2026-05-14 11:54:58 -06:00
< div style = { { display : 'flex' , gap : '0.75rem' } } >
{ isAdmin ( ) && (
< button
onClick = { ( ) => setShowManage ( true ) }
style = { {
display : 'flex' , alignItems : 'center' , gap : '0.5rem' ,
padding : '0.6rem 1rem' ,
background : 'rgba(239, 68, 68, 0.1)' ,
border : '1px solid rgba(239, 68, 68, 0.3)' ,
borderRadius : '0.5rem' ,
color : '#EF4444' ,
fontSize : '0.75rem' ,
fontWeight : '600' ,
cursor : 'pointer' ,
transition : 'background 0.15s' ,
} }
onMouseEnter = { e => e . currentTarget . style . background = 'rgba(239, 68, 68, 0.2)' }
onMouseLeave = { e => e . currentTarget . style . background = 'rgba(239, 68, 68, 0.1)' }
>
< Settings style = { { width : '14px' , height : '14px' } } / >
Manage
< / b u t t o n >
) }
< 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 >
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
) }
< / 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 */ }
2026-05-14 15:24:10 -06:00
< StatsBar
stats = { stats . stats }
onNonCompliantClick = { ( ) => setShowMetricBreakdown ( ! showMetricBreakdown ) }
ncExpanded = { showMetricBreakdown }
/ >
{ /* Metric breakdown (revealed when Non-Compliant is clicked) */ }
{ showMetricBreakdown && (
2026-05-14 15:29:20 -06:00
< MetricBreakdownPanel metrics = { stats . metric _breakdown } / >
2026-05-14 15:24:10 -06:00
) }
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
{ /* 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 >
2026-05-15 17:08:55 -06:00
{ /* Aggregated burndown forecast */ }
< AggregatedBurndownChart
data = { burndownData }
loading = { burndownLoading }
error = { burndownError }
/ >
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
{ /* 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 }
/ >
) }
2026-05-14 11:54:58 -06:00
{ /* Data Management Panel */ }
{ showManage && (
< DataManagementPanel
onClose = { ( ) => setShowManage ( false ) }
onDataChanged = { fetchData }
/ >
) }
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
< / d i v >
) ;
}