mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
Elo calculator - display cadence in tournament dropdown + link to FFE results
This commit is contained in:
@@ -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<SearchedTournament | null>(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 && (
|
||||
|
||||
@@ -32,6 +32,7 @@ export type AsyncSelectFieldProps<
|
||||
required?: boolean;
|
||||
loadOption: (id: T) => Promise<BaseOption<T, D> | undefined>;
|
||||
loadOptions: (searchString?: string) => Promise<BaseOption<T, D>[]>;
|
||||
onInformChange?: (data: BaseOption<T, D>[] | null) => void;
|
||||
separators?: boolean;
|
||||
}
|
||||
>;
|
||||
@@ -52,9 +53,11 @@ export const AsyncSelectField = <
|
||||
|
||||
loadOption,
|
||||
loadOptions,
|
||||
onInformChange,
|
||||
|
||||
placeholder,
|
||||
separators,
|
||||
|
||||
...selectProps
|
||||
}: AsyncSelectFieldProps<TFieldValues, TFieldName, IsMulti, T, D>) => {
|
||||
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<T, D>[],
|
||||
);
|
||||
|
||||
// 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<BaseOption<T, D>, IsMulti>,
|
||||
actionMeta: ActionMeta<BaseOption<T, D>>,
|
||||
) => {
|
||||
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<T, D>).value);
|
||||
onInformChange?.([newValue as BaseOption<T, D>]);
|
||||
valueOptions.current = [newValue as BaseOption<T, D>];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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<TFieldValues, TFieldName>) => {
|
||||
const at = useTranslations("App");
|
||||
const [selectedTournament, setSelectedTournament] =
|
||||
useState<SearchedTournament | null>(null);
|
||||
|
||||
const loadOption = async (ffeId: string) => {
|
||||
const { data: tournament } = await getTournamentDetails({ ffeId });
|
||||
@@ -57,6 +62,7 @@ export const TournamentSelectField = <
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<AsyncSelectField
|
||||
{...rest}
|
||||
isSearchable
|
||||
@@ -64,6 +70,9 @@ export const TournamentSelectField = <
|
||||
loadOption={loadOption}
|
||||
loadOptions={loadOptions}
|
||||
isClearable={true}
|
||||
onInformChange={(data) =>
|
||||
setSelectedTournament(data ? data[0].data! : null)
|
||||
}
|
||||
formatOptionLabel={(option, context) => {
|
||||
if (context.context === "value") return option.label;
|
||||
return (
|
||||
@@ -86,5 +95,16 @@ export const TournamentSelectField = <
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{selectedTournament && (
|
||||
<a
|
||||
href={selectedTournament.url}
|
||||
target="_blank"
|
||||
className="inline-flex items-center justify-end gap-1 text-xs text-primary hover:text-primary-800"
|
||||
>
|
||||
{selectedTournament.url}
|
||||
<FaExternalLinkAlt />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user