feat: add draft timer display with team-specific countdown UI
This commit is contained in:
parent
8a61085f3b
commit
c820dfbdad
3 changed files with 91 additions and 12 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import { DraftTimer } from "./DraftTimer";
|
||||||
|
|
||||||
interface Team {
|
interface Team {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -23,6 +24,11 @@ interface Participant {
|
||||||
sport: string;
|
sport: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Timer {
|
||||||
|
teamId: string;
|
||||||
|
timeRemaining: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface DraftGridProps {
|
interface DraftGridProps {
|
||||||
teams: Team[];
|
teams: Team[];
|
||||||
draftPicks: DraftPick[];
|
draftPicks: DraftPick[];
|
||||||
|
|
@ -30,6 +36,7 @@ interface DraftGridProps {
|
||||||
draftRounds: number;
|
draftRounds: number;
|
||||||
currentPickNumber: number;
|
currentPickNumber: number;
|
||||||
userTeamId?: string;
|
userTeamId?: string;
|
||||||
|
timers?: Timer[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DraftGrid({
|
export function DraftGrid({
|
||||||
|
|
@ -39,6 +46,7 @@ export function DraftGrid({
|
||||||
draftRounds,
|
draftRounds,
|
||||||
currentPickNumber,
|
currentPickNumber,
|
||||||
userTeamId,
|
userTeamId,
|
||||||
|
timers = [],
|
||||||
}: DraftGridProps) {
|
}: DraftGridProps) {
|
||||||
// Create a map of participant IDs to participant data for quick lookup
|
// Create a map of participant IDs to participant data for quick lookup
|
||||||
const participantMap = useMemo(() => {
|
const participantMap = useMemo(() => {
|
||||||
|
|
@ -50,6 +58,11 @@ export function DraftGrid({
|
||||||
return new Map(draftPicks.map((p) => [p.pickNumber, p]));
|
return new Map(draftPicks.map((p) => [p.pickNumber, p]));
|
||||||
}, [draftPicks]);
|
}, [draftPicks]);
|
||||||
|
|
||||||
|
// Create a map of team IDs to timers
|
||||||
|
const timerMap = useMemo(() => {
|
||||||
|
return new Map(timers.map((t) => [t.teamId, t]));
|
||||||
|
}, [timers]);
|
||||||
|
|
||||||
// Calculate pick number for a given round and team
|
// Calculate pick number for a given round and team
|
||||||
const getPickNumber = (round: number, teamIndex: number): number => {
|
const getPickNumber = (round: number, teamIndex: number): number => {
|
||||||
const isOddRound = round % 2 === 1;
|
const isOddRound = round % 2 === 1;
|
||||||
|
|
@ -66,18 +79,33 @@ export function DraftGrid({
|
||||||
<div className="w-16 flex-shrink-0 p-2 font-semibold text-sm border-r">
|
<div className="w-16 flex-shrink-0 p-2 font-semibold text-sm border-r">
|
||||||
Round
|
Round
|
||||||
</div>
|
</div>
|
||||||
{teams.map((team) => (
|
{teams.map((team) => {
|
||||||
<div
|
const timer = timerMap.get(team.id);
|
||||||
key={team.id}
|
// Determine if this team is on the clock
|
||||||
className={`flex-1 min-w-[140px] p-2 text-center font-semibold text-sm border-r last:border-r-0 ${
|
const currentPick = Math.ceil(currentPickNumber / teams.length);
|
||||||
team.id === userTeamId ? "bg-primary/10" : ""
|
const pickInRound = ((currentPickNumber - 1) % teams.length) + 1;
|
||||||
}`}
|
const isOddRound = currentPick % 2 === 1;
|
||||||
>
|
const currentTeamIndex = isOddRound ? pickInRound - 1 : teams.length - pickInRound;
|
||||||
<div className="truncate" title={team.name}>
|
const isOnClock = teams[currentTeamIndex]?.id === team.id;
|
||||||
{team.name}
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={team.id}
|
||||||
|
className={`flex-1 min-w-[140px] p-2 text-center font-semibold text-sm border-r last:border-r-0 ${
|
||||||
|
team.id === userTeamId ? "bg-primary/10" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="truncate" title={team.name}>
|
||||||
|
{team.name}
|
||||||
|
</div>
|
||||||
|
{timer && (
|
||||||
|
<div className="mt-1">
|
||||||
|
<DraftTimer timeRemaining={timer.timeRemaining} isActive={isOnClock} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
))}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Draft Rounds */}
|
{/* Draft Rounds */}
|
||||||
|
|
|
||||||
37
app/components/draft/DraftTimer.tsx
Normal file
37
app/components/draft/DraftTimer.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
interface DraftTimerProps {
|
||||||
|
timeRemaining: number; // in seconds
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DraftTimer({ timeRemaining, isActive = false }: DraftTimerProps) {
|
||||||
|
// Format time as M:SS or H:MM:SS
|
||||||
|
const formatTime = (seconds: number): string => {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const secs = seconds % 60;
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
return `${minutes}:${String(secs).padStart(2, "0")}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine color based on time remaining
|
||||||
|
const getColorClass = (): string => {
|
||||||
|
if (timeRemaining > 60) {
|
||||||
|
return "text-green-600 dark:text-green-400";
|
||||||
|
} else if (timeRemaining > 30) {
|
||||||
|
return "text-yellow-600 dark:text-yellow-400";
|
||||||
|
} else if (timeRemaining > 10) {
|
||||||
|
return "text-red-600 dark:text-red-400";
|
||||||
|
} else {
|
||||||
|
return "text-red-600 dark:text-red-400 animate-pulse";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`text-sm font-mono font-semibold ${getColorClass()} ${isActive ? "font-bold" : ""}`}>
|
||||||
|
{formatTime(timeRemaining)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -69,7 +69,19 @@ export async function loader(args: LoaderFunctionArgs) {
|
||||||
|
|
||||||
// Get draft data
|
// Get draft data
|
||||||
const draftPicks = await getDraftPicks(season.id);
|
const draftPicks = await getDraftPicks(season.id);
|
||||||
const timers = await getSeasonTimers(season.id);
|
let timers = await getSeasonTimers(season.id);
|
||||||
|
|
||||||
|
// If no timers exist, create default timers for display
|
||||||
|
if (timers.length === 0) {
|
||||||
|
timers = teams.map((team) => ({
|
||||||
|
id: team.id,
|
||||||
|
seasonId: season.id,
|
||||||
|
teamId: team.id,
|
||||||
|
timeRemaining: season.draftInitialTime,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// Get user's queue if they have a team
|
// Get user's queue if they have a team
|
||||||
const queueItems = userTeam ? await getTeamQueue(userTeam.id) : [];
|
const queueItems = userTeam ? await getTeamQueue(userTeam.id) : [];
|
||||||
|
|
@ -105,6 +117,7 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited<ReturnTy
|
||||||
draftPicks,
|
draftPicks,
|
||||||
participants,
|
participants,
|
||||||
queueItems,
|
queueItems,
|
||||||
|
timers,
|
||||||
isCommissioner,
|
isCommissioner,
|
||||||
userTeam,
|
userTeam,
|
||||||
currentPickNumber,
|
currentPickNumber,
|
||||||
|
|
@ -161,6 +174,7 @@ export default function DraftRoom({ loaderData }: { loaderData: Awaited<ReturnTy
|
||||||
draftRounds={season.draftRounds}
|
draftRounds={season.draftRounds}
|
||||||
currentPickNumber={currentPickNumber}
|
currentPickNumber={currentPickNumber}
|
||||||
userTeamId={userTeam?.id}
|
userTeamId={userTeam?.id}
|
||||||
|
timers={timers}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue