FEAT: Implement deceased/givenaway enclosure visibility rules, preserve external clan name, and hide receiver fields for deceased gerbils
This commit is contained in:
@@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'
|
||||
import { de } from '../strings/de'
|
||||
import { listLitters } from '../api/litters'
|
||||
import { listGerbils } from '../api/gerbils'
|
||||
import { andFilter, condition, type GridifyQuery } from '../api/gridify'
|
||||
import { andFilter, condition, escapeGridifyValue, type GridifyQuery } from '../api/gridify'
|
||||
import type { Litter } from '../api/types'
|
||||
import { useApi } from '../hooks/useApi'
|
||||
import { formatDate } from '../format/labels'
|
||||
@@ -17,7 +17,7 @@ const SORT_ORDER_BY: Record<SortKey, string> = { dateDesc: 'date desc', dateAsc:
|
||||
|
||||
function yearOptions(): number[] {
|
||||
const current = new Date().getFullYear()
|
||||
return Array.from({ length: 16 }, (_, i) => current - i)
|
||||
return Array.from({ length: 30 }, (_, i) => current - i)
|
||||
}
|
||||
|
||||
interface Pair {
|
||||
@@ -47,6 +47,7 @@ function derivePairs(litters: Litter[]): Pair[] {
|
||||
export default function WuerfeListPage() {
|
||||
const t = de.pages.litters
|
||||
const [tab, setTab] = useState<Tab>('litters')
|
||||
const [search, setSearch] = useState('')
|
||||
const [year, setYear] = useState('')
|
||||
const [sort, setSort] = useState<SortKey>('dateDesc')
|
||||
const [page, setPage] = useState(1)
|
||||
@@ -62,6 +63,7 @@ export default function WuerfeListPage() {
|
||||
|
||||
// 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` }),
|
||||
)
|
||||
@@ -76,6 +78,16 @@ export default function WuerfeListPage() {
|
||||
)
|
||||
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, nameById])
|
||||
|
||||
const total = litters.data?.totalCount ?? 0
|
||||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))
|
||||
const items = litters.data?.items ?? []
|
||||
@@ -113,8 +125,26 @@ export default function WuerfeListPage() {
|
||||
{tab === 'litters' && (
|
||||
<>
|
||||
<FilterPanel
|
||||
activeCount={year !== '' ? 1 : 0}
|
||||
onReset={() => { setYear(''); setSort('dateDesc'); setPage(1) }}
|
||||
searchField={
|
||||
<input
|
||||
type="search"
|
||||
className="input"
|
||||
placeholder={t.searchPlaceholder}
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
setSearch(e.target.value)
|
||||
setPage(1)
|
||||
}}
|
||||
aria-label="Wurf suchen"
|
||||
/>
|
||||
}
|
||||
activeCount={(year !== '' ? 1 : 0) + (search.trim() !== '' ? 1 : 0)}
|
||||
onReset={() => {
|
||||
setSearch('')
|
||||
setYear('')
|
||||
setSort('dateDesc')
|
||||
setPage(1)
|
||||
}}
|
||||
>
|
||||
<label className="field">
|
||||
<span>{t.filterYear}</span>
|
||||
@@ -202,11 +232,31 @@ export default function WuerfeListPage() {
|
||||
|
||||
{tab === 'pairs' && (
|
||||
<>
|
||||
<div style={{ marginBottom: '1.5rem', display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<input
|
||||
type="search"
|
||||
className="input"
|
||||
style={{ maxWidth: '320px' }}
|
||||
placeholder="Elternteil suchen …"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
aria-label="Elternteil suchen"
|
||||
/>
|
||||
{search && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn--secondary btn--sm"
|
||||
onClick={() => setSearch('')}
|
||||
>
|
||||
{de.pages.gerbils.filters.reset}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{allLitters.loading && <p className="muted">{de.common.loading}</p>}
|
||||
{!allLitters.loading && pairs.length === 0 && <p className="muted">{t.pairs.empty}</p>}
|
||||
{pairs.length > 0 && (
|
||||
{!allLitters.loading && filteredPairs.length === 0 && <p className="muted">{t.pairs.empty}</p>}
|
||||
{filteredPairs.length > 0 && (
|
||||
<ul className="card-list">
|
||||
{pairs.map((p) => (
|
||||
{filteredPairs.map((p) => (
|
||||
<li key={`${p.fatherId}|${p.motherId}`} className="gerbil-card">
|
||||
<span className="gerbil-card__name">
|
||||
{parentName(p.fatherId)} × {parentName(p.motherId)}
|
||||
|
||||
Reference in New Issue
Block a user