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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,62 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useCallback, useMemo, useState } 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 { API_BASE_URL } from '../api/client'
|
||||
import { condition } from '../api/gridify'
|
||||
import { type Gerbil } from '../api/types'
|
||||
import { useApi } from '../hooks/useApi'
|
||||
import { formatDate, genderLabel } from '../format/labels'
|
||||
import { isValidGenotype } from '../format/genotypeText'
|
||||
import { breed, fromDisplayString, genotypeToFarbschlag, UNKNOWN_FARBSCHLAG, type BreedingResult } from '../genetics'
|
||||
import BreedingResultView from '../components/BreedingResultView'
|
||||
import GerbilIcon from '../components/GerbilIcon'
|
||||
import './wuerfe.css'
|
||||
|
||||
type JuvenileFields = (typeof de.pages.litters.detail.juvenileFields)
|
||||
|
||||
function JuvenileCard({ g, farbe, receiver, tJf }: {
|
||||
g: Gerbil
|
||||
farbe: string | null
|
||||
receiver: string | null
|
||||
tJf: JuvenileFields
|
||||
}) {
|
||||
const [failedUrl, setFailedUrl] = useState<string | null>(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 (
|
||||
<Link to={`/rennmaeuse/${g.id}`} className="gerbil-card gerbil-card--with-photo">
|
||||
<div className="juvenile-photo">
|
||||
{photoUrl ? (
|
||||
<img
|
||||
src={photoUrl}
|
||||
alt={g.name || ''}
|
||||
className="juvenile-photo__avatar"
|
||||
onError={() => setFailedUrl(rawUrl)}
|
||||
/>
|
||||
) : (
|
||||
<GerbilIcon size="1.4rem" aria-hidden="true" />
|
||||
)}
|
||||
</div>
|
||||
<div className="juvenile-body">
|
||||
<span className="gerbil-card__name">{g.name}</span>
|
||||
<span className="gerbil-card__meta">{genderLabel(g.gender)}</span>
|
||||
{farbe && <span className="gerbil-card__meta">{tJf.colorVariety}: {farbe}</span>}
|
||||
{g.goHomeDate && <span className="gerbil-card__meta">{tJf.goHomeDate}: {formatDate(g.goHomeDate)}</span>}
|
||||
{receiver && <span className="gerbil-card__meta">{tJf.receiver}: {receiver}</span>}
|
||||
{g.dateOfDeath && <span className="gerbil-card__meta">{tJf.dateOfDeath}: {formatDate(g.dateOfDeath)}</span>}
|
||||
{g.causeOfDeath && <span className="gerbil-card__meta">{tJf.causeOfDeath}: {g.causeOfDeath}</span>}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default function WurfDetailPage() {
|
||||
const t = de.pages.litters
|
||||
@@ -158,19 +205,11 @@ export default function WurfDetailPage() {
|
||||
<ul className="card-list">
|
||||
{(juveniles.data?.items ?? []).map((g) => {
|
||||
const farbe = farbschlagOf(g)
|
||||
const receiver = g.receiverContactId ? contactNameById.get(g.receiverContactId) : null
|
||||
const receiver = g.receiverContactId ? contactNameById.get(g.receiverContactId) ?? null : null
|
||||
const tJf = t.detail.juvenileFields
|
||||
return (
|
||||
<li key={g.id}>
|
||||
<Link to={`/rennmaeuse/${g.id}`} className="gerbil-card">
|
||||
<span className="gerbil-card__name">{g.name}</span>
|
||||
<span className="gerbil-card__meta">{genderLabel(g.gender)}</span>
|
||||
{farbe && <span className="gerbil-card__meta">{tJf.colorVariety}: {farbe}</span>}
|
||||
{g.goHomeDate && <span className="gerbil-card__meta">{tJf.goHomeDate}: {formatDate(g.goHomeDate)}</span>}
|
||||
{receiver && <span className="gerbil-card__meta">{tJf.receiver}: {receiver}</span>}
|
||||
{g.dateOfDeath && <span className="gerbil-card__meta">{tJf.dateOfDeath}: {formatDate(g.dateOfDeath)}</span>}
|
||||
{g.causeOfDeath && <span className="gerbil-card__meta">{tJf.causeOfDeath}: {g.causeOfDeath}</span>}
|
||||
</Link>
|
||||
<JuvenileCard g={g} farbe={farbe} receiver={receiver} tJf={tJf} />
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user