FEAT-3: litter type+CRUD, Zucht strings, /wuerfe nested routes (retire LittersPage)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 00:27:29 +02:00
parent d7be812ad0
commit 87250ba007
8 changed files with 141 additions and 14 deletions

View File

@@ -11,7 +11,9 @@ import HomePage from './pages/HomePage'
import KontaktePage from './pages/KontaktePage'
import KontaktDetailPage from './pages/KontaktDetailPage'
import KontaktFormPage from './pages/KontaktFormPage'
import LittersPage from './pages/LittersPage'
import WuerfeListPage from './pages/WuerfeListPage'
import WurfDetailPage from './pages/WurfDetailPage'
import WurfFormPage from './pages/WurfFormPage'
import NotFoundPage from './pages/NotFoundPage'
import StammbaumPage from './pages/StammbaumPage'
@@ -28,7 +30,12 @@ export default function App() {
<Route path=":id/bearbeiten" element={<GerbilFormPage />} />
<Route path=":id/stammbaum" element={<StammbaumPage />} />
</Route>
<Route path="wuerfe" element={<LittersPage />} />
<Route path="wuerfe">
<Route index element={<WuerfeListPage />} />
<Route path="neu" element={<WurfFormPage />} />
<Route path=":id" element={<WurfDetailPage />} />
<Route path=":id/bearbeiten" element={<WurfFormPage />} />
</Route>
{/* FEAT-2: Kontakte ersetzt das Züchter-Platzhalter-Routing (Breeder -> Contact) */}
<Route path="becken">
<Route index element={<BeckenPage />} />

View File

@@ -1,7 +1,24 @@
/** Typed API functions for the Würfe (litters) resource. */
import { api, resources } from './client'
import type { Litter } from './types'
import { toQueryString, type GridifyQuery } from './gridify'
import type { CreateLitter, Litter, Paged, UpdateLitter } from './types'
export function getLitter(id: string): Promise<Litter> {
return api.get<Litter>(`${resources.litters}/${id}`)
}
export function listLitters(query: GridifyQuery): Promise<Paged<Litter>> {
return api.get<Paged<Litter>>(`${resources.litters}${toQueryString(query)}`)
}
export function createLitter(body: CreateLitter): Promise<Litter> {
return api.post<Litter>(resources.litters, body)
}
export function updateLitter(id: string, body: UpdateLitter): Promise<Litter> {
return api.put<Litter>(`${resources.litters}/${id}`, body)
}
export function deleteLitter(id: string): Promise<void> {
return api.delete(`${resources.litters}/${id}`)
}

View File

@@ -80,12 +80,34 @@ export interface Contact {
export interface Litter {
id: string
name: string
/** Wurfdatum (birth date). */
date: DateOnlyString
/** Wurfstärke — total born count. */
totalBorn: number | null
/**
* Abgabedatum (auto-suggested ~35 days after birth, editable).
* Optional in the type so existing Litter constructors (e.g. pedigree test
* factories) keep compiling; the API DTO always includes it (nullable).
*/
expectedGoHomeDate?: DateOnlyString | null
notes?: string | null
fatherId: string | null
motherId: string | null
}
/** Payload for POST /litters. */
export interface CreateLitter {
name: string
date: DateOnlyString
totalBorn?: number | null
expectedGoHomeDate?: DateOnlyString | null
notes?: string | null
fatherId?: string | null
motherId?: string | null
}
export type UpdateLitter = Partial<CreateLitter>
/** Paged list envelope returned by Gridify list endpoints. */
export interface Paged<T> {
items: T[]

View File

@@ -1,10 +0,0 @@
import { de } from '../strings/de'
export default function LittersPage() {
return (
<section>
<h2>{de.pages.litters.title}</h2>
<p>{de.pages.litters.comingSoon}</p>
</section>
)
}

View File

@@ -0,0 +1,10 @@
import { de } from '../strings/de'
export default function WuerfeListPage() {
// Fleshed out in the next FEAT-3 increment (Gridify list, year filter, sort).
return (
<section className="page">
<h2>{de.pages.litters.title}</h2>
</section>
)
}

View File

@@ -0,0 +1,10 @@
import { de } from '../strings/de'
export default function WurfDetailPage() {
// Fleshed out in a later FEAT-3 increment (litter workspace + Jungtier erfassen).
return (
<section className="page">
<h2>{de.pages.litters.title}</h2>
</section>
)
}

View File

@@ -0,0 +1,10 @@
import { de } from '../strings/de'
export default function WurfFormPage() {
// Fleshed out in a later FEAT-3 increment (parents picker, auto go-home date).
return (
<section className="page">
<h2>{de.pages.litters.form.createTitle}</h2>
</section>
)
}

View File

@@ -117,9 +117,70 @@ export const de = {
GivenAway: 'Abgegeben',
},
},
// ── FEAT-3 (Kevin): Zucht — Würfe + Zuchtpaare ──
litters: {
title: 'Würfe',
comingSoon: 'Die Wurf-Verwaltung entsteht gerade. Bald kannst du hier Würfe dokumentieren.',
tabs: {
litters: 'Würfe',
pairs: 'Zuchtpaare',
},
// Liste
newButton: 'Neuer Wurf',
empty: 'Keine Würfe gefunden.',
countLabel: 'Würfe',
filterYear: 'Jahr',
allYears: 'Alle Jahre',
sort: {
dateDesc: 'Wurfdatum (neueste)',
dateAsc: 'Wurfdatum (älteste)',
},
// Felder
fields: {
name: 'Bezeichnung',
date: 'Wurfdatum',
father: 'Vater',
mother: 'Mutter',
totalBorn: 'Wurfstärke',
expectedGoHomeDate: 'Voraussichtliches Abgabedatum',
notes: 'Notizen',
},
// Detail / Arbeitsbereich
detail: {
parents: 'Elterntiere',
registered: 'Erfasste Jungtiere',
juveniles: 'Jungtiere',
registerJuvenile: 'Jungtier erfassen',
expectedColors: 'Erwartete Farbschläge dieser Verpaarung',
needParentsGenotype:
'Für die Farbvorhersage müssen bei Vater und Mutter Genotypen hinterlegt sein.',
noJuveniles: 'Noch keine Jungtiere erfasst.',
unknownParent: 'Unbekannt',
edit: 'Bearbeiten',
back: 'Zurück zur Liste',
notFound: 'Dieser Wurf wurde nicht gefunden.',
},
// Formular
form: {
createTitle: 'Neuen Wurf anlegen',
editTitle: 'Wurf bearbeiten',
none: '— keine Angabe —',
goHomeHint: 'Vorschlag: 35 Tage nach dem Wurfdatum. Anpassbar.',
save: 'Speichern',
cancel: 'Abbrechen',
saving: 'Speichern …',
},
validation: {
nameRequired: 'Bitte eine Bezeichnung eingeben.',
dateRequired: 'Bitte ein Wurfdatum angeben.',
},
// Zuchtpaar-Übersicht
pairs: {
title: 'Zuchtpaare',
empty: 'Noch keine Zuchtpaare — diese ergeben sich aus den erfassten Würfen.',
litterCount: 'Würfe',
lastLitter: 'Letzter Wurf',
testMating: 'Probeverpaarung',
},
},
breeders: {
title: 'Züchter',