Skip to content

Conversation

@ncarazon
Copy link
Contributor

@ncarazon ncarazon commented Feb 6, 2026

Closes #4251

This PR adds support for displaying multiple leaderboards with tab navigation on project pages, along with custom display names and column renaming capabilities.

Multiple Leaderboards with Tabs

Tab 1: "Official Results" with renamed columns (Participant, Final Score)

tab1-official-results

Tab 3: "Pre-DQ Results" with renamed column (Raw Score)

tab3-pre-dq-results

Single Leaderboard - No Tabs (Backward Compatibility)

Bridgewater Tournament: Standard leaderboard display without tabs

single-leaderboard-no-tabs

Summary by CodeRabbit

  • New Features

    • Projects can display multiple leaderboards with tabs and select a default active leaderboard.
    • Leaderboards are sorted by configurable display order before rendering.
    • Leaderboard tables support configurable, localized column header names.
    • Prize pool, score type, and table data update based on the selected leaderboard.
  • Bug Fixes

    • Empty or missing leaderboards are gracefully handled and hidden from display.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

Fetches all project leaderboards, filters out leaderboards without entries, sorts by display_config.display_order (default 0), and passes a sorted leaderboards array to the client. Client shows tabs when >1 leaderboard, selects an active leaderboard, and table headers can be renamed via display_config.column_renames.

Changes

Cohort / File(s) Summary
Project-level fetch & server component
front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard.tsx
Fetches all leaderboards for a project, handles null/empty results, filters out leaderboards with no entries, sorts by display_config.display_order, and passes leaderboards: LeaderboardDetails[] downstream instead of a single leaderboard.
Client UI & state
front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard_client.tsx
Prop changed to leaderboards: LeaderboardDetails[]; manages activeLeaderboard state (defaults to first), renders TabBar when multiple leaderboards exist, computes tab labels from display_config.display_name/fallback, and forwards activeLeaderboard to the table.
Table & column customization
front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard_table/index.tsx
Adds display_config.column_renames handling via getColumnName (useCallback) to resolve header labels, importing English translations and LeaderboardDisplayConfig; falls back to localized translations when renames absent.
API signature & usages
front_end/src/services/api/leaderboard/leaderboard.shared.ts, front_end/src/app/(main)/(tournaments)/tournament/components/tournament_timeline.tsx
getProjectLeaderboard signature changed to `params?: URLSearchParams
Types
front_end/src/types/scoring.ts
Introduces LeaderboardDisplayConfig (display_name, column_renames, display_order, display_on_project), adds id: number to BaseLeaderboardDetails, and types display_config as `LeaderboardDisplayConfig

Sequence Diagram

sequenceDiagram
    participant Server as Server
    participant API as LeaderboardApi
    participant Client as ProjectLeaderboardClient
    participant Table as ProjectLeaderboardTable

    Server->>API: getProjectLeaderboard(projectId, params?)
    API-->>Server: LeaderboardDetails[] | null
    Server->>Server: filter out leaderboards with no entries
    Server->>Server: sort by display_config.display_order
    Server->>Client: pass leaderboards[]
    Client->>Client: set activeLeaderboard = leaderboards[0] (if any)
    alt multiple leaderboards
        Client->>Client: render TabBar using display_config.display_name / fallback
        Client->>Client: on tab select -> set activeLeaderboard
    end
    Client->>Table: render table with activeLeaderboard and display_config
    Table->>Table: resolve headers via column_renames or translations
    Table-->>Client: rendered table
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • hlbmtc
  • lsabor

Poem

🐰 I hopped through arrays and tabs with glee,

Names and columns bent to what they should be,
Sorted by order, empty boards set aside,
One tab or many — the active one we ride,
A little rabbit cheers: "Leaderboards, wide!"

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main feature: enabling multiple leaderboards with tab-based navigation for project pages.
Linked Issues check ✅ Passed All coding requirements from issue #4251 are met: multiple leaderboards display in tabs, custom display names are used for tab labels with fallbacks, leaderboards are sorted by display_order, column names are customizable via display_config.column_renames, and single-leaderboard projects work without tabs.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing multi-leaderboard display with tabs; no unrelated modifications detected. Parameter renaming from 'endpointParams' to 'params' is a minor refactoring supporting the API array response requirement.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/multi-leaderboard-tabs-frontend

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard_table/index.tsx (3)

6-6: Importing the raw JSON message bundle is brittle.

Directly importing en.json creates a tight coupling to the message file structure. If keys become nested (e.g. leaderboard.rank) or the file is restructured, the flat Record<string, unknown> cast on line 40 will silently return undefined and column renames will stop working without any visible error.

Consider adding a comment documenting this assumption or, ideally, keying column_renames by the stable translation key (e.g. "rank") instead of the English display string.


31-49: columnRenames is both a closure variable and a function parameter — pick one.

columnRenames is captured from the component scope (line 29) but also passed as an explicit argument on every call site (lines 76, 79, 82, etc.). This dual path is confusing and could diverge if a caller passes a different value.

♻️ Suggested simplification — remove the parameter
  const getColumnName = useCallback(
-   (
-     translationKey: Parameters<typeof t>[0],
-     columnRenames?: LeaderboardDisplayConfig["column_renames"]
-   ): string => {
+   (translationKey: Parameters<typeof t>[0]): string => {
      const localizedName = t(translationKey);
      if (!columnRenames) {
        return localizedName;
      }
      ...
    },
-   [t]
+   [t, columnRenames]
  );

Then update all call sites (e.g. getColumnName("rank") instead of getColumnName("rank", columnRenames)).


40-45: Nested message keys will silently fail the rename lookup.

(enMessages as Record<string, unknown>)[String(translationKey)] only works for flat, top-level keys. If translationKey is a dot-path into a nested object (e.g. "leaderboard.rank"), this returns undefined and the rename is silently skipped. For the current set of keys (rank, forecaster, totalScore, etc.) this works, but it's a latent issue.

front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard_client.tsx (1)

40-45: activeLeaderboardId state won't sync if leaderboards prop changes.

The initial state is set from leaderboards[0]?.id, but if the parent re-renders with a different set of leaderboards (e.g., due to revalidation), the stale ID could cause activeLeaderboard to fall back to leaderboards[0] silently. Since the parent is a server component, this is unlikely to be a problem in practice, but it's worth being aware of.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@front_end/src/app/`(main)/(leaderboards)/leaderboard/components/project_leaderboard_table/index.tsx:
- Around line 35-39: The current lookup uses the translated label (defaultName =
t(translationKey)) to index columnRenames which breaks in non-English locales;
change the lookup to use the stable translation key (translationKey) when
checking columnRenames (e.g. check columnRenames[translationKey] and if present
return that renamed string, otherwise fall back to the existing behavior of
columnRenames[defaultName] and finally defaultName) so renames are
locale-independent; update the code around defaultName, translationKey and
columnRenames to try translationKey first, then the translated label, then the
untranslated default.

In `@front_end/src/types/scoring.ts`:
- Around line 102-107: The new LeaderboardDisplayConfig field display_on_project
is never used; update the filtering in project_leaderboard.tsx (where it
currently checks entries.length > 0) to also respect config.display_on_project
(treat undefined as true for backward compatibility) so leaderboards with
display_on_project=false are hidden on project pages; reference the
LeaderboardDisplayConfig type and the display_on_project property when locating
the code to change and ensure the filter uses config.display_on_project
alongside entries.length.
🧹 Nitpick comments (2)
front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard_table/index.tsx (1)

30-42: columnRenames parameter is redundant — simplify by closing over the outer variable.

getColumnName always receives the same columnRenames from the component scope at every call site. Closing over it directly eliminates the repeated argument and simplifies all 7+ call sites.

♻️ Suggested simplification
- const getColumnName = useCallback(
-   (
-     translationKey: Parameters<typeof t>[0],
-     columnRenames?: LeaderboardDisplayConfig["column_renames"]
-   ): string => {
-     const defaultName = t(translationKey);
-     if (!columnRenames) {
-       return defaultName;
-     }
-     return columnRenames[defaultName] ?? defaultName;
-   },
-   [t]
- );
+ const getColumnName = useCallback(
+   (translationKey: Parameters<typeof t>[0]): string => {
+     const defaultName = t(translationKey);
+     if (!columnRenames) {
+       return defaultName;
+     }
+     return columnRenames[defaultName] ?? defaultName;
+   },
+   [t, columnRenames]
+ );

Then simplify all call sites, e.g.:

- {getColumnName("rank", columnRenames)}
+ {getColumnName("rank")}
front_end/src/app/(main)/(leaderboards)/leaderboard/components/project_leaderboard_client.tsx (1)

94-101: toLocaleString() called without explicit locale — inconsistent with tournament_timeline.

In tournament_timeline.tsx (Line 82), prize_pool is formatted with an explicit locale argument. Here it relies on the browser default, which may produce different formatting for the same user.

♻️ Suggested fix

Pass locale from next-intl for consistent formatting:

+ import { useLocale } from "next-intl";
  ...
+ const locale = useLocale();
  ...
- ${activeLeaderboard.prize_pool.toLocaleString()}
+ ${activeLeaderboard.prize_pool.toLocaleString(locale)}

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

🧹 Preview Environment Cleaned Up

The preview environment for this PR has been destroyed.

Resource Status
🌐 Preview App ✅ Deleted
🗄️ PostgreSQL Branch ✅ Deleted
⚡ Redis Database ✅ Deleted
🔧 GitHub Deployments ✅ Removed
📦 Docker Image ⚠️ Retained (auto-cleanup via GHCR policies)

Cleanup triggered by PR close at 2026-02-09T09:08:45Z

Copy link
Contributor

@elisescu elisescu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ncarazon ncarazon force-pushed the feat/multi-leaderboard-tabs-frontend branch from 64f1890 to fa3fa21 Compare February 6, 2026 14:44
Copy link
Contributor

@lsabor lsabor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!
Works well, looks good, code is clean.

Left one non-blocking comment that should at least be considered.

- Update API service to accept URLSearchParams and return LeaderboardDetails[]
- Add LeaderboardDisplayConfig type with display_name, column_renames, display_order
- Sort leaderboards by display_order in server component
- Show TabBar when multiple leaderboards exist, hide for single leaderboard
- Apply display_config.display_name for tab labels with fallback to name
- Support column_renames for custom column header names in table
@ncarazon ncarazon force-pushed the feat/multi-leaderboard-tabs-frontend branch from 024997c to 2765a2e Compare February 9, 2026 08:54
@ncarazon ncarazon requested a review from lsabor February 9, 2026 08:59
@ncarazon ncarazon merged commit f251d0a into main Feb 9, 2026
14 checks passed
@ncarazon ncarazon deleted the feat/multi-leaderboard-tabs-frontend branch February 9, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multi-leaderboard display with tabs for projects

3 participants