Verwaltung: Übersicht als Startansicht, Reiter unter den Titel
Die Reiter standen neben dem Titel. Das las sich wie eine einzige lange
Zeile, in der der Titel nur der erste von sechs Knöpfen zu sein schien --
man sah nicht auf einen Blick, in welchem Bereich man war. Jetzt zwei
Zeilen: oben WO man ist, darunter WOHIN man kann.
Die Verwaltung öffnete bisher mit der Anfragenliste. Eine Liste ist eine
Ablage: Sie zeigt, WAS es gibt, nicht was zu TUN ist. Neu ist ein
Cockpit, das in drei Stufen antwortet -- was auf mich wartet, was beim
Kunden liegt, wie es ums Geld steht -- und dann laufende Projekte mit
Fortschritt sowie den Verlauf.
Zwei Grundsätze machen die Zahlen brauchbar: Getrennt nach 'wartet auf
mich' und 'wartet auf den Kunden' (zwölf offene Punkte sind entspannt,
wenn elf beim Kunden liegen). Und das ALTER färbt, nicht die Menge --
vier neue Anfragen sind kein Problem, eine seit sechs Tagen liegende
schon. Ein offener Widerruf ist immer rot, weil eine gesetzliche Frist
läuft.
Jede Kachel ist ein echter <button> und führt in den passenden Reiter.
Farbe ist nie der einzige Träger: Neben jedem farbigen Zustand steht der
Text ('älteste seit 8 Tagen').
Drei Fehler dabei gefunden und behoben:
- Der Titel wurde nur beim Klicken gesetzt. Frisch geladen zeigte die
Seite das Cockpit, während darüber noch 'Projektanfragen' stand. Die
Zuordnung Reiter->Titel liegt jetzt ausserhalb des Klick-Zuhörers.
- '.wd h2' überstimmte '.vw-ub-h': 44px Überschrift über 33px Zahl, die
Seite las sich wie ein Plakat. Dieselbe Spezifitätsfalle wie früher
bei '.wd a'.
- Eine Antwort mit ok:true aber ohne Inhalt riss die ganze Verwaltung
mit. Wird jetzt abgefangen -- ein halber Server ist ein realistischer
Fall.
Geprüft: 22 Serverprüfungen gegen eine echte Datenbank (inkl. vier
Fällen, die belegen, dass die Rechteprüfung wirklich greift), 56 im
Browser auf Computer und Handy, 69 im bestehenden Verwaltungstest,
0 Fundstellen im Design-/WCAG-Test, 5 im Farbsystemtest.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -577,3 +577,150 @@ export async function einstellungenPruefen(req, res) {
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
ÜBERSICHT — das Cockpit
|
||||
|
||||
Die Verwaltung öffnete bisher mit einer Liste von Anfragen. Eine Liste
|
||||
ist eine Ablage, keine Auskunft: Sie zeigt, WAS es gibt, nicht was zu
|
||||
TUN ist. Wer morgens hereinkommt, muss durch fünf Reiter klicken, um
|
||||
sich ein Bild zu machen — und übersieht dabei zuverlässig das eine,
|
||||
das seit vier Tagen liegt.
|
||||
|
||||
Diese Antwort liefert alle Zahlen in EINER Abfrage. Fünf einzelne
|
||||
Abrufe würden die Seite ruckelnd aufbauen und wären beim Wechsel
|
||||
zwischen den Reitern jedes Mal fällig.
|
||||
|
||||
ZWEI GRUNDSÄTZE, DIE DIE ZAHLEN BRAUCHBAR MACHEN
|
||||
|
||||
1. GETRENNT NACH "wartet auf mich" und "wartet auf den Kunden".
|
||||
Eine Gesamtzahl offener Vorgänge sagt nichts: Zwölf offene Punkte
|
||||
sind entspannt, wenn elf davon beim Kunden liegen — und
|
||||
alarmierend, wenn alle elf bei mir liegen.
|
||||
|
||||
2. DAS ALTER ZÄHLT, NICHT DIE MENGE. "Vier neue Anfragen" ist eine
|
||||
Mengenangabe. "Die älteste wartet seit sechs Tagen" ist eine
|
||||
Aussage über Zuverlässigkeit — und genau daran misst ein Kunde,
|
||||
ob man sein Geld wert ist.
|
||||
===================================================================== */
|
||||
export async function uebersicht(req, res) {
|
||||
const p = await darf(req, "WD_ANFRAGEN_VIEW");
|
||||
if (!p.erlaubt) return json(res, { ok: false, error: "Kein Zugriff." }, p.grund);
|
||||
|
||||
const eine = (sql, ...w) => db.prepare(sql).get(...w) || {};
|
||||
const viele = (sql, ...w) => db.prepare(sql).all(...w);
|
||||
|
||||
/* ---- Was auf MICH wartet ---- */
|
||||
const anfragenOffen = eine(
|
||||
`SELECT COUNT(*) AS n, MIN(erstellt_am) AS aeltestes
|
||||
FROM wd_anfragen WHERE archiviert = 0 AND status IN ('neu','rueckfrage')`
|
||||
);
|
||||
const nachrichtenOffen = eine(
|
||||
`SELECT COUNT(*) AS n, MIN(erstellt_am) AS aeltestes
|
||||
FROM wd_postfach WHERE gelesen_admin = 0 AND autor = 'kunde'`
|
||||
);
|
||||
const wuenscheOffen = eine(
|
||||
`SELECT COUNT(*) AS n, MIN(erstellt_am) AS aeltestes
|
||||
FROM wd_aenderungen WHERE status IN ('eingereicht','geprueft')`
|
||||
);
|
||||
const widerrufeOffen = eine(
|
||||
`SELECT COUNT(*) AS n, MIN(eingegangen_am) AS aeltestes
|
||||
FROM wd_widerrufe WHERE status = 'eingegangen'`
|
||||
);
|
||||
const projekteBeiMir = eine(
|
||||
`SELECT COUNT(*) AS n FROM wd_projekte
|
||||
WHERE archiviert = 0 AND wartet_auf = 'dogfather'
|
||||
AND status NOT IN ('uebergeben','abgebrochen')`
|
||||
);
|
||||
|
||||
/* ---- Was auf den KUNDEN wartet ---- */
|
||||
const projekteBeiKunde = eine(
|
||||
`SELECT COUNT(*) AS n FROM wd_projekte
|
||||
WHERE archiviert = 0 AND wartet_auf = 'kunde'
|
||||
AND status NOT IN ('uebergeben','abgebrochen')`
|
||||
);
|
||||
const aufgabenBeiKunde = eine(
|
||||
`SELECT COUNT(*) AS n FROM wd_aufgaben
|
||||
WHERE wer_dran = 'kunde' AND status NOT IN ('erledigt','entfaellt')
|
||||
AND nicht_enthalten = 0`
|
||||
);
|
||||
const zahlungenOffen = eine(
|
||||
`SELECT COUNT(*) AS n, COALESCE(SUM(betrag_cent),0) AS summe, MIN(erstellt_am) AS aeltestes
|
||||
FROM wd_zahlungen WHERE status IN ('offen','freigegeben')`
|
||||
);
|
||||
|
||||
/* ---- Geld ---- */
|
||||
const monatsAnfang = new Date();
|
||||
monatsAnfang.setDate(1);
|
||||
monatsAnfang.setHours(0, 0, 0, 0);
|
||||
const bezahltMonat = eine(
|
||||
`SELECT COUNT(*) AS n, COALESCE(SUM(betrag_cent),0) AS brutto,
|
||||
COALESCE(SUM(netto_cent),0) AS netto,
|
||||
COALESCE(SUM(gebuehr_cent),0) AS gebuehr
|
||||
FROM wd_zahlungen WHERE status = 'bezahlt' AND bezahlt_am >= ?`,
|
||||
monatsAnfang.toISOString()
|
||||
);
|
||||
const bezahltGesamt = eine(
|
||||
`SELECT COUNT(*) AS n, COALESCE(SUM(betrag_cent),0) AS brutto
|
||||
FROM wd_zahlungen WHERE status = 'bezahlt'`
|
||||
);
|
||||
|
||||
/* ---- Laufende Projekte mit ihrem Stand ---- */
|
||||
const projekte = viele(
|
||||
`SELECT p.id, p.nummer, p.titel, p.status, p.wartet_auf, p.richttermin,
|
||||
k.name AS kunde_name,
|
||||
(SELECT COUNT(*) FROM wd_aufgaben a
|
||||
WHERE a.projekt_id = p.id AND a.status <> 'entfaellt') AS aufg_gesamt,
|
||||
(SELECT COUNT(*) FROM wd_aufgaben a
|
||||
WHERE a.projekt_id = p.id AND a.status = 'erledigt') AS aufg_fertig
|
||||
FROM wd_projekte p JOIN wd_kunden k ON k.id = p.kunde_id
|
||||
WHERE p.archiviert = 0 AND p.status NOT IN ('uebergeben','abgebrochen')
|
||||
ORDER BY p.erstellt_am DESC LIMIT 8`
|
||||
);
|
||||
|
||||
/* ---- Was zuletzt geschah ---- */
|
||||
const verlaufsListe = viele(
|
||||
`SELECT bereich, aktion, wer, erstellt_am
|
||||
FROM wd_verlauf ORDER BY erstellt_am DESC LIMIT 12`
|
||||
);
|
||||
|
||||
/* ---- Bestand ---- */
|
||||
const bestand = {
|
||||
kunden: eine(`SELECT COUNT(*) AS n FROM wd_kunden WHERE archiviert = 0`).n,
|
||||
projekte: eine(`SELECT COUNT(*) AS n FROM wd_projekte WHERE archiviert = 0`).n,
|
||||
anfragenGesamt: eine(`SELECT COUNT(*) AS n FROM wd_anfragen WHERE archiviert = 0`).n,
|
||||
};
|
||||
|
||||
return json(res, {
|
||||
ok: true,
|
||||
beiMir: {
|
||||
anfragen: anfragenOffen,
|
||||
nachrichten: nachrichtenOffen,
|
||||
wuensche: wuenscheOffen,
|
||||
widerrufe: widerrufeOffen,
|
||||
projekte: projekteBeiMir.n,
|
||||
},
|
||||
beiKunde: {
|
||||
projekte: projekteBeiKunde.n,
|
||||
aufgaben: aufgabenBeiKunde.n,
|
||||
zahlungen: zahlungenOffen,
|
||||
zahlungenText: centZuText(zahlungenOffen.summe || 0),
|
||||
},
|
||||
geld: {
|
||||
monat: { ...bezahltMonat, bruttoText: centZuText(bezahltMonat.brutto || 0),
|
||||
nettoText: centZuText(bezahltMonat.netto || 0) },
|
||||
gesamt: { ...bezahltGesamt, bruttoText: centZuText(bezahltGesamt.brutto || 0) },
|
||||
/* Ehrlich beziffern, was PayPal einbehalten hat. Der Unterschied
|
||||
zwischen Umsatz und dem, was ankommt, gehoert sichtbar gemacht --
|
||||
sonst rechnet man am Jahresende mit der falschen Zahl. */
|
||||
gebuehrenText: centZuText(bezahltMonat.gebuehr || 0),
|
||||
},
|
||||
projekte,
|
||||
verlauf: verlaufsListe,
|
||||
bestand,
|
||||
paypal: {
|
||||
eingerichtet: paypal.istEingerichtet("einmal"),
|
||||
live: paypal.istLive(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user