From 9ce938c45b9f845f97552036b75b54a3f4e02913 Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Mon, 21 Sep 2026 15:05:42 +0200 Subject: [PATCH] Kanban-Board: Ticket-Nummer auf den einzelnen Zetteln anzeigen (nicht am Stapel) Co-Authored-By: Claude Sonnet 5 --- html/css/office.css | 11 +++++++++++ html/js/office-render.js | 10 ++++++++++ html/js/office-scene.js | 12 +++++++++++- tests/frontend/office-scene.test.js | 16 ++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/html/css/office.css b/html/css/office.css index 5709c78..02ef5e2 100644 --- a/html/css/office.css +++ b/html/css/office.css @@ -212,6 +212,17 @@ header.office-header h1 { stroke: rgba(15, 23, 42, 0.35); } +/* Issue #91: Ticket-Nummer auf dem Zettel; heller Rand haelt sie auf dunklen Projektfarben lesbar. */ +.note-number { + fill: #0f172a; + font-size: 16px; + font-weight: 700; + paint-order: stroke; + stroke: rgba(255, 255, 255, 0.8); + stroke-width: 3px; + pointer-events: none; +} + .note-stack-badge { fill: var(--color-note-stack-badge); stroke: #92400e; diff --git a/html/js/office-render.js b/html/js/office-render.js index fec947e..b4a77ae 100644 --- a/html/js/office-render.js +++ b/html/js/office-render.js @@ -299,6 +299,16 @@ function renderBoard(ticketCounts, groupedTickets, onColumnClick, onReloadClick) column.noteColors[i] ) ); + if (column.noteNumbers?.[i]) { + colGroup.appendChild( + text(column.noteNumbers[i], { + x: noteX + NOTE_W / 2, + y: noteY + NOTE_H / 2 + 6, + class: "note-number", + "text-anchor": "middle", + }) + ); + } noteY += NOTE_STEP_Y; } diff --git a/html/js/office-scene.js b/html/js/office-scene.js index c3e0b88..28b835e 100644 --- a/html/js/office-scene.js +++ b/html/js/office-scene.js @@ -50,7 +50,7 @@ export const NOTE_STACK_LAYERS = 3; * @param {Record>} groupedTickets - Tickets je Spalte in derselben * Reihenfolge wie gezaehlt (siehe groupTicketsByColumn()). * @returns {Array<{key: string, label: string, count: number, visibleNotes: number, - * stackCount: number, noteColors: Array, stackColors: Array}>} + * stackCount: number, noteColors: Array, noteNumbers: Array, stackColors: Array}>} */ export function buildBoardColumnLayout(ticketCounts = {}, groupedTickets = {}) { return BOARD_COLUMNS.map(({ key, label }) => { @@ -64,6 +64,7 @@ export function buildBoardColumnLayout(ticketCounts = {}, groupedTickets = {}) { visibleNotes, stackCount, noteColors: noteColors(tickets.slice(0, visibleNotes), visibleNotes), + noteNumbers: noteNumbers(tickets.slice(0, visibleNotes), visibleNotes), stackColors: stackCount > 0 ? stackLayerColors(tickets.slice(visibleNotes)) : [], }); @@ -85,6 +86,15 @@ function noteColors(tickets, length) { return Array.from({ length }, (_, i) => tickets[i]?.color ?? null); } +// Issue #91: Ticket-Nummer auf dem Zettel (nur Codeberg-Tickets haben eine); null bei +// Tickets ohne Nummer und im Demo-Modus. Der Stapel zeigt bewusst keine Nummern. +function noteNumbers(tickets, length) { + return Array.from({ length }, (_, i) => { + const ticket = tickets[i]; + return ticket?.source === "codeberg" && ticket.external_ref ? `#${ticket.external_ref}` : null; + }); +} + // Ein Stapel steht fuer viele Tickets, aber nur fuer NOTE_STACK_LAYERS Zettel: er zeigt // deshalb die ersten verschiedenen Projektfarben darin, statt eine davon dreimal zu // wiederholen. So ist auch an "Done" (immer ein Stapel) zu sehen, aus welchen Projekten die diff --git a/tests/frontend/office-scene.test.js b/tests/frontend/office-scene.test.js index d81fa2e..663f219 100644 --- a/tests/frontend/office-scene.test.js +++ b/tests/frontend/office-scene.test.js @@ -112,6 +112,22 @@ test("buildBoardColumnLayout faerbt jeden Zettel in der Farbe seines Projekts (I assert.deepEqual(bereit.noteColors, ["#fbca04", "#28a745", "#fbca04"]); }); +test("buildBoardColumnLayout liefert Ticket-Nummern fuer Zettel, nicht fuer den Stapel (Issue #91)", () => { + const tickets = Array.from({ length: 7 }, (_, i) => ({ + column_name: "bereit", + source: "codeberg", + external_ref: String(i + 1), + })); + tickets[1] = { column_name: "bereit", source: "api" }; + const bereit = buildBoardColumnLayout( + aggregateTicketsByColumn(tickets), + groupTicketsByColumn(tickets) + ).find((c) => c.key === "bereit"); + + assert.deepEqual(bereit.noteNumbers, ["#1", null, "#3", "#4"]); + assert.equal(bereit.stackCount, 3); +}); + test("buildBoardColumnLayout laesst Zettel ohne Projektfarbe ungefaerbt", () => { const tickets = [{ column_name: "qa" }, { column_name: "qa", color: "#0366d6" }]; const qa = buildBoardColumnLayout(