- {t("title", { name })}
+
+
+
+
+
+ {t("title", { name })}
+
+
+
+
+
+
+
+ {resultMessage.message || " "}
+
+
+
-
-
-
-
-
-
- {resultMessage.message || " "}
-
-
-
);
}
diff --git a/src/components/ui/GenericButton.tsx b/src/components/ui/GenericButton.tsx
index e155b9e..76530e0 100644
--- a/src/components/ui/GenericButton.tsx
+++ b/src/components/ui/GenericButton.tsx
@@ -45,7 +45,7 @@ const GenericButton = (
props.hidden
? "hidden"
: `
- cursor-pointer p-1 text-center block w-full rounded px-2 border
+ cursor-pointer p-1 text-center block rounded px-2 border
${!props.smol ? "px-12 w-full" : ""}
${!props.square ? "rounded-lg" : "rounded"}
disabled:opacity-50 disabled:cursor-not-allowed
diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json
index 22a6f1c..ee2c5e9 100644
--- a/src/i18n/translations/en.json
+++ b/src/i18n/translations/en.json
@@ -109,13 +109,20 @@
"total_teams_hint": "The total number of teams taking part in the tournament.",
"advancing_teams_hint": "Defines how many teams advance from the group phase to the finals phase. Must be a power of 2.",
"hint_title": "Tournament Ladder",
- "hint_content": "Each box represents a debate. You can click on a specific box to see the debate details. The ladder visualizes the flow of the tournament."
+ "hint_content": "Each box represents a debate. You can click on a specific box to see the debate details. The ladder shows the structure and flow of the tournament, including the rounds and how debates are organised.",
+ "round": "Round {n}",
+ "finals": {
+ "final": "Finale",
+ "semi_final": "Semi-finals",
+ "quarter_final": "Quarter-finals",
+ "nth_final": "1/{n}-finals"
+ }
},
"debates": {
"title": "Debates"
},
"round_config": {
- "title": "Round {name} configuration",
+ "title": "{name} configuration",
"motion_title": "Motion",
"motion_description": "What should this round's topic be?",
"motion_placeholder": "(Required)",
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 6d5f7d9..1387eb4 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -2,6 +2,8 @@ import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { Motion } from "@/types/Motion";
import { Round } from "@/types/Round";
+import { Debate } from "@/types/Debate";
+import { Phase } from "@/types/Phase";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -77,3 +79,41 @@ export async function setRoundMotion(
return response.json();
}
+
+type Translator = (
+ key: string,
+ values?: Record
,
+) => string;
+
+export function getRoundLabel(
+ round: Round,
+ phase: Phase,
+ debates: Debate[],
+ t: Translator,
+): string {
+ if (phase.is_finals) {
+ const matches = debates.length;
+
+ if (matches === 1) return t("finals.final");
+ if (matches === 2) return t("finals.semi_final");
+ if (matches === 4) return t("finals.quarter_final");
+
+ if (matches > 0 && (matches & (matches - 1)) === 0) {
+ return t("finals.nth_final", { n: matches * 2 });
+ }
+
+ return round.name;
+ }
+
+ const matchRoundName = round.name.match(/^round_(\d+)$/i);
+
+ if (matchRoundName) {
+ const roundNumber = Number(matchRoundName[1]);
+
+ if (!Number.isNaN(roundNumber)) {
+ return t("round", { n: roundNumber });
+ }
+ }
+
+ return round.name;
+}
diff --git a/test/e2e/e2eUtils.ts b/test/e2e/e2eUtils.ts
index 4c9ed14..01e2e27 100644
--- a/test/e2e/e2eUtils.ts
+++ b/test/e2e/e2eUtils.ts
@@ -332,7 +332,7 @@ export async function planTournament({
totalTeams: number;
advancingTeams: number;
}) {
- await page.getByRole("link", { name: "Tournament Ladder" }).click();
+ await page.getByRole("link", { name: "Tournament Ladder" }).first().click();
await page.waitForURL(/ladder/);
await page
.getByRole("spinbutton", { name: "Group phase rounds" })
diff --git a/test/e2e/settings-shortcut.test.ts b/test/e2e/settings-shortcut.test.ts
index df765bf..fa80120 100644
--- a/test/e2e/settings-shortcut.test.ts
+++ b/test/e2e/settings-shortcut.test.ts
@@ -5,7 +5,9 @@ testInTournamentAsAdmin(
"settings shortcut redirects to settings page and is removed from account dropdown",
async ({ page }) => {
// GIVEN
- await expect(page.getByRole("heading", { name: "Overview" })).toBeVisible();
+ await expect(
+ page.getByRole("heading", { name: "Welcome to" }),
+ ).toBeVisible();
const settingsShortcut = page.getByRole("button", { name: "Settings" });
diff --git a/test/e2e/sidebarHighlight.test.ts b/test/e2e/sidebarHighlight.test.ts
index c51fe17..6668de5 100644
--- a/test/e2e/sidebarHighlight.test.ts
+++ b/test/e2e/sidebarHighlight.test.ts
@@ -6,9 +6,9 @@ testInTournamentAsAdmin(
async ({ page }) => {
// GIVEN
const overviewSidebarItem = page.getByRole("link", { name: "Overview" });
- const tournamentSidebarItem = page.getByRole("link", {
- name: "Tournament Ladder",
- });
+ const tournamentSidebarItem = page
+ .getByRole("link", { name: "Tournament Ladder" })
+ .first();
const debatesSidebarItem = page.getByRole("link", { name: "Debates" });
await expect(overviewSidebarItem).toHaveClass(/border-stone-600/);
diff --git a/test/e2e/tournament-ladder-rendering.test.ts b/test/e2e/tournament-ladder-rendering.test.ts
index e40f47d..0a18e1f 100644
--- a/test/e2e/tournament-ladder-rendering.test.ts
+++ b/test/e2e/tournament-ladder-rendering.test.ts
@@ -24,8 +24,8 @@ testInTournamentAsAdmin(
page.getByRole("heading", { name: "Tournament Ladder" }),
).toBeVisible();
- const roundLabel = page.getByText(/^round.?\d/i);
- expect(await roundLabel.count()).toBe(7);
+ const groupRoundLabel = page.getByText(/^Round \d+$/i);
+ expect(await groupRoundLabel.count()).toBe(3);
},
);
@@ -60,7 +60,7 @@ testInTournamentAsAdmin(
page.getByText("You can click on a specific box"),
).toBeVisible();
await expect(
- page.getByText("The ladder visualizes the flow of the tournament"),
+ page.getByText("The ladder shows the structure"),
).toBeVisible();
// WHEN
@@ -107,8 +107,8 @@ testInTournamentAsAdmin(
page.getByRole("heading", { name: "Tournament Ladder" }),
).toBeVisible();
- const configButton = page.getByText("round_1").first();
- const configHeading = page.getByText("Round round_1 configuration");
+ const configButton = page.getByText("Round 1");
+ const configHeading = page.getByText("Round 1 configuration");
await configButton.click();
await expect(configHeading).toBeVisible();
@@ -162,7 +162,7 @@ testInTournamentAsAdmin(
"tournament planning form should contain informative placeholders",
async ({ page }) => {
// GIVEN
- await page.getByRole("link", { name: "Tournament Ladder" }).click();
+ await page.getByRole("link", { name: "Tournament Ladder" }).first().click();
await page.waitForURL(/ladder/);
// WHEN
@@ -179,7 +179,7 @@ testInTournamentAsAdmin(
"tournament planning form should contain clickable hint icons",
async ({ page }) => {
// GIVEN
- await page.getByRole("link", { name: "Tournament Ladder" }).click();
+ await page.getByRole("link", { name: "Tournament Ladder" }).first().click();
await page.waitForURL(/ladder/);
// WHEN
@@ -220,7 +220,7 @@ testInTournamentAsAdmin(
"advancing teams input should increment by 2",
async ({ page }) => {
// GIVEN
- await page.getByRole("link", { name: "Tournament Ladder" }).click();
+ await page.getByRole("link", { name: "Tournament Ladder" }).first().click();
await page.waitForURL(/ladder/);
const advancingTeamsInput = page.getByPlaceholder("2, 4, 8, 16…");
@@ -250,8 +250,8 @@ testInTournamentAsAdmin(
page.getByRole("heading", { name: "Tournament Ladder" }),
).toBeVisible();
- const configButton = page.getByText("round_1").first();
- const configHeading = page.getByText("Round round_1 configuration");
+ const configButton = page.getByText("Round 1");
+ const configHeading = page.getByText("Round 1 configuration");
await configButton.click();
await expect(configHeading).toBeVisible();