diff --git a/gerbil-manager-web/e2e/tiere.spec.ts b/gerbil-manager-web/e2e/tiere.spec.ts index 23524ff..ee7d4f0 100644 --- a/gerbil-manager-web/e2e/tiere.spec.ts +++ b/gerbil-manager-web/e2e/tiere.spec.ts @@ -15,6 +15,22 @@ test('Liste lädt und die Namenssuche filtert', async ({ page }) => { await expect(page.getByRole('link', { name: /Fridolin/ })).toBeHidden() }) +test('Herkunft-Filter zeigt nur Tiere des gewählten Kontakts (SEARCH-2)', async ({ page }) => { + skipUnlessMock() + await page.goto('/rennmaeuse') + await expect(page.getByRole('link', { name: /Krümel/ })).toBeVisible() + await expect(page.getByRole('link', { name: /Fridolin/ })).toBeVisible() + + // Herkunft (originContactId) auf den Seed-Kontakt 'Zoohandlung Meier' (nur Krümel). + await page + .locator('label.field', { has: page.locator(`span:text-is("${t.fields.origin}")`) }) + .locator('select') + .selectOption({ label: 'Zoohandlung Meier' }) + + await expect(page.getByRole('link', { name: /Krümel/ })).toBeVisible() + await expect(page.getByRole('link', { name: /Fridolin/ })).toBeHidden() +}) + test('Neue Rennmaus anlegen — Validierung + Erfolg', async ({ page }) => { await gotoSection(page, de.nav.gerbils) await page.getByRole('link', { name: t.newButton }).click() diff --git a/gerbil-manager-web/src/pages/GerbilsPage.tsx b/gerbil-manager-web/src/pages/GerbilsPage.tsx index 776af1b..08691bf 100644 --- a/gerbil-manager-web/src/pages/GerbilsPage.tsx +++ b/gerbil-manager-web/src/pages/GerbilsPage.tsx @@ -2,7 +2,7 @@ import { useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { de } from '../strings/de' import { listGerbils, updateGerbil } from '../api/gerbils' -import { listColorVarieties } from '../api/lookups' +import { listColorVarieties, listContacts } from '../api/lookups' import { andFilter, condition, type GridifyQuery } from '../api/gridify' import { GENDERS, GERBIL_STATUSES, type Gender, type GerbilStatus } from '../api/types' import { useApi, useMutation } from '../hooks/useApi' @@ -11,6 +11,11 @@ import './gerbils.css' const PAGE_SIZE = 20 +// SEARCH-2: name search field. Switches to Pam's separator-insensitive +// normalized field once SEARCH-1 lands (see conversation SEARCH-1); 'name' +// works today via the contains hotfix. +const NAME_SEARCH_FIELD = 'name' + type SortKey = 'nameAsc' | 'nameDesc' | 'birthDesc' | 'birthAsc' const SORT_ORDER_BY: Record = { nameAsc: 'name', @@ -25,6 +30,7 @@ export default function GerbilsPage() { const [status, setStatus] = useState('Active') const [gender, setGender] = useState('') const [colorVarietyId, setColorVarietyId] = useState('') + const [originContactId, setOriginContactId] = useState('') const [sort, setSort] = useState('nameAsc') const [page, setPage] = useState(1) @@ -34,12 +40,14 @@ export default function GerbilsPage() { for (const cv of colorVarieties.data ?? []) map.set(cv.id, cv.name) return map }, [colorVarieties.data]) + const contacts = useApi(() => listContacts(), []) const filter = andFilter( - search.trim() && condition({ field: 'name', op: 'contains', value: search.trim() }), + search.trim() && condition({ field: NAME_SEARCH_FIELD, op: 'contains', value: search.trim() }), status && condition({ field: 'status', op: '==', value: status }), gender && condition({ field: 'gender', op: '==', value: gender }), colorVarietyId && condition({ field: 'colorVarietyId', op: '==', value: colorVarietyId }), + originContactId && condition({ field: 'originContactId', op: '==', value: originContactId }), ) const orderBy = SORT_ORDER_BY[sort] const query: GridifyQuery = { filter: filter || undefined, orderBy, page, pageSize: PAGE_SIZE } @@ -51,6 +59,7 @@ export default function GerbilsPage() { setStatus('Active') setGender('') setColorVarietyId('') + setOriginContactId('') setPage(1) setSort('nameAsc') } @@ -152,6 +161,20 @@ export default function GerbilsPage() { ))} +