Files
Pivot/templates/bingel.html
T
2026-10-14 12:29:29 +02:00

180 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Bingel{% endblock %}
{% block page_title %}Bingel{% endblock %}
{% block main_class %}wide{% endblock %}
{% block loader_text %}Leerlingen ophalen uit Informat...{% endblock %}
{% block extra_css %}
<style>
.balk { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin: 12px 0; }
.balk input[type=search] { flex: 0 1 300px; margin: 0; }
.balk select { width: auto; min-width: 180px; margin: 0; }
.tellers { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 6px; }
.tellers button { width: auto; margin: 0; padding: 4px 12px; border-radius: 999px; background: var(--surface-muted);
border: 1px solid var(--border); font-size: .88em; color: inherit; font-weight: 400; cursor: pointer; }
.tellers button:hover { border-color: var(--primary-color); }
.tellers button[aria-pressed="true"] { border-color: var(--primary-color); background: var(--primary-light); color: var(--primary-color); font-weight: 600; }
.tellers b { margin-left: 4px; }
.tellers .let { background: #fef3c7; border-color: #fcd34d; }
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; }
td.leeg { background: #fff7ed; }
.klaskeuze { border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 14px; margin: 10px 0; }
.klaskeuze-kop { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 8px; }
.klaskeuze-kop button { margin: 0; }
.klasvinken { display: flex; flex-wrap: wrap; gap: 6px 10px; }
.klasvinken label { display: inline-flex; gap: 6px; align-items: center; margin: 0; padding: 4px 10px; font-weight: 600;
border: 1px solid var(--border); border-radius: 999px; cursor: pointer; }
.klasvinken label:has(input:checked) { border-color: var(--primary-color); background: var(--primary-light); }
.klasvinken input { width: auto; margin: 0; }
.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 %}
{% block content %}
<div class="card">
<div class="page-head">
<h2>📗 Bingel · {{ school_naam }}</h2>
<div class="actions">
{% if rijen %}<a class="btn btn-success btn-sm" id="exportKnop" href="{{ url_for('basisonderwijs.bingel_export') }}">⬇️ Excel alle klassen</a>{% endif %}
<a class="btn btn-secondary btn-sm" href="{{ url_for('basisonderwijs.bingel', vernieuw=1) }}" onclick="showLoader()">🔄 Opnieuw ophalen</a>
</div>
</div>
<p class="field-hint" style="margin-top:-6px">Alle leerlingen uit Informat, klaar voor de import in Bingel. Kolommen in de Excel:
{{ kolommen|join(', ') }}. Het wachtwoord blijft leeg; het klasnummer is alfabetisch binnen de klas (familienaam, voornaam).</p>
{% if fout %}
<div class="alert alert-danger">{{ fout }}</div>
{% elif not rijen %}
<p class="muted">Informat gaf geen leerlingen terug.</p>
{% else %}
{% if waarschuwing %}<div class="alert alert-warning">{{ waarschuwing }}</div>{% endif %}
<div class="tellers" id="tellers">
<button type="button" data-filter="" aria-pressed="true" title="Alle leerlingen tonen">Leerlingen <b>{{ rijen|length }}</b></button>
<button type="button" data-filter="" title="Alle leerlingen tonen">Klassen <b>{{ klassen|length }}</b></button>
{% if zonder_klas %}<button type="button" class="let" data-filter="klas" aria-pressed="false" title="Enkel leerlingen zonder klas">Zonder klas <b>{{ zonder_klas }}</b></button>{% endif %}
{% if zonder_geslacht %}<button type="button" class="let" data-filter="geslacht" aria-pressed="false" title="Enkel leerlingen zonder geslacht M/V">Geslacht niet M/V <b>{{ zonder_geslacht }}</b></button>{% endif %}
</div>
<div class="klaskeuze">
<div class="klaskeuze-kop">
<strong>Klassen in de lijst</strong> <span class="muted small" id="klasTeller"></span>
<button type="button" class="btn-sm btn-light" id="allesAan">Alles aanvinken</button>
<button type="button" class="btn-sm btn-light" id="allesUit">Niets</button>
</div>
<div class="klasvinken" id="klasVinken">
{% for k in klassen %}
<label><input type="checkbox" value="{{ k }}" checked> {{ k or '(geen klas)' }}
<span class="muted small">{{ rijen|selectattr('klas', 'equalto', k)|list|length }}</span></label>
{% endfor %}
</div>
</div>
<div class="balk">
<input type="search" id="zoek" placeholder="Zoek op naam..." aria-label="Zoeken">
<span id="perPagina"></span>
</div>
<div class="table-wrap">
<table>
<thead><tr>{% for k in kolommen %}<th class="{{ 'sorteer' if k != 'Wachtwoord' }}" {{ 'data-tekst' if k not in ('Klasnummer',) }}>{{ k }}{% if k != 'Wachtwoord' %} <span class="pijl"></span>{% endif %}</th>{% endfor %}</tr></thead>
<tbody id="body">
{% for r in rijen %}
<tr data-klas="{{ r.klas }}" data-geen="{{ ' '.join([('klas' if not r.klas else ''), ('geslacht' if r.geslacht not in ('M', 'V') else '')]) }}">
<td class="{{ 'leeg' if not r.klas }}" data-sort="{{ r.klas }}"><strong>{{ r.klas }}</strong></td>
<td data-sort="{{ r.voornaam }}">{{ r.voornaam }}</td>
<td data-sort="{{ r.naam }}">{{ r.naam }}</td>
<td class="muted"></td>
<td data-sort="{{ r.klasnummer if r.klasnummer is not none else '' }}">{{ r.klasnummer if r.klasnummer is not none else '' }}</td>
<td class="{{ 'leeg' if r.geslacht not in ('M', 'V') }}" data-sort="{{ r.geslacht }}">{{ r.geslacht }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="tabel-voet">
<div class="muted small" id="teller"></div>
<nav class="paginering" id="paginering" aria-label="Pagina's"></nav>
</div>
{% if (zonder_klas or zonder_nummer or zonder_geslacht) and voorbeeld %}
<details style="margin-top:14px">
<summary class="muted small">Velden die Informat teruggeeft (om na te kijken waarom iets leeg blijft)</summary>
<p class="small" style="margin-top:6px"><strong>Leerling:</strong> <code>{{ voorbeeld.leerling|join(', ') }}</code></p>
<p class="small"><strong>Inschrijving:</strong> <code>{{ voorbeeld.inschrijving|join(', ') or '— geen inschrijvingen ontvangen —' }}</code></p>
{% if voorbeeld.klas_item %}<p class="small"><strong>Voorbeeld van een klas (inschrKlassen):</strong> <code>{{ voorbeeld.klas_item }}</code></p>{% endif %}
</details>
{% endif %}
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script>
(function () {
const body = document.getElementById('body');
if (!body) return;
const rijen = Array.from(body.rows), zoekVeld = document.getElementById('zoek');
const vinken = Array.from(document.querySelectorAll('#klasVinken input'));
const gekozen = () => new Set(vinken.filter(v => v.checked).map(v => v.value));
const exp = document.getElementById('exportKnop'), basis = exp.href;
let PER = perPagina('bingel', 25), pagina = 1, filter = '';
document.querySelectorAll('#tellers button').forEach(b => b.addEventListener('click', () => {
filter = b.dataset.filter;
document.querySelectorAll('#tellers button').forEach(x => x.setAttribute('aria-pressed', String(x === b)));
pagina = 1; toon();
}));
let kol = null, op = true;
document.querySelectorAll('th.sorteer').forEach(th => th.addEventListener('click', () => {
const k = th.cellIndex;
op = kol === k ? !op : true; kol = k;
document.querySelectorAll('th.sorteer').forEach(x => x.classList.remove('op', 'af'));
th.classList.add(op ? 'op' : 'af');
rijen.sort((a, b) => {
const x = a.cells[k].dataset.sort || '', y = b.cells[k].dataset.sort || '';
if (!x !== !y) return x ? -1 : 1;
const c = x.localeCompare(y, 'nl', { numeric: true, sensitivity: 'base' });
return op ? c : -c;
}).forEach(tr => body.appendChild(tr));
pagina = 1; toon();
}));
function toon() {
const k = gekozen(), z = zoekVeld.value.trim().toLowerCase();
const lijst = rijen.filter(tr => k.has(tr.dataset.klas) && (!z || tr.textContent.toLowerCase().includes(z))
&& (!filter || tr.dataset.geen.split(' ').includes(filter)));
const grootte = PER || Infinity, paginas = Math.max(1, Math.ceil(lijst.length / grootte));
pagina = Math.min(Math.max(1, pagina), paginas);
const van = PER ? (pagina - 1) * PER : 0, tot = van + grootte;
rijen.forEach(tr => tr.style.display = 'none');
lijst.forEach((tr, i) => tr.style.display = i >= van && i < tot ? '' : 'none');
document.getElementById('teller').textContent = lijst.length ? `${van + 1}–${Math.min(tot, lijst.length)} van ${lijst.length}` : 'Niets gevonden';
const nav = document.getElementById('paginering'); nav.innerHTML = '';
if (paginas > 1) {
const knop = (t, p, a) => { const b = document.createElement('button'); b.type = 'button'; b.textContent = t;
b.className = 'btn-sm ' + (a ? 'btn-secondary' : 'btn-light'); b.onclick = () => { pagina = p; toon(); }; nav.appendChild(b); };
if (pagina > 1) { knop('«', 1); knop('‹', 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('›', pagina + 1); knop('»', paginas); }
}
}
function klassenGewijzigd() {
const k = gekozen(), alle = k.size === vinken.length;
exp.href = alle ? basis : basis + '?' + Array.from(k).map(x => 'klas=' + encodeURIComponent(x)).join('&');
exp.textContent = alle ? '⬇️ Excel alle klassen' : (k.size === 1 ? '⬇️ Excel ' + Array.from(k)[0] : `⬇️ Excel ${k.size} klassen`);
exp.classList.toggle('disabled', !k.size);
exp.style.pointerEvents = k.size ? '' : 'none';
exp.style.opacity = k.size ? '' : '.5';
document.getElementById('klasTeller').textContent = `${k.size} van ${vinken.length} aangevinkt`;
pagina = 1; toon();
}
vinken.forEach(v => v.addEventListener('change', klassenGewijzigd));
document.getElementById('allesAan').onclick = () => { vinken.forEach(v => v.checked = true); klassenGewijzigd(); };
document.getElementById('allesUit').onclick = () => { vinken.forEach(v => v.checked = false); klassenGewijzigd(); };
zoekVeld.addEventListener('input', () => { pagina = 1; toon(); });
perPaginaKiezer(document.getElementById('perPagina'), 'bingel', n => { PER = n; pagina = 1; toon(); }, 25);
klassenGewijzigd();
})();
</script>
{% endblock %}