Files
Pivot/templates/gmail_delegatie.html
T
2026-10-14 13:34:37 +02:00

274 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Mailbox-delegatie{% endblock %}
{% block page_title %}Mailbox-delegatie{% endblock %}
{% block main_class %}medium{% endblock %}
{% block loader_text %}Bezig met Google...{% endblock %}
{% block extra_css %}
<style>
.mailbox-head { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; }
.avatar { width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center;
background: var(--primary-light); color: var(--primary-color); font-weight: 800; font-size: 18px; flex-shrink: 0; }
.mailbox-head .who { flex: 1; min-width: 200px; }
.mailbox-head .who strong { display: block; font-size: 1.1rem; }
.counter { font-size: 13px; color: var(--text-muted); }
.add-form { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--border); }
.add-form input[type=email] { flex: 1 1 260px; width: auto; }
.pick-list { list-style: none; margin: 0; padding: 0; }
.pick-list a { display: flex; justify-content: space-between; gap: 12px; padding: 10px 12px; border-radius: var(--radius-sm);
text-decoration: none; color: var(--text); }
.pick-list a:hover { background: var(--primary-light); }
.pick-list .ou { color: var(--text-muted); font-size: 13px; }
.ov-kop { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
.ov-kop .card-title { margin: 0; }
.ov-balk { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: space-between; margin: 14px 0 10px; }
.ov-balk input[type=search] { flex: 0 1 340px; }
.persoon strong { display: block; font-weight: 600; }
.persoon span { color: var(--text-muted); font-size: 13px; }
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; }
.voortgang { height: 8px; border-radius: 999px; background: var(--surface-muted); overflow: hidden; margin-top: 8px; }
.voortgang div { height: 100%; width: 0; background: var(--primary-color); transition: width .4s; }
</style>
{% endblock %}
{% block content %}
{# ---------- Zoeken ---------- #}
<div class="card">
<h2 class="card-title">✉️ Welke mailbox?</h2>
<form class="search-bar" method="GET" action="{{ url_for('gmail_delegatie.overzicht') }}" onsubmit="showLoader()" style="margin:0">
<input type="text" name="zoek" placeholder="Naam of e-mailadres van de mailbox, bv. secretariaat@..." value="{{ zoek or (mailbox.email if mailbox else '') }}" autofocus>
<button type="submit">🔍 Zoeken</button>
</form>
{% if zoekresultaten %}
<p class="muted small" style="margin:14px 0 6px">Kies een mailbox:</p>
<ul class="pick-list">
{% for u in zoekresultaten %}
<li><a href="{{ url_for('gmail_delegatie.overzicht', mailbox=u.email) }}" onclick="showLoader()">
<span><strong>{{ u.naam }}</strong> · {{ u.email }}</span><span class="ou">{{ u.ou }}</span>
</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
{% if fout %}
<div class="flash-message error" style="margin-top:20px"><span>{{ fout }}</span></div>
{% endif %}
{# ---------- Gedelegeerden van de gekozen mailbox ---------- #}
{% if mailbox %}
<div class="card">
<div class="mailbox-head">
<span class="avatar">{{ (mailbox.naam or mailbox.email)[:1]|upper }}</span>
<div class="who">
<strong>{{ mailbox.naam or mailbox.email }}</strong>
<span class="muted">{{ mailbox.email }}{% if mailbox.ou %} · {{ mailbox.ou }}{% endif %}</span>
{% if mailbox.geschorst %}<span class="badge badge-warning">Geschorst</span>{% endif %}
</div>
<span class="counter">{{ delegates|length }} / {{ max_delegates }} gedelegeerden</span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr><th>Heeft toegang</th><th>Status</th><th class="actions">Actie</th></tr>
</thead>
<tbody>
{% for d in delegates %}
<tr>
<td><strong>{{ d.email }}</strong></td>
<td>
{% if d.status == 'accepted' %}<span class="badge badge-success">{{ d.status_label }}</span>
{% elif d.status == 'pending' %}<span class="badge badge-warning">{{ d.status_label }}</span>
{% else %}<span class="badge">{{ d.status_label }}</span>{% endif %}
</td>
<td class="actions">
<form method="POST" action="{{ url_for('gmail_delegatie.verwijderen') }}"
onsubmit="if (!confirm('Toegang van {{ d.email }} tot {{ mailbox.email }} intrekken?')) return false; showLoader();">
<input type="hidden" name="mailbox" value="{{ mailbox.email }}">
<input type="hidden" name="delegate" value="{{ d.email }}">
<button type="submit" class="btn-sm btn-danger">✕ Intrekken</button>
</form>
</td>
</tr>
{% else %}
<tr class="empty-row"><td colspan="3">Niemand anders heeft toegang tot deze mailbox.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% if delegates|length < max_delegates %}
<form class="add-form" method="POST" action="{{ url_for('gmail_delegatie.toevoegen') }}" onsubmit="showLoader()">
<input type="hidden" name="mailbox" value="{{ mailbox.email }}">
<input type="email" name="delegate" required placeholder="E-mailadres van de collega die toegang krijgt">
<button type="submit" class="btn-success">➕ Toegang geven</button>
</form>
<p class="field-hint">Enkel accounts van jullie eigen Google Workspace. De collega ziet de mailbox binnen ongeveer een minuut via het profielmenu in Gmail.</p>
{% else %}
<div class="alert alert-warning" style="margin-top:16px">Deze mailbox heeft het maximum van {{ max_delegates }} gedelegeerden bereikt.</div>
{% endif %}
</div>
{% endif %}
{# ---------- Alle lopende machtigingen ---------- #}
<div class="card" id="overzicht">
<div class="ov-kop">
<h2 class="card-title">📋 Lopende machtigingen <span class="muted small">({{ machtigingen|length }})</span></h2>
<form method="POST" action="{{ url_for('gmail_delegatie.overzicht_vernieuwen') }}" style="margin:0">
<button type="submit" class="btn-light btn-sm" {{ 'disabled' if ov_status.bezig }}>🔄 Vernieuwen</button>
</form>
</div>
<p class="muted small" style="margin:6px 0 0">
{% if laatst and laatst.Opgehaald %}Alle mailboxen nagekeken op {{ laatst.Opgehaald.strftime('%d-%m-%Y om %H:%M') }}
({{ laatst.Mailboxen }} mailboxen{% if laatst.Fouten %}, {{ laatst.Fouten }} niet bevraagbaar{% endif %}).
Wijzigingen via PIVOT staan er meteen in.
{% else %}Nog niet opgehaald.{% endif %}
</p>
{% if ov_fout %}<div class="alert alert-danger" style="margin-top:12px">{{ ov_fout }}</div>{% endif %}
{% if ov_status.fout and not ov_status.bezig %}<div class="alert alert-danger" style="margin-top:12px">Ophalen mislukt: {{ ov_status.fout }}</div>{% endif %}
<div id="ovBezig" {{ '' if ov_status.bezig else 'hidden' }} style="margin-top:12px">
<span class="muted small" id="ovTekst">Mailboxen nakijken...</span>
<div class="voortgang"><div id="ovBalk"></div></div>
</div>
{% if machtigingen %}
<div class="ov-balk">
<input type="search" id="ovZoek" placeholder="Zoek een mailbox of persoon..." aria-label="Zoeken in de machtigingen">
<span class="per-pagina-plek"></span>
</div>
<div class="table-wrap">
<table id="ovTabel">
<thead>
<tr>
<th class="sorteer">Mailbox <span class="pijl"></span></th>
<th class="sorteer">Heeft toegang <span class="pijl"></span></th>
<th class="sorteer">Status <span class="pijl"></span></th>
<th class="actions"></th>
</tr>
</thead>
<tbody>
{% for m in machtigingen %}
<tr>
<td class="persoon" data-sort="{{ (m.Mailbox_naam or m.Mailbox)|lower }}"><strong>{{ m.Mailbox_naam or m.Mailbox }}</strong>{% if m.Mailbox_naam %}<span>{{ m.Mailbox }}</span>{% endif %}</td>
<td class="persoon" data-sort="{{ (m.Gedelegeerde_naam or m.Gedelegeerde)|lower }}"><strong>{{ m.Gedelegeerde_naam or m.Gedelegeerde }}</strong>{% if m.Gedelegeerde_naam %}<span>{{ m.Gedelegeerde }}</span>{% endif %}</td>
<td data-sort="{{ m.Status or '' }}">
{% if m.Status == 'accepted' %}<span class="badge badge-success">Actief</span>
{% elif m.Status == 'pending' %}<span class="badge badge-warning">In afwachting</span>
{% elif m.Status == 'rejected' %}<span class="badge">Geweigerd</span>
{% elif m.Status == 'expired' %}<span class="badge">Verlopen</span>
{% else %}<span class="badge">{{ m.Status or '?' }}</span>{% endif %}
</td>
<td class="actions"><a href="{{ url_for('gmail_delegatie.overzicht', mailbox=m.Mailbox) }}" class="btn btn-sm btn-light" onclick="showLoader()">Openen</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="tabel-voet">
<div class="muted small" id="ovTeller"></div>
<nav class="paginering" id="ovPaginas" aria-label="Pagina's"></nav>
</div>
{% elif laatst and laatst.Opgehaald %}
<p class="muted" style="margin-top:12px">Er zijn geen lopende machtigingen.</p>
{% endif %}
</div>
{# ---------- Logboek ---------- #}
{% if geschiedenis %}
<div class="card">
<h3 class="card-title">🕑 {{ 'Wijzigingen voor deze mailbox' if mailbox else 'Recente wijzigingen' }}</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Tijdstip</th><th>Door</th><th>Actie</th><th>Mailbox</th><th>Gedelegeerde</th></tr></thead>
<tbody>
{% for h in geschiedenis %}
<tr>
<td class="nowrap">{{ h.Tijdstip.strftime('%d-%m-%Y %H:%M') if h.Tijdstip else '' }}</td>
<td>{{ h.Gebruiker or '?' }}</td>
<td>{% if h.Actie == 'toegevoegd' %}<span class="badge badge-success">Toegevoegd</span>{% else %}<span class="badge badge-danger">Ingetrokken</span>{% endif %}</td>
<td>{{ h.Mailbox }}</td>
<td>{{ h.Gedelegeerde }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<script>
// Ophaling op de achtergrond: voortgang tonen en daarna de pagina vernieuwen
(function () {
const blok = document.getElementById('ovBezig');
if (!blok || blok.hidden) return;
const tekst = document.getElementById('ovTekst'), balk = document.getElementById('ovBalk');
const kijk = () => fetch({{ url_for('gmail_delegatie.overzicht_status')|tojson }}).then(r => r.json()).then(s => {
if (s.totaal) {
tekst.textContent = `Mailboxen nakijken: ${s.voortgang} van ${s.totaal}...`;
balk.style.width = Math.round(100 * s.voortgang / s.totaal) + '%';
}
if (s.bezig) setTimeout(kijk, 2000);
else location.reload();
}).catch(() => setTimeout(kijk, 5000));
kijk();
})();
// Tabel: zoeken, sorteren en pagineren
(function () {
const tabel = document.getElementById('ovTabel');
if (!tabel) return;
const body = tabel.tBodies[0], zoekveld = document.getElementById('ovZoek');
let PER_PAGINA = perPagina('gmail-machtigingen'), pagina = 1, zoek = '';
function toon() {
const rijen = Array.from(body.rows).filter(tr => !zoek || tr.textContent.toLowerCase().includes(zoek));
const grootte = PER_PAGINA || Infinity, paginas = Math.max(1, Math.ceil(rijen.length / grootte));
pagina = Math.min(Math.max(1, pagina), paginas);
const van = PER_PAGINA ? (pagina - 1) * PER_PAGINA : 0, tot = van + grootte;
Array.from(body.rows).forEach(tr => tr.style.display = 'none');
rijen.forEach((tr, i) => tr.style.display = (i >= van && i < tot) ? '' : 'none');
document.getElementById('ovTeller').textContent = rijen.length
? `${van + 1}–${Math.min(tot, rijen.length)} van ${rijen.length}` : 'Niets gevonden';
const nav = document.getElementById('ovPaginas');
nav.innerHTML = '';
if (paginas <= 1) return;
const knop = (label, p, actief) => {
const b = document.createElement('button');
b.type = 'button'; b.textContent = label; b.className = 'btn-sm ' + (actief ? 'btn-secondary' : 'btn-light');
b.onclick = () => { pagina = p; toon(); };
nav.appendChild(b);
};
if (pagina > 1) { knop('«', 1); 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); }
}
zoekveld.addEventListener('input', () => { zoek = zoekveld.value.trim().toLowerCase(); pagina = 1; toon(); });
let kol = null, op = true;
tabel.querySelectorAll('th.sorteer').forEach(th => th.addEventListener('click', () => {
const k = th.cellIndex;
op = kol === k ? !op : true;
kol = k;
tabel.querySelectorAll('th.sorteer').forEach(x => x.classList.remove('op', 'af'));
th.classList.add(op ? 'op' : 'af');
Array.from(body.rows).sort((a, b) => {
const c = (a.cells[k].dataset.sort || '').localeCompare(b.cells[k].dataset.sort || '', 'nl', { numeric: true, sensitivity: 'base' });
return op ? c : -c;
}).forEach(tr => body.appendChild(tr));
pagina = 1; toon();
}));
perPaginaKiezer(document.querySelector('#overzicht .per-pagina-plek'), 'gmail-machtigingen', n => { PER_PAGINA = n; pagina = 1; toon(); });
toon();
})();
</script>
{% endblock %}