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 @@
-
-
-
-
👤 Leerkracht toevoegen
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -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 @@
+
+
+