Compare commits
2 Commits
8e19f6cffa
...
13714410ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13714410ab | ||
|
|
cf313bd257 |
@ -740,6 +740,9 @@
|
|||||||
<a href="/quellen">📚 Quellen</a>
|
<a href="/quellen">📚 Quellen</a>
|
||||||
<a href="/methodik">🔍 Methodik</a>
|
<a href="/methodik">🔍 Methodik</a>
|
||||||
<hr style="margin:0.3rem 0;border:none;border-top:1px solid #eee;">
|
<hr style="margin:0.3rem 0;border:none;border-top:1px solid #eee;">
|
||||||
|
<button onclick="event.stopPropagation();document.getElementById('queue-panel').style.display='block';document.getElementById('hamburger-menu').classList.remove('open');loadQueuePanel();">📊 Queue</button>
|
||||||
|
<button onclick="event.stopPropagation();document.getElementById('batch-panel').style.display='block';document.getElementById('hamburger-menu').classList.remove('open');">📦 Batch-Analyse</button>
|
||||||
|
<hr style="margin:0.3rem 0;border:none;border-top:1px solid #eee;">
|
||||||
<button id="auth-btn" onclick="event.stopPropagation();">🔑 Anmelden</button>
|
<button id="auth-btn" onclick="event.stopPropagation();">🔑 Anmelden</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -781,6 +784,14 @@
|
|||||||
<select id="partei-filter" onchange="setParteiFilter(this.value)" style="padding: 0.25rem 0.5rem; border-radius: 20px; border: 1px solid var(--color-lightgray); font-size: 0.8rem; cursor: pointer;">
|
<select id="partei-filter" onchange="setParteiFilter(this.value)" style="padding: 0.25rem 0.5rem; border-radius: 20px; border: 1px solid var(--color-lightgray); font-size: 0.8rem; cursor: pointer;">
|
||||||
<option value="">Alle Parteien</option>
|
<option value="">Alle Parteien</option>
|
||||||
</select>
|
</select>
|
||||||
|
<select id="sort-select" onchange="setSortOrder(this.value)" style="padding: 0.25rem 0.5rem; border-radius: 20px; border: 1px solid var(--color-lightgray); font-size: 0.8rem; cursor: pointer;">
|
||||||
|
<option value="score-desc">↓ GWÖ-Score</option>
|
||||||
|
<option value="score-asc">↑ GWÖ-Score</option>
|
||||||
|
<option value="date-desc">↓ Datum</option>
|
||||||
|
<option value="date-asc">↑ Datum</option>
|
||||||
|
<option value="nr-desc">↓ Nummer</option>
|
||||||
|
<option value="title-asc">A-Z Titel</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-bar" style="padding: 0.5rem 1rem; gap: 1rem; flex-wrap: wrap; align-items: center;">
|
<div class="stats-bar" style="padding: 0.5rem 1rem; gap: 1rem; flex-wrap: wrap; align-items: center;">
|
||||||
@ -930,6 +941,7 @@
|
|||||||
let selectedTags = new Set();
|
let selectedTags = new Set();
|
||||||
let allTags = {};
|
let allTags = {};
|
||||||
let currentUser = null; // #43: Auth-State
|
let currentUser = null; // #43: Auth-State
|
||||||
|
let currentSort = localStorage.getItem('sortOrder') || 'score-desc';
|
||||||
|
|
||||||
// #43: Auth prüfen beim Load. Steuert ob "Jetzt prüfen" aktiv ist.
|
// #43: Auth prüfen beim Load. Steuert ob "Jetzt prüfen" aktiv ist.
|
||||||
async function initAuth() {
|
async function initAuth() {
|
||||||
@ -955,7 +967,7 @@
|
|||||||
loadAssessments(); // Liste neu rendern (Buttons deaktivieren)
|
loadAssessments(); // Liste neu rendern (Buttons deaktivieren)
|
||||||
};
|
};
|
||||||
// Bestehende Liste neu rendern damit Buttons aktiv werden
|
// Bestehende Liste neu rendern damit Buttons aktiv werden
|
||||||
if (allAssessments.length > 0) renderList(allAssessments);
|
if (allAssessments.length > 0) renderList(sortAssessments(allAssessments));
|
||||||
} else {
|
} else {
|
||||||
authBtn.textContent = '🔑 Anmelden';
|
authBtn.textContent = '🔑 Anmelden';
|
||||||
authBtn.style.color = '';
|
authBtn.style.color = '';
|
||||||
@ -968,6 +980,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sortierung (#100)
|
||||||
|
function setSortOrder(order) {
|
||||||
|
currentSort = order;
|
||||||
|
localStorage.setItem('sortOrder', order);
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
function sortAssessments(items) {
|
||||||
|
const sorted = [...items];
|
||||||
|
switch (currentSort) {
|
||||||
|
case 'score-desc': return sorted.sort((a, b) => (b.gwoeScore || 0) - (a.gwoeScore || 0));
|
||||||
|
case 'score-asc': return sorted.sort((a, b) => (a.gwoeScore || 0) - (b.gwoeScore || 0));
|
||||||
|
case 'date-desc': return sorted.sort((a, b) => (b.datum || '').localeCompare(a.datum || ''));
|
||||||
|
case 'date-asc': return sorted.sort((a, b) => (a.datum || '').localeCompare(b.datum || ''));
|
||||||
|
case 'nr-desc': return sorted.sort((a, b) => {
|
||||||
|
const na = parseInt((a.drucksache || '').split('/')[1]) || 0;
|
||||||
|
const nb = parseInt((b.drucksache || '').split('/')[1]) || 0;
|
||||||
|
return nb - na;
|
||||||
|
});
|
||||||
|
case 'title-asc': return sorted.sort((a, b) => (a.title || '').localeCompare(b.title || '', 'de'));
|
||||||
|
default: return sorted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hamburger-Menü schließen bei Klick außerhalb
|
// Hamburger-Menü schließen bei Klick außerhalb
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
const menu = document.getElementById('hamburger-menu');
|
const menu = document.getElementById('hamburger-menu');
|
||||||
@ -992,6 +1027,12 @@
|
|||||||
// Load assessments on page load — localStorage-Auswahl wiederherstellen
|
// Load assessments on page load — localStorage-Auswahl wiederherstellen
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initAuth(); // #43: Auth-State prüfen
|
initAuth(); // #43: Auth-State prüfen
|
||||||
|
// Sort-Auswahl aus localStorage wiederherstellen
|
||||||
|
const savedSort = localStorage.getItem('sortOrder');
|
||||||
|
if (savedSort) {
|
||||||
|
document.getElementById('sort-select').value = savedSort;
|
||||||
|
currentSort = savedSort;
|
||||||
|
}
|
||||||
const saved = localStorage.getItem('selectedBundesland');
|
const saved = localStorage.getItem('selectedBundesland');
|
||||||
const select = document.getElementById('bundesland-select');
|
const select = document.getElementById('bundesland-select');
|
||||||
if (saved) {
|
if (saved) {
|
||||||
@ -1155,7 +1196,7 @@
|
|||||||
const resp = await fetch(url);
|
const resp = await fetch(url);
|
||||||
allAssessments = await resp.json();
|
allAssessments = await resp.json();
|
||||||
updateStats();
|
updateStats();
|
||||||
renderList(allAssessments);
|
renderList(sortAssessments(allAssessments));
|
||||||
buildParteienFilter();
|
buildParteienFilter();
|
||||||
buildTagCloud();
|
buildTagCloud();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1749,7 +1790,7 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList(filtered);
|
renderList(sortAssessments(filtered));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setScoreFilter(filter, btn) {
|
function setScoreFilter(filter, btn) {
|
||||||
@ -1779,7 +1820,7 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList(filtered);
|
renderList(sortAssessments(filtered));
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyScoreFilter(items, filter) {
|
function applyScoreFilter(items, filter) {
|
||||||
@ -2229,5 +2270,96 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<!-- Queue-Panel Overlay -->
|
||||||
|
<div id="queue-panel" style="display:none;position:fixed;top:0;right:0;width:400px;height:100vh;background:white;box-shadow:-4px 0 12px rgba(0,0,0,0.15);z-index:200;overflow-y:auto;padding:1.5rem;">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
|
||||||
|
<h3 style="color:var(--color-blue);margin:0;">📊 Queue</h3>
|
||||||
|
<button onclick="document.getElementById('queue-panel').style.display='none'" style="background:none;border:none;font-size:1.2rem;cursor:pointer;">✕</button>
|
||||||
|
</div>
|
||||||
|
<div id="queue-panel-content"><span style="color:#aaa;">Lade...</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Batch-Panel Overlay -->
|
||||||
|
<div id="batch-panel" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:450px;background:white;box-shadow:0 8px 24px rgba(0,0,0,0.2);z-index:200;border-radius:8px;padding:1.5rem;">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
|
||||||
|
<h3 style="color:var(--color-blue);margin:0;">📦 Batch-Analyse</h3>
|
||||||
|
<button onclick="document.getElementById('batch-panel').style.display='none'" style="background:none;border:none;font-size:1.2rem;cursor:pointer;">✕</button>
|
||||||
|
</div>
|
||||||
|
<p style="font-size:0.85rem;color:#666;margin-bottom:1rem;">Analysiert automatisch die neuesten ungeprüften Anträge.</p>
|
||||||
|
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;">
|
||||||
|
<select id="batch-bl-modal" style="padding:0.4rem;border:1px solid #ddd;border-radius:4px;">
|
||||||
|
{% for bl in bundeslaender if bl.code != 'ALL' and bl.active %}
|
||||||
|
<option value="{{ bl.code }}">{{ bl.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<select id="batch-limit-modal" style="padding:0.4rem;border:1px solid #ddd;border-radius:4px;">
|
||||||
|
<option value="5">5</option>
|
||||||
|
<option value="10" selected>10</option>
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
</select>
|
||||||
|
<button onclick="startBatchModal()" style="padding:0.4rem 1rem;background:var(--color-green);color:white;border:none;border-radius:4px;cursor:pointer;">🚀 Starten</button>
|
||||||
|
</div>
|
||||||
|
<div id="batch-modal-status" style="margin-top:0.75rem;font-size:0.85rem;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function loadQueuePanel() {
|
||||||
|
const el = document.getElementById('queue-panel-content');
|
||||||
|
async function refresh() {
|
||||||
|
try {
|
||||||
|
const qs = await fetch('/api/queue/status').then(r => r.json());
|
||||||
|
const jobs = qs.jobs || [];
|
||||||
|
if (jobs.length === 0 && qs.pending === 0) {
|
||||||
|
el.innerHTML = '<p style="color:#888;">Keine Aufträge in der Warteschlange.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const completed = jobs.filter(j => j.status === 'completed').length;
|
||||||
|
const pct = jobs.length > 0 ? Math.round(completed / jobs.length * 100) : 0;
|
||||||
|
el.innerHTML = `
|
||||||
|
<div style="margin-bottom:0.5rem;font-size:0.85rem;color:#666;">
|
||||||
|
${qs.concurrency} Worker · ${qs.pending} wartend · ${qs.processed_total} verarbeitet
|
||||||
|
${qs.shutting_down ? '<br><span style="color:#dc3545;">⚠ Server wird heruntergefahren</span>' : ''}
|
||||||
|
</div>
|
||||||
|
<div style="background:#eee;border-radius:4px;height:6px;margin-bottom:0.75rem;">
|
||||||
|
<div style="background:var(--color-green);height:100%;width:${pct}%;transition:width 0.5s;border-radius:4px;"></div>
|
||||||
|
</div>
|
||||||
|
<table style="width:100%;font-size:0.8rem;border-collapse:collapse;">
|
||||||
|
${jobs.map(j => {
|
||||||
|
const icon = j.status === 'completed' ? '✅' : j.status === 'processing' ? '⏳' : j.status === 'failed' ? '❌' : '⏸';
|
||||||
|
return '<tr style="border-top:1px solid #f0f0f0;"><td>' + (j.drucksache || j.job_id.substring(0,8)) + '</td><td>' + icon + '</td><td>' + (j.duration ? j.duration + 's' : '') + '</td></tr>';
|
||||||
|
}).join('')}
|
||||||
|
</table>`;
|
||||||
|
} catch { el.innerHTML = '<span style="color:#c00;">Fehler</span>'; }
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
// Auto-refresh solange Panel offen
|
||||||
|
const iv = setInterval(() => {
|
||||||
|
if (document.getElementById('queue-panel').style.display === 'none') { clearInterval(iv); return; }
|
||||||
|
refresh();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startBatchModal() {
|
||||||
|
const bl = document.getElementById('batch-bl-modal').value;
|
||||||
|
const limit = document.getElementById('batch-limit-modal').value;
|
||||||
|
const status = document.getElementById('batch-modal-status');
|
||||||
|
status.innerHTML = '<span style="color:var(--color-blue);">⏳ Wird gestartet...</span>';
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/api/batch-analyze', {
|
||||||
|
method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||||
|
body: 'bundesland=' + bl + '&limit=' + limit
|
||||||
|
});
|
||||||
|
if (resp.status === 401 || resp.status === 403) {
|
||||||
|
status.innerHTML = '<span style="color:#dc3545;">🔒 Admin-Berechtigung erforderlich.</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = await resp.json();
|
||||||
|
status.innerHTML = '<span style="color:var(--color-green);">✓ ' + (data.enqueued || 0) + ' Anträge eingereiht. Queue-Panel öffnen um Fortschritt zu sehen.</span>';
|
||||||
|
} catch (e) {
|
||||||
|
status.innerHTML = '<span style="color:#dc3545;">❌ ' + e.message + '</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user