From afd321ec9fdbc08f4dca44310d1c82c75aa7e4f6 Mon Sep 17 00:00:00 2001 From: Gulum Date: Sat, 6 Jun 2026 11:59:18 +0200 Subject: [PATCH] =?UTF-8?q?BESTAND-FILTER:=20Tiere-Liste=20zeigt=20standar?= =?UTF-8?q?dm=C3=A4=C3=9Fig=20nur=20den=20eigenen=20Bestand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Tiere-Liste (/rennmaeuse) filtert serverseitig auf isResident==true (eigener Clan = Bestand) und blendet externe Ahnen (nur für den Stammbaum erfasste Fremdtiere) standardmäßig aus — passend zu Julians Modell (Kleine Chaoten = Bestand, extern = stammbaum-only). - Neuer Schalter „Externe Ahnen einblenden“ (Default aus). An -> isResident- Filter entfällt, externe Tiere erscheinen mit. Serverseitig (Gridify), damit Paging und Trefferzahl korrekt bleiben. „Filter zurücksetzen“ setzt ihn zurück. - Subtile „Extern“-Markierung (gestricheltes Badge) auf externen Tieren in Liste UND Detailseite, mit Tooltip-Erläuterung. - Gerbil-Typ um isResident (optional bool) ergänzt; CreateGerbil ebenso. Deutsch via de.ts (LF), mobil + Laptop. e2e-Mock: gerbil()-Factory setzt isResident=true; Hilde & Max als externe Ahnen (isResident=false). Neue e2e bestand.spec (Default blendet aus; Toggle zeigt + Extern-Markierung). Gate grün: build, eslint, vitest 78, e2e 84. Co-Authored-By: Claude Opus 4.8 (1M context) --- gerbil-manager-web/e2e/bestand.spec.ts | 29 +++++++++++++++++++ gerbil-manager-web/e2e/mock-data.ts | 7 +++-- gerbil-manager-web/src/api/types.ts | 8 +++++ gerbil-manager-web/src/index.css | 20 ++++++++++++- .../src/pages/GerbilDetailPage.tsx | 5 ++++ gerbil-manager-web/src/pages/GerbilsPage.tsx | 23 ++++++++++++++- gerbil-manager-web/src/strings/de.ts | 6 ++++ 7 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 gerbil-manager-web/e2e/bestand.spec.ts diff --git a/gerbil-manager-web/e2e/bestand.spec.ts b/gerbil-manager-web/e2e/bestand.spec.ts new file mode 100644 index 0000000..acdf559 --- /dev/null +++ b/gerbil-manager-web/e2e/bestand.spec.ts @@ -0,0 +1,29 @@ +/** BESTAND-FILTER: die Tiere-Liste zeigt standardmäßig nur den eigenen Bestand. */ +import { de, expect, gotoSection, skipUnlessMock, test } from './fixtures' + +const t = de.pages.gerbils + +test('Tiere-Liste blendet externe Ahnen standardmäßig aus', async ({ page }) => { + skipUnlessMock() + await gotoSection(page, de.nav.gerbils) + await expect(page.getByRole('heading', { name: t.title, exact: true })).toBeVisible() + + // Bestand ist sichtbar … + await expect(page.locator('.gerbil-row', { hasText: 'Krümel' })).toBeVisible() + // … aber die externe Ahne „Max“ (isResident=false) nicht. + await expect(page.locator('.gerbil-row', { hasText: 'Max' })).toHaveCount(0) +}) + +test('Toggle „Externe Ahnen einblenden“ zeigt externe Tiere mit Extern-Markierung', async ({ page }) => { + skipUnlessMock() + await gotoSection(page, de.nav.gerbils) + await expect(page.locator('.gerbil-row', { hasText: 'Krümel' })).toBeVisible() + + await page.getByRole('checkbox', { name: t.filters.showExternal }).check() + + const maxRow = page.locator('.gerbil-row', { hasText: 'Max' }) + await expect(maxRow).toBeVisible() + await expect(maxRow.getByText(t.externalBadge, { exact: true })).toBeVisible() + // Der Bestand bleibt weiterhin sichtbar. + await expect(page.locator('.gerbil-row', { hasText: 'Krümel' })).toBeVisible() +}) diff --git a/gerbil-manager-web/e2e/mock-data.ts b/gerbil-manager-web/e2e/mock-data.ts index 129874e..f481ba1 100644 --- a/gerbil-manager-web/e2e/mock-data.ts +++ b/gerbil-manager-web/e2e/mock-data.ts @@ -88,6 +88,8 @@ function gerbil( nameSearch: name.toLowerCase().replace(/[\s._-]/g, ''), genotype, notes: null, + // BESTAND-FILTER: default = eigener Bestand; einzelne externe Ahnen unten gesetzt. + isResident: true, } } @@ -110,8 +112,9 @@ export function seedDb(): MockDb { gerbil('greta', 'Greta', 'female', '2019-05-23', null, 'cv-schwarz'), gerbil('frieda', 'Frieda', 'female', '2020-01-18', null, 'cv-gold'), gerbil('emil', 'Emil', 'male', '2017-03-03', 'w-emil', 'cv-agouti'), - gerbil('hilde', 'Hilde', 'female', '2017-11-11', null, 'cv-schwarz'), - gerbil('max', 'Max', 'male', '2015-08-08', null, 'cv-agouti'), + // BESTAND-FILTER: externe Ahnen (Gründertiere fremder Zuchten, nur für den Stammbaum). + { ...gerbil('hilde', 'Hilde', 'female', '2017-11-11', null, 'cv-schwarz'), isResident: false, originBreeder: 'Zoohandlung Meier' }, + { ...gerbil('max', 'Max', 'male', '2015-08-08', null, 'cv-agouti'), isResident: false, originBreeder: 'Zoohandlung Meier' }, // Statistik: Verstorbene + Abgegebene für Verluste/Bestandskurve { ...gerbil('willi', 'Willi', 'male', '2019-09-09', null, 'cv-schwarz'), status: 'Deceased', dateOfDeath: '2022-04-04' }, { ...gerbil('rosa', 'Rosa', 'female', '2020-02-02', null, 'cv-gold'), status: 'Deceased', dateOfDeath: '2023-08-15' }, diff --git a/gerbil-manager-web/src/api/types.ts b/gerbil-manager-web/src/api/types.ts index bcd7f6f..40024b2 100644 --- a/gerbil-manager-web/src/api/types.ts +++ b/gerbil-manager-web/src/api/types.ts @@ -47,6 +47,13 @@ export interface Gerbil { */ characterTraits?: string[] | null characterNote?: string | null + /** + * Residency (Bestand): true = part of the own clan (Kleine Chaoten), false = + * external/pedigree-only ancestor imported for the Stammbaum. Gridify-filterable + * (`isResident==true`); the Tiere list defaults to resident-only. Absent = treat + * as resident (backend default true). + */ + isResident?: boolean } /** Payload for POST /gerbils. */ @@ -68,6 +75,7 @@ export interface CreateGerbil { notes?: string | null characterTraits?: string[] | null characterNote?: string | null + isResident?: boolean } /** Payload for PUT /gerbils/{id} (all optional / partial update). */ diff --git a/gerbil-manager-web/src/index.css b/gerbil-manager-web/src/index.css index f382e0c..2dbc2d4 100644 --- a/gerbil-manager-web/src/index.css +++ b/gerbil-manager-web/src/index.css @@ -330,6 +330,24 @@ textarea { color: #3a6ea5; } +/* BESTAND-FILTER: subtle marker for external (non-Bestand) animals. Inline next + to the name, so it should not be pushed to the grid edge like the status badge. */ +.badge--external { + justify-self: start; + margin-left: 0.4rem; + vertical-align: middle; + background: #f0f0f0; + color: #777; + border: 1px dashed #bbb; +} + +/* Checkbox-style filter (label text + box on one row), vs. the stacked .field. */ +.field--check { + flex-direction: row; + align-items: center; + gap: 0.4rem; +} + @media (min-width: 768px) { .gerbil-card { grid-template-columns: 2fr 1fr 1.5fr 1fr auto; @@ -741,4 +759,4 @@ textarea { border-radius: 50%; object-fit: cover; border: 2px solid var(--color-border); -} +} diff --git a/gerbil-manager-web/src/pages/GerbilDetailPage.tsx b/gerbil-manager-web/src/pages/GerbilDetailPage.tsx index c5b2b82..b8208e7 100644 --- a/gerbil-manager-web/src/pages/GerbilDetailPage.tsx +++ b/gerbil-manager-web/src/pages/GerbilDetailPage.tsx @@ -104,6 +104,11 @@ export default function GerbilDetailPage() {

{g.name}

{statusLabel(g.status)} + {g.isResident === false && ( + + {de.pages.gerbils.externalBadge} + + )}
diff --git a/gerbil-manager-web/src/pages/GerbilsPage.tsx b/gerbil-manager-web/src/pages/GerbilsPage.tsx index 5044ea2..9d90657 100644 --- a/gerbil-manager-web/src/pages/GerbilsPage.tsx +++ b/gerbil-manager-web/src/pages/GerbilsPage.tsx @@ -32,6 +32,8 @@ export default function GerbilsPage() { const [gender, setGender] = useState('') const [colorVarietyId, setColorVarietyId] = useState('') const [originBreeder, setOriginBreeder] = useState('') + // BESTAND-FILTER: default to the own clan (Bestand); opt in to external ancestors. + const [showExternal, setShowExternal] = useState(false) const [sort, setSort] = useState('nameAsc') const [page, setPage] = useState(1) @@ -50,6 +52,9 @@ export default function GerbilsPage() { gender && condition({ field: 'gender', op: '==', value: gender }), colorVarietyId && condition({ field: 'colorVarietyId', op: '==', value: colorVarietyId }), originBreeder && condition({ field: 'originBreeder', op: '==', value: originBreeder }), + // Default view = own clan (Bestand). Unless "externe Ahnen einblenden" is on, + // restrict to resident animals (server-side, so paging/counts stay correct). + !showExternal && condition({ field: 'isResident', op: '==', value: true }), ) const orderBy = SORT_ORDER_BY[sort] const query: GridifyQuery = { filter: filter || undefined, orderBy, page, pageSize: PAGE_SIZE } @@ -62,6 +67,7 @@ export default function GerbilsPage() { setGender('') setColorVarietyId('') setOriginBreeder('') + setShowExternal(false) setPage(1) setSort('nameAsc') } @@ -186,6 +192,14 @@ export default function GerbilsPage() { + @@ -234,7 +248,14 @@ export default function GerbilsPage() { aria-label={g.name} /> - {g.name} + + {g.name} + {g.isResident === false && ( + + {t.externalBadge} + + )} + {statusLabel(g.status)} diff --git a/gerbil-manager-web/src/strings/de.ts b/gerbil-manager-web/src/strings/de.ts index fd74bf3..d2d3fbe 100644 --- a/gerbil-manager-web/src/strings/de.ts +++ b/gerbil-manager-web/src/strings/de.ts @@ -49,6 +49,9 @@ export const de = { empty: 'Keine Rennmäuse gefunden.', countLabel: 'Tiere', searchPlaceholder: 'Name suchen …', + // BESTAND-FILTER: Kennzeichnung externer (nicht zum eigenen Bestand gehörender) Tiere + externalBadge: 'Extern', + externalTitle: 'Externes Tier — gehört nicht zum eigenen Bestand, nur für den Stammbaum erfasst.', // Filter-Beschriftungen filters: { title: 'Filter', @@ -58,6 +61,9 @@ export const de = { all: 'Alle', reset: 'Filter zurücksetzen', sortBy: 'Sortieren nach', + // BESTAND-FILTER: eigener Bestand (Clan) vs. externe Ahnen (nur Stammbaum) + showExternal: 'Externe Ahnen einblenden', + showExternalHint: 'Tiere aus fremden Zuchten, die nur für den Stammbaum erfasst sind.', }, // Sortier-Optionen sort: {