/** * Sessions List Page (Story 7.1) * * Route: /sessions * Features: filter bar (agent dropdown, status multi-select), * sortable table, pagination for 50+ sessions. */ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import type { SessionStatus, Agent } from '@agentkitai/agentlens-core'; import { getSessions, getAgents } from '../api/client'; import type { SessionQueryResult } from '@agentkitai/agentlens-core'; import { useApi } from '../hooks/useApi'; import { SessionList } from '../components/SessionList'; import type { SortField, SortDir } from '../components/SessionList'; // ─── Constants ────────────────────────────────────────────────────── const PAGE_SIZE = 50; const STATUS_OPTIONS: SessionStatus[] = ['active', 'completed', 'error']; // ─── Component ────────────────────────────────────────────────────── export function Sessions(): React.ReactElement { const [searchParams] = useSearchParams(); // Filters — initialise agentFilter from URL ?agentId= for deep-linking const [agentFilter, setAgentFilter] = useState( () => searchParams.get('agentId') ?? '', ); const [statusFilters, setStatusFilters] = useState>(new Set()); // Sort const [sortField, setSortField] = useState('startedAt'); const [sortDir, setSortDir] = useState('desc'); // Pagination const [page, setPage] = useState(0); // Fetch agents for dropdown const { data: agents } = useApi(() => getAgents(), []); // Build query params — pass all selected statuses to server for filtering const queryAgentId = agentFilter || undefined; const statusArray = useMemo(() => [...statusFilters], [statusFilters]); const queryStatus = statusArray.length === 1 ? statusArray[0] : statusArray.length > 1 ? statusArray.join(',') : undefined; // Fetch sessions const { data, loading, error, refetch } = useApi( () => getSessions({ agentId: queryAgentId, status: queryStatus as SessionStatus | undefined, limit: PAGE_SIZE, offset: page * PAGE_SIZE, }), [queryAgentId, queryStatus, page], ); // Reset page when filters change useEffect(() => { setPage(0); }, [agentFilter, statusFilters]); // Client-side sort (server returns by startedAt desc; status filtering is server-side) const sortedSessions = useMemo(() => { if (!data?.sessions) return []; return [...data.sessions].sort((a, b) => { let cmp = 0; switch (sortField) { case 'agentName': cmp = (a.agentName ?? a.agentId).localeCompare(b.agentName ?? b.agentId); break; case 'status': cmp = a.status.localeCompare(b.status); break; case 'startedAt': cmp = new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime(); break; case 'duration': { const durA = (a.endedAt ? new Date(a.endedAt).getTime() : Date.now()) - new Date(a.startedAt).getTime(); const durB = (b.endedAt ? new Date(b.endedAt).getTime() : Date.now()) - new Date(b.startedAt).getTime(); cmp = durA - durB; break; } case 'eventCount': cmp = a.eventCount - b.eventCount; break; case 'errorCount': cmp = a.errorCount - b.errorCount; break; case 'cost': cmp = a.totalCostUsd - b.totalCostUsd; break; } return sortDir === 'asc' ? cmp : -cmp; }); }, [data?.sessions, sortField, sortDir]); // Sort handler const handleSort = useCallback( (field: SortField) => { if (field === sortField) { setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')); } else { setSortField(field); setSortDir('desc'); } }, [sortField], ); // Status toggle const toggleStatus = useCallback((status: SessionStatus) => { setStatusFilters((prev) => { const next = new Set(prev); if (next.has(status)) { next.delete(status); } else { next.add(status); } return next; }); }, []); const agentList: Agent[] = agents ?? []; return (
{/* Page header */}

Sessions

{/* Filter bar */}
{/* Agent dropdown */}
{/* Status multi-select */}
Status
{STATUS_OPTIONS.map((status) => { const active = statusFilters.has(status); return ( ); })}
{/* Content */} {error && (
Error loading sessions: {error}
)} {loading && !data ? (
) : (
)}
); }