mgCrud v1

This commit is contained in:
Juan Ferrer Toribio 2017-02-21 11:36:43 +01:00
parent 72a9068c80
commit c694f00644
53 changed files with 486 additions and 568 deletions

View File

@ -1,4 +1,4 @@
<form name="form" ng-submit="form.$valid && addressData.onSubmit()" pad-medium>
<form name="form" ng-submit="addressData.onSubmit()" pad-medium>
<vn-card >
<vn-vertical pad-large>
<vn-title>Consignatario</vn-title>
@ -41,5 +41,6 @@
vn-id="watcher"
url="/client/api/Addresses"
id-field="id"
data="addressData.address">
data="addressData.address"
form="form">
</vn-watcher>

View File

@ -1,4 +1,4 @@
<form name="form" ng-submit="form.$valid && addressData.onSubmit()" pad-medium>
<form name="form" ng-submit="addressData.onSubmit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Consignatario</vn-title>
@ -43,5 +43,6 @@
get="true"
url="/client/api/Addresses"
id-field="id"
data="addressData.address">
data="addressData.address"
form="form">
</vn-watcher>

View File

@ -1,11 +1,13 @@
<mg-ajax path="/client/api/Clients/{{index.params.id}}/Addresses" options="vnIndex"></mg-ajax>
<vn-vertical pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-horizontal>
<vn-title vn-one>Consignatario</vn-title>
</vn-horizontal>
<vn-horizontal ng-repeat="i in address.addresses" class="pad-medium-top" style="align-items: center;">
<vn-auto style="border-radius: .5em;" class="pad-small border-solid" ng-class="{'bg-dark-item': i.default,'bg-opacity-item': !i.enabled && !i.default}">
<vn-horizontal ng-repeat="i in index.model" class="pad-medium-top" style="align-items: center;">
<vn-auto style="border-radius: .5em;" class="pad-small border-solid"
ng-class="{'bg-dark-item': i.default,'bg-opacity-item': !i.enabled && !i.default}">
<vn-horizontal style="align-items: center;">
<vn-auto>
<div><b>{{i.consignee}}</b></div>
@ -13,30 +15,18 @@
<div>{{i.city}}, {{i.province}}</div>
<div>{{i.phone}}, {{i.mobile}}</div>
</vn-auto>
<a vn-empty ui-sref="clientCard.addressEdit({ addressId: {{i.id}} })">
<a vn-empty ui-sref="clientCard.addressEdit({addressId: {{i.id}}})">
<vn-icon-button icon="edit"></vn-icon-button>
</a>
</vn-horizontal>
</vn-auto>
</vn-horizontal>
</vn-vertical>
<vn-horizontal>
<vn-one style="text-align:center;">
<paging page="address.currentPage"
page-size="address.numPerPage"
total="address.numPages"
show-prev-next="true"
show-first-last="true"
ul-class="pagination"
active-class="active"
ng-click="address.figureOutToDisplay()">
</paging>
</vn-one>
</vn-horizontal>
</vn-card>
<vn-paging index="index"></vn-paging>
<vn-float-button
fixed-bottom-right
ng-click="address.newAddress()"
ng-click="$ctrl.onNewAddressClick()"
icon="add">
</vn-float-button>
</vn-vertical>

View File

@ -1,63 +1,20 @@
import {module} from '../module';
export const NAME = 'vnClientAddresses';
export const COMPONENT = {
class Controller {
constructor($state) {
this.$state = $state;
}
onNewAddressClick() {
this.$state.go('clientCard.addressCreate', {id: this.client.id});
}
}
Controller.$inject = ['$state'];
export const component = {
template: require('./index.html'),
controllerAs: 'address',
bindings: {
client: '<'
},
controller: controller
controller: Controller
};
module.component(NAME, COMPONENT);
controller.$inject = ['$http', '$state'];
function controller($http, $state) {
var self = this;
let numPerPage = 5;
let numRecords = 0;
this.$onChanges = function(changes) {
if (this.client) {
self.client = this.client;
this.getAddresses();
}
};
this.getAddresses = () => {
var json = JSON.stringify({client: self.client.id});
$http.get(`/client/api/Addresses/count?where=${json}`).then(
json => {
numRecords = json.data.count;
self.getNumPages();
self.figureOutToDisplay();
}
);
};
this.getNumPages = () => {
var nPages = numRecords / numPerPage;
self.numPages = Math.ceil(nPages);
};
this.figureOutToDisplay = () => {
var begin = ((self.currentPage - 1) * numPerPage);
self.getAddress(begin);
};
this.getAddress = function(end) {
var json = JSON.stringify({where: {client: self.client.id}, limit: numPerPage, skip: end});
$http.get(`/client/api/Addresses?filter=${json}`).then(
json => {
self.addresses = json.data;
self.filteredTodos = self.addresses;
}
);
};
this.pageChanged = () => {
self.figureOutToDisplay();
};
this.newAddress = () => {
$state.go("clientCard.addressCreate", {id: self.client.id});
};
}
module.component('vnClientAddresses', component);

View File

@ -1,4 +1,6 @@
<form name="form" ng-submit="form.$valid && watcher.submit()" pad-medium>
<mg-ajax path="/client/api/Clients/{{put.params.id}}" options="vnPut"></mg-ajax>
<vn-watcher vn-id="watcher" data="$ctrl.client" form="form" save="put"></vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card >
<vn-vertical pad-large>
<vn-title>Datos básicos</vn-title>
@ -29,9 +31,3 @@
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>
<vn-watcher
vn-id="watcher"
url="/client/api/Clients"
id-field="id"
data="$ctrl.client">
</vn-watcher>

View File

@ -1,12 +1,9 @@
import './style.css';
import template from './card.html';
import {module} from '../module';
import './style.css';
const _NAME = 'clientClient';
// export const NAME = module.getName(_NAME);
export const NAME = "vnClientCard";
export const NAME = 'vnClientCard';
export const COMPONENT = {
template: template,
template: require('./index.html'),
controllerAs: 'card',
controller: function($http, $stateParams) {
this.client = null;

View File

@ -1,34 +1,25 @@
export * from './module';
export {NAME as CLIENT_CARD,
COMPONENT as CLIENT_CARD_COMPONENT} from './card/card';
export {NAME as CLIENT_BASIC_DATA_INDEX,
COMPONENT as CLIENT_BASIC_DATA_INDEX_COMPONENT} from './basic-data/index';
export {NAME as CLIENT_ADDRESSES,
COMPONENT as CLIENT_ADDRESSES_COMPONENT} from './addresses/index';
COMPONENT as CLIENT_CARD_COMPONENT} from './card/index';
export {NAME as CLIENTS,
COMPONENT as CLIENTS_COMPONENT} from './index/index';
export {NAME as CLIENT_FISCAL_DATA_INDEX,
COMPONENT as CLIENT_FISCAL_DATA_INDEX_COMPONENT} from './fiscal-data/index';
export {NAME as CLIENT_DESCRIPTOR,
COMPONENT as CLIENT_DESCRIPTOR_COMPONENT} from './descriptor/descriptor';
COMPONENT as CLIENT_DESCRIPTOR_COMPONENT} from './descriptor/index';
export {NAME as CLIENT_NOTES,
COMPONENT as CLIENT_NOTES_COMPONENT} from './notes/index';
export {NAME as CLIENT_SEARCH_PANEL,
COMPONENT as CLIENT_SEARCH_PANEL_COMPONENT} from './search-panel/search-panel';
export {NAME as CLIENT_ITEM,
COMPONENT as CLIENT_ITEM_COMPONENT} from './index/item-client';
COMPONENT as CLIENT_SEARCH_PANEL_COMPONENT} from './search-panel/index';
export {NAME as CLIENT_CREATE,
COMPONENT as CLIENT_CREATE_COMPONENT} from './create/index';
export {NAME as CLIENT_ADDRESS_CREATE_INDEX,
COMPONENT as CLIENT_ADDRESS_CREATE_INDEX_COMPONENT} from './address-create/index';
export {NAME as CLIENT_ADDRESS_EDIT_INDEX,
COMPONENT as CLIENT_ADDRESS_EDIT_INDEX_COMPONENT} from './address-edit/index';
export {NAME as CLIENT_CONFIRM_INDEX,
COMPONENT as CLIENT_CONFIRM_INDEX_COMPONENT} from './confirm/index';
export {NAME as NEW_NOTE_INDEX,
COMPONENT as NEW_NOTE_INDEX_COMPONENT} from './new-note/index';
export {NAME as CLIENT_PAGING,
COMPONENT as CLIENT_PAGING_COMPONENT} from './index/index-paging';
import './addresses/index';
import './address-create/index';
import './basic-data/index';
import './web-access/index';

View File

@ -1,18 +0,0 @@
<dialog class="mdl-dialog ">
<vn-vertical class="mdl-dialog__content">
<h6 class="dialog-title">
¿Seguro que quieres salir sin guardar?
</h6>
<h6>
Los cambios que no hayas guardado se perderán
</h6>
</vn-vertical>
<vn-horizontal class="mdl-dialog__actions mdl-dialog__actions--full-width">
<button vn-one type="button" class="mdl-button close" ng-click="dialogConfirm.cancel()">Cancelar</button>
<button vn-one type="button" class="mdl-button" ng-click="dialogConfirm.accept()">Aceptar</button>
</vn-horizontal>
</dialog>
<vn-confirm id="confirm-changes" on-response="$ctrl.onConfirm(response)"
title="Are you sure exit without saving?"
message="Unsaved changes will be lost">
</vn-confirm>

View File

@ -1,30 +0,0 @@
import template from './index.html';
import {module} from '../module';
require('./style.css');
export const NAME = 'vnDialogConfirm';
export const COMPONENT = {
template: template,
controllerAs: 'dialogConfirm',
bindings: {
state: '<',
object: '=',
objectOld: "="
},
controller: function($state, $element, copyObject) {
var dialog = $element.find('dialog')[0];
this.accept = function() {
copyObject(this.objectOld, this.object);
$state.go(this.state);
dialog.close();
};
this.cancel = function() {
dialog.close();
};
}
};
COMPONENT.controller.$inject = ['$state', '$element', 'copyObject'];
module.component(NAME, COMPONENT);

View File

@ -1,5 +0,0 @@
.dialog-title{
color:#424242;
font-family: raleway-bold;
}

View File

@ -1,21 +1,22 @@
<div margin-medium>
<form name="form" ng-submit="form.$valid && create.onSubmit()" style="max-width: 70em; margin: 0 auto;">
<mg-ajax path="/client/api/Clients/{{put.params.id}}" options="vnPost"></mg-ajax>
<form name="form" ng-submit="$ctrl.onSubmit()" style="max-width: 70em; margin: 0 auto;">
<vn-card>
<vn-vertical pad-large>
<vn-title>Crear Cliente</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Nombre" field="create.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="NIF/CIF" field="create.client.fi"></vn-textfield>
<vn-textfield vn-one label="Nombre" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="NIF/CIF" field="$ctrl.client.fi"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Razón social" field="create.client.socialName"></vn-textfield>
<vn-textfield vn-one label="Razón social" field="$ctrl.client.socialName"></vn-textfield>
<vn-one></vn-one>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Crear y continuar"></vn-submit>
<vn-button label="Crear" ng-click="create.onCreate()"></vn-button>
<vn-button label="Crear" ng-click="$ctrl.onCreate()"></vn-button>
</vn-button-bar>
</form>
</div>
@ -23,5 +24,6 @@
vn-id="watcher"
url="/client/api/Clients"
id-field="id"
data="create.client">
data="$ctrl.client"
form="form">
</vn-watcher>

View File

@ -22,10 +22,8 @@ class Controller {
}
Controller.$inject = ['$scope', '$state', '$window'];
export const NAME = 'vnClientCreate';
export const COMPONENT = {
export const component = {
template: require('./index.html'),
controllerAs: 'create',
controller: Controller
};
module.component(NAME, COMPONENT);
module.component('vnClientCreate', component);

View File

@ -1,10 +1,9 @@
import './style.css';
import template from './descriptor.html';
import {module} from '../module';
import './style.css';
export const NAME = 'vnDescriptor';
export const COMPONENT = {
template: template,
template: require('./index.html'),
controllerAs: 'descriptor',
bindings: {
client: '<'

View File

@ -1,4 +1,4 @@
<form name="form" ng-submit="form.$valid && watcher.submit()" pad-medium>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Datos fiscales y de facturación</vn-title>
@ -69,5 +69,7 @@
vn-id="watcher"
url="/client/api/Clients"
id-field="id"
data="fiscal.client">
form="form"
data="fiscal.client"
form="form">
</vn-watcher>

View File

@ -1,13 +0,0 @@
<div vn-horizontal>
<vn-one style="text-align:center;">
<paging page="pagingClient.currentPage"
page-size="pagingClient.numPerPage"
total="pagingClient.numPages"
show-prev-next="true"
show-first-last="true"
ul-class="pagination"
active-class="active"
ng-click="pagingClient.figureOutToDisplay()">
</paging>
</vn-one>
</div>

View File

@ -1,64 +0,0 @@
import template from './index-paging.html';
import {module} from '../module';
export const NAME = 'vnPagingClient';
export const COMPONENT = {
template: template,
bindings: {
filter: '<',
clients: '='
},
controllerAs: 'pagingClient',
controller: function($http) {
var self = this;
let queryStr = '/client/api/Clients';
let where = this.filter;
let numPerPage = 3;
this.$onChanges = function(changes) {
if (Object.keys(self.filter).length > 0){
where = self.filter;
this.getClients();
this.getNumPages();
}
else {
where = "";
this.currentPage = 1;
this.getAllClients();
}
this.figureOutToDisplay();
};
this.getNumPages = () => {
var nPages = self.clients.length / numPerPage;
this.numPages = Math.ceil(nPages);
};
this.figureOutToDisplay = () => {
var begin = ((this.currentPage - 1) * numPerPage);
this.getClients(begin);
};
this.getAllClients = () => {
$http.get(queryStr).then(
json => {
self.clients = json.data;
this.getNumPages();
}
);
};
this.getClients = function(end) {
var json = JSON.stringify({where: where, limit: numPerPage, skip: end});
self.queryStr = `${queryStr}?filter=${json}`;
$http.get(self.queryStr).then(
json => {
self.clients = json.data;
}
);
};
}
};
COMPONENT.controller.$inject = ['$http'];
module.component(NAME, COMPONENT);

View File

@ -1,25 +1,21 @@
<!--
<mg-ajax path="/client/api/Clients" options="mgIndex as index"></mg-ajax>
<button ng-click="index.accept()"></button>
<span ng-repeat="client in index.model">{{client}}</span>
-->
<mg-ajax path="/client/api/Clients/filter" options="vnIndex"></mg-ajax>
<div margin-medium>
<div style="max-width: 40em; margin: 0 auto;">
<vn-card>
<vn-horizontal pad-medium>
<vn-searchbar
vn-auto model="search.filter"
search="search.find()"
vn-auto
index="index"
on-search="index.accept()"
advanced="true"
popover="vn-client-search-panel">
</vn-searchbar>
</vn-horizontal>
</vn-card>
<vn-card margin-medium-top>
<vn-item-client ng-repeat="client in search.clients" title="View client" client="client"></vn-item-client>
<vn-item-client ng-repeat="client in index.model" title="View client" client="client"></vn-item-client>
</vn-card>
<vn-paging-client clients="search.clients" filter="search.filter"></vn-paging-client>
<vn-paging index="index"></vn-paging>
</div>
<a ui-sref="create" fixed-bottom-right>
<vn-float-button icon="person_add"></vn-float-button>

View File

@ -1,47 +1,9 @@
import template from './index.html';
import {module} from '../module';
require('./style.css');
import './style.css';
import './item-client';
export const NAME = 'vnClientIndex';
export const COMPONENT = {
template: template,
controllerAs: 'search',
controller: function($http) {
var self = this;
this.find = function() {
var where = {};
var filter = this.filter;
var search = filter.search;
if (search)
where = {name: {ilike: search}};
var params = filter.params;
if (params) {
where = {};
let partials = {
alias: true,
name: true,
socialName: true,
city: true,
email: true
};
for (let param in params)
if (params[param]) {
if (partials[param])
where[param] = {ilike: params[param]};
else
where[param] = params[param];
}
filter.params = undefined;
}
if (where) {
self.filter = where;
}
};
this.filter = {};
this.find();
}
template: require('./index.html')
};
COMPONENT.controller.$inject = ['$http'];
module.component(NAME, COMPONENT);

View File

@ -1,9 +1,8 @@
import template from './item-client.html';
import {module} from '../module';
export const NAME = 'vnItemClient';
export const COMPONENT = {
template: template,
template: require('./item-client.html'),
controllerAs: 'itemClient',
bindings: {
client: '<'

View File

@ -1,4 +1,4 @@
<form name="form" ng-submit="form.$valid && newNote.onSubmit()" pad-medium>
<form name="form" ng-submit="newNote.onSubmit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Nueva nota</vn-title>
@ -13,5 +13,6 @@
vn-id="watcher"
url="/client/api/ClientObservations"
id-field="id"
data="newNote.note">
data="newNote.note"
form="form">
</vn-watcher>

View File

@ -1,5 +1,5 @@
<div pad-large style="min-width: 30em;" ng-show="$ctrl.formVisibility">
<form name="form" ng-submit="form.$valid && $ctrl.onSubmit()" ng-keyup="$ctrl.getKeyPressed($event)">
<form name="form" ng-submit="form.$valid && $ctrl.onSubmit($ctrl.filter)" ng-keyup="$ctrl.getKeyPressed($event)">
<vn-horizontal>
<vn-textfield vn-one label="Id Cliente" model="$ctrl.filter.id" vn-focus></vn-textfield>
<vn-textfield vn-one label="NIF/CIF" model="$ctrl.filter.fi"></vn-textfield>

View File

@ -2,7 +2,7 @@ import {module} from '../module';
export const NAME = 'vnClientSearchPanel';
export const COMPONENT = {
template: require('./search-panel.html'),
template: require('./index.html'),
controller: function($scope) {
this.formVisibility = true;
this.onSubmit = function() {};

View File

@ -1,4 +1,4 @@
<form name="form" ng-submit="form.$valid && watcher.submit()" pad-medium>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Web access</vn-title>
@ -16,7 +16,8 @@
id-field="id"
url="/client/api/Accounts"
watch="$ctrl.client.account"
to="$ctrl.account">
to="$ctrl.account"
form="form">
</vn-watcher>
<vn-dialog
vn-id="change-pass"

View File

@ -1,14 +1,10 @@
<div class="mdl-textfield mdl-js-textfield *[className]*">
<input
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input type="text"
class="mdl-textfield__input"
type="text"
rule="*[rule]*"
*[enabled]*
*[focus]*
ng-keydown="$ctrl.onKeydown($event)"
ng-click="$ctrl.onClick($event)"
ng-keyup="$ctrl.onKeyup($event)"
ng-focus="$ctrl.onFocus($event)"
ng-blur="$ctrl.onBlur($event)"/>
<label class="mdl-textfield__label" translate>*[label]*</label>
<label class="mdl-textfield__label">{{$ctrl.label | translate}}</label>
</div>

View File

@ -1,7 +1,5 @@
import {module} from '../module';
import template from '../lib/template';
import Component from '../lib/component';
import './index.mdl';
import './style.scss';
/**
@ -9,7 +7,7 @@ import './style.scss';
* features.
*/
export default class Autocomplete extends Component {
constructor($element, $attrs, $scope, $http, $parse, vnPopover) {
constructor($element, $scope, $http, vnPopover) {
super($element);
this.input = $element[0].querySelector('input');
this.item = null;
@ -26,22 +24,20 @@ export default class Autocomplete extends Component {
this.requestDelay = 350;
this.locked = false;
this.$http = $http;
this.$attrs = $attrs;
this.$scope = $scope;
this.$parse = $parse;
this.vnPopover = vnPopover;
$scope.$watch('$ctrl.model', newValue => {
if(!this.locked) {
this.locked = true;
this.setValue(newValue);
this.locked = false;
}
});
componentHandler.upgradeElement($element[0].firstChild);
this.requestItem();
}
set field(value) {
this.locked = true;
this.setValue(value);
this.locked = false;
}
get field() {
return this.value;
}
mdlUpdate() {
let mdlField = this.element.firstChild.MaterialTextfield;
if (mdlField)
@ -251,10 +247,10 @@ export default class Autocomplete extends Component {
this.putItem(this.item);
}
requestItem() {
if(!this.model) return;
if(!this.value) return;
let where = {};
where[this.valueField] = this.model;
where[this.valueField] = this.value;
let filter = {
fields: this.getRequestFields(),
@ -307,6 +303,8 @@ export default class Autocomplete extends Component {
this.activeOption = index;
}
setValue(value) {
this.value = value;
if(value) {
let data = this.data;
@ -336,7 +334,7 @@ export default class Autocomplete extends Component {
let value = item ? item[this.valueField] : undefined;
if(!this.locked) {
this.model = value;
this.value = value;
setTimeout (
() => this.$scope.$apply());
}
@ -347,15 +345,16 @@ export default class Autocomplete extends Component {
this.mdlUpdate();
}
}
Autocomplete.$inject = ['$element', '$attrs', '$scope', '$http', '$parse', 'vnPopover'];
Autocomplete.$inject = ['$element', '$scope', '$http', 'vnPopover'];
module.component('vnAutocomplete', {
template: require('./index.html'),
bindings: {
url: '@',
showField: '@',
valueField: '@',
model: '='
field: '=',
label: '@'
},
template: template,
controller: Autocomplete
});

View File

@ -1,11 +0,0 @@
import {module} from '../module';
export function factory() {
return {
template: require('./index.mdl.html'),
default: {
className: 'mdl-textfield--floating-label'
}
}
}
module.factory('vnAutocompleteMdlFactory', factory);

View File

@ -1,6 +1,7 @@
import './mdl-override.css';
import './watcher/index';
import './paging/index';
import './icon/index';
import './autocomplete/index';
import './popover/index';
@ -8,6 +9,7 @@ import './dialog/index';
import './confirm/index';
import './title/index';
import './subtitle/index';
import './spinner/index';
export {NAME as BUTTON, directive as ButtonDirective} from './button/button';
export {NAME as BUTTON_MDL, factory as buttonMdl} from './button/button.mdl';
@ -29,8 +31,6 @@ export {NAME as SUBMIT, directive as SubmitDirective} from './submit/submit';
export {NAME as SUBMIT_MDL, factory as submitMdl} from './submit/submit.mdl';
export {NAME as SNACKBAR, directive as SnackbarDirective} from './snackbar/snackbar';
export {NAME as SNACKBAR_MDL, factory as snackbarMdl} from './snackbar/snackbar.mdl';
export {NAME as SPINNER, directive as SpinnerDirective} from './spinner/spinner';
export {NAME as SPINNER_MDL, factory as spinnerMdl} from './spinner/spinner.mdl';
export {NAME as COMBO, directive as ComboDirective} from './combo/combo';
export {NAME as COMBO_MDL, factory as comboMdl} from './combo/combo.mdl';
export {NAME as DATE_PICKER, directive as DatePickerDirective} from './date-picker/date-picker';

View File

@ -8,7 +8,6 @@ import {kebabToCamel} from '../lib/string';
export function directive() {
return {
restrict: 'A',
require: [],
link: function($scope, $element, $attrs) {
let id = kebabToCamel($attrs.vnId);
let controller = $element[0].$ctrl;

View File

@ -0,0 +1,33 @@
import {module} from '../module';
index.$inject = ['mgIndex'];
function index(mgIndex) {
return angular.extend(mgIndex, {
init: 'index.filter={page: 1, size: 4}'
});
}
module.factory('vnIndex', index);
successFactoryCreate.$inject = ['mgSuccessFactoryCreate'];
function successFactoryCreate(create) {
return angular.extend(create, {
back: undefined
});
}
module.factory('vnSuccessFactoryCreate', successFactoryCreate);
put.$inject = ['mgPut'];
function put(mgPut) {
return angular.extend(mgPut, {
success: 'vnSuccessFactoryCreate'
});
}
module.factory('vnPut', put);
post.$inject = ['mgCreate'];
function post(mgCreate) {
return angular.extend(mgCreate, {
success: 'vnSuccessFactoryCreate'
});
}
module.factory('vnPost', post);

View File

@ -0,0 +1,7 @@
import {kebabToCamel} from './string';
getTemplate.$inject = ['$element', '$attrs', 'vnResolveDefaultComponent'];
export default function getTemplate($element, $attrs, resolve) {
let templateName = kebabToCamel($element[0].tagName.toLowerCase().substr(3));
return resolve.getTemplate(templateName, $attrs);
}

View File

@ -1,4 +1,7 @@
import './moduleLoader';
import './crud';
import './template';
import './getTemplate';
export * from './util';
export {default as splitingRegister} from './splitingRegister';

View File

@ -1,4 +1,3 @@
/**
* Transforms a kebab-case string to camelCase. A kebab-case string
* is a string with hyphen character as word separator.

View File

@ -0,0 +1,10 @@
<paging
page="$ctrl.currentPage"
page-size="$ctrl.numPerPage"
total="$ctrl.numItems"
show-prev-next="true"
show-first-last="false"
active-class="active"
ng-click="$ctrl.figureOutToDisplay()"
paging-action="$ctrl.onPageChange(page)">
</paging>

View File

@ -0,0 +1,43 @@
import {module} from '../module';
import './style.scss';
export default class Paging {
get numPages() {
return Math.ceil(this.numItems / this.numPerPage);
}
constructor($http, $scope) {
this.$http = $http;
this.$scope = $scope;
this.where = this.filter;
this.numPerPage = null;
this.numItems = 0;
$scope.$watch('$ctrl.index.model.length', () => this.onModelUpdated());
}
$onChanges() {
if(!this.index) return;
this.numPerPage = this.index.filter.size;
}
onModelUpdated() {
let index = this.index;
let filter = index.filter;
if(filter.page >= this.numPages
&& index.model.length >= this.numPerPage)
this.numItems = filter.page * filter.size + 1;
}
onPageChange(page) {
this.index.filter.page = page;
this.index.accept();
}
}
Paging.$inject = ['$http', '$scope'];
export const NAME = 'vnPaging';
export const COMPONENT = {
template: require('./index.html'),
bindings: {
index: '<'
},
controller: Paging
};
module.component(NAME, COMPONENT);

View File

@ -0,0 +1,38 @@
vn-paging {
display: block;
text-align: center;
ul {
box-shadow: 0 0 .4em rgba(1,1,1,.4);
background-color: #fff;
display: inline-block;
margin: 20px 0;
border-radius: .1em;
padding: 0;
}
li {
display: inline;
&:first-child > a,
&:first-child > span {
margin-left: 0;
}
&.active > a {
background: #3c393b;
color: #fff;
}
& > a,
& > span {
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px;
line-height: 1.42857143;
color: inherit;
text-decoration: none;
}
&:not(.active) > a:hover {
background-color: #ddd;
}
}
}

View File

@ -0,0 +1,2 @@
<div class="mdl-spinner mdl-spinner--single-color mdl-js-spinner">
</div>

View File

@ -0,0 +1,57 @@
import {module} from '../module';
import Component from '../lib/component';
/**
* A spinner to inform the user about loading process.
*/
export default class Spinner extends Component {
constructor($element, $scope) {
super($element);
this._enable = false;
this.spinner = $element[0].firstChild;
componentHandler.upgradeElement(this.spinner);
}
/**
* Enables/disables the spinner.
*
* @param {Boolean} %true to enable, %false to disable
*/
set enable(value) {
if (value)
this.start();
else
this.stop();
}
/**
* Returns the current spinner state.
*
* @return {Boolean} %true if it's enabled, %false otherwise
*/
get enable() {
return this._enable;
}
/**
* Activates the spinner.
*/
start() {
this.spinner.MaterialSpinner.start();
this._enable = true;
}
/**
* Deactivates the spinner.
*/
stop() {
this.spinner.MaterialSpinner.stop();
this._enable = false;
}
}
Spinner.$inject = ['$element', '$scope'];
export const component = {
template: require('./index.html'),
bindings: {
enable: '='
},
controller: Spinner
};
module.component('vnSpinner', component);

View File

@ -1,41 +0,0 @@
import {module} from '../module';
import * as resolveFactory from '../lib/resolveDefaultComponents';
import * as util from '../lib/util';
const _NAME = 'spinner';
export const NAME = util.getName(_NAME);
directive.$inject = [resolveFactory.NAME];
export function directive(resolve) {
return {
restrict: 'E',
scope: {
enable: '='
},
template: function(element, attrs) {
return resolve.getTemplate(_NAME, attrs);
},
controller: controller
}
}
module.directive(NAME, directive);
controller.$inject = ['$scope', '$element'];
function controller($scope, $element) {
let spinner = $element[0].firstChild;
componentHandler.upgradeElement (spinner);
this.start = function() {
spinner.MaterialSpinner.start();
};
this.stop = function() {
spinner.MaterialSpinner.stop();
};
$scope.$watch('enable', newValue => {
if (newValue)
this.start();
else
this.stop();
});
}

View File

@ -1,4 +0,0 @@
<div
class="mdl-spinner mdl-spinner--single-color mdl-js-spinner"
*[foo]*>
</div>

View File

@ -1,9 +0,0 @@
import {module} from '../module';
export const NAME = 'vnSpinnerMdlFactory';
export function factory() {
return {
template: require('./spinner.mdl.html')
}
}
module.factory(NAME, factory);

View File

@ -47,15 +47,31 @@ export default class Watcher extends Component {
});
}
submit() {
if (!this.dataChanged()) {
this.vnAppLogger.showMessage(
this.$translate.instant('No changes to save')
if(this.form && !this.form.$valid) {
return new Promise (
(resolve, reject) => this.invalidForm(reject)
);
return;
}
if (!this.dataChanged()) {
return new Promise (
(resolve, reject) => this.noChanges(reject)
);
}
let changedData = getModifiedData(this.data, this.orgData);
if(this.save) {
this.save.model = changedData;
return new Promise((resolve, reject) => {
this.save.accept().then(
json => this.writeData({data: json}, resolve),
json => reject(json)
);
});
}
// XXX: Alternative when mgCrud is not used
let id = this.orgData[this.idField];
let changedData = getModifiedData(this.data, this.orgData);
if(id) {
return new Promise((resolve, reject) => {
@ -79,6 +95,18 @@ export default class Watcher extends Component {
this.copyData();
resolve(json);
}
noChanges(resolve) {
this.vnAppLogger.showMessage(
this.$translate.instant('No changes to save')
);
resolve();
}
invalidForm(resolve) {
this.vnAppLogger.showMessage(
this.$translate.instant('Some fields are invalid')
);
resolve();
}
copyData() {
this.orgData = copyObject(this.data);
}
@ -112,6 +140,8 @@ module.component('vnWatcher', {
url: '@?',
idField: '@?',
data: '<',
form: '<',
save: '<',
get: '=?'
},
controller: Watcher

View File

@ -1,5 +1,6 @@
{
"Are you sure exit without saving?": "¿Seguro que quieres salir sin guardar?",
"Unsaved changes will be lost": "Los cambios que no hayas guardado se perderán",
"No changes to save": "No hay cambios que guardar"
"No changes to save": "No hay cambios que guardar",
"Some fields are invalid": "Algunos campos no son válidos"
}

View File

@ -1,4 +1,4 @@
<form ng-submit="$ctrl.search()">
<form ng-submit="$ctrl.onSubmit()">
<vn-horizontal>
<vn-textfield vn-one label="Search" model="$ctrl.model.search"></vn-textfield>
<vn-icon

View File

@ -1,34 +1,46 @@
import {module} from '../../module';
require('./style.css');
class Controller {
constructor($element, $scope, $document, $compile, vnPopover) {
this.element = $element[0];
this.$scope = $scope;
this.$document = $document;
this.$compile = $compile;
this.vnPopover = vnPopover;
}
onClick(event) {
var child = this.$document[0].createElement(this.popover);
this.$compile(child)(this.$scope);
this.vnPopover.show(child, this.element);
// XXX: ¿Existe una forma más adecuada de acceder al controlador de un componente?
var childCtrl = angular.element(child).isolateScope().$ctrl;
childCtrl.onSubmit = (filter) => this.onChildSubmit(filter);
event.preventDefault();
}
onChildSubmit(filter) {
this.vnPopover.hide();
this.index.filter = filter;
this.onSubmit();
}
onSubmit() {
if(this.onSearch)
this.onSearch();
}
}
Controller.$inject = ['$element', '$scope', '$document', '$compile', 'vnPopover'];
export const NAME = 'vnSearchbar'
export const COMPONENT = {
template: require('./searchbar.html'),
bindings: {
model: '<',
search: '&',
index: '<',
onSearch: '&',
advanced: '=',
popover: '@'
},
controller: controller
controller: Controller
};
module.component(NAME, COMPONENT);
controller.$inject = ['$element', '$scope', '$document', '$compile', 'vnPopover'];
function controller($element, $scope, $document, $compile, popover) {
this.onClick = function(event) {
var child = $document[0].createElement(this.popover);
$compile(child)($scope);
popover.show(child, $element[0]);
// XXX: ¿Existe una forma más adecuada de acceder al controlador de un componente?
var childCtrl = angular.element(child).isolateScope().$ctrl;
childCtrl.onSubmit = () => {
popover.hide();
this.model.params = childCtrl.filter;
this.search();
};
event.preventDefault();
};
}

View File

@ -1,52 +1,15 @@
.display-block{
display: block;
}
/*angular-paging*/
.well {
min-height: 20px;
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}
.pagination {
display: inline-block;
padding-left: 0;
margin: 20px 0;
border-radius: 4px;
}
.pagination > li {
display: inline;
}
.pagination > li > a,
.pagination > li > span {
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px;
line-height: 1.42857143;
color: #337ab7;
text-decoration: none;
background-color: #fff;
border: 1px solid #ddd;
}
.pagination > li:first-child > a,
.pagination > li:first-child > span {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
min-height: 20px;
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}
.form-group {
margin-bottom: 15px;
margin-bottom: 15px;
}
.pagination>li.active>a {
background: #4f94ce;
color: #fff;
}
ul.pagination li a:hover:not(.active) {background-color: #ddd;}
/* fin angular-paging*/

16
db.json
View File

@ -2,10 +2,10 @@
"ids": {
"User": 2,
"AccessToken": 2,
"Client": 20,
"Client": 22,
"PaymentMethod": 4,
"SalesPerson": 11,
"Address": 83,
"Address": 86,
"Country": 10,
"Province": 44,
"Agency": 4,
@ -20,11 +20,12 @@
"NUf7o684TmteojFX9KmPOpaDLthjP5Def4wuy83Yjv31i43HHiWgV3FyBp6pX8Ue": "{\"id\":\"NUf7o684TmteojFX9KmPOpaDLthjP5Def4wuy83Yjv31i43HHiWgV3FyBp6pX8Ue\",\"ttl\":1209600,\"created\":\"2016-11-21T11:06:11.113Z\",\"userId\":1}"
},
"Client": {
"12": "{\"name\":\"Verdnatura\",\"id\":12,\"fi\":\"B97367486\",\"salesPerson\":8,\"telefono\":\"963242100\",\"socialName\":\"Verdnatura Levante SL\",\"active\":true,\"user\":\"verdnatura\",\"fax\":\"963242100\",\"phone\":\"963242101\",\"email\":\"informatica@verdnatura.es\",\"surcharge\":true,\"cyc\":321,\"credit\":1000,\"iban\":\"456\",\"street\":\"Avenida Espioca, 100\",\"city\":\"Silla\",\"postcode\":\"12345678\",\"mobile\":\"654654654\",\"dueDay\":321,\"gestdoc\":23452343,\"province\":1,\"country\":1,\"modify\":\"BasicData\",\"navigate\":true,\"payMethod\":\"1\",\"coreVnh\":true,\"coreVnl\":true,\"invoiceByEmail\":true}",
"14": "{\"name\":\"Leopoldo\",\"id\":14,\"street\":\"Casa\",\"fi\":\"1234567890A\",\"socialName\":\"Leopoldo\",\"fax\":\"963242100\",\"dischargeDate\":\"01/01/2017\",\"telefono\":\"963242100\",\"salesPerson\":\"2\",\"email\":\"informatica@verdnatura.es\",\"city\":\"Benicull\",\"postcode\":\"123\",\"phone\":\"963215469\",\"mobile\":\"667985632\",\"credit\":2345,\"cyc\":56,\"iban\":\"asdf\",\"dueDay\":345,\"gestdoc\":2435,\"surcharge\":true,\"navigate\":true,\"province\":6,\"country\":2,\"payMethod\":1}",
"12": "{\"name\":\"Verdnatura\",\"id\":12,\"fi\":\"B97367486\",\"salesPerson\":8,\"telefono\":\"963242100\",\"socialName\":\"Verdnatura Levante SL\",\"active\":true,\"user\":\"verdnatura\",\"fax\":\"963242100\",\"phone\":\"963242101\",\"email\":\"informatica@verdnatura.es\",\"surcharge\":true,\"cyc\":321,\"credit\":1000,\"iban\":\"456\",\"street\":\"Avenida Espioca, 100\",\"city\":\"Silla\",\"postcode\":\"1234567\",\"mobile\":\"654654654\",\"dueDay\":321,\"gestdoc\":23452343,\"province\":3,\"country\":1,\"modify\":\"BasicData\",\"navigate\":true,\"payMethod\":\"1\",\"coreVnh\":true,\"coreVnl\":true,\"invoiceByEmail\":true}",
"14": "{\"name\":\"Leopoldo Martin\",\"id\":14,\"street\":\"Casa\",\"fi\":\"1234567890A\",\"socialName\":\"Leopoldo\",\"fax\":\"963242100\",\"dischargeDate\":\"01/01/2017\",\"telefono\":\"963242100\",\"salesPerson\":\"2\",\"email\":\"leomartin@verdnatura.es\",\"city\":\"Benicull\",\"postcode\":\"123\",\"phone\":\"963215469\",\"mobile\":\"667985632\",\"credit\":2345,\"cyc\":56,\"iban\":\"asdf\",\"dueDay\":345,\"gestdoc\":2435,\"surcharge\":true,\"navigate\":true,\"province\":6,\"country\":2,\"payMethod\":1}",
"15": "{\"name\":\"Florsiteria Pepa\",\"fi\":\"12341234rasf\",\"socialName\":\"asdfasd\",\"dueDay\":5,\"id\":15,\"payMethod\":\"2\",\"salesPerson\":\"1\",\"modify\":\"BasicData\",\"iban\":\"sdfgsdfgsdfg\",\"email\":\"pepa@flores.es\",\"phone\":\"963242101\",\"city\":\"Alfarp\",\"postcode\":\"46985\"}",
"16": "{\"name\":\"Floristeria Antonieta\",\"fi\":\"2345234523d\",\"socialName\":\"23452345assdfgsdfgt\",\"active\":true,\"dueDay\":5,\"id\":16,\"modify\":\"FiscalData\",\"email\":\"antonieta@gmail.com\",\"phone\":\"654654654\",\"mobile\":\"654456456\",\"fax\":\"456456456\",\"street\":\"asdfasdf\",\"salesPerson\":8,\"city\":\"Albalat de la Ribera\",\"postcode\":\"46532\"}",
"19": "{\"name\":\"Planticas Eustaquio\",\"fi\":\"789456123B\",\"socialName\":\"Eustaquio Martinez\",\"active\":true,\"dueDay\":5,\"id\":19,\"email\":\"peustaquio@hotmail.es\",\"city\":\"Polinya\",\"postcode\":\"46231\",\"phone\":\"963215486\"}"
"19": "{\"name\":\"Planticas Eustaquio\",\"fi\":\"789456123B\",\"socialName\":\"Eustaquio Martinez\",\"active\":true,\"dueDay\":5,\"id\":19,\"email\":\"peustaquio@hotmail.es\",\"city\":\"Polinya\",\"postcode\":\"46231\",\"phone\":\"963215486\"}",
"21": "{\"name\":\"Ramos Antonieta\",\"fi\":\"B89564289\",\"socialName\":\"Antonia SL\",\"active\":true,\"dueDay\":5,\"id\":21,\"email\":\"ramos@rantonieta.es\",\"salesPerson\":8,\"phone\":\"986574232\"}"
},
"PaymentMethod": {
"1": "{\"name\":\"Tarjeta\",\"id\":1}",
@ -54,7 +55,10 @@
"79": "{\"street\":\"a\",\"consignee\":\"a\",\"city\":\"a\",\"enabled\":true,\"id\":79}",
"80": "{\"street\":\"b\",\"consignee\":\"a\",\"city\":\"c\",\"enabled\":true,\"id\":80}",
"81": "{\"street\":\"street\",\"consignee\":\"consignee\",\"city\":\"city\",\"enabled\":true,\"id\":81}",
"82": "{\"street\":\"Polígono\",\"consignee\":\"Verdnatura Levante SL\",\"city\":\"Picasent\",\"enabled\":true,\"client\":12,\"id\":82,\"province\":1,\"agency\":2,\"phone\":\"963242100\"}"
"82": "{\"street\":\"Polígono\",\"consignee\":\"Verdnatura Levante SL\",\"city\":\"Picasent\",\"enabled\":true,\"client\":12,\"id\":82,\"province\":1,\"agency\":2,\"phone\":\"963242100\",\"mobile\":\"698357618\"}",
"83": "{\"street\":\"San Nicolau de Bari, 6\",\"consignee\":\"Casa\",\"city\":\"Algemesi\",\"postcode\":\"46680\",\"enabled\":true,\"phone\":\"962480949\",\"client\":12,\"province\":1,\"agency\":1,\"id\":83,\"mobile\":\"658965745\"}",
"84": "{\"street\":\"Poligono\",\"consignee\":\"Madrid\",\"city\":\"Madrid\",\"enabled\":true,\"client\":12,\"id\":84,\"postcode\":\"46001\",\"province\":2,\"agency\":2,\"phone\":\"912356489\",\"mobile\":\"654236589\"}",
"85": "{\"street\":\"Avenida la Parra, 43\",\"consignee\":\"Ramos Antonienta\",\"city\":\"Llombai\",\"enabled\":true,\"client\":21,\"province\":1,\"id\":85,\"phone\":\"965874583\",\"mobile\":\"675418958\",\"postcode\":\"46985\",\"agency\":2,\"default\":true}"
},
"Country": {
"1": "{\"id\":1,\"name\":\"Spain\"}",

View File

@ -1,5 +1,4 @@
module.exports = function(Address) {
Address.validate('default',isEnabled,{message: 'No se puede poner predeterminado un consignatario desactivado'});
function isEnabled(err) {
if (!this.enabled && this.default) err();
@ -52,14 +51,11 @@ module.exports = function(Address) {
Address.update({client: cl}, {default: false});
}
function generateErrorDefaultAddress(){
var error = new Error();
error.message = "No se puede desmarcar el consignatario predeterminado";
error.status = 500;
return error;
}
};

View File

@ -37,10 +37,15 @@
}
},
"relations": {
"country":{
"country": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "id"
},
"client": {
"type": "hasOne",
"model": "Client",
"foreignKey": "id"
}
}
}

View File

@ -1,3 +1,5 @@
let installMethod = require('../util.js');
module.exports = function(Client) {
// Validations
@ -73,87 +75,34 @@ module.exports = function(Client) {
})
};
// Filters
// Basic filter
let Model = Client;
let fields = {
id: false,
fi: false,
name: true,
socialName: true,
city: true,
postcode: false,
email: true,
phone: false
};
Model.remoteMethod('filter', {
description: 'List items using a filter',
accessType: 'READ',
accepts: {
arg: 'filter',
type: 'object',
description: 'Filter defining where'
},
returns: {
arg: 'data',
type: 'Client',
root: true
},
http: {
path: '/filter',
verb: 'get'
}
});
Model.filter = function(filter, cb) {
filter = removeEmpty(filter);
let where = {};
if(filter)
for (let field in fields)
if(filter[field]) {
if(fields[field])
where[field] = {ilike: filter[field]};
else
where[field] = filter[field];
}
Model.find({where: where}, function(err, instances) {
if(!err) {
cb(null, instances);
}
})
};
function removeEmpty(o) {
if(Array.isArray(o)) {
let array = [];
for(let item of o) {
let i = removeEmpty(item);
if(!isEmpty(item))
array.push(item);
};
if(array.length > 0)
return array;
}
else if (typeof o === 'object') {
let object = {};
for(let key in o) {
let i = removeEmpty(o[key]);
if(!isEmpty(i))
object[key] = i;
}
if(Object.keys(object).length > 0)
return object;
}
else if (!isEmpty(o))
return o;
return undefined;
installMethod(Client, 'filter', filterClients);
function filterClients(p){
return {
where: {
id: p.id,
name: {ilike: p.name},
cif: p.cif,
socialName: {ilike: p.socialName},
city: {ilike: p.city},
postcode: p.postcode,
email: {ilike: p.email},
phone: p.phone
},
skip: (p.page - 1) * p.size,
limit: p.size
};
}
function isEmpty(value) {
return value === undefined || value === "";
installMethod(Client, 'filter', filterAddresses);
function filterAddresses(p){
return {
where: {
client: p.client,
},
skip: (p.page - 1) * p.size,
limit: p.size
};
}
};

View File

@ -86,6 +86,11 @@
"type": "hasOne",
"model": "Account",
"foreignKey": "id"
},
"addresses": {
"type": "hasMany",
"model": "Address",
"foreignKey": "client"
}
},
"scopes": {

View File

@ -0,0 +1,69 @@
module.exports = installMethod;
function installMethod(Model, methodName, filterCb) {
Model.remoteMethod(methodName, {
description: 'List items using a filter',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'object',
required: true,
description: 'Filter defining where',
http: function(ctx) {
return ctx.req.query;
}
}
],
returns: {
arg: 'data',
type: [Model.modelName],
root: true
},
http: {
path: `/${methodName}`,
verb: 'get'
}
});
Model.filter = function(params, cb) {
let filter = removeEmpty(filterCb(params));
Model.find(filter, function(err, instances) {
if(!err) {
cb(null, instances);
}
})
};
}
function removeEmpty(o) {
if(Array.isArray(o)) {
let array = [];
for(let item of o) {
let i = removeEmpty(item);
if(!isEmpty(item))
array.push(item);
};
if(array.length > 0)
return array;
}
else if (typeof o === 'object') {
let object = {};
for(let key in o) {
let i = removeEmpty(o[key]);
if(!isEmpty(i))
object[key] = i;
}
if(Object.keys(object).length > 0)
return object;
}
else if (!isEmpty(o))
return o;
return undefined;
}
function isEmpty(value) {
return value === undefined || value === "";
}