diff --git a/frontend/src/components/pages/CCPMetricsPage.js b/frontend/src/components/pages/CCPMetricsPage.js index ae2705e..5687dc7 100644 --- a/frontend/src/components/pages/CCPMetricsPage.js +++ b/frontend/src/components/pages/CCPMetricsPage.js @@ -8,6 +8,26 @@ const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; const TEAL = '#14B8A6'; const PURPLE = '#A78BFA'; +// Natural sort comparator for metric IDs like "2.3.6i", "5.2.6", "10.1.1". +// Splits on "." and compares each segment numerically (trailing letters after digits sort after pure numbers). +function compareMetricIds(a, b) { + const partsA = a.split('.'); + const partsB = b.split('.'); + const len = Math.max(partsA.length, partsB.length); + for (let i = 0; i < len; i++) { + const segA = partsA[i] || ''; + const segB = partsB[i] || ''; + const numA = parseFloat(segA) || 0; + const numB = parseFloat(segB) || 0; + if (numA !== numB) return numA - numB; + // Same numeric prefix — compare the suffix (e.g. "6i" vs "6") + const suffA = segA.replace(/^[\d.]+/, ''); + const suffB = segB.replace(/^[\d.]+/, ''); + if (suffA !== suffB) return suffA.localeCompare(suffB); + } + return 0; +} + // --------------------------------------------------------------------------- // Styles // --------------------------------------------------------------------------- @@ -106,7 +126,8 @@ function MetricBreakdownPanel({ metrics }) { if (!metrics || metrics.length === 0) return null; // 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) + .sort((a, b) => compareMetricIds(a.metric_id, b.metric_id)); if (ncMetrics.length === 0) return null; const TOP_COUNT = 8; @@ -628,9 +649,10 @@ function VerticalDetailView({ vertical, onBack, onSelectMetric }) { if (loading) return