From c6968b996848051184acb9a335805bd49543bf43 Mon Sep 17 00:00:00 2001 From: Gulum Date: Sat, 13 Jun 2026 08:14:31 +0200 Subject: [PATCH] FEAT: Merge Anja Sch. into Anja Schaumburg, and keep infinite scroll layout pages --- gerbil-manager-web/src/pages/GerbilsPage.tsx | Bin 14787 -> 14242 bytes gerbil-manager-web/src/pages/KontaktePage.tsx | 21 ++----- .../src/pages/VertraegeListPage.tsx | 2 +- .../src/pages/WuerfeListPage.tsx | 59 ++++++------------ tools/import/merge_and_resolve.py | 1 + 5 files changed, 26 insertions(+), 57 deletions(-) diff --git a/gerbil-manager-web/src/pages/GerbilsPage.tsx b/gerbil-manager-web/src/pages/GerbilsPage.tsx index 4c5e68ab8cd2947bdcb3ef24df1f9835139a5a0f..03a22c4baa34e1def5ad1451126c8b296b91b268 100644 GIT binary patch delta 65 zcmX?HyeNOeRYp-Ah0@|w&%CtEyv&l+;MBa5%)Hc`%~u&SB__8?35uf2XyjxTmu#LT HqbUmjW1ksf delta 410 zcmaiw&q~8U5XQ;TV($_VJ}+$pp$&K|Rxe%@i{7P&HBOp^C0lmKKP7}dMBc!w7om^f zGx!EQ`4nc8^v}(Og_&=L`F=AW`rCf}c=xn|8 GgVR6be}V7- diff --git a/gerbil-manager-web/src/pages/KontaktePage.tsx b/gerbil-manager-web/src/pages/KontaktePage.tsx index 1f5d2e9..30e8b13 100644 --- a/gerbil-manager-web/src/pages/KontaktePage.tsx +++ b/gerbil-manager-web/src/pages/KontaktePage.tsx @@ -1,10 +1,10 @@ /** FEAT-2: Kontakte-Liste — Gridify-Namenssuche + Infinite Scroll, Link auf Detailseite. */ -import { useEffect, useRef, useState } from 'react' +import { useState } from 'react' import { Link } from 'react-router-dom' import { de } from '../strings/de' import { listContactsPaged } from '../api/contacts' import { condition } from '../api/gridify' -import { useInfiniteList } from '../hooks/useInfiniteList' +import { useInfiniteList, useInfiniteSentinel } from '../hooks/useInfiniteList' const PAGE_SIZE = 20 @@ -21,21 +21,8 @@ export default function KontaktePage() { (page) => listContactsPaged({ filter, orderBy: 'name,id', page, pageSize: PAGE_SIZE }), `${filter ?? ''}`, ) - const { items, total, hasMore, loading, loadingMore, loadMore } = list - - const sentinelRef = useRef(null) - useEffect(() => { - const el = sentinelRef.current - if (!el) return - const io = new IntersectionObserver( - (entries) => { - if (entries[0].isIntersecting && hasMore && !loading && !loadingMore) loadMore() - }, - { rootMargin: '300px 0px' }, - ) - io.observe(el) - return () => io.disconnect() - }, [hasMore, loading, loadingMore, loadMore]) + const sentinelRef = useInfiniteSentinel(list) + const { items, total, hasMore, loading, loadingMore } = list return (
diff --git a/gerbil-manager-web/src/pages/VertraegeListPage.tsx b/gerbil-manager-web/src/pages/VertraegeListPage.tsx index ae38704..26b1144 100644 --- a/gerbil-manager-web/src/pages/VertraegeListPage.tsx +++ b/gerbil-manager-web/src/pages/VertraegeListPage.tsx @@ -1,5 +1,5 @@ /** FEAT-13: Abgabeverträge — Liste mit Download/Löschen (/vertraege). */ -import { useMemo, useState } from 'react' +import { useMemo } from 'react' import { Link } from 'react-router-dom' import { de } from '../strings/de' import { contractFileUrl, deleteContract, listContracts } from '../api/contracts' diff --git a/gerbil-manager-web/src/pages/WuerfeListPage.tsx b/gerbil-manager-web/src/pages/WuerfeListPage.tsx index 6412969..558de63 100644 --- a/gerbil-manager-web/src/pages/WuerfeListPage.tsx +++ b/gerbil-manager-web/src/pages/WuerfeListPage.tsx @@ -3,9 +3,10 @@ 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, type GridifyQuery } from '../api/gridify' +import { andFilter, condition, escapeGridifyValue } from '../api/gridify' import type { 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' @@ -13,7 +14,8 @@ const PAGE_SIZE = 20 type Tab = 'litters' | 'pairs' type SortKey = 'dateDesc' | 'dateAsc' -const SORT_ORDER_BY: Record = { dateDesc: 'date desc', dateAsc: 'date' } +// 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() @@ -50,7 +52,6 @@ export default function WuerfeListPage() { const [search, setSearch] = useState('') const [year, setYear] = useState('') const [sort, setSort] = useState('dateDesc') - const [page, setPage] = useState(1) // Parent names (shared by both tabs). const gerbils = useApi(() => listGerbils({ page: 1, pageSize: 1000, orderBy: 'name' }), []) @@ -68,8 +69,11 @@ export default function WuerfeListPage() { year && condition({ field: 'date', op: '<=', value: `${year}-12-31` }), ) const orderBy = SORT_ORDER_BY[sort] - const query: GridifyQuery = { filter: filter || undefined, orderBy, page, pageSize: PAGE_SIZE } - const litters = useApi(() => listLitters(query), [filter, orderBy, page]) + const litters = useInfiniteList( + (page) => listLitters({ filter: filter || undefined, orderBy, page, pageSize: PAGE_SIZE }), + `${filter}|${orderBy}`, + ) + const littersSentinelRef = useInfiniteSentinel(litters) // Pairs tab: all litters, grouped client-side. const allLitters = useApi( @@ -88,9 +92,7 @@ export default function WuerfeListPage() { ) }, [pairs, search, nameById]) - const total = litters.data?.totalCount ?? 0 - const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)) - const items = litters.data?.items ?? [] + const items = litters.items return (
@@ -131,10 +133,7 @@ export default function WuerfeListPage() { className="input" placeholder={t.searchPlaceholder} value={search} - onChange={(e) => { - setSearch(e.target.value) - setPage(1) - }} + onChange={(e) => setSearch(e.target.value)} aria-label="Wurf suchen" /> } @@ -143,17 +142,13 @@ export default function WuerfeListPage() { setSearch('') setYear('') setSort('dateDesc') - setPage(1) }} >