From 97b97dc8159fac33462ea13d77517f43370faa61 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 10 Mar 2024 14:42:27 +0200 Subject: [PATCH] Auto check in all feature Closes #1687 --- app/db/tables.ts | 1 + .../calendar/CalendarRepository.server.ts | 3 + .../calendar/calendar-schemas.server.ts | 1 + app/features/calendar/routes/calendar.new.tsx | 143 ++++++++++-------- .../routes/to.$id.brackets.tsx | 22 ++- .../tournament/TournamentRepository.server.ts | 16 +- 6 files changed, 117 insertions(+), 69 deletions(-) diff --git a/app/db/tables.ts b/app/db/tables.ts index 1c7b8bbb4..8750731ea 100644 --- a/app/db/tables.ts +++ b/app/db/tables.ts @@ -387,6 +387,7 @@ export interface TournamentSettings { teamsPerGroup?: number; thirdPlaceMatch?: boolean; isRanked?: boolean; + autoCheckInAll?: boolean; } export interface CastedMatchesInfo { diff --git a/app/features/calendar/CalendarRepository.server.ts b/app/features/calendar/CalendarRepository.server.ts index 2ca885019..87296fe05 100644 --- a/app/features/calendar/CalendarRepository.server.ts +++ b/app/features/calendar/CalendarRepository.server.ts @@ -386,6 +386,7 @@ type CreateArgs = Pick< bracketProgression: TournamentSettings["bracketProgression"] | null; teamsPerGroup?: number; thirdPlaceMatch?: boolean; + autoCheckInAll?: boolean; isRanked?: boolean; }; export async function create(args: CreateArgs) { @@ -398,6 +399,7 @@ export async function create(args: CreateArgs) { teamsPerGroup: args.teamsPerGroup, thirdPlaceMatch: args.thirdPlaceMatch, isRanked: args.isRanked, + autoCheckInAll: args.autoCheckInAll, }; tournamentId = ( @@ -468,6 +470,7 @@ export async function update(args: UpdateArgs) { teamsPerGroup: args.teamsPerGroup, thirdPlaceMatch: args.thirdPlaceMatch, isRanked: args.isRanked, + autoCheckInAll: args.autoCheckInAll, }; await trx diff --git a/app/features/calendar/calendar-schemas.server.ts b/app/features/calendar/calendar-schemas.server.ts index e8c7a233c..e8dcbeb58 100644 --- a/app/features/calendar/calendar-schemas.server.ts +++ b/app/features/calendar/calendar-schemas.server.ts @@ -80,6 +80,7 @@ export const newCalendarEventActionSchema = z checkboxValueToBoolean, z.boolean().nullish(), ), + autoCheckInAll: z.preprocess(checkboxValueToBoolean, z.boolean().nullish()), teamsPerGroup: z.coerce .number() .min(TOURNAMENT.MIN_GROUP_SIZE) diff --git a/app/features/calendar/routes/calendar.new.tsx b/app/features/calendar/routes/calendar.new.tsx index 5b74e60ab..7243839da 100644 --- a/app/features/calendar/routes/calendar.new.tsx +++ b/app/features/calendar/routes/calendar.new.tsx @@ -118,6 +118,7 @@ export const action: ActionFunction = async ({ request }) => { teamsPerGroup: data.teamsPerGroup ?? undefined, thirdPlaceMatch: data.thirdPlaceMatch ?? undefined, isRanked: data.isRanked ?? undefined, + autoCheckInAll: data.autoCheckInAll ?? undefined, }; validate( !commonArgs.toToolsEnabled || commonArgs.bracketProgression, @@ -892,19 +893,6 @@ function TournamentFormatSelector() { ) : null} - {format === "RR_TO_SE" ? ( -
- - -
- ) : null} - {format === "RR_TO_SE" ? (
@@ -922,6 +910,20 @@ function TournamentFormatSelector() {
) : null} + + {format === "RR_TO_SE" ? ( +
+ + +
+ ) : null} + {format === "RR_TO_SE" ? ( ) : null} @@ -931,6 +933,9 @@ function TournamentFormatSelector() { function FollowUpBrackets({ teamsPerGroup }: { teamsPerGroup: number }) { const data = useLoaderData(); + const [autoCheckInAll, setAutoCheckInAll] = React.useState( + data.tournamentCtx?.settings.autoCheckInAll ?? false, + ); const [_brackets, setBrackets] = React.useState>( () => { if ( @@ -958,56 +963,76 @@ function FollowUpBrackets({ teamsPerGroup }: { teamsPerGroup: number }) { const validationErrorMsg = validateFollowUpBrackets(brackets, teamsPerGroup); return ( -
- - -
- {brackets.map((b, i) => ( - { - setBrackets( - brackets.map((oldBracket, j) => - j === i ? newBracket : oldBracket, - ), - ); - }} - bracket={b} - nth={i + 1} + <> + {brackets.length > 1 ? ( +
+ + - ))} -
- - + + If disabled, the only follow-up bracket with automatic check-in is + the top cut +
+ ) : null} +
+ + +
+ {brackets.map((b, i) => ( + { + setBrackets( + brackets.map((oldBracket, j) => + j === i ? newBracket : oldBracket, + ), + ); + }} + bracket={b} + nth={i + 1} + /> + ))} +
+ + +
- {validationErrorMsg ? ( - {validationErrorMsg} - ) : null} + {validationErrorMsg ? ( + {validationErrorMsg} + ) : null} +
-
+ ); } diff --git a/app/features/tournament-bracket/routes/to.$id.brackets.tsx b/app/features/tournament-bracket/routes/to.$id.brackets.tsx index 6977eb5ed..c071a4738 100644 --- a/app/features/tournament-bracket/routes/to.$id.brackets.tsx +++ b/app/features/tournament-bracket/routes/to.$id.brackets.tsx @@ -31,7 +31,7 @@ import * as TournamentRepository from "~/features/tournament/TournamentRepositor import { HACKY_isInviteOnlyEvent } from "~/features/tournament/tournament-utils"; import { useSearchParamState } from "~/hooks/useSearchParamState"; import { useVisibilityChange } from "~/hooks/useVisibilityChange"; -import { removeDuplicates } from "~/utils/arrays"; +import { nullFilledArray, removeDuplicates } from "~/utils/arrays"; import { parseRequestFormData, validate } from "~/utils/remix"; import { assertUnreachable } from "~/utils/types"; import { @@ -113,8 +113,17 @@ export const action: ActionFunction = async ({ params, request }) => { const finalStageIdx = tournament.brackets.findIndex((b) => b.isFinals); if (finalStageIdx !== -1) { + const allFollowUpBracketIdxs = nullFilledArray( + tournament.brackets.length, + ) + .map((_, i) => i) + // filter out groups stage + .filter((i) => i !== 0); + await TournamentRepository.checkInMany({ - bracketIdx: finalStageIdx, + bracketIdxs: tournament.ctx.settings.autoCheckInAll + ? allFollowUpBracketIdxs + : [finalStageIdx], tournamentTeamIds: tournament.ctx.teams .filter((t) => t.checkIns.length > 0) .map((t) => t.id), @@ -259,7 +268,7 @@ export default function TournamentBracketsPage() { ) { return `Teams that get eliminated in the first ${Math.abs( Math.min(...(bracket.sources ?? []).flatMap((s) => s.placements)), - )} rounds of the losers bracket can play in this bracket (optional)`; + )} rounds of the losers bracket can play in this bracket`; } return null; @@ -358,6 +367,13 @@ export default function TournamentBracketsPage() { {teamsSourceText()}
) : null} + {bracket.sources && + bracket.sources.every((s) => !s.placements.includes(1)) && + !tournament.ctx.settings.autoCheckInAll ? ( +
+ Bracket requires check-in +
+ ) : null}
) : null} diff --git a/app/features/tournament/TournamentRepository.server.ts b/app/features/tournament/TournamentRepository.server.ts index a1818713b..9d0ceb529 100644 --- a/app/features/tournament/TournamentRepository.server.ts +++ b/app/features/tournament/TournamentRepository.server.ts @@ -333,19 +333,21 @@ export function checkOut({ export function checkInMany({ tournamentTeamIds, - bracketIdx, + bracketIdxs, }: { tournamentTeamIds: number[]; - bracketIdx: number; + bracketIdxs: number[]; }) { return db .insertInto("TournamentTeamCheckIn") .values( - tournamentTeamIds.map((tournamentTeamId) => ({ - checkedInAt: dateToDatabaseTimestamp(new Date()), - tournamentTeamId, - bracketIdx, - })), + tournamentTeamIds.flatMap((tournamentTeamId) => + bracketIdxs.map((bracketIdx) => ({ + checkedInAt: dateToDatabaseTimestamp(new Date()), + tournamentTeamId, + bracketIdx, + })), + ), ) .execute(); }