From cce7ce7cc2af7356305ea3c4ca2dd2f259f8a756 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Sun, 1 Feb 2026 08:46:52 +0100 Subject: [PATCH 01/17] WIP --- .../TournamentPairingsPage.tsx | 65 ++++--------- .../TournamentPairingOptionsForm.module.scss | 3 + .../TournamentPairingOptionsForm.schema.ts | 38 ++++++++ .../TournamentPairingOptionsForm.tsx | 92 +++++++++++++++++++ .../TournamentPairingOptionsForm.utils.ts | 35 +++++++ src/utils/emptyToUndefined.ts | 4 + 6 files changed, 192 insertions(+), 45 deletions(-) create mode 100644 src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.module.scss create mode 100644 src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.schema.ts create mode 100644 src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.tsx create mode 100644 src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.utils.ts create mode 100644 src/utils/emptyToUndefined.ts diff --git a/src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx b/src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx index 9fa245c0..b833af7d 100644 --- a/src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx +++ b/src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx @@ -36,6 +36,8 @@ import { toast } from '~/components/ToastProvider'; import { TournamentCompetitorsProvider } from '~/components/TournamentCompetitorsProvider'; import { TournamentProvider } from '~/components/TournamentProvider'; import { ConfirmPairingsDialog } from '~/pages/TournamentPairingsPage/components/ConfirmPairingsDialog'; +import { TournamentPairingOptionsForm } from '~/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm'; +import { TournamentPairingArgs } from '~/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.utils'; import { useGetTournamentCompetitorsByTournament } from '~/services/tournamentCompetitors'; import { useCreateTournamentPairings, useGetDraftTournamentPairings } from '~/services/tournamentPairings'; import { useGetTournament } from '~/services/tournaments'; @@ -63,35 +65,18 @@ export const TournamentPairingsPage = (): JSX.Element => { const tournamentId = params.id! as TournamentId; // Must exist or else how did we get to this route? const { data: tournament } = useGetTournament({ id: tournamentId }); const lastRound = tournament?.lastRound ?? -1; + const nextRound = lastRound + 1; const { data: tournamentCompetitors } = useGetTournamentCompetitorsByTournament({ tournamentId, rankingRound: lastRound, }); - const isFirstRound = (tournament?.lastRound ?? -1) < 0; - const defaultPairingMethod = isFirstRound ? ( - TournamentPairingMethod.Random - ) : ( - tournament?.pairingMethod ?? TournamentPairingMethod.Adjacent - ); - const [pairingMethod, setPairingMethod] = useState(defaultPairingMethod); - - const pairingOptions: TournamentPairingOptions = { - allowRepeats: false, - allowSameAlignment: pairingMethod !== TournamentPairingMethod.AdjacentAlignment, - }; - - const round = lastRound + 1; - const { data: generatedPairings } = useGetDraftTournamentPairings(tournament ? { - method: pairingMethod === TournamentPairingMethod.AdjacentAlignment ? TournamentPairingMethod.Adjacent : pairingMethod, - tournamentId, - round, - options: pairingOptions, - } : 'skip'); + const [configuration, setConfiguration] = useState(null); + const { data: generatedPairings } = useGetDraftTournamentPairings(configuration ?? 'skip'); const { mutation: createTournamentPairings } = useCreateTournamentPairings({ onSuccess: (): void => { - toast.success(`Round ${round + 1} pairings created!`); + toast.success(`Round ${nextRound + 1} pairings created!`); navigate(`${generatePath(PATHS.tournamentDetails, { id: tournamentId })}?tab=pairings`); }, }); @@ -150,16 +135,6 @@ export const TournamentPairingsPage = (): JSX.Element => { updatePairings(items, form.reset); }; - const handleChangePairingMethod = (value: TournamentPairingMethod): void => { - if (form.formState.isDirty) { - openConfirmChangePairingMethodDialog({ - onConfirm: () => setPairingMethod(value), - }); - } else { - setPairingMethod(value); - } - }; - const handleReset = (): void => { if (generatedPairings) { if (form.formState.isDirty) { @@ -182,14 +157,18 @@ export const TournamentPairingsPage = (): JSX.Element => { }; const handleConfirm = async (pairings: DraftTournamentPairing[]): Promise => { - await createTournamentPairings({ tournamentId, round, pairings }); + await createTournamentPairings({ + tournamentId: tournament._id, + round: configuration?.round ?? 0, + pairings, + }); }; - const pairingStatuses = getPairingsStatuses(pairingOptions, tournamentCompetitors, pairings); + const pairingStatuses = configuration?.options ? getPairingsStatuses(configuration.options, tournamentCompetitors, pairings) : []; return ( {
- - setConfiguration(data)} />
- { pairings={pairings} competitors={tournamentCompetitors} onConfirm={handleConfirm} - /> + /> */}
diff --git a/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.module.scss b/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.module.scss new file mode 100644 index 00000000..eae1cd53 --- /dev/null +++ b/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.module.scss @@ -0,0 +1,3 @@ +.TournamentPairingOptionsForm { + display: grid; +} diff --git a/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.schema.ts b/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.schema.ts new file mode 100644 index 00000000..7c1dd417 --- /dev/null +++ b/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.schema.ts @@ -0,0 +1,38 @@ +import { z } from 'zod'; + +import { Tournament, TournamentId } from '~/api'; +import { emptyToUndefined } from '~/utils/emptyToUndefined'; + +export const createSchema = (tournament: Tournament) => z.object({ + tournamentId: z.string({ + message: 'Please select a tournament.', + }).transform((val) => val as TournamentId), + round: z.number().min(0).max(tournament.roundCount - 1), + method: z.union([z.literal('adjacent'), z.literal('random')]), + options: z.object({ + allowSameAlignment: emptyToUndefined(z.boolean().optional()), + allowRepeat: emptyToUndefined(z.boolean().optional()), + }), +}); + +export type SubmitData = z.infer>; + +export type FormData = { + tournamentId: TournamentId | null; + round: number; + method: 'adjacent' | 'random'; + details: { + allowSameAlignment: boolean; + allowRepeat: boolean; + }; +}; + +export const defaultValues: FormData = { + tournamentId: null, + round: 0, + method: 'random', + details: { + allowSameAlignment: true, + allowRepeat: false, + }, +}; diff --git a/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.tsx b/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.tsx new file mode 100644 index 00000000..7a0a1dca --- /dev/null +++ b/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.tsx @@ -0,0 +1,92 @@ +import { useEffect } from 'react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { Select } from '@ianpaschal/combat-command-components'; +import { TournamentPairingMethod } from '@ianpaschal/combat-command-game-systems/common'; +import clsx from 'clsx'; + +import { Tournament, TournamentId } from '~/api'; +import { Checkbox } from '~/components/generic/Checkbox'; +import { Form, FormField } from '~/components/generic/Form'; +import { validateForm } from '~/utils/validateForm'; +import { + createSchema, + defaultValues, + FormData, + SubmitData, +} from './TournamentPairingOptionsForm.schema'; + +import styles from './TournamentPairingOptionsForm.module.scss'; + +export interface TournamentPairingOptionsFormProps { + className?: string; + disabled?: boolean; + forcedValues?: Partial & { tournamentId: TournamentId }; + id?: string; + loading?: boolean; + onSubmit: (data: SubmitData) => void; + setDirty?: (dirty: boolean) => void; + tournament: Tournament; +} + +export const TournamentPairingOptionsForm = ({ + className, + disabled = false, + forcedValues, + id, + loading = false, + onSubmit, + setDirty, + tournament, +}: TournamentPairingOptionsFormProps): JSX.Element => { + const schema = createSchema(tournament); + const form = useForm({ + defaultValues: { + ...defaultValues, + ...forcedValues, + }, + mode: 'onSubmit', + }); + + const methodOptions = [ + { value: TournamentPairingMethod.Adjacent, label: 'Adjacent (Swiss)' }, + { value: TournamentPairingMethod.Random, label: 'Random' }, + ]; + + // Track form dirty state and notify parent + useEffect(() => { + setDirty?.(form.formState.isDirty); + }, [form.formState.isDirty, setDirty]); + + const handleSubmit: SubmitHandler = async (formData): Promise => { + const validFormData = validateForm(schema, formData, form.setError); + if (validFormData) { + onSubmit(validFormData); + } + }; + + const showLoading = [ + loading, + ].some((l) => !!l); + + return ( +
+ + + + + + + + + + - -
- - - - - - -
- - ); -}; diff --git a/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.utils.ts b/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.utils.ts deleted file mode 100644 index a042c516..00000000 --- a/src/pages/TournamentPairingsPage/components/TournamentPairingsForm/TournamentPairingOptionsForm.utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TournamentPairingMethod } from '@ianpaschal/combat-command-game-systems/common'; - -import { - Tournament, - TournamentId, - TournamentPairingOptions, -} from '~/api'; - -export type TournamentPairingArgs = { - method: TournamentPairingMethod; - options: TournamentPairingOptions; - round: number; - tournamentId: TournamentId; -}; - -export const getDefaultValues = (tournament: Tournament): TournamentPairingArgs => { - const lastRound = tournament?.lastRound ?? -1; - const isFirstRound = (tournament?.lastRound ?? -1) < 0; - const method = isFirstRound ? ( - TournamentPairingMethod.Random - ) : ( - tournament?.pairingMethod ?? TournamentPairingMethod.Adjacent - ); - - return { - tournamentId: tournament._id, - round: lastRound + 1, - method, - options: { - allowRepeats: false, - allowSameAlignment: method !== TournamentPairingMethod.AdjacentAlignment, - }, - - }; -}; diff --git a/src/services/tournamentPairings.ts b/src/services/tournamentPairings.ts index cc8115ef..c1b2665d 100644 --- a/src/services/tournamentPairings.ts +++ b/src/services/tournamentPairings.ts @@ -1,5 +1,9 @@ import { api } from '~/api'; -import { createMutationHook, createQueryHook } from '~/services/utils'; +import { + createActionHook, + createMutationHook, + createQueryHook, +} from '~/services/utils'; // Basic Queries export const useGetTournamentPairing = createQueryHook(api.tournamentPairings.getTournamentPairing); @@ -11,3 +15,7 @@ export const useGetDraftTournamentPairings = createQueryHook(api.tournamentPairi // Mutations export const useCreateTournamentPairings = createMutationHook(api.tournamentPairings.createTournamentPairings); + +// Actions +export const useGenerateDraftTournamentPairings = createActionHook(api.tournamentPairings.generateDraftTournamentPairings); +export const useGenerateTableAssignments = createActionHook(api.tournamentPairings.generateTableAssignments); From 1684c061796e2fe5c99a01fa1ee961eb04a41642 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Sun, 8 Feb 2026 21:36:53 +0100 Subject: [PATCH 03/17] Update convex/_model/tournamentPairings/_helpers/generateDraftPairings.test.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../tournamentPairings/_helpers/generateDraftPairings.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/convex/_model/tournamentPairings/_helpers/generateDraftPairings.test.ts b/convex/_model/tournamentPairings/_helpers/generateDraftPairings.test.ts index a0589ac6..a4d469a9 100644 --- a/convex/_model/tournamentPairings/_helpers/generateDraftPairings.test.ts +++ b/convex/_model/tournamentPairings/_helpers/generateDraftPairings.test.ts @@ -59,7 +59,7 @@ describe('generateDraftPairings', () => { it('Assigns a bye to the lowest-ranked competitor if all have had byes.', () => { // ---- Arrange ---- // All competitors have already had a bye: - competitors.forEach((p) => (p.byeRounds = [ 1 ])); + competitors.forEach((p) => { p.byeRounds = [ 1 ]; }); // ---- Act ---- const pairings = generateDraftPairings(competitors, defaultPolicies); From caac9f3b413f9fe385700eb26dc2f1e751b43a32 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Sun, 8 Feb 2026 21:38:19 +0100 Subject: [PATCH 04/17] Update convex/_model/tournamentPairings/_helpers/assignTables.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../_model/tournamentPairings/_helpers/assignTables.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/convex/_model/tournamentPairings/_helpers/assignTables.tsx b/convex/_model/tournamentPairings/_helpers/assignTables.tsx index cd4e958e..dbecce5e 100644 --- a/convex/_model/tournamentPairings/_helpers/assignTables.tsx +++ b/convex/_model/tournamentPairings/_helpers/assignTables.tsx @@ -76,10 +76,12 @@ export const assignTables = ( } // Step 2: Randomly assign tables to pairings that need them: + // Step 2: Randomly assign tables to pairings that need them: + const availableTablesList = Array.from(availableTables); for (let i = 0; i < autoAssignedPairings.length; i++) { - const randomTable = Array.from(availableTables)[Math.floor(Math.random() * availableTables.size)]; - autoAssignedPairings[i].table = randomTable; - availableTables.delete(randomTable); + const randomIndex = Math.floor(Math.random() * availableTablesList.length); + autoAssignedPairings[i].table = availableTablesList[randomIndex]; + availableTablesList.splice(randomIndex, 1); } // Step 3: Optimization pass - try to swap tables to avoid repeats: From d7b9a76538b34f43cb127a4b1ed275334c1e518a Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 9 Feb 2026 07:29:23 +0100 Subject: [PATCH 05/17] Update convex/_model/tournamentPairings/mutations/createTournamentPairings.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../tournamentPairings/mutations/createTournamentPairings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/convex/_model/tournamentPairings/mutations/createTournamentPairings.ts b/convex/_model/tournamentPairings/mutations/createTournamentPairings.ts index 5c20b82d..edab209d 100644 --- a/convex/_model/tournamentPairings/mutations/createTournamentPairings.ts +++ b/convex/_model/tournamentPairings/mutations/createTournamentPairings.ts @@ -77,7 +77,7 @@ export const createTournamentPairings = async ( if (!competitor) { throw new ConvexError(getErrorMessage('CANNOT_ADD_PAIRING_FOR_MISSING_COMPETITOR')); } - if (!competitor?.active) { + if (!competitor.active) { throw new ConvexError(getErrorMessage('CANNOT_ADD_PAIRING_FOR_INACTIVE_COMPETITOR')); } if (pairedCompetitorIds.has(id)) { From c1969bbe14e0af66ecfdfbd6bce12775288fc96b Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 9 Feb 2026 07:52:11 +0100 Subject: [PATCH 06/17] Update src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx b/src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx index 70e954bc..4f0373a1 100644 --- a/src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx +++ b/src/pages/TournamentPairingsPage/TournamentPairingsPage.tsx @@ -241,7 +241,7 @@ export const TournamentPairingsPage = (): JSX.Element => {
{fields.map((field, i) => { - const { status, message } = pairingStatuses[i]; + const { status, message } = pairingStatuses[i] ?? { status: 'ok' as const, message: '' }; const statusColors: Record = { 'error': 'red', 'warning': 'yellow', From 019fc5f9fe3cbfd95cb880afbaa2c79e9743420d Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 9 Feb 2026 07:53:40 +0100 Subject: [PATCH 07/17] Update convex/_model/tournamentPairings/_helpers/assignTables.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- convex/_model/tournamentPairings/_helpers/assignTables.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/convex/_model/tournamentPairings/_helpers/assignTables.tsx b/convex/_model/tournamentPairings/_helpers/assignTables.tsx index dbecce5e..b841dafa 100644 --- a/convex/_model/tournamentPairings/_helpers/assignTables.tsx +++ b/convex/_model/tournamentPairings/_helpers/assignTables.tsx @@ -76,7 +76,7 @@ export const assignTables = ( } // Step 2: Randomly assign tables to pairings that need them: - // Step 2: Randomly assign tables to pairings that need them: + const availableTablesList = Array.from(availableTables); const availableTablesList = Array.from(availableTables); for (let i = 0; i < autoAssignedPairings.length; i++) { const randomIndex = Math.floor(Math.random() * availableTablesList.length); From 7a5cb8c072953b2961e716b3652d1d945fb39c30 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 9 Feb 2026 07:58:04 +0100 Subject: [PATCH 08/17] PR Feedback --- .../{assignTables.tsx => assignTables.ts} | 1 - .../_helpers/deepenTournamentPairing.ts | 4 +- .../_helpers/generateDraftPairings.test.ts | 8 +- .../_helpers/generateDraftPairings.ts | 12 +- .../utils/createTestTournamentMatchResults.ts | 43 +-- .../components/PairingFields.tsx | 2 +- .../TournamentPairingConfigFields.hooks.ts | 2 +- .../TournamentPairingConfigFields.tsx | 11 +- .../ActiveTournament.utils.tsx | 4 +- .../TournamentPairingsPage.module.scss | 2 +- .../TournamentPairingsPage.tsx | 4 +- .../ConfirmPairingsDialog.module.scss | 54 ---- .../ConfirmPairingsDialog.tsx | 59 ---- .../ConfirmPairingsDialog.utils.test.ts | 279 ------------------ .../ConfirmPairingsDialog.utils.tsx | 152 ---------- .../components/ConfirmPairingsDialog/index.ts | 4 - 16 files changed, 33 insertions(+), 608 deletions(-) rename convex/_model/tournamentPairings/_helpers/{assignTables.tsx => assignTables.ts} (98%) delete mode 100644 src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.module.scss delete mode 100644 src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.tsx delete mode 100644 src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.test.ts delete mode 100644 src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/ConfirmPairingsDialog.utils.tsx delete mode 100644 src/pages/TournamentPairingsPage/components/ConfirmPairingsDialog/index.ts diff --git a/convex/_model/tournamentPairings/_helpers/assignTables.tsx b/convex/_model/tournamentPairings/_helpers/assignTables.ts similarity index 98% rename from convex/_model/tournamentPairings/_helpers/assignTables.tsx rename to convex/_model/tournamentPairings/_helpers/assignTables.ts index b841dafa..23a31d26 100644 --- a/convex/_model/tournamentPairings/_helpers/assignTables.tsx +++ b/convex/_model/tournamentPairings/_helpers/assignTables.ts @@ -77,7 +77,6 @@ export const assignTables = ( // Step 2: Randomly assign tables to pairings that need them: const availableTablesList = Array.from(availableTables); - const availableTablesList = Array.from(availableTables); for (let i = 0; i < autoAssignedPairings.length; i++) { const randomIndex = Math.floor(Math.random() * availableTablesList.length); autoAssignedPairings[i].table = availableTablesList[randomIndex]; diff --git a/convex/_model/tournamentPairings/_helpers/deepenTournamentPairing.ts b/convex/_model/tournamentPairings/_helpers/deepenTournamentPairing.ts index 6bdda174..802aa520 100644 --- a/convex/_model/tournamentPairings/_helpers/deepenTournamentPairing.ts +++ b/convex/_model/tournamentPairings/_helpers/deepenTournamentPairing.ts @@ -59,8 +59,8 @@ export const deepenTournamentPairing = async ( tournamentCompetitor0, tournamentCompetitor1, playerUserIds: [ - ...(tournamentCompetitor0?.registrations ?? []).map((r) => r.user?._id), - ...(tournamentCompetitor1?.registrations ?? []).map((r) => r.user?._id), + ...(tournamentCompetitor0?.registrations ?? []).map((r) => r.userId), + ...(tournamentCompetitor1?.registrations ?? []).map((r) => r.userId), ], submittedUserIds, matchResultsProgress: { diff --git a/convex/_model/tournamentPairings/_helpers/generateDraftPairings.test.ts b/convex/_model/tournamentPairings/_helpers/generateDraftPairings.test.ts index a4d469a9..3e20afa3 100644 --- a/convex/_model/tournamentPairings/_helpers/generateDraftPairings.test.ts +++ b/convex/_model/tournamentPairings/_helpers/generateDraftPairings.test.ts @@ -59,7 +59,9 @@ describe('generateDraftPairings', () => { it('Assigns a bye to the lowest-ranked competitor if all have had byes.', () => { // ---- Arrange ---- // All competitors have already had a bye: - competitors.forEach((p) => { p.byeRounds = [ 1 ]; }); + competitors.forEach((p) => { + p.byeRounds = [ 1 ]; + }); // ---- Act ---- const pairings = generateDraftPairings(competitors, defaultPolicies); @@ -82,7 +84,7 @@ describe('generateDraftPairings', () => { } }); - it('Does not allow repeat pairings by default.', () => { + it('Does not allow repeat pairings when repeat policy is Block.', () => { // ---- Act & Assert ---- expect(() => generateDraftPairings(competitors, defaultPolicies)) .toThrow(errors.NO_VALID_PAIRINGS_POSSIBLE_WITHOUT_REPEAT); @@ -120,7 +122,7 @@ describe('generateDraftPairings', () => { })).toThrow(errors.NO_VALID_PAIRINGS_POSSIBLE_WITHOUT_SAME_ALIGNMENT); }); - it('Does allow same alignment pairings by default.', () => { + it('Does allow same alignment pairings when sameAlignment policy is Allow.', () => { // ---- Act ---- const pairings = generateDraftPairings(competitors, defaultPolicies); diff --git a/convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts b/convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts index d34f67ee..9cc7d327 100644 --- a/convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts +++ b/convex/_model/tournamentPairings/_helpers/generateDraftPairings.ts @@ -24,7 +24,7 @@ export type CompetitorPair = [DeepTournamentCompetitor, DeepTournamentCompetitor */ export const generateDraftPairings = ( orderedCompetitors: DeepTournamentCompetitor[], - polices: TournamentPairingPolicies, + policies: TournamentPairingPolicies, ): CompetitorPair[] => { const pairings: CompetitorPair[] = []; @@ -35,15 +35,15 @@ export const generateDraftPairings = ( } // Resolve pairings by input order: - const resolvedPairings = recursivePair(restCompetitors, polices); + const resolvedPairings = recursivePair(restCompetitors, policies); if (resolvedPairings === null) { // NOTE: In principle these should never happen, but it's good to know if they do. // Check if allowing repeats would have worked: - if (polices.repeat !== TournamentPairingPolicy.Allow) { + if (policies.repeat !== TournamentPairingPolicy.Allow) { const withRepeats = recursivePair(restCompetitors, { - ...polices, + ...policies, repeat: TournamentPairingPolicy.Allow, }); if (withRepeats !== null) { @@ -52,9 +52,9 @@ export const generateDraftPairings = ( } // Check if allowing same alignment would have worked: - if (polices.sameAlignment !== TournamentPairingPolicy.Allow) { + if (policies.sameAlignment !== TournamentPairingPolicy.Allow) { const withSameAlignment = recursivePair(restCompetitors, { - ...polices, + ...policies, sameAlignment: TournamentPairingPolicy.Allow, }); if (withSameAlignment !== null) { diff --git a/convex/_model/utils/createTestTournamentMatchResults.ts b/convex/_model/utils/createTestTournamentMatchResults.ts index 9a15b46f..de9685e9 100644 --- a/convex/_model/utils/createTestTournamentMatchResults.ts +++ b/convex/_model/utils/createTestTournamentMatchResults.ts @@ -55,50 +55,21 @@ export const createTestTournamentMatchResults = async ( const playerData: Pick, 'player0UserId' | 'player1UserId' | 'player1Placeholder' | 'player0Placeholder'> = {}; for (const [index, id] of [pairing.tournamentCompetitor0Id, pairing.tournamentCompetitor1Id].entries()) { + let userId: Id<'users'> | undefined; if (id) { const registrations = await ctx.db.query('tournamentRegistrations') .withIndex('by_tournament_competitor', (q) => q.eq('tournamentCompetitorId', id)) .collect(); - const userId = registrations.filter((r) => ( + userId = registrations.filter((r) => ( r.active && !usedPlayerIds.includes(r.userId) )).map((r) => r.userId).pop(); - if (userId) { - playerData[`player${index}UserId` as 'player0UserId' | 'player1UserId'] = userId; - } else { - playerData[`player${index}Placeholder` as 'player0Placeholder' | 'player1Placeholder'] = 'Bye'; - } + } + if (userId) { + playerData[`player${index}UserId` as 'player0UserId' | 'player1UserId'] = userId; + } else { + playerData[`player${index}Placeholder` as 'player0Placeholder' | 'player1Placeholder'] = 'Bye'; } } - - // const competitor0Id = pairing.tournamentCompetitor0Id; - // if (competitor0Id) { - // const tournamentCompetitor0Registrations = await ctx.db.query('tournamentRegistrations') - // .withIndex('by_tournament_competitor', (q) => q.eq('tournamentCompetitorId', competitor0Id)) - // .collect(); - // const tournamentCompetitor0UserIds = tournamentCompetitor0Registrations.filter((r) => ( - // r.active && !usedPlayerIds.includes(r.userId) - // )).map((r) => r.userId); - // const player0UserId = tournamentCompetitor0UserIds.pop(); - // if (player0UserId) { - // playerData.player0UserId = player0UserId; - // } else { - // playerData.player0Placeholder = 'Bye'; - // } - // } - - // const competitor1Id = pairing.tournamentCompetitor1Id; - // if (competitor1Id) { - // const tournamentCompetitor1Registrations = await ctx.db.query('tournamentRegistrations') - // .withIndex('by_tournament_competitor', (q) => q.eq('tournamentCompetitorId', competitor1Id)) - // .collect(); - // const tournamentCompetitor1UserIds = tournamentCompetitor1Registrations.filter((r) => ( - // r.active && !usedPlayerIds.includes(r.userId) - // )).map((r) => r.userId); - // const player1UserId = tournamentCompetitor1UserIds.pop(); - // playerData.player1UserId = player1UserId; - // } else { - // playerData.player1Placeholder = 'Bye'; - // } // TODO: Replace with actual call to the create mutation const matchResultId = await ctx.db.insert('matchResults', { diff --git a/src/components/TournamentForm/components/PairingFields.tsx b/src/components/TournamentForm/components/PairingFields.tsx index 37824dfc..31fe5991 100644 --- a/src/components/TournamentForm/components/PairingFields.tsx +++ b/src/components/TournamentForm/components/PairingFields.tsx @@ -17,7 +17,7 @@ export const PairingFields = ({ status = 'draft', }: PairingFieldsProps): JSX.Element => { const { watch, setValue } = useFormContext(); - const { pairingConfig } = watch(); + const pairingConfig = watch('pairingConfig'); useEffect(() => { if (pairingConfig.policies.sameAlignment !== TournamentPairingPolicy.Allow) { diff --git a/src/components/TournamentPairingConfigForm/TournamentPairingConfigFields.hooks.ts b/src/components/TournamentPairingConfigForm/TournamentPairingConfigFields.hooks.ts index a5626a32..c8ee6764 100644 --- a/src/components/TournamentPairingConfigForm/TournamentPairingConfigFields.hooks.ts +++ b/src/components/TournamentPairingConfigForm/TournamentPairingConfigFields.hooks.ts @@ -21,7 +21,7 @@ export type UsePresetFieldResult = { export const usePresetField = ( path?: FieldPathByValue, ): UsePresetFieldResult => { - const { watch, setValue, reset } = useFormContext(); + const { watch, setValue, reset } = useFormContext(); const currentConfig = (path ? watch(path) : watch()) as unknown as TournamentPairingConfig; return { value: getTournamentPairingMethodByConfig(currentConfig), diff --git a/src/components/TournamentPairingConfigForm/TournamentPairingConfigFields.tsx b/src/components/TournamentPairingConfigForm/TournamentPairingConfigFields.tsx index d9ab3425..6f8b91d2 100644 --- a/src/components/TournamentPairingConfigForm/TournamentPairingConfigFields.tsx +++ b/src/components/TournamentPairingConfigForm/TournamentPairingConfigFields.tsx @@ -19,6 +19,11 @@ import { usePresetField } from './TournamentPairingConfigFields.hooks'; import styles from './TournamentPairingConfigFields.module.scss'; +const TABS = [ + { value: 'preset', label: 'Preset' }, + { value: 'advanced', label: 'Advanced' }, +]; + export interface TournamentPairingConfigFieldsProps { className?: string; disabled?: boolean; @@ -32,10 +37,6 @@ export const TournamentPairingConfigFields = ({ loading = false, path, }: TournamentPairingConfigFieldsProps): JSX.Element => { - const tabs = [ - { value: 'preset', label: 'Preset' }, - { value: 'advanced', label: 'Advanced' }, - ]; const [tab, setTab] = useState('preset'); const presetFieldProps = usePresetField(path); @@ -51,7 +52,7 @@ export const TournamentPairingConfigFields = ({ value={tab} onValueChange={setTab} > - + + + + - - - - - - - - + + + + + + + +