168 lines
5.3 KiB
TypeScript
168 lines
5.3 KiB
TypeScript
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>
|
|
);
|
|
}
|