Setzt Phase 2 aus Visualization.md um: Vanilla-SVG-Buero-Szene (Waende, 2 Tueren, 8 Schreibtische, Kanban-Board-Rahmen mit 7 Spalten, Monitor-Wand mit Event-/System-/ KPI-Monitor) ohne Animation, gespeist mit statischen Demo-Daten im Format der Phase-1-API-Responses. Board-Stapel-Logik (>5 Tickets, Done-Spalte als Dauerstapel) folgt Detailantworten.md F2/F3. Reine Layout-Logik (html/js/office-scene.js) ist ausgelagert und mit dem Node- Testrunner abgedeckt (100% Zeilenabdeckung, tests/frontend/), das DOM-Rendering (office-render.js) bleibt bewusst duenne Glue-Logik. Docker-Compose um web- (nginx, serviert html/) und frontend-Service (node, "npm test") erweitert, analog zum bestehenden api-Service und zum NC-Gantt-Vorbild: kein PHP/Node auf dem Host noetig. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
105 lines
3.9 KiB
JavaScript
105 lines
3.9 KiB
JavaScript
// Reine Layout-/Datenlogik fuer die Buero-Visualisierung (Phase 2, siehe Visualization.md
|
|
// Abschnitt 9). Enthaelt keine DOM-Zugriffe, damit die Funktionen ohne Browser (z.B. mit dem
|
|
// Node-Testrunner) getestet werden koennen. Das eigentliche Zeichnen uebernimmt office-render.js.
|
|
|
|
// Spaltenreihenfolge exakt wie API.md ("Tickets" -> Spalten, F3).
|
|
export const BOARD_COLUMNS = [
|
|
{ key: "backlog", label: "Backlog" },
|
|
{ key: "bereit", label: "Bereit" },
|
|
{ key: "in_arbeit", label: "In Arbeit" },
|
|
{ key: "qa", label: "QA" },
|
|
{ key: "testing", label: "Testing" },
|
|
{ key: "fertig", label: "Fertig" },
|
|
{ key: "done", label: "Done" },
|
|
];
|
|
|
|
// Rollen exakt wie API.md ("Agenten", F1/F2). "user" hat nie einen Schreibtisch.
|
|
export const ROLES = {
|
|
entwickler: { label: "Entwickler:in", accentColor: "#3b82f6", deco: "star" },
|
|
product_owner: { label: "Product Owner:in", accentColor: "#ec4899", deco: "flower" },
|
|
qa: { label: "QA / Tester:in", accentColor: "#22c55e", deco: "check" },
|
|
user: { label: "User", accentColor: "#f59e0b", deco: "none" },
|
|
};
|
|
|
|
export const MAX_VISIBLE_NOTES_PER_COLUMN = 4;
|
|
export const MAX_SYSTEM_TILES = 9;
|
|
export const MAX_EVENT_LINES = 5;
|
|
export const DEFAULT_DESK_COUNT = 8;
|
|
|
|
/**
|
|
* Baut aus rohen Ticket-Zaehlern pro Spalte die Darstellung fuer das Board.
|
|
* Regel (Detailantworten F3): mehr als 5 Tickets in einer Spalte -> die aeltesten 4 werden
|
|
* einzeln dargestellt, der Rest als ein "Stapel" mit der Anzahl der zusaetzlichen Tickets.
|
|
* Regel (Detailantworten F2): die Spalte "done" wird immer als ein Stapel (Zeit-Historie)
|
|
* dargestellt, unabhaengig von der Anzahl.
|
|
*
|
|
* @param {Record<string, number>} ticketCounts - Anzahl Tickets je Spalten-Key.
|
|
* @returns {Array<{key: string, label: string, count: number, visibleNotes: number, stackCount: number}>}
|
|
*/
|
|
export function buildBoardColumnLayout(ticketCounts = {}) {
|
|
return BOARD_COLUMNS.map(({ key, label }) => {
|
|
const count = Math.max(0, ticketCounts[key] ?? 0);
|
|
|
|
if (key === "done") {
|
|
return { key, label, count, visibleNotes: 0, stackCount: count };
|
|
}
|
|
|
|
if (count <= MAX_VISIBLE_NOTES_PER_COLUMN + 1) {
|
|
return { key, label, count, visibleNotes: count, stackCount: 0 };
|
|
}
|
|
|
|
return {
|
|
key,
|
|
label,
|
|
count,
|
|
visibleNotes: MAX_VISIBLE_NOTES_PER_COLUMN,
|
|
stackCount: count - MAX_VISIBLE_NOTES_PER_COLUMN,
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ordnet Agenten-Demo-Daten den Schreibtischen zu. Unbesetzte Schreibtische bleiben leer.
|
|
* Ueberzaehlige Agenten (mehr als deskCount) werden ignoriert - Phase 2 zeigt nur den
|
|
* statischen Grundzustand, die "weitere Agenten kommen durch die Tuer"-Logik ist Teil von
|
|
* Phase 5 (Animation).
|
|
*
|
|
* @param {Array<{name: string, role: string, pose?: string}>} agents
|
|
* @param {number} deskCount
|
|
* @returns {Array<{deskIndex: number, agent: object|null}>}
|
|
*/
|
|
export function buildDeskLayout(agents = [], deskCount = DEFAULT_DESK_COUNT) {
|
|
const desks = [];
|
|
for (let deskIndex = 0; deskIndex < deskCount; deskIndex++) {
|
|
const agent = agents[deskIndex] ?? null;
|
|
desks.push({ deskIndex, agent: agent ? { pose: "idle", ...agent } : null });
|
|
}
|
|
return desks;
|
|
}
|
|
|
|
/**
|
|
* Bereitet Systemkacheln fuer den System-Monitor auf: Fehler zuerst (wie GET /api/v1/systems),
|
|
* maximal MAX_SYSTEM_TILES Kacheln (F6).
|
|
*
|
|
* @param {Array<{id: string, name: string, status: "ok"|"error"}>} systems
|
|
* @returns {Array<object>}
|
|
*/
|
|
export function buildSystemTiles(systems = []) {
|
|
const sorted = [...systems].sort((a, b) => {
|
|
if (a.status === b.status) return 0;
|
|
return a.status === "error" ? -1 : 1;
|
|
});
|
|
return sorted.slice(0, MAX_SYSTEM_TILES);
|
|
}
|
|
|
|
/**
|
|
* Bereitet die Zeilen des Event-Monitors auf: neuestes Event zuerst, maximal
|
|
* MAX_EVENT_LINES Zeilen (F5). Erwartet bereits absteigend nach Zeit sortierte Events
|
|
* (wie GET /api/v1/events sie liefert).
|
|
*
|
|
* @param {Array<{severity: "warning"|"error"}>} events
|
|
* @returns {Array<object>}
|
|
*/
|
|
export function buildEventLines(events = []) {
|
|
return events.slice(0, MAX_EVENT_LINES);
|
|
}
|