From f2975e26f01aa030e0da8ae7876b79fde0f63040 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 03:58:22 +0000 Subject: [PATCH] Workers P2: Kennzahlen, Worker-Karte mit Spur und Worker-Liste Co-Authored-By: Claude Sonnet 5.5 --- src/components/workers/WorkerCard.jsx | 125 +++++++++++++++++++++++++ src/components/workers/WorkerList.jsx | 62 ++++++++++++ src/components/workers/WorkersKpis.jsx | 13 +++ tests/unit/workersComponents.test.mjs | 26 +++++ 4 files changed, 226 insertions(+) create mode 100644 src/components/workers/WorkerCard.jsx create mode 100644 src/components/workers/WorkerList.jsx create mode 100644 src/components/workers/WorkersKpis.jsx create mode 100644 tests/unit/workersComponents.test.mjs diff --git a/src/components/workers/WorkerCard.jsx b/src/components/workers/WorkerCard.jsx new file mode 100644 index 0000000..8424243 --- /dev/null +++ b/src/components/workers/WorkerCard.jsx @@ -0,0 +1,125 @@ +import { useEffect, useRef } from 'react' +import { Link } from 'react-router-dom' +import Badge from '../ui/Badge' +import { counterLines, formatCount, formatUsd, safeLink, statusTone, tokenTotal, traceRows, workerStatusLabel } from '../../lib/workersView' +import { MODEL_IDS, hatForWorker, roleOf } from '../../lib/workersRoster' +import { drawWorker, setGrid } from './engine/sprites' + +const DASH = '–' + +// Modell-Chip je Hut (Wörterbuch statt zusammengesetzter Klasse: der CSS-Test in workers.css findet die Namen) +const CHIP = { + opus: 'wk-chip-opus', + sonnet: 'wk-chip-sonnet', + haiku: 'wk-chip-haiku', + overseer: 'wk-chip-overseer', +} + +const PORTRAIT_W = 72 +const PORTRAIT_H = 96 + +const isRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) + +// Status aus dem Zustandseintrag; ein nicht angeschlossener Worker ist grau (wie in indexWorkers) +function statusOf(worker) { + if (!isRecord(worker)) return '' + return worker.connected === false ? 'grau' : worker.status +} + +// Porträt der Figur auf einem eigenen Canvas (Raster 4, Figur bei 6/8) +function Portrait({ hat, grey }) { + const ref = useRef(null) + useEffect(() => { + const canvas = ref.current + const ctx = canvas && canvas.getContext ? canvas.getContext('2d') : null + if (!ctx) return + ctx.clearRect(0, 0, PORTRAIT_W, PORTRAIT_H) + setGrid(ctx, { k: 4 }) + drawWorker(ctx, 6, 8, { hat, grey, scale: 1 }) + }, [hat, grey]) + return