diff --git a/backend/routes/api.py b/backend/routes/api.py index e8cb9b7..53911ab 100644 --- a/backend/routes/api.py +++ b/backend/routes/api.py @@ -459,6 +459,29 @@ def set_my_classes(): return jsonify({'my_classes': [{'id': c.id, 'name': c.name} for c in current_user.classes]}) + +# ── Klas-leerkracht koppeling (directeur) ────────────────────────────────────── + +@api_bp.route('/classes//teachers', methods=['PUT']) +@login_required +@director_required +def set_class_teachers(class_id): + """Directeur koppelt leerkrachten aan een klas.""" + klas = Class.query.filter_by(id=class_id, school_id=current_user.school_id).first_or_404() + data = request.get_json() or {} + user_ids = data.get('teacher_ids', []) + teachers = User.query.filter( + User.id.in_(user_ids), + User.school_id == current_user.school_id, + User.is_active == True, + ).all() + klas.users = teachers + audit_log('class.user_assignment', 'class', target_id=str(class_id), + detail={'class_name': klas.name, 'teacher_ids': user_ids, + 'teacher_names': [t.full_name for t in teachers]}) + db.session.commit() + return jsonify({'teachers': [{'id': t.id, 'full_name': t.full_name} for t in teachers]}) + # ── Auditlog ─────────────────────────────────────────────────────────────────── @api_bp.route('/audit-log') diff --git a/backend/templates/directeur.html b/backend/templates/directeur.html index d93dab8..861f389 100644 --- a/backend/templates/directeur.html +++ b/backend/templates/directeur.html @@ -148,6 +148,7 @@ +
@@ -191,6 +192,10 @@
+
+ + +
@@ -234,6 +239,30 @@
+ +
+
+

👥 Leerkrachten aan klassen koppelen

+

+ Klik op een klas om de gekoppelde leerkrachten te wijzigen. +

+
Laden...
+
+
+ + + +
@@ -276,9 +305,14 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById('filterKlas').addEventListener('change', applyFilters); document.getElementById('filterStatus').addEventListener('change', applyFilters); document.getElementById('filterSearch').addEventListener('input', applyFilters); + document.getElementById('filterSectie').addEventListener('change', applyFilters); // Export knoppen document.getElementById('btnExportCSV').addEventListener('click', exportCSV); + document.getElementById('btnKoppelingAnnuleer').addEventListener('click', () => { + document.getElementById('koppelingModal').style.display = 'none'; + }); + document.getElementById('btnKoppelingOpslaan').addEventListener('click', saveKoppeling); document.getElementById('btnExportPDF').addEventListener('click', exportPDF); // Vergelijk selects @@ -331,6 +365,7 @@ async function loadOverview() { updateStats(); renderKlassen(); renderKlasProgress(); + renderKoppelingTab(); renderVakStats(); populateFilters(); applyFilters(); @@ -498,6 +533,16 @@ function populateFilters() { }); } +function populateSectieFilter(vakId) { + const sel = document.getElementById('filterSectie'); + sel.innerHTML = ''; + if (!vakId || vakId === 'all' || !allGoals[vakId]) return; + const secties = [...new Set(allGoals[vakId].map(g => g.sectie).filter(Boolean))].sort(); + secties.forEach(s => { + const o = document.createElement('option'); o.value = s; o.textContent = s; sel.appendChild(o); + }); +} + function applyFilters() { const vakFilter = document.getElementById('filterVak').value; const klasFilter = document.getElementById('filterKlas').value; @@ -505,6 +550,11 @@ function applyFilters() { const leeftFilter = [...document.querySelectorAll('#leeftijdCbs input:checked')].map(c=>c.value); const search = document.getElementById('filterSearch').value.toLowerCase(); + const sectieFilter = document.getElementById('filterSectie').value; + + // Sectie filter opties bijwerken als vak wijzigt + populateSectieFilter(vakFilter); + const vakkenToShow = vakFilter === 'all' ? Object.keys(allGoals) : [vakFilter]; const klassen = klasFilter === 'all' ? (overviewData.classes||[]) @@ -515,6 +565,7 @@ function applyFilters() { (allGoals[vakId]||[]).forEach(goal => { if (search && !(goal.goNr+' '+goal.inhoud).toLowerCase().includes(search)) return; if (leeftFilter.length > 0 && !leeftFilter.some(l=>goal.leeftijden.includes(l))) return; + if (sectieFilter !== 'all' && goal.sectie !== sectieFilter) return; const statussen = klassen.map(k => (overviewData.assessments_by_class[k.id]?.[vakId]?.[goal.id])||''); if (statusFilter==='consensus' && !statussen.every(s=>s==='groen')) return; if (statusFilter==='verschil') { @@ -662,6 +713,71 @@ function exportPDF() { showNotification('PDF geëxporteerd', 'success'); } +// ── Koppeling tab ───────────────────────────────────────────────────────────── +let allUsers = []; +let activeKlasKoppeling = null; + +async function renderKoppelingTab() { + const el = document.getElementById('koppelingContent'); + const klassen = overviewData.classes || []; + if (!klassen.length) { el.innerHTML = '
Geen klassen gevonden
'; return; } + + // Laad alle leerkrachten van de school + try { + const res = await fetch('/api/users'); + if (res.ok) { const d = await res.json(); allUsers = d.users || []; } + } catch(e) { console.warn('Kon gebruikers niet laden'); } + + el.innerHTML = '
' + klassen.map(k => { + const teachers = (k.teachers||[]).map(t => t.full_name).join(', ') || 'Geen leerkrachten'; + return `
+
+

🏫 ${k.name}

+ Wijzigen +
+
${teachers}
+
`; + }).join('') + '
'; +} + +document.addEventListener('click', function(e) { + const card = e.target.closest('[data-action="openKoppeling"]'); + if (!card) return; + activeKlasKoppeling = parseInt(card.dataset.id); + const name = card.dataset.name; + const teachers = JSON.parse(card.dataset.teachers.replace(/"/g, '"')); + + document.getElementById('koppelingModalTitle').textContent = `Leerkrachten voor ${name}`; + + const container = document.getElementById('koppelingCheckboxes'); + if (!allUsers.length) { + container.innerHTML = 'Geen leerkrachten beschikbaar. Voeg eerst leerkrachten toe via Gebruikersbeheer.'; + } else { + container.innerHTML = allUsers.map(u => ` + `).join(''); + } + document.getElementById('koppelingModal').style.display = 'flex'; +}); + +async function saveKoppeling() { + if (!activeKlasKoppeling) return; + const checked = [...document.querySelectorAll('#koppelingCheckboxes input:checked')].map(i => parseInt(i.value)); + + // Gebruik de admin route om leerkrachten aan klas te koppelen + const res = await fetch(`/api/classes/${activeKlasKoppeling}/teachers`, { + method: 'PUT', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ teacher_ids: checked }) + }); + if (!res.ok) { showNotification('Opslaan mislukt', 'error'); return; } + document.getElementById('koppelingModal').style.display = 'none'; + showNotification('Koppeling opgeslagen', 'success'); + await loadOverview(); // herlaad zodat klas-chips bijgewerkt worden +} + // ── Tabs ─────────────────────────────────────────────────────────────────────── function switchTab(name) { document.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab===name));