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";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
@@ -15,6 +15,8 @@ import { SelectField } from "@/components/form/SelectField";
|
|||||||
import { TournamentSelectField } from "@/components/form/TournamentSelectField";
|
import { TournamentSelectField } from "@/components/form/TournamentSelectField";
|
||||||
import { fetchTournamentResultsSchema } from "@/schemas";
|
import { fetchTournamentResultsSchema } from "@/schemas";
|
||||||
import { fetchTournamentResults } from "@/server/fetchTournamentResults";
|
import { fetchTournamentResults } from "@/server/fetchTournamentResults";
|
||||||
|
import { SearchedTournament } from "@/server/searchTournaments";
|
||||||
|
import { TimeControl } from "@/types";
|
||||||
import { Link } from "@/utils/navigation";
|
import { Link } from "@/utils/navigation";
|
||||||
|
|
||||||
import { KFactor } from "./KFactor";
|
import { KFactor } from "./KFactor";
|
||||||
@@ -42,6 +44,7 @@ export default function Elo() {
|
|||||||
() => new URLSearchParams(Array.from(searchParams.entries())),
|
() => new URLSearchParams(Array.from(searchParams.entries())),
|
||||||
[searchParams],
|
[searchParams],
|
||||||
);
|
);
|
||||||
|
const [tournament, setTournament] = useState<SearchedTournament | null>(null);
|
||||||
|
|
||||||
const tournamentId = searchParams.get("tId") ?? "";
|
const tournamentId = searchParams.get("tId") ?? "";
|
||||||
const kFactorParam = searchParams.get("k");
|
const kFactorParam = searchParams.get("k");
|
||||||
@@ -205,6 +208,9 @@ export default function Elo() {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
placeholder={t("searchTournamentPlaceholder")}
|
placeholder={t("searchTournamentPlaceholder")}
|
||||||
noOptionsMessage={() => t("noTournamentsFound")}
|
noOptionsMessage={() => t("noTournamentsFound")}
|
||||||
|
onInformChange={(tournaments) =>
|
||||||
|
setTournament(tournaments?.[0]?.data ?? null)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isFetching && (
|
{isFetching && (
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export type AsyncSelectFieldProps<
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
loadOption: (id: T) => Promise<BaseOption<T, D> | undefined>;
|
loadOption: (id: T) => Promise<BaseOption<T, D> | undefined>;
|
||||||
loadOptions: (searchString?: string) => Promise<BaseOption<T, D>[]>;
|
loadOptions: (searchString?: string) => Promise<BaseOption<T, D>[]>;
|
||||||
|
onInformChange?: (data: BaseOption<T, D>[] | null) => void;
|
||||||
separators?: boolean;
|
separators?: boolean;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
@@ -52,9 +53,11 @@ export const AsyncSelectField = <
|
|||||||
|
|
||||||
loadOption,
|
loadOption,
|
||||||
loadOptions,
|
loadOptions,
|
||||||
|
onInformChange,
|
||||||
|
|
||||||
placeholder,
|
placeholder,
|
||||||
separators,
|
separators,
|
||||||
|
|
||||||
...selectProps
|
...selectProps
|
||||||
}: AsyncSelectFieldProps<TFieldValues, TFieldName, IsMulti, T, D>) => {
|
}: AsyncSelectFieldProps<TFieldValues, TFieldName, IsMulti, T, D>) => {
|
||||||
const t = useTranslations("App");
|
const t = useTranslations("App");
|
||||||
@@ -92,6 +95,12 @@ export const AsyncSelectField = <
|
|||||||
|
|
||||||
setLoadingValues(false);
|
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
|
// This forced a state update, which enures that the select is updated once the query has finished
|
||||||
setLoadedInitialValues(true);
|
setLoadedInitialValues(true);
|
||||||
}
|
}
|
||||||
@@ -102,7 +111,7 @@ export const AsyncSelectField = <
|
|||||||
};
|
};
|
||||||
|
|
||||||
doLoad();
|
doLoad();
|
||||||
}, [value, setLoadingValues, loadOption]);
|
}, [value, setLoadingValues, loadOption, onInformChange]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
@@ -133,15 +142,17 @@ export const AsyncSelectField = <
|
|||||||
newValue: OnChangeValue<BaseOption<T, D>, IsMulti>,
|
newValue: OnChangeValue<BaseOption<T, D>, IsMulti>,
|
||||||
actionMeta: ActionMeta<BaseOption<T, D>>,
|
actionMeta: ActionMeta<BaseOption<T, D>>,
|
||||||
) => {
|
) => {
|
||||||
console.log(newValue, actionMeta);
|
|
||||||
if (isNil(newValue) || actionMeta.action === "clear") {
|
if (isNil(newValue) || actionMeta.action === "clear") {
|
||||||
onChange(null);
|
onChange(null);
|
||||||
|
onInformChange?.(null);
|
||||||
valueOptions.current = [];
|
valueOptions.current = [];
|
||||||
} else if (isArray(newValue)) {
|
} else if (isArray(newValue)) {
|
||||||
onChange(newValue.map((option) => option.value));
|
onChange(newValue.map((option) => option.value));
|
||||||
|
onInformChange?.(newValue);
|
||||||
valueOptions.current = newValue;
|
valueOptions.current = newValue;
|
||||||
} else {
|
} else {
|
||||||
onChange((newValue as BaseOption<T, D>).value);
|
onChange((newValue as BaseOption<T, D>).value);
|
||||||
|
onInformChange?.([newValue as BaseOption<T, D>]);
|
||||||
valueOptions.current = [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 { useTranslations } from "next-intl";
|
||||||
import { FieldPath, FieldValues } from "react-hook-form";
|
import { FieldPath, FieldValues } from "react-hook-form";
|
||||||
|
import { FaExternalLinkAlt } from "react-icons/fa";
|
||||||
|
|
||||||
import { TimeControlColours } from "@/constants";
|
import { TimeControlColours } from "@/constants";
|
||||||
import { getTournamentDetails } from "@/server/getTournamentDetails";
|
import { getTournamentDetails } from "@/server/getTournamentDetails";
|
||||||
@@ -31,6 +34,8 @@ export const TournamentSelectField = <
|
|||||||
...rest
|
...rest
|
||||||
}: TournamentSelectFieldProps<TFieldValues, TFieldName>) => {
|
}: TournamentSelectFieldProps<TFieldValues, TFieldName>) => {
|
||||||
const at = useTranslations("App");
|
const at = useTranslations("App");
|
||||||
|
const [selectedTournament, setSelectedTournament] =
|
||||||
|
useState<SearchedTournament | null>(null);
|
||||||
|
|
||||||
const loadOption = async (ffeId: string) => {
|
const loadOption = async (ffeId: string) => {
|
||||||
const { data: tournament } = await getTournamentDetails({ ffeId });
|
const { data: tournament } = await getTournamentDetails({ ffeId });
|
||||||
@@ -57,6 +62,7 @@ export const TournamentSelectField = <
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
<AsyncSelectField
|
<AsyncSelectField
|
||||||
{...rest}
|
{...rest}
|
||||||
isSearchable
|
isSearchable
|
||||||
@@ -64,6 +70,9 @@ export const TournamentSelectField = <
|
|||||||
loadOption={loadOption}
|
loadOption={loadOption}
|
||||||
loadOptions={loadOptions}
|
loadOptions={loadOptions}
|
||||||
isClearable={true}
|
isClearable={true}
|
||||||
|
onInformChange={(data) =>
|
||||||
|
setSelectedTournament(data ? data[0].data! : null)
|
||||||
|
}
|
||||||
formatOptionLabel={(option, context) => {
|
formatOptionLabel={(option, context) => {
|
||||||
if (context.context === "value") return option.label;
|
if (context.context === "value") return option.label;
|
||||||
return (
|
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