Added a dashboard
							parent
							
								
									7a1cb87e2a
								
							
						
					
					
						commit
						7cef58dae3
					
				|  | @ -116,6 +116,7 @@ const updateTotalCart = (req, res) => { | |||
| 
 | ||||
|     req.session.totalCartAmount = 0; | ||||
|     req.session.totalCartItems = 0; | ||||
|     req.session.totalCartProducts = 0; | ||||
| 
 | ||||
|     // If cart is empty return zero values
 | ||||
|     if(!req.session.cart){ | ||||
|  | @ -124,6 +125,7 @@ const updateTotalCart = (req, res) => { | |||
| 
 | ||||
|     Object.keys(req.session.cart).forEach((item) => { | ||||
|         req.session.totalCartAmount = req.session.totalCartAmount + req.session.cart[item].totalItemPrice; | ||||
|         req.session.totalCartProducts = req.session.totalCartProducts + req.session.cart[item].quantity; | ||||
|     }); | ||||
| 
 | ||||
|     // Update the total items in cart for the badge
 | ||||
|  |  | |||
|  | @ -171,5 +171,6 @@ | |||
| 	"Shipping options": "Shipping options", | ||||
| 	"Proceed to payment": "Proceed to payment", | ||||
| 	"Return to information": "Return to information", | ||||
| 	"Search shop": "Search shop" | ||||
| 	"Search shop": "Search shop", | ||||
| 	"Dashboard": "Dashboard" | ||||
| } | ||||
|  | @ -255,6 +255,10 @@ input[type=number]::-webkit-outer-spin-button { | |||
|     padding-bottom: 20px; | ||||
| } | ||||
| 
 | ||||
| .bottom-pad-30{ | ||||
|     padding-bottom: 30px; | ||||
| } | ||||
| 
 | ||||
| .bottom-marg-10{ | ||||
|     margin-bottom: 10px; | ||||
| } | ||||
|  |  | |||
|  | @ -198,6 +198,9 @@ input[type=number]::-webkit-outer-spin-button { | |||
| .bottom-pad-20 { | ||||
|   padding-bottom: 20px; | ||||
| } | ||||
| .bottom-pad-30 { | ||||
|   padding-bottom: 30px; | ||||
| } | ||||
| .bottom-marg-10 { | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -13,7 +13,7 @@ const router = express.Router(); | |||
| 
 | ||||
| // Admin section
 | ||||
| router.get('/admin', restrict, (req, res, next) => { | ||||
|     res.redirect('/admin/orders'); | ||||
|     res.redirect('/admin/dashboard'); | ||||
| }); | ||||
| 
 | ||||
| // logout
 | ||||
|  | @ -61,18 +61,18 @@ router.post('/admin/login_action', async (req, res) => { | |||
| 
 | ||||
|     // we have a user under that email so we compare the password
 | ||||
|     bcrypt.compare(req.body.password, user.userPassword) | ||||
|     .then((result) => { | ||||
|         if(result){ | ||||
|             req.session.user = req.body.email; | ||||
|             req.session.usersName = user.usersName; | ||||
|             req.session.userId = user._id.toString(); | ||||
|             req.session.isAdmin = user.isAdmin; | ||||
|             res.status(200).json({ message: 'Login successful' }); | ||||
|             return; | ||||
|         } | ||||
|         // password is not correct
 | ||||
|         res.status(400).json({ message: 'Access denied. Check password and try again.' }); | ||||
|     }); | ||||
|         .then((result) => { | ||||
|             if(result){ | ||||
|                 req.session.user = req.body.email; | ||||
|                 req.session.usersName = user.usersName; | ||||
|                 req.session.userId = user._id.toString(); | ||||
|                 req.session.isAdmin = user.isAdmin; | ||||
|                 res.status(200).json({ message: 'Login successful' }); | ||||
|                 return; | ||||
|             } | ||||
|             // password is not correct
 | ||||
|             res.status(400).json({ message: 'Access denied. Check password and try again.' }); | ||||
|         }); | ||||
| }); | ||||
| 
 | ||||
| // setup form is shown when there are no users setup in the DB
 | ||||
|  | @ -127,6 +127,57 @@ router.post('/admin/setup_action', async (req, res) => { | |||
|     res.status(200).json({ message: 'Already setup.' }); | ||||
| }); | ||||
| 
 | ||||
| // dashboard
 | ||||
| router.get('/admin/dashboard', restrict, async (req, res) => { | ||||
|     const db = req.app.db; | ||||
| 
 | ||||
|     // Collate data for dashboard
 | ||||
|     const dashboardData = { | ||||
|         productsCount: await db.products.countDocuments({ | ||||
|             productPublished: true | ||||
|         }), | ||||
|         ordersCount: await db.orders.countDocuments({}), | ||||
|         ordersAmount: await db.orders.aggregate([{ $match: {} }, | ||||
|             { $group: { _id: null, sum: { $sum: '$orderTotal' } } | ||||
|         }]).toArray(), | ||||
|         productsSold: await db.orders.aggregate([{ $match: {} }, | ||||
|             { $group: { _id: null, sum: { $sum: '$orderProductCount' } } | ||||
|         }]).toArray(), | ||||
|         topProducts: await db.orders.aggregate([ | ||||
|             { $project: { _id: 0 } }, | ||||
|             { $project: { o: { $objectToArray: '$orderProducts' } } }, | ||||
|             { $unwind: '$o' }, | ||||
|             { $group: { | ||||
|                     _id: '$o.v.productId', | ||||
|                     title: { $last: '$o.v.title' }, | ||||
|                     productImage: { $last: '$o.v.productImage' }, | ||||
|                     count: | ||||
|                     { | ||||
|                         $sum: '$o.v.quantity' | ||||
|                     } | ||||
|             } }, | ||||
|             { $sort: { count: -1 } }, | ||||
|             { $limit: 5 } | ||||
|         ]).toArray() | ||||
|     }; | ||||
| 
 | ||||
|     // Fix aggregate data
 | ||||
|     dashboardData.ordersAmount = dashboardData.ordersAmount[0].sum; | ||||
|     dashboardData.productsSold = dashboardData.productsSold[0].sum; | ||||
| 
 | ||||
|     res.render('dashboard', { | ||||
|         title: 'Cart dashboard', | ||||
|         session: req.session, | ||||
|         admin: true, | ||||
|         dashboardData, | ||||
|         themes: common.getThemes(), | ||||
|         message: common.clearSessionValue(req.session, 'message'), | ||||
|         messageType: common.clearSessionValue(req.session, 'messageType'), | ||||
|         helpers: req.handlebars.helpers, | ||||
|         config: req.app.config | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| // settings
 | ||||
| router.get('/admin/settings', restrict, (req, res) => { | ||||
|     res.render('settings', { | ||||
|  | @ -427,7 +478,7 @@ router.post('/admin/file/upload', restrict, checkAccess, upload.single('uploadFi | |||
|         if(!product.productImage){ | ||||
|             await db.products.updateOne({ _id: common.getId(req.body.productId) }, { $set: { productImage: imagePath } }, { multi: false }); | ||||
|         } | ||||
|          // Return success message
 | ||||
|         // Return success message
 | ||||
|         res.status(200).json({ message: 'File uploaded successfully' }); | ||||
|         return; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,10 +0,0 @@ | |||
| {{> partials/menu}} | ||||
| <div class="row"> | ||||
| <div class="col-sm-9"> | ||||
| 	<div class="col-sm-10"> | ||||
|         <div class="page-header"> | ||||
|             <h2>Dashboard</h2> | ||||
|         </div> | ||||
| 	</div> | ||||
| </div> | ||||
| </div> | ||||
|  | @ -0,0 +1,70 @@ | |||
| {{> partials/menu}} | ||||
| <div class="col-sm-9"> | ||||
|     <div class="col-sm-12"> | ||||
|         <h2>Dashboard</h2> | ||||
|     </div> | ||||
|     <div class="row"> | ||||
|         <div class="col-6 bottom-pad-30"> | ||||
|             <div class="card"> | ||||
|                 <div class="card-body text-center"> | ||||
|                     <h5 class="card-title">Orders placed</h5> | ||||
|                     <h6 class="card-subtitle mb-2 text-muted">Total number of orders placed</h6> | ||||
|                     <h4 class="card-text text-danger"> | ||||
|                         {{dashboardData.ordersCount}} | ||||
|                     </h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="col-6 bottom-pad-30"> | ||||
|             <div class="card"> | ||||
|                 <div class="card-body text-center"> | ||||
|                     <h5 class="card-title">Order total value</h5> | ||||
|                     <h6 class="card-subtitle mb-2 text-muted">Total value of orders placed</h6> | ||||
|                     <h4 class="card-text text-danger"> | ||||
|                         {{currencySymbol config.currencySymbol}}{{formatAmount dashboardData.ordersAmount}} | ||||
|                     </h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="col-6 bottom-pad-30"> | ||||
|             <div class="card"> | ||||
|                 <div class="card-body text-center"> | ||||
|                     <h5 class="card-title">Products for sale</h5> | ||||
|                     <h6 class="card-subtitle mb-2 text-muted">Number of products for sale</h6> | ||||
|                     <h4 class="card-text text-danger"> | ||||
|                         {{dashboardData.productsCount}} | ||||
|                     </h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="col-6 bottom-pad-30"> | ||||
|             <div class="card"> | ||||
|                 <div class="card-body text-center"> | ||||
|                     <h5 class="card-title">Total products sold</h5> | ||||
|                     <h6 class="card-subtitle mb-2 text-muted">Number of products sold</h6> | ||||
|                     <h4 class="card-text text-danger"> | ||||
|                         {{dashboardData.productsSold}} | ||||
|                     </h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="col-12 bottom-pad-30"> | ||||
|             <div class="card"> | ||||
|                 <div class="card-body text-center"> | ||||
|                     <h5 class="card-title">Top products sold</h5> | ||||
|                     <ul class="list-unstyled"> | ||||
|                         {{#each dashboardData.topProducts}} | ||||
|                         <li class="media my-4 align-middle"> | ||||
|                             <img src="{{this.productImage}}" class="col-2 mr-3 img-fluid" alt=""> | ||||
|                             <div class="media-body"> | ||||
|                                 <h5 class="mt-3 mb-1">{{this.title}}</h5> | ||||
|                                 <h4 class="mt-4">Sold: <span class="text-danger">{{this.count}}</span></h4> | ||||
|                             </div> | ||||
|                         </li> | ||||
|                         {{/each}} | ||||
|                     </ul> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | @ -23,8 +23,8 @@ | |||
|         {{#if admin}} | ||||
|         <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/codemirror.min.css" /> | ||||
|         {{/if}} | ||||
| 		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> | ||||
|         <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tokenfield/0.12.0/css/bootstrap-tokenfield.min.css"> | ||||
|         <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" integrity="sha256-+N4/V/SbAFiW1MPBCXnfnP9QSN3+Keu+NlB+0ev/YKQ=" crossorigin="anonymous" /> | ||||
|         <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tokenfield/0.12.0/css/bootstrap-tokenfield.min.css" integrity="sha256-4qBzeX420hElp9/FzsuqUNqVobcClz1BjnXoxUDSYQ0=" crossorigin="anonymous" /> | ||||
|         <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> | ||||
|         <link rel="stylesheet" href="/stylesheets/codemirror-style.min.css"> | ||||
|         <link rel="stylesheet" href="/stylesheets/style{{config.env}}.css"> | ||||
|  | @ -32,26 +32,23 @@ | |||
|         <link rel="stylesheet" href="/stylesheets/admin{{config.env}}.css"> | ||||
|         {{/if}} | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/esm/popper.min.js" integrity="sha256-g491Yv8nsEVSfQ6aonhVVFXoX5vF2uJQIU0hVNRg4JQ=" crossorigin="anonymous"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha256-WqU1JavFxSAMcLP2WIOI+GB2zWmShMI82mTpLDcqFUg=" crossorigin="anonymous"></script> | ||||
|         {{#if admin}} | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/codemirror.min.js"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/css/css.min.js"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/xml/xml.min.js"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/codemirror.min.js" integrity="sha256-K1exjHe1X4MP24jRizgBaSbUDUrNhFDRSwGoEYGmtJE=" crossorigin="anonymous"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/css/css.min.js" integrity="sha256-D5oJ11cOmRhXSYWELwG2U/XYH3YveZJr9taRYLZ2DSM=" crossorigin="anonymous"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/xml/xml.min.js" integrity="sha256-ERFGS58tayDq5kkyNwd/89iZZ+HglMH7eYXxG1hxTvA=" crossorigin="anonymous"></script> | ||||
|         {{/if}} | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.7/js/tether.min.js" integrity="sha256-4lietOiwRDBKx1goZZbRiwB06L+/bPYEGDIKZt82bgg=" crossorigin="anonymous"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.9/validator.min.js"></script> | ||||
|         <script src="/javascripts/jquery.bootpag.min.js"></script> | ||||
|         <script src="/javascripts/cssbeautify.min.js"></script> | ||||
|         {{#unless admin}} | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> | ||||
|         {{/unless}} | ||||
|         <script src="/javascripts/common{{config.env}}.js"></script> | ||||
| 		<script src="/javascripts/expressCart{{config.env}}.js"></script> | ||||
|         {{#if admin}} | ||||
|         <script src="/javascripts/admin{{config.env}}.js"></script> | ||||
|         {{/if}} | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tokenfield/0.12.0/bootstrap-tokenfield.min.js"></script> | ||||
|         <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tokenfield/0.12.0/bootstrap-tokenfield.min.js" integrity="sha256-jdwX0QzXB7z7Xc7Vz0ovtIHWO5qIZWg0aLcGv44JDgE=" crossorigin="anonymous"></script> | ||||
|         <!-- SEO data --> | ||||
|         <link rel="canonical" href="{{config.baseUrl}}" /> | ||||
|         <meta name="referrer" content="origin" /> | ||||
|  |  | |||
|  | @ -1,24 +1,25 @@ | |||
| <div class="col-sm-3"> | ||||
|     <h2> </h2> | ||||
|     <ul class="list-group"> | ||||
|         <li class="list-group-item"><i class="far fa-chart-bar fa-icon"></i>   <a href="/admin/dashboard">{{ @root.__ "Dashboard" }}</a></li> | ||||
|         <li class="list-group-item"><strong>Products</strong></li> | ||||
|         {{#ifCond session.isAdmin '===' true}} | ||||
|         <li class="list-group-item"><i class="fa fa-plus-square-o fa-icon" aria-hidden="true"></i>   <a href="/admin/product/new">{{ @root.__ "New" }}</a></li> | ||||
|         <li class="list-group-item"><i class="far fa-plus-square"></i>   <a href="/admin/product/new">{{ @root.__ "New" }}</a></li> | ||||
|         {{/ifCond}} | ||||
|         <li class="list-group-item"><i class="fa fa-list fa-icon" aria-hidden="true"></i>   <a href="/admin/products">{{ @root.__ "List" }}</a></li> | ||||
|         <li class="list-group-item"><i class="fas fa-list fa-icon"></i>   <a href="/admin/products">{{ @root.__ "List" }}</a></li> | ||||
|         <li class="list-group-item"><strong>Orders</strong></li> | ||||
|         <li class="list-group-item"><i class="fa fa-cube fa-icon" aria-hidden="true"></i>   <a href="/admin/orders">{{ @root.__ "List" }}</a></li> | ||||
|         <li class="list-group-item"><i class="fas fa-cube fa-icon"></i>   <a href="/admin/orders">{{ @root.__ "List" }}</a></li> | ||||
|         <li class="list-group-item"><strong>Customers</strong></li> | ||||
|         <li class="list-group-item"><i class="fa fa-users fa-icon" aria-hidden="true"></i>   <a href="/admin/customers">{{ @root.__ "List" }}</a></li> | ||||
|         <li class="list-group-item"><i class="fas fa-users fa-icon"></i>   <a href="/admin/customers">{{ @root.__ "List" }}</a></li> | ||||
|         <li class="list-group-item"><strong>Users</strong></li> | ||||
|         {{#ifCond session.isAdmin '===' true}} | ||||
|         <li class="list-group-item"><i class="fa fa-user-plus fa-icon" aria-hidden="true"></i>   <a href="/admin/user/new">{{ @root.__ "New" }}</a></li> | ||||
|         <li class="list-group-item"><i class="fa fa-user fa-icon" aria-hidden="true"></i>   <a href="/admin/users">{{ @root.__ "Edit" }}</a></li> | ||||
|         <li class="list-group-item"><i class="fas fa-user-plus fa-icon"></i>   <a href="/admin/user/new">{{ @root.__ "New" }}</a></li> | ||||
|         <li class="list-group-item"><i class="fas fa-user fa-icon"></i>   <a href="/admin/users">{{ @root.__ "Edit" }}</a></li> | ||||
|         {{/ifCond}} | ||||
|         <li class="list-group-item"><i class="fa fa-user-circle-o fa-icon" aria-hidden="true"></i>   <a href="/admin/user/edit/{{session.userId}}">My Account</a></li> | ||||
|         <li class="list-group-item"><i class="fas fa-user-circle"></i>   <a href="/admin/user/edit/{{session.userId}}">My Account</a></li> | ||||
|         <li class="list-group-item"><strong>{{ @root.__ "Settings" }}</strong></li> | ||||
|         <li class="list-group-item"><i class="fa fa-cog fa-icon" aria-hidden="true"></i>   <a href="/admin/settings">{{ @root.__ "General settings" }}</a></li> | ||||
|         <li class="list-group-item"><i class="fa fa-bars fa-icon" aria-hidden="true"></i>   <a href="/admin/settings/menu">Menu</a></li> | ||||
|         <li class="list-group-item"><i class="fa fa-file-o fa-icon" aria-hidden="true"></i>   <a href="/admin/settings/pages">{{ @root.__ "Static pages" }}</a></li> | ||||
|         <li class="list-group-item"><i class="fas fa-cog fa-icon"></i>   <a href="/admin/settings">{{ @root.__ "General settings" }}</a></li> | ||||
|         <li class="list-group-item"><i class="fas fa-bars fa-icon"></i>   <a href="/admin/settings/menu">Menu</a></li> | ||||
|         <li class="list-group-item"><i class="far fa-file fa-icon"></i>   <a href="/admin/settings/pages">{{ @root.__ "Static pages" }}</a></li> | ||||
|     </ul> | ||||
| </div> | ||||
		Loading…
	
		Reference in New Issue