Added global search to the admin UI

master
Mark Moffat 2020-01-07 21:15:23 +10:30
parent a880c0078f
commit 0d44ce7cbb
11 changed files with 331 additions and 5 deletions

View File

@ -643,6 +643,13 @@ const getCountryList = () => {
return countryArray; return countryArray;
}; };
const cleanAmount = (amount) => {
if(amount){
return parseInt(amount.toString().replace('.', ''));
}
return amount;
};
module.exports = { module.exports = {
allowedMimeType, allowedMimeType,
fileSizeLimit, fileSizeLimit,
@ -676,5 +683,6 @@ module.exports = {
newId, newId,
getData, getData,
hooker, hooker,
getCountryList getCountryList,
cleanAmount
}; };

View File

@ -1,5 +1,5 @@
/* eslint-disable prefer-arrow-callback, no-var, no-tabs */ /* eslint-disable prefer-arrow-callback, no-var, no-tabs */
/* globals showNotification, slugify */ /* globals showNotification, slugify, numeral */
$(document).ready(function (){ $(document).ready(function (){
$(document).on('click', '#btnGenerateAPIkey', function(e){ $(document).on('click', '#btnGenerateAPIkey', function(e){
e.preventDefault(); e.preventDefault();
@ -646,4 +646,78 @@ $(document).ready(function (){
showNotification(msg.responseJSON.message, 'danger'); showNotification(msg.responseJSON.message, 'danger');
}); });
}); });
$('#global-search-value').on('keyup', (e) => {
if($('#global-search-value').val() === ''){
$('#global-search-results').empty();
$('#global-search-results').addClass('invisible');
}
// Search when 3 or more characters are entered
if($('#global-search-value').val().length > 3){
$('#global-search').html('<span class="fa fa-spinner fa-spin"></span>');
globalSearch();
}
});
$('#globalSearchModal').on('shown.bs.modal', function (){
$('#global-search-value').focus();
});
$('body').on('click', '.gr-click', (e) => {
$('#global-search-value').val();
const url = $(e.currentTarget).closest('.global-result').attr('data-url');
if(url){
window.location = url;
}
});
}); });
function globalSearch(){
$('#global-search-results').empty();
$.ajax({
type: 'POST',
url: '/admin/searchall',
data: {
searchValue: $('#global-search-value').val()
}
}).done((res) => {
$('#global-search').html('<i class="fal fa-search"></i>');
let hasResult = false;
res.customers.forEach((value) => {
hasResult = true;
let result = '<li class="list-group-item global-result text-center" data-url="/admin/customer/view/' + value._id + '">';
result += '<div class="row">';
result += '<div class="col global-result-type gr-click"><i class="fas fa-users"></i> Customer</div>';
result += '<div class="col global-result-detail gr-click">' + value.firstName + ' ' + value.lastName + '</div>';
result += '<div class="col global-result-detail gr-click">' + value.email + '</div>';
result += '</div></li>';
$('#global-search-results').append(result);
});
res.orders.forEach((value) => {
hasResult = true;
let result = '<li class="list-group-item global-result text-center" data-url="/admin/order/view/' + value._id + '">';
result += '<div class="row">';
result += '<div class="col global-result-type gr-click"><i class="fas fa-cube"></i> Order</div>';
result += '<div class="col global-result-detail gr-click">' + value.orderFirstname + ' ' + value.orderLastname + '</div>';
result += '<div class="col global-result-detail gr-click">' + value.orderEmail + '</div>';
result += '</div></li>';
$('#global-search-results').append(result);
});
res.products.forEach((value) => {
hasResult = true;
let result = '<li class="list-group-item global-result text-center" data-url="/admin/product/edit/' + value._id + '">';
result += '<div class="row">';
result += '<div class="col global-result-type gr-click"><i class="fas fa-box-open"></i> Product</div>';
result += '<div class="col global-result-detail gr-click">' + value.productTitle + '</div>';
result += '<div class="col global-result-detail gr-click">' + numeral(value.productPrice).format('0.00') + '</div>';
result += '</div></li>';
$('#global-search-results').append(result);
});
if(hasResult === true){
$('#global-search-results').removeClass('invisible');
}
});
}

File diff suppressed because one or more lines are too long

View File

@ -109,6 +109,50 @@ body .popover {
color: #838b8f; color: #838b8f;
font-size: 20px; font-size: 20px;
} }
.global-result-type {
color: #8d8d8d;
}
.global-result:hover {
background-color: #007bff;
}
.global-result:hover .global-result-detail,
.global-result:hover .global-result-type,
.global-result:hover .global-result-type .fal {
color: #fff !important;
}
.global-result a {
text-decoration: none !important;
}
.global-result:hover {
cursor: pointer;
}
.global-result:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.global-result {
border-left: 0;
border-right: 0;
}
.global-search-modal-content,
.global-search-modal-header {
background-color: transparent;
border: none;
}
#global-search-results {
padding-right: 0;
border-bottom-left-radius: 0.3rem;
border-bottom-right-radius: 0.3rem;
}
.global-search-form {
margin-bottom: 0;
}
#global-search-value {
border-bottom-right-radius: 0;
}
.search-input-addon {
border-bottom-left-radius: 0;
}
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
.navbar-default .navbar-brand { .navbar-default .navbar-brand {
padding-top: 10px; padding-top: 10px;

View File

@ -1 +1 @@
.btn-outline-primary,.btn-warning{color:#fff!important;background-color:#000;border-color:#000}.btn-outline-danger{color:#fff!important;background-color:#cc4135;border-color:#cc4135}.has-error div,.has-error input,.has-error textarea{border-color:#cc4135}#frm_search,.search-bar-input,.search-bar-input .btn{padding-top:10px;height:45px}.productsWrapper{padding-right:10px;padding-left:10px}.searchBarWrapper{padding-right:0;padding-left:0}.footer{padding-top:20px}.product-price{padding-bottom:0}.navbarMenuWrapper{background-color:#f5f5f5}.navbarMenu>ul>li>a:hover{color:#cc4135!important}.navbarMenu{padding-right:0;padding-left:0}.product-wrapper>a:hover{color:#cc4135!important}#navbar,#navbar>.navbar-nav,#navbar>.navbar-nav>li>a,.navbar-header,.navbar-static-top{margin-bottom:0;height:100px!important}#navbar>.navbar-nav>li>a{padding-top:35px}.pagination>li>a{background-color:#cc4135!important}body .popover{display:none!important}.navbar-brand{color:#cc4135!important;letter-spacing:4px;padding-left:20px!important;padding-top:0!important;height:80px!important;font-size:55px!important}.navbar-brand,.navbar-brand-image{height:80px;padding-left:10px;padding-top:10px}.navbar-default .badge{background-color:#cc4135}#empty-cart:active,#empty-cart:active:hover,#empty-cart:focus,#empty-cart:hover,.pushy-link:active,.pushy-link:active:hover,.pushy-link:focus,.pushy-link:hover{border-color:#cc4135;background-color:#cc4135}.navActive>a{margin-bottom:0;padding-top:15px;border-bottom:5px solid #cc4135}#navbar,#navbar>.navbar-nav,#navbar>.navbar-nav>li>a,.navbar-header,.navbar-static-top{background-color:#fff}.navbar-default .navbar-nav>li>a{color:#838b8f;font-size:20px}@media only screen and (max-width:768px){.navbar-default .navbar-brand{padding-top:10px}.navbar-default .navbar-nav>li>a{font-size:16px}.searchBarWrapper{padding-top:10px}.navbarMenuWrapper{padding-left:0;padding-right:0}.navbarMenuOuter{padding-left:0;padding-right:0}.navActive>a{color:#fff!important}.navbarMenu{padding-right:7.5px;padding-left:7.5px}.navActive>a{color:#fff!important;background-color:#cc4135;border-bottom:none}.footer{padding-top:10px}} .btn-outline-primary,.btn-warning{color:#fff!important;background-color:#000;border-color:#000}.btn-outline-danger{color:#fff!important;background-color:#cc4135;border-color:#cc4135}.has-error div,.has-error input,.has-error textarea{border-color:#cc4135}#frm_search,.search-bar-input,.search-bar-input .btn{padding-top:10px;height:45px}.productsWrapper{padding-right:10px;padding-left:10px}.searchBarWrapper{padding-right:0;padding-left:0}.footer{padding-top:20px}.product-price{padding-bottom:0}.navbarMenuWrapper{background-color:#f5f5f5}.navbarMenu>ul>li>a:hover{color:#cc4135!important}.navbarMenu{padding-right:0;padding-left:0}.product-wrapper>a:hover{color:#cc4135!important}#navbar,#navbar>.navbar-nav,#navbar>.navbar-nav>li>a,.navbar-header,.navbar-static-top{margin-bottom:0;height:100px!important}#navbar>.navbar-nav>li>a{padding-top:35px}.pagination>li>a{background-color:#cc4135!important}body .popover{display:none!important}.navbar-brand{color:#cc4135!important;letter-spacing:4px;padding-left:20px!important;padding-top:0!important;height:80px!important;font-size:55px!important}.navbar-brand,.navbar-brand-image{height:80px;padding-left:10px;padding-top:10px}.navbar-default .badge{background-color:#cc4135}#empty-cart:active,#empty-cart:active:hover,#empty-cart:focus,#empty-cart:hover,.pushy-link:active,.pushy-link:active:hover,.pushy-link:focus,.pushy-link:hover{border-color:#cc4135;background-color:#cc4135}.navActive>a{margin-bottom:0;padding-top:15px;border-bottom:5px solid #cc4135}#navbar,#navbar>.navbar-nav,#navbar>.navbar-nav>li>a,.navbar-header,.navbar-static-top{background-color:#fff}.navbar-default .navbar-nav>li>a{color:#838b8f;font-size:20px}.global-result-type{color:#8d8d8d}.global-result:hover{background-color:#007bff}.global-result:hover .global-result-detail,.global-result:hover .global-result-type,.global-result:hover .global-result-type .fal{color:#fff!important}.global-result a{text-decoration:none!important}.global-result:hover{cursor:pointer}.global-result:first-child{border-top-left-radius:0;border-top-right-radius:0}.global-result{border-left:0;border-right:0}.global-search-modal-content,.global-search-modal-header{background-color:transparent;border:none}#global-search-results{padding-right:0;border-bottom-left-radius:.3rem;border-bottom-right-radius:.3rem}.global-search-form{margin-bottom:0}#global-search-value{border-bottom-right-radius:0}.search-input-addon{border-bottom-left-radius:0}@media only screen and (max-width:768px){.navbar-default .navbar-brand{padding-top:10px}.navbar-default .navbar-nav>li>a{font-size:16px}.searchBarWrapper{padding-top:10px}.navbarMenuWrapper{padding-left:0;padding-right:0}.navbarMenuOuter{padding-left:0;padding-right:0}.navActive>a{color:#fff!important}.navbarMenu{padding-right:7.5px;padding-left:7.5px}.navActive>a{color:#fff!important;background-color:#cc4135;border-bottom:none}.footer{padding-top:10px}}

View File

@ -116,6 +116,61 @@ body .popover{display:none !important; }
font-size: 20px; font-size: 20px;
} }
.global-result-type {
color: #8d8d8d;
}
.global-result:hover {
background-color: #007bff;
}
.global-result:hover .global-result-detail,
.global-result:hover .global-result-type,
.global-result:hover .global-result-type .fal {
color: #fff !important;
}
.global-result a {
text-decoration: none !important;
}
.global-result:hover {
cursor: pointer;
}
.global-result:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.global-result {
border-left: 0;
border-right: 0;
}
.global-search-modal-content, .global-search-modal-header{
background-color: transparent;
border: none;
}
#global-search-results{
padding-right: 0;
border-bottom-left-radius: .3rem;
border-bottom-right-radius: .3rem;
}
.global-search-form{
margin-bottom: 0;
}
#global-search-value {
border-bottom-right-radius: 0;
}
.search-input-addon {
border-bottom-left-radius: 0;
}
@media only screen and (max-width: 768px){ @media only screen and (max-width: 768px){
.navbar-default .navbar-brand { .navbar-default .navbar-brand {
padding-top: 10px; padding-top: 10px;

View File

@ -203,6 +203,14 @@ input[type=number]::-webkit-outer-spin-button {
margin-top: -7px; margin-top: -7px;
} }
.pad-right-10{
padding-right: 10px;
}
.pad-right-15{
padding-right: 15px;
}
.no-pad-left{ .no-pad-left{
padding-left: 0px; padding-left: 0px;
} }

View File

@ -159,6 +159,12 @@ input[type=number]::-webkit-outer-spin-button {
.list-group-input-pad select { .list-group-input-pad select {
margin-top: -7px; margin-top: -7px;
} }
.pad-right-10 {
padding-right: 10px;
}
.pad-right-15 {
padding-right: 15px;
}
.no-pad-left { .no-pad-left {
padding-left: 0px; padding-left: 0px;
} }

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,10 @@ const mime = require('mime-type/with-db');
const ObjectId = require('mongodb').ObjectID; const ObjectId = require('mongodb').ObjectID;
const router = express.Router(); const router = express.Router();
// Regex
const emailRegex = /\S+@\S+\.\S+/;
const numericRegex = /^\d*\.?\d*$/;
// Admin section // Admin section
router.get('/admin', restrict, (req, res, next) => { router.get('/admin', restrict, (req, res, next) => {
res.redirect('/admin/dashboard'); res.redirect('/admin/dashboard');
@ -497,4 +501,107 @@ router.post('/admin/testEmail', restrict, (req, res) => {
res.status(200).json({ message: 'Test email sent' }); res.status(200).json({ message: 'Test email sent' });
}); });
router.post('/admin/searchall', restrict, async (req, res, next) => {
const db = req.app.db;
const searchValue = req.body.searchValue;
const limitReturned = 5;
// Empty arrays
let customers = [];
let orders = [];
let products = [];
// Default queries
const customerQuery = {};
const orderQuery = {};
const productQuery = {};
// If an ObjectId is detected use that
if(ObjectId.isValid(req.body.searchValue)){
// Get customers
customers = await db.customers.find({
_id: ObjectId(searchValue)
})
.limit(limitReturned)
.sort({ created: 1 })
.toArray();
// Get orders
orders = await db.orders.find({
_id: ObjectId(searchValue)
})
.limit(limitReturned)
.sort({ orderDate: 1 })
.toArray();
// Get products
products = await db.products.find({
_id: ObjectId(searchValue)
})
.limit(limitReturned)
.sort({ productAddedDate: 1 })
.toArray();
return res.status(200).json({
customers,
orders,
products
});
}
// If email address is detected
if(emailRegex.test(req.body.searchValue)){
customerQuery.email = searchValue;
orderQuery.orderEmail = searchValue;
}else if(numericRegex.test(req.body.searchValue)){
// If a numeric value is detected
orderQuery.amount = common.cleanAmount(req.body.searchValue);
productQuery.productPrice = common.cleanAmount(req.body.searchValue);
}else{
// String searches
customerQuery.$or = [
{ firstName: { $regex: new RegExp(searchValue, 'img') } },
{ lastName: { $regex: new RegExp(searchValue, 'img') } }
];
orderQuery.$or = [
{ orderFirstname: { $regex: new RegExp(searchValue, 'img') } },
{ orderLastname: { $regex: new RegExp(searchValue, 'img') } }
];
productQuery.$or = [
{ productTitle: { $regex: new RegExp(searchValue, 'img') } },
{ productDescription: { $regex: new RegExp(searchValue, 'img') } }
];
}
// Get customers
if(Object.keys(customerQuery).length > 0){
customers = await db.customers.find(customerQuery)
.limit(limitReturned)
.sort({ created: 1 })
.toArray();
}
// Get orders
if(Object.keys(orderQuery).length > 0){
orders = await db.orders.find(orderQuery)
.limit(limitReturned)
.sort({ orderDate: 1 })
.toArray();
}
// Get products
if(Object.keys(productQuery).length > 0){
products = await db.products.find(productQuery)
.limit(limitReturned)
.sort({ productAddedDate: 1 })
.toArray();
}
return res.status(200).json({
customers,
orders,
products
});
});
module.exports = router; module.exports = router;

View File

@ -119,6 +119,7 @@
{{/unless}} {{/unless}}
{{#if admin}} {{#if admin}}
{{#if @root.session.user}} {{#if @root.session.user}}
<li class="nav-item"><a href="#" data-toggle="modal" data-target="#globalSearchModal" class="pad-right-15"><i class="fas fa-search"></i></a></li>
<li class="nav-item"><a href="/admin/logout"><i class="fas fa-sign-out-alt"></i> Logout</a></li> <li class="nav-item"><a href="/admin/logout"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
{{/if}} {{/if}}
{{/if}} {{/if}}
@ -176,5 +177,28 @@
</footer> </footer>
{{/if}} {{/if}}
<script src="/javascripts/pushy.min.js"></script> <script src="/javascripts/pushy.min.js"></script>
{{#if admin}}
<div class="modal fade" id="globalSearchModal" tabindex="-1" role="dialog" aria-labelledby="globalSearchModal"
aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content global-search-modal-content">
<div class="modal-header global-search-modal-header"></div>
<div class="modal-body">
<form>
<div class="form-group global-search-form">
<div class="input-group">
<div class="input-group-prepend">
<label class="input-group-text search-input-addon" for="global-search-value"><i class="fas fa-search"></i></label>
</div>
<input type="text" class="form-control form-control-lg" id="global-search-value">
</div>
</div>
</form>
<ul class="list-group col-12 invisible" id="global-search-results"></ul>
</div>
</div>
</div>
</div>
{{/if}}
</body> </body>
</html> </html>