regionals enrollments

This commit is contained in:
yohlo
2026-02-21 23:12:21 -06:00
parent 7f60b4d200
commit b9e16e2b64
27 changed files with 1212 additions and 83 deletions

View File

@@ -0,0 +1,167 @@
import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router";
import { tournamentQueries, useFreeAgents, useTournament } from "@/features/tournaments/queries";
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
import { Stack, Text, Button, Alert, LoadingOverlay, Group } from "@mantine/core";
import { useState } from "react";
import useGenerateRandomTeams from "@/features/tournaments/hooks/use-generate-random-teams";
import useConfirmTeamAssignments from "@/features/tournaments/hooks/use-confirm-team-assignments";
import TeamAssignmentPreview from "@/features/tournaments/components/team-assignment-preview";
import { WarningCircleIcon, ShuffleIcon, CheckCircleIcon } from "@phosphor-icons/react";
import { PlayerInfo } from "@/features/players/types";
import { useQueryClient } from "@tanstack/react-query";
export const Route = createFileRoute("/_authed/admin/tournaments/$id/assign-partners")({
beforeLoad: async ({ context, params }) => {
const { queryClient } = context;
const tournament = await ensureServerQueryData(
queryClient,
tournamentQueries.details(params.id)
);
if (!tournament) throw redirect({ to: "/admin/tournaments" });
return { tournament };
},
loader: ({ context }) => ({
header: {
withBackButton: true,
title: `Manage ${context.tournament.name}`,
},
}),
component: RouteComponent,
});
interface TeamAssignment {
player1: PlayerInfo;
player2: PlayerInfo;
teamName: string;
}
function RouteComponent() {
const { id } = Route.useParams();
const navigate = useNavigate();
const queryClient = useQueryClient();
const { data: freeAgents } = useFreeAgents(id);
const [assignments, setAssignments] = useState<TeamAssignment[] | null>(null);
const [currentSeed, setCurrentSeed] = useState<number | undefined>(undefined);
const generateMutation = useGenerateRandomTeams();
const confirmMutation = useConfirmTeamAssignments();
const hasOddPlayers = freeAgents.length % 2 !== 0;
const hasEnoughPlayers = freeAgents.length >= 2;
const handleGenerate = () => {
generateMutation.mutate(
{ data: { tournamentId: id, seed: currentSeed } },
{
onSuccess: (result) => {
setAssignments(result.assignments);
setCurrentSeed(result.seed);
},
}
);
};
const handleReroll = () => {
if (currentSeed === undefined) return;
generateMutation.mutate(
{ data: { tournamentId: id, seed: currentSeed + 1 } },
{
onSuccess: (result) => {
setAssignments(result.assignments);
setCurrentSeed(result.seed);
},
}
);
};
const handleConfirm = () => {
if (!assignments) return;
const formattedAssignments = assignments.map((a) => ({
player1Id: a.player1.id,
player2Id: a.player2.id,
teamName: a.teamName,
}));
confirmMutation.mutate(
{ data: { tournamentId: id, assignments: formattedAssignments } },
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: tournamentQueries.details(id).queryKey });
queryClient.invalidateQueries({ queryKey: tournamentQueries.free_agents(id).queryKey });
navigate({ to: "/admin/tournaments/$id", params: { id } });
},
}
);
};
return (
<Stack gap="lg" pos="relative">
<LoadingOverlay visible={confirmMutation.isPending} />
<Stack gap="xs">
<Group gap="xs" align="baseline">
<Text size="xl" fw={700}>
{freeAgents.length}
</Text>
<Text size="sm" c="dimmed">
{freeAgents.length === 1 ? "player enrolled" : "players enrolled"}
</Text>
</Group>
{!hasEnoughPlayers && (
<Alert color="yellow" icon={<WarningCircleIcon size={16} />}>
Need at least 2 players to create teams
</Alert>
)}
{hasOddPlayers && (
<Alert color="red" icon={<WarningCircleIcon size={16} />}>
Cannot create teams with an odd number of players. Please have one player unenroll.
</Alert>
)}
{!assignments && hasEnoughPlayers && !hasOddPlayers && (
<Button
leftSection={<ShuffleIcon size={18} />}
onClick={handleGenerate}
loading={generateMutation.isPending}
>
Generate Random Pairings
</Button>
)}
</Stack>
{assignments && (
<Stack gap="md">
<Group justify="space-between" align="center">
<Text size="lg" fw={600}>
Partner Assignments
</Text>
<Group gap="sm">
<Button
variant="subtle"
leftSection={<ShuffleIcon size={16} />}
onClick={handleReroll}
loading={generateMutation.isPending}
size="sm"
>
Re-roll
</Button>
<Button
leftSection={<CheckCircleIcon size={18} />}
onClick={handleConfirm}
loading={confirmMutation.isPending}
size="sm"
>
Confirm & Create Teams
</Button>
</Group>
</Group>
<TeamAssignmentPreview assignments={assignments} />
</Stack>
)}
</Stack>
);
}