refactor: replace inline event handlers with bind function for improved readability and maintainability
All checks were successful
Build & Push / Build & Push image (push) Successful in 39s
All checks were successful
Build & Push / Build & Push image (push) Successful in 39s
This commit is contained in:
@@ -30,10 +30,7 @@ def get_active_year(school_id=None):
|
|||||||
@login_required
|
@login_required
|
||||||
def doelen_index():
|
def doelen_index():
|
||||||
data = load_index()
|
data = load_index()
|
||||||
if not data['vakken']:
|
# Altijd een geldig object teruggeven — lege vakkenlijst is geen fout
|
||||||
return jsonify({
|
|
||||||
'error': 'Geen doelen gevonden. Upload eerst de JSON bestanden via het beheerderspaneel.'
|
|
||||||
}), 404
|
|
||||||
return jsonify(data)
|
return jsonify(data)
|
||||||
|
|
||||||
|
|
||||||
@@ -126,6 +123,7 @@ def save_assessment():
|
|||||||
detail={'status': status})
|
detail={'status': status})
|
||||||
return jsonify({'assessment': assessment.to_dict()})
|
return jsonify({'assessment': assessment.to_dict()})
|
||||||
|
|
||||||
|
|
||||||
@api_bp.route('/assessments/bulk-import', methods=['POST'])
|
@api_bp.route('/assessments/bulk-import', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@limiter.limit('5 per minute')
|
@limiter.limit('5 per minute')
|
||||||
@@ -151,6 +149,7 @@ def bulk_import_assessments():
|
|||||||
fouten = 0
|
fouten = 0
|
||||||
|
|
||||||
for vak_id, vak_data in vakken.items():
|
for vak_id, vak_data in vakken.items():
|
||||||
|
# Sanitiseer vak_id
|
||||||
if not isinstance(vak_id, str) or len(vak_id) > 100:
|
if not isinstance(vak_id, str) or len(vak_id) > 100:
|
||||||
fouten += 1
|
fouten += 1
|
||||||
continue
|
continue
|
||||||
@@ -201,6 +200,7 @@ def bulk_import_assessments():
|
|||||||
detail={'totaal': totaal, 'fouten': fouten})
|
detail={'totaal': totaal, 'fouten': fouten})
|
||||||
return jsonify({'totaal': totaal, 'fouten': fouten})
|
return jsonify({'totaal': totaal, 'fouten': fouten})
|
||||||
|
|
||||||
|
|
||||||
# ── Directeur schooloverzicht ──────────────────────────────────────────────────
|
# ── Directeur schooloverzicht ──────────────────────────────────────────────────
|
||||||
|
|
||||||
@api_bp.route('/school/overview')
|
@api_bp.route('/school/overview')
|
||||||
|
|||||||
@@ -325,15 +325,20 @@
|
|||||||
<div class="notification" id="notification"></div>
|
<div class="notification" id="notification"></div>
|
||||||
|
|
||||||
<script nonce="{{ csp_nonce() }}">
|
<script nonce="{{ csp_nonce() }}">
|
||||||
|
function bind(id, ev, fn) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener(ev, fn);
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
document.getElementById('btnCancelSg').addEventListener('click', () => { closeModal() });
|
bind('btnCancelSg', 'click', () => { closeModal() });
|
||||||
document.getElementById('btnConfirmSg').addEventListener('click', () => { addSgIct() });
|
bind('btnConfirmSg', 'click', () => { addSgIct() });
|
||||||
document.getElementById('btnCancelSchool').addEventListener('click', () => { closeModal() });
|
bind('btnCancelSchool', 'click', () => { closeModal() });
|
||||||
document.getElementById('btnConfirmSchool').addEventListener('click', () => { addSchool() });
|
bind('btnConfirmSchool', 'click', () => { addSchool() });
|
||||||
document.getElementById('btnCancelEdit').addEventListener('click', () => { closeModal() });
|
bind('btnCancelEdit', 'click', () => { closeModal() });
|
||||||
document.getElementById('btnConfirmEdit').addEventListener('click', () => { saveSchool() });
|
bind('btnConfirmEdit', 'click', () => { saveSchool() });
|
||||||
document.getElementById('btnAddSgIct').addEventListener('click', () => { openModal('addSgIct') });
|
bind('btnAddSgIct', 'click', () => { openModal('addSgIct') });
|
||||||
document.getElementById('btnAddSchool').addEventListener('click', () => { openModal('addSchool') });
|
bind('btnAddSchool', 'click', () => { openModal('addSchool') });
|
||||||
await loadStats();
|
await loadStats();
|
||||||
await loadSgIct();
|
await loadSgIct();
|
||||||
await loadSchools();
|
await loadSchools();
|
||||||
@@ -363,7 +368,7 @@ async function loadSgIct() {
|
|||||||
<td>${u.full_name}</td>
|
<td>${u.full_name}</td>
|
||||||
<td>${u.email}</td>
|
<td>${u.email}</td>
|
||||||
<td style="color:var(--gray-500);font-size:0.8rem;">${u.last_login ? new Date(u.last_login).toLocaleDateString('nl-BE') : 'Nog niet ingelogd'}</td>
|
<td style="color:var(--gray-500);font-size:0.8rem;">${u.last_login ? new Date(u.last_login).toLocaleDateString('nl-BE') : 'Nog niet ingelogd'}</td>
|
||||||
<td><button class="btn btn-danger btn-sm" onclick="removeSgIct(${u.id})">Verwijderen</button></td>
|
<td><button class="btn btn-danger btn-sm" data-action="removeSgIct" data-id="${u.id}">Verwijderen</button></td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,8 +387,8 @@ async function loadSchools() {
|
|||||||
<td>${(s.email_domains||[]).map(d=>`<span class="domain-chip">${d}</span>`).join('')||'<em style="color:var(--gray-400)">geen</em>'}</td>
|
<td>${(s.email_domains||[]).map(d=>`<span class="domain-chip">${d}</span>`).join('')||'<em style="color:var(--gray-400)">geen</em>'}</td>
|
||||||
<td style="color:var(--gray-500);">${s.user_count}</td>
|
<td style="color:var(--gray-500);">${s.user_count}</td>
|
||||||
<td style="display:flex;gap:0.4rem;">
|
<td style="display:flex;gap:0.4rem;">
|
||||||
<button class="btn btn-secondary btn-sm" onclick="editSchool(${s.id},'${s.name.replace(/'/g,"\\'")}','${(s.email_domains||[]).join(', ')}')">Bewerken</button>
|
<button class="btn btn-secondary btn-sm" data-action="editSchool" data-id="${s.id}" data-name="${s.name.replace(/'/g,''')}" data-domains="${(s.email_domains||[]).join(', ')}">Bewerken</button>
|
||||||
<button class="btn btn-danger btn-sm" onclick="deleteSchool(${s.id},'${s.name.replace(/'/g,"\\'")}')">Verwijderen</button>
|
<button class="btn btn-danger btn-sm" data-action="deleteSchool" data-id="${s.id}" data-name="${s.name.replace(/'/g,''')}">Verwijderen</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
}
|
}
|
||||||
@@ -484,6 +489,16 @@ function notify(msg, type='success') {
|
|||||||
el.textContent = msg; el.className = `notification ${type} show`;
|
el.textContent = msg; el.className = `notification ${type} show`;
|
||||||
setTimeout(() => el.classList.remove('show'), 3000);
|
setTimeout(() => el.classList.remove('show'), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Event delegation voor dynamisch gegenereerde elementen ────────────────────
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const btn = e.target.closest('[data-action]');
|
||||||
|
if (!btn) return;
|
||||||
|
const action = btn.dataset.action;
|
||||||
|
if (action === 'removeSgIct') { removeSgIct(btn.dataset.id); }
|
||||||
|
if (action === 'editSchool') { editSchool(btn.dataset.id, btn.dataset.name, btn.dataset.domains); }
|
||||||
|
if (action === 'deleteSchool') { deleteSchool(btn.dataset.id, btn.dataset.name); }
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -349,6 +349,11 @@
|
|||||||
<div class="notification" id="notification"></div>
|
<div class="notification" id="notification"></div>
|
||||||
|
|
||||||
<script nonce="{{ csp_nonce() }}">
|
<script nonce="{{ csp_nonce() }}">
|
||||||
|
function bind(id, ev, fn) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener(ev, fn);
|
||||||
|
}
|
||||||
|
|
||||||
let teachers = [];
|
let teachers = [];
|
||||||
let allGoals = {};
|
let allGoals = {};
|
||||||
let vakData = {};
|
let vakData = {};
|
||||||
@@ -356,19 +361,19 @@ let overviewData = null;
|
|||||||
let activeYearId = null; // null = huidig actief jaar
|
let activeYearId = null; // null = huidig actief jaar
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
document.getElementById('jaarSelector').addEventListener('change', switchJaar);
|
bind('jaarSelector', 'change', switchJaar);
|
||||||
document.getElementById('btnVernieuw').addEventListener('click', loadOverview);
|
bind('btnVernieuw', 'click', loadOverview);
|
||||||
document.getElementById('btnAddTeacher').addEventListener('click', openAddTeacher);
|
bind('btnAddTeacher', 'click', openAddTeacher);
|
||||||
document.getElementById('tab-doelen').addEventListener('click', () => switchTab('doelen'));
|
bind('tab-doelen', 'click', () => switchTab('doelen'));
|
||||||
document.getElementById('tab-klassen').addEventListener('click', () => switchTab('klassen'));
|
bind('tab-klassen', 'click', () => switchTab('klassen'));
|
||||||
document.getElementById('tab-vergelijk') && document.getElementById('tab-vergelijk').addEventListener('click', () => switchTab('vergelijk'));
|
document.getElementById('tab-vergelijk') && bind('tab-vergelijk', 'click', () => switchTab('vergelijk'));
|
||||||
document.getElementById('btnCancelTeacher').addEventListener('click', closeModal);
|
bind('btnCancelTeacher', 'click', closeModal);
|
||||||
document.getElementById('btnConfirmTeacher').addEventListener('click', addTeacher);
|
bind('btnConfirmTeacher', 'click', addTeacher);
|
||||||
document.getElementById('filterVak').addEventListener('change', applyFilters);
|
bind('filterVak', 'change', applyFilters);
|
||||||
document.getElementById('filterTeacher').addEventListener('change', applyFilters);
|
bind('filterTeacher', 'change', applyFilters);
|
||||||
document.getElementById('filterKlas') && document.getElementById('filterKlas').addEventListener('change', applyFilters);
|
document.getElementById('filterKlas') && bind('filterKlas', 'change', applyFilters);
|
||||||
document.getElementById('filterStatus').addEventListener('change', applyFilters);
|
bind('filterStatus', 'change', applyFilters);
|
||||||
document.getElementById('filterSearch').addEventListener('input', applyFilters);
|
bind('filterSearch', 'input', applyFilters);
|
||||||
document.querySelectorAll('.leeftijd-checkbox input').forEach(cb => cb.addEventListener('change', applyFilters));
|
document.querySelectorAll('.leeftijd-checkbox input').forEach(cb => cb.addEventListener('change', applyFilters));
|
||||||
await loadUser();
|
await loadUser();
|
||||||
await loadJaren();
|
await loadJaren();
|
||||||
@@ -432,7 +437,7 @@ function renderTeacherList() {
|
|||||||
el.innerHTML = teachers.map(t => `
|
el.innerHTML = teachers.map(t => `
|
||||||
<div class="teacher-chip">
|
<div class="teacher-chip">
|
||||||
<span>${t.full_name}</span>
|
<span>${t.full_name}</span>
|
||||||
<button onclick="removeTeacher(${t.id})"
|
<button data-action="removeTeacher" data-id="${t.id}"
|
||||||
style="width:18px;height:18px;border-radius:50%;border:none;background:var(--gray-300);cursor:pointer;font-size:0.7rem;"
|
style="width:18px;height:18px;border-radius:50%;border:none;background:var(--gray-300);cursor:pointer;font-size:0.7rem;"
|
||||||
title="Verwijderen">×</button>
|
title="Verwijderen">×</button>
|
||||||
</div>`).join('');
|
</div>`).join('');
|
||||||
@@ -944,6 +949,14 @@ function renderVergelijking() {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Event delegation voor dynamisch gegenereerde elementen ────────────────────
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const btn = e.target.closest('[data-action]');
|
||||||
|
if (!btn) return;
|
||||||
|
const action = btn.dataset.action;
|
||||||
|
if (action === 'removeTeacher') { removeTeacher(btn.dataset.id); }
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -181,18 +181,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="notification" id="notification"></div>
|
<div class="notification" id="notification"></div>
|
||||||
<script nonce="{{ csp_nonce() }}">
|
<script nonce="{{ csp_nonce() }}">
|
||||||
|
function bind(id, ev, fn) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener(ev, fn);
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// ── Tab knoppen ──────────────────────────────────────────────────────────
|
// ── Tab knoppen ──────────────────────────────────────────────────────────
|
||||||
document.getElementById('tabXlsx').addEventListener('click', () => switchUploadTab('xlsx'));
|
bind('tabXlsx', 'click', () => switchUploadTab('xlsx'));
|
||||||
document.getElementById('tabJson').addEventListener('click', () => switchUploadTab('json'));
|
bind('tabJson', 'click', () => switchUploadTab('json'));
|
||||||
|
|
||||||
// ── Drop zones ───────────────────────────────────────────────────────────
|
// ── Drop zones ───────────────────────────────────────────────────────────
|
||||||
setupDropZone('dropZoneXlsx', 'fileInputXlsx', uploadXlsx);
|
setupDropZone('dropZoneXlsx', 'fileInputXlsx', uploadXlsx);
|
||||||
setupDropZone('dropZoneJson', 'fileInputJson', uploadJson);
|
setupDropZone('dropZoneJson', 'fileInputJson', uploadJson);
|
||||||
|
|
||||||
// ── File inputs ──────────────────────────────────────────────────────────
|
// ── File inputs ──────────────────────────────────────────────────────────
|
||||||
document.getElementById('fileInputXlsx').addEventListener('change', function() { uploadXlsx(this.files); });
|
bind('fileInputXlsx', 'change', function() { uploadXlsx(this.files); });
|
||||||
document.getElementById('fileInputJson').addEventListener('change', function() { uploadJson(this.files); });
|
bind('fileInputJson', 'change', function() { uploadJson(this.files); });
|
||||||
|
|
||||||
// ── Init ─────────────────────────────────────────────────────────────────
|
// ── Init ─────────────────────────────────────────────────────────────────
|
||||||
loadDoelen();
|
loadDoelen();
|
||||||
|
|||||||
@@ -192,7 +192,7 @@
|
|||||||
.leeftijd-checkbox:has(input:checked) { background: var(--primary) !important; }
|
.leeftijd-checkbox:has(input:checked) { background: var(--primary) !important; }
|
||||||
|
|
||||||
/* Vak indicator */
|
/* Vak indicator */
|
||||||
.vak-indicator { /* gradient blijft, ziet er goed uit */ }
|
/* .vak-indicator — stijl via inline op het element */
|
||||||
|
|
||||||
/* Progress bars achtergrond */
|
/* Progress bars achtergrond */
|
||||||
.progress-bar { background: #334155 !important; }
|
.progress-bar { background: #334155 !important; }
|
||||||
@@ -232,6 +232,8 @@
|
|||||||
::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
|
::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
|
||||||
::-webkit-scrollbar-thumb:hover { background: #475569; }
|
::-webkit-scrollbar-thumb:hover { background: #475569; }
|
||||||
}
|
}
|
||||||
|
.btn-import { background: var(--warning); color: white; }
|
||||||
|
.btn-import:hover { background: #d97706; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -250,8 +252,8 @@
|
|||||||
✎ Wijzigen
|
✎ Wijzigen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button id="btnImportJson" class="btn btn-secondary" title="Importeer beoordelingen uit vorige versie (JSON)">
|
<button id="btnImportJson" class="btn btn-import" title="Importeer beoordelingen uit de vorige standalone versie van de app (JSON bestand)">
|
||||||
📥 Importeer JSON
|
📥 Vorige beoordelingen importeren
|
||||||
</button>
|
</button>
|
||||||
<input type="file" id="importJsonFile" accept=".json" style="display:none">
|
<input type="file" id="importJsonFile" accept=".json" style="display:none">
|
||||||
<a href="/auth/logout" class="btn btn-secondary">Uitloggen</a>
|
<a href="/auth/logout" class="btn btn-secondary">Uitloggen</a>
|
||||||
@@ -365,27 +367,42 @@ let filteredData = [];
|
|||||||
let saveTimeout = null;
|
let saveTimeout = null;
|
||||||
|
|
||||||
// ── Init ─────────────────────────────────────────────────────────────────────
|
// ── Init ─────────────────────────────────────────────────────────────────────
|
||||||
|
function bind(id, ev, fn) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener(ev, fn);
|
||||||
|
else console.warn('Element niet gevonden:', id);
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
document.getElementById('btnOpenKlas').addEventListener('click', openKlasModal);
|
bind('btnImportJson', 'click', function() { document.getElementById('importJsonFile').click(); });
|
||||||
document.getElementById('btnSluitKlas').addEventListener('click', closeKlasModal);
|
bind('importJsonFile', 'change', function() { importLegacyJson(this.files[0]); this.value=''; });
|
||||||
document.getElementById('btnSlaKlas').addEventListener('click', saveKlassen);
|
bind('btnOpenKlas', 'click', openKlasModal);
|
||||||
document.getElementById('vakSelector').addEventListener('change', switchVak);
|
bind('btnSluitKlas', 'click', closeKlasModal);
|
||||||
document.getElementById('searchInput').addEventListener('input', applyFilters);
|
bind('btnSlaKlas', 'click', saveKlassen);
|
||||||
document.getElementById('statusFilter').addEventListener('change', applyFilters);
|
bind('vakSelector', 'change', switchVak);
|
||||||
document.getElementById('ebgFilter').addEventListener('change', applyFilters);
|
bind('searchInput', 'input', applyFilters);
|
||||||
document.getElementById('sectieFilter').addEventListener('change', applyFilters);
|
bind('statusFilter', 'change', applyFilters);
|
||||||
|
bind('ebgFilter', 'change', applyFilters);
|
||||||
|
bind('sectieFilter', 'change', applyFilters);
|
||||||
document.querySelectorAll('.leeftijd-checkboxes input').forEach(cb => cb.addEventListener('change', applyFilters));
|
document.querySelectorAll('.leeftijd-checkboxes input').forEach(cb => cb.addEventListener('change', applyFilters));
|
||||||
await loadUser();
|
try { await loadUser(); } catch(e) { console.error('loadUser fout:', e); }
|
||||||
await loadVakken();
|
try { await loadVakken(); } catch(e) { console.error('loadVakken fout:', e); }
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadUser() {
|
async function loadUser() {
|
||||||
|
try {
|
||||||
const res = await fetch('/api/me');
|
const res = await fetch('/api/me');
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
currentUser = data.user;
|
currentUser = data.user;
|
||||||
|
if (!currentUser) throw new Error('Geen gebruikersdata');
|
||||||
document.getElementById('userInfo').textContent =
|
document.getElementById('userInfo').textContent =
|
||||||
`${currentUser.full_name} — ${currentUser.school?.name || ''}`;
|
`${currentUser.full_name} — ${currentUser.school?.name || ''}`;
|
||||||
await loadKlassen();
|
await loadKlassen();
|
||||||
|
} catch(e) {
|
||||||
|
console.error('loadUser fout:', e);
|
||||||
|
document.getElementById('userInfo').textContent = 'Fout bij laden gebruiker';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Klassen ───────────────────────────────────────────────────────────────────
|
// ── Klassen ───────────────────────────────────────────────────────────────────
|
||||||
@@ -445,21 +462,45 @@ async function saveKlassen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadVakken() {
|
async function loadVakken() {
|
||||||
|
const sel = document.getElementById('vakSelector');
|
||||||
|
try {
|
||||||
const res = await fetch('/api/doelen/index');
|
const res = await fetch('/api/doelen/index');
|
||||||
if (!res.ok) { showNotification('Kon vakken niet laden', 'error'); return; }
|
if (!res.ok) {
|
||||||
|
showNotification('Fout bij laden vakken (HTTP ' + res.status + ')', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
const sel = document.getElementById('vakSelector');
|
if (!data.vakken?.length) {
|
||||||
// Sorteer op naam — API levert ze al gesorteerd, maar voor de zekerheid
|
// Geen doelen geüpload — toon duidelijke boodschap in selector
|
||||||
const sorted = [...data.vakken].sort((a,b) => (a.naam||a.id).localeCompare(b.naam||b.id, 'nl'));
|
const opt = document.createElement('option');
|
||||||
|
opt.value = '';
|
||||||
|
opt.textContent = '⚠️ Geen vakken beschikbaar — beheerder moet doelen uploaden';
|
||||||
|
opt.disabled = true;
|
||||||
|
sel.appendChild(opt);
|
||||||
|
document.getElementById('tableBody').innerHTML =
|
||||||
|
`<tr><td colspan="6" style="padding:2rem;text-align:center;color:var(--gray-500);">
|
||||||
|
<strong>Geen leerdoelen beschikbaar.</strong><br><br>
|
||||||
|
Vraag je beheerder om de doelensets te uploaden via het beheerderspaneel
|
||||||
|
(<em>Beheer → Leerdoelen bestanden</em>).<br><br>
|
||||||
|
<span style="font-size:.85rem;">Heb je al beoordelingen uit de vorige versie?
|
||||||
|
Gebruik de <strong>📥 Vorige beoordelingen importeren</strong> knop hierboven
|
||||||
|
om ze te migreren zodra de vakken beschikbaar zijn.</span>
|
||||||
|
</td></tr>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = [...data.vakken].sort((a,b) => (a.naam||a.id).localeCompare(b.naam||b.id, 'nl'));
|
||||||
sorted.forEach(v => {
|
sorted.forEach(v => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
opt.value = v.id;
|
opt.value = v.id;
|
||||||
// Gebruik naam uit API, fallback op lokale functie
|
|
||||||
opt.textContent = `${v.naam || vakNaam(v.id)} (${v.aantalDoelzinnen} doelen)`;
|
opt.textContent = `${v.naam || vakNaam(v.id)} (${v.aantalDoelzinnen} doelen)`;
|
||||||
sel.appendChild(opt);
|
sel.appendChild(opt);
|
||||||
});
|
});
|
||||||
|
} catch(e) {
|
||||||
|
console.error('loadVakken fout:', e);
|
||||||
|
showNotification('Netwerkfout bij laden vakken', 'error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Vak wisselen ─────────────────────────────────────────────────────────────
|
// ── Vak wisselen ─────────────────────────────────────────────────────────────
|
||||||
@@ -568,7 +609,7 @@ function renderTable() {
|
|||||||
const ebg = (d.kennisverwerking||'').toLowerCase();
|
const ebg = (d.kennisverwerking||'').toLowerCase();
|
||||||
return `
|
return `
|
||||||
<tr class="${s ? 'status-'+s : ''}">
|
<tr class="${s ? 'status-'+s : ''}">
|
||||||
<td><button class="status-selector status-${s||'none'}" onclick="cycleStatus('${d.id}')"></button></td>
|
<td><button class="status-selector status-${s||'none'}" data-action="cycleStatus" data-id="${d.id}"></button></td>
|
||||||
<td><strong>${d.goNr}</strong></td>
|
<td><strong>${d.goNr}</strong></td>
|
||||||
<td>${ebg ? `<span class="ebg-badge ebg-${ebg}">${ebg.charAt(0).toUpperCase()+ebg.slice(1)}</span>` : '-'}</td>
|
<td>${ebg ? `<span class="ebg-badge ebg-${ebg}">${ebg.charAt(0).toUpperCase()+ebg.slice(1)}</span>` : '-'}</td>
|
||||||
<td><div class="leeftijden">${d.leeftijden.map(l=>`<span class="leeftijd-badge">${l}</span>`).join('')}</div></td>
|
<td><div class="leeftijden">${d.leeftijden.map(l=>`<span class="leeftijd-badge">${l}</span>`).join('')}</div></td>
|
||||||
@@ -677,12 +718,26 @@ function showNotification(msg, type='success') {
|
|||||||
setTimeout(() => el.classList.remove('show'), 3000);
|
setTimeout(() => el.classList.remove('show'), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Event delegation voor dynamisch gegenereerde elementen ────────────────────
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const btn = e.target.closest('[data-action]');
|
||||||
|
if (!btn) return;
|
||||||
|
const action = btn.dataset.action;
|
||||||
|
if (action === 'cycleStatus') { cycleStatus(btn.dataset.id); }
|
||||||
|
});
|
||||||
|
|
||||||
// ── Legacy JSON import (uit vorige standalone versie) ────────────────────────
|
// ── Legacy JSON import (uit vorige standalone versie) ────────────────────────
|
||||||
async function importLegacyJson(file) {
|
async function importLegacyJson(file) {
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(await file.text());
|
const text = await file.text();
|
||||||
|
data = JSON.parse(text);
|
||||||
|
// Valideer dat het een herkenbaar formaat is
|
||||||
|
if (!data.vakken) {
|
||||||
|
showNotification('Ongeldig bestand — geen vakken gevonden. Verwacht een export uit de Leerdoelen Tracker.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
showNotification('Ongeldig JSON bestand', 'error'); return;
|
showNotification('Ongeldig JSON bestand', 'error'); return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script nonce="{{ csp_nonce() }}">
|
<script nonce="{{ csp_nonce() }}">
|
||||||
|
function bind(id, ev, fn) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener(ev, fn);
|
||||||
|
}
|
||||||
|
|
||||||
let saVisible = false;
|
let saVisible = false;
|
||||||
|
|
||||||
function toggleSuperadmin() {
|
function toggleSuperadmin() {
|
||||||
@@ -347,8 +352,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
document.getElementById('btnToggleSuperadmin').addEventListener('click', () => { toggleSuperadmin() });
|
bind('btnToggleSuperadmin', 'click', () => { toggleSuperadmin() });
|
||||||
document.getElementById('btnSuperadminLogin').addEventListener('click', () => { superadminLogin() });
|
bind('btnSuperadminLogin', 'click', () => { superadminLogin() });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -333,6 +333,11 @@ toevoegen</button>
|
|||||||
<div class="notification" id="notification"></div>
|
<div class="notification" id="notification"></div>
|
||||||
|
|
||||||
<script nonce="{{ csp_nonce() }}">
|
<script nonce="{{ csp_nonce() }}">
|
||||||
|
function bind(id, ev, fn) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener(ev, fn);
|
||||||
|
}
|
||||||
|
|
||||||
const IS_SUPERADMIN = {{ 'true' if is_superadmin else 'false' }};
|
const IS_SUPERADMIN = {{ 'true' if is_superadmin else 'false' }};
|
||||||
let schools = [];
|
let schools = [];
|
||||||
|
|
||||||
@@ -343,21 +348,21 @@ const SCHOOL_ROLLEN = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
document.getElementById('btnAddSgIct') && document.getElementById('btnAddSgIct').addEventListener('click', () => openModal('addSgIct'));
|
document.getElementById('btnAddSgIct') && bind('btnAddSgIct', 'click', () => openModal('addSgIct'));
|
||||||
document.getElementById('btnAddJaar') && document.getElementById('btnAddJaar').addEventListener('click', () => openModal('addJaar'));
|
document.getElementById('btnAddJaar') && bind('btnAddJaar', 'click', () => openModal('addJaar'));
|
||||||
document.getElementById('btnAddSchool') && document.getElementById('btnAddSchool').addEventListener('click', () => openModal('addSchool'));
|
document.getElementById('btnAddSchool') && bind('btnAddSchool', 'click', () => openModal('addSchool'));
|
||||||
document.getElementById('btnAddUser') && document.getElementById('btnAddUser').addEventListener('click', () => openModal('addUser'));
|
document.getElementById('btnAddUser') && bind('btnAddUser', 'click', () => openModal('addUser'));
|
||||||
document.getElementById('auditCategory').addEventListener('change', loadAuditLog);
|
bind('auditCategory', 'change', loadAuditLog);
|
||||||
document.getElementById('auditSchoolFilter') && document.getElementById('auditSchoolFilter').addEventListener('change', loadAuditLog);
|
document.getElementById('auditSchoolFilter') && bind('auditSchoolFilter', 'change', loadAuditLog);
|
||||||
document.getElementById('auditSearch').addEventListener('input', loadAuditLog);
|
bind('auditSearch', 'input', loadAuditLog);
|
||||||
document.getElementById('btnCancelSchool') && document.getElementById('btnCancelSchool').addEventListener('click', closeModal);
|
document.getElementById('btnCancelSchool') && bind('btnCancelSchool', 'click', closeModal);
|
||||||
document.getElementById('btnSaveSchool') && document.getElementById('btnSaveSchool').addEventListener('click', addSchool);
|
document.getElementById('btnSaveSchool') && bind('btnSaveSchool', 'click', addSchool);
|
||||||
document.getElementById('btnCancelSgIct') && document.getElementById('btnCancelSgIct').addEventListener('click', closeModal);
|
document.getElementById('btnCancelSgIct') && bind('btnCancelSgIct', 'click', closeModal);
|
||||||
document.getElementById('btnSaveSgIct') && document.getElementById('btnSaveSgIct').addEventListener('click', addSgIct);
|
document.getElementById('btnSaveSgIct') && bind('btnSaveSgIct', 'click', addSgIct);
|
||||||
document.getElementById('btnCancelJaar') && document.getElementById('btnCancelJaar').addEventListener('click', closeModal);
|
document.getElementById('btnCancelJaar') && bind('btnCancelJaar', 'click', closeModal);
|
||||||
document.getElementById('btnSaveJaar') && document.getElementById('btnSaveJaar').addEventListener('click', addJaar);
|
document.getElementById('btnSaveJaar') && bind('btnSaveJaar', 'click', addJaar);
|
||||||
document.getElementById('btnCancelUser') && document.getElementById('btnCancelUser').addEventListener('click', closeModal);
|
document.getElementById('btnCancelUser') && bind('btnCancelUser', 'click', closeModal);
|
||||||
document.getElementById('btnSaveUser') && document.getElementById('btnSaveUser').addEventListener('click', addUser);
|
document.getElementById('btnSaveUser') && bind('btnSaveUser', 'click', addUser);
|
||||||
const tasks = [loadStats(), loadSchoolsTable(), loadSchoolsGrid()];
|
const tasks = [loadStats(), loadSchoolsTable(), loadSchoolsGrid()];
|
||||||
if (IS_SUPERADMIN) tasks.push(loadSgIct());
|
if (IS_SUPERADMIN) tasks.push(loadSgIct());
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
@@ -391,7 +396,7 @@ async function loadSgIct() {
|
|||||||
<td>${u.full_name}</td>
|
<td>${u.full_name}</td>
|
||||||
<td style="color:var(--gray-500);font-size:.82rem;">${u.email}</td>
|
<td style="color:var(--gray-500);font-size:.82rem;">${u.email}</td>
|
||||||
<td style="color:var(--gray-500);font-size:.8rem;">${u.last_login ? new Date(u.last_login).toLocaleDateString('nl-BE') : 'Nog niet ingelogd'}</td>
|
<td style="color:var(--gray-500);font-size:.8rem;">${u.last_login ? new Date(u.last_login).toLocaleDateString('nl-BE') : 'Nog niet ingelogd'}</td>
|
||||||
<td><button class="btn btn-danger btn-sm" onclick="removeSgIct(${u.id},'${u.full_name.replace(/'/g,"\\'")}')">Verwijderen</button></td>
|
<td><button class="btn btn-danger btn-sm" data-action="removeSgIct" data-id="${u.id}" data-name="${u.full_name.replace(/'/g,''')}">Verwijderen</button></td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,8 +437,8 @@ async function loadSchoolsTable() {
|
|||||||
<td>${(s.email_domains||[]).map(d=>`<span class="domain-chip">${d}</span>`).join('') || '<em style="color:var(--gray-400)">geen</em>'}</td>
|
<td>${(s.email_domains||[]).map(d=>`<span class="domain-chip">${d}</span>`).join('') || '<em style="color:var(--gray-400)">geen</em>'}</td>
|
||||||
<td style="color:var(--gray-500);">${s.user_count}</td>
|
<td style="color:var(--gray-500);">${s.user_count}</td>
|
||||||
<td style="display:flex;gap:.35rem;">
|
<td style="display:flex;gap:.35rem;">
|
||||||
<button class="btn btn-secondary btn-sm" onclick="editSchool(${s.id},'${s.name.replace(/'/g,"\\'")}','${(s.email_domains||[]).join(', ')}')">Bewerken</button>
|
<button class="btn btn-secondary btn-sm" data-action="editSchool" data-id="${s.id}" data-name="${s.name.replace(/'/g,''')}" data-domains="${(s.email_domains||[]).join(', ')}">Bewerken</button>
|
||||||
<button class="btn btn-danger btn-sm" onclick="deleteSchool(${s.id},'${s.name.replace(/'/g,"\\'")}')">Verwijderen</button>
|
<button class="btn btn-danger btn-sm" data-action="deleteSchool" data-id="${s.id}" data-name="${s.name.replace(/'/g,''')}">Verwijderen</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
|
|
||||||
@@ -544,10 +549,10 @@ function renderUserGroup(schoolId, label, users, maxShow=99) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-actions">
|
<div class="user-actions">
|
||||||
<select class="role-select" onchange="changeRole(${schoolId},${u.id},this.value,'${u.full_name.replace(/'/g,"\\'")}',this)">
|
<select class="role-select" data-action="changeRole" data-school-id="${schoolId}" data-user-id="${u.id}" data-name="${u.full_name.replace(/'/g,''')}">
|
||||||
${SCHOOL_ROLLEN.map(r=>`<option value="${r.value}" ${r.value===u.role?'selected':''}>${r.label}</option>`).join('')}
|
${SCHOOL_ROLLEN.map(r=>`<option value="${r.value}" ${r.value===u.role?'selected':''}>${r.label}</option>`).join('')}
|
||||||
</select>
|
</select>
|
||||||
<button class="btn btn-danger btn-sm" onclick="removeUser(${schoolId},${u.id},'${u.full_name.replace(/'/g,"\\'")}')">×</button>
|
<button class="btn btn-danger btn-sm" data-action="removeUser" data-school-id="${schoolId}" data-user-id="${u.id}" data-name="${u.full_name.replace(/'/g,''')}">×</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`).join('')}
|
</div>`).join('')}
|
||||||
${hidden > 0 ? `<div style="color:var(--gray-500);font-size:.8rem;padding:.3rem 0 0;">+ ${hidden} meer...</div>` : ''}`;
|
${hidden > 0 ? `<div style="color:var(--gray-500);font-size:.8rem;padding:.3rem 0 0;">+ ${hidden} meer...</div>` : ''}`;
|
||||||
@@ -610,7 +615,7 @@ async function loadJaren() {
|
|||||||
: '<span style="color:var(--gray-400);font-size:.82rem;">Inactief</span>'}
|
: '<span style="color:var(--gray-400);font-size:.82rem;">Inactief</span>'}
|
||||||
</td>
|
</td>
|
||||||
<td>${!y.is_active
|
<td>${!y.is_active
|
||||||
? `<button class="btn btn-secondary btn-sm" onclick="activeerJaar(${y.id},'${y.label}')">Activeren</button>`
|
? `<button class="btn btn-secondary btn-sm" data-action="activeerJaar" data-id="${y.id}" data-label="${y.label}">Activeren</button>`
|
||||||
: ''}
|
: ''}
|
||||||
</td>
|
</td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
@@ -696,7 +701,7 @@ async function loadAuditLog(page = 1) {
|
|||||||
const pager = document.getElementById('auditPager');
|
const pager = document.getElementById('auditPager');
|
||||||
if (data.pages <= 1) { pager.innerHTML = ''; return; }
|
if (data.pages <= 1) { pager.innerHTML = ''; return; }
|
||||||
pager.innerHTML = Array.from({length: data.pages}, (_, i) => `
|
pager.innerHTML = Array.from({length: data.pages}, (_, i) => `
|
||||||
<button onclick="loadAuditLog(${i+1})"
|
<button data-action="auditPage" data-page="${i+1}"
|
||||||
style="padding:.3rem .6rem;border:1px solid var(--gray-300);border-radius:4px;cursor:pointer;min-width:36px;
|
style="padding:.3rem .6rem;border:1px solid var(--gray-300);border-radius:4px;cursor:pointer;min-width:36px;
|
||||||
${i+1 === data.page ? 'background:var(--primary);color:white;border-color:var(--primary);' : ''}">
|
${i+1 === data.page ? 'background:var(--primary);color:white;border-color:var(--primary);' : ''}">
|
||||||
${i+1}
|
${i+1}
|
||||||
@@ -715,6 +720,23 @@ function notify(msg, type='success') {
|
|||||||
el.textContent = msg; el.className = `notification ${type} show`;
|
el.textContent = msg; el.className = `notification ${type} show`;
|
||||||
setTimeout(() => el.classList.remove('show'), 3500);
|
setTimeout(() => el.classList.remove('show'), 3500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Event delegation voor dynamisch gegenereerde elementen ────────────────────
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const btn = e.target.closest('[data-action]');
|
||||||
|
if (!btn) return;
|
||||||
|
const action = btn.dataset.action;
|
||||||
|
if (action === 'removeSgIct') { removeSgIct(btn.dataset.id, btn.dataset.name); }
|
||||||
|
if (action === 'editSchool') { editSchool(btn.dataset.id, btn.dataset.name, btn.dataset.domains); }
|
||||||
|
if (action === 'deleteSchool') { deleteSchool(btn.dataset.id, btn.dataset.name); }
|
||||||
|
if (action === 'removeUser') { removeUser(btn.dataset.schoolId, btn.dataset.userId, btn.dataset.name); }
|
||||||
|
if (action === 'activeerJaar') { activeerJaar(btn.dataset.id, btn.dataset.label); }
|
||||||
|
if (action === 'auditPage') { loadAuditLog(parseInt(btn.dataset.page)); }
|
||||||
|
});
|
||||||
|
document.addEventListener('change', function(e) {
|
||||||
|
const sel = e.target.closest('[data-action="changeRole"]');
|
||||||
|
if (sel) { changeRole(sel.dataset.schoolId, sel.dataset.userId, sel.value, sel.dataset.name, sel); }
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -319,6 +319,11 @@
|
|||||||
<div class="notification" id="notification"></div>
|
<div class="notification" id="notification"></div>
|
||||||
|
|
||||||
<script nonce="{{ csp_nonce() }}">
|
<script nonce="{{ csp_nonce() }}">
|
||||||
|
function bind(id, ev, fn) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.addEventListener(ev, fn);
|
||||||
|
}
|
||||||
|
|
||||||
let mySchoolId = null;
|
let mySchoolId = null;
|
||||||
|
|
||||||
const ROLLEN = [
|
const ROLLEN = [
|
||||||
@@ -328,12 +333,13 @@ const ROLLEN = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
document.getElementById('btnAddUser') && document.getElementById('btnAddUser').addEventListener('click', openModal);
|
const addModalEl = document.getElementById('addModal'); if (addModalEl) addModalEl.addEventListener('click', e => { if (e.target === addModalEl) closeModal(); });
|
||||||
document.getElementById('btnAddKlas') && document.getElementById('btnAddKlas').addEventListener('click', openAddKlas);
|
document.getElementById('btnAddUser') && bind('btnAddUser', 'click', openModal);
|
||||||
document.getElementById('btnCancelUser') && document.getElementById('btnCancelUser').addEventListener('click', closeModal);
|
document.getElementById('btnAddKlas') && bind('btnAddKlas', 'click', openAddKlas);
|
||||||
document.getElementById('btnConfirmUser') && document.getElementById('btnConfirmUser').addEventListener('click', addUser);
|
document.getElementById('btnCancelUser') && bind('btnCancelUser', 'click', closeModal);
|
||||||
document.getElementById('auditCategory') && document.getElementById('auditCategory').addEventListener('change', loadAuditLog);
|
document.getElementById('btnConfirmUser') && bind('btnConfirmUser', 'click', addUser);
|
||||||
document.getElementById('auditSearch') && document.getElementById('auditSearch').addEventListener('input', loadAuditLog);
|
document.getElementById('auditCategory') && bind('auditCategory', 'change', loadAuditLog);
|
||||||
|
document.getElementById('auditSearch') && bind('auditSearch', 'input', loadAuditLog);
|
||||||
const me = await fetch('/api/me').then(r => r.json());
|
const me = await fetch('/api/me').then(r => r.json());
|
||||||
mySchoolId = me.user?.school_id;
|
mySchoolId = me.user?.school_id;
|
||||||
document.getElementById('schoolName').textContent = me.user?.school_name || '';
|
document.getElementById('schoolName').textContent = me.user?.school_name || '';
|
||||||
@@ -366,7 +372,7 @@ async function loadUsers() {
|
|||||||
<td style="color:var(--gray-500);font-size:.82rem;">${u.email}</td>
|
<td style="color:var(--gray-500);font-size:.82rem;">${u.email}</td>
|
||||||
<td>
|
<td>
|
||||||
<select class="role-select"
|
<select class="role-select"
|
||||||
onchange="changeRole(${u.id}, this.value, '${u.full_name.replace(/'/g,"\\'")}', this)">
|
data-action="changeRole" data-user-id="${u.id}" data-name="${u.full_name.replace(/'/g,''')}">
|
||||||
${ROLLEN.map(r =>
|
${ROLLEN.map(r =>
|
||||||
`<option value="${r.value}" ${r.value === u.role ? 'selected' : ''}>${r.label}</option>`
|
`<option value="${r.value}" ${r.value === u.role ? 'selected' : ''}>${r.label}</option>`
|
||||||
).join('')}
|
).join('')}
|
||||||
@@ -374,7 +380,7 @@ async function loadUsers() {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-danger btn-sm"
|
<button class="btn btn-danger btn-sm"
|
||||||
onclick="removeUser(${u.id}, '${u.full_name.replace(/'/g,"\\'")}')">
|
data-action="removeUser" data-user-id="${u.id}" data-name="${u.full_name.replace(/'/g,''')}">
|
||||||
Verwijderen
|
Verwijderen
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -432,9 +438,7 @@ function closeModal() {
|
|||||||
document.getElementById('addModal').classList.remove('active');
|
document.getElementById('addModal').classList.remove('active');
|
||||||
document.getElementById('addError').style.display = 'none';
|
document.getElementById('addError').style.display = 'none';
|
||||||
}
|
}
|
||||||
document.getElementById('addModal').addEventListener('click', e => {
|
|
||||||
if (e.target === document.getElementById('addModal')) closeModal();
|
|
||||||
});
|
|
||||||
|
|
||||||
function notify(msg, type = 'success') {
|
function notify(msg, type = 'success') {
|
||||||
const el = document.getElementById('notification');
|
const el = document.getElementById('notification');
|
||||||
@@ -471,10 +475,10 @@ async function loadKlassen() {
|
|||||||
: '<em>Geen leerkracht gekoppeld</em>'}
|
: '<em>Geen leerkracht gekoppeld</em>'}
|
||||||
</td>
|
</td>
|
||||||
<td style="white-space:nowrap;">
|
<td style="white-space:nowrap;">
|
||||||
<button class="btn btn-secondary btn-sm" onclick="openAssignTeachers(${c.id}, '${c.name.replace(/'/g,"\'")}', ${JSON.stringify(c.teachers?.map(t=>t.id)||[])})">
|
<button class="btn btn-secondary btn-sm" data-action="assignTeachers" data-id="${c.id}" data-name="${c.name.replace(/'/g,''')}" data-teachers="${JSON.stringify(c.teachers?.map(t=>t.id)||[]).replace(/"/g,'"')}">
|
||||||
Leerkrachten
|
Leerkrachten
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger btn-sm" onclick="deleteKlas(${c.id}, '${c.name.replace(/'/g,"\'")}')">
|
<button class="btn btn-danger btn-sm" data-action="deleteKlas" data-id="${c.id}" data-name="${c.name.replace(/'/g,''')}">
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -525,8 +529,8 @@ async function openAssignTeachers(classId, className, currentTeacherIds) {
|
|||||||
: '<em style="color:gray;">Geen leerkrachten beschikbaar</em>'}
|
: '<em style="color:gray;">Geen leerkrachten beschikbaar</em>'}
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:.5rem;justify-content:flex-end;">
|
<div style="display:flex;gap:.5rem;justify-content:flex-end;">
|
||||||
<button onclick="document.getElementById('assignModal').remove()" class="btn btn-secondary">Annuleren</button>
|
<button data-action="closeAssignModal" class="btn btn-secondary">Annuleren</button>
|
||||||
<button onclick="saveAssignTeachers()" class="btn btn-primary">Opslaan</button>
|
<button data-action="saveAssignTeachers" class="btn btn-primary">Opslaan</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -581,12 +585,29 @@ async function loadAuditLog(page = 1) {
|
|||||||
const pager = document.getElementById('auditPager');
|
const pager = document.getElementById('auditPager');
|
||||||
if (data.pages <= 1) { pager.innerHTML = ''; return; }
|
if (data.pages <= 1) { pager.innerHTML = ''; return; }
|
||||||
pager.innerHTML = Array.from({length: data.pages}, (_, i) => `
|
pager.innerHTML = Array.from({length: data.pages}, (_, i) => `
|
||||||
<button onclick="loadAuditLog(${i+1})"
|
<button data-action="auditPage" data-page="${i+1}"
|
||||||
style="padding:.3rem .6rem;border:1px solid var(--gray-300);border-radius:4px;cursor:pointer;
|
style="padding:.3rem .6rem;border:1px solid var(--gray-300);border-radius:4px;cursor:pointer;
|
||||||
${i+1 === data.page ? 'background:var(--primary);color:white;border-color:var(--primary);' : ''}">
|
${i+1 === data.page ? 'background:var(--primary);color:white;border-color:var(--primary);' : ''}">
|
||||||
${i+1}
|
${i+1}
|
||||||
</button>`).join('');
|
</button>`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Event delegation voor dynamisch gegenereerde elementen ────────────────────
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
const btn = e.target.closest('[data-action]');
|
||||||
|
if (!btn) return;
|
||||||
|
const action = btn.dataset.action;
|
||||||
|
if (action === 'removeUser') { removeUser(btn.dataset.userId, btn.dataset.name); }
|
||||||
|
if (action === 'assignTeachers') { openAssignTeachers(btn.dataset.id, btn.dataset.name, JSON.parse(btn.dataset.teachers.replace(/"/g,'"'))); }
|
||||||
|
if (action === 'deleteKlas') { deleteKlas(btn.dataset.id, btn.dataset.name); }
|
||||||
|
if (action === 'closeAssignModal') { document.getElementById('assignModal')?.remove(); }
|
||||||
|
if (action === 'saveAssignTeachers'){ saveAssignTeachers(); }
|
||||||
|
if (action === 'auditPage') { loadAuditLog(parseInt(btn.dataset.page)); }
|
||||||
|
});
|
||||||
|
document.addEventListener('change', function(e) {
|
||||||
|
const sel = e.target.closest('[data-action="changeRole"]');
|
||||||
|
if (sel) { changeRole(sel.dataset.userId, sel.value, sel.dataset.name, sel); }
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -244,9 +244,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script nonce="{{ csp_nonce() }}">
|
<script nonce="{{ csp_nonce() }}">
|
||||||
document.getElementById('password').addEventListener('keydown', e => {
|
function bind(id, ev, fn) {
|
||||||
if (e.key === 'Enter') doLogin();
|
const el = document.getElementById(id);
|
||||||
});
|
if (el) el.addEventListener(ev, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pwEl = document.getElementById('password'); if (pwEl) pwEl.addEventListener('keydown', e => { if (e.key === 'Enter') doLogin(); });
|
||||||
|
|
||||||
async function doLogin() {
|
async function doLogin() {
|
||||||
const errorEl = document.getElementById('errorMsg');
|
const errorEl = document.getElementById('errorMsg');
|
||||||
@@ -271,7 +274,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
document.getElementById('btnLogin').addEventListener('click', () => { doLogin() });
|
bind('btnLogin', 'click', () => { doLogin() });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user