feat: add option sort dropdown

This commit is contained in:
TopherMayor
2026-04-30 11:25:10 -07:00
parent 3ee4fb7914
commit 8726eb7cf9

View File

@@ -1001,6 +1001,37 @@
}
.results-header h2 { font-size: 1.1rem; color: var(--accent); margin-bottom: 4px; }
.results-header p { font-size: 0.75rem; color: var(--text-muted); }
.sort-bar {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.sort-bar label {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.9px;
color: var(--text-muted);
font-weight: 700;
}
.sort-select {
min-width: 220px;
background: rgba(19,22,31,0.96);
border: 1px solid rgba(0,212,255,0.18);
color: #e6f7ff;
border-radius: 10px;
padding: 9px 10px;
font-size: 0.78rem;
outline: none;
appearance: none;
cursor: pointer;
}
.sort-select:focus {
border-color: rgba(0,212,255,0.48);
box-shadow: 0 0 0 2px rgba(0,212,255,0.08);
}
.results-category {
margin-bottom: 20px;
background: var(--surface);
@@ -1278,6 +1309,16 @@
<!-- Main -->
<main>
<div class="sort-bar" id="sortBar">
<label for="sortModeSelect">Sort by</label>
<select id="sortModeSelect" class="sort-select" onchange="setSortMode(this.value)">
<option value="vote-desc">Votes: High to Low</option>
<option value="vote-asc">Votes: Low to High</option>
<option value="price-asc">Price: Low to High</option>
<option value="price-desc">Price: High to Low</option>
</select>
</div>
<div class="status-bar">
<div><span class="status-dot" id="wsDot"></span><span id="wsStatus">Connecting…</span></div>
<div><span class="polls-badge open" id="pollsBadge">POLLS OPEN</span></div>
@@ -1369,6 +1410,7 @@
budgetScenarios: [],
priceUpdatedAt: '',
priceHistoryRunCount: 0,
sortMode: localStorage.getItem('cabo_sort_mode') || 'vote-desc',
priceSourceSelections: (() => {
try {
return JSON.parse(localStorage.getItem('cabo_price_source_selections') || '{}');
@@ -1648,6 +1690,55 @@
render();
}
function setSortMode(sortMode) {
const allowedModes = new Set(['vote-desc', 'vote-asc', 'price-asc', 'price-desc']);
state.sortMode = allowedModes.has(sortMode) ? sortMode : 'vote-desc';
localStorage.setItem('cabo_sort_mode', state.sortMode);
render();
}
function getOptionOrderIndexMap() {
return new Map(state.options.map((opt, index) => [opt.id, index]));
}
function getSelectedOptionPrice(opt) {
const selectedSeries = getOptionSourceSeries(opt);
const selectedPoint = selectedSeries.at(-1) || opt.latestPricePoint || null;
return typeof selectedPoint?.price === 'number' ? selectedPoint.price : null;
}
function sortOptionsByMode(opts, orderIndexMap) {
const mode = state.sortMode || 'vote-desc';
const ascending = mode.endsWith('asc');
const isVoteSort = mode.startsWith('vote');
return [...opts].sort((a, b) => {
const aIndex = orderIndexMap.get(a.id) ?? 0;
const bIndex = orderIndexMap.get(b.id) ?? 0;
if (isVoteSort) {
const aVotes = getVoteEntries(a).length;
const bVotes = getVoteEntries(b).length;
if (aVotes !== bVotes) {
return ascending ? aVotes - bVotes : bVotes - aVotes;
}
} else {
const aPrice = getSelectedOptionPrice(a);
const bPrice = getSelectedOptionPrice(b);
const aHasPrice = typeof aPrice === 'number';
const bHasPrice = typeof bPrice === 'number';
if (aHasPrice && bHasPrice && aPrice !== bPrice) {
return ascending ? aPrice - bPrice : bPrice - aPrice;
}
if (aHasPrice !== bHasPrice) {
return aHasPrice ? -1 : 1;
}
}
return aIndex - bIndex;
});
}
function escapeHtml(value) {
return String(value ?? '')
.replace(/&/g, '&amp;')
@@ -1995,6 +2086,10 @@
// ── Render options ────────────────────────────────────────
function render() {
const list = document.getElementById('optionsList');
const sortModeSelect = document.getElementById('sortModeSelect');
if (sortModeSelect && sortModeSelect.value !== state.sortMode) {
sortModeSelect.value = state.sortMode;
}
// ── Results tab ──────────────────────────────────────────
if (activeTab === 'results') {
@@ -2062,7 +2157,7 @@
return;
}
const sorted = [...opts];
const sorted = sortOptionsByMode(opts, getOptionOrderIndexMap());
const maxVotes = sorted[0] ? getVoteEntries(sorted[0]).length : 1;
const budgetBoard = activeTab === 'budget' ? renderBudgetBoard() : '';