feat: add functionality for linking teachers to classes with a new UI tab and API endpoint
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:
@@ -148,6 +148,7 @@
|
||||
<button class="tab-btn" data-tab="vakken">📚 Per vak</button>
|
||||
<button class="tab-btn" data-tab="doelen">📋 Doelen detail</button>
|
||||
<button class="tab-btn" data-tab="vergelijk">⚖️ Vergelijken</button>
|
||||
<button class="tab-btn" data-tab="koppeling">👥 Leerkrachten</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-content active" id="tab-klassen">
|
||||
@@ -191,6 +192,10 @@
|
||||
<label>Leeftijd</label>
|
||||
<div class="leeftijd-cbs" id="leeftijdCbs"></div>
|
||||
</div>
|
||||
<div class="fg">
|
||||
<label>Sectie</label>
|
||||
<select id="filterSectie"><option value="all">Alle secties</option></select>
|
||||
</div>
|
||||
<div class="fg">
|
||||
<label>Zoeken</label>
|
||||
<input type="text" id="filterSearch" placeholder="Code of beschrijving...">
|
||||
@@ -234,6 +239,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB: Leerkrachten koppelen -->
|
||||
<div class="tab-content" id="tab-koppeling">
|
||||
<div class="card">
|
||||
<h2>👥 Leerkrachten aan klassen koppelen</h2>
|
||||
<p style="font-size:.85rem;color:var(--gray-500);margin-bottom:1rem;">
|
||||
Klik op een klas om de gekoppelde leerkrachten te wijzigen.
|
||||
</p>
|
||||
<div id="koppelingContent"><div class="empty">Laden...</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal: leerkrachten koppelen -->
|
||||
<div id="koppelingModal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1000;align-items:center;justify-content:center;">
|
||||
<div style="background:white;border-radius:12px;padding:1.5rem;max-width:420px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,.3);">
|
||||
<h2 style="font-size:1.1rem;margin-bottom:.5rem;" id="koppelingModalTitle">Leerkrachten koppelen</h2>
|
||||
<p style="font-size:.82rem;color:var(--gray-500);margin-bottom:1rem;">Selecteer de leerkrachten voor deze klas.</p>
|
||||
<div id="koppelingCheckboxes" style="display:flex;flex-direction:column;gap:.4rem;max-height:280px;overflow-y:auto;margin-bottom:1rem;"></div>
|
||||
<div style="display:flex;gap:.5rem;justify-content:flex-end;">
|
||||
<button id="btnKoppelingAnnuleer" class="btn btn-secondary">Annuleren</button>
|
||||
<button id="btnKoppelingOpslaan" class="btn btn-primary">Opslaan</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="notification" id="notification"></div>
|
||||
@@ -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 = '<option value="all">Alle secties</option>';
|
||||
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 = '<div class="empty">Geen klassen gevonden</div>'; 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 = '<div class="vak-grid">' + klassen.map(k => {
|
||||
const teachers = (k.teachers||[]).map(t => t.full_name).join(', ') || '<em style="color:var(--gray-400)">Geen leerkrachten</em>';
|
||||
return `<div class="vak-card" style="cursor:pointer;" data-action="openKoppeling" data-id="${k.id}" data-name="${k.name.replace(/"/g,'"')}" data-teachers="${JSON.stringify((k.teachers||[]).map(t=>t.id)).replace(/"/g,'"')}">
|
||||
<div class="vak-card-header">
|
||||
<h3>🏫 ${k.name}</h3>
|
||||
<span style="font-size:.75rem;background:var(--primary);color:white;padding:.2rem .5rem;border-radius:4px;">Wijzigen</span>
|
||||
</div>
|
||||
<div style="font-size:.82rem;color:var(--gray-600);">${teachers}</div>
|
||||
</div>`;
|
||||
}).join('') + '</div>';
|
||||
}
|
||||
|
||||
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 = '<em style="color:var(--gray-400)">Geen leerkrachten beschikbaar. Voeg eerst leerkrachten toe via Gebruikersbeheer.</em>';
|
||||
} else {
|
||||
container.innerHTML = allUsers.map(u => `
|
||||
<label style="display:flex;align-items:center;gap:.5rem;cursor:pointer;padding:.35rem;border-radius:4px;">
|
||||
<input type="checkbox" value="${u.id}" ${teachers.includes(u.id)?'checked':''}>
|
||||
<span>${u.full_name} <span style="color:var(--gray-400);font-size:.8rem;">(${u.email})</span></span>
|
||||
</label>`).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));
|
||||
|
||||
Reference in New Issue
Block a user