merge
This commit is contained in:
commit
af789caff3
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"module": "client",
|
||||
"name": "Clients",
|
||||
"icon": "/static/images/icon_client.png",
|
||||
"icon": "person",
|
||||
"validations" : true,
|
||||
"routes": [
|
||||
{
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
<vn-title>Address</vn-title>
|
||||
<vn-horizontal>
|
||||
<vn-check vn-one label="Enabled" field="$ctrl.address.isActive"></vn-check>
|
||||
<vn-check vn-one label="Is equalizated" field="$ctrl.address.isEqualizated" vn-acl="administrative"></vn-check>
|
||||
<vn-check
|
||||
vn-one label="Is equalizated"
|
||||
field="$ctrl.address.isEqualizated"
|
||||
vn-acl="administrative, salesAssistant">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield vn-one label="Consignee" field="$ctrl.address.nickname" vn-focus></vn-textfield>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<span>{{::observation.description}}</span>
|
||||
</vn-one>
|
||||
</vn-vertical>
|
||||
<a vn-auto ui-sref="clientCard.addresses.edit({addressId: {{address.id}}})">
|
||||
<a vn-auto ui-sref="clientCard.addresses.edit({addressId: {{::address.id}}})">
|
||||
<vn-icon-button icon="edit"></vn-icon-button>
|
||||
</a>
|
||||
</vn-horizontal>
|
||||
|
|
|
@ -10,30 +10,50 @@
|
|||
<vn-title>Pay method</vn-title>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-two
|
||||
vn-acl="administrative"
|
||||
vn-acl="administrative, salesAssistant"
|
||||
field="$ctrl.client.payMethodFk"
|
||||
url="/client/api/PayMethods"
|
||||
select-fields="ibanRequired"
|
||||
initial-data="$ctrl.client.payMethod"
|
||||
label="Pay method">
|
||||
</vn-autocomplete>
|
||||
<vn-textfield vn-two label="IBAN" field="$ctrl.client.iban" vn-acl="administrative"></vn-textfield>
|
||||
<vn-textfield vn-one label="Due day" field="$ctrl.client.dueDay" vn-acl="administrative"></vn-textfield>
|
||||
<vn-textfield
|
||||
vn-two label="IBAN"
|
||||
field="$ctrl.client.iban"
|
||||
vn-acl="administrative, salesAssistant">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one label="Due day"
|
||||
field="$ctrl.client.dueDay"
|
||||
vn-acl="administrative, salesAssistant">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal margin-medium-bottom>
|
||||
<vn-one>
|
||||
<vn-check label="Received core VNH" field="$ctrl.client.hasCoreVnh" vn-acl="administrative"></vn-check>
|
||||
<vn-check
|
||||
label="Received core VNH"
|
||||
field="$ctrl.client.hasCoreVnh"
|
||||
vn-acl="administrative, salesAssistant">
|
||||
</vn-check>
|
||||
</vn-one>
|
||||
<vn-one>
|
||||
<vn-check label="Received core VNL" field="$ctrl.client.hasCoreVnl" vn-acl="administrative"></vn-check>
|
||||
<vn-check
|
||||
label="Received core VNL"
|
||||
field="$ctrl.client.hasCoreVnl"
|
||||
vn-acl="administrative, salesAssistant">
|
||||
</vn-check>
|
||||
</vn-one>
|
||||
<vn-one>
|
||||
<vn-check label="Received B2B VNL" field="$ctrl.client.hasSepaVnl" vn-acl="administrative"></vn-check>
|
||||
<vn-check
|
||||
label="Received B2B VNL"
|
||||
field="$ctrl.client.hasSepaVnl"
|
||||
vn-acl="administrative, salesAssistant">
|
||||
</vn-check>
|
||||
</vn-one>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-submit label="Save" vn-acl="administrative"></vn-submit>
|
||||
<vn-submit label="Save" vn-acl="administrative, salesAssistant"></vn-submit>
|
||||
</vn-button-bar>
|
||||
</form>
|
||||
<vn-confirm
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('Descriptor', () => {
|
|||
}));
|
||||
|
||||
describe('_getClientDebt()', () => {
|
||||
it(`should call _getClientDebt() and define the client.debt value on the controller`, () => {
|
||||
it(`should call _getClientDebt() and define the clientDebt value on the controller`, () => {
|
||||
controller.client = {};
|
||||
let response = {debt: 100};
|
||||
$httpBackend.whenGET(`/client/api/Clients/101/getDebt`).respond(response);
|
||||
|
@ -25,7 +25,7 @@ describe('Descriptor', () => {
|
|||
controller._getClientDebt(101);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.client.debt).toEqual(100);
|
||||
expect(controller.clientDebt).toEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -14,21 +14,21 @@
|
|||
vn-focus
|
||||
label="Social name"
|
||||
field="$ctrl.client.socialName"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Tax number"
|
||||
field="$ctrl.client.fi"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-textfield>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Is equalizated"
|
||||
field="$ctrl.client.isEqualizated"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
|
@ -37,14 +37,14 @@
|
|||
vn-two
|
||||
label="Street"
|
||||
field="$ctrl.client.street"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="City"
|
||||
field="$ctrl.client.city"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
|
@ -53,7 +53,7 @@
|
|||
vn-one
|
||||
label="Postcode"
|
||||
field="$ctrl.client.postcode"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-textfield>
|
||||
<vn-autocomplete
|
||||
|
@ -64,7 +64,7 @@
|
|||
show-field="name"
|
||||
value-field="id"
|
||||
label="Province"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
|
@ -75,7 +75,7 @@
|
|||
show-field="country"
|
||||
value-field="id"
|
||||
label="Country"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
|
@ -84,21 +84,21 @@
|
|||
vn-one
|
||||
label="Active"
|
||||
field="$ctrl.client.isActive"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Invoice by address"
|
||||
field="$ctrl.client.hasToInvoiceByAddress"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Verified data"
|
||||
field="$ctrl.client.isTaxDataChecked"
|
||||
vn-acl="administrative">
|
||||
vn-acl="administrative, salesAssistant, salesAssistant">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
|
@ -106,21 +106,21 @@
|
|||
vn-one
|
||||
label="Has to invoice"
|
||||
field="$ctrl.client.hasToInvoice"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Invoice by mail"
|
||||
field="$ctrl.client.isToBeMailed"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Vies"
|
||||
field="$ctrl.client.isVies"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
|
@ -128,7 +128,7 @@
|
|||
<vn-button-bar>
|
||||
<vn-submit
|
||||
label="Save"
|
||||
vn-acl="administrative, salesPerson"
|
||||
vn-acl="administrative, salesAssistant, salesPerson"
|
||||
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
|
||||
</vn-submit>
|
||||
</vn-button-bar>
|
||||
|
|
|
@ -9,13 +9,6 @@ vn-autocomplete > div > .mdl-textfield {
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
& > .icons {
|
||||
display: none;
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
vn-date-picker {
|
||||
div {
|
||||
outline: none; //remove chrome outline
|
||||
}
|
||||
.mdl-chip__action {
|
||||
position: absolute;
|
||||
width: auto;
|
||||
|
|
|
@ -1,35 +1,38 @@
|
|||
<div
|
||||
class="body"
|
||||
ng-mousedown="$ctrl.onMouseDown($event)">
|
||||
<div ng-show="$ctrl.showFilter" class="filter">
|
||||
<input
|
||||
type="text"
|
||||
ng-model="$ctrl.search"
|
||||
tabindex="-1"
|
||||
class="search"
|
||||
ng-blur="$ctrl.onFocusOut()"
|
||||
translate-attr="{placeholder: 'Search'}"/>
|
||||
<vn-icon
|
||||
icon="clear"
|
||||
ng-click="$ctrl.onClearClick()"
|
||||
translate-attr="{title: 'Clear'}">
|
||||
</vn-icon>
|
||||
</div>
|
||||
<vn-model
|
||||
vn-id="model"
|
||||
on-data-change="$ctrl.onModelDataChange()">
|
||||
</vn-model>
|
||||
<div class="list" tabindex="-1">
|
||||
<ul
|
||||
class="dropdown"
|
||||
ng-click="$ctrl.onContainerClick($event)">
|
||||
</ul>
|
||||
<div
|
||||
ng-if="$ctrl.statusText"
|
||||
ng-click="$ctrl.onLoadMoreClick($event)"
|
||||
class="status"
|
||||
translate>
|
||||
{{$ctrl.statusText}}
|
||||
<vn-model
|
||||
vn-id="model"
|
||||
on-data-change="$ctrl.onModelDataChange()">
|
||||
</vn-model>
|
||||
<vn-popover
|
||||
vn-id="popover"
|
||||
on-open="$ctrl.onOpen()"
|
||||
on-close="$ctrl.onClose()">
|
||||
<div class="dropdown">
|
||||
<div ng-show="$ctrl.showFilter" class="filter">
|
||||
<input
|
||||
type="text"
|
||||
ng-model="$ctrl.search"
|
||||
tabindex="-1"
|
||||
class="search"
|
||||
ng-blur="$ctrl.onFocusOut()"
|
||||
translate-attr="{placeholder: 'Search'}"/>
|
||||
<vn-icon
|
||||
icon="clear"
|
||||
ng-click="$ctrl.onClearClick()"
|
||||
translate-attr="{title: 'Clear'}">
|
||||
</vn-icon>
|
||||
</div>
|
||||
<div class="list" tabindex="-1">
|
||||
<ul
|
||||
class="dropdown"
|
||||
ng-click="$ctrl.onContainerClick($event)">
|
||||
</ul>
|
||||
<div
|
||||
ng-if="$ctrl.statusText"
|
||||
ng-click="$ctrl.onLoadMoreClick($event)"
|
||||
class="status"
|
||||
translate>
|
||||
{{$ctrl.statusText}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</vn-popover>
|
||||
|
|
|
@ -4,45 +4,34 @@ import './style.scss';
|
|||
import './model';
|
||||
|
||||
export default class DropDown extends Component {
|
||||
constructor($element, $scope, $transclude, $timeout, $http, $translate) {
|
||||
constructor($element, $scope, $transclude, $timeout, $http) {
|
||||
super($element, $scope);
|
||||
this.$transclude = $transclude;
|
||||
this.$timeout = $timeout;
|
||||
this.$translate = $translate;
|
||||
|
||||
this.valueField = 'id';
|
||||
this.showField = 'name';
|
||||
this._search = undefined;
|
||||
this._shown = false;
|
||||
this._activeOption = -1;
|
||||
this.showLoadMore = true;
|
||||
this.showFilter = true;
|
||||
|
||||
this.input = this.element.querySelector('.search');
|
||||
this.body = this.element.querySelector('.body');
|
||||
this.container = this.element.querySelector('ul');
|
||||
this.list = this.element.querySelector('.list');
|
||||
|
||||
this.docKeyDownHandler = e => this.onDocKeyDown(e);
|
||||
this.docFocusInHandler = e => this.onDocFocusIn(e);
|
||||
}
|
||||
|
||||
this.element.addEventListener('mousedown',
|
||||
e => this.onBackgroundMouseDown(e));
|
||||
this.element.addEventListener('focusin',
|
||||
e => this.onFocusIn(e));
|
||||
this.list.addEventListener('scroll',
|
||||
e => this.onScroll(e));
|
||||
$postLink() {
|
||||
this.input = this.element.querySelector('.search');
|
||||
this.ul = this.element.querySelector('ul');
|
||||
this.list = this.element.querySelector('.list');
|
||||
this.list.addEventListener('scroll', e => this.onScroll(e));
|
||||
}
|
||||
|
||||
get shown() {
|
||||
return this._shown;
|
||||
return this.$.popover.shown;
|
||||
}
|
||||
|
||||
set shown(value) {
|
||||
if (value)
|
||||
this.show();
|
||||
else
|
||||
this.hide();
|
||||
this.$.popover.shown = value;
|
||||
}
|
||||
|
||||
get search() {
|
||||
|
@ -97,62 +86,18 @@ export default class DropDown extends Component {
|
|||
* @param {String} search The initial search term or %null
|
||||
*/
|
||||
show(search) {
|
||||
if (this._shown) return;
|
||||
this._shown = true;
|
||||
this._activeOption = -1;
|
||||
this.search = search;
|
||||
this._activeOption = -1;
|
||||
this.buildList();
|
||||
this.element.style.display = 'block';
|
||||
this.list.scrollTop = 0;
|
||||
this.$timeout(() => this.$element.addClass('shown'), 40);
|
||||
this.document.addEventListener('keydown', this.docKeyDownHandler);
|
||||
this.document.addEventListener('focusin', this.docFocusInHandler);
|
||||
this.relocate();
|
||||
this.input.focus();
|
||||
this.$.popover.parent = this.parent;
|
||||
this.$.popover.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the drop-down.
|
||||
*/
|
||||
hide() {
|
||||
if (!this._shown) return;
|
||||
this._shown = false;
|
||||
this.element.style.display = '';
|
||||
this.$element.removeClass('shown');
|
||||
this.document.removeEventListener('keydown', this.docKeyDownHandler);
|
||||
this.document.removeEventListener('focusin', this.docFocusInHandler);
|
||||
if (this.parent)
|
||||
this.parent.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositions the drop-down to a correct location relative to the parent.
|
||||
*/
|
||||
relocate() {
|
||||
if (!this.parent) return;
|
||||
|
||||
let style = this.body.style;
|
||||
style.width = '';
|
||||
style.height = '';
|
||||
|
||||
let parentRect = this.parent.getBoundingClientRect();
|
||||
let bodyRect = this.body.getBoundingClientRect();
|
||||
|
||||
let top = parentRect.top + parentRect.height;
|
||||
let height = bodyRect.height;
|
||||
let width = Math.max(bodyRect.width, parentRect.width);
|
||||
|
||||
let margin = 10;
|
||||
|
||||
if (top + height + margin > window.innerHeight)
|
||||
top = Math.max(parentRect.top - height, margin);
|
||||
|
||||
style.top = `${top}px`;
|
||||
style.left = `${parentRect.left}px`;
|
||||
style.width = `${width}px`;
|
||||
|
||||
if (height + margin * 2 > window.innerHeight)
|
||||
style.height = `${window.innerHeight - margin * 2}px`;
|
||||
this.$.popover.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -190,7 +135,7 @@ export default class DropDown extends Component {
|
|||
let data = this.$.model.data;
|
||||
|
||||
if (option >= 0 && data && option < data.length) {
|
||||
this.activeLi = this.container.children[option];
|
||||
this.activeLi = this.ul.children[option];
|
||||
this.activeLi.className = 'active';
|
||||
}
|
||||
}
|
||||
|
@ -224,7 +169,7 @@ export default class DropDown extends Component {
|
|||
}
|
||||
|
||||
if (!this.multiple)
|
||||
this.hide();
|
||||
this.$.popover.hide();
|
||||
}
|
||||
|
||||
refreshModel() {
|
||||
|
@ -260,22 +205,14 @@ export default class DropDown extends Component {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
onMouseDown(event) {
|
||||
this.lastMouseEvent = event;
|
||||
onOpen() {
|
||||
this.document.addEventListener('keydown', this.docKeyDownHandler);
|
||||
this.list.scrollTop = 0;
|
||||
this.input.focus();
|
||||
}
|
||||
|
||||
onBackgroundMouseDown(event) {
|
||||
if (event != this.lastMouseEvent)
|
||||
this.hide();
|
||||
}
|
||||
|
||||
onFocusIn(event) {
|
||||
this.lastFocusEvent = event;
|
||||
}
|
||||
|
||||
onDocFocusIn(event) {
|
||||
if (event !== this.lastFocusEvent)
|
||||
this.hide();
|
||||
onClose() {
|
||||
this.document.removeEventListener('keydown', this.docKeyDownHandler);
|
||||
}
|
||||
|
||||
onClearClick() {
|
||||
|
@ -299,7 +236,7 @@ export default class DropDown extends Component {
|
|||
|
||||
onContainerClick(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
let index = getPosition(this.container, event);
|
||||
let index = getPosition(this.ul, event);
|
||||
if (index != -1) this.selectOption(index);
|
||||
}
|
||||
|
||||
|
@ -318,9 +255,6 @@ export default class DropDown extends Component {
|
|||
case 13: // Enter
|
||||
this.selectOption(option);
|
||||
break;
|
||||
case 27: // Escape
|
||||
this.hide();
|
||||
break;
|
||||
case 38: // Up
|
||||
this.moveToOption(option <= 0 ? nOpts : option - 1);
|
||||
break;
|
||||
|
@ -342,8 +276,7 @@ export default class DropDown extends Component {
|
|||
}
|
||||
|
||||
buildList() {
|
||||
this.destroyScopes();
|
||||
this.scopes = [];
|
||||
this.destroyList();
|
||||
|
||||
let hasTemplate = this.$transclude &&
|
||||
this.$transclude.isSlotFilled('tplItem');
|
||||
|
@ -377,23 +310,26 @@ export default class DropDown extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
this.container.innerHTML = '';
|
||||
this.container.appendChild(fragment);
|
||||
this.ul.appendChild(fragment);
|
||||
this.activateOption(this._activeOption);
|
||||
this.relocate();
|
||||
this.$.popover.relocate();
|
||||
}
|
||||
|
||||
destroyScopes() {
|
||||
destroyList() {
|
||||
this.ul.innerHTML = '';
|
||||
|
||||
if (this.scopes)
|
||||
for (let scope of this.scopes)
|
||||
scope.$destroy();
|
||||
|
||||
this.scopes = [];
|
||||
}
|
||||
|
||||
$onDestroy() {
|
||||
this.destroyScopes();
|
||||
this.destroyList();
|
||||
}
|
||||
}
|
||||
DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$http', '$translate'];
|
||||
DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$http'];
|
||||
|
||||
/**
|
||||
* Gets the position of an event element relative to a parent.
|
||||
|
|
|
@ -7,6 +7,7 @@ describe('Component vnDropDown', () => {
|
|||
let $element;
|
||||
let $scope;
|
||||
let $httpBackend;
|
||||
let $transitions;
|
||||
let $q;
|
||||
let $filter;
|
||||
let controller;
|
||||
|
@ -15,18 +16,23 @@ describe('Component vnDropDown', () => {
|
|||
angular.mock.module('client');
|
||||
});
|
||||
|
||||
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$timeout_, _$httpBackend_, _$q_, _$filter_) => {
|
||||
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$timeout_, _$httpBackend_, _$q_, _$filter_, _$transitions_) => {
|
||||
$componentController = _$componentController_;
|
||||
$element = angular.element(`<div>${template}</div>`);
|
||||
$timeout = _$timeout_;
|
||||
$transitions = _$transitions_;
|
||||
$q = _$q_;
|
||||
$filter = _$filter_;
|
||||
$scope = $rootScope.$new();
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
|
||||
|
||||
let popoverTemplate = require('../popover/popover.html');
|
||||
let $popover = angular.element(`<div>${popoverTemplate}</div>`);
|
||||
$scope.popover = $componentController('vnPopover', {$element: $popover, $scope, $timeout, $transitions});
|
||||
$scope.model = $componentController('vnModel', {$httpBackend, $q, $filter});
|
||||
controller = $componentController('vnDropDown', {$element, $scope, $transclude: null, $timeout, $httpBackend, $translate: null});
|
||||
controller.$postLink();
|
||||
controller.parent = angular.element('<vn-parent></vn-parent>')[0];
|
||||
}));
|
||||
|
||||
|
|
|
@ -1,30 +1,7 @@
|
|||
vn-drop-down {
|
||||
z-index: 10;
|
||||
position: fixed;
|
||||
display: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
&.shown {
|
||||
& > .body {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
& > .body {
|
||||
position: fixed;
|
||||
box-shadow: 0 .1em .4em rgba(1, 1, 1, .4);
|
||||
border-radius: .1em;
|
||||
background-color: white;
|
||||
.dropdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translateY(-.4em);
|
||||
opacity: 0;
|
||||
transition-property: opacity, transform;
|
||||
transition-duration: 250ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
|
||||
& > .filter {
|
||||
position: relative;
|
||||
|
@ -35,6 +12,7 @@ vn-drop-down {
|
|||
box-sizing: border-box;
|
||||
border: none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: inherit;
|
||||
padding: .6em;
|
||||
}
|
||||
& > vn-icon[icon=clear] {
|
||||
|
@ -64,10 +42,9 @@ vn-drop-down {
|
|||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
li, .status {
|
||||
outline: none;
|
||||
list-style-type: none;
|
||||
padding: .6em;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
vn-icon{
|
||||
cursor: pointer;
|
||||
}
|
||||
&:focus, &:active, &:hover{
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
.primaryCheckbox {
|
||||
vn-icon{
|
||||
font-size: 22px;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<div class="popover">
|
||||
<div class="arrow"></div>
|
||||
<div class="content" ng-transclude></div>
|
||||
</div>
|
|
@ -1,205 +1,220 @@
|
|||
import ngModule from '../../module';
|
||||
import './style.css';
|
||||
import Component from '../../lib/component';
|
||||
import './style.scss';
|
||||
|
||||
directive.$inject = ['vnPopover'];
|
||||
export function directive(vnPopover) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.on('click', function(event) {
|
||||
vnPopover.showComponent($attrs.vnDialog, $scope, $element);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
ngModule.directive('vnPopover', directive);
|
||||
|
||||
export class Popover {
|
||||
constructor($document, $compile, $transitions) {
|
||||
this.document = $document[0];
|
||||
this.$compile = $compile;
|
||||
/**
|
||||
* A simple popover.
|
||||
*/
|
||||
export default class Popover extends Component {
|
||||
constructor($element, $scope, $timeout, $transitions) {
|
||||
super($element, $scope);
|
||||
this.$timeout = $timeout;
|
||||
this.$transitions = $transitions;
|
||||
this.removeScope = false;
|
||||
this.popOpens = 0;
|
||||
}
|
||||
_init() {
|
||||
this.docMouseDownHandler = e => this.onDocMouseDown(e);
|
||||
this.document.addEventListener('mousedown', this.docMouseDownHandler);
|
||||
this._shown = false;
|
||||
|
||||
this.docKeyDownHandler = e => this.onDocKeyDown(e);
|
||||
this.docFocusInHandler = e => this.onDocFocusIn(e);
|
||||
|
||||
this.element.addEventListener('mousedown',
|
||||
e => this.onBackgroundMouseDown(e));
|
||||
this.element.addEventListener('focusin',
|
||||
e => this.onFocusIn(e));
|
||||
|
||||
this.popover = this.element.querySelector('.popover');
|
||||
this.popover.addEventListener('mousedown',
|
||||
e => this.onMouseDown(e));
|
||||
|
||||
this.arrow = this.element.querySelector('.arrow');
|
||||
this.content = this.element.querySelector('.content');
|
||||
}
|
||||
|
||||
set child(value) {
|
||||
this.content.appendChild(value);
|
||||
}
|
||||
|
||||
get child() {
|
||||
return this.content.firstChild;
|
||||
}
|
||||
|
||||
get shown() {
|
||||
return this._shown;
|
||||
}
|
||||
|
||||
set shown(value) {
|
||||
if (value)
|
||||
this.show();
|
||||
else
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popover. If a parent is specified it is shown in a visible
|
||||
* relative position to it.
|
||||
*/
|
||||
show() {
|
||||
if (this._shown) return;
|
||||
|
||||
this._shown = true;
|
||||
this.element.style.display = 'block';
|
||||
this.$timeout.cancel(this.showTimeout);
|
||||
this.showTimeout = this.$timeout(() => {
|
||||
this.$element.addClass('shown');
|
||||
this.showTimeout = null;
|
||||
}, 30);
|
||||
|
||||
this.document.addEventListener('keydown', this.docKeyDownHandler);
|
||||
this.deregisterCallback = this.$transitions.onStart({},
|
||||
() => this.hideAll());
|
||||
this.document.addEventListener('focusin', this.docFocusInHandler);
|
||||
|
||||
this.deregisterCallback = this.$transitions.onStart({}, () => this.hide());
|
||||
this.relocate();
|
||||
|
||||
if (this.onOpen)
|
||||
this.onOpen();
|
||||
}
|
||||
_destroy() {
|
||||
this.document.removeEventListener('mousedown', this.docMouseDownHandler);
|
||||
|
||||
/**
|
||||
* Hides the popover.
|
||||
*/
|
||||
hide() {
|
||||
if (!this._shown) return;
|
||||
|
||||
this._shown = false;
|
||||
this.$element.removeClass('shown');
|
||||
this.$timeout.cancel(this.showTimeout);
|
||||
this.showTimeout = this.$timeout(() => {
|
||||
this.element.style.display = 'none';
|
||||
this.showTimeout = null;
|
||||
}, 250);
|
||||
|
||||
this.document.removeEventListener('keydown', this.docKeyDownHandler);
|
||||
this.docMouseDownHandler = null;
|
||||
this.docKeyDownHandler = null;
|
||||
this.deregisterCallback();
|
||||
}
|
||||
show(childElement, parent, popoverId) {
|
||||
this.childElement = childElement;
|
||||
let popover = this.document.createElement('div');
|
||||
this.popOpens++;
|
||||
this.document.removeEventListener('focusin', this.docFocusInHandler);
|
||||
|
||||
if (!popoverId) {
|
||||
popoverId = 'popover-' + this.popOpens;
|
||||
popover.id = popoverId;
|
||||
}
|
||||
if (this.deregisterCallback)
|
||||
this.deregisterCallback();
|
||||
|
||||
popover.className = 'vn-popover';
|
||||
popover.addEventListener('mousedown',
|
||||
e => this.onPopoverMouseDown(e));
|
||||
popover.appendChild(childElement);
|
||||
this.popover = popover;
|
||||
if (this.parent)
|
||||
this.parent.focus();
|
||||
|
||||
let style = popover.style;
|
||||
|
||||
let spacing = 0;
|
||||
let screenMargin = 20;
|
||||
let dblMargin = screenMargin * 2;
|
||||
|
||||
let width = popover.offsetWidth;
|
||||
let height = popover.offsetHeight;
|
||||
let innerWidth = window.innerWidth;
|
||||
let innerHeight = window.innerHeight;
|
||||
|
||||
if (width + dblMargin > innerWidth) {
|
||||
width = innerWidth - dblMargin;
|
||||
style.width = width + 'px';
|
||||
}
|
||||
|
||||
if (height + dblMargin > innerHeight) {
|
||||
height = innerHeight - dblMargin;
|
||||
style.height = height + 'px';
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
let parentNode = parent;
|
||||
let rect = parentNode.getBoundingClientRect();
|
||||
let left = rect.left;
|
||||
let top = rect.top + spacing + parentNode.offsetHeight;
|
||||
|
||||
if (left + width > innerWidth)
|
||||
left -= (left + width) - innerWidth + margin;
|
||||
|
||||
if (top + height > innerHeight)
|
||||
top -= height + parentNode.offsetHeight + spacing * 2;
|
||||
|
||||
if (left < 0)
|
||||
left = screenMargin;
|
||||
|
||||
if (top < 0)
|
||||
top = screenMargin;
|
||||
|
||||
style.top = (top) + 'px';
|
||||
style.left = (left) + 'px';
|
||||
style.minWidth = (rect.width) + 'px';
|
||||
}
|
||||
|
||||
this.document.body.appendChild(popover);
|
||||
|
||||
if (this.popOpens === 1) {
|
||||
this._init();
|
||||
}
|
||||
return popoverId;
|
||||
if (this.onClose)
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
showComponent(childComponent, $scope, parent) {
|
||||
let childElement = this.document.createElement(childComponent);
|
||||
let id = 'popover-' + this.popOpens;
|
||||
childElement.id = id;
|
||||
this.removeScope = true;
|
||||
this.$compile(childElement)($scope.$new());
|
||||
this.show(childElement, parent, id);
|
||||
return childElement;
|
||||
}
|
||||
/**
|
||||
* Repositions the popover to a correct location relative to the parent.
|
||||
*/
|
||||
relocate() {
|
||||
if (!(this.parent && this._shown)) return;
|
||||
|
||||
_checkOpens() {
|
||||
this.popOpens = this.document.querySelectorAll('*[id^="popover-"]').length;
|
||||
if (this.popOpens === 0) {
|
||||
this._destroy();
|
||||
}
|
||||
}
|
||||
let style = this.popover.style;
|
||||
style.width = '';
|
||||
style.height = '';
|
||||
|
||||
_removeElement(val) {
|
||||
if (!val) return;
|
||||
let element = angular.element(val);
|
||||
let parent = val.parentNode;
|
||||
if (element.scope() && element.scope().$id > 1) {
|
||||
element.scope().$destroy();
|
||||
}
|
||||
element.remove();
|
||||
if (parent.className.indexOf('vn-popover') !== -1)
|
||||
this._removeElement(parent);
|
||||
}
|
||||
let arrowStyle = this.arrow.style;
|
||||
arrowStyle.top = '';
|
||||
arrowStyle.bottom = '';
|
||||
|
||||
hide(id) {
|
||||
let popover = this.document.querySelector(`#${id}`);
|
||||
if (popover) {
|
||||
this._removeElement(popover);
|
||||
}
|
||||
this._checkOpens();
|
||||
}
|
||||
let parentRect = this.parent.getBoundingClientRect();
|
||||
let popoverRect = this.popover.getBoundingClientRect();
|
||||
let arrowRect = this.arrow.getBoundingClientRect();
|
||||
|
||||
hideChilds(id) {
|
||||
let popovers = this.document.querySelectorAll('*[id^="popover-"]');
|
||||
let idNumber = parseInt(id.split('-')[1], 10);
|
||||
popovers.forEach(
|
||||
val => {
|
||||
if (parseInt(val.id.split('-')[1], 10) > idNumber)
|
||||
this._removeElement(val);
|
||||
}
|
||||
);
|
||||
this._checkOpens();
|
||||
}
|
||||
let arrowHeight = Math.sqrt(Math.pow(arrowRect.height, 2) * 2) / 2;
|
||||
|
||||
hideAll() {
|
||||
let popovers = this.document.querySelectorAll('*[id^="popover-"]');
|
||||
popovers.forEach(
|
||||
val => {
|
||||
this._removeElement(val);
|
||||
}
|
||||
);
|
||||
this._checkOpens();
|
||||
}
|
||||
let top = parentRect.top + parentRect.height + arrowHeight;
|
||||
let left = parentRect.left;
|
||||
let height = popoverRect.height;
|
||||
let width = Math.max(popoverRect.width, parentRect.width);
|
||||
|
||||
_findPopOver(node) {
|
||||
while (node != null) {
|
||||
if (node.id && node.id.startsWith('popover-')) {
|
||||
return node.id;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
let margin = 10;
|
||||
let showTop = top + height + margin > window.innerHeight;
|
||||
|
||||
onDocMouseDown(event) {
|
||||
let targetId = this._findPopOver(event.target);
|
||||
if (targetId) {
|
||||
this.hideChilds(targetId);
|
||||
} else {
|
||||
this.hideAll();
|
||||
}
|
||||
if (showTop)
|
||||
top = Math.max(parentRect.top - height - arrowHeight, margin);
|
||||
if (left + width + margin > window.innerWidth)
|
||||
left = window.innerWidth - width - margin;
|
||||
|
||||
if (showTop)
|
||||
arrowStyle.bottom = `0`;
|
||||
else
|
||||
arrowStyle.top = `0`;
|
||||
|
||||
arrowStyle.left = `${(parentRect.left - left) + parentRect.width / 2}px`;
|
||||
|
||||
style.top = `${top}px`;
|
||||
style.left = `${left}px`;
|
||||
style.width = `${width}px`;
|
||||
|
||||
if (height + margin * 2 + arrowHeight > window.innerHeight)
|
||||
style.height = `${window.innerHeight - margin * 2 - arrowHeight}px`;
|
||||
}
|
||||
|
||||
onDocKeyDown(event) {
|
||||
if (event.keyCode === 27) {
|
||||
let targetId = this._findPopOver(this.lastTarget);
|
||||
if (targetId) {
|
||||
this.hideChilds(targetId);
|
||||
} else {
|
||||
this.hideAll();
|
||||
}
|
||||
this.lastTarget = null;
|
||||
if (event.defaultPrevented) return;
|
||||
|
||||
if (event.keyCode == 27) { // Esc
|
||||
event.preventDefault();
|
||||
this.hide();
|
||||
this.$.$applyAsync();
|
||||
}
|
||||
}
|
||||
|
||||
onPopoverMouseDown(event) {
|
||||
this.lastTarget = event.target;
|
||||
onMouseDown(event) {
|
||||
this.lastMouseEvent = event;
|
||||
}
|
||||
|
||||
onBackgroundMouseDown(event) {
|
||||
if (event != this.lastMouseEvent)
|
||||
this.hide();
|
||||
}
|
||||
|
||||
onFocusIn(event) {
|
||||
this.lastFocusEvent = event;
|
||||
}
|
||||
|
||||
onDocFocusIn(event) {
|
||||
if (event !== this.lastFocusEvent)
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
Popover.$inject = ['$document', '$compile', '$transitions'];
|
||||
Popover.$inject = ['$element', '$scope', '$timeout', '$transitions'];
|
||||
|
||||
ngModule.service('vnPopover', Popover);
|
||||
ngModule.component('vnPopover', {
|
||||
template: require('./popover.html'),
|
||||
controller: Popover,
|
||||
transclude: true,
|
||||
bindings: {
|
||||
onOpen: '&?',
|
||||
onClose: '&?'
|
||||
}
|
||||
});
|
||||
|
||||
class PopoverService {
|
||||
constructor($document, $compile, $transitions, $rootScope) {
|
||||
this.$compile = $compile;
|
||||
this.$rootScope = $rootScope;
|
||||
this.$document = $document;
|
||||
this.stack = [];
|
||||
}
|
||||
show(child, parent, $scope) {
|
||||
let element = this.$compile('<vn-popover/>')($scope || this.$rootScope)[0];
|
||||
let popover = element.$ctrl;
|
||||
popover.parent = parent;
|
||||
popover.child = child;
|
||||
popover.show();
|
||||
popover.onClose = () => {
|
||||
this.$document[0].body.removeChild(element);
|
||||
if ($scope) $scope.$destroy();
|
||||
};
|
||||
this.$document[0].body.appendChild(element);
|
||||
return popover;
|
||||
}
|
||||
|
||||
showComponent(componentTag, $scope, parent) {
|
||||
let $newScope = $scope.$new();
|
||||
let childElement = this.$compile(`<${componentTag}/>`)($newScope)[0];
|
||||
this.show(childElement, parent, $newScope);
|
||||
return childElement;
|
||||
}
|
||||
}
|
||||
PopoverService.$inject = ['$document', '$compile', '$transitions', '$rootScope'];
|
||||
|
||||
ngModule.service('vnPopover', PopoverService);
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
.vn-popover {
|
||||
position: fixed;
|
||||
box-shadow: 0 0 .4em rgba(1,1,1,.4);
|
||||
background-color: white;
|
||||
z-index: 100;
|
||||
border-radius: .1em;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
vn-popover {
|
||||
display: none;
|
||||
z-index: 10;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
opacity: 0;
|
||||
transform: translateY(-.6em);
|
||||
transition-property: opacity, transform;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
|
||||
&.shown {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
& > .popover {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
box-shadow: 0 .1em .4em rgba(1, 1, 1, .4);
|
||||
|
||||
& > .arrow {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin: -.5em;
|
||||
background-color: white;
|
||||
box-shadow: 0 .1em .4em rgba(1, 1, 1, .4);
|
||||
position: absolute;
|
||||
transform: rotate(45deg);
|
||||
z-index: 0;
|
||||
}
|
||||
& > .content {
|
||||
width: 100%;
|
||||
border-radius: .1em;
|
||||
overflow: auto;
|
||||
background-color: white;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
vn-textfield {
|
||||
div {
|
||||
outline: none; //remove chrome outline
|
||||
}
|
||||
.mdl-chip__action {
|
||||
position: absolute;
|
||||
width: auto;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import './id';
|
||||
import './focus';
|
||||
import './dialog';
|
||||
import './popover';
|
||||
import './validation';
|
||||
import './acl';
|
||||
import './on-error-src';
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import ngModule from '../module';
|
||||
import Popover from '../components/popover/popover';
|
||||
import {kebabToCamel} from '../lib/string';
|
||||
|
||||
/**
|
||||
* Directive used to open a popover.
|
||||
*
|
||||
* @return {Object} The directive
|
||||
*/
|
||||
export function directive() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.on('click', function(event) {
|
||||
let popoverKey = kebabToCamel($attrs.vnPopover);
|
||||
let popover = $scope[popoverKey];
|
||||
if (popover instanceof Popover) {
|
||||
popover.parent = $element[0];
|
||||
popover.show();
|
||||
}
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
ngModule.directive('vnPopover', directive);
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"module": "item",
|
||||
"name": "Items",
|
||||
"icon": "/static/images/icon_item.png",
|
||||
"icon": "inbox",
|
||||
"validations" : true,
|
||||
"routes": [
|
||||
{
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<vn-float-button
|
||||
icon="edit"
|
||||
style="position: absolute; margin: 1em; bottom: 0; right: 0;"
|
||||
vn-visible-by="administrative">
|
||||
vn-visible-by="administrative, salesAssistant">
|
||||
</vn-float-button>
|
||||
</a>
|
||||
</vn-auto>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"module": "production",
|
||||
"name": "Production",
|
||||
"icon": "/static/images/icon_production.png",
|
||||
"icon": "local_florist",
|
||||
"validations" : false,
|
||||
"routes": [
|
||||
{
|
||||
|
|
|
@ -6,29 +6,33 @@
|
|||
<vn-icon
|
||||
id="apps"
|
||||
icon="apps"
|
||||
vn-popover="apps-menu"
|
||||
translate-attr="{title: 'Applications'}">
|
||||
</vn-icon>
|
||||
<ul id="apps-menu" for="apps" class="mdl-menu mdl-js-menu mdl-menu--bottom-right" pad-small>
|
||||
<li ng-repeat="mod in ::$ctrl.modules" class="mdl-menu__item" ui-sref="{{::mod.route.state}}">
|
||||
<vn-icon ng-if="::mod.icon && !mod.icon.startsWith('/')" icon="{{::mod.icon}}"></vn-icon>
|
||||
<img ng-if="::mod.icon && mod.icon.startsWith('/')" ng-src="{{::mod.icon}}" />
|
||||
<span translate="{{::mod.name}}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<vn-popover vn-id="apps-menu" ng-click="appsMenu.hide()">
|
||||
<ul id="apps-menu" pad-small>
|
||||
<li ng-repeat="mod in ::$ctrl.modules" ui-sref="{{::mod.route.state}}">
|
||||
<vn-icon icon="{{::mod.icon}}"></vn-icon>
|
||||
<span translate>{{::mod.name}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</vn-popover>
|
||||
<vn-icon
|
||||
id="lang"
|
||||
icon="language"
|
||||
vn-popover="langs-menu"
|
||||
translate-attr="{title: 'Change language'}">
|
||||
</vn-icon>
|
||||
<ul id="langs-menu" for="lang" class="mdl-menu mdl-js-menu mdl-menu--bottom-right" pad-small>
|
||||
<li
|
||||
ng-repeat="lang in ::$ctrl.langs"
|
||||
name="{{::lang}}"
|
||||
class="mdl-menu__item"
|
||||
ng-click="$ctrl.onChangeLangClick(lang)">
|
||||
<span>{{::lang}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<vn-popover vn-id="langs-menu">
|
||||
<ul id="langs-menu" pad-small>
|
||||
<li
|
||||
ng-repeat="lang in ::$ctrl.langs"
|
||||
name="{{::lang}}"
|
||||
ng-click="$ctrl.onChangeLangClick(lang)">
|
||||
<span>{{::lang}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</vn-popover>
|
||||
<vn-icon
|
||||
id="logout"
|
||||
icon="exit_to_app"
|
||||
|
|
|
@ -14,26 +14,30 @@ vn-main-menu {
|
|||
color: #FF9300;
|
||||
}
|
||||
}
|
||||
li.mdl-menu__item {
|
||||
background-color: #FF9300;
|
||||
margin-bottom: 8px;
|
||||
vn-popover ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
color: white;
|
||||
img {
|
||||
max-width: 18px;
|
||||
vertical-align: middle;
|
||||
margin-top: -3px;
|
||||
|
||||
li {
|
||||
background-color: #FF9300;
|
||||
margin-bottom: .6em;
|
||||
cursor: pointer;
|
||||
padding: .8em;
|
||||
border-radius: .1em;
|
||||
min-width: 4em;
|
||||
|
||||
& > vn-icon {
|
||||
padding-right: .3em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #FF9300;
|
||||
opacity: 0.7 !important;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
i {
|
||||
float: left;
|
||||
padding-top: 13px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
li.mdl-menu__item:hover {
|
||||
background-color: #FF9300;
|
||||
opacity: 0.7 !important;
|
||||
}
|
||||
li.mdl-menu__item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,21 @@
|
|||
@import "colors";
|
||||
@import "border";
|
||||
|
||||
|
||||
a:focus,
|
||||
input:focus,
|
||||
button:focus
|
||||
{
|
||||
outline: none;
|
||||
}
|
||||
button::-moz-focus-inner,
|
||||
input[type=submit]::-moz-focus-inner,
|
||||
input[type=button]::-moz-focus-inner,
|
||||
input[type=reset]::-moz-focus-inner
|
||||
{
|
||||
border: none;
|
||||
}
|
||||
|
||||
.form {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
|
|
@ -8,7 +8,6 @@ export default {
|
|||
vnSubmit: 'vn-submit > input',
|
||||
vnTopbar: 'vn-topbar > header',
|
||||
vnIcon: 'vn-icon',
|
||||
vnMainMenu: 'vn-main-menu > div',
|
||||
vnModuleContainer: 'vn-module-container > a',
|
||||
vnSearchBar: 'vn-searchbar > form > vn-horizontal',
|
||||
vnFloatButton: 'vn-float-button > button',
|
||||
|
|
|
@ -6,8 +6,8 @@ export default {
|
|||
globalItems: {
|
||||
logOutButton: `#logout`,
|
||||
applicationsMenuButton: `#apps`,
|
||||
applicationsMenuVisible: `${components.vnMainMenu} .is-visible > div`,
|
||||
clientsButton: `${components.vnMainMenu} > div > ul > li:nth-child(1)`
|
||||
applicationsMenuVisible: `vn-main-menu [vn-id="apps-menu"] ul`,
|
||||
clientsButton: `vn-main-menu [vn-id="apps-menu"] ul > li:nth-child(1)`
|
||||
},
|
||||
moduleAccessView: {
|
||||
clientsSectionButton: `${components.vnModuleContainer}[ui-sref="clients"]`,
|
||||
|
|
|
@ -280,4 +280,193 @@ describe('lock verified data path', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as salesAssistant', () => {
|
||||
beforeAll(() => {
|
||||
return nightmare
|
||||
.waitToClick(selectors.globalItems.logOutButton)
|
||||
.waitForLogin('salesAssistant');
|
||||
});
|
||||
|
||||
it('should navigate to clients index', () => {
|
||||
return nightmare
|
||||
.waitToClick(selectors.globalItems.applicationsMenuButton)
|
||||
.wait(selectors.globalItems.applicationsMenuVisible)
|
||||
.waitToClick(selectors.globalItems.clientsButton)
|
||||
.wait(selectors.clientsIndex.createClientButton)
|
||||
.parsedUrl()
|
||||
.then(url => {
|
||||
expect(url.hash).toEqual('#!/clients');
|
||||
});
|
||||
});
|
||||
|
||||
it('should search again for the user Petter Parker', () => {
|
||||
return nightmare
|
||||
.wait(selectors.clientsIndex.searchResult)
|
||||
.type(selectors.clientsIndex.searchClientInput, 'Petter Parker')
|
||||
.click(selectors.clientsIndex.searchButton)
|
||||
.waitForNumberOfElements(selectors.clientsIndex.searchResult, 1)
|
||||
.countSearchResults(selectors.clientsIndex.searchResult)
|
||||
.then(result => {
|
||||
expect(result).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should click on the search result to access to the Petter Parkers fiscal data`, () => {
|
||||
return nightmare
|
||||
.waitForTextInElement(selectors.clientsIndex.searchResult, 'Petter Parker')
|
||||
.waitToClick(selectors.clientsIndex.searchResult)
|
||||
.waitToClick(selectors.clientFiscalData.fiscalDataButton)
|
||||
.waitForURL('fiscal-data')
|
||||
.parsedUrl()
|
||||
.then(url => {
|
||||
expect(url.hash).toContain('fiscal-data');
|
||||
});
|
||||
});
|
||||
|
||||
it(`should click on the fiscal data button`, () => {
|
||||
return nightmare
|
||||
.waitToClick(selectors.clientFiscalData.fiscalDataButton)
|
||||
.waitForURL('fiscal-data')
|
||||
.parsedUrl()
|
||||
.then(url => {
|
||||
expect(url.hash).toContain('fiscal-data');
|
||||
});
|
||||
});
|
||||
|
||||
it('should confirm verified data button is enabled for salesAssistant', () => {
|
||||
return nightmare
|
||||
.wait(selectors.clientFiscalData.verifiedDataCheckboxInput)
|
||||
.evaluate(selector => {
|
||||
return document.querySelector(selector).className;
|
||||
}, 'body > vn-app > vn-vertical > vn-vertical > vn-client-card > vn-main-block > vn-horizontal > vn-one > vn-vertical > vn-client-fiscal-data > form > vn-card > div > vn-horizontal:nth-child(5) > vn-check:nth-child(3) > label')
|
||||
.then(result => {
|
||||
expect(result).not.toContain('is-disabled');
|
||||
});
|
||||
});
|
||||
|
||||
it('should uncheck the Verified data checkbox', () => {
|
||||
return nightmare
|
||||
.waitToClick(selectors.clientFiscalData.verifiedDataCheckboxInput)
|
||||
.waitToClick(selectors.clientFiscalData.saveButton)
|
||||
.waitForSnackbar()
|
||||
.then(result => {
|
||||
expect(result).toEqual('Data saved!');
|
||||
});
|
||||
});
|
||||
|
||||
it('should confirm Verified data checkbox is unchecked', () => {
|
||||
return nightmare
|
||||
.waitToClick(selectors.clientBasicData.basicDataButton)
|
||||
.wait(selectors.clientBasicData.nameInput)
|
||||
.waitToClick(selectors.clientFiscalData.fiscalDataButton)
|
||||
.wait(selectors.clientFiscalData.verifiedDataCheckboxInput)
|
||||
.evaluate(selector => {
|
||||
return document.querySelector(selector).checked;
|
||||
}, selectors.clientFiscalData.verifiedDataCheckboxInput)
|
||||
.then(value => {
|
||||
expect(value).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should again edit the social name', () => {
|
||||
return nightmare
|
||||
.wait(selectors.clientFiscalData.socialNameInput)
|
||||
.clearInput(selectors.clientFiscalData.socialNameInput)
|
||||
.type(selectors.clientFiscalData.socialNameInput, 'salesAssistant was here')
|
||||
.click(selectors.clientFiscalData.saveButton)
|
||||
.waitForSnackbar()
|
||||
.then(result => {
|
||||
expect(result).toEqual('Data saved!');
|
||||
});
|
||||
});
|
||||
|
||||
it('should confirm the social name have been edited once and for all', () => {
|
||||
return nightmare
|
||||
.waitToClick(selectors.clientBasicData.basicDataButton)
|
||||
.wait(selectors.clientBasicData.nameInput)
|
||||
.waitToClick(selectors.clientFiscalData.fiscalDataButton)
|
||||
.wait(selectors.clientFiscalData.socialNameInput)
|
||||
.getInputValue(selectors.clientFiscalData.socialNameInput)
|
||||
.then(result => {
|
||||
expect(result).toEqual('salesAssistant was here');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as salesPerson third run', () => {
|
||||
beforeAll(() => {
|
||||
return nightmare
|
||||
.waitToClick(selectors.globalItems.logOutButton)
|
||||
.waitForLogin('salesPerson');
|
||||
});
|
||||
|
||||
it('should again click on the Clients button of the top bar menu', () => {
|
||||
return nightmare
|
||||
.waitToClick(selectors.globalItems.applicationsMenuButton)
|
||||
.wait(selectors.globalItems.applicationsMenuVisible)
|
||||
.waitToClick(selectors.globalItems.clientsButton)
|
||||
.wait(selectors.clientsIndex.createClientButton)
|
||||
.parsedUrl()
|
||||
.then(url => {
|
||||
expect(url.hash).toEqual('#!/clients');
|
||||
});
|
||||
});
|
||||
|
||||
it('should once again search for the user Petter Parker', () => {
|
||||
return nightmare
|
||||
.wait(selectors.clientsIndex.searchResult)
|
||||
.type(selectors.clientsIndex.searchClientInput, 'Petter Parker')
|
||||
.click(selectors.clientsIndex.searchButton)
|
||||
.waitForNumberOfElements(selectors.clientsIndex.searchResult, 1)
|
||||
.countSearchResults(selectors.clientsIndex.searchResult)
|
||||
.then(result => {
|
||||
expect(result).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should click on the search result to access to the client's fiscal data`, () => {
|
||||
return nightmare
|
||||
.waitForTextInElement(selectors.clientsIndex.searchResult, 'Petter Parker')
|
||||
.waitToClick(selectors.clientsIndex.searchResult)
|
||||
.waitToClick(selectors.clientFiscalData.fiscalDataButton)
|
||||
.waitForURL('fiscal-data')
|
||||
.parsedUrl()
|
||||
.then(url => {
|
||||
expect(url.hash).toContain('fiscal-data');
|
||||
});
|
||||
});
|
||||
|
||||
it(`should click on the fiscal data button to start editing`, () => {
|
||||
return nightmare
|
||||
.waitToClick(selectors.clientFiscalData.fiscalDataButton)
|
||||
.waitForURL('fiscal-data')
|
||||
.parsedUrl()
|
||||
.then(url => {
|
||||
expect(url.hash).toContain('fiscal-data');
|
||||
});
|
||||
});
|
||||
|
||||
it('should confirm verified data button is enabled once again', () => {
|
||||
return nightmare
|
||||
.wait(selectors.clientFiscalData.verifiedDataCheckboxInput)
|
||||
.evaluate(selector => {
|
||||
return document.querySelector(selector).className;
|
||||
}, 'body > vn-app > vn-vertical > vn-vertical > vn-client-card > vn-main-block > vn-horizontal > vn-one > vn-vertical > vn-client-fiscal-data > form > vn-card > div > vn-horizontal:nth-child(5) > vn-check:nth-child(3) > label')
|
||||
.then(result => {
|
||||
expect(result).toContain('is-disabled');
|
||||
});
|
||||
});
|
||||
|
||||
it('should confirm the form is enabled for salesPerson', () => {
|
||||
return nightmare
|
||||
.wait(selectors.clientFiscalData.socialNameInput)
|
||||
.evaluate(selector => {
|
||||
return document.querySelector(selector).className;
|
||||
}, 'vn-textfield[field="$ctrl.client.socialName"] > div')
|
||||
.then(result => {
|
||||
expect(result).not.toContain('is-disabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -63,11 +63,11 @@ gulp.task('services-only', async () => {
|
|||
/**
|
||||
* Runs the e2e tests, restoring the fixtures first.
|
||||
*/
|
||||
gulp.task('e2e', ['docker-rebuild'], async () => {
|
||||
gulp.task('e2e', ['docker'], async () => {
|
||||
await runSequenceP('e2e-only');
|
||||
});
|
||||
|
||||
gulp.task('smokes', ['docker-rebuild'], async () => {
|
||||
gulp.task('smokes', ['docker'], async () => {
|
||||
await runSequenceP('smokes-only');
|
||||
});
|
||||
|
||||
|
@ -416,7 +416,7 @@ gulp.task('docker', async () => {
|
|||
* Does the minium effort to start the docker, if it doesn't exists calls
|
||||
* the 'docker-run' task, if it is started does nothing. Keep in mind that when
|
||||
* you do not rebuild the docker you may be using an outdated version of it.
|
||||
* See the 'docker-rebuild' task for more info.
|
||||
* See the 'docker' task for more info.
|
||||
*/
|
||||
gulp.task('docker-start', async () => {
|
||||
let state;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
let md5 = require('md5');
|
||||
|
||||
module.exports = function(Self) {
|
||||
Self.remoteMethod('createWithUser', {
|
||||
description: 'Creates both client and its web account',
|
||||
|
@ -18,43 +16,33 @@ module.exports = function(Self) {
|
|||
}
|
||||
});
|
||||
|
||||
Self.createWithUser = (data, callback) => {
|
||||
Self.createWithUser = async data => {
|
||||
let firstEmail = data.email ? data.email.split(',')[0] : null;
|
||||
let user = {
|
||||
name: data.userName,
|
||||
email: firstEmail,
|
||||
password: md5(parseInt(Math.random() * 100000000000000))
|
||||
password: parseInt(Math.random() * 100000000000000)
|
||||
};
|
||||
let Account = Self.app.models.Account;
|
||||
|
||||
Account.beginTransaction({}, (error, transaction) => {
|
||||
if (error) return callback(error);
|
||||
let transaction = await Account.beginTransaction({});
|
||||
|
||||
Account.create(user, {transaction}, (error, account) => {
|
||||
if (error) {
|
||||
transaction.rollback();
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
let client = {
|
||||
name: data.name,
|
||||
fi: data.fi,
|
||||
socialName: data.socialName,
|
||||
id: account.id,
|
||||
email: data.email,
|
||||
salesPersonFk: data.salesPersonFk
|
||||
};
|
||||
|
||||
Self.create(client, {transaction}, (error, newClient) => {
|
||||
if (error) {
|
||||
transaction.rollback();
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
transaction.commit();
|
||||
callback(null, newClient);
|
||||
});
|
||||
});
|
||||
});
|
||||
try {
|
||||
let account = await Account.create(user, {transaction});
|
||||
let client = {
|
||||
id: account.id,
|
||||
name: data.name,
|
||||
fi: data.fi,
|
||||
socialName: data.socialName,
|
||||
email: data.email,
|
||||
salesPersonFk: data.salesPersonFk
|
||||
};
|
||||
newClient = await Self.create(client, {transaction});
|
||||
await transaction.commit();
|
||||
return newClient;
|
||||
} catch (e) {
|
||||
transaction.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -48,45 +48,39 @@ describe('Client Create', () => {
|
|||
.catch(catchErrors(done));
|
||||
});
|
||||
|
||||
it('should not be able to create a user if exists', done => {
|
||||
app.models.Client.findOne({where: {name: 'Charles Xavier'}})
|
||||
.then(client => {
|
||||
app.models.Account.findOne({where: {id: client.id}})
|
||||
.then(account => {
|
||||
let formerAccountData = {
|
||||
name: client.name,
|
||||
userName: account.name,
|
||||
email: client.email,
|
||||
fi: client.fi,
|
||||
socialName: client.socialName
|
||||
};
|
||||
it('should not be able to create a user if exists', async() => {
|
||||
let client = await app.models.Client.findOne({where: {name: 'Charles Xavier'}});
|
||||
let account = await app.models.Account.findOne({where: {id: client.id}});
|
||||
|
||||
app.models.Client.createWithUser(formerAccountData, (err, client) => {
|
||||
expect(err.details.codes.name[0]).toEqual('uniqueness');
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(catchErrors(done));
|
||||
let formerAccountData = {
|
||||
name: client.name,
|
||||
userName: account.name,
|
||||
email: client.email,
|
||||
fi: client.fi,
|
||||
socialName: client.socialName
|
||||
};
|
||||
|
||||
try {
|
||||
let client = await app.models.Client.createWithUser(formerAccountData);
|
||||
|
||||
expect(client).toBeNull();
|
||||
} catch (err) {
|
||||
expect(err.details.codes.name[0]).toEqual('uniqueness');
|
||||
}
|
||||
});
|
||||
|
||||
it('should create a new account', done => {
|
||||
app.models.Client.createWithUser(newAccountData, (error, client) => {
|
||||
if (error) return catchErrors(done)(error);
|
||||
app.models.Account.findOne({where: {name: newAccountData.userName}})
|
||||
.then(account => {
|
||||
expect(account.name).toEqual(newAccountData.userName);
|
||||
app.models.Client.findOne({where: {name: newAccountData.name}})
|
||||
.then(client => {
|
||||
expect(client.id).toEqual(account.id);
|
||||
expect(client.name).toEqual(newAccountData.name);
|
||||
expect(client.email).toEqual(newAccountData.email);
|
||||
expect(client.fi).toEqual(newAccountData.fi);
|
||||
expect(client.socialName).toEqual(newAccountData.socialName);
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(catchErrors(done));
|
||||
});
|
||||
it('should create a new account', async() => {
|
||||
let client = await app.models.Client.createWithUser(newAccountData);
|
||||
let account = await app.models.Account.findOne({where: {name: newAccountData.userName}});
|
||||
|
||||
expect(account.name).toEqual(newAccountData.userName);
|
||||
|
||||
client = await app.models.Client.findOne({where: {name: newAccountData.name}});
|
||||
|
||||
expect(client.id).toEqual(account.id);
|
||||
expect(client.name).toEqual(newAccountData.name);
|
||||
expect(client.email).toEqual(newAccountData.email);
|
||||
expect(client.fi).toEqual(newAccountData.fi);
|
||||
expect(client.socialName).toEqual(newAccountData.socialName);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,7 +35,7 @@ module.exports = function(Self) {
|
|||
message: 'Correo electrónico inválido',
|
||||
allowNull: true,
|
||||
allowBlank: true,
|
||||
with: /^[\w|-|.]+@[\w|-]+(\.[\w|-]+)*(,[\w|-|.]+@[\w|-]+(\.[\w|-]+)*)*$/
|
||||
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
|
||||
});
|
||||
Self.validatesLengthOf('postcode', {
|
||||
allowNull: true,
|
||||
|
|
Loading…
Reference in New Issue