import { useMemo } from 'react' import { useGenlockeLineages } from '../hooks/useGenlockes' import type { LineageEntry, LineageLegEntry } from '../types' interface GenlockeLineageProps { genlockeId: number } function LegDot({ leg }: { leg: LineageLegEntry }) { let color: string let label: string if (leg.faintLevel !== null) { color = 'bg-red-500' label = 'Dead' } else if (leg.wasTransferred) { color = 'bg-blue-500' label = 'Transferred' } else if (leg.enteredHof) { color = 'bg-yellow-500' label = 'Hall of Fame' } else { color = 'bg-green-500' label = 'Alive' } const displayPokemon = leg.currentPokemon ?? leg.pokemon return (
{/* Tooltip */}
{leg.gameName}
{displayPokemon.spriteUrl && ( {displayPokemon.name} )} {displayPokemon.name}
{leg.catchLevel !== null && (
Caught Lv. {leg.catchLevel}
)} {leg.faintLevel !== null && (
Died Lv. {leg.faintLevel}
)} {leg.deathCause && (
{leg.deathCause}
)}
{label}
{leg.enteredHof && leg.faintLevel === null && (
Hall of Fame
)}
) } function TimelineGrid({ lineage, allLegOrders, }: { lineage: LineageEntry allLegOrders: number[] }) { const legMap = new Map(lineage.legs.map((l) => [l.legOrder, l])) const minLeg = lineage.legs[0].legOrder const maxLeg = lineage.legs[lineage.legs.length - 1].legOrder return (
{allLegOrders.map((legOrder, i) => { const leg = legMap.get(legOrder) const inRange = legOrder >= minLeg && legOrder <= maxLeg const showLeftLine = inRange && i > 0 && allLegOrders[i - 1] >= minLeg const showRightLine = inRange && i < allLegOrders.length - 1 && allLegOrders[i + 1] <= maxLeg return (
{/* Left half connector */} {showLeftLine && (
)} {/* Right half connector */} {showRightLine && (
)} {/* Dot or empty */} {leg ? (
) : (
)}
) })}
) } function LineageCard({ lineage, allLegOrders, }: { lineage: LineageEntry allLegOrders: number[] }) { const firstLeg = lineage.legs[0] const displayPokemon = firstLeg.currentPokemon ?? firstLeg.pokemon return (
{/* Left: Pokemon sprite + nickname */}
{displayPokemon.spriteUrl ? ( {displayPokemon.name} ) : (
{displayPokemon.name[0].toUpperCase()}
)} {lineage.nickname || lineage.pokemon.name} {lineage.nickname && ( {lineage.pokemon.name} )}
{/* Center: Timeline */}
{/* Right: Status badge */}
{lineage.status === 'alive' ? 'Alive' : 'Dead'}
) } export function GenlockeLineage({ genlockeId }: GenlockeLineageProps) { const { data, isLoading, error } = useGenlockeLineages(genlockeId) const allLegOrders = useMemo(() => { if (!data) return [] return [...new Set(data.lineages.flatMap((l) => l.legs.map((leg) => leg.legOrder)))].sort( (a, b) => a - b ) }, [data]) const legGameNames = useMemo(() => { if (!data) return new Map() const map = new Map() for (const lineage of data.lineages) { for (const leg of lineage.legs) { map.set(leg.legOrder, leg.gameName) } } return map }, [data]) if (isLoading) { return (
) } if (error) { return (
Failed to load lineage data.
) } if (!data || data.totalLineages === 0) { return (
No Pokemon have been transferred between legs yet.
) } return (
{/* Summary bar */}
{data.totalLineages} lineage{data.totalLineages !== 1 ? 's' : ''} across{' '} {allLegOrders.length} leg{allLegOrders.length !== 1 ? 's' : ''}
{/* Column header row */}
{/* Spacer matching pokemon info column */}
{/* Leg headers */}
{allLegOrders.map((legOrder) => (
Leg {legOrder} {legGameNames.get(legOrder)}
))}
{/* Spacer matching status badge */}
{/* Lineage cards */}
{data.lineages.map((lineage) => ( ))}
) }