new typeahead
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
Autocomplete,
|
||||
Stack,
|
||||
ActionIcon,
|
||||
Text,
|
||||
Group,
|
||||
Loader,
|
||||
} from "@mantine/core";
|
||||
import Typeahead, { TypeaheadOption } from "@/components/typeahead";
|
||||
import { TrashIcon } from "@phosphor-icons/react";
|
||||
import { useState, useCallback, useMemo, memo } from "react";
|
||||
import { useTournament, useUnenrolledTeams } from "../queries";
|
||||
@@ -68,8 +68,6 @@ const TeamItem = memo(({ team, onUnenroll, disabled }: TeamItemProps) => {
|
||||
});
|
||||
|
||||
const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const { data: tournament, isLoading: tournamentLoading } =
|
||||
useTournament(tournamentId);
|
||||
const { data: unenrolledTeams = [], isLoading: unenrolledLoading } =
|
||||
@@ -78,27 +76,24 @@ const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => {
|
||||
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 searchTeams = async (query: string): Promise<TypeaheadOption<Team>[]> => {
|
||||
if (!query.trim()) return [];
|
||||
|
||||
const filtered = unenrolledTeams.filter((team: Team) =>
|
||||
team.name.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
|
||||
return filtered.map((team: Team) => ({
|
||||
id: team.id,
|
||||
data: team
|
||||
}));
|
||||
};
|
||||
|
||||
const handleEnrollTeam = useCallback(
|
||||
(teamId: string) => {
|
||||
enrollTeam(
|
||||
{ tournamentId, teamId },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setSearch("");
|
||||
},
|
||||
}
|
||||
);
|
||||
(option: TypeaheadOption<Team>) => {
|
||||
enrollTeam({ tournamentId, teamId: option.data.id });
|
||||
},
|
||||
[enrollTeam, tournamentId, setSearch]
|
||||
[enrollTeam, tournamentId]
|
||||
);
|
||||
|
||||
const handleUnenrollTeam = useCallback(
|
||||
@@ -108,6 +103,31 @@ const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => {
|
||||
[unenrollTeam, tournamentId]
|
||||
);
|
||||
|
||||
const renderTeamOption = (option: TypeaheadOption<Team>) => {
|
||||
const team = option.data;
|
||||
return (
|
||||
<Group py="xs" px="sm" gap="sm" align="center">
|
||||
<Avatar
|
||||
size={32}
|
||||
radius="sm"
|
||||
name={team.name}
|
||||
src={
|
||||
team.logo
|
||||
? `/api/files/teams/${team.id}/${team.logo}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Text fw={500} truncate>
|
||||
{team.name}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
const formatTeam = (option: TypeaheadOption<Team>) => {
|
||||
return option.data.name;
|
||||
};
|
||||
|
||||
const isLoading = tournamentLoading || unenrolledLoading;
|
||||
const enrolledTeams = tournament?.teams || [];
|
||||
const hasEnrolledTeams = enrolledTeams.length > 0;
|
||||
@@ -118,16 +138,13 @@ const EditEnrolledTeams = ({ tournamentId }: EditEnrolledTeamsProps) => {
|
||||
<Text fw={600} size="sm">
|
||||
Add Team
|
||||
</Text>
|
||||
<Autocomplete
|
||||
<Typeahead
|
||||
placeholder="Search for teams to enroll..."
|
||||
data={autocompleteData}
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
onOptionSubmit={handleEnrollTeam}
|
||||
onSelect={handleEnrollTeam}
|
||||
searchFn={searchTeams}
|
||||
renderOption={renderTeamOption}
|
||||
format={formatTeam}
|
||||
disabled={isEnrolling || unenrolledLoading}
|
||||
rightSection={isEnrolling ? <Loader size="xs" /> : null}
|
||||
maxDropdownHeight={200}
|
||||
limit={10}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Stack, Button, Divider, Autocomplete, Group, ComboboxItem } from '@mantine/core';
|
||||
import { Stack, Button, Divider, Group, ComboboxItem, Text } from '@mantine/core';
|
||||
import { PlusIcon } from '@phosphor-icons/react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import Typeahead, { TypeaheadOption } from '@/components/typeahead';
|
||||
|
||||
interface TeamSelectionViewProps {
|
||||
options: ComboboxItem[];
|
||||
@@ -11,11 +12,39 @@ const TeamSelectionView: React.FC<TeamSelectionViewProps> = React.memo(({
|
||||
options,
|
||||
onSelect
|
||||
}) => {
|
||||
const [value, setValue] = useState<string>('');
|
||||
const selectedOption = useMemo(() => options.find(option => option.label === value), [value, options])
|
||||
const [selectedTeam, setSelectedTeam] = React.useState<ComboboxItem | null>(null);
|
||||
|
||||
const searchTeams = async (query: string): Promise<TypeaheadOption<ComboboxItem>[]> => {
|
||||
if (!query.trim()) return [];
|
||||
|
||||
const filtered = options.filter(option =>
|
||||
option.label.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
|
||||
return filtered.map(option => ({
|
||||
id: String(option.value),
|
||||
data: option
|
||||
}));
|
||||
};
|
||||
|
||||
const handleTeamSelect = (option: TypeaheadOption<ComboboxItem>) => {
|
||||
setSelectedTeam(option.data);
|
||||
};
|
||||
|
||||
const renderTeamOption = (option: TypeaheadOption<ComboboxItem>) => {
|
||||
return (
|
||||
<Group py="xs" px="sm" gap="sm">
|
||||
<Text fw={500}>{option.data.label}</Text>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
const formatTeam = (option: TypeaheadOption<ComboboxItem>) => {
|
||||
return option.data.label;
|
||||
};
|
||||
|
||||
const handleCreateNewTeamClicked = () => onSelect(undefined);
|
||||
const handleSelectExistingTeam = () => onSelect(selectedOption?.value)
|
||||
const handleSelectExistingTeam = () => onSelect(selectedTeam?.value);
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
@@ -31,17 +60,17 @@ const TeamSelectionView: React.FC<TeamSelectionViewProps> = React.memo(({
|
||||
<Divider my="sm" label="or" />
|
||||
|
||||
<Stack gap="sm">
|
||||
<Autocomplete
|
||||
<Typeahead
|
||||
placeholder="Select one of your existing teams"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
data={options.map(option => option.label)}
|
||||
comboboxProps={{ withinPortal: false }}
|
||||
onSelect={handleTeamSelect}
|
||||
searchFn={searchTeams}
|
||||
renderOption={renderTeamOption}
|
||||
format={formatTeam}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={handleSelectExistingTeam}
|
||||
disabled={!selectedOption}
|
||||
disabled={!selectedTeam}
|
||||
fullWidth
|
||||
>
|
||||
Enroll Selected Team
|
||||
|
||||
Reference in New Issue
Block a user