+
\ No newline at end of file
diff --git a/imports/ui/Products.import.styl b/imports/ui/Products.import.styl
index 7962983..08ee3f1 100644
--- a/imports/ui/Products.import.styl
+++ b/imports/ui/Products.import.styl
@@ -1,52 +1,98 @@
#products
- height: 100%;
+ margin: 20px 20px
+ height: 100%
+ text-align: left
- .editor
- height: 100%;
- overflow-y: auto;
+ .tableControls
+ text-align: right
+ margin-right: 20px
+ .controlLabel
+ font-size: 9px
+ font-weight: 700
+ color: #5a5a5a
+ position: relative
+ top: -2px
+ .toggleShowHidden
+ margin: 0 40px 0 0
+ position: relative
+ top: -4px
+ display: inline-block
- .grid
- height: 100%;
- //Flex container options.
- flex-flow: column nowrap;
- justify-content: space-around; //Spacing between items along the primary axis. (vertical spacing for a column layout)
- align-items: flex-start; //Align the items within a line along the primary axis. (horizontal alignment for a column layout)
- align-content: center; //Spacing between lines along the secondary axis. (spacing between columns for a column layout)
- display: -webkit-box;
- display: -moz-box;
- display: -ms-flexbox;
- display: -moz-flex;
- display: -webkit-flex;
- display: flex;
+ .tableContainer
+ width: 100%
+ margin-bottom: 20px
+ border: 0
+ font-size: 12.5px
- .buttonContainer
- //Flex element options.
- //flex: 0 0; //Grow, Shrink, Basis
- flex: none;
-
- .dataTable
- overflow-y: auto;
- //Flex element options.
- flex: auto;
- align-self: stretch;
- height: 10%;
- max-height: 100%;
-
- .padding
- flex: none;
- height: 1px;
- width: 100%;
-
- #DFAliases
- width: 100%;
- height: 150px;
- overflow: auto;
-
- span
- font-family: Arial, Helvetica, sans-serif;
- font-size: 1.5em;
- cursor: pointer;
- display: block;
-
- span.selected
- background-color: rgba(255, 248, 131, 0.51);
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/imports/ui/Products.js b/imports/ui/Products.js
index d455c57..6bc7a83 100644
--- a/imports/ui/Products.js
+++ b/imports/ui/Products.js
@@ -1,33 +1,90 @@
import './Products.html';
+let QUERY_LIMIT = 20;
+let PREFIX = "Products.";
+
Tracker.autorun(function() {
Meteor.subscribe("products");
Meteor.subscribe("productTags");
+ Meteor.subscribe("measures");
});
+Template.Products.onCreated(function() {
+ Session.set(PREFIX + "displayNewProduct", false);
+ Session.set(PREFIX + "showHidden", false);
+});
Template.Products.helpers({
+ displayNewProduct: function() {
+ return Session.get(PREFIX + "displayNewProduct");
+ },
products: function() {
- let query = Session.get('searchQuery');
- let dbQuery = {};
+ let skipCount = Session.get(PREFIX + 'skipCount') || 0;
+ let query = Session.get(PREFIX + 'searchQuery');
+ let dbQuery = [];
if(query) {
_.each(_.keys(query), function(key) {
- if(_.isFunction(query[key])) dbQuery[key] = query[key]();
- else if(_.isObject(query[key])) dbQuery[key] = query[key];
- else if(_.isNumber(query[key])) dbQuery[key] = query[key];
- else dbQuery[key] = {$regex: query[key], $options: 'i'};
+ if(_.isFunction(query[key])) dbQuery.push({[key]: query[key]}); //dbQuery[key] = query[key]();
+ else if(_.isObject(query[key])) dbQuery.push({[key]: query[key]}); //dbQuery[key] = query[key]; //Will look something like: {$in: [xxx,xxx,xxx]}
+ else if(_.isNumber(query[key])) dbQuery.push({[key]: query[key]}); //dbQuery[key] = query[key];
+ else {
+ //dbQuery[key] = {$regex: query[key], $options: 'i'};
+ 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'}});
+ }
+ }
})
}
- return Meteor.collections.Products.find(dbQuery, {limit: 20, sort: {name: 1}});
+ if(!Session.get(PREFIX + "showHidden")) {
+ //Ignore any hidden elements by showing those not hidden, or those without the hidden field.
+ dbQuery.push({$or: [{hidden: false}, {hidden: {$exists:false}}]});
+ }
+
+ dbQuery = dbQuery.length > 0 ? {$and: dbQuery} : {};
+ Session.set(PREFIX + 'productCount', Meteor.collections.Products.find(dbQuery).count()); //Always get a full count.
+ return Meteor.collections.Products.find(dbQuery, {limit: QUERY_LIMIT, skip: skipCount, sort: {name: 1}});
+ },
+ disablePrev: function() {
+ return (Session.get(PREFIX + 'skipCount') || 0) == 0;
+ },
+ disableNext: function() {
+ return Session.get(PREFIX + 'productCount') - (Session.get(PREFIX + 'skipCount') || 0) - QUERY_LIMIT <= 0;
+ }
+});
+Template.Products.events({
+ 'click .prevProducts': function(event, template) {
+ if(!$(event.target).hasClass('disabled'))
+ Session.set(PREFIX + 'skipCount', Math.max(0, (Session.get(PREFIX + 'skipCount') || 0) - QUERY_LIMIT));
+ },
+ 'click .nextProducts': function(event, template) {
+ if(!$(event.target).hasClass('disabled'))
+ Session.set(PREFIX + 'skipCount', (Session.get(PREFIX + 'skipCount') || 0) + QUERY_LIMIT);
+ },
+ 'click .newProductButton': function(event, template) {
+ if(template.$('.newProductButton').hasClass('active')) {
+ Session.set(PREFIX + 'displayNewProduct', false);
+ }
+ else {
+ Session.set(PREFIX + 'displayNewProduct', true);
+ Session.set(PREFIX + "editedProduct", undefined); //Clear the edited product so that only one editor is open at a time.
+ }
+ template.$('.newProductButton').toggleClass('active');
+ },
+ 'change input[name="showHidden"]': function(event, template) {
+ //console.log("changed " + $(event.target).prop('checked'));
+ Session.set(PREFIX + "showHidden", $(event.target).prop('checked'));
}
});
Template.ProductSearch.events({
"keyup .searchInput": _.throttle(function(event, template) {
- let searchQuery = Session.get('searchQuery') || {};
- let searchFields = Session.get('searchFields') || {};
+ let searchQuery = Session.get(PREFIX + 'searchQuery') || {};
+ let searchFields = Session.get(PREFIX + 'searchFields') || {};
let searchValue = template.$('.searchInput').val();
if(searchValue) {
@@ -51,13 +108,13 @@ Template.ProductSearch.events({
delete searchFields[this.columnName];
}
- Session.set('searchQuery', searchQuery);
+ Session.set(PREFIX + 'searchQuery', searchQuery);
+ Session.set(PREFIX + 'searchFields', searchFields);
}, 500)
});
-
Template.ProductSearch.helpers({
searchValue: function() {
- let searchFields = Session.get('searchFields');
+ let searchFields = Session.get(PREFIX + 'searchFields');
return (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : '';
}
@@ -82,6 +139,9 @@ Template.Product.helpers({
return result;
},
+ aliases: function() {
+ return this.aliases.join(', ');
+ },
tags: function() {
let result = "";
@@ -99,5 +159,112 @@ Template.Product.helpers({
}
return result;
+ },
+ editing: function() {
+ let editedProduct = Session.get(PREFIX + "editedProduct");
+
+ return editedProduct == this._id;
+ },
+ getRowClass: function() {
+ return this.hidden ? "hidden" : this.deactivated ? "deactivated" : "";
+ }
+});
+Template.Product.events({
+ "click .actionEdit": function(event, template) {
+ Session.set(PREFIX + "editedProduct", this._id);
+ Session.set(PREFIX + 'displayNewProduct', false); //Ensure the new product editor is closed.
+ template.$('.newProductButton').removeClass('active');
+ },
+ "click .actionRemove": function(event, template) {
+ Meteor.call('deactivateProduct', this._id, function(error, result) {
+ if(error) sAlert.error(error);
+ else sAlert.success("Product Deactivated");
+ });
+ },
+ 'click .actionActivate': function(event, template) {
+ Meteor.call('reactivateProduct', this._id, function(error, result) {
+ if(error) sAlert.error(error);
+ else sAlert.success("Product Reactivated");
+ });
+ },
+ "click .actionShow": function(event, template) {
+ Meteor.call('showProduct', this._id, function(error, result) {
+ if(error) sAlert.error(error);
+ else sAlert.success("Product Visibility Enabled");
+ });
+ },
+ 'click .actionHide': function(event, template) {
+ Meteor.call('hideProduct', this._id, function(error, result) {
+ if(error) sAlert.error(error);
+ else sAlert.success("Product Visibility Disabled");
+ });
+ }
+});
+
+
+Template.ProductEditor.onRendered(function() {
+ this.$(".productTagsEditor").select2();
+ this.$(".productAliasesEditor").select2({tags: true, tokenSeparators: [';', '.']});
+ this.$(".productMeasuresEditor").select2();
+});
+Template.ProductEditor.helpers({
+ measures: function() {
+ return Meteor.collections.Measures.find({});
+ },
+ measureSelected: function() {
+ let measure = this;
+ let product = Template.parentData();
+
+ return product.measures && product.measures.includes(measure._id) ? "selected" : "";
+ },
+ aliases: function() {
+ return this.aliases;
+ },
+ tags: function() {
+ return Meteor.collections.ProductTags.find({});
+ },
+ tagSelected: function() {
+ let tag = this;
+ let product = Template.parentData();
+
+ return product.tags && product.tags.includes(tag._id) ? "selected" : "";
+ }
+});
+Template.ProductEditor.events({
+ "click .editorCancel": function(event, template) {
+ Session.set(PREFIX + "editedProduct", undefined);
+ Session.set(PREFIX + 'displayNewProduct', false);
+ template.$('.newProductButton').removeClass('active');
+ },
+ "click .editorApply": function(event, template) {
+ 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.$('.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);
+ }
+ });
+ }
+
}
});
\ No newline at end of file
diff --git a/imports/ui/Sales.html b/imports/ui/Sales.html
index 9cb60d3..23edb38 100644
--- a/imports/ui/Sales.html
+++ b/imports/ui/Sales.html
@@ -5,16 +5,16 @@
{{>InsertSale}}