791 lines
37 KiB
HTML
791 lines
37 KiB
HTML
|
|
{% extends "v2/base.html" %}
|
|||
|
|
|
|||
|
|
{% from "v2/components/score_hero.html" import score_hero %}
|
|||
|
|
{% from "v2/components/matrix_mini.html" import matrix_mini %}
|
|||
|
|
{% from "v2/components/quote_card.html" import quote_card %}
|
|||
|
|
{% from "v2/components/kasten.html" import kasten %}
|
|||
|
|
{% from "v2/components/redline.html" import redline %}
|
|||
|
|
|
|||
|
|
{% block title %}
|
|||
|
|
{% if antrag is defined and antrag %}{{ antrag.title }} — {% endif %}GWÖ-Antragsprüfer
|
|||
|
|
{% endblock %}
|
|||
|
|
|
|||
|
|
{% block head_extra %}
|
|||
|
|
{% if antrag is defined and antrag %}
|
|||
|
|
{# ── Open-Graph / Twitter-Card-Meta (#141) ────────────────────────── #}
|
|||
|
|
{% set _og_img = "/api/og/" ~ (antrag.drucksache | urlencode) ~ ".png" %}
|
|||
|
|
<meta property="og:type" content="article">
|
|||
|
|
<meta property="og:title" content="{{ antrag.title }} — GWÖ-Antragsprüfer">
|
|||
|
|
<meta property="og:description" content="GWÖ-Score {{ '%.1f'|format(antrag.score|float) }}/10 — {{ antrag.zusammenfassung | truncate(160, True) }}">
|
|||
|
|
<meta property="og:image" content="{{ _og_img }}">
|
|||
|
|
<meta property="og:image:width" content="1200">
|
|||
|
|
<meta property="og:image:height" content="630">
|
|||
|
|
{% if antrag.updated_at_raw %}
|
|||
|
|
<meta property="og:updated_time" content="{{ antrag.updated_at_raw }}">
|
|||
|
|
{% endif %}
|
|||
|
|
<meta name="twitter:card" content="summary_large_image">
|
|||
|
|
<meta name="twitter:title" content="{{ antrag.title }} — GWÖ-Antragsprüfer">
|
|||
|
|
<meta name="twitter:description" content="GWÖ-Score {{ '%.1f'|format(antrag.score|float) }}/10 — {{ antrag.zusammenfassung | truncate(160, True) }}">
|
|||
|
|
<meta name="twitter:image" content="{{ _og_img }}">
|
|||
|
|
{% endif %}
|
|||
|
|
{% endblock %}
|
|||
|
|
|
|||
|
|
{% set v2_active_nav = "durchsuchen" %}
|
|||
|
|
|
|||
|
|
{% block main %}
|
|||
|
|
|
|||
|
|
{# ── Fehlerfall ──────────────────────────────────────────────────── #}
|
|||
|
|
{% if error is defined and error %}
|
|||
|
|
<div class="v2-kasten" style="border-color:var(--redline-contra);margin-top:32px;">
|
|||
|
|
<h3 style="color:var(--redline-contra);">Antrag nicht gefunden</h3>
|
|||
|
|
<p>{{ error }}</p>
|
|||
|
|
<p><a href="/">← Zurück zur Übersicht</a></p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{% elif antrag is not defined or not antrag %}
|
|||
|
|
{# ── Demo-Daten wenn kein echtes Antrag-Objekt übergeben ────────── #}
|
|||
|
|
{% set antrag = {
|
|||
|
|
"drucksache": "18/4412",
|
|||
|
|
"bundesland": "BW",
|
|||
|
|
"parlament": "Landtag",
|
|||
|
|
"typ": "Antrag",
|
|||
|
|
"datum": "12.04.2026",
|
|||
|
|
"analysiert": "14.04.2026",
|
|||
|
|
"modell": "qwen-plus",
|
|||
|
|
"parteien": ["GRÜNE", "SPD"],
|
|||
|
|
"zitate_count": 3,
|
|||
|
|
"title": "Kommunale Wärmeplanung bis 2028 verpflichtend machen",
|
|||
|
|
"score": 9.1,
|
|||
|
|
"verdict_title": "Vorbildlich",
|
|||
|
|
"verdict_body": "Starker Beitrag zur ökologischen Nachhaltigkeit und Transparenz auf kommunaler Ebene.",
|
|||
|
|
"zusammenfassung": "Der Antrag verpflichtet Kommunen ab 10 000 Einwohner:innen zur Erstellung einer kommunalen Wärmeplanung bis Ende 2028.",
|
|||
|
|
"staerkster_wert": {
|
|||
|
|
"titel": "Ökologische Nachhaltigkeit",
|
|||
|
|
"text": "Verpflichtende Wärmeplanung führt zu messbaren Klimazielen. E3 = ++, D3 = ++."
|
|||
|
|
},
|
|||
|
|
"schwaechster_wert": {
|
|||
|
|
"titel": "Soziale Gerechtigkeit",
|
|||
|
|
"text": "Kostenverteilung auf Mieter:innen versus Eigentümer:innen ist im Antrag nicht geregelt."
|
|||
|
|
},
|
|||
|
|
"redline": {
|
|||
|
|
"segments": [
|
|||
|
|
{"type": "ctx", "text": "§ 3 Abs. 2 "},
|
|||
|
|
{"type": "del", "text": "auf Antrag"},
|
|||
|
|
{"type": "ins", "text": "verpflichtend"},
|
|||
|
|
{"type": "ctx", "text": " eine sozialverträgliche Umlage"}
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"matrix": {
|
|||
|
|
"A1": {"rating": 0, "symbol": "○"}, "A2": {"rating": 1, "symbol": "+"},
|
|||
|
|
"A3": {"rating": 2, "symbol": "++"}, "A4": {"rating": 0, "symbol": "○"},
|
|||
|
|
"A5": {"rating": 1, "symbol": "+"},
|
|||
|
|
"B1": {"rating": 0, "symbol": "○"}, "B2": {"rating": 1, "symbol": "+"},
|
|||
|
|
"B3": {"rating": 2, "symbol": "++"}, "B4": {"rating": -1, "symbol": "−"},
|
|||
|
|
"B5": {"rating": 1, "symbol": "+"},
|
|||
|
|
"C1": {"rating": 0, "symbol": "○"}, "C2": {"rating": 1, "symbol": "+"},
|
|||
|
|
"C3": {"rating": 1, "symbol": "+"}, "C4": {"rating": 0, "symbol": "○"},
|
|||
|
|
"C5": {"rating": 2, "symbol": "++"},
|
|||
|
|
"D1": {"rating": 1, "symbol": "+"}, "D2": {"rating": 1, "symbol": "+"},
|
|||
|
|
"D3": {"rating": 2, "symbol": "++"}, "D4": {"rating": 1, "symbol": "+"},
|
|||
|
|
"D5": {"rating": 2, "symbol": "++"},
|
|||
|
|
"E1": {"rating": 1, "symbol": "+"}, "E2": {"rating": 2, "symbol": "++"},
|
|||
|
|
"E3": {"rating": 2, "symbol": "++"}, "E4": {"rating": 1, "symbol": "+"},
|
|||
|
|
"E5": {"rating": 1, "symbol": "+"}
|
|||
|
|
},
|
|||
|
|
"zitate": [
|
|||
|
|
{
|
|||
|
|
"text": "Wir verpflichten alle Kommunen zu einer verbindlichen kommunalen Wärmeplanung bis 2028.",
|
|||
|
|
"source": "Wahlprogramm GRÜNE 2022 · S. 84",
|
|||
|
|
"partei": "GRÜNE",
|
|||
|
|
"verified": True,
|
|||
|
|
"contra": False,
|
|||
|
|
"pdf_href": "/api/wahlprogramm-cite?pid=gruene-nrw-2022&seite=84&q=Wärmeplanung"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"verbesserungen": [],
|
|||
|
|
"staerken": [],
|
|||
|
|
"schwaechen": []
|
|||
|
|
} %}
|
|||
|
|
|
|||
|
|
{# Fallthrough: Demo-Daten rendern wie echte Daten #}
|
|||
|
|
{% set _render = True %}
|
|||
|
|
{% else %}
|
|||
|
|
{% set _render = True %}
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
{# ── Eigentlicher Detail-Inhalt ──────────────────────────────────── #}
|
|||
|
|
{% if _render is defined and _render and antrag is defined and antrag %}
|
|||
|
|
|
|||
|
|
{# ── Zurück-Link ─────────────────────────────────────────────────── #}
|
|||
|
|
<p style="font-family:var(--font-mono);font-size:11px;margin-bottom:20px;">
|
|||
|
|
<a href="/">← Zurück zur Übersicht</a>
|
|||
|
|
</p>
|
|||
|
|
|
|||
|
|
{# ── Split-Layout ────────────────────────────────────────────────── #}
|
|||
|
|
<div class="v2-detail">
|
|||
|
|
|
|||
|
|
{# ── Linke Spalte: Redaktionelle Analyse ── #}
|
|||
|
|
<div class="left">
|
|||
|
|
<div class="v2-antrag-id">
|
|||
|
|
{{ antrag.bundesland | default("") }}
|
|||
|
|
{% if antrag.drucksache %} · Drs. {{ antrag.drucksache }}{% endif %}
|
|||
|
|
{% if antrag.typ %} · {{ antrag.typ }}{% endif %}
|
|||
|
|
{% if antrag.datum %} · eingebracht {{ antrag.datum }}{% endif %}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h1 class="v2-big-title">{{ antrag.title | default("Antrag") }}</h1>
|
|||
|
|
|
|||
|
|
{% if antrag.parteien or antrag.analysiert %}
|
|||
|
|
<div class="v2-byline">
|
|||
|
|
{% if antrag.parteien %}Eingebracht von {{ antrag.parteien | join(", ") }}{% endif %}
|
|||
|
|
{% if antrag.analysiert %} — Analyse {{ antrag.analysiert }}{% endif %}
|
|||
|
|
{% if antrag.modell %}, {{ antrag.modell }}{% endif %}
|
|||
|
|
{% if antrag.zitate_count %} · {{ antrag.zitate_count }} Zitat{{ "e" if antrag.zitate_count != 1 else "" }} verifiziert{% endif %}
|
|||
|
|
</div>
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
{% if antrag.zusammenfassung %}
|
|||
|
|
<h3 class="v2-h3">Zusammenfassung</h3>
|
|||
|
|
<p style="font-size:14.5px;line-height:1.55">{{ antrag.zusammenfassung }}</p>
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
{# Stärkster Wert #}
|
|||
|
|
{% if antrag.staerkster_wert and antrag.staerkster_wert.text %}
|
|||
|
|
<div class="v2-kasten outline-green" style="margin-top:20px;">
|
|||
|
|
<h4>Stärkster Wert{% if antrag.staerkster_wert.titel %} — {{ antrag.staerkster_wert.titel }}{% endif %}</h4>
|
|||
|
|
<p>{{ antrag.staerkster_wert.text }}</p>
|
|||
|
|
</div>
|
|||
|
|
{% elif antrag.staerken %}
|
|||
|
|
<div class="v2-kasten outline-green" style="margin-top:20px;">
|
|||
|
|
<h4>Stärken</h4>
|
|||
|
|
<ul style="margin:0;padding-left:1.2em;">
|
|||
|
|
{% for s in antrag.staerken %}
|
|||
|
|
<li style="font-size:13.5px;line-height:1.5;">{{ s }}</li>
|
|||
|
|
{% endfor %}
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
{# Schwächster Wert #}
|
|||
|
|
{% if antrag.schwaechster_wert and antrag.schwaechster_wert.text %}
|
|||
|
|
<div class="v2-kasten outline-blue">
|
|||
|
|
<h4>Schwächster Wert{% if antrag.schwaechster_wert.titel %} — {{ antrag.schwaechster_wert.titel }}{% endif %}</h4>
|
|||
|
|
<p>{{ antrag.schwaechster_wert.text }}</p>
|
|||
|
|
</div>
|
|||
|
|
{% elif antrag.schwaechen %}
|
|||
|
|
<div class="v2-kasten outline-blue">
|
|||
|
|
<h4>Schwächen</h4>
|
|||
|
|
<ul style="margin:0;padding-left:1.2em;">
|
|||
|
|
{% for s in antrag.schwaechen %}
|
|||
|
|
<li style="font-size:13.5px;line-height:1.5;">{{ s }}</li>
|
|||
|
|
{% endfor %}
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
{# Redline-Vorschläge: alle verbesserungen rendern wenn vorhanden #}
|
|||
|
|
{% if antrag.verbesserungen %}
|
|||
|
|
<h3 class="v2-h3" style="margin-top:24px;">Redline-Vorschläge</h3>
|
|||
|
|
{% for v in antrag.verbesserungen %}
|
|||
|
|
<div style="margin-bottom:16px;">
|
|||
|
|
{% if antrag.verbesserungen | length > 1 %}
|
|||
|
|
<div style="font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);opacity:0.65;margin-bottom:4px;">
|
|||
|
|
Vorschlag {{ loop.index }} von {{ antrag.verbesserungen | length }}
|
|||
|
|
</div>
|
|||
|
|
{% endif %}
|
|||
|
|
{% from "v2/components/redline.html" import redline %}
|
|||
|
|
{% if v.segments %}
|
|||
|
|
{{ redline(original=v.original | default(""), segments=v.segments) }}
|
|||
|
|
{% else %}
|
|||
|
|
{{ redline(original=v.original | default(""), vorschlag=v.vorschlag | default("")) }}
|
|||
|
|
{% endif %}
|
|||
|
|
{% if v.begruendung %}
|
|||
|
|
<p style="font-size:12px;color:var(--ecg-dark);opacity:0.75;margin:4px 0 0;font-family:var(--font-mono);">
|
|||
|
|
{{ v.begruendung }}
|
|||
|
|
</p>
|
|||
|
|
{% endif %}
|
|||
|
|
</div>
|
|||
|
|
{% endfor %}
|
|||
|
|
{% elif antrag.redline and antrag.redline.segments %}
|
|||
|
|
<h3 class="v2-h3" style="margin-top:24px;">Redline-Vorschlag</h3>
|
|||
|
|
{% from "v2/components/redline.html" import redline %}
|
|||
|
|
{{ redline(segments=antrag.redline.segments) }}
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
</div>{# .left #}
|
|||
|
|
|
|||
|
|
{# ── Rechte Spalte: Bewertungs-Panel ── #}
|
|||
|
|
<div class="right">
|
|||
|
|
<div class="v2-antrag-id">Bewertung</div>
|
|||
|
|
|
|||
|
|
{{ score_hero(antrag.score | default(0), antrag.verdict_title | default(""), antrag.verdict_body | default("")) }}
|
|||
|
|
|
|||
|
|
{# ── Merkliste-Stern (#140) ── #}
|
|||
|
|
<div style="margin-top:12px;margin-bottom:4px;">
|
|||
|
|
<button id="v2-merkliste-btn"
|
|||
|
|
onclick="v2DetailMerklisteToggle()"
|
|||
|
|
style="display:inline-flex;align-items:center;gap:6px;padding:5px 12px;
|
|||
|
|
border:1px solid var(--hairline);border-radius:4px;background:none;
|
|||
|
|
cursor:pointer;font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);">
|
|||
|
|
<span id="v2-merkliste-star">☆</span>
|
|||
|
|
<span id="v2-merkliste-label">Merken</span>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{# ── Namentliche Abstimmung (#106 Phase 1) ── #}
|
|||
|
|
{% if antrag.abstimmungsverhalten %}
|
|||
|
|
{% set aw = antrag.abstimmungsverhalten %}
|
|||
|
|
<h3 class="v2-h3" style="margin-top:24px;">Namentliche Abstimmung</h3>
|
|||
|
|
<div style="font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);opacity:0.75;margin-bottom:10px;">
|
|||
|
|
{% if aw.datum %}{{ aw.datum }} · {% endif %}
|
|||
|
|
{% if aw.accepted %}Angenommen{% else %}Abgelehnt{% endif %}
|
|||
|
|
</div>
|
|||
|
|
{% for f in aw.fraktionen %}
|
|||
|
|
{% set total = (f.yes + f.no + f.abstain + f.no_show) | int %}
|
|||
|
|
{% if total > 0 %}
|
|||
|
|
<div style="margin-bottom:8px;">
|
|||
|
|
<div style="font-family:var(--font-mono);font-size:11px;margin-bottom:3px;display:flex;justify-content:space-between;">
|
|||
|
|
<span>{{ f.partei }}</span>
|
|||
|
|
<span style="opacity:0.65;">{{ f.yes }}✓ {{ f.abstain }}○ {{ f.no }}✗</span>
|
|||
|
|
</div>
|
|||
|
|
<div style="display:flex;height:8px;border-radius:4px;overflow:hidden;background:var(--ecg-light,#f0f0f0);">
|
|||
|
|
{% if f.yes > 0 %}
|
|||
|
|
<div style="flex:{{ f.yes }};background:#2da44e;" title="{{ f.yes }} Ja"></div>
|
|||
|
|
{% endif %}
|
|||
|
|
{% if f.abstain > 0 %}
|
|||
|
|
<div style="flex:{{ f.abstain }};background:#adb5bd;" title="{{ f.abstain }} Enthaltung"></div>
|
|||
|
|
{% endif %}
|
|||
|
|
{% if f.no > 0 %}
|
|||
|
|
<div style="flex:{{ f.no }};background:#cf222e;" title="{{ f.no }} Nein"></div>
|
|||
|
|
{% endif %}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{% endif %}
|
|||
|
|
{% endfor %}
|
|||
|
|
{% endif %}{# abstimmungsverhalten #}
|
|||
|
|
|
|||
|
|
{% if antrag.matrix %}
|
|||
|
|
<h3 class="v2-h3">Matrix 2.0 · 25 Felder</h3>
|
|||
|
|
{{ matrix_mini(antrag.matrix) }}
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
{# Fraktions-Score-Tabelle (Fix 2+3): auch Fraktionen ohne Zitate sichtbar #}
|
|||
|
|
{% if antrag.fraktions_scores %}
|
|||
|
|
<h3 class="v2-h3" style="margin-top:24px;">Programm-Treue pro Fraktion</h3>
|
|||
|
|
<div class="v2-fraktions-scores">
|
|||
|
|
{% for fs in antrag.fraktions_scores %}
|
|||
|
|
<div class="v2-fraktion-row">
|
|||
|
|
<div class="v2-fraktion-label">
|
|||
|
|
{{ fs.fraktion }}
|
|||
|
|
{% if fs.ist_antragsteller %}<span class="v2-badge-antragsteller" title="Antragstellende Fraktion">A</span>{% endif %}
|
|||
|
|
{% if fs.ist_regierung %}<span class="v2-badge-regierung" title="Regierungsfraktion">R</span>{% endif %}
|
|||
|
|
</div>
|
|||
|
|
<div class="v2-fraktion-scores">
|
|||
|
|
{% set wp_score = fs.wahlprogramm.score | float %}
|
|||
|
|
{% set pp_score = fs.parteiprogramm.score | float %}
|
|||
|
|
<span class="v2-score-chip {% if wp_score >= 7 %}chip-green{% elif wp_score >= 4 %}chip-mid{% else %}chip-red{% endif %}"
|
|||
|
|
title="Wahlprogramm-Treue: {{ fs.wahlprogramm.begruendung }}">
|
|||
|
|
WP {{ "%.0f"|format(wp_score) }}/10
|
|||
|
|
</span>
|
|||
|
|
<span class="v2-score-chip {% if pp_score >= 7 %}chip-green{% elif pp_score >= 4 %}chip-mid{% else %}chip-red{% endif %}"
|
|||
|
|
title="Parteiprogramm-Treue: {{ fs.parteiprogramm.begruendung }}">
|
|||
|
|
PP {{ "%.0f"|format(pp_score) }}/10
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{% endfor %}
|
|||
|
|
</div>
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
{# Zitate nach Partei gruppiert; Fraktion ohne Zitate erhält Hinweis via fraktions_scores-Block oben #}
|
|||
|
|
{% if antrag.zitate %}
|
|||
|
|
{% set current_partei = namespace(value="") %}
|
|||
|
|
{% for z in antrag.zitate %}
|
|||
|
|
{% if z.partei != current_partei.value %}
|
|||
|
|
{% set current_partei.value = z.partei %}
|
|||
|
|
<h3 class="v2-h3" style="margin-top:24px;">Belege — {{ z.partei }}</h3>
|
|||
|
|
{% endif %}
|
|||
|
|
{{ quote_card(z.text, z.source, z.verified | default(True), z.contra | default(False), z.pdf_href | default("")) }}
|
|||
|
|
{% endfor %}
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
{# Aktions-Links #}
|
|||
|
|
<div style="margin-top:24px;font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);opacity:0.85;display:flex;gap:16px;flex-wrap:wrap;">
|
|||
|
|
<a href="/api/assessment/pdf?drucksache={{ antrag.drucksache | urlencode }}"
|
|||
|
|
style="color:var(--ecg-blue);border-bottom:1px solid rgba(0,157,165,0.35);">
|
|||
|
|
PDF-Bericht
|
|||
|
|
</a>
|
|||
|
|
<a href="/api/assessment?drucksache={{ antrag.drucksache | urlencode }}"
|
|||
|
|
style="color:var(--ecg-blue);border-bottom:1px solid rgba(0,157,165,0.35);">
|
|||
|
|
JSON-Export
|
|||
|
|
</a>
|
|||
|
|
<a href="/antrag/{{ antrag.drucksache }}"
|
|||
|
|
style="color:var(--ecg-blue);border-bottom:1px solid rgba(0,157,165,0.35);">
|
|||
|
|
Permalink
|
|||
|
|
</a>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{# ── Voting-Block ─────────────────────────────────────────────── #}
|
|||
|
|
<div style="margin-top:24px;">
|
|||
|
|
<div style="font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:0.07em;color:var(--ecg-dark);opacity:0.6;margin-bottom:8px;">Bewertung treffend?</div>
|
|||
|
|
<div id="v2-vote-overall" style="display:flex;gap:8px;align-items:center;">
|
|||
|
|
<button id="v2-vote-up"
|
|||
|
|
onclick="v2DetailCastVote('{{ antrag.drucksache | e }}','up')"
|
|||
|
|
style="display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border:1px solid var(--hairline);border-radius:4px;background:none;cursor:pointer;font-family:var(--font-mono);font-size:12px;color:var(--ecg-dark);">
|
|||
|
|
👍 <span id="v2-vote-up-count">0</span>
|
|||
|
|
</button>
|
|||
|
|
<button id="v2-vote-down"
|
|||
|
|
onclick="v2DetailCastVote('{{ antrag.drucksache | e }}','down')"
|
|||
|
|
style="display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border:1px solid var(--hairline);border-radius:4px;background:none;cursor:pointer;font-family:var(--font-mono);font-size:12px;color:var(--ecg-dark);">
|
|||
|
|
👎 <span id="v2-vote-down-count">0</span>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{# ── Share-Block ──────────────────────────────────────────────── #}
|
|||
|
|
<div style="margin-top:20px;">
|
|||
|
|
<div style="font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:0.07em;color:var(--ecg-dark);opacity:0.6;margin-bottom:8px;">Teilen</div>
|
|||
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
|||
|
|
<button onclick="v2DetailShare('threads')"
|
|||
|
|
style="padding:5px 10px;border:1px solid var(--hairline);border-radius:4px;background:none;cursor:pointer;font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);">
|
|||
|
|
Threads
|
|||
|
|
</button>
|
|||
|
|
<button onclick="v2DetailShare('twitter')"
|
|||
|
|
style="padding:5px 10px;border:1px solid var(--hairline);border-radius:4px;background:none;cursor:pointer;font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);">
|
|||
|
|
X
|
|||
|
|
</button>
|
|||
|
|
<button onclick="v2DetailShareMastodon()"
|
|||
|
|
style="padding:5px 10px;border:1px solid var(--hairline);border-radius:4px;background:none;cursor:pointer;font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);">
|
|||
|
|
Mastodon
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{# ── Re-Analyze-Block ─────────────────────────────────────────── #}
|
|||
|
|
<div style="margin-top:20px;">
|
|||
|
|
<button id="v2-reanalyze-btn"
|
|||
|
|
onclick="v2DetailReAnalyze(this)"
|
|||
|
|
style="padding:5px 12px;border:1px solid var(--hairline);border-radius:4px;background:none;cursor:pointer;font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);opacity:0.8;">
|
|||
|
|
Neu analysieren
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{# ── Bewertungs-Historie ───────────────────────────────────────── #}
|
|||
|
|
<div style="margin-top:24px;">
|
|||
|
|
<div style="font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:0.07em;color:var(--ecg-dark);opacity:0.6;margin-bottom:8px;">Bewertungs-Historie</div>
|
|||
|
|
<div id="v2-history-list">
|
|||
|
|
<span style="font-family:var(--font-mono);font-size:12px;color:var(--ecg-dark);opacity:0.45;">Lade…</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
</div>{# .right #}
|
|||
|
|
|
|||
|
|
</div>{# .v2-detail #}
|
|||
|
|
|
|||
|
|
{# ── Kommentare ───────────────────────────────────────────────────────── #}
|
|||
|
|
<div style="margin-top:40px;border-top:1px solid var(--hairline);padding-top:28px;">
|
|||
|
|
<h3 class="v2-h3" style="margin-bottom:16px;">Kommentare</h3>
|
|||
|
|
|
|||
|
|
<div id="v2-comments-list" style="margin-bottom:20px;">
|
|||
|
|
<span style="font-family:var(--font-mono);font-size:12px;color:var(--ecg-dark);opacity:0.5;">Lade…</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{# Kommentar-Formular — wird per JS eingeblendet wenn angemeldet #}
|
|||
|
|
<div id="v2-comment-form" style="display:none;">
|
|||
|
|
<div style="font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:0.07em;color:var(--ecg-dark);opacity:0.6;margin-bottom:8px;">Kommentar hinzufügen</div>
|
|||
|
|
<textarea id="v2-comment-input"
|
|||
|
|
rows="3"
|
|||
|
|
placeholder="Kommentar…"
|
|||
|
|
style="width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--hairline);border-radius:4px;font-family:var(--font-sans);font-size:13px;background:var(--surface);color:var(--ecg-dark);resize:vertical;margin-bottom:8px;outline:none;"></textarea>
|
|||
|
|
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
|||
|
|
<select id="v2-comment-visibility"
|
|||
|
|
style="padding:5px 8px;border:1px solid var(--hairline);border-radius:4px;font-family:var(--font-mono);font-size:11px;background:var(--surface);color:var(--ecg-dark);">
|
|||
|
|
<option value="all">Öffentlich</option>
|
|||
|
|
<option value="authenticated">Nur Angemeldete</option>
|
|||
|
|
<option value="private">Nur ich</option>
|
|||
|
|
</select>
|
|||
|
|
<button onclick="v2DetailAddComment('{{ antrag.drucksache | e }}')"
|
|||
|
|
style="padding:5px 14px;border:none;border-radius:4px;background:var(--ecg-blue);color:#fff;cursor:pointer;font-family:var(--font-mono);font-size:11px;font-weight:700;">
|
|||
|
|
Absenden
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div id="v2-comment-login-hint" style="display:none;">
|
|||
|
|
<button onclick="v2AuthModalOpen()"
|
|||
|
|
style="padding:5px 12px;border:1px solid var(--hairline);border-radius:4px;background:none;cursor:pointer;font-family:var(--font-mono);font-size:11px;color:var(--ecg-blue);">
|
|||
|
|
Anmelden um zu kommentieren
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{# ── Matrix-Feld-Info-Modal ───────────────────────────────────────────── #}
|
|||
|
|
<div id="v2-matrix-field-modal"
|
|||
|
|
role="dialog" aria-modal="true" aria-label="Matrix-Feld Erklärung"
|
|||
|
|
style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);
|
|||
|
|
z-index:9100;align-items:center;justify-content:center;"
|
|||
|
|
onclick="if(event.target===this)this.style.display='none'">
|
|||
|
|
<div style="background:var(--ecg-card-bg,#fff);border:1px solid var(--ecg-border,#ddd);
|
|||
|
|
border-radius:8px;padding:28px 32px;min-width:280px;max-width:480px;
|
|||
|
|
font-family:var(--font-sans);font-size:14px;color:var(--ecg-dark);
|
|||
|
|
line-height:1.55;box-shadow:0 8px 32px rgba(0,0,0,0.18);">
|
|||
|
|
<div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px;">
|
|||
|
|
<strong id="v2-matrix-field-title"
|
|||
|
|
style="font-family:var(--font-display,inherit);font-size:16px;font-weight:900;
|
|||
|
|
color:var(--ecg-teal,#009da5);letter-spacing:0.03em;"></strong>
|
|||
|
|
<button onclick="document.getElementById('v2-matrix-field-modal').style.display='none'"
|
|||
|
|
style="background:none;border:none;font-size:18px;cursor:pointer;color:var(--ecg-dark);
|
|||
|
|
opacity:0.55;padding:0;line-height:1;"
|
|||
|
|
aria-label="Schließen">×</button>
|
|||
|
|
</div>
|
|||
|
|
<p id="v2-matrix-field-text" style="margin:0;"></p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{% endif %}{# _render #}
|
|||
|
|
|
|||
|
|
{% endblock %}
|
|||
|
|
|
|||
|
|
{% block body_scripts %}
|
|||
|
|
<script>
|
|||
|
|
/* Escape auf der Detailseite → history.back() (außer wenn Matrix-Modal offen) */
|
|||
|
|
document.addEventListener('keydown', function (e) {
|
|||
|
|
if (e.key === 'Escape' && !e.target.matches('input, textarea, select')) {
|
|||
|
|
var modal = document.getElementById('v2-matrix-field-modal');
|
|||
|
|
if (modal && modal.style.display === 'flex') {
|
|||
|
|
modal.style.display = 'none';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
e.preventDefault();
|
|||
|
|
history.back();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
{# Matrix-Erklärungen als JSON in den Browser übertragen #}
|
|||
|
|
{% if matrix_explanations is defined %}
|
|||
|
|
<script>
|
|||
|
|
window._v2MatrixExplanations = {{ matrix_explanations | tojson }};
|
|||
|
|
window.v2ShowMatrixFieldInfo = function(field) {
|
|||
|
|
var explains = window._v2MatrixExplanations || {};
|
|||
|
|
var text = explains[field] || '';
|
|||
|
|
var titleEl = document.getElementById('v2-matrix-field-title');
|
|||
|
|
var textEl = document.getElementById('v2-matrix-field-text');
|
|||
|
|
var modal = document.getElementById('v2-matrix-field-modal');
|
|||
|
|
if (!modal) return;
|
|||
|
|
if (titleEl) titleEl.textContent = 'Feld ' + field;
|
|||
|
|
if (textEl) textEl.textContent = text || '(Keine Erklärung vorhanden)';
|
|||
|
|
modal.style.display = 'flex';
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
{% endif %}
|
|||
|
|
|
|||
|
|
{% if antrag is defined and antrag and antrag.drucksache %}
|
|||
|
|
<script>
|
|||
|
|
(function () {
|
|||
|
|
var DRS = {{ antrag.drucksache | tojson }};
|
|||
|
|
var BL = {{ antrag.bundesland | tojson }};
|
|||
|
|
var SHARE_THR = {{ (antrag.share_threads or '') | tojson }};
|
|||
|
|
var SHARE_TWI = {{ (antrag.share_twitter or '') | tojson }};
|
|||
|
|
var SHARE_MAS = {{ (antrag.share_mastodon or '') | tojson }};
|
|||
|
|
var TITLE = {{ antrag.title | tojson }};
|
|||
|
|
var SCORE = {{ antrag.score | tojson }};
|
|||
|
|
var PERMALINK = 'https://gwoe.toppyr.de/antrag/' + encodeURIComponent(DRS);
|
|||
|
|
|
|||
|
|
var currentUser = null;
|
|||
|
|
|
|||
|
|
/* ── Auth-State laden ─────────────────────────────────────────── */
|
|||
|
|
async function initAuth() {
|
|||
|
|
try {
|
|||
|
|
var resp = await fetch('/api/auth/me');
|
|||
|
|
var data = await resp.json();
|
|||
|
|
currentUser = (data && data.authenticated) ? data : null;
|
|||
|
|
} catch (_) { currentUser = null; }
|
|||
|
|
|
|||
|
|
var form = document.getElementById('v2-comment-form');
|
|||
|
|
var loginHint = document.getElementById('v2-comment-login-hint');
|
|||
|
|
if (currentUser) {
|
|||
|
|
if (form) form.style.display = 'block';
|
|||
|
|
if (loginHint) loginHint.style.display = 'none';
|
|||
|
|
} else {
|
|||
|
|
if (form) form.style.display = 'none';
|
|||
|
|
if (loginHint) loginHint.style.display = 'block';
|
|||
|
|
}
|
|||
|
|
loadComments();
|
|||
|
|
loadVotes();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Kommentare ───────────────────────────────────────────────── */
|
|||
|
|
async function loadComments() {
|
|||
|
|
var container = document.getElementById('v2-comments-list');
|
|||
|
|
if (!container) return;
|
|||
|
|
try {
|
|||
|
|
var comments = await fetch('/api/comments?drucksache=' + encodeURIComponent(DRS)).then(function(r){ return r.json(); });
|
|||
|
|
var visible = comments.filter(function(c) {
|
|||
|
|
if (c.visibility === 'all') return true;
|
|||
|
|
if (!currentUser) return false;
|
|||
|
|
if (c.visibility === 'authenticated') return true;
|
|||
|
|
if (c.visibility === 'private') return c.user_id === currentUser.sub;
|
|||
|
|
if (c.visibility && c.visibility.startsWith('group:')) return true;
|
|||
|
|
return false;
|
|||
|
|
});
|
|||
|
|
if (visible.length === 0) {
|
|||
|
|
container.innerHTML = '<span style="font-family:var(--font-mono);font-size:12px;color:var(--ecg-dark);opacity:0.45;">Noch keine Kommentare.</span>';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
function visBadge(v) {
|
|||
|
|
var lbl = v === 'private' ? '👤' : v === 'authenticated' ? '🔒' : '🌐';
|
|||
|
|
return '<span style="font-family:var(--font-mono);font-size:10px;background:var(--surface);border:1px solid var(--hairline);padding:1px 4px;border-radius:2px;">' + lbl + '</span>';
|
|||
|
|
}
|
|||
|
|
container.innerHTML = visible.map(function(c) {
|
|||
|
|
var dateStr = new Date(c.created_at).toLocaleString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'});
|
|||
|
|
var delBtn = (currentUser && currentUser.sub === c.user_id)
|
|||
|
|
? '<button onclick="v2DetailDeleteComment(' + c.id + ')" style="margin-left:auto;background:none;border:none;color:#c33;cursor:pointer;font-family:var(--font-mono);font-size:11px;padding:0;" title="Löschen">✕</button>'
|
|||
|
|
: '';
|
|||
|
|
return '<div style="padding:10px 0;border-bottom:1px solid var(--hairline);">'
|
|||
|
|
+ '<div style="font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);opacity:0.75;margin-bottom:5px;display:flex;align-items:center;gap:6px;">'
|
|||
|
|
+ '<strong>' + (c.user_name || 'Anonym') + '</strong>'
|
|||
|
|
+ visBadge(c.visibility)
|
|||
|
|
+ '<span style="opacity:0.6;">' + dateStr + '</span>'
|
|||
|
|
+ delBtn
|
|||
|
|
+ '</div>'
|
|||
|
|
+ '<div style="font-size:13.5px;line-height:1.5;">' + c.text + '</div>'
|
|||
|
|
+ '</div>';
|
|||
|
|
}).join('');
|
|||
|
|
} catch (_) {
|
|||
|
|
document.getElementById('v2-comments-list').innerHTML = '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
window.v2DetailAddComment = async function(drucksache) {
|
|||
|
|
var input = document.getElementById('v2-comment-input');
|
|||
|
|
var visSel = document.getElementById('v2-comment-visibility');
|
|||
|
|
if (!input || !input.value.trim()) return;
|
|||
|
|
var visibility = visSel ? visSel.value : 'all';
|
|||
|
|
await fetch('/api/comment', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
|||
|
|
body: 'drucksache=' + encodeURIComponent(drucksache)
|
|||
|
|
+ '&text=' + encodeURIComponent(input.value)
|
|||
|
|
+ '&visibility=' + visibility
|
|||
|
|
});
|
|||
|
|
input.value = '';
|
|||
|
|
loadComments();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
window.v2DetailDeleteComment = async function(commentId) {
|
|||
|
|
await fetch('/api/comment/' + commentId, {method: 'DELETE'});
|
|||
|
|
loadComments();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/* ── Voting ───────────────────────────────────────────────────── */
|
|||
|
|
async function loadVotes() {
|
|||
|
|
try {
|
|||
|
|
var data = await fetch('/api/votes?drucksache=' + encodeURIComponent(DRS)).then(function(r){ return r.json(); });
|
|||
|
|
var counts = (data.counts && data.counts.overall) ? data.counts.overall : {up:0, down:0};
|
|||
|
|
var myVote = data.my_votes && data.my_votes.overall;
|
|||
|
|
var upEl = document.getElementById('v2-vote-up-count');
|
|||
|
|
var downEl = document.getElementById('v2-vote-down-count');
|
|||
|
|
var upBtn = document.getElementById('v2-vote-up');
|
|||
|
|
var downBtn = document.getElementById('v2-vote-down');
|
|||
|
|
if (upEl) upEl.textContent = counts.up || 0;
|
|||
|
|
if (downEl) downEl.textContent = counts.down || 0;
|
|||
|
|
if (upBtn) {
|
|||
|
|
upBtn.style.background = myVote === 'up' ? 'rgba(137,158,51,0.12)' : '';
|
|||
|
|
upBtn.style.borderColor = myVote === 'up' ? 'var(--ecg-green)' : '';
|
|||
|
|
}
|
|||
|
|
if (downBtn) {
|
|||
|
|
downBtn.style.background = myVote === 'down' ? 'rgba(220,53,69,0.10)' : '';
|
|||
|
|
downBtn.style.borderColor = myVote === 'down' ? '#dc3545' : '';
|
|||
|
|
}
|
|||
|
|
} catch (_) {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
window.v2DetailCastVote = async function(drucksache, vote) {
|
|||
|
|
if (!currentUser) { v2AuthModalOpen(); return; }
|
|||
|
|
try {
|
|||
|
|
var resp = await fetch('/api/vote', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
|||
|
|
body: 'drucksache=' + encodeURIComponent(drucksache) + '&target=overall&vote=' + vote
|
|||
|
|
});
|
|||
|
|
if (resp.ok) loadVotes();
|
|||
|
|
} catch (_) {}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/* ── Share ────────────────────────────────────────────────────── */
|
|||
|
|
function buildShareText(platform) {
|
|||
|
|
var LIMITS = {twitter: 240, threads: 460, mastodon: 460};
|
|||
|
|
var limit = LIMITS[platform] || 460;
|
|||
|
|
var text;
|
|||
|
|
if (platform === 'twitter' && SHARE_TWI) text = SHARE_TWI;
|
|||
|
|
else if (platform === 'threads' && SHARE_THR) text = SHARE_THR;
|
|||
|
|
else if (platform === 'mastodon' && SHARE_MAS) text = SHARE_MAS;
|
|||
|
|
else {
|
|||
|
|
var emoji = SCORE >= 8 ? '🟢' : SCORE >= 5 ? '🟡' : SCORE >= 3 ? '🟠' : '🔴';
|
|||
|
|
text = emoji + ' GWÖ-Score ' + SCORE + '/10: „' + TITLE.substring(0, 70) + '" (' + DRS + ')\n\n#Gemeinwohl #GWÖ';
|
|||
|
|
}
|
|||
|
|
if (text.length > limit) text = text.substring(0, limit - 1) + '…';
|
|||
|
|
return text;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
window.v2DetailShare = function(platform) {
|
|||
|
|
var text = buildShareText(platform) + '\n' + PERMALINK;
|
|||
|
|
var urls = {
|
|||
|
|
twitter: 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(text),
|
|||
|
|
threads: 'https://www.threads.net/intent/post?text=' + encodeURIComponent(text)
|
|||
|
|
};
|
|||
|
|
if (urls[platform]) window.open(urls[platform], '_blank', 'noopener');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
window.v2DetailShareMastodon = function() {
|
|||
|
|
var text = buildShareText('mastodon') + '\n' + PERMALINK;
|
|||
|
|
var instance = localStorage.getItem('mastodon_instance');
|
|||
|
|
if (!instance) {
|
|||
|
|
instance = prompt('Deine Mastodon-Instanz (z.B. mastodon.social):');
|
|||
|
|
if (!instance) return;
|
|||
|
|
instance = instance.trim().replace(/^https?:\/\//, '').replace(/\/+$/, '');
|
|||
|
|
localStorage.setItem('mastodon_instance', instance);
|
|||
|
|
}
|
|||
|
|
window.open('https://' + instance + '/share?text=' + encodeURIComponent(text), '_blank', 'noopener');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/* ── Re-Analyze ───────────────────────────────────────────────── */
|
|||
|
|
function pollReAnalyze(jobId, btn) {
|
|||
|
|
fetch('/status/' + jobId)
|
|||
|
|
.then(function(r){ return r.json(); })
|
|||
|
|
.then(function(data) {
|
|||
|
|
if (data.status === 'completed') {
|
|||
|
|
btn.textContent = 'Fertig — lade neu…';
|
|||
|
|
setTimeout(function(){ location.reload(); }, 800);
|
|||
|
|
} else if (data.status === 'failed') {
|
|||
|
|
btn.textContent = 'Analyse fehlgeschlagen';
|
|||
|
|
btn.disabled = false;
|
|||
|
|
} else {
|
|||
|
|
setTimeout(function(){ pollReAnalyze(jobId, btn); }, 2000);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch(function() { btn.textContent = 'Polling-Fehler'; btn.disabled = false; });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
window.v2DetailReAnalyze = async function(btn) {
|
|||
|
|
if (!currentUser) { v2AuthModalOpen(); return; }
|
|||
|
|
btn.disabled = true;
|
|||
|
|
btn.textContent = 'Lösche alte Bewertung…';
|
|||
|
|
try {
|
|||
|
|
var delResp = await fetch('/api/assessment/delete?drucksache=' + encodeURIComponent(DRS), {method: 'DELETE'});
|
|||
|
|
if (delResp.status === 401) {
|
|||
|
|
btn.textContent = 'Nicht angemeldet';
|
|||
|
|
setTimeout(function(){ btn.textContent = 'Neu analysieren'; btn.disabled = false; }, 3000);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
btn.textContent = 'Analyse läuft…';
|
|||
|
|
var resp = await fetch('/api/analyze-drucksache', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
|||
|
|
body: 'drucksache=' + encodeURIComponent(DRS) + '&bundesland=' + encodeURIComponent(BL)
|
|||
|
|
});
|
|||
|
|
if (resp.status === 401) {
|
|||
|
|
btn.textContent = 'Nicht angemeldet';
|
|||
|
|
setTimeout(function(){ btn.textContent = 'Neu analysieren'; btn.disabled = false; }, 3000);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
var data = await resp.json();
|
|||
|
|
if (data.status === 'queued') {
|
|||
|
|
btn.textContent = 'Wird analysiert…';
|
|||
|
|
pollReAnalyze(data.job_id, btn);
|
|||
|
|
} else {
|
|||
|
|
btn.textContent = 'Fehler: ' + (data.detail || 'unbekannt');
|
|||
|
|
setTimeout(function(){ btn.textContent = 'Neu analysieren'; btn.disabled = false; }, 4000);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
btn.textContent = 'Fehler: ' + e.message;
|
|||
|
|
setTimeout(function(){ btn.textContent = 'Neu analysieren'; btn.disabled = false; }, 4000);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/* ── Merkliste (#140) ────────────────────────────────────────── */
|
|||
|
|
var _merklisteActive = false;
|
|||
|
|
|
|||
|
|
async function initMerkliste() {
|
|||
|
|
if (!currentUser) return;
|
|||
|
|
try {
|
|||
|
|
var resp = await fetch('/api/me/merkliste');
|
|||
|
|
if (!resp.ok) return;
|
|||
|
|
var entries = await resp.json();
|
|||
|
|
var isInList = entries.some(function(e) { return e.antrag_id === DRS; });
|
|||
|
|
_updateMerkliste(isInList);
|
|||
|
|
} catch (_) {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function _updateMerkliste(active) {
|
|||
|
|
_merklisteActive = active;
|
|||
|
|
var star = document.getElementById('v2-merkliste-star');
|
|||
|
|
var label = document.getElementById('v2-merkliste-label');
|
|||
|
|
var btn = document.getElementById('v2-merkliste-btn');
|
|||
|
|
if (!star) return;
|
|||
|
|
star.textContent = active ? '★' : '☆';
|
|||
|
|
label.textContent = active ? 'Gemerkt' : 'Merken';
|
|||
|
|
if (btn) {
|
|||
|
|
btn.style.background = active ? 'rgba(0,157,165,0.10)' : '';
|
|||
|
|
btn.style.borderColor = active ? 'var(--ecg-teal)' : '';
|
|||
|
|
btn.style.color = active ? 'var(--ecg-teal)' : '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
window.v2DetailMerklisteToggle = async function() {
|
|||
|
|
if (!currentUser) { v2AuthModalOpen(); return; }
|
|||
|
|
try {
|
|||
|
|
if (_merklisteActive) {
|
|||
|
|
var r = await fetch('/api/me/merkliste/' + encodeURIComponent(DRS), { method: 'DELETE' });
|
|||
|
|
if (r.ok) _updateMerkliste(false);
|
|||
|
|
} else {
|
|||
|
|
var r = await fetch('/api/me/merkliste', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
|
body: JSON.stringify({ antrag_id: DRS })
|
|||
|
|
});
|
|||
|
|
if (r.ok) _updateMerkliste(true);
|
|||
|
|
}
|
|||
|
|
} catch (_) {}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/* ── Bewertungs-Historie ─────────────────────────────────────── */
|
|||
|
|
async function loadHistory() {
|
|||
|
|
var container = document.getElementById('v2-history-list');
|
|||
|
|
if (!container) return;
|
|||
|
|
try {
|
|||
|
|
var entries = await fetch('/api/assessment/history?drucksache=' + encodeURIComponent(DRS)).then(function(r){ return r.json(); });
|
|||
|
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|||
|
|
container.innerHTML = '<span style="font-family:var(--font-mono);font-size:12px;color:var(--ecg-dark);opacity:0.45;">Nur eine Version vorhanden.</span>';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
container.innerHTML = entries.map(function(v, i) {
|
|||
|
|
var dateStr = v.created_at
|
|||
|
|
? new Date(v.created_at).toLocaleString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})
|
|||
|
|
: '–';
|
|||
|
|
var version = v.version !== undefined ? v.version : (entries.length - i);
|
|||
|
|
return '<div style="display:flex;justify-content:space-between;align-items:baseline;'
|
|||
|
|
+ 'padding:5px 0;border-bottom:1px solid var(--hairline);'
|
|||
|
|
+ 'font-family:var(--font-mono);font-size:11px;color:var(--ecg-dark);">'
|
|||
|
|
+ '<span>v' + version + ' <span style="opacity:0.55;">' + dateStr + '</span></span>'
|
|||
|
|
+ '<a href="/api/assessment?drucksache=' + encodeURIComponent(DRS) + '&version=' + version + '" target="_blank" rel="noopener"'
|
|||
|
|
+ ' style="color:var(--ecg-blue);border-bottom:1px solid rgba(0,157,165,0.35);">JSON</a>'
|
|||
|
|
+ '</div>';
|
|||
|
|
}).join('');
|
|||
|
|
} catch (_) {
|
|||
|
|
if (container) container.innerHTML = '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* ── Init ─────────────────────────────────────────────────────── */
|
|||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|||
|
|
initAuth();
|
|||
|
|
initMerkliste();
|
|||
|
|
loadHistory();
|
|||
|
|
});
|
|||
|
|
})();
|
|||
|
|
</script>
|
|||
|
|
{% endif %}
|
|||
|
|
{% endblock %}
|