Session 51: Complete Team Hub (2234 tests)
Research-depth team view: /team/[abbr] with roster, archetypes, stats, props.
- Team API: mlbStatsAdapter.getTeams/resolveTeam/getTeamRoster (statsapi, abbr→id
+ active roster, cached). teamService.getTeamHub assembles roster → per-player
season stats (bounded concurrency) + archetype (snapshot grade or classify) +
tonight's graded props from grades:{sport}; whole hub cached 15min. MLB real;
NBA/WNBA graceful snapshot roster. GET /api/team/:abbr (404 unknown) + proxy.
- Team Hub page: server page.tsx (generateMetadata) + TeamHub client — header,
sort (archetype/graded/A-Z), archetype filter chips, roster rows (archetype +
player link + position + stats + graded props + parlay "+"), "No active props"
greyed state, loading/error.
- Game cards: team abbreviations are now TeamLinks → /team/:abbr?sport= (green
hover, stops propagation). Team Hub has "← Back to Slate".
Backend 2215 -> 2234 tests (+19), 190 suites. Web build clean (exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,24 @@ interface GameCardProps {
|
||||
preferredBooks?: string[];
|
||||
}
|
||||
|
||||
/** Clickable team abbreviation → /team/:abbr (Session 51). Stops propagation so
|
||||
* it doesn't trigger the card's open-game handler; green underline on hover. */
|
||||
function TeamLink({ abbr, sport }: { abbr: string; sport: string }) {
|
||||
if (!abbr) return <span>—</span>;
|
||||
return (
|
||||
<a
|
||||
href={`/team/${encodeURIComponent(abbr)}?sport=${encodeURIComponent(sport || 'mlb')}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{ color: '#fff', textDecoration: 'none', borderBottom: '1px solid transparent' }}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.color = 'var(--g-a)'; e.currentTarget.style.borderBottomColor = 'var(--g-a)'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.color = '#fff'; e.currentTarget.style.borderBottomColor = 'transparent'; }}
|
||||
title={`${abbr} team hub`}
|
||||
>
|
||||
{abbr}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
/** A book-line cell with the Bloomberg pattern: best = green tint + green left
|
||||
* border, worst = subtle red. The #1 visual upgrade (§13). */
|
||||
function LineCell({ value, best, worst }: { value: string; best?: boolean; worst?: boolean }) {
|
||||
@@ -148,7 +166,7 @@ export default function GameCard({ game: g, onAddParlay, onOpen, preferredBooks
|
||||
<div onClick={() => onOpen && onOpen(g.id)} title="Open game detail" style={{ display: 'flex', alignItems: 'center', gap: 11, minWidth: 0, cursor: onOpen ? 'pointer' : 'default' }}>
|
||||
<SportBadge sport={g.sport} />
|
||||
<span className="mono" style={{ fontSize: 18, fontWeight: 700, letterSpacing: '0.01em' }}>
|
||||
{g.away.abbr} <span style={{ color: 'var(--text-2)', fontWeight: 400 }}>@</span> {g.home.abbr}
|
||||
<TeamLink abbr={g.away.abbr} sport={g.sport} /> <span style={{ color: 'var(--text-2)', fontWeight: 400 }}>@</span> <TeamLink abbr={g.home.abbr} sport={g.sport} />
|
||||
</span>
|
||||
{g.live && (
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, marginLeft: 2 }}>
|
||||
|
||||
Reference in New Issue
Block a user