diff --git a/package-lock.json b/package-lock.json index edbe659..eafcafb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "granda-front", - "version": "0.3.18", + "version": "0.3.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "granda-front", - "version": "0.3.18", + "version": "0.3.19", "dependencies": { "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-select": "^2.2.6", diff --git a/package.json b/package.json index 1d49dc1..35c8da5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "granda-front", - "version": "0.3.18", + "version": "0.3.19", "private": true, "scripts": { "dev": "next dev --turbopack", diff --git a/src/app/[locale]/t/[path]/debates/[debate_id]/page.tsx b/src/app/[locale]/t/[path]/debates/[debate_id]/page.tsx index c85e2c3..a4c5531 100644 --- a/src/app/[locale]/t/[path]/debates/[debate_id]/page.tsx +++ b/src/app/[locale]/t/[path]/debates/[debate_id]/page.tsx @@ -120,7 +120,6 @@ export default async function DebateDetailsPage({

{motion}

-
ret
diff --git a/src/components/tournament/dashboard/DashSide.tsx b/src/components/tournament/dashboard/DashSide.tsx index 6538539..228238b 100644 --- a/src/components/tournament/dashboard/DashSide.tsx +++ b/src/components/tournament/dashboard/DashSide.tsx @@ -6,7 +6,6 @@ import { LucideFileClock, LucideFileImage, LucideLayoutDashboard, - LucidePaintBucket, LucideScale, LucideScrollText, LucideSettings, @@ -125,17 +124,6 @@ const DashSide = ({ tournament_path }: { tournament_path: string }) => { }, ], }, - { - catname: t("sidebar.shareable.catname"), - links: [ - { - name: t("sidebar.shareable.image_generation"), - href: `/t/${tournament_path}/image-generation`, - icon: LucidePaintBucket, - disabled: true, - }, - ], - }, { catname: t("sidebar.organisational.catname"), links: [ diff --git a/src/components/tournament/ladder/LadderRoundRow.tsx b/src/components/tournament/ladder/LadderRoundRow.tsx index 5d9e2d4..7eb6f19 100644 --- a/src/components/tournament/ladder/LadderRoundRow.tsx +++ b/src/components/tournament/ladder/LadderRoundRow.tsx @@ -4,6 +4,8 @@ import { LadderDebateNode } from "./LadderDebateNode"; import { Phase } from "@/types/Phase"; import { Motion } from "@/types/Motion"; import { useTranslations } from "next-intl"; +import { GenericButton } from "@/components/ui/GenericButton"; +import { getRoundLabel } from "@/lib/utils"; export function LadderRoundRow({ onOpenConfig, @@ -36,12 +38,24 @@ export function LadderRoundRow({ return (
- + {getRoundLabel(round, phase, debates, t)} +
{debates.map((debate) => ( setIsRoundConfigOpen(false)} tournamentId={tournamentId} round={configuredRound} + phase={phases?.find((p) => p.id == configuredRound.phase_id)} + debates={ + debates?.filter((d) => d.round_id == configuredRound.id) || [] + } motion={motions?.find( (motion) => configuredRound.motion_id == motion.id, )} diff --git a/src/components/tournament/ladder/RoundConfig.tsx b/src/components/tournament/ladder/RoundConfig.tsx index 80f42da..2583bbd 100644 --- a/src/components/tournament/ladder/RoundConfig.tsx +++ b/src/components/tournament/ladder/RoundConfig.tsx @@ -6,13 +6,18 @@ import { LucideX } from "lucide-react"; import { ReusableButton } from "../../ui/ReusableButton"; import { InputBlock } from "../../ui/InputBlock"; import { Round } from "@/types/Round"; +import { Phase } from "@/types/Phase"; +import { Debate } from "@/types/Debate"; import { Motion } from "@/types/Motion"; import { createMotion, setRoundMotion } from "@/lib/utils"; +import { getRoundLabel } from "@/lib/utils"; type RoundConfigProps = { motion?: Motion; round: Round; tournamentId: string; + phase: Phase; + debates?: Debate[]; onApplyAction: () => void; onClose: () => void; }; @@ -21,10 +26,13 @@ export function RoundConfig({ motion, tournamentId, round, + phase, + debates = [], onApplyAction: onApply, onClose, }: RoundConfigProps) { const t = useTranslations("round_config"); + const tLadder = useTranslations("ladder"); const [motionText, setMotionText] = useState(motion?.motion || ""); const [infoslide, setInfoslide] = useState(motion?.adinfo || ""); const [isApplying, setIsApplying] = useState(false); @@ -32,7 +40,8 @@ export function RoundConfig({ message: "", error: false, }); - const name = round.name; + + const name = getRoundLabel(round, phase, debates, tLadder); const isApplyDisabled = motionText.trim().length === 0 || isApplying; @@ -71,45 +80,47 @@ export function RoundConfig({ }; return ( -
- - -
- {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();