fix(analytics): 4 UX polish fixes — resolution time column, empty states, type badges, select styling
- CoverageHeatmap: add Avg Resolution column with inverse color thresholds (green <10 min, amber 10–20 min, red >20 min); updated colSpan to 7 - FlowPilotAnalyticsPage: show positive empty state when knowledge gaps list is empty instead of rendering nothing - FlowQualityTable: add tree_type badge next to flow name (Troubleshooting/Project/Maintenance with distinct colors) - FlowPilotAnalyticsPage: add [&>option] Tailwind classes to period select for improved dark-theme contrast in Chrome/Firefox Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,10 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Guided %
|
Guided %
|
||||||
</th>
|
</th>
|
||||||
|
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground"
|
||||||
|
title="Average time to resolve sessions in this domain. Green: <10 min, Amber: 10–20 min, Red: >20 min. Lower is better.">
|
||||||
|
Avg Resolution
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -138,13 +142,32 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
{(row.guided_rate * 100).toFixed(1)}%
|
{(row.guided_rate * 100).toFixed(1)}%
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td className="px-3 py-2 text-sm text-right">
|
||||||
|
{row.avg_resolution_minutes == null ? (
|
||||||
|
<span className="text-muted-foreground">—</span>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'inline-block rounded px-1.5 py-0.5 text-xs font-medium',
|
||||||
|
row.avg_resolution_minutes < 10
|
||||||
|
? 'bg-emerald-400/10 text-emerald-400'
|
||||||
|
: row.avg_resolution_minutes <= 20
|
||||||
|
? 'bg-amber-400/10 text-amber-400'
|
||||||
|
: 'bg-rose-500/10 text-rose-500',
|
||||||
|
)}
|
||||||
|
title={`Avg resolution: ${row.avg_resolution_minutes.toFixed(1)} min — ${row.avg_resolution_minutes < 10 ? 'Good (<10 min)' : row.avg_resolution_minutes <= 20 ? 'Needs Improvement (10–20 min)' : 'Slow (>20 min)'}`}
|
||||||
|
>
|
||||||
|
{row.avg_resolution_minutes.toFixed(1)} min
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
{data.unmapped_session_count > 0 && (
|
{data.unmapped_session_count > 0 && (
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="px-3 py-2 text-xs text-muted-foreground">
|
<td colSpan={7} className="px-3 py-2 text-xs text-muted-foreground">
|
||||||
{data.unmapped_session_count} sessions had no domain classification
|
{data.unmapped_session_count} sessions had no domain classification
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ interface FlowQualityTableProps {
|
|||||||
type SortColumn = 'name' | 'usage_count' | 'success_rate' | 'last_matched_at' | 'avg_confidence' | 'quality_score'
|
type SortColumn = 'name' | 'usage_count' | 'success_rate' | 'last_matched_at' | 'avg_confidence' | 'quality_score'
|
||||||
type SortDir = 'asc' | 'desc'
|
type SortDir = 'asc' | 'desc'
|
||||||
|
|
||||||
|
const TYPE_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
troubleshooting: { label: 'Troubleshooting', color: 'text-primary' },
|
||||||
|
procedural: { label: 'Project', color: 'text-amber-400' },
|
||||||
|
maintenance: { label: 'Maintenance', color: 'text-blue-400' },
|
||||||
|
}
|
||||||
|
|
||||||
function formatRelativeTime(dateStr: string | null): string {
|
function formatRelativeTime(dateStr: string | null): string {
|
||||||
if (!dateStr) return 'Never'
|
if (!dateStr) return 'Never'
|
||||||
const diff = Date.now() - new Date(dateStr).getTime()
|
const diff = Date.now() - new Date(dateStr).getTime()
|
||||||
@@ -215,6 +221,9 @@ function FlowRow({
|
|||||||
>
|
>
|
||||||
{flow.name}
|
{flow.name}
|
||||||
</Link>
|
</Link>
|
||||||
|
<span className={cn('font-label text-[0.5rem] uppercase tracking-wider ml-2 shrink-0', TYPE_LABELS[flow.tree_type]?.color || 'text-muted-foreground')}>
|
||||||
|
{TYPE_LABELS[flow.tree_type]?.label || flow.tree_type}
|
||||||
|
</span>
|
||||||
{needsAttention && (
|
{needsAttention && (
|
||||||
<span className="shrink-0 bg-amber-400/10 text-amber-400 font-label text-[0.625rem] px-1.5 py-0.5 rounded">
|
<span className="shrink-0 bg-amber-400/10 text-amber-400 font-label text-[0.625rem] px-1.5 py-0.5 rounded">
|
||||||
Needs attention
|
Needs attention
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
<select
|
<select
|
||||||
value={period}
|
value={period}
|
||||||
onChange={(e) => setPeriod(e.target.value)}
|
onChange={(e) => setPeriod(e.target.value)}
|
||||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-primary/20 [&>option]:bg-[#1a1c21] [&>option]:text-foreground"
|
||||||
>
|
>
|
||||||
{PERIOD_OPTIONS.map((opt) => (
|
{PERIOD_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||||
@@ -390,7 +390,7 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Fourth row — Knowledge Gaps */}
|
{/* Fourth row — Knowledge Gaps */}
|
||||||
{gaps && gaps.gaps.length > 0 && (
|
{gaps && gaps.gaps.length > 0 ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2 px-1">
|
<div className="flex items-center gap-2 px-1">
|
||||||
<AlertTriangle size={14} className="text-amber-400" />
|
<AlertTriangle size={14} className="text-amber-400" />
|
||||||
@@ -404,6 +404,14 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : gaps && (
|
||||||
|
<div className="glass-card-static p-4 flex items-center gap-3">
|
||||||
|
<CheckCircle2 size={20} className="text-emerald-400 shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-foreground">No knowledge gaps detected</p>
|
||||||
|
<p className="text-xs text-muted-foreground">Your flow coverage looks healthy for this period.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user