Skip to content

Commit 3c99de3

Browse files
committed
refactor: 검색하는 로직 분리
1 parent f92d9b9 commit 3c99de3

File tree

6 files changed

+117
-27
lines changed

6 files changed

+117
-27
lines changed

src/advanced/App.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ const App = () => {
1515
const { notifications, removeNotification } = useNotifications();
1616
const { cart, cartItemCount } = useCart();
1717

18+
// 검색 핸들러 - presenter layer
19+
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
20+
setSearchTerm(event.target.value);
21+
};
22+
1823
return (
1924
<>
2025
<ToastContainer
@@ -32,7 +37,7 @@ const App = () => {
3237
<input
3338
type="text"
3439
value={searchTerm}
35-
onChange={(event) => setSearchTerm(event.target.value)}
40+
onChange={handleSearchChange}
3641
placeholder="상품 검색..."
3742
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500"
3843
/>

src/advanced/components/cart/ProductList.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { formatPriceForUser } from "../../models/product";
2+
import { filterProductsBySearchTerm } from "../../models/search";
23
import { ImageIcon } from "../icons";
34
import { getStockStatus } from "../../utils/validators";
45
import { useCart } from "../../hooks/useCart";
@@ -14,18 +15,7 @@ export const ProductList = () => {
1415
const { products } = useProducts();
1516
const { addToCart, getStockForProduct } = useCart();
1617

17-
const filteredProducts = debouncedSearchTerm
18-
? products.filter(
19-
(product) =>
20-
product.name
21-
.toLowerCase()
22-
.includes(debouncedSearchTerm.toLowerCase()) ||
23-
(product.description &&
24-
product.description
25-
.toLowerCase()
26-
.includes(debouncedSearchTerm.toLowerCase()))
27-
)
28-
: products;
18+
const filteredProducts = filterProductsBySearchTerm(products, debouncedSearchTerm);
2919
return (
3020
<section>
3121
<div className="mb-6 flex justify-between items-center">

src/advanced/models/search.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { ProductWithUI } from "./product";
2+
3+
/**
4+
* 상품 검색 필터링 함수
5+
* 상품명과 설명에서 검색어를 찾아 필터링
6+
*/
7+
export const filterProductsBySearchTerm = (
8+
products: ProductWithUI[],
9+
searchTerm: string
10+
): ProductWithUI[] => {
11+
if (!searchTerm.trim()) {
12+
return products;
13+
}
14+
15+
const normalizedSearchTerm = searchTerm.toLowerCase().trim();
16+
17+
return products.filter((product) => {
18+
const matchesName = product.name
19+
.toLowerCase()
20+
.includes(normalizedSearchTerm);
21+
22+
const matchesDescription = product.description
23+
? product.description.toLowerCase().includes(normalizedSearchTerm)
24+
: false;
25+
26+
return matchesName || matchesDescription;
27+
});
28+
};
29+
30+
/**
31+
* 검색어 정규화 함수
32+
*/
33+
export const normalizeSearchTerm = (searchTerm: string): string => {
34+
return searchTerm.toLowerCase().trim();
35+
};
36+
37+
/**
38+
* 검색 결과가 있는지 확인하는 함수
39+
*/
40+
export const hasSearchResults = (
41+
products: ProductWithUI[],
42+
searchTerm: string
43+
): boolean => {
44+
if (!searchTerm.trim()) {
45+
return true;
46+
}
47+
48+
return filterProductsBySearchTerm(products, searchTerm).length > 0;
49+
};

src/basic/App.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useNotifications } from "./hooks/useNotifications";
1010
import { createEmptyCouponForm } from "./models/coupon";
1111
import { useLocalStorage } from "./utils/hooks/useLocalStorage";
1212
import { useDebounce } from "./utils/hooks/useDebounce";
13+
1314
import { CartIcon } from "./components/icons";
1415
import { ToastContainer } from "./components/ui";
1516

@@ -73,6 +74,10 @@ const App = () => {
7374
handleDiscountRateChange,
7475
} = useProducts(initialProducts, onSuccess);
7576

77+
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
78+
setSearchTerm(event.target.value);
79+
};
80+
7681
return (
7782
<>
7883
<ToastContainer
@@ -90,7 +95,7 @@ const App = () => {
9095
<input
9196
type="text"
9297
value={searchTerm}
93-
onChange={(e) => setSearchTerm(e.target.value)}
98+
onChange={handleSearchChange}
9499
placeholder="상품 검색..."
95100
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500"
96101
/>

src/basic/components/CartPage.tsx

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Cart from "./cart/Cart";
33
import { calculateItemTotal } from "../models/cart";
44
import { ProductList } from "./cart/ProductList";
55
import { ProductWithUI } from "../models/product";
6-
import { NotificationType } from "../App";
6+
import { filterProductsBySearchTerm } from "../models/search";
77

88
interface CartPageProps {
99
products: ProductWithUI[];
@@ -57,18 +57,10 @@ export const CartPage = ({
5757
}: CartPageProps) => {
5858
const totals = calculateTotal(cart, selectedCoupon);
5959

60-
const filteredProducts = debouncedSearchTerm
61-
? products.filter(
62-
(product) =>
63-
product.name
64-
.toLowerCase()
65-
.includes(debouncedSearchTerm.toLowerCase()) ||
66-
(product.description &&
67-
product.description
68-
.toLowerCase()
69-
.includes(debouncedSearchTerm.toLowerCase()))
70-
)
71-
: products;
60+
const filteredProducts = filterProductsBySearchTerm(
61+
products,
62+
debouncedSearchTerm
63+
);
7264

7365
return (
7466
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">

src/basic/models/search.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { ProductWithUI } from "./product";
2+
3+
/**
4+
* 상품 검색 필터링 함수
5+
* 상품명과 설명에서 검색어를 찾아 필터링
6+
*/
7+
export const filterProductsBySearchTerm = (
8+
products: ProductWithUI[],
9+
searchTerm: string
10+
): ProductWithUI[] => {
11+
if (!searchTerm.trim()) {
12+
return products;
13+
}
14+
15+
const normalizedSearchTerm = searchTerm.toLowerCase().trim();
16+
17+
return products.filter((product) => {
18+
const matchesName = product.name
19+
.toLowerCase()
20+
.includes(normalizedSearchTerm);
21+
22+
const matchesDescription = product.description
23+
? product.description.toLowerCase().includes(normalizedSearchTerm)
24+
: false;
25+
26+
return matchesName || matchesDescription;
27+
});
28+
};
29+
30+
/**
31+
* 검색어 정규화 함수
32+
*/
33+
export const normalizeSearchTerm = (searchTerm: string): string => {
34+
return searchTerm.toLowerCase().trim();
35+
};
36+
37+
/**
38+
* 검색 결과가 있는지 확인하는 함수
39+
*/
40+
export const hasSearchResults = (
41+
products: ProductWithUI[],
42+
searchTerm: string
43+
): boolean => {
44+
if (!searchTerm.trim()) {
45+
return true;
46+
}
47+
48+
return filterProductsBySearchTerm(products, searchTerm).length > 0;
49+
};

0 commit comments

Comments
 (0)