diff --git a/db/changes/10200-normality/01-calendar_employee.sql b/db/changes/10200-normality/01-calendar_employee.sql new file mode 100644 index 000000000..c5db9da2c --- /dev/null +++ b/db/changes/10200-normality/01-calendar_employee.sql @@ -0,0 +1,5 @@ +ALTER TABLE `postgresql`.`calendar_employee` +ADD COLUMN `id` INT NULL AUTO_INCREMENT FIRST, +ADD UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE, +ADD INDEX `id_index` (`id` ASC) VISIBLE; +; diff --git a/db/changes/10200-normality/02-workerCalendar.sql b/db/changes/10200-normality/02-workerCalendar.sql new file mode 100644 index 000000000..479470ac0 --- /dev/null +++ b/db/changes/10200-normality/02-workerCalendar.sql @@ -0,0 +1,12 @@ +USE `vn`; +CREATE + OR REPLACE ALGORITHM = UNDEFINED + DEFINER = `root`@`%` + SQL SECURITY DEFINER +VIEW `workerCalendar2` AS + SELECT + `ce`.`id` AS `id`, + `ce`.`business_id` AS `businessFk`, + `ce`.`calendar_state_id` AS `absenceTypeFk`, + `ce`.`date` AS `dated` + FROM `postgresql`.`calendar_employee` `ce`; diff --git a/front/core/components/calendar/index.js b/front/core/components/calendar/index.js index 96bc435db..02d2a4798 100644 --- a/front/core/components/calendar/index.js +++ b/front/core/components/calendar/index.js @@ -135,7 +135,7 @@ export default class Calendar extends FormInput { $days: [day], $type: 'day' }); - this.repaint(); + // this.repaint(); } /* diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js new file mode 100644 index 000000000..28a2b9d9d --- /dev/null +++ b/modules/worker/back/methods/worker/createAbsence.js @@ -0,0 +1,58 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('createAbsence', { + description: 'Creates a new worker absence', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'absenceTypeId', + type: 'Number', + required: true + }, + { + arg: 'dated', + type: 'Date', + required: false + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/:id/createAbsence`, + verb: 'POST' + } + }); + + Self.createAbsence = async(ctx, id, absenceTypeId, dated) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss'); + + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) + throw new UserError(`You don't have enough privileges`); + + const labour = await models.WorkerLabour.findOne({ + where: { + and: [ + {workerFk: id}, + {or: [{ + ended: {gte: [dated]} + }, {ended: null}]} + ] + } + }); + + return models.WorkerCalendar.create({ + businessFk: labour.businessFk, + absenceTypeFk: absenceTypeId, + dated: dated + }); + }; +}; diff --git a/modules/worker/back/methods/worker/deleteAbsence.js b/modules/worker/back/methods/worker/deleteAbsence.js new file mode 100644 index 000000000..ea156d7eb --- /dev/null +++ b/modules/worker/back/methods/worker/deleteAbsence.js @@ -0,0 +1,37 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('deleteAbsence', { + description: 'Deletes a worker absence', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'absenceId', + type: 'Number', + required: true + }], + returns: 'Object', + http: { + path: `/:id/deleteAbsence`, + verb: 'DELETE' + } + }); + + Self.deleteAbsence = async(ctx, id, absenceId) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss'); + + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) + throw new UserError(`You don't have enough privileges`); + + const absence = await models.WorkerCalendar.findById(absenceId); + + return absence.destroy(); + }; +}; diff --git a/modules/worker/back/methods/worker/specs/createAbsence.spec.js b/modules/worker/back/methods/worker/specs/createAbsence.spec.js new file mode 100644 index 000000000..33bc2a80e --- /dev/null +++ b/modules/worker/back/methods/worker/specs/createAbsence.spec.js @@ -0,0 +1,39 @@ +const app = require('vn-loopback/server/server'); + +describe('Worker createAbsence()', () => { + const workerId = 106; + let createdAbsence; + + afterAll(async() => { + const absence = await app.models.WorkerCalendar.findById(createdAbsence.id); + await absence.destroy(); + }); + + it('should return an error for a user without enough privileges', async() => { + const ctx = {req: {accessToken: {userId: 106}}}; + const absenceTypeId = 1; + const dated = new Date(); + + let error; + await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); + + it('should create a new absence', async() => { + const ctx = {req: {accessToken: {userId: 37}}}; + const absenceTypeId = 1; + const dated = new Date(); + createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated); + + const expectedBusinessId = 106; + const expectedAbsenceTypeId = 1; + + expect(createdAbsence.businessFk).toEqual(expectedBusinessId); + expect(createdAbsence.absenceTypeFk).toEqual(expectedAbsenceTypeId); + }); +}); diff --git a/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js new file mode 100644 index 000000000..c506ae86d --- /dev/null +++ b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js @@ -0,0 +1,38 @@ +const app = require('vn-loopback/server/server'); + +describe('Worker deleteAbsence()', () => { + const workerId = 106; + let createdAbsence; + + it('should return an error for a user without enough privileges', async() => { + const ctx = {req: {accessToken: {userId: 106}}}; + const businessId = 106; + createdAbsence = await app.models.WorkerCalendar.create({ + businessFk: businessId, + absenceTypeFk: 1, + dated: new Date() + }); + + let error; + await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); + + it('should create a new absence', async() => { + const ctx = {req: {accessToken: {userId: 37}}}; + const businessId = 106; + + expect(createdAbsence.businessFk).toEqual(businessId); + + await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id); + + const deletedAbsence = await app.models.WorkerCalendar.findById(createdAbsence.id); + + expect(deletedAbsence).toBeNull(); + }); +}); diff --git a/modules/worker/back/methods/worker/specs/updateAbsence.spec.js b/modules/worker/back/methods/worker/specs/updateAbsence.spec.js new file mode 100644 index 000000000..689d36136 --- /dev/null +++ b/modules/worker/back/methods/worker/specs/updateAbsence.spec.js @@ -0,0 +1,38 @@ +const app = require('vn-loopback/server/server'); + +describe('Worker updateAbsence()', () => { + const workerId = 106; + let createdAbsence; + + afterAll(async() => { + const absence = await app.models.WorkerCalendar.findById(createdAbsence.id); + await absence.destroy(); + }); + + it('should return an error for a user without enough privileges', async() => { + const ctx = {req: {accessToken: {userId: 106}}}; + const expectedAbsenceTypeId = 2; + createdAbsence = await app.models.WorkerCalendar.create({ + businessFk: 106, + absenceTypeFk: 1, + dated: new Date() + }); + + let error; + await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); + + it('should create a new absence', async() => { + const ctx = {req: {accessToken: {userId: 37}}}; + const expectedAbsenceTypeId = 2; + const updatedAbsence = await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId); + + expect(updatedAbsence.absenceTypeFk).toEqual(expectedAbsenceTypeId); + }); +}); diff --git a/modules/worker/back/methods/worker/updateAbsence.js b/modules/worker/back/methods/worker/updateAbsence.js new file mode 100644 index 000000000..719bca7e4 --- /dev/null +++ b/modules/worker/back/methods/worker/updateAbsence.js @@ -0,0 +1,42 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('updateAbsence', { + description: 'Updates a worker absence', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'absenceId', + type: 'Number', + required: true + }, + { + arg: 'absenceTypeId', + type: 'Number', + required: true + }], + returns: 'Object', + http: { + path: `/:id/updateAbsence`, + verb: 'PATCH' + } + }); + + Self.updateAbsence = async(ctx, id, absenceId, absenceTypeId) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss'); + + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) + throw new UserError(`You don't have enough privileges`); + + const absence = await models.WorkerCalendar.findById(absenceId); + + return absence.updateAttribute('absenceTypeFk', absenceTypeId); + }; +}; diff --git a/modules/worker/back/models/worker-calendar.json b/modules/worker/back/models/worker-calendar.json index 569d4d1ba..ca802caa7 100644 --- a/modules/worker/back/models/worker-calendar.json +++ b/modules/worker/back/models/worker-calendar.json @@ -3,29 +3,22 @@ "base": "VnModel", "options": { "mysql": { - "table": "workerCalendar" + "table": "workerCalendar2" } }, "properties": { - "businessFk": { - "id": 1, + "id": { + "id": true, "type": "Number" }, - "workerFk": { - "id": 2, + "businessFk": { "type": "Number" }, "dated": { - "id": 3, "type": "Date" } }, "relations": { - "worker": { - "type": "belongsTo", - "model": "Worker", - "foreignKey": "workerFk" - }, "absenceType": { "type": "belongsTo", "model": "AbsenceType", diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 692c8c735..0d94c788e 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -4,4 +4,7 @@ module.exports = Self => { require('../methods/worker/isSubordinate')(Self); require('../methods/worker/getWorkedHours')(Self); require('../methods/worker/uploadFile')(Self); + require('../methods/worker/createAbsence')(Self); + require('../methods/worker/deleteAbsence')(Self); + require('../methods/worker/updateAbsence')(Self); }; diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index 08b3f469c..197fb0797 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -5,15 +5,19 @@
- - + + + +
@@ -26,12 +30,19 @@
- + + {{absenceType.name}}
-
\ No newline at end of file + + + diff --git a/modules/worker/front/calendar/index.js b/modules/worker/front/calendar/index.js index 6b849d19f..32adc272d 100644 --- a/modules/worker/front/calendar/index.js +++ b/modules/worker/front/calendar/index.js @@ -43,15 +43,17 @@ class Controller extends Section { set worker(value) { this._worker = value; - if (!value) return; - let params = { - workerFk: this.worker.id, - started: this.started, - ended: this.ended - }; - this.$http.get(`WorkerCalendars/absences`, {params}) - .then(res => this.onData(res.data)); + if (value) { + this.refresh().then(() => this.repaint()); + this.getIsSubordinate(); + } + } + + getIsSubordinate() { + this.$http.get(`Workers/${this.worker.id}/isSubordinate`).then(res => + this.isSubordinate = res.data + ); } onData(data) { @@ -79,12 +81,12 @@ class Controller extends Section { let type = absence.absenceType; addEvent(absence.dated, { name: type.name, - color: type.rgb + color: type.rgb, + type: type.code, + absenceId: absence.id }); }); } - - this.repaint(); } repaint() { @@ -102,6 +104,105 @@ class Controller extends Section { dayNumber.style.backgroundColor = event.color; dayNumber.style.color = 'rgba(0, 0, 0, 0.7)'; } + + pick(absenceType) { + if (!this.isSubordinate) return; + if (absenceType == this.absenceType) + absenceType = null; + + this.absenceType = absenceType; + } + + onSelection($event, $days) { + if (!this.absenceType) + return this.vnApp.showMessage(this.$t('Choose an absence type from the right menu')); + + const day = $days[0]; + const stamp = day.getTime(); + const event = this.events[stamp]; + const calendar = $event.target.closest('vn-calendar').$ctrl; + + if (event) { + if (event.type == this.absenceType.code) + this.delete(calendar, day, event); + else + this.edit(calendar, event); + } else + this.create(calendar, day); + } + + create(calendar, dated) { + const absenceType = this.absenceType; + const params = { + dated: dated, + absenceTypeId: absenceType.id + }; + + const path = `Workers/${this.$params.id}/createAbsence`; + this.$http.post(path, params).then(res => { + const newEvent = res.data; + this.events[dated.getTime()] = { + name: absenceType.name, + color: absenceType.rgb, + type: absenceType.code, + absenceId: newEvent.id + }; + + this.repaintCanceller(() => + this.refresh().then(calendar.repaint()) + ); + }); + } + + edit(calendar, event) { + const absenceType = this.absenceType; + const params = { + absenceId: event.absenceId, + absenceTypeId: absenceType.id + }; + const path = `Workers/${this.$params.id}/updateAbsence`; + this.$http.patch(path, params).then(() => { + event.color = absenceType.rgb; + event.name = absenceType.name; + event.type = absenceType.code; + + this.repaintCanceller(() => + this.refresh().then(calendar.repaint()) + ); + }); + } + + delete(calendar, day, event) { + const params = {absenceId: event.absenceId}; + const path = `Workers/${this.$params.id}/deleteAbsence`; + this.$http.delete(path, {params}).then(() => { + delete this.events[day.getTime()]; + + this.repaintCanceller(() => + this.refresh().then(calendar.repaint()) + ); + }); + } + + repaintCanceller(cb) { + if (this.canceller) { + clearTimeout(this.canceller); + this.canceller = null; + } + + this.canceller = setTimeout( + () => cb(), 500); + } + + refresh() { + const params = { + workerFk: this.worker.id, + started: this.started, + ended: this.ended + }; + return this.$http.get(`WorkerCalendars/absences`, {params}) + .then(res => this.onData(res.data)); + } } ngModule.component('vnWorkerCalendar', { diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index da49b8f0f..9d14cca20 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -3,17 +3,23 @@ import './index'; describe('Worker', () => { describe('Component vnWorkerCalendar', () => { let $httpBackend; + let $httpParamSerializer; let $scope; let controller; let year = new Date().getFullYear(); beforeEach(ngModule('worker')); - beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => { + beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => { $scope = $rootScope.$new(); $httpBackend = _$httpBackend_; - const $element = angular.element(''); + $httpParamSerializer = _$httpParamSerializer_; + const $element = angular.element(''); controller = $componentController('vnWorkerCalendar', {$element, $scope}); + controller.isSubordinate = true; + controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'}; + controller.$params.id = 106; + controller._worker = {id: 106}; })); describe('started property', () => { @@ -44,8 +50,9 @@ describe('Worker', () => { describe('worker() setter', () => { it(`should perform a get query and set the reponse data on the model`, () => { - let today = new Date(); + jest.spyOn(controller, 'getIsSubordinate').mockReturnValue(true); + let today = new Date(); let tomorrow = new Date(today.getTime()); tomorrow.setDate(tomorrow.getDate() + 1); @@ -64,7 +71,7 @@ describe('Worker', () => { ] }); - controller.worker = {id: 1}; + controller.worker = {id: 107}; $httpBackend.flush(); let events = controller.events; @@ -73,11 +80,14 @@ describe('Worker', () => { expect(events[tomorrow.getTime()].name).toEqual('Easter'); expect(events[yesterday.getTime()].name).toEqual('Leave'); expect(events[yesterday.getTime()].color).toEqual('#bbb'); + expect(controller.getIsSubordinate).toHaveBeenCalledWith(); }); }); describe('formatDay()', () => { it(`should set the day element style`, () => { + jest.spyOn(controller, 'getIsSubordinate').mockReturnThis(); + let today = new Date(); $httpBackend.whenRoute('GET', 'WorkerCalendars/absences') @@ -99,5 +109,219 @@ describe('Worker', () => { expect(dayNumber.style.backgroundColor).toEqual('rgb(0, 0, 0)'); }); }); + + describe('pick()', () => { + it(`should set the absenceType property to null if they match with the current one`, () => { + const absenceType = {id: 1, name: 'Holiday'}; + controller.absenceType = absenceType; + controller.pick(absenceType); + + expect(controller.absenceType).toBeNull(); + }); + + it(`should set the absenceType property`, () => { + const absenceType = {id: 1, name: 'Holiday'}; + const expectedAbsence = {id: 2, name: 'Leave of absence'}; + controller.absenceType = absenceType; + controller.pick(expectedAbsence); + + expect(controller.absenceType).toEqual(expectedAbsence); + }); + }); + + describe('onSelection()', () => { + it(`should show an snackbar message if no absence type is selected`, () => { + jest.spyOn(controller.vnApp, 'showMessage').mockReturnThis(); + + const $event = {}; + const $days = []; + controller.absenceType = null; + controller.onSelection($event, $days); + + expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu'); + }); + + it(`should call to the create() method`, () => { + jest.spyOn(controller, 'create').mockReturnThis(); + + const selectedDay = new Date(); + const $event = { + target: { + closest: () => { + return {$ctrl: {}}; + } + } + }; + const $days = [selectedDay]; + controller.absenceType = {id: 1}; + controller.onSelection($event, $days); + + expect(controller.create).toHaveBeenCalledWith(jasmine.any(Object), selectedDay); + }); + + it(`should call to the delete() method`, () => { + jest.spyOn(controller, 'delete').mockReturnThis(); + + const selectedDay = new Date(); + const expectedEvent = { + dated: selectedDay, + type: 'holiday' + }; + const $event = { + target: { + closest: () => { + return {$ctrl: {}}; + } + } + }; + const $days = [selectedDay]; + controller.events[selectedDay.getTime()] = expectedEvent; + controller.absenceType = {id: 1, code: 'holiday'}; + controller.onSelection($event, $days); + + expect(controller.delete).toHaveBeenCalledWith(jasmine.any(Object), selectedDay, expectedEvent); + }); + + it(`should call to the edit() method`, () => { + jest.spyOn(controller, 'edit').mockReturnThis(); + + const selectedDay = new Date(); + const expectedEvent = { + dated: selectedDay, + type: 'leaveOfAbsence' + }; + const $event = { + target: { + closest: () => { + return {$ctrl: {}}; + } + } + }; + const $days = [selectedDay]; + controller.events[selectedDay.getTime()] = expectedEvent; + controller.absenceType = {id: 1, code: 'holiday'}; + controller.onSelection($event, $days); + + expect(controller.edit).toHaveBeenCalledWith(jasmine.any(Object), expectedEvent); + }); + }); + + describe('create()', () => { + it(`should make a HTTP POST query and then call to the repaintCanceller() method`, () => { + jest.spyOn(controller, 'repaintCanceller').mockReturnThis(); + + const dated = new Date(); + const calendarElement = {}; + const expectedResponse = {id: 10}; + + $httpBackend.expect('POST', `Workers/106/createAbsence`).respond(200, expectedResponse); + controller.create(calendarElement, dated); + $httpBackend.flush(); + + const createdEvent = controller.events[dated.getTime()]; + const absenceType = controller.absenceType; + + expect(createdEvent.absenceId).toEqual(expectedResponse.id); + expect(createdEvent.color).toEqual(absenceType.rgb); + expect(controller.repaintCanceller).toHaveBeenCalled(); + }); + }); + + describe('edit()', () => { + it(`should make a HTTP PATCH query and then call to the repaintCanceller() method`, () => { + jest.spyOn(controller, 'repaintCanceller').mockReturnThis(); + + const event = {absenceId: 10}; + const calendarElement = {}; + const newAbsenceType = { + id: 2, + name: 'Leave of absence', + code: 'leaveOfAbsence', + rgb: 'purple' + }; + controller.absenceType = newAbsenceType; + + const expectedParams = {absenceId: 10, absenceTypeId: 2}; + $httpBackend.expect('PATCH', `Workers/106/updateAbsence`, expectedParams).respond(200); + controller.edit(calendarElement, event); + $httpBackend.flush(); + + expect(event.name).toEqual(newAbsenceType.name); + expect(event.color).toEqual(newAbsenceType.rgb); + expect(event.type).toEqual(newAbsenceType.code); + expect(controller.repaintCanceller).toHaveBeenCalled(); + }); + }); + + describe('delete()', () => { + it(`should make a HTTP DELETE query and then call to the repaintCanceller() method`, () => { + jest.spyOn(controller, 'repaintCanceller').mockReturnThis(); + + const expectedParams = {absenceId: 10}; + const calendarElement = {}; + const selectedDay = new Date(); + const expectedEvent = { + dated: selectedDay, + type: 'leaveOfAbsence', + absenceId: 10 + }; + + controller.events[selectedDay.getTime()] = expectedEvent; + + const serializedParams = $httpParamSerializer(expectedParams); + $httpBackend.expect('DELETE', `Workers/106/deleteAbsence?${serializedParams}`).respond(200); + controller.delete(calendarElement, selectedDay, expectedEvent); + $httpBackend.flush(); + + const event = controller.events[selectedDay.getTime()]; + + expect(event).toBeUndefined(); + expect(controller.repaintCanceller).toHaveBeenCalled(); + }); + }); + + describe('repaintCanceller()', () => { + it(`should cancell the callback execution timer`, () => { + jest.spyOn(window, 'clearTimeout'); + jest.spyOn(window, 'setTimeout'); + + const timeoutId = 90; + controller.canceller = timeoutId; + + controller.repaintCanceller(() => { + return 'My callback'; + }); + + expect(window.clearTimeout).toHaveBeenCalledWith(timeoutId); + expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 500); + }); + }); + + describe('refresh()', () => { + it(`should make a HTTP GET query and then call to the onData() method`, () => { + jest.spyOn(controller, 'onData').mockReturnThis(); + + const dated = controller.date; + const started = new Date(dated.getTime()); + started.setMonth(0); + started.setDate(1); + + const ended = new Date(dated.getTime()); + ended.setMonth(12); + ended.setDate(0); + + controller.started = started; + controller.ended = ended; + + const expecteResponse = [{id: 1}]; + const expectedParams = {workerFk: 106, started: started, ended: ended}; + const serializedParams = $httpParamSerializer(expectedParams); + $httpBackend.expect('GET', `WorkerCalendars/absences?${serializedParams}`).respond(200, expecteResponse); + controller.refresh(); + $httpBackend.flush(); + + expect(controller.onData).toHaveBeenCalledWith(expecteResponse); + }); + }); }); }); diff --git a/modules/worker/front/calendar/locale/es.yml b/modules/worker/front/calendar/locale/es.yml index 82939ce91..6681f730f 100644 --- a/modules/worker/front/calendar/locale/es.yml +++ b/modules/worker/front/calendar/locale/es.yml @@ -2,4 +2,6 @@ Calendar: Calendario Holidays: Vacaciones Used: Utilizados of: de -days: días \ No newline at end of file +days: días +Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha +To start adding absences, click an absence type from the right menu and then on the day you want to add an absence: Para empezar a añadir ausencias, haz clic en un tipo de ausencia desde el menu de la derecha y después en el día que quieres añadir la ausencia \ No newline at end of file diff --git a/modules/worker/front/calendar/style.scss b/modules/worker/front/calendar/style.scss index 9b3fc749b..1934a6ca0 100644 --- a/modules/worker/front/calendar/style.scss +++ b/modules/worker/front/calendar/style.scss @@ -2,6 +2,7 @@ vn-worker-calendar { .calendars { + position: relative; display: flex; flex-wrap: wrap; justify-content: center; @@ -16,4 +17,23 @@ vn-worker-calendar { max-width: 288px; } } + + vn-chip.selectable { + cursor: pointer + } + + vn-chip.selectable:hover { + opacity: 0.8 + } + + vn-chip vn-avatar { + text-align: center; + color: white + } + + vn-icon[icon="info"] { + position: absolute; + top: 16px; + right: 16px + } }