Clean up metric breakdown panel — compact grid, top 8 with show-all toggle
Replaced the large flex-wrap button cards with a tight CSS grid of compact cells (130px min). Each cell shows metric ID, current %, and NC count only. Category text and target removed to reduce noise. Capped to top 8 metrics by default with a 'Show all N' toggle for the rest. Removes visual clutter while keeping the data accessible.
This commit is contained in:
@@ -100,55 +100,55 @@ function StatsBar({ stats, onNonCompliantClick, ncExpanded }) {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Metric Breakdown Panel (shown when Non-Compliant is clicked)
|
// 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;
|
if (!metrics || metrics.length === 0) return null;
|
||||||
|
|
||||||
// Only show metrics with non_compliant > 0
|
// Only show metrics with non_compliant > 0
|
||||||
const ncMetrics = metrics.filter(m => m.non_compliant > 0);
|
const ncMetrics = metrics.filter(m => m.non_compliant > 0);
|
||||||
if (ncMetrics.length === 0) return null;
|
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 (
|
return (
|
||||||
<div style={{ ...CARD_STYLE, marginBottom: '1.5rem' }}>
|
<div style={{ ...CARD_STYLE, marginBottom: '1.5rem' }}>
|
||||||
<div style={{ fontSize: '0.7rem', color: '#94A3B8', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '1rem' }}>
|
<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
|
Non-Compliant by Metric
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '0.625rem', flexWrap: 'wrap' }}>
|
{hasMore && (
|
||||||
{ncMetrics.map(m => {
|
<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: '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 pct = m.total > 0 ? (m.compliant / m.total) : 0;
|
||||||
const target = Number(m.target || 0);
|
const target = Number(m.target || 0);
|
||||||
const color = pct >= target ? '#10B981' : pct >= target * 0.85 ? '#F59E0B' : '#EF4444';
|
const color = pct >= target ? '#10B981' : pct >= target * 0.85 ? '#F59E0B' : '#EF4444';
|
||||||
return (
|
return (
|
||||||
<button
|
<div
|
||||||
key={m.metric_id}
|
key={m.metric_id}
|
||||||
onClick={() => onSelectMetric(m.metric_id)}
|
|
||||||
style={{
|
style={{
|
||||||
background: 'linear-gradient(135deg, rgba(30,41,59,0.95) 0%, rgba(15,23,42,0.98) 100%)',
|
background: 'rgba(15, 23, 42, 0.7)',
|
||||||
border: `1.5px solid ${color}40`,
|
border: `1px solid ${color}30`,
|
||||||
borderRadius: '0.5rem',
|
borderRadius: '0.4rem',
|
||||||
padding: '0.75rem 1rem',
|
padding: '0.6rem 0.75rem',
|
||||||
cursor: 'pointer',
|
|
||||||
textAlign: 'left',
|
|
||||||
transition: 'all 0.15s',
|
|
||||||
minWidth: '140px',
|
|
||||||
flex: '1 1 0',
|
|
||||||
maxWidth: '200px',
|
|
||||||
}}
|
}}
|
||||||
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' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '0.3rem' }}>
|
||||||
{m.metric_id}
|
<span style={{ fontFamily: 'monospace', fontSize: '0.8rem', fontWeight: '700', color: '#E2E8F0' }}>{m.metric_id}</span>
|
||||||
|
<span style={{ fontSize: '0.55rem', color: '#475569', fontFamily: 'monospace' }}>{(pct * 100).toFixed(0)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '0.6rem', color: '#475569', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '0.5rem' }}>
|
<div style={{ fontSize: '1rem', fontWeight: '700', color }}>{m.non_compliant.toLocaleString()}</div>
|
||||||
{m.category}
|
|
||||||
</div>
|
</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>
|
</div>
|
||||||
@@ -801,7 +801,6 @@ function DataManagementPanel({ onClose, onDataChanged }) {
|
|||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}>
|
<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>
|
<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 */}
|
||||||
{/* ⚠️ CONVENTION: Use lucide-react <X /> icon instead of raw Unicode character */}
|
|
||||||
<button onClick={onClose} style={{ background: 'none', border: 'none', color: '#64748B', cursor: 'pointer' }}>✕</button>
|
<button onClick={onClose} style={{ background: 'none', border: 'none', color: '#64748B', cursor: 'pointer' }}>✕</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1052,14 +1051,7 @@ export default function CCPMetricsPage() {
|
|||||||
|
|
||||||
{/* Metric breakdown (revealed when Non-Compliant is clicked) */}
|
{/* Metric breakdown (revealed when Non-Compliant is clicked) */}
|
||||||
{showMetricBreakdown && (
|
{showMetricBreakdown && (
|
||||||
<MetricBreakdownPanel
|
<MetricBreakdownPanel metrics={stats.metric_breakdown} />
|
||||||
metrics={stats.metric_breakdown}
|
|
||||||
onSelectMetric={(metricId) => {
|
|
||||||
// Find the first vertical that has this metric with non-compliant > 0
|
|
||||||
// and navigate to it
|
|
||||||
setShowMetricBreakdown(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Charts row */}
|
{/* Charts row */}
|
||||||
|
|||||||
Reference in New Issue
Block a user