Skip to content

Commit dc6a1c4

Browse files
authored
Merge pull request #80 from basir/Video-61-Create-Dashboard-Screen
Video-61-Create-Dashboard-Screen
2 parents a930b4e + b446326 commit dc6a1c4

File tree

10 files changed

+233
-0
lines changed

10 files changed

+233
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -406,3 +406,6 @@ $ npm start
406406
4. set api key in env file
407407
5. change pay order in orderRouter
408408
6. send email the
409+
61. Create Dashboard Screen
410+
1. Create chart data in backend
411+
2. Build Chart screen

backend/routers/orderRouter.js

+47
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import express from 'express';
22
import expressAsyncHandler from 'express-async-handler';
33
import Order from '../models/orderModel.js';
4+
import User from '../models/userModel.js';
5+
import Product from '../models/productModel.js';
46
import {
57
isAdmin,
68
isAuth,
@@ -25,6 +27,51 @@ orderRouter.get(
2527
res.send(orders);
2628
})
2729
);
30+
31+
orderRouter.get(
32+
'/summary',
33+
isAuth,
34+
isAdmin,
35+
expressAsyncHandler(async (req, res) => {
36+
const orders = await Order.aggregate([
37+
{
38+
$group: {
39+
_id: null,
40+
numOrders: { $sum: 1 },
41+
totalSales: { $sum: '$totalPrice' },
42+
},
43+
},
44+
]);
45+
const users = await User.aggregate([
46+
{
47+
$group: {
48+
_id: null,
49+
numUsers: { $sum: 1 },
50+
},
51+
},
52+
]);
53+
const dailyOrders = await Order.aggregate([
54+
{
55+
$group: {
56+
_id: { $dateToString: { format: '%Y-%m-%d', date: '$createdAt' } },
57+
orders: { $sum: 1 },
58+
sales: { $sum: '$totalPrice' },
59+
},
60+
},
61+
{ $sort: { _id: 1 } },
62+
]);
63+
const productCategories = await Product.aggregate([
64+
{
65+
$group: {
66+
_id: '$category',
67+
count: { $sum: 1 },
68+
},
69+
},
70+
]);
71+
res.send({ users, orders, dailyOrders, productCategories });
72+
})
73+
);
74+
2875
orderRouter.get(
2976
'/mine',
3077
isAuth,

frontend/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"axios": "^0.20.0",
1212
"react": "^16.13.1",
1313
"react-dom": "^16.13.1",
14+
"react-google-charts": "^3.0.15",
1415
"react-paypal-button-v2": "^2.6.2",
1516
"react-redux": "^7.2.1",
1617
"react-responsive-carousel": "^3.2.10",

frontend/src/App.js

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { listProductCategories } from './actions/productActions';
2828
import LoadingBox from './components/LoadingBox';
2929
import MessageBox from './components/MessageBox';
3030
import MapScreen from './screens/MapScreen';
31+
import DashboardScreen from './screens/DashboardScreen';
3132

3233
function App() {
3334
const cart = useSelector((state) => state.cart);
@@ -230,6 +231,12 @@ function App() {
230231
path="/user/:id/edit"
231232
component={UserEditScreen}
232233
></AdminRoute>
234+
235+
<AdminRoute
236+
path="/dashboard"
237+
component={DashboardScreen}
238+
></AdminRoute>
239+
233240
<SellerRoute
234241
path="/productlist/seller"
235242
component={ProductListScreen}

frontend/src/actions/orderActions.js

+23
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
ORDER_DELIVER_REQUEST,
2323
ORDER_DELIVER_SUCCESS,
2424
ORDER_DELIVER_FAIL,
25+
ORDER_SUMMARY_REQUEST,
26+
ORDER_SUMMARY_SUCCESS,
2527
} from '../constants/orderConstants';
2628

2729
export const createOrder = (order) => async (dispatch, getState) => {
@@ -169,3 +171,24 @@ export const deliverOrder = (orderId) => async (dispatch, getState) => {
169171
dispatch({ type: ORDER_DELIVER_FAIL, payload: message });
170172
}
171173
};
174+
175+
export const summaryOrder = () => async (dispatch, getState) => {
176+
dispatch({ type: ORDER_SUMMARY_REQUEST });
177+
const {
178+
userSignin: { userInfo },
179+
} = getState();
180+
try {
181+
const { data } = await Axios.get('/api/orders/summary', {
182+
headers: { Authorization: `Bearer ${userInfo.token}` },
183+
});
184+
dispatch({ type: ORDER_SUMMARY_SUCCESS, payload: data });
185+
} catch (error) {
186+
dispatch({
187+
type: ORDER_CREATE_FAIL,
188+
payload:
189+
error.response && error.response.data.message
190+
? error.response.data.message
191+
: error.message,
192+
});
193+
}
194+
};

frontend/src/constants/orderConstants.js

+4
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ export const ORDER_DELETE_REQUEST = 'ORDER_DELETE_REQUEST';
2929
export const ORDER_DELETE_SUCCESS = 'ORDER_DELETE_SUCCESS';
3030
export const ORDER_DELETE_FAIL = 'ORDER_DELETE_FAIL';
3131
export const ORDER_DELETE_RESET = 'ORDER_DELETE_RESET';
32+
33+
export const ORDER_SUMMARY_REQUEST = 'ORDER_SUMMARY_REQUEST';
34+
export const ORDER_SUMMARY_SUCCESS = 'ORDER_SUMMARY_SUCCESS';
35+
export const ORDER_SUMMARY_FAIL = 'ORDER_SUMMARY_FAIL';

frontend/src/index.css

+28
Original file line numberDiff line numberDiff line change
@@ -364,3 +364,31 @@ img.large {
364364
.pagination a.active {
365365
font-weight: bold;
366366
}
367+
368+
/* Dashboard */
369+
370+
.summary > li {
371+
border: 0.1rem #c0c0c0 solid;
372+
margin: 2rem;
373+
border-radius: 0.5rem;
374+
flex: 1 1 20rem;
375+
}
376+
.summary-title {
377+
font-size: 2rem;
378+
padding: 1rem;
379+
}
380+
.summary-body {
381+
font-size: 4rem;
382+
padding: 1rem;
383+
text-align: center;
384+
}
385+
386+
.summary-title.color1 {
387+
background-color: #f0e0e0;
388+
}
389+
.summary-title.color2 {
390+
background-color: #e0f0e0;
391+
}
392+
.summary-title.color3 {
393+
background-color: #e0e0f0;
394+
}

frontend/src/reducers/orderReducers.js

+19
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import {
2424
ORDER_DELIVER_SUCCESS,
2525
ORDER_DELIVER_FAIL,
2626
ORDER_DELIVER_RESET,
27+
ORDER_SUMMARY_REQUEST,
28+
ORDER_SUMMARY_SUCCESS,
29+
ORDER_SUMMARY_FAIL,
2730
} from '../constants/orderConstants';
2831

2932
export const orderCreateReducer = (state = {}, action) => {
@@ -121,3 +124,19 @@ export const orderDeliverReducer = (state = {}, action) => {
121124
return state;
122125
}
123126
};
127+
128+
export const orderSummaryReducer = (
129+
state = { loading: true, summary: {} },
130+
action
131+
) => {
132+
switch (action.type) {
133+
case ORDER_SUMMARY_REQUEST:
134+
return { loading: true };
135+
case ORDER_SUMMARY_SUCCESS:
136+
return { loading: false, summary: action.payload };
137+
case ORDER_SUMMARY_FAIL:
138+
return { loading: false, error: action.payload };
139+
default:
140+
return state;
141+
}
142+
};
+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React, { useEffect } from 'react';
2+
import { useDispatch, useSelector } from 'react-redux';
3+
import Chart from 'react-google-charts';
4+
import { summaryOrder } from '../actions/orderActions';
5+
import LoadingBox from '../components/LoadingBox';
6+
import MessageBox from '../components/MessageBox';
7+
8+
export default function DashboardScreen() {
9+
const orderSummary = useSelector((state) => state.orderSummary);
10+
const { loading, summary, error } = orderSummary;
11+
const dispatch = useDispatch();
12+
useEffect(() => {
13+
dispatch(summaryOrder());
14+
}, [dispatch]);
15+
return (
16+
<div>
17+
<div className="row">
18+
<h1>Dashboard</h1>
19+
</div>
20+
{loading ? (
21+
<LoadingBox />
22+
) : error ? (
23+
<MessageBox variant="danger">{error}</MessageBox>
24+
) : (
25+
<>
26+
<ul className="row summary">
27+
<li>
28+
<div className="summary-title color1">
29+
<span>
30+
<i className="fa fa-users" /> Users
31+
</span>
32+
</div>
33+
<div className="summary-body">{summary.users[0].numUsers}</div>
34+
</li>
35+
<li>
36+
<div className="summary-title color2">
37+
<span>
38+
<i className="fa fa-shopping-cart" /> Orders
39+
</span>
40+
</div>
41+
<div className="summary-body">
42+
{summary.orders[0] ? summary.orders[0].numOrders : 0}
43+
</div>
44+
</li>
45+
<li>
46+
<div className="summary-title color3">
47+
<span>
48+
<i className="fa fa-money" /> Sales
49+
</span>
50+
</div>
51+
<div className="summary-body">
52+
$
53+
{summary.orders[0]
54+
? summary.orders[0].totalSales.toFixed(2)
55+
: 0}
56+
</div>
57+
</li>
58+
</ul>
59+
<div>
60+
<div>
61+
<h2>Sales</h2>
62+
{summary.dailyOrders.length === 0 ? (
63+
<MessageBox>No Sale</MessageBox>
64+
) : (
65+
<Chart
66+
width="100%"
67+
height="400px"
68+
chartType="AreaChart"
69+
loader={<div>Loading Chart</div>}
70+
data={[
71+
['Date', 'Sales'],
72+
...summary.dailyOrders.map((x) => [x._id, x.sales]),
73+
]}
74+
></Chart>
75+
)}
76+
</div>
77+
</div>
78+
<div>
79+
<h2>Categories</h2>
80+
{summary.productCategories.length === 0 ? (
81+
<MessageBox>No Category</MessageBox>
82+
) : (
83+
<Chart
84+
width="100%"
85+
height="400px"
86+
chartType="PieChart"
87+
loader={<div>Loading Chart</div>}
88+
data={[
89+
['Category', 'Products'],
90+
...summary.productCategories.map((x) => [x._id, x.count]),
91+
]}
92+
/>
93+
)}
94+
</div>
95+
</>
96+
)}
97+
</div>
98+
);
99+
}

frontend/src/store.js

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
orderListReducer,
1010
orderMineListReducer,
1111
orderPayReducer,
12+
orderSummaryReducer,
1213
} from './reducers/orderReducers';
1314
import {
1415
productCategoryListReducer,
@@ -72,6 +73,7 @@ const reducer = combineReducers({
7273
productCategoryList: productCategoryListReducer,
7374
productReviewCreate: productReviewCreateReducer,
7475
userAddressMap: userAddressMapReducer,
76+
orderSummary: orderSummaryReducer,
7577
});
7678
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
7779
const store = createStore(

0 commit comments

Comments
 (0)