FEAT: Implement deceased/givenaway enclosure visibility rules, preserve external clan name, and hide receiver fields for deceased gerbils
This commit is contained in:
@@ -49,6 +49,7 @@ export default function GerbilDetailPage() {
|
||||
const [charTraits, setCharTraits] = useState<string[]>([])
|
||||
const [charNote, setCharNote] = useState('')
|
||||
const [charInit, setCharInit] = useState<string | null>(null)
|
||||
const [isCharacterExpanded, setIsCharacterExpanded] = useState(true)
|
||||
const saveCharacter = useMutation(() =>
|
||||
updateGerbil(id, { characterTraits: charTraits, characterNote: charNote.trim() || null }),
|
||||
)
|
||||
@@ -78,6 +79,12 @@ export default function GerbilDetailPage() {
|
||||
[litters.data],
|
||||
)
|
||||
|
||||
const receiverContact = useMemo(() => {
|
||||
const receiverId = gerbil.data?.receiverContactId
|
||||
if (!receiverId || !contacts.data) return null
|
||||
return contacts.data.find((c) => c.id === receiverId) ?? null
|
||||
}, [gerbil.data?.receiverContactId, contacts.data])
|
||||
|
||||
if (gerbil.loading) return <p className="muted">{de.common.loading}</p>
|
||||
if (gerbil.error || !gerbil.data) {
|
||||
return (
|
||||
@@ -91,6 +98,11 @@ export default function GerbilDetailPage() {
|
||||
}
|
||||
|
||||
const g = gerbil.data
|
||||
const showEnclosure = g.status !== 'Deceased' && (
|
||||
g.status !== 'GivenAway' ||
|
||||
(receiverContact ? (receiverContact.isBreeder || receiverContact.isReceiver) : false)
|
||||
)
|
||||
|
||||
const geno = describeGenotype(g.genotype)
|
||||
const lookup = (map: Map<string, string>, key: string | null) => (key ? (map.get(key) ?? '—') : '—')
|
||||
const storedColorName = g.colorVarietyId ? (colorName.get(g.colorVarietyId) ?? null) : null
|
||||
@@ -100,20 +112,25 @@ export default function GerbilDetailPage() {
|
||||
setCharInit(g.id)
|
||||
setCharTraits(g.characterTraits ?? [])
|
||||
setCharNote(g.characterNote ?? '')
|
||||
setIsCharacterExpanded(g.status !== 'Deceased' && g.status !== 'GivenAway')
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="page">
|
||||
<header className="page-head">
|
||||
<div>
|
||||
<div className="profile-header-summary">
|
||||
<GerbilProfilePhoto gerbilId={g.id} />
|
||||
<h2>{g.name || de.pages.gerbils.nameless}</h2>
|
||||
<span className={`badge badge--${g.status.toLowerCase()}`}>{statusLabel(g.status)}</span>
|
||||
{g.isResident === false && (
|
||||
<span className="badge badge--external" title={de.pages.gerbils.externalTitle}>
|
||||
{de.pages.gerbils.externalBadge}
|
||||
</span>
|
||||
)}
|
||||
<div className="profile-header-info">
|
||||
<h2>{g.name || de.pages.gerbils.nameless}</h2>
|
||||
<div className="profile-header-badges">
|
||||
<span className={`badge badge--${g.status.toLowerCase()}`}>{statusLabel(g.status)}</span>
|
||||
{g.isResident === false && (
|
||||
<span className="badge badge--external" title={de.pages.gerbils.externalTitle}>
|
||||
{de.pages.gerbils.externalBadge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="head-actions">
|
||||
<Link to={`/rennmaeuse/${g.id}/bearbeiten`} className="btn btn--primary">
|
||||
@@ -122,7 +139,7 @@ export default function GerbilDetailPage() {
|
||||
<Link to={`/genetik?parent=${g.id}`} className="btn">
|
||||
{t.detail.testMating}
|
||||
</Link>
|
||||
{g.status !== 'ForSale' && (
|
||||
{g.status !== 'ForSale' && g.status !== 'Deceased' && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn"
|
||||
@@ -152,7 +169,15 @@ export default function GerbilDetailPage() {
|
||||
|
||||
<h3>{t.detail.masterData}</h3>
|
||||
<dl className="def-list">
|
||||
<Row label={t.fields.gender} value={genderLabel(g.gender)} />
|
||||
<Row
|
||||
label={t.fields.gender}
|
||||
value={
|
||||
<>
|
||||
{genderLabel(g.gender)}
|
||||
{g.isCastrated && <span style={{ marginLeft: '0.5rem', opacity: 0.8 }}>(Kastrat)</span>}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Row label={t.fields.status} value={statusLabel(g.status)} />
|
||||
<Row label={t.fields.dateOfBirth} value={formatDate(g.dateOfBirth)} />
|
||||
{g.status === 'Deceased' && (
|
||||
@@ -177,7 +202,18 @@ export default function GerbilDetailPage() {
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Row label={t.fields.enclosure} value={lookup(enclosureName, g.enclosureId)} />
|
||||
{g.spottingType && (
|
||||
<Row
|
||||
label={t.fields.spottingType}
|
||||
value={
|
||||
de.pages.gerbils.spottingTypes[g.spottingType as keyof typeof de.pages.gerbils.spottingTypes] ||
|
||||
g.spottingType
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{showEnclosure && (
|
||||
<Row label={t.fields.enclosure} value={lookup(enclosureName, g.enclosureId)} />
|
||||
)}
|
||||
<Row
|
||||
label={t.fields.litter}
|
||||
value={
|
||||
@@ -194,7 +230,9 @@ export default function GerbilDetailPage() {
|
||||
: (g.originBreeder || null)
|
||||
}
|
||||
/>
|
||||
<Row label={t.fields.receiver} value={lookup(contactName, g.receiverContactId)} />
|
||||
{g.status !== 'Deceased' && (
|
||||
<Row label={t.fields.receiver} value={lookup(contactName, g.receiverContactId)} />
|
||||
)}
|
||||
<Row label={t.fields.notes} value={g.notes || null} />
|
||||
</dl>
|
||||
|
||||
@@ -209,7 +247,7 @@ export default function GerbilDetailPage() {
|
||||
{geno.farbschlag}
|
||||
{storedColorName &&
|
||||
geno.farbschlag !== de.genetics.unknownFarbschlag &&
|
||||
storedColorName !== geno.farbschlag && (
|
||||
storedColorName !== geno.farbschlag.replace(' Schecke', '').replace(' Rex', '') && (
|
||||
<small className="mismatch-hint"> ⚠ {t.detail.farbschlagMismatch}</small>
|
||||
)}
|
||||
</>
|
||||
@@ -220,27 +258,38 @@ export default function GerbilDetailPage() {
|
||||
<p className="muted">{t.detail.genotypeNotSet}</p>
|
||||
)}
|
||||
|
||||
<h3>{de.character.sectionTitle}</h3>
|
||||
<Charakterbogen
|
||||
traits={charTraits}
|
||||
note={charNote}
|
||||
onTraitsChange={setCharTraits}
|
||||
onNoteChange={setCharNote}
|
||||
/>
|
||||
<div className="form-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn--primary"
|
||||
disabled={saveCharacter.pending}
|
||||
onClick={async () => {
|
||||
const r = await saveCharacter.run()
|
||||
if (r.ok) gerbil.reload()
|
||||
}}
|
||||
>
|
||||
{saveCharacter.pending ? t.form.saving : de.character.save}
|
||||
</button>
|
||||
{saveCharacter.error && <span className="error-text">{saveCharacter.error}</span>}
|
||||
</div>
|
||||
<details
|
||||
className="charakter-details"
|
||||
open={isCharacterExpanded}
|
||||
onToggle={(e) => setIsCharacterExpanded(e.currentTarget.open)}
|
||||
>
|
||||
<summary className="charakter-summary">
|
||||
<h3>{de.character.sectionTitle}</h3>
|
||||
<span className="charakter-chevron" aria-hidden="true">▾</span>
|
||||
</summary>
|
||||
<div className="charakter-body">
|
||||
<Charakterbogen
|
||||
traits={charTraits}
|
||||
note={charNote}
|
||||
onTraitsChange={setCharTraits}
|
||||
onNoteChange={setCharNote}
|
||||
/>
|
||||
<div className="form-actions" style={{ marginTop: '1rem' }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn--primary"
|
||||
disabled={saveCharacter.pending}
|
||||
onClick={async () => {
|
||||
const r = await saveCharacter.run()
|
||||
if (r.ok) gerbil.reload()
|
||||
}}
|
||||
>
|
||||
{saveCharacter.pending ? t.form.saving : de.character.save}
|
||||
</button>
|
||||
{saveCharacter.error && <span className="error-text">{saveCharacter.error}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<h3>{t.detail.parentLittersTitle}</h3>
|
||||
{parentLitters.loading && <p className="muted">{de.common.loading}</p>}
|
||||
|
||||
Reference in New Issue
Block a user