'use client'; import { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import SportBadge from '@/components/vyndr/SportBadge'; import GradeBadge from '@/components/vyndr/GradeBadge'; import ArchetypeBadge from '@/components/vyndr/ArchetypeBadge'; import TeamLogo from '@/components/vyndr/TeamLogo'; import ModelRecord from '@/components/vyndr/ModelRecord'; import { playerHref } from '@/lib/playerHref'; import { useParlay, legKey } from '@/contexts/ParlayContext'; interface RosterProp { stat: string; line: number | string; side: string; grade: string } interface RosterPlayer { player: string; position: string | null; archetype: { primary: string } | null; stats: { k: string; v: string }[]; props: RosterProp[]; propCount: number; } interface TeamHubData { team: { name: string; abbr: string; sport: string }; roster: RosterPlayer[]; record?: { wins: number; losses: number }; note?: string | null; } type SortKey = 'archetype' | 'props' | 'name'; export default function TeamHub({ abbr, sport }: { abbr: string; sport: string }) { const router = useRouter(); const { addLeg, removeLeg, legs, hasLeg } = useParlay(); const [data, setData] = useState(null); const [state, setState] = useState<'loading' | 'ready' | 'error'>('loading'); const [sortBy, setSortBy] = useState('props'); const [archetypeFilter, setArchetypeFilter] = useState(null); useEffect(() => { let active = true; setState('loading'); fetch(`/api/team/${encodeURIComponent(abbr)}?sport=${encodeURIComponent(sport)}`) .then((r) => (r.ok ? r.json() : Promise.reject())) .then((d) => { if (active) { setData(d); setState('ready'); } }) .catch(() => { if (active) setState('error'); }); return () => { active = false; }; }, [abbr, sport]); const archetypesPresent = useMemo(() => { const set = new Set(); (data?.roster || []).forEach((p) => { if (p.archetype?.primary) set.add(p.archetype.primary); }); return [...set].sort(); }, [data]); const roster = useMemo(() => { let list = [...(data?.roster || [])]; if (archetypeFilter) list = list.filter((p) => p.archetype?.primary === archetypeFilter); list.sort((a, b) => { if (sortBy === 'name') return a.player.localeCompare(b.player); if (sortBy === 'props') return b.propCount - a.propCount || a.player.localeCompare(b.player); // archetype: named first (A-Z), unclassified last const aa = a.archetype?.primary || 'zzz'; const bb = b.archetype?.primary || 'zzz'; return aa.localeCompare(bb) || b.propCount - a.propCount; }); return list; }, [data, sortBy, archetypeFilter]); if (state === 'loading') { return

Loading team intelligence…

; } if (state === 'error' || !data) { return (

Team not found.

← Back to Slate
); } const SortBtn = ({ k, label }: { k: SortKey; label: string }) => ( ); const onPropClick = (p: RosterPlayer, pr: RosterProp) => { const sp = (data.team.sport || 'mlb').toUpperCase(); const leg = { sport: (sp === 'MLB' || sp === 'WNBA' ? sp : 'NBA') as 'NBA' | 'MLB' | 'WNBA', player: p.player, team: data.team.abbr, game: '', archetype: p.archetype?.primary, stat: String(pr.stat), line: Number(pr.line) || 0, direction: (String(pr.side).toUpperCase() === 'U' ? 'under' : 'over') as 'over' | 'under', grade: String(pr.grade || 'C'), confidence: 60, }; const k = legKey(leg); const existing = legs.find((l) => legKey(l) === k); if (existing) removeLeg(existing.id); else addLeg(leg); }; const propActive = (p: RosterPlayer, pr: RosterProp) => hasLeg(legKey({ player: p.player, stat: String(pr.stat), line: Number(pr.line) || 0, direction: String(pr.side).toUpperCase() === 'U' ? 'under' : 'over' })); return (
← Back to Slate {/* Header — DS0: the team's real crest leads the hub. */}

{data.team.name}

{data.team.abbr} {data.record && {data.record.wins}-{data.record.losses}}
{/* Session 60 (5.2, migration 020) — VYNDR-on-team: the model's settled record on THIS team's players. Deferred-render until rows exist. */}
{data.note &&

{data.note}

} {/* Controls */}
SORT {archetypesPresent.length > 0 && } {archetypesPresent.map((a) => ( ))}
{/* Roster */}
{roster.map((p, i) => { const noProps = p.propCount === 0; return (
{p.archetype && } {p.player} {p.position && {p.position}}
{p.stats.length > 0 && (
{p.stats.map((s, j) => ( {j > 0 && ·}{s.v} {s.k} ))}
)} {noProps ? (
No active props
) : (
{p.props.map((pr, j) => { const active = propActive(p, pr); return ( {pr.stat} {pr.side}{pr.line} ); })}
)}
); })} {roster.length === 0 &&

No players match this filter.

}
); }