카테고리 무한 스크롤 구현

This commit is contained in:
hyeonsu
2025-09-03 17:57:37 +09:00
parent 0e14f9cf3f
commit ce4a25a10b
5 changed files with 201 additions and 30 deletions

View File

@@ -8,8 +8,8 @@ import { CodeCategoryFormModal } from "./CodeCategoryFormModal";
import { CategoryItem } from "./CategoryItem";
import { AlertModal } from "@/components/common/AlertModal";
import { Search, Plus } from "lucide-react";
import { useCategories, useDeleteCategory } from "@/hooks/queries/useCategories";
import { useSearchAndFilter } from "@/hooks/useSearchAndFilter";
import { useDeleteCategory } from "@/hooks/queries/useCategories";
import { useCategoriesInfinite } from "@/hooks/queries/useCategoriesInfinite";
interface CodeCategoryPanelProps {
selectedCategoryCode: string;
@@ -17,20 +17,23 @@ interface CodeCategoryPanelProps {
}
export function CodeCategoryPanel({ selectedCategoryCode, onSelectCategory }: CodeCategoryPanelProps) {
// React Query로 카테고리 데이터 관리
const { data: categories = [], isLoading, error } = useCategories();
const deleteCategoryMutation = useDeleteCategory();
// 검색 및 필터 상태 (먼저 선언)
const [searchTerm, setSearchTerm] = useState("");
const [showActiveOnly, setShowActiveOnly] = useState(false);
// 검색 및 필터링 훅 사용
// React Query로 카테고리 데이터 관리 (무한 스크롤)
const {
searchTerm,
setSearchTerm,
showActiveOnly,
setShowActiveOnly,
filteredItems: filteredCategories,
} = useSearchAndFilter(categories, {
searchFields: ["category_name", "category_code"],
data: categories = [],
isLoading,
error,
handleScroll,
isFetchingNextPage,
hasNextPage,
} = useCategoriesInfinite({
search: searchTerm || undefined,
active: showActiveOnly || undefined, // isActive -> active로 수정
});
const deleteCategoryMutation = useDeleteCategory();
// 모달 상태
const [showFormModal, setShowFormModal] = useState(false);
@@ -125,29 +128,44 @@ export function CodeCategoryPanel({ selectedCategoryCode, onSelectCategory }: Co
</div>
</div>
{/* 카테고리 목록 */}
<div className="flex-1 overflow-y-auto">
{/* 카테고리 목록 (무한 스크롤) */}
<div className="h-96 overflow-y-auto" onScroll={handleScroll}>
{isLoading ? (
<div className="flex h-32 items-center justify-center">
<LoadingSpinner />
</div>
) : filteredCategories.length === 0 ? (
) : categories.length === 0 ? (
<div className="p-4 text-center text-gray-500">
{searchTerm ? "검색 결과가 없습니다." : "카테고리가 없습니다."}
</div>
) : (
<div className="space-y-1 p-2">
{filteredCategories.map((category) => (
<CategoryItem
key={category.category_code}
category={category}
isSelected={selectedCategoryCode === category.category_code}
onSelect={() => onSelectCategory(category.category_code)}
onEdit={() => handleEditCategory(category.category_code)}
onDelete={() => handleDeleteCategory(category.category_code)}
/>
))}
</div>
<>
<div className="space-y-1 p-2">
{categories.map((category, index) => (
<CategoryItem
key={`${category.category_code}-${index}`}
category={category}
isSelected={selectedCategoryCode === category.category_code}
onSelect={() => onSelectCategory(category.category_code)}
onEdit={() => handleEditCategory(category.category_code)}
onDelete={() => handleDeleteCategory(category.category_code)}
/>
))}
</div>
{/* 추가 로딩 표시 */}
{isFetchingNextPage && (
<div className="flex justify-center py-4">
<LoadingSpinner size="sm" />
<span className="ml-2 text-sm text-gray-500"> ...</span>
</div>
)}
{/* 더 이상 데이터가 없을 때 */}
{!hasNextPage && categories.length > 0 && (
<div className="py-4 text-center text-sm text-gray-400"> .</div>
)}
</>
)}
</div>