import { useEffect, useMemo, useState } from 'react'; import { api } from '../api'; import { useI18n, type MsgKey } from '../i18n'; import type { Health, TrackDetail, TrackRun, TrackSummary } from '../types'; import { modelName } from './ModelSelect'; const LANG_NAME: Record = { python: 'Python', javascript: 'JavaScript', typescript: 'TypeScript', java: 'Java' }; /** Listedeki küçük eğilim çizgisi (mutasyon, yoksa kapsama). */ function Spark({ data }: { data: (number | null)[] }) { const v = data.filter((x): x is number => x != null); const w = 72, h = 22; if (v.length < 2) return ; const pts = v.map((y, i) => `${(i * (w - 4)) / (v.length - 1) + 2},${h - 2 - (y / 100) * (h - 4)}`).join(' '); const last = pts.split(' ').pop()!.split(','); return ( ); } /** Zaman içinde kapsama ve mutasyon: tek eksen (0–100 %), iki seri, üzerine gelince ipucu. */ function TrendChart({ runs, health }: { runs: TrackRun[]; health: Health | null }) { const { t, pct, date } = useI18n(); const [hover, setHover] = useState(null); const done = runs.filter((r) => r.status === 'done' && r.total); if (done.length < 2) return

{t('trk.chart.one')}

; const W = 720, H = 240, L = 40, R = 96, T = 14, B = 30; const x = (i: number) => L + (i * (W - L - R)) / (done.length - 1); const y = (v: number) => T + (1 - v / 100) * (H - T - B); const series = [ { key: 'coverage' as const, cls: 's1', label: t('trk.chart.cov') }, { key: 'mutation' as const, cls: 's2', label: t('trk.chart.mut') }, ]; const path = (k: 'coverage' | 'mutation') => { let d = ''; done.forEach((r, i) => { const v = r[k]; if (v == null) return; d += `${d && done[i - 1]?.[k] != null ? 'L' : 'M'}${x(i).toFixed(1)},${y(v).toFixed(1)}`; }); return d; }; const onMove = (e: React.MouseEvent) => { const rect = (e.currentTarget as SVGElement).getBoundingClientRect(); const px = ((e.clientX - rect.left) / rect.width) * W; let best = 0; done.forEach((_, i) => { if (Math.abs(x(i) - px) < Math.abs(x(best) - px)) best = i; }); setHover(best); }; const h = hover != null ? done[hover] : null; const lastIdx = done.length - 1; return (
{series.map((s) => {s.label})}
setHover(null)}> {[0, 25, 50, 75, 100].map((v) => ( {v} ))} {done.map((r, i) => (i === 0 || i === lastIdx || done.length <= 8 || i % Math.ceil(done.length / 8) === 0) && ( {i + 1} ))} {hover != null && } {series.map((s) => ( {done.map((r, i) => r[s.key] != null && ( ))} {done[lastIdx][s.key] != null && ( {pct(done[lastIdx][s.key], 0)} )} ))} {h && (
{t('trk.tip.run', { n: hover! + 1 })} · {date(h.created_at)}
{h.model ? modelName(h.model, health) : '—'} · {t(`trk.origin.${h.origin}` as MsgKey)}
{t('hist.tests')}: {h.passed}/{h.total} · {t('trk.chart.cov')}: {pct(h.coverage)} · {t('trk.chart.mut')}: {pct(h.mutation)}
)}
); } export default function TrackingPage({ health, initial }: { health: Health | null; initial?: string }) { const { t, pct, date, lang } = useI18n(); const [list, setList] = useState([]); const [loaded, setLoaded] = useState(false); const [sel, setSel] = useState(initial || null); const [detail, setDetail] = useState(null); const [q, setQ] = useState(''); const [fLang, setFLang] = useState(''); const [fSrc, setFSrc] = useState(''); const [sort, setSort] = useState<'recent' | 'runs' | 'name' | 'best'>('recent'); useEffect(() => { api.tracking().then((l) => { setList(l); setLoaded(true); setSel((s) => s || l[0]?.key || null); }).catch(() => setLoaded(true)); }, []); useEffect(() => { if (!sel) return; window.history.replaceState(null, '', `#/tracking/${sel}`); api.trackingDetail(sel).then(setDetail).catch(() => setDetail(null)); }, [sel]); const title = (g: { title: string; title_en: string }) => (lang === 'en' ? g.title_en || g.title : g.title); const shown = useMemo(() => { const f = list.filter((g) => (!fLang || g.language === fLang) && (!fSrc || g.source === fSrc) && (!q || `${title(g)} ${g.sample_id || ''}`.toLowerCase().includes(q.toLowerCase()))); const by: Record number> = { recent: (a, b) => b.last_at - a.last_at, runs: (a, b) => b.runs - a.runs || b.last_at - a.last_at, name: (a, b) => title(a).localeCompare(title(b), lang), best: (a, b) => (b.best?.mutation ?? -1) - (a.best?.mutation ?? -1), }; return [...f].sort(by[sort]); }, [list, q, fLang, fSrc, sort, lang]); // eslint-disable-line react-hooks/exhaustive-deps const dur = (s: number | null) => (s == null ? '—' : s < 120 ? `${Math.round(s)} ${lang === 'tr' ? 'sn' : 's'}` : `${(s / 60).toFixed(1)} ${lang === 'tr' ? 'dk' : 'min'}`); const d = detail && detail.key === sel ? detail : null; return (

{t('trk.title')}

{t('trk.lead')}

{loaded && !list.length ? (

{t('trk.emptyTitle')}

{t('trk.emptyText')}

) : (

{t('trk.codes')}

{shown.length}
setQ(e.target.value)} />
{shown.map((g) => ( ))}
{!d ? (

{t('trk.pick')}

) : (<>

{title(d)}

{LANG_NAME[d.language]} · {t(`kind.${d.kind}` as MsgKey)} · {t('trk.lines', { n: d.lines })} · {t(`trk.src.${d.source}`)} {d.sample_id && <> · {d.sample_id}}
{[ [String(d.stats.runs), t('trk.k.runs')], [`${d.stats.green}/${d.stats.runs}`, t('trk.k.green')], [pct(d.stats.best_coverage), t('trk.k.bestCov')], [pct(d.stats.best_mutation), t('trk.k.bestMut')], [dur(d.stats.avg_duration_s), t('trk.k.avgDur')], ].map(([v, k]) =>
{v}{k}
)}

{t('trk.chart')}

{t('trk.byModel')}

{d.models.map((m) => ( ))}
{t('trk.col.model')}{t('trk.col.runs')}{t('trk.col.green')} {t('trk.col.avgCov')}{t('trk.col.avgMut')}{t('trk.col.bestMut')} {t('trk.col.avgDur')}
{m.model === '—' ? '—' : modelName(m.model, health)} {m.runs}{m.green}{pct(m.avg_coverage)} {pct(m.avg_mutation)}{pct(m.best_mutation)} {dur(m.avg_duration_s)}

{t('trk.allRuns')}

{[...d.runs].reverse().map((r, i) => { const best = d.best?.job_id === r.job_id; return ( ); })}
#{t('trk.col.date')}{t('trk.col.origin')}{t('trk.col.model')} {t('auto.col.status')}{t('hist.tests')}{t('ev.col.cov')} {t('ev.col.mut')}{t('trk.col.rounds')}{t('trk.col.time')} {t('auto.col.reports')}
{d.runs.length - i}{best && ★} {date(r.created_at)} {t(`trk.origin.${r.origin}` as MsgKey)} {r.model ? modelName(r.model, health) : '—'} {t(`exp.status.${r.status === 'queued' ? 'pending' : r.status}` as MsgKey)} {r.total != null ? `${r.passed}/${r.total}` : '—'} {pct(r.coverage)} {r.mutation_before != null && r.mutation != null && r.mutation_before !== r.mutation ? `${pct(r.mutation_before, 0)} → ${pct(r.mutation, 0)}` : pct(r.mutation)} {r.iterations} {dur(r.duration_s)} HTML{' '} PDF
)}
)}
); }