tests & refactor calendar component #811
This commit is contained in:
parent
836f210f6c
commit
f337799e39
|
@ -75,14 +75,14 @@
|
|||
</section>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="days">
|
||||
<section ng-repeat="day in $ctrl.days" class="day {{day.className}}"
|
||||
<section ng-repeat="day in $ctrl.days" class="day {{day.event.className || day.className}}"
|
||||
ng-click="$ctrl.select($index)"
|
||||
ng-style="{'color': day.style.color}">
|
||||
ng-style="{'color': day.event.style.color}">
|
||||
<span ng-if="day.event" vn-tooltip="{{day.event.title}}"
|
||||
ng-style="{'background-color': day.style.background}">
|
||||
{{::day.date | date: 'd'}}
|
||||
ng-style="{'background-color': day.event.style.background}">
|
||||
{{::day.dated | date: 'd'}}
|
||||
</span>
|
||||
<span ng-if="!day.event">{{::day.date | date: 'd'}}</span>
|
||||
<span ng-if="!day.event">{{::day.dated | date: 'd'}}</span>
|
||||
</section>
|
||||
</vn-horizontal>
|
||||
</vn-vertical>
|
||||
|
|
|
@ -15,37 +15,57 @@ export default class Calendar extends Component {
|
|||
this.skip = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initial date
|
||||
*
|
||||
* @return {Date} - Default date
|
||||
*/
|
||||
get defaultDate() {
|
||||
return this._defaultDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new initial date
|
||||
*
|
||||
* @param {Date} value - New default date
|
||||
*/
|
||||
set defaultDate(value) {
|
||||
this._defaultDate = value;
|
||||
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
get currentMonth() {
|
||||
return this.defaultDate;
|
||||
}
|
||||
|
||||
get events() {
|
||||
return this._events;
|
||||
}
|
||||
|
||||
set events(value) {
|
||||
/**
|
||||
* Sets initial events
|
||||
*
|
||||
* @param {Array} value - Array of events
|
||||
*/
|
||||
set data(value) {
|
||||
if (!value) return;
|
||||
|
||||
value.map(event => {
|
||||
event.date = new Date(event.date);
|
||||
});
|
||||
this.events = [];
|
||||
|
||||
this._events = value;
|
||||
value.forEach(event => {
|
||||
this.addEvent(event);
|
||||
});
|
||||
|
||||
if (value.length && this.defaultDate)
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current month date
|
||||
*/
|
||||
|
||||
get currentMonth() {
|
||||
return this.defaultDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets next month date
|
||||
*
|
||||
* @return {Date}
|
||||
*/
|
||||
get nextMonth() {
|
||||
const newDate = new Date(this.currentMonth);
|
||||
newDate.setMonth(this.currentMonth.getMonth() + 1);
|
||||
|
@ -53,6 +73,11 @@ export default class Calendar extends Component {
|
|||
return newDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets previous month date
|
||||
*
|
||||
* @return {Date}
|
||||
*/
|
||||
get previousMonth() {
|
||||
const newDate = new Date(this.currentMonth);
|
||||
newDate.setMonth(this.currentMonth.getMonth() - 1);
|
||||
|
@ -101,73 +126,100 @@ export default class Calendar extends Component {
|
|||
|
||||
this.days = [];
|
||||
|
||||
for (let fieldIndex = 1; fieldIndex <= maxFields; fieldIndex++) {
|
||||
for (let fieldIndex = 1; fieldIndex < maxFields; fieldIndex++) {
|
||||
// Insert previous month days
|
||||
if (fieldIndex < weekdayOffset) {
|
||||
this.addDay(this.previousMonth, dayPrevious, 'gray');
|
||||
const dated = new Date(
|
||||
this.previousMonth.getFullYear(),
|
||||
this.previousMonth.getMonth(), dayPrevious);
|
||||
|
||||
this.insertDay(dated, 'gray');
|
||||
dayPrevious++;
|
||||
} else if (fieldIndex >= weekdayOffset && dayCurrent <= currentLastDay) {
|
||||
this.addDay(this.currentMonth, dayCurrent);
|
||||
}
|
||||
|
||||
// Insert current month days
|
||||
if (fieldIndex >= weekdayOffset && dayCurrent <= currentLastDay) {
|
||||
const dated = new Date(
|
||||
this.currentMonth.getFullYear(),
|
||||
this.currentMonth.getMonth(), dayCurrent);
|
||||
|
||||
this.insertDay(dated);
|
||||
dayCurrent++;
|
||||
} else if (fieldIndex >= weekdayOffset && dayCurrent > currentLastDay) {
|
||||
this.addDay(this.nextMonth, dayNext, 'gray');
|
||||
}
|
||||
|
||||
// Insert next month days
|
||||
if (fieldIndex >= weekdayOffset && dayCurrent > currentLastDay) {
|
||||
const dated = new Date(
|
||||
this.nextMonth.getFullYear(),
|
||||
this.nextMonth.getMonth(), dayNext);
|
||||
|
||||
this.insertDay(dated, 'gray');
|
||||
dayNext++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addDay(date, day, className = '', style) {
|
||||
const newDate = new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(), day);
|
||||
|
||||
/**
|
||||
* Inserts a date on an array of month days
|
||||
*
|
||||
* @param {Date} dated - Date of month
|
||||
* @param {String} className - Default class style
|
||||
*/
|
||||
insertDay(dated, className = '') {
|
||||
let event = this.events.find(event => {
|
||||
return event.date >= newDate && event.date <= newDate;
|
||||
return event.dated >= dated && event.dated <= dated;
|
||||
});
|
||||
|
||||
if (newDate.getMonth() === this.currentMonth.getMonth() && newDate.getDay() == 0)
|
||||
// Weeekends
|
||||
if (dated.getMonth() === this.currentMonth.getMonth() && dated.getDay() == 0)
|
||||
className = 'red';
|
||||
|
||||
if (event) {
|
||||
className = event.className;
|
||||
style = event.style;
|
||||
}
|
||||
|
||||
|
||||
this.days.push({date: newDate, className, style, event});
|
||||
this.days.push({dated, className, event});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new calendar event
|
||||
*
|
||||
* @param {Date} date - Day to add event
|
||||
* @param {String} className - [green, blue, orange, red]
|
||||
* @param {String} title - Tooltip description
|
||||
* @param {Boolean} isRemovable - True if is removable by users
|
||||
* @param {Object} options - Event params
|
||||
* @param {Date} options.dated - Day to add event
|
||||
* @param {String} options.title - Tooltip description
|
||||
* @param {String} options.className - ClassName style
|
||||
* @param {Object} options.style - Style properties
|
||||
* @param {Boolean} options.isRemovable - True if is removable by users
|
||||
*/
|
||||
addEvent(date, className, title = '', isRemovable = true) {
|
||||
addEvent(options) {
|
||||
if (!Object.hasOwnProperty.call(options, 'isRemovable'))
|
||||
options.isRemovable = true;
|
||||
|
||||
options.dated = new Date(options.dated);
|
||||
options.dated.setHours(0, 0, 0, 0);
|
||||
|
||||
const event = this.events.findIndex(event => {
|
||||
return event.date >= date && event.date <= date;
|
||||
return event.dated >= options.dated && event.dated <= options.dated;
|
||||
});
|
||||
|
||||
if (event == -1)
|
||||
this.events.push({date, className, title, isRemovable});
|
||||
|
||||
this.repaint();
|
||||
if (event < 0)
|
||||
this.events.push(options);
|
||||
}
|
||||
|
||||
removeEvent(date) {
|
||||
/**
|
||||
* Removes an event from an array of events
|
||||
* @param {Object} dated - Dated event
|
||||
*/
|
||||
removeEvent(dated) {
|
||||
dated = new Date(dated);
|
||||
dated.setHours(0, 0, 0, 0);
|
||||
|
||||
const event = this.events.findIndex(event => {
|
||||
return event.date >= date && event.date <= date;
|
||||
return event.dated >= dated && event.dated <= dated;
|
||||
});
|
||||
|
||||
if (event > -1)
|
||||
this.events.splice(event, 1);
|
||||
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves to next month
|
||||
* Moves to next month(s)
|
||||
*
|
||||
* @param {Integer} skip - Months to skip at once
|
||||
*/
|
||||
|
@ -180,7 +232,7 @@ export default class Calendar extends Component {
|
|||
}
|
||||
|
||||
/**
|
||||
* Moves to previous month
|
||||
* Moves to previous month(s)
|
||||
*
|
||||
* @param {Integer} skip - Months to skip at once
|
||||
*/
|
||||
|
@ -204,11 +256,16 @@ export default class Calendar extends Component {
|
|||
this.emit('selection', {values: [day]});
|
||||
}
|
||||
|
||||
/**
|
||||
* WeekDay selection event
|
||||
*
|
||||
* @param {Integer} weekday - weekday index
|
||||
*/
|
||||
selectAll(weekday) {
|
||||
let selected = [];
|
||||
for (let i in this.days) {
|
||||
const day = this.days[i];
|
||||
const date = day.date;
|
||||
const date = day.dated;
|
||||
day.index = i;
|
||||
if (date.getDay() === weekday && date.getMonth() == this.defaultDate.getMonth())
|
||||
selected.push(day);
|
||||
|
@ -217,14 +274,14 @@ export default class Calendar extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
Calendar.$inject = ['$element', '$scope', '$attrs'];
|
||||
Calendar.$inject = ['$element', '$scope'];
|
||||
|
||||
ngModule.component('vnCalendar', {
|
||||
template: require('./index.html'),
|
||||
controller: Calendar,
|
||||
bindings: {
|
||||
model: '<',
|
||||
events: '<?',
|
||||
data: '<?',
|
||||
defaultDate: '<?',
|
||||
onSelection: '&?',
|
||||
onMoveNext: '&?',
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
fdescribe('Component vnCalendar', () => {
|
||||
let controller;
|
||||
let $element;
|
||||
|
||||
beforeEach(ngModule('vnCore'));
|
||||
|
||||
beforeEach(inject(($compile, $rootScope) => {
|
||||
$element = $compile(`<vn-calendar></vn-calendar`)($rootScope);
|
||||
controller = $element.controller('vnCalendar');
|
||||
controller.defaultDate = new Date();
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
$element.remove();
|
||||
});
|
||||
|
||||
describe('events() setter', () => {
|
||||
it(`should set an array of events and convert string dates to string object, then call repaint() method`, () => {
|
||||
spyOn(controller, 'repaint');
|
||||
|
||||
let currentDate = new Date().toString();
|
||||
controller.events = [
|
||||
{dated: currentDate, title: 'Event 1'},
|
||||
{dated: currentDate, title: 'Event 2'},
|
||||
];
|
||||
|
||||
expect(controller.events[0].dated instanceof Object).toBeTruthy();
|
||||
expect(controller.repaint).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('addEvent()', () => {
|
||||
it(`should add an event to an array of events, then call repaint() method`, () => {
|
||||
spyOn(controller, 'repaint');
|
||||
controller.events = [];
|
||||
controller.addEvent({
|
||||
dated: new Date(),
|
||||
title: 'My event',
|
||||
className: 'color'
|
||||
});
|
||||
const firstEvent = controller.events[0];
|
||||
|
||||
expect(firstEvent.title).toEqual('My event');
|
||||
expect(firstEvent.isRemovable).toBeDefined();
|
||||
expect(firstEvent.isRemovable).toBeTruthy();
|
||||
expect(controller.repaint).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it(`should not repeat an event for the same date, should not call repaint() method`, () => {
|
||||
spyOn(controller, 'repaint');
|
||||
const curDate = new Date();
|
||||
controller._events = [{
|
||||
dated: curDate,
|
||||
title: 'My event 1',
|
||||
className: 'color'
|
||||
}];
|
||||
controller.addEvent({
|
||||
dated: curDate,
|
||||
title: 'My event 2',
|
||||
className: 'color'
|
||||
});
|
||||
|
||||
const firstEvent = controller.events[0];
|
||||
|
||||
expect(controller.events.length).toEqual(1);
|
||||
expect(firstEvent.title).toEqual('My event 1');
|
||||
expect(controller.repaint).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeEvent()', () => {
|
||||
it(`should remove an event from an array of events, then call repaint() method`, () => {
|
||||
spyOn(controller, 'repaint');
|
||||
const curDate = new Date();
|
||||
controller._events = [{
|
||||
dated: curDate,
|
||||
title: 'My event 1',
|
||||
className: 'color'
|
||||
}];
|
||||
controller.removeEvent(curDate);
|
||||
|
||||
expect(controller.events.length).toEqual(0);
|
||||
expect(controller.repaint).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('moveNext()', () => {
|
||||
it(`should shift to the next n months, then emit a 'moveNext' event`, () => {
|
||||
spyOn(controller, 'emit');
|
||||
const currentMonth = controller.defaultDate.getMonth();
|
||||
let nextMonth = currentMonth + 1;
|
||||
|
||||
controller.moveNext(1);
|
||||
|
||||
expect(controller.defaultDate.getMonth()).toEqual(nextMonth);
|
||||
expect(controller.emit).toHaveBeenCalledWith('moveNext');
|
||||
});
|
||||
});
|
||||
|
||||
describe('movePrevious()', () => {
|
||||
it(`should shift to the previous n months, then emit a 'movePrevious' event`, () => {
|
||||
spyOn(controller, 'emit');
|
||||
const currentMonth = controller.defaultDate.getMonth();
|
||||
let previousMonth = currentMonth - 1;
|
||||
|
||||
controller.movePrevious(1);
|
||||
|
||||
expect(controller.defaultDate.getMonth()).toEqual(previousMonth);
|
||||
expect(controller.emit).toHaveBeenCalledWith('movePrevious');
|
||||
});
|
||||
});
|
||||
|
||||
describe('select()', () => {
|
||||
it(`should return the selected element, then emit a 'selection' event`, () => {
|
||||
spyOn(controller, 'emit');
|
||||
const days = [{dated: new Date()}];
|
||||
controller.days = days;
|
||||
|
||||
controller.select(0);
|
||||
|
||||
expect(controller.emit).toHaveBeenCalledWith('selection', {values: days});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -19,6 +19,7 @@ vn-calendar {
|
|||
.weekdays {
|
||||
border-bottom: 1px solid $color-hover-cd;
|
||||
border-top: 1px solid $color-hover-cd;
|
||||
color: $color-font-secondary;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
|
|
|
@ -7,20 +7,14 @@
|
|||
primary-key="zoneFk"
|
||||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<vn-calendar
|
||||
pad-small
|
||||
vn-id="stMonth"
|
||||
events="$ctrl.events"
|
||||
skip="2"
|
||||
<vn-calendar pad-small vn-id="stMonth" skip="2"
|
||||
data="$ctrl.events"
|
||||
on-selection="$ctrl.onSelection(stMonth, values)"
|
||||
on-move-next="$ctrl.onMoveNext(ndMonth)"
|
||||
on-move-previous="$ctrl.onMovePrevious(ndMonth)">
|
||||
</vn-calendar>
|
||||
<vn-calendar
|
||||
pad-small
|
||||
vn-id="ndMonth"
|
||||
events="$ctrl.events"
|
||||
skip="2"
|
||||
<vn-calendar pad-small vn-id="ndMonth" skip="2"
|
||||
data="$ctrl.events"
|
||||
display-controls="false"
|
||||
on-selection="$ctrl.onSelection(ndMonth, values)"
|
||||
default-date="$ctrl.ndMonthDate">
|
||||
|
|
|
@ -9,7 +9,6 @@ class Controller {
|
|||
this.stMonthDate = new Date();
|
||||
this.ndMonthDate = new Date();
|
||||
this.ndMonthDate.setMonth(this.ndMonthDate.getMonth() + 1);
|
||||
this.events = [];
|
||||
}
|
||||
|
||||
$postLink() {
|
||||
|
@ -53,28 +52,23 @@ class Controller {
|
|||
this._data = value;
|
||||
|
||||
if (!value) return;
|
||||
|
||||
const events = [];
|
||||
value.forEach(event => {
|
||||
let date = new Date(event.delivered);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
events.push({
|
||||
date: date,
|
||||
dated: event.delivered,
|
||||
className: 'green-circle',
|
||||
title: 'Has delivery',
|
||||
isRemovable: true
|
||||
title: 'Has delivery'
|
||||
});
|
||||
});
|
||||
|
||||
this.events = this.events.concat(events);
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
onSelection(calendar, values) {
|
||||
let totalEvents = 0;
|
||||
values.forEach(day => {
|
||||
const exists = this.events.findIndex(event => {
|
||||
return event.date >= day.date && event.date <= day.date
|
||||
const exists = calendar.events.findIndex(event => {
|
||||
return event.dated >= day.dated && event.dated <= day.dated
|
||||
&& event.isRemovable;
|
||||
});
|
||||
|
||||
|
@ -82,54 +76,56 @@ class Controller {
|
|||
});
|
||||
|
||||
if (totalEvents > (values.length / 2))
|
||||
this.removeEvents(values);
|
||||
this.removeEvents(calendar, values);
|
||||
else
|
||||
this.addEvents(values);
|
||||
this.insertEvents(calendar, values);
|
||||
}
|
||||
|
||||
addEvents(days) {
|
||||
insertEvents(calendar, days) {
|
||||
days.forEach(day => {
|
||||
const event = this.events.find(event => {
|
||||
return event.date >= day.date && event.date <= day.date;
|
||||
const event = calendar.events.find(event => {
|
||||
return event.dated >= day.dated && event.dated <= day.dated;
|
||||
});
|
||||
|
||||
if (event)
|
||||
return false;
|
||||
if (event) return false;
|
||||
|
||||
this.$scope.model.insert({
|
||||
zoneFk: this.zone.id,
|
||||
delivered: day.date
|
||||
delivered: day.dated
|
||||
});
|
||||
|
||||
this.stMonth.addEvent(day.date, 'green-circle', 'Has delivery', true);
|
||||
this.stMonth.repaint();
|
||||
this.ndMonth.addEvent(day.date, 'green-circle', 'Has delivery', true);
|
||||
this.ndMonth.repaint();
|
||||
calendar.addEvent({
|
||||
dated: day.dated,
|
||||
className: 'green-circle',
|
||||
title: 'Has delivery'
|
||||
});
|
||||
});
|
||||
|
||||
this.$scope.model.save().then(() => {
|
||||
this.events = calendar.events;
|
||||
});
|
||||
this.$scope.model.save();
|
||||
}
|
||||
|
||||
removeEvents(days) {
|
||||
removeEvents(calendar, days) {
|
||||
let dates = [];
|
||||
days.forEach(day => {
|
||||
const event = this.events.find(event => {
|
||||
return event.date >= day.date && event.date <= day.date;
|
||||
const event = calendar.events.find(event => {
|
||||
return event.dated >= day.dated && event.dated <= day.dated;
|
||||
});
|
||||
|
||||
if (event && !event.isRemovable)
|
||||
return false;
|
||||
|
||||
dates.push(day.date);
|
||||
dates.push(day.dated);
|
||||
|
||||
this.stMonth.removeEvent(day.date);
|
||||
this.stMonth.repaint();
|
||||
this.ndMonth.removeEvent(day.date);
|
||||
this.ndMonth.repaint();
|
||||
calendar.removeEvent(day.dated);
|
||||
});
|
||||
|
||||
if (dates.length == 0) return;
|
||||
const params = {zoneFk: this.zone.id, dates};
|
||||
this.$http.post('/agency/api/zoneCalendars/removeByDate', params);
|
||||
this.$http.post('/agency/api/zoneCalendars/removeByDate', params).then(() => {
|
||||
this.events = calendar.events;
|
||||
});
|
||||
}
|
||||
|
||||
onMoveNext(calendar) {
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
</vn-horizontal>
|
||||
<vn-horizontal class="calendar-list">
|
||||
<section class="calendar" ng-repeat="month in $ctrl.months">
|
||||
<vn-calendar vn-id="stMonth"
|
||||
<vn-calendar
|
||||
data="$ctrl.events"
|
||||
default-date="month"
|
||||
events="$ctrl.events"
|
||||
display-controls="false">
|
||||
</vn-calendar>
|
||||
</section>
|
||||
|
|
|
@ -7,7 +7,6 @@ class Controller {
|
|||
this.$http = $http;
|
||||
this.months = this.monthsOfYear();
|
||||
this.events = [];
|
||||
this.eventMap = {};
|
||||
}
|
||||
|
||||
get worker() {
|
||||
|
@ -31,7 +30,6 @@ class Controller {
|
|||
|
||||
this.setHolidays(res.data);
|
||||
this.setWorkerCalendar(res.data);
|
||||
|
||||
this.calendar = res.data.calendar;
|
||||
});
|
||||
}
|
||||
|
@ -43,7 +41,7 @@ class Controller {
|
|||
const events = [];
|
||||
holidays.forEach(holiday => {
|
||||
events.push({
|
||||
date: holiday.dated,
|
||||
dated: holiday.dated,
|
||||
className: 'red',
|
||||
title: holiday.detail.description || holiday.type.name,
|
||||
isRemovable: false
|
||||
|
@ -60,7 +58,7 @@ class Controller {
|
|||
absences.forEach(absence => {
|
||||
const absenceType = absence.absenceType;
|
||||
events.push({
|
||||
date: absence.dated,
|
||||
dated: absence.dated,
|
||||
title: absenceType.name,
|
||||
style: {
|
||||
background: absenceType.rgb
|
||||
|
|
Loading…
Reference in New Issue