diff --git a/src/refactoring/components/AdminPage.tsx b/src/refactoring/components/AdminPage.tsx index bb6e0d75..71f6252a 100644 --- a/src/refactoring/components/AdminPage.tsx +++ b/src/refactoring/components/AdminPage.tsx @@ -1,349 +1,31 @@ -import { useState } from "react"; -import { Coupon, Discount, Product } from "../../types"; +import { Coupon, Product } from "../../types"; +import { ProductList } from "./adminpage/AdminProductList"; +import { CouponList } from "./adminpage/AdminCouponList"; +import { CouponForm } from "./adminpage/AdminCouponForm"; interface Props { products: Product[]; coupons: Coupon[]; onProductUpdate: (updatedProduct: Product) => void; - onProductAdd: (newProduct: Product) => void; + onProductAdd: (newProduct: Product) => void; onCouponAdd: (newCoupon: Coupon) => void; } export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, onCouponAdd }: Props) => { - const [openProductIds, setOpenProductIds] = useState>(new Set()); - const [editingProduct, setEditingProduct] = useState(null); - const [newDiscount, setNewDiscount] = useState({ quantity: 0, rate: 0 }); - const [newCoupon, setNewCoupon] = useState({ - name: "", - code: "", - discountType: "percentage", - discountValue: 0 - }); - const [showNewProductForm, setShowNewProductForm] = useState(false); - const [newProduct, setNewProduct] = useState>({ - name: "", - price: 0, - stock: 0, - discounts: [] - }); - - const toggleProductAccordion = (productId: string) => { - setOpenProductIds(prev => { - const newSet = new Set(prev); - if (newSet.has(productId)) { - newSet.delete(productId); - } else { - newSet.add(productId); - } - return newSet; - }); - }; - - // handleEditProduct 함수 수정 - const handleEditProduct = (product: Product) => { - setEditingProduct({...product}); - }; - - // 새로운 핸들러 함수 추가 - const handleProductNameUpdate = (productId: string, newName: string) => { - if (editingProduct && editingProduct.id === productId) { - const updatedProduct = { ...editingProduct, name: newName }; - setEditingProduct(updatedProduct); - } - }; - - // 새로운 핸들러 함수 추가 - const handlePriceUpdate = (productId: string, newPrice: number) => { - if (editingProduct && editingProduct.id === productId) { - const updatedProduct = { ...editingProduct, price: newPrice }; - setEditingProduct(updatedProduct); - } - }; - - // 수정 완료 핸들러 함수 추가 - const handleEditComplete = () => { - if (editingProduct) { - onProductUpdate(editingProduct); - setEditingProduct(null); - } - }; - - const handleStockUpdate = (productId: string, newStock: number) => { - const updatedProduct = products.find(p => p.id === productId); - if (updatedProduct) { - const newProduct = { ...updatedProduct, stock: newStock }; - onProductUpdate(newProduct); - setEditingProduct(newProduct); - } - }; - - const handleAddDiscount = (productId: string) => { - const updatedProduct = products.find(p => p.id === productId); - if (updatedProduct && editingProduct) { - const newProduct = { - ...updatedProduct, - discounts: [...updatedProduct.discounts, newDiscount] - }; - onProductUpdate(newProduct); - setEditingProduct(newProduct); - setNewDiscount({ quantity: 0, rate: 0 }); - } - }; - - const handleRemoveDiscount = (productId: string, index: number) => { - const updatedProduct = products.find(p => p.id === productId); - if (updatedProduct) { - const newProduct = { - ...updatedProduct, - discounts: updatedProduct.discounts.filter((_, i) => i !== index) - }; - onProductUpdate(newProduct); - setEditingProduct(newProduct); - } - }; - - const handleAddCoupon = () => { - onCouponAdd(newCoupon); - setNewCoupon({ - name: "", - code: "", - discountType: "percentage", - discountValue: 0 - }); - }; - - const handleAddNewProduct = () => { - const productWithId = { ...newProduct, id: Date.now().toString() }; - - // 새 상품에 할인율 추가 - if (!Array.isArray(productWithId.discounts)) { - productWithId.discounts = []; - } - - onProductAdd(productWithId); - setNewProduct({ - name: "", - price: 0, - stock: 0, - discounts: [], - }); - setShowNewProductForm(false); - }; return (

관리자 페이지

-
-

상품 관리

- - {showNewProductForm && ( -
-

새 상품 추가

-
- - setNewProduct({ ...newProduct, name: e.target.value })} - className="w-full p-2 border rounded" - /> -
-
- - setNewProduct({ ...newProduct, price: parseInt(e.target.value) })} - className="w-full p-2 border rounded" - /> -
-
- - setNewProduct({ ...newProduct, stock: parseInt(e.target.value) })} - className="w-full p-2 border rounded" - /> -
- -
- )} -
- {products.map((product, index) => ( -
- - {openProductIds.has(product.id) && ( -
- {editingProduct && editingProduct.id === product.id ? ( -
-
- - handleProductNameUpdate(product.id, e.target.value)} - className="w-full p-2 border rounded" - /> -
-
- - handlePriceUpdate(product.id, parseInt(e.target.value))} - className="w-full p-2 border rounded" - /> -
-
- - handleStockUpdate(product.id, parseInt(e.target.value))} - className="w-full p-2 border rounded" - /> -
- {/* 할인 정보 수정 부분 */} -
-

할인 정보

- {editingProduct.discounts.map((discount, index) => ( -
- {discount.quantity}개 이상 구매 시 {discount.rate * 100}% 할인 - -
- ))} -
- setNewDiscount({ ...newDiscount, quantity: parseInt(e.target.value) })} - className="w-1/3 p-2 border rounded" - /> - setNewDiscount({ ...newDiscount, rate: parseInt(e.target.value) / 100 })} - className="w-1/3 p-2 border rounded" - /> - -
-
- -
- ) : ( -
- {product.discounts.map((discount, index) => ( -
- {discount.quantity}개 이상 구매 시 {discount.rate * 100}% 할인 -
- ))} - -
- )} -
- )} -
- ))} -
-
+

쿠폰 관리

-
-
- setNewCoupon({ ...newCoupon, name: e.target.value })} - className="w-full p-2 border rounded" - /> - setNewCoupon({ ...newCoupon, code: e.target.value })} - className="w-full p-2 border rounded" - /> -
- - setNewCoupon({ ...newCoupon, discountValue: parseInt(e.target.value) })} - className="w-full p-2 border rounded" - /> -
- -
-
-

현재 쿠폰 목록

-
- {coupons.map((coupon, index) => ( -
- {coupon.name} ({coupon.code}): - {coupon.discountType === "amount" ? `${coupon.discountValue}원` : `${coupon.discountValue}%`} 할인 -
- ))} -
-
-
+ +
diff --git a/src/refactoring/components/CartPage.tsx b/src/refactoring/components/CartPage.tsx index 6b335295..6ecab631 100644 --- a/src/refactoring/components/CartPage.tsx +++ b/src/refactoring/components/CartPage.tsx @@ -1,7 +1,11 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback, useMemo } from "react"; import { useCart } from "../hooks/useCart.ts"; import { useProducts } from "../hooks/useProducts.ts"; -import { Coupon, Product } from "../../types.ts"; +import { Coupon, Product, CartTotal } from "../../types.ts"; +import { ProductList } from "./cartpage/CartProductList.tsx"; +import { CartDetail } from "./cartpage/CartDetail.tsx"; +import { CouponSection } from "./cartpage/CartCouponSection.tsx"; +import { OrderSummary } from "./cartpage/CartOrderSummary.tsx"; interface Props { products: Product[]; @@ -24,18 +28,14 @@ export const CartPage = ({ products = [], coupons = [] }: Props) => { const { getMaxDiscount } = useProducts(products); const [selectedCoupon, setSelectedCoupon] = useState(null); - const [summary, setSummary] = useState<{ - totalBeforeDiscount: number; - totalAfterDiscount: number; - totalDiscount: number; - }>({ + const [summary, setSummary] = useState({ totalBeforeDiscount: 0, totalAfterDiscount: 0, totalDiscount: 0, }); // 쿠폰 적용 및 상태 업데이트 - const handleApplyCoupon = (index: number) => { + const handleApplyCoupon = useCallback((index: number) => { const selected = coupons[index]; if (index === -1) { // "쿠폰 선택"을 선택한 경우 applyCoupon(null); // 쿠폰을 취소 @@ -44,152 +44,49 @@ export const CartPage = ({ products = [], coupons = [] }: Props) => { applyCoupon(selected); // 새로운 쿠폰 적용 setSelectedCoupon(selected); // 상태 업데이트 } - }; + }, [coupons, applyCoupon]); + + // 상품별 최대 할인율 계산 메모이제이션 + const memoizedMaxDiscount = useMemo(() => { + return products.reduce>((acc, product) => ({ + ...acc, + [product.id]: getMaxDiscount(product.discounts) + }), {}); + }, [products, getMaxDiscount]); // selectedCoupon이 변경될 때마다 계산 useEffect(() => { - const { totalBeforeDiscount, totalAfterDiscount, totalDiscount } = calculateTotal(); - - setSummary({ - totalBeforeDiscount: totalBeforeDiscount, - totalAfterDiscount: totalAfterDiscount, - totalDiscount: totalDiscount, - }); + const cartTotal = calculateTotal(); + setSummary(cartTotal); }, [selectedCoupon, cart]); // selectedCoupon 또는 cart 변경 시 재계산 return (

장바구니

+
-

상품 목록

-
- {products.length > 0 ? ( - products.map((product) => { - const remainingStock = getRemainingStock(product); - return ( -
-
- {product.name} - {product.price.toLocaleString()}원 -
-
- 0 ? "text-green-600" : "text-red-600"}`}> - 재고: {remainingStock}개 - - {product.discounts.length > 0 && ( - - 최대 {(getMaxDiscount(product.discounts) * 100).toFixed(0)}% 할인 - - )} -
- {product.discounts.length > 0 && ( -
    - {product.discounts.map((discount, index) => ( -
  • - {discount.quantity}개 이상: {(discount.rate * 100).toFixed(0)}% 할인 -
  • - ))} -
- )} - -
- ); - }) - ) : ( -

표시할 상품이 없습니다.

- )} -
-
-
-

장바구니 내역

- -
- {cart.map(item => { - const appliedDiscount = getAppliedDiscount(item); - return ( -
-
- {item.product.name} -
- - {item.product.price}원 x {item.quantity} - {appliedDiscount > 0 && ( - - ({(appliedDiscount * 100).toFixed(0)}% 할인 적용) - - )} - -
-
- - - -
-
- ); - })} -
- -
-

쿠폰 적용

- - {selectedCoupon && ( -

- 적용된 쿠폰: {selectedCoupon.name} - ({selectedCoupon.discountType === "amount" ? `${selectedCoupon.discountValue}원` : `${selectedCoupon.discountValue}%`} 할인) -

- )} -
- -
-

주문 요약

-
-

상품 금액: {(summary?.totalBeforeDiscount).toLocaleString()}원

-

할인 금액: {(summary?.totalDiscount).toLocaleString()}원

-

- 최종 결제 금액: {(summary?.totalAfterDiscount).toLocaleString()}원 -

-
-
+ + +
- ); }; diff --git a/src/refactoring/components/adminpage/AdminCouponForm.tsx b/src/refactoring/components/adminpage/AdminCouponForm.tsx new file mode 100644 index 00000000..d5cd0b13 --- /dev/null +++ b/src/refactoring/components/adminpage/AdminCouponForm.tsx @@ -0,0 +1,66 @@ +import { useState } from "react"; +import { CouponFormProps, Coupon } from "../../../types"; + +export const CouponForm = ({ onCouponAdd }: CouponFormProps) => { + const [newCoupon, setNewCoupon] = useState({ + name: "", + code: "", + discountType: "percentage", + discountValue: 0 + }); + + const handleAddCoupon = () => { + onCouponAdd(newCoupon); + setNewCoupon({ + name: "", + code: "", + discountType: "percentage", + discountValue: 0 + }); + }; + + return ( +
+
+ setNewCoupon({ ...newCoupon, name: e.target.value })} + className="w-full p-2 border rounded" + /> + setNewCoupon({ ...newCoupon, code: e.target.value })} + className="w-full p-2 border rounded" + /> +
+ + setNewCoupon({ ...newCoupon, discountValue: parseInt(e.target.value) })} + className="w-full p-2 border rounded" + /> +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/src/refactoring/components/adminpage/AdminCouponList.tsx b/src/refactoring/components/adminpage/AdminCouponList.tsx new file mode 100644 index 00000000..7d7c3f2c --- /dev/null +++ b/src/refactoring/components/adminpage/AdminCouponList.tsx @@ -0,0 +1,17 @@ +import { CouponListProps } from "../../../types"; + +export const CouponList = ({ coupons }: CouponListProps) => { + return ( +
+

현재 쿠폰 목록

+
+ {coupons.map((coupon, index) => ( +
+ {coupon.name} ({coupon.code}): + {coupon.discountType === "amount" ? `${coupon.discountValue}원` : `${coupon.discountValue}%`} 할인 +
+ ))} +
+
+ ); +} diff --git a/src/refactoring/components/adminpage/AdminProductForm.tsx b/src/refactoring/components/adminpage/AdminProductForm.tsx new file mode 100644 index 00000000..8c8c8dac --- /dev/null +++ b/src/refactoring/components/adminpage/AdminProductForm.tsx @@ -0,0 +1,59 @@ +import { useState } from "react"; +import { ProductFormProps, Product } from "../../../types"; + +export const ProductForm = ({ onProductAdd }: ProductFormProps) => { + const [newProduct, setNewProduct] = useState>({ + name: "", + price: 0, + stock: 0, + discounts: [], + }); + + const handleAddNewProduct = () => { + const productWithId = { ...newProduct, id: Date.now().toString() }; + onProductAdd(productWithId); + setNewProduct({ name: "", price: 0, stock: 0, discounts: [] }); + }; + + return ( +
+

새 상품 추가

+
+ + setNewProduct({ ...newProduct, name: e.target.value })} + className="w-full p-2 border rounded" + /> +
+
+ + setNewProduct({ ...newProduct, price: parseInt(e.target.value) })} + className="w-full p-2 border rounded" + /> +
+
+ + setNewProduct({ ...newProduct, stock: parseInt(e.target.value) })} + className="w-full p-2 border rounded" + /> +
+ +
+ ); +}; \ No newline at end of file diff --git a/src/refactoring/components/adminpage/AdminProductList.tsx b/src/refactoring/components/adminpage/AdminProductList.tsx new file mode 100644 index 00000000..ed312671 --- /dev/null +++ b/src/refactoring/components/adminpage/AdminProductList.tsx @@ -0,0 +1,198 @@ +import { useState } from "react"; +import { Product, AdminProductListProps } from "../../../types"; +import { ProductForm } from "./AdminProductForm"; +import { useProductAccordion } from "../../hooks/useProductAccordion"; +import { useEditingProduct } from "../../hooks/useEditingProduct"; +import { useDiscounts } from "../../hooks/useDiscounts"; +import { updateProduct, filterDiscounts } from "../../utils/productUtils"; + +export const ProductList = ({ products, onProductUpdate, onProductAdd }: AdminProductListProps) => { + const { openProductIds, toggleProductAccordion } = useProductAccordion(); + const { editingProduct, startEditing, stopEditing, updateProductField, setEditingProduct } = useEditingProduct(); + const { newDiscount, setNewDiscount, addDiscount, removeDiscount } = useDiscounts(); + + const [showNewProductForm, setShowNewProductForm] = useState(false); + // handleEditProduct 함수 수정 + const handleProductAdd = (newProduct: Product) => { + onProductAdd(newProduct); + }; + + // 수정 완료 핸들러 함수 추가 + const handleEditComplete = () => { + if (editingProduct) { + onProductUpdate(editingProduct); + stopEditing(); + } + }; + + const handleStockUpdate = (productId: string, newStock: number) => { + const updatedProduct = products.find(p => p.id === productId); + if (updatedProduct) { + const newProduct = { ...updatedProduct, stock: newStock }; + // 이 부분에서 onProductUpdate 호출 + onProductUpdate(newProduct); + setEditingProduct(newProduct); // 수정된 제품을 state로 반영 + } + }; + + const handleAddDiscount = (productId: string) => { + if (editingProduct && editingProduct.id === productId) { + const updatedDiscounts = addDiscount(editingProduct.discounts, newDiscount); + const updatedProduct = { + ...editingProduct, + discounts: updatedDiscounts + }; + setEditingProduct(updatedProduct); + onProductUpdate(updatedProduct); + + // Reset discount input fields + setNewDiscount({ quantity: 0, rate: 0 }); + } + }; + + const handleRemoveDiscount = (productId: string, index: number) => { + const product = products.find(p => p.id === productId); + if (product && editingProduct && editingProduct.id === productId) { + const updatedDiscounts = [...editingProduct.discounts]; + updatedDiscounts.splice(index, 1); + + const updatedProduct = { + ...editingProduct, + discounts: updatedDiscounts + }; + + setEditingProduct(updatedProduct); + onProductUpdate(updatedProduct); + } + }; + + return ( +
+

상품 관리

+ + {showNewProductForm && ( + + )} +
+ {products.map((product, index) => ( +
+ + {openProductIds.has(product.id) && ( +
+ {editingProduct && editingProduct.id === product.id ? ( +
+
+ + updateProductField("name", e.target.value)} + className="w-full p-2 border rounded" + /> +
+
+ + updateProductField("price", parseInt(e.target.value))} + className="w-full p-2 border rounded" + /> +
+
+ + handleStockUpdate(product.id, parseInt(e.target.value))} + className="w-full p-2 border rounded" + /> +
+ {/* 할인 정보 수정 부분 */} +
+

할인 정보

+ {editingProduct.discounts.map((discount, index) => ( +
+ {discount.quantity}개 이상 구매 시 {discount.rate * 100}% 할인 + +
+ ))} +
+ setNewDiscount({ ...newDiscount, quantity: parseInt(e.target.value) })} + className="w-1/3 p-2 border rounded" + /> + setNewDiscount({ ...newDiscount, rate: parseInt(e.target.value) / 100 })} + className="w-1/3 p-2 border rounded" + /> + +
+
+ +
+ ) : ( +
+ {product.discounts.length > 0 ? ( + product.discounts.map((discount, index) => ( +
+ + {discount.quantity}개 이상 구매 시 {discount.rate * 100}% 할인 + +
+ )) + ) : ( +
할인 정보가 없습니다.
+ )} + +
+ )} +
+ )} +
+ ))} +
+
+ ); +}; \ No newline at end of file diff --git a/src/refactoring/components/cartpage/CartCouponSection.tsx b/src/refactoring/components/cartpage/CartCouponSection.tsx new file mode 100644 index 00000000..29002e6f --- /dev/null +++ b/src/refactoring/components/cartpage/CartCouponSection.tsx @@ -0,0 +1,28 @@ +import { CouponSectionProps } from "../../../types"; + +export const CouponSection = ({ coupons, selectedCoupon, handleApplyCoupon }: CouponSectionProps) => { + + return ( +
+

쿠폰 적용

+ + {selectedCoupon && ( +

+ 적용된 쿠폰: {selectedCoupon.name} + ({selectedCoupon.discountType === "amount" ? `${selectedCoupon.discountValue}원` : `${selectedCoupon.discountValue}%`} 할인) +

+ )} +
+ ); +}; \ No newline at end of file diff --git a/src/refactoring/components/cartpage/CartDetail.tsx b/src/refactoring/components/cartpage/CartDetail.tsx new file mode 100644 index 00000000..5cc6848f --- /dev/null +++ b/src/refactoring/components/cartpage/CartDetail.tsx @@ -0,0 +1,51 @@ +import { CartDetailsProps } from "../../../types"; + +export const CartDetail = ({ cart, updateQuantity, removeFromCart, getAppliedDiscount }: CartDetailsProps) => { + + return ( +
+

장바구니 내역

+
+ {cart.map(item => { + const appliedDiscount = getAppliedDiscount(item); + return ( +
+
+ {item.product.name} +
+ + {item.product.price}원 x {item.quantity} + {appliedDiscount > 0 && ( + + ({(appliedDiscount * 100).toFixed(0)}% 할인 적용) + + )} + +
+
+ + + +
+
+ ); + })} +
+
+ ); +}; \ No newline at end of file diff --git a/src/refactoring/components/cartpage/CartOrderSummary.tsx b/src/refactoring/components/cartpage/CartOrderSummary.tsx new file mode 100644 index 00000000..dcc9b987 --- /dev/null +++ b/src/refactoring/components/cartpage/CartOrderSummary.tsx @@ -0,0 +1,16 @@ +import { OrderSummaryProps } from "../../../types"; + +export const OrderSummary = ({ summary }: OrderSummaryProps) => { + return ( +
+

주문 요약

+
+

상품 금액: {(summary.totalBeforeDiscount).toLocaleString()}원

+

할인 금액: {(summary?.totalDiscount).toLocaleString()}원

+

+ 최종 결제 금액: {(summary?.totalAfterDiscount).toLocaleString()}원 +

+
+
+ ); +}; \ No newline at end of file diff --git a/src/refactoring/components/cartpage/CartProductList.tsx b/src/refactoring/components/cartpage/CartProductList.tsx new file mode 100644 index 00000000..1231a924 --- /dev/null +++ b/src/refactoring/components/cartpage/CartProductList.tsx @@ -0,0 +1,59 @@ +import { ProductListProps } from "../../../types"; + +export const ProductList = ({ products, addToCart, getRemainingStock, getMaxDiscount }: ProductListProps) => { + + return ( +
+

상품 목록

+
+ {products.length > 0 ? ( + products.map((product) => { + const remainingStock = getRemainingStock(product); + + return ( +
+
+ {product.name} + {product.price.toLocaleString()}원 +
+
+ 0 ? "text-green-600" : "text-red-600"}`}> + 재고: {remainingStock}개 + + {product.discounts.length > 0 && ( + + 최대 {(getMaxDiscount[product.id] * 100).toFixed(0)}% 할인 + + )} +
+ {product.discounts.length > 0 && ( +
    + {product.discounts.map((discount, index) => ( +
  • + {discount.quantity}개 이상: {(discount.rate * 100).toFixed(0)}% 할인 +
  • + ))} +
+ )} + +
+ ); + }) + ) : ( +

표시할 상품이 없습니다.

+ )} +
+
+ ); +}; + diff --git a/src/refactoring/hooks/useCart.ts b/src/refactoring/hooks/useCart.ts index cc39c779..edb8aff1 100644 --- a/src/refactoring/hooks/useCart.ts +++ b/src/refactoring/hooks/useCart.ts @@ -46,7 +46,7 @@ export const useCart = () => { ); }; - const applyCoupon = (coupon: Coupon) => { + const applyCoupon = (coupon: Coupon | null) => { setSelectedCoupon(coupon); }; diff --git a/src/refactoring/hooks/useDiscounts.ts b/src/refactoring/hooks/useDiscounts.ts new file mode 100644 index 00000000..751b9bad --- /dev/null +++ b/src/refactoring/hooks/useDiscounts.ts @@ -0,0 +1,20 @@ +import { useState } from "react"; +import { Discount } from "../../types"; + +export const useDiscounts = () => { + const [newDiscount, setNewDiscount] = useState({ quantity: 0, rate: 0 }); + const [discounts, setDiscounts] = useState([]); + + const addDiscount = (discounts: Discount[], newDiscount: Discount) => { + return [ ...discounts, newDiscount ]; + }; + + const removeDiscount = (existingDiscounts: Discount[], index: number) => { + const updatedDiscounts = [...existingDiscounts]; + updatedDiscounts.splice(index, 1); // 해당 인덱스의 할인 삭제 + + return updatedDiscounts; + }; + + return { newDiscount, setNewDiscount, addDiscount, removeDiscount }; +}; \ No newline at end of file diff --git a/src/refactoring/hooks/useEditingProduct.ts b/src/refactoring/hooks/useEditingProduct.ts new file mode 100644 index 00000000..4f9af8fa --- /dev/null +++ b/src/refactoring/hooks/useEditingProduct.ts @@ -0,0 +1,17 @@ +import { useState } from "react"; +import { Product } from "../../types"; + +export const useEditingProduct = () => { + const [editingProduct, setEditingProduct] = useState(null); + + const startEditing = (product: Product) => setEditingProduct({ ...product }); + const stopEditing = () => setEditingProduct(null); + + const updateProductField = (field: T, value: Product[T]) => { + if (editingProduct) { + setEditingProduct(prev => (prev ? { ...prev, [field]: value } : null)); + } + }; + + return { editingProduct, startEditing, stopEditing, updateProductField, setEditingProduct }; +}; diff --git a/src/refactoring/hooks/useProductAccordion.ts b/src/refactoring/hooks/useProductAccordion.ts new file mode 100644 index 00000000..a8bc7b58 --- /dev/null +++ b/src/refactoring/hooks/useProductAccordion.ts @@ -0,0 +1,19 @@ +import { useState } from "react"; + +export const useProductAccordion = () => { + const [openProductIds, setOpenProductIds] = useState>(new Set()); + + const toggleProductAccordion = (productId: string) => { + setOpenProductIds(prev => { + const newSet = new Set(prev); + if (newSet.has(productId)) { + newSet.delete(productId); + } else { + newSet.add(productId); + } + return newSet; + }); + }; + + return { openProductIds, toggleProductAccordion }; +}; diff --git a/src/refactoring/models/cart.ts b/src/refactoring/models/cart.ts index 001ab24f..0c4735c4 100644 --- a/src/refactoring/models/cart.ts +++ b/src/refactoring/models/cart.ts @@ -1,9 +1,35 @@ -import { CartItem, Coupon } from "../../types"; +import { CartItem, Coupon, CartTotal } from "../../types"; + +// 할인 계산 결과를 위한 인터페이스 +interface DiscountResult { + discountedTotal: number; + discountAmount: number; +} + +// 쿠폰 할인 계산을 위한 함수 +const calculateCouponDiscount = ( + total: number, + coupon: Coupon +): DiscountResult => { + if (coupon.discountType === "percentage") { + const discountAmount = total * (coupon.discountValue / 100); + return { + discountedTotal: total - discountAmount, + discountAmount + }; + } else { + const discountAmount = Math.min(coupon.discountValue, total); + return { + discountedTotal: total - discountAmount, + discountAmount + }; + } +}; export const calculateCartTotal = ( cart: CartItem[], selectedCoupon: Coupon | null -) => { +): CartTotal => { // 1. 상품 금액 합계 (할인 전) const totalBeforeDiscount = cart.reduce( (sum, item) => sum + item.product.price * item.quantity, @@ -16,22 +42,13 @@ export const calculateCartTotal = ( 0 ); - // 쿠폰 할인 적용 - let couponDiscount = 0; - - if (selectedCoupon) { - // 전체 금액에 대해 한 번만 쿠폰 할인 적용 - if (selectedCoupon.discountType === "percentage") { - couponDiscount = totalAfterItemDiscount * (selectedCoupon.discountValue / 100); - } else if (selectedCoupon.discountType === "amount") { - couponDiscount = Math.min(selectedCoupon.discountValue, totalAfterItemDiscount); - } - } - - // 4. 최종 결제 금액 - const totalAfterDiscount = Math.max(0, totalAfterItemDiscount - couponDiscount); + // 3. 쿠폰 할인 적용 + const { discountedTotal: totalAfterDiscount, discountAmount: couponDiscount } = + selectedCoupon + ? calculateCouponDiscount(totalAfterItemDiscount, selectedCoupon) + : { discountedTotal: totalAfterItemDiscount, discountAmount: 0 }; - // 5. 총 할인 금액 + // 4. 총 할인 금액 계산 const totalDiscount = totalBeforeDiscount - totalAfterItemDiscount + couponDiscount; return { diff --git a/src/refactoring/utils/productUtils.ts b/src/refactoring/utils/productUtils.ts new file mode 100644 index 00000000..efc45720 --- /dev/null +++ b/src/refactoring/utils/productUtils.ts @@ -0,0 +1,9 @@ +import { Product, Discount } from "../../types"; + +export const updateProduct = (product: Product, updates: Partial): Product => { + return { ...product, ...updates }; +}; + +export const filterDiscounts = (discounts: Discount[], indexToRemove: number): Discount[] => { + return discounts.filter((_, index) => index !== indexToRemove); +}; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index aafe0aba..0fcda6e8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,3 +22,55 @@ export interface Coupon { discountType: "amount" | "percentage"; discountValue: number; } + +// 계산 결과를 위한 인터페이스 정의 +export interface CartTotal { + totalBeforeDiscount: number; + totalAfterDiscount: number; + totalDiscount: number; +} + +export interface OrderSummaryProps { + summary: CartTotal; +} + +export interface ProductListProps { + products: Product[]; + addToCart: (product: Product) => void; + getRemainingStock: (product: Product) => number; + getMaxDiscount: Record; +} + +export interface CartDetailsProps { + cart: { product: Product; quantity: number }[]; + updateQuantity: (productId: string, quantity: number) => void; + removeFromCart: (productId: string) => void; + getAppliedDiscount: (item: { product: Product; quantity: number }) => number; +} + +export interface CouponSectionProps { + coupons: Coupon[]; + selectedCoupon: Coupon | null; + handleApplyCoupon: (index: number) => void; +} + +export interface ProductFormProps { + onProductAdd: (newProduct: Product) => void; +} + +export interface AdminProductListProps { + products: Product[]; + onProductUpdate: (updatedProduct: Product) => void; + onProductAdd: (newProduct: Product) => void; +} + +export interface CouponListProps { + coupons: Coupon[]; +} + +export interface CouponFormProps { + onCouponAdd: (newCoupon: Coupon) => void; +} + + +