PIVOT 1.1.0

This commit is contained in:
marijn committed 2026-10-14 13:34:37 +02:00
commit 8a53234eb6
161 files changed
+31109

No files matched your search

+201
View File
@@ -0,0 +1,201 @@
{% extends "base.html" %}
{% block title %}{{ u.Gebruikersnaam }} – rechten{% endblock %}
{% block page_title %}Gebruiker: {{ u.Gebruikersnaam }}{% endblock %}
{% block extra_css %}
<style>
.terug { margin-bottom: 14px; display: inline-block; }
.rechten-layout { display: grid; grid-template-columns: minmax(240px, 320px) 1fr; gap: 20px; align-items: start; }
.scholen-paneel { border: 1px solid var(--border); border-radius: var(--radius-lg, 12px); overflow: hidden;
position: sticky; top: 12px; }
.scholen-paneel .zoek { padding: 10px; border-bottom: 1px solid var(--border); background: var(--surface-muted); }
.scholen-paneel .zoek input[type=search] { width: 100%; }
.scholen-paneel .zoek label { font-size: .85rem; margin-top: 8px; display: flex; gap: 6px; align-items: center; font-weight: 400; }
.scholen-lijst { list-style: none; margin: 0; padding: 0; max-height: 62vh; overflow-y: auto; }
.scholen-lijst a { display: flex; justify-content: space-between; gap: 8px; padding: 9px 12px; text-decoration: none;
color: inherit; border-bottom: 1px solid var(--border); font-size: .92rem; }
.scholen-lijst a:hover { background: var(--primary-light); }
.scholen-lijst a.actief { background: var(--primary-light); border-left: 3px solid var(--primary-color); font-weight: 700; }
.scholen-lijst a.leeg { color: var(--text-muted, #64748b); }
.scholen-lijst .teller { white-space: nowrap; font-size: .8rem; font-weight: 600; color: var(--primary-color); }
.scholen-lijst a.leeg .teller { color: var(--text-muted, #94a3b8); font-weight: 400; }
.scholen-lijst .geen { padding: 12px; color: var(--text-muted, #64748b); font-size: .9rem; }
.school-select { display: none; margin-bottom: 14px; }
.cat { border: 1px solid var(--border); border-radius: 10px; margin-bottom: 12px; }
.cat-kop { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px;
background: var(--surface-muted); border-bottom: 1px solid var(--border); border-radius: 10px 10px 0 0; }
.cat-kop strong { color: var(--primary-color); font-size: .92rem; }
.cat-kop .knoppen button, .globaal button { padding: 2px 10px; font-size: 12px; }
.vinkjes { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 2px 16px; padding: 8px 12px; }
.vinkjes label { display: flex; gap: 8px; align-items: center; font-weight: 400; padding: 5px 0; cursor: pointer; margin: 0; }
.vinkjes input { width: 17px; height: 17px; flex: none; }
.globaal { display: flex; gap: 6px; align-items: center; }
details.ook { border: 1px dashed var(--border); border-radius: 10px; padding: 10px 12px; margin: 14px 0; }
details.ook summary { cursor: pointer; font-weight: 600; }
.ook-lijst { max-height: 260px; overflow-y: auto; margin-top: 8px; columns: 2 260px; }
.ook-lijst label { display: flex; gap: 8px; align-items: center; font-weight: 400; margin: 0; padding: 3px 0; break-inside: avoid; }
.ook-lijst input, details.ook .alle input { width: auto; }
@media (max-width: 760px) {
.rechten-layout { grid-template-columns: 1fr; }
.scholen-paneel { display: none; }
.school-select { display: block; }
}
</style>
{% endblock %}
{% block content %}
<a class="terug" href="{{ url_for('admin.settings') }}">← Alle gebruikers</a>
<div class="card">
<div class="page-head">
<h2>🔐 Rechten van {{ u.Gebruikersnaam }}</h2>
<a class="btn btn-sm btn-light" href="{{ url_for('admin.gebruiker', gid=u.Gebruiker_ID) }}">👤 Account</a>
</div>
{% if u.Veiligheidsgroep == ADMIN %}
<div class="alert alert-info">{{ u.Gebruikersnaam }} is <strong>administrator</strong> en heeft daardoor altijd alle rechten in alle scholen.
De vinkjes hieronder gelden pas als de rol ooit "Gebruiker" wordt.</div>
{% endif %}
{% if not scholen %}
<p class="muted">Je mag in geen enkele school rechten instellen.</p>
{% else %}
<div class="rechten-layout">
{# ---------- Scholen ---------- #}
<div class="scholen-paneel">
<div class="zoek">
<input type="search" id="zoekSchool" placeholder="🔍 Zoek school… ({{ scholen|length }})" oninput="filterScholen()">
<label><input type="checkbox" id="enkelMet" onchange="filterScholen()" style="width:auto"> Enkel scholen met rechten</label>
</div>
<ul class="scholen-lijst" id="scholenLijst">
{% for s in scholen %}
{% set n = aantallen.get(s.id, 0) %}
<li data-naam="{{ s.naam|lower }}" data-n="{{ n }}">
<a href="{{ url_for('admin.gebruiker_rechten', gid=u.Gebruiker_ID, school=s.id) }}"
class="{{ 'actief' if gekozen and s.id == gekozen.id else '' }} {{ 'leeg' if not n else '' }}">
<span>{{ s.naam }}{% if not s.actief %} <span class="badge badge-warning">inactief</span>{% endif %}</span>
<span class="teller">{{ n }}/{{ totaal }}</span>
</a>
</li>
{% endfor %}
<li class="geen" id="geenScholen" style="display:none">Geen scholen gevonden.</li>
</ul>
</div>
{# ---------- Rechten van de gekozen school ---------- #}
<div>
<select class="school-select" onchange="location.href=this.value" aria-label="School">
{% for s in scholen %}
<option value="{{ url_for('admin.gebruiker_rechten', gid=u.Gebruiker_ID, school=s.id) }}"
{% if gekozen and s.id == gekozen.id %}selected{% endif %}>{{ s.naam }} ({{ aantallen.get(s.id, 0) }}/{{ totaal }})</option>
{% endfor %}
</select>
{% if gekozen %}
<form method="POST" action="{{ url_for('admin.gebruiker_rechten', gid=u.Gebruiker_ID) }}" id="rechtenForm"
onsubmit="return bevestig()">
<input type="hidden" name="school_id" value="{{ gekozen.id }}">
<div class="page-head" style="margin-bottom:10px">
<h3 style="margin:0">🏫 {{ gekozen.naam }}</h3>
<div class="globaal">
<span class="muted small" id="teller"></span>
<button type="button" class="btn-light" onclick="zet(null, true)">alles</button>
<button type="button" class="btn-light" onclick="zet(null, false)">niets</button>
</div>
</div>
<p class="muted small" style="margin-top:0">Zonder vinkje in deze school kan {{ u.Gebruikersnaam }} er niet inloggen.</p>
{% for cat, items in categorieen.items() %}
<div class="cat">
<div class="cat-kop">
<strong>{{ cat }}</strong>
<span class="knoppen">
<button type="button" class="btn-light" onclick="zet('{{ loop.index }}', true)">alles</button>
<button type="button" class="btn-light" onclick="zet('{{ loop.index }}', false)">niets</button>
</span>
</div>
<div class="vinkjes">
{% set ci = loop.index %}
{% for code, naam in items %}
<label><input type="checkbox" name="r|{{ code }}" value="1" data-cat="{{ ci }}" onchange="tel()"
{% if code in huidig %}checked{% endif %}> {{ naam }}</label>
{% endfor %}
</div>
</div>
{% endfor %}
{% if scholen|length > 1 %}
<details class="ook">
<summary>📋 Deze rechten ook toepassen op andere scholen…</summary>
<p class="field-hint">De aangevinkte scholen krijgen exact dezelfde rechten als {{ gekozen.naam }}. Wat {{ u.Gebruikersnaam }} daar nu mag, wordt overschreven.</p>
<label class="alle" style="font-weight:600;display:flex;gap:8px;align-items:center">
<input type="checkbox" name="ook_alle" value="1" id="ookAlle" onchange="ookAlleWissel()"> Alle {{ scholen|length }} scholen</label>
<input type="search" placeholder="🔍 Zoek school…" oninput="filterOok(this.value)" style="margin-top:8px">
<div class="ook-lijst" id="ookLijst">
{% for s in scholen if s.id != gekozen.id %}
<label data-naam="{{ s.naam|lower }}"><input type="checkbox" name="ook" value="{{ s.id }}"> {{ s.naam }}
<span class="muted small">({{ aantallen.get(s.id, 0) }})</span></label>
{% endfor %}
</div>
</details>
{% endif %}
<button type="submit" class="btn-success">💾 Rechten opslaan</button>
</form>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script>
function vinkjes(cat) {
var sel = '#rechtenForm .vinkjes input' + (cat ? '[data-cat="' + cat + '"]' : '');
return document.querySelectorAll(sel);
}
function zet(cat, aan) { vinkjes(cat).forEach(function (cb) { cb.checked = aan; }); tel(); }
function tel() {
var t = document.getElementById('teller'); if (!t) return;
var alle = vinkjes(null), n = 0;
alle.forEach(function (cb) { if (cb.checked) n++; });
t.textContent = n + ' van ' + alle.length + ' aangevinkt';
}
function filterScholen() {
var q = document.getElementById('zoekSchool').value.trim().toLowerCase();
var enkel = document.getElementById('enkelMet').checked, zichtbaar = 0;
document.querySelectorAll('#scholenLijst li[data-naam]').forEach(function (li) {
var ok = li.dataset.naam.indexOf(q) !== -1 && (!enkel || li.dataset.n !== '0');
li.style.display = ok ? '' : 'none'; if (ok) zichtbaar++;
});
document.getElementById('geenScholen').style.display = zichtbaar ? 'none' : '';
}
function filterOok(q) {
q = q.trim().toLowerCase();
document.querySelectorAll('#ookLijst label').forEach(function (l) {
l.style.display = l.dataset.naam.indexOf(q) !== -1 ? '' : 'none';
});
}
function ookAlleWissel() {
var aan = document.getElementById('ookAlle').checked;
document.querySelectorAll('#ookLijst input').forEach(function (cb) { cb.checked = aan; cb.disabled = aan; });
}
function bevestig() {
var alle = document.getElementById('ookAlle');
var n = (alle && alle.checked) ? {{ scholen|length - 1 }} : document.querySelectorAll('#ookLijst input:checked').length;
if (!n) { if (typeof showLoader === 'function') showLoader(); return true; }
var ok = confirm('Dezelfde rechten ook instellen in ' + n + ' andere school/scholen? Wat daar nu aangevinkt is, wordt overschreven.');
if (ok && typeof showLoader === 'function') showLoader();
return ok;
}
tel();
// gekozen school in beeld scrollen
var actief = document.querySelector('#scholenLijst a.actief');
if (actief) { var l = document.getElementById('scholenLijst'); l.scrollTop = actief.parentNode.offsetTop - l.clientHeight / 2; }
</script>
{% endblock %}