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() {