From c0f22d971203c0bd426a76f1906584b4e31daac4 Mon Sep 17 00:00:00 2001 From: Timothy Armes Date: Mon, 15 Apr 2024 14:51:23 +0200 Subject: [PATCH] Elo calculator - display cadence in tournament dropdown + link to FFE results --- src/app/[locale]/(main)/elo/page.tsx | 8 +- src/components/form/AsyncSelectField.tsx | 21 ++++-- src/components/form/TournamentSelectField.tsx | 74 ++++++++++++------- 3 files changed, 70 insertions(+), 33 deletions(-) diff --git a/src/app/[locale]/(main)/elo/page.tsx b/src/app/[locale]/(main)/elo/page.tsx index 2af42dc..c58dd24 100644 --- a/src/app/[locale]/(main)/elo/page.tsx +++ b/src/app/[locale]/(main)/elo/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; @@ -15,6 +15,8 @@ import { SelectField } from "@/components/form/SelectField"; import { TournamentSelectField } from "@/components/form/TournamentSelectField"; import { fetchTournamentResultsSchema } from "@/schemas"; import { fetchTournamentResults } from "@/server/fetchTournamentResults"; +import { SearchedTournament } from "@/server/searchTournaments"; +import { TimeControl } from "@/types"; import { Link } from "@/utils/navigation"; import { KFactor } from "./KFactor"; @@ -42,6 +44,7 @@ export default function Elo() { () => new URLSearchParams(Array.from(searchParams.entries())), [searchParams], ); + const [tournament, setTournament] = useState(null); const tournamentId = searchParams.get("tId") ?? ""; const kFactorParam = searchParams.get("k"); @@ -205,6 +208,9 @@ export default function Elo() { control={form.control} placeholder={t("searchTournamentPlaceholder")} noOptionsMessage={() => t("noTournamentsFound")} + onInformChange={(tournaments) => + setTournament(tournaments?.[0]?.data ?? null) + } /> {isFetching && ( diff --git a/src/components/form/AsyncSelectField.tsx b/src/components/form/AsyncSelectField.tsx index bd3217f..06ff6ab 100644 --- a/src/components/form/AsyncSelectField.tsx +++ b/src/components/form/AsyncSelectField.tsx @@ -32,6 +32,7 @@ export type AsyncSelectFieldProps< required?: boolean; loadOption: (id: T) => Promise | undefined>; loadOptions: (searchString?: string) => Promise[]>; + onInformChange?: (data: BaseOption[] | null) => void; separators?: boolean; } >; @@ -52,9 +53,11 @@ export const AsyncSelectField = < loadOption, loadOptions, + onInformChange, placeholder, separators, + ...selectProps }: AsyncSelectFieldProps) => { const t = useTranslations("App"); @@ -92,6 +95,12 @@ export const AsyncSelectField = < setLoadingValues(false); + onInformChange?.( + values.map( + (v) => valueOptions.current.find((o) => o.value === v)!, + ) as BaseOption[], + ); + // This forced a state update, which enures that the select is updated once the query has finished setLoadedInitialValues(true); } @@ -102,7 +111,7 @@ export const AsyncSelectField = < }; doLoad(); - }, [value, setLoadingValues, loadOption]); + }, [value, setLoadingValues, loadOption, onInformChange]); const { formState: { errors }, @@ -133,15 +142,17 @@ export const AsyncSelectField = < newValue: OnChangeValue, IsMulti>, actionMeta: ActionMeta>, ) => { - console.log(newValue, actionMeta); if (isNil(newValue) || actionMeta.action === "clear") { onChange(null); + onInformChange?.(null); valueOptions.current = []; } else if (isArray(newValue)) { onChange(newValue.map((option) => option.value)); + onInformChange?.(newValue); valueOptions.current = newValue; } else { onChange((newValue as BaseOption).value); + onInformChange?.([newValue as BaseOption]); valueOptions.current = [newValue as BaseOption]; } }; @@ -150,9 +161,9 @@ export const AsyncSelectField = < isNil(value) || value === "" ? null : isArray(value) - ? // @ts-ignore - this is too complex for TS to understand - value.map(valueToOption) - : valueToOption(value); + ? // @ts-ignore - this is too complex for TS to understand + value.map(valueToOption) + : valueToOption(value); return ( , IsMulti> diff --git a/src/components/form/TournamentSelectField.tsx b/src/components/form/TournamentSelectField.tsx index 0858de6..2424a7a 100644 --- a/src/components/form/TournamentSelectField.tsx +++ b/src/components/form/TournamentSelectField.tsx @@ -1,5 +1,8 @@ +import { useState } from "react"; + import { useTranslations } from "next-intl"; import { FieldPath, FieldValues } from "react-hook-form"; +import { FaExternalLinkAlt } from "react-icons/fa"; import { TimeControlColours } from "@/constants"; import { getTournamentDetails } from "@/server/getTournamentDetails"; @@ -31,6 +34,8 @@ export const TournamentSelectField = < ...rest }: TournamentSelectFieldProps) => { const at = useTranslations("App"); + const [selectedTournament, setSelectedTournament] = + useState(null); const loadOption = async (ffeId: string) => { const { data: tournament } = await getTournamentDetails({ ffeId }); @@ -57,34 +62,49 @@ export const TournamentSelectField = < }; return ( - { - if (context.context === "value") return option.label; - return ( -
-
-
{option.data?.town}
-
- {at("timeControlEnum", { tc: option.data?.timeControl })} +
+ + setSelectedTournament(data ? data[0].data! : null) + } + formatOptionLabel={(option, context) => { + if (context.context === "value") return option.label; + return ( +
+
+
{option.data?.town}
+
+ {at("timeControlEnum", { tc: option.data?.timeControl })} +
+
{option.data?.date}
-
{option.data?.date}
-
-
{option.data?.tournament}
-
- ); - }} - /> +
{option.data?.tournament}
+
+ ); + }} + /> + {selectedTournament && ( + + {selectedTournament.url} + + + )} +
); };