mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-06 05:07:36 -05:00
Fix A/B round robin only tournament placements on front page
Closes #3009
This commit is contained in:
parent
a8a42797e5
commit
49e35fd4c2
|
|
@ -46,13 +46,13 @@ export interface ShowcaseCalendarEvent extends CommonEvent {
|
|||
hidden: boolean;
|
||||
isFinalized: boolean;
|
||||
minMembersPerTeam: number;
|
||||
firstPlacer: {
|
||||
firstPlacers: Array<{
|
||||
teamName: string;
|
||||
logoUrl: string | null;
|
||||
members: (CommonUser & { country: Tables["User"]["country"] })[];
|
||||
notShownMembersCount: number;
|
||||
div: string | null;
|
||||
} | null;
|
||||
}>;
|
||||
hasVods?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,8 @@ export function TournamentCard({
|
|||
return (
|
||||
<div
|
||||
className={clsx(className, styles.container, {
|
||||
[styles.containerTall]: isShowcase && tournament.firstPlacer,
|
||||
[styles.containerTall]:
|
||||
isShowcase && tournament.firstPlacers.length > 0,
|
||||
})}
|
||||
data-testid="tournament-card"
|
||||
>
|
||||
|
|
@ -117,15 +118,17 @@ export function TournamentCard({
|
|||
<Tags tags={tournament.tags} small centered />
|
||||
</div>
|
||||
) : null}
|
||||
{isShowcase && tournament.firstPlacer ? (
|
||||
{isShowcase && tournament.firstPlacers.length > 0 ? (
|
||||
<TournamentFirstPlacers
|
||||
firstPlacer={tournament.firstPlacer}
|
||||
firstPlacers={tournament.firstPlacers}
|
||||
censored={isCensored(tournament.id)}
|
||||
/>
|
||||
) : null}
|
||||
</Link>
|
||||
<div className="stack horizontal justify-between items-center">
|
||||
{isShowcase && tournament.firstPlacer && isCensored(tournament.id) ? (
|
||||
{isShowcase &&
|
||||
tournament.firstPlacers.length > 0 &&
|
||||
isCensored(tournament.id) ? (
|
||||
<SpoilerRevealPill onReveal={() => reveal(tournament.id)} />
|
||||
) : null}
|
||||
{isShowcase && "hasVods" in tournament && tournament.hasVods ? (
|
||||
|
|
@ -157,20 +160,52 @@ export function TournamentCard({
|
|||
}
|
||||
|
||||
function TournamentFirstPlacers({
|
||||
firstPlacer,
|
||||
firstPlacers,
|
||||
censored,
|
||||
}: {
|
||||
firstPlacer: NonNullable<ShowcaseCalendarEvent["firstPlacer"]>;
|
||||
firstPlacers: ShowcaseCalendarEvent["firstPlacers"];
|
||||
censored: boolean;
|
||||
}) {
|
||||
if (firstPlacers.length > 1) {
|
||||
return (
|
||||
<div className={styles.firstPlacers}>
|
||||
<div className="stack md items-start">
|
||||
{firstPlacers.map((placer) => (
|
||||
<TournamentFirstPlacerTeamNameOnly
|
||||
key={placer.div ?? placer.teamName}
|
||||
placer={placer}
|
||||
censored={censored}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const placer = firstPlacers[0];
|
||||
|
||||
return (
|
||||
<div className={styles.firstPlacers}>
|
||||
<TournamentFirstPlacerWithMembers placer={placer} censored={censored} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TournamentFirstPlacerWithMembers({
|
||||
placer,
|
||||
censored,
|
||||
}: {
|
||||
placer: ShowcaseCalendarEvent["firstPlacers"][number];
|
||||
censored: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation(["front"]);
|
||||
|
||||
return (
|
||||
<div className={styles.firstPlacers}>
|
||||
<>
|
||||
<div className="stack xs horizontal items-center text-xs">
|
||||
{!censored && firstPlacer.logoUrl ? (
|
||||
{!censored && placer.logoUrl ? (
|
||||
<img
|
||||
src={firstPlacer.logoUrl}
|
||||
src={placer.logoUrl}
|
||||
alt=""
|
||||
width={24}
|
||||
className="rounded-full"
|
||||
|
|
@ -178,16 +213,16 @@ function TournamentFirstPlacers({
|
|||
) : null}{" "}
|
||||
<div className="stack items-start">
|
||||
<span className={styles.firstPlacersTeamName}>
|
||||
{censored ? "???" : firstPlacer.teamName}
|
||||
{censored ? "???" : placer.teamName}
|
||||
</span>
|
||||
<div className="text-xxxs text-lighter font-bold text-uppercase">
|
||||
{t("front:showcase.card.winner")}
|
||||
{firstPlacer.div ? ` (${firstPlacer.div})` : null}
|
||||
{placer.div ? ` (${placer.div})` : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xxs stack items-start mt-1">
|
||||
{firstPlacer.members.map((member) => (
|
||||
{placer.members.map((member) => (
|
||||
<div key={member.id} className="stack horizontal xs items-center">
|
||||
{!censored && member.country ? (
|
||||
<Flag tiny countryCode={member.country} />
|
||||
|
|
@ -195,12 +230,34 @@ function TournamentFirstPlacers({
|
|||
{censored ? "???" : member.username}{" "}
|
||||
</div>
|
||||
))}
|
||||
{!censored && firstPlacer.notShownMembersCount > 0 ? (
|
||||
{!censored && placer.notShownMembersCount > 0 ? (
|
||||
<div className="font-bold text-lighter">
|
||||
+{firstPlacer.notShownMembersCount}
|
||||
+{placer.notShownMembersCount}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TournamentFirstPlacerTeamNameOnly({
|
||||
placer,
|
||||
censored,
|
||||
}: {
|
||||
placer: ShowcaseCalendarEvent["firstPlacers"][number];
|
||||
censored: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation(["front"]);
|
||||
|
||||
return (
|
||||
<div className="stack items-start">
|
||||
<span className={styles.firstPlacersTeamName}>
|
||||
{censored ? "???" : placer.teamName}
|
||||
</span>
|
||||
<div className="text-xxxs text-lighter font-bold text-uppercase">
|
||||
{t("front:showcase.card.winner")}
|
||||
{placer.div ? ` (${placer.div})` : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import cachified from "@epic-web/cachified";
|
||||
import * as R from "remeda";
|
||||
import type { ShowcaseCalendarEvent } from "~/features/calendar/calendar-types";
|
||||
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
|
||||
import {
|
||||
getBracketProgressionLabel,
|
||||
tournamentIsRanked,
|
||||
} from "~/features/tournament/tournament-utils";
|
||||
import * as Progression from "~/features/tournament-bracket/core/Progression";
|
||||
import { getTentativeTier } from "~/features/tournament-organization/core/tentativeTiers.server";
|
||||
import { cache, IN_MILLISECONDS, ttl } from "~/utils/cache.server";
|
||||
import {
|
||||
|
|
@ -186,16 +188,21 @@ function deleteExtraResults(tournaments: ShowcaseCalendarEvent[]) {
|
|||
const threeDaysAgo = databaseTimestampThreeDaysAgo();
|
||||
const nonResults = tournaments.filter(
|
||||
(tournament) =>
|
||||
!tournament.firstPlacer &&
|
||||
tournament.firstPlacers.length === 0 &&
|
||||
!tournament.isFinalized &&
|
||||
tournament.startTime > threeDaysAgo,
|
||||
);
|
||||
|
||||
const rankedResults = tournaments
|
||||
.filter((tournament) => tournament.firstPlacer && tournament.isRanked)
|
||||
.filter(
|
||||
(tournament) => tournament.firstPlacers.length > 0 && tournament.isRanked,
|
||||
)
|
||||
.sort((a, b) => showcaseScore(b) - showcaseScore(a));
|
||||
const nonRankedResults = tournaments
|
||||
.filter((tournament) => tournament.firstPlacer && !tournament.isRanked)
|
||||
.filter(
|
||||
(tournament) =>
|
||||
tournament.firstPlacers.length > 0 && !tournament.isRanked,
|
||||
)
|
||||
.sort((a, b) => showcaseScore(b) - showcaseScore(a));
|
||||
|
||||
const rankedResultsToKeep = rankedResults.slice(0, 4);
|
||||
|
|
@ -283,7 +290,7 @@ const MEMBERS_TO_SHOW = 5;
|
|||
function mapTournamentFromDB(
|
||||
tournament: TournamentRepository.ForShowcase,
|
||||
): ShowcaseCalendarEvent {
|
||||
const highestDivWinners = resolveHighestDivisionWinners(tournament);
|
||||
const firstPlacers = resolveFirstPlacers(tournament);
|
||||
|
||||
const tentativeTier =
|
||||
tournament.tier === null &&
|
||||
|
|
@ -320,41 +327,35 @@ function mapTournamentFromDB(
|
|||
minMembersPerTeam: tournament.settings.minMembersPerTeam ?? 4,
|
||||
modes: null,
|
||||
hasVods: (tournament.vodCount ?? 0) > 0,
|
||||
firstPlacer:
|
||||
highestDivWinners.length > 0
|
||||
? {
|
||||
teamName: highestDivWinners[0].teamName,
|
||||
logoUrl:
|
||||
highestDivWinners[0].teamLogoUrl ??
|
||||
highestDivWinners[0].pickupAvatarUrl,
|
||||
div: highestDivWinners[0].div,
|
||||
members: highestDivWinners
|
||||
.slice(0, MEMBERS_TO_SHOW)
|
||||
.map((firstPlacer) => ({
|
||||
customUrl: firstPlacer.customUrl,
|
||||
discordAvatar: firstPlacer.discordAvatar,
|
||||
discordId: firstPlacer.discordId,
|
||||
id: firstPlacer.id,
|
||||
username: firstPlacer.username,
|
||||
country: firstPlacer.country,
|
||||
})),
|
||||
notShownMembersCount:
|
||||
highestDivWinners.length > MEMBERS_TO_SHOW
|
||||
? highestDivWinners.length - MEMBERS_TO_SHOW
|
||||
: 0,
|
||||
}
|
||||
: null,
|
||||
firstPlacers,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveHighestDivisionWinners(
|
||||
type FirstPlacerRow = TournamentRepository.ForShowcase["firstPlacers"][number];
|
||||
|
||||
function resolveFirstPlacers(
|
||||
tournament: TournamentRepository.ForShowcase,
|
||||
) {
|
||||
): ShowcaseCalendarEvent["firstPlacers"] {
|
||||
if (tournament.firstPlacers.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// not a "many starting brackets" tournament
|
||||
if (
|
||||
Progression.hasAbDivisionsFinals(tournament.settings.bracketProgression)
|
||||
) {
|
||||
const byDiv = R.groupBy(tournament.firstPlacers, (p) => p.div ?? "");
|
||||
return Object.values(byDiv)
|
||||
.map((rows) => buildFirstPlacerEntry(rows, { withMembers: false }))
|
||||
.sort((a, b) => (a.div ?? "").localeCompare(b.div ?? ""));
|
||||
}
|
||||
|
||||
const winnerRows = winnersOfHighestDivision(tournament);
|
||||
return [buildFirstPlacerEntry(winnerRows, { withMembers: true })];
|
||||
}
|
||||
|
||||
function winnersOfHighestDivision(
|
||||
tournament: TournamentRepository.ForShowcase,
|
||||
): FirstPlacerRow[] {
|
||||
if (tournament.firstPlacers.every((p) => p.div === null)) {
|
||||
return tournament.firstPlacers;
|
||||
}
|
||||
|
|
@ -363,8 +364,6 @@ function resolveHighestDivisionWinners(
|
|||
0,
|
||||
tournament.settings.bracketProgression,
|
||||
);
|
||||
|
||||
// Filter to only include winners from the highest division
|
||||
const highestDivWinners = tournament.firstPlacers.filter(
|
||||
(p) => p.div === highestDivName,
|
||||
);
|
||||
|
|
@ -374,6 +373,34 @@ function resolveHighestDivisionWinners(
|
|||
: tournament.firstPlacers;
|
||||
}
|
||||
|
||||
function buildFirstPlacerEntry(
|
||||
rows: FirstPlacerRow[],
|
||||
{ withMembers }: { withMembers: boolean },
|
||||
): ShowcaseCalendarEvent["firstPlacers"][number] {
|
||||
const first = rows[0];
|
||||
const members = withMembers
|
||||
? rows.slice(0, MEMBERS_TO_SHOW).map((row) => ({
|
||||
customUrl: row.customUrl,
|
||||
discordAvatar: row.discordAvatar,
|
||||
discordId: row.discordId,
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
country: row.country,
|
||||
}))
|
||||
: [];
|
||||
|
||||
return {
|
||||
teamName: first.teamName,
|
||||
logoUrl: first.teamLogoUrl ?? first.pickupAvatarUrl,
|
||||
div: first.div,
|
||||
members,
|
||||
notShownMembersCount:
|
||||
withMembers && rows.length > MEMBERS_TO_SHOW
|
||||
? rows.length - MEMBERS_TO_SHOW
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function databaseTimestampWeekFromNow() {
|
||||
const now = new Date();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user