FEAT-14b: Charakterbogen on animal detail (persist) + Abgabe composer; feed traits+note into sale-ad; e2e smoke
This commit is contained in:
@@ -7,7 +7,9 @@ import { listGerbilPhotos, photoSrc, type GerbilPhoto } from '../api/photos'
|
||||
import { generateSaleAd } from '../api/saleAd'
|
||||
import { useApi, useMutation } from '../hooks/useApi'
|
||||
import { formatDate, genderLabel } from '../format/labels'
|
||||
import { traitLabels } from '../format/traits'
|
||||
import FarbschlagImage from './FarbschlagImage'
|
||||
import Charakterbogen from './Charakterbogen'
|
||||
|
||||
type SaleStatus = 'free' | 'loose' | 'reserved'
|
||||
|
||||
@@ -34,9 +36,13 @@ export default function GroupComposer({ groupNumber, animals, farbschlagOf }: Gr
|
||||
const [status, setStatus] = useState<SaleStatus>('free')
|
||||
const [reservedName, setReservedName] = useState('')
|
||||
const [tagline, setTagline] = useState('')
|
||||
// Per-animal personality text, seeded from notes on first render.
|
||||
// Per-animal character note, seeded from the persisted characterNote (or notes).
|
||||
const [personality, setPersonality] = useState<Record<string, string>>(() =>
|
||||
Object.fromEntries(animals.map((a) => [a.id, a.notes ?? ''])),
|
||||
Object.fromEntries(animals.map((a) => [a.id, a.characterNote ?? a.notes ?? ''])),
|
||||
)
|
||||
// Per-animal selected trait keys, seeded from the persisted Charakterbogen.
|
||||
const [traitsByAnimal, setTraitsByAnimal] = useState<Record<string, string[]>>(() =>
|
||||
Object.fromEntries(animals.map((a) => [a.id, a.characterTraits ?? []])),
|
||||
)
|
||||
const [selectedPhotos, setSelectedPhotos] = useState<Record<string, boolean>>({})
|
||||
const [hints, setHints] = useState('')
|
||||
@@ -147,7 +153,9 @@ export default function GroupComposer({ groupNumber, animals, farbschlagOf }: Gr
|
||||
name: a.name,
|
||||
farbschlag: farbschlagOf(a),
|
||||
dateOfBirth: a.dateOfBirth,
|
||||
notes: personality[a.id] ?? a.notes ?? null,
|
||||
notes: a.notes ?? null,
|
||||
traits: traitLabels(traitsByAnimal[a.id] ?? []),
|
||||
characterNote: personality[a.id] ?? null,
|
||||
})),
|
||||
statusLine,
|
||||
hints,
|
||||
@@ -201,14 +209,12 @@ export default function GroupComposer({ groupNumber, animals, farbschlagOf }: Gr
|
||||
{a.dateOfBirth ? ` · ${t.listing.bornOn} ${formatDate(a.dateOfBirth)}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
<label className="field">
|
||||
<span>{t.listing.personality}</span>
|
||||
<textarea
|
||||
value={personality[a.id] ?? ''}
|
||||
placeholder={t.listing.personalityPlaceholder}
|
||||
onChange={(e) => setPersonality((s) => ({ ...s, [a.id]: e.target.value }))}
|
||||
/>
|
||||
</label>
|
||||
<Charakterbogen
|
||||
traits={traitsByAnimal[a.id] ?? []}
|
||||
note={personality[a.id] ?? ''}
|
||||
onTraitsChange={(tr) => setTraitsByAnimal((s) => ({ ...s, [a.id]: tr }))}
|
||||
onNoteChange={(v) => setPersonality((s) => ({ ...s, [a.id]: v }))}
|
||||
/>
|
||||
<div className="photo-select">
|
||||
{(photosById.get(a.id) ?? []).length === 0 ? (
|
||||
<small className="muted">{t.listing.noPhotos}</small>
|
||||
|
||||
Reference in New Issue
Block a user