From 6bb25381437611b8e5a662de5fe1c7c61e13b2ad Mon Sep 17 00:00:00 2001 From: Mark Kalsbeek Date: Sun, 5 Apr 2026 19:05:22 +0200 Subject: [PATCH] feat: energielabel badge, disable-filters toggle, freetext search - Render energielabel as a coloured EU-style letter badge (A+++ to G) instead of plain icon text - Add "Filters uit" toggle button that bypasses all numeric filters while keeping sort and freetext search active; turns orange when on - Add freetext search bar that filters across adres, stad, postcode, source_makelaar and woningtype in real time - Reset button also clears search and deactivates the disable toggle Co-Authored-By: Claude Sonnet 4.6 --- src/templates/index.html | 110 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 6 deletions(-) diff --git a/src/templates/index.html b/src/templates/index.html index c14b8c3..1658a30 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -357,6 +357,73 @@ .extra-kv-item .ek { color: var(--text-dimmer); } .extra-kv-item .ev { color: var(--text); margin-left: 0.3rem; } + /* ── Energielabel badge ── */ + .el-badge { + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.62rem; + font-weight: 700; + font-family: var(--font-mono); + padding: 0.1rem 0.35rem; + border-radius: 3px; + letter-spacing: 0.03em; + line-height: 1.5; + color: #fff; + min-width: 1.8rem; + text-align: center; + } + .el-Appp { background: #004f2d; } + .el-App { background: #006837; } + .el-Ap { background: #1a9641; } + .el-A { background: #3cb54a; } + .el-B { background: #69b444; } + .el-C { background: #a6d854; color: #2e2a25; } + .el-D { background: #f9c819; color: #2e2a25; } + .el-E { background: #f4a432; color: #2e2a25; } + .el-F { background: #e8612d; } + .el-G { background: #c0392b; } + .el-unknown { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); } + + /* ── Search bar ── */ + #f-search { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--text); + font-family: var(--font-ui); + font-size: 0.75rem; + font-weight: 500; + padding: 0.3rem 0.6rem; + outline: none; + width: 11rem; + transition: border-color 0.15s; + } + #f-search::placeholder { color: var(--text-dimmer); } + #f-search:focus { border-color: var(--accent); } + + /* ── Disable filters toggle ── */ + #filter-disable { + background: none; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--text-dimmer); + font-family: var(--font-ui); + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + padding: 0.3rem 0.7rem; + cursor: pointer; + transition: color 0.15s, border-color 0.15s, background 0.15s; + } + #filter-disable:hover { color: var(--text); border-color: var(--text-dim); } + #filter-disable.active { + background: var(--orange); + border-color: var(--orange); + color: #fff; + } + /* ── No results ── */ #empty { display: none; } #empty.visible { display: block; } @@ -407,6 +474,8 @@ + + @@ -464,6 +533,14 @@ function fmt_extra_val(v) { return s.length > 120 ? s.slice(0, 120) + '…' : s; } +function el_class(label) { + if (!label) return 'el-unknown'; + const s = label.replace(/\+/g, 'p').replace(/-/g, ''); + const map = { 'Appp': 'el-Appp', 'App': 'el-App', 'Ap': 'el-Ap', 'A': 'el-A', + 'B': 'el-B', 'C': 'el-C', 'D': 'el-D', 'E': 'el-E', 'F': 'el-F', 'G': 'el-G' }; + return map[s] || 'el-unknown'; +} + function ef(label, val) { if (val == null || val === '' || val === 'null') return ''; return `
@@ -529,7 +606,7 @@ function render_card(l) {
${l.woonoppervlak ? `
📐${l.woonoppervlak} m²
` : ''} ${l.kamers ? `
🚪${l.kamers} kamers
` : ''} - ${l.energielabel ? `
🔋${l.energielabel} energielabel
` : ''} + ${l.energielabel ? `
${l.energielabel}
` : ''}
meer ↓
@@ -558,6 +635,8 @@ function render_card(l) { // ── Filter + sort + render ── +let filters_disabled = false; + function get_filters() { return { ov_mark: parseInt(document.getElementById('f-ov-mark').value) || Infinity, @@ -566,6 +645,7 @@ function get_filters() { prijs: parseInt(document.getElementById('f-prijs').value) || Infinity, opp: parseInt(document.getElementById('f-opp').value) || 0, sort: document.getElementById('f-sort').value, + search: document.getElementById('f-search').value.trim().toLowerCase(), }; } @@ -583,11 +663,18 @@ const SORT_FNS = { function apply() { const f = get_filters(); let filtered = LISTINGS.filter(l => { - if (f.ov_mark < Infinity && (l.ov_mark == null || l.ov_mark > f.ov_mark)) return false; - if (f.ov_michelle < Infinity && (l.ov_michelle == null || l.ov_michelle > f.ov_michelle)) return false; - if (f.fiets_mark < Infinity && (l.fiets_mark == null || l.fiets_mark > f.fiets_mark)) return false; - if (l.prijs != null && l.prijs > f.prijs) return false; - if (f.opp > 0 && (l.woonoppervlak == null || l.woonoppervlak < f.opp)) return false; + if (!filters_disabled) { + if (f.ov_mark < Infinity && (l.ov_mark == null || l.ov_mark > f.ov_mark)) return false; + if (f.ov_michelle < Infinity && (l.ov_michelle == null || l.ov_michelle > f.ov_michelle)) return false; + if (f.fiets_mark < Infinity && (l.fiets_mark == null || l.fiets_mark > f.fiets_mark)) return false; + if (l.prijs != null && l.prijs > f.prijs) return false; + if (f.opp > 0 && (l.woonoppervlak == null || l.woonoppervlak < f.opp)) return false; + } + if (f.search) { + const haystack = [l.adres, l.stad, l.postcode, l.source_makelaar, l.woningtype] + .filter(Boolean).join(' ').toLowerCase(); + if (!haystack.includes(f.search)) return false; + } return true; }); @@ -630,10 +717,21 @@ document.querySelectorAll('#filters input, #filters select').forEach(el => { el.addEventListener('input', apply); }); +document.getElementById('filter-disable').addEventListener('click', () => { + filters_disabled = !filters_disabled; + document.getElementById('filter-disable').classList.toggle('active', filters_disabled); + document.getElementById('filter-disable').textContent = filters_disabled ? 'Filters aan' : 'Filters uit'; + apply(); +}); + document.getElementById('filter-reset').addEventListener('click', () => { Object.entries(DEFAULTS).forEach(([id, val]) => { document.getElementById(id).value = val; }); + document.getElementById('f-search').value = ''; + filters_disabled = false; + document.getElementById('filter-disable').classList.remove('active'); + document.getElementById('filter-disable').textContent = 'Filters uit'; apply(); });