diff --git a/apps/nowait-admin/src/hooks/Reservation/useUpdateReservationStatus.tsx b/apps/nowait-admin/src/hooks/Reservation/useUpdateReservationStatus.tsx index 742452e0..5dee5c5f 100644 --- a/apps/nowait-admin/src/hooks/Reservation/useUpdateReservationStatus.tsx +++ b/apps/nowait-admin/src/hooks/Reservation/useUpdateReservationStatus.tsx @@ -1,18 +1,21 @@ import { useMutation } from "@tanstack/react-query"; import AdminApi from "../../utils/AdminApi"; +interface UpdateReservationParams { + storeId: number; + userId: number; + status: "WAITING" | "CALLING" | "CONFIRMED" | "CANCELLED" | "NO_SHOW"; +} + export const useUpdateReservationStatus = () => { return useMutation({ mutationFn: async ({ - reservationId, + storeId, + userId, status, - }: { - reservationId: number; - status: "WAITING" | "CALLING" | "CONFIRMED" | "CANCELLED" | "NO_SHOW"; - }) => { + }: UpdateReservationParams) => { const res = await AdminApi.patch( - `/reservations/admin/updates/${reservationId}`, - { status } + `/reservations/admin/update/${storeId}/${userId}/${status}` ); return res.data; }, diff --git a/apps/nowait-admin/src/hooks/analytics/useGetPopularMenu.tsx b/apps/nowait-admin/src/hooks/analytics/useGetPopularMenu.tsx index 1fc73c10..26560943 100644 --- a/apps/nowait-admin/src/hooks/analytics/useGetPopularMenu.tsx +++ b/apps/nowait-admin/src/hooks/analytics/useGetPopularMenu.tsx @@ -6,6 +6,7 @@ interface PopularMenuItem { menuName: string; totalSalesCount: number; boothName: string; + soldCount: number; } interface PopularMenuResponse { diff --git a/apps/nowait-admin/src/hooks/analytics/useGetSalesByDate.tsx b/apps/nowait-admin/src/hooks/analytics/useGetSalesByDate.tsx index 35a5daf4..d5170139 100644 --- a/apps/nowait-admin/src/hooks/analytics/useGetSalesByDate.tsx +++ b/apps/nowait-admin/src/hooks/analytics/useGetSalesByDate.tsx @@ -1,12 +1,20 @@ import { useQuery } from "@tanstack/react-query"; import AdminApi from "../../utils/AdminApi"; +interface SalesData { + storeId: number; + date: string; + todaySalesSum: number; + yesterdaySalesSum: number; + cumulativeSalesBeforeYesterday: number; + ObjectallZero: boolean; +} + interface SalesResponse { success: boolean; - response: number | string; // "해당일 매출 데이터가 없습니다." 또는 숫자형 매출값 + response: SalesData | string; } - -const fetchSalesByDate = async (date: string): Promise => { +const fetchSalesByDate = async (date: string): Promise => { const res = await AdminApi.get(`/admin/statistics/sales`, { params: { date }, }); diff --git a/apps/nowait-admin/src/pages/AdminAnalytics/AdminAnalytics.tsx b/apps/nowait-admin/src/pages/AdminAnalytics/AdminAnalytics.tsx index b76bde79..3a8754ad 100644 --- a/apps/nowait-admin/src/pages/AdminAnalytics/AdminAnalytics.tsx +++ b/apps/nowait-admin/src/pages/AdminAnalytics/AdminAnalytics.tsx @@ -1,3 +1,6 @@ +import { useGetPopularMenu } from "../../hooks/analytics/useGetPopularMenu"; +import { useGetSalesByDate } from "../../hooks/analytics/useGetSalesByDate"; +import { useGetTopSales } from "../../hooks/analytics/useGetTopSalse"; import BoothSalesRankingCard from "./components/BoothSalesRankingCard "; import HeaderStatus from "./components/HeaderStatus"; @@ -55,11 +58,25 @@ const boothData: BoothRanking[] = [ ]; const AdminAnalytics = () => { - // const { data, isLoading, isError } = useGetTopSales(); + const today = new Date(); + const formatted = today.toISOString().slice(0, 10); + const { data: boothRank } = useGetTopSales(); + const { data: sales } = useGetSalesByDate(formatted); + const { data: popularMenu } = useGetPopularMenu(); + console.log(boothRank, "부스별 판매순위"); + console.log(sales, "판매량"); + console.log(popularMenu, "인기 메뉴"); + if (typeof sales === "string") { + return

매출 데이터가 없습니다.

; + } + return ( -
- - +
+ +
); }; diff --git a/apps/nowait-admin/src/pages/AdminAnalytics/components/BoothSalesRankingCard .tsx b/apps/nowait-admin/src/pages/AdminAnalytics/components/BoothSalesRankingCard .tsx index 4f182cd8..e657f5d3 100644 --- a/apps/nowait-admin/src/pages/AdminAnalytics/components/BoothSalesRankingCard .tsx +++ b/apps/nowait-admin/src/pages/AdminAnalytics/components/BoothSalesRankingCard .tsx @@ -20,7 +20,7 @@ const BoothSalesRankingCard: React.FC = ({ data, }) => { return ( -
+

부스별 판매순위

@@ -43,27 +43,30 @@ const BoothSalesRankingCard: React.FC = ({ : "" }`} > -
- +
+ {item.rank} -
-
- {item.name} +
+
+ + {item.name} + {item.department}
-
- +
+
+ {item.salesCount}건 {/* 랭크 변화 표시 */}
{ +interface HeaderStatusProps { + sales?: { + storeId: number; + date: string; + todaySalesSum: number; + yesterdaySalesSum: number; + cumulativeSalesBeforeYesterday: number; + ObjectallZero: boolean; + }; + popularMenu?: { + menuId: number; + menuName: string; + soldCount: number; + }[]; +} + +const HeaderStatus: React.FC = ({ sales, popularMenu }) => { + const todayAmount = sales?.todaySalesSum ?? 0; + const yesterdayAmount = sales?.yesterdaySalesSum ?? 0; + const diffAmount = todayAmount - yesterdayAmount; + const percent = yesterdayAmount + ? parseFloat(((diffAmount / yesterdayAmount) * 100).toFixed(1)) + : 0; + + const todayDate = sales?.date ?? "오늘"; + const yesterdayDate = "어제"; + return ( -
+
- {/* 오늘 매출 */} - {/* 누적 매출 */}
- {/* 인기 메뉴 TOP 5 */} -
+ +

인기 메뉴 TOP 5

- 2025.07.18 금 + {todayDate}
    - {[1, 2, 3, 4, 5].map((rank) => ( -
  • + {(popularMenu ?? []).slice(0, 5).map((menu, i) => ( +
  • - {rank}

    참치마요주먹밥

    + {i + 1}

    {menu.menuName}

    - 100개 + {menu.soldCount}개
  • ))}
@@ -48,5 +71,4 @@ const HeaderStatus = () => {
); }; - export default HeaderStatus; diff --git a/apps/nowait-admin/src/pages/AdminAnalytics/components/SalesCard.tsx b/apps/nowait-admin/src/pages/AdminAnalytics/components/SalesCard.tsx index 83d99038..c3f90b82 100644 --- a/apps/nowait-admin/src/pages/AdminAnalytics/components/SalesCard.tsx +++ b/apps/nowait-admin/src/pages/AdminAnalytics/components/SalesCard.tsx @@ -23,7 +23,7 @@ const SalesCard: React.FC = ({ today, previous }) => { const [isHoverForward, setIsHoverForward] = useState(false); return ( -
+

@@ -35,19 +35,24 @@ const SalesCard: React.FC = ({ today, previous }) => { {/* 이전,다음 이동 버튼 */} + {/* 이전 버튼: 오늘 매출일 때만 활성화 */} setIsHoverBack(true)} - onMouseLeave={() => setIsHoverBack(false)} - onClick={() => setShowToday(false)} + src={ + showToday ? activeBackIcon : backIcon // 비활성 이미지로 고정 + } + className={`h-5 w-5 cursor-pointer`} + onMouseEnter={() => showToday && setIsHoverBack(true)} + onMouseLeave={() => showToday && setIsHoverBack(false)} + onClick={() => showToday && setShowToday(false)} /> + + {/* 다음 버튼: 이전 매출일 때만 활성화 */} setIsHoverForward(true)} - onMouseLeave={() => setIsHoverForward(false)} - onClick={() => setShowToday(true)} + src={!showToday ? activeForwardIcon : forwardIcon} + className={`h-5 w-5 cursor-pointer `} + onMouseEnter={() => !showToday && setIsHoverForward(true)} + onMouseLeave={() => !showToday && setIsHoverForward(false)} + onClick={() => !showToday && setShowToday(true)} />

@@ -59,17 +64,27 @@ const SalesCard: React.FC = ({ today, previous }) => { {showToday && ( <> - + 0 + ? "text-primary" // 상승 + : today.percent < 0 + ? "text-[#3A75E5]" // 하락 + : "text-gray-500" // 변동 없음 + }`} + > {today.percent >= 0 ? "+" : ""} {today.percent}% )}
-

- 어제보다 {today.diffAmount.toLocaleString()}원{" "} - {today.diffAmount >= 0 ? "더 벌었어요!" : "덜 벌었어요!"} -

+ {showToday && ( +

+ 어제보다 {today.diffAmount.toLocaleString()}원{" "} + {today.diffAmount >= 0 ? "더 벌었어요!" : "덜 벌었어요!"} +

+ )}
); diff --git a/apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx b/apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx index fdd11fd0..0584055b 100644 --- a/apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx +++ b/apps/nowait-admin/src/pages/AdminBooth/AdminBooth.tsx @@ -315,7 +315,7 @@ const MenuSection = () => { const BoothForm = () => { const width = useWindowWidth(); const isTablet = width >= 768 && width <= 1024; - const [activeTab, setActiveTab] = useState<"booth" | "menu">("booth"); + const [activeTab, setActiveTab] = useState<"booth" | "menu">("menu"); const [boothName, setBoothName] = useState(""); const [boothIntro, setBoothIntro] = useState(""); const [isFocused, setIsFocused] = useState(false); diff --git a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx index eae8aebd..408be5ca 100644 --- a/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx +++ b/apps/nowait-admin/src/pages/AdminHome/AdminHome.tsx @@ -13,6 +13,7 @@ type WaitingStatus = "WAITING" | "CALLING" | "CONFIRMED" | "CANCELLED"; interface Reservation { id: number; + userId: number; time: string; requestedAt: string; waitMinutes: number; @@ -36,7 +37,7 @@ const AdminHome = () => { const { data: completedList } = useGetCompletedList(storeId); //canceled, conformed console.log(waitingList); - + console.log(completedList); const toggle = () => setIsOn((prev) => !prev); //대기 중 카드 개수 const waitingCount = reservations.filter( @@ -84,15 +85,14 @@ const AdminHome = () => { }, [reservations, activeTab]); // 호출 버튼 클릭 이벤트 - const handleCall = (id: number) => { - // 상태 변화 api 호출 --> 성공시 --> reservation status 변경(호출 시간 calledAt추가해야 됨) + const handleCall = (userId: number) => { updateStatus( - { reservationId: id, status: "CALLING" }, + { storeId, userId, status: "CALLING" }, { onSuccess: () => { setReservations((prev) => prev.map((res) => - res.id === id + res.id === userId ? { ...res, status: "CALLING", @@ -109,14 +109,14 @@ const AdminHome = () => { ); }; - const handleEnter = (id: number) => { + const handleEnter = (userId: number) => { updateStatus( - { reservationId: id, status: "CONFIRMED" }, + { storeId, userId, status: "CONFIRMED" }, { onSuccess: () => { setReservations((prev) => prev.map((res) => - res.id === id ? { ...res, status: "CONFIRMED" } : res + res.id === userId ? { ...res, status: "CONFIRMED" } : res ) ); }, @@ -124,14 +124,14 @@ const AdminHome = () => { ); }; - const handleClose = (id: number) => { + const handleClose = (userId: number) => { updateStatus( - { reservationId: id, status: "CANCELLED" }, + { storeId, userId, status: "CANCELLED" }, { onSuccess: () => { setReservations((prev) => prev.map((res) => - res.id === id ? { ...res, status: "CANCELLED" } : res + res.id === userId ? { ...res, status: "CANCELLED" } : res ) ); }, @@ -152,9 +152,11 @@ const AdminHome = () => { const normalize = (res: any): Reservation => { const requested = new Date(res.createdAt ?? ""); - const called = new Date(res.calledAt ?? ""); + const calledAtValid = res.calledAt && !isNaN(Date.parse(res.calledAt)); + const called = calledAtValid ? new Date(res.calledAt) : undefined; return { id: Number(res.id), + userId: Number(res.userId), requestedAt: res.createdAt, time: requested.toLocaleTimeString("ko-KR", { hour: "2-digit", @@ -166,7 +168,8 @@ const AdminHome = () => { name: res.userName, phone: "010-****-****", status: res.status, - calledAt: res.status === "CALLING" ? called.toISOString() : undefined, + calledAt: + res.status === "CALLING" && called ? called.toISOString() : undefined, }; }; @@ -253,10 +256,10 @@ const AdminHome = () => { phone="010-1234-1234" status={res.status} calledAt={res.calledAt} - isNoShow={noShowIds.includes(res.id)} - onCall={() => handleCall(res.id)} - onEnter={() => handleEnter(res.id)} - onClose={() => handleClose(res.id)} + isNoShow={noShowIds.includes(res.userId)} + onCall={() => handleCall(res.userId)} + onEnter={() => handleEnter(res.userId)} + onClose={() => handleClose(res.userId)} onDelete={() => setShowModal(true)} onNoShow={() => handleNoShow(res.id)} /> diff --git a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx index f21601e7..e3f48e3b 100644 --- a/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx +++ b/apps/nowait-admin/src/pages/AdminHome/components/WaitingCard.tsx @@ -23,7 +23,7 @@ interface WaitingCardProps { onDelete: () => void; } const truncateName = (name: string, maxLength: number = 3) => { - return name.length > maxLength ? name.slice(0, maxLength) + "..." : name; + return name?.length > maxLength ? name.slice(0, maxLength) + "..." : name; }; export function WaitingCard({ @@ -137,13 +137,13 @@ export function WaitingCard({ <> @@ -155,7 +155,7 @@ export function WaitingCard({
@@ -163,13 +163,13 @@ export function WaitingCard({ ))} {status === "CANCELLED" && ( -
+
취소된 입장
)} {status === "CONFIRMED" && ( -
+
완료된 입장
)}