From a3a44aacf4e3a55c5b3b7002eb519615ed68bda1 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sun, 20 Sep 2026 11:27:25 +0300 Subject: [PATCH] Tournament team-picked map lists and check-ins fixes --- .../calendar/CalendarRepository.server.ts | 51 +++++++++++++++---- .../actions/calendar.new.server.test.ts | 37 ++++++++++++++ app/features/calendar/routes/calendar.new.tsx | 17 ++++--- .../tournament/routes/to.$id.register.tsx | 7 ++- locales/da/tournament.json | 1 + locales/de/tournament.json | 1 + locales/en/forms.json | 2 +- locales/en/tournament.json | 1 + locales/es-ES/tournament.json | 1 + locales/es-US/tournament.json | 1 + locales/fr-CA/tournament.json | 1 + locales/fr-EU/tournament.json | 1 + locales/he/tournament.json | 1 + locales/it/tournament.json | 1 + locales/ja/tournament.json | 1 + locales/ko/tournament.json | 1 + locales/nl/tournament.json | 1 + locales/pl/tournament.json | 1 + locales/pt-BR/tournament.json | 1 + locales/ru/tournament.json | 1 + locales/zh/tournament.json | 1 + 21 files changed, 113 insertions(+), 17 deletions(-) diff --git a/app/features/calendar/CalendarRepository.server.ts b/app/features/calendar/CalendarRepository.server.ts index b18bfe004..8aa12442b 100644 --- a/app/features/calendar/CalendarRepository.server.ts +++ b/app/features/calendar/CalendarRepository.server.ts @@ -815,15 +815,7 @@ async function updateTournamentTables( // the teams' picks were made against the old settings, so they pick again if (changedMapPickingStyle || changedMapPool) { - await trx - .deleteFrom("MapPoolMap") - .where("tournamentTeamId", "in", (eb) => - eb - .selectFrom("TournamentTeam") - .select("id") - .where("tournamentId", "=", tournamentId), - ) - .execute(); + await resetTeamMapPicks({ tournamentId, args }, trx); } if ( @@ -841,6 +833,47 @@ async function updateTournamentTables( } } +/** + * Deletes every team's map picks. Teams that had picked are also checked out when picks are still + * a check-in requirement, as otherwise they would enter the bracket without a pool. + */ +async function resetTeamMapPicks( + { + tournamentId, + args, + }: { tournamentId: number; args: Pick }, + trx: Transaction, +) { + // before the picks go, as the teams to check out are the ones that have them + if (args.mapPickingStyle !== "TO") { + await trx + .deleteFrom("TournamentTeamCheckIn") + .where("bracketIdx", "is", null) + .where("tournamentTeamId", "in", (eb) => + eb + .selectFrom("MapPoolMap") + .innerJoin( + "TournamentTeam", + "TournamentTeam.id", + "MapPoolMap.tournamentTeamId", + ) + .select("TournamentTeam.id") + .where("TournamentTeam.tournamentId", "=", tournamentId), + ) + .execute(); + } + + await trx + .deleteFrom("MapPoolMap") + .where("tournamentTeamId", "in", (eb) => + eb + .selectFrom("TournamentTeam") + .select("id") + .where("tournamentId", "=", tournamentId), + ) + .execute(); +} + function teamPickSettings( args: Pick, ) { diff --git a/app/features/calendar/actions/calendar.new.server.test.ts b/app/features/calendar/actions/calendar.new.server.test.ts index 97a226b9f..9187f60ea 100644 --- a/app/features/calendar/actions/calendar.new.server.test.ts +++ b/app/features/calendar/actions/calendar.new.server.test.ts @@ -3,6 +3,7 @@ import { beforeEach, describe, expect, test } from "vitest"; import * as BadgeFactory from "~/db/seed/factories/BadgeFactory"; import * as TournamentFactory from "~/db/seed/factories/TournamentFactory"; import * as TournamentOrganizationFactory from "~/db/seed/factories/TournamentOrganizationFactory"; +import * as TournamentTeamFactory from "~/db/seed/factories/TournamentTeamFactory"; import * as UserFactory from "~/db/seed/factories/UserFactory"; import * as CalendarRepository from "~/features/calendar/CalendarRepository.server"; import { MapPool } from "~/features/map-list-generator/core/map-pool"; @@ -21,6 +22,8 @@ const editAction = wrappedAction({ const users = UserFactory.pool(); const badgeManagingAuthorId = () => users.id(1); +const teamPickAuthorId = () => users.id(5); +const teamPickTeamMemberIds = () => users.ids(4); describe("calendar new action: editing an event with badge prizes", () => { let orgAdminId: number; @@ -168,6 +171,7 @@ describe("calendar new action: bracket URL", () => { describe("calendar new action: team picked tournament", () => { beforeEach(async () => { await UserFactory.createRegular(null, { roles: ["TOURNAMENT_ORGANIZER"] }); + await users.create(5); }); test("saves the team pick settings and the custom pool", async () => { @@ -211,4 +215,37 @@ describe("calendar new action: team picked tournament", () => { customPool.serialized, ); }); + + test("changing the pick settings resets the teams' picks and checks them out", async () => { + const tournament = await TournamentFactory.create({ + authorId: teamPickAuthorId(), + mapPickingStyle: "AUTO", + teamPick: { modes: [{ mode: "SZ", count: 2 }], pool: "ALL" }, + }); + await TournamentTeamFactory.create( + { + tournamentId: tournament.id, + memberUserIds: teamPickTeamMemberIds(), + mapPool: new MapPool({ ...MapPool.EMPTY.parsed, SZ: [1, 2] }), + }, + { isCheckedIn: true }, + ); + + const res = await editAction( + calendarNewFormValues({ + eventToEditId: tournament.eventId, + bracketUrl: "https://sendou.ink", + mapPickingStyle: "AUTO", + teamPickModes: ["SZ"], + teamPickCounts: [{ mode: "SZ", count: 3 }], + teamPickPool: "ALL", + }), + { user: teamPickAuthorId() }, + ); + expect(res.fieldErrors).toBeUndefined(); + + const edited = await tournamentFromDB(tournament.id); + expect(edited.ctx.teams[0].hasMapPool).toBe(0); + expect(edited.ctx.teams[0].checkIns).toEqual([]); + }); }); diff --git a/app/features/calendar/routes/calendar.new.tsx b/app/features/calendar/routes/calendar.new.tsx index 02f99875e..4e97dd3fa 100644 --- a/app/features/calendar/routes/calendar.new.tsx +++ b/app/features/calendar/routes/calendar.new.tsx @@ -601,13 +601,18 @@ function TeamPickCountInputs({ value={count} onChange={(e) => onChange( - pickedModes.map((m) => ({ - mode: m, - count: + // an emptied input drops the entry so the mode falls back to the default count + pickedModes.flatMap((m) => { + const newCount = m === mode - ? Number(e.target.value) - : (value.find((c) => c.mode === m)?.count ?? 1), - })), + ? Number.parseInt(e.target.value, 10) + : value.find((c) => c.mode === m)?.count; + + return typeof newCount === "number" && + !Number.isNaN(newCount) + ? [{ mode: m, count: newCount }] + : []; + }), mode, ) } diff --git a/app/features/tournament/routes/to.$id.register.tsx b/app/features/tournament/routes/to.$id.register.tsx index 00004b7ff..2e8942b4c 100644 --- a/app/features/tournament/routes/to.$id.register.tsx +++ b/app/features/tournament/routes/to.$id.register.tsx @@ -118,7 +118,12 @@ export default function TournamentRegisterPage() { return (
- {isRegularMemberOfATeam ? ( + {tournament.hasStarted && teamMemberOf ? ( +
+ {t("tournament:pre.startedNoEdit")} + +
+ ) : isRegularMemberOfATeam ? (
{t("tournament:pre.captainOnlyEdit")}
diff --git a/locales/da/tournament.json b/locales/da/tournament.json index 0808ecfe6..c41aa9ba4 100644 --- a/locales/da/tournament.json +++ b/locales/da/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "Log in for at tilmelde dig", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/de/tournament.json b/locales/de/tournament.json index 6a5a652ca..c6876e51e 100644 --- a/locales/de/tournament.json +++ b/locales/de/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "Logge dich zum Registrieren ein", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/en/forms.json b/locales/en/forms.json index fd85b652b..952febfbe 100644 --- a/locales/en/forms.json +++ b/locales/en/forms.json @@ -362,7 +362,7 @@ "options.teamPickPool.SENDOUQ": "SendouQ legal maps", "options.teamPickPool.ALL": "All maps", "options.teamPickPool.CUSTOM": "Custom", - "bottomTexts.teamPickReset": "Changing the map settings resets the maps every team has already picked", + "bottomTexts.teamPickReset": "Changing the map settings resets the maps every team has already picked and checks those teams out", "bottomTexts.regClosesAt": "All times relative to the reported tournament start time e.g. \"30 minutes\" means \"30 minutes before event start time\". After registration closes only TO's can make changes to team rosters.", "bottomTexts.maxTeamSize": "Maximum number of players that can be registered per team. Doesn't apply to tournament organizers. Default is 6.", "bottomTexts.avatarValidation": "Note that for non-patrons there is a validation process before avatar is shown.", diff --git a/locales/en/tournament.json b/locales/en/tournament.json index 709d9865b..4731821fa 100644 --- a/locales/en/tournament.json +++ b/locales/en/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "To play tournaments on sendou.ink you'll need to register your friend code.", "pre.logIn": "Log in to register", "pre.captainOnlyEdit": "You are in a team for this event. Only the team captain can edit the registration.", + "pre.startedNoEdit": "The tournament has started. Registration can no longer be edited.", "pre.leave.button": "Leave the team", "pre.leave.confirm": "Leave \"{{teamName}}\"?", "pre.leave.cant.organizerAdded": "You were added to the team by the organizer. Contact the TO to leave the team.", diff --git a/locales/es-ES/tournament.json b/locales/es-ES/tournament.json index ec9c7932b..af225a554 100644 --- a/locales/es-ES/tournament.json +++ b/locales/es-ES/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "Inicia sesión para inscribirte", "pre.captainOnlyEdit": "Estás en un equipo para este evento. Solo el capitán del equipo puede editar la inscripción.", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/es-US/tournament.json b/locales/es-US/tournament.json index 3796fbfa1..900ffc8ed 100644 --- a/locales/es-US/tournament.json +++ b/locales/es-US/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "Inicia sesión para inscribirte", "pre.captainOnlyEdit": "Estás en un equipo para este evento. Solo el capitán del equipo puede editar la inscripción.", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/fr-CA/tournament.json b/locales/fr-CA/tournament.json index ca6713a0b..ff69377ec 100644 --- a/locales/fr-CA/tournament.json +++ b/locales/fr-CA/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "Connectez-vous pour vous inscrire", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/fr-EU/tournament.json b/locales/fr-EU/tournament.json index 067c4c04f..a580500c5 100644 --- a/locales/fr-EU/tournament.json +++ b/locales/fr-EU/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "Connectez-vous pour vous inscrire", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/he/tournament.json b/locales/he/tournament.json index d75ed43a7..10efa0241 100644 --- a/locales/he/tournament.json +++ b/locales/he/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "התחבר כדי להירשם", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/it/tournament.json b/locales/it/tournament.json index 345b74364..dd4129d29 100644 --- a/locales/it/tournament.json +++ b/locales/it/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "Accedi per iscriverti", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/ja/tournament.json b/locales/ja/tournament.json index 908b12c22..a5a33ba6c 100644 --- a/locales/ja/tournament.json +++ b/locales/ja/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "ログインして登録する", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/ko/tournament.json b/locales/ko/tournament.json index 1513d6fdf..7fca89573 100644 --- a/locales/ko/tournament.json +++ b/locales/ko/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/nl/tournament.json b/locales/nl/tournament.json index b5b0f9192..68d7f0cf6 100644 --- a/locales/nl/tournament.json +++ b/locales/nl/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/pl/tournament.json b/locales/pl/tournament.json index 9bd27f782..39f020161 100644 --- a/locales/pl/tournament.json +++ b/locales/pl/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/pt-BR/tournament.json b/locales/pt-BR/tournament.json index 0873dd448..56c494378 100644 --- a/locales/pt-BR/tournament.json +++ b/locales/pt-BR/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "Faça o login para registrar", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/ru/tournament.json b/locales/ru/tournament.json index 1af6e3b48..8a18f52c2 100644 --- a/locales/ru/tournament.json +++ b/locales/ru/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "Войдите, чтобы зарегистрироваться", "pre.captainOnlyEdit": "", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "", diff --git a/locales/zh/tournament.json b/locales/zh/tournament.json index ff20e860c..6375c0b51 100644 --- a/locales/zh/tournament.json +++ b/locales/zh/tournament.json @@ -37,6 +37,7 @@ "pre.friendCode.needed": "", "pre.logIn": "登录以报名", "pre.captainOnlyEdit": "您已加入此赛事的某支队伍。只有队长可以修改报名信息。", + "pre.startedNoEdit": "", "pre.leave.button": "", "pre.leave.confirm": "", "pre.leave.cant.organizerAdded": "",