Compare commits

..

2 Commits

Author SHA1 Message Date
9149d11a06 infra: get claude code from unstable 2026-04-05 19:06:05 +02:00
6bb2538143 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 <noreply@anthropic.com>
2026-04-05 19:05:22 +02:00
2 changed files with 108 additions and 9 deletions

View File

@@ -1,5 +1,7 @@
{ pkgs ? import <nixpkgs> { config.allowUnfree = true; } }:
let
unstable = import <nixos-unstable> { config.allowUnfree = true; };
in
pkgs.mkShell {
packages = [
(pkgs.python3.withPackages (ps: with ps; [
@@ -9,9 +11,8 @@ pkgs.mkShell {
lxml
waitress
]))
pkgs.claude-code
unstable.claude-code
];
shellHook = ''
if [ -f .env ]; then
set -a

View File

@@ -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 @@
<option value="opp_desc">Opp. ↓</option>
</select>
</div>
<input type="search" id="f-search" placeholder="Zoek adres, stad…">
<button id="filter-disable">Filters uit</button>
<button id="filter-reset">Reset</button>
</div>
@@ -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 `<div class="expanded-field">
@@ -529,7 +606,7 @@ function render_card(l) {
</div>
${l.woonoppervlak ? `<div class="card-meta-item"><span class="icon">📐</span><span class="val">${l.woonoppervlak} m²</span></div>` : ''}
${l.kamers ? `<div class="card-meta-item"><span class="icon">🚪</span><span class="val">${l.kamers} kamers</span></div>` : ''}
${l.energielabel ? `<div class="card-meta-item"><span class="icon">🔋</span><span class="val">${l.energielabel} energielabel</span></div>` : ''}
${l.energielabel ? `<div class="card-meta-item"><span class="el-badge ${el_class(l.energielabel)}">${l.energielabel}</span></div>` : ''}
</div>
<div class="card-toggle">meer ↓</div>
</div>
@@ -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 (!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();
});