diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index f507d43de..f0d726ed6 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -394,11 +394,18 @@ export default {
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]',
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Origin"]',
buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Buyer"]',
+ densityCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Density"]',
+ openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
+ advancedSearchItemType: 'vn-item-search-panel vn-autocomplete[ng-model="filter.typeFk"]',
+ advancedSearchButton: 'vn-item-search-panel button[type=submit]',
+ advancedSmartTableButton: 'vn-item-index vn-button[icon="search"]',
+ advancedSmartTableGrouping: 'vn-item-index vn-textfield[name=grouping]',
weightByPieceCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Weight/Piece"]',
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
},
itemFixedPrice: {
add: 'vn-fixed-price vn-icon-button[icon="add_circle"]',
+ firstItemID: 'vn-fixed-price tr:nth-child(2) vn-autocomplete[ng-model="price.itemFk"]',
fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)',
fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]',
fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]',
@@ -408,7 +415,8 @@ export default {
fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]',
fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]',
fourthEnded: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.ended"]',
- fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]'
+ fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]',
+ orderColumnId: 'vn-fixed-price th[field="itemFk"]'
},
itemCreateView: {
temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]',
diff --git a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js
new file mode 100644
index 000000000..4fc280209
--- /dev/null
+++ b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js
@@ -0,0 +1,77 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('SmartTable SearchBar integration', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('salesPerson', 'item');
+ await page.waitToClick(selectors.globalItems.searchButton);
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ describe('as filters', () => {
+ it('should search by type in searchBar', async() => {
+ await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton);
+ await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium');
+ await page.waitToClick(selectors.itemsIndex.advancedSearchButton);
+ await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3);
+ });
+
+ it('should reload page and have same results', async() => {
+ await page.reload({
+ waitUntil: 'networkidle2'
+ });
+
+ await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3);
+ });
+
+ it('should search by grouping in smartTable', async() => {
+ await page.waitToClick(selectors.itemsIndex.advancedSmartTableButton);
+ await page.write(selectors.itemsIndex.advancedSmartTableGrouping, '1');
+ await page.keyboard.press('Enter');
+ await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2);
+ });
+
+ it('should now reload page and have same results', async() => {
+ await page.reload({
+ waitUntil: 'networkidle2'
+ });
+
+ await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2);
+ });
+ });
+
+ describe('as orders', () => {
+ it('should order by first id', async() => {
+ await page.loginAndModule('developer', 'item');
+ await page.accessToSection('item.fixedPrice');
+ await page.doSearch();
+
+ const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
+
+ expect(result).toEqual('1');
+ });
+
+ it('should order by last id', async() => {
+ await page.waitToClick(selectors.itemFixedPrice.orderColumnId);
+ const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
+
+ expect(result).toEqual('13');
+ });
+
+ it('should reload page and have same order', async() => {
+ await page.reload({
+ waitUntil: 'networkidle2'
+ });
+ const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
+
+ expect(result).toEqual('13');
+ });
+ });
+});
diff --git a/front/core/components/crud-model/crud-model.js b/front/core/components/crud-model/crud-model.js
index 4994e1547..1095985dc 100644
--- a/front/core/components/crud-model/crud-model.js
+++ b/front/core/components/crud-model/crud-model.js
@@ -99,6 +99,18 @@ export default class CrudModel extends ModelProxy {
return this.refresh();
}
+ /**
+ * Applies a new filter to the model.
+ *
+ * @param {Object} params Custom parameters
+ * @return {Promise} The request promise
+ */
+
+ applyParams(params) {
+ this.userParams = params;
+ return this.refresh();
+ }
+
removeFilter() {
return this.applyFilter(null, null);
}
diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js
index 89b5d7df6..10ec1f608 100644
--- a/front/core/components/searchbar/searchbar.js
+++ b/front/core/components/searchbar/searchbar.js
@@ -139,8 +139,12 @@ export default class Searchbar extends Component {
}
removeParam(index) {
+ const field = this.params[index].key;
+ this.filterSanitizer(field);
+
this.params.splice(index, 1);
- this.doSearch(this.fromBar(), 'bar');
+ this.toRemove = field;
+ this.doSearch(this.fromBar(), 'removeBar');
}
fromBar() {
@@ -163,7 +167,7 @@ export default class Searchbar extends Component {
let keys = Object.keys(filter);
keys.forEach(key => {
- if (key == 'search') return;
+ if (key == 'search' || key == 'tableQ' || key == 'tableOrder') return;
let value = filter[key];
let chip;
@@ -198,6 +202,7 @@ export default class Searchbar extends Component {
let promise = this.onSearch({$params: filter});
promise = promise || this.$q.resolve();
promise.then(data => this.onFilter(filter, source, data));
+ this.toBar(filter);
}
onFilter(filter, source, data) {
@@ -238,8 +243,11 @@ export default class Searchbar extends Component {
} else {
state = this.searchState;
- if (filter)
+ if (filter) {
+ if (this.tableQ)
+ filter.tableQ = this.tableQ;
params = {q: JSON.stringify(filter)};
+ }
if (this.$state.is(state))
opts = {location: 'replace'};
}
@@ -247,6 +255,12 @@ export default class Searchbar extends Component {
this.filter = filter;
+ if (source == 'removeBar') {
+ delete params[this.toRemove];
+ delete this.model.userParams[this.toRemove];
+ this.model.refresh();
+ }
+
if (!filter && this.model)
this.model.clear();
if (source != 'state')
@@ -269,9 +283,14 @@ export default class Searchbar extends Component {
this.model.clear();
return;
}
+ if (Object.keys(filter).length === 0) {
+ this.filterSanitizer('search');
+ if (this.model.userParams)
+ delete this.model.userParams['search'];
+ }
let where = null;
- let params = null;
+ let params = {};
if (this.exprBuilder) {
where = buildFilter(filter,
@@ -283,9 +302,89 @@ export default class Searchbar extends Component {
params = this.fetchParams({$params: params});
}
+ this.tableQ = null;
+
+ const hasParams = this.$params.q && Object.keys(JSON.parse(this.$params.q)).length;
+ if (hasParams) {
+ const stateFilter = JSON.parse(this.$params.q);
+ for (let param in stateFilter) {
+ if (param != 'tableQ' && param != 'orderQ')
+ this.filterSanitizer(param);
+ }
+
+ for (let param in this.suggestedFilter) {
+ this.filterSanitizer(param);
+ delete stateFilter[param];
+ }
+
+ this.tableQ = stateFilter.tableQ;
+ for (let param in stateFilter.tableQ)
+ params[param] = stateFilter.tableQ[param];
+
+ Object.assign(stateFilter, params);
+ return this.model.applyParams(params)
+ .then(() => this.model.data);
+ }
+
return this.model.applyFilter(where ? {where} : null, params)
.then(() => this.model.data);
}
+
+ filterSanitizer(field) {
+ if (!field) return;
+ const userFilter = this.model.userFilter;
+ const userParams = this.model.userParams;
+ const where = userFilter && userFilter.where;
+
+ if (this.model.userParams)
+ delete this.model.userParams[field];
+
+ if (this.exprBuilder) {
+ const param = this.exprBuilder({
+ param: field,
+ value: null
+ });
+ if (param) [field] = Object.keys(param);
+ }
+
+ if (!where) return;
+
+ const whereKeys = Object.keys(where);
+ for (let key of whereKeys) {
+ removeProp(where, field, key);
+
+ if (Object.keys(where).length == 0)
+ delete userFilter.where;
+ }
+
+ function removeProp(obj, targetProp, prop) {
+ if (prop == targetProp)
+ delete obj[prop];
+
+ if (prop === 'and' || prop === 'or' && obj[prop]) {
+ const arrayCopy = obj[prop].slice();
+ for (let param of arrayCopy) {
+ const [key] = Object.keys(param);
+ const index = obj[prop].findIndex(param => {
+ return Object.keys(param)[0] == key;
+ });
+ if (key == targetProp)
+ obj[prop].splice(index, 1);
+
+ if (param[key] instanceof Array)
+ removeProp(param, field, key);
+
+ if (Object.keys(param).length == 0)
+ obj[prop].splice(index, 1);
+ }
+
+ if (obj[prop].length == 0)
+ delete obj[prop];
+ }
+ }
+
+ return {userFilter, userParams};
+ }
}
ngModule.vnComponent('vnSearchbar', {
diff --git a/front/core/components/searchbar/searchbar.spec.js b/front/core/components/searchbar/searchbar.spec.js
index e4f58d294..ed8fd9d07 100644
--- a/front/core/components/searchbar/searchbar.spec.js
+++ b/front/core/components/searchbar/searchbar.spec.js
@@ -6,7 +6,7 @@ describe('Component vnSearchbar', () => {
let $state;
let $params;
let $scope;
- let filter = {id: 1, search: 'needle'};
+ const filter = {id: 1, search: 'needle'};
beforeEach(ngModule('vnCore', $stateProvider => {
$stateProvider
@@ -70,8 +70,8 @@ describe('Component vnSearchbar', () => {
describe('filter() setter', () => {
it(`should update the bar params and search`, () => {
- let withoutHours = new Date(2000, 1, 1);
- let withHours = new Date(withoutHours.getTime());
+ const withoutHours = new Date(2000, 1, 1);
+ const withHours = new Date(withoutHours.getTime());
withHours.setHours(12, 30, 15, 10);
controller.filter = {
@@ -83,8 +83,8 @@ describe('Component vnSearchbar', () => {
myObjectProp: {myProp: 1}
};
- let chips = {};
- for (let param of controller.params || [])
+ const chips = {};
+ for (const param of controller.params || [])
chips[param.key] = param.chip;
expect(controller.searchString).toBe('needle');
@@ -172,13 +172,22 @@ describe('Component vnSearchbar', () => {
describe('removeParam()', () => {
it(`should remove the parameter from the filter`, () => {
jest.spyOn(controller, 'doSearch');
+ controller.model = {
+ refresh: jest.fn(),
+ userParams: {
+ id: 1
+ }
+ };
+
+ controller.model.applyParams = jest.fn().mockReturnValue(Promise.resolve());
+ jest.spyOn(controller.model, 'applyParams');
controller.filter = filter;
controller.removeParam(0);
expect(controller.doSearch).toHaveBeenCalledWith({
search: 'needle'
- }, 'bar');
+ }, 'removeBar');
});
});
@@ -199,7 +208,7 @@ describe('Component vnSearchbar', () => {
it(`should go to the summary state when one result`, () => {
jest.spyOn($state, 'go');
- let data = [{id: 1}];
+ const data = [{id: 1}];
controller.baseState = 'foo';
controller.onFilter(filter, 'any', data);
@@ -214,7 +223,7 @@ describe('Component vnSearchbar', () => {
$scope.$apply();
jest.spyOn($state, 'go');
- let data = [{id: 1}];
+ const data = [{id: 1}];
controller.baseState = 'foo';
controller.onFilter(filter, 'any', data);
@@ -229,7 +238,7 @@ describe('Component vnSearchbar', () => {
$scope.$apply();
jest.spyOn($state, 'go');
- let data = [{id: 1}];
+ const data = [{id: 1}];
controller.baseState = 'foo';
controller.onFilter(filter, 'any', data);
@@ -247,7 +256,7 @@ describe('Component vnSearchbar', () => {
controller.onFilter(filter, 'any');
$scope.$apply();
- let queryParams = {q: JSON.stringify(filter)};
+ const queryParams = {q: JSON.stringify(filter)};
expect($state.go).toHaveBeenCalledWith('search.state', queryParams, undefined);
expect(controller.filter).toEqual(filter);
diff --git a/front/core/components/smart-table/index.html b/front/core/components/smart-table/index.html
index f26a6b4a2..752019313 100644
--- a/front/core/components/smart-table/index.html
+++ b/front/core/components/smart-table/index.html
@@ -103,3 +103,4 @@
+
diff --git a/front/core/components/smart-table/index.js b/front/core/components/smart-table/index.js
index 9e6e7009c..8d2c3c153 100644
--- a/front/core/components/smart-table/index.js
+++ b/front/core/components/smart-table/index.js
@@ -15,9 +15,17 @@ export default class SmartTable extends Component {
this.$inputsScope;
this.columns = [];
this.autoSave = false;
+ this.autoState = true;
this.transclude();
}
+ $onChanges() {
+ if (this.model) {
+ this.defaultFilter();
+ this.defaultOrder();
+ }
+ }
+
$onDestroy() {
const styleElement = document.querySelector('style[id="smart-table"]');
if (this.$.css && styleElement)
@@ -47,10 +55,8 @@ export default class SmartTable extends Component {
set model(value) {
this._model = value;
- if (value) {
+ if (value)
this.$.model = value;
- this.defaultOrder();
- }
}
getDefaultViewConfig() {
@@ -160,8 +166,36 @@ export default class SmartTable extends Component {
}
}
+ defaultFilter() {
+ if (this.disabledTableFilter || !this.$params.q) return;
+
+ const stateFilter = JSON.parse(this.$params.q).tableQ;
+ if (!stateFilter || !this.exprBuilder) return;
+
+ const columns = this.columns.map(column => column.field);
+
+ this.displaySearch();
+ if (!this.$inputsScope.searchProps)
+ this.$inputsScope.searchProps = {};
+
+ for (let param in stateFilter) {
+ if (columns.includes(param)) {
+ const whereParams = {[param]: stateFilter[param]};
+ Object.assign(this.$inputsScope.searchProps, whereParams);
+ this.addFilter(param, stateFilter[param]);
+ }
+ }
+ }
+
defaultOrder() {
- const order = this.model.order;
+ if (this.disabledTableOrder) return;
+
+ let stateOrder;
+ if (this.$params.q)
+ stateOrder = JSON.parse(this.$params.q).tableOrder;
+
+ const order = stateOrder ? stateOrder : this.model.order;
+
if (!order) return;
const orderFields = order.split(', ');
@@ -195,6 +229,9 @@ export default class SmartTable extends Component {
this.setPriority(column.element, priority);
}
}
+
+ this.model.order = order;
+ this.refresh();
}
registerColumns() {
@@ -395,28 +432,54 @@ export default class SmartTable extends Component {
}
searchByColumn(field) {
- const searchCriteria = this.$inputsScope.searchProps[field];
- const emptySearch = searchCriteria === '' || searchCriteria == null;
-
const filters = this.filterSanitizer(field);
if (filters && filters.userFilter)
this.model.userFilter = filters.userFilter;
- if (!emptySearch)
- this.addFilter(field, this.$inputsScope.searchProps[field]);
- else this.model.refresh();
+ this.addFilter(field, this.$inputsScope.searchProps[field]);
+ }
+
+ searchPropsSanitizer() {
+ if (!this.$inputsScope || !this.$inputsScope.searchProps) return null;
+ let searchProps = this.$inputsScope.searchProps;
+ const searchPropsArray = Object.entries(searchProps);
+ searchProps = searchPropsArray.filter(
+ ([key, value]) => value && value != ''
+ );
+
+ return Object.fromEntries(searchProps);
}
addFilter(field, value) {
- let where = {[field]: value};
+ if (value == '') value = null;
- if (this.exprBuilder) {
- where = buildFilter(where, (param, value) =>
- this.exprBuilder({param, value})
- );
+ let stateFilter = {tableQ: {}};
+ if (this.$params.q) {
+ stateFilter = JSON.parse(this.$params.q);
+ if (!stateFilter.tableQ)
+ stateFilter.tableQ = {};
+ delete stateFilter.tableQ[field];
}
- this.model.addFilter({where});
+ const whereParams = {[field]: value};
+ if (value) {
+ let where = {[field]: value};
+ if (this.exprBuilder) {
+ where = buildFilter(whereParams, (param, value) =>
+ this.exprBuilder({param, value})
+ );
+ }
+ this.model.addFilter({where});
+ }
+
+ const searchProps = this.searchPropsSanitizer();
+
+ Object.assign(stateFilter.tableQ, searchProps);
+
+ const params = {q: JSON.stringify(stateFilter)};
+
+ this.$state.go(this.$state.current.name, params, {location: 'replace'});
+ this.refresh();
}
applySort() {
@@ -426,7 +489,18 @@ export default class SmartTable extends Component {
if (order)
this.model.order = order;
- this.model.refresh();
+ let stateFilter = {tableOrder: {}};
+ if (this.$params.q) {
+ stateFilter = JSON.parse(this.$params.q);
+ if (!stateFilter.tableOrder)
+ stateFilter.tableOrder = {};
+ }
+
+ stateFilter.tableOrder = order;
+
+ const params = {q: JSON.stringify(stateFilter)};
+ this.$state.go(this.$state.current.name, params, {location: 'replace'});
+ this.refresh();
}
filterSanitizer(field) {
@@ -535,6 +609,8 @@ ngModule.vnComponent('smartTable', {
autoSave: '',
exprBuilder: '&?',
defaultNewData: '&?',
- options: ''
+ options: '',
+ disabledTableFilter: '',
+ disabledTableOrder: '',
}
});
diff --git a/front/core/components/smart-table/index.spec.js b/front/core/components/smart-table/index.spec.js
index 720e24c7e..5fd4c33b7 100644
--- a/front/core/components/smart-table/index.spec.js
+++ b/front/core/components/smart-table/index.spec.js
@@ -9,6 +9,11 @@ describe('Component smartTable', () => {
$httpBackend = _$httpBackend_;
$element = $compile(`
Item ID | -+ | Description | Warehouse | -P.P.U. | -
P.P.P.
@@ -170,7 +170,7 @@
-
+
|
---|