diff --git a/.env.json b/.env.json deleted file mode 100644 index de4ba70f8..000000000 --- a/.env.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "salixHost": "localhost", - "salixPort": 3306, - "salixUser": "root", - "salixPassword": "" -} \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 617a4f8f9..d004f86bc 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,16 +6,10 @@ def branchTest = "test"; env.BRANCH_NAME = branchName; env.TAG = "${env.BUILD_NUMBER}"; -env.salixUser="${env.salixUser}"; -env.salixPassword="${env.salixPassword}"; -env.salixHost = "${env.productionSalixHost}"; -env.salixPort = "${env.productionSalixPort}"; switch (branchName){ case branchTest: env.NODE_ENV = "test"; - env.salixHost = "${env.testSalixHost}"; - env.salixPort = "${env.testSalixPort}"; break; case branchProduction: env.DOCKER_HOST = "tcp://172.16.255.29:2375"; @@ -26,7 +20,7 @@ switch (branchName){ node { stage ('Print environment variables'){ - echo "Branch ${branchName}, Build ${env.TAG}, salixHost ${env.salixHost}, NODE_ENV ${env.NODE_ENV} en docker Host ${env.DOCKER_HOST}" + echo "Branch ${branchName}, Build ${env.TAG}, NODE_ENV ${env.NODE_ENV} en docker Host ${env.DOCKER_HOST}" } stage ('Checkout') { checkout scm diff --git a/client/client/src/address-edit/address-edit.html b/client/client/src/address-edit/address-edit.html index a6e2b9499..a5830d715 100644 --- a/client/client/src/address-edit/address-edit.html +++ b/client/client/src/address-edit/address-edit.html @@ -61,29 +61,36 @@ {{$parent.$parent.item.description}} - + - - + + + + diff --git a/client/client/src/address-edit/address-edit.js b/client/client/src/address-edit/address-edit.js index ef20f68f1..a715dc8a4 100644 --- a/client/client/src/address-edit/address-edit.js +++ b/client/client/src/address-edit/address-edit.js @@ -17,18 +17,6 @@ export default class Controller { this.observationsRemoved = []; } - _setIconAdd() { - if (this.observations.length) { - this.observations.map(element => { - element.showAddIcon = false; - return true; - }); - this.observations[this.observations.length - 1].showAddIcon = true; - } else { - this.addObservation(); - } - } - _setDirtyForm() { if (this.$scope.form) { this.$scope.form.$setDirty(); @@ -41,20 +29,21 @@ export default class Controller { } addObservation() { - this.observations.push({observationTypeFk: null, addressFk: this.address.id, description: null, showAddIcon: true}); - this._setIconAdd(); + this.observations.push({observationTypeFk: null, addressFk: this.address.id, description: null}); } removeObservation(index) { let item = this.observations[index]; if (item) { this.observations.splice(index, 1); - this._setIconAdd(); if (item.id) { this.observationsRemoved.push(item.id); this._setDirtyForm(); } } + if (this.observations.length === 0 && Object.keys(this.observationsOld).length === 0) { + this._unsetDirtyForm(); + } } _submitObservations(objectObservations) { return this.$http.post(`/client/api/AddressObservations/crudAddressObservations`, objectObservations); @@ -65,7 +54,10 @@ export default class Controller { } submit() { - this._unsetDirtyForm(); + if (this.$scope.form.$invalid) { + return false; + } + let canWatcherSubmit = this.$scope.watcher.dataChanged(); let canObservationsSubmit; let repeatedTypes = false; @@ -118,6 +110,7 @@ export default class Controller { this.$translate.instant('No changes to save') ); } + this._unsetDirtyForm(); } $onInit() { diff --git a/client/client/src/address-edit/address-edit.spec.js b/client/client/src/address-edit/address-edit.spec.js index 727772d7e..23499bd33 100644 --- a/client/client/src/address-edit/address-edit.spec.js +++ b/client/client/src/address-edit/address-edit.spec.js @@ -23,56 +23,6 @@ describe('Client', () => { expect(controller.address.id).toEqual(1); }); - describe('_setIconAdd()', () => { - it('should set the propertie sowAddIcon from all observations to false less last one that be true', () => { - controller.observations = [ - {id: 1, description: 'Spiderman rocks', showAddIcon: true}, - {id: 2, description: 'Batman sucks', showAddIcon: true}, - {id: 3, description: 'Ironman rules', showAddIcon: false} - ]; - - controller._setIconAdd(); - - expect(controller.observations[0].showAddIcon).toBeFalsy(); - expect(controller.observations[1].showAddIcon).toBeFalsy(); - expect(controller.observations[2].showAddIcon).toBeTruthy(); - }); - }); - - describe('addObservation()', () => { - it('should add one empty observation into controller observations collection and call _setIconAdd()', () => { - controller.observations = []; - spyOn(controller, '_setIconAdd').and.callThrough(); - controller.addObservation(); - - expect(controller._setIconAdd).toHaveBeenCalledWith(); - expect(controller.observations.length).toEqual(1); - expect(controller.observations[0].id).toBe(undefined); - expect(controller.observations[0].showAddIcon).toBeTruthy(); - }); - }); - - describe('removeObservation(index)', () => { - it('should remove an observation that occupies the position in the index given and call _setIconAdd()', () => { - let index = 2; - controller.observations = [ - {id: 1, description: 'Spiderman rocks', showAddIcon: false}, - {id: 2, description: 'Batman sucks', showAddIcon: false}, - {id: 3, description: 'Ironman rules', showAddIcon: true} - ]; - - spyOn(controller, '_setIconAdd').and.callThrough(); - - controller.removeObservation(index); - - expect(controller._setIconAdd).toHaveBeenCalledWith(); - expect(controller.observations.length).toEqual(2); - expect(controller.observations[0].showAddIcon).toBeFalsy(); - expect(controller.observations[1].showAddIcon).toBeTruthy(); - expect(controller.observations[index]).toBe(undefined); - }); - }); - describe('_observationsEquals', () => { it('should return true if two observations are equals independent of control attributes', () => { let ob1 = {id: 1, observationTypeFk: 1, description: 'Spiderman rocks', showAddIcon: true}; diff --git a/client/client/src/address-edit/locale/es.yml b/client/client/src/address-edit/locale/es.yml index 78f236039..aa9c23ef0 100644 --- a/client/client/src/address-edit/locale/es.yml +++ b/client/client/src/address-edit/locale/es.yml @@ -2,4 +2,6 @@ Enabled: Activo Is equalizated: Recargo de equivalencia Observation type: Tipo de observación Description: Descripción -The observation type must be unique: El tipo de observación ha de ser único \ No newline at end of file +The observation type must be unique: El tipo de observación ha de ser único +Add note: Añadir nota +Remove note: Quitar nota \ No newline at end of file diff --git a/client/client/src/descriptor/descriptor.html b/client/client/src/descriptor/descriptor.html index a47c491e0..950baf89c 100644 --- a/client/client/src/descriptor/descriptor.html +++ b/client/client/src/descriptor/descriptor.html @@ -25,4 +25,30 @@ - + \ No newline at end of file diff --git a/client/client/src/descriptor/descriptor.js b/client/client/src/descriptor/descriptor.js index d3467e82d..729b85fb3 100644 --- a/client/client/src/descriptor/descriptor.js +++ b/client/client/src/descriptor/descriptor.js @@ -1,8 +1,35 @@ import ngModule from '../module'; +class ClientDescriptor { + constructor($http) { + this.$http = $http; + } + _getClientDebt(clientFk) { + this.$http.get(`/client/api/Clients/${clientFk}/getDebt`) + .then(response => { + this.clientDebt = response.data.debt; + }); + } + + _getClient(clientFk) { + this.$http.get(`/client/api/Clients/${clientFk}/card`) + .then(response => { + Object.assign(this.client, response.data); + }); + } + + $onChanges(changes) { + if (changes.client && this.client) { + this._getClient(this.client.id); + this._getClientDebt(this.client.id); + } + } +} + ngModule.component('vnClientDescriptor', { template: require('./descriptor.html'), bindings: { client: '<' - } + }, + controller: ClientDescriptor }); diff --git a/client/client/src/descriptor/descriptor.spec.js b/client/client/src/descriptor/descriptor.spec.js new file mode 100644 index 000000000..a5139298f --- /dev/null +++ b/client/client/src/descriptor/descriptor.spec.js @@ -0,0 +1,45 @@ +import './descriptor.js'; + +describe('Descriptor', () => { + describe('Component vnClientDescriptor', () => { + let $componentController; + let $httpBackend; + let controller; + + beforeEach(() => { + angular.mock.module('client'); + }); + + beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => { + $componentController = _$componentController_; + $httpBackend = _$httpBackend_; + controller = $componentController('vnClientDescriptor'); + })); + + describe('_getClientDebt()', () => { + it(`should call _getClientDebt() and define the client.debt value on the controller`, () => { + controller.client = {}; + let response = {debt: 100}; + $httpBackend.whenGET(`/client/api/Clients/101/getDebt`).respond(response); + $httpBackend.expectGET(`/client/api/Clients/101/getDebt`); + controller._getClientDebt(101); + $httpBackend.flush(); + + expect(controller.client.debt).toEqual(100); + }); + }); + + describe('_getClient()', () => { + it(`should call _getClient() and define the client value on the controller`, () => { + controller.client = {}; + let response = {id: 101, name: 'Batman'}; + $httpBackend.whenGET(`/client/api/Clients/101/card`).respond(response); + $httpBackend.expectGET(`/client/api/Clients/101/card`); + controller._getClient(101); + $httpBackend.flush(); + + expect(controller.client.name).toEqual('Batman'); + }); + }); + }); +}); diff --git a/client/client/src/recovery-list/locale/es.yml b/client/client/src/recovery-list/locale/es.yml index f69b3fa2a..3ad385a5c 100644 --- a/client/client/src/recovery-list/locale/es.yml +++ b/client/client/src/recovery-list/locale/es.yml @@ -1,4 +1,6 @@ Since: Desde Employee: Empleado No results: Sin resultados -To: Hasta \ No newline at end of file +To: Hasta +Recovery: Recobros +Finish that recovery period: Terminar el recobro \ No newline at end of file diff --git a/client/client/src/recovery-list/recovery-list.html b/client/client/src/recovery-list/recovery-list.html index 77a31d5d3..b8d083e99 100644 --- a/client/client/src/recovery-list/recovery-list.html +++ b/client/client/src/recovery-list/recovery-list.html @@ -4,7 +4,7 @@ Recovery - + @@ -13,11 +13,17 @@ - {{::recovery.started | date:'dd/MM/yyyy' }} - {{::recovery.finished | date:'dd/MM/yyyy' }} - {{::recovery.amount | currency:'€':0}} - {{::recovery.period}} + ng-repeat="recovery in index.model.instances track by $index"> + + lock + + {{recovery.started | date:'dd/MM/yyyy' }} + {{recovery.finished | date:'dd/MM/yyyy' }} + {{recovery.amount | currency:'€':0}} + {{recovery.period}} No results diff --git a/client/client/src/recovery-list/recovery-list.js b/client/client/src/recovery-list/recovery-list.js index 949dcbad1..5996f2d50 100644 --- a/client/client/src/recovery-list/recovery-list.js +++ b/client/client/src/recovery-list/recovery-list.js @@ -1,7 +1,24 @@ import ngModule from '../module'; import FilterClientList from '../filter-client-list'; +class ClientRecoveryList extends FilterClientList { + constructor($scope, $timeout, $state, $http) { + super($scope, $timeout, $state); + this.$http = $http; + } + setFinished(recovery) { + if (!recovery.finished) { + let params = {finished: Date.now()}; + this.$http.patch(`/client/api/Recoveries/${recovery.id}`, params).then( + () => this.$.index.accept() + ); + } + } +} + +ClientRecoveryList.$inject = ['$scope', '$timeout', '$state', '$http']; + ngModule.component('vnClientRecoveryList', { template: require('./recovery-list.html'), - controller: FilterClientList + controller: ClientRecoveryList }); diff --git a/client/client/src/summary/client-summary.html b/client/client/src/summary/client-summary.html index ab9186ae7..e506ae23e 100644 --- a/client/client/src/summary/client-summary.html +++ b/client/client/src/summary/client-summary.html @@ -2,45 +2,154 @@ -
{{$ctrl.client.name}} - {{$ctrl.client.id}} - {{$ctrl.client.salesPerson.name}}
+
{{$ctrl.summary.name}} - {{$ctrl.summary.id}} - {{$ctrl.summary.salesPerson.name}}
Basic data
-

Commercial name: {{$ctrl.client.name}}

-

Contact: {{$ctrl.client.contact}}

-

Phone: {{$ctrl.client.phone}}

-

Mobile: {{$ctrl.client.mobile}}

-

Email: {{$ctrl.client.email}}

-

Salesperson: {{$ctrl.client.salesPerson}}

-

Channel: {{$ctrl.client.contactChannel}}

+

Comercial Name {{$ctrl.summary.name}}

+

Contact {{$ctrl.summary.contact}}

+

Phone {{$ctrl.summary.phone}}

+

Mobile {{$ctrl.summary.mobile}}

+

Email {{$ctrl.summary.email}}

+

Salesperson {{$ctrl.summary.salesPerson.name}}

+

Channel {{$ctrl.summary.contactChannel.name}}

Fiscal data
-

Social name: {{$ctrl.client.socialName}}

-

NIF / CIF: {{$ctrl.client.fi}}

-

Fiscal address: {{$ctrl.client.street}}

-

City: {{$ctrl.client.city}}

-

Postcode: {{$ctrl.client.postcode}}

-

Province: {{$ctrl.client.province}}

-

Country: {{$ctrl.client.country}}

+

Social name {{$ctrl.summary.socialName}}

+

NIF / CIF {{$ctrl.summary.fi}}

+

Street {{$ctrl.summary.street}}

+

City {{$ctrl.summary.city}}

+

Postcode {{$ctrl.summary.postcode}}

+

Province {{$ctrl.summary.province.name}}

+

Country {{$ctrl.summary.country.country}}

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

Pay method
-

Pay method: {{$ctrl.client.payMethodFk}}

+

Pay method {{$ctrl.summary.payMethod.name}}

+

IBAN {{$ctrl.summary.iban}}

+

Due day {{$ctrl.summary.dueDay}}

+

+ + +

+

+ + +

+

+ + +

-
Address
+
Default address
+

{{$ctrl.address.nickname}}

+

{{$ctrl.address.street}}

+

{{$ctrl.address.city}}

-
Credit
+
Web access
+

User {{$ctrl.summary.account.name}}

+

+ + +

-
Greuge
+
Recovery
+ +

Since {{$ctrl.recovery.started}}

+

To {{$ctrl.recovery.finished}}

+

Amount {{$ctrl.recovery.amount | currency:'€':2}}

+

Period {{$ctrl.recovery.period}}

+
+
+ + +
Total greuge
+

Total {{$ctrl.greuge.sumAmount | currency:'€':2}}

+
+ + +
Credit
+

+ Credit + {{$ctrl.summary.credit | currency:'€':2}} +

+

+ Secured credit + - + {{$ctrl.summary.creditInsurance | currency:'€':2}} +

+
+
\ No newline at end of file diff --git a/client/client/src/summary/client-summary.js b/client/client/src/summary/client-summary.js index 4109b1b77..a3e7c7bae 100644 --- a/client/client/src/summary/client-summary.js +++ b/client/client/src/summary/client-summary.js @@ -1,24 +1,92 @@ import ngModule from '../module'; class ClientSummary { + constructor($http) { this.$http = $http; } - getRelations() { - let filter = { - include: ['account', 'salesPerson', 'province'] - }; - let url = `/client/api/Clients/${this.client.id}?filter=${JSON.stringify(filter)}`; - this.$http.get(encodeURIComponent(url)).then(res => { - if (res.data) - this.client = res.data; + set client(value) { + if (!value) + return; + + let filter = { + include: [ + { + relation: 'account', + scope: { + fields: ['name', 'active'] + } + }, + { + relation: 'salesPerson', + scope: { + fields: ['name'] + } + }, + { + relation: 'country', + scope: { + fields: ['country'] + } + }, + { + relation: 'province', + scope: { + fields: ['name'] + } + }, + { + relation: 'contactChannel', + scope: { + fields: ['name'] + } + }, + { + relation: 'payMethod', + scope: { + fields: ['name'] + } + }, + { + relation: 'addresses', + scope: { + where: {isDefaultAddress: true}, + fields: ['nickname', 'street', 'city', 'postalCode'] + } + } + ] + }; + + let clientSummary = `/client/api/Clients/${value.id}?filter=${JSON.stringify(filter)}`; + + this.$http.get(encodeURIComponent(clientSummary)).then(res => { + if (res.data) { + this.summary = res.data; + this.address = res.data.addresses[0]; + } + }); + + let greugeSum = `/client/api/Greuges/${value.id}/sumAmount`; + + this.$http.get(encodeURIComponent(greugeSum)).then(res => { + if (res.data) + this.greuge = res.data; + }); + + let recoveryFilter = { + where: { + and: [{clientFk: value.id}, {or: [{finished: null}, {finished: {gt: Date.now()}}]}] + }, + limit: 1 + }; + + let recovery = `/client/api/Recoveries?filter=${JSON.stringify(recoveryFilter)}`; + + this.$http.get(encodeURIComponent(recovery)).then(res => { + if (res.data) + this.recovery = res.data[0]; }); - } - $onChanges(changesObj) { - if (this.client && this.client.id && !this.client.salesPerson) { - this.getRelations(); - } } } ClientSummary.$inject = ['$http']; diff --git a/client/client/src/summary/locale/es.yml b/client/client/src/summary/locale/es.yml new file mode 100644 index 000000000..27ebf6e37 --- /dev/null +++ b/client/client/src/summary/locale/es.yml @@ -0,0 +1,2 @@ +Default address: Consignatario pred. +Total greuge: Greuge total \ No newline at end of file diff --git a/client/client/src/web-access/web-access.html b/client/client/src/web-access/web-access.html index aaeffa5ba..47e05759a 100644 --- a/client/client/src/web-access/web-access.html +++ b/client/client/src/web-access/web-access.html @@ -12,16 +12,18 @@ + field="$ctrl.account.active" + vn-acl="employee" + acl-conditional-to-employee="{{$ctrl.canEnableCheckBox}}"> + field="$ctrl.account.name"> @@ -47,7 +49,7 @@ - + diff --git a/client/client/src/web-access/web-access.js b/client/client/src/web-access/web-access.js index 41aefc0bf..5545b96f8 100644 --- a/client/client/src/web-access/web-access.js +++ b/client/client/src/web-access/web-access.js @@ -6,21 +6,30 @@ export default class Controller { this.$http = $http; this.vnApp = vnApp; this.canChangePassword = false; + this.canEnableCheckBox = true; } + $onChanges() { if (this.client) { this.account = this.client.account; this.isCustomer(); + this.checkConditions(); } } isCustomer() { - if (this.client && this.client.id) { + if (this.client.id) { this.$http.get(`/client/api/Clients/${this.client.id}/hasCustomerRole`).then(res => { - this.canChangePassword = (res.data) ? res.data.isCustomer : false; + this.canChangePassword = res.data && res.data.isCustomer; + }); + } + } + + checkConditions() { + if (this.client.id) { + this.$http.get(`/client/api/Clients/${this.client.id}/isValidClient`).then(res => { + this.canEnableCheckBox = res.data; }); - } else { - this.canChangePassword = false; } } @@ -31,7 +40,7 @@ export default class Controller { } onPassChange(response) { - if (response == 'ACCEPT' && this.canChangePassword) + if (response == 'ACCEPT') try { if (!this.newPassword) throw new Error(`Passwords can't be empty`); diff --git a/client/core/src/components/autocomplete/autocomplete.html b/client/core/src/components/autocomplete/autocomplete.html old mode 100644 new mode 100755 index 6dd9b3ab7..0ee10c5ae --- a/client/core/src/components/autocomplete/autocomplete.html +++ b/client/core/src/components/autocomplete/autocomplete.html @@ -1,22 +1,23 @@ - - - - - {{$parent.item[$ctrl.showField]}} +
+
+ + +
+ + +
+ +
+ - \ No newline at end of file +
\ No newline at end of file diff --git a/client/core/src/components/autocomplete/autocomplete.js b/client/core/src/components/autocomplete/autocomplete.js old mode 100644 new mode 100755 index 2dc37b659..5c216d21d --- a/client/core/src/components/autocomplete/autocomplete.js +++ b/client/core/src/components/autocomplete/autocomplete.js @@ -1,381 +1,255 @@ import ngModule from '../../module'; import Component from '../../lib/component'; -import copyObject from '../../lib/copy'; import './style.scss'; -class Autocomplete extends Component { - constructor($element, $scope, $http, $timeout, $filter) { - super($element); - this.$element = $element; - this.$scope = $scope; +/** + * Input with option selector. + * + * @property {String} valueField The data field name that should be shown + * @property {String} showFiled The data field name that should be used as value + * @property {Array} data Static data for the autocomplete + * @property {Object} intialData A initial data to avoid the server request used to get the selection + * @property {Boolean} multiple Wether to allow multiple selection + */ +export default class Autocomplete extends Component { + constructor($element, $scope, $http, $transclude) { + super($element, $scope); this.$http = $http; - this.$timeout = $timeout; - this.$filter = $filter; + this.$transclude = $transclude; - this._showDropDown = false; - this.finding = false; - this.findMore = false; - this._value = null; - this._field = null; - this._preLoad = false; - this.maxRow = 10; - this.showField = 'name'; + this._field = undefined; + this._selection = null; this.valueField = 'id'; - this.items = copyObject(this.data) || []; - this.displayValueMultiCheck = []; + this.showField = 'name'; this._multiField = []; this.readonly = true; - this.removeLoadMore = false; this.form = null; - this.findForm = false; - } - - get showDropDown() { - return this._showDropDown; - } - - set showDropDown(value) { - if (value && this.url && !this._preLoad) { - this._preLoad = true; - this.getItems(); - } - if (value && !this.width) { - let rectangle = this.$element[0].getBoundingClientRect(); - this.width = Math.round(rectangle.width) - 10; - } - this._showDropDown = value; - } - - get displayValue() { - return this._value; - } - - set displayValue(value) { - let val = (value === undefined || value === '') ? null : value; - if (this.multiple && val) { - let index = this.displayValueMultiCheck.indexOf(val); - if (index === -1) - this.displayValueMultiCheck.push(val); - else - this.displayValueMultiCheck.splice(index, 1); - - this._value = this.displayValueMultiCheck.join(', '); - } else { - this._value = val; - } - - if (value === null) { - this.field = null; - if (this.multiple && this.items.length) { - this.displayValueMultiCheck = []; - this.items.map(item => { - item.checked = false; - return item; - }); - } - } + this.input = this.element.querySelector('.mdl-textfield__input'); + + componentHandler.upgradeElement( + this.element.querySelector('.mdl-textfield')); } + /** + * @type {any} The autocomplete value. + */ get field() { - return this.multiple ? this._multiField : this._field; + return this._field; } set field(value) { - if (!angular.equals(value, this.field)) { - this.finding = true; - if (value && value.hasOwnProperty(this.valueField)) { - this._field = value[this.valueField]; - if (this.multiple) { - this.setMultiField(value[this.valueField]); - } - this.setDirtyForm(); - } else { - this.setValue(value); - } + if (angular.equals(value, this._field)) + return; - if (value && value.hasOwnProperty(this.showField)) - this.displayValue = value[this.showField]; + this._field = value; + this.refreshSelection(); - this.finding = false; - - if (this.onChange) - this.onChange({item: this._field}); - } + if (this.onChange) + this.onChange(value); } - set initialData(value) { - if (value && value.hasOwnProperty(this.valueField)) { - this._field = value[this.valueField]; - if (this.multiple) { - this._multiField = [value[this.valueField]]; - } - if (value.hasOwnProperty(this.showField)) { - this.displayValue = value[this.showField]; - } - } + /** + * @type {Object} The selected data object, you can use this property + * to prevent requests to display the initial value. + */ + get selection() { + return this._selection; } - setMultiField(val) { - if (val && typeof val === 'object' && val[this.valueField]) { - val = val[this.valueField]; - } - if (val === null) { - this._multiField = []; - } else { - let index = this._multiField.indexOf(val); - if (index === -1) { - this._multiField.push(val); - } else { - this._multiField.splice(index, 1); - } - } + set selection(value) { + this._selection = value; + this.refreshDisplayed(); } - setValue(value) { - if (value) { - let data = this.items; + selectionIsValid(selection) { + return selection + && selection[this.valueField] == this._field + && selection[this.showField] != null; + } - if (data && data.length) + refreshSelection() { + if (this.selectionIsValid(this._selection)) + return; + + let value = this._field; + + if (value && this.valueField && this.showField) { + if (this.selectionIsValid(this.initialData)) { + this.selection = this.initialData; + return; + } + + let data = this.data; + + if (!data && this.$.dropDown) + data = this.$.dropDown.$.model.data; + + if (data) for (let i = 0; i < data.length; i++) if (data[i][this.valueField] === value) { - this.showItem(data[i]); + this.selection = data[i]; return; } - this.requestItem(value); - } else { - this._field = null; - this.setMultiField(null); - this.displayValue = ''; - } + if (this.url) { + this.requestSelection(value); + return; + } + } else + this.selection = null; } - requestItem(value) { - if (!value) return; - + requestSelection(value) { let where = {}; - where[this.valueField] = value; + + if (this.multiple) + where[this.valueField] = {inq: this.field}; + else + where[this.valueField] = value; let filter = { - fields: this.getRequestFields(), + fields: this.getFields(), where: where }; - let json = JSON.stringify(filter); - + let json = encodeURIComponent(JSON.stringify(filter)); this.$http.get(`${this.url}?filter=${json}`).then( - json => this.onItemRequest(json.data), - json => this.onItemRequest(null) + json => this.onSelectionRequest(json.data), + () => this.onSelectionRequest(null) ); } - onItemRequest(data) { - if (data && data.length > 0) - this.showItem(data[0]); - else - this.showItem(null); + onSelectionRequest(data) { + if (data && data.length > 0) { + if (this.multiple) + this.selection = data; + else + this.selection = data[0]; + } else + this.selection = null; } - showItem(item) { - this.displayValue = item ? item[this.showField] : ''; - this.field = item; + refreshDisplayed() { + let display = ''; + + if (this._selection && this.showField) { + if (this.multiple && Array.isArray(this._selection)) { + for (var item of this._selection) { + if (display.length > 0) display += ', '; + display += item[this.showField]; + } + } else { + display = this._selection[this.showField]; + } + } + + this.input.value = display; + this.mdlUpdate(); } - getRequestFields() { - let fields = {}; - fields[this.valueField] = true; - fields[this.showField] = true; + getFields() { + let fields = []; + fields.push(this.valueField); + fields.push(this.showField); - if (this._selectFields) - for (let field of this._selectFields) - fields[field] = true; + if (this.selectFields) + for (let field of this.selectFields) + fields.push(field); return fields; } - getOrder() { - return this.order ? this.order : `${this.showField} ASC`; + mdlUpdate() { + let field = this.element.querySelector('.mdl-textfield'); + let mdlField = field.MaterialTextfield; + if (mdlField) mdlField.updateClasses_(); } - findItems(search) { - if (this.url && search && !this.finding) { - this.maxRow = false; - let filter = {}; - if (this.filterSearch) { - let toSearch = this.filterSearch.replace(/search/g, search); - filter = this.$scope.$eval(toSearch); - } else { - filter = {where: {name: {regexp: search}}}; - if (this.filter && this.filter.where) { - Object.assign(filter.where, this.filter.where); - } - } - filter.order = this.getOrder(); - let json = JSON.stringify(filter); - this.finding = true; - this.$http.get(`${this.url}?filter=${json}`).then( - json => { - this.items = []; - json.data.forEach( - el => { - if (this.multiple) { - el.checked = this.field.indexOf(el[this.valueField]) !== -1; - } - this.items.push(el); - } - ); - this.finding = false; - }, - () => { - this.finding = false; - } - ); - } else if (search && !this.url && this.data) { - this.items = this.$filter('filter')(this.data, search); - } else if (!search && !this.finding) { - this.maxRow = 10; - this.items = []; - this.getItems(); - } + setValue(value) { + this.field = value; + if (this.form) this.form.$setDirty(); } - getItems() { - if (this.url === undefined) { - this.items = copyObject(this.data); - this.maxRow = false; - this.removeLoadMore = true; - } else { - let filter = {}; - if (!this.finding) { - this.finding = true; - - if (this.maxRow) { - if (this.items) { - filter.skip = this.items.length; - } - filter.limit = this.maxRow; - filter.order = this.getOrder(); - } - if (this.filter) { - Object.assign(filter, this.filter); - } - - let json = JSON.stringify(filter); - - this.removeLoadMore = false; - - this.$http.get(`${this.url}?filter=${json}`).then( - json => { - if (json.data.length) { - json.data.forEach( - el => { - if (this.multiple) { - el.checked = this.field.indexOf(el[this.valueField]) !== -1; - } - this.items.push(el); - } - ); - if (filter.skip === 0 && this.maxRow && json.data.length < this.maxRow) { - this.removeLoadMore = true; - } - } else { - this.maxRow = false; - } - this.finding = false; - }, - () => { - this.finding = false; - } - ); - } - } - } - _parentForm() { - this.findForm = true; - let formScope = this.$scope; - while (formScope && !formScope.form && formScope.$id > 1) { - formScope = formScope.$parent; - } - this.form = formScope ? formScope.form || null : null; - } - setDirtyForm() { - if (!this.form && !this.findForm) { - this._parentForm(); - } - if (this.form) { - this.form.$setDirty(); - } + onDropDownSelect(value) { + this.setValue(value); + this.field = value; } - $onInit() { - this.findMore = this.url && this.maxRow; - this.mouseFocus = false; - this.focused = false; + onClearClick(event) { + event.preventDefault(); + this.setValue(null); + } - this.$element.bind('mouseover', e => { - this.$timeout(() => { - this.mouseFocus = true; - this.showDropDown = this.focused; - }); + onKeyDown(event) { + if (event.defaultPrevented) return; + + switch (event.keyCode) { + case 38: // Up + case 40: // Down + case 13: // Enter + this.showDropDown(); + break; + default: + if (event.key.length == 1) + this.showDropDown(event.key); + else + return; + } + + event.preventDefault(); + } + + onMouseDown(event) { + event.preventDefault(); + this.showDropDown(); + } + + showDropDown(search) { + Object.assign(this.$.dropDown.$.model, { + url: this.url, + staticData: this.data }); - this.$element.bind('mouseout', () => { - this.$timeout(() => { - this.mouseFocus = false; - this.showDropDown = this.focused; - }); - }); - this.$element.bind('focusin', e => { - this.$timeout(() => { - this.focused = true; - this.showDropDown = true; - }); - }); - this.$element.bind('focusout', e => { - this.$timeout(() => { - this.focused = false; - this.showDropDown = this.mouseFocus; - }); + Object.assign(this.$.dropDown, { + valueField: this.valueField, + showField: this.showField, + selectFields: this.getFields(), + where: this.where, + order: this.order, + parent: this.input, + multiple: this.multiple, + limit: this.limit, + $transclude: this.$transclude }); + this.$.dropDown.show(search); } - - $onDestroy() { - this.$element.unbind('mouseover'); - this.$element.unbind('mouseout'); - this.$element.unbind('focusin'); - this.$element.unbind('focusout'); - } - - $onChanges(objectChange) { - if (objectChange.data && objectChange.data.currentValue && objectChange.data.currentValue.length) { - this.items = copyObject(objectChange.data.currentValue); - this.maxRow = false; - this.removeLoadMore = true; - } - } - } -Autocomplete.$inject = ['$element', '$scope', '$http', '$timeout', '$filter']; +Autocomplete.$inject = ['$element', '$scope', '$http', '$transclude']; ngModule.component('vnAutocomplete', { template: require('./autocomplete.html'), controller: Autocomplete, bindings: { url: '@?', + data: ' { - let $componentController; + let $element; let $scope; let $httpBackend; - let $timeout; - let $element; let controller; + let data = {id: 1, name: 'Bruce Wayne'}; + beforeEach(() => { angular.mock.module('client'); }); beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$timeout_) => { - $componentController = _$componentController_; $scope = $rootScope.$new(); + $element = angular.element(`
${template}
`); $httpBackend = _$httpBackend_; $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); - $timeout = _$timeout_; - $element = angular.element('
'); - controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout}); + controller = _$componentController_('vnAutocomplete', {$element, $scope, $httpBackend, $transclude: null}); })); - describe('showDropDown() setter', () => { - it(`should set _showDropDown value`, () => { - controller._showDropDown = ''; - controller.showDropDown = 'some value'; + describe('field() setter/getter', () => { + it(`should set field controllers property`, () => { + controller.field = data.id; - expect(controller._showDropDown).toEqual('some value'); + expect(controller.field).toEqual(data.id); }); - it(`should set _showDropDown value`, () => { - controller._showDropDown = ''; - controller.showDropDown = 'some value'; + it(`should set selection finding an existing item in the initialData property`, () => { + controller.valueField = 'id'; + controller.showField = 'name'; + controller.initialData = data; + controller.field = data.id; - expect(controller._showDropDown).toEqual('some value'); - }); - }); - - describe('displayValue() setter', () => { - it(`should display value in a formated way`, () => { - let value = 'some value'; - controller.displayValue = value; - - expect(controller._value).toEqual(value); + expect(controller.selection).toEqual(data); }); - describe('when the autocomeplete is multiple', () => { - it(`should display values separated with commas`, () => { - controller.multiple = true; - controller.displayValue = 'some value'; - controller.displayValue = 'another value'; + it(`should set selection finding an existing item in the data property`, () => { + controller.valueField = 'id'; + controller.showField = 'name'; + controller.data = [data]; + controller.field = data.id; - expect(controller._value).toEqual('some value, another value'); - }); - }); - }); - - describe('field() setter', () => { - describe('when value is an object', () => { - it(`should set _field controllers property`, () => { - controller.field = {id: 1, name: 'Bruce Wayne'}; - - expect(controller._field).toEqual(1); - }); - - it(`should set _multifield controllers property `, () => { - controller.multiple = true; - controller.field = {id: 1, name: 'Bruce Wayne'}; - - expect(controller._field).toEqual(1); - expect(controller._multiField[0]).toEqual(1); - - controller.field = {id: 1, name: 'Bruce Wayne'}; - - expect(controller._multiField).toEqual([]); - expect(controller._field).toEqual(1); - }); - - it(`should set _multifield value and remove it if called a second type with same value`, () => { - controller.multiple = true; - controller.field = {id: 1, name: 'Bruce Wayne'}; - - expect(controller._field).toEqual(1); - expect(controller._multiField[0]).toEqual(1); - - controller.field = {id: 1, name: 'Bruce Wayne'}; - - expect(controller._multiField).toEqual([]); - expect(controller._field).toEqual(1); - }); - - it(`should set displayValue finding an existing item in the controller.items property`, () => { - controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}]; - controller.field = {id: 2, name: 'Bruce Wayne'}; - - expect(controller.displayValue).toEqual('Bruce Wayne'); - }); + expect(controller.selection).toEqual(data); }); - describe('when value is a number', () => { - it(`should set _field controller property finding an existing item in the controller.items property`, () => { - controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}]; - controller.field = 2; + it(`should set selection to null when can't find an existing item in the data property`, () => { + controller.valueField = 'id'; + controller.showField = 'name'; + controller.field = data.id; - expect(controller._field).toEqual(2); - }); - - it(`should set _multifield value and remove it if called a second type with same value finding an existing item in the controller.items property`, () => { - controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}]; - controller.multiple = true; - controller.field = 2; - - expect(controller._multiField[0]).toEqual(2); - - controller.field = 2; - - expect(controller._multiField).toEqual([]); - }); - - it(`should perform a query if the item id isn't present in the controller.items property`, () => { - controller.url = 'test.com'; - $httpBackend.expectGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`); - controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}]; - controller.field = 3; - $httpBackend.flush(); - }); - - it(`should set displayValue finding an existing item in the controller.items property`, () => { - controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}]; - controller.field = 2; - - expect(controller.displayValue).toEqual('Bruce Wayne'); - }); - - it(`should set field performing a query as the item id isn't present in the controller.items property`, () => { - controller.url = 'test.com'; - $httpBackend.expectGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`); - controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}]; - controller.field = 3; - $httpBackend.flush(); - }); + expect(controller.selection).toEqual(null); }); - }); - describe('findItems()', () => { - it(`should perform a search and store the result in controller items`, () => { - let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout}); - controller.url = 'test.com'; - let search = 'The Joker'; - let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()}); - $httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]); - $httpBackend.expectGET(`${controller.url}?filter=${json}`); - controller.findItems(search); + it(`should perform a query if the item id isn't present in the data property`, () => { + controller.valueField = 'id'; + controller.showField = 'name'; + controller.url = 'localhost'; + controller.field = data.id; + + let filter = { + fields: ['id', 'name'], + where: {id: data.id} + }; + let json = encodeURIComponent(JSON.stringify(filter)); + + $httpBackend.expectGET(`localhost?filter=${json}`); + controller.field = data.id; $httpBackend.flush(); - - expect(controller.items[0]).toEqual({id: 3, name: 'The Joker'}); - }); - - it(`should perform a search and store the result in controller items with filterSearch`, () => { - let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout}); - controller.url = 'test.com'; - let search = 'The Joker'; - controller.filterSearch = "{where: {name: {regexp: 'search'}}}"; - let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()}); - $httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]); - $httpBackend.expectGET(`${controller.url}?filter=${json}`); - controller.findItems(search); - $httpBackend.flush(); - - expect(controller.items[0]).toEqual({id: 3, name: 'The Joker'}); - }); - - it(`should perform a search with multiple true and store the result in controller items with the checked property defined`, () => { - controller.url = 'test.com'; - let search = 'Joker'; - controller.multiple = true; - let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()}); - $httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}, {id: 4, name: 'Joker'}]); - $httpBackend.expectGET(`${controller.url}?filter=${json}`); - controller.findItems(search); - $httpBackend.flush(); - - expect(controller.items).toEqual([{id: 3, name: 'The Joker', checked: false}, {id: 4, name: 'Joker', checked: false}]); - }); - - it(`should call getItems function if there's no search value`, () => { - controller.url = 'test.com'; - spyOn(controller, 'getItems'); - controller.findItems(); - - expect(controller.getItems).toHaveBeenCalledWith(); - }); - }); - - describe('getItems()', () => { - it(`should perfom a query to fill the items without filter`, () => { - controller.url = 'test.com'; - $httpBackend.whenGET(`${controller.url}?filter={"skip":0,"limit":10,"order":"name ASC"}`).respond([{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}]); - $httpBackend.expectGET(`${controller.url}?filter={"skip":0,"limit":10,"order":"name ASC"}`); - controller.getItems(); - $httpBackend.flush(); - - expect(controller.items).toEqual([{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}]); }); }); }); diff --git a/client/core/src/components/autocomplete/style.scss b/client/core/src/components/autocomplete/style.scss old mode 100644 new mode 100755 index 15e59d4d3..26143c035 --- a/client/core/src/components/autocomplete/style.scss +++ b/client/core/src/components/autocomplete/style.scss @@ -1,3 +1,47 @@ + +vn-autocomplete > div > .mdl-textfield { + position: relative; + width: 100%; + + & > input { + cursor: pointer; + height: 26px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + &:focus { + outline: none; + } + &::-moz-focus-inner { + border: 0; + } + } + & > .icons { + display: none; + position: absolute; + right: 0; + top: 1.3em; + height: 1em; + color: #888; + border-radius: .2em; + background-color: rgba(255, 255, 255, .8); + + & > vn-icon { + cursor: pointer; + font-size: 18px; + + &:hover { + color: #333; + } + } + } + &:hover > .icons, + & > input:focus + .icons { + display: block; + } +} + ul.vn-autocomplete { list-style-type: none; padding: 1em; @@ -14,7 +58,7 @@ ul.vn-autocomplete { &.active, &:hover { - background-color: rgba(1,1,1,.1); + background-color: rgba(1, 1, 1, .1); } &.load-more { color: #ffa410; @@ -22,27 +66,4 @@ ul.vn-autocomplete { padding: .4em .8em; } } -} -vn-autocomplete { - position: relative; - vn-vertical { - outline:none; - } - - .mdl-chip__action { - position: absolute; - top: 0px; - right: -6px; - margin: 22px 0px; - background: transparent; - } - .material-icons { - font-size: 18px; - } - vn-drop-down{ - margin-top: 47px; - } - vn-drop-down .dropdown-body .filter vn-icon { - margin-left: -26px; - } } \ No newline at end of file diff --git a/client/core/src/components/dialog/dialog.js b/client/core/src/components/dialog/dialog.js index 602acfe45..401a13e11 100644 --- a/client/core/src/components/dialog/dialog.js +++ b/client/core/src/components/dialog/dialog.js @@ -37,14 +37,16 @@ export default class Dialog extends Component { show() { if (this.shown) return; this.shown = true; - this.keypressHandler = e => this.onKeypress(e); - this.document.addEventListener('keypress', this.keypressHandler); + this.keyDownHandler = e => this.onkeyDown(e); + this.document.addEventListener('keydown', this.keyDownHandler); this.element.style.display = 'flex'; - this.transitionTimeout = - setTimeout(() => this.$element.addClass('shown'), 30); + this.transitionTimeout = setTimeout(() => this.$element.addClass('shown'), 30); if (this.onOpen) this.onOpen(); + + let firstFocusable = this.element.querySelector('input, textarea'); + if (firstFocusable) firstFocusable.focus(); } /** * Hides the dialog calling the response handler. @@ -68,7 +70,7 @@ export default class Dialog extends Component { realHide() { if (!this.shown) return; this.element.style.display = 'none'; - this.document.removeEventListener('keypress', this.keypressHandler); + this.document.removeEventListener('keydown', this.keyDownHandler); this.lastEvent = null; this.shown = false; this.transitionTimeout = @@ -94,7 +96,7 @@ export default class Dialog extends Component { if (event != this.lastEvent) this.hide(); } - onKeypress(event) { + onkeyDown(event) { if (event.keyCode == 27) // Esc this.hide(); } diff --git a/client/core/src/components/dialog/style.scss b/client/core/src/components/dialog/style.scss index 12477a037..de13f0f7c 100644 --- a/client/core/src/components/dialog/style.scss +++ b/client/core/src/components/dialog/style.scss @@ -28,7 +28,10 @@ display: block; width: 20em; } - button { + button, + input[type="button"], + input[type="submit"], + input[type="reset"] { text-transform: uppercase; background-color: transparent; border: none; @@ -55,7 +58,10 @@ margin-top: 1.5em; text-align: right; - button { + button, + input[type="button"], + input[type="submit"], + input[type="reset"] { color: #ffa410; font-family: vn-font-bold; padding: .7em; diff --git a/client/core/src/components/drop-down/drop-down.html b/client/core/src/components/drop-down/drop-down.html old mode 100644 new mode 100755 index e155c67af..02d2d50b2 --- a/client/core/src/components/drop-down/drop-down.html +++ b/client/core/src/components/drop-down/drop-down.html @@ -1,28 +1,35 @@ - - - - - - - - -