diff --git a/apps/client/src/features/base/components/views/kanban/base-kanban.tsx b/apps/client/src/features/base/components/views/kanban/base-kanban.tsx index 36ce5d030..6f74b8f8c 100644 --- a/apps/client/src/features/base/components/views/kanban/base-kanban.tsx +++ b/apps/client/src/features/base/components/views/kanban/base-kanban.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useRef } from "react"; +import { useCallback, useEffect, useMemo, useRef } from "react"; import { Badge } from "@mantine/core"; import { IconPlus } from "@tabler/icons-react"; import { useTranslation } from "react-i18next"; @@ -30,6 +30,9 @@ type BaseKanbanProps = { rows: IBaseRow[]; effectiveView: IBaseView | undefined; onCardClick: (rowId: string) => void; + hasNextPage: boolean; + isFetchingNextPage: boolean; + onFetchNextPage: () => void; }; export function BaseKanban({ @@ -37,6 +40,9 @@ export function BaseKanban({ rows, effectiveView, onCardClick, + hasNextPage, + isFetchingNextPage, + onFetchNextPage, }: BaseKanbanProps) { const { t } = useTranslation(); const groupByPropertyId = effectiveView?.config?.groupByPropertyId; @@ -58,6 +64,7 @@ export function BaseKanban({ const reorderRowMutation = useReorderRowMutation(); const sortsActive = (effectiveView?.config?.sorts?.length ?? 0) > 0; const boardRef = useRef(null); + const endRef = useRef(null); const canScrollBoard = useCallback( ({ source }: { source: { data: Record } }) => source.data.type === "base-kanban-card" || @@ -66,6 +73,21 @@ export function BaseKanban({ ); useKanbanAutoScroll(boardRef, canScrollBoard); + useEffect(() => { + if (!hasNextPage || isFetchingNextPage) return; + const target = endRef.current; + const root = boardRef.current; + if (!target || !root) return; + const observer = new IntersectionObserver( + (entries) => { + if (entries.some((e) => e.isIntersecting)) onFetchNextPage(); + }, + { root, threshold: 0.1 }, + ); + observer.observe(target); + return () => observer.disconnect(); + }, [hasNextPage, isFetchingNextPage, onFetchNextPage]); + // Rules of Hooks: call useKanbanGroups unconditionally with `undefined` // when not groupable; switch the render path on isGroupable below. const { columns } = useKanbanGroups( @@ -276,6 +298,7 @@ export function BaseKanban({ sortsActive={sortsActive} /> ))} +
); diff --git a/apps/client/src/features/base/components/views/view-renderer.tsx b/apps/client/src/features/base/components/views/view-renderer.tsx index 47e1dab21..c464fd902 100644 --- a/apps/client/src/features/base/components/views/view-renderer.tsx +++ b/apps/client/src/features/base/components/views/view-renderer.tsx @@ -42,6 +42,9 @@ export function ViewRenderer(props: ViewRendererProps) { rows={props.rows} effectiveView={props.effectiveView} onCardClick={props.onCardClick} + hasNextPage={props.hasNextPage} + isFetchingNextPage={props.isFetchingNextPage} + onFetchNextPage={props.onFetchNextPage} /> ); }