selection of club and players passes to form data on submit

This commit is contained in:
2025-12-09 16:43:01 +01:00
parent f55064adc9
commit 933ef56031
8 changed files with 444 additions and 4 deletions
@@ -0,0 +1,76 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { FormProvider, useForm } from "react-hook-form";
import Select from "react-select";
import { z } from "zod";
import TeamSelection from "@/app/[locale]/(main)/match/components/TeamSelection";
import { matchSchema } from "@/schemas";
import { Club } from "@/types";
type MatchFormValues = z.infer<typeof matchSchema>;
interface IProps {
clubs: Club[];
}
const MatchForm = ({ clubs }: IProps) => {
const form = useForm<MatchFormValues>({
// resolver: zodResolver(addTournamentSchema),
// defaultValues: {
// tournament: {
// norm_tournament: false,
// coordinates: [47.0844, 2.3964],
// },
// },
});
const onSubmit = async (data: MatchFormValues) => {
try {
console.log(data);
// await addTournament(data);
// setResponseMessage({
// type: "success",
// message: t("success"),
// });
form.reset();
} catch (err: unknown) {
console.log(err);
//
// setResponseMessage({
// type: "error",
// message: t("failure"),
// });
}
};
const clubOptions = clubs.map((club) => ({
value: club.name,
label: club.name,
url: club.url,
}));
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-3/4 space-y-8">
<div className="grid grid-cols-4 items-start gap-6">
<TeamSelection
clubOptions={clubOptions}
name="team1"
label="Team 1"
/>
<TeamSelection
clubOptions={clubOptions}
name="team2"
label="Team 2"
/>
</div>
<button type="submit">Submit</button>
</form>
</FormProvider>
);
};
export default MatchForm;
@@ -0,0 +1,170 @@
"use client";
import { useEffect, useState } from "react";
import { useAtom } from "jotai";
import {
Controller,
useFieldArray,
useFormContext,
useWatch,
} from "react-hook-form";
import { FaMinus, FaPlus } from "react-icons/fa";
import Select from "react-select";
import { clubPlayersAtom } from "@/atoms";
import { fetchClubPlayers } from "@/server/fetchClubPlayers";
interface IProps {
name: string;
label: string;
clubOptions: {
value: string;
label: string;
url: string;
}[];
}
const TeamSelection = ({ name, label, clubOptions }: IProps) => {
const [isLoading, setIsLoading] = useState(false);
const { control } = useFormContext();
const [teamsPlayers, setTeamsPlayers] = useAtom(clubPlayersAtom);
const { fields, append, remove } = useFieldArray({
control,
name: `${name}_players`, // e.g., "team1_players"
});
const selectedDbId = useWatch({
control,
name: name,
});
const selectedOption = clubOptions.find((c) => c.value === selectedDbId);
const selectedId = selectedOption?.url.split("=").pop();
useEffect(() => {
const fetchPlayers = async () => {
if (!selectedId) return;
setIsLoading(true);
try {
const response = await fetchClubPlayers({ clubId: selectedId });
const data = response?.data;
if (data) {
setTeamsPlayers((prev) => ({
...prev,
[name]: data,
}));
}
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
};
fetchPlayers();
}, [selectedId, name, setTeamsPlayers]);
const playerOptions =
teamsPlayers[name]?.map((player) => ({
value: {
nrFFE: player.nrFFE,
name: player.name,
elo: player.elo,
},
label: `${player.name} (${player.elo})`,
})) || [];
return (
<div className="col-span-2 space-y-4">
<label className="mb-2 block text-sm font-medium text-gray-900">
{label}
</label>
{/* Team/Club Select */}
<Controller
name={name}
control={control}
render={({ field }) => (
<Select
{...field}
instanceId={`${name}-select`}
options={clubOptions}
isSearchable
placeholder="Select a club..."
onChange={(option) => {
field.onChange(option?.value);
// Optional: Clear players if club changes, e.g. remove(0, fields.length)
}}
value={clubOptions.find((c) => c.value === field.value)}
classNames={{
control: () => "border-gray-300 rounded-md shadow-sm",
}}
/>
)}
/>
{/* Dynamic Player List */}
<div className="space-y-3">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-2">
<div className="flex-1">
<Controller
name={`${name}_players.${index}.playerId`} // Saves as { playerId: "nrFFE_123" }
control={control}
render={({ field: selectField }) => (
<Select
{...selectField}
instanceId={`${name}-player-${index}`}
options={playerOptions}
isSearchable
placeholder="Search player..."
onChange={(option) => selectField.onChange(option?.value)}
// Compare values using strict equality since value is now a string (nrFFE)
value={playerOptions.find(
(op) => op.value === selectField.value,
)}
classNames={{
control: () => "border-gray-300 rounded-md shadow-sm",
}}
/>
)}
/>
</div>
<button
type="button"
onClick={() => remove(index)}
className="p-2 text-red-500 hover:text-red-700"
title="Remove player"
>
<FaMinus />
</button>
</div>
))}
</div>
{/* Add Button */}
{isLoading ? (
<p className="text-sm text-gray-500">Loading players...</p>
) : (
teamsPlayers[name]?.length > 0 && (
<button
type="button" // Important to prevent form submission
onClick={() => append({ playerId: "" })} // Add new empty row to field array
className="flex items-center gap-2 text-sm font-medium text-blue-600 hover:text-blue-800"
>
<FaPlus />
Add Player
</button>
)
)}
</div>
);
};
export default TeamSelection;
+63
View File
@@ -0,0 +1,63 @@
import { unstable_cache } from "next/cache";
import MatchForm from "@/app/[locale]/(main)/match/MatchForm";
import { collections, dbConnect } from "@/server/mongodb";
import { Club } from "@/types";
import { filterClubsByManualEntry } from "@/utils/clubFilters";
import { errorLog } from "@/utils/logger";
const getClubs = async () => {
try {
await dbConnect();
const data = await collections
.clubs!.find({ pending: { $ne: true } })
.sort({ name: 1 })
.toArray();
return filterClubsByManualEntry(data)
.filter((c) => !!c.coordinates)
.map<Club>((club) => {
return {
id: club._id.toString(),
name: club.name,
url: club.url,
address: club.address,
email: club.email,
website: club.website,
latLng: { lat: club.coordinates[0], lng: club.coordinates[1] },
};
});
} catch (error) {
errorLog(error);
throw new Error("Error fetching club data");
}
};
export default async function Match() {
const clubs = await unstable_cache(
async () => {
const data = await getClubs();
return data;
},
["clubs"],
{
revalidate: 60 * 60 * 6,
},
)();
return (
<section className="grid place-items-center bg-white pb-20 dark:bg-gray-800">
<div className="mx-auto max-w-screen-md px-4 py-8 lg:py-16">
<h2 className="mb-4 text-center text-4xl font-extrabold tracking-tight text-gray-900 dark:text-white">
Match
</h2>
<p className="mb-8 text-center font-light text-gray-500 dark:text-gray-400 sm:text-xl lg:mb-16">
Match
</p>
</div>
<MatchForm clubs={clubs} />
</section>
);
}