Initial commit
This commit is contained in:
commit
536ff43ce4
158 files changed
+30355
No files matched your search
@@ -0,0 +1,179 @@
|
||||
{% 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 %}
|
||||
Reference in new issue
Block a user