FEAT-5: Probeverpaarung page — free genotype entry, client-side breed(), Farbschlag cards + warnings

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 00:06:22 +02:00
parent f7ba1fa70e
commit 5a6326a967
4 changed files with 287 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
import type { ReactNode } from 'react'
import { isValidGenotype, previewFarbschlag } from '../format/genotypeText'
import { de } from '../strings/de'
export interface ParentValue {
/** Free-text genotype (GEN-1 compact string; '?' wildcards allowed). */
genotype: string
/** Name of a picked animal, if the genotype came from one (display only). */
animalName?: string | null
}
export interface ParentSelectorProps {
label: string
value: ParentValue
onChange: (value: ParentValue) => void
/** Optional animal-picker UI (FEAT-5 increment 3) rendered above the input. */
picker?: ReactNode
}
export default function ParentSelector({ label, value, onChange, picker }: ParentSelectorProps) {
const t = de.pages.genetik
const preview = previewFarbschlag(value.genotype)
const invalid = value.genotype.trim() !== '' && !isValidGenotype(value.genotype)
return (
<fieldset className="parent-selector">
<legend>{label}</legend>
{picker}
{value.animalName && (
<p className="muted parent-selector__animal">
{value.animalName}
<button
type="button"
className="link-btn"
onClick={() => onChange({ genotype: '', animalName: null })}
>
{t.clearAnimal}
</button>
</p>
)}
<label className="field">
<span>{t.genotypeLabel}</span>
<input
className="input"
value={value.genotype}
placeholder="Aa CC Dd EE GG Pp Spsp rere"
onChange={(e) => onChange({ genotype: e.target.value, animalName: null })}
aria-invalid={invalid}
/>
{invalid ? (
<small className="error-text">{t.genotypeInvalid}</small>
) : preview ? (
<small className="muted">
{t.genotypePreview}: <strong>{preview}</strong>
</small>
) : (
<small className="muted">{t.genotypeHint}</small>
)}
</label>
</fieldset>
)
}