diff --git a/backend/reviews/templates/proposals-recap.html b/backend/reviews/templates/proposals-recap.html index 184a99d9df..943daf2dbe 100644 --- a/backend/reviews/templates/proposals-recap.html +++ b/backend/reviews/templates/proposals-recap.html @@ -32,6 +32,14 @@ top: 43px; } + .results-table thead th { + padding: 4px 6px; + } + + .results-table thead th .text span { + padding-inline: 0; + } + .results-table ul { margin-left: 0; padding: 0; @@ -43,6 +51,11 @@ list-style-type: none; } + .results-table .narrow-col { + width: 50px; + max-width: 70px; + } + .results-table .votes-list { max-width: 400px; } @@ -66,90 +79,160 @@ position: fixed; bottom: 0; left: 0; - width: 100%; + right: 0; z-index: 500; background-color: #417690; color: #fff; - transition: transform 0.3s ease-in-out; - } - - .reviews-bottom-bar.hidden { - transform: translateY(100%); } .reviews-bottom-bar-content { display: flex; align-items: center; justify-content: space-between; + padding: 8px 20px; + gap: 20px; } - .reviews-bottom-bar-stats { + .reviews-bottom-bar-confirm-bar { display: flex; align-items: center; - justify-content: flex-end; - gap: 50px; + gap: 15px; } - .reviews-bottom-bar-stats ul { - padding: 0 !important; + .reviews-bottom-bar-confirm-bar p { + margin: 0; + font-size: 13px; } - .reviews-bottom-bar-confirm-bar { - max-width: 800px; + .reviews-bottom-bar-confirm-bar input[type="submit"] { + background: #fff; + color: #417690; + border: none; + padding: 8px 20px; + font-size: 14px; + font-weight: bold; + border-radius: 4px; + cursor: pointer; } - .reviews-bottom-bar-toggle { - position: fixed; - bottom: 10px; - right: 20px; - z-index: 501; - background-color: #417690; - color: #fff; - border: 2px solid #fff; - border-radius: 50%; - width: 50px; - height: 50px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; - transition: bottom 0.3s ease-in-out, background-color 0.2s; + .reviews-bottom-bar-confirm-bar input[type="submit"]:hover { + background: #e0e0e0; } - .reviews-bottom-bar-toggle:hover { - background-color: #2a5d75; + .reviews-bottom-bar-counts { + display: flex; + gap: 20px; + font-size: 14px; + font-weight: bold; + white-space: nowrap; } - .reviews-bottom-bar.hidden ~ .reviews-bottom-bar-toggle { - bottom: 10px; + .reviews-bottom-bar-counts span { + color: #48ff5a; } - .reviews-bottom-bar:not(.hidden) ~ .reviews-bottom-bar-toggle { - bottom: 100px; + .reviews-stats-sidebar { + position: sticky; + top: 0; + flex: 0 0 240px; + align-self: flex-start; + height: 100vh; + overflow-y: auto; + background: var(--body-bg, #fff); + border-left: 1px solid var(--hairline-color, #ddd); + padding: 12px; + padding-bottom: 60px; + z-index: 15; + transition: flex-basis 0.2s, padding 0.2s, border-width 0.2s; + } + + .reviews-stats-sidebar.collapsed { + flex-basis: 0; + padding: 0; + border-left-width: 0; + overflow: hidden; } - ul.proposal-ranking { + .reviews-stats-sidebar h3 { margin-top: 0; - margin-left: 30px; + margin-bottom: 12px; + font-size: 14px; + color: var(--body-quiet-color, #666); + text-transform: uppercase; + letter-spacing: 0.5px; + white-space: nowrap; } - .hidden { - display: none; + .reviews-stats-sidebar h4 { + margin: 12px 0 4px; + font-size: 13px; + color: var(--body-fg, #333); + white-space: nowrap; } - #accepted-proposals-num { - color: #48ff5a; + .reviews-stats-sidebar ul { + padding: 0 !important; + margin: 0 0 8px; + list-style: none; } - #rejected-proposals-num { - color: #48ff5a; + .reviews-stats-sidebar ul li { + font-size: 12px; + padding: 2px 0; + list-style: none; + color: var(--body-quiet-color, #666); + white-space: nowrap; + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 8px; } - #waitinglist-proposals-num { - color: #48ff5a; + .reviews-stats-sidebar .stat-values { + text-align: right; + font-variant-numeric: tabular-nums; + } + + .reviews-stats-sidebar .stats-note { + font-size: 11px; + color: var(--body-quiet-color, #999); + margin: 0 0 12px; + } + + + .stats-sidebar-toggle { + position: sticky; + top: 0; + flex: 0 0 23px; + align-self: flex-start; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + background: var(--body-bg, #fff); + border: none; + border-left: 1px solid var(--hairline-color, #ddd); + padding: 0; + z-index: 15; + color: var(--body-quiet-color, #666); + font-size: 14px; } + .stats-sidebar-toggle:hover { + background: var(--darkened-bg, #e8e8e8); + } + + ul.proposal-ranking { + margin-top: 0; + margin-left: 30px; + } + + .hidden { + display: none; + } + + .opt-filter { display: grid; gap: 10px; @@ -207,11 +290,17 @@ let acceptedProposalsNumRef; let rejectedProposalsNumRef; let waitinglistProposalsNumRef; + let sidebarAcceptedNumRef; + let sidebarRejectedNumRef; + let sidebarWaitinglistNumRef; const getRefs = () => { acceptedProposalsNumRef = document.querySelector("#accepted-proposals-num"); rejectedProposalsNumRef = document.querySelector("#rejected-proposals-num"); waitinglistProposalsNumRef = document.querySelector("#waitinglist-proposals-num"); + sidebarAcceptedNumRef = document.querySelector("#sidebar-accepted-num"); + sidebarRejectedNumRef = document.querySelector("#sidebar-rejected-num"); + sidebarWaitinglistNumRef = document.querySelector("#sidebar-waitinglist-num"); }; window.addEventListener("load", () => { @@ -220,22 +309,28 @@ getRefs(); updateBottomBarUI(); - // Initialize bottom bar toggle functionality - const bottomBarHidden = localStorage.getItem('proposalsBottomBarHidden') === 'true'; - const bottomBar = document.querySelector('.reviews-bottom-bar'); - const toggleIcon = document.getElementById('toggle-icon'); - const toggleButton = document.getElementById('bottom-bar-toggle'); - - if (bottomBar && toggleIcon && toggleButton) { - if (bottomBarHidden) { - bottomBar.classList.add('hidden'); - toggleIcon.textContent = '▲'; - toggleButton.setAttribute('aria-expanded', 'false'); - } else { - toggleButton.setAttribute('aria-expanded', 'true'); + // Move sidebar and toggle into #main as flex siblings (mirroring Django's left nav) + const mainEl = document.getElementById('main'); + const sidebarToggle = document.getElementById('stats-sidebar-toggle'); + const sidebar = document.getElementById('stats-sidebar'); + const sidebarToggleIcon = document.getElementById('sidebar-toggle-icon'); + + if (mainEl && sidebar && sidebarToggle) { + mainEl.appendChild(sidebarToggle); + mainEl.appendChild(sidebar); + + const sidebarCollapsed = localStorage.getItem('proposalsSidebarCollapsed') === 'true'; + if (sidebarCollapsed) { + sidebar.classList.add('collapsed'); + sidebarToggleIcon.innerHTML = '«'; } - toggleButton.addEventListener('click', toggleBottomBar); + sidebarToggle.addEventListener('click', () => { + sidebar.classList.toggle('collapsed'); + const isCollapsed = sidebar.classList.contains('collapsed'); + sidebarToggleIcon.innerHTML = isCollapsed ? '«' : '»'; + localStorage.setItem('proposalsSidebarCollapsed', isCollapsed); + }); } submissions.forEach((submissionData) => { @@ -327,31 +422,47 @@ }); }); + const genderKeys = ["male", "female", "other", "not_say", "unknown"]; + const updateBottomBarUI = () => { const acceptedAudienceLevels = {}; const rejectedAudienceLevels = {}; const waitinglistAudienceLevels = {}; + const genderCounts = { accepted: {}, rejected: {}, waiting_list: {} }; + const typeCounts = { accepted: {}, rejected: {}, waiting_list: {} }; const results = submissions.reduce( (acc, submission) => { const language = submission.languages.length > 1 ? "en" : submission.languages[0]; + const gender = submission.speakerGender || "unknown"; + const subType = submission.submissionType; if (submission.status === "accepted") { acc[0]++; const audienceLevelObject = acceptedAudienceLevels[submission.audienceLevel] || {}; audienceLevelObject[language] = (audienceLevelObject[language] || 0) + 1; acceptedAudienceLevels[submission.audienceLevel] = audienceLevelObject; + genderCounts.accepted[gender] = (genderCounts.accepted[gender] || 0) + 1; + const typeObj = typeCounts.accepted[subType] || {}; + typeObj[language] = (typeObj[language] || 0) + 1; + typeCounts.accepted[subType] = typeObj; } else if (submission.status === "rejected") { acc[1]++; - const audienceLevelObject = rejectedAudienceLevels[submission.audienceLevel] || {}; audienceLevelObject[language] = (audienceLevelObject[language] || 0) + 1; rejectedAudienceLevels[submission.audienceLevel] = audienceLevelObject; + genderCounts.rejected[gender] = (genderCounts.rejected[gender] || 0) + 1; + const typeObj = typeCounts.rejected[subType] || {}; + typeObj[language] = (typeObj[language] || 0) + 1; + typeCounts.rejected[subType] = typeObj; } else if (submission.status === "waiting_list") { acc[2]++; - const audienceLevelObject = waitinglistAudienceLevels[submission.audienceLevel] || {}; audienceLevelObject[language] = (audienceLevelObject[language] || 0) + 1; waitinglistAudienceLevels[submission.audienceLevel] = audienceLevelObject; + genderCounts.waiting_list[gender] = (genderCounts.waiting_list[gender] || 0) + 1; + const typeObj = typeCounts.waiting_list[subType] || {}; + typeObj[language] = (typeObj[language] || 0) + 1; + typeCounts.waiting_list[subType] = typeObj; } return acc; }, @@ -361,6 +472,9 @@ acceptedProposalsNumRef.innerText = results[0]; rejectedProposalsNumRef.innerText = results[1]; waitinglistProposalsNumRef.innerText = results[2]; + sidebarAcceptedNumRef.innerText = results[0]; + sidebarRejectedNumRef.innerText = results[1]; + sidebarWaitinglistNumRef.innerText = results[2]; audienceLevels.forEach( audienceLevel => { @@ -390,6 +504,25 @@ waitinglistRef.innerText = Object.values(waitinglistAudienceLevels[audienceLevel] || {}).reduce((acc, value) => acc + value, 0); } ); + + // Update gender stats + ["accepted", "rejected", "waiting_list"].forEach(status => { + genderKeys.forEach(gender => { + const ref = document.querySelector(`#gender-${status}-${gender}`); + if (ref) ref.innerText = genderCounts[status][gender] || 0; + }); + }); + + // Update submission type stats + ["accepted", "rejected", "waiting_list"].forEach(status => { + document.querySelectorAll(`.type-stat-${status}`).forEach(el => { + const typeName = el.dataset.typeName; + const total = Object.values(typeCounts[status][typeName] || {}).reduce((a, v) => a + v, 0); + const en = typeCounts[status][typeName]?.en || 0; + const it = typeCounts[status][typeName]?.it || 0; + el.innerText = `${total} (\uD83C\uDDEC\uD83C\uDDE7 ${en}, \uD83C\uDDEE\uD83C\uDDF9 ${it})`; + }); + }); }; // Filter by speaker name (called when clicking a speaker name link) @@ -407,30 +540,6 @@ input.dispatchEvent(new Event('input')); }; - // Toggle bottom bar visibility - const toggleBottomBar = () => { - const bottomBar = document.querySelector('.reviews-bottom-bar'); - const toggleIcon = document.getElementById('toggle-icon'); - const toggleButton = document.getElementById('bottom-bar-toggle'); - - if (!bottomBar || !toggleIcon || !toggleButton) { - console.warn('Required elements for bottom bar toggle not found'); - return; - } - - bottomBar.classList.toggle('hidden'); - - if (bottomBar.classList.contains('hidden')) { - toggleIcon.textContent = '▲'; - toggleButton.setAttribute('aria-expanded', 'false'); - } else { - toggleIcon.textContent = '▼'; - toggleButton.setAttribute('aria-expanded', 'true'); - } - - // Save preference in localStorage - localStorage.setItem('proposalsBottomBarHidden', bottomBar.classList.contains('hidden')); - };