Initial commit
This commit is contained in:
commit
536ff43ce4
158 files changed
+30355
No files matched your search
@@ -0,0 +1,365 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}IP-blokkades{% endblock %}
|
||||
{% block page_title %}IP-blokkades{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.ip-form { display: grid; grid-template-columns: 1.2fr 1fr 2fr auto; gap: 10px; align-items: end; }
|
||||
.ip-form.met-school { grid-template-columns: 1.2fr 1fr 1.4fr 2fr auto; }
|
||||
.ip-form .field { margin: 0; }
|
||||
.ip-form .school-veld[hidden] { display: none; }
|
||||
/* Keuzelijst met vinkjes voor één of meer scholen */
|
||||
.schoolkiezer { position: relative; }
|
||||
.schoolkiezer summary { list-style: none; cursor: pointer; padding: 9px 30px 9px 12px; border: 1px solid var(--border);
|
||||
border-radius: var(--radius); background: var(--surface, #fff); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
position: relative; min-height: 20px; }
|
||||
.schoolkiezer summary::-webkit-details-marker { display: none; }
|
||||
.schoolkiezer summary::after { content: "▾"; position: absolute; right: 11px; top: 50%; transform: translateY(-50%); opacity: .6; }
|
||||
.schoolkiezer[open] summary { border-color: var(--primary-color); }
|
||||
.schoolkiezer .sk-lijst { position: absolute; z-index: 30; left: 0; top: calc(100% + 4px); min-width: 100%; width: max-content;
|
||||
max-width: 380px; max-height: 300px; overflow: auto; background: var(--surface, #fff); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); box-shadow: 0 8px 24px rgba(15,23,42,.14); padding: 6px; }
|
||||
.schoolkiezer .sk-zoek { width: 100%; margin-bottom: 6px; }
|
||||
.schoolkiezer label { display: flex; gap: 8px; align-items: center; padding: 6px 8px; border-radius: 6px; cursor: pointer;
|
||||
font-weight: 400; margin: 0; white-space: nowrap; }
|
||||
.schoolkiezer label:hover { background: var(--surface-muted); }
|
||||
.schoolkiezer label input { width: auto; margin: 0; }
|
||||
.schoolkiezer .sk-acties { display: flex; justify-content: space-between; gap: 8px; padding: 6px 4px 2px; border-top: 1px solid var(--border); margin-top: 4px; }
|
||||
.schoolkiezer .sk-acties button { all: unset; cursor: pointer; color: var(--primary-color); font-size: .88em; }
|
||||
.chip { display: inline-block; padding: 2px 9px; margin: 2px 4px 2px 0; border-radius: 999px; background: var(--surface-muted);
|
||||
border: 1px solid var(--border); font-size: .88em; white-space: nowrap; }
|
||||
/* Bewerken in de tabel "Altijd toestaan" */
|
||||
tr .bew { display: none; }
|
||||
tr.bewerken .bew { display: block; }
|
||||
tr.bewerken .toon { display: none; }
|
||||
tr.bewerken td { background: var(--surface-muted); vertical-align: top; }
|
||||
tr.bewerken td.actions .bew { display: flex; flex-direction: column; gap: 6px; align-items: stretch; }
|
||||
tr.bewerken td.actions .bew button { width: 100%; white-space: nowrap; }
|
||||
tr .bew input[type=text] { width: 100%; min-width: 120px; }
|
||||
tr.bewerken .schoolkiezer { min-width: 200px; max-width: 280px; }
|
||||
tr.bewerken .schoolkiezer label { white-space: normal; }
|
||||
tr.bewerken .schoolkiezer .sk-lijst { position: static; box-shadow: none; max-width: none; width: auto; margin-top: 4px; }
|
||||
.ip-tabel[data-sleutel="toe"] tbody tr:not(.bewerken):not(.empty-row) td:not(.actions) { cursor: text; }
|
||||
.acties-rij { display: inline-flex; gap: 6px; }
|
||||
@media (max-width: 760px) { .ip-form { grid-template-columns: 1fr; } }
|
||||
.mijn-ip { display: inline-flex; gap: 8px; align-items: center; padding: 6px 12px; border-radius: 999px;
|
||||
background: var(--surface-muted); border: 1px solid var(--border); font-size: .92em; }
|
||||
td.mono, .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
.test-form { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
|
||||
.test-form input { flex: 1 1 220px; max-width: 320px; }
|
||||
.modus { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; justify-content: space-between;
|
||||
padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius); margin-top: 16px; }
|
||||
.modus.aan { border-color: var(--danger-text); background: var(--danger-bg); }
|
||||
.modus strong { display: block; }
|
||||
.schakelaar { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; font-weight: 600; }
|
||||
.schakelaar .spoor { width: 44px; height: 24px; border-radius: 999px; background: #cbd5e1; position: relative; transition: background .15s; flex-shrink: 0; }
|
||||
.schakelaar .spoor::after { content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%;
|
||||
background: #fff; transition: transform .15s; box-shadow: 0 1px 2px rgba(0,0,0,.25); }
|
||||
.schakelaar.aan .spoor { background: var(--danger-text); }
|
||||
.schakelaar.aan .spoor::after { transform: translateX(20px); }
|
||||
.schakelaar button { all: unset; display: inline-flex; align-items: center; gap: 10px; cursor: pointer; }
|
||||
.schakelaar button:focus-visible .spoor { outline: 2px solid var(--primary-color); outline-offset: 2px; }
|
||||
.tabel-zoek { margin-bottom: 10px; max-width: 360px; }
|
||||
th.sorteer { cursor: pointer; user-select: none; white-space: nowrap; }
|
||||
th.sorteer:hover { color: var(--primary-color); }
|
||||
th.sorteer .pijl::after { content: "↕"; opacity: .35; margin-left: 2px; }
|
||||
th.sorteer.op .pijl::after { content: "▲"; opacity: 1; }
|
||||
th.sorteer.af .pijl::after { content: "▼"; opacity: 1; }
|
||||
.tabel-voet { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 10px; margin-top: 12px; }
|
||||
.paginering { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% macro schoolkiezer(gekozen=[], formid='') %}
|
||||
<details class="schoolkiezer">
|
||||
<summary class="sk-tekst" data-leeg="— geen school —">— geen school —</summary>
|
||||
<div class="sk-lijst">
|
||||
{% if scholen|length > 8 %}<input type="search" class="sk-zoek" placeholder="Zoek een school..." aria-label="Zoek een school">{% endif %}
|
||||
{% for s in scholen %}
|
||||
<label><input type="checkbox" name="scholen" value="{{ s.code }}" data-naam="{{ s.naam }}"{% if formid %} form="{{ formid }}"{% endif %}{{ ' checked' if s.code in gekozen }}>
|
||||
{{ s.naam }}{% if not s.actief %} <span class="muted small">(inactief)</span>{% endif %}</label>
|
||||
{% else %}
|
||||
<div class="muted small" style="padding:6px 8px">Er zijn nog geen scholen.</div>
|
||||
{% endfor %}
|
||||
{% if scholen %}<div class="sk-acties"><button type="button" class="sk-alles">Alles aanvinken</button><button type="button" class="sk-geen">Niets</button></div>{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
{% endmacro %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="page-head">
|
||||
<h2>🚫 IP-blokkades</h2>
|
||||
<span class="mijn-ip">Jouw IP-adres: <strong class="mono">{{ mijn_ip }}</strong></span>
|
||||
</div>
|
||||
<p class="field-hint" style="margin-top:-6px">Geldt voor alle scholen. Een geblokkeerd adres krijgt bij elke aanvraag
|
||||
"Toegang geweigerd", ook op de loginpagina. Een adres bij <em>Altijd toestaan</em> mag altijd, ook als het in een
|
||||
geblokkeerde reeks valt.</p>
|
||||
|
||||
<form method="POST" action="{{ url_for('ipblokkade.toevoegen') }}" class="ip-form">
|
||||
<div class="field">
|
||||
<label for="reeks">IP-adres of reeks</label>
|
||||
<input type="text" id="reeks" name="reeks" required class="mono" placeholder="bv. 85. of 85.12.3.4 of 85.0.0.0/8">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="soort">Soort</label>
|
||||
<select id="soort" name="soort">
|
||||
<option value="blokkeren">🚫 Blokkeren</option>
|
||||
<option value="toestaan">✅ Altijd toestaan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field school-veld" hidden>
|
||||
<label>School(s)</label>
|
||||
{{ schoolkiezer() }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="opmerking">Opmerking</label>
|
||||
<input type="text" id="opmerking" name="opmerking" maxlength="255" placeholder="Waarom? (optioneel)">
|
||||
</div>
|
||||
<button type="submit">➕ Toevoegen</button>
|
||||
</form>
|
||||
|
||||
{% if lokaal %}
|
||||
<div class="alert alert-warning" style="margin:16px 0 0">PIVOT ziet jouw adres als <strong class="mono">{{ mijn_ip }}</strong>.
|
||||
Waarschijnlijk draait PIVOT achter een reverse proxy, en dan lijkt elke bezoeker van dat adres te komen. Zet in
|
||||
<code>config.ini › [algemeen] proxy_aantal = 1</code> en herstart PIVOT, anders werken de IP-regels niet zoals bedoeld.</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="modus {{ 'aan' if enkel_toegestaan }}">
|
||||
<div>
|
||||
<strong>Enkel "Altijd toestaan" binnenlaten</strong>
|
||||
<span class="muted small">
|
||||
{% if enkel_toegestaan %}Staat <b>aan</b>: elk adres dat niet bij Altijd toestaan staat, wordt geweigerd
|
||||
({{ modus_geweigerd }} keer sinds de teller begon){% if modus and modus.Door %} · aangezet door {{ modus.Door }}{% endif %}.
|
||||
{% else %}Staat uit: iedereen mag binnen, behalve de geblokkeerde adressen.{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<form method="POST" action="{{ url_for('ipblokkade.modus') }}" style="margin:0"
|
||||
onsubmit="return confirm({{ ("Iedereen weer binnenlaten (behalve de geblokkeerde adressen)?" if enkel_toegestaan else "Enkel nog adressen uit 'Altijd toestaan' binnenlaten? Iedereen anders krijgt 'Toegang geweigerd', ook op de loginpagina.")|tojson|forceescape }})">
|
||||
<input type="hidden" name="aan" value="{{ '0' if enkel_toegestaan else '1' }}">
|
||||
<span class="schakelaar {{ 'aan' if enkel_toegestaan }}">
|
||||
<button type="submit" role="switch" aria-checked="{{ 'true' if enkel_toegestaan else 'false' }}">
|
||||
<span class="spoor"></span> {{ 'Aan' if enkel_toegestaan else 'Uit' }}
|
||||
</button>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for titel, lijst, leeg, sleutel in (('🚫 Geblokkeerd', blokkeren, 'Er is nog niets geblokkeerd.', 'blok'),
|
||||
('✅ Altijd toestaan', toestaan, 'Geen uitzonderingen.', 'toe')) %}
|
||||
<div class="card ip-tabel" data-sleutel="{{ sleutel }}">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;">
|
||||
<h2 class="card-title">{{ titel }} <span class="muted small">({{ lijst|length }})</span></h2>
|
||||
{% if sleutel == 'blok' and lijst %}
|
||||
<form method="post" action="{{ url_for('ipblokkade.leegmaken') }}" style="margin:0;"
|
||||
onsubmit="return confirm({{ ('Alle ' ~ (lijst|length) ~ ' geblokkeerde regel' ~ ('s' if lijst|length != 1 else '') ~ ' verwijderen? De lijst \'Altijd toestaan\' blijft ongewijzigd.')|tojson|forceescape }})">
|
||||
<button type="submit" class="btn-sm btn-danger">🗑️ Bloklijst leegmaken</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if lijst|length > 10 %}<input type="search" class="tabel-zoek" placeholder="Zoek een reeks{{ ', school' if sleutel == 'toe' }} of opmerking..." aria-label="Zoeken in {{ titel }}">{% endif %}
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sorteer" data-tekst>Reeks <span class="pijl"></span></th>
|
||||
{% if sleutel == 'toe' %}<th class="sorteer" data-tekst>School <span class="pijl"></span></th>{% endif %}
|
||||
<th class="sorteer" data-tekst>Opmerking <span class="pijl"></span></th>
|
||||
<th class="sorteer">Toegevoegd <span class="pijl"></span></th>
|
||||
{% if lijst is sameas blokkeren %}<th class="sorteer text-right">Geweigerd <span class="pijl"></span></th>
|
||||
<th class="sorteer">Laatst geweigerd <span class="pijl"></span></th>{% endif %}
|
||||
<th class="actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in lijst %}
|
||||
{% set bf = 'bew-' ~ r.ID %}
|
||||
<tr data-id="{{ r.ID }}">
|
||||
{% if sleutel == 'toe' %}
|
||||
<td class="mono" data-sort="{{ r.Sorteer }}"><span class="toon"><strong>{{ r.Reeks }}</strong></span>
|
||||
<span class="bew"><input type="text" name="reeks" form="{{ bf }}" value="{{ r.Reeks }}" required class="mono" aria-label="Reeks"></span></td>
|
||||
<td data-sort="{{ r.Scholen_namen|join(', ') }}"><span class="toon">{% for n in r.Scholen_namen %}<span class="chip">{{ n }}</span>{% else %}<span class="muted">–</span>{% endfor %}</span>
|
||||
<span class="bew">{{ schoolkiezer(r.Scholen_codes, bf) }}</span></td>
|
||||
<td data-sort="{{ r.Opmerking or '' }}"><span class="toon">{{ r.Opmerking or '' }}</span>
|
||||
<span class="bew"><input type="text" name="opmerking" form="{{ bf }}" value="{{ r.Opmerking or '' }}" maxlength="255" placeholder="Opmerking" aria-label="Opmerking"></span></td>
|
||||
{% else %}
|
||||
<td class="mono" data-sort="{{ r.Sorteer }}"><strong>{{ r.Reeks }}</strong></td>
|
||||
<td data-sort="{{ r.Opmerking or '' }}">{{ r.Opmerking or '' }}</td>
|
||||
{% endif %}
|
||||
<td class="muted small" data-sort="{{ r.Aangemaakt.isoformat() }}">{{ r.Aangemaakt.strftime('%d-%m-%Y %H:%M') }}{% if r.Door %} · {{ r.Door }}{% endif %}</td>
|
||||
{% if lijst is sameas blokkeren %}
|
||||
<td class="text-right" data-sort="{{ '%010d'|format(r.Aantal_geweigerd) }}">{{ r.Aantal_geweigerd }}</td>
|
||||
<td class="muted small" data-sort="{{ r.Laatst_geweigerd.isoformat() if r.Laatst_geweigerd else '' }}">{{ r.Laatst_geweigerd.strftime('%d-%m-%Y %H:%M') if r.Laatst_geweigerd else '–' }}</td>
|
||||
{% endif %}
|
||||
<td class="actions">
|
||||
<span class="toon acties-rij">
|
||||
{% if sleutel == 'toe' %}<button type="button" class="btn-sm btn-ghost start-bew">✏️ Bewerken</button>{% endif %}
|
||||
<form method="POST" action="{{ url_for('ipblokkade.verwijderen', regel_id=r.ID) }}" style="margin:0"
|
||||
onsubmit="return confirm({{ (r.Reeks ~ ' uit de lijst verwijderen?')|tojson|forceescape }})">
|
||||
<button type="submit" class="btn-sm btn-ghost">🗑️ Verwijderen</button>
|
||||
</form>
|
||||
</span>
|
||||
{% if sleutel == 'toe' %}
|
||||
<span class="bew">
|
||||
<form id="{{ bf }}" method="POST" action="{{ url_for('ipblokkade.bewerken', regel_id=r.ID) }}" style="margin:0">
|
||||
<button type="submit" class="btn-sm">💾 Opslaan</button>
|
||||
</form>
|
||||
<button type="button" class="btn-sm btn-light stop-bew">Annuleren</button>
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr class="empty-row"><td colspan="6">{{ leeg }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if lijst %}
|
||||
<div class="tabel-voet">
|
||||
<div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap">
|
||||
<div class="muted small teller"></div>
|
||||
<span class="per-pagina-plek"></span>
|
||||
</div>
|
||||
<nav class="paginering" aria-label="Pagina's"></nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="card">
|
||||
<h2 class="card-title">🔎 Een adres testen</h2>
|
||||
<form method="GET" action="{{ url_for('ipblokkade.overzicht') }}" class="test-form">
|
||||
<input type="text" name="test" class="mono" placeholder="bv. 85.12.3.4" value="{{ test.ip if test else '' }}">
|
||||
<button type="submit" class="btn-secondary">Testen</button>
|
||||
{% if test %}
|
||||
{% if test.geweigerd %}
|
||||
<span class="badge badge-danger">🚫 Geweigerd door {{ test.regel.Reeks if test.regel else 'een regel' }}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-success">✅ Toegelaten</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Keuzelijst met vinkjes: tekst bovenaan = gekozen scholen
|
||||
function skTekst(sk) {
|
||||
const namen = Array.from(sk.querySelectorAll('input[type=checkbox]:checked')).map(c => c.dataset.naam);
|
||||
const t = sk.querySelector('.sk-tekst');
|
||||
t.textContent = !namen.length ? t.dataset.leeg : namen.length <= 2 ? namen.join(', ') : `${namen.length} scholen`;
|
||||
t.title = namen.join('\n');
|
||||
}
|
||||
document.querySelectorAll('.schoolkiezer').forEach(sk => {
|
||||
skTekst(sk);
|
||||
sk.addEventListener('change', () => skTekst(sk));
|
||||
const zoek = sk.querySelector('.sk-zoek');
|
||||
if (zoek) zoek.addEventListener('keydown', e => { if (e.key === 'Enter') e.preventDefault(); });
|
||||
if (zoek) zoek.addEventListener('input', () => {
|
||||
const q = zoek.value.trim().toLowerCase();
|
||||
sk.querySelectorAll('label').forEach(l => l.style.display = l.textContent.toLowerCase().includes(q) ? '' : 'none');
|
||||
});
|
||||
sk.querySelector('.sk-alles')?.addEventListener('click', () => {
|
||||
sk.querySelectorAll('label').forEach(l => { if (l.style.display !== 'none') l.querySelector('input').checked = true; });
|
||||
skTekst(sk);
|
||||
});
|
||||
sk.querySelector('.sk-geen')?.addEventListener('click', () => {
|
||||
sk.querySelectorAll('input[type=checkbox]').forEach(c => c.checked = false); skTekst(sk);
|
||||
});
|
||||
sk.addEventListener('toggle', () => { if (sk.open && zoek) zoek.focus(); });
|
||||
});
|
||||
// Klik buiten een open keuzelijst = dichtklappen
|
||||
document.addEventListener('click', e => {
|
||||
document.querySelectorAll('.schoolkiezer[open]').forEach(sk => { if (!sk.contains(e.target)) sk.open = false; });
|
||||
});
|
||||
// Tabel "Altijd toestaan" bewerken: ✏️ of dubbelklik op een rij; Esc = annuleren, Enter = opslaan
|
||||
function stopBewerken(tr) {
|
||||
const f = document.getElementById('bew-' + tr.dataset.id);
|
||||
if (f) f.reset();
|
||||
tr.classList.remove('bewerken');
|
||||
tr.querySelectorAll('.schoolkiezer').forEach(sk => { sk.open = false; skTekst(sk); });
|
||||
}
|
||||
function startBewerken(tr) {
|
||||
document.querySelectorAll('tr.bewerken').forEach(stopBewerken);
|
||||
tr.classList.add('bewerken');
|
||||
const veld = tr.querySelector('.bew input[name=reeks]');
|
||||
if (veld) { veld.focus(); veld.select(); }
|
||||
}
|
||||
document.querySelectorAll('.ip-tabel[data-sleutel="toe"] tbody tr[data-id]').forEach(tr => {
|
||||
tr.querySelector('.start-bew')?.addEventListener('click', () => startBewerken(tr));
|
||||
tr.querySelector('.stop-bew')?.addEventListener('click', () => stopBewerken(tr));
|
||||
tr.addEventListener('dblclick', e => {
|
||||
if (!tr.classList.contains('bewerken') && !e.target.closest('td.actions')) startBewerken(tr);
|
||||
});
|
||||
tr.addEventListener('keydown', e => { if (e.key === 'Escape') stopBewerken(tr); });
|
||||
});
|
||||
|
||||
// Schoolkeuze enkel tonen bij "Altijd toestaan"
|
||||
(function () {
|
||||
const soort = document.getElementById('soort'), veld = document.querySelector('.school-veld');
|
||||
if (!soort || !veld) return;
|
||||
const pas = () => {
|
||||
const toe = soort.value === 'toestaan';
|
||||
veld.hidden = !toe;
|
||||
veld.closest('form').classList.toggle('met-school', toe);
|
||||
if (!toe) veld.querySelectorAll('input[type=checkbox]').forEach(c => c.checked = false);
|
||||
veld.querySelectorAll('.schoolkiezer').forEach(skTekst);
|
||||
};
|
||||
soort.addEventListener('change', pas); pas();
|
||||
})();
|
||||
// Zoeken en pagineren per tabel (Geblokkeerd / Altijd toestaan)
|
||||
document.querySelectorAll('.ip-tabel').forEach(kaart => {
|
||||
const body = kaart.querySelector('tbody');
|
||||
if (!body || body.querySelector('.empty-row')) return;
|
||||
const sleutel = 'ip-' + kaart.dataset.sleutel;
|
||||
let PER_PAGINA = perPagina(sleutel), pagina = 1, zoek = '';
|
||||
const zoekveld = kaart.querySelector('.tabel-zoek');
|
||||
function toon() {
|
||||
const tekst = tr => Array.from(tr.cells).map(td => (td.querySelector('.toon') || td).textContent).join(' ').toLowerCase();
|
||||
const rijen = Array.from(body.rows).filter(tr => !zoek || tekst(tr).includes(zoek));
|
||||
const grootte = PER_PAGINA || Infinity, paginas = Math.max(1, Math.ceil(rijen.length / grootte));
|
||||
pagina = Math.min(Math.max(1, pagina), paginas);
|
||||
const van = PER_PAGINA ? (pagina - 1) * PER_PAGINA : 0, tot = van + grootte;
|
||||
Array.from(body.rows).forEach(tr => tr.style.display = 'none');
|
||||
rijen.forEach((tr, i) => tr.style.display = (i >= van && i < tot) ? '' : 'none');
|
||||
kaart.querySelector('.teller').textContent = rijen.length
|
||||
? `${van + 1}–${Math.min(tot, rijen.length)} van ${rijen.length}` : 'Niets gevonden';
|
||||
const nav = kaart.querySelector('.paginering');
|
||||
nav.innerHTML = '';
|
||||
if (paginas <= 1) return;
|
||||
const knop = (label, p, actief) => {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button'; b.textContent = label; b.className = 'btn-sm ' + (actief ? 'btn-secondary' : 'btn-light');
|
||||
b.onclick = () => { pagina = p; toon(); };
|
||||
nav.appendChild(b);
|
||||
};
|
||||
if (pagina > 1) { knop('«', 1); knop('‹ Vorige', pagina - 1); }
|
||||
for (let p = Math.max(1, pagina - 2); p <= Math.min(paginas, pagina + 2); p++) knop(String(p), p, p === pagina);
|
||||
if (pagina < paginas) { knop('Volgende ›', pagina + 1); knop('»', paginas); }
|
||||
}
|
||||
if (zoekveld) zoekveld.addEventListener('input', () => { zoek = zoekveld.value.trim().toLowerCase(); pagina = 1; toon(); });
|
||||
// Sorteren: klik op een kolomkop (nog eens klikken = omgekeerd). Aantallen en datums: eerst hoog/nieuw.
|
||||
let kol = null, op = true;
|
||||
kaart.querySelectorAll('th.sorteer').forEach(th => th.addEventListener('click', () => {
|
||||
const k = th.cellIndex;
|
||||
op = kol === k ? !op : 'tekst' in th.dataset;
|
||||
kol = k;
|
||||
kaart.querySelectorAll('th.sorteer').forEach(x => x.classList.remove('op', 'af'));
|
||||
th.classList.add(op ? 'op' : 'af');
|
||||
Array.from(body.rows).sort((a, b) => {
|
||||
const x = a.cells[k].dataset.sort || '', y = b.cells[k].dataset.sort || '';
|
||||
if (!x !== !y) return x ? -1 : 1; // lege waarden altijd onderaan
|
||||
const c = x.localeCompare(y, 'nl', { numeric: true, sensitivity: 'base' });
|
||||
return op ? c : -c;
|
||||
}).forEach(tr => body.appendChild(tr));
|
||||
pagina = 1; toon();
|
||||
}));
|
||||
perPaginaKiezer(kaart.querySelector('.per-pagina-plek'), sleutel, n => { PER_PAGINA = n; pagina = 1; toon(); });
|
||||
toon();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user