Skip to content

Commit acfa109

Browse files
authored
analystic UI구현 (#57)
* chore: chart 그리기 위해 Recharts 설치 * feat: routes에 analytics 추가 * feat: analytics에 ChartOrder 컴포넌트 추가 * feat: analytics에 TrendingItems 컴포넌트 UI구현 * feat: analytics에 FavoritesItems 컴포넌트 UI구현 * feat: analytics에 SellingItems 컴포넌트 UI구현 * feat: analytics UI 구현 * feat: menu에 anaytics 메뉴 추가 * etc: anlytics 관련 dummy data 변경 * fix: 차트 디자인 수정 * feat: analytics 카드들에 클릭이벤트 추가 * fix: analytics 루트에 스토어 id 추가
1 parent def9a21 commit acfa109

File tree

11 files changed

+1398
-18
lines changed

11 files changed

+1398
-18
lines changed

package-lock.json

Lines changed: 325 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"react-dom": "^18.3.1",
3434
"react-hook-form": "^7.53.0",
3535
"react-router-dom": "^6.26.1",
36+
"recharts": "^2.13.1",
3637
"save": "^2.9.0",
3738
"zod": "^3.23.8"
3839
},

src/common/components/layout/menus/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ const MENUS = {
3232
link: ROUTES.REVIEW,
3333
icon: <Review width={'100%'} height={'100%'} fill="currentColor" />,
3434
},
35-
// analytics: { title: "Analytics", link: "/", icon: <Dashboard /> },
35+
analytics: {
36+
title: 'Analytics',
37+
link: ROUTES.ANALYTICS,
38+
icon: <Dashboard width={'100%'} height={'100%'} fill="currentColor" />,
39+
},
3640
} as const;
3741

3842
//TODO API 연동후 맞는 Store id로 수정할 것

src/common/constants/routes.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
export const ROUTES = {
2-
HOME: "/",
3-
STORE: "/store", // 스토어 리스트, 관리자만 접근
4-
STORE_DETAIL: "/store/:storeId",
5-
USER: "/user", // 유저 리스트, 관리자만 접근
6-
USER_DETAIL: "user/:userId", //관리자만 접근
7-
ORDER: "/:storeId/order",
8-
ORDER_DETAIL: "/:storeId/order/:orderId",
9-
MENU: "/:storeId/menu",
10-
MENU_REGISTRATION: "/:storeId/menu/registration",
11-
MENU_DETAIL: "/:storeId/menu/:menuId",
12-
REVIEW: "/:storeId/review",
13-
PROFILE: "/profile",
14-
SIGN_IN: "/sign-in",
2+
HOME: '/',
3+
STORE: '/store', // 스토어 리스트, 관리자만 접근
4+
STORE_DETAIL: '/store/:storeId',
5+
USER: '/user', // 유저 리스트, 관리자만 접근
6+
USER_DETAIL: 'user/:userId', //관리자만 접근
7+
ORDER: '/:storeId/order',
8+
ORDER_DETAIL: '/:storeId/order/:orderId',
9+
MENU: '/:storeId/menu',
10+
MENU_REGISTRATION: '/:storeId/menu/registration',
11+
MENU_DETAIL: '/:storeId/menu/:menuId',
12+
REVIEW: '/:storeId/review',
13+
PROFILE: '/profile',
14+
SIGN_IN: '/sign-in',
15+
ANALYTICS: '/:storeId/analytics',
1516
} as const;
1617

17-
export const BASE_URL = "/api/v1/";
18+
export const BASE_URL = '/api/v1/';
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React, { HTMLAttributes } from 'react';
2+
import {
3+
AreaChart,
4+
Area,
5+
XAxis,
6+
YAxis,
7+
Tooltip,
8+
ResponsiveContainer,
9+
} from 'recharts';
10+
import styled from '@emotion/styled';
11+
12+
interface AreaLineChartProps extends HTMLAttributes<HTMLDivElement> {
13+
data?: { month: string; value: number }[];
14+
}
15+
16+
const AreaLineChart = ({ data, ...rest }: AreaLineChartProps) => {
17+
return (
18+
<ChartContainer {...rest}>
19+
<ResponsiveContainer width="100%" height={300}>
20+
<AreaChart
21+
data={data}
22+
margin={{ top: 20, right: 40, left: 20, bottom: 20 }}
23+
>
24+
<XAxis dataKey="month" tick={{ fontSize: 14, fill: '#666' }} />
25+
<YAxis tick={{ fontSize: 14, fill: '#666' }} />
26+
<Tooltip
27+
contentStyle={{
28+
borderRadius: 8,
29+
boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)',
30+
}}
31+
/>
32+
<defs>
33+
<linearGradient id="colorValue" x1="0" y1="0" x2="0" y2="1">
34+
<stop offset="5%" stopColor="#2E73FF" stopOpacity={0.6} />
35+
<stop offset="95%" stopColor="#2E73FF" stopOpacity={0} />
36+
</linearGradient>
37+
</defs>
38+
<Area
39+
type="monotone"
40+
dataKey="value"
41+
stroke="#2E73FF"
42+
strokeWidth={3}
43+
fillOpacity={1}
44+
fill="url(#colorValue)"
45+
dot={{ fill: '#2E73FF', stroke: '#fff', strokeWidth: 2, r: 6 }}
46+
/>
47+
</AreaChart>
48+
</ResponsiveContainer>
49+
</ChartContainer>
50+
);
51+
};
52+
53+
export default AreaLineChart;
54+
55+
const ChartContainer = styled.div`
56+
padding: 0;
57+
background-color: white;
58+
border-radius: 16px;
59+
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.05);
60+
text-align: center;
61+
max-width: 100%;
62+
`;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import React, { HTMLAttributes } from 'react';
2+
3+
import styled from '@emotion/styled';
4+
import AreaLineChart from './AreaLineChart';
5+
6+
interface ChartOrdersProps extends HTMLAttributes<HTMLDivElement> {
7+
data?: { month: string; value: number }[];
8+
}
9+
10+
const ChartOrders = ({ data = dummyData, ...rest }: ChartOrdersProps) => {
11+
return (
12+
<ChartContainer {...rest}>
13+
<Header>
14+
<Title>Chart Orders</Title>
15+
<Description>Lorem ipsum dolor sit amet, consectetur</Description>
16+
<StatsContainer>
17+
<Stat>
18+
<StatValue>257k</StatValue>
19+
<StatLabel>Total Sales</StatLabel>
20+
</Stat>
21+
<Stat>
22+
<StatValue>1,245</StatValue>
23+
<StatLabel>Avg. Sales per day</StatLabel>
24+
</Stat>
25+
</StatsContainer>
26+
<Tabs>
27+
<Tab active>Monthly</Tab>
28+
<Tab>Weekly</Tab>
29+
<Tab>Daily</Tab>
30+
</Tabs>
31+
</Header>
32+
<AreaLineChart data={data} />
33+
</ChartContainer>
34+
);
35+
};
36+
37+
export default ChartOrders;
38+
39+
const ChartContainer = styled.div`
40+
padding: 16px;
41+
background-color: white;
42+
border-radius: 16px;
43+
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.05);
44+
text-align: center;
45+
max-width: 100%;
46+
`;
47+
48+
const Header = styled.div`
49+
text-align: left;
50+
margin-bottom: 16px;
51+
`;
52+
53+
const Title = styled.h2`
54+
font-size: 24px;
55+
font-weight: 600;
56+
margin: 0;
57+
`;
58+
59+
const Description = styled.p`
60+
font-size: 14px;
61+
color: #999;
62+
margin: 8px 0;
63+
`;
64+
65+
const StatsContainer = styled.div`
66+
display: flex;
67+
justify-content: space-between;
68+
margin: 16px 0;
69+
`;
70+
71+
const Stat = styled.div`
72+
text-align: center;
73+
`;
74+
75+
const StatValue = styled.p`
76+
font-size: 24px;
77+
font-weight: 600;
78+
margin: 0;
79+
`;
80+
81+
const StatLabel = styled.p`
82+
font-size: 12px;
83+
color: #999;
84+
margin: 0;
85+
`;
86+
87+
const Tabs = styled.div`
88+
display: flex;
89+
justify-content: flex-end;
90+
margin-bottom: 16px;
91+
`;
92+
93+
const Tab = styled.button<{ active?: boolean }>`
94+
font-size: 14px;
95+
padding: 8px 16px;
96+
background: ${({ active }) => (active ? '#f0f0f0' : '#fff')};
97+
border: 1px solid #ddd;
98+
border-radius: 8px;
99+
margin-left: 8px;
100+
cursor: pointer;
101+
transition: background 0.3s;
102+
&:hover {
103+
background: #f0f0f0;
104+
}
105+
`;
106+
107+
// 예시 데이터
108+
type DummyData = { month: string; value: number }[];
109+
110+
const dummyData: DummyData = [
111+
{ month: 'Jan', value: 200000 },
112+
{ month: 'Feb', value: 250000 },
113+
{ month: 'Mar', value: 300000 },
114+
{ month: 'Apr', value: 350000 },
115+
{ month: 'May', value: 400000 },
116+
{ month: 'Jun', value: 450000 },
117+
{ month: 'Jul', value: 500000 },
118+
{ month: 'Aug', value: 550000 },
119+
{ month: 'Sep', value: 400000 },
120+
{ month: 'Oct', value: 350000 },
121+
{ month: 'Nov', value: 300000 },
122+
];

0 commit comments

Comments
 (0)