mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-15 23:41:53 -05:00
Allow reporting weapons after tournament has finalized
This commit is contained in:
parent
a5e69273c7
commit
ee6e92967b
|
|
@ -10,6 +10,7 @@ import {
|
|||
import {
|
||||
modesIncluded,
|
||||
sortTeamsBySeeding,
|
||||
tournamentInWeaponReportingWindow,
|
||||
tournamentIsRanked,
|
||||
} from "~/features/tournament/tournament-utils";
|
||||
import type * as Progression from "~/features/tournament-bracket/core/Progression";
|
||||
|
|
@ -895,6 +896,16 @@ export class Tournament {
|
|||
return this.registrationClosesAt > new Date();
|
||||
}
|
||||
|
||||
/** Can participants submit/undo their own weapon reports right now?
|
||||
* Always open while the tournament is running; once finalized it stays open only for tournaments
|
||||
* whose startTime is inside the current-season-plus-adjacent-off-season window. */
|
||||
get weaponReportingOpen() {
|
||||
if (!this.ctx.isFinalized) return true;
|
||||
return tournamentInWeaponReportingWindow({
|
||||
tournamentStartTime: this.ctx.startTime,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this tournament have autonomous subs feature enabled?
|
||||
* If enabled, teams can add members to their roster while tournament is in progress without having to request the organizer to do it.
|
||||
|
|
|
|||
|
|
@ -774,7 +774,10 @@ export const action: ActionFunction = async ({ params, request }) => {
|
|||
(p) => p.id === user.id,
|
||||
);
|
||||
errorToastIfFalsy(isMemberOfATeamInTheMatch, "Unauthorized");
|
||||
errorToastIfFalsy(!tournament.ctx.isFinalized, "Tournament is finalized");
|
||||
errorToastIfFalsy(
|
||||
tournament.weaponReportingOpen,
|
||||
"Weapon reporting is closed",
|
||||
);
|
||||
|
||||
await ReportedWeaponRepository.upsertOneTournament({
|
||||
tournamentMatchId: matchId,
|
||||
|
|
@ -790,7 +793,10 @@ export const action: ActionFunction = async ({ params, request }) => {
|
|||
(p) => p.id === user.id,
|
||||
);
|
||||
errorToastIfFalsy(isMemberOfATeamInTheMatch, "Unauthorized");
|
||||
errorToastIfFalsy(!tournament.ctx.isFinalized, "Tournament is finalized");
|
||||
errorToastIfFalsy(
|
||||
tournament.weaponReportingOpen,
|
||||
"Weapon reporting is closed",
|
||||
);
|
||||
|
||||
await ReportedWeaponRepository.deleteByUserMapIndexTournament({
|
||||
tournamentMatchId: matchId,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export function TournamentMatchActionTab({
|
|||
const weaponReport = useTournamentWeaponReport({
|
||||
data,
|
||||
viewerUserId: user?.id,
|
||||
weaponReportingOpen: tournament.weaponReportingOpen,
|
||||
});
|
||||
|
||||
if (!currentMap) {
|
||||
|
|
@ -152,9 +153,11 @@ export function TournamentMatchActionTab({
|
|||
function useTournamentWeaponReport({
|
||||
data,
|
||||
viewerUserId,
|
||||
weaponReportingOpen,
|
||||
}: {
|
||||
data: TournamentMatchLoaderData;
|
||||
viewerUserId: number | undefined;
|
||||
weaponReportingOpen: boolean;
|
||||
}) {
|
||||
const playOrderMaps = (data.mapList ?? []).filter(
|
||||
(m) => !m.bannedByTournamentTeamId,
|
||||
|
|
@ -177,6 +180,7 @@ function useTournamentWeaponReport({
|
|||
});
|
||||
|
||||
if (viewerUserId === undefined) return null;
|
||||
if (!weaponReportingOpen) return null;
|
||||
|
||||
const isParticipant = data.match.players.some((p) => p.id === viewerUserId);
|
||||
if (!isParticipant) return null;
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export function MatchPageProvider({
|
|||
user,
|
||||
}),
|
||||
canReportWeapons:
|
||||
isParticipant && !tournament.ctx.isFinalized && hasReportedMaps,
|
||||
isParticipant && tournament.weaponReportingOpen && hasReportedMaps,
|
||||
canJoin: data.canJoin,
|
||||
hasCurrentMap: Boolean(currentMap),
|
||||
hasMissingActiveRoster: teamsMissingActiveRoster.length > 0,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { CastedMatchesInfo } from "~/db/tables";
|
||||
import * as Seasons from "../mmr/core/Seasons";
|
||||
import type { ParsedBracket } from "../tournament-bracket/core/Progression";
|
||||
import {
|
||||
compareTeamsForOrdering,
|
||||
|
|
@ -7,6 +8,7 @@ import {
|
|||
getBracketProgressionLabel,
|
||||
sortTeamsBySeeding,
|
||||
type TeamForOrdering,
|
||||
tournamentInWeaponReportingWindow,
|
||||
updatedCastedMatchesInfo,
|
||||
} from "./tournament-utils";
|
||||
|
||||
|
|
@ -642,3 +644,43 @@ describe("updatedCastedMatchesInfo", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("tournamentInWeaponReportingWindow", () => {
|
||||
const anchorSeason = Seasons.list[2]!;
|
||||
const previousSeason = Seasons.list[1]!;
|
||||
|
||||
const dateInside = (range: { starts: Date; ends: Date }) =>
|
||||
new Date((range.starts.getTime() + range.ends.getTime()) / 2);
|
||||
|
||||
const inSeasonNow = dateInside(anchorSeason);
|
||||
const offSeasonNow = new Date(
|
||||
(previousSeason.ends.getTime() + anchorSeason.starts.getTime()) / 2,
|
||||
);
|
||||
|
||||
it("allows tournaments started in the off-season before current season (in-season)", () => {
|
||||
expect(
|
||||
tournamentInWeaponReportingWindow({
|
||||
tournamentStartTime: offSeasonNow,
|
||||
now: inSeasonNow,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects tournaments started before the previous season ended (in-season)", () => {
|
||||
expect(
|
||||
tournamentInWeaponReportingWindow({
|
||||
tournamentStartTime: dateInside(previousSeason),
|
||||
now: inSeasonNow,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("allows tournaments started during the previous season (off-season)", () => {
|
||||
expect(
|
||||
tournamentInWeaponReportingWindow({
|
||||
tournamentStartTime: dateInside(previousSeason),
|
||||
now: offSeasonNow,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -159,6 +159,31 @@ export function tournamentIsRanked({
|
|||
return isSetAsRanked ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a tournament's startTime falls inside the active weapon-reporting window
|
||||
* for late (post-finalization) reporting.
|
||||
*
|
||||
* - In-season: window is `(previousSeason.ends, now]` — current season plus the off-season immediately before it.
|
||||
* - Off-season: window is `[previousSeason.starts, now]` — previous full season plus the current off-season.
|
||||
*/
|
||||
export function tournamentInWeaponReportingWindow({
|
||||
tournamentStartTime,
|
||||
now = new Date(),
|
||||
}: {
|
||||
tournamentStartTime: Date;
|
||||
now?: Date;
|
||||
}) {
|
||||
const previousSeason = Seasons.previous(now);
|
||||
if (!previousSeason) return true;
|
||||
|
||||
const currentSeason = Seasons.current(now);
|
||||
const windowStart = currentSeason
|
||||
? previousSeason.ends
|
||||
: previousSeason.starts;
|
||||
|
||||
return tournamentStartTime > windowStart;
|
||||
}
|
||||
|
||||
export function resolveLeagueRoundStartDate(
|
||||
tournament: TournamentClass,
|
||||
roundId: number,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user