PIVOT 1.1.0

This commit is contained in:
marijn committed 2026-10-14 13:34:37 +02:00
commit 8a53234eb6
161 files changed
+31109

No files matched your search

+416
View File
@@ -0,0 +1,416 @@
{% extends "base.html" %}
{% block title %}Chromebooks{% endblock %}
{% block page_title %}Chromebooks{% endblock %}
{% block main_class %}wide{% endblock %}
{% block extra_css %}
<style>
/* Knoppen naast elkaar; enkel op een smal scherm lopen ze door naar een tweede regel */
.knoppen { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; min-width: 200px; }
.knoppen > button { white-space: nowrap; margin: 0; }
/* Garantie en Opmerking zijn standaard verborgen */
.col-garantie, .col-opmerking { display: none; }
.toggle-garantie-label, .toggle-opmerking-label { display: none !important; }
.toggle-garantie-label.shown, .toggle-opmerking-label.shown { display: inline-flex !important; }
th.sorteer { cursor: pointer; user-select: none; white-space: nowrap; }
th.sorteer:hover { color: var(--primary-color); }
th.sorteer .pijl::after { content: "↕"; opacity: .35; margin-left: 2px; }
th.sorteer.op .pijl::after { content: "▲"; opacity: 1; }
th.sorteer.af .pijl::after { content: "▼"; opacity: 1; }
.tabel-voet { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 10px; margin-top: 12px; }
.paginering { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
[hidden] { display: none !important; }
.blok-badge { margin-left: 4px; }
</style>
{% endblock %}
{% block content %}
<div class="card">
<div class="page-head">
<h2>💻 Alle toestellen</h2>
<form method="POST" action="{{ url_for('devices.google_import') }}" style="margin:0"
onsubmit="return confirm('Alle Chromebooks uit Google Admin ophalen? Nieuwe serienummers worden toegevoegd, ontbrekende modellen aangevuld. Er wordt niets verwijderd of overschreven.') && (showLoader('Chromebooks ophalen bij Google...'), true)">
<button type="submit" class="btn-sm btn-secondary">🔄 Chromebooks ophalen uit Google</button>
</form>
</div>
<form class="search-bar" method="POST" action="{{ url_for('devices.inventaris_toestellen') }}">
<input type="text" name="query" placeholder="Zoek op toestelnummer, serienummer, ..." value="{{ query or '' }}">
<button type="submit">🔍 Zoeken</button>
</form>
{% if message %}
<div class="alert">{{ message }}</div>
{% endif %}
{% if results %}
<div class="column-toggles" id="column-toggles">
<label><input type="checkbox" id="toggle-kamxm-nummer" checked onchange="toggleColumn('col-kamxm-nummer')"> Toestelnummer</label>
<label><input type="checkbox" id="toggle-signpostnummer" checked onchange="toggleColumn('col-signpostnummer')"> Signpostnummer</label>
<label><input type="checkbox" id="toggle-serienummer" checked onchange="toggleColumn('col-serienummer')"> Serienummer</label>
<label><input type="checkbox" id="toggle-type" checked onchange="toggleColumn('col-type')"> Type</label>
<label class="toggle-garantie-label"><input type="checkbox" id="toggle-garantie" onchange="toggleColumn('col-garantie')"> Garantie</label>
<label class="toggle-opmerking-label"><input type="checkbox" id="toggle-opmerking" onchange="toggleColumn('col-opmerking')"> Opmerking</label>
<label><input type="checkbox" id="toggle-status" checked onchange="toggleColumn('col-status')"> Status</label>
<label><input type="checkbox" id="toggle-bewerken" checked onchange="toggleColumn('col-bewerken')"> Bewerken</label>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th class="col-kamxm-nummer sorteer" data-kol="0">Toestelnummer <span class="pijl"></span></th>
<th class="col-signpostnummer sorteer" data-kol="1">Signpostnummer <span class="pijl"></span></th>
<th class="col-serienummer sorteer" data-kol="2">Serienummer <span class="pijl"></span></th>
<th class="col-type sorteer" data-kol="3">Type <span class="pijl"></span></th>
<th class="col-garantie sorteer" data-kol="4">Garantie <span class="pijl"></span></th>
<th class="col-opmerking sorteer" data-kol="5">Opmerking <span class="pijl"></span></th>
<th class="col-status sorteer" data-kol="6">Status <span class="pijl"></span></th>
<th class="actions" aria-label="Acties"></th>
<th class="col-bewerken actions" aria-label="Bewerken"></th>
</tr>
</thead>
<tbody id="toestelBody">
{% for row in results %}
{% set g = (row['Garantie'] or '').split('-') %}
<tr>
<td class="col-kamxm-nummer" data-sort="{{ row['KAMXM_nummer'] or '' }}"><strong>{{ row['KAMXM_nummer'] or '' }}</strong></td>
<td class="col-signpostnummer" data-sort="{{ row['Signpostnummer'] or '' }}">{{ row['Signpostnummer'] or '' }}</td>
<td class="col-serienummer" data-sort="{{ row['Serienummer'] or '' }}">{{ row['Serienummer'] or '' }}</td>
<td class="col-type" data-sort="{{ row['Type'] or '' }}">{{ row['Type'] or '' }}</td>
<td class="col-garantie" data-sort="{{ (g[2] ~ '-' ~ g[1] ~ '-' ~ g[0]) if g|length == 3 else '' }}">{{ row['Garantie'] or '' }}</td>
<td class="col-opmerking" data-sort="{{ row['Opmerking'] or '' }}">{{ row['Opmerking'] or '' }}</td>
<td class="col-status" data-sort="{{ row['Status'] or 'Beschikbaar' }}">
{% if row['Status'] == 'Defect' %}<span class="badge badge-danger">🛠️ Defect</span>
{% elif row['Status'] == 'Uitgeleend' %}<span class="badge badge-info">🧑‍💻 Uitgeleend</span>
{% else %}<span class="badge badge-success">✅ Beschikbaar</span>
{% endif %}
<span class="badge badge-danger blok-badge" {{ '' if row['Geblokkeerd']|int == 1 else 'hidden' }}>🔒 Geblokkeerd</span>
</td>
<td class="actions">
<span class="knoppen">
<button type="button" class="btn-sm" onclick="openStatusModal('{{ row['Toestel_ID'] }}', '{{ query }}', '{{ row['KAMXM_nummer'] or row['Serienummer'] or '' }}')">Wijzig status</button>
{% if mag('toestel_blokkeren') and row['Serienummer'] %}
{% set blok = row['Geblokkeerd']|int == 1 %}
<button type="button" class="btn-sm blok-knop {{ 'btn-success' if blok else 'btn-danger' }}"
data-id="{{ row['Toestel_ID'] }}" data-label="{{ row['KAMXM_nummer'] or row['Serienummer'] }}"
data-geblokkeerd="{{ 1 if blok else 0 }}" onclick="wisselBlokkering(this)">{{ '🔓 Deblokkeren' if blok else '🔒 Blokkeren' }}</button>
{% endif %}
{% if row['Serienummer'] %}
<button type="button" class="btn-sm btn-light" title="Wie gebruikte dit toestel? (Google Admin)"
data-serie="{{ row['Serienummer'] }}" data-label="{{ row['KAMXM_nummer'] or row['Serienummer'] }}"
onclick="openGoogleInfo(this)">👤 Gebruik</button>
{% endif %}
<button type="button" class="btn-sm btn-light" title="Welke leerlingen hadden dit toestel?"
data-id="{{ row['Toestel_ID'] }}" data-label="{{ row['KAMXM_nummer'] or row['Serienummer'] or '' }}"
onclick="openToestelHistorie(this)">🕑 Historie</button>
</span>
</td>
<td class="col-bewerken actions">
<button type="button" class="btn-bewerk btn-sm"
data-kamxm-key="{{ row['Toestel_ID'] }}"
data-serienummer="{{ row['Serienummer'] or '' }}"
data-kamxm="{{ row['KAMXM_nummer'] or '' }}"
data-signpost="{{ row['Signpostnummer'] or '' }}"
data-type="{{ row['Type'] or '' }}"
data-garantie="{{ row['Garantie'] or '' }}"
data-opmerking="{{ row['Opmerking'] or '' }}"
onclick="openEditModalFromButton(this)">✏️ Bewerken</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="tabel-voet">
<div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap">
<div class="muted small" id="toestelTeller"></div>
<span id="toestellenPerPagina"></span>
</div>
<nav class="paginering" id="toestelPaginering" aria-label="Pagina's"></nav>
</div>
{% elif query %}
<p class="muted">Geen resultaten gevonden voor "{{ query }}".</p>
{% endif %}
</div>
{% endblock %}
{% block modals %}
<div id="statusModal" class="modal">
<div class="modal-content">
<span class="close" onclick="document.getElementById('statusModal').style.display='none'">&times;</span>
<h3 id="modalTitle">Status wijzigen: <span id="modalkamxm_nummer"></span></h3>
<form id="statusForm" method="POST" action="{{ url_for('devices.wijzig_status') }}">
<input type="hidden" name="toestel_id" id="modalHiddenkamxm_nummer">
<input type="hidden" name="query" id="modalHiddenQuery" value="{{ query or '' }}">
<input type="hidden" name="new_status" id="modalHiddenStatus">
<button type="button" class="status-button status-success" id="btn-beschikbaar" onclick="submitStatus('Beschikbaar')">✅ Beschikbaar</button>
<button type="button" class="status-button status-info" id="btn-uitgeleend" onclick="submitStatus('Uitgeleend')">🧑‍💻 Uitgeleend</button>
<button type="button" class="status-button status-danger" id="btn-defect" onclick="submitStatus('Defect')">🛠️ Defect</button>
</form>
</div>
</div>
<div id="googleInfoModal" class="modal">
<div class="modal-content wide">
<span class="close" onclick="document.getElementById('googleInfoModal').style.display='none'">&times;</span>
<h3>👤 Gebruik van toestel <span id="giTitel"></span></h3>
<div id="giInhoud"><p class="muted">Gegevens ophalen bij Google…</p></div>
</div>
</div>
<div id="historieModal" class="modal">
<div class="modal-content wide">
<span class="close" onclick="document.getElementById('historieModal').style.display='none'">&times;</span>
<h3>🕑 Eigenaars van toestel <span id="thTitel"></span></h3>
<div id="thInhoud"><p class="muted">Laden...</p></div>
</div>
</div>
<div id="editModal" class="modal">
<div class="modal-content">
<span class="close" onclick="document.getElementById('editModal').style.display='none'">&times;</span>
<h3>Bewerk toestel: <span id="editModalkamxm_nummerTitle"></span></h3>
<form id="editForm" method="POST" action="{{ url_for('devices.bewerk_toestel') }}">
<input type="hidden" name="toestel_id" id="editHiddenkamxm_nummerKey">
<input type="hidden" name="query" id="editHiddenQuery" value="{{ query or '' }}">
<div class="field"><label>Toestelnummer</label><input type="text" name="schooleigen_id" id="edit_kamxm_nummer_display"></div>
<div class="field"><label>Serienummer</label><input type="text" name="serienummer" id="edit_serienummer"></div>
<div class="field"><label>Signpostnummer</label><input type="text" name="signpostnummer" id="edit_signpostnummer"></div>
<div class="field"><label>Type</label><input type="text" name="type" id="edit_type"></div>
<div class="field"><label>Garantie tot</label><input type="text" name="garantie" id="edit_garantie" placeholder="dd-mm-jjjj"></div>
<div class="field"><label>Opmerking</label><textarea name="opmerking" id="edit_opmerking"></textarea></div>
<button type="submit" class="btn-block">💾 Gegevens opslaan</button>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
<style>
#giInhoud h4 { margin: 16px 0 6px; font-size: .95rem; }
#giInhoud table { width: 100%; border-collapse: collapse; font-size: .92rem; }
#giInhoud td, #giInhoud th { padding: 6px 8px; border-bottom: 1px solid var(--border); text-align: left; }
.gi-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 8px 16px; font-size: .9rem; }
.gi-meta div span { display: block; color: var(--text-muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .03em; }
.gi-balk { display: inline-block; height: 8px; border-radius: 4px; background: var(--primary-color); vertical-align: middle; margin-right: 6px; }
</style>
<script>
function giEsc(t) { return String(t ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
function giDatum(iso) {
if (!iso) return 'onbekend';
const d = new Date(iso);
return isNaN(d) ? iso : d.toLocaleString('nl-BE', { dateStyle: 'short', timeStyle: 'short' });
}
function giDuur(min) { return min >= 60 ? `${Math.floor(min / 60)} u ${String(min % 60).padStart(2, '0')}` : `${min} min`; }
const TOESTEL_HISTORIE_URL = {{ url_for('devices.toestel_historie', toestel_id=0)|tojson }}.replace(/0$/, '');
function thDuur(dagen) {
if (dagen == null) return '';
if (dagen < 31) return dagen + ' dag' + (dagen === 1 ? '' : 'en');
const m = Math.round(dagen / 30.44);
return m < 12 ? m + ' maand' + (m === 1 ? '' : 'en') : (dagen / 365.25).toFixed(1).replace('.', ',') + ' jaar';
}
function openToestelHistorie(knop) {
const venster = document.getElementById('historieModal'), inhoud = document.getElementById('thInhoud');
document.getElementById('thTitel').textContent = knop.dataset.label;
inhoud.innerHTML = '<p class="muted">Laden...</p>';
venster.style.display = 'block';
fetch(TOESTEL_HISTORIE_URL + encodeURIComponent(knop.dataset.id), { credentials: 'same-origin' })
.then(r => r.json()).then(res => {
const e = res.eigenaars || [];
if (!e.length) { inhoud.innerHTML = '<p class="muted">Dit toestel werd nog nooit aan een leerling uitgedeeld.</p>'; return; }
inhoud.innerHTML = `<p class="muted small">${e.length} eigenaar(s) in totaal</p>
<div class="table-wrap"><table><thead><tr><th>Internnummer</th><th>Leerling</th><th>Gebruikersnaam</th>
<th>Gekregen</th><th>Ingeleverd</th><th>Duur</th></tr></thead><tbody>`
+ e.map(x => `<tr${x.huidig ? ' style="background:var(--primary-light)"' : ''}>
<td><strong>${giEsc(x.internnummer)}</strong></td><td>${giEsc(x.naam)}</td><td>${giEsc(x.gebruikersnaam)}</td>
<td style="white-space:nowrap">${giEsc(x.van)}</td>
<td style="white-space:nowrap">${x.huidig ? '<span class="badge badge-success">nu in gebruik</span>' : giEsc(x.tot)}${x.geblokkeerd && x.huidig ? ' <span class="badge badge-danger">🔒</span>' : ''}</td>
<td style="white-space:nowrap">${thDuur(x.dagen)}</td></tr>`).join('')
+ '</tbody></table></div>';
}).catch(() => { inhoud.innerHTML = '<div class="alert alert-danger">Kon de historie niet ophalen.</div>'; });
}
window.addEventListener('click', e => { if (e.target.id === 'historieModal') e.target.style.display = 'none'; });
// Blokkeren / deblokkeren in Google zonder de pagina te herladen (zoekopdracht, sortering en pagina blijven)
const BLOKKEER_URL = {{ url_for('devices.toestel_blokkeren')|tojson }};
function wisselBlokkering(knop) {
const blokkeren = knop.dataset.geblokkeerd !== '1';
const vraag = blokkeren
? `Chromebook ${knop.dataset.label} blokkeren in Google Admin?\nDe leerling kan het toestel dan niet meer gebruiken.`
: `Chromebook ${knop.dataset.label} deblokkeren in Google Admin?`;
if (!confirm(vraag)) return;
const tekst = knop.textContent;
knop.disabled = true; knop.textContent = '⏳';
const data = new FormData();
data.append('toestel_id', knop.dataset.id);
data.append('actie', blokkeren ? 'blokkeren' : 'deblokkeren');
fetch(BLOKKEER_URL, { method: 'POST', body: data, credentials: 'same-origin', headers: { 'X-Requested-With': 'fetch' } })
.then(r => r.json()).then(res => {
knop.disabled = false;
if (!res.ok) { knop.textContent = tekst; alert(res.melding || 'Mislukt.'); return; }
const nu = !!res.geblokkeerd;
knop.dataset.geblokkeerd = nu ? '1' : '0';
knop.textContent = nu ? '🔓 Deblokkeren' : '🔒 Blokkeren';
knop.classList.toggle('btn-success', nu); knop.classList.toggle('btn-danger', !nu);
const badge = knop.closest('tr').querySelector('.blok-badge');
if (badge) badge.hidden = !nu;
}).catch(() => { knop.disabled = false; knop.textContent = tekst; alert('Er ging iets mis; probeer opnieuw.'); });
}
function openGoogleInfo(knop) {
document.getElementById('giTitel').textContent = knop.dataset.label;
const doel = document.getElementById('giInhoud');
doel.innerHTML = '<p class="muted">Gegevens ophalen bij Google…</p>';
document.getElementById('googleInfoModal').style.display = 'block';
fetch("{{ url_for('devices.google_info') }}?serie=" + encodeURIComponent(knop.dataset.serie), { credentials: 'same-origin' })
.then(r => r.json())
.then(res => {
if (!res.ok) { doel.innerHTML = `<div class="alert alert-danger">${giEsc(res.fout)}</div>`; return; }
const i = res.info;
let h = `<div class="gi-meta">
<div><span>Laatste synchronisatie</span>${giEsc(giDatum(i.laatste_sync))}</div>
<div><span>Model</span>${giEsc(i.model || '–')}</div>
<div><span>Organisatie-eenheid</span>${giEsc(i.ou || '–')}</div>
<div><span>Status in Google</span>${giEsc({ACTIVE: 'Actief', DISABLED: 'Geblokkeerd', DEPROVISIONED: 'Uitgeschreven'}[i.status] || i.status || '–')}</div>
</div>`;
h += '<h4>Recente gebruikers <span class="muted small">(meest recent bovenaan)</span></h4>';
if (i.gebruikers.length) {
h += '<table><tbody>' + i.gebruikers.map((g, n) => `<tr>
<td style="width:28px" class="muted">${n + 1}</td>
<td>${g.beheerd ? giEsc(g.email) : '<span class="muted">niet-beheerd account (gast of extern)</span>'}</td>
<td>${giEsc(g.naam || '')}</td></tr>`).join('') + '</tbody></table>';
} else {
h += '<p class="muted">Google geeft geen gebruikers terug. Staat de gebruikersrapportage aan voor deze OU '
+ '(Google Admin › Apparaten › Chrome › Instellingen › Apparaat)?</p>';
}
h += '<h4>Actieve tijd per dag</h4>';
if (i.actief.length) {
const max = Math.max(...i.actief.map(a => a.minuten), 1);
h += '<table><tbody>' + i.actief.map(a => `<tr>
<td style="width:110px">${giEsc(new Date(a.datum).toLocaleDateString('nl-BE', { weekday: 'short', day: '2-digit', month: '2-digit' }))}</td>
<td><span class="gi-balk" style="width:${Math.round(a.minuten / max * 220)}px"></span>${giDuur(a.minuten)}</td></tr>`).join('') + '</tbody></table>';
} else {
h += '<p class="muted">Geen gegevens over actieve tijd.</p>';
}
doel.innerHTML = h;
})
.catch(e => { doel.innerHTML = `<div class="alert alert-danger">Ophalen mislukt: ${giEsc(e)}</div>`; });
}
// Sorteren (klik op kolomkop) en 10 toestellen per pagina — alles in de browser
(function () {
const body = document.getElementById('toestelBody');
if (!body) return;
let PER_PAGINA = perPagina('toestellen');
const alle = Array.from(body.rows);
let pagina = 1, kol = null, op = true;
function toon() {
const grootte = PER_PAGINA || Infinity;
const paginas = Math.max(1, Math.ceil(alle.length / grootte));
pagina = Math.min(Math.max(1, pagina), paginas);
const van = PER_PAGINA ? (pagina - 1) * PER_PAGINA : 0, tot = van + grootte; // 0 = alles
Array.from(body.rows).forEach((tr, i) => tr.style.display = (i >= van && i < tot) ? '' : 'none');
document.getElementById('toestelTeller').textContent =
`${van + 1}–${Math.min(tot, alle.length)} van ${alle.length} toestel(len)`;
const nav = document.getElementById('toestelPaginering');
nav.innerHTML = '';
if (paginas <= 1) return;
const knop = (label, p, actief, titel) => {
const b = document.createElement('button');
b.type = 'button'; b.textContent = label; b.className = 'btn-sm ' + (actief ? 'btn-secondary' : 'btn-light');
if (titel) b.title = titel;
b.onclick = () => { pagina = p; toon(); body.closest('.table-wrap').scrollIntoView({ block: 'start' }); };
nav.appendChild(b);
};
if (pagina > 1) { knop('«', 1, false, 'Eerste pagina'); knop('‹ Vorige', pagina - 1); }
for (let p = Math.max(1, pagina - 2); p <= Math.min(paginas, pagina + 2); p++) knop(String(p), p, p === pagina);
if (pagina < paginas) { knop('Volgende ›', pagina + 1); knop('»', paginas, false, 'Laatste pagina'); }
const info = document.createElement('span');
info.className = 'muted small'; info.textContent = `pagina ${pagina} van ${paginas}`;
nav.appendChild(info);
}
document.querySelectorAll('th.sorteer').forEach(th => th.addEventListener('click', () => {
const k = +th.dataset.kol;
op = kol === k ? !op : true; kol = k;
document.querySelectorAll('th.sorteer').forEach(x => x.classList.remove('op', 'af'));
th.classList.add(op ? 'op' : 'af');
const rijen = Array.from(body.rows);
rijen.sort((a, b) => {
const x = a.cells[k].dataset.sort || '', y = b.cells[k].dataset.sort || '';
if (!x !== !y) return x ? -1 : 1; // lege waarden altijd onderaan
const c = x.localeCompare(y, 'nl', { numeric: true, sensitivity: 'base' });
return op ? c : -c;
});
rijen.forEach(tr => body.appendChild(tr));
pagina = 1; toon();
}));
perPaginaKiezer(document.getElementById('toestellenPerPagina'), 'toestellen', n => { PER_PAGINA = n; pagina = 1; toon(); });
toon();
})();
function openStatusModal(toestelId, currentQuery, label) {
document.getElementById('modalkamxm_nummer').textContent = label || toestelId;
document.getElementById('modalHiddenkamxm_nummer').value = toestelId;
document.getElementById('modalHiddenQuery').value = currentQuery;
document.getElementById('statusModal').style.display = 'block';
}
function submitStatus(status) {
document.getElementById('modalHiddenStatus').value = status;
document.getElementById('statusForm').submit();
}
function openEditModalFromButton(buttonElement) {
openEditModal({
kamxm_nummer_key: buttonElement.getAttribute('data-kamxm-key'),
kamxm: buttonElement.getAttribute('data-kamxm'),
serienummer: buttonElement.getAttribute('data-serienummer'),
signpostnummer: buttonElement.getAttribute('data-signpost'),
type: buttonElement.getAttribute('data-type'),
garantie: buttonElement.getAttribute('data-garantie'),
opmerking: buttonElement.getAttribute('data-opmerking')
});
}
function openEditModal(rowData) {
document.getElementById('editModalkamxm_nummerTitle').textContent = rowData.kamxm || rowData.serienummer || '';
document.getElementById('editHiddenkamxm_nummerKey').value = rowData.kamxm_nummer_key;
document.getElementById('edit_kamxm_nummer_display').value = rowData.kamxm || '';
document.getElementById('edit_serienummer').value = rowData.serienummer || '';
document.getElementById('edit_signpostnummer').value = rowData.signpostnummer || '';
document.getElementById('edit_type').value = rowData.type || '';
document.getElementById('edit_garantie').value = rowData.garantie || '';
document.getElementById('edit_opmerking').value = rowData.opmerking || '';
document.getElementById('editModal').style.display = 'block';
makeHiddenTogglesVisible();
}
function toggleColumn(columnClass) {
const checkbox = document.getElementById('toggle-' + columnClass.replace('col-', ''));
if (!checkbox) return;
const displayStyle = checkbox.checked ? 'table-cell' : 'none';
document.querySelectorAll('.' + columnClass).forEach(cell => { cell.style.display = displayStyle; });
}
function makeHiddenTogglesVisible() {
['.toggle-garantie-label', '.toggle-opmerking-label'].forEach(selector => {
const el = document.querySelector(selector);
if (el) el.classList.add('shown');
});
}
document.addEventListener('DOMContentLoaded', () => {
['col-kamxm-nummer', 'col-signpostnummer', 'col-serienummer', 'col-type', 'col-status', 'col-bewerken']
.forEach(toggleColumn);
});
window.onclick = function (event) {
if (event.target.classList.contains('modal')) event.target.style.display = 'none';
};
</script>
{% endblock %}