From 4ecab8211c558127d833a68466a77e4ae629b2b7 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Mon, 2 Feb 2026 12:04:09 +0000 Subject: [PATCH 1/3] Improve recap screen --- .../reviews/templates/proposals-recap.html | 501 +++++++++++------- 1 file changed, 324 insertions(+), 177 deletions(-) diff --git a/backend/reviews/templates/proposals-recap.html b/backend/reviews/templates/proposals-recap.html index 184a99d9df..08dafe5889 100644 --- a/backend/reviews/templates/proposals-recap.html +++ b/backend/reviews/templates/proposals-recap.html @@ -66,90 +66,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 +277,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 +296,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 +409,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 +459,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 +491,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 +527,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')); - };
@@ -526,6 +622,7 @@

Show proposals of type:

numOfVotes: {{item.userreview_set.count}}, submissionType: "{{ item.type.name }}", speakerName: "{{ item.speaker.fullname|escapejs }}", + speakerGender: "{{ item.speaker.gender|default:'unknown' }}", }; @@ -536,59 +633,62 @@

Show proposals of type:

href="{% url 'admin:submissions_submission_change' item.id %}"> {{item.title}} - + {{ item.score }} @@ -665,62 +765,109 @@

Show proposals of type:

-
+
-
+
-

- Once done, click the button to save your choices. - You can change as many times as you need, no emails will be sent. -

- +

Save your choices (no emails will be sent).

+
-
-
-

For multiple languages proposals, we count it as English.

-
-
-
-

Accepted: 0

-
    - {% for audience_level in audience_levels %} -
  • - {{audience_level.name}}: 0 - (English: 0, Italian: 0) -
  • - {% endfor %} -
-
-
-

Rejected: 0

-
    - {% for audience_level in audience_levels %} -
  • - {{audience_level.name}}: 0 - (English: 0, Italian: 0) -
  • - {% endfor %} -
-
-
-

Waiting List: 0

-
    - {% for audience_level in audience_levels %} -
  • - {{audience_level.name}}: 0 - (English: 0, Italian: 0) -
  • - {% endfor %} -
-
-
+
+
Accepted: 0
+
Rejected: 0
+
Waiting List: 0
- + + {% endblock %} From 5d22f2633b42df53e3477deb3a286c7fad4f867b Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Mon, 2 Feb 2026 12:31:12 +0000 Subject: [PATCH 2/3] Move duration, type, and level to table columns in recap screen Promote duration, type, and audience level from the collapsible "Additional info" section to dedicated table columns. Add compact column styling and reduce header padding for a denser layout. --- .../reviews/templates/proposals-recap.html | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/backend/reviews/templates/proposals-recap.html b/backend/reviews/templates/proposals-recap.html index 08dafe5889..9b81dc4e92 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: 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; } @@ -588,7 +601,19 @@

Show proposals of type:

Title
- + +
Duration
+
+ + +
Type
+
+ + +
Level
+
+ +
Score
@@ -636,18 +661,6 @@

Show proposals of type:

Additional info
    -
  • - Duration: - {{ item.duration.duration }} mins -
  • -
  • - Type: - {{ item.type.name }} -
  • -
  • - Audience level: - {{ item.audience_level.name }} -
  • Languages: {{ item.languages.all|join:", " }} @@ -691,7 +704,10 @@

    Show proposals of type:

- {{ item.score }} + {{ item.duration.duration }}' + {{ item.type.name }} + {{ item.audience_level.name }} + {{ item.score }}
    From 1801e3ce88570ae8a3c2efcd48ea11ee005e36d5 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Mon, 2 Feb 2026 12:42:24 +0000 Subject: [PATCH 3/3] Improve recap table: add lang/gender columns, merge status, truncate score - Add language column with flag emojis and gender column (M/F/Other) - Merge status and pending status into one column with arrow and tooltip - Remove details wrapper, show additional info directly - Truncate score to 2 decimal places - Fix header padding --- .../reviews/templates/proposals-recap.html | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/backend/reviews/templates/proposals-recap.html b/backend/reviews/templates/proposals-recap.html index 9b81dc4e92..943daf2dbe 100644 --- a/backend/reviews/templates/proposals-recap.html +++ b/backend/reviews/templates/proposals-recap.html @@ -37,7 +37,7 @@ } .results-table thead th .text span { - padding: 0; + padding-inline: 0; } .results-table ul { @@ -613,6 +613,14 @@

    Show proposals of type:

    Level
    + +
    Lang
    +
    + + +
    Gender
    +
    +
    Score
    @@ -625,10 +633,6 @@

    Show proposals of type:

    Status
    - -
    Pending Status
    -
    -
    Decision
    @@ -658,27 +662,17 @@

    Show proposals of type:

    href="{% url 'admin:submissions_submission_change' item.id %}"> {{item.title}} -
    - Additional info -
      -
    • - Languages: - {{ item.languages.all|join:", " }} -
    • +
        {% with speaker_id=item.speaker_id|stringformat:"i" %}
      • - Speaker Name + Speaker {{ item.speaker.fullname }}
      • - Speaker Country + Country {{ item.speaker.country|countryname }}
      • -
      • - Speaker Gender - {{ item.speaker.gender }} -
      • Requested a grant? {% if speaker_id in grants %} @@ -700,14 +694,15 @@

        Show proposals of type:

        {% endfor %}
      -
    -
    +
{{ item.duration.duration }}' {{ item.type.name }} {{ item.audience_level.name }} - {{ item.score }} + {% for lang in item.languages.all %}{% if lang.code == "en" %}🇬🇧{% elif lang.code == "it" %}🇮🇹{% else %}{{ lang.code }}{% endif %}{% endfor %} + {% if item.speaker.gender == "male" %}M{% elif item.speaker.gender == "female" %}F{% elif item.speaker.gender == "other" %}Other{% elif item.speaker.gender == "not_say" %}—{% else %}—{% endif %} + {{ item.score|floatformat:2 }}
    @@ -730,11 +725,12 @@

    Show proposals of type:

    - {{item.status}} - - - - {{item.pending_status}} + {% if item.pending_status and item.pending_status != item.status %} + {{item.status}} → {{item.pending_status}} + + {% else %} + {{item.status}} + {% endif %}