Bundesland filter & transparency: stringent split + visible source (#8)
Brings the Bundesland-Dropdown from a cosmetic header widget to a real filter that propagates through every layer (Listing, internal search, statistics, party/tag filters, upload mode), and at the same time makes the source parliament visible in every place where assessments from multiple bundesländer can be mixed. Backend - database.get_all_assessments(bundesland=None) — new optional filter, "ALL" treated as None. - database.search_assessments — bug fix: previous `if bundesland:` branch incorrectly added a `WHERE bundesland='ALL'` clause; now guarded with `bundesland and bundesland != "ALL"`. - main.list_assessments — accepts ?bundesland= query param, includes the bundesland field in the response so the frontend can render badges. - main.get_single_assessment — also includes bundesland in the response so the detail header can show the source parlament. - main.search_landtag — early HTTP 400 when bundesland is missing or "ALL"; the live Landtag adapter cannot serve a synthetic Bundesweit request. - main.index() and main.list_bundeslaender — synthetic "🌍 Bundesweit" entry prepended to the bundesländer list (kept out of bundeslaender.py on purpose — ALL is not a real state). Both endpoints additionally expose a parlament_names map so the frontend can render the source parliament without an extra round-trip. Report (PDF + HTML) - generate_html_report / generate_pdf_report — new optional bundesland parameter. When set, the report header carries the parliament name ("Landtag von Sachsen-Anhalt", "Landtag Nordrhein-Westfalen", …) beside the title. Three call sites updated: run_analysis, run_drucksache_analysis, download_assessment_pdf. Frontend (templates/index.html) - Header dropdown gets the synthetic ALL entry as first option; initial currentBundesland is now 'ALL' (was 'NRW'). - localStorage persistence: changeBundesland writes, DOMContentLoaded reads and validates against the visible options. - changeBundesland resets the score / party / tag filter state, syncs the upload-mode bundesland select, disables the Landtag-Suche button + tooltip when ALL, and toggles a data-mode attribute on .list-content (used by CSS to show/hide the per-item bundesland badge). - loadAssessments now sends ?bundesland=… so the API does the actual filtering. updateStats renders an additional per-bundesland average block (Ø NRW: x · Ø LSA: y) when in ALL mode and the loaded list spans more than one bundesland. - renderList prepends a small "bl-badge" beside the Drucksachen-Nummer. Hidden in single-bundesland mode via CSS selector to avoid clutter. - showDetail header now shows the parliament name as its own line (.detail-parlament). - searchLandtag has an early-out alert if currentBundesland === 'ALL', saving a network round-trip. - Upload-Mode bundesland select now starts with a "— Bundesland wählen —" placeholder (no auto-default), and startAnalysis validates that a concrete bundesland was chosen. CSS - .bl-badge plus the .list-content[data-mode="single"] hide rule. - .detail-parlament for the detail header line. - .header-parlament for the PDF report header line. Resolves #8. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
87874a7a14
commit
f1867d463c
@ -200,14 +200,24 @@ async def get_assessment(drucksache: str) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
|
||||
async def get_all_assessments() -> list[dict]:
|
||||
"""Get all assessments from database."""
|
||||
async def get_all_assessments(bundesland: str = None) -> list[dict]:
|
||||
"""Get all assessments from database, optionally filtered by Bundesland.
|
||||
|
||||
The special value ``"ALL"`` and ``None`` mean no filter — both behave
|
||||
identically and return every row. Any other value becomes a strict
|
||||
``WHERE bundesland = ?`` match.
|
||||
"""
|
||||
import json
|
||||
sql = "SELECT * FROM assessments"
|
||||
params: list = []
|
||||
if bundesland and bundesland != "ALL":
|
||||
sql += " WHERE bundesland = ?"
|
||||
params.append(bundesland)
|
||||
sql += " ORDER BY gwoe_score DESC"
|
||||
|
||||
async with aiosqlite.connect(settings.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute(
|
||||
"SELECT * FROM assessments ORDER BY gwoe_score DESC"
|
||||
)
|
||||
cursor = await db.execute(sql, params)
|
||||
rows = await cursor.fetchall()
|
||||
results = []
|
||||
for row in rows:
|
||||
@ -293,7 +303,7 @@ async def search_assessments(query: str, bundesland: str = None, limit: int = 50
|
||||
"""
|
||||
params = [f"%{first_term}%"] * 4
|
||||
|
||||
if bundesland:
|
||||
if bundesland and bundesland != "ALL":
|
||||
sql += " AND bundesland = ?"
|
||||
params.append(bundesland)
|
||||
|
||||
|
||||
87
app/main.py
87
app/main.py
@ -84,13 +84,24 @@ async def startup():
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def index(request: Request):
|
||||
"""Landing page with upload form."""
|
||||
# Frontend-Liste: synthetischer "ALL"-Eintrag (Bundesweit) zuerst, dann
|
||||
# die echten Bundesländer aus der Konfig. Der "ALL"-Code ist eine reine
|
||||
# Frontend/API-Konvention, kein Eintrag in bundeslaender.py.
|
||||
bl_list = [{"code": "ALL", "name": "🌍 Bundesweit", "active": True}]
|
||||
bl_list.extend(
|
||||
{"code": bl.code, "name": bl.name, "active": bl.aktiv}
|
||||
for bl in alle_bundeslaender()
|
||||
)
|
||||
# Map code → parlament_name, damit das Frontend ohne extra Backend-Call
|
||||
# für jeden Antrag den Parlamentsnamen anzeigen kann.
|
||||
parlament_names = {
|
||||
bl.code: bl.parlament_name for bl in alle_bundeslaender()
|
||||
}
|
||||
return templates.TemplateResponse("index.html", {
|
||||
"request": request,
|
||||
"app_name": settings.app_name,
|
||||
"bundeslaender": [
|
||||
{"code": bl.code, "name": bl.name, "active": bl.aktiv}
|
||||
for bl in alle_bundeslaender()
|
||||
],
|
||||
"bundeslaender": bl_list,
|
||||
"parlament_names": parlament_names,
|
||||
})
|
||||
|
||||
|
||||
@ -138,9 +149,9 @@ async def run_analysis(job_id: str, text: str, bundesland: str, model: str):
|
||||
html_path = settings.reports_dir / f"{job_id}.html"
|
||||
pdf_path = settings.reports_dir / f"{job_id}.pdf"
|
||||
|
||||
await generate_html_report(assessment, html_path)
|
||||
await generate_pdf_report(assessment, pdf_path)
|
||||
|
||||
await generate_html_report(assessment, html_path, bundesland=bundesland)
|
||||
await generate_pdf_report(assessment, pdf_path, bundesland=bundesland)
|
||||
|
||||
await update_job(
|
||||
job_id,
|
||||
status="completed",
|
||||
@ -203,10 +214,13 @@ async def get_pdf(job_id: str):
|
||||
|
||||
# API: Load assessments from database
|
||||
@app.get("/api/assessments")
|
||||
async def list_assessments():
|
||||
"""Return all assessments from database."""
|
||||
rows = await get_all_assessments()
|
||||
|
||||
async def list_assessments(bundesland: Optional[str] = None):
|
||||
"""Return assessments from database, optionally filtered by Bundesland.
|
||||
|
||||
``bundesland="ALL"`` and missing parameter both mean "no filter".
|
||||
"""
|
||||
rows = await get_all_assessments(bundesland)
|
||||
|
||||
# Convert DB format to frontend format
|
||||
assessments = []
|
||||
for row in rows:
|
||||
@ -216,6 +230,7 @@ async def list_assessments():
|
||||
"fraktionen": row.get("fraktionen", []),
|
||||
"datum": row.get("datum"),
|
||||
"link": row.get("link"),
|
||||
"bundesland": row.get("bundesland"),
|
||||
"gwoeScore": row.get("gwoe_score"),
|
||||
"gwoeBegründung": row.get("gwoe_begruendung"),
|
||||
"gwoeMatrix": row.get("gwoe_matrix", []),
|
||||
@ -231,7 +246,7 @@ async def list_assessments():
|
||||
"antragZusammenfassung": row.get("antrag_zusammenfassung"),
|
||||
"antragKernpunkte": row.get("antrag_kernpunkte", []),
|
||||
})
|
||||
|
||||
|
||||
return assessments
|
||||
|
||||
|
||||
@ -249,6 +264,7 @@ async def get_single_assessment(drucksache: str):
|
||||
"fraktionen": row.get("fraktionen", []),
|
||||
"datum": row.get("datum"),
|
||||
"link": row.get("link"),
|
||||
"bundesland": row.get("bundesland"),
|
||||
"gwoeScore": row.get("gwoe_score"),
|
||||
"gwoeBegründung": row.get("gwoe_begruendung"),
|
||||
"gwoeMatrix": row.get("gwoe_matrix", []),
|
||||
@ -306,7 +322,11 @@ async def download_assessment_pdf(drucksache: str):
|
||||
|
||||
try:
|
||||
assessment = Assessment(**assessment_data)
|
||||
await generate_pdf_report(assessment, pdf_path)
|
||||
await generate_pdf_report(
|
||||
assessment,
|
||||
pdf_path,
|
||||
bundesland=row.get("bundesland"),
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"PDF-Generierung fehlgeschlagen: {e}")
|
||||
|
||||
@ -356,7 +376,15 @@ async def search_landtag(
|
||||
"""
|
||||
Search external parliament portal (e.g., NRW OPAL).
|
||||
Returns results that can be analyzed with "Jetzt prüfen".
|
||||
|
||||
Requires a concrete Bundesland — the special "ALL" / Bundesweit mode
|
||||
cannot pick a single Landtag adapter and is rejected with HTTP 400.
|
||||
"""
|
||||
if not bundesland or bundesland == "ALL":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Landtag-Suche benötigt ein konkretes Bundesland",
|
||||
)
|
||||
adapter = get_adapter(bundesland)
|
||||
if not adapter:
|
||||
return {"error": f"Bundesland {bundesland} noch nicht unterstützt"}
|
||||
@ -471,10 +499,10 @@ async def run_drucksache_analysis(
|
||||
# Generate reports
|
||||
html_path = settings.reports_dir / f"{job_id}.html"
|
||||
pdf_path = settings.reports_dir / f"{job_id}.pdf"
|
||||
|
||||
await generate_html_report(assessment, html_path)
|
||||
await generate_pdf_report(assessment, pdf_path)
|
||||
|
||||
|
||||
await generate_html_report(assessment, html_path, bundesland=bundesland)
|
||||
await generate_pdf_report(assessment, pdf_path, bundesland=bundesland)
|
||||
|
||||
await update_job(
|
||||
job_id,
|
||||
status="completed",
|
||||
@ -492,11 +520,26 @@ async def run_drucksache_analysis(
|
||||
# API: List available Bundesländer
|
||||
@app.get("/api/bundeslaender")
|
||||
async def list_bundeslaender():
|
||||
"""List available bundesländer with their status."""
|
||||
return [
|
||||
{"code": bl.code, "name": bl.name, "active": bl.aktiv}
|
||||
for bl in alle_bundeslaender()
|
||||
]
|
||||
"""List available bundesländer with their status.
|
||||
|
||||
Includes the synthetic "ALL" / Bundesweit entry as the first item so
|
||||
that the frontend can render it directly. ``parlament_name`` is added
|
||||
so the detail view can show the source parliament without an extra
|
||||
backend round-trip.
|
||||
"""
|
||||
out = [{
|
||||
"code": "ALL",
|
||||
"name": "🌍 Bundesweit",
|
||||
"parlament_name": None,
|
||||
"active": True,
|
||||
}]
|
||||
out.extend({
|
||||
"code": bl.code,
|
||||
"name": bl.name,
|
||||
"parlament_name": bl.parlament_name,
|
||||
"active": bl.aktiv,
|
||||
} for bl in alle_bundeslaender())
|
||||
return out
|
||||
|
||||
|
||||
# === Quellen / Programme ===
|
||||
|
||||
@ -7,6 +7,7 @@ from typing import Optional
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from .models import Assessment, MATRIX_LABELS, EMPFEHLUNG_CONFIG
|
||||
from .bundeslaender import BUNDESLAENDER
|
||||
|
||||
# ECOnGOOD Colors
|
||||
COLORS = {
|
||||
@ -93,10 +94,25 @@ def build_matrix_html(assessment: Assessment) -> str:
|
||||
return '\n'.join(html)
|
||||
|
||||
|
||||
async def generate_html_report(assessment: Assessment, output_path: Path) -> None:
|
||||
"""Generate HTML report."""
|
||||
|
||||
async def generate_html_report(
|
||||
assessment: Assessment,
|
||||
output_path: Path,
|
||||
bundesland: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Generate HTML report.
|
||||
|
||||
``bundesland`` is the optional state code (e.g. ``"NRW"``, ``"LSA"``).
|
||||
When set and known in ``BUNDESLAENDER``, the resulting report carries
|
||||
the parlament name in its header so the source parliament is always
|
||||
visible — important since assessments from multiple bundesländer share
|
||||
the same Drucksachen-ID space.
|
||||
"""
|
||||
|
||||
empf_config = EMPFEHLUNG_CONFIG.get(assessment.empfehlung.value, {})
|
||||
|
||||
parlament_name = ""
|
||||
if bundesland and bundesland in BUNDESLAENDER:
|
||||
parlament_name = BUNDESLAENDER[bundesland].parlament_name
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
@ -141,6 +157,14 @@ async def generate_html_report(assessment: Assessment, output_path: Path) -> Non
|
||||
color: var(--color-blue);
|
||||
margin-bottom: 0.5rem;
|
||||
}}
|
||||
|
||||
.header-parlament {{
|
||||
font-size: 9pt;
|
||||
color: var(--color-blue);
|
||||
font-weight: bold;
|
||||
margin-top: 0.4rem;
|
||||
letter-spacing: 0.3px;
|
||||
}}
|
||||
|
||||
h1 {{
|
||||
color: var(--color-darkgray);
|
||||
@ -327,8 +351,9 @@ async def generate_html_report(assessment: Assessment, output_path: Path) -> Non
|
||||
<div class="header">
|
||||
<div class="header-label">GEMEINWOHL-ÖKONOMIE | ANTRAGSBEWERTUNG</div>
|
||||
<h1>{assessment.title}</h1>
|
||||
{f'<div class="header-parlament">{parlament_name}</div>' if parlament_name else ''}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="meta-box">
|
||||
<strong>Drucksache:</strong> {assessment.drucksache} |
|
||||
<strong>Datum:</strong> {assessment.datum} |
|
||||
@ -414,12 +439,20 @@ async def generate_html_report(assessment: Assessment, output_path: Path) -> Non
|
||||
output_path.write_text(html)
|
||||
|
||||
|
||||
async def generate_pdf_report(assessment: Assessment, output_path: Path) -> None:
|
||||
"""Generate PDF report using WeasyPrint."""
|
||||
async def generate_pdf_report(
|
||||
assessment: Assessment,
|
||||
output_path: Path,
|
||||
bundesland: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Generate PDF report using WeasyPrint.
|
||||
|
||||
``bundesland`` is forwarded to ``generate_html_report`` so the source
|
||||
parlament name appears in the report header.
|
||||
"""
|
||||
# First generate HTML
|
||||
html_path = output_path.with_suffix('.tmp.html')
|
||||
await generate_html_report(assessment, html_path)
|
||||
|
||||
await generate_html_report(assessment, html_path, bundesland=bundesland)
|
||||
|
||||
try:
|
||||
from weasyprint import HTML
|
||||
HTML(filename=str(html_path)).write_pdf(str(output_path))
|
||||
|
||||
@ -170,6 +170,31 @@
|
||||
font-weight: bold;
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
/* Bundesland-Badge: Im Listen-Item links neben der Drucksachen-Nummer.
|
||||
Im Bundesland-spezifischen Modus per data-mode="single" am Container
|
||||
ausgeblendet (redundant, da alle Einträge demselben Land zugehören). */
|
||||
.bl-badge {
|
||||
display: inline-block;
|
||||
padding: 1px 6px;
|
||||
margin-right: 0.4rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--color-blue);
|
||||
border: 1px solid var(--color-blue);
|
||||
border-radius: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.list-content[data-mode="single"] .bl-badge { display: none; }
|
||||
|
||||
/* Detail-Header: Parlament-Name unter dem Titel, vor der Drucksache-Zeile */
|
||||
.detail-parlament {
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-blue);
|
||||
font-weight: 600;
|
||||
margin: 0.2rem 0 0.1rem;
|
||||
}
|
||||
|
||||
.list-item-score {
|
||||
font-size: 0.9rem;
|
||||
@ -698,6 +723,7 @@
|
||||
</div>
|
||||
<div class="stats-bar" style="padding: 0.5rem 1rem; gap: 1rem; flex-wrap: wrap; align-items: center;">
|
||||
<span style="font-size: 0.8rem;"><strong id="stat-total">0</strong> geprüft · <strong id="stat-high">0</strong> vorbildlich · Ø <strong id="stat-avg">0</strong></span>
|
||||
<span id="bundesland-stats" style="font-size: 0.8rem; color: var(--color-darkgray); display: none; gap: 0.6rem; flex-wrap: wrap;"></span>
|
||||
<span style="color: var(--color-lightgray);">|</span>
|
||||
<span id="partei-stats" style="font-size: 0.8rem; display: flex; gap: 0.75rem; flex-wrap: wrap;"></span>
|
||||
</div>
|
||||
@ -770,8 +796,9 @@
|
||||
<div style="margin-top: 1rem;">
|
||||
<label>Bundesland:</label>
|
||||
<select id="bundesland" style="padding: 0.5rem; margin-left: 0.5rem;">
|
||||
{% for bl in bundeslaender %}
|
||||
<option value="{{ bl.code }}" {% if bl.active %}selected{% endif %} {% if not bl.active %}disabled{% endif %}>
|
||||
<option value="" disabled selected>— Bundesland wählen —</option>
|
||||
{% for bl in bundeslaender if bl.code != 'ALL' %}
|
||||
<option value="{{ bl.code }}" {% if not bl.active %}disabled{% endif %}>
|
||||
{{ bl.name }}{% if not bl.active %} (bald){% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
@ -798,14 +825,39 @@
|
||||
let allAssessments = [];
|
||||
let currentScoreFilter = 'all';
|
||||
let currentParteiFilter = '';
|
||||
let currentBundesland = 'NRW';
|
||||
let currentBundesland = 'ALL';
|
||||
let searchTimeout = null;
|
||||
let isSearching = false;
|
||||
let selectedTags = new Set();
|
||||
let allTags = {};
|
||||
|
||||
// Load assessments on page load
|
||||
|
||||
// Map code → parlament_name, vom Backend mit dem Initial-Render geliefert.
|
||||
// Wird im Detail-Header und im Listen-Item-Badge-Tooltip verwendet.
|
||||
const PARLAMENT_NAMES = {{ parlament_names | tojson }};
|
||||
|
||||
// Load assessments on page load — localStorage-Auswahl wiederherstellen
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const saved = localStorage.getItem('selectedBundesland');
|
||||
const select = document.getElementById('bundesland-select');
|
||||
if (saved) {
|
||||
// Validieren: existiert die Option?
|
||||
const exists = Array.from(select.options).some(
|
||||
o => o.value === saved && !o.disabled
|
||||
);
|
||||
if (exists) {
|
||||
currentBundesland = saved;
|
||||
select.value = saved;
|
||||
}
|
||||
}
|
||||
// Modus-Klasse für CSS (Badges aus/an)
|
||||
document.getElementById('list-content').dataset.mode =
|
||||
(currentBundesland === 'ALL') ? 'all' : 'single';
|
||||
// Landtag-Button-State für Initial-Auswahl
|
||||
const btnLandtag = document.getElementById('btn-landtag');
|
||||
if (currentBundesland === 'ALL') {
|
||||
btnLandtag.disabled = true;
|
||||
btnLandtag.title = 'Landtag-Suche nur mit konkretem Bundesland möglich';
|
||||
}
|
||||
loadAssessments();
|
||||
});
|
||||
|
||||
@ -944,14 +996,15 @@
|
||||
|
||||
async function loadAssessments() {
|
||||
try {
|
||||
const resp = await fetch('/api/assessments');
|
||||
const url = `/api/assessments?bundesland=${encodeURIComponent(currentBundesland)}`;
|
||||
const resp = await fetch(url);
|
||||
allAssessments = await resp.json();
|
||||
updateStats();
|
||||
renderList(allAssessments);
|
||||
buildParteienFilter();
|
||||
buildTagCloud();
|
||||
} catch (e) {
|
||||
document.getElementById('list-content').innerHTML =
|
||||
document.getElementById('list-content').innerHTML =
|
||||
'<p style="padding: 1rem; color: #888;">Fehler beim Laden</p>';
|
||||
}
|
||||
}
|
||||
@ -960,11 +1013,40 @@
|
||||
const checked = allAssessments.filter(a => a.status !== 'unchecked').length;
|
||||
const high = allAssessments.filter(a => a.gwoeScore >= 8).length;
|
||||
const avg = checked > 0 ? (allAssessments.filter(a => a.gwoeScore != null).reduce((s, a) => s + (a.gwoeScore || 0), 0) / checked).toFixed(1) : 0;
|
||||
|
||||
|
||||
document.getElementById('stat-total').textContent = checked;
|
||||
document.getElementById('stat-high').textContent = high;
|
||||
document.getElementById('stat-avg').textContent = avg;
|
||||
|
||||
|
||||
// Pro-Bundesland-Aufschlüsselung — nur im Bundesweit-Modus, und nur
|
||||
// wenn tatsächlich mehr als ein Bundesland in der Liste vorkommt.
|
||||
const blContainer = document.getElementById('bundesland-stats');
|
||||
if (currentBundesland === 'ALL') {
|
||||
const blStats = {};
|
||||
allAssessments.forEach(a => {
|
||||
if (a.gwoeScore == null || !a.bundesland) return;
|
||||
if (!blStats[a.bundesland]) blStats[a.bundesland] = { sum: 0, count: 0 };
|
||||
blStats[a.bundesland].sum += a.gwoeScore;
|
||||
blStats[a.bundesland].count += 1;
|
||||
});
|
||||
const codes = Object.keys(blStats);
|
||||
if (codes.length > 1) {
|
||||
const sortedBl = codes
|
||||
.map(c => ({ code: c, avg: blStats[c].sum / blStats[c].count, count: blStats[c].count }))
|
||||
.sort((a, b) => b.avg - a.avg);
|
||||
blContainer.innerHTML = sortedBl.map(b =>
|
||||
`<span title="${PARLAMENT_NAMES[b.code] || b.code}">Ø <strong>${b.code}</strong> ${b.avg.toFixed(1)} <span style="color:#888">(n=${b.count})</span></span>`
|
||||
).join(' · ');
|
||||
blContainer.style.display = 'inline-flex';
|
||||
} else {
|
||||
blContainer.style.display = 'none';
|
||||
blContainer.innerHTML = '';
|
||||
}
|
||||
} else {
|
||||
blContainer.style.display = 'none';
|
||||
blContainer.innerHTML = '';
|
||||
}
|
||||
|
||||
// Partei-Durchschnitte berechnen
|
||||
const parteiStats = {};
|
||||
allAssessments.forEach(a => {
|
||||
@ -975,12 +1057,12 @@
|
||||
parteiStats[f].count += 1;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Sortiert nach Durchschnitt (absteigend)
|
||||
const sorted = Object.entries(parteiStats)
|
||||
.map(([partei, data]) => ({ partei, avg: data.sum / data.count, count: data.count }))
|
||||
.sort((a, b) => b.avg - a.avg);
|
||||
|
||||
|
||||
const container = document.getElementById('partei-stats');
|
||||
container.innerHTML = sorted.map(p => {
|
||||
const color = p.avg >= 7 ? '#889e33' : p.avg >= 4 ? '#fd7e14' : '#dc3545';
|
||||
@ -1006,10 +1088,13 @@
|
||||
const themen = (item.themen || []).slice(0, 3);
|
||||
const scoreText = isUnchecked ? '⏳' : `${item.gwoeScore}/10`;
|
||||
|
||||
const blBadge = item.bundesland
|
||||
? `<span class="bl-badge" title="${PARLAMENT_NAMES[item.bundesland] || item.bundesland}">${item.bundesland}</span>`
|
||||
: '';
|
||||
return `
|
||||
<div class="list-item ${isUnchecked ? 'unchecked' : ''}" data-drucksache="${item.drucksache}" onclick="${isUnchecked ? '' : `showDetail('${item.drucksache}')`}">
|
||||
<div class="list-item-header">
|
||||
<span class="list-item-id">${item.drucksache}</span>
|
||||
<span class="list-item-id">${blBadge}${item.drucksache}</span>
|
||||
<span class="list-item-score ${scoreClass}">${scoreText}</span>
|
||||
</div>
|
||||
<div class="list-item-title">${item.title || 'Ohne Titel'}</div>
|
||||
@ -1133,11 +1218,53 @@
|
||||
|
||||
function changeBundesland(code) {
|
||||
currentBundesland = code;
|
||||
localStorage.setItem('selectedBundesland', code);
|
||||
|
||||
// Filter zurücksetzen — Parteien & Tags pro Bundesland unterschiedlich,
|
||||
// ein "LINKE"-Filter aus LSA würde in NRW eine leere Liste zeigen.
|
||||
currentScoreFilter = 'all';
|
||||
currentParteiFilter = '';
|
||||
selectedTags.clear();
|
||||
document.getElementById('search-input').value = '';
|
||||
document.querySelectorAll('.filter-btn').forEach(b => {
|
||||
b.classList.toggle('active', b.dataset.filter === 'all');
|
||||
});
|
||||
const parteiSelect = document.getElementById('partei-filter');
|
||||
if (parteiSelect) parteiSelect.value = '';
|
||||
|
||||
// Upload-Mode-Dropdown synchronisieren. Bei "ALL" KEIN automatischer
|
||||
// Default — der User soll im Upload-Form bewusst ein Bundesland wählen.
|
||||
const uploadDropdown = document.getElementById('bundesland');
|
||||
if (uploadDropdown) {
|
||||
if (code === 'ALL') {
|
||||
uploadDropdown.value = '';
|
||||
} else {
|
||||
uploadDropdown.value = code;
|
||||
}
|
||||
}
|
||||
|
||||
// Landtag-Suche-Button im Bundesweit-Modus deaktivieren
|
||||
const btnLandtag = document.getElementById('btn-landtag');
|
||||
if (code === 'ALL') {
|
||||
btnLandtag.disabled = true;
|
||||
btnLandtag.title = 'Landtag-Suche nur mit konkretem Bundesland möglich';
|
||||
} else {
|
||||
btnLandtag.disabled = false;
|
||||
btnLandtag.title = '';
|
||||
}
|
||||
|
||||
// Modus-Klasse für CSS (Badges aus/an im Single-Modus)
|
||||
document.getElementById('list-content').dataset.mode =
|
||||
(code === 'ALL') ? 'all' : 'single';
|
||||
|
||||
loadAssessments();
|
||||
}
|
||||
|
||||
async function searchLandtag() {
|
||||
if (currentBundesland === 'ALL') {
|
||||
alert('Landtag-Suche ist nur mit Auswahl eines konkreten Bundeslands möglich.\nBitte oben ein Bundesland auswählen.');
|
||||
return;
|
||||
}
|
||||
const query = document.getElementById('search-input').value.trim();
|
||||
if (query.length < 2) {
|
||||
alert('Bitte mindestens 2 Zeichen eingeben');
|
||||
@ -1378,6 +1505,7 @@
|
||||
<div class="detail-header">
|
||||
<div>
|
||||
<div class="detail-title">${item.title || 'Ohne Titel'}</div>
|
||||
${item.bundesland && PARLAMENT_NAMES[item.bundesland] ? `<div class="detail-parlament">${PARLAMENT_NAMES[item.bundesland]}</div>` : ''}
|
||||
<div class="detail-id">${item.drucksache} · ${(item.fraktionen || []).join(', ')} · ${item.datum || ''}</div>
|
||||
</div>
|
||||
<div class="score-display">
|
||||
@ -1497,11 +1625,15 @@
|
||||
const text = document.getElementById('antrag-text').value;
|
||||
const file = document.getElementById('file-input').files[0];
|
||||
const bundesland = document.getElementById('bundesland').value;
|
||||
|
||||
|
||||
if (!text && !file) {
|
||||
alert('Bitte Text eingeben oder PDF hochladen');
|
||||
return;
|
||||
}
|
||||
if (!bundesland) {
|
||||
alert('Bitte ein Bundesland wählen.');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
statusDiv.style.display = 'block';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user