/** * Benchmark List Page (Story 6.1) * * Route: /benchmarks * * Features: * - Table of benchmarks: Name, Status badge, Variants count, Sessions total, Created, Actions * - Status filter tabs: All, Draft, Running, Completed, Cancelled * - "New Benchmark" button → /benchmarks/new * - Row click → /benchmarks/:id * - Actions: View, Start (if draft), Cancel (if running), Delete (if draft/cancelled) * - Loading/empty states, pagination */ import React, { useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { useApi } from '../hooks/useApi'; import { getBenchmarks, updateBenchmarkStatus, deleteBenchmark as deleteBenchmarkApi, } from '../api/client'; import type { BenchmarkStatus, BenchmarkData } from '../api/client'; // ─── Constants ────────────────────────────────────────────────────── const PAGE_SIZE = 20; const STATUS_TABS: { label: string; value: BenchmarkStatus | 'all' }[] = [ { label: 'All', value: 'all' }, { label: 'Draft', value: 'draft' }, { label: 'Running', value: 'running' }, { label: 'Completed', value: 'completed' }, { label: 'Cancelled', value: 'cancelled' }, ]; // ─── Helpers ──────────────────────────────────────────────────────── function statusBadge(status: BenchmarkStatus): React.ReactElement { const styles: Record = { completed: 'bg-green-100 text-green-800', running: 'bg-blue-100 text-blue-800', draft: 'bg-gray-100 text-gray-600', cancelled: 'bg-red-100 text-red-800', }; return ( {status.charAt(0).toUpperCase() + status.slice(1)} ); } function formatDate(iso: string): string { try { return new Date(iso).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', }); } catch { return iso; } } // ─── Component ────────────────────────────────────────────────────── export function Benchmarks(): React.ReactElement { const navigate = useNavigate(); const [statusFilter, setStatusFilter] = useState('all'); const [page, setPage] = useState(0); const [actionLoading, setActionLoading] = useState(null); const { data, loading, error, refetch } = useApi( () => getBenchmarks({ status: statusFilter === 'all' ? undefined : statusFilter, limit: PAGE_SIZE, offset: page * PAGE_SIZE, }), [statusFilter, page], ); const handleTabChange = useCallback((tab: BenchmarkStatus | 'all') => { setStatusFilter(tab); setPage(0); }, []); const handleStart = useCallback( async (e: React.MouseEvent, id: string) => { e.stopPropagation(); setActionLoading(id); try { await updateBenchmarkStatus(id, 'running'); refetch(); } catch (err) { console.error('Failed to start benchmark:', err); } finally { setActionLoading(null); } }, [refetch], ); const handleCancel = useCallback( async (e: React.MouseEvent, id: string) => { e.stopPropagation(); setActionLoading(id); try { await updateBenchmarkStatus(id, 'cancelled'); refetch(); } catch (err) { console.error('Failed to cancel benchmark:', err); } finally { setActionLoading(null); } }, [refetch], ); const handleDelete = useCallback( async (e: React.MouseEvent, id: string) => { e.stopPropagation(); if (!confirm('Delete this benchmark? This cannot be undone.')) return; setActionLoading(id); try { await deleteBenchmarkApi(id); refetch(); } catch (err) { console.error('Failed to delete benchmark:', err); } finally { setActionLoading(null); } }, [refetch], ); const benchmarks = data?.benchmarks ?? []; const total = data?.total ?? 0; const hasMore = data?.hasMore ?? false; const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); return (
{/* Header */}

Benchmarks

Compare agent configurations with A/B testing

{/* Status filter tabs */}
{/* Error */} {error && (
Failed to load benchmarks: {error}
)} {/* Loading */} {loading && (
)} {/* Empty state */} {!loading && !error && benchmarks.length === 0 && (

No benchmarks

{statusFilter === 'all' ? 'Get started by creating your first A/B benchmark.' : `No ${statusFilter} benchmarks found.`}

{statusFilter === 'all' && ( )}
)} {/* Table */} {!loading && !error && benchmarks.length > 0 && ( <>
{benchmarks.map((b: BenchmarkData) => ( navigate(`/benchmarks/${b.id}`)} className="hover:bg-gray-50 cursor-pointer transition-colors" > ))}
Name Status Variants Sessions Created Actions
{b.name} {statusBadge(b.status)} {b.variants?.length ?? 0} {b.totalSessions ?? 0} {formatDate(b.createdAt)}
{b.status === 'draft' && ( )} {b.status === 'running' && ( )} {(b.status === 'draft' || b.status === 'cancelled') && ( )}
{/* Pagination */} {totalPages > 1 && (

Showing {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, total)} of {total}

)} )}
); }