Added custom scroll bars and separated the table header from the tables for Sales and the Graphs tables.
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
<option value="types">Types</option>
|
||||
</select>
|
||||
<svg class="salesGraph"></svg>
|
||||
<div class="salesTable">
|
||||
<div class="salesTableHeader">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -30,24 +30,50 @@
|
||||
<th class="total">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each sales}}
|
||||
<tr>
|
||||
<td>{{year}}</td>
|
||||
{{#if showTime "monthly"}}
|
||||
<td>{{month}}</td>
|
||||
{{/if}}
|
||||
{{#if showTime "weekly"}}
|
||||
<td>{{week}}</td>
|
||||
{{/if}}
|
||||
{{#if showOption "markets"}}
|
||||
<td>{{venue}}</td>
|
||||
{{/if}}
|
||||
<td>{{formatTotal total}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="salesTable mCustomScrollbar" data-mcs-theme="dark">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="year"></th>
|
||||
{{#if showTime "monthly"}}
|
||||
<th class="month"></th>
|
||||
{{/if}}
|
||||
{{#if showTime "weekly"}}
|
||||
<th class="week"></th>
|
||||
{{/if}}
|
||||
{{#if showOption "markets"}}
|
||||
<th class="market"></th>
|
||||
{{/if}}
|
||||
<th class="total"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each sales}}
|
||||
<tr>
|
||||
<td class="year">{{year}}</td>
|
||||
{{#if showTime "monthly"}}
|
||||
<td class="month">{{month}}</td>
|
||||
{{/if}}
|
||||
{{#if showTime "weekly"}}
|
||||
<td class="week">{{week}}</td>
|
||||
{{/if}}
|
||||
{{#if showOption "markets"}}
|
||||
<td class="market">{{venue}}</td>
|
||||
{{/if}}
|
||||
<td class="total">{{formatTotal total}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacerCell">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
67
imports/ui/Graphs.import.styl
vendored
67
imports/ui/Graphs.import.styl
vendored
@@ -1,6 +1,10 @@
|
||||
#graphs
|
||||
margin: 10px 20px
|
||||
display: table
|
||||
content-box: border-box
|
||||
padding: 10px 20px
|
||||
height: 100%
|
||||
width: 100%
|
||||
text-align: left
|
||||
svg
|
||||
width: 100%
|
||||
.bar
|
||||
@@ -17,22 +21,47 @@
|
||||
font-size: 14px
|
||||
font-family: "Arial Black", "Arial Bold", Gadget, sans-serif
|
||||
font-weight: 800
|
||||
table
|
||||
.table
|
||||
table-layout: fixed
|
||||
> thead
|
||||
> tr
|
||||
> th.total
|
||||
width: 200px
|
||||
> th.market
|
||||
width: 200px
|
||||
> th.week
|
||||
width: 200px
|
||||
> th.month
|
||||
width: 200px
|
||||
> th.year
|
||||
width: 200px
|
||||
> tbody
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
.total
|
||||
width: 200px
|
||||
.market
|
||||
width: 200px
|
||||
.week
|
||||
width: 200px
|
||||
.month
|
||||
width: 200px
|
||||
.year
|
||||
width: 200px
|
||||
.listRow
|
||||
display: table-row
|
||||
.listCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
height: 100%
|
||||
width: auto
|
||||
min-width: 300px
|
||||
.salesTable
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
width: auto
|
||||
height: auto
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: scroll
|
||||
.table
|
||||
> thead
|
||||
display: none
|
||||
> tbody
|
||||
> tr.deactivated
|
||||
background-color: #fac0d1
|
||||
> tr.deactivated:hover
|
||||
background-color: #ffcadb
|
||||
.spacerCell
|
||||
display: table-cell
|
||||
position: relative
|
||||
width: 100%
|
||||
height: 100%
|
||||
@@ -28,6 +28,13 @@ Template.Graphs.onCreated(function() {
|
||||
});
|
||||
});
|
||||
Template.Graphs.onRendered(function() {
|
||||
|
||||
$(".salesTable").mCustomScrollbar({
|
||||
scrollButtons:{enable:true},
|
||||
theme:"light-thick",
|
||||
scrollbarPosition:"outside"
|
||||
});
|
||||
|
||||
//Reset the pull downs to their former states.
|
||||
Template.instance().$('select[name="time"]').val(time);
|
||||
Template.instance().$('select[name="options"]').val(options);
|
||||
@@ -35,12 +42,12 @@ Template.Graphs.onRendered(function() {
|
||||
//Build the SVG Graphs
|
||||
let margin = {top: 10, right: 0, bottom: 30, left: 40};
|
||||
let width = 960 - margin.left - margin.right;
|
||||
let height = 500 - margin.top - margin.bottom;
|
||||
let height = 200 - margin.top - margin.bottom;
|
||||
let x0Scale = d3.scaleBand().range([0, width]).padding(0.05);
|
||||
let x1Scale = d3.scaleBand().padding(0.05);
|
||||
let yScale = d3.scaleLinear().range([height, 0]);
|
||||
let svg = d3.select('svg.salesGraph')
|
||||
.attr("viewBox", "0 0 960 500")
|
||||
.attr("viewBox", "0 0 960 200")
|
||||
.attr("perserveAspectRatio", "xMidYMid meet")
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<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/>
|
||||
<a href="javascript:" class="enhanceSaleDates">Enhance Sale Dates (adds timestamp and weekOfYear to every Sale)</a><br/>
|
||||
<div class="logCount">{{logCount}}</div>
|
||||
</div>
|
||||
<div class="pageContentRow">
|
||||
|
||||
@@ -72,5 +72,8 @@ Template.MiscManagement.events({
|
||||
},
|
||||
"click .deleteDuplicateSales": function(event, template) {
|
||||
Meteor.call("deleteDuplicateSales");
|
||||
},
|
||||
"click .enhanceSaleDates": function(event, template) {
|
||||
Meteor.call("enhanceSalesDateFields");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -49,17 +49,21 @@
|
||||
{{#if editing}}
|
||||
{{> ProductEditor}}
|
||||
{{else}}
|
||||
<td class="noselect nonclickable left">{{name}}</td>
|
||||
<td class="noselect nonclickable left">{{tags}}</td>
|
||||
<td class="noselect nonclickable left">{{aliases}}</td>
|
||||
<td class="noselect nonclickable left">{{measures}}</td>
|
||||
{{#if hidden}}
|
||||
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i> / <i class="actionShow fa fa-eye fa-lg noselect clickable" title="Show" aria-hidden="true"></i></td>
|
||||
{{#if converting}}
|
||||
{{> ConvertProduct}}
|
||||
{{else}}
|
||||
{{#if deactivated}}
|
||||
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i> / <i class="actionActivate fa fa-toggle-on fa-lg noselect clickable" title="Activate" aria-hidden="true"></i> / <i class="actionHide fa fa-eye-slash fa-lg noselect clickable" title="Hide" aria-hidden="true"></i></td>
|
||||
<td class="noselect nonclickable left">{{name}}</td>
|
||||
<td class="noselect nonclickable left">{{tags}}</td>
|
||||
<td class="noselect nonclickable left">{{aliases}}</td>
|
||||
<td class="noselect nonclickable left">{{measures}}</td>
|
||||
{{#if hidden}}
|
||||
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i> / <i class="actionShow fa fa-eye fa-lg noselect clickable" title="Show" aria-hidden="true"></i> / <i class="actionConvert fa fa-exclamation-triangle fa-lg noselect clickable" title="Convert" aria-hidden="true"></i></td>
|
||||
{{else}}
|
||||
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i> / <i class="actionRemove fa fa-times-circle fa-lg noselect clickable" title="Deactivate" aria-hidden="true"></i></td>
|
||||
{{#if deactivated}}
|
||||
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i> / <i class="actionActivate fa fa-toggle-on fa-lg noselect clickable" title="Activate" aria-hidden="true"></i> / <i class="actionHide fa fa-eye-slash fa-lg noselect clickable" title="Hide" aria-hidden="true"></i></td>
|
||||
{{else}}
|
||||
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i> / <i class="actionDeactivate fa fa-times-circle fa-lg noselect clickable" title="Deactivate" aria-hidden="true"></i></td>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
@@ -71,29 +75,39 @@
|
||||
<div class="editorDiv"><label>Name:</label><input name="name" class="form-control" type="text" value="{{name}}" autocomplete="off" required></div>
|
||||
<div class="editorDiv"><label>Tags:</label>
|
||||
<select class="productTagsEditor" multiple="multiple">
|
||||
{{#each tags}}
|
||||
<option value="{{_id}}" {{tagSelected}}>{{name}}</option>
|
||||
{{/each}}
|
||||
{{#each tags}}
|
||||
<option value="{{_id}}" {{tagSelected}}>{{name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="editorDiv"><label>Aliases:</label>
|
||||
<select class="productAliasesEditor" multiple="multiple">
|
||||
{{#each aliases}}
|
||||
<option value="{{this}}" selected>{{this}}</option>
|
||||
{{/each}}
|
||||
{{#each aliases}}
|
||||
<option value="{{this}}" selected>{{this}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="editorDiv"><label>Measures:</label>
|
||||
<select class="productMeasuresEditor" multiple="multiple">
|
||||
{{#each measures}}
|
||||
<option value="{{_id}}" {{measureSelected}}>{{name}}</option>
|
||||
{{/each}}
|
||||
{{#each measures}}
|
||||
<option value="{{_id}}" {{measureSelected}}>{{name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center productEditorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i> / <i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
|
||||
</template>
|
||||
|
||||
<template name="ConvertProduct">
|
||||
<td>{{name}}</td>
|
||||
<td colspan="3" class="convertProductTd">
|
||||
<label class='control-label'>Alternate Product</label>
|
||||
<input name="product" class="form-control" type="text" required/>
|
||||
<label><em>Convert sales from this product to an alternate.</em></label>
|
||||
</td>
|
||||
<td class="center productEditorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i> / <i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
|
||||
</template>
|
||||
|
||||
<template name="ProductSearch">
|
||||
<div class="productSearch">
|
||||
<input type="text" class="searchInput" placeholder="Filter..." value="{{searchValue}}"/>
|
||||
|
||||
2
imports/ui/Products.import.styl
vendored
2
imports/ui/Products.import.styl
vendored
@@ -109,5 +109,7 @@
|
||||
color: #0101e4
|
||||
.actionShow
|
||||
color: #027905
|
||||
.actionConvert
|
||||
color: #fb0909
|
||||
> tr.hidden:hover
|
||||
background-color: #ffb5ff
|
||||
@@ -72,6 +72,7 @@ Template.Products.events({
|
||||
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.
|
||||
Session.set(PREFIX + "convertedProduct", undefined); //Clear the converted product so that only one editor is open at a time.
|
||||
}
|
||||
template.$('.newProductButton').toggleClass('active');
|
||||
},
|
||||
@@ -166,6 +167,11 @@ Template.Product.helpers({
|
||||
|
||||
return editedProduct == this._id;
|
||||
},
|
||||
converting: function() {
|
||||
let convertedProduct = Session.get(PREFIX + "convertedProduct");
|
||||
|
||||
return convertedProduct == this._id;
|
||||
},
|
||||
getRowClass: function() {
|
||||
return this.hidden ? "hidden" : this.deactivated ? "deactivated" : "";
|
||||
}
|
||||
@@ -174,9 +180,10 @@ 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.
|
||||
Session.set(PREFIX + "convertedProduct", undefined); //Clear the converted product so that only one editor is open at a time.
|
||||
template.$('.newProductButton').removeClass('active');
|
||||
},
|
||||
"click .actionRemove": function(event, template) {
|
||||
"click .actionDeactivate": function(event, template) {
|
||||
Meteor.call('deactivateProduct', this._id, function(error, result) {
|
||||
if(error) sAlert.error(error);
|
||||
else sAlert.success("Product Deactivated");
|
||||
@@ -194,6 +201,12 @@ Template.Product.events({
|
||||
else sAlert.success("Product Visibility Enabled");
|
||||
});
|
||||
},
|
||||
"click .actionConvert": function(event, template) {
|
||||
Session.set(PREFIX + "convertedProduct", this._id);
|
||||
Session.set(PREFIX + 'displayNewProduct', false); //Ensure the new product editor is closed.
|
||||
Session.set(PREFIX + "editedProduct", undefined); //Clear the edited product so that only one editor is open at a time.
|
||||
template.$('.newProductButton').removeClass('active');
|
||||
},
|
||||
'click .actionHide': function(event, template) {
|
||||
Meteor.call('hideProduct', this._id, function(error, result) {
|
||||
if(error) sAlert.error(error);
|
||||
@@ -268,4 +281,35 @@ Template.ProductEditor.events({
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Template.ConvertProduct.onCreated(function() {
|
||||
this.selectedProduct = new ReactiveVar();
|
||||
});
|
||||
Template.ConvertProduct.onRendered(function() {
|
||||
this.$('[name="product"]').buildCombo({cursor: Meteor.collections.Products.find({$or: [{hidden: false}, {hidden: {$exists:false}}]}), selection: this.selectedProduct, textAttr: 'name', listClass: 'comboList', getClasses: function(data) {
|
||||
return (data && data.deactivated) ? "deactivated" : "";
|
||||
}});
|
||||
});
|
||||
Template.ConvertProduct.events({
|
||||
"click .editorCancel": function(event, template) {
|
||||
Session.set(PREFIX + "editedProduct", undefined);
|
||||
Session.set(PREFIX + "convertedProduct", undefined);
|
||||
Session.set(PREFIX + 'displayNewProduct', false);
|
||||
template.parentTemplate().$('.newProductButton').removeClass('active');
|
||||
},
|
||||
"click .editorApply": function(event, template) {
|
||||
let productId = template.selectedProduct.get()._id;
|
||||
|
||||
Meteor.call("convertProduct", this._id, productId, function(error, result) {
|
||||
if(error) sAlert.error(error);
|
||||
else {
|
||||
sAlert.success("Sales of this product were converted.");
|
||||
Session.set(PREFIX + "editedProduct", undefined);
|
||||
Session.set(PREFIX + "convertedProduct", undefined);
|
||||
Session.set(PREFIX + 'displayNewProduct', false);
|
||||
template.parentTemplate().$('.newProductButton').removeClass('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -16,20 +16,36 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separatedTableHeader">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="amount noselect nonclickable">Amount {{>SaleSearch columnName='amount' width='90%'}}</th>
|
||||
<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) {{>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>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<div class="listRow">
|
||||
<div class="listCell">
|
||||
<div class="tableContainer">
|
||||
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="amount noselect nonclickable">Amount {{>SaleSearch columnName='amount' width='90%'}}</th>
|
||||
<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) {{>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>
|
||||
<th class="amount"></th>
|
||||
<th class="product"></th>
|
||||
<th class="price"></th>
|
||||
<th class="measure"></th>
|
||||
<th class="saleDate"></th>
|
||||
<th class="createdDate"></th>
|
||||
<th class="venue"></th>
|
||||
<th class="actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
93
imports/ui/Sales.import.styl
vendored
93
imports/ui/Sales.import.styl
vendored
@@ -19,6 +19,54 @@
|
||||
padding: 4px 8px
|
||||
margin: 4px 12px 4px 8px
|
||||
display: table-cell
|
||||
.table
|
||||
table-layout: fixed
|
||||
min-width: 100%
|
||||
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
|
||||
.separatedTableHeader
|
||||
table
|
||||
thead
|
||||
> tr
|
||||
> th.actions
|
||||
.newSaleButton
|
||||
padding: 0px 12px
|
||||
.fa-plus-circle
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newSaleButton:active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
display: inline-block
|
||||
.fa-plus-circle
|
||||
display: none
|
||||
.showOnlyComments
|
||||
color: #bcb95f
|
||||
padding: 4px 8px
|
||||
.showOnlyComments:hover
|
||||
color: white
|
||||
text-shadow: 0px 0px 10px #ff6d1f
|
||||
.showOnlyComments.on
|
||||
color: white
|
||||
.listRow
|
||||
display: table-row
|
||||
.listCell
|
||||
@@ -38,14 +86,12 @@
|
||||
//margin-bottom: 20px
|
||||
border: 0
|
||||
font-size: 12.5px
|
||||
overflow-y: auto
|
||||
overflow-y: scroll
|
||||
//height: 100%
|
||||
label
|
||||
font-size: 10px
|
||||
font-weight: 800
|
||||
table
|
||||
table-layout: fixed
|
||||
min-width: 100%
|
||||
.saleRemove
|
||||
color: red
|
||||
margin-left: 8px
|
||||
@@ -57,45 +103,8 @@
|
||||
.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
|
||||
.newSaleButton
|
||||
padding: 0px 12px
|
||||
.fa-plus-circle
|
||||
display: inline-block
|
||||
.fa-times-circle
|
||||
display: none
|
||||
.newSaleButton:active
|
||||
background-color: #fb557b
|
||||
color: black
|
||||
.fa-times-circle
|
||||
display: inline-block
|
||||
.fa-plus-circle
|
||||
display: none
|
||||
.showOnlyComments
|
||||
color: #bcb95f
|
||||
padding: 4px 8px
|
||||
.showOnlyComments:hover
|
||||
color: white
|
||||
text-shadow: 0px 0px 10px #ff6d1f
|
||||
.showOnlyComments.on
|
||||
color: white
|
||||
visibility: hidden
|
||||
display: none
|
||||
.editComment
|
||||
color: grey
|
||||
.hasComment
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
import './Sales.html';
|
||||
import '/imports/util/selectize/selectize.js';
|
||||
import swal from 'sweetalert2';
|
||||
//import 'malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js';
|
||||
//import 'jquery-mousewheel';
|
||||
//import 'malihu-custom-scrollbar-plugin';
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
@@ -34,6 +37,20 @@ Template.Sales.onCreated(function() {
|
||||
Session.set(PREFIX + 'saleCount', Meteor.call('getSalesCount', Session.get(PREFIX + 'searchQuery')));
|
||||
});
|
||||
});
|
||||
Template.Sales.onRendered(function() {
|
||||
//$('.tableContainer').mCustomScrollbar();
|
||||
$(".tableContainer").mCustomScrollbar({
|
||||
scrollButtons:{enable:true},
|
||||
theme:"light-thick",
|
||||
scrollbarPosition:"outside"
|
||||
});
|
||||
//(function($){
|
||||
// $(window).on("load",function(){
|
||||
// $(".tableContainer").mCustomScrollbar();
|
||||
// });
|
||||
//})(jQuery);
|
||||
//$('#test').mCustomScrollbar();
|
||||
});
|
||||
Template.Sales.onDestroyed(function() {
|
||||
if(Template.Sales.salesSubscription) {
|
||||
Template.Sales.salesSubscription.stop();
|
||||
|
||||
@@ -11,10 +11,15 @@
|
||||
<!-- ******** The Sheet Editor's Product Selector ********* -->
|
||||
|
||||
<template name="SalesSheetEditorProductSelection">
|
||||
<div class="salesSheetEditorProductSelectionControls vscFixed">
|
||||
<span class="button showAlternateNames">Alt. Names</span>
|
||||
<i class="fa fa-question-circle clickable noselect" aria-hidden="true"></i>
|
||||
<label>Filter </label><input class="form-control" type="text" name="productFilter" autocomplete="off"/>
|
||||
<div class="tableControls vscFixed">
|
||||
<span class="controlLabel">Show Hidden</span>
|
||||
<div class="toggleShowHidden checkbox checkbox-slider--b-flat">
|
||||
<label>
|
||||
<input type="checkbox" name="showHidden"><span></span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="margin-bottom: 6px"><label>Filter </label><input class="form-control" type="text" name="productFilter" autocomplete="off"/></div>
|
||||
<i class="fa fa-times-circle clickable noselect clearFilter" style="margin-bottom: 5px" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="selectionProductsListing columnContainer">
|
||||
{{#each products}}
|
||||
@@ -31,9 +36,9 @@
|
||||
{{else}}
|
||||
<i class="fa fa-circle-o" aria-hidden="true"></i>
|
||||
{{/if}}
|
||||
<span class="productName noselect">{{name}}</span>
|
||||
<span class="productName noselect {{#if hidden}}hidden{{/if}} {{#if deactivated}}deactivated{{/if}}">{{name}}</span>
|
||||
</span>
|
||||
{{#if sheetProduct}}
|
||||
{{#if showAlternateName}}
|
||||
<div class="includeAs"> as "{{sheetProductName}}"</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -42,11 +47,17 @@
|
||||
<!-- ******** The Sheet Editor's Configuration ********* -->
|
||||
<!-- The overall Sheet Configuration Editor. Contains multiple rows in columns, one row for each PRODUCT or HEADER. -->
|
||||
<template name="SalesSheetEditorConfiguration">
|
||||
<div class="vscFixed configurationControls">
|
||||
<div class="vscFixed tableControls">
|
||||
<div class="heading columnContent noselect">
|
||||
<div class="name clickable">New Heading</div>
|
||||
<div class="name clickable" title="Drag to the list to create a new heading.">New Heading</div>
|
||||
<div class="nameEditor"><input name="name" tabindex="1" value="New Heading"/> <i class="fa fa-check-circle accept" aria-hidden="true"></i> <i class="fa fa-times-circle reject" aria-hidden="true"></i></div>
|
||||
</div>
|
||||
<span class="controlLabel" title="Show/Hide the measures available for each product.">Show Measures</span>
|
||||
<div class="toggleShowHidden checkbox checkbox-slider--b-flat" title="Show/Hide the measures available for each product.">
|
||||
<label>
|
||||
<input type="checkbox" name="showMeasures"><span></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="configurationProductsListing columnContainer">
|
||||
{{#each products}}
|
||||
@@ -61,11 +72,13 @@
|
||||
<div class="product columnContent noselect" data-model="{{productId}}">
|
||||
<div class="name clickable">{{name}}</div>
|
||||
<div class="nameEditor"><input class="form-control" name="name" type="text" tabindex="1" value="{{name}}"/> <i class="fa fa-check-circle accept" aria-hidden="true"></i> <i class="fa fa-times-circle reject" aria-hidden="true"></i></div>
|
||||
<div class="measures">
|
||||
{{#each measureId in measures}}
|
||||
<span class="measureButton button {{#if isSelected measureId}}selected{{/if}}" data-model="{{measureId}}">{{measureName measureId}}</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{#if showMeasures}}
|
||||
<div class="measures">
|
||||
{{#each measureId in measures}}
|
||||
<span class="measureButton button {{#if isSelected measureId}}selected{{/if}}" data-model="{{measureId}}">{{measureName measureId}}</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}} {{! HEADING }}
|
||||
<div class="heading columnContent noselect">
|
||||
|
||||
69
imports/ui/SalesSheetEditor.import.styl
vendored
69
imports/ui/SalesSheetEditor.import.styl
vendored
@@ -1,21 +1,58 @@
|
||||
#salesSheetsMain
|
||||
.salesSheetEditorControls
|
||||
margin-bottom: 8px
|
||||
.salesSheetEditorProductSelectionControls
|
||||
.tableControls
|
||||
margin-bottom: 8px
|
||||
width: 100%
|
||||
text-align: right
|
||||
//text-align: left
|
||||
border-bottom: 2px solid #a7a8ff
|
||||
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
align-content: stretch;
|
||||
|
||||
div, span, i
|
||||
//display: inline-block
|
||||
flex: 0 0 auto
|
||||
margin-right: 4px
|
||||
.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
|
||||
input[name='productFilter']
|
||||
font-size: 1.2em
|
||||
display: inline
|
||||
width: auto
|
||||
.showAlternateNames
|
||||
margin-right: 20px
|
||||
.heading
|
||||
display: inline-block
|
||||
.name
|
||||
padding: 6px 10px
|
||||
margin-bottom: 6px
|
||||
border-radius: 10px
|
||||
font-size: 1.4em
|
||||
text-transform: uppercase
|
||||
font-weight: 800
|
||||
background: #c1c2ff
|
||||
.nameEditor
|
||||
display: none
|
||||
.clearFilter
|
||||
font-size: 1.6em
|
||||
.selectionProductsListing
|
||||
width: 100%
|
||||
.selectionProduct
|
||||
color: #9f9f9f
|
||||
font-size: 1.5em
|
||||
font-size: 1.3em
|
||||
width: 400px
|
||||
.include, .includeAs
|
||||
text-overflow: ellipsis
|
||||
@@ -25,26 +62,26 @@
|
||||
cursor: pointer
|
||||
.includedRemove:hover, .includedAdd:hover
|
||||
color: blue
|
||||
span.deactivated
|
||||
background: #ff0
|
||||
//-webkit-box-shadow: inset 28px 0 10px -10px #fff
|
||||
//-moz-box-shadow: inset 28px 0 10px -10px #fff
|
||||
//box-shadow: inset 28px 0 10px -10px #fff
|
||||
box-shadow: inset 0px 0 100px 0px #fff
|
||||
span.hidden
|
||||
background: rgba(255, 20, 20, .7)
|
||||
//-webkit-box-shadow: inset 28px 0 10px -10px #fff
|
||||
//-moz-box-shadow: inset 28px 0 10px -10px #fff
|
||||
box-shadow: inset 0px 0px 100px 0px #fff
|
||||
.selectionProduct.selected
|
||||
color: black
|
||||
.configurationControls
|
||||
width: 100%
|
||||
background: #c1c2ff
|
||||
border-bottom: 2px solid #a7a8ff
|
||||
.heading
|
||||
.name
|
||||
font-size: 1.5em
|
||||
text-transform: uppercase
|
||||
font-weight: 800
|
||||
.nameEditor
|
||||
display: none
|
||||
.configurationProductsListing
|
||||
width: 100%
|
||||
.product
|
||||
width: 300px
|
||||
.name
|
||||
color: #9f9f9f
|
||||
font-size: 1.5em
|
||||
color: #4a4a4a
|
||||
font-size: 1.1em
|
||||
margin-bottom: 6px
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
@@ -83,11 +83,20 @@ Template.SalesSheetEditorProductSelection.onCreated(function() {
|
||||
//Note: This is not reactive because we don't expect the sales sheet to change without closing the editor (re-editing would open a new template instance). Also, the sales sheet is a clone of the real one, so any changes will be lost if not saved.
|
||||
this.salesSheet = this.data.salesSheet;
|
||||
this.productNameFilter = new ReactiveVar("");
|
||||
Session.set(PREFIX + "showHidden", false);
|
||||
});
|
||||
Template.SalesSheetEditorProductSelection.events({
|
||||
'keyup input[name="productFilter"]': _.throttle(function(event, template) {
|
||||
template.productNameFilter.set($(event.target).val());
|
||||
})
|
||||
}),
|
||||
'click .clearFilter': function(event, template) {
|
||||
template.$('input[name="productFilter"]').val('');
|
||||
template.productNameFilter.set('');
|
||||
},
|
||||
'change input[name="showHidden"]': function(event, template) {
|
||||
//console.log("changed " + $(event.target).prop('checked'));
|
||||
Session.set(PREFIX + "showHidden", $(event.target).prop('checked'));
|
||||
}
|
||||
});
|
||||
Template.SalesSheetEditorProductSelection.helpers({
|
||||
products: function() {
|
||||
@@ -95,7 +104,8 @@ Template.SalesSheetEditorProductSelection.helpers({
|
||||
let filter = Template.instance().productNameFilter.get();
|
||||
let products = salesSheet.products ? salesSheet.products : [];
|
||||
let productMap = {};
|
||||
let dbQuery;
|
||||
let dbQuery = [];
|
||||
let showHidden = Session.get(PREFIX + "showHidden");
|
||||
|
||||
//Map the products in the sales sheet by their id so we can later only add products to the list that are not on the sheet.
|
||||
for(next of products) {
|
||||
@@ -115,9 +125,17 @@ Template.SalesSheetEditorProductSelection.helpers({
|
||||
}
|
||||
|
||||
regex += '.*';
|
||||
dbQuery = {name: {$regex: regex, $options: 'i'}};
|
||||
dbQuery.push({name: {$regex: regex, $options: 'i'}});
|
||||
}
|
||||
|
||||
if(!showHidden) {
|
||||
//Ignore any hidden elements by showing those not hidden, or those without the hidden field.
|
||||
dbQuery.push({$or: [{hidden: false}, {hidden: {$exists:false}}]});
|
||||
}
|
||||
|
||||
if(dbQuery.length > 0) dbQuery = {$and: dbQuery};
|
||||
else dbQuery = {};
|
||||
|
||||
let allProducts = Meteor.collections.Products.find(dbQuery).fetch();
|
||||
|
||||
//Mark all the products that are currently included in the sheet and note the name they are included as.
|
||||
@@ -145,18 +163,21 @@ Template.SalesSheetEditorProductSelectionRow.events({
|
||||
let sheet = template.parentTemplate(1).salesSheet;
|
||||
|
||||
if(template.sheetProduct.get()) { //Remove the product from the sheet, or rename it (if the names don't match and the user clicked on the name instead of the checkbox).
|
||||
|
||||
// TODO: The commented code fails to detect newly added (to the sheet) products and instead of removing them, it seems to be changing the name when unchecking. This should also keep the element checked in the list instead of unchecking.
|
||||
|
||||
//If the click was on the product's name, and the product name is different from the name used for it in the sheet, then use the product name as the name in the sheet instead of removing it from the sheet.
|
||||
if($(event.target).closest('.productName') && this.name != template.sheetProduct.get().name) {
|
||||
template.sheetProduct.get().name = this.name;
|
||||
}
|
||||
else {
|
||||
let index = sheet.products.indexOf(this.sheetProduct);
|
||||
//if($(event.target).closest('.productName') && this.name !== template.sheetProduct.get().name) {
|
||||
// template.sheetProduct.get().name = this.name;
|
||||
//}
|
||||
//else {
|
||||
let index = sheet.products.indexOf(template.sheetProduct.get());
|
||||
|
||||
//Remove the product data from the sheet first. Template.parentData(1) is the sheet.
|
||||
if(index >= 0) sheet.products.splice(index, 1);
|
||||
//Clear the sheet product data from the actual product.
|
||||
template.sheetProduct.set(undefined);
|
||||
}
|
||||
//}
|
||||
}
|
||||
else {
|
||||
let sheetProduct = {name: this.name, productId: this._id, measureIds: this.measures.length > 2 ? this.measures.slice(0,2) : this.measures};
|
||||
@@ -172,8 +193,19 @@ Template.SalesSheetEditorProductSelectionRow.helpers({
|
||||
sheetProduct: function() {
|
||||
return Template.instance().sheetProduct.get();
|
||||
},
|
||||
hidden: function() {
|
||||
return this.hidden;
|
||||
},
|
||||
deactivated: function() {
|
||||
return this.deactivated;
|
||||
},
|
||||
sheetProductName: function() {
|
||||
return Template.instance().sheetProduct.get().name;
|
||||
},
|
||||
showAlternateName: function() {
|
||||
let sheetProduct = Template.instance().sheetProduct.get();
|
||||
|
||||
return sheetProduct && sheetProduct.name !== this.name;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -187,7 +219,7 @@ Template.SalesSheetEditorConfiguration.onCreated(function() {
|
||||
//Note: This is not reactive because we don't expect the sales sheet to change without closing the editor (re-editing would open a new template instance). Also, the sales sheet is a clone of the real one, so any changes will be lost if not saved.
|
||||
this.salesSheet = this.data.salesSheet;
|
||||
this.measures = new ReactiveDict();
|
||||
this.productsDependancy = new Tracker.Dependency;
|
||||
this.productsDependency = new Tracker.Dependency;
|
||||
|
||||
Tracker.autorun(function() {
|
||||
let measures = Meteor.collections.Measures.find({}).fetch();
|
||||
@@ -197,12 +229,14 @@ Template.SalesSheetEditorConfiguration.onCreated(function() {
|
||||
template.measures.set(measure._id, measure);
|
||||
}
|
||||
});
|
||||
|
||||
Session.set(PREFIX + "showMeasures", false);
|
||||
});
|
||||
Template.SalesSheetEditorConfiguration.onRendered(function() {
|
||||
let template = this;
|
||||
|
||||
//Setup the drag and drop for the view.
|
||||
this.drake = dragula([this.$('.configurationProductsListing')[0], this.$('.configurationControls')[0]], {
|
||||
this.drake = dragula([this.$('.configurationProductsListing')[0], this.$('.tableControls')[0]], {
|
||||
moves: function(el, container, handle, sibling) {
|
||||
//Don't allow drag and drop of buttons - we want them to be clickable.
|
||||
return !$(handle).hasClass("button");
|
||||
@@ -212,7 +246,7 @@ Template.SalesSheetEditorConfiguration.onRendered(function() {
|
||||
return (!sibling || !$(sibling).hasClass('newHeading'));
|
||||
},
|
||||
copy: function(el, source) {
|
||||
return $(el).hasClass('heading') && $(source).hasClass('configurationControls');
|
||||
return $(el).hasClass('heading') && $(source).hasClass('tableControls');
|
||||
},
|
||||
ignoreInputTextSelection: true
|
||||
}).on('drop', function(el, target, source, sibling) {
|
||||
@@ -225,7 +259,7 @@ Template.SalesSheetEditorConfiguration.onRendered(function() {
|
||||
//Remove the element that was just added by the D&D. The element will be re-added by the template in just a moment. We need the template to add the element so that events will be properly handled for it by meteor.
|
||||
el.parentNode.removeChild(el);
|
||||
//Notify the template engine that the products list has changed so it can be re-rendered.
|
||||
template.productsDependancy.changed();
|
||||
template.productsDependency.changed();
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -257,6 +291,9 @@ Template.SalesSheetEditorConfiguration.onDestroyed(function() {
|
||||
this.drake.destroy();
|
||||
});
|
||||
Template.SalesSheetEditorConfiguration.events({
|
||||
'change input[name="showMeasures"]': function(event, template) {
|
||||
Session.set(PREFIX + "showMeasures", $(event.target).prop('checked'));
|
||||
}
|
||||
});
|
||||
Template.SalesSheetEditorConfiguration.helpers({
|
||||
products: function() {
|
||||
@@ -264,7 +301,7 @@ Template.SalesSheetEditorConfiguration.helpers({
|
||||
let products = template.salesSheet.products;
|
||||
|
||||
//Mark this call as depending on the products array. When we change the array later, we will call changed() on the dependency and it will trigger this function (and the calling template setup) to be re-run.
|
||||
template.productsDependancy.depend();
|
||||
template.productsDependency.depend();
|
||||
|
||||
return products;
|
||||
}
|
||||
@@ -295,7 +332,7 @@ Template.SalesSheetEditorConfigurationRow.onCreated(function() {
|
||||
}
|
||||
else {
|
||||
template.parentTemplate(1).salesSheet.products.splice(index, 1);
|
||||
template.parentTemplate(1).productsDependancy.changed();
|
||||
template.parentTemplate(1).productsDependency.changed();
|
||||
}
|
||||
|
||||
template.$('.heading .nameEditor, .heading .headingNameRow').removeClass('edit');
|
||||
@@ -332,10 +369,13 @@ Template.SalesSheetEditorConfigurationRow.helpers({
|
||||
},
|
||||
isProduct: function() {
|
||||
return !!this.productId;
|
||||
},
|
||||
showMeasures: function() {
|
||||
return Session.get(PREFIX + "showMeasures");
|
||||
}
|
||||
});
|
||||
Template.SalesSheetEditorConfigurationRow.events({
|
||||
'click .heading .name': function(event, template) {
|
||||
'dblclick .heading .name': function(event, template) {
|
||||
template.$('.nameEditor, .headingNameRow').addClass('edit');
|
||||
template.$('input[name="name"]').select();
|
||||
},
|
||||
@@ -360,7 +400,7 @@ Template.SalesSheetEditorConfigurationRow.events({
|
||||
'click .heading .reject': function(event, template) {
|
||||
template.handleHeaderEditorCancelAndClose();
|
||||
},
|
||||
'click .product .name': function(event, template) {
|
||||
'dblclick .product .name': function(event, template) {
|
||||
template.$('.nameEditor, .name').addClass('edit');
|
||||
template.$('input[name="name"]').select();
|
||||
},
|
||||
@@ -414,6 +454,6 @@ Template.SalesSheetEditorConfigurationRow.events({
|
||||
});
|
||||
|
||||
//Notify anything depending on the products list that they have been modified.
|
||||
template.parentTemplate(1).productsDependancy.changed();
|
||||
template.parentTemplate(1).productsDependency.changed();
|
||||
}
|
||||
});
|
||||
@@ -208,7 +208,6 @@ Template.SalesSheetFormProduct.onCreated(function() {
|
||||
this.product = new ReactiveVar(); //The actual product for this sheet product.
|
||||
this.total = new ReactiveVar(0);
|
||||
|
||||
|
||||
this.reset = function() {
|
||||
for(let measureTemplate of template.measureTemplates) {
|
||||
measureTemplate.reset();
|
||||
|
||||
27
imports/ui/TestList.html
Normal file
27
imports/ui/TestList.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<template name="TestList">
|
||||
<div id="testList">
|
||||
<div id="sortAsc" class="clickable">Sort ASC</div>
|
||||
<div id="sortDesc" class="clickable">Sort DESC</div>
|
||||
<div class="testListListing columnContainer">
|
||||
{{#each products}}
|
||||
{{>TestListRow}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="TestListRow">
|
||||
<div class="selectionProduct {{#if sheetProduct}}selected{{/if}} columnContent">
|
||||
<span class="include clickable">
|
||||
{{#if sheetProduct}}
|
||||
<i class="fa fa-check-circle" aria-hidden="true"></i>
|
||||
{{else}}
|
||||
<i class="fa fa-circle-o" aria-hidden="true"></i>
|
||||
{{/if}}
|
||||
<span class="productName noselect">{{name}}</span>
|
||||
</span>
|
||||
{{#if showAlternateName}}
|
||||
<div class="includeAs"> as "{{sheetProductName}}"</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
4
imports/ui/TestList.import.styl
vendored
Normal file
4
imports/ui/TestList.import.styl
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
#testList
|
||||
margin: 10px 20px
|
||||
height: 100%
|
||||
|
||||
266
imports/ui/TestList.js
Normal file
266
imports/ui/TestList.js
Normal file
@@ -0,0 +1,266 @@
|
||||
|
||||
import './TestList.html';
|
||||
import swal from 'sweetalert2';
|
||||
import dragula from 'dragula';
|
||||
|
||||
//******************************************************************
|
||||
//** Tests a list of objects held by an object, where the list is sortable and dragable.
|
||||
//** The objects are not in a repository (not in Mongo).
|
||||
//**
|
||||
//** The specific domain is a `SalesSheet` which holds a list of `Product` objects which each have a `name` property.
|
||||
//******************************************************************
|
||||
|
||||
Template.TestList.onCreated(function() {
|
||||
let template = this;
|
||||
//Here, this is the template, and this.data is an object containing the 'parentTemplate' and 'salesSheet' properties.
|
||||
//Save the sales sheet as a property of this template to make the code later easier to read.
|
||||
//Note: This is not reactive because we don't expect the sales sheet to change without closing the editor (re-editing would open a new template instance). Also, the sales sheet is a clone of the real one, so any changes will be lost if not saved.
|
||||
this.salesSheet = {products: [{name: "A Product", productId: 1}, {name: "E Product", productId: 2}, {name: "B Product", productId: 3}, {name: "G Product", productId: 4}, {name: "H Product", productId: 5}, {name: "I Product", productId: 6}, {name: "C Product", productId: 7}, {name: "D Product", productId: 8}, {name: "F Product", productId: 9}]};
|
||||
this.productsDependency = new Tracker.Dependency;
|
||||
|
||||
this.sort = function(sortAlphabetical) {
|
||||
let firstIndex = 0;
|
||||
let products = template.salesSheet.products;
|
||||
let length = 0;
|
||||
|
||||
while(firstIndex + length < products.length && products[firstIndex + length].productId) {
|
||||
length++;
|
||||
}
|
||||
|
||||
//Sort the part of the array that contains products under the sorted heading.
|
||||
products.partialSort(firstIndex, length, function(a, b) {
|
||||
return sortAlphabetical ? (a.name < b.name ? -1 : 1) : (a.name > b.name ? -1 : 1);
|
||||
});
|
||||
|
||||
//Notify anything depending on the products list that they have been modified.
|
||||
template.productsDependency.changed();
|
||||
}
|
||||
});
|
||||
Template.TestList.onRendered(function() {
|
||||
let template = this;
|
||||
|
||||
//Setup the drag and drop for the view.
|
||||
this.drake = dragula([this.$('.configurationProductsListing')[0], this.$('.configurationControls')[0]], {
|
||||
moves: function(el, container, handle, sibling) {
|
||||
//Don't allow drag and drop of buttons - we want them to be clickable.
|
||||
return !$(handle).hasClass("button");
|
||||
},
|
||||
//Checks whether the element `el` can be moved from the container `target`, to the container `source`, above the `sibling` element.
|
||||
accepts: function(el, target, source, sibling) {
|
||||
return (!sibling || !$(sibling).hasClass('newHeading'));
|
||||
},
|
||||
copy: function(el, source) {
|
||||
return $(el).hasClass('heading') && $(source).hasClass('configurationControls');
|
||||
},
|
||||
ignoreInputTextSelection: true
|
||||
}).on('drop', function(el, target, source, sibling) {
|
||||
if($(el).hasClass('heading')) {
|
||||
if(el.parentNode) {
|
||||
let array = template.salesSheet.products;
|
||||
|
||||
//Add the heading to the product array.
|
||||
array.add({name: "New Heading"}, $(el).index());
|
||||
//Remove the element that was just added by the D&D. The element will be re-added by the template in just a moment. We need the template to add the element so that events will be properly handled for it by meteor.
|
||||
el.parentNode.removeChild(el);
|
||||
//Notify the template engine that the products list has changed so it can be re-rendered.
|
||||
template.productsDependency.changed();
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Get the item from the DOM using the blaze data structure. We could make this more blaze agnostic by attaching the object as data to the DOM in the view, but we really can't escape blaze, so why bother.
|
||||
//let item = el.$blaze_range.view._templateInstance.data;
|
||||
let productId = $(el).data('model');
|
||||
let array = template.salesSheet.products;
|
||||
let item = undefined;
|
||||
|
||||
for(let product of array) {
|
||||
if(productId === product.productId) {
|
||||
item = product;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(item) {
|
||||
//Rearrange the array of products on the sheet.
|
||||
array.move(array.indexOf(item), $(el).index());
|
||||
}
|
||||
else {
|
||||
console.log("ERROR: Unable to locate the moved item.");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Template.TestList.onDestroyed(function() {
|
||||
//Clean up after the drag and drop.
|
||||
this.drake.destroy();
|
||||
});
|
||||
Template.TestList.events({
|
||||
'click #sortAsc': function(event, template) {
|
||||
Template.instance().sort(true);
|
||||
},
|
||||
'click #sortDesc': function(event, template) {
|
||||
Template.instance().sort(false);
|
||||
}
|
||||
});
|
||||
Template.TestList.helpers({
|
||||
products: function() {
|
||||
let template = Template.instance();
|
||||
let products = template.salesSheet.products;
|
||||
|
||||
//Mark this call as depending on the products array. When we change the array later, we will call changed() on the dependency and it will trigger this function (and the calling template setup) to be re-run.
|
||||
template.productsDependency.depend();
|
||||
|
||||
return products;
|
||||
}
|
||||
});
|
||||
|
||||
//Note: The data to this template is a product metadata object that is part of a sheet and wrappers (by ID association) a product in the system. See the schema in SalesSheet.js, look for 'products.$' to see the type definition for this data.
|
||||
Template.TestListRow.onCreated(function() {
|
||||
let template = this;
|
||||
|
||||
this.handleHeaderEditorCancelAndClose = function() {
|
||||
let $inputField = template.$("input[name='name']");
|
||||
let index = template.$('.heading').index();
|
||||
|
||||
//Reset the text field.
|
||||
$inputField.val(template.parentTemplate(1).salesSheet.products[index].name);
|
||||
template.$('.heading .nameEditor, .heading .headingNameRow').removeClass('edit');
|
||||
};
|
||||
this.handleHeaderEditorApplyAndClose = function() {
|
||||
let $inputField = template.$("input[name='name']");
|
||||
let name = $inputField.val();
|
||||
let index = template.$('.heading').index();
|
||||
|
||||
if(name) name = name.trim();
|
||||
|
||||
if(name && name.length > 0) {
|
||||
template.parentTemplate(1).salesSheet.products[index].name = name;
|
||||
template.$('.heading .name').text(name);
|
||||
}
|
||||
else {
|
||||
template.parentTemplate(1).salesSheet.products.splice(index, 1);
|
||||
template.parentTemplate(1).productsDependency.changed();
|
||||
}
|
||||
|
||||
template.$('.heading .nameEditor, .heading .headingNameRow').removeClass('edit');
|
||||
};
|
||||
this.handleProductEditorCancelAndClose = function() {
|
||||
let $inputField = template.$("input[name='name']");
|
||||
let index = template.$('.product').index();
|
||||
|
||||
//Reset the text field.
|
||||
$inputField.val(template.parentTemplate(1).salesSheet.products[index].name);
|
||||
template.$('.product .nameEditor, .product .name').removeClass('edit');
|
||||
};
|
||||
this.handleProductEditorApplyAndClose = function() {
|
||||
let $inputField = template.$("input[name='name']");
|
||||
let name = $inputField.val();
|
||||
let index = template.$('.product').index();
|
||||
|
||||
template.parentTemplate(1).salesSheet.products[index].name = name;
|
||||
template.$('.product .name').text(name);
|
||||
template.$('.product .nameEditor, .product .name').removeClass('edit');
|
||||
};
|
||||
});
|
||||
Template.TestListRow.helpers({
|
||||
measureName: function(measureId) {
|
||||
return Template.instance().parentTemplate(1).measures.get(measureId).name;
|
||||
},
|
||||
measures: function() {
|
||||
let product = Meteor.collections.Products.findOne(this.productId);
|
||||
|
||||
return product.measures;
|
||||
},
|
||||
isSelected: function(measureId) {
|
||||
return this.measureIds.includes(measureId);
|
||||
},
|
||||
isProduct: function() {
|
||||
return !!this.productId;
|
||||
},
|
||||
showMeasures: function() {
|
||||
return Session.get(PREFIX + "showMeasures");
|
||||
}
|
||||
});
|
||||
Template.TestListRow.events({
|
||||
'dblclick .heading .name': function(event, template) {
|
||||
template.$('.nameEditor, .headingNameRow').addClass('edit');
|
||||
template.$('input[name="name"]').select();
|
||||
},
|
||||
'blur .heading input[name="name"]': function(event, template) {
|
||||
template.handleHeaderEditorApplyAndClose();
|
||||
},
|
||||
'keyup .heading input[name="name"]': function(event, template) {
|
||||
if(event.which === 13 || event.which === 9) { //Enter or Tab
|
||||
template.handleHeaderEditorApplyAndClose();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
else if(event.which === 27) { //Escape
|
||||
template.handleHeaderEditorCancelAndClose();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'click .heading .accept': function(event, template) {
|
||||
template.handleHeaderEditorApplyAndClose();
|
||||
},
|
||||
'click .heading .reject': function(event, template) {
|
||||
template.handleHeaderEditorCancelAndClose();
|
||||
},
|
||||
'dblclick .product .name': function(event, template) {
|
||||
template.$('.nameEditor, .name').addClass('edit');
|
||||
template.$('input[name="name"]').select();
|
||||
},
|
||||
'blur .product input[name="name"]': function(event, template) {
|
||||
template.handleProductEditorApplyAndClose();
|
||||
},
|
||||
'keyup .product input[name="name"]': function(event, template) {
|
||||
if(event.which === 13 || event.which === 9) { //Enter or Tab
|
||||
template.handleProductEditorApplyAndClose();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
else if(event.which === 27) { //Escape
|
||||
template.handleProductEditorCancelAndClose();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'click .product .accept': function(event, template) {
|
||||
template.handleProductEditorApplyAndClose();
|
||||
},
|
||||
'click .product .reject': function(event, template) {
|
||||
template.handleProductEditorCancelAndClose();
|
||||
},
|
||||
'click .measureButton': function(event, template) {
|
||||
let measureId = $(event.target).data("model");
|
||||
|
||||
$(event.target).toggleClass("selected");
|
||||
|
||||
if(this.measureIds.includes(measureId))
|
||||
this.measureIds.remove(measureId);
|
||||
else
|
||||
this.measureIds.add(measureId);
|
||||
},
|
||||
'click .heading .sort': function(event, template) {
|
||||
let width = event.currentTarget.offsetWidth;
|
||||
let x = event.pageX - event.currentTarget.offsetLeft;
|
||||
let sortAlphabetical = x <= (width / 2);
|
||||
let headingIndex = template.$(event.target).closest(".heading").index();
|
||||
let firstIndex = headingIndex + 1;
|
||||
let products = template.parentTemplate(1).salesSheet.products;
|
||||
let length = 0;
|
||||
|
||||
while(firstIndex + length < products.length && products[firstIndex + length].productId) {
|
||||
length++;
|
||||
}
|
||||
|
||||
//Sort the part of the array that contains products under the sorted heading.
|
||||
products.partialSort(firstIndex, length, function(a, b) {
|
||||
return sortAlphabetical ? (a.name < b.name ? -1 : 1) : (a.name > b.name ? -1 : 1);
|
||||
});
|
||||
|
||||
//Notify anything depending on the products list that they have been modified.
|
||||
template.parentTemplate(1).productsDependency.changed();
|
||||
}
|
||||
});
|
||||
2
imports/ui/layouts/Body.import.styl
vendored
2
imports/ui/layouts/Body.import.styl
vendored
@@ -80,7 +80,7 @@
|
||||
display: block
|
||||
a
|
||||
color: black
|
||||
padding: 15px 20px
|
||||
padding: 10px 20px
|
||||
cursor: pointer
|
||||
text-decoration: none
|
||||
display: block
|
||||
|
||||
Reference in New Issue
Block a user