Klik op je naam rechtsboven om een menu te openen met een schakelaar voor het donkere thema. De keuze wordt per browser onthouden. - global.css: donkere kleurvariabelen, alleen op scherm (afdrukken blijft licht) - base.html: gebruikersmenu met themaschakelaar - vaste witte achtergronden en pastelkleuren op enkele pagina's volgen nu het thema Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
163 lines
9.8 KiB
HTML
163 lines
9.8 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Eigendom importeren{% endblock %}
|
||
{% block page_title %}Database › Eigendom{% endblock %}
|
||
{% block loader_text %}Bestand controleren...{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.stappen { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
|
||
.stappen > .card + .card { margin-top: 0; }
|
||
@media (max-width: 860px) { .stappen { grid-template-columns: 1fr; } }
|
||
.stap-nr { display: inline-grid; place-items: center; width: 26px; height: 26px; border-radius: 50%;
|
||
background: var(--primary-color); color: #fff; font-size: 14px; margin-right: 6px; }
|
||
.kolom-lijst { margin: 10px 0 0; padding: 0; list-style: none; font-size: 14px; }
|
||
.kolom-lijst li { padding: 6px 0; border-bottom: 1px solid var(--border); }
|
||
.kolom-lijst li:last-child { border-bottom: 0; }
|
||
.kolom-lijst strong { display: inline-block; min-width: 120px; }
|
||
.modus-keuze { display: grid; gap: 8px; margin: 4px 0 16px; }
|
||
.modus-keuze label { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border: 1px solid var(--border);
|
||
border-radius: var(--radius-sm); font-weight: 500; cursor: pointer; background: var(--surface-muted); }
|
||
.modus-keuze label:has(input:checked) { border-color: var(--primary-color); background: var(--primary-light); }
|
||
.modus-keuze input { margin-top: 2px; flex-shrink: 0; }
|
||
.modus-keuze small { display: block; color: var(--text-muted); font-weight: 400; }
|
||
.tellers { display: flex; flex-wrap: wrap; gap: 10px; margin: 6px 0 14px; }
|
||
.teller-knop { all: unset; cursor: pointer; display: inline-flex; gap: 8px; align-items: center; padding: 8px 14px;
|
||
border-radius: var(--radius); border: 1px solid var(--border); background: var(--surface); font-weight: 600; font-size: 14px; }
|
||
.teller-knop.actief { border-color: var(--primary-color); box-shadow: 0 0 0 2px rgba(30,111,165,.18); }
|
||
.teller-knop .n { font-size: 18px; }
|
||
tr.rij-fout td { background: #fff7f7; }
|
||
td.melding { font-size: 13px; color: var(--text-muted); min-width: 220px; }
|
||
td.melding .fout { color: var(--danger-text); }
|
||
td.melding .wijz { color: var(--text); }
|
||
.bevestig { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: space-between; margin-top: 16px; }
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
{% if not plan %}
|
||
<div class="stappen">
|
||
<div class="card">
|
||
<h2 class="card-title"><span class="stap-nr">1</span>Sjabloon downloaden</h2>
|
||
<p class="muted">Vul het sjabloon in met één toekenning (leerling + toestel) per rij. Leerling en toestel moeten al
|
||
in PIVOT staan{% if mag('database_toestellen') %}; nieuwe toestellen voeg je eerst toe via
|
||
<a href="{{ url_for('database_beheer.toestellen') }}">Toestellen importeren</a>{% endif %}.</p>
|
||
<a class="btn" href="{{ url_for('database_beheer.eigendom_sjabloon') }}">⬇️ Sjabloon downloaden (.xlsx)</a>
|
||
<ul class="kolom-lijst">
|
||
{% for k in kolommen %}
|
||
<li><strong>{{ k[1] }}</strong> <span class="muted">{{ k[3] }}</span></li>
|
||
{% endfor %}
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2 class="card-title"><span class="stap-nr">2</span>Excel uploaden</h2>
|
||
<p class="muted">Je krijgt eerst een voorbeeld te zien. Er wordt pas iets bewaard als je bevestigt.
|
||
{% if aantal is not none %}Er zijn nu <strong>{{ aantal }}</strong> toestellen uitgeleend.{% endif %}</p>
|
||
<form method="POST" action="{{ url_for('database_beheer.eigendom_controleren') }}" enctype="multipart/form-data"
|
||
onsubmit="showLoader('Bestand controleren...')">
|
||
<div class="field">
|
||
<label for="bestand">Excel-bestand (.xlsx)</label>
|
||
<input type="file" id="bestand" name="bestand" accept=".xlsx,.xlsm,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" required>
|
||
<div class="field-hint">Max. {{ max_rijen }} rijen. Het tabblad <em>Eigendom</em> wordt gelezen (of anders het eerste tabblad).</div>
|
||
</div>
|
||
<label>Toestel staat nu bij een andere leerling</label>
|
||
<div class="modus-keuze">
|
||
<label><input type="radio" name="modus" value="aanvullen" checked>
|
||
<span>Als fout tonen <small>De rij wordt overgeslagen; de huidige toekenning blijft staan. (Aanbevolen)</small></span></label>
|
||
<label><input type="radio" name="modus" value="overschrijven">
|
||
<span>Vorige toekenning afsluiten <small>Het toestel wordt bij de vorige leerling ingeleverd op de datum toekenning uit Excel. Ook een andere datum inlevering wordt dan overschreven.</small></span></label>
|
||
</div>
|
||
<button type="submit">🔎 Controleren</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
{% else %}
|
||
{% set t = plan.tellers %}
|
||
<div class="card">
|
||
<div class="page-head">
|
||
<h2>🔎 Voorbeeld: {{ bestandsnaam }}</h2>
|
||
<a class="btn btn-light" href="{{ url_for('database_beheer.eigendom') }}">↩️ Ander bestand</a>
|
||
</div>
|
||
<p class="muted" style="margin-top:-8px">Nog niets bewaard. Toestel bij een andere leerling:
|
||
<strong>{{ 'vorige toekenning afsluiten' if plan.modus == 'overschrijven' else 'als fout tonen' }}</strong>.</p>
|
||
{% if onbekend %}
|
||
<div class="alert alert-warning">Deze kolommen worden genegeerd: {{ onbekend|join(', ') }}.</div>
|
||
{% endif %}
|
||
|
||
<div class="tellers" role="group" aria-label="Filter">
|
||
<button type="button" class="teller-knop actief" data-filter="">Alle <span class="n">{{ plan.rijen|length }}</span></button>
|
||
<button type="button" class="teller-knop" data-filter="nieuw"><span class="badge badge-success">➕ Nieuw</span> <span class="n">{{ t.nieuw }}</span></button>
|
||
<button type="button" class="teller-knop" data-filter="bijwerken"><span class="badge badge-info">✏️ Bijwerken</span> <span class="n">{{ t.bijwerken }}</span></button>
|
||
<button type="button" class="teller-knop" data-filter="ongewijzigd"><span class="badge">= Ongewijzigd</span> <span class="n">{{ t.ongewijzigd }}</span></button>
|
||
<button type="button" class="teller-knop" data-filter="fout"><span class="badge badge-danger">⚠️ Fout</span> <span class="n">{{ t.fout }}</span></button>
|
||
</div>
|
||
|
||
<div class="table-wrap">
|
||
<table id="voorbeeld">
|
||
<thead>
|
||
<tr>
|
||
<th>Rij</th><th>Actie</th>
|
||
{% for k in kolommen %}<th>{{ k[1] }}</th>{% endfor %}
|
||
<th>Wat gebeurt er</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for r in plan.rijen %}
|
||
<tr data-actie="{{ r.actie }}" class="{{ 'rij-fout' if r.actie == 'fout' }}">
|
||
<td class="muted">{{ r.rij }}</td>
|
||
<td>
|
||
{% if r.actie == 'nieuw' %}<span class="badge badge-success">➕ Nieuw</span>
|
||
{% elif r.actie == 'bijwerken' %}<span class="badge badge-info">✏️ Bijwerken</span>
|
||
{% elif r.actie == 'ongewijzigd' %}<span class="badge">= Ongewijzigd</span>
|
||
{% else %}<span class="badge badge-danger">⚠️ Fout</span>{% endif %}
|
||
</td>
|
||
{% for k in kolommen %}<td class="{{ 'mono' if k[0] in ('leerling', 'toestel') }}">{{ r.invoer[k[0]] }}</td>{% endfor %}
|
||
<td class="melding">
|
||
{% if r.actie == 'fout' %}{% for m in r.meldingen %}<div class="fout">{{ m }}</div>{% endfor %}
|
||
{% else %}
|
||
{% if r.label %}<div>{{ r.label }}</div>{% endif %}
|
||
{% if r.actie == 'nieuw' %}<div class="wijz">Wordt toegevoegd{{ ' als geschiedenis' if r.invoer.datum_inlevering }}.</div>{% endif %}
|
||
{% for w in r.wijzigingen %}<div class="wijz">{{ w }}</div>{% endfor %}
|
||
{% for m in r.meldingen %}<div>{{ m }}</div>{% endfor %}
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{% set te_doen = t.nieuw + t.bijwerken %}
|
||
<form method="POST" action="{{ url_for('database_beheer.eigendom_importeren') }}" class="bevestig"
|
||
onsubmit="return confirm({{ ('Nu ' ~ t.nieuw ~ ' toekenning(en) toevoegen en ' ~ t.bijwerken ~ ' bijwerken?' ~ (' De ' ~ t.fout ~ ' rij(en) met een fout worden overgeslagen.' if t.fout else ''))|tojson|forceescape }}) && (showLoader('Importeren...'), true)">
|
||
<input type="hidden" name="modus" value="{{ plan.modus }}">
|
||
<input type="hidden" name="rijen" value="{{ rijen_json }}">
|
||
<span class="muted">
|
||
{% if te_doen %}{{ t.nieuw }} nieuw en {{ t.bijwerken }} bij te werken toekenning(en).
|
||
{% if t.fout %}Rijen met een fout worden overgeslagen; verbeter ze in Excel en upload opnieuw.{% endif %}
|
||
{% else %}Er is niets te importeren.{% endif %}
|
||
</span>
|
||
<span style="display:flex;gap:8px">
|
||
<a class="btn btn-light" href="{{ url_for('database_beheer.eigendom') }}">Annuleren</a>
|
||
<button type="submit" class="btn-success" {{ 'disabled' if not te_doen }}>✅ Importeren</button>
|
||
</span>
|
||
</form>
|
||
</div>
|
||
{% endif %}
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script>
|
||
// Voorbeeld filteren op actie (nieuw / bijwerken / ongewijzigd / fout)
|
||
document.querySelectorAll('.teller-knop').forEach(knop => knop.addEventListener('click', () => {
|
||
document.querySelectorAll('.teller-knop').forEach(k => k.classList.toggle('actief', k === knop));
|
||
const f = knop.dataset.filter;
|
||
document.querySelectorAll('#voorbeeld tbody tr').forEach(tr => {
|
||
tr.style.display = !f || tr.dataset.actie === f ? '' : 'none';
|
||
});
|
||
}));
|
||
</script>
|
||
{% endblock %}
|