This commit is contained in:
yohlo
2025-08-28 18:09:09 -05:00
parent 8eea99b125
commit 381ddc8f34
17 changed files with 343 additions and 98 deletions

View File

@@ -0,0 +1,123 @@
import { Autocomplete, Stack, ActionIcon, Text, Group, Loader } from "@mantine/core";
import { TrashIcon } from "@phosphor-icons/react";
import { useQuery } from "@tanstack/react-query";
import { useState, useCallback, useMemo, memo } from "react";
import { tournamentQueries } from "../queries";
import useEnrollTeam from "../hooks/use-enroll-team";
import useUnenrollTeam from "../hooks/use-unenroll-team";
import Avatar from "@/components/avatar";
import { Team } from "@/features/teams/types";
interface EditEnrolledTeamsProps {
tournamentId: string;
}
const TeamItem = memo(({ team, onUnenroll, disabled }: {
team: Team;
onUnenroll: (teamId: string) => void;
disabled: boolean;
}) => {
const playerNames = useMemo(() =>
team.players?.map(p => `${p.first_name} ${p.last_name}`).join(", ") || "",
[team.players]
);
return (
<Group py="xs" px="sm" w="100%" gap="sm" align="center">
<Avatar size={32} name={team.name} />
<Stack gap={0} style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} truncate>{team.name}</Text>
{playerNames && (
<Text size="xs" c="dimmed" truncate>{playerNames}</Text>
)}
</Stack>
<ActionIcon
variant="subtle"
color="red"
onClick={() => onUnenroll(team.id)}
disabled={disabled}
size="sm"
>
<TrashIcon size={14} />
</ActionIcon>
</Group>
);
});
const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => {
const [search, setSearch] = useState("");
const { data: tournament, isLoading: tournamentLoading } =
useQuery(tournamentQueries.details(tournamentId));
const { data: unenrolledTeams = [], isLoading: unenrolledLoading } =
useQuery(tournamentQueries.unenrolled(tournamentId));
const { mutate: enrollTeam, isPending: isEnrolling } = useEnrollTeam();
const { mutate: unenrollTeam, isPending: isUnenrolling } = useUnenrollTeam();
const autocompleteData = useMemo(() =>
unenrolledTeams.map((team: Team) => ({ value: team.id, label: team.name })),
[unenrolledTeams]
);
const handleEnrollTeam = useCallback((teamId: string) => {
enrollTeam({ tournamentId, teamId }, {
onSuccess: () => {
setSearch("");
}
});
}, [enrollTeam, tournamentId, setSearch]);
const handleUnenrollTeam = useCallback((teamId: string) => {
unenrollTeam({ tournamentId, teamId });
}, [unenrollTeam, tournamentId]);
const isLoading = tournamentLoading || unenrolledLoading;
const enrolledTeams = tournament?.teams || [];
const hasEnrolledTeams = enrolledTeams.length > 0;
return (
<Stack gap="lg" w="100%">
<Stack gap="xs">
<Text fw={600} size="sm">Add Team</Text>
<Autocomplete
placeholder="Search for teams to enroll..."
data={autocompleteData}
value={search}
onChange={setSearch}
onOptionSubmit={handleEnrollTeam}
disabled={isEnrolling || unenrolledLoading}
rightSection={isEnrolling ? <Loader size="xs" /> : null}
maxDropdownHeight={200}
limit={10}
/>
</Stack>
<Stack gap="xs">
<Group justify="space-between">
<Text fw={600} size="sm">Enrolled Teams</Text>
<Text size="xs" c="dimmed">{enrolledTeams.length} teams</Text>
</Group>
{isLoading ? (
<Group justify="center" py="md"><Loader size="sm" /></Group>
) : !hasEnrolledTeams ? (
<Text size="sm" c="dimmed" ta="center" py="lg">No teams enrolled yet</Text>
) : (
<Stack gap="xs" w="100%">
{enrolledTeams.map((team: Team) => (
<TeamItem
key={team.id}
team={team}
onUnenroll={handleUnenrollTeam}
disabled={isUnenrolling}
/>
))}
</Stack>
)}
</Stack>
</Stack>
);
};
export default EditEnrolledTeams;

View File

@@ -0,0 +1,59 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { tournamentQueries } from "../queries";
import { List } from "@mantine/core";
import ListButton from "@/components/list-button";
import Sheet from "@/components/sheet/sheet";
import TournamentForm from "./tournament-form";
import { HardDrivesIcon, PencilLineIcon, UsersThreeIcon } from "@phosphor-icons/react";
import { useSheet } from "@/hooks/use-sheet";
import EditEnrolledTeams from "./edit-enrolled-teams";
interface ManageTournamentProps {
tournamentId: string;
}
const ManageTournament = ({ tournamentId }: ManageTournamentProps) => {
const { data: tournament } = useSuspenseQuery(
tournamentQueries.details(tournamentId)
);
if (!tournament) throw new Error("Tournament not found.");
const { isOpen: editTournamentOpened, open: openEditTournament, close: closeEditTournament } = useSheet();
const { isOpen: editRulesOpened, open: openEditRules, close: closeEditRules } = useSheet();
const { isOpen: editTeamsOpened, open: openEditTeams, close: closeEditTeams } = useSheet();
return (
<>
<List>
<ListButton label="Edit Tournament" Icon={HardDrivesIcon} onClick={openEditTournament} />
<ListButton label="Edit Rules" Icon={PencilLineIcon} onClick={openEditRules} />
<ListButton label="Edit Enrolled Teams" Icon={UsersThreeIcon} onClick={openEditTeams} />
</List>
<Sheet title="Edit Tournament" opened={editTournamentOpened} onChange={closeEditTournament}>
<TournamentForm
tournamentId={tournament.id}
initialValues={{
name: tournament.name,
location: tournament.location,
desc: tournament.desc,
start_time: tournament.start_time,
enroll_time: tournament.enroll_time,
end_time: tournament.end_time,
}}
close={closeEditTournament}
/>
</Sheet>
<Sheet title="Edit Rules" opened={editRulesOpened} onChange={closeEditRules}>
<p>Test</p>
</Sheet>
<Sheet title="Edit Enrolled Teams" opened={editTeamsOpened} onChange={closeEditTeams}>
<EditEnrolledTeams tournamentId={tournamentId} />
</Sheet>
</>
)
};
export default ManageTournament;