From 08500757d7bad6dafe9b89f4a8cc273a02911e56 Mon Sep 17 00:00:00 2001 From: Gulum Date: Sun, 7 Jun 2026 01:37:08 +0200 Subject: [PATCH] STAMMBAUM-LITTERS: Wuerfe des Wurzeltiers links im Stammbaum-Viewer Layout: stammbaum-layout (flex-row Desktop / flex-column Mobil) wraps [Wuerfe-Panel | Canvas]. Panel 148px breit, border-right Trenner; auf Mobil (<=520px) horizontaler Scroll-Streifen ueber dem Canvas. Daten: useApi(listLitters fatherId=id|motherId=id) reagiert automatisch auf Umwurzeln (id-Param). Kein Panel wenn Wurzeltier keine Wuerfe hat. Pro Wurf: Wurfname (fett) + N Junge + Link zu /wuerfe/{id}. CSS: flex 0 0 auto Mobil-Override sichert Canvas-Hoehe im column-Mode. de.ts: littersTitle, littersJunge (Stammbaum-Sektion, disjunkt). Gate: vitest 122/122 e2e 20/20 Stammbaum (4 neue Tests x 2 Viewports) build+tsc+eslint clean. --- gerbil-manager-web/e2e/stammbaum.spec.ts | 27 ++++ .../src/pages/StammbaumPage.tsx | 81 +++++++++--- gerbil-manager-web/src/pages/stammbaum.css | 123 ++++++++++++++++++ gerbil-manager-web/src/strings/de.ts | 3 + 4 files changed, 215 insertions(+), 19 deletions(-) diff --git a/gerbil-manager-web/e2e/stammbaum.spec.ts b/gerbil-manager-web/e2e/stammbaum.spec.ts index db9da06..2f55793 100644 --- a/gerbil-manager-web/e2e/stammbaum.spec.ts +++ b/gerbil-manager-web/e2e/stammbaum.spec.ts @@ -110,3 +110,30 @@ test('+-Knopf ist sichtbar und lädt weitere Vorfahren nach (STAMMBAUM-EXPAND) await expandBtn.click({ force: true }) await expect(page.getByRole('link', { name: 'Max' })).toBeVisible({ timeout: 8000 }) }) + +test('Würfe-Panel zeigt Würfe des Wurzeltiers + Link öffnet Wurf (STAMMBAUM-LITTERS)', async ({ page }) => { + skipUnlessMock() + // Fridolin ist Vater von Wurf K (5 Junge) — Panel muss erscheinen. + await page.goto('/rennmaeuse/fridolin/stammbaum') + await expect(page.locator('.pedigree-card').first()).toBeVisible() + + const panel = page.locator('.stammbaum-litters-panel') + await expect(panel).toBeVisible() + await expect(panel).toContainText(t.littersTitle) + await expect(panel).toContainText('Wurf K') + await expect(panel).toContainText('5') + + // Link-Klick → Wurf-Detailseite + const wurfLink = panel.getByRole('link', { name: /Wurf K/ }) + await expect(wurfLink).toBeVisible() + await wurfLink.click() + await expect(page).toHaveURL(/\/wuerfe\/w-kruemel/) +}) + +test('Kein Würfe-Panel wenn Wurzeltier keine Würfe hat (STAMMBAUM-LITTERS)', async ({ page }) => { + skipUnlessMock() + // Krümel hat noch keine Würfe als Elternteil → Panel muss fehlen. + await page.goto('/rennmaeuse/kruemel/stammbaum') + await expect(page.locator('.pedigree-card').first()).toBeVisible() + await expect(page.locator('.stammbaum-litters-panel')).not.toBeVisible() +}) diff --git a/gerbil-manager-web/src/pages/StammbaumPage.tsx b/gerbil-manager-web/src/pages/StammbaumPage.tsx index 8f93b01..9e511c0 100644 --- a/gerbil-manager-web/src/pages/StammbaumPage.tsx +++ b/gerbil-manager-web/src/pages/StammbaumPage.tsx @@ -22,9 +22,10 @@ import Tree from 'react-d3-tree' import type { CustomNodeElementProps, Point, RawNodeDatum } from 'react-d3-tree' import { de } from '../strings/de' import { ApiError } from '../api/client' +import { listLitters } from '../api/litters' import { listColorVarieties } from '../api/lookups' import { getInbreedingCoefficient } from '../api/pedigree' -import type { Gender, Gerbil } from '../api/types' +import type { Gender, Gerbil, Litter } from '../api/types' import { useApi } from '../hooks/useApi' import { formatDate, genderLabel } from '../format/labels' import { UNKNOWN_FARBSCHLAG, fromDisplayString, genotypeToFarbschlag } from '../genetics' @@ -156,6 +157,13 @@ export default function StammbaumPage() { ? `${(inbreeding.data * 100).toLocaleString('de-DE', { maximumFractionDigits: 1 })} %` : t.inbreeding.unavailable + /* ── Würfe des Wurzeltiers (STAMMBAUM-LITTERS): aktualisiert bei Umwurzeln ── */ + const rootLitters = useApi( + () => listLitters({ filter: `fatherId=${id}|motherId=${id}`, orderBy: 'date desc', pageSize: 50 }), + [id], + ) + const rootLitterItems = rootLitters.data?.items ?? [] + /* ── react-d3-tree-Daten ── */ const nodesByPath = useMemo(() => (root ? collectNodes(root) : null), [root]) const datum = useMemo(() => (root ? toRawNodeDatum(root, t.unknown) : null), [root, t]) @@ -315,25 +323,30 @@ export default function StammbaumPage() { -
- {view && ( - +
+ {rootLitterItems.length > 0 && ( + )} +
+ {view && ( + + )} +
  • {t.tapHint}
  • @@ -417,6 +430,36 @@ function PedigreeCard({ ) } +/* ── Würfe-Panel (STAMMBAUM-LITTERS) ─────────────────────────────── */ + +function LittersPanel({ + litters, + t, +}: { + litters: Litter[] + t: { littersTitle: string; littersJunge: string } +}) { + return ( + + ) +} + function SexIcon({ gender }: { gender: Gender }) { const symbol = gender === 'male' ? '♂' : gender === 'female' ? '♀' : '?' return ( diff --git a/gerbil-manager-web/src/pages/stammbaum.css b/gerbil-manager-web/src/pages/stammbaum.css index d03692c..4814943 100644 --- a/gerbil-manager-web/src/pages/stammbaum.css +++ b/gerbil-manager-web/src/pages/stammbaum.css @@ -45,9 +45,132 @@ color: var(--color-text-muted); } +/* ── Würfe-Panel + Layout (STAMMBAUM-LITTERS) ────────────────── */ + +.stammbaum-layout { + display: flex; + align-items: stretch; + gap: 0; +} + +/* Desktop: Würfe-Panel links vom Baum. */ +.stammbaum-litters-panel { + flex: none; + width: 148px; + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.35rem; + padding: 0.5rem 0.75rem 0.5rem 0; + border-right: 1px solid var(--color-border); + margin-right: 0; +} + +.stammbaum-litters-panel__title { + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--color-text-muted); +} + +.stammbaum-litters-panel__list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.3rem; + overflow-y: auto; + max-height: calc(clamp(18rem, 62dvh, 46rem) - 3rem); +} + +.stammbaum-litters-panel__link { + display: flex; + flex-direction: column; + gap: 0.1rem; + padding: 0.3rem 0.45rem; + border-radius: 0.4rem; + text-decoration: none; + color: inherit; + background: var(--color-surface); + border: 1px solid var(--color-border); + font-size: 0.8rem; + font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; +} + +.stammbaum-litters-panel__link:hover { + background: var(--color-accent-soft); + border-color: var(--color-accent); + color: var(--color-accent); +} + +.stammbaum-litters-panel__name { + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.stammbaum-litters-panel__born { + color: var(--color-text-muted); + font-size: 0.72rem; +} + +/* Mobil (≤520px): Panel als kompakter horizontaler Streifen ÜBER dem Baum. */ +@media (max-width: 520px) { + .stammbaum-layout { + flex-direction: column; + } + + .stammbaum-litters-panel { + width: auto; + flex-direction: row; + align-items: center; + justify-content: flex-start; + gap: 0.5rem; + padding: 0.4rem 0.5rem; + border-right: none; + border-bottom: 1px solid var(--color-border); + overflow-x: auto; + } + + .stammbaum-litters-panel__title { + flex: none; + white-space: nowrap; + } + + .stammbaum-litters-panel__list { + flex-direction: row; + flex-wrap: nowrap; + max-height: none; + overflow-x: auto; + overflow-y: hidden; + gap: 0.4rem; + } + + .stammbaum-litters-panel__link { + flex-direction: row; + align-items: center; + gap: 0.35rem; + white-space: nowrap; + } + + .stammbaum-litters-panel__born { + color: var(--color-text-muted); + } + + /* Canvas behält explizite Höhe in column-Richtung. */ + .stammbaum-canvas { + flex: 0 0 auto; + } +} + /* ── Zeichenfläche ────────────────────────────────────────────── */ .stammbaum-canvas { + flex: 1 1 auto; + min-width: 0; height: clamp(18rem, 62dvh, 46rem); border: 1px solid var(--color-border); border-radius: 0.6rem; diff --git a/gerbil-manager-web/src/strings/de.ts b/gerbil-manager-web/src/strings/de.ts index 1e6e40a..6b599d1 100644 --- a/gerbil-manager-web/src/strings/de.ts +++ b/gerbil-manager-web/src/strings/de.ts @@ -358,6 +358,9 @@ export const de = { /** Mini-Legende unter dem Baum (STAMMBAUM-EXPAND). */ hintName: 'Namenslink: Tierakte öffnen', hintExpand: '+: weitere Vorfahren nachladen', + /** Würfe-Panel links (STAMMBAUM-LITTERS). */ + littersTitle: 'Würfe', + littersJunge: 'Junge', zoomIn: 'Vergrößern', zoomOut: 'Verkleinern', zoomFit: 'Ansicht einpassen',