several
This commit is contained in:
123
src/features/tournaments/components/edit-enrolled-teams.tsx
Normal file
123
src/features/tournaments/components/edit-enrolled-teams.tsx
Normal 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;
|
||||
59
src/features/tournaments/components/manage-tournament.tsx
Normal file
59
src/features/tournaments/components/manage-tournament.tsx
Normal 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;
|
||||
@@ -13,7 +13,9 @@ const useEnrollTeam = () => {
|
||||
if (!data) {
|
||||
toast.error('There was an issue enrolling. Please try again later.');
|
||||
} else {
|
||||
queryClient.invalidateQueries({ queryKey: ['tournaments', 'detail', tournamentId] });
|
||||
// Invalidate both tournament details and unenrolled teams queries
|
||||
queryClient.invalidateQueries({ queryKey: ['tournaments', 'details', tournamentId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['tournaments', 'unenrolled', tournamentId] });
|
||||
toast.success('Team enrolled successfully!');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,7 +13,9 @@ const useUnenrollTeam = () => {
|
||||
if (!data) {
|
||||
toast.error('There was an issue unenrolling. Please try again later.');
|
||||
} else {
|
||||
queryClient.invalidateQueries({ queryKey: ['tournaments', 'detail', tournamentId] });
|
||||
// Invalidate both tournament details and unenrolled teams queries
|
||||
queryClient.invalidateQueries({ queryKey: ['tournaments', 'details', tournamentId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['tournaments', 'unenrolled', tournamentId] });
|
||||
toast.success('Team unenrolled successfully.');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import { queryOptions, useQuery } from "@tanstack/react-query";
|
||||
import { getTournament, listTournaments } from "./server";
|
||||
import { getTournament, getUnenrolledTeams, listTournaments } from "./server";
|
||||
|
||||
const tournamentKeys = {
|
||||
list: ['tournaments', 'list'] as const,
|
||||
details: (id: string) => ['tournaments', 'details', id] as const
|
||||
details: (id: string) => ['tournaments', 'details', id] as const,
|
||||
unenrolled: (id: string) => ['tournaments', 'unenrolled', id] as const
|
||||
};
|
||||
|
||||
export const tournamentQueries = {
|
||||
list: () => queryOptions({
|
||||
queryKey: tournamentKeys.list,
|
||||
queryFn: listTournaments,
|
||||
queryFn: listTournaments
|
||||
}),
|
||||
details: (id: string) => queryOptions({
|
||||
queryKey: tournamentKeys.details(id),
|
||||
queryFn: () => getTournament({ data: id }),
|
||||
queryFn: () => getTournament({ data: id })
|
||||
}),
|
||||
unenrolled: (id: string) => queryOptions({
|
||||
queryKey: tournamentKeys.unenrolled(id),
|
||||
queryFn: () => getUnenrolledTeams({ data: id })
|
||||
})
|
||||
};
|
||||
|
||||
@@ -105,3 +105,12 @@ export const unenrollTeam = createServerFn()
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
export const getUnenrolledTeams = createServerFn()
|
||||
.validator(z.string())
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async ({ data: tournamentId }) => {
|
||||
logger.info('Getting unenrolled teams', tournamentId);
|
||||
const teams = await pbAdmin.getUnenrolledTeams(tournamentId);
|
||||
return teams;
|
||||
});
|
||||
Reference in New Issue
Block a user