Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions frontend/js/leaderboard/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function createExternalIcon() {

function renderLeaderboardRow(user, rank) {
const rankTagEl = createRankTagElement(rank);
const rankChangeEl = createRankChangeElement(user.rankChange);
const rankChangeEl = user.score > 0 ? createRankChangeElement(user.rankChange) : null;
const easyPoints = 1;
const mediumPoints = 3;
const hardPoints = 5;
Expand Down Expand Up @@ -136,7 +136,16 @@ function renderLeaderboardRow(user, rank) {
// Rank (numeric — safe)
const rankDiv = document.createElement("div");
rankDiv.className = "rank";
rankDiv.textContent = rank;
if (rank === "") {
rankDiv.textContent = "[IDLE]";
rankDiv.style.color = "var(--text-dim)";
rankDiv.style.fontSize = "0.75rem";
} else if (rank === "--") {
rankDiv.textContent = "[--]";
rankDiv.style.color = "var(--text-dim)";
} else {
rankDiv.textContent = rank;
}
row.appendChild(rankDiv);

// Name cell — tag is safe DOM element, name is user-controlled (textContent)
Expand Down Expand Up @@ -260,7 +269,16 @@ function renderMobileCard(user, rank) {

const mobileRank = document.createElement("div");
mobileRank.className = "mobile-rank";
mobileRank.textContent = `#${rank}`;
if (rank === "") {
mobileRank.textContent = "[IDLE]";
mobileRank.style.color = "var(--text-dim)";
mobileRank.style.fontSize = "0.75rem";
} else if (rank === "--") {
mobileRank.textContent = "[--]";
mobileRank.style.color = "var(--text-dim)";
} else {
mobileRank.textContent = `#${rank}`;
}
cardHeader.appendChild(mobileRank);

const mobileScore = document.createElement("div");
Expand Down Expand Up @@ -293,7 +311,7 @@ function renderMobileCard(user, rank) {
const mobileName = document.createElement("div");
mobileName.className = "mobile-name";
const mobileRankTagEl = createRankTagElement(rank);
const mobileRankChangeEl = createRankChangeElement(user.rankChange);
const mobileRankChangeEl = user.score > 0 ? createRankChangeElement(user.rankChange) : null;
if (mobileRankTagEl) {
mobileName.appendChild(mobileRankTagEl);
}
Expand Down
123 changes: 85 additions & 38 deletions frontend/leaderboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -400,20 +400,37 @@ <h1 class="page-title">Leaderboard</h1>
countEl.textContent = "";
countEl.style.opacity = "0";
}
document.getElementById("prev-page-btn").disabled = currentPage === 1;

const overallDataset =
window.leaderboardData && window.leaderboardData["overall"]
? window.leaderboardData["overall"]
: originalData || [];

const zeroScoreUserIds = new Set(
overallDataset
.filter((user) => user && user.score === 0)
.map((user) => user.id),
);

const renderableData = filteredData.filter(
(user) => user && !zeroScoreUserIds.has(user.id),
);

const totalPages = Math.ceil(renderableData.length / itemsPerPage) || 1;
document.getElementById("prev-page-btn").disabled = currentPage === 1;
document.getElementById("next-page-btn").disabled =
currentPage === Math.ceil(filteredData.length / itemsPerPage);
const statsEl = document.getElementById("leaderboard-stats");
currentPage === totalPages;

const totalPages = Math.ceil(filteredData.length / itemsPerPage);
const statsEl = document.getElementById("leaderboard-stats");

const startRow =
filteredData.length === 0 ? 0 : (currentPage - 1) * itemsPerPage + 1;
renderableData.length === 0
? 0
: (currentPage - 1) * itemsPerPage + 1;

const endRow = Math.min(
currentPage * itemsPerPage,
filteredData.length,
renderableData.length,
);

statsEl.innerHTML = `
Expand All @@ -428,10 +445,11 @@ <h1 class="page-title">Leaderboard</h1>
| Page: ${currentPage}/${totalPages}
</div>
`;
renderLeaderboard(filteredData);

renderLeaderboard(filteredData, zeroScoreUserIds, totalPages);
}

function renderLeaderboard(data) {
function renderLeaderboard(data, zeroScoreUserIds, totalPages) {
const body = document.getElementById("leaderboard-body");
const mobileCards = document.getElementById("mobile-cards");

Expand All @@ -443,51 +461,80 @@ <h1 class="page-title">Leaderboard</h1>
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;

const displayData = isSearching
? data
: data.slice(startIndex, endIndex);

if (displayData.length === 0) {
body.innerHTML = `
<div class="no-results">
[SYS]: NO_MATCHING_USERS_FOUND
</div>
`;

mobileCards.innerHTML = `
<div class="no-results">
[SYS]: NO_MATCHING_USERS_FOUND
</div>
`;
const activeList = data.filter(
(user) => !zeroScoreUserIds.has(user.id),
);
const inactiveList = data.filter((user) =>
zeroScoreUserIds.has(user.id),
);

const displayActiveData = isSearching
? activeList
: activeList.slice(startIndex, endIndex);

if (displayActiveData.length === 0 && inactiveList.length === 0) {
const emptyStateHtml = `<div class="no-results" style="grid-column: 1 / -1;">[SYS]: NO_MATCHING_USERS_FOUND</div>`;
body.innerHTML = emptyStateHtml;
mobileCards.innerHTML = emptyStateHtml;
return;
}

displayData.forEach((user, index) => {
const rank =
displayActiveData.forEach((user, index) => {
let rank =
user.originalRank !== undefined
? user.originalRank
: startIndex + index + 1;
const row = renderLeaderboardRow(user, rank);
const card = renderMobileCard(user, rank);
body.appendChild(row);
mobileCards.appendChild(card);
});
renderPagination(data.length);
}
function setActiveTab(activeTab) {
document.querySelectorAll(".tab").forEach((tab) => {
tab.classList.remove("active");
if (tab.dataset.tab === activeTab) {
tab.classList.add("active");

if (activeDatasetType !== "overall" && user.score === 0) {
rank = "--";
}

body.appendChild(renderLeaderboardRow(user, rank));
mobileCards.appendChild(renderMobileCard(user, rank));
});

if (
inactiveList.length > 0 &&
(currentPage === totalPages || isSearching)
) {
const dividerText = "── [ SECTION: NO ACTIVITY YET ] ──";

const tableHeader = document.createElement("div");
tableHeader.className = "no-results";
tableHeader.style.gridColumn = "1 / -1";
tableHeader.innerText = dividerText;
body.appendChild(tableHeader);

const mobileHeader = document.createElement("div");
mobileHeader.className = "no-results";
mobileHeader.innerText = dividerText;
mobileCards.appendChild(mobileHeader);

inactiveList.forEach((user) => {
body.appendChild(renderLeaderboardRow(user, ""));
mobileCards.appendChild(renderMobileCard(user, ""));
});
}

renderPagination(activeList.length);
}

function setActiveTab(activeTab) {
if (!window.leaderboardData[activeTab]) {
return;
}

activeDatasetType = activeTab;
currentPage = 1;

document.querySelectorAll(".tab").forEach((tab) => {
if (tab.dataset.tab === activeTab) {
tab.classList.add("active");
} else {
tab.classList.remove("active");
}
});

applyFiltersAndRender();
}
</script>
Expand Down
3 changes: 2 additions & 1 deletion frontend/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -1574,7 +1574,8 @@ body.crt-scrolling {
}

.leaderboard-header,
.leaderboard-row {
.leaderboard-row,
.leaderboard .no-results {
display: none;
}

Expand Down
10 changes: 7 additions & 3 deletions scripts/sync-leaderboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ function getFileName(daysAgo) {
function assignCompetitionRanks(sortedData) {
let currentRank = 1;
for (let i = 0; i < sortedData.length; i++) {
if (i > 0 && sortedData[i].score < sortedData[i - 1].score) {
currentRank = i + 1;
if (sortedData[i].score === 0) {
sortedData[i].originalRank = "--";
} else {
if (i > 0 && sortedData[i].score < sortedData[i - 1].score) {
currentRank = i + 1;
}
sortedData[i].originalRank = currentRank;
Comment on lines +37 to +43

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since the frontend is already handling this, I don't think adding the same logic again inside the sync script is really necessary

}
sortedData[i].originalRank = currentRank;
}
}

Expand Down
Loading