Skip to content

Commit

Permalink
Fixed scroll issue in Shifting and Resource (ohcnetwork#9475)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeffrin2005 authored Dec 23, 2024
1 parent e002861 commit 79e098e
Showing 1 changed file with 81 additions and 76 deletions.
157 changes: 81 additions & 76 deletions src/components/Kanban/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import {
Droppable,
OnDragEndResponder,
} from "@hello-pangea/dnd";
import { ReactNode, RefObject, useEffect, useRef, useState } from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
import { ReactNode, RefObject, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";

import CareIcon from "@/CAREUI/icons/CareIcon";

import request from "@/Utils/request/request";
import { callApi } from "@/Utils/request/query";
import { QueryRoute } from "@/Utils/request/types";
import { QueryOptions } from "@/Utils/request/useQuery";

Expand Down Expand Up @@ -57,7 +58,7 @@ export default function KanbanBoard<T extends { id: string }>(
</div>
</div>
<DragDropContext onDragEnd={props.onDragEnd}>
<div className="h-full overflow-scroll" ref={board}>
<div className="h-full overflow-x-auto scrollbar-hide" ref={board}>
<div className="flex items-stretch px-0 pb-2">
{props.sections.map((section, i) => (
<KanbanSection<T>
Expand All @@ -74,114 +75,118 @@ export default function KanbanBoard<T extends { id: string }>(
);
}

interface QueryResponse<T> {
results: T[];
next: string | null;
count: number;
}

export function KanbanSection<T extends { id: string }>(
props: Omit<KanbanBoardProps<T>, "sections" | "onDragEnd"> & {
section: KanbanBoardProps<T>["sections"][number];
boardRef: RefObject<HTMLDivElement>;
},
) {
const { section } = props;
const [offset, setOffset] = useState(0);
const [pages, setPages] = useState<T[][]>([]);
const [fetchingNextPage, setFetchingNextPage] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [totalCount, setTotalCount] = useState<number>();

const options = section.fetchOptions(section.id);
const sectionRef = useRef<HTMLDivElement>(null);
const defaultLimit = 14;
const { t } = useTranslation();

// should be replaced with useInfiniteQuery when we move over to react query

const fetchNextPage = async (refresh: boolean = false) => {
if (!refresh && (fetchingNextPage || !hasMore)) return;
if (refresh) setPages([]);
const offsetToUse = refresh ? 0 : offset;
setFetchingNextPage(true);
const res = await request(options.route, {
...options.options,
query: { ...options.options?.query, offsetToUse, limit: defaultLimit },
});
const newPages = refresh ? [] : [...pages];
const page = Math.floor(offsetToUse / defaultLimit);
if (res.error) return;
newPages[page] = (res.data as any).results;
setPages(newPages);
setHasMore(!!(res.data as any)?.next);
setTotalCount((res.data as any)?.count);
setOffset(offsetToUse + defaultLimit);
setFetchingNextPage(false);
const fetchPage = async ({ pageParam = 0 }) => {
const options = section.fetchOptions(section.id);
try {
const data = await callApi(options.route, {
...options.options,
queryParams: {
...options.options?.query,
offset: pageParam,
limit: defaultLimit,
},
});
return data as QueryResponse<T>;
} catch (error) {
console.error("Error fetching section data:", error);
return { results: [], next: null, count: 0 };
}
};

const items = pages.flat();
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
refetch,
} = useInfiniteQuery({
queryKey: [section.id],
queryFn: fetchPage,
getNextPageParam: (lastPage, pages) => {
if (!lastPage.next) return undefined;
return pages.length * defaultLimit;
},
initialPageParam: 0,
});

useEffect(() => {
const onBoardReachEnd = async () => {
const sectionElementHeight =
sectionRef.current?.getBoundingClientRect().height;
const scrolled = props.boardRef.current?.scrollTop;
// if user has scrolled 3/4th of the current items
if (
scrolled &&
sectionElementHeight &&
scrolled > sectionElementHeight * (3 / 4)
) {
fetchNextPage();
}
};

props.boardRef.current?.addEventListener("scroll", onBoardReachEnd);
return () =>
props.boardRef.current?.removeEventListener("scroll", onBoardReachEnd);
}, [props.boardRef, fetchingNextPage, hasMore]);
const items = data?.pages?.flatMap((page) => page.results || []) ?? [];
const totalCount = data?.pages[0]?.count ?? 0;

useEffect(() => {
fetchNextPage(true);
}, [props.section]);
refetch();
}, [section.id, refetch]);

return (
<Droppable droppableId={section.id}>
{(provided) => (
{(provided, _snapshot) => (
<div
ref={provided.innerRef}
className={
"relative mr-2 w-[300px] shrink-0 rounded-xl bg-secondary-200"
}
className="relative mr-2 w-[300px] shrink-0 rounded-xl bg-secondary-200"
>
<div className="sticky top-0 rounded-xl bg-secondary-200 pt-2">
<div className="mx-2 flex items-center justify-between rounded-lg border border-secondary-300 bg-white p-4">
<div>{section.title}</div>
<div>
<span className="ml-2 rounded-lg bg-secondary-300 px-2">
{typeof totalCount === "undefined" ? "..." : totalCount}
{isLoading ? "..." : totalCount}
</span>
</div>
</div>
</div>
<div ref={sectionRef}>
{!fetchingNextPage && totalCount === 0 && (
<div
ref={sectionRef}
className="h-[calc(100vh-200px)] overflow-y-auto overflow-x-hidden"
onScroll={(e) => {
const target = e.target as HTMLDivElement;
if (
target.scrollTop + target.clientHeight >=
target.scrollHeight - 100
) {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}
}}
>
{!isLoading && items.length === 0 && (
<div className="flex items-center justify-center py-10 text-secondary-500">
{t("no_results_found")}
</div>
)}
{items
.filter((item) => item)
.map((item, i) => (
<Draggable draggableId={item.id} key={i} index={i}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className="mx-2 mt-2 w-[284px] rounded-lg border border-secondary-300 bg-white"
>
{props.itemRender(item)}
</div>
)}
</Draggable>
))}
{fetchingNextPage && (
{items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, _snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className="mx-2 mt-2 w-[284px] rounded-lg border border-secondary-300 bg-white"
>
{props.itemRender(item)}
</div>
)}
</Draggable>
))}
{provided.placeholder}
{isFetchingNextPage && (
<div className="mt-2 h-[300px] w-[284px] animate-pulse rounded-lg bg-secondary-300" />
)}
</div>
Expand Down

0 comments on commit 79e098e

Please sign in to comment.