File tree Expand file tree Collapse file tree 6 files changed +120
-27
lines changed
Expand file tree Collapse file tree 6 files changed +120
-27
lines changed Original file line number Diff line number Diff 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 />
Original file line number Diff line number Diff line change 11import { formatPriceForUser } from "../../models/product" ;
2+ import { filterProductsBySearchTerm } from "../../models/search" ;
23import { ImageIcon } from "../icons" ;
34import { getStockStatus } from "../../utils/validators" ;
45import { useCart } from "../../hooks/useCart" ;
@@ -14,18 +15,10 @@ 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 (
19+ products ,
20+ debouncedSearchTerm
21+ ) ;
2922 return (
3023 < section >
3124 < div className = "mb-6 flex justify-between items-center" >
Original file line number Diff line number Diff line change 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+ } ;
Original file line number Diff line number Diff line change @@ -10,6 +10,7 @@ import { useNotifications } from "./hooks/useNotifications";
1010import { createEmptyCouponForm } from "./models/coupon" ;
1111import { useLocalStorage } from "./utils/hooks/useLocalStorage" ;
1212import { useDebounce } from "./utils/hooks/useDebounce" ;
13+
1314import { CartIcon } from "./components/icons" ;
1415import { 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 />
Original file line number Diff line number Diff line change @@ -3,7 +3,7 @@ import Cart from "./cart/Cart";
33import { calculateItemTotal } from "../models/cart" ;
44import { ProductList } from "./cart/ProductList" ;
55import { ProductWithUI } from "../models/product" ;
6- import { NotificationType } from "../App " ;
6+ import { filterProductsBySearchTerm } from "../models/search " ;
77
88interface 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" >
Original file line number Diff line number Diff line change 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+ } ;
You can’t perform that action at this time.
0 commit comments