KPI-Monitor zeigt Agenten/Online-Kacheln wieder an

Die KPI-Kacheln "Agenten" (registrierte Agenten) und "Online" (davon
in den letzten 10 Minuten aktiv, siehe AgentRepository::isPresent())
wurden auf der Monitor-Wand nie angezeigt. Statt eine eigene
Meldepflicht dafuer einzufuehren, leitet buildAgentCountKpis() beide
Werte direkt aus der ohnehin geladenen Agentenliste ab und stellt sie
den ueber /api/v1/kpis gemeldeten Kennzahlen voran.

Schliesst #39
This commit is contained in:
fds2610-bot 2026-08-04 12:51:32 +02:00
parent 5471940389
commit 042a535c78
4 changed files with 57 additions and 4 deletions

View file

@ -245,8 +245,10 @@ externe Datenbankserver (nur PHP/Python-Standardbibliothek) auskommen, um die
Klick-Modals. ✅ erledigt: Events/Systeme/KPIs kommen live von der API, ein neuer Klick-Modals. ✅ erledigt: Events/Systeme/KPIs kommen live von der API, ein neuer
SSE-Endpoint (`GET /api/v1/stream`, F13) stoesst Frontend-Updates an (Polling-Fallback in SSE-Endpoint (`GET /api/v1/stream`, F13) stoesst Frontend-Updates an (Polling-Fallback in
`office-realtime.js`), KPI-Monitor zeichnet eine eigene SVG-Sparkline (F10, kein Chart-Lib) `office-realtime.js`), KPI-Monitor zeichnet eine eigene SVG-Sparkline (F10, kein Chart-Lib)
sobald KPIs gemeldet wurden - vorher weiterhin der Platzhalter aus Phase 2 (F7 ist sobald KPIs gemeldet wurden. "Agenten" (registrierte Agenten) und "Online" (davon
inhaltlich weiterhin offen). praesent, isAgentPresent()) sind als erste zwei KPI-Kacheln immer sichtbar, direkt aus der
Agentenliste abgeleitet statt gemeldet (Issue #39) - darueber hinaus ist F7 inhaltlich
weiterhin offen, ohne gemeldete KPIs zeigt die Wand nur diese beiden Kacheln.
6. **Phase 5 – Agenten & Animation**: Zustandsmaschine, Laufwege, Tipp-/Lese-/Kaffee-Animation, 6. **Phase 5 – Agenten & Animation**: Zustandsmaschine, Laufwege, Tipp-/Lese-/Kaffee-Animation,
Reaktion auf neue Ticket-/Event-/Systemstatus-Meldungen. ✅ erledigt: `resolveAgentVisualState` Reaktion auf neue Ticket-/Event-/Systemstatus-Meldungen. ✅ erledigt: `resolveAgentVisualState`
bildet die von der API gemeldeten Zustaende auf Ort/Pose ab, Agenten-Avatare sind eine bildet die von der API gemeldeten Zustaende auf Ort/Pose ab, Agenten-Avatare sind eine

View file

@ -24,6 +24,7 @@ import {
buildSystemModalContent, buildSystemModalContent,
buildKpiTiles, buildKpiTiles,
buildKpiModalContent, buildKpiModalContent,
buildAgentCountKpis,
mapAgentsToDesks, mapAgentsToDesks,
normalizeAgentForDisplay, normalizeAgentForDisplay,
resolveAgentVisualState, resolveAgentVisualState,
@ -1401,6 +1402,11 @@ export function renderOfficeScene(container, data = {}) {
// is_present-Feld wie registrierte Agenten (Issue #19). // is_present-Feld wie registrierte Agenten (Issue #19).
poIsLive = isLive; poIsLive = isLive;
// "Agenten"/"Online" (Issue #39) stehen vor den gemeldeten KPIs (F7), damit sie auf der
// Wandkachel (MAX_KPI_TILES) immer sichtbar sind, statt von gemeldeten Kennzahlen verdraengt
// zu werden.
const displayKpis = [...buildAgentCountKpis(agents), ...kpis];
const groupedTickets = groupTicketsByColumn(tickets); const groupedTickets = groupTicketsByColumn(tickets);
const boardGroup = renderBoard(ticketCounts, groupedTickets, (columnKey) => { const boardGroup = renderBoard(ticketCounts, groupedTickets, (columnKey) => {
@ -1417,10 +1423,10 @@ export function renderOfficeScene(container, data = {}) {
svg.insertBefore(boardGroup, svg.querySelector(".agents-layer")); svg.insertBefore(boardGroup, svg.querySelector(".agents-layer"));
} }
const wallGroup = renderMonitorWall(systems, events, kpis, { const wallGroup = renderMonitorWall(systems, events, displayKpis, {
onEventsClick: () => openModal(buildEventModalContent(events), renderEventModalBody), onEventsClick: () => openModal(buildEventModalContent(events), renderEventModalBody),
onSystemsClick: () => openModal(buildSystemModalContent(systems), renderSystemModalBody), onSystemsClick: () => openModal(buildSystemModalContent(systems), renderSystemModalBody),
onKpiClick: () => openModal(buildKpiModalContent(kpis), renderKpiModalBody), onKpiClick: () => openModal(buildKpiModalContent(displayKpis), renderKpiModalBody),
}); });
const existingWall = svg.querySelector(".monitor-wall"); const existingWall = svg.querySelector(".monitor-wall");
if (existingWall) { if (existingWall) {

View file

@ -415,6 +415,27 @@ export function isAgentPresent(agent) {
return Boolean(agent.is_present); return Boolean(agent.is_present);
} }
/**
* Die beiden im KPI-Monitor erwarteten Kennzahlen "Agenten" (registrierte Agenten) und
* "Online" (davon innerhalb der letzten 10 Minuten aktiv, siehe isAgentPresent()) werden
* anders als die per POST /api/v1/kpis/{key} gemeldeten Kennzahlen (F7, weiterhin offen)
* direkt aus der ohnehin geladenen Agentenliste abgeleitet statt separat gemeldet.
* Gibt KPI-foermige Eintraege zurueck (gleiche Form wie GET /api/v1/kpis), damit sie sich
* mit den gemeldeten KPIs vor buildKpiTiles()/buildKpiModalContent() zusammenfuehren lassen.
*
* @param {Array<object|null>} agents - z.B. leere Schreibtisch-Plaetze der Demo-Daten sind null.
* @returns {Array<object>}
*/
export function buildAgentCountKpis(agents = []) {
const registered = agents.filter(Boolean);
const online = registered.filter(isAgentPresent).length;
return [
{ key: "agents_total", label: "Agenten", unit: "", current_value: registered.length, history: [] },
{ key: "agents_online", label: "Online", unit: "", current_value: online, history: [] },
];
}
/** /**
* Ordnet eine rohe Agentenliste (GET /api/v1/agents) den Schreibtischen zu - anders als * Ordnet eine rohe Agentenliste (GET /api/v1/agents) den Schreibtischen zu - anders als
* buildDeskLayout()/Demo-Daten steht der Sitzplatz hier nicht in der Array-Reihenfolge, * buildDeskLayout()/Demo-Daten steht der Sitzplatz hier nicht in der Array-Reihenfolge,

View file

@ -20,6 +20,7 @@ import {
buildKpiSparklinePoints, buildKpiSparklinePoints,
buildKpiTiles, buildKpiTiles,
buildKpiModalContent, buildKpiModalContent,
buildAgentCountKpis,
MAX_KPI_TILES, MAX_KPI_TILES,
AGENT_LOCATIONS, AGENT_LOCATIONS,
resolveAgentVisualState, resolveAgentVisualState,
@ -331,6 +332,29 @@ test("buildKpiModalContent zeigt alle KPIs (kein Limit) inkl. Historie", () => {
assert.equal(content.items[0].label, "Offene Tickets"); assert.equal(content.items[0].label, "Offene Tickets");
}); });
test("buildAgentCountKpis liefert Anzahl registrierter und online-Agenten (Issue #39)", () => {
const kpis = buildAgentCountKpis([
{ id: 1, is_present: true },
{ id: 2, is_present: false },
{ id: 3, is_present: true },
]);
assert.equal(kpis.length, 2);
assert.deepEqual(kpis[0], { key: "agents_total", label: "Agenten", unit: "", current_value: 3, history: [] });
assert.deepEqual(kpis[1], { key: "agents_online", label: "Online", unit: "", current_value: 2, history: [] });
});
test("buildAgentCountKpis ignoriert leere Schreibtisch-Plaetze (null) und faellt ohne is_present auf anwesend zurueck", () => {
const kpis = buildAgentCountKpis([{ id: 1 }, null, { id: 2 }, null]);
assert.equal(kpis[0].current_value, 2);
assert.equal(kpis[1].current_value, 2);
});
test("buildAgentCountKpis liefert 0/0 ohne Agenten", () => {
const kpis = buildAgentCountKpis([]);
assert.equal(kpis[0].current_value, 0);
assert.equal(kpis[1].current_value, 0);
});
// --------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------
// Phase 5 - Agenten-Zustandsmaschine & Animation // Phase 5 - Agenten-Zustandsmaschine & Animation
// --------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------