diff --git a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx
index f2538cff..ba88fc7b 100644
--- a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx
+++ b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
import RoundTabButton from "./components/RoundTabButton";
import refreshIcon from "../../assets/refresh.svg";
import { WaitingCard } from "./components/WaitingCard";
@@ -104,74 +104,83 @@ const AdminHome = () => {
}, [reservations, activeTab]);
// 호출 버튼 클릭 이벤트
- const handleCall = (id: number, reservationNumber: number) => {
- updateStatus(
- { storeId, reservationNumber, status: "CALLING" },
- {
- onSuccess: () => {
- setReservations((prev) =>
- prev.map((res) =>
- res.id === id
- ? (() => {
- // 입장/취소 기록은 흐름상 초기화하는게 보통 자연스러움
- return {
- ...res,
- status: "CALLING",
- calledAt: new Date().toISOString(),
- confirmedAt: undefined,
- cancelledAt: undefined,
- };
- })()
- : res
- )
- );
- },
- }
- );
- };
+ const handleCall = useCallback(
+ (id: number, reservationNumber: number) => {
+ updateStatus(
+ { storeId, reservationNumber, status: "CALLING" },
+ {
+ onSuccess: () => {
+ setReservations((prev) =>
+ prev.map((res) =>
+ res.id === id
+ ? (() => {
+ // 입장/취소 기록은 흐름상 초기화하는게 보통 자연스러움
+ return {
+ ...res,
+ status: "CALLING",
+ calledAt: new Date().toISOString(),
+ confirmedAt: undefined,
+ cancelledAt: undefined,
+ };
+ })()
+ : res
+ )
+ );
+ },
+ }
+ );
+ },
+ [storeId, updateStatus]
+ );
- const handleEnter = (id: number, reservationNumber: number) => {
- const now = new Date().toISOString();
- updateStatus(
- { storeId, reservationNumber, status: "CONFIRMED" },
- {
- onSuccess: () => {
- setReservations((prev) =>
- prev.map((res) => {
- if (res.id !== id) return res;
- return { ...res, status: "CONFIRMED", confirmedAt: now };
- })
- );
- },
- }
- );
- };
+ const handleEnter = useCallback(
+ (id: number, reservationNumber: number) => {
+ const now = new Date().toISOString();
+ updateStatus(
+ { storeId, reservationNumber, status: "CONFIRMED" },
+ {
+ onSuccess: () => {
+ setReservations((prev) =>
+ prev.map((res) => {
+ if (res.id !== id) return res;
+ return { ...res, status: "CONFIRMED", confirmedAt: now };
+ })
+ );
+ },
+ }
+ );
+ },
+ [storeId, updateStatus]
+ );
- const handleClose = (id: number, reservationNumber: number) => {
- const now = new Date().toISOString();
- updateStatus(
- { storeId, reservationNumber, status: "CANCELLED" },
- {
- onSuccess: () => {
- setReservations((prev) =>
- prev.map((res) => {
- if (res.id !== id) return res;
- return { ...res, status: "CANCELLED", cancelledAt: now };
- })
- );
- },
- }
- );
- };
+ const handleClose = useCallback(
+ (id: number, reservationNumber: number) => {
+ const now = new Date().toISOString();
+ updateStatus(
+ { storeId, reservationNumber, status: "CANCELLED" },
+ {
+ onSuccess: () => {
+ setReservations((prev) =>
+ prev.map((res) => {
+ if (res.id !== id) return res;
+ return { ...res, status: "CANCELLED", cancelledAt: now };
+ })
+ );
+ },
+ }
+ );
+ },
+ [storeId, updateStatus]
+ );
- const handleNoShow = (id: number) => {
+ const handleNoShow = useCallback((id: number) => {
setNoShowIds((prev) => {
if (!prev.includes(id)) return [...prev, id];
return prev;
});
- };
+ }, []);
- const handleRefresh = async () => {
+ const handleRefresh = useCallback(async () => {
if (isRefreshing) return; // 중복 클릭 방지
setIsRefreshing(true);
try {
@@ -180,7 +189,7 @@ const AdminHome = () => {
// 살짝 딜레이를 주면 회전이 끊기지 않고 보여짐 (선택)
setTimeout(() => setIsRefreshing(false), 300);
}
- };
+ }, [isRefreshing, refetchWaiting, refetchCompleted]);
useEffect(() => {
if (!Array.isArray(waitingList) || !Array.isArray(completedList)) return;
diff --git a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx
index ac1ebab4..2b587a59 100644
--- a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx
+++ b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx
@@ -2,7 +2,7 @@ import CloseButton from "../../../components/closeButton";
import callIcon from "../../../assets/Call.svg";
import openDoorIcon from "../../../assets/door_open.svg";
import alarmIcon from "../../../assets/alarm.svg";
-import { useEffect, useState } from "react";
+import { memo, useEffect, useState } from "react";
const totalDurationSec = 10; // 10초, 10분은 600
@@ -37,7 +37,24 @@ const diffMinutes = (start?: string, end?: string) => {
);
};
-export function WaitingCard({
+const areEqual = (prev: WaitingCardProps, next: WaitingCardProps) => {
+ return (
+ prev.number === next.number &&
+ prev.time === next.time &&
+ prev.waitMinutes === next.waitMinutes &&
+ prev.peopleCount === next.peopleCount &&
+ prev.name === next.name &&
+ prev.phoneNumber === next.phoneNumber &&
+ prev.status === next.status &&
+ prev.requestedAt === next.requestedAt &&
+ prev.calledAt === next.calledAt &&
+ prev.confirmedAt === next.confirmedAt &&
+ prev.cancelledAt === next.cancelledAt &&
+ prev.isNoShow === next.isNoShow
+ );
+};
+
+export const WaitingCard = memo(function WaitingCard({
number,
time,
waitMinutes,
@@ -222,4 +239,4 @@ export function WaitingCard({
);
-}
+}, areEqual);
diff --git a/apps/nowait-user/src/hooks/order/useStoreMenus.ts b/apps/nowait-user/src/hooks/order/useStoreMenus.ts
new file mode 100644
index 00000000..c5edf52d
--- /dev/null
+++ b/apps/nowait-user/src/hooks/order/useStoreMenus.ts
@@ -0,0 +1,23 @@
+import { useQuery } from "@tanstack/react-query";
+import { getStoreMenus } from "../../api/menu";
+
+export const storeMenusQuery = (storeId?: string) => ({
+ queryKey: ["storeMenus", storeId],
+ queryFn: () => getStoreMenus(storeId!),
+});
+
+export const useStoreMenus = (storeId?: string) => {
+ return useQuery({
+ ...storeMenusQuery(storeId!),
+ enabled: !!storeId,
+ select: (data) => data?.response,
+ });
+};
+
+export const useStoreMenuList = (storeId?: string) => {
+ return useQuery({
+ ...storeMenusQuery(storeId!),
+ enabled: !!storeId,
+ select: (data) => data?.response?.menuReadDto,
+ });
+};
diff --git a/apps/nowait-user/src/pages/order/addMenu/AddMenuPage.tsx b/apps/nowait-user/src/pages/order/addMenu/AddMenuPage.tsx
index 3d278cfe..5c47cc0f 100644
--- a/apps/nowait-user/src/pages/order/addMenu/AddMenuPage.tsx
+++ b/apps/nowait-user/src/pages/order/addMenu/AddMenuPage.tsx
@@ -1,82 +1,72 @@
-import { useState } from "react";
-import QuantitySelector from "../../../components/common/QuantitySelector";
+import { useState, useMemo } from "react";
import { useNavigate, useParams } from "react-router-dom";
+import { useQuery } from "@tanstack/react-query";
import PageFooterButton from "../../../components/order/PageFooterButton";
+import FullPageLoader from "../../../components/FullPageLoader";
import { Button } from "@repo/ui";
-import type { CartType } from "../../../types/order/cart";
-import { useCartStore } from "../../../stores/cartStore";
-import NumberFlow from "@number-flow/react";
-import defaultMenuImageLg from "../../../assets/default-image-lg.png";
-import { useQuery } from "@tanstack/react-query";
import { getStoreMenu } from "../../../api/menu";
-import FullPageLoader from "../../../components/FullPageLoader";
+import { useCartStore } from "../../../stores/cartStore";
+import type { CartType } from "../../../types/order/cart";
+import MenuInfoSection from "./components/MenuInfoSection";
+import MenuOrderSection from "./components/MenuOrderSection";
+
const AddMenuPage = () => {
const navigate = useNavigate();
- const { storeId, menuId } = useParams();
+ const { storeId, menuId } = useParams<{ storeId: string; menuId: string }>();
+
+ const [quantity, setQuantity] = useState(1);
+ const { addToCart } = useCartStore();
const { data: menu, isLoading } = useQuery({
- queryKey: ["menu", menuId],
- queryFn: () => getStoreMenu(storeId!, Number(menuId!)),
- select: (data) => data?.response,
+ queryKey: ["menu", storeId, menuId],
+ queryFn: () => getStoreMenu(storeId!, Number(menuId)),
+ select: (data) => data.response,
+ enabled: !!storeId && !!menuId,
});
- const [quantity, setQuantity] = useState(1);
- const { addToCart } = useCartStore();
+ const totalPrice = useMemo(
+ () => (menu ? menu.price * quantity : 0),
+ [menu, quantity]
+ );
+
+ const handleAddToCart = () => {
+ if (!menu) return;
- const addToCartButton = () => {
const item: CartType = {
- menuId: menu!.menuId,
- image: menu!.images[0]?.imageUrl,
- name: menu!.name,
+ menuId: menu.menuId,
+ image: menu.images?.[0]?.imageUrl,
+ name: menu.name,
quantity,
- originPrice: menu!.price,
- price: menu!.price * quantity,
+ originPrice: menu.price,
+ price: totalPrice,
};
+
addToCart(item);
navigate(`/${storeId}`, {
- state: { added: true, addedPrice: menu!.price * quantity, isBack: true },
- replace: false,
+ state: {
+ added: true,
+ addedPrice: totalPrice,
+ isBack: true,
+ },
});
};
- if (isLoading) return ;
+ if (isLoading || !menu) return ;
return (
-
-
-
-
-
-
- {menu!.name}
-
-
- {menu!.description}
-
-
-
- {/* 메뉴 가격 및 수량 컨트롤 */}
-
+
+
+
+
-
@@ -84,4 +74,4 @@ const AddMenuPage = () => {
);
};
-export default AddMenuPage;
+export default AddMenuPage;
\ No newline at end of file
diff --git a/apps/nowait-user/src/pages/order/addMenu/components/MenuInfoSection.tsx b/apps/nowait-user/src/pages/order/addMenu/components/MenuInfoSection.tsx
new file mode 100644
index 00000000..ce183997
--- /dev/null
+++ b/apps/nowait-user/src/pages/order/addMenu/components/MenuInfoSection.tsx
@@ -0,0 +1,36 @@
+import defaultMenuImageLg from "../../../../assets/default-image-lg.png";
+
+interface Props {
+ menu: {
+ name: string;
+ description?: string;
+ images?: { imageUrl: string }[];
+ };
+}
+
+const MenuInfoSection = ({ menu }: Props) => {
+ const image = menu.images?.[0]?.imageUrl ?? defaultMenuImageLg;
+
+ return (
+
+
+

+
+
+
+
{menu.name}
+ {menu.description && (
+
+ {menu.description}
+
+ )}
+
+
+ );
+};
+
+export default MenuInfoSection;
diff --git a/apps/nowait-user/src/pages/order/addMenu/components/MenuOrderSection.tsx b/apps/nowait-user/src/pages/order/addMenu/components/MenuOrderSection.tsx
new file mode 100644
index 00000000..a250b802
--- /dev/null
+++ b/apps/nowait-user/src/pages/order/addMenu/components/MenuOrderSection.tsx
@@ -0,0 +1,32 @@
+import NumberFlow from "@number-flow/react";
+import QuantitySelector from "../../../../components/common/QuantitySelector";
+
+interface Props {
+ price: number;
+ quantity: number;
+ onChangeQuantity: React.Dispatch
>;
+}
+
+const MenuOrderSection = ({
+ price,
+ quantity,
+ onChangeQuantity,
+}: Props) => {
+ return (
+
+ );
+};
+
+export default MenuOrderSection;
diff --git a/apps/nowait-user/src/pages/order/home/StorePage.tsx b/apps/nowait-user/src/pages/order/home/StorePage.tsx
index 843ea730..f9dabe09 100644
--- a/apps/nowait-user/src/pages/order/home/StorePage.tsx
+++ b/apps/nowait-user/src/pages/order/home/StorePage.tsx
@@ -8,8 +8,7 @@ import { useToastStore } from "../../../stores/toastStore";
import StoreHeader from "./components/StoreHeader";
import MenuList from "../../../components/common/MenuList";
import SectionDivider from "../../../components/SectionDivider";
-import { useQuery } from "@tanstack/react-query";
-import { getStoreMenus } from "../../../api/menu";
+import { useStoreMenus } from "../../../hooks/order/useStoreMenus";
const StorePage = () => {
const navigate = useNavigate();
@@ -20,6 +19,7 @@ const StorePage = () => {
?.addedPrice;
const { cart } = useCartStore();
const { showToast } = useToastStore();
+ const { data: menus, isLoading } = useStoreMenus(storeId);
//메뉴 추가 시 toast 띄우기
useEffect(() => {
@@ -29,16 +29,13 @@ const StorePage = () => {
}
}, [added]);
- const { data: menus, isLoading } = useQuery({
- queryKey: ["storeMenus", storeId],
- queryFn: () => getStoreMenus(storeId!),
- select: (data) => data?.response,
- });
-
- console.log(menus, "asd");
return (
-
0 ? "pb-[116px]" : ""} px-5`}>
+
0 ? "pb-[116px]" : ""
+ } px-5`}
+ >
diff --git a/apps/nowait-user/src/pages/order/home/RedirectToStorePage.tsx b/apps/nowait-user/src/pages/order/home/components/RedirectToStorePage.tsx
similarity index 95%
rename from apps/nowait-user/src/pages/order/home/RedirectToStorePage.tsx
rename to apps/nowait-user/src/pages/order/home/components/RedirectToStorePage.tsx
index 329a6e0b..46c9fb69 100644
--- a/apps/nowait-user/src/pages/order/home/RedirectToStorePage.tsx
+++ b/apps/nowait-user/src/pages/order/home/components/RedirectToStorePage.tsx
@@ -1,6 +1,6 @@
import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
-import { useCartStore } from "../../../stores/cartStore";
+import { useCartStore } from "../../../../stores/cartStore";
const RedirectToStorePage = () => {
const { storeId, tableId } = useParams();
diff --git a/apps/nowait-user/src/pages/order/orderDetails/OrderDetailsPage.tsx b/apps/nowait-user/src/pages/order/orderDetails/OrderDetailsPage.tsx
index 35c06f87..1c8856fe 100644
--- a/apps/nowait-user/src/pages/order/orderDetails/OrderDetailsPage.tsx
+++ b/apps/nowait-user/src/pages/order/orderDetails/OrderDetailsPage.tsx
@@ -1,9 +1,9 @@
-import EmptyOrderDetails from "./components/EmptyOrderDetails";
import { useQuery } from "@tanstack/react-query";
-import { getOrderDetails } from "../../../api/order";
import { useParams } from "react-router-dom";
import BackOnlyHeader from "../../../components/BackOnlyHeader";
import FullPageLoader from "../../../components/FullPageLoader";
+import EmptyOrderDetails from "./components/EmptyOrderDetails";
+import { getOrderDetails } from "../../../api/order";
interface OrderDetailsType {
menuId: number;
@@ -17,7 +17,8 @@ const statusMap = {
WAITING_FOR_PAYMENT: { label: "입금 대기 중", color: "text-black-90" },
COOKING: { label: "조리 중", color: "text-black-90" },
COOKED: { label: "조리 완료", color: "text-black-60" },
-};
+} as const;
+
type OrderStatus = keyof typeof statusMap;
const OrderDetailsPage = () => {
@@ -29,8 +30,9 @@ const OrderDetailsPage = () => {
queryFn: () => getOrderDetails(storeId!, Number(tableId!)),
select: (data) => data?.response,
});
- console.log(orderDetails);
+
if (isLoading) return
;
+
//주문내역 없을 시
if (!orderDetails || orderDetails?.length < 1) return
;
diff --git a/apps/nowait-user/src/pages/order/orderList/OrderListPage.tsx b/apps/nowait-user/src/pages/order/orderList/OrderListPage.tsx
index 420067c8..dfdcc3f7 100644
--- a/apps/nowait-user/src/pages/order/orderList/OrderListPage.tsx
+++ b/apps/nowait-user/src/pages/order/orderList/OrderListPage.tsx
@@ -1,59 +1,21 @@
-import CartItem from "./components/CartItem";
-import PageFooterButton from "../../../components/order/PageFooterButton";
-import { Button } from "@repo/ui";
-import { useNavigate, useParams } from "react-router-dom";
-import TotalButton from "../../../components/order/TotalButton";
-import { useCartStore } from "../../../stores/cartStore";
-import { AnimatePresence } from "framer-motion";
+import SoldOutModal from "./components/SoldOutModal";
+import { useOrderListLogic } from "./hooks/useOrderListLogic";
+import CartList from "./components/CartList";
+import OrderFooter from "./components/OrderFooter";
import EmptyCart from "./components/EmptyCart";
-import { SmallActionButton } from "../../../components/SmallActionButton";
-import Add from "../../../assets/icon/Add.svg?react";
import BackHeader from "../../../components/BackHeader";
-import { useEffect, useState } from "react";
-import { useQuery } from "@tanstack/react-query";
-import { getStoreMenus } from "../../../api/menu";
-import { getSoldOutMenusInCart } from "../../../utils/checkSoldOutMenus";
-import useModal from "../../../hooks/useModal";
-import Portal from "../../../components/common/modal/Portal";
-import type { CartType } from "../../../types/order/cart";
-import { motion } from "framer-motion";
const OrderListPage = () => {
- const navigate = useNavigate();
- const { storeId } = useParams();
- const { cart, removeFromCart } = useCartStore();
- const modal = useModal();
- const [soldOutMenus, setSoldOutMenus] = useState
();
- const [isAnimatingOut, setIsAnimatingOut] = useState(false);
-
- const { data: menus } = useQuery({
- queryKey: ["storeMenuList", storeId],
- queryFn: () => getStoreMenus(storeId!),
- select: (data) => data?.response?.menuReadDto,
- });
-
- // 맨위로 위치 초기화
- useEffect(() => {
- window.scrollTo(0, 0);
- }, []);
-
- useEffect(() => {
- let timerId: number | undefined;
-
- if (cart.length === 0) {
- if (!isAnimatingOut) {
- timerId = window.setTimeout(() => {
- setIsAnimatingOut(true);
- }, 350);
- }
- } else {
- if (isAnimatingOut) setIsAnimatingOut(false);
- }
-
- return () => {
- if (timerId) clearTimeout(timerId);
- };
- }, [cart, isAnimatingOut]);
+
+ const {
+ cart,
+ storeId,
+ isAnimatingOut,
+ modal,
+ soldOutMenus,
+ handleOrder,
+ handleRemoveSoldOutMenus,
+ } = useOrderListLogic();
if (cart.length === 0 && isAnimatingOut) {
return ;
@@ -66,97 +28,14 @@ const OrderListPage = () => {
주문 총 {cart.length}건
-
-
- {cart.map((item) => {
- return (
-
- );
- })}
-
-
- navigate(`/${storeId}`, { state: { isBack: true } })
- }
- className="py-5 border-none"
- >
- 메뉴 추가하기
-
-
-
-
-
-
-
+
-
- {
- if (!menus) return;
- // 장바구니와 최신 메뉴 데이터 동기화
- const soldOut = getSoldOutMenusInCart(cart, menus);
- if (soldOut.length > 0) {
- setSoldOutMenus(soldOut);
- modal.open();
- } else {
- navigate(`/${storeId}/remittance`);
- }
- }}
- >
-
-
-
- {modal.isOpen && soldOutMenus!.length > 0 && (
-
- {
- modal.close();
- soldOutMenus?.forEach((menu: CartType) =>
- removeFromCart(menu.menuId)
- );
- }}
- >
-
e.stopPropagation()}
- className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 min-w-[calc(100%-35px)] max-w-[430px] bg-white rounded-[20px] px-[22px] pt-[30px] pb-[22px]"
- >
-
- 현재{" "}
- {soldOutMenus?.map((menu: CartType, idx: number) => (
-
- {menu.name}
- {idx < soldOutMenus.length - 1 && ", "}
-
- ))}
- 는(은) 품절 상태입니다.
-
- {
- modal.close();
- soldOutMenus?.forEach((menu: CartType) =>
- removeFromCart(menu.menuId)
- );
- }}
- >
- 주문 계속하기
-
-
-
-
+
+ {modal.isOpen && soldOutMenus && soldOutMenus!.length > 0 && (
+
)}
);
diff --git a/apps/nowait-user/src/pages/order/orderList/components/CartItem.tsx b/apps/nowait-user/src/pages/order/orderList/components/CartItem.tsx
index 702aeeff..ea29aca1 100644
--- a/apps/nowait-user/src/pages/order/orderList/components/CartItem.tsx
+++ b/apps/nowait-user/src/pages/order/orderList/components/CartItem.tsx
@@ -4,14 +4,20 @@ import { useCartStore } from "../../../../stores/cartStore";
import { motion } from "framer-motion";
interface PropsType {
- id: number;
+ menuId: number;
name: string;
originPrice: number;
price: number;
quantity: number;
}
-const CartItem = ({ id, name, originPrice, price, quantity }: PropsType) => {
+const CartItem = ({
+ menuId,
+ name,
+ originPrice,
+ price,
+ quantity,
+}: PropsType) => {
const { removeFromCart, increaseQuantity, decreaseQuantity } = useCartStore();
return (
{
{originPrice.toLocaleString()}원
-
removeFromCart(id)} className="p-1">
+ removeFromCart(menuId)} className="p-1">
{
+ const navigate = useNavigate();
+
+ return (
+
+
+ {cart.map((item) => (
+
+ ))}
+
+ navigate(`/${storeId}`, { state: { isBack: true } })}
+ className="py-5 border-none"
+ >
+ 메뉴 추가하기
+
+
+
+
+
+
+
+ );
+};
+
+export default CartList;
diff --git a/apps/nowait-user/src/pages/order/orderList/components/OrderFooter.tsx b/apps/nowait-user/src/pages/order/orderList/components/OrderFooter.tsx
new file mode 100644
index 00000000..5b1fbdf0
--- /dev/null
+++ b/apps/nowait-user/src/pages/order/orderList/components/OrderFooter.tsx
@@ -0,0 +1,19 @@
+import { Button } from "@repo/ui";
+import PageFooterButton from "../../../../components/order/PageFooterButton";
+import TotalButton from "../../../../components/order/TotalButton";
+
+interface Props {
+ onOrder: () => void;
+}
+
+const OrderFooter = ({ onOrder }: Props) => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default OrderFooter;
diff --git a/apps/nowait-user/src/pages/order/orderList/components/SoldOutModal.tsx b/apps/nowait-user/src/pages/order/orderList/components/SoldOutModal.tsx
new file mode 100644
index 00000000..e96f1421
--- /dev/null
+++ b/apps/nowait-user/src/pages/order/orderList/components/SoldOutModal.tsx
@@ -0,0 +1,48 @@
+import { Button } from "@repo/ui";
+import Portal from "../../../../components/common/modal/Portal";
+import type { CartType } from "../../../../types/order/cart";
+
+interface Props {
+ menus: CartType[];
+ onConfirm: () => void;
+}
+
+const SoldOutModal = ({ menus, onConfirm }: Props) => {
+ return (
+
+ {
+ onConfirm();
+ }}
+ >
+
e.stopPropagation()}
+ className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 min-w-[calc(100%-35px)] max-w-[430px] bg-white rounded-[20px] px-[22px] pt-[30px] pb-[22px]"
+ >
+
+ 현재{" "}
+ {menus?.map((menu: CartType, idx: number) => (
+
+ {menu.name}
+ {idx < menus.length - 1 && ", "}
+
+ ))}
+ 는(은) 품절 상태입니다.
+
+ {
+ onConfirm();
+ }}
+ >
+ 주문 계속하기
+
+
+
+
+ );
+};
+
+export default SoldOutModal;
diff --git a/apps/nowait-user/src/pages/order/orderList/hooks/useOrderListLogic.ts b/apps/nowait-user/src/pages/order/orderList/hooks/useOrderListLogic.ts
new file mode 100644
index 00000000..dc3898c9
--- /dev/null
+++ b/apps/nowait-user/src/pages/order/orderList/hooks/useOrderListLogic.ts
@@ -0,0 +1,70 @@
+import { useEffect, useState } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useCartStore } from "../../../../stores/cartStore";
+import useModal from "../../../../hooks/useModal";
+import { useStoreMenuList } from "../../../../hooks/order/useStoreMenus";
+import type { CartType } from "../../../../types/order/cart";
+import { getSoldOutMenusInCart } from "../../../../utils/checkSoldOutMenus";
+
+export const useOrderListLogic = () => {
+ const navigate = useNavigate();
+ const { storeId } = useParams();
+ const { cart, removeFromCart } = useCartStore();
+ const modal = useModal();
+
+ const { data: menus } = useStoreMenuList(storeId);
+
+ const [soldOutMenus, setSoldOutMenus] = useState();
+ const [isAnimatingOut, setIsAnimatingOut] = useState(false);
+
+ // 스크롤 초기화
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, []);
+
+ // EmptyCart 애니메이션 타이밍 제어
+ useEffect(() => {
+ let timer: number | undefined;
+
+ if (cart.length === 0 && !isAnimatingOut) {
+ timer = window.setTimeout(() => setIsAnimatingOut(true), 350);
+ }
+
+ if (cart.length > 0 && isAnimatingOut) {
+ setIsAnimatingOut(false);
+ }
+
+ return () => {
+ if (timer) clearTimeout(timer);
+ };
+ }, [cart, isAnimatingOut]);
+
+ const handleOrder = () => {
+ if (!menus) return;
+
+ const soldOut = getSoldOutMenusInCart(cart, menus);
+
+ if (soldOut.length > 0) {
+ setSoldOutMenus(soldOut);
+ modal.open();
+ return;
+ }
+
+ navigate(`/${storeId}/remittance`);
+ };
+
+ const handleRemoveSoldOutMenus = () => {
+ soldOutMenus?.forEach((menu) => removeFromCart(menu.menuId));
+ modal.close();
+ };
+
+ return {
+ cart,
+ storeId,
+ isAnimatingOut,
+ modal,
+ soldOutMenus,
+ handleOrder,
+ handleRemoveSoldOutMenus,
+ };
+};
diff --git a/apps/nowait-user/src/pages/order/remittenceWait/RemittanceWaitPage.tsx b/apps/nowait-user/src/pages/order/remittenceWait/RemittanceWaitPage.tsx
index 4892e397..0c46e357 100644
--- a/apps/nowait-user/src/pages/order/remittenceWait/RemittanceWaitPage.tsx
+++ b/apps/nowait-user/src/pages/order/remittenceWait/RemittanceWaitPage.tsx
@@ -14,44 +14,48 @@ import { useFallbackImage } from "../../../hooks/useFallbackImage";
const RemittanceWaitPage = () => {
const navigate = useNavigate();
const location = useLocation();
- const payer = location.state as string;
const { storeId } = useParams();
+
const tableId = localStorage.getItem("tableId");
+ const payer = location.state as string;
+
const { cart, clearCart } = useCartStore();
const { showToast } = useToastStore();
- const totalPrice = sumTotalPrice(cart);
+
const [isLoading, setIsLoading] = useState(false); // 중복 요청 방지
const { isLoaded, loadedSrc } = useFallbackImage(remittanceWait);
+ const totalPrice = sumTotalPrice(cart);
+
+ const payload = {
+ depositorName: payer,
+ items: cart.map((item) => ({
+ menuId: item.menuId,
+ quantity: item.quantity,
+ })),
+ totalPrice,
+ };
+
const orderButton = async () => {
+ if (isLoading) return;
try {
setIsLoading(true);
- const payload = {
- depositorName: payer,
- items: cart.map((item) => ({
- menuId: item.menuId,
- quantity: item.quantity,
- })),
- totalPrice,
- };
const res = await createOrder(storeId!, Number(tableId!), payload);
- console.log(res, "주문 생성");
- if (res?.success) {
- //입금자명 로컬스토리지 저장
- localStorage.setItem("depositorName", res.response.depositorName);
- //장바구니 비우기
- clearCart();
- navigate(`/${storeId}/order/success`, { replace: true });
- } else {
- // 서버가 success:false 반환한 경우
- console.error("주문 실패:", res);
+ if (!res?.success) {
showToast("주문에 실패했습니다. 다시 시도해 주세요");
return;
}
+
+ //입금자명 로컬스토리지 저장
+ localStorage.setItem("depositorName", res.response.depositorName);
+ //장바구니 비우기
+ clearCart();
+ navigate(`/${storeId}/order/success`, { replace: true });
} catch (error) {
console.log(error);
+ showToast("주문 처리 중 오류가 발생했습니다");
} finally {
- setIsLoading(true);
+ setIsLoading(false);
}
};
@@ -66,7 +70,6 @@ const RemittanceWaitPage = () => {
>
{
const navigate = useNavigate();
const { id: storeId } = useParams();
+ const { data: menus, isLoading: menusIsLoading } = useStoreMenus(storeId);
const {
data: store,
- isLoading,
+ isLoading: storeIsLoading,
isError,
} = useQuery({
queryKey: ["store", storeId],
@@ -33,12 +34,6 @@ const StoreDetailPage = () => {
select: (data) => data?.response,
});
- const { data: menus, isLoading: menusIsLoading } = useQuery({
- queryKey: ["storeMenus", storeId],
- queryFn: () => getStoreMenus(storeId!),
- select: (data) => data?.response,
- });
-
//맨위로 위치 초기화
useEffect(() => {
window.scrollTo(0, 0);
@@ -61,7 +56,7 @@ const StoreDetailPage = () => {
console.log(error);
}
};
- if (isLoading) return ;
+ if (storeIsLoading) return ;
if (isError) return ;
return (
diff --git a/apps/nowait-user/src/routes/Router.tsx b/apps/nowait-user/src/routes/Router.tsx
index e384e4ab..adb14e28 100644
--- a/apps/nowait-user/src/routes/Router.tsx
+++ b/apps/nowait-user/src/routes/Router.tsx
@@ -3,7 +3,7 @@ import HomePage from "../pages/home/HomePage";
import WaitingSuccessPage from "../pages/waiting/waitingSuccess/WaitingSuccessPage";
import MapPage from "../pages/waiting/boothMap/MapPage";
import StoreDetailPage from "../pages/waiting/storeDetail/StoreDetailPage";
-import RedirectToStorePage from "../pages/order/home/RedirectToStorePage";
+import RedirectToStorePage from "../pages/order/home/components/RedirectToStorePage";
import StorePage from "../pages/order/home/StorePage";
import OrderListPage from "../pages/order/orderList/OrderListPage";
import OrderSuccessPage from "../pages/order/orderSuccess/OrderSuccessPage";