feat(ui): globaler Aktivitäts-Feed auf dem Dashboard
Karte "Letzte Aktivität" nutzt GET /historie (kein neuer Endpunkt), statt Ereignisse nur pro Objekt in der Akte sehen zu können. Lesbare Ereignis-Labels (EREIGNIS_LABEL) auch in AktePage statt rohem ereignistyp-String. Angeregt durch Resgrid-Doku (Activity-Log-Panel). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -116,6 +116,10 @@ Aktueller Stand vor Redesign: 39 Seiten-/Komponenten-Dateien, ~7.000 Zeilen, ein
|
|||||||
behobene Vorab-Lücke: `.text-success`/`.text-danger` fehlten in
|
behobene Vorab-Lücke: `.text-success`/`.text-danger` fehlten in
|
||||||
`global.css`, obwohl in `AktePage.tsx` bereits referenziert.
|
`global.css`, obwohl in `AktePage.tsx` bereits referenziert.
|
||||||
|
|
||||||
|
Ergänzung 2026-09-06 (Resgrid-Doku als Ideengeber): Karte "Letzte Aktivität"
|
||||||
|
über `GET /historie` (kein neuer Endpunkt), globales Aktivitäts-Feed statt
|
||||||
|
nur pro-Objekt-Historie in der Akte.
|
||||||
|
|
||||||
## UI-005 — Objektakte modernisiert
|
## UI-005 — Objektakte modernisiert
|
||||||
|
|
||||||
- **Ziel:** `AktePage.tsx` (bereits FILE-006/Akte-Aggregation) optisch/strukturell
|
- **Ziel:** `AktePage.tsx` (bereits FILE-006/Akte-Aggregation) optisch/strukturell
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/** Spiegelt alle ereignistyp-Strings aus den Backend-Services/Endpunkten
|
||||||
|
* (app/services/*.py, app/api/v1/endpoints/*.py) - gemeinsam genutzt vom
|
||||||
|
* globalen Aktivitäts-Feed (Dashboard) und der Objektakte-Historie. */
|
||||||
|
export const EREIGNIS_LABEL: Record<string, string> = {
|
||||||
|
fehlbestand_entstanden: "Fehlbestand entstanden",
|
||||||
|
fehlbestand_erledigt: "Fehlbestand erledigt",
|
||||||
|
geraet_status_geaendert: "Gerätestatus geändert",
|
||||||
|
istmenge_erfasst: "Istmenge erfasst",
|
||||||
|
kontrolle_abgebrochen: "Kontrolle abgebrochen",
|
||||||
|
kontrolle_abgeschlossen: "Kontrolle abgeschlossen",
|
||||||
|
kontrolle_gestartet: "Kontrolle gestartet",
|
||||||
|
kontrolle_uebernommen: "Kontrolle übernommen",
|
||||||
|
mangel_gemeldet: "Mangel gemeldet",
|
||||||
|
mangel_status_geaendert: "Mangel-Status geändert",
|
||||||
|
mindermenge_abgelaufen: "Mindermenge abgelaufen",
|
||||||
|
mindermenge_beendet_durch_erledigung: "Mindermenge beendet (Fehlbestand erledigt)",
|
||||||
|
mindermenge_genehmigt: "Mindermenge genehmigt",
|
||||||
|
nachfuellung_erfasst: "Nachfüllung erfasst",
|
||||||
|
objekt_geaendert: "Objekt geändert",
|
||||||
|
qualifikation_erfasst: "Qualifikation erfasst",
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import type { Akte } from "../api/types";
|
|||||||
import { DokumentePanel } from "../components/DokumentePanel";
|
import { DokumentePanel } from "../components/DokumentePanel";
|
||||||
import { ReadinessBadge } from "../components/status/ReadinessBadge";
|
import { ReadinessBadge } from "../components/status/ReadinessBadge";
|
||||||
import { GRUND_TEXT } from "../components/status/gruendeText";
|
import { GRUND_TEXT } from "../components/status/gruendeText";
|
||||||
|
import { EREIGNIS_LABEL } from "../components/status/ereignisText";
|
||||||
|
|
||||||
const MANGEL_PRIORITAET_LABEL: Record<string, string> = {
|
const MANGEL_PRIORITAET_LABEL: Record<string, string> = {
|
||||||
niedrig: "Niedrig",
|
niedrig: "Niedrig",
|
||||||
@@ -151,7 +152,7 @@ export function AktePage() {
|
|||||||
<ul>
|
<ul>
|
||||||
{akte.historie.map((h) => (
|
{akte.historie.map((h) => (
|
||||||
<li key={h.id}>
|
<li key={h.id}>
|
||||||
{new Date(h.zeitpunkt).toLocaleString("de-DE")} — {h.ereignistyp}
|
{new Date(h.zeitpunkt).toLocaleString("de-DE")} — {EREIGNIS_LABEL[h.ereignistyp] ?? h.ereignistyp}
|
||||||
{h.benutzer_name && <span className="text-muted"> ({h.benutzer_name})</span>}
|
{h.benutzer_name && <span className="text-muted"> ({h.benutzer_name})</span>}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import { useEffect, useState } from "react";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import { apiRequest } from "../api/client";
|
import { apiRequest } from "../api/client";
|
||||||
import type { Mangel } from "../api/types";
|
import type { HistorieEintrag, Mangel } from "../api/types";
|
||||||
import { InspectionStatus } from "../components/status/InspectionStatus";
|
import { InspectionStatus } from "../components/status/InspectionStatus";
|
||||||
import { InventoryStatus } from "../components/status/InventoryStatus";
|
import { InventoryStatus } from "../components/status/InventoryStatus";
|
||||||
import type { Readiness } from "../components/status/ReadinessBadge";
|
import type { Readiness } from "../components/status/ReadinessBadge";
|
||||||
import { ReadinessBadge } from "../components/status/ReadinessBadge";
|
import { ReadinessBadge } from "../components/status/ReadinessBadge";
|
||||||
import { GRUND_TEXT } from "../components/status/gruendeText";
|
import { GRUND_TEXT } from "../components/status/gruendeText";
|
||||||
|
import { EREIGNIS_LABEL } from "../components/status/ereignisText";
|
||||||
|
|
||||||
interface Kennzahlen {
|
interface Kennzahlen {
|
||||||
anzahl_offener_fehlbestaende: number;
|
anzahl_offener_fehlbestaende: number;
|
||||||
@@ -82,6 +83,7 @@ export function DashboardPage() {
|
|||||||
const [kennzahlen, setKennzahlen] = useState<Kennzahlen | null>(null);
|
const [kennzahlen, setKennzahlen] = useState<Kennzahlen | null>(null);
|
||||||
const [maengel, setMaengel] = useState<Mangel[]>([]);
|
const [maengel, setMaengel] = useState<Mangel[]>([]);
|
||||||
const [qualifikationsablaeufe, setQualifikationsablaeufe] = useState<Qualifikationsablauf[]>([]);
|
const [qualifikationsablaeufe, setQualifikationsablaeufe] = useState<Qualifikationsablauf[]>([]);
|
||||||
|
const [aktivitaet, setAktivitaet] = useState<HistorieEintrag[]>([]);
|
||||||
const [laedt, setLaedt] = useState(true);
|
const [laedt, setLaedt] = useState(true);
|
||||||
const [fehler, setFehler] = useState<string | null>(null);
|
const [fehler, setFehler] = useState<string | null>(null);
|
||||||
const [detailsOffen, setDetailsOffen] = useState(false);
|
const [detailsOffen, setDetailsOffen] = useState(false);
|
||||||
@@ -95,14 +97,16 @@ export function DashboardPage() {
|
|||||||
apiRequest<Kennzahlen>("/dashboard/kennzahlen"),
|
apiRequest<Kennzahlen>("/dashboard/kennzahlen"),
|
||||||
apiRequest<Mangel[]>("/maengel"),
|
apiRequest<Mangel[]>("/maengel"),
|
||||||
apiRequest<Qualifikationsablauf[]>("/dashboard/qualifikationsablaeufe"),
|
apiRequest<Qualifikationsablauf[]>("/dashboard/qualifikationsablaeufe"),
|
||||||
|
apiRequest<HistorieEintrag[]>("/historie?limit=15"),
|
||||||
])
|
])
|
||||||
.then(([b, p, a, k, m, q]) => {
|
.then(([b, p, a, k, m, q, h]) => {
|
||||||
setBereitschaft(b);
|
setBereitschaft(b);
|
||||||
setPrueftermine(p);
|
setPrueftermine(p);
|
||||||
setAblaufdaten(a);
|
setAblaufdaten(a);
|
||||||
setKennzahlen(k);
|
setKennzahlen(k);
|
||||||
setMaengel(m);
|
setMaengel(m);
|
||||||
setQualifikationsablaeufe(q);
|
setQualifikationsablaeufe(q);
|
||||||
|
setAktivitaet(h);
|
||||||
})
|
})
|
||||||
.catch(() => setFehler("Dashboard-Daten konnten nicht geladen werden."))
|
.catch(() => setFehler("Dashboard-Daten konnten nicht geladen werden."))
|
||||||
.finally(() => setLaedt(false));
|
.finally(() => setLaedt(false));
|
||||||
@@ -313,6 +317,31 @@ export function DashboardPage() {
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="card">
|
||||||
|
<h3 style={{ marginTop: 0 }}>Letzte Aktivität</h3>
|
||||||
|
{aktivitaet.length === 0 ? (
|
||||||
|
<p className="text-muted">Noch keine Ereignisse.</p>
|
||||||
|
) : (
|
||||||
|
<ul className="card-list">
|
||||||
|
{aktivitaet.map((h) => (
|
||||||
|
<li key={h.id} className="row-between" style={{ padding: "0.3rem 0" }}>
|
||||||
|
<span>
|
||||||
|
{EREIGNIS_LABEL[h.ereignistyp] ?? h.ereignistyp}
|
||||||
|
{h.entitaet_typ === "objekt" && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
<Link to={`/akte/objekt/${h.entitaet_id}`}>#{h.entitaet_id}</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{h.benutzer_name && <span className="text-muted"> · {h.benutzer_name}</span>}
|
||||||
|
</span>
|
||||||
|
<span className="text-muted">{new Date(h.zeitpunkt).toLocaleString("de-DE")}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
Reference in New Issue
Block a user