From 83503f482bbd9b19418fd902b8e563caf2fdbd2c Mon Sep 17 00:00:00 2001 From: oriNuguri25 Date: Wed, 6 Aug 2025 22:56:28 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20admin=20order=EB=B6=80=EB=B6=84=20api?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20ui=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/AdminOrders/AdminOrders.tsx | 69 +++++-- .../src/pages/AdminOrders/CookedPage.tsx | 54 +++++- .../src/pages/AdminOrders/DetailCard.tsx | 173 +++++++++++++++++ .../src/pages/AdminOrders/OrderCard.tsx | 180 +++--------------- apps/nowait-admin/src/types/order.ts | 11 +- 5 files changed, 313 insertions(+), 174 deletions(-) create mode 100644 apps/nowait-admin/src/pages/AdminOrders/DetailCard.tsx diff --git a/apps/nowait-admin/src/pages/AdminOrders/AdminOrders.tsx b/apps/nowait-admin/src/pages/AdminOrders/AdminOrders.tsx index 9fdb7c20..d9de2741 100644 --- a/apps/nowait-admin/src/pages/AdminOrders/AdminOrders.tsx +++ b/apps/nowait-admin/src/pages/AdminOrders/AdminOrders.tsx @@ -1,5 +1,6 @@ -import { useState, useRef } from "react"; -import { PaymentCard, PaymentDetail, CookCard, CookedCard } from "./OrderCard"; +import { useState, useRef, useEffect } from "react"; +import { PaymentCard, CookCard, CookedCard } from "./OrderCard"; +import { DetailCard } from "./DetailCard"; import RefreshIcon from "../../assets/refresh.svg?react"; import CookedPage from "./CookedPage"; import { useGetOrderList } from "../../hooks/useGetOrderList"; @@ -62,6 +63,22 @@ const AdminOrders = () => { }, 0); }; + // CookedCard 클릭 핸들러 + const handleCookedCardClick = (cooked: Order) => { + if (scrollContainerRef.current) { + // 현재 스크롤 위치 저장 + setSavedScrollPosition(scrollContainerRef.current.scrollTop); + // 스크롤을 맨 위로 올리기 + scrollContainerRef.current.scrollTop = 0; + } + setSelectedPayment(cooked); + }; + + // 탭 변경 시 Detail 닫기 + useEffect(() => { + setSelectedPayment(null); + }, [activeTab, mobileActiveTab]); + return (
{
)} - {/* PaymentDetail 오버레이 */} + {/* DetailCard 오버레이 */} {selectedPayment && ( - @@ -251,7 +267,7 @@ const AdminOrders = () => { key={cooking.id} orderId={cooking.id} tableNumber={cooking.tableId} - menuNamesAndQuantities={cooking.menuNamesAndQuantities} + menuDetails={cooking.menuDetails} onSuccess={refetch} /> )) @@ -326,17 +342,16 @@ const AdminOrders = () => { )} - {/* PaymentDetail 오버레이 */} + {/* DetailCard 오버레이 */} {selectedPayment && ( - @@ -367,7 +382,7 @@ const AdminOrders = () => { key={cooking.id} orderId={cooking.id} tableNumber={cooking.tableId} - menuNamesAndQuantities={cooking.menuNamesAndQuantities} + menuDetails={cooking.menuDetails} onSuccess={refetch} /> )) @@ -391,7 +406,13 @@ const AdminOrders = () => {
금액
주문 시간
-
+
{isLoading ? (
@@ -405,10 +426,11 @@ const AdminOrders = () => { orderId={cooked.id} tableNumber={cooked.tableId} depositorName={cooked.depositorName} - menuNamesAndQuantities={cooked.menuNamesAndQuantities} + menuDetails={cooked.menuDetails} totalAmount={cooked.totalPrice || 0} createdAt={getFormattedTime(cooked.createdAt)} onSuccess={refetch} + onClick={() => handleCookedCardClick(cooked)} /> )) ) : ( @@ -420,6 +442,21 @@ const AdminOrders = () => {
)} + + {/* DetailCard 오버레이 */} + {selectedPayment && mobileActiveTab === "조리 완료" && ( + + )}
)} diff --git a/apps/nowait-admin/src/pages/AdminOrders/CookedPage.tsx b/apps/nowait-admin/src/pages/AdminOrders/CookedPage.tsx index 0d59f5b1..85839841 100644 --- a/apps/nowait-admin/src/pages/AdminOrders/CookedPage.tsx +++ b/apps/nowait-admin/src/pages/AdminOrders/CookedPage.tsx @@ -1,5 +1,7 @@ import { CookedCard } from "./OrderCard"; +import { DetailCard } from "./DetailCard"; import type { Order } from "../../types/order"; +import { useState, useRef } from "react"; interface CookedPageProps { cookedOrders: Order[]; @@ -14,6 +16,9 @@ const CookedPage = ({ error, onRefresh, }: CookedPageProps) => { + const [selectedCooked, setSelectedCooked] = useState(null); + const [savedScrollPosition, setSavedScrollPosition] = useState(0); + const scrollContainerRef = useRef(null); // 시간 포맷팅 함수 (17:30 형식) const getFormattedTime = (createdAt: string) => { const date = new Date(createdAt); @@ -22,8 +27,30 @@ const CookedPage = ({ return `${hours}:${minutes}`; }; + // CookedCard 클릭 핸들러 + const handleCookedCardClick = (cooked: Order) => { + if (scrollContainerRef.current) { + // 현재 스크롤 위치 저장 + setSavedScrollPosition(scrollContainerRef.current.scrollTop); + // 스크롤을 맨 위로 올리기 + scrollContainerRef.current.scrollTop = 0; + } + setSelectedCooked(cooked); + }; + + // CookedDetail 닫기 핸들러 + const handleCloseCookedDetail = () => { + setSelectedCooked(null); + // 약간의 딜레이 후 스크롤 위치 복원 + setTimeout(() => { + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollTop = savedScrollPosition; + } + }, 0); + }; + return ( -
+
{/* 조리 완료 블럭 */}
@@ -55,7 +82,12 @@ const CookedPage = ({
{/* 스크롤 가능한 내용 영역 */} -
+
{isLoading ? (
로딩 중...
@@ -67,10 +99,11 @@ const CookedPage = ({ orderId={order.id} tableNumber={order.tableId} depositorName={order.depositorName} - menuNamesAndQuantities={order.menuNamesAndQuantities} + menuDetails={order.menuDetails} totalAmount={order.totalPrice || 0} createdAt={getFormattedTime(order.createdAt)} onSuccess={onRefresh} + onClick={() => handleCookedCardClick(order)} /> )) ) : ( @@ -82,6 +115,21 @@ const CookedPage = ({
)} + + {/* DetailCard 오버레이 */} + {selectedCooked && ( + + )}
diff --git a/apps/nowait-admin/src/pages/AdminOrders/DetailCard.tsx b/apps/nowait-admin/src/pages/AdminOrders/DetailCard.tsx new file mode 100644 index 00000000..f09059f2 --- /dev/null +++ b/apps/nowait-admin/src/pages/AdminOrders/DetailCard.tsx @@ -0,0 +1,173 @@ +import ArrowRight from "../../assets/arrow_back.svg?react"; +import { useState } from "react"; +import { PaymentCheckModal, CookedModal } from "./OrderPageModal"; +import type { MenuDetails } from "../../types/order"; +import { getTableBackgroundColor } from "../../utils/tableColors"; + +type DetailType = "payment" | "cooked"; + +interface DetailCardProps { + type: DetailType; + orderId: number; + tableNumber: number; + timeText: string; + depositorName: string; + totalAmount: number; + menuDetails?: MenuDetails; + onClose: () => void; + onSuccess?: () => void; +} + +const DetailCard = ({ + type, + orderId, + tableNumber, + timeText, + depositorName, + totalAmount, + menuDetails, + onClose, + onSuccess, +}: DetailCardProps) => { + const [showModal, setShowModal] = useState(false); + + const menuEntries = menuDetails ? Object.entries(menuDetails) : []; + + const handleActionClick = () => { + setShowModal(true); + }; + + const handleCloseModal = () => { + setShowModal(false); + }; + + // 타입에 따른 설정 + const getConfig = () => { + if (type === "payment") { + return { + title: "입금 내역", + depositorLabel: "입금자", + amountLabel: "입금 금액", + actionText: "입금 확인", + modalComponent: PaymentCheckModal, + buttonStyle: + "flex px-2.5 py-1.25 bg-black-20 items-center justify-center rounded-lg w-full h-12 text-14-semibold text-black-70 cursor-pointer", + }; + } else { + return { + title: "주문 내역", + depositorLabel: "주문자", + amountLabel: "주문 금액", + actionText: "주문 복구", + modalComponent: CookedModal, + buttonStyle: + "flex px-2.5 py-1.25 bg-white border border-black-30 items-center justify-center rounded-lg w-full h-12 text-14-semibold text-black-70 cursor-pointer", + }; + } + }; + + const config = getConfig(); + const ModalComponent = config.modalComponent; + + return ( + <> +
+
+ {/* 헤더 */} +
+ +
이전으로
+
+ +
+
+ {tableNumber} +
+
+ {tableNumber}번 테이블 +
+
+ +
+ {config.title} +
+ +
+
+
+ {config.depositorLabel} +
+
+ {depositorName} +
+
+ +
+
+ {config.amountLabel} +
+
+ {totalAmount.toLocaleString()}원 +
+
+ +
+
주문 시간
+
+ {timeText} +
+
+
+ +
+ +
+ 주문 메뉴 {menuEntries.length} +
+ +
+ {menuEntries.map(([menuName, menuInfo], index) => ( +
+
{menuName}
+
+
{menuInfo.quantity}
+
+ {(menuInfo.quantity * menuInfo.price).toLocaleString()}원 +
+
+
+ ))} +
+
+ +
+ {config.actionText} +
+
+ + {/* Modal 오버레이 */} + {showModal && ( +
+ +
+ )} + + ); +}; + +export { DetailCard }; +export type { DetailType }; diff --git a/apps/nowait-admin/src/pages/AdminOrders/OrderCard.tsx b/apps/nowait-admin/src/pages/AdminOrders/OrderCard.tsx index 1acef7a9..a6d5c97e 100644 --- a/apps/nowait-admin/src/pages/AdminOrders/OrderCard.tsx +++ b/apps/nowait-admin/src/pages/AdminOrders/OrderCard.tsx @@ -5,7 +5,7 @@ import { CookedModal, CookCompleteModal, } from "./OrderPageModal"; -import type { MenuNamesAndQuantities } from "../../types/order"; +import type { MenuDetails } from "../../types/order"; import { getTableBackgroundColor } from "../../utils/tableColors"; import { useWindowWidth } from "../../hooks/useWindowWidth"; @@ -98,159 +98,23 @@ const PaymentCard = ({ ); }; -interface PaymentDetailProps { - orderId: number; - tableNumber: number; - timeText: string; - depositorName: string; - totalAmount: number; - menuNamesAndQuantities?: MenuNamesAndQuantities; - onClose: () => void; - onSuccess?: () => void; -} - -const PaymentDetail = ({ - orderId, - tableNumber, - timeText, - depositorName, - totalAmount, - menuNamesAndQuantities, - onClose, - onSuccess, -}: PaymentDetailProps) => { - const [showPaymentCheckModal, setShowPaymentCheckModal] = useState(false); - - const menuEntries = menuNamesAndQuantities - ? Object.entries(menuNamesAndQuantities) - : []; - - const handlePaymentCheckClick = () => { - setShowPaymentCheckModal(true); - }; - - const handleClosePaymentCheckModal = () => { - setShowPaymentCheckModal(false); - }; - - return ( - <> -
-
- {/* 헤더 */} -
- -
이전으로
-
- -
-
- {tableNumber} -
-
- {tableNumber}번 테이블 -
-
- -
- 입금 내역 -
- -
-
-
입금자
-
- {depositorName} -
-
- -
-
입금 금액
-
- {totalAmount.toLocaleString()}원 -
-
- -
-
주문 시간
-
- {timeText} -
-
-
- -
- -
- 주문 메뉴 {menuEntries.length} -
- -
- {menuEntries.map(([menuName, quantity], index) => ( -
-
{menuName}
-
-
{quantity}
-
- {(12000 * quantity).toLocaleString()}원 -
-
-
- ))} -
-
- -
- 입금 확인 -
-
- - {/* PaymentCheckModal 오버레이 */} - {showPaymentCheckModal && ( -
- -
- )} - - ); -}; - interface CookCardProps { orderId: number; tableNumber: number; - menuNamesAndQuantities?: MenuNamesAndQuantities; + menuDetails?: MenuDetails; onSuccess?: () => void; } const CookCard = ({ orderId, tableNumber, - menuNamesAndQuantities, + menuDetails, onSuccess, }: CookCardProps) => { const [showCookCompleteModal, setShowCookCompleteModal] = useState(false); const windowWidth = useWindowWidth(); - const menuEntries = menuNamesAndQuantities - ? Object.entries(menuNamesAndQuantities) - : []; + const menuEntries = menuDetails ? Object.entries(menuDetails) : []; const isOneMenu = menuEntries.length === 1; // 화면 크기에 따른 글자 수 제한 @@ -287,7 +151,7 @@ const CookCard = ({ isOneMenu ? "justify-center" : "" }`} > - {menuEntries.map(([menuName, quantity], index) => ( + {menuEntries.map(([menuName, menuInfo], index) => (
-
{quantity}
+
{menuInfo.quantity}
))}
@@ -333,29 +197,29 @@ interface CookedCardProps { orderId: number; tableNumber: number; depositorName: string; - menuNamesAndQuantities?: MenuNamesAndQuantities; + menuDetails?: MenuDetails; totalAmount: number; createdAt: string; onSuccess?: () => void; + onClick?: () => void; } const CookedCard = ({ orderId, tableNumber, depositorName, - menuNamesAndQuantities, + menuDetails, totalAmount, createdAt, onSuccess, + onClick, }: CookedCardProps) => { const [showCookedModal, setShowCookedModal] = useState(false); const windowWidth = useWindowWidth(); const isMobile = windowWidth <= 450; // 첫 번째 메뉴 이름과 나머지 메뉴 개수 계산 - const menuEntries = menuNamesAndQuantities - ? Object.entries(menuNamesAndQuantities) - : []; + const menuEntries = menuDetails ? Object.entries(menuDetails) : []; const firstMenuName = menuEntries[0]?.[0] || ""; const remainingMenuCount = menuEntries.length - 1; const menuDisplayText = @@ -375,7 +239,10 @@ const CookedCard = ({ <> {/* 모바일 버전 */} {isMobile ? ( -
+
{tableNumber}번
@@ -385,14 +252,20 @@ const CookedCard = ({
{ + e.stopPropagation(); + handleRestoreClick(); + }} > 주문 복구
) : ( /* 데스크톱 버전 */ -
+
{tableNumber}번
@@ -411,7 +284,10 @@ const CookedCard = ({
{ + e.stopPropagation(); + handleRestoreClick(); + }} > 주문 복구
@@ -436,4 +312,4 @@ const CookedCard = ({ ); }; -export { PaymentCard, PaymentDetail, CookCard, CookedCard }; +export { PaymentCard, CookCard, CookedCard }; diff --git a/apps/nowait-admin/src/types/order.ts b/apps/nowait-admin/src/types/order.ts index aec51e2c..19656d22 100644 --- a/apps/nowait-admin/src/types/order.ts +++ b/apps/nowait-admin/src/types/order.ts @@ -1,8 +1,13 @@ // 주문 상태 타입 export type OrderStatus = "WAITING_FOR_PAYMENT" | "COOKING" | "COOKED"; -// 메뉴 수량 타입 -export type MenuNamesAndQuantities = Record; +// 메뉴 상세 정보 타입 +export interface MenuDetail { + quantity: number; + price: number; +} + +export type MenuDetails = Record; // 주문 데이터 타입 export interface Order { @@ -11,6 +16,6 @@ export interface Order { depositorName: string; totalPrice: number | null; status: OrderStatus; - menuNamesAndQuantities: MenuNamesAndQuantities; + menuDetails?: MenuDetails; createdAt: string; }