added category infinite grid and fixed missing items at useInfiniteQuery

This commit is contained in:
2025-03-15 15:20:17 +01:00
parent f591e18847
commit 4ad0357133
15 changed files with 321 additions and 1100 deletions
@@ -1,13 +1,14 @@
import CategoryCard from "@/components/category/category-card";
import { Category } from "@/server/db/schema";
import React from "react";
import { Skeleton } from "../ui/skeleton";
import { Skeleton } from "@/components/ui/skeleton";
const GRID_CLASS = "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3";
export const CATEGORY_GRID_CLASS =
"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3";
function CategoriesGrid({ categories }: { categories: Category[] }) {
function CategoryGrid({ categories }: { categories: Category[] }) {
return (
<menu className={GRID_CLASS}>
<menu className={CATEGORY_GRID_CLASS}>
{categories.map((category) => (
<li key={category.id}>
<CategoryCard {...category} />
@@ -17,12 +18,12 @@ function CategoriesGrid({ categories }: { categories: Category[] }) {
);
}
export default CategoriesGrid;
export default CategoryGrid;
export function CategoriesGridSkeleton() {
export function CategoryGridSkeleton() {
const range = Array.from(new Array(6).keys());
return (
<ul className={GRID_CLASS}>
<ul className={CATEGORY_GRID_CLASS}>
{range.map((i) => (
<li key={i}>
<Skeleton className="h-12 w-full" />
@@ -0,0 +1,67 @@
"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 ArticleFilterBar, { ArticleFilter } from "../article-filter-bar";
export default function InfiniteCategoryGrid() {
// const [filter, setFilter] = React.useState<ArticleFilter | undefined>(
// undefined,
// );
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
api.category.getByCursor.useInfiniteQuery(
{
// filter,
},
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
},
);
// 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">
{/* <ArticleFilterBar 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>
);
}