Added a dashboard

master
Mark Moffat 2019-12-30 22:58:37 +10:30
parent 7a1cb87e2a
commit 7cef58dae3
10 changed files with 166 additions and 47 deletions

View File

@ -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

View File

@ -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"
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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>

70
views/dashboard.hbs Normal file
View File

@ -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>

View File

@ -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" />

View File

@ -1,24 +1,25 @@
<div class="col-sm-3">
<h2>&nbsp;</h2>
<ul class="list-group">
<li class="list-group-item"><i class="far fa-chart-bar fa-icon"></i> &nbsp; <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> &nbsp; <a href="/admin/product/new">{{ @root.__ "New" }}</a></li>
<li class="list-group-item"><i class="far fa-plus-square"></i> &nbsp; <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> &nbsp; <a href="/admin/products">{{ @root.__ "List" }}</a></li>
<li class="list-group-item"><i class="fas fa-list fa-icon"></i> &nbsp; <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> &nbsp; <a href="/admin/orders">{{ @root.__ "List" }}</a></li>
<li class="list-group-item"><i class="fas fa-cube fa-icon"></i> &nbsp; <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> &nbsp; <a href="/admin/customers">{{ @root.__ "List" }}</a></li>
<li class="list-group-item"><i class="fas fa-users fa-icon"></i> &nbsp; <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> &nbsp; <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> &nbsp; <a href="/admin/users">{{ @root.__ "Edit" }}</a></li>
<li class="list-group-item"><i class="fas fa-user-plus fa-icon"></i> &nbsp; <a href="/admin/user/new">{{ @root.__ "New" }}</a></li>
<li class="list-group-item"><i class="fas fa-user fa-icon"></i> &nbsp; <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> &nbsp; <a href="/admin/user/edit/{{session.userId}}">My Account</a></li>
<li class="list-group-item"><i class="fas fa-user-circle"></i> &nbsp; <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> &nbsp; <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> &nbsp; <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> &nbsp; <a href="/admin/settings/pages">{{ @root.__ "Static pages" }}</a></li>
<li class="list-group-item"><i class="fas fa-cog fa-icon"></i> &nbsp; <a href="/admin/settings">{{ @root.__ "General settings" }}</a></li>
<li class="list-group-item"><i class="fas fa-bars fa-icon"></i> &nbsp; <a href="/admin/settings/menu">Menu</a></li>
<li class="list-group-item"><i class="far fa-file fa-icon"></i> &nbsp; <a href="/admin/settings/pages">{{ @root.__ "Static pages" }}</a></li>
</ul>
</div>