From 85f9de74bfbcca4c68573b1eaaa21cc2adae95b9 Mon Sep 17 00:00:00 2001 From: Meik Date: Mon, 23 Feb 2026 20:48:55 +0100 Subject: [PATCH] Implement direct days-until-start column filter --- web/style.css | 24 ++++++++++ web/trade-fairs.js | 106 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/web/style.css b/web/style.css index e49b918..0cbd7c7 100644 --- a/web/style.css +++ b/web/style.css @@ -2038,6 +2038,30 @@ h1 { color: #0f4bb8; } +.bookmark-subpage__th-stack { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 5px; +} + +.bookmark-subpage__column-filter { + width: 84px; + max-width: 100%; + border: 1px solid #cbd5e1; + border-radius: 4px; + background: #fff; + color: #111827; + font-size: 11px; + padding: 2px 6px; +} + +.bookmark-subpage__column-filter:focus { + outline: 2px solid #93c5fd; + outline-offset: 0; + border-color: #60a5fa; +} + .bookmark-subpage__table th[draggable="true"] { cursor: move; } diff --git a/web/trade-fairs.js b/web/trade-fairs.js index 830d96b..7878d20 100644 --- a/web/trade-fairs.js +++ b/web/trade-fairs.js @@ -1,6 +1,7 @@ (function initTradeFairsSubpage() { const tableBody = document.getElementById('tradeFairTableBody'); const searchInput = document.getElementById('tradeFairSearchInput'); + const daysFilterInput = document.getElementById('tradeFairDaysFilterInput'); const meta = document.getElementById('tradeFairMeta'); const columnSettingsBtn = document.getElementById('tradeFairColumnSettingsBtn'); const columnsModal = document.getElementById('tradeFairColumnsModal'); @@ -719,6 +720,7 @@ let sortKey = 'rang'; let sortDirection = 'asc'; let searchTerm = ''; + let daysFilterTerm = ''; let draggedColumnKey = null; let lastOpenedByTradeFair = {}; let columnVisibility = Object.fromEntries(DEFAULT_COLUMN_ORDER.map((key) => [key, true])); @@ -853,6 +855,95 @@ return haystack.includes(term); } + function parseDaysFilter(term) { + const normalized = String(term || '').trim(); + if (!normalized) { + return { type: 'all' }; + } + + const comparisonMatch = normalized.match(/^(<=|>=|!=|=|<|>)\s*(-?\d+)$/); + if (comparisonMatch) { + return { + type: 'comparison', + operator: comparisonMatch[1], + value: Number(comparisonMatch[2]) + }; + } + + const rangeMatch = normalized.match(/^(-?\d+)\s*-\s*(-?\d+)$/); + if (rangeMatch) { + const first = Number(rangeMatch[1]); + const second = Number(rangeMatch[2]); + return { + type: 'range', + min: Math.min(first, second), + max: Math.max(first, second) + }; + } + + const exactMatch = normalized.match(/^-?\d+$/); + if (exactMatch) { + return { + type: 'comparison', + operator: '=', + value: Number(normalized) + }; + } + + return { type: 'invalid', raw: normalized }; + } + + function matchesDaysFilter(value, parsedFilter) { + if (!parsedFilter || parsedFilter.type === 'all' || parsedFilter.type === 'invalid') { + return true; + } + + const numeric = toNumber(value); + if (numeric === null) { + return false; + } + + if (parsedFilter.type === 'range') { + return numeric >= parsedFilter.min && numeric <= parsedFilter.max; + } + + if (parsedFilter.type === 'comparison') { + switch (parsedFilter.operator) { + case '>': + return numeric > parsedFilter.value; + case '>=': + return numeric >= parsedFilter.value; + case '<': + return numeric < parsedFilter.value; + case '<=': + return numeric <= parsedFilter.value; + case '!=': + return numeric !== parsedFilter.value; + case '=': + default: + return numeric === parsedFilter.value; + } + } + + return true; + } + + function getDaysFilterMetaLabel(parsedFilter) { + if (!parsedFilter || parsedFilter.type === 'all') { + return ''; + } + + if (parsedFilter.type === 'invalid') { + return `Tage-bis-Start-Filter ungültig (${parsedFilter.raw})`; + } + + if (parsedFilter.type === 'range') { + return `Tage-bis-Start-Filter ${parsedFilter.min}-${parsedFilter.max}`; + } + + return `Tage-bis-Start-Filter ${parsedFilter.operator}${parsedFilter.value}`; + } + function sortRows(rows) { return [...rows].sort((a, b) => { const aValue = getSortValue(a, sortKey); @@ -1570,7 +1661,10 @@ function render() { const normalizedRows = EFFECTIVE_TRADE_FAIRS.map(normalizeRow); - const filtered = normalizedRows.filter((row) => matchesSearch(row, searchTerm)); + const parsedDaysFilter = parseDaysFilter(daysFilterTerm); + const filtered = normalizedRows.filter( + (row) => matchesSearch(row, searchTerm) && matchesDaysFilter(row.tage_bis_start, parsedDaysFilter) + ); const sorted = sortRows(filtered); const visibleColumnOrder = getVisibleColumnOrder(); @@ -1598,7 +1692,8 @@ const activeSortButton = sortButtons.find((button) => button.dataset.tradeSort === sortKey); const sortLabel = activeSortButton?.dataset.baseLabel || sortKey; - meta.textContent = `${sorted.length} von ${EFFECTIVE_TRADE_FAIRS.length} Messen | Sortierung: ${sortLabel} (${sortDirection === 'asc' ? 'aufsteigend' : 'absteigend'})`; + const daysFilterMeta = getDaysFilterMetaLabel(parsedDaysFilter); + meta.textContent = `${sorted.length} von ${EFFECTIVE_TRADE_FAIRS.length} Messen | Sortierung: ${sortLabel} (${sortDirection === 'asc' ? 'aufsteigend' : 'absteigend'})${daysFilterMeta ? ` | ${daysFilterMeta}` : ''}`; } sortButtons.forEach((button) => { @@ -1632,6 +1727,13 @@ render(); }); + if (daysFilterInput) { + daysFilterInput.addEventListener('input', () => { + daysFilterTerm = daysFilterInput.value.trim(); + render(); + }); + } + lastOpenedByTradeFair = loadLastOpenedState(); loadColumnOrder(); loadColumnVisibility();