import clsx from "clsx"; import { format, sub } from "date-fns"; import * as React from "react"; import { Autocomplete, Button, Input, type Key, ListBox, ListBoxItem, Popover, SearchField, Select, type SelectProps, SelectValue, } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { useFetcher } from "react-router"; import { useDebounce } from "react-use"; import { SendouBottomTexts } from "~/components/elements/BottomTexts"; import { SendouLabel } from "~/components/elements/Label"; import { ChevronUpDownIcon } from "~/components/icons/ChevronUpDown"; import { CrossIcon } from "~/components/icons/Cross"; import type { TournamentSearchLoaderData } from "~/features/tournament/routes/to.search"; import { databaseTimestampToDate } from "~/utils/dates"; import { SearchIcon } from "../icons/Search"; import selectStyles from "./Select.module.css"; import tournamentSearchStyles from "./TournamentSearch.module.css"; type TournamentSearchItem = NonNullable< Extract >["tournaments"][number]; interface TournamentSearchProps extends Omit, "children" | "onChange"> { name?: string; label?: string; bottomText?: string; errorText?: string; initialTournamentId?: number; onChange?: (tournament: TournamentSearchItem | null) => void; } export const TournamentSearch = React.forwardRef(function TournamentSearch< T extends object, >( { name, label, bottomText, errorText, initialTournamentId, onChange, ...rest }: TournamentSearchProps, ref?: React.Ref, ) { const [selectedKey, setSelectedKey] = React.useState( initialTournamentId ?? null, ); const list = useTournamentSearch(setSelectedKey); const onSelectionChange = (tournamentId: number) => { setSelectedKey(tournamentId); const tournament = list.items.find( (tournament) => typeof tournament.id === "number" && tournament.id === tournamentId, ); if (tournament && typeof tournament.id === "number") { onChange?.(tournament as TournamentSearchItem); } }; // clear if selected user is not in the new filtered items React.useEffect(() => { if ( selectedKey && selectedKey !== initialTournamentId && !list.items.some( (tournament) => typeof tournament.id === "number" && tournament.id === selectedKey, ) ) { setSelectedKey(null); onChange?.(null); } }, [list.items, selectedKey, onChange, initialTournamentId]); return ( tournament !== undefined)} className={selectStyles.listBox} > {(item) => } ); }); function TournamentItem({ item, }: { item: | TournamentSearchItem | { id: "NO_RESULTS"; } | { id: "PLACEHOLDER"; }; }) { const { t } = useTranslation(["common"]); if (typeof item.id === "string") { return ( {item.id === "PLACEHOLDER" ? t("common:forms.tournamentSearch.placeholder") : t("common:forms.tournamentSearch.noResults")} ); } const additionalText = () => { const date = databaseTimestampToDate(item.startTime); return format(date, "MMM d, yyyy"); }; return ( clsx(tournamentSearchStyles.item, { [selectStyles.itemFocused]: isFocused, [selectStyles.itemSelected]: isSelected, }) } data-testid="tournament-search-item" >
{item.name} {additionalText() ? (
{additionalText()}
) : null}
); } function useTournamentSearch( setSelectedKey: (tournamentId: number | null) => void, ) { const [filterText, setFilterText] = React.useState(""); const queryFetcher = useFetcher(); useDebounce( () => { if (!filterText) return; queryFetcher.load( `/to/search?q=${filterText}&limit=6&minStartTime=${sub(new Date(), { days: 7 }).toISOString()}`, ); setSelectedKey(null); }, 500, [filterText], ); const items = () => { if ( queryFetcher.data && !Array.isArray(queryFetcher.data) && queryFetcher.data.query === filterText ) { if (queryFetcher.data.tournaments.length === 0) { return [{ id: "NO_RESULTS" }]; } return queryFetcher.data.tournaments; } return [{ id: "PLACEHOLDER" }]; }; return { filterText, setFilterText, items: items(), }; }