diff --git a/public/svg/ic_copy.svg b/public/svg/ic_copy.svg new file mode 100644 index 0000000..9778629 --- /dev/null +++ b/public/svg/ic_copy.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/svg/IcCopy.tsx b/src/assets/svg/IcCopy.tsx new file mode 100644 index 0000000..c31d847 --- /dev/null +++ b/src/assets/svg/IcCopy.tsx @@ -0,0 +1,30 @@ +import type { SVGProps } from "react"; +const SvgIcCopy = (props: SVGProps) => ( + + + + + + + + +); +export default SvgIcCopy; diff --git a/src/assets/svg/index.ts b/src/assets/svg/index.ts index 5fd0b10..32d5911 100644 --- a/src/assets/svg/index.ts +++ b/src/assets/svg/index.ts @@ -6,6 +6,7 @@ export { default as IcCheckedTrue } from "./IcCheckedTrue"; export { default as IcClipboardCopy } from "./IcClipboardCopy"; export { default as IcClose } from "./IcClose"; export { default as IcComplete } from "./IcComplete"; +export { default as IcCopy } from "./IcCopy"; export { default as IcDelete } from "./IcDelete"; export { default as IcDownload } from "./IcDownload"; export { default as IcFix } from "./IcFix"; diff --git a/src/pages/Admin/components/OrderTable/OrderTable.style.ts b/src/pages/Admin/components/OrderTable/OrderTable.style.ts index 964915c..a02396d 100644 --- a/src/pages/Admin/components/OrderTable/OrderTable.style.ts +++ b/src/pages/Admin/components/OrderTable/OrderTable.style.ts @@ -74,3 +74,43 @@ export const checkboxStyle = css` width: 1.6rem; height: 1.6rem; `; + +export const numberText = css` + ${flexGenerator()}; +`; + +export const copyIconStyle = (theme: Theme) => css` + display: inline-block; + width: 2.4rem; + height: 2.4rem; + padding: 0.2rem; + cursor: pointer; + border-radius: 0.4rem; + + &:hover { + background-color: ${theme.color.lightgray3}; + } +`; + +export const confrimModal = css` + ${flexGenerator("column")}; + padding: 2rem; + gap: 2rem; + width: 35rem; + + & hr { + width: 100%; + } +`; + +export const modalTitle = (theme: Theme) => css` + ${theme.font["head02-b-20"]}; +`; + +export const productText = (theme: Theme) => css` + ${theme.font["head06-b-16"]}; +`; + +export const modalNotice = (theme: Theme) => css` + ${theme.font["head06-b-16"]}; +`; diff --git a/src/pages/Admin/components/OrderTable/OrderTable.tsx b/src/pages/Admin/components/OrderTable/OrderTable.tsx index 7fd8431..5851138 100644 --- a/src/pages/Admin/components/OrderTable/OrderTable.tsx +++ b/src/pages/Admin/components/OrderTable/OrderTable.tsx @@ -2,7 +2,13 @@ import { useState } from "react"; import { buttonContainer, checkboxStyle, + confrimModal, + copyIconStyle, iconStyle, + modalNotice, + modalTitle, + numberText, + productText, sectionStyle, sectionTitle, tableHeader, @@ -11,9 +17,10 @@ import { } from "./OrderTable.style"; import { Order } from "@types"; import { usePatchDeliveryShipped } from "@apis/domains/admin/usePatchDeliveryShipped"; -import { Button } from "@components"; -import { IcCheckedTrue, IcDownload } from "@svg"; +import { Button, Modal, Toast } from "@components"; +import { IcCheckedTrue, IcCopy, IcDownload } from "@svg"; import * as XLSX from "xlsx"; +import useToast from "src/hooks/useToast"; interface OrderTableProps { orders: Order[]; @@ -22,12 +29,51 @@ interface OrderTableProps { const OrderTable = ({ orders }: OrderTableProps) => { const [selectedOrders, setSelectedOrders] = useState([]); + const [isModalOpen, setIsModalOpen] = useState(false); + + const { showToast, isToastVisible } = useToast(); + const [toastMessage, setToastMessage] = useState(""); + + const [productCount, setProductCount] = useState>({}); + const { mutate } = usePatchDeliveryShipped(); const handleShippedClick = () => { mutate(selectedOrders); }; + const handleModalClose = () => { + setIsModalOpen(false); + }; + + const handleExcelClick = () => { + if (selectedOrders.length === 0) { + setToastMessage("주문을 선택해주세요."); + showToast(); + return; + } + const selectedData = orders.filter((order) => + selectedOrders.includes(order.deliveryId) + ); + + const count = selectedData + .map((order) => order.productList) + .flat() + .reduce((acc, product) => { + const match = product.match(/(.+)\s(\d+)EA$/); + if (match) { + const productName = match[1].trim(); + const quantity = parseInt(match[2], 10); + + acc[productName] = (acc[productName] || 0) + quantity; + } + return acc; + }, {} as Record); + + setIsModalOpen(true); + setProductCount(count); + }; + const exportToExcel = () => { const selectedData = orders.filter((order) => selectedOrders.includes(order.deliveryId) @@ -75,6 +121,8 @@ const OrderTable = ({ orders }: OrderTableProps) => { link.href = url; link.download = "export.xlsx"; link.click(); + + handleModalClose(); }; const handleCheckboxChange = (id: number) => { @@ -98,6 +146,37 @@ const OrderTable = ({ orders }: OrderTableProps) => { } }; + const handleCopyClick = async (order: Order) => { + const orderInfoData = `[접수날짜] +${order.orderReceivedDate} + +[보내는 분] +${order.senderName} +${order.senderPhone} + +[받는 분] +${order.recipientName} +${order.recipientPhone} +${order.recipientAddress} ${order.recipientAddressDetail} + +[상품] +${order.productList.join(", ")}`; + + if (navigator.clipboard) { + try { + await navigator.clipboard.writeText(orderInfoData); + setToastMessage("주문내역이 복사되었어요."); + showToast(); + } catch { + setToastMessage("클립보드 복사 실패"); + showToast(); + } + } else { + setToastMessage("클립보드에 복사할 수 없습니다."); + showToast(); + } + }; + const isAllSelected = selectedOrders.length > 0 && selectedOrders.length === @@ -111,7 +190,7 @@ const OrderTable = ({ orders }: OrderTableProps) => { 선택 발송완료 - @@ -158,7 +237,17 @@ const OrderTable = ({ orders }: OrderTableProps) => { /> {order.orderReceivedDate} - {order.orderNumber} + +
+ {order.orderNumber} + handleCopyClick(order)} + > + + +
+ {order.productList.map((product) => { return
{product}
; @@ -176,6 +265,30 @@ const OrderTable = ({ orders }: OrderTableProps) => { + {isModalOpen && ( + +
+

{`총 ${selectedOrders.length}개의 주문을 선택했습니다.`}

+
+ {Object.entries(productCount).map(([productName, count]) => ( +
+ {`${productName}: `} + {`${count} 개`} +
+ ))} +
+

이대로 엑셀을 다운로드 하시겠습니까?

+ +
+
+ )} + + {toastMessage} + ); }; diff --git a/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.tsx b/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.tsx index d869760..04ad392 100644 --- a/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.tsx +++ b/src/pages/orderCheck/components/OrderInfoSection/OrderInfoSection.tsx @@ -63,7 +63,7 @@ const OrderInfoSection = () => { {orderInfo?.senderName}
- {`상품 (${orderCount}건의 주문)`} + {`상품 (총 ${orderCount}개)`} {(mergedOrders || []).map((order, i) => (