From 5afe29716164b0aa5d4c0869869c8868980925f3 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 4 Mar 2026 11:45:45 +0100 Subject: [PATCH] feat: add class management page for directors and enhance access control --- backend/routes/admin.py | 18 +- backend/routes/pages.py | 10 + backend/templates/directeur.html | 87 +----- backend/templates/directeur_klassen.html | 363 +++++++++++++++++++++++ 4 files changed, 394 insertions(+), 84 deletions(-) create mode 100644 backend/templates/directeur_klassen.html diff --git a/backend/routes/admin.py b/backend/routes/admin.py index a3b04f2..3ad77ab 100644 --- a/backend/routes/admin.py +++ b/backend/routes/admin.py @@ -48,6 +48,16 @@ def school_ict_required(f): return decorated +def director_or_ict_required(f): + """Decorator: school_ict én director mogen door (binnen eigen school).""" + @wraps(f) + def decorated(*args, **kwargs): + if not current_user.is_director: # is_director omvat ook school_ict en hoger + return jsonify({'error': 'Geen toegang'}), 403 + return f(*args, **kwargs) + return decorated + + # ── Scholen (scholengroep_ict) ──────────────────────────────────────────────── @admin_bp.route('/schools', methods=['GET']) @@ -577,7 +587,7 @@ def global_stats(): @admin_bp.route('/schools//classes', methods=['GET']) @login_required -@school_ict_required +@director_or_ict_required def list_classes(school_id): if not current_user.is_scholengroep_ict and current_user.school_id != school_id: return jsonify({'error': 'Geen toegang'}), 403 @@ -587,7 +597,7 @@ def list_classes(school_id): @admin_bp.route('/schools//classes', methods=['POST']) @login_required -@school_ict_required +@director_or_ict_required def create_class(school_id): if not current_user.is_scholengroep_ict and current_user.school_id != school_id: return jsonify({'error': 'Geen toegang'}), 403 @@ -609,7 +619,7 @@ def create_class(school_id): @admin_bp.route('/schools//classes/', methods=['DELETE']) @login_required -@school_ict_required +@director_or_ict_required def delete_class(school_id, class_id): if not current_user.is_scholengroep_ict and current_user.school_id != school_id: return jsonify({'error': 'Geen toegang'}), 403 @@ -623,7 +633,7 @@ def delete_class(school_id, class_id): @admin_bp.route('/schools//classes//teachers', methods=['PUT']) @login_required -@school_ict_required +@director_or_ict_required def set_class_teachers(school_id, class_id): """Vervang alle leerkrachten van een klas in één keer.""" if not current_user.is_scholengroep_ict and current_user.school_id != school_id: diff --git a/backend/routes/pages.py b/backend/routes/pages.py index 93f1e2f..29946d2 100644 --- a/backend/routes/pages.py +++ b/backend/routes/pages.py @@ -58,3 +58,13 @@ def doelen_beheer(): @login_required def admin_page(): return redirect(url_for('pages.dashboard')) + + +@pages_bp.route('/klassen') +@login_required +def klassen_beheer(): + """Klassenbeheer voor directeurs (en school_ict).""" + if not current_user.is_director: + from flask import abort + abort(403) + return render_template('directeur_klassen.html', org_name=_org_name()) diff --git a/backend/templates/directeur.html b/backend/templates/directeur.html index 03d412b..5648101 100644 --- a/backend/templates/directeur.html +++ b/backend/templates/directeur.html @@ -437,7 +437,12 @@
- + + + + + Klassenbeheer +
- -
@@ -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() {
${t.full_name} ${(t.classes||[]).map(c=>c.name).join(', ') || ''} -
`).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) }); diff --git a/backend/templates/directeur_klassen.html b/backend/templates/directeur_klassen.html new file mode 100644 index 0000000..f583b3f --- /dev/null +++ b/backend/templates/directeur_klassen.html @@ -0,0 +1,363 @@ + + + + + + Klassenbeheer — {{ org_name }} + + + +
+ +
+
+ + + + + Terug naar dashboard + +

+ 🏫 Klassenbeheer + Laden... +

+
+ Uitloggen +
+ +
+
+

+ + + + Klassen +

+ +
+ +
+
Laden...
+
+
+ +
+
+

+ + + + + + Leerkrachten van deze school +

+
+
+
Laden...
+
+
+ +
+ +
+ + + +