add pagination on search list

This commit is contained in:
Abhimanyu Yadav
2025-05-28 13:28:32 +05:30
parent 79afa6db99
commit 596824c1e7
2 changed files with 152 additions and 30 deletions

View File

@@ -1,15 +1,116 @@
import React from "react";
import React, { useEffect, useState, useCallback, useRef } from "react";
import FiltersList from "./FiltersList";
import SearchList from "./SearchList";
import { useBlockMenuContext } from "../block-menu-provider";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
const BlockMenuSearch: React.FC = ({}) => {
const { searchData } = useBlockMenuContext();
const { searchData, searchQuery, searchId, setSearchData } =
useBlockMenuContext();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [hasMore, setHasMore] = useState<boolean>(true);
const [page, setPage] = useState<number>(0);
const [loadingMore, setLoadingMore] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const api = useBackendAPI();
const pageSize = 10;
const fetchSearchData = useCallback(
async (pageNum: number, isLoadMore: boolean = false) => {
if (isLoadMore) {
setLoadingMore(true);
} else {
setIsLoading(true);
}
try {
const response = await api.searchBlocks({
search_query: searchQuery,
search_id: searchId,
page: pageNum,
page_size: pageSize,
});
await new Promise((resolve) => setTimeout(resolve, 2000));
if (isLoadMore) {
setSearchData((prev) => [...prev, ...response.items]);
} else {
setSearchData(response.items);
}
setHasMore(response.more_pages);
setError(null);
} catch (error) {
console.error("Error fetching search data:", error);
setError(
error instanceof Error
? error.message
: "Failed to load search results",
);
if (!isLoadMore) {
setPage(0);
}
} finally {
setIsLoading(false);
setLoadingMore(false);
}
},
[searchQuery, searchId, api, setSearchData, pageSize],
);
const handleScroll = useCallback(() => {
if (!scrollRef.current || loadingMore || !hasMore) return;
const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
if (scrollTop + clientHeight >= scrollHeight - 100) {
const nextPage = page + 1;
setPage(nextPage);
fetchSearchData(nextPage, true);
}
}, [loadingMore, hasMore, page, fetchSearchData]);
useEffect(() => {
const scrollElement = scrollRef.current;
if (scrollElement) {
scrollElement.addEventListener("scroll", handleScroll);
return () => scrollElement.removeEventListener("scroll", handleScroll);
}
}, [handleScroll]);
useEffect(() => {
if (searchQuery) {
setPage(0);
setHasMore(true);
setError(null);
fetchSearchData(0, false);
} else {
setSearchData([]);
setError(null);
setPage(0);
setHasMore(true);
}
}, [searchQuery, searchId, fetchSearchData, setSearchData]);
return (
<div className="scrollbar-thumb-rounded h-full space-y-4 overflow-y-auto py-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-zinc-200">
<div
ref={scrollRef}
className="scrollbar-thumb-rounded h-full space-y-4 overflow-y-auto py-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-zinc-200"
>
{searchData.length !== 0 && <FiltersList />}
<SearchList />
<SearchList
isLoading={isLoading}
loadingMore={loadingMore}
hasMore={hasMore}
error={error}
onRetry={() => {
setPage(0);
setError(null);
fetchSearchData(0, false);
}}
/>
</div>
);
};

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import React from "react";
import MarketplaceAgentBlock from "../MarketplaceAgentBlock";
import Block from "../Block";
import UGCAgentBlock from "../UGCAgentBlock";
@@ -6,13 +6,24 @@ import AiBlock from "./AiBlock";
import IntegrationBlock from "../IntegrationBlock";
import { SearchItem, useBlockMenuContext } from "../block-menu-provider";
import NoSearchResult from "./NoSearchResult";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { Button } from "@/components/ui/button";
const SearchList = () => {
const { searchQuery, addNode, searchId, searchData, setSearchData } =
useBlockMenuContext();
const [isLoading, setIsLoading] = useState<boolean>(true);
const api = useBackendAPI();
interface SearchListProps {
isLoading: boolean;
loadingMore: boolean;
hasMore: boolean;
error: string | null;
onRetry: () => void;
}
const SearchList: React.FC<SearchListProps> = ({
isLoading,
loadingMore,
hasMore,
error,
onRetry,
}) => {
const { searchQuery, addNode, searchData } = useBlockMenuContext();
// Need to change it once, we got provider blocks
const getBlockType = (item: any) => {
@@ -31,25 +42,6 @@ const SearchList = () => {
return null;
};
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const response = await api.searchBlocks({
search_query: searchQuery,
search_id: searchId,
});
setSearchData(response.items);
} catch (error) {
console.error("Error fetching search data:", error);
} finally {
setIsLoading(false);
}
};
fetchData();
}, [searchQuery, setSearchData]);
if (isLoading) {
return (
<div className="space-y-2.5 px-4">
@@ -65,6 +57,26 @@ const SearchList = () => {
);
}
if (error) {
return (
<div className="px-4">
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
<p className="mb-2 text-sm text-red-600">
Error loading search results: {error}
</p>
<Button
variant="outline"
size="sm"
onClick={onRetry}
className="h-7 text-xs"
>
Retry
</Button>
</div>
</div>
);
}
if (searchData.length === 0) {
return <NoSearchResult />;
}
@@ -131,6 +143,15 @@ const SearchList = () => {
return null;
}
})}
{loadingMore && hasMore && (
<div className="space-y-2.5">
{Array(3)
.fill(0)
.map((_, i) => (
<Block.Skeleton key={`loading-more-${i}`} />
))}
</div>
)}
</div>
);
};