91 lines
5.8 KiB
HTML
91 lines
5.8 KiB
HTML
{# Zoekvelden met suggesties voor uitdelen / inleveren:
|
|
- Internnummer (<input id="internnummer"> + <ul id="leerlingSuggesties">): zoeken op internnummer of naam
|
|
- Toestelnummer (<input id="serienummer"> + <ul id="toestelSuggesties">): zoeken op toestel- of serienummer
|
|
Opties: toestel_voorkeur = 'beschikbaar' | 'uitgeleend' (die eerst tonen),
|
|
toestel_vul_intern = bij inleveren het andere veld automatisch invullen. #}
|
|
<style>
|
|
.zoek-wrap { position: relative; }
|
|
.suggesties { display: none; position: absolute; left: 0; right: 0; top: 100%; z-index: 20; margin: 2px 0 0; padding: 4px;
|
|
list-style: none; background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius);
|
|
box-shadow: var(--shadow-lg); max-height: 320px; overflow-y: auto; }
|
|
.suggesties.open { display: block; }
|
|
.suggesties li { padding: 8px 10px; border-radius: 6px; cursor: pointer; display: flex; justify-content: space-between; gap: 10px; }
|
|
.suggesties li.actief, .suggesties li:hover { background: var(--primary-light); }
|
|
.suggesties .nr { font-weight: 700; }
|
|
.suggesties .sub { color: var(--text-muted); font-size: .85em; }
|
|
.suggesties .leeg { color: var(--text-muted); cursor: default; }
|
|
</style>
|
|
<script>
|
|
const zoekEsc = t => String(t ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
function zoekFlits(el) { el.style.backgroundColor = '#d1fae5'; setTimeout(() => el.style.backgroundColor = '', 600); }
|
|
|
|
// Algemeen zoekveld met suggestielijst (pijltjes, Enter, Escape, muis). Enter zonder keuze = gewoon indienen (scanner).
|
|
function zoekveld({ veldId, lijstId, url, regel, kies, leeg }) {
|
|
const veld = document.getElementById(veldId), lijst = document.getElementById(lijstId);
|
|
if (!veld || !lijst) return;
|
|
let timer, items = [], actief = -1;
|
|
const sluit = () => { lijst.classList.remove('open'); veld.setAttribute('aria-expanded', 'false'); actief = -1; };
|
|
const neem = i => { const t = items[i]; if (!t) return; kies(t, veld); sluit(); };
|
|
const teken = () => {
|
|
lijst.innerHTML = items.length
|
|
? items.map((t, i) => `<li role="option" data-i="${i}" class="${i === actief ? 'actief' : ''}">${regel(t)}</li>`).join('')
|
|
: `<li class="leeg">${leeg}</li>`;
|
|
lijst.classList.add('open'); veld.setAttribute('aria-expanded', 'true');
|
|
};
|
|
veld.addEventListener('input', () => {
|
|
clearTimeout(timer);
|
|
const q = veld.value.trim();
|
|
if (!q) { sluit(); return; }
|
|
timer = setTimeout(() => {
|
|
fetch(url + encodeURIComponent(q), { credentials: 'same-origin' })
|
|
.then(r => r.json()).then(d => { items = d; actief = -1; teken(); }).catch(sluit);
|
|
}, 200);
|
|
});
|
|
veld.addEventListener('keydown', e => {
|
|
if (!lijst.classList.contains('open') || !items.length) return;
|
|
if (e.key === 'ArrowDown') { actief = (actief + 1) % items.length; teken(); e.preventDefault(); }
|
|
else if (e.key === 'ArrowUp') { actief = (actief - 1 + items.length) % items.length; teken(); e.preventDefault(); }
|
|
else if (e.key === 'Enter' && actief >= 0) { neem(actief); e.preventDefault(); }
|
|
else if (e.key === 'Escape') sluit();
|
|
});
|
|
lijst.addEventListener('mousedown', e => { const li = e.target.closest('li[data-i]'); if (li) { e.preventDefault(); neem(+li.dataset.i); } });
|
|
veld.addEventListener('blur', () => setTimeout(sluit, 150));
|
|
}
|
|
|
|
// Internnummer: zoeken op internnummer of naam; het internnummer komt in het veld
|
|
zoekveld({
|
|
veldId: 'internnummer', lijstId: 'leerlingSuggesties', leeg: 'Geen leerling gevonden',
|
|
url: "{{ url_for('devices.zoek_leerlingen') }}?q=",
|
|
regel: l => `<span><span class="nr">${zoekEsc(l.naam || '(geen naam)')}</span>
|
|
<span class="sub"> · ${zoekEsc(l.intern)}${l.gebruiker ? ' · ' + zoekEsc(l.gebruiker) : ''}</span></span>
|
|
<span class="sub">${l.toestellen ? '💻 ' + zoekEsc(l.toestellen) : 'geen toestel'}</span>`,
|
|
kies: (l, veld) => {
|
|
veld.value = l.intern;
|
|
{% if toestel_vul_intern %}
|
|
// Inleveren: het toestel van deze leerling meteen invullen (als het nog leeg is en hij er juist één heeft)
|
|
const toestel = document.getElementById('serienummer');
|
|
if (toestel && !toestel.value.trim() && l.toestellen && !l.toestellen.includes(',')) { toestel.value = l.toestellen; zoekFlits(toestel); }
|
|
{% endif %}
|
|
document.getElementById('serienummer')?.focus();
|
|
},
|
|
});
|
|
|
|
// Toestelnummer: zoeken op toestel- of serienummer
|
|
zoekveld({
|
|
veldId: 'serienummer', lijstId: 'toestelSuggesties', leeg: 'Geen toestel gevonden',
|
|
url: "{{ url_for('devices.zoek_toestellen') }}?voorkeur={{ toestel_voorkeur|default('') }}&q=",
|
|
regel: t => `<span><span class="nr">${zoekEsc(t.nummer || '(geen toestelnummer)')}</span>
|
|
<span class="sub"> · ${zoekEsc(t.serie)}${t.type ? ' · ' + zoekEsc(t.type) : ''}</span></span>
|
|
<span class="sub">${t.bij ? 'uitgeleend aan ' + zoekEsc(t.bij) : zoekEsc(t.status || 'Beschikbaar')}</span>`,
|
|
kies: (t, veld) => {
|
|
veld.value = t.nummer || t.serie;
|
|
{% if toestel_vul_intern %}
|
|
// Inleveren: internnummer van de leerling die het toestel heeft meteen invullen (als het nog leeg is)
|
|
const intern = document.getElementById('internnummer');
|
|
if (intern && !intern.value.trim() && t.bij) { intern.value = t.bij; zoekFlits(intern); }
|
|
{% endif %}
|
|
veld.focus();
|
|
},
|
|
});
|
|
</script>
|