Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/nowait-admin/src/assets/analytics/arrow_back.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions apps/nowait-admin/src/assets/analytics/arrow_back_active.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions apps/nowait-admin/src/assets/analytics/arrow_forward.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions apps/nowait-admin/src/hooks/analytics/useGetPopularMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useQuery } from "@tanstack/react-query";
import AdminApi from "../../utils/AdminApi";

interface PopularMenuItem {
menuId: number;
menuName: string;
totalSalesCount: number;
boothName: string;
}

interface PopularMenuResponse {
success: boolean;
response: PopularMenuItem[];
}

const fetchPopularMenu = async (): Promise<PopularMenuItem[]> => {
const res = await AdminApi.get<PopularMenuResponse>(
"/admin/statistics/popular-menu"
);
return res.data.response;
};

export const useGetPopularMenu = () => {
return useQuery({
queryKey: ["popular-menu"],
queryFn: fetchPopularMenu,
staleTime: 1000 * 60 * 5, // 5분 캐싱
});
};
23 changes: 23 additions & 0 deletions apps/nowait-admin/src/hooks/analytics/useGetSalesByDate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useQuery } from "@tanstack/react-query";
import AdminApi from "../../utils/AdminApi";

interface SalesResponse {
success: boolean;
response: number | string; // "해당일 매출 데이터가 없습니다." 또는 숫자형 매출값
}

const fetchSalesByDate = async (date: string): Promise<number | string> => {
const res = await AdminApi.get<SalesResponse>(`/admin/statistics/sales`, {
params: { date },
});
return res.data.response;
};

export const useGetSalesByDate = (date: string) => {
return useQuery({
queryKey: ["sales-by-date", date],
queryFn: () => fetchSalesByDate(date),
enabled: !!date, // 날짜 있을 때만 실행
staleTime: 1000 * 60, // 1분 캐싱
});
};
31 changes: 31 additions & 0 deletions apps/nowait-admin/src/hooks/analytics/useGetTopSalse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useQuery } from "@tanstack/react-query";
import AdminApi from "../../utils/AdminApi";

interface TopSalesItem {
rank: number;
name: string;
department: string;
salesCount: number;
isCurrentBooth: boolean;
rankChange: number;
}

interface TopSalesResponse {
success: boolean;
response: TopSalesItem[];
}

const fetchTopSales = async (): Promise<TopSalesItem[]> => {
const res = await AdminApi.get<TopSalesResponse>(
"/admin/statistics/top-sales"
);
return res.data.response;
};

export const useGetTopSales = () => {
return useQuery({
queryKey: ["top-sales"],
queryFn: fetchTopSales,
staleTime: 1000 * 60 * 5, // 5분 캐싱
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const boothData: BoothRanking[] = [
];

const AdminAnalytics = () => {
// const { data, isLoading, isError } = useGetTopSales();
return (
<div className="w-full">
<HeaderStatus />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,15 @@ const BoothSalesRankingCard: React.FC<BoothSalesRankingCardProps> = ({
data,
}) => {
return (
<div className="bg-white rounded-[12px] p-6 shadow-sm w-full h-full mt-[10px]">
<div className="bg-white max-h-[364px] rounded-[12px] p-6 shadow-sm w-full h-full mt-[10px]">
<div className="flex justify-between mb-4">
<div className="flex flex-col">
<h2 className="text-13-regular font-semibold text-black-60">
{"통계2"}
</h2>
<div className="flex">
<h5>매출 순위</h5>
<span>
<img src={arrowIcon} />
</span>
</div>
<h2 className="text-title-18-bold text-navy-80">부스별 판매순위</h2>
</div>
<span className="text-[12px] text-gray-400">{date}</span>
</div>

<ul className="space-y-3">
<ul>
{data.map((item) => {
const isUp = item.rankChange > 0;
const isDown = item.rankChange < 0;
Expand All @@ -45,15 +37,17 @@ const BoothSalesRankingCard: React.FC<BoothSalesRankingCardProps> = ({
return (
<li
key={item.rank + item.name}
className={`flex justify-between items-center p-2 rounded ${
item.isCurrentBooth ? "bg-[#F5F8FA]" : ""
className={`flex h-[54px] justify-between items-center p-2 rounded relative ${
item.isCurrentBooth
? "bg-[#F5F8FA] w-[103%] left-1/2 -translate-x-1/2 rounded-[10px] px-[2.5%]"
: ""
}`}
>
<div className="flex items-center gap-3">
<span className="text-[14px] font-semibold text-gray-600">
{item.rank}
<div className="flex items-center w-[710px]">
<span className="text-[14px] font-semibold text-gray-600 w-[30px] h-[23px]">
{item.rank}
</span>
<div className="w-6 h-6 rounded-full bg-[#5A6ACF]" />
<div className="w-6 h-6 rounded-full bg-[#5A6ACF] mr-[10px]" />
<div className="flex flex-col text-sm">
<span className="font-medium text-black">{item.name}</span>
<span className="text-[12px] text-gray-400">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from "react";
import SalesCard from "./SalesCard";
import TotalSalesCard from "./TotalSalesCard";

const HeaderStatus = () => {
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-[10px] h-[352px]">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-[10px] max-h-[352px]">
<div className="flex flex-col gap-[10px]">
{/* 오늘 매출 */}
<SalesCard
Expand All @@ -14,25 +15,26 @@ const HeaderStatus = () => {
percent={13.6}
/>
{/* 누적 매출 */}
<SalesCard
title="누적 매출"
date="2025.07.18 금"
amount={6400000}
diffAmount={40000}
percent={0.63}
<TotalSalesCard
title="누적매출"
date="2025. 07.18 - 07.19"
amount={1800000}
percent={-0.6}
/>
</div>
{/* 인기 메뉴 TOP 5 */}
<div className="flex flex-col justify-between bg-white rounded-xl p-6 shadow h-full">
<div className="flex flex-col mb-2">
<div className="flex flex-col bg-white rounded-xl p-6 shadow max-h-[352px]">
<div className="flex flex-col mb-[25px]">
<p className="text-title-18-bold text-navy-80">인기 메뉴 TOP 5</p>
<span className="text-13-regular text-black-60">2025.07.18 금</span>
</div>
<ul className="space-y-2">
<ul>
{[1, 2, 3, 4, 5].map((rank) => (
<li key={rank} className="flex justify-between">
<span>{rank}위 참치마요주먹밥</span>
<span className="font-medium">100개</span>
<li key={rank} className="flex justify-between h-[52px]">
<span className="flex text-16-bold gap-[10px]">
{rank} <p className="text-16-semibold">참치마요주먹밥</p>
</span>
<span className="text-16-medium">100개</span>
</li>
))}
</ul>
Expand Down
58 changes: 42 additions & 16 deletions apps/nowait-admin/src/pages/AdminAnalytics/components/SalesCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import backIcon from "../../../assets/analytics/arrow_back.svg";
import forwardIcon from "../../../assets/analytics/arrow_forward.svg";
import activeBackIcon from "../../../assets/analytics/arrow_back_active.svg";
import activeForwardIcon from "../../../assets/analytics/arrow_forward_active.svg";
import { useState } from "react";
interface SalesCardProps {
title: string; // 예: "오늘 매출" 또는 "누적 매출"
date: string;
Expand All @@ -12,23 +17,44 @@ const SalesCard: React.FC<SalesCardProps> = ({
amount,
diffAmount,
percent,
}) => (
<div className="bg-white rounded-[12px] p-6 shadow-sm w-full h-full flex flex-col justify-between">
<div>
<p className="text-title-18-bold text-navy-80">{title}</p>
<p className="text-13-regular text-black-60 mt-1">{date}</p>
</div>
<div>
<div className="flex items-baseline gap-2">
<p className="text-headline-22-bold text-navy-80">
{amount.toLocaleString()}원
}) => {
const [isHoverBack, setIsHoverBack] = useState(false);
const [isHoverForward, setIsHoverForward] = useState(false);
return (
<div className="bg-white rounded-[12px] p-6 shadow-sm w-full h-full flex flex-col justify-between">
<div className="flex justify-between">
<span>
<p className="text-title-18-bold text-navy-80">{title}</p>
<p className="text-13-regular text-black-60 mt-1">{date}</p>
</span>
<span className="flex">
<img
src={isHoverBack ? activeBackIcon : backIcon}
className="h-5 w-5"
onMouseEnter={() => setIsHoverBack(true)}
onMouseLeave={() => setIsHoverBack(false)}
/>
<img
src={isHoverForward ? activeForwardIcon : forwardIcon}
className="h-5 w-5"
onMouseEnter={() => setIsHoverForward(true)}
onMouseLeave={() => setIsHoverForward(false)}
/>
</span>
</div>

<div>
<div className="flex items-baseline gap-2">
<p className="text-headline-22-bold text-navy-80">
{amount.toLocaleString()}원
</p>
<span className="text-14-regular text-primary">+{percent}%</span>
</div>
<p className="text-13-regular text-black-80 mt-1">
어제보다 {diffAmount.toLocaleString()}원 더 벌었어요!
</p>
<span className="text-14-regular text-primary">+{percent}%</span>
</div>
<p className="text-13-regular text-black-80 mt-1">
어제보다 {diffAmount.toLocaleString()}원 더 벌었어요!
</p>
</div>
</div>
);
);
};
export default SalesCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";

interface TotalSalesCardProps {
title: string; // 예: "누적매출"
date: string; // 예: "2025. 07.18 - 07.19"
amount: number; // 예: 1800000
percent: number; // 예: -0.6
}

const TotalSalesCard: React.FC<TotalSalesCardProps> = ({
title,
date,
amount,
percent,
}) => {
const percentColor =
percent > 0
? "text-[#FF5A1F]" // 상승: 주황
: percent < 0
? "text-[#3A75E5]" // 하락: 파랑
: "text-gray-500"; // 변동 없음

return (
<div className="bg-white rounded-[12px] p-6 shadow-sm w-full h-full flex flex-col justify-between">
{/* 상단 제목 + 날짜 */}
<div>
<p className="text-title-18-bold text-black">{title}</p>
<p className="text-13-regular text-gray-400 mt-1">{date}</p>
</div>

{/* 금액 + 퍼센트 변화 */}
<div className="flex items-baseline gap-2 mt-4">
<p className="text-headline-22-bold text-[#1C1C1C]">
{amount.toLocaleString()}원
</p>
<span className={`text-14-regular ${percentColor}`}>
{percent > 0 ? `+${percent}%` : `${percent}%`}
</span>
</div>
</div>
);
};

export default TotalSalesCard;