From 299c038449dd58091c7964b33b1212039de271d1 Mon Sep 17 00:00:00 2001 From: Gulum Date: Sun, 7 Jun 2026 08:20:19 +0200 Subject: [PATCH] feat(JUVENILE-AVATARS): Foto-Avatare pro Jungtier im Wurf-Detail - JuvenileCard-Komponente in WurfDetailPage mit 36px-Kreis-Avatar links - Fallback auf GerbilIcon (SVG-Silhouette) wenn kein profilePhotoUrl - URL-basiertes Fehler-Tracking (failedUrl) statt useEffect+imgError - wuerfe.css: Flex-Layout fuer Card+Avatar, overflow:hidden/object-fit:cover - StammbaumPage: gleiche Lint-Verletzung (react-hooks/set-state-in-effect) behoben (failedUrl-Pattern, kein useEffect mehr fuer Bild-Reset) - E2E: profilePhotoUrl auf Pippa (pup-abgabe), neuer Avatar-Test Co-Authored-By: Claude Sonnet 4.6 --- .../e2e/juvenile-details.spec.ts | 17 +++++- gerbil-manager-web/e2e/mock-data.ts | 2 + .../src/pages/StammbaumPage.tsx | 17 +++--- .../src/pages/WurfDetailPage.tsx | 61 +++++++++++++++---- gerbil-manager-web/src/pages/wuerfe.css | 34 +++++++++++ 5 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 gerbil-manager-web/src/pages/wuerfe.css diff --git a/gerbil-manager-web/e2e/juvenile-details.spec.ts b/gerbil-manager-web/e2e/juvenile-details.spec.ts index 28ede90..92a3d7a 100644 --- a/gerbil-manager-web/e2e/juvenile-details.spec.ts +++ b/gerbil-manager-web/e2e/juvenile-details.spec.ts @@ -1,4 +1,4 @@ -/** LITTER-JUVENILE-DETAILS: Jungtier-Felder (Farbschlag/Abgabe/Verstorben) im Wurf-Detail. */ +/** LITTER-JUVENILE-DETAILS + JUVENILE-AVATARS: Jungtier-Felder und Foto-Avatare im Wurf-Detail. */ import { de, expect, skipUnlessMock, test } from './fixtures' const t = de.pages.litters @@ -28,3 +28,18 @@ test('Jungtier-Liste zeigt Farbschlag, Abgabedatum, Abnehmer, Todesdatum und Tod await expect(bennyCard).toContainText(t.detail.juvenileFields.dateOfDeath) await expect(bennyCard).toContainText(`${t.detail.juvenileFields.causeOfDeath}: Altersschwäche`) }) + +test('JUVENILE-AVATARS: Pippa zeigt Foto-Avatar, Benny zeigt GerbilIcon', async ({ page }) => { + skipUnlessMock() + await page.goto('/wuerfe/w-kruemel') + await expect(page.getByRole('heading', { name: 'Wurf K' })).toBeVisible() + + // Pippa hat profilePhotoUrl → sichtbar + const pippaCard = page.getByRole('link', { name: /Pippa/ }) + await expect(pippaCard.locator('.juvenile-photo__avatar')).toBeVisible() + + // Benny hat kein Foto → kein img, SVG-Icon (GerbilIcon) sichtbar + const bennyCard = page.getByRole('link', { name: /Benny/ }) + await expect(bennyCard.locator('.juvenile-photo__avatar')).not.toBeVisible() + await expect(bennyCard.locator('svg')).toBeVisible() +}) diff --git a/gerbil-manager-web/e2e/mock-data.ts b/gerbil-manager-web/e2e/mock-data.ts index 66a3c4c..5fa5877 100644 --- a/gerbil-manager-web/e2e/mock-data.ts +++ b/gerbil-manager-web/e2e/mock-data.ts @@ -153,11 +153,13 @@ export function seedDb(): MockDb { notes: 'neugieriger Entdecker', }, // LITTER-JUVENILE-DETAILS: Jungtiere für Wurf K mit Zusatzinfos. + // JUVENILE-AVATARS: Pippa hat profilePhotoUrl (1×1-PNG data-URI) → Avatar-Img sichtbar. { ...gerbil('pup-abgabe', 'Pippa', 'female', '2025-03-12', 'w-kruemel', 'cv-gold'), status: 'GivenAway' as const, goHomeDate: '2025-05-01', receiverContactId: 'con-huber', + profilePhotoUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', }, { ...gerbil('pup-verstorben', 'Benny', 'male', '2025-03-12', 'w-kruemel', null), diff --git a/gerbil-manager-web/src/pages/StammbaumPage.tsx b/gerbil-manager-web/src/pages/StammbaumPage.tsx index 4612121..8a3b994 100644 --- a/gerbil-manager-web/src/pages/StammbaumPage.tsx +++ b/gerbil-manager-web/src/pages/StammbaumPage.tsx @@ -381,14 +381,13 @@ function PedigreeCard({ const g = node.gerbil const chip = farbschlag ? chipColorFor(farbschlag) : null const dob = g.dateOfBirth ? formatDate(g.dateOfBirth) : null - const [imgError, setImgError] = useState(false) - useEffect(() => setImgError(false), [g.id]) - const photoUrl = - !imgError && g.profilePhotoUrl - ? /^(https?:|data:|\/\/)/.test(g.profilePhotoUrl) - ? g.profilePhotoUrl - : `${API_BASE_URL}${g.profilePhotoUrl}` - : null + const [failedUrl, setFailedUrl] = useState(null) + const rawUrl = g.profilePhotoUrl + ? /^(https?:|data:|\/\/)/.test(g.profilePhotoUrl) + ? g.profilePhotoUrl + : `${API_BASE_URL}${g.profilePhotoUrl}` + : null + const photoUrl = rawUrl !== failedUrl ? rawUrl : null return (
setImgError(true)} + onError={() => setFailedUrl(rawUrl)} /> ) : (