diff --git a/apps/web/features/issues/components/list-view.tsx b/apps/web/features/issues/components/list-view.tsx index bc0ef4368..dc40f17ac 100644 --- a/apps/web/features/issues/components/list-view.tsx +++ b/apps/web/features/issues/components/list-view.tsx @@ -1,7 +1,7 @@ "use client"; -import { useMemo } from "react"; -import { ChevronRight, Plus } from "lucide-react"; +import { useMemo, useEffect, useRef } from "react"; +import { ChevronRight, Loader2, Plus } from "lucide-react"; import { Accordion } from "@base-ui/react/accordion"; import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; @@ -10,10 +10,35 @@ import { STATUS_CONFIG } from "@/features/issues/config"; import { useModalStore } from "@/features/modals"; import { useViewStore } from "@/features/issues/stores/view-store-context"; import { useIssueSelectionStore } from "@/features/issues/stores/selection-store"; +import { useLoadMoreDoneIssues } from "@core/issues/mutations"; import { sortIssues } from "@/features/issues/utils/sort"; import { StatusIcon } from "./status-icon"; import { ListRow } from "./list-row"; +/** 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 && } +
+ ); +} + export function ListView({ issues, visibleStatuses, @@ -32,6 +57,7 @@ export function ListView({ const selectedIds = useIssueSelectionStore((s) => s.selectedIds); const select = useIssueSelectionStore((s) => s.select); const deselect = useIssueSelectionStore((s) => s.deselect); + const { loadMore, hasMore, isLoading: loadingMore, doneTotal } = useLoadMoreDoneIssues(); const issuesByStatus = useMemo(() => { const map = new Map(); @@ -101,7 +127,7 @@ export function ListView({ {cfg.label} - {statusIssues.length} + {status === "done" ? doneTotal : statusIssues.length}
@@ -128,9 +154,14 @@ export function ListView({ {statusIssues.length > 0 ? ( - statusIssues.map((issue) => ( - - )) + <> + {statusIssues.map((issue) => ( + + ))} + {status === "done" && hasMore && ( + + )} + ) : (

No issues