Kennzahlen der Verwaltung: aus stillen Zahlen werden Werkzeuge

Rueckmeldung 22.08.2026: "mach es noch krasser noch detaillierter noch
viel besser und perfekter und auch die kacheln viel geiler."

Die Kacheln waren eine Reihe stiller Zahlen. Zahlen, die man nur anschauen
kann, sind Dekoration. Jetzt:

* Jede Kachel FILTERT die Liste darunter beim Antippen. Als echter
  <button>, nicht als div mit Klickzuhoerer -- sonst ist sie mit der
  Tastatur nicht erreichbar und ein Screenreader kuendigt sie nicht als
  Bedienelement an.
* Jede Kachel sagt, was zu TUN ist, nicht nur wie viele es sind:
  "warten auf dich" / "du bist dran" / "Kunde ist dran" /
  "Projekt anlegen".
* Neue Kachel: der aelteste unbearbeitete Vorgang in Tagen. Das ist die
  ehrlichste Kennzahl ueberhaupt -- sie sagt nicht, wie viel man
  geschafft hat, sondern wie lange jemand schon auf Antwort wartet.
  Genau daran misst ein Kunde Zuverlaessigkeit. Ab sieben Tagen rot.
  Diese eine Kachel filtert bewusst NICHT und ist deshalb auch kein
  Knopf: sie zeigt einen Zustand, keinen Status.
* Eine Null wird gedaempft dargestellt. Sonst konkurriert "0 abgelehnt"
  optisch mit "3 neu" -- und genau die Drei ist die, auf die man schauen
  soll. Was nichts zu tun gibt, soll auch nicht leuchten.
* Gold bleibt ausschliesslich dem echten Handlungsbedarf vorbehalten.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-22 21:44:37 +02:00
co-authored by Claude Opus 5
parent d93625439a
commit 77674cb801
+124 -17
View File
@@ -35,16 +35,60 @@
.vw-leiste h1 { margin: 0; font-size: clamp(1.4rem, 3.4vw, 2rem); }
.vw-wer { margin-left: auto; display: flex; align-items: center; gap: .6rem; font-size: .9rem; color: var(--wd-text-soft); }
/* ---------- Kennzahlen ---------- */
.vw-zahlen { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: .7rem; margin-bottom: 1.4rem; }
.vw-zahl {
padding: .9rem 1rem; border-radius: 14px;
background: rgba(16,26,41,.6); border: 1px solid var(--wd-rand);
text-align: left;
/* ---------- Kennzahlen, gehobene Fassung ----------
Die erste Fassung war eine Reihe stiller Zahlen. Zahlen, die man
nur anschauen kann, sind Dekoration. Diese hier sind Werkzeuge:
jede Kachel filtert die Liste darunter, sobald man sie antippt.
Eine Null wird bewusst gedaempft. Sonst konkurriert "0 abgelehnt"
optisch mit "3 neu" -- und genau die Drei ist die, auf die man
schauen soll. Was nichts zu tun gibt, soll auch nicht leuchten. */
.vw-zahlen {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: .7rem; margin-bottom: 1.4rem;
}
.vw-zahl-wert { font-family: var(--font-head); font-weight: 800; font-size: 1.7rem; line-height: 1; color: var(--wd-blau); }
.vw-zahl-name { font-size: .78rem; letter-spacing: .06em; color: var(--wd-text-soft); margin-top: .3rem; }
.vw-zahl {
position: relative; overflow: hidden;
display: block; width: 100%; text-align: left; cursor: pointer;
padding: .95rem 1.05rem;
border-radius: 16px;
background: linear-gradient(180deg, rgba(20,32,50,.62), rgba(9,15,26,.8));
border: 1px solid var(--wd-rand);
transition: border-color var(--wd-zeit-schnell) ease,
transform var(--wd-zeit-schnell) ease,
box-shadow var(--wd-zeit) ease;
}
.vw-zahl:hover { border-color: var(--wd-rand-stark); transform: translateY(-2px); }
.vw-zahl.vw-an {
border-color: var(--wd-blau);
box-shadow: 0 0 0 1px var(--wd-blau) inset, 0 14px 34px -20px rgba(127,208,232,.7);
}
.vw-zahl-kopf { display: flex; align-items: center; gap: .45rem; margin-bottom: .35rem; }
.vw-zahl-symbol { font-size: 1rem; line-height: 1; opacity: .85; }
.vw-zahl-wert {
font-family: var(--font-head); font-weight: 800;
font-size: clamp(1.5rem, 3.4vw, 1.9rem); line-height: 1;
color: var(--wd-blau); font-variant-numeric: tabular-nums;
}
.vw-zahl-name { font-size: .78rem; letter-spacing: .03em; color: var(--wd-text-soft); }
.vw-zahl-zusatz { font-size: .74rem; color: var(--wd-text-soft); margin-top: .3rem; display: block; }
/* Null: alles gedaempft, damit es nicht um Aufmerksamkeit buhlt. */
.vw-zahl--null { opacity: .5; }
.vw-zahl--null .vw-zahl-wert { color: var(--wd-text-soft); }
/* Handlungsbedarf: Gold, und nur hier. */
.vw-zahl--achtung { border-color: rgba(232,200,119,.34); background: linear-gradient(180deg, rgba(232,200,119,.07), rgba(9,15,26,.8)); }
.vw-zahl--achtung .vw-zahl-wert { color: var(--wd-gold); }
.vw-zahl--achtung .vw-zahl-zusatz { color: var(--wd-gold); }
/* Dringend: etwas liegt zu lange. */
.vw-zahl--dringend { border-color: rgba(255,157,157,.4); background: linear-gradient(180deg, rgba(255,157,157,.07), rgba(9,15,26,.8)); }
.vw-zahl--dringend .vw-zahl-wert,
.vw-zahl--dringend .vw-zahl-zusatz { color: var(--wd-fehler); }
/* ---------- Filter ---------- */
.vw-filter { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem; align-items: center; }
@@ -453,20 +497,83 @@
}
function zeichneZahlen(zaehler) {
var offen = (zaehler.neu || 0) + (zaehler.rueckfrage || 0);
var gesamt = Object.keys(zaehler).reduce(function (n, k) { return n + zaehler[k]; }, 0);
/* Der aelteste unbearbeitete Vorgang ist die ehrlichste Kennzahl
ueberhaupt: Er sagt nicht, wie viel man geschafft hat, sondern wie
lange jemand schon auf eine Antwort wartet. Genau daran misst ein
Kunde, ob man zuverlaessig ist. */
var wartend = alleAnfragen.filter(function (a) {
return a.status === "neu" || a.status === "rueckfrage";
});
var aeltesteTage = wartend.reduce(function (max, a) {
return Math.max(max, tageSeit(a.erstellt_am));
}, 0);
var kacheln = [
{ wert: zaehler.neu || 0, name: "neu, ungelesen", achtung: (zaehler.neu || 0) > 0 },
{ wert: zaehler.pruefung || 0, name: "in Prüfung" },
{ wert: zaehler.angebot || 0, name: "Angebot raus" },
{ wert: zaehler.angenommen || 0, name: "angenommen" },
{ wert: gesamt, name: "insgesamt offen" },
{
wert: zaehler.neu || 0, name: "neu, ungelesen", symbol: "📬", filter: "neu",
achtung: (zaehler.neu || 0) > 0,
zusatz: (zaehler.neu || 0) > 0 ? "warten auf dich" : "alles gesichtet",
},
{
wert: zaehler.pruefung || 0, name: "in Prüfung", symbol: "🔍", filter: "pruefung",
zusatz: "du bist dran",
},
{
wert: zaehler.angebot || 0, name: "Angebot raus", symbol: "📄", filter: "angebot",
zusatz: "Kunde ist dran",
},
{
wert: zaehler.angenommen || 0, name: "angenommen", symbol: "✅", filter: "angenommen",
zusatz: "Projekt anlegen",
},
{
/* Diese Kachel filtert bewusst NICHT -- sie zeigt einen Zustand,
keinen Status. Deshalb ist sie auch nicht anklickbar. */
wert: aeltesteTage, name: aeltesteTage === 1 ? "Tag wartet der Älteste" : "Tage wartet der Älteste",
symbol: "⏳", filter: null,
dringend: aeltesteTage >= 7,
zusatz: aeltesteTage === 0 ? "nichts liegen geblieben"
: aeltesteTage >= 7 ? "das ist zu lang" : "noch im Rahmen",
},
{
wert: gesamt, name: "insgesamt offen", symbol: "📊", filter: "",
zusatz: "alle anzeigen",
},
];
document.getElementById("vw-zahlen").innerHTML = kacheln.map(function (k) {
return '<div class="vw-zahl' + (k.achtung ? " vw-zahl--achtung" : "") + '">' +
'<div class="vw-zahl-wert">' + k.wert + "</div>" +
'<div class="vw-zahl-name">' + s(k.name) + "</div></div>";
var klassen = "vw-zahl";
if (k.dringend) klassen += " vw-zahl--dringend";
else if (k.achtung) klassen += " vw-zahl--achtung";
else if (!k.wert) klassen += " vw-zahl--null";
if (k.filter !== null && filterStatus === k.filter && !filterArchiv) klassen += " vw-an";
var innen =
'<span class="vw-zahl-kopf">' +
'<span class="vw-zahl-symbol" aria-hidden="true">' + k.symbol + "</span>" +
'<span class="vw-zahl-wert">' + k.wert + "</span>" +
"</span>" +
'<span class="vw-zahl-name">' + s(k.name) + "</span>" +
'<span class="vw-zahl-zusatz">' + s(k.zusatz) + "</span>";
/* Anklickbares wird ein echter Knopf, nicht ein div mit
Klickzuhoerer -- sonst ist es mit der Tastatur nicht erreichbar
und ein Screenreader kuendigt es nicht als Bedienelement an. */
return k.filter === null
? '<div class="' + klassen + '" style="cursor:default">' + innen + "</div>"
: '<button type="button" class="' + klassen + '" data-kachel="' + k.filter +
'" aria-label="' + s(k.wert + " " + k.name + " anzeigen") + '">' + innen + "</button>";
}).join("");
document.querySelectorAll("[data-kachel]").forEach(function (b) {
b.addEventListener("click", function () {
filterStatus = b.dataset.kachel;
filterArchiv = false;
ladeAnfragen();
});
});
}
function zeichneFilter(zaehler) {