import React, { useMemo, useState } from 'react'; import { Box, Typography, Chip, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TableSortLabel, Collapse, TextField, InputAdornment, Button, Tooltip, } from '@mui/material'; import { Search as SearchIcon, FileDownload as FileDownloadIcon, ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon, } from '@mui/icons-material'; import { AgentEventItem } from '../../hooks/useAgentHuddleFeed'; interface CommitteeProposal { agent: string; title: string; pillar_id: string; priority: string; valid: boolean; accepted: boolean; reasoning?: string; rejected_reason?: string | null; estimated_time?: number; action_type?: string; } interface CommitteePayload { agents_polled: number; total_proposals: number; accepted_count: number; rejected_count: number; proposals: CommitteeProposal[]; } const PILLAR_LABELS: Record = { plan: 'Plan', generate: 'Generate', publish: 'Publish', analyze: 'Analyze', engage: 'Engage', remarket: 'Remarket', }; type SortKey = 'agent' | 'title' | 'pillar_id' | 'priority' | 'valid' | 'accepted'; type SortDir = 'asc' | 'desc'; type FilterStatus = 'all' | 'accepted' | 'rejected' | 'invalid'; const sortProposals = (props: CommitteeProposal[], key: SortKey, dir: SortDir): CommitteeProposal[] => { return [...props].sort((a, b) => { const aVal = String(a[key] ?? ''); const bVal = String(b[key] ?? ''); const cmp = aVal.localeCompare(bVal); return dir === 'asc' ? cmp : -cmp; }); }; const CommitteeAuditTable: React.FC<{ events: AgentEventItem[] }> = ({ events }) => { const [sortKey, setSortKey] = useState('agent'); const [sortDir, setSortDir] = useState('asc'); const [search, setSearch] = useState(''); const [filterStatus, setFilterStatus] = useState('all'); const [filterAgent, setFilterAgent] = useState(null); const [expandedRow, setExpandedRow] = useState(null); const meeting = useMemo(() => { const last = events.find((e) => e.event_type === 'committee_meeting'); if (!last?.payload) return null; return (typeof last.payload === 'string' ? JSON.parse(last.payload) : last.payload) as CommitteePayload; }, [events]); const allAgents = useMemo(() => { if (!meeting) return []; return Array.from(new Set(meeting.proposals.map((p) => p.agent))); }, [meeting]); const filtered = useMemo(() => { if (!meeting) return []; let list = meeting.proposals; if (filterStatus === 'accepted') list = list.filter((p) => p.accepted); else if (filterStatus === 'rejected') list = list.filter((p) => !p.accepted); else if (filterStatus === 'invalid') list = list.filter((p) => !p.valid); if (filterAgent) list = list.filter((p) => p.agent === filterAgent); if (search.trim()) { const q = search.toLowerCase(); list = list.filter((p) => p.title.toLowerCase().includes(q) || p.agent.toLowerCase().includes(q)); } return sortProposals(list, sortKey, sortDir); }, [meeting, filterStatus, filterAgent, search, sortKey, sortDir]); const handleSort = (key: SortKey) => () => { if (sortKey === key) { setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')); } else { setSortKey(key); setSortDir('asc'); } }; const exportCsv = () => { if (!meeting) return; const headers = ['Agent', 'Title', 'Pillar', 'Priority', 'Valid', 'Accepted', 'Rejected Reason', 'Reasoning', 'Est. Time', 'Action Type']; const rows = meeting.proposals.map((p) => [ p.agent, `"${p.title.replace(/"/g, '""')}"`, p.pillar_id, p.priority, p.valid ? 'Yes' : 'No', p.accepted ? 'Yes' : 'No', p.rejected_reason ? `"${p.rejected_reason.replace(/"/g, '""')}"` : '', p.reasoning ? `"${p.reasoning.replace(/"/g, '""')}"` : '', p.estimated_time ?? '', p.action_type ?? '', ].join(',')); const csv = [headers.join(','), ...rows].join('\n'); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `committee_audit_${new Date().toISOString().slice(0, 10)}.csv`; a.click(); URL.revokeObjectURL(url); }; if (!meeting) return null; return ( {/* Header */} Committee Audit — {meeting.total_proposals} proposals {/* Filters */} setSearch(e.target.value)} InputProps={{ startAdornment: ( ), }} sx={{ minWidth: 200, '& .MuiOutlinedInput-root': { bgcolor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.8)', fontSize: 13, '& fieldset': { borderColor: 'rgba(255,255,255,0.12)' }, '&:hover fieldset': { borderColor: 'rgba(255,255,255,0.25)' }, '&.Mui-focused fieldset': { borderColor: 'rgba(102,126,234,0.5)' }, }, }} /> {(['all', 'accepted', 'rejected', 'invalid'] as FilterStatus[]).map((s) => ( setFilterStatus(s)} sx={{ height: 26, fontSize: 11, fontWeight: 600, textTransform: 'capitalize', bgcolor: filterStatus === s ? 'rgba(102,126,234,0.25)' : 'rgba(255,255,255,0.06)', color: filterStatus === s ? '#8b9cf7' : 'rgba(255,255,255,0.5)', border: `1px solid ${filterStatus === s ? 'rgba(102,126,234,0.4)' : 'transparent'}`, '&:hover': { bgcolor: filterStatus === s ? 'rgba(102,126,234,0.3)' : 'rgba(255,255,255,0.1)' }, }} /> ))} {allAgents.map((a) => ( setFilterAgent(filterAgent === a ? null : a)} sx={{ height: 26, fontSize: 11, fontWeight: 600, bgcolor: filterAgent === a ? 'rgba(102,126,234,0.25)' : 'rgba(255,255,255,0.06)', color: filterAgent === a ? '#8b9cf7' : 'rgba(255,255,255,0.5)', border: `1px solid ${filterAgent === a ? 'rgba(102,126,234,0.4)' : 'transparent'}`, '&:hover': { bgcolor: filterAgent === a ? 'rgba(102,126,234,0.3)' : 'rgba(255,255,255,0.1)' }, }} /> ))} {/* Table */} {([{ key: 'agent', label: 'Agent' }, { key: 'title', label: 'Title' }, { key: 'pillar_id', label: 'Pillar' }, { key: 'priority', label: 'Priority' }, { key: 'valid', label: 'Valid' }, { key: 'accepted', label: 'Accepted' }] as { key: SortKey; label: string }[]).map(({ key, label }) => ( {label} ))} Reason {filtered.map((p, i) => { const isExpanded = expandedRow === i; return ( setExpandedRow(isExpanded ? null : i)} sx={{ cursor: 'pointer', '&:hover': { bgcolor: 'rgba(255,255,255,0.04)' }, '& td': { borderBottom: '1px solid rgba(255,255,255,0.04)' }, opacity: p.accepted ? 1 : 0.55, }} > {p.agent} {p.title} {p.rejected_reason || (p.accepted ? '—' : 'Duplicate / lower priority')} {isExpanded ? : } Reasoning {p.reasoning || 'No reasoning provided.'} Est. Time {p.estimated_time ? `${p.estimated_time} min` : '—'} Action Type {p.action_type || '—'} ); })} {filtered.length === 0 && ( No proposals match current filters. )}
); }; export default CommitteeAuditTable;