Improve year picker on stats page
Django CI/CD / test (push) Successful in 45s
Django CI/CD / build-and-push (push) Successful in 1m16s

This commit is contained in:
2026-06-07 10:48:23 +02:00
parent 2c2827df47
commit 3b9c05d674
6 changed files with 196 additions and 43 deletions
+4
View File
@@ -20,6 +20,7 @@ from common.components.primitives import (
ButtonGroup,
CsrfInput,
Div,
ExternalScript,
H1,
Icon,
Input,
@@ -35,6 +36,7 @@ from common.components.primitives import (
TableHeader,
TableRow,
TableTd,
YearPicker,
paginated_table_content,
)
from common.components.search_select import (
@@ -73,6 +75,7 @@ __all__ = [
"ButtonGroup",
"CsrfInput",
"Div",
"ExternalScript",
"H1",
"Icon",
"Input",
@@ -91,6 +94,7 @@ __all__ = [
"TableHeader",
"TableRow",
"TableTd",
"YearPicker",
"paginated_table_content",
"GameLink",
"GameStatus",
+86
View File
@@ -433,6 +433,92 @@ def ModuleScript(filename: str) -> SafeText:
)
def ExternalScript(url: str) -> SafeText:
"""A plain `<script src=...>` tag for an external/CDN script."""
return mark_safe(f'<script src="{url}"></script>')
def YearPicker(
year: int | None = None,
available_years: tuple[int, ...] = (),
url_template: str = "",
) -> SafeText:
"""A Flowbite-datepicker year picker.
`year` is the selected year, or ``None`` for the all-time view (the empty
state). `available_years` are the years to enable in the popup grid.
`url_template` is a navigation URL containing the literal ``__year__``
placeholder, substituted with the chosen year in JS (keeps this component
decoupled from the project's URL names).
The Flowbite-datepicker UMD bundle is *not* loaded here — the view hoists it
via ``render_page(scripts=...)``.
"""
label = str(year) if year is not None else "Choose a year"
selected = str(year) if year is not None else ""
classes = (
"bg-brand text-white border-transparent hover:bg-brand-strong"
if year is not None
else "bg-neutral-secondary-medium text-heading border border-default-medium "
"hover:bg-neutral-tertiary-medium focus:ring-4 focus:ring-brand-medium"
)
years_csv = ",".join(str(y) for y in available_years)
return mark_safe(f"""<div class="relative inline-block" x-data="{{ pickerOpen: false }}"
@keydown.escape.window="pickerOpen = false">
<button type="button"
x-on:click="pickerOpen = !pickerOpen; $refs.pickerInput._pickerInstance && ($refs.pickerInput._pickerInstance.active ? $refs.pickerInput._pickerInstance.hide() : $refs.pickerInput._pickerInstance.show())"
class="inline-flex items-center rounded-base px-4 py-2 text-sm font-medium {classes}">
{label}
<svg class="w-4 h-4 ms-2 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 10">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h12m0 0L9 1m4 4L9 9"/>
</svg>
</button>
<input type="text" x-ref="pickerInput" id="year-picker-input"
class="absolute opacity-0 pointer-events-none"
style="width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0;"
data-available-years="{years_csv}"
data-selected-year="{selected}"
data-url-template="{url_template}">
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {{
const pickerEl = document.getElementById('year-picker-input');
if (!pickerEl || pickerEl._pickerInstance) return;
const selectedYear = pickerEl.dataset.selectedYear;
const urlTemplate = pickerEl.dataset.urlTemplate;
const currentYear = new Date().getFullYear();
const availableYears = new Set(pickerEl.dataset.availableYears
.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n)));
const picker = new Datepicker(pickerEl, {{
pickLevel: 2,
format: 'yyyy',
minDate: new Date(1999, 0, 1),
maxDate: new Date(currentYear, 11, 31),
autohide: false,
orientation: 'bottom end',
showOnClick: false,
showOnFocus: false,
beforeShowYear: (date) => ({{ enabled: availableYears.has(date.getFullYear()) }})
}});
pickerEl._pickerInstance = picker;
picker.element.addEventListener('changeDate', (e) => {{
const year = e.detail.date?.getFullYear();
if (year && urlTemplate) {{
window.location.href = urlTemplate.replace('__year__', year);
}}
}});
if (selectedYear) {{
picker.dates = [new Date(parseInt(selectedYear), 0, 1)];
picker.update();
}}
}});
</script>""")
def AddForm(
form,
*,