import { useState, useMemo } from 'react' import { Link } from 'react-router-dom' import { ArrowUpDown, ArrowUp, ArrowDown, Info } from 'lucide-react' import { cn } from '@/lib/utils' import { getTreeEditorPath } from '@/lib/routing' import type { FlowQualityResponse, FlowQualityRow } from '@/types/flowpilot-analytics' interface FlowQualityTableProps { data: FlowQualityResponse } type SortColumn = 'name' | 'usage_count' | 'success_rate' | 'last_matched_at' | 'avg_confidence' | 'quality_score' type SortDir = 'asc' | 'desc' const TYPE_LABELS: Record = { 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 { if (!dateStr) return 'Never' const diff = Date.now() - new Date(dateStr).getTime() const minutes = Math.floor(diff / 60000) if (minutes < 1) return 'Just now' if (minutes < 60) return `${minutes}m ago` const hours = Math.floor(minutes / 60) if (hours < 24) return `${hours}h ago` const days = Math.floor(hours / 24) if (days < 30) return `${days}d ago` const months = Math.floor(days / 30) return `${months}mo ago` } function rateColor(value: number | null): string { if (value === null) return 'text-muted-foreground' if (value > 75) return 'text-emerald-400' if (value >= 50) return 'text-amber-400' return 'text-rose-500' } function barColor(score: number): string { if (score > 0.7) return 'bg-emerald-400' if (score >= 0.4) return 'bg-amber-400' return 'bg-rose-500' } export default function FlowQualityTable({ data }: FlowQualityTableProps) { const [sortCol, setSortCol] = useState('quality_score') const [sortDir, setSortDir] = useState('desc') const topPerformerIds = useMemo( () => new Set(data.top_performers.map((f) => f.flow_id)), [data.top_performers], ) const needsAttentionIds = useMemo( () => new Set(data.needs_attention.map((f) => f.flow_id)), [data.needs_attention], ) const sorted = useMemo(() => { const rows = [...data.flows] rows.sort((a, b) => { let aVal: number | string let bVal: number | string switch (sortCol) { case 'name': aVal = a.name.toLowerCase() bVal = b.name.toLowerCase() break case 'usage_count': aVal = a.usage_count bVal = b.usage_count break case 'success_rate': aVal = a.success_rate ?? -1 bVal = b.success_rate ?? -1 break case 'last_matched_at': aVal = a.last_matched_at ? new Date(a.last_matched_at).getTime() : 0 bVal = b.last_matched_at ? new Date(b.last_matched_at).getTime() : 0 break case 'avg_confidence': aVal = a.avg_confidence ?? -1 bVal = b.avg_confidence ?? -1 break case 'quality_score': default: aVal = a.quality_score bVal = b.quality_score break } if (aVal < bVal) return sortDir === 'asc' ? -1 : 1 if (aVal > bVal) return sortDir === 'asc' ? 1 : -1 return 0 }) return rows }, [data.flows, sortCol, sortDir]) function handleSort(col: SortColumn) { if (sortCol === col) { setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')) } else { setSortCol(col) setSortDir('desc') } } if (data.flows.length === 0) { return (

No flows found for this account. Create your first flow to start tracking quality.

) } const columns: { key: SortColumn; label: string; title?: string }[] = [ { key: 'name', label: 'Flow Name' }, { key: 'usage_count', label: 'Usage' }, { key: 'success_rate', label: 'Success Rate' }, { key: 'last_matched_at', label: 'Last Used' }, { key: 'avg_confidence', label: 'Avg Confidence' }, { key: 'quality_score', label: 'Quality Score', title: 'Combines success rate (50%), AI confidence (30%), and recent usage (20%)' }, ] return (

Flow Quality Scores

Quality score combines success rate (50%), AI confidence level (30%), and recent usage (20%). Higher is better.

Performance and usage metrics for your troubleshooting flows

{columns.map((col) => { const isActive = sortCol === col.key const SortIcon = isActive ? sortDir === 'asc' ? ArrowUp : ArrowDown : ArrowUpDown return ( ) })} {sorted.map((flow) => ( ))}
handleSort(col.key)} title={col.title} > {col.label}
) } function FlowRow({ flow, isTopPerformer, needsAttention, }: { flow: FlowQualityRow isTopPerformer: boolean needsAttention: boolean }) { return ( {/* Flow Name */}
{flow.name} {TYPE_LABELS[flow.tree_type]?.label || flow.tree_type} {needsAttention && ( Needs attention )}
{/* Usage */} {flow.usage_count} {/* Success Rate */} {flow.success_rate !== null ? `${flow.success_rate.toFixed(1)}%` : '--'} {/* Last Used */} {formatRelativeTime(flow.last_matched_at)} {/* Avg Confidence */} {flow.avg_confidence !== null ? `${flow.avg_confidence.toFixed(1)}%` : '--'} {/* Quality Score */}
{(flow.quality_score * 100).toFixed(0)}
) }