Compare commits
2 Commits
7577ab1219
...
b808d0e38e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b808d0e38e
|
||
|
|
a72300475b
|
@@ -100,55 +100,55 @@ function StatsBar({ stats, onNonCompliantClick, ncExpanded }) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Metric Breakdown Panel (shown when Non-Compliant is clicked)
|
||||
// ---------------------------------------------------------------------------
|
||||
function MetricBreakdownPanel({ metrics, onSelectMetric }) {
|
||||
function MetricBreakdownPanel({ metrics }) {
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
|
||||
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;
|
||||
|
||||
const TOP_COUNT = 8;
|
||||
const displayMetrics = showAll ? ncMetrics : ncMetrics.slice(0, TOP_COUNT);
|
||||
const hasMore = ncMetrics.length > TOP_COUNT;
|
||||
|
||||
return (
|
||||
<div style={{ ...CARD_STYLE, marginBottom: '1.5rem' }}>
|
||||
<div style={{ fontSize: '0.7rem', color: '#94A3B8', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '1rem' }}>
|
||||
Non-Compliant by Metric
|
||||
<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
|
||||
</div>
|
||||
{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}`}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '0.625rem', flexWrap: 'wrap' }}>
|
||||
{ncMetrics.map(m => {
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(130px, 1fr))', gap: '0.5rem' }}>
|
||||
{displayMetrics.map(m => {
|
||||
const pct = m.total > 0 ? (m.compliant / m.total) : 0;
|
||||
const target = Number(m.target || 0);
|
||||
const color = pct >= target ? '#10B981' : pct >= target * 0.85 ? '#F59E0B' : '#EF4444';
|
||||
const pctColor = pct >= target ? '#10B981' : pct >= target * 0.85 ? '#F59E0B' : '#EF4444';
|
||||
return (
|
||||
<button
|
||||
<div
|
||||
key={m.metric_id}
|
||||
onClick={() => onSelectMetric(m.metric_id)}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%)',
|
||||
border: `1.5px solid ${color}40`,
|
||||
borderRadius: '0.5rem',
|
||||
padding: '0.75rem 1rem',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
transition: 'all 0.15s',
|
||||
minWidth: '140px',
|
||||
flex: '1 1 0',
|
||||
maxWidth: '200px',
|
||||
background: 'rgba(15, 23, 42, 0.7)',
|
||||
border: `1px solid ${pctColor}30`,
|
||||
borderRadius: '0.4rem',
|
||||
padding: '0.6rem 0.75rem',
|
||||
}}
|
||||
onMouseEnter={e => { e.currentTarget.style.borderColor = color + '80'; e.currentTarget.style.background = `${color}10`; }}
|
||||
onMouseLeave={e => { e.currentTarget.style.borderColor = color + '40'; e.currentTarget.style.background = 'linear-gradient(135deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%)'; }}
|
||||
>
|
||||
<div style={{ fontFamily: 'monospace', fontSize: '0.9rem', fontWeight: '700', color: '#E2E8F0', marginBottom: '0.2rem' }}>
|
||||
{m.metric_id}
|
||||
<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}</span>
|
||||
<span style={{ fontSize: '0.55rem', fontFamily: 'monospace', color: pctColor }}>{(pct * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '0.6rem', color: '#475569', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '0.5rem' }}>
|
||||
{m.category}
|
||||
</div>
|
||||
<div style={{ fontSize: '1.1rem', fontWeight: '700', color: '#EF4444', marginBottom: '0.2rem' }}>
|
||||
{m.non_compliant.toLocaleString()}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.6rem', color: '#64748B', fontFamily: 'monospace' }}>
|
||||
{(pct * 100).toFixed(0)}% / target {(target * 100).toFixed(0)}%
|
||||
</div>
|
||||
</button>
|
||||
<div style={{ fontSize: '1rem', fontWeight: '700', color: '#EF4444' }}>{m.non_compliant.toLocaleString()}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -801,7 +801,6 @@ function DataManagementPanel({ onClose, onDataChanged }) {
|
||||
<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</h2>
|
||||
{/* ⚠️ CONVENTION: Use lucide-react <X /> icon instead of raw Unicode character */}
|
||||
{/* ⚠️ CONVENTION: Use lucide-react <X /> icon instead of raw Unicode character */}
|
||||
<button onClick={onClose} style={{ background: 'none', border: 'none', color: '#64748B', cursor: 'pointer' }}>✕</button>
|
||||
</div>
|
||||
|
||||
@@ -1052,14 +1051,7 @@ export default function CCPMetricsPage() {
|
||||
|
||||
{/* Metric breakdown (revealed when Non-Compliant is clicked) */}
|
||||
{showMetricBreakdown && (
|
||||
<MetricBreakdownPanel
|
||||
metrics={stats.metric_breakdown}
|
||||
onSelectMetric={(metricId) => {
|
||||
// Find the first vertical that has this metric with non-compliant > 0
|
||||
// and navigate to it
|
||||
setShowMetricBreakdown(false);
|
||||
}}
|
||||
/>
|
||||
<MetricBreakdownPanel metrics={stats.metric_breakdown} />
|
||||
)}
|
||||
|
||||
{/* Charts row */}
|
||||
|
||||
Reference in New Issue
Block a user