catalog filters #598

This commit is contained in:
Joan Sanchez 2018-09-14 09:10:30 +02:00
parent 8d1aa30287
commit fa31010069
19 changed files with 376 additions and 154 deletions

View File

@ -4,8 +4,7 @@
type="button"
class="mdl-textfield__input"
ng-click="$ctrl.onMouseDown($event)"
ng-keydown="$ctrl.onKeyDown($event)">
</input>
ng-keydown="$ctrl.onKeyDown($event)"/>
<div class="icons">
<vn-icon
ng-show="!$ctrl.disabled"

View File

@ -1,8 +1,8 @@
<div>
<vn-one>
<span ng-class="{'mdl-chip--deletable': !$ctrl.disabled}" class="mdl-chip">
<span class="mdl-chip__text" ng-transclude></span>
<span class="mdl-chip__text ellipsize" ng-transclude></span>
<button ng-click="$ctrl.remove()" ng-show="!$ctrl.disabled" type="button" class="mdl-chip__action">
<i class="material-icons">cancel</i>
</button>
</span>
</div>
</vn-one>

View File

@ -2,11 +2,6 @@ import ngModule from '../../module';
import './style.scss';
export default class Chip {
constructor($element, $scope, $transclude) {
$transclude($scope.$parent, clone => {
angular.element($element[0].querySelector('div')).append(clone);
});
}
/**
* Remove chip event

View File

@ -1,11 +1,18 @@
@import 'colors';
vn-chip {
margin: 0 0.5em 0.5em 0;
.mdl-chip {
background-color: rgba($main-01, 0.9);
color: #FFF
}
.mdl-chip:active {
background-color: $main-01
}
& > vn-one > span > span {
max-width: 100%;
}
}

View File

@ -1,7 +1,7 @@
<div class="container"
ng-class="{selected: $ctrl.hasFocus}">
<div class="textField">
<div class="leftIcons"></div>
<div class="leftIcons" ng-transclude="leftIcons"></div>
<div class="infix">
<input
class="mdl-textfield__input"
@ -33,6 +33,6 @@
info_outline
</i>
</div>
<div class="rightIcons"></div>
<div class="rightIcons" ng-transclude="rightIcons"></div>
</div>
</div>

View File

@ -3,7 +3,7 @@ import Input from '../../lib/input';
import './style.scss';
export default class Textfield extends Input {
constructor($element, $scope, $attrs, vnTemplate, $transclude) {
constructor($element, $scope, $attrs, vnTemplate) {
super($element, $scope);
vnTemplate.normalizeInputAttrs($attrs);
this._value = null;
@ -14,20 +14,12 @@ export default class Textfield extends Input {
this.hasFocus = false;
this.hasMouseIn = false;
if ($transclude) {
$transclude($scope.$parent, tClone => {
this.leftIcons = tClone[0];
}, null, 'leftIcons');
$transclude($scope.$parent, tClone => {
this.rightIcons = tClone[0];
}, null, 'rightIcons');
}
this.input.addEventListener('keydown', () => {
if (!this.oldValue) {
this.saveOldValue();
}
});
this.input.addEventListener('keyup', e => {
if (e.key == "Escape") {
this.value = this.oldValue;
@ -37,6 +29,7 @@ export default class Textfield extends Input {
if (e.key == "Escape" || e.key == "Enter")
this.input.blur();
});
this.input.addEventListener('blur', () => {
if (this.onChange && !this.cancelled &&
(this.oldValue && this.oldValue != this.value))
@ -45,19 +38,11 @@ export default class Textfield extends Input {
this.cancelled = false;
});
}
saveOldValue() {
this.oldValue = this.value;
}
set leftIcons(value) {
for (let i = 0; i < value.children.length; i++) {
this.element.querySelector('.leftIcons').appendChild(value.children[i]);
}
}
set rightIcons(value) {
for (let i = 0; i < value.children.length; i++) {
this.element.querySelector('.rightIcons').appendChild(value.children[i]);
}
}
set value(value) {
this._value = (value === undefined || value === '') ? null : value;
this.input.value = this._value;
@ -66,18 +51,23 @@ export default class Textfield extends Input {
if (this.hasValue) this.element.classList.add('not-empty');
else this.element.classList.remove('not-empty');
}
get value() {
return this._value;
}
set type(value) {
this._type = value || 'text';
}
get type() {
return this._type;
}
set vnTabIndex(value) {
this.input.tabindex = value;
}
clear() {
this.saveOldValue();
this.value = null;
@ -85,7 +75,7 @@ export default class Textfield extends Input {
this.input.focus();
}
}
Textfield.$inject = ['$element', '$scope', '$attrs', 'vnTemplate', '$transclude'];
Textfield.$inject = ['$element', '$scope', '$attrs', 'vnTemplate'];
ngModule.component('vnTextfield', {
template: require('./textfield.html'),

View File

@ -3,7 +3,7 @@
.icon-volume:before { content: '\e801'; } /* '' */
.icon-barcode:before { content: '\e802'; } /* '' */
.icon-bucket:before { content: '\e803'; } /* '' */
.icon-accesory:before { content: '\e804'; } /* '' */
.icon-accessory:before { content: '\e804'; } /* '' */
.icon-dfiscales:before { content: '\e805'; } /* '' */
.icon-doc:before { content: '\e806'; } /* '' */
.icon-eye:before { content: '\e807'; } /* '' */
@ -37,7 +37,7 @@
.icon-entry:before { content: '\e829'; } /* '' */
.icon-traceability:before { content: '\e82a'; } /* '' */
.icon-transaction:before { content: '\e82b'; } /* '' */
.icon-verde:before { content: '\e82c'; } /* '' */
.icon-greenery:before { content: '\e82c'; } /* '' */
.icon-regentry:before { content: '\e82d'; } /* '' */
.icon-plant:before { content: '\e82e'; } /* '' */
.icon-artificial:before { content: '\e82f'; } /* '' */

View File

@ -34,7 +34,7 @@
}
},
{
"url": "/catalog?q",
"url": "/catalog?category&type",
"state": "order.card.catalog",
"component": "vn-order-catalog",
"description": "Catalog",

View File

@ -1,7 +1,8 @@
<vn-crud-model
vn-id="model"
url="/order/api/Orders/CatalogFilter"
filter="::$ctrl.filter"
filter="$ctrl.filter"
limit="50"
data="items" auto-load="false">
</vn-crud-model>
@ -9,9 +10,19 @@
<vn-vertical vn-one>
<vn-card>
<vn-vertical>
<vn-horizontal class="catalog-header" pad-medium>
<vn-horizontal class="catalog-header" pad-medium-h>
<vn-one>{{model.data.length}} <span translate>results</span></vn-one>
<vn-one>-</vn-one>
<vn-one>
<vn-autocomplete vn-none
data="$ctrl.orderList"
initial-data="$ctrl.orderBy"
field="$ctrl.orderBy"
on-change="$ctrl.setOrder(value)"
show-field="name"
value-field="order"
label="Order by">
</vn-autocomplete>
</vn-one>
</vn-horizontal>
<vn-horizontal class="catalog-list" pad-small>
<section class="product" ng-repeat="item in items">
@ -74,6 +85,10 @@
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-pagination
model="model"
scroll-selector="ui-view">
</vn-pagination>
</vn-vertical>
<vn-auto class="right-block">

View File

@ -1,29 +1,48 @@
import ngModule from '../module';
import './style.scss';
class Controller {
constructor($scope, $stateParams) {
constructor($scope, $stateParams, $translate) {
this.$scope = $scope;
this.$stateParams = $stateParams;
}
applyFilter() {
this.$scope.model.filter = this.filter;
this.$scope.model.refresh();
}
set order(value) {
this._order = value;
if (!value) return;
this.orderList = [
{
order: 'relevancy DESC, name',
name: $translate.instant('Default order')
},
{
order: 'name',
name: $translate.instant('Ascendant name')
},
{
order: 'name DESC',
name: $translate.instant('Descendant name')
},
{
order: 'price',
name: $translate.instant('Ascendant price')
},
{
order: 'price DESC',
name: $translate.instant('Descendant price')
}
];
this.filter = {
orderFk: value.id,
where: {}
order: this.orderList[0].order
};
}
get order() {
return this._order;
get orderBy() {
return this._orderBy;
}
set orderBy(value) {
this._orderBy = value;
}
setOrder(order) {
this.$scope.model.filter.order = order;
this.$scope.model.refresh();
}
preview(event, item) {
@ -36,7 +55,7 @@ class Controller {
}
}
Controller.$inject = ['$scope', '$stateParams'];
Controller.$inject = ['$scope', '$stateParams', '$translate'];
ngModule.component('vnOrderCatalog', {
template: require('./index.html'),

View File

@ -0,0 +1,18 @@
@import "colors";
.catalog-header {
border-color: $lines;
border-bottom: 1px solid rgba($lines, 0.5);
vn-one:first-child {
padding-top: 2em;
}
vn-one:nth-child(2) {
padding-top: 0.5em;
}
span {
color: $secondary-font-color
}
}

View File

@ -3,8 +3,6 @@
url="/order/api/ItemCategories"
data="categories">
</vn-crud-model>
<vn-horizontal>
<vn-vertical vn-one>
<vn-card >
@ -12,37 +10,91 @@
<vn-horizontal pad-medium class="item-category">
<vn-one margin-small-v ng-repeat="category in categories">
<vn-icon
ng-class="{'active': $ctrl.categoryFk == category.id}"
ng-class="{'active': $ctrl.category.id == category.id}"
pad-small
icon="{{::category.icon}}"
vn-tooltip="{{::category.name}}"
ng-click="$ctrl.categoryFk = category.id">
ng-click="$ctrl.category = {
id: category.id,
value: category.name
}">
</vn-icon>
</vn-one>
</vn-horizontal>
<vn-horizontal pad-medium class="catalog-header">
<vn-icon icon="search"></vn-icon>
<vn-autocomplete vn-one
initial-data="$ctrl.typeFk"
field="$ctrl.typeFk"
vn-id="type"
data="$ctrl.itemTypes"
on-change="$ctrl.type = {
id: value,
value: type.selection.name
}"
field="$ctrl.type.id"
show-field="name"
value-field="id"
label="Type">
<t-left-icons>
<i class="material-icons">search</i>
</t-left-icons>
<t-right-icons>
<i class="material-icons"
ng-click="$ctrl.openPanel($event)"
style="cursor: pointer; color: #aaa">
keyboard_arrow_down
</i>
</t-right-icons>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal pad-medium class="catalog-header">
<vn-searchbar
panel="vn-order-search-panel"
on-search="$ctrl.onSearch(filter)"
expr-builder="$ctrl.exprBuilder(param, value)">
</vn-searchbar>
<vn-one>
<vn-textfield
vn-id="search"
ng-keyUp="$ctrl.onSearch($event)"
label="Search tag"
model="$ctrl.value">
<t-left-icons>
<i class="material-icons">search</i>
</t-left-icons>
<t-right-icons>
<i class="material-icons"
ng-click="$ctrl.openPanel($event)"
style="cursor: pointer; color: #aaa">
keyboard_arrow_down
</i>
</t-right-icons>
</vn-textfield>
<vn-popover
vn-id="popover"
on-close="$ctrl.onPopoverClose()">
<vn-order-search-panel/>
</vn-popover>
</vn-one>
</vn-horizontal>
<vn-horizontal pad-medium>
<vn-chip onRemove="$ctrl.onRemove($index)">Tag1</vn-chip>
<vn-horizontal pad-medium style="flex-wrap: wrap">
<vn-chip
ng-if="$ctrl.category"
vn-tooltip="Category"
on-remove="$ctrl.category = null" ellipsize>
<span translate>{{$ctrl.category.value}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.type"
vn-tooltip="Type"
on-remove="$ctrl.type = null" ellipsize>
<span translate>{{$ctrl.type.value}}</span>
</vn-chip>
<vn-chip
ng-repeat="tag in $ctrl.tags"
vn-tooltip="Value"
on-remove="$ctrl.remove($index)" ellipsize>
<span translate>{{::tag.value}}</span>
</vn-chip>
</vn-horizontal>
</vn-vertical>
</vn-card>

View File

@ -2,74 +2,175 @@ import ngModule from '../module';
import './style.scss';
class Controller {
constructor($http) {
constructor($http, $scope, $state, $compile, $transitions, $window) {
this.$http = $http;
this.$scope = $scope;
this.$state = $state;
this.$stateParams = $state.params;
this.$compile = $compile;
this.$transitions = $transitions;
this.itemTypes = [];
this.tags = [];
/* $transitions.onSuccess({}, transition => {
let params = {};
if (this.category)
params.category = this.category;
if (this.type)
params.type = this.type;
$window.history.replaceState(params);
this.applyFilters();
}); */
}
get where() {
return this.catalog.filter.where;
get order() {
return this._order;
}
set categoryFk(value) {
if (this.where['it.categoryFk'] == value) {
this.where['it.categoryFk'] = null;
this.where['i.typeFk'] = null;
this.itemTypes = [];
set order(value) {
this._order = value;
if (!value.id) return;
this.$scope.$$postDigest(() => {
let category;
let type;
if (this.$stateParams.category)
category = JSON.parse(this.$stateParams.category);
if (this.$stateParams.type)
type = JSON.parse(this.$stateParams.type);
if (category && category.id)
this.category = category;
if (type && type.id)
this.type = type;
});
}
get category() {
return this._category;
}
set category(value) {
this.itemTypes = [];
this.type = null;
if (!value || (this.category && this.category.id == value.id)) {
this._category = null;
this.updateStateParams();
return;
}
this.where['it.categoryFk'] = value;
this.where['i.typeFk'] = null;
let query = `/item/api/ItemCategories/${value}/itemTypes`;
this._category = value;
this.updateStateParams();
let query = `/item/api/ItemCategories/${value.id}/itemTypes`;
this.$http.get(query).then(res => {
this.itemTypes = res.data;
});
}
get categoryFk() {
return this.where['it.categoryFk'];
get type() {
return this._type;
}
set typeFk(value) {
this.where['i.typeFk'] = value;
set type(value) {
if (value && this.type && this.type.id == value.id) return;
this.catalog.applyFilter();
}
if (!value || !value.id) {
this._type = null;
this.updateStateParams();
get typeFk() {
return this.where['i.typeFk'];
}
onSearch(filter) {
if (!Object.keys(filter).length) return;
if (filter.search) {
console.log(this.exprBuilder('search', filter.search));
Object.assign(this.where, this.exprBuilder('search', filter.search));
} else {
Object.assign(this.where, filter);
console.log(this.where);
return;
}
this.catalog.applyFilter();
this._type = value;
this.updateStateParams();
this.applyFilters();
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return {'itg.value': {like: value}};
case 'itg.tagFk':
case 'itg.value':
return {[param]: value};
}
onSearch(event) {
if (event.key !== 'Enter') return;
this.tags.push({
value: this.value
});
this.$scope.search.value = null;
this.applyFilters();
}
applyFilters() {
let newArgs = {orderFk: this.order.id};
let model = this.catalog.$scope.model;
if (this.category)
newArgs.categoryFk = this.category.id;
if (this.type)
newArgs.typeFk = this.type.id;
model.params = {
args: newArgs,
tags: this.tags
};
this.catalog.$scope.model.refresh();
}
remove(index) {
this.tags.splice(index, 1);
this.applyFilters();
}
openPanel(event) {
if (event.defaultPrevented) return;
event.preventDefault();
this.$panel = this.$compile(`<vn-order-search-panel/>`)(this.$scope.$new());
let panel = this.$panel.isolateScope().$ctrl;
panel.filter = this.filter;
panel.onSubmit = filter => this.onPanelSubmit(filter);
this.$scope.popover.parent = this.$scope.search.element;
this.$scope.popover.child = this.$panel[0];
this.$scope.popover.show();
}
onPanelSubmit(filter) {
this.$scope.popover.hide();
this.tags.push(filter);
this.applyFilters();
}
onPopoverClose() {
this.$panel.scope().$destroy();
this.$panel.remove();
this.$panel = null;
}
updateStateParams() {
let params = {};
if (this.category)
params.category = JSON.stringify(this.category);
if (this.type)
params.type = JSON.stringify(this.type);
else
params.type = undefined;
this.$state.go(this.$state.current.name, params);
}
}
Controller.$inject = ['$http'];
Controller.$inject = ['$http', '$scope', '$state', '$compile', '$transitions', '$window'];
ngModule.component('vnCatalogFilter', {
template: require('./index.html'),

View File

@ -1,22 +1,6 @@
@import "colors";
vn-catalog-filter {
/* .item-category, .item-category * {
vn-button button {
border-radius: 50%;
padding: 1em;
width: 4.9em;
height: 4.9em;
vn-icon i {
font-size: 32pt
}
}
} */
.item-category {
justify-content: flex-start;
align-items: flex-start;

View File

@ -1,5 +1,18 @@
Address: Consignatario
Catalogue: Catálogo
Catalog: Catálogo
from: desde
results: resultados
No results: Sin resultados
No results: Sin resultados
Plant: Planta
Flower: Flor
Handmade: Confección
Green: Verde
Accessories: Complemento
Category: Reino
Search tag: Buscar etiqueta
Order by: Ordenar por
Default order: Orden predeterminado
Ascendant name: Nombre ascendiente
Descendant name: Nombre descendiente
Ascendant price: Precio ascendiente
Descendant price: Precio descendiente

View File

@ -4,7 +4,7 @@
<vn-autocomplete
vn-one
label="Tag"
field="filter['itg.tagFk']"
field="filter.tagFk"
url="/api/Tags"
show-field="name"
value-field="id">
@ -15,7 +15,7 @@
<vn-textfield
vn-one
label="Value"
model="filter['itg.value']">
model="filter.value">
</vn-textfield>
</vn-horizontal>
<vn-horizontal margin-large-top>

View File

@ -54,6 +54,7 @@ function config($stateProvider, $urlRouterProvider, aclServiceProvider, modulesF
url: route.url,
template: `<${route.component} ${getParams(route)}></${route.component}>`,
description: route.description,
reloadOnSearch: false,
resolve: {
loader: loader(moduleName, validations)
},

View File

@ -26,15 +26,6 @@
}
}
.catalog-header {
border-color: $lines;
border-bottom: 1px solid rgba($lines, 0.5);
span {
color: $secondary-font-color
}
}
.catalog-list {
justify-content: flex-start;
align-items: flex-start;

View File

@ -10,6 +10,19 @@ module.exports = Self => {
type: 'Object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
},
{
arg: 'args',
type: 'Object',
description: 'orderFk, categoryFk, typeFk',
required: true,
http: {source: 'query'}
},
{
arg: 'tags',
type: ['Object'],
description: 'Request tags',
http: {source: 'query'}
}
],
returns: {
@ -22,7 +35,7 @@ module.exports = Self => {
}
});
Self.catalogFilter = async filter => {
Self.catalogFilter = async (filter, args, tags) => {
let stmts = [];
let stmt;
@ -31,23 +44,46 @@ module.exports = Self => {
stmt = new ParameterizedSQL(
`CREATE TEMPORARY TABLE tmp.item
(PRIMARY KEY (itemFk)) ENGINE = MEMORY
SELECT
SELECT DISTINCT
i.id AS itemFk,
i.typeFk,
it.categoryFk
FROM vn.item i
JOIN vn.itemType it ON it.id = i.typeFk
JOIN vn.itemCategory ic ON ic.id = it.categoryFk`
JOIN vn.itemType it ON it.id = i.typeFk
JOIN vn.itemCategory ic ON ic.id = it.categoryFk`
);
if (filter.where['itg.tagFk'] || filter.where['itg.value']) {
stmt.merge('JOIN vn.itemTag itg ON itg.itemFk = i.id');
if (tags) {
let i = 1;
for (let tag of tags) {
let tAlias = `it${i++}`;
if (tag.tagFk) {
stmt.merge({
sql: `JOIN vn.itemTag ${tAlias} ON ${tAlias}.itemFk = i.id
AND ${tAlias}.tagFk = ?
AND ${tAlias}.value LIKE ?`,
params: [tag.tagFk, `%${tag.value}%`]
});
} else {
stmt.merge({
sql: `JOIN vn.itemTag ${tAlias} ON ${tAlias}.itemFk = i.id
AND ${tAlias}.value LIKE ?`,
params: [`%${tag.value}%`]
});
}
}
}
stmt.merge(Self.buildSuffix(filter));
if (args.typeFk)
stmt.merge({
sql: 'WHERE it.categoryFk = ? AND i.typeFk = ?',
params: [args.categoryFk, args.typeFk]
});
stmts.push(stmt);
let order = await Self.findById(filter.orderFk);
let order = await Self.findById(args.orderFk);
stmts.push(new ParameterizedSQL(
'CALL vn.ticketCalculate(?, ?, ?)', [
order.landed,
@ -56,8 +92,7 @@ module.exports = Self => {
]
));
let itemsIndex = stmts.push(
`SELECT
stmt = new ParameterizedSQL(`SELECT
i.id,
i.name,
i.subName,
@ -77,9 +112,11 @@ module.exports = Self => {
FROM tmp.ticketCalculateItem tci
JOIN vn.item i ON i.id = tci.itemFk
JOIN vn.itemType it ON it.id = i.typeFk
JOIN vn.worker w on w.id = it.workerFk
ORDER BY relevancy DESC, itemFk ASC, producer DESC`
) - 1;
JOIN vn.worker w on w.id = it.workerFk`
);
stmt.merge(Self.buildSuffix(filter));
// stmt.merge(Self.buildOrderBy(orderBy));
let itemsIndex = stmts.push(stmt) - 1;
let pricesIndex = stmts.push(
`SELECT