Redesigned the querying for the sale duplicates screen to use aggregation; Finished the styling of the sale duplicate screen; Tested the functionality of sale duplicates; Added a way to show hidden (ignored) duplicates.
This commit is contained in:
@@ -1,37 +1,44 @@
|
||||
<template name="Measures">
|
||||
<div id="measures">
|
||||
<div class="tableControls">
|
||||
<span class="controlLabel">Show Hidden</span>
|
||||
<div class="toggleShowHidden checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="showHidden"><span></span>
|
||||
</label>
|
||||
{{#if Template.subscriptionsReady}}
|
||||
<div class="tableControls">
|
||||
<span class="controlLabel">Show Hidden</span>
|
||||
<div class="toggleShowHidden checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="showHidden"><span></span>
|
||||
</label>
|
||||
</div>
|
||||
<span class="pagination">
|
||||
<span class="prevMeasures noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextMeasures noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="pagination">
|
||||
<span class="prevMeasures noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextMeasures noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name {{>MeasureSearch columnName='name'}}</th>
|
||||
<th class="postfix">Postfix {{>MeasureSearch columnName='postfix'}}</th>
|
||||
<th class="actions">Actions <span class="newMeasureButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span></th>
|
||||
</tr>
|
||||
<!--<button type="button" name="newMeasureButton"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>-->
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if displayNewMeasure}}
|
||||
{{> MeasureEditor isNew=true}}
|
||||
{{/if}}
|
||||
{{#each measures}}
|
||||
{{> Measure}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name {{>MeasureSearch columnName='name'}}</th>
|
||||
<th class="postfix">Postfix {{>MeasureSearch columnName='postfix'}}</th>
|
||||
<th class="actions">Actions <span class="newMeasureButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span></th>
|
||||
</tr>
|
||||
<!--<button type="button" name="newMeasureButton"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>-->
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if displayNewMeasure}}
|
||||
{{> MeasureEditor isNew=true}}
|
||||
{{/if}}
|
||||
{{#each measures}}
|
||||
{{> Measure}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
163
imports/ui/Measures.import.styl
vendored
163
imports/ui/Measures.import.styl
vendored
@@ -1,6 +1,9 @@
|
||||
#measures
|
||||
margin: 20px 20px
|
||||
display: table
|
||||
content-box: border-box
|
||||
padding: 10px 20px
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
|
||||
.tableControls
|
||||
@@ -18,77 +21,89 @@
|
||||
top: -4px
|
||||
display: inline-block
|
||||
|
||||
.tableContainer
|
||||
width: 100%
|
||||
margin-bottom: 20px
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
|
||||
table
|
||||
table-layout: fixed
|
||||
.listRow
|
||||
display: table-row
|
||||
.listCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
width: 100%
|
||||
.measureSearch
|
||||
margin: 3px 0 2px 1px
|
||||
.measureEditorTd
|
||||
background: #deeac0
|
||||
input[name="name"], input[name="postfix"]
|
||||
width: 100%
|
||||
.editorDiv
|
||||
margin: 4px 0
|
||||
label
|
||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
||||
font-size: .9em
|
||||
padding-bottom: 4px
|
||||
select2
|
||||
font-size: .4em
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.postfix
|
||||
width: auto
|
||||
> th.actions
|
||||
width: 90px
|
||||
text-align: center
|
||||
.newMeasureButton
|
||||
margin-top: 4px
|
||||
padding: 0px 12px
|
||||
.fa-plus-circle
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newMeasureButton.active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
display: inline-block
|
||||
.fa-plus-circle
|
||||
display: none
|
||||
> tbody
|
||||
> tr
|
||||
.actionRemove
|
||||
color: #F77
|
||||
.actionEdit
|
||||
color: #44F
|
||||
.editorApply
|
||||
color: green
|
||||
.editorCancel
|
||||
color: red
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
.actionActivate
|
||||
color: #158b18
|
||||
.actionHide
|
||||
color: #6a0707
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
> tr.hidden
|
||||
background-color: #e995ff
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
.actionShow
|
||||
color: #027905
|
||||
> tr.hidden:hover
|
||||
background-color: #ffb5ff
|
||||
.tableContainer
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: auto
|
||||
table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
.measureSearch
|
||||
margin: 3px 0 2px 1px
|
||||
.measureEditorTd
|
||||
background: #deeac0
|
||||
input[name="name"], input[name="postfix"]
|
||||
width: 100%
|
||||
.editorDiv
|
||||
margin: 4px 0
|
||||
label
|
||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
||||
font-size: .9em
|
||||
padding-bottom: 4px
|
||||
select2
|
||||
font-size: .4em
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.postfix
|
||||
width: auto
|
||||
> th.actions
|
||||
width: 90px
|
||||
text-align: center
|
||||
.newMeasureButton
|
||||
margin-top: 4px
|
||||
padding: 0px 12px
|
||||
.fa-plus-circle
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newMeasureButton.active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
display: inline-block
|
||||
.fa-plus-circle
|
||||
display: none
|
||||
> tbody
|
||||
> tr
|
||||
.actionRemove
|
||||
color: #F77
|
||||
.actionEdit
|
||||
color: #44F
|
||||
.editorApply
|
||||
color: green
|
||||
.editorCancel
|
||||
color: red
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
.actionActivate
|
||||
color: #158b18
|
||||
.actionHide
|
||||
color: #6a0707
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
> tr.hidden
|
||||
background-color: #e995ff
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
.actionShow
|
||||
color: #027905
|
||||
> tr.hidden:hover
|
||||
background-color: #ffb5ff
|
||||
27
imports/ui/MiscManagement.html
Normal file
27
imports/ui/MiscManagement.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<template name="MiscManagement">
|
||||
<div id="miscManagement">
|
||||
{{#if Template.subscriptionsReady}}
|
||||
<div class="controls">
|
||||
<a href="javascript:" class="cleanDates">Clean Dates (removes time components)</a><br/>
|
||||
<!--<a href="javascript:" class="importMissingSalesData">Import Sales Data (JSON)</a><br/>-->
|
||||
<a href="javascript:" class="clearLogs">Clear Logs</a><br/>
|
||||
<a href="javascript:" class="countDuplicateSales">Count Duplicate Sales</a><br/>
|
||||
<a href="javascript:" class="deleteDuplicateSales">Delete Duplicate Sales</a><br/>
|
||||
<div class="logCount">{{logCount}}</div>
|
||||
</div>
|
||||
<div class="pageContentRow">
|
||||
<div class="pageContentCell">
|
||||
<div class="pageContentContainer">
|
||||
<ul class="logs">
|
||||
{{#each logs}}
|
||||
<li>{{message}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
35
imports/ui/MiscManagement.import.styl
vendored
Normal file
35
imports/ui/MiscManagement.import.styl
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
#miscManagement
|
||||
display: table
|
||||
content-box: border-box
|
||||
padding: 10px 20px
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
|
||||
.controls
|
||||
text-align: right
|
||||
margin-right: 20px
|
||||
|
||||
.pageContentRow
|
||||
display: table-row
|
||||
.pageContentCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
width: 100%
|
||||
.pageContentContainer
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: auto
|
||||
.logs
|
||||
list-style-type: none
|
||||
height: 100%
|
||||
:hover
|
||||
background: #CCC
|
||||
76
imports/ui/MiscManagement.js
Normal file
76
imports/ui/MiscManagement.js
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
import './MiscManagement.html';
|
||||
import '/imports/util/selectize/selectize.js'
|
||||
|
||||
let PREFIX = "MiscManagement";
|
||||
|
||||
Meteor.subscribe("logs");
|
||||
Meteor.subscribe("products");
|
||||
Meteor.subscribe("venues");
|
||||
Meteor.subscribe("measures");
|
||||
|
||||
Template.MiscManagement.helpers({
|
||||
logs: function() {
|
||||
return Meteor.collections.Logs.find({}, {sort: {date: 1}});
|
||||
},
|
||||
logCount: function() {
|
||||
return Meteor.collections.Logs.find({}).count();
|
||||
}
|
||||
});
|
||||
Template.MiscManagement.events({
|
||||
"click .cleanDates": function(event, template) {
|
||||
Meteor.call("cleanDates");
|
||||
},
|
||||
"click .importMissingSalesData": function(event, template) {
|
||||
console.log("Calling importMissingSales");
|
||||
Meteor.call("importMissingSales");
|
||||
},
|
||||
"click .clearLogs": function(event, template) {
|
||||
Meteor.call("clearLogs");
|
||||
},
|
||||
"click .countDuplicateSales": function(event, template) {
|
||||
Meteor.log.info("Starting to count duplicates...");
|
||||
|
||||
let products = Meteor.collections.Products.find({}).fetch();
|
||||
let venues = Meteor.collections.Venues.find({}).fetch();
|
||||
let measures = Meteor.collections.Measures.find({}).fetch();
|
||||
let productNameMap = {};
|
||||
let venueNameMap = {};
|
||||
let measureNameMap = {};
|
||||
|
||||
for(let i = 0; i < products.length; i++) {
|
||||
productNameMap[products[i]._id] = products[i].name;
|
||||
}
|
||||
for(let i = 0; i < venues.length; i++) {
|
||||
venueNameMap[venues[i]._id] = venues[i].name;
|
||||
}
|
||||
for(let i = 0; i < measures.length; i++) {
|
||||
measureNameMap[measures[i]._id] = measures[i].name;
|
||||
}
|
||||
|
||||
Meteor.call("countSales", function(err, result) {
|
||||
if(err) Meteor.log.error(err);
|
||||
else {
|
||||
let salesCount = result;
|
||||
|
||||
Meteor.call("countDuplicateSales", function(err, result) {
|
||||
if(err) Meteor.log.error(err);
|
||||
else {
|
||||
Meteor.log.info("Duplicate Sales Counted: " + result.length + " out of " + salesCount + " total sales.");
|
||||
|
||||
for(let i = 0; i < result.length; i++) {
|
||||
let sale = result[i][0];
|
||||
Meteor.log.info("\tdate: " + sale.date + " product: " + productNameMap[sale.productId] + " venue: " + venueNameMap[sale.venueId] + " measure" + measureNameMap[sale.measureId] + " price: " + sale.price.toFixed(2) + " amount: " + sale.amount + " id: " + sale._id);
|
||||
sale = result[i][1];
|
||||
Meteor.log.info("\tdate: " + sale.date + " product: " + productNameMap[sale.productId] + " venue: " + venueNameMap[sale.venueId] + " measure" + measureNameMap[sale.measureId] + " price: " + sale.price.toFixed(2) + " amount: " + sale.amount + " id: " + sale._id);
|
||||
Meteor.log.info(" -- ");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
"click .deleteDuplicateSales": function(event, template) {
|
||||
Meteor.call("deleteDuplicateSales");
|
||||
}
|
||||
});
|
||||
@@ -1,54 +1,61 @@
|
||||
<template name="Pricing">
|
||||
<div id="pricing">
|
||||
<div class="controls">
|
||||
<div class="measureGroup" style="vertical-align: bottom">
|
||||
<label class='controlLabel'>Selected Measure: </label>
|
||||
<select name="measures">
|
||||
{{#each measures}}
|
||||
<option value="{{_id}}">{{name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="controlGroup" style="text-align: center">
|
||||
<label class='controlLabel'>New Price: </label>
|
||||
<input type="number" class="price" name="price" min="0" data-schema-key='currency' value="{{price}}" required>
|
||||
<input type="button" class="btn btn-success applyButton" title="Applies the price to selected products." value="Apply">
|
||||
<input type="button" class="btn btn-danger resetButton" title="Resets this form." value="Reset">
|
||||
<br/>
|
||||
<!--<span class="toggleUpdateHistory toggleButton clickable">Set Prev</span>-->
|
||||
<div class="previousSettings outline">
|
||||
<span class="controlLabel">Set Previous:</span>
|
||||
<div class="toggleUpdateHistory checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="setPrevious" checked><span></span>
|
||||
</label>
|
||||
{{#if Template.subscriptionsReady}}
|
||||
<div class="controls">
|
||||
<div class="measureGroup" style="vertical-align: bottom">
|
||||
<label class='controlLabel'>Selected Measure: </label>
|
||||
<select name="measures">
|
||||
{{#each measures}}
|
||||
<option value="{{_id}}">{{name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="controlGroup" style="text-align: center">
|
||||
<label class='controlLabel'>New Price: </label>
|
||||
<input type="number" class="price" name="price" min="0" data-schema-key='currency' value="{{price}}" required>
|
||||
<input type="button" class="btn btn-success applyButton" title="Applies the price to selected products." value="Apply">
|
||||
<input type="button" class="btn btn-danger resetButton" title="Resets this form." value="Reset">
|
||||
<br/>
|
||||
<!--<span class="toggleUpdateHistory toggleButton clickable">Set Prev</span>-->
|
||||
<div class="previousSettings outline">
|
||||
<span class="controlLabel">Set Previous:</span>
|
||||
<div class="toggleUpdateHistory checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="setPrevious" checked><span></span>
|
||||
</label>
|
||||
</div>
|
||||
<label class='controlLabel' style="margin-left: 10px">Effective: </label>
|
||||
<input type="date" class="form-control" name="date" data-schema-key='date' required>
|
||||
</div>
|
||||
</div>
|
||||
<span class="pagination">
|
||||
<span class="prevProducts noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextProducts noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name</th>
|
||||
<th class="current">Current</th>
|
||||
<th class="changeDate">Change Date</th>
|
||||
<th class="previous">Previous</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each product}}
|
||||
{{> PricingForProduct}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<label class='controlLabel' style="margin-left: 10px">Effective: </label>
|
||||
<input type="date" class="form-control" name="date" data-schema-key='date' required>
|
||||
</div>
|
||||
</div>
|
||||
<span class="pagination">
|
||||
<span class="prevProducts noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextProducts noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name</th>
|
||||
<th class="current">Current</th>
|
||||
<th class="changeDate">Change Date</th>
|
||||
<th class="previous">Previous</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each product}}
|
||||
{{> PricingForProduct}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
62
imports/ui/Pricing.import.styl
vendored
62
imports/ui/Pricing.import.styl
vendored
@@ -1,6 +1,9 @@
|
||||
#pricing
|
||||
margin: 20px 20px
|
||||
display: table
|
||||
content-box: border-box
|
||||
padding: 10px 20px
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
|
||||
.controls
|
||||
@@ -60,26 +63,39 @@
|
||||
.resetButton
|
||||
margin-left: 20px
|
||||
|
||||
.tableContainer
|
||||
width: 100%
|
||||
margin-bottom: 20px
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
table
|
||||
table-layout: fixed
|
||||
.listRow
|
||||
display: table-row
|
||||
.listCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
width: 100%
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.current
|
||||
width: 200px
|
||||
> th.previous
|
||||
width: 200px
|
||||
> th.changeDate
|
||||
width: 200px
|
||||
> tbody
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
.tableContainer
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: auto
|
||||
table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.current
|
||||
width: 200px
|
||||
> th.previous
|
||||
width: 200px
|
||||
> th.changeDate
|
||||
width: 200px
|
||||
> tbody
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
@@ -1,6 +1,13 @@
|
||||
|
||||
import './Pricing.html';
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
* The Product object has a prices field which is an object whose fields names are Measure ID's. Each field value (for each Measure ID) is an object that has a 'price', 'effectiveDate', and 'previousPrice'.
|
||||
* The effectiveDate field stores the date as a number in the format YYYYMMDD. Converting this number into a local date is done with moment(sale.date.toString(), "YYYYMMDD").toDate(), and converting it to a number from a date can be accomplished with ~~(moment(date).format("YYYYMMDD")), where the ~~ is a bitwise not and converts a string to a number quickly and reliably.
|
||||
* Because the structure of the Product object is so complicated, the normal checking that is done by the framework cannot be used.
|
||||
*/
|
||||
|
||||
let QUERY_LIMIT = 20;
|
||||
let PREFIX = "Pricing.";
|
||||
|
||||
@@ -70,7 +77,7 @@ Template.Pricing.events({
|
||||
Meteor.call("clearProductPrice", productIds, measureId)
|
||||
}
|
||||
else {
|
||||
date = moment(date ? date : new Date().toDateInputValue(), "YYYY-MM-DD").toDate();
|
||||
date = ~~(moment(date ? date : new Date().toDateInputValue(), "YYYY-MM-DD").format("YYYYMMDD")); // The ~~ is a bitwise not which converts the string into a number in the format of YYYYMMDD for storage in the database; to avoid timezone issues.
|
||||
setPrevious = setPrevious == true || setPrevious == 'on' || setPrevious == "true" || setPrevious == "yes";
|
||||
|
||||
if(setPrevious == true && !date) {
|
||||
@@ -117,9 +124,8 @@ Template.PricingForProduct.helpers({
|
||||
},
|
||||
priceChangeDate: function() {
|
||||
let measureId = Session.get(PREFIX + "selectedMeasure");
|
||||
let date = this.prices && measureId && this.prices[measureId] && this.prices[measureId].effectiveDate ? this.prices[measureId].effectiveDate : undefined;
|
||||
|
||||
return date ? moment(date).format("MM/DD/YYYY (w)") : "-";
|
||||
return this.prices && measureId && this.prices[measureId] && this.prices[measureId].effectiveDate ? moment(this.prices[measureId].effectiveDate.toString(), "YYYYMMDD").format("MM/DD/YYYY (w)") : "-";
|
||||
},
|
||||
rowClass: function() {
|
||||
return this.deactivated ? "deactivated" : "";
|
||||
|
||||
@@ -26,20 +26,24 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name {{>ProductTag_ProductSearch columnName='name'}}</th>
|
||||
<th class="tags">Tags {{>ProductTag_ProductSearch columnName='tags' collectionQueryColumnName='name' collection='ProductTags' collectionResultColumnName='_id'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each products}}
|
||||
{{> ProductTag_Product}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name {{>ProductTag_ProductSearch columnName='name'}}</th>
|
||||
<th class="tags">Tags {{>ProductTag_ProductSearch columnName='tags' collectionQueryColumnName='name' collection='ProductTags' collectionResultColumnName='_id'}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each products}}
|
||||
{{> ProductTag_Product}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{/if}}
|
||||
|
||||
54
imports/ui/ProductTags.import.styl
vendored
54
imports/ui/ProductTags.import.styl
vendored
@@ -1,6 +1,9 @@
|
||||
#productTags
|
||||
margin: 20px 20px
|
||||
display: table
|
||||
content-box: border-box
|
||||
padding: 10px 20px
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
|
||||
.tagInfo
|
||||
@@ -107,25 +110,38 @@
|
||||
display: table-cell
|
||||
width: 240px
|
||||
vertical-align: bottom;
|
||||
.tableContainer
|
||||
width: 100%
|
||||
margin-bottom: 20px
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
table
|
||||
table-layout: fixed
|
||||
.listRow
|
||||
display: table-row
|
||||
.listCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
width: 100%
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.tags
|
||||
width: auto
|
||||
> tbody
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
.tableContainer
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: auto
|
||||
table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.tags
|
||||
width: auto
|
||||
> tbody
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
td.roles
|
||||
.role
|
||||
padding: 4px 4px
|
||||
|
||||
@@ -1,39 +1,46 @@
|
||||
<template name="Products">
|
||||
<div id="products">
|
||||
<div class="tableControls">
|
||||
<span class="controlLabel">Show Hidden</span>
|
||||
<div class="toggleShowHidden checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="showHidden"><span></span>
|
||||
</label>
|
||||
{{#if Template.subscriptionsReady}}
|
||||
<div class="tableControls">
|
||||
<span class="controlLabel">Show Hidden</span>
|
||||
<div class="toggleShowHidden checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="showHidden"><span></span>
|
||||
</label>
|
||||
</div>
|
||||
<span class="pagination">
|
||||
<span class="prevProducts noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextProducts noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="pagination">
|
||||
<span class="prevProducts noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextProducts noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name {{>ProductSearch columnName='name'}}</th>
|
||||
<th class="tags">Tags {{>ProductSearch columnName='tags' collectionQueryColumnName='name' collection='ProductTags' collectionResultColumnName='_id'}}</th>
|
||||
<th class="aliases">Aliases {{>ProductSearch columnName='aliases'}}</th>
|
||||
<th class="measures">Measures {{>ProductSearch columnName='measures' collectionQueryColumnName='name' collection='Measures' collectionResultColumnName='_id'}}</th>
|
||||
<th class="actions">Actions <span class="newProductButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span></th>
|
||||
</tr>
|
||||
<!--<button type="button" name="newProductButton"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>-->
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if displayNewProduct}}
|
||||
{{> ProductEditor isNew=true}}
|
||||
{{/if}}
|
||||
{{#each products}}
|
||||
{{> Product}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name {{>ProductSearch columnName='name'}}</th>
|
||||
<th class="tags">Tags {{>ProductSearch columnName='tags' collectionQueryColumnName='name' collection='ProductTags' collectionResultColumnName='_id'}}</th>
|
||||
<th class="aliases">Aliases {{>ProductSearch columnName='aliases'}}</th>
|
||||
<th class="measures">Measures {{>ProductSearch columnName='measures' collectionQueryColumnName='name' collection='Measures' collectionResultColumnName='_id'}}</th>
|
||||
<th class="actions">Actions <span class="newProductButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span></th>
|
||||
</tr>
|
||||
<!--<button type="button" name="newProductButton"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>-->
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if displayNewProduct}}
|
||||
{{> ProductEditor isNew=true}}
|
||||
{{/if}}
|
||||
{{#each products}}
|
||||
{{> Product}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
171
imports/ui/Products.import.styl
vendored
171
imports/ui/Products.import.styl
vendored
@@ -1,6 +1,9 @@
|
||||
#products
|
||||
margin: 20px 20px
|
||||
display: table
|
||||
content-box: border-box
|
||||
padding: 10px 20px
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
|
||||
.tableControls
|
||||
@@ -18,81 +21,93 @@
|
||||
top: -4px
|
||||
display: inline-block
|
||||
|
||||
.tableContainer
|
||||
width: 100%
|
||||
margin-bottom: 20px
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
|
||||
table
|
||||
table-layout: fixed
|
||||
.listRow
|
||||
display: table-row
|
||||
.listCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
width: 100%
|
||||
.productSearch
|
||||
margin: 3px 0 2px 1px
|
||||
.productEditorTd
|
||||
background: #deeac0
|
||||
input[name="name"], .productTagsEditor, .productAliasesEditor, .productMeasuresEditor
|
||||
width: 100%
|
||||
.editorDiv
|
||||
margin: 4px 0
|
||||
label
|
||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
||||
font-size: .9em
|
||||
padding-bottom: 4px
|
||||
select2
|
||||
font-size: .4em
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.tags
|
||||
width: 220px
|
||||
> th.aliases
|
||||
width: 220px
|
||||
> th.measures
|
||||
width: 220px
|
||||
> th.actions
|
||||
width: 90px
|
||||
text-align: center
|
||||
.newProductButton
|
||||
margin-top: 4px
|
||||
padding: 0px 12px
|
||||
.fa-plus-circle
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newProductButton.active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
display: inline-block
|
||||
.fa-plus-circle
|
||||
display: none
|
||||
> tbody
|
||||
> tr
|
||||
.actionRemove
|
||||
color: #F77
|
||||
.actionEdit
|
||||
color: #44F
|
||||
.editorApply
|
||||
color: green
|
||||
.editorCancel
|
||||
color: red
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
.actionActivate
|
||||
color: #158b18
|
||||
.actionHide
|
||||
color: #6a0707
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
> tr.hidden
|
||||
background-color: #e995ff
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
.actionShow
|
||||
color: #027905
|
||||
> tr.hidden:hover
|
||||
background-color: #ffb5ff
|
||||
.tableContainer
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: auto
|
||||
table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
.productSearch
|
||||
margin: 3px 0 2px 1px
|
||||
.productEditorTd
|
||||
background: #deeac0
|
||||
input[name="name"], .productTagsEditor, .productAliasesEditor, .productMeasuresEditor
|
||||
width: 100%
|
||||
.editorDiv
|
||||
margin: 4px 0
|
||||
label
|
||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
||||
font-size: .9em
|
||||
padding-bottom: 4px
|
||||
select2
|
||||
font-size: .4em
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.tags
|
||||
width: 220px
|
||||
> th.aliases
|
||||
width: 220px
|
||||
> th.measures
|
||||
width: 220px
|
||||
> th.actions
|
||||
width: 90px
|
||||
text-align: center
|
||||
.newProductButton
|
||||
margin-top: 4px
|
||||
padding: 0px 12px
|
||||
.fa-plus-circle
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newProductButton:active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
display: inline-block
|
||||
.fa-plus-circle
|
||||
display: none
|
||||
> tbody
|
||||
> tr
|
||||
.actionRemove
|
||||
color: #F77
|
||||
.actionEdit
|
||||
color: #44F
|
||||
.editorApply
|
||||
color: green
|
||||
.editorCancel
|
||||
color: red
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
.actionActivate
|
||||
color: #158b18
|
||||
.actionHide
|
||||
color: #6a0707
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
> tr.hidden
|
||||
background-color: #e995ff
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
.actionShow
|
||||
color: #027905
|
||||
> tr.hidden:hover
|
||||
background-color: #ffb5ff
|
||||
61
imports/ui/SaleDuplicates.html
Normal file
61
imports/ui/SaleDuplicates.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<template name="SaleDuplicates">
|
||||
<div id="saleDuplicates">
|
||||
<div class="controls">
|
||||
<div class="pageControls">
|
||||
<input class="duplicateScan btn btn-info" type="button" value="Scan For Duplicates"/>
|
||||
</div>
|
||||
<div class="tableControls">
|
||||
<span class="controlLabel">Show Hidden</span>
|
||||
<div class="toggleShowHidden checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="showHidden" {{showHidden}}><span></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="amount noselect nonclickable">Duplicates</th>
|
||||
<th class="amount noselect nonclickable">Amount</th>
|
||||
<th class="product noselect nonclickable">Product <br/>{{>SaleDuplicateSearch columnName='productName' width='90%'}}</th>
|
||||
<th class="price noselect nonclickable">Price</th>
|
||||
<th class="measure noselect nonclickable">Measure</th>
|
||||
<th class="saleDate noselect nonclickable">Date (Week)</th>
|
||||
<th class="createdDate noselect nonclickable">Created On</th>
|
||||
<th class="venue noselect nonclickable">Venue</th>
|
||||
<th class="actions noselect nonclickable">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each sales}}
|
||||
{{> SaleDuplicate}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="SaleDuplicate">
|
||||
<tr class="{{duplicateClasses}}">
|
||||
<td class="tdLarge noselect nonclickable center">{{duplicateCount}}</td>
|
||||
<td class="tdLarge noselect nonclickable center">{{amount}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{productName}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{formatPrice price}}{{#if showTotalPrice amount}} ({{formatTotalPrice price amount}}){{/if}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{measureName}}</td> <!-- measureName measureId -->
|
||||
<td class="tdLarge noselect nonclickable left">{{formatDateAndWeek date}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{formatDateTime createdAt}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{venueName}}</td>
|
||||
<td class="tdLarge noselect left actions"><i class="fa fa-check fa-lg clickable ignoreDuplicatesButton {{#if ignoreDuplicates}}hidden{{/if}}" title="Ignore All Duplicates" aria-hidden="true"></i> <i class="fa fa-minus-circle fa-lg clickable removeAllDuplicatesButton" title="Remove All Duplicates" aria-hidden="true"></i> <span class="clickable removeOneDuplicateButton" title="Remove One Duplicate"><i class="fa fa-minus-circle fa-lg" aria-hidden="true"></i><sup>1</sup></span></td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<template name="SaleDuplicateSearch">
|
||||
<input type="text" class="searchInput" placeholder="Filter..." value="{{searchValue}}" style="padding-right: 10px; width: {{width}}"/>
|
||||
</template>
|
||||
149
imports/ui/SaleDuplicates.import.styl
vendored
Normal file
149
imports/ui/SaleDuplicates.import.styl
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
#saleDuplicates
|
||||
display: table
|
||||
content-box: border-box
|
||||
padding: 10px 20px
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
.controls
|
||||
text-align: left
|
||||
display: table
|
||||
width: 100%
|
||||
.pageControls
|
||||
padding: 4px 8px
|
||||
margin: 4px 8px
|
||||
display: table-cell
|
||||
width: 240px
|
||||
.tableControls
|
||||
text-align: right
|
||||
padding: 4px 8px
|
||||
margin: 4px 12px 4px 8px
|
||||
display: table-cell
|
||||
.toggleShowHidden
|
||||
margin: 0 40px 0 0
|
||||
position: relative
|
||||
top: -4px
|
||||
display: inline-block
|
||||
.listRow
|
||||
display: table-row
|
||||
.listCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
width: 100%
|
||||
.tableContainer
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
//width: 100%
|
||||
//margin-bottom: 20px
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: auto
|
||||
//height: 100%
|
||||
label
|
||||
font-size: 10px
|
||||
font-weight: 800
|
||||
table
|
||||
table-layout: fixed
|
||||
min-width: 100%
|
||||
.saleRemove
|
||||
color: red
|
||||
margin-left: 8px
|
||||
.saleEdit
|
||||
color: darkblue
|
||||
margin-right: 8px
|
||||
.editorApply
|
||||
color: green
|
||||
.editorCancel
|
||||
color: red
|
||||
thead
|
||||
> tr
|
||||
> th.amount
|
||||
width: 90px
|
||||
> th.product
|
||||
width: auto
|
||||
min-width: 140px
|
||||
> th.price
|
||||
width: 140px
|
||||
> th.measure
|
||||
width: 100px
|
||||
> th.saleDate
|
||||
width: 140px
|
||||
> th.createdDate
|
||||
width: 100px
|
||||
> th.venue
|
||||
width: 160px
|
||||
> th.actions
|
||||
width: 90px
|
||||
tbody
|
||||
> tr
|
||||
> td.actions
|
||||
.ignoreDuplicatesButton
|
||||
padding: 0 2px
|
||||
color: green
|
||||
.ignoreDuplicatesButton:hover
|
||||
color: #00bb00
|
||||
.ignoreDuplicatesButton:active
|
||||
color: black
|
||||
.ignoreDuplicatesButton.hidden
|
||||
visibility: hidden
|
||||
.removeAllDuplicatesButton, .removeOneDuplicateButton
|
||||
padding: 0 2px
|
||||
color: #a00000
|
||||
.removeAllDuplicatesButton:hover, .removeOneDuplicateButton:hover
|
||||
color: red
|
||||
.removeAllDuplicatesButton:active, .removeOneDuplicateButton:active
|
||||
color: black
|
||||
> tr.hidden:nth-child(odd)
|
||||
background-color: #f4f0ab
|
||||
> tr.hidden:nth-child(even)
|
||||
background-color: #fff6c0
|
||||
> tr.hidden:hover
|
||||
background-color: #ded
|
||||
|
||||
.editComment
|
||||
color: grey
|
||||
.hasComment
|
||||
color: black
|
||||
.actionEdit
|
||||
margin-right: 6px
|
||||
color: #44F
|
||||
.saleEditor
|
||||
.heading
|
||||
font-size: 2em
|
||||
font-family: verdana, arial, helvetica, sans-serif
|
||||
text-transform: uppercase
|
||||
font-weight: 800
|
||||
margin: 6px 0 14px 0
|
||||
.priceContainer
|
||||
display: table
|
||||
width: 100%
|
||||
.price
|
||||
display: table-cell
|
||||
padding-right: 10px
|
||||
.priceButtons
|
||||
display: table-cell
|
||||
width: 1.5em
|
||||
.setDefaultPrice
|
||||
font-size: 1.5em
|
||||
padding: 6px 8px
|
||||
margin-left: 8px
|
||||
border-radius: 8px
|
||||
.setDefaultPrice:hover
|
||||
text-shadow: 0px 0px 6px #00b900
|
||||
.setDefaultPrice:active
|
||||
text-shadow: 0px 0px 6px grey
|
||||
.insertSaleForm
|
||||
.form-group, label
|
||||
text-align: left
|
||||
.formGroupHeading
|
||||
font-size: 1.6em
|
||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
||||
font-style: normal
|
||||
font-variant: normal
|
||||
font-weight: 500
|
||||
212
imports/ui/SaleDuplicates.js
Normal file
212
imports/ui/SaleDuplicates.js
Normal file
@@ -0,0 +1,212 @@
|
||||
|
||||
import './SaleDuplicates.html';
|
||||
import '/imports/util/selectize/selectize.js';
|
||||
import swal from 'sweetalert2';
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
* The Sale object has a date field which stores the date as a number in the format YYYYMMDD. Converting this number into a local date is done with moment(sale.date.toString(), "YYYYMMDD").toDate(), and converting it to a number from a date can be accomplished with ~~(moment(date).format("YYYYMMDD")), where the ~~ is a bitwise not and converts a string to a number quickly and reliably.
|
||||
*/
|
||||
|
||||
let PREFIX = "SaleDuplicates.";
|
||||
let DuplicateSales = new Meteor.Collection("duplicateSales");
|
||||
let duplicateSalesSubscription;
|
||||
|
||||
Template.SaleDuplicates.onCreated(function() {
|
||||
let template = Template.instance();
|
||||
|
||||
//Tracker.autorun(function() {
|
||||
// let query = _.clone(Session.get(PREFIX + 'searchQuery'));
|
||||
//
|
||||
// duplicateSalesSubscription = template.subscribe("duplicateSales", query, Session.get(PREFIX + "showHidden"));
|
||||
//});
|
||||
|
||||
Tracker.autorun(function() {
|
||||
duplicateSalesSubscription = template.subscribe("duplicateSales", null, Session.get(PREFIX + "showHidden"));
|
||||
});
|
||||
});
|
||||
Template.SaleDuplicates.onDestroyed(function() {
|
||||
if(duplicateSalesSubscription) {
|
||||
duplicateSalesSubscription.stop();
|
||||
}
|
||||
});
|
||||
Template.SaleDuplicates.helpers({
|
||||
sales: function() {
|
||||
let dbQuery = [];
|
||||
let query = _.clone(Session.get(PREFIX + 'searchQuery'));
|
||||
|
||||
if(query) {
|
||||
// Add each query requirement sent by the client.
|
||||
_.each(_.keys(query), function(key) {
|
||||
//if(_.isObject(query[key])) dbQuery.push({[key]: query[key]});
|
||||
if(_.isObject(query[key])) {
|
||||
if(query[key].type === 'dateRange') {
|
||||
if(query[key].start && query[key].end)
|
||||
dbQuery.push({[key]: {$gte: query[key].start, $lte: query[key].end}});
|
||||
else if(query[key].start)
|
||||
dbQuery.push({[key]: {$gte: query[key].start}});
|
||||
else if(query[key].end)
|
||||
dbQuery.push({[key]: {$lte: query[key].end}});
|
||||
// Do nothing if a start and/or end are not provided.
|
||||
}
|
||||
else {
|
||||
dbQuery.push({[key]: query[key]});
|
||||
}
|
||||
}
|
||||
else if(_.isNumber(query[key])) dbQuery.push({[key]: query[key]});
|
||||
else {
|
||||
let searchValue = query[key];
|
||||
let searches = searchValue && searchValue.length > 0 ? searchValue.split(/\s+/) : undefined;
|
||||
|
||||
for(let search of searches) {
|
||||
dbQuery.push({[key]: {$regex: '\\b' + search, $options: 'i'}});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(dbQuery.length > 1) dbQuery = {$and: dbQuery};
|
||||
else if(dbQuery.length == 1) dbQuery = dbQuery[0];
|
||||
else dbQuery = {};
|
||||
|
||||
return DuplicateSales.find(dbQuery, {sort: {date: -1, productName: 1}});
|
||||
},
|
||||
showHidden: function() {
|
||||
return Session.get(PREFIX + "showHidden") ? "checked": "";
|
||||
}
|
||||
});
|
||||
Template.SaleDuplicates.events({
|
||||
'click .duplicateScan': function(event, template) {
|
||||
Meteor.call("markDuplicateSales", function(err, result) {
|
||||
Meteor.log.error(err);
|
||||
});
|
||||
},
|
||||
'change input[name="showHidden"]': function(event, template) {
|
||||
//console.log("changed " + $(event.target).prop('checked'));
|
||||
Session.set(PREFIX + "showHidden", $(event.target).prop('checked'));
|
||||
}
|
||||
});
|
||||
|
||||
Template.SaleDuplicate.helpers({
|
||||
//measureName: function(id) {
|
||||
// return Meteor.collections.Measures.findOne({_id: id}, {fields: {name: 1}}).name;
|
||||
//},
|
||||
//venueName: function(id) {
|
||||
// return Meteor.collections.Venues.findOne({_id: id}, {fields: {name: 1}}).name;
|
||||
//},
|
||||
//productName: function(id) {
|
||||
// return Meteor.collections.Products.findOne({_id: id}, {fields: {name: 1}}).name;
|
||||
//},
|
||||
formatDateAndWeek: function(date) {
|
||||
return moment.utc(date.toString(), "YYYYMMDD").utc().format("MM/DD/YYYY (w)");
|
||||
},
|
||||
formatDateTime: function(date) {
|
||||
return moment.utc(date).format("MM/DD/YYYY");
|
||||
},
|
||||
formatPrice: function(price) {
|
||||
return price.toLocaleString("en-US", {style: 'currency', currency: 'USD', minimumFractionDigits: 2});
|
||||
},
|
||||
formatTotalPrice: function(price, amount) {
|
||||
return (price * amount).toLocaleString("en-US", {style: 'currency', currency: 'USD', minimumFractionDigits: 2});
|
||||
},
|
||||
showTotalPrice: function(amount) {
|
||||
return amount > 1;
|
||||
},
|
||||
duplicateClasses: function() {
|
||||
return this.ignoreDuplicates ? "hidden" : "";
|
||||
}
|
||||
});
|
||||
Template.SaleDuplicate.events({
|
||||
"click .ignoreDuplicatesButton": function(event, template) {
|
||||
Meteor.call('ignoreDuplicateSales', this._id, function(err, result) {
|
||||
if(err) sAlert.error(err);
|
||||
//else sAlert.success("Duplicates Ignored");
|
||||
});
|
||||
},
|
||||
"click .removeAllDuplicatesButton": function(event, template) {
|
||||
let _this = this;
|
||||
swal({
|
||||
title: "Are you sure?",
|
||||
text: "This will permanently remove ALL duplicate sales.",
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "Yes"
|
||||
}).then(
|
||||
function(isConfirm) {
|
||||
if(isConfirm) {
|
||||
Meteor.call('removeDuplicateSales', _this._id, function(err, result) {
|
||||
if(err) sAlert.error(err);
|
||||
//else sAlert.success("Duplicates Removed");
|
||||
});
|
||||
}
|
||||
},
|
||||
function(dismiss) {
|
||||
}
|
||||
);
|
||||
},
|
||||
"click .removeOneDuplicateButton": function(event, template) {
|
||||
let _this = this;
|
||||
swal({
|
||||
title: "Are you sure?",
|
||||
text: "This will permanently remove ONE duplicate sale.",
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#DD6B55",
|
||||
confirmButtonText: "Yes"
|
||||
}).then(
|
||||
function(isConfirm) {
|
||||
if(isConfirm) {
|
||||
Meteor.call('removeDuplicateSales', _this._id, true, function(err, result) {
|
||||
if(err) sAlert.error(err);
|
||||
//else sAlert.success("Duplicates Removed");
|
||||
});
|
||||
}
|
||||
},
|
||||
function(dismiss) {
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Template.SaleDuplicateSearch.helpers({
|
||||
searchValue: function() {
|
||||
let searchFields = Session.get(PREFIX + 'searchFields');
|
||||
|
||||
return (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : '';
|
||||
}
|
||||
});
|
||||
Template.SaleDuplicateSearch.events({
|
||||
"keyup .searchInput": _.throttle(function(event, template) {
|
||||
let searchQuery = Session.get(PREFIX + 'searchQuery') || {};
|
||||
let searchFields = Session.get(PREFIX + 'searchFields') || {};
|
||||
let searchValue = template.$(event.target).val();
|
||||
|
||||
if(searchValue) {
|
||||
if(this.number) searchValue = parseFloat(searchValue);
|
||||
|
||||
// A collection name will be provided if there is a related table of data that will contain the text provided and will map to an ID that is then searched for in the current table of data.
|
||||
// For example we are displaying a table of Sales which has the ID of a Product. The Product table has a Name field and the search box searches for Product Names. The ID's of the Products found should be used to filter the Sales by Product ID.
|
||||
if(this.collection) {
|
||||
let ids = Meteor.collections[this.collection].find({[this.collectionQueryColumnName]: {$regex: searchValue, $options: 'i'}}, {fields: {[this.collectionResultColumnName]: 1}}).fetch();
|
||||
|
||||
//Convert the ids to an array of ids instead of an array of objects containing an id.
|
||||
for(let i = 0; i < ids.length; i++) {ids[i] = ids[i]._id;}
|
||||
searchQuery[this.columnName] = {$in: ids};
|
||||
searchFields[this.columnName] = searchValue;
|
||||
}
|
||||
else {
|
||||
searchFields[this.columnName] = searchQuery[this.columnName] = searchValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Remove columns from the search query whose values are empty so we don't bother the database with them.
|
||||
delete searchQuery[this.columnName];
|
||||
delete searchFields[this.columnName];
|
||||
}
|
||||
|
||||
Session.set(PREFIX + 'searchQuery', searchQuery);
|
||||
Session.set(PREFIX + 'searchFields', searchFields);
|
||||
Session.set(PREFIX + 'skipCount', 0); //Reset the paging of the results.
|
||||
}, 500)
|
||||
});
|
||||
@@ -1,18 +1,23 @@
|
||||
<template name="Sales">
|
||||
<div id="salesMain">
|
||||
{{#if Template.subscriptionsReady}}
|
||||
<div class="tableControls">
|
||||
<select name="sortSelect">
|
||||
<option value="date" selected>Sale Date</option>
|
||||
<option value="createdAt">Data Entry Date</option>
|
||||
</select>
|
||||
<div class="pagination">
|
||||
<span class="prevButton noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextButton noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
<div class="controls">
|
||||
<div class="pageControls">
|
||||
<input type="button" class="showDuplicates btn btn-info" style="margin-right: 30px" value="Duplicate Analysis"/>
|
||||
</div>
|
||||
<div class="tableControls">
|
||||
<select name="sortSelect" class="form-control" style="width: auto; display: inline;">
|
||||
<option value="date" selected>Sale Date</option>
|
||||
<option value="createdAt">Data Entry Date</option>
|
||||
</select>
|
||||
<div class="pagination">
|
||||
<span class="prevButton noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextButton noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="salesListRow">
|
||||
<div class="salesListCell">
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
@@ -21,7 +26,7 @@
|
||||
<th class="product noselect nonclickable">Product <br/>{{>SaleSearch columnName='productId' collectionQueryColumnName='name' collection='Products' collectionResultColumnName='_id' width='90%'}}</th>
|
||||
<th class="price noselect nonclickable">Price {{>SaleSearch columnName='price' width='90%'}}</th>
|
||||
<th class="measure noselect nonclickable">Measure {{>SaleSearch columnName='measureId' collectionQueryColumnName='name' collection='Measures' collectionResultColumnName='_id' width='90%'}}</th>
|
||||
<th class="saleDate noselect nonclickable">Date (Week)</th>
|
||||
<th class="saleDate noselect nonclickable">Date (Week) {{>DateRangeSearch columnName='date' width='90%'}}</th>
|
||||
<th class="createdDate noselect nonclickable">Created On</th>
|
||||
<th class="venue noselect nonclickable">Venue {{>SaleSearch columnName='venueId' collectionQueryColumnName='name' collection='Venues' collectionResultColumnName='_id' width='90%'}}</th>
|
||||
<th class="actions noselect nonclickable">Actions <span class="newSaleButton btn btn-success" title="Create Sale"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span> <i class="fa fa-commenting fa-lg showOnlyComments clickable" title="Show Commented Sales" aria-hidden="true"></i></th>
|
||||
@@ -55,7 +60,7 @@
|
||||
<td class="tdLarge noselect nonclickable left">{{formatPrice price}}{{#if showTotalPrice amount}} ({{formatTotalPrice price amount}}){{/if}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{measureName measureId}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{formatDateAndWeek date}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{formatDate createdAt}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{formatDateTime createdAt}}</td>
|
||||
<td class="tdLarge noselect nonclickable left">{{venueName venueId}}</td>
|
||||
<td class="tdLarge noselect left"><i class="fa fa-pencil-square-o fa-lg actionEdit noselect clickable" title="Edit" aria-hidden="true"></i> <i class="fa fa-commenting fa-lg editComment noselect clickable {{commentClass}}" aria-hidden="true"></i> <i class="fa fa-times-circle fa-lg saleRemove noselect clickable" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
@@ -72,7 +77,7 @@
|
||||
<div class="editorDiv"><label>Venue</label><input name="venue" class="form-control" type="text" required/></div>
|
||||
</div>
|
||||
<div class="col-6-12">
|
||||
<div class="editorDiv"><label>Amount</label><input type="number" class="form-control amount" name="amount" min="0" step="0.01" data-schema-key='amount' value="{{amount}}" required></div>
|
||||
<div class="editorDiv"><label>Amount</label><input type="number" class="form-control amount" name="amount" min="0" step="1" data-schema-key='amount' value="{{amount}}" required></div>
|
||||
<div class="editorDiv"><label>Price</label><div class="priceContainer"><input type="number" class="form-control price" name="price" min="0" step="0.01" data-schema-key='currency' value="{{price}}" required><div class="priceButtons"><i class="fa fa-cogs setDefaultPrice noselect clickable" title="Calculate Default Price" aria-hidden="true"></i></div></div></div>
|
||||
<div class="editorDiv"><label>Total</label><input type="number" class="form-control total" name="total" data-schema-key='currency' value="{{total}}" tabindex="-1" readonly></div>
|
||||
</div>
|
||||
@@ -87,6 +92,10 @@
|
||||
<input type="text" class="searchInput" placeholder="Filter..." value="{{searchValue}}" style="padding-right: 10px; width: {{width}}"/>
|
||||
</template>
|
||||
|
||||
<template name="DateRangeSearch">
|
||||
<div style="padding-right: 10px; width: {{width}};"><input type="date" class="searchDateStartInput" value="{{startDate}}" data-schema-key='date' required> - <input type="date" class="searchDateEndInput" value="{{endDate}}" data-schema-key='date' required></div>
|
||||
</template>
|
||||
|
||||
<template name="InsertSale">
|
||||
<tr>
|
||||
<td colspan="8">
|
||||
|
||||
23
imports/ui/Sales.import.styl
vendored
23
imports/ui/Sales.import.styl
vendored
@@ -5,12 +5,23 @@
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
.tableControls
|
||||
text-align: right
|
||||
margin-right: 20px
|
||||
.salesListRow
|
||||
.controls
|
||||
text-align: left
|
||||
display: table
|
||||
width: 100%
|
||||
.pageControls
|
||||
padding: 4px 8px
|
||||
margin: 4px 8px
|
||||
display: table-cell
|
||||
width: 240px
|
||||
.tableControls
|
||||
text-align: right
|
||||
padding: 4px 8px
|
||||
margin: 4px 12px 4px 8px
|
||||
display: table-cell
|
||||
.listRow
|
||||
display: table-row
|
||||
.salesListCell
|
||||
.listCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
@@ -70,7 +81,7 @@
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newSaleButton.active
|
||||
.newSaleButton:active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
|
||||
@@ -3,30 +3,41 @@ import './Sales.html';
|
||||
import '/imports/util/selectize/selectize.js';
|
||||
import swal from 'sweetalert2';
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
* The Sale object has a date field which stores the date as a number in the format YYYYMMDD. Converting this number into a local date is done with moment(sale.date.toString(), "YYYYMMDD").toDate(), and converting it to a number from a date can be accomplished with ~~(moment(date).format("YYYYMMDD")), where the ~~ is a bitwise not and converts a string to a number quickly and reliably.
|
||||
*/
|
||||
|
||||
let QUERY_LIMIT = 20;
|
||||
let PREFIX = "Sales.";
|
||||
|
||||
Meteor.subscribe("products");
|
||||
Session.set(PREFIX + "sortOption", "date");
|
||||
Session.set(PREFIX + "showOnlyComments", false);
|
||||
|
||||
Tracker.autorun(function() {
|
||||
let sortOption = Session.get(PREFIX + "sortOption");
|
||||
let sort = sortOption == 'createdAt' ? {createdAt: -1} : {date: -1, createdAt: -1};
|
||||
let showOnlyComments = Session.get(PREFIX + "showOnlyComments");
|
||||
let query = _.clone(Session.get(PREFIX + 'searchQuery'));
|
||||
|
||||
if(showOnlyComments) {
|
||||
if(!query) query = {};
|
||||
query.comment = {$exists: true};
|
||||
}
|
||||
|
||||
Meteor.subscribe("sales", query, sort, QUERY_LIMIT, Session.get(PREFIX + 'skipCount'));
|
||||
Session.set(PREFIX + 'saleCount', Meteor.call('getSalesCount', Session.get(PREFIX + 'searchQuery')));
|
||||
});
|
||||
|
||||
Template.Sales.onCreated(function() {
|
||||
Session.set(PREFIX + "displayNewSale", false);
|
||||
|
||||
Meteor.subscribe("products");
|
||||
Session.set(PREFIX + "sortOption", "date");
|
||||
Session.set(PREFIX + "showOnlyComments", false);
|
||||
|
||||
Tracker.autorun(function() {
|
||||
let sortOption = Session.get(PREFIX + "sortOption");
|
||||
let sort = sortOption == 'createdAt' ? {createdAt: -1} : {date: -1, createdAt: -1};
|
||||
let showOnlyComments = Session.get(PREFIX + "showOnlyComments");
|
||||
let query = _.clone(Session.get(PREFIX + 'searchQuery'));
|
||||
|
||||
if(showOnlyComments) {
|
||||
if(!query) query = {};
|
||||
query.comment = {$exists: true};
|
||||
}
|
||||
|
||||
//if(Template.Sales.salesSubscription) Template.Sales.salesSubscription.stop();
|
||||
Template.Sales.salesSubscription = Meteor.subscribe("sales", query, sort, QUERY_LIMIT, Session.get(PREFIX + 'skipCount'));
|
||||
Session.set(PREFIX + 'saleCount', Meteor.call('getSalesCount', Session.get(PREFIX + 'searchQuery')));
|
||||
});
|
||||
});
|
||||
Template.Sales.onDestroyed(function() {
|
||||
if(Template.Sales.salesSubscription) {
|
||||
Template.Sales.salesSubscription.stop();
|
||||
}
|
||||
});
|
||||
Template.Sales.helpers({
|
||||
displayNewSale: function() {
|
||||
@@ -77,6 +88,9 @@ Template.Sales.events({
|
||||
|
||||
Session.set(PREFIX + "showOnlyComments", !$button.hasClass('on'));
|
||||
$button.toggleClass('on');
|
||||
},
|
||||
'click .showDuplicates': function(event, template) {
|
||||
FlowRouter.go('SaleDuplicates');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -93,10 +107,10 @@ Template.Sale.helpers({
|
||||
return Meteor.collections.Products.findOne({_id: id}, {fields: {name: 1}}).name;
|
||||
},
|
||||
formatDateAndWeek: function(date) {
|
||||
return moment(date).format("MM/DD/YYYY (w)");
|
||||
return moment.utc(date.toString(), "YYYYMMDD").utc().format("MM/DD/YYYY (w)");
|
||||
},
|
||||
formatDate: function(date) {
|
||||
return moment(date).format("MM/DD/YYYY");
|
||||
formatDateTime: function(date) {
|
||||
return moment.utc(date).format("MM/DD/YYYY");
|
||||
},
|
||||
formatPrice: function(price) {
|
||||
return price.toLocaleString("en-US", {style: 'currency', currency: 'USD', minimumFractionDigits: 2});
|
||||
@@ -162,10 +176,8 @@ Template.Sale.events({
|
||||
});
|
||||
|
||||
Template.SaleEditor.onCreated(function() {
|
||||
let _this = this;
|
||||
|
||||
this.product = Meteor.collections.Products.findOne({_id: this.data.productId});
|
||||
this.selectedDate = new ReactiveVar(this.data.date);
|
||||
this.selectedDate = new ReactiveVar(moment(this.data.date.toString(), "YYYYMMDD").toDate());
|
||||
this.selectedVenue = new ReactiveVar(Meteor.collections.Venues.findOne({_id: this.data.venueId}));
|
||||
this.price = new ReactiveVar(this.data.price);
|
||||
this.amount = new ReactiveVar(this.data.amount);
|
||||
@@ -207,7 +219,7 @@ Template.SaleEditor.events({
|
||||
|
||||
//If this product has pricing data for the given measure, then either use the price, or the previousPrice (if there is one and the effectiveDate is after the sale date).
|
||||
if(priceData) {
|
||||
if(priceData.effectiveDate && date && moment(priceData.effectiveDate).isAfter(date))
|
||||
if(priceData.effectiveDate && date && moment.utc(priceData.effectiveDate.toString(), "YYYYMMDD").isAfter(date))
|
||||
price = priceData.previousPrice;
|
||||
else
|
||||
price = priceData.price
|
||||
@@ -231,7 +243,7 @@ Template.SaleEditor.events({
|
||||
template.$('form[name="editSaleForm"]').data('bs.validator').validate(function(isValid) {
|
||||
if(isValid) {
|
||||
let id = template.data._id;
|
||||
let date = template.selectedDate.get();
|
||||
let date = ~~(moment(template.selectedDate.get()).format("YYYYMMDD")); // Note: The ~~ is a bitwise not that is a fast method of converting a string to a number.
|
||||
let venue = template.selectedVenue.get();
|
||||
let price = template.price.get();
|
||||
let amount = template.amount.get();
|
||||
@@ -245,36 +257,6 @@ Template.SaleEditor.events({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//let name = template.$("input[name='name']").val().trim();
|
||||
//let tags = template.$(".productTagsEditor").select2('data');
|
||||
//let aliases = template.$(".productAliasesEditor").select2('data');
|
||||
//let measures = template.$(".productMeasuresEditor").select2('data');
|
||||
//
|
||||
//tags = tags.map((n)=>n.id);
|
||||
//aliases = aliases.map((n)=>n.id);
|
||||
//measures = measures.map((n)=>n.id);
|
||||
//
|
||||
//if(Session.get(PREFIX + 'displayNewProduct')) {
|
||||
// Meteor.call("createProduct", name, tags, aliases, measures, function(error, result) {
|
||||
// if(error) sAlert.error(error);
|
||||
// else {
|
||||
// sAlert.success("Product created.");
|
||||
// Session.set(PREFIX + 'displayNewProduct', false);
|
||||
// template.parentTemplate().$('.newProductButton').removeClass('active');
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
//else {
|
||||
// Meteor.call("updateProduct", this._id, name, tags, aliases, measures, function(error, result) {
|
||||
// if(error) sAlert.error(error);
|
||||
// else {
|
||||
// sAlert.success("Product updated.");
|
||||
// Session.set(PREFIX + "editedProduct", undefined);
|
||||
// template.parentTemplate().$('.newProductButton').removeClass('active');
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -289,11 +271,13 @@ Template.SaleSearch.events({
|
||||
"keyup .searchInput": _.throttle(function(event, template) {
|
||||
let searchQuery = Session.get(PREFIX + 'searchQuery') || {};
|
||||
let searchFields = Session.get(PREFIX + 'searchFields') || {};
|
||||
let searchValue = template.$('.searchInput').val();
|
||||
let searchValue = template.$(event.target).val();
|
||||
|
||||
if(searchValue) {
|
||||
if(this.number) searchValue = parseFloat(searchValue);
|
||||
|
||||
// A collection name will be provided if there is a related table of data that will contain the text provided and will map to an ID that is then searched for in the current table of data.
|
||||
// For example we are displaying a table of Sales which has the ID of a Product. The Product table has a Name field and the search box searches for Product Names. The ID's of the Products found should be used to filter the Sales by Product ID.
|
||||
if(this.collection) {
|
||||
let ids = Meteor.collections[this.collection].find({[this.collectionQueryColumnName]: {$regex: searchValue, $options: 'i'}}, {fields: {[this.collectionResultColumnName]: 1}}).fetch();
|
||||
|
||||
@@ -313,11 +297,70 @@ Template.SaleSearch.events({
|
||||
}
|
||||
|
||||
Session.set(PREFIX + 'searchQuery', searchQuery);
|
||||
Session.set(PREFIX + 'searchFields', searchFields)
|
||||
Session.set(PREFIX + 'searchFields', searchFields);
|
||||
Session.set(PREFIX + 'skipCount', 0); //Reset the paging of the results.
|
||||
}, 500)
|
||||
});
|
||||
|
||||
Template.DateRangeSearch.helpers({
|
||||
startDate: function() {
|
||||
let searchFields = Session.get(PREFIX + 'searchFields');
|
||||
let searchValue = (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : {};
|
||||
|
||||
return searchValue.start ? moment(searchValue.start.toString(), "YYYYMMDD").format("MM/DD/YYYY") : "";
|
||||
},
|
||||
endDate: function() {
|
||||
let searchFields = Session.get(PREFIX + 'searchFields');
|
||||
let searchValue = (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : {};
|
||||
|
||||
return searchValue.end ? moment(searchValue.end.toString(), "YYYYMMDD").format("MM/DD/YYYY") : "";
|
||||
}
|
||||
});
|
||||
Template.DateRangeSearch.events({
|
||||
"change .searchDateStartInput": function(event, template) {Template.DateRangeSearch.dateChanged(true, event, template)},
|
||||
"keyup .searchDateStartInput": _.throttle(function(event, template) {Template.DateRangeSearch.dateChanged(true, event, template)}, 500),
|
||||
"change .searchDateEndInput": function(event, template) {Template.DateRangeSearch.dateChanged(false, event, template)},
|
||||
"keyup .searchDateEndInput": _.throttle(function(event, template) {Template.DateRangeSearch.dateChanged(false, event, template)}, 500)
|
||||
});
|
||||
Template.DateRangeSearch.dateChanged = function(isStart, event, template) {
|
||||
let searchQuery = Session.get(PREFIX + 'searchQuery') || {};
|
||||
let searchFields = Session.get(PREFIX + 'searchFields') || {};
|
||||
let searchValue = template.$(event.target).val();
|
||||
let columnName = template.data.columnName;
|
||||
|
||||
if(searchValue) {
|
||||
let search = searchQuery[columnName];
|
||||
|
||||
// Create a search object and attach it to the searchFields and searchQuery objects if needed.
|
||||
if(!search) {
|
||||
search = {type: 'dateRange'};
|
||||
searchFields[columnName] = searchQuery[columnName] = search;
|
||||
}
|
||||
|
||||
// Use moment to parse date and convert it to YYYYMMDD for searching the database.
|
||||
searchValue = ~~(moment(searchValue, searchValue.includes("-") ? "YYYY-MM-DD" : "MM/DD/YYYY").format("YYYYMMDD")); // Note: ~~ performs a bitwise not which is a fast method of converting a string to a number.
|
||||
// Save the search ending date.
|
||||
isStart ? search.start = searchValue : search.end = searchValue;
|
||||
}
|
||||
else {
|
||||
if(searchQuery[columnName]) {
|
||||
// Remove columns from the search query whose values are empty so we don't bother the database with them.
|
||||
if(isStart) {
|
||||
delete searchQuery[columnName].start;
|
||||
delete searchFields[columnName].start;
|
||||
}
|
||||
else {
|
||||
delete searchQuery[columnName].end;
|
||||
delete searchFields[columnName].end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Session.set(PREFIX + 'searchQuery', searchQuery);
|
||||
Session.set(PREFIX + 'searchFields', searchFields);
|
||||
Session.set(PREFIX + 'skipCount', 0); //Reset the paging of the results.
|
||||
};
|
||||
|
||||
Template.InsertSale.onCreated(function() {
|
||||
this.selectedDate = new ReactiveVar();
|
||||
this.selectedProduct = new ReactiveVar();
|
||||
@@ -351,7 +394,7 @@ Template.InsertSale.events({
|
||||
let insertSaleMeasures = template.$(".insertSaleMeasure");
|
||||
|
||||
let sale = {
|
||||
date: moment(template.find("[name='date']").value, "YYYY-MM-DD").toDate(),
|
||||
date: ~~(moment(template.find("[name='date']").value, "YYYY-MM-DD").format("YYYYMMDD")), // Note: ~~ performs a bitwise not which is a fast method of converting a string to a number.
|
||||
productId: template.selectedProduct.get()._id,
|
||||
venueId: template.selectedVenue.get()._id
|
||||
};
|
||||
@@ -381,6 +424,7 @@ Template.InsertSale.events({
|
||||
if(error) sAlert.error("Failed to insert the sale!\n" + error);
|
||||
else {
|
||||
sAlert.success("Sale Created");
|
||||
nextMeasure.find(".amount").val(0);
|
||||
|
||||
//Clear the measure quantity fields so the user can enter another sale without the quantities already set.
|
||||
for(let next = 0; next < insertSaleMeasures.length; next++) {
|
||||
|
||||
@@ -1,37 +1,44 @@
|
||||
<template name="Venues">
|
||||
<div id="venues">
|
||||
<div class="tableControls">
|
||||
<span class="controlLabel">Show Hidden</span>
|
||||
<div class="toggleShowHidden checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="showHidden"><span></span>
|
||||
</label>
|
||||
{{#if Template.subscriptionsReady}}
|
||||
<div class="tableControls">
|
||||
<span class="controlLabel">Show Hidden</span>
|
||||
<div class="toggleShowHidden checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="showHidden"><span></span>
|
||||
</label>
|
||||
</div>
|
||||
<span class="pagination">
|
||||
<span class="prevVenues noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextVenues noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="pagination">
|
||||
<span class="prevVenues noselect {{#if disablePrev}}disabled{{/if}}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i> Prev</span>
|
||||
<span class="nextVenues noselect {{#if disableNext}}disabled{{/if}}">Next <i class="fa fa-long-arrow-right" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name {{>VenueSearch columnName='name'}}</th>
|
||||
<th class="type">Type {{>VenueSearch columnName='type'}}</th>
|
||||
<th class="actions">Actions <span class="newVenueButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span></th>
|
||||
</tr>
|
||||
<!--<button type="button" name="newVenueButton"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>-->
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if displayNewVenue}}
|
||||
{{> VenueEditor isNew=true}}
|
||||
{{/if}}
|
||||
{{#each venues}}
|
||||
{{> Venue}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">Name {{>VenueSearch columnName='name'}}</th>
|
||||
<th class="type">Type {{>VenueSearch columnName='type'}}</th>
|
||||
<th class="actions">Actions <span class="newVenueButton btn btn-success"><i class="fa fa-plus-circle" aria-hidden="true"></i><i class="fa fa-times-circle" aria-hidden="true"></i></span></th>
|
||||
</tr>
|
||||
<!--<button type="button" name="newVenueButton"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>-->
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if displayNewVenue}}
|
||||
{{> VenueEditor isNew=true}}
|
||||
{{/if}}
|
||||
{{#each venues}}
|
||||
{{> Venue}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
163
imports/ui/Venues.import.styl
vendored
163
imports/ui/Venues.import.styl
vendored
@@ -1,6 +1,9 @@
|
||||
#venues
|
||||
margin: 20px 20px
|
||||
display: table
|
||||
content-box: border-box
|
||||
padding: 10px 20px
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
|
||||
.tableControls
|
||||
@@ -18,77 +21,89 @@
|
||||
top: -4px
|
||||
display: inline-block
|
||||
|
||||
.tableContainer
|
||||
width: 100%
|
||||
margin-bottom: 20px
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
|
||||
table
|
||||
table-layout: fixed
|
||||
.listRow
|
||||
display: table-row
|
||||
.listCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
width: 100%
|
||||
.venueSearch
|
||||
margin: 3px 0 2px 1px
|
||||
.venueEditorTd
|
||||
background: #deeac0
|
||||
input[name="name"], input[name="type"]
|
||||
width: 100%
|
||||
.editorDiv
|
||||
margin: 4px 0
|
||||
label
|
||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
||||
font-size: .9em
|
||||
padding-bottom: 4px
|
||||
select2
|
||||
font-size: .4em
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.type
|
||||
width: auto
|
||||
> th.actions
|
||||
width: 90px
|
||||
text-align: center
|
||||
.newVenueButton
|
||||
margin-top: 4px
|
||||
padding: 0px 12px
|
||||
.fa-plus-circle
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newVenueButton.active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
display: inline-block
|
||||
.fa-plus-circle
|
||||
display: none
|
||||
> tbody
|
||||
> tr
|
||||
.actionRemove
|
||||
color: #F77
|
||||
.actionEdit
|
||||
color: #44F
|
||||
.editorApply
|
||||
color: green
|
||||
.editorCancel
|
||||
color: red
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
.actionActivate
|
||||
color: #158b18
|
||||
.actionHide
|
||||
color: #6a0707
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
> tr.hidden
|
||||
background-color: #e995ff
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
.actionShow
|
||||
color: #027905
|
||||
> tr.hidden:hover
|
||||
background-color: #ffb5ff
|
||||
.tableContainer
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: auto
|
||||
table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
.venueSearch
|
||||
margin: 3px 0 2px 1px
|
||||
.venueEditorTd
|
||||
background: #deeac0
|
||||
input[name="name"], input[name="type"]
|
||||
width: 100%
|
||||
.editorDiv
|
||||
margin: 4px 0
|
||||
label
|
||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
||||
font-size: .9em
|
||||
padding-bottom: 4px
|
||||
select2
|
||||
font-size: .4em
|
||||
> thead
|
||||
> tr
|
||||
> th.name
|
||||
width: auto
|
||||
> th.type
|
||||
width: auto
|
||||
> th.actions
|
||||
width: 90px
|
||||
text-align: center
|
||||
.newVenueButton
|
||||
margin-top: 4px
|
||||
padding: 0px 12px
|
||||
.fa-plus-circle
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newVenueButton.active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
display: inline-block
|
||||
.fa-plus-circle
|
||||
display: none
|
||||
> tbody
|
||||
> tr
|
||||
.actionRemove
|
||||
color: #F77
|
||||
.actionEdit
|
||||
color: #44F
|
||||
.editorApply
|
||||
color: green
|
||||
.editorCancel
|
||||
color: red
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
.actionActivate
|
||||
color: #158b18
|
||||
.actionHide
|
||||
color: #6a0707
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
> tr.hidden
|
||||
background-color: #e995ff
|
||||
.actionEdit
|
||||
color: #0101e4
|
||||
.actionShow
|
||||
color: #027905
|
||||
> tr.hidden:hover
|
||||
background-color: #ffb5ff
|
||||
@@ -17,6 +17,11 @@
|
||||
User Management
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{isActiveRoute 'MiscManagement'}}">
|
||||
<a href="{{pathFor 'MiscManagement'}}">
|
||||
Misc Management
|
||||
</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
<li class="{{isActiveRoute 'Sales'}}">
|
||||
<a href="{{pathFor 'Sales'}}">
|
||||
|
||||
Reference in New Issue
Block a user