import { useCallback, useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { de } from '../strings/de' import { listLitters } from '../api/litters' import { listGerbils } from '../api/gerbils' import { andFilter, condition, escapeGridifyValue } from '../api/gridify' import type { Gerbil, Litter } from '../api/types' import { useApi } from '../hooks/useApi' import { useInfiniteList, useInfiniteSentinel } from '../hooks/useInfiniteList' import { formatDate } from '../format/labels' import { FilterPanel } from '../components/FilterPanel' import { useGerbilName } from '../components/breederSuffix' const PAGE_SIZE = 20 type Tab = 'litters' | 'pairs' type SortKey = 'dateDesc' | 'dateAsc' // Trailing `id` → total order, so OFFSET pagination is stable for infinite scroll. const SORT_ORDER_BY: Record = { dateDesc: 'date desc,id', dateAsc: 'date,id' } function yearOptions(): number[] { const current = new Date().getFullYear() return Array.from({ length: 30 }, (_, i) => current - i) } interface Pair { fatherId: string motherId: string count: number lastDate: string } /** Group litters (with both parents known) by (father, mother). */ function derivePairs(litters: Litter[]): Pair[] { const map = new Map() for (const l of litters) { if (!l.fatherId || !l.motherId) continue const key = `${l.fatherId}|${l.motherId}` const existing = map.get(key) if (existing) { existing.count += 1 if (l.date > existing.lastDate) existing.lastDate = l.date } else { map.set(key, { fatherId: l.fatherId, motherId: l.motherId, count: 1, lastDate: l.date }) } } return Array.from(map.values()).sort((a, b) => b.lastDate.localeCompare(a.lastDate)) } export default function WuerfeListPage() { const t = de.pages.litters const [tab, setTab] = useState('litters') const [search, setSearch] = useState('') const [year, setYear] = useState('') const [sort, setSort] = useState('dateDesc') // Parent names (shared by both tabs) — keep the full gerbil so the Zucht suffix applies. const gerbilName = useGerbilName() const gerbils = useApi(() => listGerbils({ page: 1, pageSize: 1000, orderBy: 'name' }), []) const gerbilById = useMemo(() => { const map = new Map() for (const g of gerbils.data?.items ?? []) map.set(g.id, g) return map }, [gerbils.data]) const parentName = useCallback((id: string | null): string => { if (!id) return '—' const gp = gerbilById.get(id) return gp ? gerbilName(gp) || de.pages.gerbils.nameless : t.detail.unknownParent }, [gerbilById, gerbilName, t.detail.unknownParent]) // Litters tab (paged, filtered). const filter = andFilter( search.trim() && `(name=*${escapeGridifyValue(search)}/i|notes=*${escapeGridifyValue(search)}/i|litterLetter=*${escapeGridifyValue(search)}/i)`, year && condition({ field: 'date', op: '>=', value: `${year}-01-01` }), year && condition({ field: 'date', op: '<=', value: `${year}-12-31` }), ) const orderBy = SORT_ORDER_BY[sort] const litters = useInfiniteList( (page) => listLitters({ filter: filter || undefined, orderBy, page, pageSize: PAGE_SIZE }), `${filter}|${orderBy}`, 'wuerfe', ) const littersSentinelRef = useInfiniteSentinel(litters) // Pairs tab: all litters, grouped client-side. const allLitters = useApi( () => (tab === 'pairs' ? listLitters({ page: 1, pageSize: 1000, orderBy: 'date desc' }) : Promise.resolve(null)), [tab], ) const pairs = useMemo(() => derivePairs(allLitters.data?.items ?? []), [allLitters.data]) // Search-enabled pairs list (client-side search by parent name) const filteredPairs = useMemo(() => { if (!search.trim()) return pairs const term = search.toLowerCase() return pairs.filter((p) => parentName(p.fatherId).toLowerCase().includes(term) || parentName(p.motherId).toLowerCase().includes(term) ) }, [pairs, search, parentName]) const items = litters.items return (

{t.title}

+ {t.newButton}
{tab === 'litters' && ( <> setSearch(e.target.value)} aria-label="Wurf suchen" /> } activeCount={(year !== '' ? 1 : 0) + (search.trim() !== '' ? 1 : 0)} onReset={() => { setSearch('') setYear('') setSort('dateDesc') }} > {litters.loading &&

{de.common.loading}

} {litters.error && (
{litters.error}
)} {!litters.loading && !litters.error && items.length === 0 && (

{t.empty}

)} {items.length > 0 && (
    {items.map((l) => (
  • {l.name} {formatDate(l.date)} {parentName(l.fatherId)} × {parentName(l.motherId)} {l.totalBorn != null ? `${l.totalBorn} ${t.fields.totalBorn}` : '—'}
  • ))}
)} {/* Infinite-scroll sentinel + "loading more" indicator. */} {litters.hasMore && (
) }