aboutsummaryrefslogtreecommitdiffstats
path: root/static
diff options
context:
space:
mode:
authorLouis Burda <dev@sinitax.com>2026-01-30 23:39:40 +0100
committerLouis Burda <dev@sinitax.com>2026-01-30 23:39:40 +0100
commitc28b16ff096b6a77baeb787b721af828532d5a58 (patch)
tree9bf1125a955461e0d591ec2195911df520bc156b /static
parentfd1917ae649ff1c6fede1b437543477d61b2afd7 (diff)
downloadslotfinder-main.tar.gz
slotfinder-main.zip
Add ability to sort by available and date in time slot listHEADmain
Diffstat (limited to 'static')
-rw-r--r--static/css/style.css6
-rw-r--r--static/js/poll.js75
2 files changed, 73 insertions, 8 deletions
diff --git a/static/css/style.css b/static/css/style.css
index 7e0c888..c04ef2d 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -209,6 +209,12 @@ body:not(.poll-view) .container { padding-top: 2rem; padding-bottom: 2rem; }
.attendees-mode-toggle.active { opacity: 1; font-weight: 600; background: var(--color-primary-light); }
.attendees-mode-separator { opacity: 0.3; margin: 0 0.25rem; }
+#results-table th[data-sort] { cursor: pointer; user-select: none; }
+#results-table th[data-sort]:hover { opacity: 0.7; }
+
+.sortable-header { display: inline-flex; align-items: center; gap: 0.25rem; }
+.sortable-header .sort-arrow { font-size: 0.75em; min-width: 0.75em; color: var(--color-primary); }
+
.submit-section { margin-top: var(--layout-spacing); background: var(--color-surface); padding: var(--layout-spacing); border-radius: var(--layout-border-radius); box-shadow: var(--shadow-sm); display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; }
.guest-info { display: flex; gap: 0.5rem; flex: 1; }
.guest-info input { padding: 0.5rem; border: 1px solid var(--color-border); border-radius: var(--layout-border-radius-sm); flex: 1; background: var(--color-input-bg); color: var(--color-text); }
diff --git a/static/js/poll.js b/static/js/poll.js
index 8216180..899a00f 100644
--- a/static/js/poll.js
+++ b/static/js/poll.js
@@ -15,6 +15,9 @@ let isDayWise = false;
let hoveredPane = null; // 'calendar' or 'time'
let viewMode = false; // true = show all availability, false = show only yours
let attendeesMode = 'available'; // 'available' or 'unavailable'
+let sortColumn = 'available'; // 'date' or 'available'
+let sortDirection = 'desc'; // 'asc' or 'desc'
+let hasUserSorted = false; // Track if user has clicked to sort
let allAvailability = { dates: new Map(), times: new Map() }; // Maps date/time -> count
let myAvailability = { dates: new Set(), times: new Set() };
let maxDateCount = 0;
@@ -551,8 +554,34 @@ function processPollData(data) {
// Update table header and notes pane for day-wise polls
const slotHeader = document.querySelector('#results-table thead th:first-child');
if (slotHeader) {
- slotHeader.textContent = isDayWise ? 'Date' : 'Time Slot';
+ slotHeader.innerHTML = `<span class="sortable-header" data-sort="date">${isDayWise ? 'Date' : 'Time Slot'} <span class="sort-arrow"></span></span>`;
+ slotHeader.style.cursor = 'pointer';
+ slotHeader.dataset.sort = 'date';
}
+ const availableHeader = document.querySelector('#results-table thead th:nth-child(2)');
+ if (availableHeader) {
+ availableHeader.innerHTML = '<span class="sortable-header" data-sort="available">Available <span class="sort-arrow"></span></span>';
+ availableHeader.style.cursor = 'pointer';
+ availableHeader.dataset.sort = 'available';
+ }
+
+ // Add click handlers to entire th elements for sortable headers
+ document.querySelectorAll('#results-table thead th[data-sort]').forEach(th => {
+ th.onclick = () => {
+ const newSortColumn = th.dataset.sort;
+ hasUserSorted = true;
+ if (sortColumn === newSortColumn) {
+ // Toggle direction
+ sortDirection = sortDirection === 'desc' ? 'asc' : 'desc';
+ } else {
+ // New column - default to desc for available, asc for date
+ sortColumn = newSortColumn;
+ sortDirection = newSortColumn === 'available' ? 'desc' : 'asc';
+ }
+ updateSortIndicators();
+ renderResults(pollData.responses);
+ };
+ });
const notesPane = document.getElementById('notes-pane');
if (notesPane && notesPane.querySelector('.notes-pane-empty')) {
notesPane.innerHTML = `<span class="notes-pane-empty">Click a ${isDayWise ? 'date' : 'time slot'} to view notes</span>`;
@@ -577,6 +606,22 @@ function processPollData(data) {
}
+function updateSortIndicators() {
+ // Only show sort indicators after user has clicked to sort
+ if (!hasUserSorted) return;
+
+ document.querySelectorAll('.sortable-header').forEach(header => {
+ const arrow = header.querySelector('.sort-arrow');
+ if (header.dataset.sort === sortColumn) {
+ header.classList.add('active');
+ arrow.textContent = sortDirection === 'desc' ? '▼' : '▲';
+ } else {
+ header.classList.remove('active');
+ arrow.textContent = '';
+ }
+ });
+}
+
function renderResults(responses) {
const tbody = document.querySelector('#results-table tbody');
const slotMap = {};
@@ -614,14 +659,27 @@ function renderResults(responses) {
});
});
- // Sort by available count (descending), then by date+time (ascending)
- // Sorting is always based on available attendees, regardless of display mode
+ // Sort based on user's selected column and direction
const sorted = Object.entries(slotMap).sort((a, b) => {
- const countDiff = b[1].attendees.length - a[1].attendees.length;
- if (countDiff !== 0) return countDiff;
- const dateCmp = a[1].date.localeCompare(b[1].date);
- if (dateCmp !== 0) return dateCmp;
- return a[1].start - b[1].start;
+ if (sortColumn === 'available') {
+ // Sort by available count
+ const countDiff = sortDirection === 'desc'
+ ? b[1].attendees.length - a[1].attendees.length
+ : a[1].attendees.length - b[1].attendees.length;
+ if (countDiff !== 0) return countDiff;
+ // Secondary sort by date+time
+ const dateCmp = a[1].date.localeCompare(b[1].date);
+ if (dateCmp !== 0) return dateCmp;
+ return a[1].start - b[1].start;
+ } else {
+ // Sort by date+time
+ const dateCmp = a[1].date.localeCompare(b[1].date);
+ if (dateCmp !== 0) return sortDirection === 'asc' ? dateCmp : -dateCmp;
+ const timeDiff = a[1].start - b[1].start;
+ if (timeDiff !== 0) return sortDirection === 'asc' ? timeDiff : -timeDiff;
+ // Secondary sort by count (descending)
+ return b[1].attendees.length - a[1].attendees.length;
+ }
});
const totalPeople = allParticipants.length;
@@ -707,6 +765,7 @@ function renderResults(responses) {
});
applyAttendeeFilter();
+ updateSortIndicators();
}
function toggleAttendeeFilter(name) {