import { useEffect, useState } from 'react'; import { useAI } from '../hooks/useAI'; import { markdownToHtml } from '../lib/markdown'; import type { Issue, PageScanSummary } from '../lib/types'; function scoreColor(s: number) { return s >= 90 ? '#22c55e' : s >= 75 ? '#84cc16' : s >= 55 ? '#f59e0b' : s >= 35 ? '#f97316' : '#ef4444'; } interface Props { scan: PageScanSummary | null; onClose: () => void; } export default function ScanSlideIn({ scan, onClose }: Props) { const { run, hasProvider } = useAI(); const [filter, setFilter] = useState<'all' | 'critical' | 'warning' | 'notice'>('all'); const [detailIssue, setDetailIssue] = useState(null); const [visible, setVisible] = useState(false); const [aiOutput, setAiOutput] = useState(null); const [aiError, setAiError] = useState(null); const [activeAction, setActiveAction] = useState(null); const [openNodeIdx, setOpenNodeIdx] = useState(0); // Animate in when scan arrives; reset state when scan changes useEffect(() => { if (scan) { setFilter('all'); setDetailIssue(null); requestAnimationFrame(() => setVisible(true)); } else { setVisible(false); } }, [scan]); // Reset AI + accordion when switching issues useEffect(() => { setAiOutput(null); setAiError(null); setActiveAction(null); setOpenNodeIdx(0); }, [detailIssue?.id]); // Escape key: close detail → then close panel useEffect(() => { if (!scan) return; const handler = (e: KeyboardEvent) => { if (e.key !== 'Escape') return; if (detailIssue) setDetailIssue(null); else onClose(); }; document.addEventListener('keydown', handler); return () => document.removeEventListener('keydown', handler); }, [scan, onClose, detailIssue]); const callAI = async (task: string) => { if (!detailIssue) return; setAiOutput(null); setAiError(null); setActiveAction(task); try { const result = await run.mutateAsync({ task, params: { title: detailIssue.title, wcag: detailIssue.wcag ?? '', html: detailIssue.element ?? '', context: detailIssue.description, }, }); if (result.error) setAiError(result.error); else setAiOutput(result.text ?? ''); } catch (e) { setAiError((e as Error).message); } finally { setActiveAction(null); } }; if (!scan) return null; const issues = scan.issues ?? []; const filtered = filter === 'all' ? issues : issues.filter((i) => i.severity === filter); const critical = issues.filter((i) => i.severity === 'critical').length; const warning = issues.filter((i) => i.severity === 'warning').length; const notice = issues.filter((i) => i.severity === 'notice').length; const color = scoreColor(scan.score); const showDetail = detailIssue !== null; const sevColor = detailIssue ? detailIssue.severity === 'critical' ? '#ef4444' : detailIssue.severity === 'warning' ? '#f59e0b' : '#3b82f6' : '#3b82f6'; return ( <> {/* Backdrop */}