From 8ff61a5d3abfaf543417cf6ce965eaf514aa7462 Mon Sep 17 00:00:00 2001 From: Joan Sanchez Date: Wed, 26 Jun 2019 13:35:38 +0200 Subject: [PATCH] added zone prices, radio-group and calendar refactor #1320 --- back/methods/dms/downloadFile.js | 3 +- back/methods/dms/uploadFile.js | 1 - db/changes/10060-verano/00-zoneCalendar.sql | 3 + front/core/components/calendar/index.html | 48 ++-- front/core/components/calendar/index.js | 64 +++-- front/core/components/calendar/index.spec.js | 21 +- front/core/components/calendar/style.scss | 218 ++++++++---------- front/core/components/dialog/dialog.js | 5 +- front/core/components/dialog/dialog.spec.js | 8 +- front/core/components/index.js | 2 +- front/core/components/input-number/index.html | 5 +- front/core/components/input-number/index.js | 1 + .../components/radio-group/radio-group.html | 8 + .../components/radio-group/radio-group.js | 41 ++++ front/core/components/radio-group/style.scss | 11 + front/core/components/radio/radio.html | 7 - front/core/components/radio/radio.js | 15 -- .../core/components/textfield/textfield.html | 2 +- front/salix/styles/variables.scss | 2 + .../agency/back/methods/zone/editPrices.js | 81 +++++++ .../methods/zone/specs/editPrices.spec.js | 84 +++++++ modules/agency/back/models/zone-calendar.json | 6 + modules/agency/back/models/zone.js | 1 + modules/agency/front/calendar/index.html | 79 +++++-- modules/agency/front/calendar/index.js | 172 +++++++------- modules/agency/front/calendar/locale/es.yml | 6 + modules/agency/front/calendar/style.scss | 3 + modules/agency/front/index.js | 1 + modules/agency/front/location/calendar.html | 21 ++ modules/agency/front/location/calendar.js | 150 ++++++++++++ modules/agency/front/location/index.html | 2 +- modules/agency/front/routes.json | 40 ++-- modules/worker/front/calendar/index.html | 2 +- modules/worker/front/calendar/index.js | 14 +- modules/worker/front/calendar/index.spec.js | 6 +- modules/worker/front/calendar/style.scss | 10 +- 36 files changed, 816 insertions(+), 327 deletions(-) create mode 100644 db/changes/10060-verano/00-zoneCalendar.sql create mode 100644 front/core/components/radio-group/radio-group.html create mode 100644 front/core/components/radio-group/radio-group.js create mode 100644 front/core/components/radio-group/style.scss delete mode 100644 front/core/components/radio/radio.html delete mode 100644 front/core/components/radio/radio.js create mode 100644 modules/agency/back/methods/zone/editPrices.js create mode 100644 modules/agency/back/methods/zone/specs/editPrices.spec.js create mode 100644 modules/agency/front/calendar/locale/es.yml create mode 100644 modules/agency/front/calendar/style.scss create mode 100644 modules/agency/front/location/calendar.html create mode 100644 modules/agency/front/location/calendar.js diff --git a/back/methods/dms/downloadFile.js b/back/methods/dms/downloadFile.js index 523bd39bd..72090f33b 100644 --- a/back/methods/dms/downloadFile.js +++ b/back/methods/dms/downloadFile.js @@ -1,5 +1,4 @@ const UserError = require('vn-loopback/util/user-error'); -const fs = require('fs-extra'); module.exports = Self => { Self.remoteMethodCtx('downloadFile', { @@ -8,7 +7,7 @@ module.exports = Self => { accepts: [ { arg: 'id', - type: 'String', + type: 'Number', description: 'The document id', http: {source: 'path'} } diff --git a/back/methods/dms/uploadFile.js b/back/methods/dms/uploadFile.js index 2d4cc94da..9ef60c005 100644 --- a/back/methods/dms/uploadFile.js +++ b/back/methods/dms/uploadFile.js @@ -1,6 +1,5 @@ const UserError = require('vn-loopback/util/user-error'); const fs = require('fs-extra'); -const md5 = require('md5'); module.exports = Self => { Self.remoteMethodCtx('uploadFile', { diff --git a/db/changes/10060-verano/00-zoneCalendar.sql b/db/changes/10060-verano/00-zoneCalendar.sql new file mode 100644 index 000000000..a3c091643 --- /dev/null +++ b/db/changes/10060-verano/00-zoneCalendar.sql @@ -0,0 +1,3 @@ +ALTER TABLE `vn`.`zoneCalendar` +ADD COLUMN `price` DOUBLE NOT NULL AFTER `delivered`, +ADD COLUMN `bonus` DOUBLE NOT NULL AFTER `price`; diff --git a/front/core/components/calendar/index.html b/front/core/components/calendar/index.html index e505bfad9..189e9b4eb 100644 --- a/front/core/components/calendar/index.html +++ b/front/core/components/calendar/index.html @@ -26,37 +26,55 @@ -
+
L
-
+
M
-
+
X
-
+
J
-
+
V
-
+
S
-
+
D
-
- - {{::day.dated | date: 'd'}} - - {{::day.dated | date: 'd'}} +
+
+
+ {{::day.dated | date: 'd'}} +
+
+
+ + {{::event.name}} + +
+
+
diff --git a/front/core/components/calendar/index.js b/front/core/components/calendar/index.js index ada238d67..9e920eea2 100644 --- a/front/core/components/calendar/index.js +++ b/front/core/components/calendar/index.js @@ -13,6 +13,23 @@ export default class Calendar extends Component { this.defaultDate = new Date(); this.displayControls = true; this.skip = 1; + + this.window.addEventListener('resize', () => { + this.checkSize(); + }); + } + + /** + * Resizes the calendar + * based on component height + */ + checkSize() { + const height = this.$element[0].clientHeight; + + if (height < 530) + this.$element.addClass('small'); + else + this.$element.removeClass('small'); } /** @@ -49,8 +66,10 @@ export default class Calendar extends Component { this.addEvent(event); }); - if (value.length && this.defaultDate) + if (value.length && this.defaultDate) { this.repaint(); + this.checkSize(); + } } /** @@ -165,16 +184,28 @@ export default class Calendar extends Component { * @param {Date} dated - Date of month * @param {String} className - Default class style */ - insertDay(dated, className = '') { - let event = this.events.find(event => { + insertDay(dated) { + let events = this.events.filter(event => { return event.dated >= dated && event.dated <= dated; }); - // Weeekends - if (dated.getMonth() === this.currentMonth.getMonth() && dated.getDay() == 0) - className = 'red'; + const params = {dated, events}; - this.days.push({dated, className, event}); + const isSaturday = dated.getDay() === 6; + const isSunday = dated.getDay() === 0; + const isCurrentMonth = dated.getMonth() === this.currentMonth.getMonth(); + const hasEvents = events.length > 0; + + if (isCurrentMonth && isSunday && !hasEvents) + params.style = {color: '#f42121'}; + + if (isCurrentMonth && isSaturday && !hasEvents) + params.style = {color: '#666666'}; + + if (!isCurrentMonth && !hasEvents) + params.style = {color: '#9b9b9b'}; + + this.days.push(params); } /** @@ -182,7 +213,7 @@ export default class Calendar extends Component { * * @param {Object} options - Event params * @param {Date} options.dated - Day to add event - * @param {String} options.title - Tooltip description + * @param {String} options.name - Tooltip description * @param {String} options.className - ClassName style * @param {Object} options.style - Style properties * @param {Boolean} options.isRemovable - True if is removable by users @@ -194,12 +225,7 @@ export default class Calendar extends Component { options.dated = new Date(options.dated); options.dated.setHours(0, 0, 0, 0); - const event = this.events.findIndex(event => { - return event.dated >= options.dated && event.dated <= options.dated; - }); - - if (event < 0) - this.events.push(options); + this.events.push(options); } /** @@ -274,6 +300,16 @@ export default class Calendar extends Component { } this.emit('selection', {values: selected}); } + + renderStyle(style) { + if (style) { + return { + 'background-color': style.backgroundColor, + 'font-weight': style.fontWeight, + 'color': style.color + }; + } + } } Calendar.$inject = ['$element', '$scope']; diff --git a/front/core/components/calendar/index.spec.js b/front/core/components/calendar/index.spec.js index 6a082a236..86bf3c240 100644 --- a/front/core/components/calendar/index.spec.js +++ b/front/core/components/calendar/index.spec.js @@ -20,8 +20,8 @@ describe('Component vnCalendar', () => { let currentDate = new Date().toString(); controller.data = [ - {dated: currentDate, title: 'Event 1'}, - {dated: currentDate, title: 'Event 2'}, + {dated: currentDate, name: 'Event 1'}, + {dated: currentDate, name: 'Event 2'}, ]; expect(controller.events[0].dated instanceof Object).toBeTruthy(); @@ -34,12 +34,11 @@ describe('Component vnCalendar', () => { controller.events = []; controller.addEvent({ dated: new Date(), - title: 'My event', - className: 'color' + name: 'My event' }); const firstEvent = controller.events[0]; - expect(firstEvent.title).toEqual('My event'); + expect(firstEvent.name).toEqual('My event'); expect(firstEvent.isRemovable).toBeDefined(); expect(firstEvent.isRemovable).toBeTruthy(); }); @@ -50,19 +49,17 @@ describe('Component vnCalendar', () => { controller.events = [{ dated: curDate, - title: 'My event 1', - className: 'color' + name: 'My event 1' }]; controller.addEvent({ dated: curDate, - title: 'My event 2', - className: 'color' + name: 'My event 2' }); const firstEvent = controller.events[0]; - expect(controller.events.length).toEqual(1); - expect(firstEvent.title).toEqual('My event 1'); + expect(controller.events.length).toEqual(2); + expect(firstEvent.name).toEqual('My event 1'); }); }); @@ -71,7 +68,7 @@ describe('Component vnCalendar', () => { const curDate = new Date(); controller._events = [{ dated: curDate, - title: 'My event 1', + name: 'My event 1', className: 'color' }]; controller.removeEvent(curDate); diff --git a/front/core/components/calendar/style.scss b/front/core/components/calendar/style.scss index 6baaa0f65..333bfb428 100644 --- a/front/core/components/calendar/style.scss +++ b/front/core/components/calendar/style.scss @@ -1,143 +1,123 @@ @import "variables"; +vn-calendar.small { + .events { + display: none + } +} + vn-calendar { display: block; - max-width: 250px; .header vn-one { text-align: center; - padding: 0.2em 0 + padding: 0.2em 0; + height: 1.5em } - .body { - .days { - justify-content: flex-start; - align-items: flex-start; - flex-wrap: wrap; + + .weekdays { + color: $color-font-secondary; + margin-bottom: 0.5em; + padding: 0.5em 0; + font-weight: bold; + font-size: 0.8em; + } + + .weekdays section { + cursor: pointer + } + + .weekdays section, .day { + position: relative; + text-align: center; + box-sizing: border-box; + width: 14.28%; + outline: 0; + } + + .days { + justify-content: flex-start; + align-items: flex-start; + flex-wrap: wrap; + } + + + .day { + .content { + position: absolute; + bottom: 0; + right: 0; + left: 0; + top: 0 } - .weekdays { - border-bottom: 1px solid $color-hover-cd; - border-top: 1px solid $color-hover-cd; - color: $color-font-secondary; - font-weight: bold + .day-number { + transition: background-color 0.3s; + text-align:center; + float:inline-end; + margin: 0 auto; + border-radius: 50%; + font-size: 0.85em; + width:2.2em; + height: 1.2em; + padding: 0.5em 0; + cursor: pointer; + outline: 0 } - .day { - box-sizing: border-box; - padding: 0.1em; - width: 14.2857143%; - line-height: 1.5em; - outline: 0; - - span { - transition: background-color 0.3s; - text-align: center; - font-size: .8em; - border-radius: 50%; - display: block; - padding: 0.2em; - cursor: pointer - } + .day-number:hover { + background-color: lighten($color-font-secondary, 20%); + opacity: 0.8 } + } - .day:hover span { - background-color: #DDD + .day::after { + content: ""; + display: block; + padding-top: 100%; + } + + .day.primary .day-number { + background-color: $color-main; + color: $color-font-bg; + } + + .events { + margin-top: 0.5em; + font-size: 0.6em + } + + + .events { + color: $color-font-secondary; + + .event { + margin-bottom: .1em; } + } - .day.gray { + .chip { + background-color: $color-main; + color: $color-font-bg; + display: inline-block; + border-radius: .3em; + padding: 0.3em .8em; + max-width: 5em; + } + + .day.gray { + .day-number { color: $color-font-secondary } + } - .day.orange { - font-weight: bold; - color: $color-main; - } - - .day.orange-circle { - color: $color-font; - & > span { - background-color: $color-main - } - } + - .day.orange-circle:hover { - & > span { - background-color: $color-main-medium - } - } - - .day.light-orange { - color: $color-main-medium - } - - .day.green { - font-weight: bold; - color: $color-success; - } - - .day.green-circle { - color: $color-font; - & > span { - background-color: $color-success - } - } - - .day.green-circle:hover { - & > span { - background-color: $color-success-medium - } - } - - .day.light-green { - font-weight: bold; - color: $color-success-medium - } - - .day.blue { - font-weight: bold; - color: $color-notice; - } - - .day.blue-circle { - color: $color-font; - & > span { - background-color: $color-notice - } - } - - .day.blue-circle:hover { - & > span { - background-color: $color-notice-medium - } - } - - .day.light-blue { - font-weight: bold; - color: $color-notice-medium - } - - .day.red { - font-weight: bold; - color: $color-alert - } - - .day.red-circle { - color: $color-font; - & > span { - background-color: $color-alert - } - } - - .day.red-circle:hover { - & > span { - background-color: $color-alert-medium - } - } - - .day.light-red { - font-weight: bold; - color: $color-alert-medium; + .day.sunday { + .day-number { + color: $color-alert; + font-weight: bold } } } diff --git a/front/core/components/dialog/dialog.js b/front/core/components/dialog/dialog.js index 7ea588a27..4c4535b4c 100644 --- a/front/core/components/dialog/dialog.js +++ b/front/core/components/dialog/dialog.js @@ -45,8 +45,7 @@ export default class Dialog extends Component { this.element.style.display = 'flex'; this.transitionTimeout = setTimeout(() => this.$element.addClass('shown'), 30); - if (this.onOpen) - this.onOpen(); + this.emit('open'); } /** @@ -55,6 +54,7 @@ export default class Dialog extends Component { hide() { this.fireResponse(); this.realHide(); + this.emit('close'); } /** @@ -120,7 +120,6 @@ ngModule.component('vnDialog', { buttons: '?tplButtons' }, bindings: { - onOpen: '&?', onResponse: '&?' }, controller: Dialog diff --git a/front/core/components/dialog/dialog.spec.js b/front/core/components/dialog/dialog.spec.js index 4937fdba3..b889f1bc0 100644 --- a/front/core/components/dialog/dialog.spec.js +++ b/front/core/components/dialog/dialog.spec.js @@ -7,7 +7,7 @@ describe('Component vnDialog', () => { beforeEach(angular.mock.inject($componentController => { $element = angular.element(''); controller = $componentController('vnDialog', {$element, $transclude: null}); - controller.onOpen = jasmine.createSpy('onOpen'); + controller.emit = jasmine.createSpy('emit'); })); describe('show()', () => { @@ -17,15 +17,15 @@ describe('Component vnDialog', () => { controller.show(); expect(controller.element.style.display).toEqual('none'); - expect(controller.onOpen).not.toHaveBeenCalledWith(); + expect(controller.emit).not.toHaveBeenCalledWith('open'); }); - it(`should set shown on the controller, set style.display on the element and call onOpen()`, () => { + it(`should set shown on the controller, set style.display on the element and emit onOpen() event`, () => { controller.show(); expect(controller.element.style.display).toEqual('flex'); expect(controller.shown).toBeTruthy(); - expect(controller.onOpen).toHaveBeenCalledWith(); + expect(controller.emit).toHaveBeenCalledWith('open'); }); }); diff --git a/front/core/components/index.js b/front/core/components/index.js index d626470c3..09ee97963 100644 --- a/front/core/components/index.js +++ b/front/core/components/index.js @@ -20,7 +20,7 @@ import './multi-check/multi-check'; import './date-picker/date-picker'; import './button/button'; import './check/check'; -import './radio/radio'; +import './radio-group/radio-group'; import './textarea/textarea'; import './icon-button/icon-button'; import './submit/submit'; diff --git a/front/core/components/input-number/index.html b/front/core/components/input-number/index.html index cb3f24bd5..9fd024280 100644 --- a/front/core/components/input-number/index.html +++ b/front/core/components/input-number/index.html @@ -22,7 +22,10 @@ ng-focus="$ctrl.hasFocus = true" ng-blur="$ctrl.hasFocus = false" tabindex="{{$ctrl.input.tabindex}}"/> - +
diff --git a/front/core/components/input-number/index.js b/front/core/components/input-number/index.js index 7eb8cbd54..6c9f378e8 100644 --- a/front/core/components/input-number/index.js +++ b/front/core/components/input-number/index.js @@ -192,6 +192,7 @@ ngModule.component('vnInputNumber', { label: '@?', name: '@?', disabled: ' + + {{::option.label}} + + \ No newline at end of file diff --git a/front/core/components/radio-group/radio-group.js b/front/core/components/radio-group/radio-group.js new file mode 100644 index 000000000..80c77a7e2 --- /dev/null +++ b/front/core/components/radio-group/radio-group.js @@ -0,0 +1,41 @@ +import ngModule from '../../module'; +import Component from '../../lib/component'; +import './style.scss'; + +export default class Controller extends Component { + constructor($element, $scope, $attrs) { + super($element, $scope); + this.hasInfo = Boolean($attrs.info); + this.info = $attrs.info || null; + } + + get model() { + return this._model; + } + + set model(value) { + this._model = value; + } + + get field() { + return this._model; + } + + set field(value) { + this._model = value; + } +} + +Controller.$inject = ['$element', '$scope', '$attrs']; + +ngModule.component('vnRadioGroup', { + template: require('./radio-group.html'), + controller: Controller, + + bindings: { + field: '=?', + options: ' -*[text]* diff --git a/front/core/components/radio/radio.js b/front/core/components/radio/radio.js deleted file mode 100644 index 70dbc059d..000000000 --- a/front/core/components/radio/radio.js +++ /dev/null @@ -1,15 +0,0 @@ -import ngModule from '../../module'; -import template from './radio.html'; - -directive.$inject = ['vnTemplate']; -export default function directive(vnTemplate) { - return { - restrict: 'E', - template: (_, $attrs) => - vnTemplate.get(template, $attrs, { - enabled: 'true', - className: 'mdl-radio mdl-js-radio mdl-js-ripple-effect' - }) - }; -} -ngModule.directive('vnRadio', directive); diff --git a/front/core/components/textfield/textfield.html b/front/core/components/textfield/textfield.html index 0c1058148..22756497f 100644 --- a/front/core/components/textfield/textfield.html +++ b/front/core/components/textfield/textfield.html @@ -16,7 +16,7 @@ tabindex="{{$ctrl.input.tabindex}}"/>
diff --git a/front/salix/styles/variables.scss b/front/salix/styles/variables.scss index 732389fb9..5fd31a8c6 100644 --- a/front/salix/styles/variables.scss +++ b/front/salix/styles/variables.scss @@ -52,6 +52,8 @@ $color-hover-cd: rgba(0, 0, 0, .1); $color-hover-dc: .7; $color-disabled: .6; +$color-font-link-medium: lighten($color-font-link, 20%); +$color-font-link-light: lighten($color-font-link, 35%); $color-main-medium: lighten($color-main, 20%); $color-main-light: lighten($color-main, 35%); $color-success-medium: lighten($color-success, 20%); diff --git a/modules/agency/back/methods/zone/editPrices.js b/modules/agency/back/methods/zone/editPrices.js new file mode 100644 index 000000000..b0203dc4b --- /dev/null +++ b/modules/agency/back/methods/zone/editPrices.js @@ -0,0 +1,81 @@ +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethod('editPrices', { + description: 'Changes the price and bonus of a delivery day', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The zone id', + http: {source: 'path'} + }, + { + arg: 'delivered', + type: 'Date', + required: true, + }, + { + arg: 'price', + type: 'Number', + required: true, + }, + { + arg: 'bonus', + type: 'Number', + required: true, + }, + { + arg: 'option', + type: 'String', + required: true, + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/:id/editPrices`, + verb: 'POST' + } + }); + + Self.editPrices = async(id, delivered, price, bonus, option) => { + const models = Self.app.models; + + let filter = { + where: { + zoneFk: id + } + }; + + let where; + let shouldPropagate = true; + + if (option == 'Only this day') { + shouldPropagate = false; + where = {delivered}; + } else if (option == 'From this day') { + where = { + delivered: { + gte: delivered + } + }; + } + + filter = mergeFilters(filter, {where}); + + const days = await models.ZoneCalendar.find(filter); + const areAllFromSameZone = days.every(day => day.zoneFk === id); + + if (!areAllFromSameZone) + throw new UserError('All delivery days must belong to the same zone'); + + if (shouldPropagate) { + const zone = await models.Zone.findById(id); + zone.updateAttributes({price, bonus}); + } + + return models.ZoneCalendar.updateAll(filter.where, {price, bonus}); + }; +}; diff --git a/modules/agency/back/methods/zone/specs/editPrices.spec.js b/modules/agency/back/methods/zone/specs/editPrices.spec.js new file mode 100644 index 000000000..610496425 --- /dev/null +++ b/modules/agency/back/methods/zone/specs/editPrices.spec.js @@ -0,0 +1,84 @@ +const app = require('vn-loopback/server/server'); + +describe('agency editPrices()', () => { + const zoneId = 1; + let originalZone; + + beforeAll(async done => { + originalZone = await app.models.Zone.findById(zoneId); + done(); + }); + + afterAll(async done => { + await await app.models.ZoneCalendar.updateAll({zoneFk: zoneId}, { + price: originalZone.price, + bonus: originalZone.bonus + }); + done(); + }); + + it('should apply price and bonus for a selected day', async() => { + const delivered = new Date(); + delivered.setHours(0, 0, 0, 0); + delivered.setDate(delivered.getDate() + 1); + await app.models.Zone.editPrices(zoneId, delivered, 4.00, 2.00, 'Only this day'); + + const editedDays = await app.models.ZoneCalendar.find({ + where: { + zoneFk: zoneId, + delivered: delivered + } + }); + const firstEditedDay = editedDays[0]; + + expect(editedDays.length).toEqual(1); + expect(firstEditedDay.price).toEqual(4.00); + expect(firstEditedDay.bonus).toEqual(2.00); + }); + + it('should apply price and bonus for all delivery days starting from selected day', async() => { + const delivered = new Date(); + delivered.setHours(0, 0, 0, 0); + delivered.setDate(delivered.getDate() + 1); + await app.models.Zone.editPrices(1, delivered, 5.50, 1.00, 'From this day'); + + const editedDays = await app.models.ZoneCalendar.find({ + where: { + zoneFk: zoneId, + delivered: { + gte: delivered + } + } + }); + const firstEditedDay = editedDays[0]; + const lastEditedDay = editedDays[editedDays.length - 1]; + + expect(editedDays.length).toEqual(4); + expect(firstEditedDay.price).toEqual(5.50); + expect(firstEditedDay.bonus).toEqual(1.00); + expect(lastEditedDay.price).toEqual(5.50); + expect(lastEditedDay.bonus).toEqual(1.00); + }); + + it('should apply price and bonus for all delivery days', async() => { + const delivered = new Date(); + delivered.setHours(0, 0, 0, 0); + delivered.setDate(delivered.getDate() + 1); + await app.models.Zone.editPrices(1, delivered, 7.00, 0.00, 'All days'); + + const editedDays = await app.models.ZoneCalendar.find({ + where: { + zoneFk: zoneId + } + }); + const firstEditedDay = editedDays[0]; + const lastEditedDay = editedDays[editedDays.length - 1]; + + expect(editedDays.length).toEqual(5); + expect(firstEditedDay.price).toEqual(7.00); + expect(firstEditedDay.bonus).toEqual(0.00); + expect(lastEditedDay.price).toEqual(7.00); + expect(lastEditedDay.bonus).toEqual(0.00); + }); +}); + diff --git a/modules/agency/back/models/zone-calendar.json b/modules/agency/back/models/zone-calendar.json index 35926a0db..bed9a8011 100644 --- a/modules/agency/back/models/zone-calendar.json +++ b/modules/agency/back/models/zone-calendar.json @@ -14,6 +14,12 @@ "delivered": { "id": true, "type": "Date" + }, + "price": { + "type": "Number" + }, + "bonus": { + "type": "Number" } }, "relations": { diff --git a/modules/agency/back/models/zone.js b/modules/agency/back/models/zone.js index d282224b9..c7ba642f3 100644 --- a/modules/agency/back/models/zone.js +++ b/modules/agency/back/models/zone.js @@ -1,5 +1,6 @@ module.exports = Self => { require('../methods/zone/clone')(Self); + require('../methods/zone/editPrices')(Self); Self.validatesPresenceOf('warehouseFk', { message: `Warehouse cannot be blank` diff --git a/modules/agency/front/calendar/index.html b/modules/agency/front/calendar/index.html index d02be3059..0a86d2254 100644 --- a/modules/agency/front/calendar/index.html +++ b/modules/agency/front/calendar/index.html @@ -1,21 +1,74 @@ - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + +
Edit price
+ + + + + + + + + + +
+ + + + +
\ No newline at end of file diff --git a/modules/agency/front/calendar/index.js b/modules/agency/front/calendar/index.js index bf1625585..5e6cb19b9 100644 --- a/modules/agency/front/calendar/index.js +++ b/modules/agency/front/calendar/index.js @@ -1,49 +1,26 @@ import ngModule from '../module'; +import './style.scss'; class Controller { - constructor($element, $scope, $stateParams, $http) { + constructor($element, $scope, $http, $filter, $translate, $stateParams, vnApp) { this.$element = $element; - this.$stateParams = $stateParams; - this.$scope = $scope; + this.$ = $scope; this.$http = $http; + this.$filter = $filter; + this.$translate = $translate; + this.$stateParams = $stateParams; + this.vnApp = vnApp; this.stMonthDate = new Date(); this.ndMonthDate = new Date(); this.ndMonthDate.setMonth(this.ndMonthDate.getMonth() + 1); + this.selectedDay = {}; } $postLink() { - this.stMonth = this.$scope.stMonth; - this.ndMonth = this.$scope.ndMonth; + this.stMonth = this.$.stMonth; + this.ndMonth = this.$.ndMonth; } - // Disabled until implementation - // of holidays by node - /* get zone() { - return this._zone; - } - - set zone(value) { - this._zone = value; - - if (!value) return; - - let query = '/agency/api/LabourHolidays/getByWarehouse'; - this.$http.get(query, {params: {warehouseFk: value.warehouseFk}}).then(res => { - if (!res.data) return; - const events = []; - res.data.forEach(holiday => { - events.push({ - date: holiday.dated, - className: 'red', - title: holiday.description || holiday.name, - isRemovable: false - }); - }); - - this.events = this.events.concat(events); - }); - } */ - get data() { return this._data; } @@ -52,97 +29,106 @@ class Controller { this._data = value; if (!value) return; + const events = []; value.forEach(event => { events.push({ + name: `P: ${this.$filter('currency')(event.price)}`, + description: 'Price', dated: event.delivered, - className: 'green-circle', - title: 'Has delivery' + style: {backgroundColor: '#a3d131'}, + data: {price: event.price} + }); + events.push({ + name: `B: ${this.$filter('currency')(event.bonus)}`, + description: 'Bonus', + dated: event.delivered, + data: {bonus: event.bonus} }); }); this.events = events; } - onSelection(calendar, values) { - let totalEvents = 0; - values.forEach(day => { - const exists = calendar.events.findIndex(event => { - return event.dated >= day.dated && event.dated <= day.dated - && event.isRemovable; - }); + onSelection(values) { + if (values.length > 1) return false; - if (exists > -1) totalEvents++; + this.options = [ + {label: 'Only this day', value: 'Only this day'}, + {label: 'From this day', value: 'From this day'}, + {label: 'All days', value: 'All days'} + ]; + const selection = values[0]; + const events = selection.events; + const hasEvents = events.length > 0; + + if (!hasEvents) + return this.vnApp.showMessage(this.$translate.instant(`There's no delivery for this day`)); + + this.selectedDay = { + delivered: selection.dated, + option: 'Only this day' + }; + + events.forEach(event => { + this.selectedDay = Object.assign(this.selectedDay, event.data); }); - - if (totalEvents > (values.length / 2)) - this.removeEvents(calendar, values); - else - this.insertEvents(calendar, values); + this.$.priceDialog.show(); } - insertEvents(calendar, days) { - days.forEach(day => { - const event = calendar.events.find(event => { - return event.dated >= day.dated && event.dated <= day.dated; - }); + onResponse(response) { + if (response == 'ACCEPT') { + try { + const data = { + delivered: this.selectedDay.delivered, + price: this.selectedDay.price, + bonus: this.selectedDay.bonus, + option: this.selectedDay.option + }; - if (event) return false; + this.$.watcher.check(); - this.$scope.model.insert({ - zoneFk: this.zone.id, - delivered: day.dated - }); - - calendar.addEvent({ - dated: day.dated, - className: 'green-circle', - title: 'Has delivery' - }); - }); - - this.$scope.model.save().then(() => { - this.events = calendar.events; - }); - } - - removeEvents(calendar, days) { - let dates = []; - days.forEach(day => { - const event = calendar.events.find(event => { - return event.dated >= day.dated && event.dated <= day.dated; - }); - - if (event && !event.isRemovable) + const path = `/api/Zones/${this.zone.id}/editPrices`; + this.$http.post(path, data).then(() => { + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + this.$.model.refresh(); + this.card.reload(); + }); + } catch (e) { + this.vnApp.showError(this.$translate.instant(e.message)); return false; + } + } - dates.push(day.dated); + return this.onClose(); + } - calendar.removeEvent(day.dated); - }); + onClose() { + this.$.watcher.updateOriginalData(); + } - if (dates.length == 0) return; - const params = {zoneFk: this.zone.id, dates}; - this.$http.post('/agency/api/zoneCalendars/removeByDate', params).then(() => { - this.events = calendar.events; + onMoveNext(calendars) { + calendars.forEach(calendar => { + calendar.moveNext(2); }); } - onMoveNext(calendar) { - calendar.moveNext(2); - } - - onMovePrevious(calendar) { - calendar.movePrevious(2); + onMovePrevious(calendars) { + calendars.forEach(calendar => { + calendar.movePrevious(2); + }); } } -Controller.$inject = ['$element', '$scope', '$stateParams', '$http']; +Controller.$inject = ['$element', '$scope', '$http', '$filter', '$translate', '$stateParams', 'vnApp']; ngModule.component('vnZoneCalendar', { template: require('./index.html'), controller: Controller, bindings: { zone: '<' + }, + require: { + card: '^vnZoneCard' } }); diff --git a/modules/agency/front/calendar/locale/es.yml b/modules/agency/front/calendar/locale/es.yml new file mode 100644 index 000000000..a37eeb272 --- /dev/null +++ b/modules/agency/front/calendar/locale/es.yml @@ -0,0 +1,6 @@ +Prices: Precios +Edit price: Modificar precio +Only this day: Solo este día +From this day: A partir de este día +All days: Todos los días +There's no delivery for this day: No hay reparto para este día \ No newline at end of file diff --git a/modules/agency/front/calendar/style.scss b/modules/agency/front/calendar/style.scss new file mode 100644 index 000000000..783bad77d --- /dev/null +++ b/modules/agency/front/calendar/style.scss @@ -0,0 +1,3 @@ +vn-calendar:nth-child(2n + 1) { + border-right:1px solid #ddd +} \ No newline at end of file diff --git a/modules/agency/front/index.js b/modules/agency/front/index.js index f58cc8f60..5e4103b43 100644 --- a/modules/agency/front/index.js +++ b/modules/agency/front/index.js @@ -8,4 +8,5 @@ import './search-panel'; import './create'; import './basic-data'; import './location'; +import './location/calendar'; import './calendar'; diff --git a/modules/agency/front/location/calendar.html b/modules/agency/front/location/calendar.html new file mode 100644 index 000000000..a1db6f08a --- /dev/null +++ b/modules/agency/front/location/calendar.html @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/modules/agency/front/location/calendar.js b/modules/agency/front/location/calendar.js new file mode 100644 index 000000000..802a9946e --- /dev/null +++ b/modules/agency/front/location/calendar.js @@ -0,0 +1,150 @@ +import ngModule from '../module'; + +class Controller { + constructor($element, $scope, $stateParams, $http) { + this.$element = $element; + this.$stateParams = $stateParams; + this.$scope = $scope; + this.$http = $http; + this.stMonthDate = new Date(); + this.ndMonthDate = new Date(); + this.ndMonthDate.setMonth(this.ndMonthDate.getMonth() + 1); + } + + $postLink() { + this.stMonth = this.$scope.stMonth; + this.ndMonth = this.$scope.ndMonth; + } + + // Disabled until implementation + // of holidays by node + /* get zone() { + return this._zone; + } + + set zone(value) { + this._zone = value; + + if (!value) return; + + let query = '/agency/api/LabourHolidays/getByWarehouse'; + this.$http.get(query, {params: {warehouseFk: value.warehouseFk}}).then(res => { + if (!res.data) return; + const events = []; + res.data.forEach(holiday => { + events.push({ + date: holiday.dated, + className: 'red', + title: holiday.description || holiday.name, + isRemovable: false + }); + }); + + this.events = this.events.concat(events); + }); + } */ + + get data() { + return this._data; + } + + set data(value) { + this._data = value; + + if (!value) return; + const events = []; + value.forEach(event => { + events.push({ + name: 'Has delivery', + dated: event.delivered, + style: {backgroundColor: '#a3d131'} + }); + }); + + this.events = events; + } + + onSelection(values, calendar) { + let totalEvents = 0; + values.forEach(day => { + const exists = calendar.events.findIndex(event => { + return event.dated >= day.dated && event.dated <= day.dated + && event.isRemovable; + }); + + if (exists > -1) totalEvents++; + }); + + if (totalEvents > (values.length / 2)) + this.removeEvents(calendar, values); + else + this.insertEvents(calendar, values); + } + + insertEvents(calendar, days) { + days.forEach(day => { + const event = calendar.events.find(event => { + return event.dated >= day.dated && event.dated <= day.dated; + }); + + if (event) return false; + + this.$scope.model.insert({ + zoneFk: this.zone.id, + delivered: day.dated, + price: this.zone.price, + bonus: this.zone.bonus + }); + + calendar.addEvent({ + name: 'Has delivery', + dated: day.dated, + style: {backgroundColor: '#a3d131'} + }); + }); + + this.$scope.model.save().then(() => { + this.events = calendar.events; + }); + } + + removeEvents(calendar, days) { + let dates = []; + days.forEach(day => { + const event = calendar.events.find(event => { + return event.dated >= day.dated && event.dated <= day.dated; + }); + + if (event && !event.isRemovable) + return false; + + dates.push(day.dated); + + calendar.removeEvent(day.dated); + }); + + if (dates.length == 0) return; + const params = {zoneFk: this.zone.id, dates}; + this.$http.post('/agency/api/zoneCalendars/removeByDate', params).then(() => { + this.events = calendar.events; + }); + } + + onMoveNext(calendar) { + calendar.moveNext(2); + } + + onMovePrevious(calendar) { + calendar.movePrevious(2); + } +} + +Controller.$inject = ['$element', '$scope', '$stateParams', '$http']; + +ngModule.component('vnZoneLocationCalendar', { + template: require('./calendar.html'), + controller: Controller, + bindings: { + zone: '<' + } +}); diff --git a/modules/agency/front/location/index.html b/modules/agency/front/location/index.html index 42662ef2b..fb9bf863c 100644 --- a/modules/agency/front/location/index.html +++ b/modules/agency/front/location/index.html @@ -18,6 +18,6 @@ - + \ No newline at end of file diff --git a/modules/agency/front/routes.json b/modules/agency/front/routes.json index fe4e74707..2125b1172 100644 --- a/modules/agency/front/routes.json +++ b/modules/agency/front/routes.json @@ -6,7 +6,8 @@ "dependencies": ["worker"], "menu": [ {"state": "zone.card.basicData", "icon": "settings"}, - {"state": "zone.card.location", "icon": "my_location"} + {"state": "zone.card.location", "icon": "my_location"}, + {"state": "zone.card.calendar"} ], "routes": [ { @@ -15,27 +16,31 @@ "abstract": true, "component": "ui-view", "description": "Zones" - }, { + }, + { "url": "/index?q", "state": "zone.index", "component": "vn-zone-index", "description": "Zones" - }, { + }, + { "url": "/create", "state": "zone.create", "component": "vn-zone-create", "description": "New zone" - }, { + }, + { "url": "/:id", "state": "zone.card", "component": "vn-zone-card", "abstract": true, "description": "Detail" - }, { - "url": "/location?q", - "state": "zone.card.location", - "component": "vn-zone-location", - "description": "Locations", + }, + { + "url": "/summary", + "state": "zone.card.summary", + "component": "vn-zone-summary", + "description": "Summary", "params": { "zone": "$ctrl.zone" } @@ -50,10 +55,19 @@ } }, { - "url": "/summary", - "state": "zone.card.summary", - "component": "vn-zone-summary", - "description": "Summary", + "url": "/location?q", + "state": "zone.card.location", + "component": "vn-zone-location", + "description": "Locations", + "params": { + "zone": "$ctrl.zone" + } + }, + { + "url": "/calendar", + "state": "zone.card.calendar", + "component": "vn-zone-calendar", + "description": "Prices", "params": { "zone": "$ctrl.zone" } diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index 9952e1d81..9427ca9e8 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -3,7 +3,7 @@ data="$ctrl.absenceTypes" auto-load="true">
- +
{ const absenceType = absence.absenceType; events.push({ + name: absenceType.name, + description: absenceType.name, dated: absence.dated, - title: absenceType.name, - style: { - background: absenceType.rgb - } + style: {backgroundColor: absenceType.rgb} }); }); this.events = this.events.concat(events); diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index 194ea8f8b..f29b8fecc 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -93,7 +93,7 @@ describe('Worker', () => { controller.setHolidays(data); expect(controller.events.length).toEqual(2); - expect(controller.events[0].title).toEqual('New year'); + expect(controller.events[0].name).toEqual('New year'); expect(controller.events[0].isRemovable).toEqual(false); }); }); @@ -107,9 +107,9 @@ describe('Worker', () => { controller.setWorkerCalendar(data); expect(controller.events.length).toEqual(2); - expect(controller.events[0].title).toEqual('Holiday'); + expect(controller.events[0].name).toEqual('Holiday'); expect(controller.events[0].style).toBeDefined(); - expect(controller.events[1].title).toEqual('Leave'); + expect(controller.events[1].name).toEqual('Leave'); expect(controller.events[1].style).toBeDefined(); }); }); diff --git a/modules/worker/front/calendar/style.scss b/modules/worker/front/calendar/style.scss index 70503336a..5028edf32 100644 --- a/modules/worker/front/calendar/style.scss +++ b/modules/worker/front/calendar/style.scss @@ -1,5 +1,13 @@ @import "variables"; +.calendar-list .calendar { + border-bottom:1px solid #ddd +} + +.calendar-list .calendar:nth-child(2n + 1) { + border-right:1px solid #ddd +} + .calendar-list { align-items: flex-start; flex-wrap: wrap; @@ -9,7 +17,7 @@ box-sizing: border-box; padding: $pad-medium; overflow: hidden; - width: 20em + width: 50% } }