Skip to content

Commit 55021d5

Browse files
bebuslclaude
andcommitted
feat: [FN-347] 카드셋 리스트 정렬, 필터, 검색 기능 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8497cb6 commit 55021d5

3 files changed

Lines changed: 66 additions & 8 deletions

File tree

src/domain/cardsets/hooks/use-card-sets.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useSuspenseInfiniteQuery } from "@tanstack/react-query";
22
import { cardSetApi } from "@/shared/apis/card-set";
3+
import type { CardSetSortBy, SortOrder } from "@/shared/apis/card-set";
34
import type { CardSetCategory } from "@/domain/cardsets/types";
45

56
const CARDSETS_QUERY_KEY = ["cardsets"];
@@ -8,9 +9,8 @@ interface UseCardSetsParams {
89
keyword?: string;
910
category?: CardSetCategory;
1011
size?: number;
11-
sortBy?: string;
12-
order?: string;
13-
suspense?: boolean;
12+
sortBy?: CardSetSortBy;
13+
order?: SortOrder;
1414
}
1515

1616
export const useCardSets = (params?: UseCardSetsParams) => {

src/pages/cardset-list.tsx

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,44 @@ import { Button } from "@/shared/components/button";
33
import { Input } from "@/shared/components/input";
44
import { ThumbnailCard } from "@/shared/components/thumbnail-card";
55
import { CardGridSkeleton } from "@/shared/components/skeletons";
6+
import {
7+
Select,
8+
SelectContent,
9+
SelectItem,
10+
SelectTrigger,
11+
SelectValue,
12+
} from "@/shared/components/select";
613

714
import BaseLayout from "@/shared/layouts/base-layout";
815
import { Link } from "@tanstack/react-router";
9-
import { Search, SearchX } from "lucide-react";
16+
import { ArrowDownUp, Search, SearchX } from "lucide-react";
1017
import { useCardSets } from "@/domain/cardsets/hooks/use-card-sets";
1118
import { CardSetFilterSection } from "@/domain/cardsets/components/card-set-filter-section";
1219
import type { CardSetCategory } from "@/domain/cardsets/types";
20+
import type { CardSetSortBy, SortOrder } from "@/shared/apis/card-set";
1321
import { EmptyState } from "@/shared/components/empty-state";
1422
import { ErrorBoundary } from "@/shared/components/error-boundary";
1523

24+
const SORT_BY_OPTIONS: { value: CardSetSortBy; label: string }[] = [
25+
{ value: "id", label: "최신순" },
26+
{ value: "like", label: "좋아요순" },
27+
{ value: "book", label: "북마크순" },
28+
];
29+
1630
interface CardSetGridProps {
1731
keyword?: string;
1832
category?: CardSetCategory;
33+
sortBy?: CardSetSortBy;
34+
order?: SortOrder;
1935
}
2036

21-
const CardSetGrid = ({ keyword, category }: CardSetGridProps) => {
37+
const CardSetGrid = ({ keyword, category, sortBy, order }: CardSetGridProps) => {
2238
const {
2339
data: cardsetsData,
2440
fetchNextPage,
2541
hasNextPage,
2642
isFetchingNextPage,
27-
} = useCardSets({ keyword, category, size: 20 });
43+
} = useCardSets({ keyword, category, size: 20, sortBy, order });
2844

2945
return (
3046
<>
@@ -77,6 +93,8 @@ const CardSetList = () => {
7793
const [selectedCategory, setSelectedCategory] = useState<
7894
CardSetCategory | undefined
7995
>(undefined);
96+
const [sortBy, setSortBy] = useState<CardSetSortBy>("id");
97+
const [order, setOrder] = useState<SortOrder>("desc");
8098

8199
useEffect(() => {
82100
const timer = setTimeout(() => {
@@ -93,6 +111,10 @@ const CardSetList = () => {
93111
setSelectedCategory(checked ? category : undefined);
94112
};
95113

114+
const toggleOrder = () => {
115+
setOrder((prev) => (prev === "desc" ? "asc" : "desc"));
116+
};
117+
96118
return (
97119
<BaseLayout>
98120
<div className="space-y-6">
@@ -115,21 +137,50 @@ const CardSetList = () => {
115137
</div>
116138
</div>
117139

118-
{/* 필터 영역 */}
140+
{/* 필터 및 정렬 영역 */}
119141
<div className="flex items-center justify-between gap-4">
120142
<div className="flex-1">
121143
<CardSetFilterSection
122144
selectedCategories={selectedCategory ? [selectedCategory] : []}
123145
onCategoryChange={handleCategoryChange}
124146
/>
125147
</div>
148+
<div className="flex items-center gap-2 shrink-0">
149+
<Select
150+
value={sortBy}
151+
onValueChange={(v) => setSortBy(v as CardSetSortBy)}
152+
>
153+
<SelectTrigger size="sm">
154+
<SelectValue />
155+
</SelectTrigger>
156+
<SelectContent>
157+
{SORT_BY_OPTIONS.map((opt) => (
158+
<SelectItem key={opt.value} value={opt.value}>
159+
{opt.label}
160+
</SelectItem>
161+
))}
162+
</SelectContent>
163+
</Select>
164+
<Button
165+
variant="outline"
166+
size="sm"
167+
onClick={toggleOrder}
168+
className="flex items-center gap-1"
169+
>
170+
<ArrowDownUp className="w-3.5 h-3.5" />
171+
{order === "desc" ? "내림차순" : "오름차순"}
172+
</Button>
173+
</div>
126174
</div>
175+
127176
<ErrorBoundary>
128177
{/* 카드셋 리스트 - 로딩 중에는 그리드 영역만 스켈레톤으로 대체 */}
129178
<Suspense fallback={<CardGridSkeleton />}>
130179
<CardSetGrid
131180
keyword={searchKeyword || undefined}
132181
category={selectedCategory}
182+
sortBy={sortBy}
183+
order={order}
133184
/>
134185
</Suspense>
135186
</ErrorBoundary>

src/shared/apis/card-set.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,14 @@ export interface CardSetUpdateRequest {
9090
// managers?: number[];
9191
}
9292

93-
export interface CardSetSearchRequest extends PaginationRequest {
93+
export type CardSetSortBy = "id" | "like" | "book";
94+
export type SortOrder = "asc" | "desc";
95+
96+
export interface CardSetSearchRequest {
97+
page?: number;
98+
size?: number;
99+
sortBy?: CardSetSortBy;
100+
order?: SortOrder;
94101
keyword?: string;
95102
category?: string;
96103
}

0 commit comments

Comments
 (0)