Skip to content

Commit ac8a591

Browse files
committed
feat: 아이템명 완전 일치 옵션 추가
1 parent 22a7470 commit ac8a591

8 files changed

Lines changed: 153 additions & 4 deletions

File tree

src/app/(main)/auction-history/page.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ function parseSearchParamsFromUrl(
296296
}
297297
}
298298

299+
const exactMatch = urlSearchParams.get("exact_match");
300+
if (exactMatch === "true") {
301+
parsed.isExactItemName = true;
302+
}
303+
299304
urlSearchParams.forEach((value, key) => {
300305
if (
301306
[
@@ -313,6 +318,7 @@ function parseSearchParamsFromUrl(
313318
"subCategory",
314319
"itemSubCategory",
315320
"category",
321+
"exact_match",
316322
].includes(key)
317323
) {
318324
return;
@@ -363,6 +369,10 @@ function serializeSearchParams(params: AuctionHistorySearchParams): string {
363369
queryParams.set("item_name", normalized.itemName);
364370
queryParams.delete("itemName");
365371
}
372+
if (normalized.isExactItemName) {
373+
queryParams.set("exact_match", "true");
374+
}
375+
queryParams.delete("isExactItemName");
366376
if (normalized.itemTopCategory) {
367377
queryParams.set("top_category", normalized.itemTopCategory);
368378
queryParams.delete("itemTopCategory");
@@ -514,6 +524,14 @@ export default function Page() {
514524
setMobileDateTo(parsed.dateAuctionBuyRequest?.dateAuctionBuyTo ?? "");
515525
}, [urlSearchParams]);
516526

527+
const handleExactItemNameChange = useCallback((value: boolean) => {
528+
setSearchParams((prev) => ({
529+
...prev,
530+
isExactItemName: value || undefined,
531+
page: 1,
532+
}));
533+
}, []);
534+
517535
// state -> URL 동기화
518536
useEffect(() => {
519537
const nextQuery = serializeSearchParams(searchParams);
@@ -599,6 +617,7 @@ export default function Page() {
599617

600618
return normalizeCategorySelection(next);
601619
});
620+
// isExactItemName은 SearchFilterCard에서 직접 onExactItemNameChange로 관리
602621

603622
setMobilePriceMin(filters.priceSearchRequest?.priceFrom?.toString() ?? "");
604623
setMobilePriceMax(filters.priceSearchRequest?.priceTo?.toString() ?? "");
@@ -780,11 +799,13 @@ export default function Page() {
780799
<div className="mb-4">
781800
<MobileFilterChips
782801
activeFilters={{
802+
hasExactItemName: !!searchParams.isExactItemName,
783803
hasCategory: selectedCategory !== "all",
784804
hasPrice: !!(mobilePriceMin || mobilePriceMax),
785805
hasDate: !!(mobileDateFrom || mobileDateTo),
786806
hasOptions: mobileActiveFilters.length > 0,
787807
}}
808+
onExactItemNameToggle={() => handleExactItemNameChange(!searchParams.isExactItemName)}
788809
onCategoryClick={() => setMobileFilterType("category")}
789810
onPriceClick={() => setMobileFilterType("price")}
790811
onDateClick={() => setMobileFilterType("date")}
@@ -916,6 +937,8 @@ export default function Page() {
916937
isModal={isFilterModalOpen}
917938
onClose={() => setIsFilterModalOpen(false)}
918939
layoutMode={layoutMode}
940+
isExactItemName={!!searchParams.isExactItemName}
941+
onExactItemNameChange={handleExactItemNameChange}
919942
/>
920943
)}
921944

src/app/(main)/auction-realtime/page.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@ function parseSearchParamsFromUrl(
268268
}
269269
}
270270

271+
const exactMatch = urlSearchParams.get("exact_match");
272+
if (exactMatch === "true") {
273+
parsed.isExactItemName = true;
274+
}
275+
271276
urlSearchParams.forEach((value, key) => {
272277
if (
273278
[
@@ -285,6 +290,7 @@ function parseSearchParamsFromUrl(
285290
"subCategory",
286291
"itemSubCategory",
287292
"category",
293+
"exact_match",
288294
].includes(key)
289295
) {
290296
return;
@@ -335,6 +341,10 @@ function serializeSearchParams(params: AuctionRealtimeSearchParams): string {
335341
queryParams.set("item_name", normalized.itemName);
336342
queryParams.delete("itemName");
337343
}
344+
if (normalized.isExactItemName) {
345+
queryParams.set("exact_match", "true");
346+
}
347+
queryParams.delete("isExactItemName");
338348
if (normalized.itemTopCategory) {
339349
queryParams.set("top_category", normalized.itemTopCategory);
340350
queryParams.delete("itemTopCategory");
@@ -479,6 +489,14 @@ export default function Page() {
479489
setMobilePriceMax(parsed.priceSearchRequest?.priceTo?.toString() ?? "");
480490
}, [urlSearchParams]);
481491

492+
const handleExactItemNameChange = useCallback((value: boolean) => {
493+
setSearchParams((prev) => ({
494+
...prev,
495+
isExactItemName: value || undefined,
496+
page: 1,
497+
}));
498+
}, []);
499+
482500
useEffect(() => {
483501
const nextQuery = serializeSearchParams(searchParams);
484502

@@ -722,10 +740,12 @@ export default function Page() {
722740
<div className="mb-4">
723741
<MobileFilterChips
724742
activeFilters={{
743+
hasExactItemName: !!searchParams.isExactItemName,
725744
hasCategory: selectedCategory !== "all",
726745
hasPrice: !!(mobilePriceMin || mobilePriceMax),
727746
hasOptions: mobileActiveFilters.length > 0,
728747
}}
748+
onExactItemNameToggle={() => handleExactItemNameChange(!searchParams.isExactItemName)}
729749
onCategoryClick={() => setMobileFilterType("category")}
730750
onPriceClick={() => setMobileFilterType("price")}
731751
onOptionsClick={() => setMobileFilterType("options")}
@@ -856,6 +876,8 @@ export default function Page() {
856876
isModal={isFilterModalOpen}
857877
onClose={() => setIsFilterModalOpen(false)}
858878
layoutMode={layoutMode}
879+
isExactItemName={!!searchParams.isExactItemName}
880+
onExactItemNameChange={handleExactItemNameChange}
859881
/>
860882
)}
861883

src/components/page/auction-history/MobileFilterChips.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
"use client";
22

3-
import { DollarSign, Calendar, Settings, FolderTree } from "lucide-react";
3+
import { DollarSign, Calendar, Settings, FolderTree, TextSearch } from "lucide-react";
44

55
interface MobileFilterChipsProps {
66
activeFilters: {
7+
hasExactItemName: boolean;
78
hasCategory: boolean;
89
hasPrice: boolean;
910
hasDate: boolean;
1011
hasOptions: boolean;
1112
};
13+
onExactItemNameToggle: () => void;
1214
onCategoryClick: () => void;
1315
onPriceClick: () => void;
1416
onDateClick: () => void;
@@ -17,13 +19,27 @@ interface MobileFilterChipsProps {
1719

1820
export default function MobileFilterChips({
1921
activeFilters,
22+
onExactItemNameToggle,
2023
onCategoryClick,
2124
onPriceClick,
2225
onDateClick,
2326
onOptionsClick,
2427
}: MobileFilterChipsProps) {
2528
return (
2629
<div className="flex items-center gap-1.5 overflow-x-auto pb-2 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
30+
{/* Exact Item Name Toggle Chip */}
31+
<button
32+
onClick={onExactItemNameToggle}
33+
className={`flex items-center gap-1 px-3 py-1.5 rounded-full transition-all whitespace-nowrap ${
34+
activeFilters.hasExactItemName
35+
? "bg-[var(--color-ds-primary)] text-white border-2 border-[var(--color-ds-primary)]"
36+
: "bg-white dark:bg-navy-800 text-[var(--color-ds-text)] border border-[var(--color-ds-neutral-tone)] hover:border-[var(--color-ds-primary)]"
37+
}`}
38+
>
39+
<TextSearch className="w-3.5 h-3.5" />
40+
<span className="text-xs font-medium">완전 일치</span>
41+
</button>
42+
2743
{/* Category Filter Chip */}
2844
<button
2945
onClick={onCategoryClick}

src/components/page/auction-history/SearchFilterCard.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ import {
1818
} from "@/types/search-filter";
1919
import { AuctionHistorySearchParams } from "@/types/auction-history";
2020
import { LayoutMode } from "@/hooks/useAuctionHistoryLayout";
21-
import { Plus, X, RotateCcw, ChevronDown, ChevronUp, ArrowUp, ArrowDown } from "lucide-react";
21+
import { Plus, X, RotateCcw, ChevronDown, ChevronUp, ArrowUp, ArrowDown, Check } from "lucide-react";
2222

2323
interface SearchFilterCardProps {
2424
onFilterApply: (filters: AuctionHistorySearchParams) => void;
2525
isModal?: boolean;
2626
onClose?: () => void;
2727
/** 현재 레이아웃 모드 - 데스크탑/태블릿에 따라 위치 조정 */
2828
layoutMode?: LayoutMode;
29+
/** 아이템명 완전 일치 검색 여부 */
30+
isExactItemName?: boolean;
31+
onExactItemNameChange?: (value: boolean) => void;
2932
}
3033

3134
type DatePreset = "1week" | "1month" | "3months";
@@ -62,6 +65,8 @@ export default function SearchFilterCard({
6265
isModal = false,
6366
onClose,
6467
layoutMode = "desktop",
68+
isExactItemName = false,
69+
onExactItemNameChange,
6570
}: SearchFilterCardProps) {
6671
// 레이아웃 모드에 따른 필터 카드 위치 계산
6772
// 데스크탑(3-column): 간격 6px → 50% - 448 - 6 - 256 = 50% - 710px
@@ -440,6 +445,36 @@ export default function SearchFilterCard({
440445
</div>
441446
</div>
442447

448+
{/* Exact Item Name Toggle - Compact Card */}
449+
<button
450+
type="button"
451+
onClick={() => onExactItemNameChange?.(!isExactItemName)}
452+
className={`w-full rounded-xl border cursor-pointer py-2 px-3 flex items-center gap-2.5 transition-all ${
453+
isExactItemName
454+
? "bg-blaanid-50 dark:bg-coral-500/10 border-blaanid-400 dark:border-coral-500"
455+
: "bg-white dark:bg-navy-700 border-gray-200 dark:border-navy-500 hover:border-gray-300 dark:hover:border-navy-400"
456+
}`}
457+
>
458+
<div
459+
className={`w-4 h-4 rounded flex items-center justify-center shrink-0 transition-all ${
460+
isExactItemName
461+
? "bg-blaanid-500 dark:bg-coral-500 border-0"
462+
: "border border-gray-300 dark:border-navy-400 bg-white dark:bg-navy-600"
463+
}`}
464+
>
465+
{isExactItemName && <Check className="w-3 h-3 text-white" strokeWidth={3} />}
466+
</div>
467+
<span
468+
className={`text-xs font-medium leading-none ${
469+
isExactItemName
470+
? "text-blaanid-700 dark:text-coral-300"
471+
: "text-gray-600 dark:text-gray-300"
472+
}`}
473+
>
474+
아이템명 완전 일치
475+
</span>
476+
</button>
477+
443478
{/* Price & Date Combined Filter - Compact */}
444479
<div className="bg-gray-50 dark:bg-navy-700 rounded-xl border border-gray-200 dark:border-navy-500 p-3">
445480
{/* Price Section */}

src/components/page/auction-realtime/MobileFilterChips.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,42 @@
11
"use client";
22

3-
import { DollarSign, Settings, FolderTree } from "lucide-react";
3+
import { DollarSign, Settings, FolderTree, TextSearch } from "lucide-react";
44

55
interface MobileFilterChipsProps {
66
activeFilters: {
7+
hasExactItemName: boolean;
78
hasCategory: boolean;
89
hasPrice: boolean;
910
hasOptions: boolean;
1011
};
12+
onExactItemNameToggle: () => void;
1113
onCategoryClick: () => void;
1214
onPriceClick: () => void;
1315
onOptionsClick: () => void;
1416
}
1517

1618
export default function MobileFilterChips({
1719
activeFilters,
20+
onExactItemNameToggle,
1821
onCategoryClick,
1922
onPriceClick,
2023
onOptionsClick,
2124
}: MobileFilterChipsProps) {
2225
return (
2326
<div className="flex items-center gap-1.5 overflow-x-auto pb-2 [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
27+
{/* Exact Item Name Toggle Chip */}
28+
<button
29+
onClick={onExactItemNameToggle}
30+
className={`flex items-center gap-1 px-3 py-1.5 rounded-full transition-all whitespace-nowrap ${
31+
activeFilters.hasExactItemName
32+
? "bg-[var(--color-ds-primary)] text-white border-2 border-[var(--color-ds-primary)]"
33+
: "bg-white dark:bg-navy-800 text-[var(--color-ds-text)] border border-[var(--color-ds-neutral-tone)] hover:border-[var(--color-ds-primary)]"
34+
}`}
35+
>
36+
<TextSearch className="w-3.5 h-3.5" />
37+
<span className="text-xs font-medium">완전 일치</span>
38+
</button>
39+
2440
{/* Category Filter Chip */}
2541
<button
2642
onClick={onCategoryClick}

src/components/page/auction-realtime/SearchFilterCard.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ import {
1818
} from "@/types/search-filter";
1919
import { AuctionRealtimeSearchParams } from "@/types/auction-realtime";
2020
import { LayoutMode } from "@/hooks/useAuctionHistoryLayout";
21-
import { Plus, X, RotateCcw, ArrowUp, ArrowDown } from "lucide-react";
21+
import { Plus, X, RotateCcw, ArrowUp, ArrowDown, Check } from "lucide-react";
2222

2323
interface SearchFilterCardProps {
2424
onFilterApply: (filters: AuctionRealtimeSearchParams) => void;
2525
isModal?: boolean;
2626
onClose?: () => void;
2727
/** 현재 레이아웃 모드 - 데스크탑/태블릿에 따라 위치 조정 */
2828
layoutMode?: LayoutMode;
29+
/** 아이템명 완전 일치 검색 여부 */
30+
isExactItemName?: boolean;
31+
onExactItemNameChange?: (value: boolean) => void;
2932
}
3033

3134
interface BasicFilters {
@@ -38,6 +41,8 @@ export default function SearchFilterCard({
3841
isModal = false,
3942
onClose,
4043
layoutMode = "desktop",
44+
isExactItemName = false,
45+
onExactItemNameChange,
4146
}: SearchFilterCardProps) {
4247
// 레이아웃 모드에 따른 필터 카드 위치 계산
4348
const filterRightPosition =
@@ -362,6 +367,36 @@ export default function SearchFilterCard({
362367
</div>
363368
</div>
364369

370+
{/* Exact Item Name Toggle - Compact Card */}
371+
<button
372+
type="button"
373+
onClick={() => onExactItemNameChange?.(!isExactItemName)}
374+
className={`w-full rounded-xl border cursor-pointer py-2 px-3 flex items-center gap-2.5 transition-all ${
375+
isExactItemName
376+
? "bg-blaanid-50 dark:bg-coral-500/10 border-blaanid-400 dark:border-coral-500"
377+
: "bg-white dark:bg-navy-700 border-gray-200 dark:border-navy-500 hover:border-gray-300 dark:hover:border-navy-400"
378+
}`}
379+
>
380+
<div
381+
className={`w-4 h-4 rounded flex items-center justify-center shrink-0 transition-all ${
382+
isExactItemName
383+
? "bg-blaanid-500 dark:bg-coral-500 border-0"
384+
: "border border-gray-300 dark:border-navy-400 bg-white dark:bg-navy-600"
385+
}`}
386+
>
387+
{isExactItemName && <Check className="w-3 h-3 text-white" strokeWidth={3} />}
388+
</div>
389+
<span
390+
className={`text-xs font-medium leading-none ${
391+
isExactItemName
392+
? "text-blaanid-700 dark:text-coral-300"
393+
: "text-gray-600 dark:text-gray-300"
394+
}`}
395+
>
396+
아이템명 완전 일치
397+
</span>
398+
</button>
399+
365400
{/* Price Filter Only - 날짜 필터 없음 */}
366401
<div className="bg-gray-50 dark:bg-navy-700 rounded-xl border border-gray-200 dark:border-navy-500 p-3">
367402
{/* Price Section */}

src/types/auction-history.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ export interface ItemOptionSearchRequest {
198198
*/
199199
export interface AuctionHistorySearchRequest {
200200
itemName?: string;
201+
isExactItemName?: boolean;
201202
itemTopCategory?: string;
202203
itemSubCategory?: string;
203204
dateAuctionBuyRequest?: DateAuctionBuyRequest;

src/types/auction-realtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type { SearchStandard, PriceSearchRequest, ItemOptionSearchRequest };
2020
*/
2121
export interface AuctionRealtimeSearchRequest {
2222
itemName?: string;
23+
isExactItemName?: boolean;
2324
itemTopCategory?: string;
2425
itemSubCategory?: string;
2526
priceSearchRequest?: PriceSearchRequest;

0 commit comments

Comments
 (0)