PIVOT 1.1.0
This commit is contained in:
commit
8a53234eb6
161 files changed
+31109
No files matched your search
@@ -0,0 +1,161 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Toestellen importeren{% endblock %}
|
||||
{% block page_title %}Database › Toestellen{% 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: #fff; 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 toestel per rij. De kolom <em>Lokaal</em> heeft een keuzelijst met
|
||||
de lokalen van {{ school.naam if school else 'deze school' }}.</p>
|
||||
<a class="btn" href="{{ url_for('database_beheer.toestellen_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 staan nu <strong>{{ aantal }}</strong> toestellen in de database.{% endif %}</p>
|
||||
<form method="POST" action="{{ url_for('database_beheer.toestellen_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>Toestellen</em> wordt gelezen (of anders het eerste tabblad).</div>
|
||||
</div>
|
||||
<label>Toestellen die al bestaan</label>
|
||||
<div class="modus-keuze">
|
||||
<label><input type="radio" name="modus" value="aanvullen" checked>
|
||||
<span>Enkel lege velden aanvullen <small>Wat al ingevuld is in PIVOT blijft staan. (Aanbevolen)</small></span></label>
|
||||
<label><input type="radio" name="modus" value="overschrijven">
|
||||
<span>Ingevulde cellen overschrijven <small>Wat in Excel staat, vervangt de waarde in PIVOT (ook de status). Lege cellen wissen niets.</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.toestellen') }}">↩️ Ander bestand</a>
|
||||
</div>
|
||||
<p class="muted" style="margin-top:-8px">Nog niets bewaard. Bestaande toestellen:
|
||||
<strong>{{ 'ingevulde cellen overschrijven' if plan.modus == 'overschrijven' else 'enkel lege velden aanvullen' }}</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 ('schooleigen_id', 'serienummer') }}">{{ 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.actie == 'nieuw' %}<div class="wijz">Wordt toegevoegd{% if not r.invoer.status %} (status Beschikbaar){% endif %}.</div>{% endif %}
|
||||
{% if r.label %}<div>Toestel {{ r.label }}</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.toestellen_importeren') }}" class="bevestig"
|
||||
onsubmit="return confirm({{ ('Nu ' ~ t.nieuw ~ ' toestel(len) 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 toestel(len).
|
||||
{% 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.toestellen') }}">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 %}
|
||||
Reference in new issue
Block a user