Skip to content

Commit 994861b

Browse files
committed
improve: [FN-315] 카드셋 검색페이지 로딩 UI 업데이트 및 검색 디바운스 시간 조정
1 parent fc8c4d1 commit 994861b

2 files changed

Lines changed: 88 additions & 128 deletions

File tree

src/pages/cardset-list.tsx

Lines changed: 77 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,80 @@
1-
import { useState, useEffect } from "react";
1+
import { Suspense, useState, useEffect } from "react";
22
import { Button } from "@/shared/components/button";
33
import { Input } from "@/shared/components/input";
44
import { ThumbnailCard } from "@/shared/components/thumbnail-card";
5+
import { CardGridSkeleton } from "@/shared/components/skeletons";
56

67
import BaseLayout from "@/shared/layouts/base-layout";
78
import { Link } from "@tanstack/react-router";
89
import { Search } from "lucide-react";
910
import { useCardSets } from "@/domain/cardsets/hooks/use-card-sets";
1011
import { CardSetFilterSection } from "@/domain/cardsets/components/card-set-filter-section";
1112
import type { CardSetCategory } from "@/domain/cardsets/types";
12-
import { CardGridSkeleton } from "@/shared/components/skeletons";
13+
14+
interface CardSetGridProps {
15+
keyword?: string;
16+
category?: CardSetCategory;
17+
}
18+
19+
const CardSetGrid = ({ keyword, category }: CardSetGridProps) => {
20+
const {
21+
data: cardsetsData,
22+
fetchNextPage,
23+
hasNextPage,
24+
isFetchingNextPage,
25+
} = useCardSets({ keyword, category, size: 20 });
26+
27+
return (
28+
<>
29+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
30+
{cardsetsData?.cardsets.map((cardset) => (
31+
<Link
32+
to="/groups/$groupId/cardsets/$cardsetId"
33+
params={{
34+
groupId: cardset.groupId.toString(),
35+
cardsetId: cardset.cardSetId.toString(),
36+
}}
37+
key={cardset.cardSetId}
38+
>
39+
<ThumbnailCard
40+
imageUrl={cardset.imageUrl}
41+
title={cardset.name}
42+
category={cardset.category}
43+
subtitle={
44+
cardset.hashtag
45+
? `${cardset.hashtag
46+
.split(",")
47+
.map((tag) => `#${tag}`)
48+
.join(" ")}`
49+
: undefined
50+
}
51+
/>
52+
</Link>
53+
))}
54+
</div>
55+
56+
{hasNextPage && (
57+
<div className="flex justify-center">
58+
<Button
59+
onClick={() => fetchNextPage()}
60+
disabled={isFetchingNextPage}
61+
variant="outline"
62+
>
63+
{isFetchingNextPage ? "로딩 중..." : "더 보기"}
64+
</Button>
65+
</div>
66+
)}
67+
68+
{(!cardsetsData?.cardsets || cardsetsData.cardsets.length === 0) && (
69+
<div className="flex justify-center items-center min-h-[200px]">
70+
<div className="text-gray-500">
71+
검색 조건에 맞는 카드셋이 없습니다.
72+
</div>
73+
</div>
74+
)}
75+
</>
76+
);
77+
};
1378

1479
const CardSetList = () => {
1580
const [searchInput, setSearchInput] = useState("");
@@ -18,67 +83,21 @@ const CardSetList = () => {
1883
CardSetCategory | undefined
1984
>(undefined);
2085

21-
// Debounce 검색어 처리
2286
useEffect(() => {
2387
const timer = setTimeout(() => {
2488
setSearchKeyword(searchInput);
25-
}, 300);
89+
}, 1000);
2690

2791
return () => clearTimeout(timer);
2892
}, [searchInput]);
2993

30-
// API에서 카드셋 데이터 가져오기
31-
const {
32-
data: cardsetsData,
33-
fetchNextPage,
34-
hasNextPage,
35-
isFetchingNextPage,
36-
isLoading,
37-
// error,
38-
} = useCardSets({
39-
keyword: searchKeyword || undefined,
40-
category: selectedCategory,
41-
size: 20,
42-
});
43-
44-
// 카테고리 체크박스 핸들러
4594
const handleCategoryChange = (
4695
category: CardSetCategory,
47-
checked: boolean
96+
checked: boolean,
4897
) => {
4998
setSelectedCategory(checked ? category : undefined);
5099
};
51100

52-
// // 정렬 필드 핸들러
53-
// const handleSortByChange = (value: string) => {
54-
// setSortBy(value);
55-
// };
56-
57-
// // 정렬 순서 핸들러
58-
// const handleOrderChange = (value: string) => {
59-
// setOrder(value as "ASC" | "DESC");
60-
// };
61-
62-
// if (isLoading) {
63-
// return (
64-
// <BaseLayout>
65-
// <div className="flex justify-center items-center min-h-[400px]">
66-
// <div className="text-gray-500">로딩 중...</div>
67-
// </div>
68-
// </BaseLayout>
69-
// );
70-
// }
71-
72-
// if (error) {
73-
// return (
74-
// <BaseLayout>
75-
// <div className="flex justify-center items-center min-h-[400px]">
76-
// <div className="text-red-500">데이터를 불러오는데 실패했습니다.</div>
77-
// </div>
78-
// </BaseLayout>
79-
// );
80-
// }
81-
82101
return (
83102
<BaseLayout>
84103
<div className="space-y-6">
@@ -101,93 +120,23 @@ const CardSetList = () => {
101120
</div>
102121
</div>
103122

104-
{/* 필터 및 정렬 영역 */}
123+
{/* 필터 영역 */}
105124
<div className="flex items-center justify-between gap-4">
106125
<div className="flex-1">
107126
<CardSetFilterSection
108127
selectedCategories={selectedCategory ? [selectedCategory] : []}
109128
onCategoryChange={handleCategoryChange}
110129
/>
111130
</div>
112-
{/*
113-
<div className="flex items-center gap-2 text-sm text-gray-600">
114-
<span>정렬:</span>
115-
<Select value={sortBy} onValueChange={handleSortByChange}>
116-
<SelectTrigger className="w-24 h-8 text-xs">
117-
<SelectValue />
118-
</SelectTrigger>
119-
<SelectContent>
120-
<SelectItem value="createdAt">생성일</SelectItem>
121-
<SelectItem value="name">이름</SelectItem>
122-
<SelectItem value="modifiedAt">수정일</SelectItem>
123-
</SelectContent>
124-
</Select>
125-
<Select value={order} onValueChange={handleOrderChange}>
126-
<SelectTrigger className="w-20 h-8 text-xs">
127-
<SelectValue />
128-
</SelectTrigger>
129-
<SelectContent>
130-
<SelectItem value="DESC">내림차순↓</SelectItem>
131-
<SelectItem value="ASC">오름차순↑</SelectItem>
132-
</SelectContent>
133-
</Select>
134-
</div> */}
135131
</div>
136132

137-
{/* 카드셋 리스트 */}
138-
{isLoading ? (
139-
<CardGridSkeleton />
140-
) : (
141-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
142-
{cardsetsData?.cardsets.map((cardset) => (
143-
<Link
144-
to="/groups/$groupId/cardsets/$cardsetId"
145-
params={{
146-
groupId: cardset.groupId.toString(),
147-
cardsetId: cardset.cardSetId.toString(),
148-
}}
149-
key={cardset.cardSetId}
150-
>
151-
<ThumbnailCard
152-
imageUrl={cardset.imageUrl}
153-
title={cardset.name}
154-
category={cardset.category}
155-
subtitle={
156-
cardset.hashtag
157-
? `${cardset.hashtag
158-
.split(",")
159-
.map((tag) => `#${tag}`)
160-
.join(" ")}`
161-
: undefined
162-
}
163-
/>
164-
</Link>
165-
))}
166-
</div>
167-
)}
168-
169-
{/* 더 보기 버튼 */}
170-
{hasNextPage && (
171-
<div className="flex justify-center">
172-
<Button
173-
onClick={() => fetchNextPage()}
174-
disabled={isFetchingNextPage}
175-
variant="outline"
176-
>
177-
{isFetchingNextPage ? "로딩 중..." : "더 보기"}
178-
</Button>
179-
</div>
180-
)}
181-
182-
{/* 결과 없음 */}
183-
{(!cardsetsData?.cardsets || cardsetsData.cardsets.length === 0) &&
184-
!isLoading && (
185-
<div className="flex justify-center items-center min-h-[200px]">
186-
<div className="text-gray-500">
187-
검색 조건에 맞는 카드셋이 없습니다.
188-
</div>
189-
</div>
190-
)}
133+
{/* 카드셋 리스트 - 로딩 중에는 그리드 영역만 스켈레톤으로 대체 */}
134+
<Suspense fallback={<CardGridSkeleton />}>
135+
<CardSetGrid
136+
keyword={searchKeyword || undefined}
137+
category={selectedCategory}
138+
/>
139+
</Suspense>
191140
</div>
192141
</BaseLayout>
193142
);

src/routes/cardset-list.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import CardSetList from "@/pages/cardset-list";
22
import { createFileRoute } from "@tanstack/react-router";
3+
import BaseLayout from "@/shared/layouts/base-layout";
4+
import { CardGridSkeleton } from "@/shared/components/skeletons";
35

46
export const Route = createFileRoute("/cardset-list")({
57
component: RouteComponent,
8+
pendingComponent: PendingComponent,
69
head: () => ({
710
meta: [
811
{ title: "카드셋 목록 | FlipNote" },
@@ -17,3 +20,11 @@ export const Route = createFileRoute("/cardset-list")({
1720
function RouteComponent() {
1821
return <CardSetList />;
1922
}
23+
24+
function PendingComponent() {
25+
return (
26+
<BaseLayout>
27+
<CardGridSkeleton />
28+
</BaseLayout>
29+
);
30+
}

0 commit comments

Comments
 (0)