feat: add class management page for directors and enhance access control
All checks were successful
Build & Push / Build & Push image (push) Successful in 40s
All checks were successful
Build & Push / Build & Push image (push) Successful in 40s
This commit is contained in:
@@ -437,7 +437,12 @@
|
||||
</h2>
|
||||
<div class="teacher-chips" id="teacherChips"></div>
|
||||
<div class="action-bar" style="margin-top:1rem;">
|
||||
<button id="btnAddTeacher" class="btn btn-primary">+ Leerkracht toevoegen</button>
|
||||
<a href="/klassen" class="btn btn-primary">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/>
|
||||
</svg>
|
||||
Klassenbeheer
|
||||
</a>
|
||||
<button id="btnExportCSV" class="btn btn-secondary">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
Exporteer CSV
|
||||
@@ -616,33 +621,6 @@
|
||||
|
||||
</div><!-- /container -->
|
||||
|
||||
<!-- Modal: leerkracht toevoegen -->
|
||||
<div class="modal-overlay" id="addTeacherModal">
|
||||
<div class="modal">
|
||||
<h2>👤 Leerkracht toevoegen</h2>
|
||||
<div class="form-group">
|
||||
<label>Voornaam</label>
|
||||
<input type="text" id="newFirstName" placeholder="Voornaam...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Achternaam</label>
|
||||
<input type="text" id="newLastName" placeholder="Achternaam...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>E-mailadres</label>
|
||||
<input type="email" id="newEmail" placeholder="naam@school.be">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Wachtwoord (tijdelijk)</label>
|
||||
<input type="password" id="newPassword" placeholder="Minimaal 8 tekens...">
|
||||
</div>
|
||||
<div id="addTeacherError" style="display:none;color:var(--danger);font-size:0.85rem;margin-top:0.5rem;"></div>
|
||||
<div class="modal-buttons">
|
||||
<button id="btnCancelTeacher" class="btn btn-secondary">Annuleren</button>
|
||||
<button id="btnConfirmTeacher" class="btn btn-primary">Toevoegen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notification" id="notification"></div>
|
||||
|
||||
@@ -661,14 +639,11 @@ let activeYearId = null; // null = huidig actief jaar
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
bind('jaarSelector', 'change', switchJaar);
|
||||
bind('btnVernieuw', 'click', loadOverview);
|
||||
bind('btnAddTeacher', 'click', openAddTeacher);
|
||||
bind('btnExportCSV', 'click', exportToCSV);
|
||||
bind('btnExportPDF', 'click', exportToPDF);
|
||||
bind('tab-doelen', 'click', () => switchTab('doelen'));
|
||||
bind('tab-klassen', 'click', () => switchTab('klassen'));
|
||||
document.getElementById('tab-vergelijk') && bind('tab-vergelijk', 'click', () => switchTab('vergelijk'));
|
||||
bind('btnCancelTeacher', 'click', closeModal);
|
||||
bind('btnConfirmTeacher', 'click', addTeacher);
|
||||
bind('filterVak', 'change', applyFilters);
|
||||
bind('filterTeacher', 'change', applyFilters);
|
||||
document.getElementById('filterKlas') && bind('filterKlas', 'change', applyFilters);
|
||||
@@ -746,9 +721,6 @@ function renderTeacherList() {
|
||||
<div class="teacher-chip">
|
||||
<span class="name">${t.full_name}</span>
|
||||
<span class="klas">${(t.classes||[]).map(c=>c.name).join(', ') || ''}</span>
|
||||
<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;"
|
||||
title="Verwijderen">×</button>
|
||||
</div>`).join('');
|
||||
}
|
||||
// Toon/verberg de sectie
|
||||
@@ -1018,50 +990,6 @@ function applyFilters() {
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ── Leerkrachten beheer ───────────────────────────────────────────────────────
|
||||
function openAddTeacher() {
|
||||
document.getElementById('addTeacherModal').classList.add('active');
|
||||
}
|
||||
function closeModal() {
|
||||
document.getElementById('addTeacherModal').classList.remove('active');
|
||||
}
|
||||
|
||||
async function addTeacher() {
|
||||
const errorEl = document.getElementById('addTeacherError');
|
||||
errorEl.style.display = 'none';
|
||||
|
||||
const res = await fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
first_name: document.getElementById('newFirstName').value,
|
||||
last_name: document.getElementById('newLastName').value,
|
||||
email: document.getElementById('newEmail').value,
|
||||
password: document.getElementById('newPassword').value,
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
errorEl.textContent = data.error;
|
||||
errorEl.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
closeModal();
|
||||
showNotification(`${data.user.full_name} toegevoegd!`, 'success');
|
||||
await loadTeachers();
|
||||
await loadOverview();
|
||||
}
|
||||
|
||||
async function removeTeacher(userId) {
|
||||
if (!confirm('Leerkracht deactiveren?')) return;
|
||||
await fetch(`/api/users/${userId}`, { method: 'DELETE' });
|
||||
showNotification('Leerkracht verwijderd', 'success');
|
||||
await loadTeachers();
|
||||
await loadOverview();
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
function vakNaam(id) {
|
||||
return id.replace(/^doelenset-bao-/, '').replace(/-/g, ' ')
|
||||
@@ -1473,8 +1401,7 @@ function renderVergelijking() {
|
||||
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); }
|
||||
// (geen acties meer via event delegation in directeur dashboard)
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
363
backend/templates/directeur_klassen.html
Normal file
363
backend/templates/directeur_klassen.html
Normal file
@@ -0,0 +1,363 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Klassenbeheer — {{ org_name }}</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary: #4f46e5; --primary-dark: #4338ca;
|
||||
--success: #10b981; --danger: #ef4444; --warning: #f59e0b;
|
||||
--gray-50: #f9fafb; --gray-100: #f3f4f6; --gray-200: #e5e7eb;
|
||||
--gray-300: #d1d5db; --gray-400: #9ca3af; --gray-500: #6b7280;
|
||||
--gray-600: #4b5563; --gray-700: #374151; --gray-800: #1f2937;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--gray-100); color: var(--gray-800); line-height: 1.5; }
|
||||
.container { max-width: 900px; margin: 0 auto; padding: 1.25rem; }
|
||||
|
||||
/* Header */
|
||||
.header { background: white; border-radius: 12px; padding: 1.25rem 1.5rem;
|
||||
margin-bottom: 1.25rem; box-shadow: 0 1px 3px rgba(0,0,0,.1);
|
||||
display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .75rem; }
|
||||
.header-left { display: flex; align-items: center; gap: .75rem; }
|
||||
.back-link { display: inline-flex; align-items: center; gap: .35rem; padding: .4rem .75rem;
|
||||
border: 1px solid var(--gray-300); border-radius: 6px; font-size: .85rem;
|
||||
color: var(--gray-600); text-decoration: none; transition: all .15s; }
|
||||
.back-link:hover { background: var(--gray-100); border-color: var(--gray-400); }
|
||||
.header h1 { font-size: 1.25rem; color: var(--gray-900);
|
||||
display: flex; align-items: center; gap: .5rem; }
|
||||
.school-badge { font-size: .75rem; background: var(--primary); color: white;
|
||||
padding: .2rem .55rem; border-radius: 9999px; font-weight: 500; }
|
||||
|
||||
/* Section */
|
||||
.section { background: white; border-radius: 12px; padding: 1.5rem;
|
||||
margin-bottom: 1.25rem; box-shadow: 0 1px 3px rgba(0,0,0,.1); }
|
||||
.section-header { display: flex; align-items: center; justify-content: space-between;
|
||||
margin-bottom: 1.1rem; flex-wrap: wrap; gap: .5rem; }
|
||||
.section-header h2 { font-size: 1rem; color: var(--gray-700);
|
||||
display: flex; align-items: center; gap: .4rem; }
|
||||
|
||||
/* Buttons */
|
||||
.btn { display: inline-flex; align-items: center; gap: .35rem; padding: .45rem .9rem;
|
||||
border: none; border-radius: 6px; font-size: .85rem; font-weight: 500;
|
||||
cursor: pointer; transition: all .15s; }
|
||||
.btn-primary { background: var(--primary); color: white; }
|
||||
.btn-primary:hover { background: var(--primary-dark); }
|
||||
.btn-secondary { background: var(--gray-200); color: var(--gray-700); }
|
||||
.btn-secondary:hover { background: var(--gray-300); }
|
||||
.btn-danger { background: var(--danger); color: white; }
|
||||
.btn-danger:hover { background: #dc2626; }
|
||||
.btn-sm { padding: .3rem .65rem; font-size: .8rem; }
|
||||
|
||||
/* Tabel */
|
||||
table { width: 100%; border-collapse: collapse; font-size: .875rem; }
|
||||
thead th { padding: .6rem .85rem; text-align: left; font-size: .75rem; font-weight: 700;
|
||||
text-transform: uppercase; letter-spacing: .04em; color: var(--gray-500);
|
||||
border-bottom: 2px solid var(--gray-200); background: var(--gray-50); }
|
||||
tbody td { padding: .7rem .85rem; border-bottom: 1px solid var(--gray-100); vertical-align: middle; }
|
||||
tbody tr:hover td { background: var(--gray-50); }
|
||||
tbody tr:last-child td { border-bottom: none; }
|
||||
.teacher-names { font-size: .82rem; color: var(--gray-500); }
|
||||
.no-teacher { font-style: italic; color: var(--gray-400); font-size: .82rem; }
|
||||
td:last-child { white-space: nowrap; text-align: right; }
|
||||
|
||||
/* Empty state */
|
||||
.empty { text-align: center; padding: 2.5rem; color: var(--gray-500); font-style: italic; }
|
||||
|
||||
/* Assign modal (dynamisch ingeladen) */
|
||||
.assign-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.5);
|
||||
z-index: 1000; display: flex; align-items: center; justify-content: center; }
|
||||
.assign-modal { background: white; border-radius: 12px; padding: 1.5rem;
|
||||
max-width: 420px; width: 90%; max-height: 80vh; overflow-y: auto; }
|
||||
.assign-modal h3 { font-size: 1rem; margin-bottom: 1rem; }
|
||||
.checkbox-list { max-height: 240px; overflow-y: auto; display: flex;
|
||||
flex-direction: column; gap: .4rem; margin-bottom: 1.1rem; }
|
||||
.checkbox-list label { display: flex; align-items: center; gap: .5rem;
|
||||
cursor: pointer; font-size: .875rem; padding: .25rem 0; }
|
||||
.checkbox-list label:hover { color: var(--primary); }
|
||||
.modal-buttons { display: flex; gap: .5rem; justify-content: flex-end; }
|
||||
.form-error { color: var(--danger); font-size: .82rem; margin-top: .4rem; display: none; }
|
||||
|
||||
/* Notification */
|
||||
.notification { position: fixed; bottom: 1rem; right: 1rem; padding: .85rem 1.25rem;
|
||||
border-radius: 8px; color: white; font-weight: 500;
|
||||
transform: translateY(100px); opacity: 0; transition: all .3s; z-index: 2000; }
|
||||
.notification.show { transform: translateY(0); opacity: 1; }
|
||||
.notification.success { background: var(--success); }
|
||||
.notification.error { background: var(--danger); }
|
||||
.notification.warning { background: var(--warning); }
|
||||
|
||||
/* Dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root { --gray-50:#1a1a2e; --gray-100:#16213e; --gray-200:#0f3460;
|
||||
--gray-300:#1a1a3e; --gray-500:#9ca3af; --gray-600:#d1d5db;
|
||||
--gray-700:#e5e7eb; --gray-800:#f3f4f6; }
|
||||
body { background: #0f172a; color: #e2e8f0; }
|
||||
.header, .section, .assign-modal { background: #1e293b !important; }
|
||||
.back-link { border-color: #334155; color: #94a3b8; }
|
||||
.back-link:hover { background: #263548; }
|
||||
thead th { background: #1e293b !important; border-color: #334155 !important; }
|
||||
tbody td { border-color: #1e293b !important; }
|
||||
tbody tr:hover td { background: #263548 !important; }
|
||||
.btn-secondary { background: #334155 !important; color: #e2e8f0 !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<a href="/dashboard" class="back-link">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="15 18 9 12 15 6"></polyline>
|
||||
</svg>
|
||||
Terug naar dashboard
|
||||
</a>
|
||||
<h1>
|
||||
🏫 Klassenbeheer
|
||||
<span class="school-badge" id="schoolBadge">Laden...</span>
|
||||
</h1>
|
||||
</div>
|
||||
<a href="/auth/logout" class="btn btn-secondary btn-sm">Uitloggen</a>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2>
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/>
|
||||
</svg>
|
||||
Klassen
|
||||
</h2>
|
||||
<button class="btn btn-primary btn-sm" id="btnAddKlas">+ Klas toevoegen</button>
|
||||
</div>
|
||||
|
||||
<div id="klassenList">
|
||||
<div class="empty">Laden...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<h2>
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="9" cy="7" r="4"/>
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/>
|
||||
</svg>
|
||||
Leerkrachten van deze school
|
||||
</h2>
|
||||
</div>
|
||||
<div id="teachersList">
|
||||
<div class="empty">Laden...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="notification" id="notification"></div>
|
||||
|
||||
<script nonce="{{ csp_nonce() }}">
|
||||
let mySchoolId = null;
|
||||
let allTeachers = [];
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────────
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
document.getElementById('btnAddKlas').addEventListener('click', openAddKlas);
|
||||
|
||||
const me = await fetch('/api/me').then(r => r.json());
|
||||
mySchoolId = me.user?.school_id;
|
||||
document.getElementById('schoolBadge').textContent = me.user?.school_name || 'Mijn school';
|
||||
|
||||
await Promise.all([loadKlassen(), loadTeachers()]);
|
||||
});
|
||||
|
||||
// ── Klassen laden & renderen ──────────────────────────────────────────────────
|
||||
async function loadKlassen() {
|
||||
if (!mySchoolId) return;
|
||||
const res = await fetch(`/admin/schools/${mySchoolId}/classes`);
|
||||
const data = await res.json();
|
||||
const klassen = data.classes || [];
|
||||
|
||||
const container = document.getElementById('klassenList');
|
||||
if (!klassen.length) {
|
||||
container.innerHTML = '<div class="empty">Nog geen klassen aangemaakt. Klik op "+ Klas toevoegen" om te starten.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Klas</th>
|
||||
<th>Leerkrachten</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${klassen.map(c => `
|
||||
<tr>
|
||||
<td><strong>${c.name}</strong></td>
|
||||
<td>
|
||||
${c.teachers?.length
|
||||
? `<span class="teacher-names">${c.teachers.map(t => t.full_name).join(', ')}</span>`
|
||||
: '<span class="no-teacher">Geen leerkracht gekoppeld</span>'}
|
||||
</td>
|
||||
<td>
|
||||
<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 koppelen
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm"
|
||||
data-action="deleteKlas"
|
||||
data-id="${c.id}"
|
||||
data-name="${c.name.replace(/'/g,''')}">
|
||||
×
|
||||
</button>
|
||||
</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
// ── Leerkrachtenlijst ─────────────────────────────────────────────────────────
|
||||
async function loadTeachers() {
|
||||
if (!mySchoolId) return;
|
||||
const res = await fetch(`/admin/schools/${mySchoolId}/users`);
|
||||
const data = await res.json();
|
||||
allTeachers = (data.users || []).filter(u => u.role === 'teacher');
|
||||
|
||||
const container = document.getElementById('teachersList');
|
||||
if (!allTeachers.length) {
|
||||
container.innerHTML = '<div class="empty">Nog geen leerkrachten geregistreerd via SSO.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Naam</th>
|
||||
<th>E-mail</th>
|
||||
<th>Laatste login</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${allTeachers.map(u => `
|
||||
<tr>
|
||||
<td><strong>${u.full_name}</strong></td>
|
||||
<td style="color:var(--gray-500);font-size:.85rem;">${u.email}</td>
|
||||
<td style="color:var(--gray-500);font-size:.82rem;">
|
||||
${u.last_login
|
||||
? new Date(u.last_login).toLocaleString('nl-BE',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})
|
||||
: '<em>Nog niet ingelogd</em>'}
|
||||
</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
// ── Klas toevoegen ────────────────────────────────────────────────────────────
|
||||
async function openAddKlas() {
|
||||
const name = prompt('Naam van de nieuwe klas (bv. 3A):');
|
||||
if (!name?.trim()) return;
|
||||
const res = await fetch(`/admin/schools/${mySchoolId}/classes`, {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ name: name.trim() })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) { notify(data.error || 'Aanmaken mislukt', 'error'); return; }
|
||||
notify(`Klas "${name.trim()}" aangemaakt`, 'success');
|
||||
await loadKlassen();
|
||||
}
|
||||
|
||||
// ── Klas verwijderen ──────────────────────────────────────────────────────────
|
||||
async function deleteKlas(classId, name) {
|
||||
if (!confirm(`Klas "${name}" verwijderen? Alle leerkrachtkoppelingen worden ook verwijderd.`)) return;
|
||||
const res = await fetch(`/admin/schools/${mySchoolId}/classes/${classId}`, { method: 'DELETE' });
|
||||
if (!res.ok) { notify('Verwijderen mislukt', 'error'); return; }
|
||||
notify(`Klas "${name}" verwijderd`, 'success');
|
||||
await loadKlassen();
|
||||
}
|
||||
|
||||
// ── Leerkrachten koppelen ─────────────────────────────────────────────────────
|
||||
let assignClassId = null;
|
||||
|
||||
async function openAssignTeachers(classId, className, currentTeacherIds) {
|
||||
assignClassId = classId;
|
||||
|
||||
// Haal verse leerkrachtenlijst op als nog niet geladen
|
||||
if (!allTeachers.length) await loadTeachers();
|
||||
|
||||
const html = `
|
||||
<div class="assign-overlay" id="assignModal">
|
||||
<div class="assign-modal">
|
||||
<h3>Leerkrachten koppelen aan <strong>${className}</strong></h3>
|
||||
<div class="checkbox-list">
|
||||
${allTeachers.length
|
||||
? allTeachers.map(t => `
|
||||
<label>
|
||||
<input type="checkbox" value="${t.id}"
|
||||
${currentTeacherIds.includes(t.id) ? 'checked' : ''}>
|
||||
<span>${t.full_name}</span>
|
||||
<span style="font-size:.75rem;color:var(--gray-400);">(${t.email})</span>
|
||||
</label>`).join('')
|
||||
: '<em style="color:var(--gray-400);">Geen leerkrachten beschikbaar.<br>Leerkrachten verschijnen hier zodra ze voor het eerst inloggen via SSO.</em>'}
|
||||
</div>
|
||||
<div class="modal-buttons">
|
||||
<button class="btn btn-secondary" id="btnCancelAssign">Annuleren</button>
|
||||
<button class="btn btn-primary" id="btnSaveAssign">Opslaan</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.insertAdjacentHTML('beforeend', html);
|
||||
document.getElementById('btnCancelAssign').addEventListener('click', () => document.getElementById('assignModal')?.remove());
|
||||
document.getElementById('btnSaveAssign').addEventListener('click', saveAssignTeachers);
|
||||
// Klik buiten modal sluit ook
|
||||
document.getElementById('assignModal').addEventListener('click', e => {
|
||||
if (e.target.id === 'assignModal') document.getElementById('assignModal').remove();
|
||||
});
|
||||
}
|
||||
|
||||
async function saveAssignTeachers() {
|
||||
const ids = [...document.querySelectorAll('#assignModal input:checked')].map(i => parseInt(i.value));
|
||||
const res = await fetch(`/admin/schools/${mySchoolId}/classes/${assignClassId}/teachers`, {
|
||||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ user_ids: ids })
|
||||
});
|
||||
document.getElementById('assignModal')?.remove();
|
||||
if (!res.ok) { notify('Opslaan mislukt', 'error'); return; }
|
||||
notify('Leerkrachten bijgewerkt', 'success');
|
||||
await loadKlassen();
|
||||
}
|
||||
|
||||
// ── Event delegation ──────────────────────────────────────────────────────────
|
||||
document.addEventListener('click', e => {
|
||||
const btn = e.target.closest('[data-action]');
|
||||
if (!btn) return;
|
||||
const action = btn.dataset.action;
|
||||
if (action === 'deleteKlas') {
|
||||
deleteKlas(btn.dataset.id, btn.dataset.name);
|
||||
}
|
||||
if (action === 'assignTeachers') {
|
||||
const ids = JSON.parse(btn.dataset.teachers.replace(/"/g, '"'));
|
||||
openAssignTeachers(btn.dataset.id, btn.dataset.name, ids);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Notificaties ──────────────────────────────────────────────────────────────
|
||||
function notify(msg, type = 'success') {
|
||||
const el = document.getElementById('notification');
|
||||
el.textContent = msg;
|
||||
el.className = `notification ${type} show`;
|
||||
setTimeout(() => el.classList.remove('show'), 3500);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user