Fixed all known bugs; Modified the menu to hide; Fixed the tables to scroll with a fixed header.

This commit is contained in:
Wynne Crisman
2017-10-20 14:54:58 -07:00
parent 83e8268375
commit f848ea9a8f
34 changed files with 1019 additions and 821 deletions

View File

@@ -153,6 +153,10 @@ body
overflow: visible !important
max-width: none !important
// Keep the custom scroll bars on top so they can be interacted with. They are placed outside the content div that they scroll.
.mCSB_1_scrollbar
z-index: 999
@import "../imports/ui/styles/effects.import.styl"
@import "../imports/ui/styles/buttons.import.styl"
@import "../imports/ui/styles/maxHeightLayout.import.styl"

View File

@@ -296,7 +296,7 @@ if(Meteor.isServer) {
comment: Match.Optional(String)
});
let dateString = date.toString();
let dateString = sale.date.toString();
sale.createdAt = new Date();
sale.timestamp = new Date(dateString.substring(0, 4) + "-" + dateString.substring(4, 6) + "-" + dateString.substring(6, 8) + "T00:00:00Z");

View File

@@ -9,22 +9,22 @@ if(Meteor.isServer) {
});
Meteor.methods({
"insertUser": function(user, roles) {
"insertUser": function(user) {
check(user, {
username: String,
email: String
email: String,
roles: [String]
});
check(roles, [String]);
//Verify the currently logged in user has authority to manage users.
if(Roles.userIsInRole(this.userId, [Meteor.UserRoles.ROLE_MANAGE])) {
//Verify the user name isn't already used.
if(Meteor.collections.Users.findOne({username: user.username}) == undefined) {
if(Meteor.collections.Users.findOne({username: user.username}) === undefined) {
let pwd = Random.secret(20);
let id = Accounts.createUser({password: pwd, username: user.username, email: user.email});
//Requires the alanning:roles package.
Roles.addUsersToRoles(id, roles);
Roles.addUsersToRoles(id, user.roles);
}
else {
throw new Meteor.Error(400, "User already exists.");

View File

@@ -2,20 +2,19 @@
<div id="measures">
{{#if Template.subscriptionsReady}}
<div class="tableControls">
<div class="showHidden">
<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>
<div class="listRow">
<div class="listCell">
<div class="tableContainer">
<div class="contentControls">
<a class="loadMoreLink {{#if disableLoadMore}}disabled{{/if}}" href="javascript:">Load More...</a>
</div>
</div>
<div class="separatedTableHeader">
<table class="table table-striped table-hover">
<thead>
<tr>
@@ -23,11 +22,16 @@
<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>
</table>
</div>
<div class="listRow">
<div class="listCell">
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
<table class="table table-striped table-hover">
<tbody>
{{#if displayNewMeasure}}
{{> MeasureEditor isNew=true}}
<tr>{{> MeasureEditor isNew=true}}</tr>
{{/if}}
{{#each measures}}
{{> Measure}}
@@ -47,15 +51,15 @@
{{#if editing}}
{{> MeasureEditor}}
{{else}}
<td class="noselect nonclickable left">{{name}}</td>
<td class="noselect nonclickable left">{{postfix}}</td>
<td class="name noselect nonclickable left">{{name}}</td>
<td class="postfix noselect nonclickable left">{{postfix}}</td>
{{#if hidden}}
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionShow fa fa-eye fa-lg noselect clickable" title="Show" aria-hidden="true"></i></td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionShow fa fa-eye fa-lg noselect clickable" title="Show" aria-hidden="true"></i></td>
{{else}}
{{#if deactivated}}
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionActivate fa fa-toggle-on fa-lg noselect clickable" title="Activate" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionHide fa fa-eye-slash fa-lg noselect clickable" title="Hide" aria-hidden="true"></i></td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionActivate fa fa-toggle-on fa-lg noselect clickable" title="Activate" aria-hidden="true"></i>&nbsp;/&nbsp;<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>&nbsp;/&nbsp;<i class="actionRemove fa fa-times-circle fa-lg noselect clickable" title="Deactivate" aria-hidden="true"></i></td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionRemove fa fa-times-circle fa-lg noselect clickable" title="Deactivate" aria-hidden="true"></i></td>
{{/if}}
{{/if}}
{{/if}}

View File

@@ -7,8 +7,13 @@
text-align: left
.tableControls
display: table
width: 100%
text-align: right
margin-right: 20px
.showHidden
display: table-cell
width: 100%
.controlLabel
font-size: 9px
font-weight: 700
@@ -20,7 +25,55 @@
position: relative
top: -4px
display: inline-block
.contentControls
vertical-align: bottom
display: table-cell
text-align: right
min-width: 100px
a
font-size: 12px
font-family: "Arial", san-serif
font-weight: 800
color: #2d1b8c
text-decoration: none
a:hover
text-decoration: underline
a.disabled
visibility: hidden
.table
table-layout: fixed
min-width: 100%
thead, tbody
> tr
> .name
width: 50%
min-width: 100px
> .postfix
width: 50%
min-width: 100px
> .actions
width: 90px
min-width: 90px
.separatedTableHeader
table
thead
> tr
.actions
text-align: center
.newMeasureButton
margin-top: 4px
padding: 0 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
.listRow
display: table-row
.listCell
@@ -57,28 +110,8 @@
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
visibility: hidden
> tbody
> tr
.actionRemove

View File

@@ -1,7 +1,8 @@
import './Measures.html';
let QUERY_LIMIT = 20;
let QUERY_LIMIT = 100;
let QUERY_LIMIT_INCREMENT = 100;
let PREFIX = "Measures.";
Tracker.autorun(function() {
@@ -11,6 +12,15 @@ Tracker.autorun(function() {
Template.Measures.onCreated(function() {
Session.set(PREFIX + "displayNewMeasure", false);
Session.set(PREFIX + "showHidden", false);
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
});
Template.Measures.onRendered(function() {
$(".tableContainer").mCustomScrollbar({
scrollButtons: {enable:true},
theme: "light-thick",
scrollbarPosition: "outside",
scrollEasing: "linear"
});
});
Template.Measures.helpers({
displayNewMeasure: function() {
@@ -45,23 +55,16 @@ Template.Measures.helpers({
dbQuery = dbQuery.length > 0 ? {$and: dbQuery} : {};
Session.set(PREFIX + 'measureCount', Meteor.collections.Measures.find(dbQuery).count()); //Always get a full count.
return Meteor.collections.Measures.find(dbQuery, {limit: QUERY_LIMIT, skip: skipCount, sort: {order: 1}});
return Meteor.collections.Measures.find(dbQuery, {limit: Session.get(PREFIX + "queryLimit"), skip: skipCount, sort: {order: 1}});
},
disablePrev: function() {
return (Session.get(PREFIX + 'skipCount') || 0) == 0;
},
disableNext: function() {
return Session.get(PREFIX + 'measureCount') - (Session.get(PREFIX + 'skipCount') || 0) - QUERY_LIMIT <= 0;
disableLoadMore: function() {
return Session.get(PREFIX + 'measureCount') - (Session.get(PREFIX + 'skipCount') || 0) - Session.get(PREFIX + "queryLimit") <= 0;
}
});
Template.Measures.events({
'click .prevMeasures': function(event, template) {
if(!$(event.target).hasClass('disabled'))
Session.set(PREFIX + 'skipCount', Math.max(0, (Session.get(PREFIX + 'skipCount') || 0) - QUERY_LIMIT));
},
'click .nextMeasures': function(event, template) {
if(!$(event.target).hasClass('disabled'))
Session.set(PREFIX + 'skipCount', (Session.get(PREFIX + 'skipCount') || 0) + QUERY_LIMIT);
'click .loadMoreLink': function(event, template) {
event.preventDefault();
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
},
'click .newMeasureButton': function(event, template) {
if(template.$('.newMeasureButton').hasClass('active')) {
@@ -150,7 +153,7 @@ Template.Measure.events({
"click .actionEdit": function(event, template) {
Session.set(PREFIX + "editedMeasure", this._id);
Session.set(PREFIX + 'displayNewMeasure', false); //Ensure the new measure editor is closed.
template.$('.newMeasureButton').removeClass('active');
template.parentTemplate().$('.newMeasureButton').removeClass('active');
},
"click .actionRemove": function(event, template) {
Meteor.call('deactivateMeasure', this._id, function(error, result) {

View File

@@ -6,7 +6,7 @@
<label class='controlLabel'>Selected Measure: </label>
<select name="measures">
{{#each measures}}
<option value="{{_id}}">{{name}}</option>
<option class="{{#if deactivated}}deactivated{{/if}}" value="{{_id}}">{{name}}</option>
{{/each}}
</select>
</div>
@@ -28,14 +28,11 @@
<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 class="contentControls">
<a class="loadMoreLink {{#if disableLoadMore}}disabled{{/if}}" href="javascript:">Load More...</a>
</div>
<div class="listRow">
<div class="listCell">
<div class="tableContainer">
</div>
<div class="separatedTableHeader">
<table class="table table-striped table-hover">
<thead>
<tr>
@@ -45,6 +42,12 @@
<th class="previous">Previous</th>
</tr>
</thead>
</table>
</div>
<div class="listRow">
<div class="listCell">
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
<table class="table table-striped table-hover">
<tbody>
{{#each product}}
{{> PricingForProduct}}
@@ -61,9 +64,9 @@
<template name="PricingForProduct">
<tr class="clickable noselect {{rowClass}}">
<td class="tdLarge noselect nonclickable left">{{name}}</td>
<td class="tdLarge noselect nonclickable left">{{currentPrice}}</td>
<td class="tdLarge noselect nonclickable left">{{priceChangeDate}}</td>
<td class="tdLarge noselect nonclickable left">{{previousPrice}}</td>
<td class="name tdLarge noselect nonclickable left">{{name}}</td>
<td class="current tdLarge noselect nonclickable left">{{currentPrice}}</td>
<td class="changeDate tdLarge noselect nonclickable left">{{priceChangeDate}}</td>
<td class="previous tdLarge noselect nonclickable left">{{previousPrice}}</td>
</tr>
</template>

View File

@@ -18,6 +18,8 @@
width: 240px
select
width: 100%
option.deactivated
color: #a6a6a6
.controlGroup
padding: 4px 8px
margin: 4px 8px
@@ -62,7 +64,38 @@
display: inline-block
.resetButton
margin-left: 20px
.contentControls
vertical-align: bottom
display: table-cell
text-align: right
a
font-size: 12px
font-family: "Arial", san-serif
font-weight: 800
color: #2d1b8c
text-decoration: none
a:hover
text-decoration: underline
a.disabled
visibility: hidden
.table
table-layout: fixed
min-width: 100%
thead, tbody
tr
> .name
width: 100%
min-width: 100px
> .current
width: 200px
min-width: 200px
> .previous
width: 200px
min-width: 200px
> .changeDate
width: 200px
min-width: 200px
.listRow
display: table-row
.listCell
@@ -85,15 +118,8 @@
table-layout: fixed
width: 100%
> thead
> tr
> th.name
width: auto
> th.current
width: 200px
> th.previous
width: 200px
> th.changeDate
width: 200px
visibility: hidden
display: none
> tbody
> tr.deactivated
background-color: #fac0d1

View File

@@ -8,7 +8,8 @@ import './Pricing.html';
* 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 QUERY_LIMIT = 100;
let QUERY_LIMIT_INCREMENT = 100;
let PREFIX = "Pricing.";
Meteor.subscribe("products");
@@ -24,14 +25,22 @@ Tracker.autorun(function() {
});
Template.Pricing.onCreated(function() {
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
});
Template.Pricing.onRendered(function() {
this.$('input[name="date"]').val(new Date().toDateInputValue());
// this>$('select[name="measures"]').val()
$(".tableContainer").mCustomScrollbar({
scrollButtons: {enable:true},
theme: "light-thick",
scrollbarPosition: "outside",
scrollEasing: "linear"
});
});
Template.Pricing.helpers({
measures: function() {
let measures = Meteor.collections.Measures.find({}, {sort: {order: 1}}).fetch();
let measures = Meteor.collections.Measures.find({$or: [{hidden: false}, {hidden: {$exists: false}}]}, {sort: {order: 1}}).fetch();
for(let i = 0; i < measures; i++) {
if(Meteor.collections.Products.find({measures: {$all: [measures[i]._id]}}, {sort: {name: 1}}).count() == 0)
@@ -46,18 +55,20 @@ Template.Pricing.helpers({
let dbQuery = {measures: {$all: [measureId]}, $or: [{hidden: false}, {hidden: {$exists:false}}]};
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}});
return Meteor.collections.Products.find(dbQuery, {limit: Session.get(PREFIX + "queryLimit"), 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;
disableLoadMore: function() {
return Session.get(PREFIX + 'productCount') - (Session.get(PREFIX + 'skipCount') || 0) - Session.get(PREFIX + "queryLimit") <= 0;
}
});
Template.Pricing.events({
'click .loadMoreLink': function(event, template) {
event.preventDefault();
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
},
'change select[name="measures"]': function(event, template) {
Session.get(PREFIX + 'skipCount', 0);
Session.set(PREFIX + 'skipCount', 0);
Session.set(PREFIX + 'queryLimit', QUERY_LIMIT);
Session.set(PREFIX + "selectedMeasure", $(event.target).val());
},
'click .applyButton': function(event, template) {
@@ -95,14 +106,6 @@ Template.Pricing.events({
template.$('input.price').val(0);
template.$('input.date').val(new Date().toDateInputValue());
template.$('input[name="setPrevious"]').removeProp('checked');
},
'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);
}
});

View File

@@ -20,15 +20,12 @@
{{> ProductTag}}
{{/each}}
</span>
<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 class="contentControls">
<a class="loadMoreLink {{#if disableLoadMore}}disabled{{/if}}" href="javascript:">Load More...</a>
</div>
</div>
<div class="listRow">
<div class="listCell">
<div class="tableContainer">
</div>
<div class="separatedTableHeader">
<table class="table table-striped table-hover">
<thead>
<tr>
@@ -36,6 +33,12 @@
<th class="tags">Tags {{>ProductTag_ProductSearch columnName='tags' collectionQueryColumnName='name' collection='ProductTags' collectionResultColumnName='_id'}}</th>
</tr>
</thead>
</table>
</div>
<div class="listRow">
<div class="listCell">
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
<table class="table table-striped table-hover">
<tbody>
{{#each products}}
{{> ProductTag_Product}}
@@ -56,8 +59,8 @@
<template name="ProductTag_Product">
<tr class="{{rowClass}}">
<td class="tdLarge noselect nonclickable left">{{name}}</td>
<td class="tdLarge noselect nonclickable left">{{tags}}</td>
<td class="name tdLarge noselect nonclickable left">{{name}}</td>
<td class="tags tdLarge noselect nonclickable left">{{tags}}</td>
</tr>
</template>

View File

@@ -106,10 +106,32 @@
border: 0
padding: 0
margin: 0
.pagination
.contentControls
vertical-align: bottom
display: table-cell
width: 240px
vertical-align: bottom;
text-align: right
min-width: 100px
a
font-size: 12px
font-family: "Arial", san-serif
font-weight: 800
color: #2d1b8c
text-decoration: none
a:hover
text-decoration: underline
a.disabled
visibility: hidden
.table
table-layout: fixed
min-width: 100%
thead, tbody
> tr
> .name
width: 100%
min-width: 100px
> .tags
width: 100%
min-width: 100px
.listRow
display: table-row
.listCell
@@ -132,11 +154,6 @@
table-layout: fixed
width: 100%
> thead
> tr
> th.name
width: auto
> th.tags
width: auto
> tbody
> tr.deactivated
background-color: #fac0d1

View File

@@ -1,7 +1,8 @@
import './ProductTags.html';
let QUERY_LIMIT = 20;
let QUERY_LIMIT = 100;
let QUERY_LIMIT_INCREMENT = 100;
let PREFIX = "ProductTags.";
Tracker.autorun(function() {
@@ -11,6 +12,15 @@ Tracker.autorun(function() {
Template.ProductTags.onCreated(function() {
Session.set(PREFIX + "editTags", false);
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
});
Template.ProductTags.onRendered(function() {
$(".tableContainer").mCustomScrollbar({
scrollButtons: {enable:true},
theme: "light-thick",
scrollbarPosition: "outside",
scrollEasing: "linear"
});
});
Template.ProductTags.helpers({
productTags: function() {
@@ -60,13 +70,10 @@ Template.ProductTags.helpers({
//Collect a count of the products first, and store it in the session.
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}});
return Meteor.collections.Products.find(dbQuery, {limit: Session.get(PREFIX + "queryLimit"), 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;
disableLoadMore: function() {
return Session.get(PREFIX + 'productCount') - (Session.get(PREFIX + 'skipCount') || 0) - Session.get(PREFIX + "queryLimit") <= 0;
}
});
Template.ProductTags.events({
@@ -86,14 +93,10 @@ Template.ProductTags.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 .loadMoreLink': function(event, template) {
event.preventDefault();
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
},
'click .nextProducts': function(event, template) {
if(!$(event.target).hasClass('disabled'))
Session.set(PREFIX + 'skipCount', (Session.get(PREFIX + 'skipCount') || 0) + QUERY_LIMIT);
}
});
Template.ProductTag.onCreated(function() {

View File

@@ -2,20 +2,19 @@
<div id="products">
{{#if Template.subscriptionsReady}}
<div class="tableControls">
<div class="showHidden">
<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>
<div class="listRow">
<div class="listCell">
<div class="tableContainer">
<div class="contentControls">
<a class="loadMoreLink {{#if disableLoadMore}}disabled{{/if}}" href="javascript:">Load More...</a>
</div>
</div>
<div class="separatedTableHeader">
<table class="table table-striped table-hover">
<thead>
<tr>
@@ -25,8 +24,13 @@
<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>
</table>
</div>
<div class="listRow">
<div class="listCell">
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
<table class="table table-striped table-hover">
<tbody>
{{#if displayNewProduct}}
{{> ProductEditor isNew=true}}
@@ -52,17 +56,17 @@
{{#if converting}}
{{> ConvertProduct}}
{{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>
<td class="name noselect nonclickable left">{{name}}</td>
<td class="tags noselect nonclickable left">{{tags}}</td>
<td class="aliases noselect nonclickable left">{{aliases}}</td>
<td class="measures 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>&nbsp;/&nbsp;<i class="actionShow fa fa-eye fa-lg noselect clickable" title="Show" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionConvert fa fa-exclamation-triangle fa-lg noselect clickable" title="Convert" aria-hidden="true"></i></td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionShow fa fa-eye fa-lg noselect clickable" title="Show" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionConvert fa fa-exclamation-triangle fa-lg noselect clickable" title="Convert" aria-hidden="true"></i></td>
{{else}}
{{#if deactivated}}
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionActivate fa fa-toggle-on fa-lg noselect clickable" title="Activate" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionHide fa fa-eye-slash fa-lg noselect clickable" title="Hide" aria-hidden="true"></i></td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionActivate fa fa-toggle-on fa-lg noselect clickable" title="Activate" aria-hidden="true"></i>&nbsp;/&nbsp;<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>&nbsp;/&nbsp;<i class="actionDeactivate fa fa-times-circle fa-lg noselect clickable" title="Deactivate" aria-hidden="true"></i></td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionDeactivate fa fa-times-circle fa-lg noselect clickable" title="Deactivate" aria-hidden="true"></i></td>
{{/if}}
{{/if}}
{{/if}}

View File

@@ -9,6 +9,13 @@
.tableControls
text-align: right
margin-right: 20px
margin-bottom: 4px
display: table
width: 100%
.showHidden
display: table-cell
text-align: right
width: 100%
.controlLabel
font-size: 9px
font-weight: 700
@@ -20,7 +27,62 @@
position: relative
top: -4px
display: inline-block
.contentControls
vertical-align: bottom
display: table-cell
text-align: right
min-width: 100px
a
font-size: 12px
font-family: "Arial", san-serif
font-weight: 800
color: #2d1b8c
text-decoration: none
a:hover
text-decoration: underline
a.disabled
visibility: hidden
.table
table-layout: fixed
min-width: 100%
thead, tbody
> tr
> .name
//width: auto
width: 100%
> .tags
width: 220px
min-width: 220px
> .aliases
width: 220px
min-width: 220px
> .measures
width: 220px
min-width: 220px
> .actions
width: 90px
min-width: 90px
.separatedTableHeader
table
thead
> tr
> th.actions
text-align: center
.newProductButton
margin-top: 4px
padding: 0 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
.listRow
display: table-row
.listCell
@@ -40,8 +102,9 @@
font-size: 12.5px
overflow-y: auto
table
table-layout: fixed
width: 100%
thead
visibility: hidden
display: none
.productSearch
margin: 3px 0 2px 1px
.productEditorTd
@@ -56,33 +119,6 @@
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

View File

@@ -1,7 +1,8 @@
import './Products.html';
let QUERY_LIMIT = 20;
let QUERY_LIMIT = 100;
let QUERY_LIMIT_INCREMENT = 100;
let PREFIX = "Products.";
Tracker.autorun(function() {
@@ -13,6 +14,15 @@ Tracker.autorun(function() {
Template.Products.onCreated(function() {
Session.set(PREFIX + "displayNewProduct", false);
Session.set(PREFIX + "showHidden", false);
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
});
Template.Products.onRendered(function() {
$(".tableContainer").mCustomScrollbar({
scrollButtons: {enable:true},
theme: "light-thick",
scrollbarPosition: "outside",
scrollEasing: "linear"
});
});
Template.Products.helpers({
displayNewProduct: function() {
@@ -47,23 +57,16 @@ Template.Products.helpers({
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}});
return Meteor.collections.Products.find(dbQuery, {limit: Session.get(PREFIX + "queryLimit"), 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;
disableLoadMore: function() {
return Session.get(PREFIX + 'productCount') - (Session.get(PREFIX + 'skipCount') || 0) - Session.get(PREFIX + "queryLimit") <= 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 .loadMoreLink': function(event, template) {
event.preventDefault();
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
},
'click .newProductButton': function(event, template) {
if(template.$('.newProductButton').hasClass('active')) {
@@ -181,7 +184,7 @@ Template.Product.events({
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');
template.parentTemplate().$('.newProductButton').removeClass('active');
},
"click .actionDeactivate": function(event, template) {
Meteor.call('deactivateProduct', this._id, function(error, result) {

View File

@@ -36,18 +36,6 @@
<div class="listCell">
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
<table class="table table-striped table-hover">
<thead>
<tr>
<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>
{{#if displayNewSale}}
{{> InsertSale}}
@@ -71,14 +59,14 @@
<template name="Sale">
<tr>
<td class="tdLarge noselect nonclickable center">{{amount}}</td>
<td class="tdLarge noselect nonclickable left">{{productName productId}}</td>
<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">{{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>
<td class="amount tdLarge noselect nonclickable center">{{amount}}</td>
<td class="product tdLarge noselect nonclickable left">{{productName productId}}</td>
<td class="price tdLarge noselect nonclickable left">{{formatPrice price}}{{#if showTotalPrice amount}} ({{formatTotalPrice price amount}}){{/if}}</td>
<td class="measure tdLarge noselect nonclickable left">{{measureName measureId}}</td>
<td class="saleDate tdLarge noselect nonclickable left">{{formatDateAndWeek date weekOfYear}}</td>
<td class="createdDate tdLarge noselect nonclickable left">{{formatDateTime createdAt}}</td>
<td class="venue tdLarge noselect nonclickable left">{{venueName venueId}}</td>
<td class="actions 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>
</template>
@@ -115,7 +103,7 @@
<template name="InsertSale">
<tr>
<td colspan="8">
<form class="insertSaleForm" autocomplete="off">
<form name="insertSaleForm" class="insertSaleForm" autocomplete="off">
<div class="grid">
<div class="col-4-12">
<div class="formGroupHeading">New Sale</div>

View File

@@ -22,37 +22,44 @@
.table
table-layout: fixed
min-width: 100%
thead
thead, tbody
> tr
> th.amount
> .amount
width: 90px
> th.product
width: auto
min-width: 90px
> .product
width: 100%
min-width: 140px
> th.price
> .price
width: 140px
> th.measure
min-width: 140px
> .measure
width: 100px
> th.saleDate
width: 140px
> th.createdDate
min-width: 100px
> .saleDate
width: 150px
min-width: 150px
> .createdDate
width: 100px
> th.venue
min-width: 100px
> .venue
width: 160px
> th.actions
min-width: 160px
> .actions
width: 90px
min-width: 90px
.separatedTableHeader
table
thead
> tr
> th.actions
.newSaleButton
padding: 0px 12px
padding: 0 12px
.fa-plus-circle
display: inline-block
.fa-times-circle
display: none
.newSaleButton:active
.newSaleButton.active
background-color: #fb557b
color: black
.fa-times-circle
@@ -67,6 +74,9 @@
text-shadow: 0px 0px 10px #ff6d1f
.showOnlyComments.on
color: white
> th.saleDate
input
width: 130px
.listRow
display: table-row
.listCell
@@ -82,16 +92,16 @@
right: 0
width: auto
height: auto
//width: 100%
//margin-bottom: 20px
border: 0
font-size: 12.5px
overflow-y: scroll
//height: 100%
label
font-size: 10px
font-weight: 800
table
thead
visibility: hidden
display: none
.saleRemove
color: red
margin-left: 8px
@@ -102,9 +112,10 @@
color: green
.editorCancel
color: red
thead
visibility: hidden
display: none
.saleDate
text-align: left
.createdDate
text-align: left
.editComment
color: grey
.hasComment

View File

@@ -38,18 +38,12 @@ Template.Sales.onCreated(function() {
});
});
Template.Sales.onRendered(function() {
//$('.tableContainer').mCustomScrollbar();
$(".tableContainer").mCustomScrollbar({
scrollButtons:{enable:true},
theme:"light-thick",
scrollbarPosition:"outside"
scrollButtons: {enable:true},
theme: "light-thick",
scrollbarPosition: "outside",
scrollEasing: "linear"
});
//(function($){
// $(window).on("load",function(){
// $(".tableContainer").mCustomScrollbar();
// });
//})(jQuery);
//$('#test').mCustomScrollbar();
});
Template.Sales.onDestroyed(function() {
if(Template.Sales.salesSubscription) {
@@ -85,6 +79,11 @@ Template.Sales.events({
else {
Session.set(PREFIX + 'displayNewSale', true);
Session.set(PREFIX + "editedSale", undefined); //Clear the edited sale so that only one editor is open at a time.
//Set the focus to the date field of the form. Put this in a timeout so that it is queued for processing after the form is added to the DOM.
setTimeout(function() {
$("form[name='insertSaleForm'] input[name='date']").focus();
}, 10);
}
template.$('.newSaleButton').toggleClass('active');
},
@@ -123,8 +122,8 @@ Template.Sale.helpers({
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)");
formatDateAndWeek: function(date, weekOfYear) {
return moment.utc(date.toString(), "YYYYMMDD").utc().format("MM/DD/YYYY") + "(" + weekOfYear + ")";
},
formatDateTime: function(date) {
return moment.utc(date).format("MM/DD/YYYY");
@@ -146,7 +145,7 @@ Template.Sale.events({
"click .actionEdit": function(event, template) {
Session.set(PREFIX + "editedSale", this._id);
Session.set(PREFIX + 'displayNewSale', false); //Ensure the new sale editor is closed.
template.$('.newSaleButton').removeClass('active');
template.parentTemplate().$('.newSaleButton').removeClass('active');
},
"click .saleRemove": function(event, template) {
let _this = this;
@@ -441,7 +440,6 @@ 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++) {
@@ -449,6 +447,11 @@ Template.InsertSale.events({
nextMeasure.find(".amount").val(0);
}
//Set the focus to the product field of the form.
$("form[name='insertSaleForm'] input[name='product']").focus();
//Clear the product since it is highly unlikely the same product will be added twice for the same date and market.
$("form[name='insertSaleForm'] input[name='product']").val("");
}
});
}

View File

@@ -70,7 +70,7 @@
<template name="SalesSheetEditorConfigurationRow">
{{#if isProduct}} {{! PRODUCT }}
<div class="product columnContent noselect" data-model="{{productId}}">
<div class="name clickable">{{name}}</div>
<div class="name clickable"></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>
{{#if showMeasures}}
<div class="measures">
@@ -82,7 +82,7 @@
</div>
{{else}} {{! HEADING }}
<div class="heading columnContent noselect">
<div class="headingNameRow"><span class="name clickable">{{name}}</span><span class="sort clickable noselect"><i class="fa fa-arrow-down" aria-hidden="true"></i> sort <i class="fa fa-arrow-up" aria-hidden="true"></i></span></div>
<div class="headingNameRow"><span class="name clickable"></span><span class="sort clickable noselect"><i class="fa fa-arrow-down" aria-hidden="true"></i> sort <i class="fa fa-arrow-up" aria-hidden="true"></i></span></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>
{{/if}}

View File

@@ -354,6 +354,40 @@ Template.SalesSheetEditorConfigurationRow.onCreated(function() {
template.$('.product .name').text(name);
template.$('.product .nameEditor, .product .name').removeClass('edit');
};
this.handleHeadingSort = function(event) {
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();
};
});
Template.SalesSheetEditorConfigurationRow.onRendered(function() {
let template = this;
Tracker.autorun(function() {
let data = Blaze.getData(template.view);
template.parentTemplate(1).productsDependency.depend();
if(data) {
template.$('.heading .name, .product .name').text(data.name);
}
});
});
Template.SalesSheetEditorConfigurationRow.helpers({
measureName: function(measureId) {
@@ -436,24 +470,6 @@ Template.SalesSheetEditorConfigurationRow.events({
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();
template.handleHeadingSort(event);
}
});

View File

@@ -29,19 +29,28 @@ Template.SalesSheetForm.onCreated(function() {
// Place the sales sheet in a reactive var and put the setting of the reactive var in an autorun.
// The autorun is needed apparently to ensure changes to the data force a change in the reactive var.
Tracker.autorun(function() {
let data = Blaze.getData(template.view);
//****
// Note: I have commented the code below out because I was unable to get Meteor to properly (according to their docs on Template.currentData() being reactive) call my code when the template's input data changes.
// Because of this I have instead used the parent template's session variable "selectedSheet" to detect and handle the template data changing.
// To avoid session variable conflicts, I have been using a session variable name prefix that is the template's name followed by a '.' as a convention.
//****
//Force this to be reactive on the current data.
try {
Template.currentData();
} catch(err) {
// Ignore it. This always has an error accessing the currentData as the template is destroyed.
}
//try {
// Template.currentData();
//} catch(err) {
// // Ignore it. This always has an error accessing the currentData as the template is destroyed.
//}
//For some reason the current data is not always set, and does not always equal the template.data. We will use the template.data to get the actual ID of the sales sheet for the query.
template.salesSheet.set(Meteor.collections.SalesSheets.findOne(template.data));
//template.salesSheet.set(Template.instance() ? Meteor.collections.SalesSheets.findOne(Template.currentData()) : null);
//template.salesSheet.set(Session.get("SalesSheets." + "selectedSheet"));
template.salesSheet.set(Meteor.collections.SalesSheets.findOne(data));
});
Tracker.autorun(function() {
let products = template.salesSheet.get().products;
let products = template.salesSheet.get() ? template.salesSheet.get().products : [];
let index = 1;
// Note: We will ignore orphaned data in the dictionary so we don't have to clear the dictionary, or identify the orphans. The orphans are just a few extra product id's mapped to booleans, and should be fairly rare anyway.
@@ -103,7 +112,7 @@ Template.SalesSheetForm.events({
event.preventDefault();
template.$('.sheetHeader').data('bs.validator').validate(function(isValid) {
if(isValid) {
let date = template.selectedDate.get();
let date = ~~(moment(template.selectedDate.get()).format("YYYYMMDD"));
let venueId = template.selectedVenue.get()._id;
// Track the inserts and errors, display output to the user and log when everything is done.
let insertMetadata = {

View File

@@ -15,7 +15,7 @@
<label style="margin-right: 10px">Selected Sheet</label>
<select name="sheetSelection" class="form-control">
{{#each sheets}}
<option value="{{_id}}" {{sheetsSelectIsSelected this isFirst}}>{{name}}</option>
<option value="{{_id}}" {{isSheetSelected}}>{{name}}</option>
{{/each}}
</select>
<i class="fa fa-wrench editSheet noselect clickable {{#if disableButtons}}disabled{{/if}} {{#if isEditingSheet}}selected{{/if}}" aria-hidden="true">
@@ -27,8 +27,8 @@
<div class="separator" style="width: 50%; opacity: .25"></div>
</section>
<section class="tabSection verticalStack vscExpand">
{{#if isSheetSelected}}
{{>Template.dynamic template=tab data=tabData}}
{{#if hasSelectedSheet}}
{{>Template.dynamic template=activeTemplateName data=selectedSheetId}}
{{/if}}
</section>
</template>

View File

@@ -15,7 +15,7 @@ Template.SalesSheets.onCreated(function() {
Template.SalesSheets.onDestroyed(function() {
// Reset the view's session variables used for navigation.
Session.set(PREFIX + "currentFormName", undefined);
Session.set(PREFIX + "tab", undefined);
Session.set(PREFIX + "activeTemplateName", undefined);
});
//******************************************************************
@@ -24,10 +24,11 @@ Template.SalesSheets.onDestroyed(function() {
Template.SalesSheetsMain.onCreated(function() {
//Save the previous session state - whether we are editing the selected sheet.
//The name of the currently active page tab. This will either be the SalesSheetForm or the SalesSheetEditor.
if(!Session.get(PREFIX + "tab")) Session.set(PREFIX + "tab", "SalesSheetForm");
if(!Session.get(PREFIX + 'selectedSheet')) {
Session.set(PREFIX + 'selectedSheet', Meteor.collections.SalesSheets.findOne({}, {sort: {name: 1}}));
}
if(!Session.get(PREFIX + "activeTemplateName")) Session.set(PREFIX + "activeTemplateName", "SalesSheetForm");
this.sheets = Meteor.collections.SalesSheets.find({}, {sort: {name: 1}});
let sheetArray = this.sheets.fetch();
Session.set(PREFIX + 'selectedSheet', sheetArray.length > 0 ? sheetArray[0] : null);
});
Template.SalesSheetsMain.helpers({
sheets: function() {
@@ -36,35 +37,27 @@ Template.SalesSheetsMain.helpers({
//if(sheets && sheets.length > 0) sheets[0].isFirst = true;
//
//return sheets;
return Meteor.collections.SalesSheets.find({}, {sort: {name: 1}});
return Template.instance().sheets;
},
sheetsSelectIsSelected: function(sheet, isFirst) {
isSheetSelected: function() { // Determines if the passed sheet is the selected sheet and returns either "selected" or "".
let selectedSheet = Session.get(PREFIX + "selectedSheet");
if(!selectedSheet && isFirst) Session.set(PREFIX + "selectedSheet", selectedSheet = sheet);
return selectedSheet == sheet ? "selected" : "";
return selectedSheet == this ? "selected" : "";
},
disableButtons: function() {
//Disable the edit & delete functionality if nothing is selected.
disableButtons: function() { // Disable the edit & delete functionality if nothing is selected.
return !Session.get(PREFIX + "selectedSheet");
},
selected: function() {
//Get whether the current sheet is selected and return the string for use in the option tag.
//return this.isSelected ? "selected" : "";
return this._id == Session.get(PREFIX + 'selectedSheet')._id;
activeTemplateName: function() { // The name of the template actively being shown to the user in the content area.
return Session.get(PREFIX + "activeTemplateName");
},
tab: function() {
return Session.get(PREFIX + "tab");
selectedSheetId: function() { // Gets the ID of the sheet currently selected. This is passed to the template being actively show to the user.
return Session.get(PREFIX + "selectedSheet") ? Session.get(PREFIX + "selectedSheet")._id : null;
},
tabData: function() {
return Session.get(PREFIX + "selectedSheet")._id;
},
isSheetSelected: function() {
hasSelectedSheet: function() { // Determines whether any sheet has been selected.
return Session.get(PREFIX + "selectedSheet");
},
isEditingSheet: function() {
return Session.get(PREFIX + "tab") == "SalesSheetEditor";
return Session.get(PREFIX + "activeTemplateName") == "SalesSheetEditor";
}
});
Template.SalesSheetsMain.events({
@@ -74,16 +67,16 @@ Template.SalesSheetsMain.events({
Session.set(PREFIX + "selectedSheet", selected);
// Reset the editor button & the displayed tab.
Session.set(PREFIX + "tab", "SalesSheetForm");
Session.set(PREFIX + "activeTemplateName", "SalesSheetForm");
},
'click .editSheet': function(event, template) {
if(!$(event.target).hasClass("selected")) {
// Display the editor for the sheet.
Session.set(PREFIX + "tab", "SalesSheetEditor");
Session.set(PREFIX + "activeTemplateName", "SalesSheetEditor");
}
else {
// Remove the sheet editor and show the form to fill out the sheet.
Session.set(PREFIX + "tab", "SalesSheetForm");
Session.set(PREFIX + "activeTemplateName", "SalesSheetForm");
// Reset the editor session variables.
Session.set(PREFIX + "currentFormName", undefined);
}
@@ -137,7 +130,7 @@ Template.SalesSheetsMain.events({
template.$('select[name="sheetSelection"]').val(id);
Session.set(PREFIX + "selectedSheet", selected);
//Display the editor tab.
Session.set(PREFIX + "tab", "SalesSheetEditor");
Session.set(PREFIX + "activeTemplateName", "SalesSheetEditor");
clearInterval(interval);
}
else count++;

View File

@@ -1,32 +1,39 @@
<template name="UserManagement">
<div id="userManagement">
{{#if Template.subscriptionsReady}}
<div class="insert">
{{>UserInsert}}
<div class="tableControls">
<div class="contentControls">
<a class="loadMoreLink {{#if disableLoadMore}}disabled{{/if}}" href="javascript:">Load More...</a>
</div>
<div class="tableContainer">
<table class="dataTable table table-striped table-hover">
</div>
<div class="separatedTableHeader">
<table class="table table-striped table-hover">
<thead>
<tr class="headers">
<th class="username">Username</th>
<th class="email">Email</th>
<tr>
<th class="username">Username {{>UserSearch columnName='username'}}</th>
<th class="email">Email {{>UserSearch columnName='email' collectionQueryColumnName='name' collection='Items' collectionResultColumnName='_id'}}</th>
<th class="roles">Roles</th>
<th class="actions">Actions</th>
</tr>
<tr class="footers">
<th>{{>UserSearch columnName='username'}}</th>
<th>{{>UserSearch columnName='email' collectionQueryColumnName='name' collection='Items' collectionResultColumnName='_id'}}</th>
<th></th>
<th></th>
<th class="actions">Actions <span class="newUserButton 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>
</thead>
</table>
</div>
<div class="listRow">
<div class="listCell">
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
<table class="dataTable table table-striped table-hover">
<tbody>
{{#if displayNewUser}}
<tr>{{> UserEditor isNew=true}}</tr>
{{/if}}
{{#each users}}
{{> User}}
{{/each}}
</tbody>
</table>
</div>
</div>
</div>
{{else}}
{{/if}}
</div>
@@ -35,21 +42,31 @@
<template name="User">
<tr>
{{#if editing}}
<td><input name="username" class="form-control" type="text" value="{{username}}" required></td>
<td><input name="email" class="form-control" type="text" value="{{email}}" required></td>
<td class="roles center" style="font-size: 1.2em">
{{> UserEditor}}
{{else}}
<td class="username tdLarge noselect nonclickable">{{username}}</td>
<td class="email tdLarge noselect nonclickable">{{email}}</td>
<td class="roles tdLarge noselect nonclickable">{{roles}}</td>
<td class="actions center tdLarge"><i class="userRemove fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="userEdit fa fa-pencil-square-o fa-lg noselect clickable" aria-hidden="true"></i></td>
{{/if}}
</tr>
</template>
<template name="UserEditor">
<td colspan="3" class="userEditor measureEditorTd">
<div>
<div class="username editorDiv"><label>User Name:</label><input name="username" class="form-control" type="text" value="{{username}}" autocomplete="off" required></div>
<div class="email editorDiv"><label>User Email:</label><input name="email" class="form-control" type="text" value="{{email}}" autocomplete="off" required></div>
<div class="rolesContainer editorDiv"><label>Roles:</label>
<div class="roles center" style="font-size: 1.2em">
{{#each allRoles}}
<span class="role {{getRoleState this}} noselect">{{name}}</span>
{{/each}}
</div>
</div>
</div>
</td>
<td class="center tdLarge"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
{{else}}
<td class="tdLarge noselect nonclickable">{{username}}</td>
<td class="tdLarge noselect nonclickable">{{email}}</td>
<td class="tdLarge noselect nonclickable">{{roles}}</td>
<td class="center tdLarge"><i class="userRemove fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="userEdit fa fa-pencil-square-o fa-lg noselect clickable" aria-hidden="true"></i></td>
{{/if}}
</tr>
<td class="actions center measureEditorTd"><i class="editorApply fa fa-check-square-o fa-lg noselect clickable" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="editorCancel fa fa-times-circle fa-lg noselect clickable" aria-hidden="true"></i></td>
</template>
<template name="UserSearch">
@@ -57,34 +74,3 @@
<input type="text" class="searchInput" placeholder="Filter..." value="{{searchValue}}" style="width: 90%"/>
</div>
</template>
<template name="UserInsert">
<form name="insert" autocomplete="off">
<div class="grid">
<div class="col-3-12"></div>
<div class="col-6-12">
<div class="formGroupHeading">New User</div>
<div class="form-group">
<label class='control-label'>User Name</label>
<input name="username" type="text" class="form-control" required>
</div>
<div class="form-group">
<label class='control-label'>Email</label>
<input name="email" class="form-control" type="text" required/>
</div>
<div class="form-group">
<label class='control-label'>Roles</label>
<div class="roles">
{{#each allRoles}}
<span class="role">{{name}}</span>
{{/each}}
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-success" value="Create">
</div>
</div>
<div class="col-3-12"></div>
</div>
</form>
</template>

View File

@@ -1,19 +1,31 @@
#userManagement
margin: 20px 20px
display: table
content-box: border-box
padding: 10px 20px
height: 100%
//Flex container options.
flex-flow: column nowrap
justify-content: space-around //Spacing between sales along the primary axis. (vertical spacing for a column layout)
align-items: flex-start //Align the sales 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
width: 100%
text-align: left
.tableControls
display: table
width: 100%
text-align: right
margin-right: 20px
.contentControls
vertical-align: bottom
display: table-cell
text-align: right
min-width: 100px
a
font-size: 12px
font-family: "Arial", san-serif
font-weight: 800
color: #2d1b8c
text-decoration: none
a:hover
text-decoration: underline
a.disabled
visibility: hidden
.editor
height: 100%
overflow-y: auto
@@ -33,26 +45,64 @@
font-style: normal
font-variant: normal
font-weight: 500
.tableContainer
width: 100%
.table
table-layout: fixed
min-width: 100%
thead, tbody
> tr
> .username
width: 50%
min-width: 100px
> .email
width: 50%
min-width: 100px
> .roles
width: 260px
min-width: 260px
> .actions
width: 80px
min-width: 80px
.separatedTableHeader
.actions
text-align: center
.newUserButton
margin-top: 4px
padding: 0 12px
.fa-plus-circle
display: inline-block
.fa-times-circle
display: none
.newUserButton.active
background-color: #fb557b
color: black
.fa-times-circle
display: inline-block
.fa-plus-circle
display: none
.listRow
display: table-row
.listCell
display: table-cell
position: relative
height: 100%
margin-top: 20px
margin-bottom: 20px
width: 100%
.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.username
width: auto
> th.email
width: auto
> th.roles
width: 260px
> th.actions
width: 80px
display: none
visibility: hidden
.userRemove
color: red
.userEdit
@@ -61,6 +111,14 @@
color: green
.editorCancel
color: red
.userEditor > div
display: table
> div
display: table-cell
padding: 10px
.roles
.role
vertical-align: middle
td.roles
.role
padding: 4px 4px

View File

@@ -2,6 +2,8 @@
import './UserManagement.html';
import '/imports/util/selectize/selectize.js'
let QUERY_LIMIT = 100;
let QUERY_LIMIT_INCREMENT = 100;
let PREFIX = "UserManagement";
Tracker.autorun(function() {
@@ -9,9 +11,71 @@ Tracker.autorun(function() {
Meteor.subscribe("roles");
});
Template.UserManagement.onCreated(function() {
Session.set(PREFIX + "displayNewUser", false);
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
});
Template.UserManagement.onRendered(function() {
$(".tableContainer").mCustomScrollbar({
scrollButtons: {enable:true},
theme: "light-thick",
scrollbarPosition: "outside",
scrollEasing: "linear"
});
});
Template.UserManagement.helpers({
displayNewUser: function() {
return Session.get(PREFIX + "displayNewUser");
},
users: function() {
return Meteor.collections.Users.find({}, {sort: {username: 1}});
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.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'}});
}
}
})
}
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 + 'userCount', Meteor.collections.Users.find(dbQuery).count()); //Always get a full count.
return Meteor.collections.Users.find(dbQuery, {limit: Session.get(PREFIX + "queryLimit"), skip: skipCount, sort: {username: 1}});
},
disableLoadMore: function() {
return Session.get(PREFIX + 'userCount') - (Session.get(PREFIX + 'skipCount') || 0) - Session.get(PREFIX + "queryLimit") <= 0;
}
});
Template.UserManagement.events({
'click .loadMoreLink': function(event, template) {
event.preventDefault();
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
},
'click .newUserButton': function(event, template) {
if(template.$('.newUserButton').hasClass('active')) {
Session.set(PREFIX + 'displayNewUser', false);
}
else {
Session.set(PREFIX + 'displayNewUser', true);
Session.set(PREFIX + "editedUser", undefined); //Clear the edited user so that only one editor is open at a time.
}
template.$('.newUserButton').toggleClass('active');
}
});
@@ -20,7 +84,10 @@ Template.User.onCreated(function() {
});
Template.User.events({
"click .userEdit": function(event, template) {
template.edited.set(this);
//template.edited.set(this);
Session.set(PREFIX + "editedUser", this._id);
Session.set(PREFIX + 'displayNewUser', false); //Ensure the new measure editor is closed.
template.parentTemplate().$('.newUserButton').removeClass('active');
},
"click .userRemove": function(event, template) {
let _this = this;
@@ -40,48 +107,6 @@ Template.User.events({
}
}
});
},
"click .editorCancel": function(event, template) {
template.edited.set(undefined);
},
"click .editorApply": function(event, template) {
let username = template.$("input[name='username']").val().trim();
let email = template.$("input[name='email']").val().trim();
let roleSpans = template.$(".roles > span");
let roles = [];
for(let i = 0; i < roleSpans.length; i++) {
if($(roleSpans[i]).hasClass("selected")) {
roles.push($(roleSpans[i]).text());
}
}
//Basic validation.
if(username && username.length > 0 && email && email.length > 0) {
let emails = _.clone(this.emails);
if(!emails || emails.length == 0) {
emails = [{address: email, verified: true}];
}
else {
emails[0].address = email;
emails[0].verified = true;
}
Meteor.call("updateUser", {_id: this._id, username: username, emails: emails, roles: roles}, function(error, result) {
if(error) {
sAlert.error(error);
}
else {
sAlert.success("User updated.");
}
});
}
template.edited.set(undefined);
},
"click .role": function(event, template) {
$(event.target).toggleClass("selected");
}
});
Template.User.helpers({
@@ -89,7 +114,15 @@ Template.User.helpers({
return this.emails && this.emails.length > 0 ? this.emails[0].address : "";
},
editing: function() {
return Template.instance().edited.get() == this;
let editedUser = Session.get(PREFIX + "editedUser");
return editedUser == this._id;
}
});
Template.UserEditor.helpers({
email: function() {
return this.emails && this.emails.length > 0 ? this.emails[0].address : "";
},
allRoles: function() {
return Meteor.collections.UserRoles.find();
@@ -97,7 +130,56 @@ Template.User.helpers({
getRoleState: function(role) {
let user = Template.parentData(1);
return user.roles.includes(role.name) ? "selected" : "";
return !user.isNew && user.roles.includes(role.name) ? "selected" : "";
}
});
Template.UserEditor.events({
"click .editorCancel": function(event, template) {
Session.set(PREFIX + "editedUser", undefined);
Session.set(PREFIX + 'displayNewUser', false);
template.parentTemplate().$('.newUserButton').removeClass('active');
},
"click .editorApply": function(event, template) {
let user = {};
let roles = [];
user.username = template.$('input[name="username"]').val();
user.email = template.$('input[name="email"]').val();
let roleSpans = template.$('.role.selected');
for(let i = 0; i < roleSpans.length; i++) {
roles.push($(roleSpans[i]).text());
}
user.roles = roles;
if(Session.get(PREFIX + 'displayNewUser')) {
Meteor.call('insertUser', user, function(error, result) {
if(error) {
sAlert.error(error);
}
else {
sAlert.success("User created.");
Session.set(PREFIX + 'displayNewUser', false);
template.parentTemplate().$('.newUserButton').removeClass('active');
}
});
}
else {
user._id = this._id;
Meteor.call("updateUser", user, function(error, result) {
if(error) sAlert.error(error);
else {
sAlert.success("User updated.");
Session.set(PREFIX + "editedUser", undefined);
template.parentTemplate().$('.newUserButton').removeClass('active');
}
});
}
},
"click .role": function(event, template) {
$(event.target).toggleClass("selected");
}
});
@@ -139,43 +221,3 @@ Template.UserSearch.helpers({
return (searchFields && searchFields[this.columnName]) ? searchFields[this.columnName] : '';
}
});
Template.UserInsert.onRendered(function() {
this.$('form[name="insert"]').validator();
});
Template.UserInsert.events({
'click input[type="submit"]': function(event, template) {
event.preventDefault();
template.$('form[name="insert"]').data('bs.validator').validate(function(isValid) {
if(isValid) {
let user = {};
let roles = [];
user.username = template.$('input[name="username"]').val();
user.email = template.$('input[name="email"]').val();
let roleSpans = template.$('.role.selected');
for(let i = 0; i < roleSpans.length; i++) {
roles.push($(roleSpans[i]).text());
}
Meteor.call('insertUser', user, roles, function(error, result) {
if(error) {
sAlert.error(error);
}
else {
sAlert.success("User created.");
}
});
}
});
},
"click .role": function(event, template) {
$(event.target).toggleClass("selected");
}
});
Template.UserInsert.helpers({
allRoles: function() {
return Meteor.collections.UserRoles.find();
}
});

View File

@@ -2,20 +2,19 @@
<div id="venues">
{{#if Template.subscriptionsReady}}
<div class="tableControls">
<div class="showHidden">
<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>
<div class="listRow">
<div class="listCell">
<div class="tableContainer">
<div class="contentControls">
<a class="loadMoreLink {{#if disableLoadMore}}disabled{{/if}}" href="javascript:">Load More...</a>
</div>
</div>
<div class="separatedTableHeader">
<table class="table table-striped table-hover">
<thead>
<tr>
@@ -23,8 +22,13 @@
<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>
</table>
</div>
<div class="listRow">
<div class="listCell">
<div class="tableContainer mCustomScrollbar" data-mcs-theme="dark">
<table class="table table-striped table-hover">
<tbody>
{{#if displayNewVenue}}
{{> VenueEditor isNew=true}}
@@ -47,15 +51,15 @@
{{#if editing}}
{{> VenueEditor}}
{{else}}
<td class="noselect nonclickable left">{{name}}</td>
<td class="noselect nonclickable left">{{type}}</td>
<td class="name noselect nonclickable left">{{name}}</td>
<td class="type noselect nonclickable left">{{type}}</td>
{{#if hidden}}
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionShow fa fa-eye fa-lg noselect clickable" title="Show" aria-hidden="true"></i></td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionShow fa fa-eye fa-lg noselect clickable" title="Show" aria-hidden="true"></i></td>
{{else}}
{{#if deactivated}}
<td class="center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionActivate fa fa-toggle-on fa-lg noselect clickable" title="Activate" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionHide fa fa-eye-slash fa-lg noselect clickable" title="Hide" aria-hidden="true"></i></td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionActivate fa fa-toggle-on fa-lg noselect clickable" title="Activate" aria-hidden="true"></i>&nbsp;/&nbsp;<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>&nbsp;/&nbsp;<i class="actionRemove fa fa-times-circle fa-lg noselect clickable" title="Deactivate" aria-hidden="true"></i></td>
<td class="actions center"><i class="actionEdit fa fa-pencil-square-o fa-lg noselect clickable" title="Edit" aria-hidden="true"></i>&nbsp;/&nbsp;<i class="actionRemove fa fa-times-circle fa-lg noselect clickable" title="Deactivate" aria-hidden="true"></i></td>
{{/if}}
{{/if}}
{{/if}}

View File

@@ -7,8 +7,13 @@
text-align: left
.tableControls
display: table
width: 100%
text-align: right
margin-right: 20px
.showHidden
display: table-cell
width: 100%
.controlLabel
font-size: 9px
font-weight: 700
@@ -20,7 +25,55 @@
position: relative
top: -4px
display: inline-block
.contentControls
vertical-align: bottom
display: table-cell
text-align: right
min-width: 100px
a
font-size: 12px
font-family: "Arial", san-serif
font-weight: 800
color: #2d1b8c
text-decoration: none
a:hover
text-decoration: underline
a.disabled
visibility: hidden
.table
table-layout: fixed
min-width: 100%
thead, tbody
> tr
> .name
width: 50%
min-width: 100px
> .type
width: 50%
min-width: 100px
> .actions
width: 90px
min-width: 90px
.separatedTableHeader
table
thead
> tr
.actions
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
.listRow
display: table-row
.listCell
@@ -57,28 +110,8 @@
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
visibility: hidden
> tbody
> tr
.actionRemove

View File

@@ -1,7 +1,8 @@
import './Venues.html';
let QUERY_LIMIT = 20;
let QUERY_LIMIT = 100;
let QUERY_LIMIT_INCREMENT = 100;
let PREFIX = "Venues.";
Tracker.autorun(function() {
@@ -11,6 +12,15 @@ Tracker.autorun(function() {
Template.Venues.onCreated(function() {
Session.set(PREFIX + "displayNewVenue", false);
Session.set(PREFIX + "showHidden", false);
Session.set(PREFIX + "queryLimit", QUERY_LIMIT);
});
Template.Venues.onRendered(function() {
$(".tableContainer").mCustomScrollbar({
scrollButtons: {enable:true},
theme: "light-thick",
scrollbarPosition: "outside",
scrollEasing: "linear"
});
});
Template.Venues.helpers({
displayNewVenue: function() {
@@ -45,23 +55,16 @@ Template.Venues.helpers({
dbQuery = dbQuery.length > 0 ? {$and: dbQuery} : {};
Session.set(PREFIX + 'venueCount', Meteor.collections.Venues.find(dbQuery).count()); //Always get a full count.
return Meteor.collections.Venues.find(dbQuery, {limit: QUERY_LIMIT, skip: skipCount, sort: {order: 1}});
return Meteor.collections.Venues.find(dbQuery, {limit: Session.get(PREFIX + "queryLimit"), skip: skipCount, sort: {order: 1}});
},
disablePrev: function() {
return (Session.get(PREFIX + 'skipCount') || 0) == 0;
},
disableNext: function() {
return Session.get(PREFIX + 'venueCount') - (Session.get(PREFIX + 'skipCount') || 0) - QUERY_LIMIT <= 0;
disableLoadMore: function() {
return Session.get(PREFIX + 'venueCount') - (Session.get(PREFIX + 'skipCount') || 0) - Session.get(PREFIX + "queryLimit") <= 0;
}
});
Template.Venues.events({
'click .prevVenues': function(event, template) {
if(!$(event.target).hasClass('disabled'))
Session.set(PREFIX + 'skipCount', Math.max(0, (Session.get(PREFIX + 'skipCount') || 0) - QUERY_LIMIT));
},
'click .nextVenues': function(event, template) {
if(!$(event.target).hasClass('disabled'))
Session.set(PREFIX + 'skipCount', (Session.get(PREFIX + 'skipCount') || 0) + QUERY_LIMIT);
'click .loadMoreLink': function(event, template) {
event.preventDefault();
Session.set(PREFIX + 'queryLimit', Session.get(PREFIX + "queryLimit") + QUERY_LIMIT_INCREMENT);
},
'click .newVenueButton': function(event, template) {
if(template.$('.newVenueButton').hasClass('active')) {
@@ -150,7 +153,7 @@ Template.Venue.events({
"click .actionEdit": function(event, template) {
Session.set(PREFIX + "editedVenue", this._id);
Session.set(PREFIX + 'displayNewVenue', false); //Ensure the new venue editor is closed.
template.$('.newVenueButton').removeClass('active');
template.parentTemplate().$('.newVenueButton').removeClass('active');
},
"click .actionRemove": function(event, template) {
Meteor.call('deactivateVenue', this._id, function(error, result) {

View File

@@ -1,8 +1,9 @@
<template name="Body">
{{> sAlert}}
<!--<div id="layoutBody" class="verticalStack">-->
<div id="mainBody" class="mainBody verticalStack vscExpand">
<div class="leftSidebar vscFixed">
<div id="mainBody" class="mainBody">
<nav class="leftSidebarContainer">
<a href="javascript:" class="fa fa-bars leftSidebarMenuButton" aria-hidden="true"></a>
<div class="leftSidebar">
<div class="logoArea">
<i class="fa fa-sign-out fa-2x signOut" aria-hidden="true"></i>
<div class="logo">
@@ -74,11 +75,11 @@
&copy; Petit Teton LLC 2017
</div>
</div>
</nav>
<div class="contentBody verticalStack">
{{> Template.dynamic template=content}}
</div>
</div>
<!--</div>-->
</template>
<!--<template name="Body">-->

View File

@@ -9,8 +9,8 @@
#mainBody
//position: relative
display: flex;
flex-flow: row;
//display: flex;
//flex-flow: row;
//display: inline-block // Requried by Firefox for absolute positioning.
margin: 0
padding: 0
@@ -18,6 +18,69 @@
height: 100%
width: 100%
nav.leftSidebarContainer
z-index:999
position: fixed
top: 0
width: 220px
padding: 0
height: 100%
border: 0
vertical-align: top
text-align: left
background-color: #90b272 //Old browsers
background: -moz-linear-gradient(-180deg, #90b272 0%, #4d7727 100%) //FF3.6-15
background: -webkit-linear-gradient(-180deg, #90b272 0%,#4d7727 100%) //Chrome10-25,Safari5.1-6
background: linear-gradient(180deg, #90b272 0%,#4d7727 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
font-size: 14px
font-weight: 700
overflow: visible
margin: 0 0 0 -220px
-webkit-transition: .5s ease-in
-moz-transition: .5s ease-in
-o-transition: .5s ease-in
-ms-transition: .5s ease-in
transition: .5s ease-in
.leftSidebarMenuButton
position: absolute
right: -30px
top: 10px
-webkit-transition: .25s ease-in
-moz-transition: .25s ease-in
-o-transition: .25s ease-in
-ms-transition: .25s ease-in
transition: .25s ease-in
-webkit-border-top-right-radius: 5px
-webkit-border-bottom-right-radius: 5px
-moz-border-radius-topright: 5px
-moz-border-radius-bottomright: 5px
border-top-right-radius: 5px
border-bottom-right-radius: 5px
color: black
font-size: 20px
line-height: 20px
font-weight: 900
text-align: center
text-decoration: none
width: 30px
height: 30px
padding: 5px 0
background-color: #90b272
display: block
.leftSidebarMenuButton:hover
color: rgba(150,0,0,.5)
nav.menuShow
margin: 0
nav.menuShow .leftSidebarMenuButton
right: -15px
-webkit-transform: rotate(45deg) !important
-moz-transform: rotate(45deg) !important
-o-transform: rotate(45deg) !important
-ms-transform: rotate(45deg) !important
transform: rotate(45deg) !important
-moz-border-radius-bottomright: 0
border-top-right-radius: 0
border-bottom-right-radius: 0
.leftSidebar
flex: 0 0 auto
display: flex
@@ -118,198 +181,4 @@
-webkit-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
-moz-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
box-shadow: inset 8px 0px 10px -3px rgba(168,165,168,1)
//position: absolute
//top: 0
//bottom: 0
//left: 220px
//right: 0
overflow: hidden
//.contentBody
// //display: table-cell
// position: absolute
// top: 0
// bottom: 0
// left: 220px
// right: 0
// //background: #4d7727
//
// .contentContainer
// display: table
// width: 100%
// height: 100%
// //border-radius 20px
// //border: 0;
// background: white
//
// .content
// display: table-row
// width: 100%
// -webkit-box-shadow: inset 4px 2px 10px -3px rgba(168,165,168,1)
// -moz-box-shadow: inset 4px 2px 6px 2px rgba(168,165,168,1)
// box-shadow: inset 4px 2px 6px 2px rgba(168,165,168,1)
//#layoutBody
// width: 100%
// height: 100%
// display: table
// margin: 0
// padding: 0
// border: 0
//
// .bodyTable
// display: table
// margin: 0
// padding: 0
// border: 0
// .bodyTableRow
// display: table-row
// .bodyTableCell
// display: table-cell
//
// .left
// display: table-cell
// border: 0
// vertical-align: top
// padding: 0
// text-align: left
// width: 220px
// height: 100%
// //Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#627d4d+0,1f3b08+100;Olive+3D
// background: #627d4d //Old browsers
// background: -moz-linear-gradient(-180deg, #627d4d 0%, #1f3b08 100%) //FF3.6-15
// background: -webkit-linear-gradient(-180deg, #627d4d 0%,#1f3b08 100%) //Chrome10-25,Safari5.1-6
// background: linear-gradient(180deg, #627d4d 0%,#1f3b08 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
// font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif
// font-size: 14px
// font-weight: 700
//
// ul
// padding: 50px 0 0 0
// margin: 0
// list-style: none
//
// li:first-child
// border-top: 1px solid #e4e5e7
// li
// border-bottom: 1px solid #e4e5e7
// color: #96a2ae
// text-transform: uppercase
// display: block
//
// a
// color: #96a2ae
// padding: 15px 20px
// cursor: pointer
// text-decoration: none
// display: block
//
// .tag
// padding: .2em .5em
// font-size: .7em
// color: #fff
// white-space: nowrap
// vertical-align: baseline
// border-radius: .25em
// border: 1px solid #000000
// float: right
// li:hover
// background-color: #333
// li.active
// background-color: #333
//
// .header
// height: 1px
// background: #627d4d
// width: 100%
// .content
// background: white
// .footer
// text-align: center
// height: 1px;
// background: #1f3b08
// color: white
//
//.header
// display: table-row
// height: 1px
// background: #627d4d
//
//#layoutBody.body
// display: table
// margin: 0
// padding: 0
// width: 100%
// height: 100%
//
// .body
// display: table-row
// width: 100%
//
// .left
// display: table-cell
// border: 0
// vertical-align: top
// padding: 0
// text-align: left
// width: 220px
// //Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#627d4d+0,1f3b08+100;Olive+3D
// background: #627d4d //Old browsers
// background: -moz-linear-gradient(-180deg, #627d4d 0%, #1f3b08 100%) //FF3.6-15
// background: -webkit-linear-gradient(-180deg, #627d4d 0%,#1f3b08 100%) //Chrome10-25,Safari5.1-6
// background: linear-gradient(180deg, #627d4d 0%,#1f3b08 100%) //W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+
// font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif
// font-size: 14px;
// font-weight: 700
//
// ul
// padding: 50px 0 0 0
// margin: 0
// list-style: none
//
// li:first-child
// border-top: 1px solid #e4e5e7
// li
// border-bottom: 1px solid #e4e5e7
// color: #96a2ae
// text-transform: uppercase
// display: block
//
// a
// color: #96a2ae
// padding: 15px 20px
// cursor: pointer
// text-decoration: none
// display: block
//
// .tag
// padding: .2em .5em
// font-size: .7em
// color: #fff
// white-space: nowrap
// vertical-align: baseline
// border-radius: .25em
// border: 1px solid #000000
// float: right
// li:hover
// background-color: #333
// li.active
// background-color: #333
// .main
// display: table-row
// background: white
// border: 0
// vertical-align: top
// padding: 0
// text-align: left
//
// .footer
// display: table-row
// text-align: center
// height: 1px;
// background: #1f3b08
// color: white

View File

@@ -5,5 +5,12 @@ import './Body.html';
Template.Body.events({
"click .signOut": function(event, template) {
AccountsTemplates.logout();
},
"click .leftSidebarMenuButton": function(event, template) {
event.preventDefault();
$('nav.leftSidebarContainer').toggleClass('menuShow');
},
"click .menuArea a": function(event, template) {
$('nav.leftSidebarContainer').toggleClass('menuShow');
}
});

View File

@@ -38,3 +38,34 @@ Date.prototype.getWeek = function() {
return weeknum;
};
Date.prototype.getUTCWeek = function() {
let dowOffset = 1; // I am fixing this to indicate that the first day of the week is always Monday (weeks end on Sunday), for this application. This was a parameter in the original code.
//dowOffset = typeof(dowOffset) == 'number' ? dowOffset : 0; //default dowOffset to zero - This should check to ensure that dowOffset is between 0..6
let newYear = new Date(this.getUTCFullYear(),0,1);
let day = newYear.getDay() - dowOffset; //the day of week the year begins on
day = (day >= 0 ? day : day + 7);
// The number of days from the beginning of the year to this.day
let daynum = Math.floor((this.getTime() - newYear.getTime() - (this.getTimezoneOffset() - newYear.getTimezoneOffset())*60000)/86400000) + 1;
let weeknum;
// I have removed the mid-week starting cutoff detection because in this app we always want to start with week #1 (never have a week zero).
//if(day < 4) { //if the year starts before the middle of a week
weeknum = Math.floor((daynum + day - 1) / 7) + 1;
// I have turned off the detection of whether the last days of the year belong to this year's last week or next year's first week. This gets too confusing and does not result in any additional usefulness.
//if(weeknum > 52) {
// nYear = new Date(this.getFullYear() + 1, 0, 1);
// nday = nYear.getDay() - dowOffset;
// nday = nday >= 0 ? nday : nday + 7;
// // if the next year starts before the middle of the week, it is week #1 of that year
// weeknum = nday < 4 ? 1 : 53;
//}
//}
//else {
// weeknum = Math.floor((daynum+day-1)/7);
//}
return weeknum;
};

View File

@@ -16,14 +16,16 @@ Meteor.methods({
for(let i = 0; i < sales.length; i++) {
let dateString = sales[i].date.toString();
dateString = dateString.substring(0, 4) + "-" + dateString.substring(4,6) + "-" + dateString.substring(6,8) + "T00:00:00Z";
let timestamp = new Date(dateString);
let weekOfYear = timestamp.getWeek();
let timestamp = new Date(Date.UTC(parseInt(dateString.substring(0, 4)), parseInt(dateString.substring(4,6)) - 1, parseInt(dateString.substring(6,8))));
let weekOfYear = timestamp.getUTCWeek();
//console.log("Converted " + sales[i].date + " to " + timestamp + " using " + dateString);
if(sales[i]._id === "s9hJLRsQhha3w3ce2") {
console.log(parseInt(dateString.substring(6,8)));
console.log("Converted " + sales[i].date + " to " + timestamp + " where the week # is " + weekOfYear);
}
// Save to the database.
Sales.update(sales[i]._id, {$set: {timestamp, weekOfYear}}, {bypassCollection2: true}, function(err, id) {
Sales.update(sales[i]._id, {$set: {"timestamp": timestamp.getTime(), weekOfYear}}, {bypassCollection2: true}, function(err, id) {
if(err) console.log(err);
});
}