antragstracker/static/_app/immutable/nodes/5.DWEKtTGz.js
Dotty Dotter ea3e5cd329 feat: Intuitivere Bedienung — klickbare Stats + Abstimmungs-Filter + Fraktions-Normalisierung (#14)
Dashboard:
- Neuer Endpoint GET /api/stats/dashboard mit allen Kennzahlen
- Klickbare Kacheln: Vorlagen nach Typ, Ketten nach Status → navigieren zu Filterlisten
- Umsetzungsquote als horizontaler Balken mit klickbaren Segmenten

Abstimmungen:
- Stimmverhalten-Tabelle klickbar: Fraktion oder Ja/Nein/Enthaltung → filtert
- Neuer Endpoint GET /api/abstimmungen/details (?fraktion=&stimme=) mit Pagination
- Neuer Endpoint GET /api/abstimmungen/vergleich (?f1=&f2=) für Koalitionsmatrix-Drill-Down
- Koalitionsmatrix-Zellen klickbar → zeigt Abstimmungsvergleich beider Fraktionen

Fraktions-Normalisierung:
- fraktionen_mapping.py: 40+ DB-Varianten → kanonische Namen
- 'Bündnis 90 / Die Grünen' / 'Bündnis 90/Die Grünen' / 'Grüne' → 'Grüne'
- 'Die Linke' / 'Die Linke.' / 'Linke' → 'Linke'
- BfHo-Varianten, Hagen Aktiv, Einzelvertreter etc. normalisiert
- Mapping in allen Abstimmungs-Endpoints aktiv
- ist_ratsfraktion Flag in Fraktionen-Response

Closes #14
2026-04-01 14:32:06 +02:00

2 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import{h as Ae,a as d,f as g,d as Je,b as ce,c as Be,t as Oe}from"../chunks/B89f14j0.js";import{o as Pe}from"../chunks/DrzKg1h1.js";import{p as Ee,h as r,r as t,b as s,g as e,t as p,c as Me,u as O,s as P,n as R,d as F,an as Re,H as Se,$ as We,f as ge}from"../chunks/reyx9_7L.js";import{s as i}from"../chunks/BwTTNG21.js";import{s as qe,a as Ce}from"../chunks/CvtDgobB.js";import{i as y}from"../chunks/Do7Yo2YN.js";import{h as Ge,e as Z,i as V}from"../chunks/D5EBvEcH.js";import{s as be}from"../chunks/B-WTs0fq.js";import{s as Qe}from"../chunks/C7sCDBjT.js";import{s as ae}from"../chunks/DnBxR3jh.js";import{b as Xe}from"../chunks/Be1Vm8i2.js";import{p as Ye}from"../chunks/wNiDNfGw.js";import{i as et}from"../chunks/nhOotKLT.js";import{p as Fe}from"../chunks/DfsAIpU3.js";import{f as Ke}from"../chunks/utcFFRIM.js";const fe={erfuellt:{label:"Erfüllt",farbe:"#22c55e",icon:"✅",beschreibung:"Forderung vollständig oder weitgehend umgesetzt. Konkreter Beschluss liegt vor, der die Kernpunkte des Antrags aufgreift.",beispiel:"Antrag auf Zuschuss → Zuschuss in beantragter Höhe einstimmig bewilligt.",score_range:[.8,1]},teilweise:{label:"Teilweise",farbe:"#eab308",icon:"⚠️",beschreibung:"Kernpunkt wurde adressiert, aber mit Abstrichen. Verwaltung greift Teile auf, lässt andere fallen oder verwässert die Forderung.",beispiel:"Antrag auf Radweg → Prüfauftrag für Machbarkeitsstudie statt direktem Baubeschluss.",score_range:[.5,.7]},abgewiegelt:{label:"Abgewiegelt",farbe:"#f97316",icon:"🚫",beschreibung:"Verwaltung weicht aus. Kündigt Prüfung an, verweist auf Zuständigkeiten oder beantwortet die Frage nicht substantiell.",beispiel:'Anfrage zu Missständen → „Wird geprüft" ohne Zeitrahmen oder konkrete Zusagen.',score_range:[.2,.4]},nebelkerze:{label:"Nebelkerze",farbe:"#ef4444",icon:"💨",beschreibung:"Thema komplett ignoriert, Diskussion auf Nebenschauplatz verlagert oder Antrag ohne Behandlung von der Tagesordnung genommen.",beispiel:'Antrag zu Sauberkeit → „Ohne Beschlussfassung" oder keine Wortmeldung.',score_range:[0,.1]},vertagt:{label:"Vertagt",farbe:"#8b5cf6",icon:"⏳",beschreibung:"Antrag explizit verschoben, ruhend gestellt oder in einen anderen Ausschuss verwiesen. Kein inhaltliches Ergebnis, aber formal nicht abgelehnt.",beispiel:"Antrag wird zur weiteren Beratung in den Fachausschuss überwiesen.",score_range:null},unklar:{label:"Unklar",farbe:"#6b7280",icon:"❓",beschreibung:"Beschlusslage nicht eindeutig zuordenbar. Möglicherweise fehlen Dokumente oder der Beschlusstext ist nicht aussagekräftig genug.",beispiel:'Nur „Kenntnisnahme" ohne weitere Erläuterung.',score_range:null}},Te={label:"Unbekannt",farbe:"#9ca3af",icon:"❓",beschreibung:"Keine Bewertung vorhanden.",beispiel:"",score_range:null};function tt(H){return H?fe[H]??Te:Te}var rt=g('<span class="ml-1 opacity-60"> </span>'),at=g('<div class="mt-1 text-gray-400"> </div>'),st=g('<div class="absolute z-50 bottom-full left-1/2 -translate-x-1/2 mb-2 w-72 p-3 bg-gray-900 text-white text-xs rounded-lg shadow-lg pointer-events-none"><div class="font-bold mb-1"> </div> <div class="mb-2 leading-relaxed"> </div> <div class="text-gray-400 italic"> </div> <!> <div class="absolute top-full left-1/2 -translate-x-1/2 -mt-1 w-2 h-2 bg-gray-900 rotate-45"></div></div>'),nt=g('<span class="relative inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium cursor-help"><span class="mr-1"> </span> <!> <!></span>');function De(H,L){Ee(L,!0);let S=Fe(L,"score",3,null),se=Fe(L,"showTooltip",3,!0);const m=O(()=>tt(L.kategorie));let o=P(!1);var $=nt(),E=r($),W=r(E,!0);t(E);var z=s(E),j=s(z);{var q=K=>{var T=rt(),M=r(T);t(T),p(Q=>i(M,`(${Q??""}%)`),[()=>(S()*100).toFixed(0)]),d(K,T)};y(j,K=>{S()!==null&&S()!==void 0&&K(q)})}var C=s(j,2);{var G=K=>{var T=st(),M=r(T),Q=r(M);t(M);var v=s(M,2),A=r(v,!0);t(v);var D=s(v,2),te=r(D);t(D);var re=s(D,2);{var X=Y=>{var I=at(),ne=r(I);t(I),p(()=>i(ne,`Score: ${e(m).score_range[0]??""}${e(m).score_range[1]??""}`)),d(Y,I)};y(re,Y=>{e(m).score_range&&Y(X)})}R(2),t(T),p(()=>{i(Q,`${e(m).icon??""} ${e(m).label??""}`),i(A,e(m).beschreibung),i(te,`Beispiel: ${e(m).beispiel??""}`)}),d(K,T)};y(C,K=>{se()&&e(o)&&K(G)})}t($),p(()=>{ae($,`background-color: ${e(m).farbe??""}20; color: ${e(m).farbe??""}; border: 1px solid ${e(m).farbe??""}40;`),i(W,e(m).icon),i(z,` ${e(m).label??""} `)}),Ae("mouseenter",$,()=>F(o,!0)),Ae("mouseleave",$,()=>F(o,!1)),d(H,$),Me()}var it=g('<div class="text-gray-500">Laden...</div>'),lt=g('<div class="text-red-600"> </div>'),ot=g('<div class="bg-green-50 rounded-lg border border-green-200 p-4"><div class="text-2xl sm:text-3xl font-bold text-green-700"> </div> <div class="text-xs sm:text-sm text-green-600">Erfüllt</div></div>'),dt=g('<div class="bg-red-50 rounded-lg border border-red-200 p-4"><div class="text-2xl sm:text-3xl font-bold text-red-700"> </div> <div class="text-xs sm:text-sm text-red-600">Nebelkerzen</div></div>'),vt=g('<div class="flex items-center justify-center text-xs font-medium text-white transition-all"><!></div>'),ut=g('<button><span class="w-3 h-3 rounded-full inline-block shrink-0"></span> </button>'),ct=g('<div class="bg-white rounded-lg border p-4 sm:p-6 mb-8"><h2 class="font-bold mb-4">Umsetzungsquote</h2> <div class="flex rounded-full overflow-hidden h-8 mb-4"></div> <div class="flex flex-wrap gap-3 sm:gap-4 text-sm"></div></div>'),gt=g("<option> </option>"),bt=g('<button class="text-sm text-blue-600 hover:underline p-1">Filter zurücksetzen</button>'),ft=g('<span class="text-gray-400 text-xs"></span>'),mt=g('<tr class="hover:bg-gray-50"><td class="px-4 py-3"><a class="text-blue-600 hover:underline font-mono text-xs"> </a></td><td class="px-4 py-3 max-w-md truncate"> </td><td class="px-4 py-3 text-gray-500 whitespace-nowrap"> </td><td class="px-4 py-3"><!></td></tr>'),pt=g('<a class="block bg-white rounded-lg border p-4 hover:shadow-md transition-shadow"><div class="flex items-start justify-between gap-2 mb-2"><span class="font-mono text-xs text-blue-600"> </span> <span class="text-xs text-gray-500 shrink-0"> </span></div> <div class="text-sm text-gray-700 line-clamp-2 mb-2"> </div> <!></a>'),xt=g('<div class="flex items-center gap-4 mb-6"><div class="w-3 h-12 rounded shrink-0"></div> <div class="min-w-0"><h1 class="text-2xl font-bold"> </h1> <span class="text-sm text-gray-500"> </span></div> <a href="/fraktionen" class="ml-auto text-sm text-blue-600 hover:underline shrink-0">← Alle Fraktionen</a></div> <div class="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4 mb-8"><div class="bg-white rounded-lg border p-4"><div class="text-2xl sm:text-3xl font-bold"> </div> <div class="text-xs sm:text-sm text-gray-500">Anträge gesamt</div></div> <div class="bg-white rounded-lg border p-4"><div class="text-2xl sm:text-3xl font-bold"> </div> <div class="text-xs sm:text-sm text-gray-500">Mit Bewertung</div></div> <!> <!></div> <!> <div class="flex flex-wrap gap-3 sm:gap-4 mb-4 items-center"><select class="border rounded px-3 py-2 text-sm"><option>Alle Jahre</option><!></select> <!> <span class="text-sm text-gray-500 sm:ml-auto"> </span></div> <div class="hidden md:block bg-white rounded-lg border overflow-hidden"><table class="w-full text-sm"><thead class="bg-gray-50 text-left"><tr><th class="px-4 py-3 font-medium">Aktenzeichen</th><th class="px-4 py-3 font-medium">Betreff</th><th class="px-4 py-3 font-medium">Datum</th><th class="px-4 py-3 font-medium">Umsetzung</th></tr></thead><tbody class="divide-y"></tbody></table></div> <div class="md:hidden space-y-3"></div>',1),_t=g('<div class="max-w-6xl mx-auto"><!></div>');function Ut(H,L){Ee(L,!0);const S=()=>Ce(Ye,"$page",se),[se,m]=qe();let o=P(null),$=P(!0),E=P(null),W=P(""),z=P(""),j=O(()=>S().params.kuerzel);async function q(){F($,!0),F(E,null);try{F(o,await et(e(j),e(W)||void 0),!0)}catch(v){F(E,v.message,!0)}F($,!1)}Pe(q),Re(()=>{e(j)&&q()});let C=O(()=>e(o)?.antraege.filter(v=>!e(z)||v.umsetzung_bewertung===e(z))??[]);var G=_t();Ge("1jei61n",v=>{Se(()=>{We.title=`${e(o)?.partei?.name??e(j)??""} — Antragstracker Hagen`})});var K=r(G);{var T=v=>{var A=it();d(v,A)},M=v=>{var A=lt(),D=r(A);t(A),p(()=>i(D,`Fehler: ${e(E)??""}`)),d(v,A)},Q=v=>{var A=xt(),D=ge(A),te=r(D),re=s(te,2),X=r(re),Y=r(X,!0);t(X);var I=s(X,2),ne=r(I,!0);t(I),t(re),R(2),t(D);var ie=s(D,2),le=r(ie),me=r(le),Ne=r(me,!0);t(me),R(2),t(le);var oe=s(le,2),pe=r(oe),Ue=r(pe,!0);t(pe),R(2),t(oe);var xe=s(oe,2);Z(xe,17,()=>e(o).umsetzung.filter(l=>l.bewertung==="erfuellt"),V,(l,a)=>{var n=ot(),u=r(n),c=r(u,!0);t(u),R(2),t(n),p(()=>i(c,e(a).anzahl)),d(l,n)});var Ze=s(xe,2);Z(Ze,17,()=>e(o).umsetzung.filter(l=>l.bewertung==="nebelkerze"),V,(l,a)=>{var n=dt(),u=r(n),c=r(u,!0);t(u),R(2),t(n),p(()=>i(c,e(a).anzahl)),d(l,n)}),t(ie);var _e=s(ie,2);{var Ve=l=>{var a=ct(),n=s(r(a),2);Z(n,21,()=>e(o).umsetzung,V,(c,x)=>{const f=O(()=>fe[e(x).bewertung]),h=O(()=>e(x).anzahl/e(o).bewertet*100);var w=Be(),N=ge(w);{var B=_=>{var b=vt(),J=r(b);{var k=U=>{var $e=Oe();p(Ie=>i($e,`${e(f).label??""} ${Ie??""}%`),[()=>e(h).toFixed(0)]),d(U,$e)};y(J,U=>{e(h)>8&&U(k)})}t(b),p(U=>{ae(b,`width: ${e(h)??""}%; background-color: ${e(f).farbe??""};`),be(b,"title",`${e(f).label??""}: ${e(x).anzahl??""} (${U??""}%)`)},[()=>e(h).toFixed(1)]),d(_,b)};y(N,_=>{e(f)&&e(h)>0&&_(B)})}d(c,w)}),t(n);var u=s(n,2);Z(u,21,()=>e(o).umsetzung,V,(c,x)=>{const f=O(()=>fe[e(x).bewertung]);var h=Be(),w=ge(h);{var N=B=>{var _=ut();let b;var J=r(_),k=s(J);t(_),p(()=>{b=Qe(_,1,"flex items-center gap-1.5 hover:opacity-70 transition-opacity p-1",null,b,{"opacity-40":e(z)&&e(z)!==e(x).bewertung}),ae(J,`background-color: ${e(f).farbe??""}`),i(k,` ${e(f).label??""}: ${e(x).anzahl??""}`)}),ce("click",_,()=>F(z,e(z)===e(x).bewertung?"":e(x).bewertung,!0)),d(B,_)};y(w,B=>{e(f)&&B(N)})}d(c,h)}),t(u),t(a),d(l,a)};y(_e,l=>{e(o).bewertet>0&&l(Ve)})}var de=s(_e,2),ee=r(de),ve=r(ee);ve.value=ve.__value="";var He=s(ve);Z(He,17,()=>e(o).jahre,V,(l,a)=>{var n=gt(),u=r(n,!0);t(n);var c={};p(()=>{i(u,e(a)),c!==(c=e(a))&&(n.value=(n.__value=e(a))??"")}),d(l,n)}),t(ee);var he=s(ee,2);{var Le=l=>{var a=bt();ce("click",a,()=>F(z,"")),d(l,a)};y(he,l=>{e(z)&&l(Le)})}var we=s(he,2),je=r(we);t(we),t(de);var ue=s(de,2),ke=r(ue),ze=s(r(ke));Z(ze,21,()=>e(C),V,(l,a)=>{var n=mt(),u=r(n),c=r(u),x=r(c,!0);t(c),t(u);var f=s(u),h=r(f,!0);t(f);var w=s(f),N=r(w,!0);t(w);var B=s(w),_=r(B);{var b=k=>{De(k,{get kategorie(){return e(a).umsetzung_bewertung},get score(){return e(a).umsetzung_score}})},J=k=>{var U=ft();d(k,U)};y(_,k=>{e(a).umsetzung_bewertung?k(b):k(J,-1)})}t(B),t(n),p(k=>{be(c,"href",`/vorlagen/${e(a).id??""}`),i(x,e(a).aktenzeichen),i(h,e(a).betreff),i(N,k)},[()=>Ke(e(a).datum_eingang)]),d(l,n)}),t(ze),t(ke),t(ue);var ye=s(ue,2);Z(ye,21,()=>e(C),V,(l,a)=>{var n=pt(),u=r(n),c=r(u),x=r(c,!0);t(c);var f=s(c,2),h=r(f,!0);t(f),t(u);var w=s(u,2),N=r(w,!0);t(w);var B=s(w,2);{var _=b=>{De(b,{get kategorie(){return e(a).umsetzung_bewertung},get score(){return e(a).umsetzung_score}})};y(B,b=>{e(a).umsetzung_bewertung&&b(_)})}t(n),p(b=>{be(n,"href",`/vorlagen/${e(a).id??""}`),i(x,e(a).aktenzeichen),i(h,b),i(N,e(a).betreff)},[()=>Ke(e(a).datum_eingang)]),d(l,n)}),t(ye),p(()=>{ae(te,`background-color: ${(e(o).partei.farbe||"#6b7280")??""}`),i(Y,e(o).partei.name),i(ne,e(o).partei.kuerzel),i(Ne,e(o).total_antraege),i(Ue,e(o).bewertet),i(je,`${e(C).length??""} Anträge`)}),ce("change",ee,q),Xe(ee,()=>e(W),l=>F(W,l)),d(v,A)};y(K,v=>{e($)&&!e(o)?v(T):e(E)?v(M,1):e(o)&&v(Q,2)})}t(G),d(H,G),Me(),m()}Je(["click","change"]);export{Ut as component};