diff --git a/frontend/js/leaderboard/render.js b/frontend/js/leaderboard/render.js index 4b0ffe1b..c23bb9d6 100644 --- a/frontend/js/leaderboard/render.js +++ b/frontend/js/leaderboard/render.js @@ -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; @@ -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) @@ -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"); @@ -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); } diff --git a/frontend/leaderboard.html b/frontend/leaderboard.html index ab852474..3c4345df 100644 --- a/frontend/leaderboard.html +++ b/frontend/leaderboard.html @@ -400,20 +400,37 @@

Leaderboard

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 = ` @@ -428,10 +445,11 @@

Leaderboard

| Page: ${currentPage}/${totalPages} `; - 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"); @@ -443,51 +461,80 @@

Leaderboard

const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; - const displayData = isSearching - ? data - : data.slice(startIndex, endIndex); - - if (displayData.length === 0) { - body.innerHTML = ` -
- [SYS]: NO_MATCHING_USERS_FOUND -
- `; - - mobileCards.innerHTML = ` -
- [SYS]: NO_MATCHING_USERS_FOUND -
- `; + 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 = `
[SYS]: NO_MATCHING_USERS_FOUND
`; + 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(); } diff --git a/frontend/styles/main.css b/frontend/styles/main.css index d9fcf7e8..b3a46993 100644 --- a/frontend/styles/main.css +++ b/frontend/styles/main.css @@ -1574,7 +1574,8 @@ body.crt-scrolling { } .leaderboard-header, - .leaderboard-row { + .leaderboard-row, + .leaderboard .no-results { display: none; } diff --git a/scripts/sync-leaderboard.js b/scripts/sync-leaderboard.js index 105698e7..79073617 100644 --- a/scripts/sync-leaderboard.js +++ b/scripts/sync-leaderboard.js @@ -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; } - sortedData[i].originalRank = currentRank; } }