FEAT-5: Probeverpaarung entry point from animal detail (?parent=) + stored-vs-engine Farbschlag mismatch hint
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -597,3 +597,8 @@ textarea {
|
|||||||
.animal-picker__hint {
|
.animal-picker__hint {
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mismatch-hint {
|
||||||
|
color: #7a5a14;
|
||||||
|
margin-left: 0.4rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { de } from '../strings/de'
|
import { de } from '../strings/de'
|
||||||
import { breed, fromDisplayString, type BreedingResult } from '../genetics'
|
import { breed, fromDisplayString, type BreedingResult } from '../genetics'
|
||||||
|
import { getGerbil } from '../api/gerbils'
|
||||||
|
import { useApi } from '../hooks/useApi'
|
||||||
import ParentSelector, { type ParentValue } from '../components/ParentSelector'
|
import ParentSelector, { type ParentValue } from '../components/ParentSelector'
|
||||||
import AnimalPicker from '../components/AnimalPicker'
|
import AnimalPicker from '../components/AnimalPicker'
|
||||||
import { isValidGenotype } from '../format/genotypeText'
|
import { isValidGenotype } from '../format/genotypeText'
|
||||||
@@ -18,6 +21,22 @@ export default function GenetikPage() {
|
|||||||
const [mother, setMother] = useState<ParentValue>(EMPTY_PARENT)
|
const [mother, setMother] = useState<ParentValue>(EMPTY_PARENT)
|
||||||
const [showGenotypes, setShowGenotypes] = useState(false)
|
const [showGenotypes, setShowGenotypes] = useState(false)
|
||||||
|
|
||||||
|
// Pre-fill a parent when arriving from an animal's detail page (?parent=<id>).
|
||||||
|
// Gender decides the side: female -> Mutter, otherwise Vater.
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
const parentId = searchParams.get('parent')
|
||||||
|
const prefill = useApi(
|
||||||
|
() => (parentId ? getGerbil(parentId) : Promise.resolve(null)),
|
||||||
|
[parentId],
|
||||||
|
)
|
||||||
|
const [prefilledFor, setPrefilledFor] = useState<string | null>(null)
|
||||||
|
if (prefill.data && prefilledFor !== prefill.data.id) {
|
||||||
|
setPrefilledFor(prefill.data.id)
|
||||||
|
const value: ParentValue = { genotype: prefill.data.genotype ?? '', animalName: prefill.data.name }
|
||||||
|
if (prefill.data.gender === 'female') setMother(value)
|
||||||
|
else setFather(value)
|
||||||
|
}
|
||||||
|
|
||||||
const bothValid = isValidGenotype(father.genotype) && isValidGenotype(mother.genotype)
|
const bothValid = isValidGenotype(father.genotype) && isValidGenotype(mother.genotype)
|
||||||
|
|
||||||
const result: BreedingResult | null = useMemo(() => {
|
const result: BreedingResult | null = useMemo(() => {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export default function GerbilDetailPage() {
|
|||||||
const g = gerbil.data
|
const g = gerbil.data
|
||||||
const geno = describeGenotype(g.genotype)
|
const geno = describeGenotype(g.genotype)
|
||||||
const lookup = (map: Map<string, string>, key: string | null) => (key ? (map.get(key) ?? '—') : '—')
|
const lookup = (map: Map<string, string>, key: string | null) => (key ? (map.get(key) ?? '—') : '—')
|
||||||
|
const storedColorName = g.colorVarietyId ? (colorName.get(g.colorVarietyId) ?? null) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="page">
|
<section className="page">
|
||||||
@@ -85,6 +86,9 @@ export default function GerbilDetailPage() {
|
|||||||
<Link to={`/rennmaeuse/${g.id}/bearbeiten`} className="btn btn--primary">
|
<Link to={`/rennmaeuse/${g.id}/bearbeiten`} className="btn btn--primary">
|
||||||
{t.detail.edit}
|
{t.detail.edit}
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link to={`/genetik?parent=${g.id}`} className="btn">
|
||||||
|
{t.detail.testMating}
|
||||||
|
</Link>
|
||||||
<Link to="/rennmaeuse" className="btn">
|
<Link to="/rennmaeuse" className="btn">
|
||||||
{t.detail.back}
|
{t.detail.back}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -117,7 +121,19 @@ export default function GerbilDetailPage() {
|
|||||||
{geno ? (
|
{geno ? (
|
||||||
<dl className="def-list">
|
<dl className="def-list">
|
||||||
<Row label={t.fields.genotype} value={<code>{geno.display}</code>} />
|
<Row label={t.fields.genotype} value={<code>{geno.display}</code>} />
|
||||||
<Row label={t.detail.resolvedFarbschlag} value={geno.farbschlag} />
|
<Row
|
||||||
|
label={t.detail.resolvedPrefix}
|
||||||
|
value={
|
||||||
|
<>
|
||||||
|
{geno.farbschlag}
|
||||||
|
{storedColorName &&
|
||||||
|
geno.farbschlag !== de.genetics.unknownFarbschlag &&
|
||||||
|
storedColorName !== geno.farbschlag && (
|
||||||
|
<small className="mismatch-hint"> ⚠ {t.detail.farbschlagMismatch}</small>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</dl>
|
</dl>
|
||||||
) : (
|
) : (
|
||||||
<p className="muted">{t.detail.genotypeNotSet}</p>
|
<p className="muted">{t.detail.genotypeNotSet}</p>
|
||||||
|
|||||||
@@ -73,7 +73,10 @@ export const de = {
|
|||||||
masterData: 'Stammdaten',
|
masterData: 'Stammdaten',
|
||||||
genetics: 'Genetik',
|
genetics: 'Genetik',
|
||||||
resolvedFarbschlag: 'Errechneter Farbschlag',
|
resolvedFarbschlag: 'Errechneter Farbschlag',
|
||||||
|
resolvedPrefix: 'Errechnet',
|
||||||
|
farbschlagMismatch: 'Weicht vom eingetragenen Farbschlag ab.',
|
||||||
genotypeNotSet: 'Kein Genotyp hinterlegt.',
|
genotypeNotSet: 'Kein Genotyp hinterlegt.',
|
||||||
|
testMating: 'Probeverpaarung',
|
||||||
edit: 'Bearbeiten',
|
edit: 'Bearbeiten',
|
||||||
back: 'Zurück zur Liste',
|
back: 'Zurück zur Liste',
|
||||||
tabs: {
|
tabs: {
|
||||||
@@ -192,6 +195,34 @@ export const de = {
|
|||||||
'Becken ist nicht leer — bitte zuerst alle Tiere in ein anderes Becken umsetzen.',
|
'Becken ist nicht leer — bitte zuerst alle Tiere in ein anderes Becken umsetzen.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// ── FEAT-4 (Kelly): Stammbaum (Ahnentafel) ──
|
||||||
|
stammbaum: {
|
||||||
|
title: 'Stammbaum',
|
||||||
|
/** Überschrift mit Tiername. */
|
||||||
|
titleFor: (name: string) => `Stammbaum von ${name}`,
|
||||||
|
/** Button auf der Tier-Detailseite (Integration durch FEAT-1). */
|
||||||
|
openButton: 'Stammbaum',
|
||||||
|
backToAnimal: 'Zur Tierakte',
|
||||||
|
notFound: 'Diese Rennmaus wurde nicht gefunden.',
|
||||||
|
unknown: 'unbekannt',
|
||||||
|
/** Auf Karten am Rand: weitere Vorfahren nachladen. */
|
||||||
|
expand: 'Vorfahren laden',
|
||||||
|
tapHint: 'Tippe auf ein Tier, um dessen Stammbaum anzuzeigen.',
|
||||||
|
zoomIn: 'Vergrößern',
|
||||||
|
zoomOut: 'Verkleinern',
|
||||||
|
zoomFit: 'Ansicht einpassen',
|
||||||
|
print: 'Drucken / PDF',
|
||||||
|
inbreeding: {
|
||||||
|
label: 'Inzuchtkoeffizient',
|
||||||
|
unavailable: 'wird berechnet …',
|
||||||
|
},
|
||||||
|
printView: {
|
||||||
|
/** Spaltenüberschriften der Ahnentafel, Index = Generation. */
|
||||||
|
generations: ['Tier', 'Eltern', 'Großeltern', 'Urgroßeltern', 'Ur-Urgroßeltern'],
|
||||||
|
createdOn: 'Erstellt am',
|
||||||
|
born: 'geb.',
|
||||||
|
},
|
||||||
|
},
|
||||||
// ── FEAT-2 (Oscar): Kontakte (Contacts — Herkunft/Abnehmer) ──
|
// ── FEAT-2 (Oscar): Kontakte (Contacts — Herkunft/Abnehmer) ──
|
||||||
kontakte: {
|
kontakte: {
|
||||||
title: 'Kontakte',
|
title: 'Kontakte',
|
||||||
|
|||||||
Reference in New Issue
Block a user