69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { api } from "@/trpc/react";
|
|
import React from "react";
|
|
|
|
import CategoryCard from "../category-card";
|
|
import { useInfiniteItemsObserver } from "@/lib/hooks/infinite-items-observer-hook";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { CATEGORY_GRID_CLASS } from "./category-grid";
|
|
import CategoryFilterBar, { CategoryFilter } from "../category-filter-bar";
|
|
|
|
export default function InfiniteCategoryGrid() {
|
|
const [filter, setFilter] = React.useState<CategoryFilter>({});
|
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
|
|
api.category.getByCursor.useInfiniteQuery(
|
|
{
|
|
filter,
|
|
},
|
|
{
|
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
staleTime: 60 * 4 * 1000, // 4 minutes stale time
|
|
refetchOnMount: false, // Prevents unnecessary refetching
|
|
refetchOnWindowFocus: false, // Avoids refetch when switching tabs
|
|
},
|
|
);
|
|
// Calculate all visible items across all loaded pages
|
|
const allItems = React.useMemo(() => {
|
|
return data?.pages.flatMap((page) => page.items) || [];
|
|
}, [data]);
|
|
|
|
// Ref for bottom observation
|
|
const bottomObserverRef = React.useRef(null);
|
|
|
|
useInfiniteItemsObserver({
|
|
bottomObserverRef,
|
|
fetchNextPage,
|
|
hasNextPage,
|
|
isFetchingNextPage,
|
|
});
|
|
|
|
return (
|
|
<div className="relative space-y-4">
|
|
<CategoryFilterBar onFilterUpdate={setFilter} />
|
|
<menu className={`${CATEGORY_GRID_CLASS} overflow-auto`}>
|
|
{data?.pages?.length
|
|
? allItems.map((category, idx) => (
|
|
<li key={`category-${idx}`}>
|
|
<CategoryCard {...category} />
|
|
</li>
|
|
))
|
|
: null}
|
|
|
|
{/* Loading indicator */}
|
|
{(isLoading || isFetchingNextPage) &&
|
|
Array.from(new Array(isLoading ? 16 : 4).keys()).map((idx) => (
|
|
<li key={idx}>
|
|
<Skeleton className="size-full min-h-20" />
|
|
</li>
|
|
))}
|
|
|
|
{/* Bottom observer element */}
|
|
{hasNextPage && (
|
|
<li ref={bottomObserverRef} className="col-span-full h-12" />
|
|
)}
|
|
</menu>
|
|
</div>
|
|
);
|
|
}
|