diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index 667ab467e..81ad3a0dc 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -219,7 +219,7 @@ export default {
goBackToModuleIndexButton: 'vn-item-descriptor a[href="#!/item/index"]',
moreMenu: 'vn-item-descriptor vn-icon-menu > div > vn-icon',
moreMenuRegularizeButton: '.vn-popover.shown .vn-drop-down li[name="Regularize stock"]',
- regularizeQuantityInput: 'vn-item-descriptor > vn-dialog > div > form > div.body > tpl-body > div > vn-textfield > div > div > div.infix > input',
+ regularizeQuantityInput: 'vn-item-descriptor vn-dialog tpl-body > div > vn-textfield input',
regularizeWarehouseAutocomplete: 'vn-item-descriptor vn-dialog vn-autocomplete[ng-model="$ctrl.warehouseFk"]',
editButton: 'vn-item-card vn-item-descriptor vn-float-button[icon="edit"]',
regularizeSaveButton: 'vn-item-descriptor > vn-dialog > div > form > div.buttons > tpl-buttons > button',
@@ -232,7 +232,7 @@ export default {
typeAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.typeFk"]',
intrastatAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.intrastatFk"]',
nameInput: 'vn-textfield[label="Name"] input',
- relevancyInput: 'vn-input-number[ng-model="Relevancy"] input',
+ relevancyInput: 'vn-input-number[ng-model="$ctrl.item.relevancy"] input',
originAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]',
expenceAutocomplete: 'vn-autocomplete[ng-model="$ctrl.item.expenceFk"]',
longNameInput: 'vn-textfield[ng-model="$ctrl.item.longName"] input',
@@ -324,7 +324,7 @@ export default {
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button'
},
ticketsIndex: {
- openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append > vn-icon[icon="keyboard_arrow_down"]',
+ openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"] input',
newTicketButton: 'vn-ticket-index > a',
searchResult: 'vn-ticket-index vn-card > div > vn-table > div > vn-tbody > a.vn-tr',
@@ -332,7 +332,7 @@ export default {
searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)',
searchTicketInput: `vn-ticket-index ${components.vnTextfield}`,
searchWeeklyTicketInput: `vn-ticket-weekly-index ${components.vnTextfield}`,
- searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar i[class="material-icons clear"]',
+ searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon=clear]',
advancedSearchButton: 'vn-ticket-search-panel vn-submit[label="Search"] input',
searchButton: 'vn-ticket-index vn-searchbar vn-icon[icon="search"]',
searchWeeklyButton: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon="search"]',
@@ -390,7 +390,7 @@ export default {
ticketPackages: {
packagesButton: 'vn-left-menu a[ui-sref="ticket.card.package"]',
firstPackageAutocomplete: 'vn-autocomplete[label="Package"]',
- firstQuantityInput: 'vn-input-number[ng-model="Quantity"] input',
+ firstQuantityInput: 'vn-input-number[ng-model="package.quantity"] input',
firstRemovePackageButton: 'vn-icon-button[vn-tooltip="Remove package"]',
addPackageButton: 'vn-icon-button[vn-tooltip="Add package"]',
clearPackageAutocompleteButton: 'vn-autocomplete[label="Package"] .icons > vn-icon[icon=clear]',
@@ -477,20 +477,18 @@ export default {
chargesReasonAutocomplete: 'vn-autocomplete[ng-model="$ctrl.ticket.option"]',
},
ticketComponents: {
- base: 'vn-ticket-components tfoot > tr:nth-child(1) > td',
- margin: 'vn-ticket-components tfoot > tr:nth-child(2) > td',
- total: 'vn-ticket-components tfoot > tr:nth-child(3) > td'
+ base: 'vn-ticket-components [name="base-sum"]'
},
ticketRequests: {
addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button',
- request: 'vn-ticket-request-index > form > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr',
- descriptionInput: 'vn-ticket-request-create > form > div > vn-card > div > vn-horizontal:nth-child(1) > vn-textfield > div > div > div.infix > input',
+ request: 'vn-ticket-request-index vn-table vn-tr',
+ descriptionInput: 'vn-ticket-request-create > form > div > vn-card > div > vn-horizontal:nth-child(1) > vn-textfield input',
atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.atenderFk"]',
quantityInput: 'vn-ticket-request-create vn-input-number input[name=quantity]',
priceInput: 'vn-ticket-request-create vn-input-number input[name=price]',
firstRemoveRequestButton: 'vn-ticket-request-index vn-icon[icon="delete"]:nth-child(1)',
saveButton: 'vn-ticket-request-create > form > div > vn-button-bar > vn-submit[label="Create"] input',
- firstDescription: 'vn-ticket-request-index > form > vn-card > div > vn-horizontal > vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(2)',
+ firstDescription: 'vn-ticket-request-index vn-table vn-tr:nth-child(1) > vn-td:nth-child(2)',
},
ticketLog: {
@@ -506,7 +504,7 @@ export default {
firstQuantityInput: 'vn-ticket-service vn-input-number[label="Quantity"] input',
firstPriceInput: 'vn-ticket-service vn-input-number[label="Price"] input',
firstVatTypeAutocomplete: 'vn-ticket-service vn-autocomplete[label="Tax class"]',
- fistDeleteServiceButton: 'vn-ticket-card > vn-main-block > div.content-block.ng-scope > vn-ticket-service > form > vn-card > div > vn-one:nth-child(1) > vn-horizontal:nth-child(1) > vn-auto > vn-icon-button[icon="delete"]',
+ fistDeleteServiceButton: 'vn-ticket-service form vn-horizontal:nth-child(1) vn-icon-button[icon="delete"]',
newDescriptionInput: 'vn-ticket-service > vn-dialog vn-textfield[ng-model="$ctrl.newServiceType.name"] input',
serviceLine: 'vn-ticket-service > form > vn-card > div > vn-one:nth-child(2) > vn-horizontal',
saveServiceButton: `${components.vnSubmit}`,
@@ -531,7 +529,7 @@ export default {
claimSummary: {
header: 'vn-claim-summary > vn-card > div > h5',
state: 'vn-claim-summary vn-label-value[label="State"] > section > span',
- observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"] > div > textarea',
+ observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"] textarea',
firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) > vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span',
firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img',
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
@@ -549,7 +547,7 @@ export default {
},
claimDetail: {
secondItemDiscount: 'vn-claim-detail > vn-vertical > vn-card > div > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(6) > span',
- discountInput: '.vn-popover.shown vn-input-number[ng-model="$ctrl.newDiscount"] > div > div > div.infix > input',
+ discountInput: '.vn-popover.shown vn-input-number[ng-model="$ctrl.newDiscount"] input',
discoutPopoverMana: '.vn-popover.shown .content > div > vn-horizontal > h5',
addItemButton: 'vn-claim-detail a vn-float-button',
firstClaimableSaleFromTicket: 'vn-claim-detail > vn-dialog vn-tbody > vn-tr',
@@ -608,7 +606,7 @@ export default {
typeAutocomplete: 'vn-autocomplete[data="$ctrl.itemTypes"]',
itemIdInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.itemFk"] input',
itemTagValueInput: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.value"] input',
- openTagSearch: 'vn-catalog-filter > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append > i',
+ openTagSearch: 'vn-catalog-filter > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i',
tagAutocomplete: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]',
tagValueInput: 'vn-order-catalog-search-panel vn-textfield[ng-model="filter.value"] input',
searchTagButton: 'vn-order-catalog-search-panel > div > form > vn-horizontal:nth-child(3) > vn-submit > input',
diff --git a/e2e/paths/02-client-module/12_lock_of_verified_data.spec.js b/e2e/paths/02-client-module/12_lock_of_verified_data.spec.js
index a4c97eb62..52e2db237 100644
--- a/e2e/paths/02-client-module/12_lock_of_verified_data.spec.js
+++ b/e2e/paths/02-client-module/12_lock_of_verified_data.spec.js
@@ -176,7 +176,7 @@ describe('Client lock verified data path', () => {
.wait(selectors.clientFiscalData.socialNameInput)
.evaluate(selector => {
return document.querySelector(selector).disabled;
- }, 'vn-textfield[model="$ctrl.client.socialName"] > div');
+ }, 'vn-textfield[ng-model="$ctrl.client.socialName"] > div');
expect(result).toBeFalsy();
});
diff --git a/e2e/paths/05-ticket-module/09_weekly.spec.js b/e2e/paths/05-ticket-module/09_weekly.spec.js
index bba4a20e7..a7a301d79 100644
--- a/e2e/paths/05-ticket-module/09_weekly.spec.js
+++ b/e2e/paths/05-ticket-module/09_weekly.spec.js
@@ -160,7 +160,7 @@ describe('Ticket descriptor path', () => {
it('should confirm the sixth weekly ticket was deleted', async() => {
const result = await nightmare
- .waitToClick('vn-ticket-weekly-index vn-searchbar i[class="material-icons clear"]')
+ .waitToClick('vn-ticket-weekly-index vn-searchbar vn-icon[icon=clear]')
.waitToClick(selectors.ticketsIndex.searchWeeklyButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchWeeklyResult, 5)
.countElement(selectors.ticketsIndex.searchWeeklyResult);
diff --git a/e2e/paths/05-ticket-module/13_services.spec.js b/e2e/paths/05-ticket-module/13_services.spec.js
index 31d262778..f9deb6c93 100644
--- a/e2e/paths/05-ticket-module/13_services.spec.js
+++ b/e2e/paths/05-ticket-module/13_services.spec.js
@@ -78,7 +78,7 @@ describe('Ticket services path', () => {
.waitToClick(selectors.ticketService.saveDescriptionButton)
.waitForLastSnackbar();
- expect(result).toEqual(`Name can't be empty`);
+ expect(result).toEqual(`can't be blank`);
});
it('should create a new description then add price then create the service', async() => {
diff --git a/e2e/paths/08-route-module/03_tickets.spec.js b/e2e/paths/08-route-module/03_tickets.spec.js
index c1a0350b3..24a3ebbcf 100644
--- a/e2e/paths/08-route-module/03_tickets.spec.js
+++ b/e2e/paths/08-route-module/03_tickets.spec.js
@@ -49,7 +49,7 @@ xdescribe('Route basic Data path', () => {
it('should count how many tickets are in route', async() => {
const result = await nightmare
- .countElement('vn-route-tickets vn-textfield[model="ticket.priority"]');
+ .countElement('vn-route-tickets vn-textfield[ng-model="ticket.priority"]');
expect(result).toEqual(11);
});
@@ -74,7 +74,7 @@ xdescribe('Route basic Data path', () => {
it('should now count how many tickets are in route to find one less', async() => {
const result = await nightmare
- .countElement('vn-route-tickets vn-textfield[model="ticket.priority"]');
+ .countElement('vn-route-tickets vn-textfield[ng-model="ticket.priority"]');
expect(result).toEqual(9);
});
diff --git a/front/core/components/field/index.js b/front/core/components/field/index.js
index a14f21b35..bc60af0d3 100644
--- a/front/core/components/field/index.js
+++ b/front/core/components/field/index.js
@@ -32,6 +32,7 @@ export default class Field extends FormInput {
}
set field(value) {
+ if (value === this.field) return;
super.field = value;
this.classList.toggle('not-empty', value != null && value !== '');
this.validateValue();
@@ -149,20 +150,28 @@ export default class Field extends FormInput {
}
set error(value) {
+ if (value === this.error) return;
this._error = value;
this.refreshHint();
- this.classList.toggle('invalid', Boolean(value));
}
get error() {
return this._error;
}
+ get shownError() {
+ return this.error || this.inputError || null;
+ }
+
refreshHint() {
- let hint = this.error || this.hint || '';
+ let error = this.shownError;
+ let hint = error || this.hint;
+
let hintEl = this.element.querySelector('.hint');
- hintEl.innerText = hint;
+ hintEl.innerText = hint || '';
hintEl.classList.toggle('filled', Boolean(hint));
+
+ this.classList.toggle('invalid', Boolean(error));
}
refreshFix(selector, text) {
@@ -205,18 +214,21 @@ export default class Field extends FormInput {
}
buildInput(type) {
- let template = ``;
+ let template = ``;
this.input = this.$compile(template)(this.$)[0];
- this.type = type;
}
/**
* If input value is invalid, sets the error message as hint.
*/
validateValue() {
- this.error = this.input.checkValidity()
+ let error = this.input.checkValidity()
? null
: this.input.validationMessage;
+
+ if (error === this.inputError) return;
+ this.inputError = error;
+ this.refreshHint();
}
}
Field.$inject = ['$element', '$scope', '$compile'];
@@ -239,8 +251,7 @@ ngModule.vnComponent('vnField', {
hint: '@?',
error: '',
tabIndex: '',
- onChange: '&?', // FIXME: Not implemented, nesessary?
- rule: '@?' // FIXME: Not implemented
+ rule: '@?'
}
});
diff --git a/front/core/components/input-file/index.js b/front/core/components/input-file/index.js
index 24c0cb44a..d0f93e4f8 100644
--- a/front/core/components/input-file/index.js
+++ b/front/core/components/input-file/index.js
@@ -3,15 +3,13 @@ import Input from '../../lib/input';
import './style.scss';
export default class InputFile extends Input {
- constructor($element, $scope, $attrs, vnTemplate) {
+ constructor($element, $scope) {
super($element, $scope);
this.element = $element[0];
this.hasFocus = false;
this._multiple = false;
this._value = 'Select a file';
- vnTemplate.normalizeInputAttrs($attrs);
-
this.registerEvents();
}
@@ -108,7 +106,7 @@ export default class InputFile extends Input {
}
}
-InputFile.$inject = ['$element', '$scope', '$attrs', 'vnTemplate'];
+InputFile.$inject = ['$element', '$scope'];
ngModule.component('vnInputFile', {
template: require('./index.html'),
diff --git a/front/core/components/input-number/index.spec.js b/front/core/components/input-number/index.spec.js
index c7bae7835..9d1943e0e 100644
--- a/front/core/components/input-number/index.spec.js
+++ b/front/core/components/input-number/index.spec.js
@@ -23,15 +23,15 @@ describe('Component vnInputNumber', () => {
$ctrl.min = 0;
// FIXME: Input validation doesn't work with Jest?
- // expect($ctrl.error).toContain('Please select a value that is no less than 0');
- expect($ctrl.error).toBeNull();
+ // expect($ctrl.shownError).toContain('Please select a value that is no less than 0');
+ expect($ctrl.shownError).toBeNull();
});
it(`should unset error property when value is upper than min`, () => {
$ctrl.field = 1;
$ctrl.min = 0;
- expect($ctrl.error).toBeNull();
+ expect($ctrl.shownError).toBeNull();
});
});
@@ -41,8 +41,8 @@ describe('Component vnInputNumber', () => {
$ctrl.max = 0;
// FIXME: Input validation doesn't work with Jest?
- // expect($ctrl.error).toContain('Please select a value that is no more than 0');
- expect($ctrl.error).toBeNull();
+ // expect($ctrl.shownError).toContain('Please select a value that is no more than 0');
+ expect($ctrl.shownError).toBeNull();
});
// FIXME: Input validation doesn't work with Jest?
@@ -50,7 +50,7 @@ describe('Component vnInputNumber', () => {
$ctrl.field = -1;
$ctrl.min = 0;
- expect($ctrl.error).toBeNull();
+ expect($ctrl.shownError).toBeNull();
});
});
diff --git a/front/core/components/table/index.html b/front/core/components/table/index.html
index e7ae44886..5c36dd6a0 100644
--- a/front/core/components/table/index.html
+++ b/front/core/components/table/index.html
@@ -1,2 +1,2 @@
-
+
\ No newline at end of file
diff --git a/front/core/components/table/style.scss b/front/core/components/table/style.scss
index 9c14e3e03..839e1a7bb 100644
--- a/front/core/components/table/style.scss
+++ b/front/core/components/table/style.scss
@@ -5,181 +5,169 @@ vn-table {
display: block;
overflow: auto;
width: 100%;
+}
+.vn-table {
+ width: 100%;
+ display: table;
+ border-collapse: collapse;
- & > div {
- width: inherit;
- display: table;
- border-collapse: collapse;
+ & > vn-thead,
+ & > thead {
+ display: table-header-group;
+ border-bottom: .15em solid $color-spacer;
- & > vn-thead {
- display: table-header-group;
- border-bottom: .15em solid $color-spacer;
+ & > * > th {
+ font-weight: normal;
+ }
+ & > * > vn-th[field] {
+ position: relative;
+ overflow: visible;
+ cursor: pointer;
- & > * > vn-th[field] {
- position: relative;
- overflow: visible;
- cursor: pointer;
-
- &.active > :after {
- color: $color-font;
- opacity: 1;
- }
- &.desc > :after {
- content: 'arrow_drop_down';
- }
- &.asc > :after {
- content: 'arrow_drop_up';
- }
- & > :after {
- font-family: 'Material Icons';
- content: 'arrow_drop_down';
- position: absolute;
- color: $color-spacer;
- opacity: 0;
- }
- &:hover > :after {
- opacity: 1;
- }
+ &.active > :after {
+ color: $color-font;
+ opacity: 1;
+ }
+ &.desc > :after {
+ content: 'arrow_drop_down';
+ }
+ &.asc > :after {
+ content: 'arrow_drop_up';
+ }
+ & > :after {
+ font-family: 'Material Icons';
+ content: 'arrow_drop_down';
+ position: absolute;
+ color: $color-spacer;
+ opacity: 0;
+ }
+ &:hover > :after {
+ opacity: 1;
}
}
- & > vn-tbody {
- display: table-row-group;
- }
- & > vn-tfoot {
- border-top: .15em solid $color-spacer;
- display: table-footer-group
- }
- & > * > vn-tr,
- & > * > a.vn-tr {
+ }
+ & > vn-tbody,
+ & > tbody {
+ display: table-row-group;
+ }
+ & > vn-tfoot,
+ & > tfoot {
+ border-top: .15em solid $color-spacer;
+ display: table-footer-group
+ }
+ & > * > vn-tr,
+ & > * > a.vn-tr,
+ & > * > tr {
+ display: table-row;
+ height: 3em;
+ }
+ vn-thead, vn-tbody, vn-tfoot,
+ thead, tbody, tfoot {
+ & > * {
display: table-row;
- height: 3em;
- }
- vn-thead, vn-tbody, vn-tfoot {
- & > * {
- display: table-row;
- & > vn-th {
- color: $color-font-light;
- padding-top: 1em;
- padding-bottom: .8em;
+ & > vn-th,
+ & > th {
+ color: $color-font-light;
+ padding-top: 1em;
+ padding-bottom: .8em;
+ }
+ & > vn-th,
+ & > vn-td,
+ & > th,
+ & > td {
+ overflow: hidden;
+ }
+ & > vn-th,
+ & > vn-td,
+ & > vn-td-editable,
+ & > th,
+ & > td {
+ vertical-align: middle;
+ display: table-cell;
+ text-align: left;
+ padding: .6em .5em;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ max-width: 5em;
+
+ &[number] {
+ text-align: right;
+ width: 6em;
}
- & > vn-th,
- & > vn-td {
- overflow: hidden;
+ &[center] {
+ text-align: center;
}
- & > vn-th,
- & > vn-td,
- & > vn-td-editable {
- vertical-align: middle;
- display: table-cell;
- text-align: left;
- padding: .6em .5em;
- white-space: nowrap;
- text-overflow: ellipsis;
- max-width: 5em;
-
- &[number] {
- text-align: right;
- width: 6em;
- }
- &[center] {
- text-align: center;
- }
- &[shrink] {
- width: 1px;
- text-align: center;
- }
- &[expand] {
- max-width: 10em;
- min-width: 0;
- }
- vn-icon.bright, i.bright {
- color: #f7931e;
- }
+ &[shrink] {
+ width: 1px;
+ text-align: center;
}
- & > :last-child {
- padding-right: 1.4em;
+ &[expand] {
+ max-width: 10em;
+ min-width: 0;
}
- & > :first-child {
- padding-left: 1.4em;
+ vn-icon.bright, i.bright {
+ color: #f7931e;
}
}
- & > a.vn-tr {
- color: inherit;
+ & > :last-child {
+ padding-right: 1.4em;
+ }
+ & > :first-child {
+ padding-left: 1.4em;
}
}
- vn-tbody > * {
- border-bottom: .1em solid $color-spacer-light;
+ & > a.vn-tr {
+ color: inherit;
+ }
+ }
+ vn-tbody > *,
+ tbody > * {
+ border-bottom: .1em solid $color-spacer-light;
- &:last-child {
- border-bottom: none;
- }
- &.clickable {
- @extend %clickable;
- }
- & > vn-td .chip {
+ &:last-child {
+ border-bottom: none;
+ }
+ &.clickable {
+ @extend %clickable;
+ }
+ & > vn-td,
+ & > td {
+ .chip {
padding: .3em;
- border-radius: .3em
- }
-
- & > vn-td .chip.notice {
+ border-radius: .3em;
color: $color-font-bg;
- background-color: $color-notice-medium
- }
- & > vn-td .chip.success {
- color: $color-font-bg;
- background-color: $color-success-medium
+ &.notice {
+ background-color: $color-notice-medium
+ }
+ &.success {
+ background-color: $color-success-medium;
+ }
+ &.warning {
+ background-color: $color-main-medium;
+ }
+ &.alert {
+ background-color: $color-alert-medium;
+ }
+ &.message {
+ color: $color-font-dark;
+ background-color: $color-bg-dark
+ }
}
-
- & > vn-td .chip.warning {
- color: $color-font-bg;
- background-color: $color-main-medium;
- }
-
- & > vn-td .chip.alert {
- color: $color-font-bg;
- background-color: $color-alert-medium;
- }
-
- & > vn-td .chip.message {
- color: $color-font-dark;
- background-color: $color-bg-dark
- }
-
- & > vn-td vn-icon-menu {
+ vn-icon-menu {
display: inline-block;
color: $color-main;
padding: .25em
}
+ }
+ & > [actions] {
+ width: 1px;
- & > [actions] {
- width: 1px;
-
- & > * {
- vertical-align: middle;
- }
+ & > * {
+ vertical-align: middle;
}
}
- & > vn-empty-rows {
- display: table-caption;
- caption-side: bottom;
- text-align: center;
- padding: 1.5em;
- width: 100%;
- box-sizing: border-box;
- }
- }
- vn-autocomplete {
- div.mdl-textfield {
- padding: 0 !important;
- }
- label.mdl-textfield__label:after {
- bottom: 0;
- }
- div.icons {
- display: none !important;
- }
}
vn-textfield {
float: right;
diff --git a/front/core/directives/index.js b/front/core/directives/index.js
index 08adfac07..05f21b5cd 100644
--- a/front/core/directives/index.js
+++ b/front/core/directives/index.js
@@ -2,7 +2,7 @@ import './id';
import './focus';
import './dialog';
import './popover';
-import './validation';
+import './rule';
import './acl';
import './on-error-src';
import './zoom-image';
diff --git a/front/core/directives/rule.js b/front/core/directives/rule.js
new file mode 100644
index 000000000..85b6041f8
--- /dev/null
+++ b/front/core/directives/rule.js
@@ -0,0 +1,72 @@
+import ngModule from '../module';
+import {validateAll} from '../lib/validator';
+import {firstUpper} from '../lib/string';
+
+directive.$inject = ['$translate', '$window'];
+export function directive($translate, $window) {
+ return {
+ restrict: 'A',
+ link: link,
+ require: {
+ ngModel: 'ngModel',
+ form: '^^?form'
+ }
+ };
+
+ function link($scope, $element, $attrs, $ctrl) {
+ let vnValidations = $window.validations;
+ if (!vnValidations) return;
+
+ if (!/^([A-Z]\w+(\.[a-z]\w*)?)?$/.test($attrs.rule))
+ throw new Error(`rule: Attribute must have this syntax: [ModelName[.fieldName]]`);
+
+ let rule = $attrs.rule.split('.');
+ let modelName = rule.shift();
+ let fieldName = rule.shift();
+
+ let split = $attrs.ngModel.split('.');
+ if (!fieldName) fieldName = split.pop() || null;
+ if (!modelName) modelName = firstUpper(split.pop() || '');
+
+ if (!modelName || !fieldName)
+ throw new Error(`rule: Cannot retrieve model or field attribute`);
+
+ let modelValidations = vnValidations[modelName];
+
+ if (!modelValidations)
+ throw new Error(`rule: Model '${modelName}' doesn't exist`);
+
+ let validations = modelValidations.validations[fieldName];
+ if (!validations || validations.length == 0)
+ return;
+
+ let ngModel = $ctrl.ngModel;
+ let form = $ctrl.form;
+ let field = $element[0].$ctrl;
+ let error;
+
+ function refreshError() {
+ if (!field) return;
+ let canShow = ngModel.$dirty || (form && form.$submitted);
+ field.error = error && canShow ? error.message : null;
+ }
+
+ ngModel.$options.$$options.allowInvalid = true;
+ ngModel.$validators.entity = value => {
+ try {
+ error = null;
+ validateAll($translate, value, validations);
+ } catch (e) {
+ error = e;
+ }
+
+ refreshError();
+ return error == null;
+ };
+
+ if (form)
+ $scope.$watch(form.$submitted, refreshError);
+ }
+}
+ngModule.directive('rule', directive);
+
diff --git a/front/core/directives/specs/rule.spec.js b/front/core/directives/specs/rule.spec.js
new file mode 100644
index 000000000..f3671a5e2
--- /dev/null
+++ b/front/core/directives/specs/rule.spec.js
@@ -0,0 +1,115 @@
+describe('Directive rule', () => {
+ let $scope;
+ let $element;
+ let element;
+
+ beforeEach(angular.mock.module('vnCore', $translateProvider => {
+ $translateProvider.translations('en', {});
+ }));
+
+ function compile(html, value) {
+ inject(($compile, $rootScope, $window) => {
+ $window.validations = {Model:
+ {validations: {field: [{validation: 'absence'}]}}
+ };
+
+ $scope = $rootScope.$new();
+
+ $element = angular.element(html);
+ $compile($element)($scope);
+ element = $element[0];
+
+ $scope.model = {field: value};
+ $scope.$digest();
+ });
+ }
+
+ afterEach(() => {
+ $scope.$destroy();
+ $element.remove();
+ });
+
+ describe('Errors', () => {
+ it(`should throw an error if the rule doesn't have the right syntax`, () => {
+ expect(() => {
+ compile(`
+
+ `);
+ }).toThrow(new Error(`rule: Attribute must have this syntax: [ModelName[.fieldName]]`));
+ });
+
+ it('should throw an error if cannot retrieve model or field', () => {
+ expect(() => {
+ compile(`
+
+ `);
+ }).toThrow(new Error(`rule: Cannot retrieve model or field attribute`));
+ });
+
+ it('should throw an error if the model is not defined', () => {
+ expect(() => {
+ compile(`
+
+ `);
+ }).toThrow(new Error(`rule: Model 'NonExistentModel' doesn't exist`));
+ });
+ });
+
+ describe('Validator extended', () => {
+ let html = `
+
+ `;
+
+ it('should not validate the entity as it has a wrong value', () => {
+ compile(html, 'invalidValue');
+
+ expect(element.classList).toContain('ng-invalid');
+ });
+
+ it('should validate the entity as it has a valid value', () => {
+ compile(html, '');
+
+ expect(element.classList).toContain('ng-valid');
+ });
+ });
+
+ describe('Validator minimal', () => {
+ let html = `
+
+
+ `;
+
+ it('should validate with empty rule and without specifying a parent form', () => {
+ compile(html, 'invalidValue');
+
+ expect(element.classList).toContain('ng-invalid');
+ });
+ });
+});
diff --git a/front/core/directives/specs/validation.spec.js b/front/core/directives/specs/validation.spec.js
deleted file mode 100644
index 9f8567df7..000000000
--- a/front/core/directives/specs/validation.spec.js
+++ /dev/null
@@ -1,186 +0,0 @@
-describe('Directive validation', () => {
- let scope;
- let element;
- let compile;
-
- beforeEach(angular.mock.module('vnCore', $translateProvider => {
- $translateProvider.translations('en', {});
- }));
-
- compile = (_element, validations, value) => {
- inject(($compile, $rootScope, $window) => {
- $window.validations = validations;
- scope = $rootScope.$new();
- scope.user = {name: value};
- element = angular.element(_element);
- $compile(element)(scope);
- scope.$digest();
- });
- };
-
- it(`should throw an error if the vnValidation doesn't have the right syntax`, () => {
- let html = `
`;
-
- expect(() => {
- compile(html, {});
- }).toThrow(new Error(`vnValidation: Attribute must have this syntax: [entity].[field]`));
- });
-
- it('should throw an error if the window.validations aint defined', () => {
- let html = `
`;
-
- expect(() => {
- compile(html, {});
- }).toThrow(new Error(`vnValidation: Entity 'User' doesn't exist`));
- });
-
- describe('Validator presence()', () => {
- it('should not validate the user name as it is an empty string', () => {
- let html = `
`;
- let validations = {User: {validations: {name: [{validation: 'presence'}]}}};
- compile(html, validations, 'Spiderman');
- scope.user.name = '';
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-invalid');
- expect(element[0].classList).not.toContain('ng-valid');
- });
- });
-
- describe('Validator absence()', () => {
- it('should not validate the entity as it should be an empty string', () => {
- let html = `
`;
- let validations = {User: {validations: {name: [{validation: 'absence'}]}}};
- compile(html, validations, 'Spiderman');
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-invalid');
- expect(element[0].classList).not.toContain('ng-valid');
- });
-
- it('should validate the entity as it is an empty string', () => {
- let html = `
`;
- let validations = {User: {validations: {name: [{validation: 'absence'}]}}};
- compile(html, validations, '');
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-valid');
- expect(element[0].classList).not.toContain('ng-invalid');
- });
- });
-
- describe('Validator length()', () => {
- it('should not validate the user name as it should have min length of 15', () => {
- let html = `
`;
- let validations = {User: {validations: {name: [{validation: 'length', min: 10, max: 50, is: 15}]}}};
- compile(html, validations, 'fifteen!');
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-invalid');
- expect(element[0].classList).not.toContain('ng-valid');
- });
-
- it('should validate the user name as it has length of 15', () => {
- let html = `
`;
- let validations = {User: {validations: {name: [{validation: 'length', min: 10, max: 50, is: 15}]}}};
- compile(html, validations, 'fifteen length!');
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-valid');
- expect(element[0].classList).not.toContain('ng-invalid');
- });
-
- it('should not validate the user name as it should have min length of 10', () => {
- let html = `
`;
- let validations = {User: {validations: {name: [{validation: 'length', min: 10}]}}};
- compile(html, validations, 'shortname');
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-invalid');
- expect(element[0].classList).not.toContain('ng-valid');
- });
-
- it('should validate the user name as its length is greater then the minimum', () => {
- let html = `
`;
- let validations = {User: {validations: {name: [{validation: 'length', min: 10}]}}};
- compile(html, validations, 'verylongname');
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-valid');
- expect(element[0].classList).not.toContain('ng-invalid');
- });
-
- it('should not validate the user name as its length is greater then the maximum', () => {
- let html = `
`;
- let validations = {User: {validations: {name: [{validation: 'length', max: 10}]}}};
- compile(html, validations, 'toolongname');
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-invalid');
- expect(element[0].classList).not.toContain('ng-valid');
- });
- });
-
- describe('Validator numericality()', () => {
- it('should not validate the phone number as it should a integer', () => {
- let html = `
`;
- let validations = {User: {validations: {phone: [{validation: 'numericality', is: 'what is this?'}]}}};
- compile(html, validations, 'spiderman');
- scope.user.phone = 'this is not a phone number!';
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-invalid');
- expect(element[0].classList).not.toContain('ng-valid');
- });
-
- it('should validate the phone number as it an integer', () => {
- let html = `
`;
- let validations = {User: {validations: {phone: [{validation: 'numericality', is: 'what is this?'}]}}};
- compile(html, validations, 'spiderman');
- scope.user.phone = '555555555';
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-valid');
- expect(element[0].classList).not.toContain('ng-invalid');
- });
- });
-
- describe('Validator inclusion()', () => {
- it('should not validate the phone number as it is not an integer', () => {
- let html = `
`;
- let validations = {User: {validations: {phone: [{validation: 'inclusion'}]}}};
- compile(html, validations, 'spiderman');
- scope.user.phone = 'this is not a phone number!';
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-invalid');
- expect(element[0].classList).not.toContain('ng-valid');
- });
- });
-
- describe('Validator exclusion()', () => {
- it('should validate the phone number as it is an integer', () => {
- let html = `
`;
- let validations = {User: {validations: {phone: [{validation: 'exclusion'}]}}};
- compile(html, validations, 'spiderman');
- scope.user.phone = '555555555';
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-valid');
- expect(element[0].classList).not.toContain('ng-invalid');
- });
- });
-
- describe('Validator format()', () => {
- it('should not validate the email number as it doesnt contain @', () => {
- let html = `
`;
- let validations = {User: {validations: {email: [{validation: 'format', with: '@'}]}}};
- compile(html, validations, 'spiderman');
- scope.user.email = 'userverdnatura.es';
- scope.$digest();
-
- expect(element[0].classList).toContain('ng-invalid');
- expect(element[0].classList).not.toContain('ng-valid');
- });
- });
-});
diff --git a/front/core/directives/validation.js b/front/core/directives/validation.js
deleted file mode 100644
index d234f09bf..000000000
--- a/front/core/directives/validation.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import ngModule from '../module';
-import {validateAll} from '../lib/validator';
-import {firstUpper} from '../lib/string';
-
-directive.$inject = ['$interpolate', '$compile', '$translate', '$window'];
-export function directive(interpolate, compile, $translate, $window) {
- return {
- restrict: 'A',
- require: ['ngModel', '^^?form'],
- link: link
- };
-
- function link(scope, element, attrs, ctrl) {
- let vnValidations = $window.validations;
-
- if (!attrs.vnValidation || !vnValidations)
- return;
-
- let split = attrs.vnValidation.split('.');
-
- if (split.length !== 2)
- throw new Error(`vnValidation: Attribute must have this syntax: [entity].[field]`);
-
- let entityName = firstUpper(split[0]);
- let fieldName = split[1];
- let entity = vnValidations[entityName];
-
- if (!entity)
- throw new Error(`vnValidation: Entity '${entityName}' doesn't exist`);
-
- let validations = entity.validations[fieldName];
- if (!validations || validations.length == 0)
- return;
-
- let ngModel = ctrl[0];
- let form = ctrl[1];
- let errorSpan = angular.element('
');
- let errorMsg;
- let errorShown = false;
-
- ngModel.$options.$$options.allowInvalid = true;
- ngModel.$validators.entity = value => {
- try {
- validateAll($translate, value, validations);
- return true;
- } catch (e) {
- errorMsg = e.message;
- if (errorShown) changeError();
- return false;
- }
- };
-
- scope.$watch(() => {
- return (form.$submitted || ngModel.$dirty) && ngModel.$invalid;
- }, value => {
- let parent = element.parent();
-
- if (value) {
- changeError();
- parent.addClass('invalid');
- element.after(errorSpan);
- } else if (errorShown) {
- parent.removeClass('invalid');
- parent.removeAttr('title');
- errorSpan.remove();
- errorSpan.empty();
- }
-
- errorShown = value;
- });
-
- function changeError() {
- let parent = element.parent();
- errorSpan.text(errorMsg);
- parent.attr('title', errorMsg);
- }
- }
-}
-ngModule.directive('vnValidation', directive);
-
diff --git a/front/core/lib/index.js b/front/core/lib/index.js
index dac8e460d..2682dfdf9 100644
--- a/front/core/lib/index.js
+++ b/front/core/lib/index.js
@@ -1,7 +1,5 @@
import './module-loader';
import './crud';
-import './acl-service';
-import './template';
import './copy';
import './equals';
import './modified';
diff --git a/front/core/lib/specs/validator.spec.js b/front/core/lib/specs/validator.spec.js
new file mode 100644
index 000000000..c61faa793
--- /dev/null
+++ b/front/core/lib/specs/validator.spec.js
@@ -0,0 +1,186 @@
+import {validate} from '../validator.js';
+
+describe('Validator', () => {
+ let $translate;
+
+ beforeEach(angular.mock.module('vnCore', $translateProvider => {
+ $translateProvider.translations('en', {});
+ }));
+
+ beforeEach(inject(_$translate_ => {
+ $translate = _$translate_;
+ }));
+
+ describe('presence', () => {
+ let conf = {validation: 'presence'};
+
+ it('should not validate the value as it should be defined', () => {
+ expect(() => {
+ validate($translate, '', conf);
+ }).toThrowError();
+ });
+
+ it('should validate the value as it should be defined', () => {
+ expect(() => {
+ validate($translate, 'aDefinedValue', conf);
+ }).not.toThrowError();
+ });
+ });
+
+ describe('absence', () => {
+ let conf = {validation: 'absence'};
+
+ it('should not validate the value as it should be undefined', () => {
+ expect(() => {
+ validate($translate, 'aDefinedValue', conf);
+ }).toThrowError();
+ });
+
+ it('should validate the value as it should be undefined', () => {
+ expect(() => {
+ validate($translate, '', conf);
+ }).not.toThrowError();
+ });
+ });
+
+ describe('length', () => {
+ it('should not validate the value as it should have an specific valid length', () => {
+ let conf = {
+ validation: 'length',
+ is: 25
+ };
+
+ expect(() => {
+ validate($translate, 'invalidSpecificLengthString', conf);
+ }).toThrowError();
+ });
+
+ it('should validate the value as it should have an specific valid length', () => {
+ let conf = {
+ validation: 'length',
+ is: 25
+ };
+
+ expect(() => {
+ validate($translate, 'validSpecificLengthString', conf);
+ }).not.toThrowError();
+ });
+
+ it('should not validate the value as it should have a min length', () => {
+ let conf = {
+ validation: 'length',
+ min: 10
+ };
+
+ expect(() => {
+ validate($translate, 'shortName', conf);
+ }).toThrowError();
+ });
+
+ it('should validate the value as it should have a min length', () => {
+ let conf = {
+ validation: 'length',
+ min: 10
+ };
+
+ expect(() => {
+ validate($translate, 'veryLongName', conf);
+ }).not.toThrowError();
+ });
+
+ it('should not validate the value as it should be smaller than the maximum', () => {
+ let conf = {
+ validation: 'length',
+ max: 20
+ };
+
+ expect(() => {
+ validate($translate, 'chainThatExceedsMaxLength', conf);
+ }).toThrowError();
+ });
+
+ it('should validate the value as it should be smaller than the maximum', () => {
+ let conf = {
+ validation: 'length',
+ max: 20
+ };
+
+ expect(() => {
+ validate($translate, 'shortString', conf);
+ }).not.toThrowError();
+ });
+ });
+
+ describe('numericality', () => {
+ let conf = {validation: 'numericality'};
+
+ it('should not validate the value as it should be an integer', () => {
+ expect(() => {
+ validate($translate, 'notANumber', conf);
+ }).toThrowError();
+ });
+
+ it('should validate the value as it should be an integer', () => {
+ expect(() => {
+ validate($translate, '123456789', conf);
+ }).not.toThrowError();
+ });
+ });
+
+ describe('inclusion', () => {
+ let conf = {
+ validation: 'inclusion',
+ in: ['firstValue', 'seekValue', 'lastValue']
+ };
+
+ it('should not validate the value as it should be in array', () => {
+ expect(() => {
+ validate($translate, 'notIncludedValue', conf);
+ }).toThrowError();
+ });
+
+ it('should validate the value as it should be in array', () => {
+ expect(() => {
+ validate($translate, 'seekValue', conf);
+ }).not.toThrowError();
+ });
+ });
+
+ describe('exclusion', () => {
+ let conf = {
+ validation: 'exclusion',
+ in: ['firstValue', 'seekValue', 'lastValue']
+ };
+
+ it('should not validate the value as it should not be in array', () => {
+ expect(() => {
+ validate($translate, 'seekValue', conf);
+ }).toThrowError();
+ });
+
+ it('should validate the value as it should not be in array', () => {
+ expect(() => {
+ validate($translate, 'notIncludedValue', conf);
+ }).not.toThrowError();
+ });
+ });
+
+ describe('format', () => {
+ let conf = {
+ validation: 'format',
+ with: /[a-z-]+@[a-z-]+\.[a-z]+/
+ };
+
+ it('should not validate the value as it should match regexp', () => {
+ expect(() => {
+ validate($translate, 'wrongValue', conf);
+ }).toThrowError();
+ });
+
+ it('should validate the value as it should match regexp', () => {
+ expect(() => {
+ validate($translate, 'valid-value@domain.com', conf);
+ }).not.toThrowError();
+ });
+ });
+});
diff --git a/front/core/lib/string.js b/front/core/lib/string.js
index 920640a95..582485cb1 100644
--- a/front/core/lib/string.js
+++ b/front/core/lib/string.js
@@ -6,7 +6,7 @@
* @return {String} The camelized string
*/
export function kebabToCamel(str) {
- var camelCased = str.replace(/-([a-z])/g, function(g) {
+ let camelCased = str.replace(/-([a-z])/g, function(g) {
return g[1].toUpperCase();
});
return camelCased;
diff --git a/front/core/lib/template.js b/front/core/lib/template.js
deleted file mode 100644
index ae2f1b188..000000000
--- a/front/core/lib/template.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import ngModule from '../module';
-
-export default class Template {
- getNormalized(template, $attrs, defaults) {
- this.normalizeInputAttrs($attrs);
- return this.get(template, $attrs, defaults);
- }
- normalizeInputAttrs($attrs) {
- const field = $attrs.field || $attrs.model;
- const split = field.split('.');
- const len = split.length;
-
- let i = len - 1;
- const fieldName = split[i--];
- const entity = i >= 0 ? split[i--] : 'model';
- const ctrl = i >= 0 ? split[i--] : '$ctrl';
-
- if ($attrs.field) {
- if (len == 0)
- throw new Error(`Attribute 'field' can not be empty`);
- if (len > 3)
- throw new Error(`Attribute 'field' must have this syntax: [ctrl].[entity].[field]`);
-
- if ($attrs.model === undefined)
- $attrs.model = `${ctrl}.${entity}.${fieldName}`;
- if ($attrs.rule === undefined && len >= 2)
- $attrs.rule = `${entity}.${fieldName}`;
- if ($attrs.label === undefined && len >= 2)
- $attrs.label = `${entity}.${fieldName}`;
- }
-
- if ($attrs.name === undefined)
- $attrs.name = fieldName;
-
- if ($attrs.focus !== undefined)
- $attrs.focus = 'vn-focus';
- }
-}
-
-ngModule.service('vnTemplate', Template);
diff --git a/front/core/lib/acl-service.js b/front/core/services/acl-service.js
similarity index 100%
rename from front/core/lib/acl-service.js
rename to front/core/services/acl-service.js
diff --git a/front/core/services/config.js b/front/core/services/config.js
index 256308fb6..e2d3035fb 100644
--- a/front/core/services/config.js
+++ b/front/core/services/config.js
@@ -9,9 +9,9 @@ export default class Config {
$http,
vnApp,
$translate,
+ storage: $window.localStorage,
user: {},
- local: {},
- storage: $window.localStorage
+ local: {}
});
this.params = [
@@ -29,8 +29,8 @@ export default class Config {
.then(res => {
for (let param of this.params)
this.user[param] = res.data[param];
- this.mergeParams();
- });
+ })
+ .finally(() => this.mergeParams());
}
mergeParams() {
@@ -47,13 +47,19 @@ export default class Config {
this.mergeParams();
let params = {[param]: value};
- return this.$http.post('api/UserConfigs/setUserConfig', params);
+ return this.$http.post('api/UserConfigs/setUserConfig', params)
+ .then(() => this.showSaved());
}
setLocal(param, value) {
this.setItem(param, value);
this.local[param] = value;
this.mergeParams();
+ this.showSaved();
+ }
+
+ showSaved() {
+ this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
}
getItem(key) {
diff --git a/front/core/services/index.js b/front/core/services/index.js
index 2f72e3a2d..8ff95188a 100644
--- a/front/core/services/index.js
+++ b/front/core/services/index.js
@@ -1,4 +1,5 @@
+import './acl-service';
import './app';
import './auth';
import './token';
diff --git a/front/salix/module.js b/front/salix/module.js
index 86c8f48d0..58bdec105 100644
--- a/front/salix/module.js
+++ b/front/salix/module.js
@@ -62,7 +62,7 @@ export function config($translatePartialLoaderProvider, $httpProvider, $compileP
$translatePartialLoaderProvider.addPart(appName);
$httpProvider.interceptors.push('vnInterceptor');
- $compileProvider.debugInfoEnabled(false);
+ // $compileProvider.debugInfoEnabled(false);
$compileProvider.commentDirectivesEnabled(false);
$compileProvider.cssClassDirectivesEnabled(false);
}
diff --git a/front/salix/styles/misc.scss b/front/salix/styles/misc.scss
index cccb0fc92..8066759ac 100644
--- a/front/salix/styles/misc.scss
+++ b/front/salix/styles/misc.scss
@@ -116,52 +116,6 @@ html [scrollable] {
margin-right: auto;
max-width: $width-md;
}
-
-.vn-grid {
- border-collapse: collapse;
- width: 100%;
-
- & > thead,
- & > tbody,
- & > tfoot {
- tr {
- td, th {
- text-align: left;
- padding: 10px;
-
- &[number]{
- text-align: right;
- }
- }
- }
- }
- & > thead, & > tbody {
- border-bottom: 3px solid $color-spacer;
- }
- & > tbody > tr {
- border-bottom: 1px solid $color-spacer;
- transition: background-color 200ms ease-in-out;
-
- &.clickable {
- @extend %clickable;
- }
- &.success {
- background-color: rgba(163, 209, 49, 0.3);
-
- &:hover {
- background-color: rgba(163, 209, 49, 0.5);
- }
- }
- &.warning {
- background-color: rgba(247, 147, 30, 0.3);
-
- &:hover {
- background-color: rgba(247, 147, 30, 0.5);
- }
- }
- }
-}
-
vn-empty-rows {
display: block;
text-align: center;
@@ -180,12 +134,12 @@ vn-empty-rows {
[uppercase], .uppercase {
text-transform: uppercase;
}
-html [text-center], .text-center {
+html [text-center] {
text-align: center;
}
-html [text-right], .text-right {
+html [text-right] {
text-align: right;
}
-html [text-left], .text-left {
+html [text-left] {
text-align: left;
}
diff --git a/modules/agency/front/basic-data/index.html b/modules/agency/front/basic-data/index.html
index 10c989b12..ec652c0e7 100644
--- a/modules/agency/front/basic-data/index.html
+++ b/modules/agency/front/basic-data/index.html
@@ -8,8 +8,7 @@