diff --git a/frontend/src/components/analytics/CoverageHeatmap.tsx b/frontend/src/components/analytics/CoverageHeatmap.tsx index be3e7d8d..a66560d1 100644 --- a/frontend/src/components/analytics/CoverageHeatmap.tsx +++ b/frontend/src/components/analytics/CoverageHeatmap.tsx @@ -18,6 +18,18 @@ function getCellStyle(value: number, thresholds: { green: number; amber: number return 'bg-rose-500/10 text-rose-500' } +function getCellTitle(value: number, label: string, thresholds: { green: number; amber: number }, inverse?: boolean): string { + const pct = (value * 100).toFixed(1) + if (inverse) { + if (value <= thresholds.green) return `${label}: ${pct}% (Good — below ${(thresholds.green * 100).toFixed(0)}%)` + if (value <= thresholds.amber) return `${label}: ${pct}% (Needs Improvement — below ${(thresholds.amber * 100).toFixed(0)}%)` + return `${label}: ${pct}% (Critical — above ${(thresholds.amber * 100).toFixed(0)}%)` + } + if (value >= thresholds.green) return `${label}: ${pct}% (Good — above ${(thresholds.green * 100).toFixed(0)}%)` + if (value >= thresholds.amber) return `${label}: ${pct}% (Needs Improvement — above ${(thresholds.amber * 100).toFixed(0)}%)` + return `${label}: ${pct}% (Critical — below ${(thresholds.amber * 100).toFixed(0)}%)` +} + function getFlowCountStyle(count: number) { if (count >= 5) return 'bg-emerald-400/10 text-emerald-400' if (count >= 1) return 'bg-amber-400/10 text-amber-400' @@ -91,7 +103,10 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) { Create Flow ) : ( - + = 5 ? 'Good' : row.flow_count >= 1 ? 'Needs Improvement' : 'Critical'}`} + > {row.flow_count} )} @@ -100,17 +115,26 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) { {row.session_count}
Performance and usage metrics for your troubleshooting flows
@@ -137,24 +146,31 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {| handleSort(col.key)}
- >
-
- {col.label}
- |
- ))}
+ {columns.map((col) => {
+ const isActive = sortCol === col.key
+ const SortIcon = isActive
+ ? sortDir === 'asc' ? ArrowUp : ArrowDown
+ : ArrowUpDown
+ return (
+ handleSort(col.key)}
+ title={col.title}
+ >
+
+ {col.label}
+ |
+ )
+ })}
{flow.name}
diff --git a/frontend/src/components/analytics/PsaMetricsPanel.tsx b/frontend/src/components/analytics/PsaMetricsPanel.tsx
index 28dd88d3..7cf098bb 100644
--- a/frontend/src/components/analytics/PsaMetricsPanel.tsx
+++ b/frontend/src/components/analytics/PsaMetricsPanel.tsx
@@ -34,19 +34,19 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
{/* Row 1 — Metric cards */}
{/* Tab bar */}
-
-
{data.total_time_entries} +Time Entries +{data.total_time_entries} logged to PSA -Time Entries
-
{data.total_hours_logged.toFixed(1)} +Hours Logged +{data.total_hours_logged.toFixed(1)} total hours tracked -Hours Logged
-
{data.avg_hours_per_session.toFixed(2)} +Avg Hours/Session +{data.avg_hours_per_session.toFixed(2)} per resolved session -Avg Hours/Session
+
@@ -498,6 +516,22 @@ function ConfidenceTierRow({
)
}
+function ErrorRetry({ label, onRetry }: { label: string; onRetry: () => void }) {
+ return (
+
{TABS.map((tab) => (
)}
{activeTab === 'quality' && (
- {qualityLoading ? (
+ {qualityError ? (
+
)}
{activeTab === 'psa' && (
-
- )}
+ ) : null}
No flow quality data available -
- {psaLoading ? (
+ {psaError ? (
+
)}
-
- )}
+ ) : null}
No PSA data available -
+
+ )
+}
+
function GapCard({ gap }: { gap: KnowledgeGap }) {
const severityStyle = SEVERITY_STYLES[gap.severity as keyof typeof SEVERITY_STYLES] ?? SEVERITY_STYLES.low
return (
Failed to load {label} + + |
|---|