mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Auto check in all feature Closes #1687
This commit is contained in:
parent
6e95763b17
commit
97b97dc815
|
|
@ -387,6 +387,7 @@ export interface TournamentSettings {
|
|||
teamsPerGroup?: number;
|
||||
thirdPlaceMatch?: boolean;
|
||||
isRanked?: boolean;
|
||||
autoCheckInAll?: boolean;
|
||||
}
|
||||
|
||||
export interface CastedMatchesInfo {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
{format === "RR_TO_SE" ? (
|
||||
<div>
|
||||
<Label htmlFor="thirdPlaceMatch">Third place match</Label>
|
||||
<Toggle
|
||||
checked={thirdPlaceMatch}
|
||||
setChecked={setThirdPlaceMatch}
|
||||
name="thirdPlaceMatch"
|
||||
id="thirdPlaceMatch"
|
||||
tiny
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{format === "RR_TO_SE" ? (
|
||||
<div>
|
||||
<Label htmlFor="teamsPerGroup">Teams per group</Label>
|
||||
|
|
@ -922,6 +910,20 @@ function TournamentFormatSelector() {
|
|||
</select>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{format === "RR_TO_SE" ? (
|
||||
<div>
|
||||
<Label htmlFor="thirdPlaceMatch">Third place match</Label>
|
||||
<Toggle
|
||||
checked={thirdPlaceMatch}
|
||||
setChecked={setThirdPlaceMatch}
|
||||
name="thirdPlaceMatch"
|
||||
id="thirdPlaceMatch"
|
||||
tiny
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{format === "RR_TO_SE" ? (
|
||||
<FollowUpBrackets teamsPerGroup={teamsPerGroup} />
|
||||
) : null}
|
||||
|
|
@ -931,6 +933,9 @@ function TournamentFormatSelector() {
|
|||
|
||||
function FollowUpBrackets({ teamsPerGroup }: { teamsPerGroup: number }) {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const [autoCheckInAll, setAutoCheckInAll] = React.useState(
|
||||
data.tournamentCtx?.settings.autoCheckInAll ?? false,
|
||||
);
|
||||
const [_brackets, setBrackets] = React.useState<Array<FollowUpBracket>>(
|
||||
() => {
|
||||
if (
|
||||
|
|
@ -958,56 +963,76 @@ function FollowUpBrackets({ teamsPerGroup }: { teamsPerGroup: number }) {
|
|||
const validationErrorMsg = validateFollowUpBrackets(brackets, teamsPerGroup);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RequiredHiddenInput
|
||||
isValid={!validationErrorMsg}
|
||||
name="followUpBrackets"
|
||||
value={JSON.stringify(brackets)}
|
||||
/>
|
||||
<Label>Follow-up brackets</Label>
|
||||
<div className="stack lg">
|
||||
{brackets.map((b, i) => (
|
||||
<FollowUpBracketInputs
|
||||
key={i}
|
||||
teamsPerGroup={teamsPerGroup}
|
||||
onChange={(newBracket) => {
|
||||
setBrackets(
|
||||
brackets.map((oldBracket, j) =>
|
||||
j === i ? newBracket : oldBracket,
|
||||
),
|
||||
);
|
||||
}}
|
||||
bracket={b}
|
||||
nth={i + 1}
|
||||
<>
|
||||
{brackets.length > 1 ? (
|
||||
<div>
|
||||
<Label htmlFor="autoCheckInAll">
|
||||
Auto check-in to follow-up brackets
|
||||
</Label>
|
||||
<Toggle
|
||||
checked={autoCheckInAll}
|
||||
setChecked={setAutoCheckInAll}
|
||||
name="autoCheckInAll"
|
||||
id="autoCheckInAll"
|
||||
tiny
|
||||
/>
|
||||
))}
|
||||
<div className="stack sm horizontal">
|
||||
<Button
|
||||
size="tiny"
|
||||
onClick={() => {
|
||||
setBrackets([...brackets, { name: "", placements: [] }]);
|
||||
}}
|
||||
data-testid="add-bracket"
|
||||
>
|
||||
Add bracket
|
||||
</Button>
|
||||
<Button
|
||||
size="tiny"
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
setBrackets(brackets.slice(0, -1));
|
||||
}}
|
||||
disabled={brackets.length === 1}
|
||||
>
|
||||
Remove bracket
|
||||
</Button>
|
||||
<FormMessage type="info">
|
||||
If disabled, the only follow-up bracket with automatic check-in is
|
||||
the top cut
|
||||
</FormMessage>
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<RequiredHiddenInput
|
||||
isValid={!validationErrorMsg}
|
||||
name="followUpBrackets"
|
||||
value={JSON.stringify(brackets)}
|
||||
/>
|
||||
<Label>Follow-up brackets</Label>
|
||||
<div className="stack lg">
|
||||
{brackets.map((b, i) => (
|
||||
<FollowUpBracketInputs
|
||||
key={i}
|
||||
teamsPerGroup={teamsPerGroup}
|
||||
onChange={(newBracket) => {
|
||||
setBrackets(
|
||||
brackets.map((oldBracket, j) =>
|
||||
j === i ? newBracket : oldBracket,
|
||||
),
|
||||
);
|
||||
}}
|
||||
bracket={b}
|
||||
nth={i + 1}
|
||||
/>
|
||||
))}
|
||||
<div className="stack sm horizontal">
|
||||
<Button
|
||||
size="tiny"
|
||||
onClick={() => {
|
||||
setBrackets([...brackets, { name: "", placements: [] }]);
|
||||
}}
|
||||
data-testid="add-bracket"
|
||||
>
|
||||
Add bracket
|
||||
</Button>
|
||||
<Button
|
||||
size="tiny"
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
setBrackets(brackets.slice(0, -1));
|
||||
}}
|
||||
disabled={brackets.length === 1}
|
||||
>
|
||||
Remove bracket
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{validationErrorMsg ? (
|
||||
<FormMessage type="error">{validationErrorMsg}</FormMessage>
|
||||
) : null}
|
||||
{validationErrorMsg ? (
|
||||
<FormMessage type="error">{validationErrorMsg}</FormMessage>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
</div>
|
||||
) : null}
|
||||
{bracket.sources &&
|
||||
bracket.sources.every((s) => !s.placements.includes(1)) &&
|
||||
!tournament.ctx.settings.autoCheckInAll ? (
|
||||
<div className="text-center text-sm font-semi-bold text-lighter mt-2 text-warning">
|
||||
Bracket requires check-in
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user