import { useState } from 'react' import { useParams, Link } from 'react-router-dom' import { useRun, useUpdateRun } from '../hooks/useRuns' import { useGameRoutes } from '../hooks/useGames' import { useUpdateEncounter } from '../hooks/useEncounters' import { StatCard, PokemonCard, RuleBadges, StatusChangeModal, EndRunModal } from '../components' import type { RunStatus, EncounterDetail } from '../types' const statusStyles: Record = { active: 'bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300', completed: 'bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300', failed: 'bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300', } function formatDuration(start: string, end: string) { const ms = new Date(end).getTime() - new Date(start).getTime() const days = Math.floor(ms / (1000 * 60 * 60 * 24)) if (days === 0) return 'Less than a day' if (days === 1) return '1 day' return `${days} days` } export function RunDashboard() { const { runId } = useParams<{ runId: string }>() const runIdNum = Number(runId) const { data: run, isLoading, error } = useRun(runIdNum) const { data: routes } = useGameRoutes(run?.gameId ?? null) const updateEncounter = useUpdateEncounter(runIdNum) const updateRun = useUpdateRun(runIdNum) const [selectedEncounter, setSelectedEncounter] = useState(null) const [showEndRun, setShowEndRun] = useState(false) if (isLoading) { return (
) } if (error || !run) { return (
Failed to load run. It may not exist.
Back to runs
) } const isActive = run.status === 'active' const alive = run.encounters.filter( (e) => e.status === 'caught' && e.faintLevel === null, ) const dead = run.encounters.filter( (e) => e.status === 'caught' && e.faintLevel !== null, ) const visitedRoutes = new Set(run.encounters.map((e) => e.routeId)).size const totalRoutes = routes?.length return (
{/* Header */}
← All Runs

{run.name}

{run.game.name} · {run.game.region} · Started{' '} {new Date(run.startedAt).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', })}

{run.status}
{/* Completion Banner */} {!isActive && (
{run.status === 'completed' ? '\u{1f3c6}' : '\u{1faa6}'}

{run.status === 'completed' ? 'Victory!' : 'Defeat'}

{run.completedAt && ( <> Ended{' '} {new Date(run.completedAt).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric', })} {' \u00b7 '} Duration: {formatDuration(run.startedAt, run.completedAt)} )}

)} {/* Stats */}
{/* Rules */}

Active Rules

{/* Active Team */}

{isActive ? 'Active Team' : 'Final Team'}

{alive.length === 0 ? (

No pokemon caught yet — head to encounters to start building your team!

) : (
{alive.map((enc) => ( setSelectedEncounter(enc) : undefined} /> ))}
)}
{/* Graveyard */} {dead.length > 0 && (

Graveyard

{dead.map((enc) => ( setSelectedEncounter(enc) : undefined} /> ))}
)} {/* Quick Actions */}
{isActive && ( <> Log Encounter )}
{/* Status Change Modal */} {selectedEncounter && ( { updateEncounter.mutate(data, { onSuccess: () => setSelectedEncounter(null), }) }} onClose={() => setSelectedEncounter(null)} isPending={updateEncounter.isPending} /> )} {/* End Run Modal */} {showEndRun && ( { updateRun.mutate( { status }, { onSuccess: () => setShowEndRun(false) }, ) }} onClose={() => setShowEndRun(false)} isPending={updateRun.isPending} /> )}
) }