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