diff --git a/gerbil-manager-web/e2e/juvenile-details.spec.ts b/gerbil-manager-web/e2e/juvenile-details.spec.ts new file mode 100644 index 0000000..28ede90 --- /dev/null +++ b/gerbil-manager-web/e2e/juvenile-details.spec.ts @@ -0,0 +1,30 @@ +/** LITTER-JUVENILE-DETAILS: Jungtier-Felder (Farbschlag/Abgabe/Verstorben) im Wurf-Detail. */ +import { de, expect, skipUnlessMock, test } from './fixtures' + +const t = de.pages.litters + +test('Jungtier-Liste zeigt Farbschlag, Abgabedatum, Abnehmer, Todesdatum und Todesursache', async ({ + page, +}) => { + skipUnlessMock() + await page.goto('/wuerfe/w-kruemel') + await expect(page.getByRole('heading', { name: 'Wurf K' })).toBeVisible() + + // Krümel: Active, colorVarietyId cv-agouti → Farbschlag 'Agouti' + const kruemelCard = page.getByRole('link', { name: /Krümel/ }) + await expect(kruemelCard).toBeVisible() + await expect(kruemelCard).toContainText(`${t.detail.juvenileFields.colorVariety}: Agouti`) + + // Pippa: GivenAway, Gold, goHomeDate 2025-05-01, Abnehmer Familie Huber + const pippaCard = page.getByRole('link', { name: /Pippa/ }) + await expect(pippaCard).toBeVisible() + await expect(pippaCard).toContainText(`${t.detail.juvenileFields.colorVariety}: Gold`) + await expect(pippaCard).toContainText(t.detail.juvenileFields.goHomeDate) + await expect(pippaCard).toContainText(`${t.detail.juvenileFields.receiver}: Familie Huber`) + + // Benny: Deceased, no colorVarietyId, dateOfDeath + causeOfDeath + const bennyCard = page.getByRole('link', { name: /Benny/ }) + await expect(bennyCard).toBeVisible() + await expect(bennyCard).toContainText(t.detail.juvenileFields.dateOfDeath) + await expect(bennyCard).toContainText(`${t.detail.juvenileFields.causeOfDeath}: Altersschwäche`) +}) diff --git a/gerbil-manager-web/e2e/mock-data.ts b/gerbil-manager-web/e2e/mock-data.ts index 74544ef..f61fcb3 100644 --- a/gerbil-manager-web/e2e/mock-data.ts +++ b/gerbil-manager-web/e2e/mock-data.ts @@ -151,6 +151,19 @@ export function seedDb(): MockDb { enclosureId: 'enc-leer', notes: 'neugieriger Entdecker', }, + // LITTER-JUVENILE-DETAILS: Jungtiere für Wurf K mit Zusatzinfos. + { + ...gerbil('pup-abgabe', 'Pippa', 'female', '2025-03-12', 'w-kruemel', 'cv-gold'), + status: 'GivenAway' as const, + goHomeDate: '2025-05-01', + receiverContactId: 'con-huber', + }, + { + ...gerbil('pup-verstorben', 'Benny', 'male', '2025-03-12', 'w-kruemel', null), + status: 'Deceased' as const, + dateOfDeath: '2025-04-15', + causeOfDeath: 'Altersschwäche', + }, // UI-POLISH-1: Import-Stub ohne Namen — testet den '(ohne Namen)'-Platzhalter in Liste + Detail. gerbil('nameless-stub', '', 'male', '2023-01-01', null, null), ] diff --git a/gerbil-manager-web/src/pages/WurfDetailPage.tsx b/gerbil-manager-web/src/pages/WurfDetailPage.tsx index 4d42732..4596727 100644 --- a/gerbil-manager-web/src/pages/WurfDetailPage.tsx +++ b/gerbil-manager-web/src/pages/WurfDetailPage.tsx @@ -1,13 +1,14 @@ -import { useMemo } from 'react' +import { useCallback, useMemo } from 'react' import { Link, useParams } from 'react-router-dom' import { de } from '../strings/de' import { getLitter } from '../api/litters' import { getGerbil, listGerbils } from '../api/gerbils' +import { listColorVarieties, listContacts } from '../api/lookups' import { condition } from '../api/gridify' import { useApi } from '../hooks/useApi' -import { formatDate } from '../format/labels' +import { formatDate, genderLabel } from '../format/labels' import { isValidGenotype } from '../format/genotypeText' -import { breed, fromDisplayString, type BreedingResult } from '../genetics' +import { breed, fromDisplayString, genotypeToFarbschlag, UNKNOWN_FARBSCHLAG, type BreedingResult } from '../genetics' import BreedingResultView from '../components/BreedingResultView' export default function WurfDetailPage() { @@ -43,6 +44,38 @@ export default function WurfDetailPage() { return breed(fromDisplayString(fg), fromDisplayString(mg)) }, [father.data, mother.data]) + // Farbschlag + Abnehmer lookups for the juvenile list. + const colorVarieties = useApi(() => listColorVarieties(), []) + const contacts = useApi(() => listContacts(), []) + + const colorNameById = useMemo( + () => new Map((colorVarieties.data ?? []).map((c) => [c.id, c.name])), + [colorVarieties.data], + ) + const contactNameById = useMemo( + () => new Map((contacts.data ?? []).map((c) => [c.id, c.name])), + [contacts.data], + ) + + const farbschlagOf = useCallback( + (g: { colorVarietyId: string | null; genotype: string | null }): string | null => { + if (g.colorVarietyId) { + const name = colorNameById.get(g.colorVarietyId) + if (name) return name + } + if (g.genotype?.trim()) { + try { + const name = genotypeToFarbschlag(fromDisplayString(g.genotype)) + return name === UNKNOWN_FARBSCHLAG ? null : name + } catch { + return null + } + } + return null + }, + [colorNameById], + ) + if (litter.loading) return

{de.common.loading}

if (litter.error || !litter.data) { return ( @@ -117,14 +150,24 @@ export default function WurfDetailPage() { {!juveniles.loading && registered === 0 &&

{t.detail.noJuveniles}

} {registered > 0 && ( )} diff --git a/gerbil-manager-web/src/strings/de.ts b/gerbil-manager-web/src/strings/de.ts index 6b599d1..486ac6f 100644 --- a/gerbil-manager-web/src/strings/de.ts +++ b/gerbil-manager-web/src/strings/de.ts @@ -189,6 +189,14 @@ export const de = { edit: 'Bearbeiten', back: 'Zurück zur Liste', notFound: 'Dieser Wurf wurde nicht gefunden.', + // LITTER-JUVENILE-DETAILS: pro-Jungtier-Felder + juvenileFields: { + colorVariety: 'Farbschlag', + goHomeDate: 'Abgegeben', + receiver: 'Abnehmer', + dateOfDeath: 'Verstorben', + causeOfDeath: 'Todesursache', + }, }, // Formular form: {