feat(rennmaeuse): Tiere-Liste neu im Rennmaus-Filter-Design
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m2s
CI / Frontend Tests (Node/Vite) (push) Failing after 4m54s
CI / Docker Build & Push (push) Has been skipped

Filter-UX aus dem Design:
- Quick-Chip-Kategorien (Status/Geschlecht/Farbschlag/Herkunft) öffnen ein
  Bottom-Sheet mit Mehrfachauswahl (suchbar bei Farbschlag/Herkunft); aktive
  Filter als entfernbare Chips; Sortierung + "Externe Ahnen" als gleich große
  Chips (Sortierung öffnet dasselbe Drawer).
- Neue zweizeilige Tierkarte (Name + Geschlechts-Kreis + Status-Badge, Meta-Zeile),
  cardIn/Chip/Sheet-Animationen, Titel blendet beim Scrollen aus.

App-weit:
- Design-Palette (Creme/Braun/Terrakotta), Hanken-Grotesk-Schrift und gemeinsamer
  --shadow-Token; obere "Rennmaus-Manager"-Titelleiste entfernt (mobil bleibt die
  untere Aktionsleiste); fixe Shell-Höhe, nur der Hauptbereich scrollt.

Funktion:
- Infinite Scroll via useInfiniteList (seitenweise) + IntersectionObserver-Sentinel.
- Namensgenerator: Option männlich/weiblich/egal.
- Mehrfach-OR-Filter über gridify orGroup; e2e an die neue UI angepasst.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-13 01:05:44 +02:00
parent 0185446bfe
commit 5732398e60
19 changed files with 1254 additions and 149 deletions

View File

@@ -61,6 +61,23 @@ export function andFilter(...conditions: Array<string | null | undefined | false
return conditions.filter((c): c is string => Boolean(c)).join(',')
}
/**
* Build an OR-group of conditions for one field, e.g. `(status=Breeding|status=ForSale)`.
* Used for multi-select filters: several allowed values within one category are OR'd,
* while categories are AND'd together by {@link andFilter}.
* Returns '' for an empty value list (so andFilter drops it); a single value needs no parens.
*/
export function orGroup(
field: string,
values: ReadonlyArray<string | number | boolean>,
op: GridifyOp = '==',
): string {
const parts = values.map((value) => condition({ field, op, value }))
if (parts.length === 0) return ''
if (parts.length === 1) return parts[0]
return `(${parts.join('|')})`
}
/** Assemble the URL query string ("?...") from a GridifyQuery. */
export function toQueryString(q: GridifyQuery): string {
const params = new URLSearchParams()