gwoe-antragspruefer/app/templates/v2/screens/admin_wahlprogramme.html

123 lines
4.2 KiB
HTML
Raw Normal View History

{% extends "v2/base.html" %}
{% block title %}Wahlprogramme — GWÖ-Antragsprüfer{% endblock %}
{% set v2_active_nav = "admin_wahlprogramme" %}
{% block main %}
<div style="padding:0 0 1.5rem;">
<h1 style="font-family:var(--font-display);font-size:22px;color:var(--ecg-teal);margin:0 0 4px;">
Wahlprogramm-Beschaffung
</h1>
<p style="font-size:12px;font-family:var(--font-mono);color:var(--ecg-dark);opacity:0.6;">
Halbautomatisch (#138) · nur Lücken mit Kandidaten-URL aus wahlprogramm-links.yaml
</p>
</div>
<div id="flash" class="v2-kasten" style="display:none;margin-bottom:16px;"></div>
{% if not missing %}
<div class="v2-kasten" style="opacity:0.7;">
<p style="font-family:var(--font-mono);font-size:13px;">Keine Lücken gefunden — alle registrierten Einträge haben eine Datei.</p>
</div>
{% else %}
<table style="width:100%;border-collapse:collapse;font-size:13px;font-family:var(--font-mono);">
<thead>
<tr style="border-bottom:1px solid var(--ecg-border, rgba(255,255,255,0.1));opacity:0.5;">
<th style="text-align:left;padding:8px 12px;">BL</th>
<th style="text-align:left;padding:8px 12px;">Partei</th>
<th style="text-align:left;padding:8px 12px;">Dateiname</th>
<th style="text-align:left;padding:8px 12px;">Kandidat-URL</th>
<th style="padding:8px 12px;"></th>
</tr>
</thead>
<tbody>
{% for entry in missing %}
{% set first_url = entry.kandidaten[0].url if entry.kandidaten else "" %}
<tr style="border-bottom:1px solid rgba(255,255,255,0.06);" data-bl="{{ entry.bl }}" data-partei="{{ entry.partei }}">
<td style="padding:10px 12px;font-weight:700;color:var(--ecg-teal);">{{ entry.bl }}</td>
<td style="padding:10px 12px;">{{ entry.partei }}</td>
<td style="padding:10px 12px;opacity:0.6;">
{{ entry.dateiname or "— noch nicht registriert —" }}
</td>
<td style="padding:10px 12px;max-width:380px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
<input type="text"
class="url-input"
value="{{ first_url }}"
style="width:100%;background:rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.12);
border-radius:4px;padding:4px 8px;color:inherit;font-family:inherit;font-size:12px;"
>
</td>
<td style="padding:10px 12px;">
<button class="fetch-btn v2-btn"
style="font-size:11px;padding:4px 14px;cursor:pointer;"
onclick="fetchProgramm(this)">
Laden
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<p style="margin-top:24px;font-size:11px;font-family:var(--font-mono);opacity:0.4;">
Nach einem erfolgreichen Download müssen die Embeddings neu indexiert werden:
<code>python -m app.reindex_embeddings</code>
</p>
<script>
async function fetchProgramm(btn) {
const row = btn.closest("tr");
const bl = row.dataset.bl;
const partei = row.dataset.partei;
const url = row.querySelector(".url-input").value.trim();
const flash = document.getElementById("flash");
if (!url) {
showFlash("Keine URL eingetragen.", "error");
return;
}
btn.disabled = true;
btn.textContent = "…";
try {
const resp = await fetch("/api/admin/wahlprogramm-fetch", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({bl, partei, url}),
});
const data = await resp.json();
if (resp.ok) {
const note = data.changed ? "gespeichert" : "unverändert";
showFlash(
`${bl}/${partei}: ${note} — SHA ${data.sha256.slice(0,12)}…`,
"ok",
);
if (data.changed) row.style.opacity = "0.4";
} else {
showFlash(`Fehler: ${data.detail || resp.status}`, "error");
}
} catch (err) {
showFlash(`Netzwerkfehler: ${err}`, "error");
} finally {
btn.disabled = false;
btn.textContent = "Laden";
}
}
function showFlash(msg, type) {
const el = document.getElementById("flash");
el.textContent = msg;
el.style.display = "block";
el.style.borderColor = type === "ok"
? "var(--ecg-green, #2d9e5f)"
: "var(--redline-contra, #e05252)";
clearTimeout(el._t);
el._t = setTimeout(() => { el.style.display = "none"; }, 8000);
}
</script>
{% endblock %}