feat: Add month range filter to adults and juniors dashboards

Add from/to combobox selectors and Apply/All buttons to filter
which month columns are displayed. Defaults to last 5 months on load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 13:26:04 +02:00
parent 1ac5df7be5
commit f712198319
2 changed files with 134 additions and 6 deletions

View File

@@ -259,6 +259,24 @@
border-color: #00ff00;
}
.filter-select {
background-color: #1a1a1a;
border: 1px solid #333;
color: #00ff00;
font-family: inherit;
font-size: 11px;
padding: 4px 8px;
outline: none;
}
.filter-select:focus {
border-color: #00ff00;
}
.month-hidden {
display: none !important;
}
.filter-label {
color: #888;
text-transform: lowercase;
@@ -478,6 +496,20 @@
<div class="filter-container">
<span class="filter-label">search member:</span>
<input type="text" id="nameFilter" class="filter-input" placeholder="..." autocomplete="off">
<span class="filter-label" style="margin-left: 16px;">from:</span>
<select id="fromMonth" class="filter-select">
{% for m in months %}
<option value="{{ loop.index0 }}">{{ m }}</option>
{% endfor %}
</select>
<span class="filter-label" style="margin-left: 8px;">to:</span>
<select id="toMonth" class="filter-select">
{% for m in months %}
<option value="{{ loop.index0 }}">{{ m }}</option>
{% endfor %}
</select>
<button type="button" onclick="applyMonthFilter()" class="filter-select" style="cursor: pointer;">Apply</button>
<button type="button" onclick="resetMonthFilter()" class="filter-select" style="cursor: pointer;">All</button>
</div>
<div class="table-container">
@@ -486,7 +518,7 @@
<tr>
<th>Member</th>
{% for m in months %}
<th>{{ m }}</th>
<th data-month-idx="{{ loop.index0 }}">{{ m }}</th>
{% endfor %}
<th>Balance</th>
</tr>
@@ -499,7 +531,7 @@
<span class="info-icon" onclick="showMemberDetails('{{ row.name|e }}')">[i]</span>
</td>
{% for cell in row.months %}
<td title="{{ cell.tooltip }}"
<td data-month-idx="{{ loop.index0 }}" title="{{ cell.tooltip }}"
class="{% if cell.status == 'empty' %}cell-empty{% elif cell.status == 'unpaid' or cell.status == 'partial' %}cell-unpaid{% elif cell.status == 'ok' %}cell-ok{% endif %}{% if cell.overridden %} cell-overridden{% endif %}">
{{ cell.text }}
{% if cell.status == 'unpaid' or cell.status == 'partial' %}
@@ -522,7 +554,7 @@
TOTAL
</td>
{% for t in totals %}
<td class="{% if t.status == 'ok' %}cell-ok{% elif t.status == 'unpaid' %}cell-unpaid{% elif t.status == 'surplus' %}cell-overridden{% endif %}" style="padding-top: 4px; padding-bottom: 4px;">
<td data-month-idx="{{ loop.index0 }}" class="{% if t.status == 'ok' %}cell-ok{% elif t.status == 'unpaid' %}cell-unpaid{% elif t.status == 'surplus' %}cell-overridden{% endif %}" style="padding-top: 4px; padding-bottom: 4px;">
<span style="font-size: 0.6em; font-weight: normal; color: #666; text-transform: lowercase; display: block; margin-bottom: 2px;">received / expected</span>
{{ t.text }}
</td>
@@ -889,6 +921,38 @@
event.target.style.display = 'none';
}
}
// Month range filter
function applyMonthFilter() {
var fromIdx = parseInt(document.getElementById('fromMonth').value);
var toIdx = parseInt(document.getElementById('toMonth').value);
document.querySelectorAll('[data-month-idx]').forEach(function(el) {
var idx = parseInt(el.getAttribute('data-month-idx'));
if (idx >= fromIdx && idx <= toIdx) {
el.classList.remove('month-hidden');
} else {
el.classList.add('month-hidden');
}
});
}
function resetMonthFilter() {
var fromSelect = document.getElementById('fromMonth');
var toSelect = document.getElementById('toMonth');
fromSelect.value = 0;
toSelect.value = fromSelect.options.length - 1;
applyMonthFilter();
}
// Set defaults (last 5 months) and apply on load
(function() {
var fromSelect = document.getElementById('fromMonth');
var toSelect = document.getElementById('toMonth');
var defaultFrom = Math.max(0, fromSelect.options.length - 5);
fromSelect.value = defaultFrom;
toSelect.value = fromSelect.options.length - 1;
applyMonthFilter();
})();
</script>
</body>

View File

@@ -259,6 +259,24 @@
border-color: #00ff00;
}
.filter-select {
background-color: #1a1a1a;
border: 1px solid #333;
color: #00ff00;
font-family: inherit;
font-size: 11px;
padding: 4px 8px;
outline: none;
}
.filter-select:focus {
border-color: #00ff00;
}
.month-hidden {
display: none !important;
}
.filter-label {
color: #888;
text-transform: lowercase;
@@ -478,6 +496,20 @@
<div class="filter-container">
<span class="filter-label">search member:</span>
<input type="text" id="nameFilter" class="filter-input" placeholder="..." autocomplete="off">
<span class="filter-label" style="margin-left: 16px;">from:</span>
<select id="fromMonth" class="filter-select">
{% for m in months %}
<option value="{{ loop.index0 }}">{{ m }}</option>
{% endfor %}
</select>
<span class="filter-label" style="margin-left: 8px;">to:</span>
<select id="toMonth" class="filter-select">
{% for m in months %}
<option value="{{ loop.index0 }}">{{ m }}</option>
{% endfor %}
</select>
<button type="button" onclick="applyMonthFilter()" class="filter-select" style="cursor: pointer;">Apply</button>
<button type="button" onclick="resetMonthFilter()" class="filter-select" style="cursor: pointer;">All</button>
</div>
<div class="table-container">
@@ -486,7 +518,7 @@
<tr>
<th>Member</th>
{% for m in months %}
<th>{{ m }}</th>
<th data-month-idx="{{ loop.index0 }}">{{ m }}</th>
{% endfor %}
<th>Balance</th>
</tr>
@@ -499,7 +531,7 @@
<span class="info-icon" onclick="showMemberDetails('{{ row.name|e }}')">[i]</span>
</td>
{% for cell in row.months %}
<td title="{{ cell.tooltip }}"
<td data-month-idx="{{ loop.index0 }}" title="{{ cell.tooltip }}"
class="{% if cell.status == 'empty' %}cell-empty{% elif cell.status == 'unpaid' or cell.status == 'partial' %}cell-unpaid{% elif cell.status == 'ok' %}cell-ok{% endif %}{% if cell.overridden %} cell-overridden{% endif %}">
{{ cell.text }}
{% if cell.status == 'unpaid' or cell.status == 'partial' %}
@@ -522,7 +554,7 @@
TOTAL
</td>
{% for t in totals %}
<td class="{% if t.status == 'ok' %}cell-ok{% elif t.status == 'unpaid' %}cell-unpaid{% elif t.status == 'surplus' %}cell-overridden{% endif %}" style="padding-top: 4px; padding-bottom: 4px;">
<td data-month-idx="{{ loop.index0 }}" class="{% if t.status == 'ok' %}cell-ok{% elif t.status == 'unpaid' %}cell-unpaid{% elif t.status == 'surplus' %}cell-overridden{% endif %}" style="padding-top: 4px; padding-bottom: 4px;">
<span style="font-size: 0.6em; font-weight: normal; color: #666; text-transform: lowercase; display: block; margin-bottom: 2px;">received / expected</span>
{{ t.text }}
</td>
@@ -870,6 +902,38 @@
event.target.style.display = 'none';
}
}
// Month range filter
function applyMonthFilter() {
var fromIdx = parseInt(document.getElementById('fromMonth').value);
var toIdx = parseInt(document.getElementById('toMonth').value);
document.querySelectorAll('[data-month-idx]').forEach(function(el) {
var idx = parseInt(el.getAttribute('data-month-idx'));
if (idx >= fromIdx && idx <= toIdx) {
el.classList.remove('month-hidden');
} else {
el.classList.add('month-hidden');
}
});
}
function resetMonthFilter() {
var fromSelect = document.getElementById('fromMonth');
var toSelect = document.getElementById('toMonth');
fromSelect.value = 0;
toSelect.value = fromSelect.options.length - 1;
applyMonthFilter();
}
// Set defaults (last 5 months) and apply on load
(function() {
var fromSelect = document.getElementById('fromMonth');
var toSelect = document.getElementById('toMonth');
var defaultFrom = Math.max(0, fromSelect.options.length - 5);
fromSelect.value = defaultFrom;
toSelect.value = fromSelect.options.length - 1;
applyMonthFilter();
})();
</script>
</body>