From 76354cd968a541fd1ff98eeff750ea333d90ffa6 Mon Sep 17 00:00:00 2001 From: LinYushen Date: Wed, 8 Apr 2026 16:15:15 +0800 Subject: [PATCH] fix(board): show total count in Done column and infinite scroll (#498) * fix(board): show total count in Done column header and auto-load on scroll - Column header now shows server-side doneTotal instead of loaded count - Replace "Load more" button with IntersectionObserver sentinel for infinite scroll in the Done column Co-Authored-By: Claude Opus 4.6 (1M context) * fix(board): move sentinel below imports and stabilize observer - Move InfiniteScrollSentinel after all import statements - Use callback ref to avoid recreating IntersectionObserver on every render Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- apps/web/core/issues/mutations.ts | 2 +- .../issues/components/board-column.tsx | 4 +- .../features/issues/components/board-view.tsx | 39 +++++++++++++------ 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/apps/web/core/issues/mutations.ts b/apps/web/core/issues/mutations.ts index 85c15166..78493f10 100644 --- a/apps/web/core/issues/mutations.ts +++ b/apps/web/core/issues/mutations.ts @@ -67,7 +67,7 @@ export function useLoadMoreDoneIssues() { } }, [qc, wsId, doneLoaded, hasMore, isLoading]); - return { loadMore, hasMore, isLoading }; + return { loadMore, hasMore, isLoading, doneTotal }; } // --------------------------------------------------------------------------- diff --git a/apps/web/features/issues/components/board-column.tsx b/apps/web/features/issues/components/board-column.tsx index 88e177e9..207d4400 100644 --- a/apps/web/features/issues/components/board-column.tsx +++ b/apps/web/features/issues/components/board-column.tsx @@ -23,11 +23,13 @@ export function BoardColumn({ status, issueIds, issueMap, + totalCount, footer, }: { status: IssueStatus; issueIds: string[]; issueMap: Map; + totalCount?: number; footer?: ReactNode; }) { const cfg = STATUS_CONFIG[status]; @@ -54,7 +56,7 @@ export function BoardColumn({ {cfg.label} - {issueIds.length} + {totalCount ?? issueIds.length} diff --git a/apps/web/features/issues/components/board-view.tsx b/apps/web/features/issues/components/board-view.tsx index 026897ba..6ea8ba0a 100644 --- a/apps/web/features/issues/components/board-view.tsx +++ b/apps/web/features/issues/components/board-view.tsx @@ -33,6 +33,30 @@ import { StatusIcon } from "./status-icon"; import { BoardColumn } from "./board-column"; import { BoardCardContent } from "./board-card"; +/** Sentinel that triggers `onVisible` when scrolled into view. */ +function InfiniteScrollSentinel({ onVisible, loading }: { onVisible: () => void; loading: boolean }) { + const sentinelRef = useRef(null); + const onVisibleRef = useRef(onVisible); + onVisibleRef.current = onVisible; + + useEffect(() => { + const node = sentinelRef.current; + if (!node) return; + const observer = new IntersectionObserver( + ([entry]) => { if (entry.isIntersecting) onVisibleRef.current(); }, + { rootMargin: "100px" }, + ); + observer.observe(node); + return () => observer.disconnect(); + }, []); + + return ( +
+ {loading && } +
+ ); +} + const COLUMN_IDS = new Set(ALL_STATUSES); const kanbanCollision: CollisionDetection = (args) => { @@ -111,7 +135,7 @@ export function BoardView({ }) { const sortBy = useViewStore((s) => s.sortBy); const sortDirection = useViewStore((s) => s.sortDirection); - const { loadMore, hasMore, isLoading: loadingMore } = useLoadMoreDoneIssues(); + const { loadMore, hasMore, isLoading: loadingMore, doneTotal } = useLoadMoreDoneIssues(); // --- Drag state --- const [activeIssue, setActiveIssue] = useState(null); @@ -274,19 +298,10 @@ export function BoardView({ status={status} issueIds={columns[status] ?? []} issueMap={issueMapRef.current} + totalCount={status === "done" ? doneTotal : undefined} footer={ status === "done" && hasMore ? ( - + ) : undefined } />