src/components/SchedulerData.js
import dayjs from 'dayjs';
import quarterOfYear from 'dayjs/plugin/quarterOfYear';
import utc from 'dayjs/plugin/utc';
import weekday from 'dayjs/plugin/weekday';
import { RRuleSet, rrulestr } from 'rrule';
import { CellUnit, DATE_FORMAT, DATETIME_FORMAT, ViewType } from '../config/default';
import config from '../config/scheduler';
import behaviors from '../helper/behaviors';
export default class SchedulerData {
constructor(date = dayjs(), viewType = ViewType.Week, showAgenda = false, isEventPerspective = false, newConfig = undefined, newBehaviors = undefined) {
this.resources = [];
this.events = [];
this.eventGroups = [];
this.eventGroupsAutoGenerated = true;
this.viewType = viewType;
this.cellUnit = viewType === ViewType.Day ? CellUnit.Hour : CellUnit.Day;
this.showAgenda = showAgenda;
this.isEventPerspective = isEventPerspective;
this.resizing = false;
this.scrollToSpecialDayjs = false;
this.documentWidth = 0;
this._shouldReloadViewType = false;
this.calendarPopoverLocale = undefined;
dayjs.extend(quarterOfYear);
dayjs.extend(weekday);
dayjs.extend(utc);
this.localeDayjs = dayjs;
this.config = newConfig === undefined ? config : { ...config, ...newConfig };
this._validateMinuteStep(this.config.minuteStep);
this.behaviors = newBehaviors === undefined ? behaviors : { ...behaviors, ...newBehaviors };
this._resolveDate(0, date);
this._createHeaders();
this._createRenderData();
}
setSchedulerLocale(preset) {
if (!preset) return;
this.localeDayjs.locale(preset);
this._shouldReloadViewType = true;
this.setViewType(this.viewType, this.showAgenda, this.isEventPerspective);
}
setCalendarPopoverLocale(lang) {
if (lang) {
this.calendarPopoverLocale = lang;
}
}
setResources(resources) {
this._validateResource(resources);
this.resources = Array.from(new Set(resources));
this._createRenderData();
this.setScrollToSpecialDayjs(true);
}
setEventGroupsAutoGenerated(autoGenerated) {
this.eventGroupsAutoGenerated = autoGenerated;
}
// optional
setEventGroups(eventGroups) {
this._validateEventGroups(eventGroups);
this.eventGroups = Array.from(new Set(eventGroups));
this.eventGroupsAutoGenerated = false;
this._createRenderData();
this.setScrollToSpecialDayjs(true);
}
setMinuteStep(minuteStep) {
if (this.config.minuteStep !== minuteStep) {
this._validateMinuteStep(minuteStep);
this.config.minuteStep = minuteStep;
this._createHeaders();
this._createRenderData();
}
}
setBesidesWidth(besidesWidth) {
if (besidesWidth >= 0) {
this.config.besidesWidth = besidesWidth;
}
}
getMinuteStepsInHour() {
return 60 / this.config.minuteStep;
}
addResource(resource) {
const existedResources = this.resources.filter(x => x.id === resource.id);
if (existedResources.length === 0) {
this.resources.push(resource);
this._createRenderData();
}
}
addEventGroup(eventGroup) {
const existedEventGroups = this.eventGroups.filter(x => x.id === eventGroup.id);
if (existedEventGroups.length === 0) {
this.eventGroups.push(eventGroup);
this._createRenderData();
}
}
removeEventGroupById(eventGroupId) {
let index = -1;
this.eventGroups.forEach((item, idx) => {
if (item.id === eventGroupId) index = idx;
});
if (index !== -1) this.eventGroups.splice(index, 1);
}
containsEventGroupId(eventGroupId) {
let index = -1;
this.eventGroups.forEach((item, idx) => {
if (item.id === eventGroupId) index = idx;
});
return index !== -1;
}
setEvents(events) {
this._validateEvents(events);
this.events = Array.from(events);
if (this.eventGroupsAutoGenerated) this._generateEventGroups();
if (this.config.recurringEventsEnabled) this._handleRecurringEvents();
this._createRenderData();
}
setScrollToSpecialDayjs(scrollToSpecialDayjs) {
if (this.config.scrollToSpecialDayjsEnabled) this.scrollToSpecialDayjs = scrollToSpecialDayjs;
}
prev() {
this._resolveDate(-1);
this.events = [];
this._createHeaders();
this._createRenderData();
}
next() {
this._resolveDate(1);
this.events = [];
this._createHeaders();
this._createRenderData();
}
setDate(date = dayjs(new Date())) {
this._resolveDate(0, date);
this.events = [];
this._createHeaders();
this._createRenderData();
}
setViewType(viewType = ViewType.Week, showAgenda = false, isEventPerspective = false) {
this.showAgenda = showAgenda;
this.isEventPerspective = isEventPerspective;
this.cellUnit = CellUnit.Day;
if (this.viewType !== viewType || this._shouldReloadViewType) {
let date = this.startDate;
if (viewType === ViewType.Custom || viewType === ViewType.Custom1 || viewType === ViewType.Custom2) {
this.viewType = viewType;
this._resolveDate(0, date);
} else {
if (this.viewType < viewType) {
if (viewType === ViewType.Week) {
this.startDate = this.localeDayjs(new Date(date)).startOf('week');
this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('week');
} else if (viewType === ViewType.Month) {
this.startDate = this.localeDayjs(new Date(date)).startOf('month');
this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('month');
} else if (viewType === ViewType.Quarter) {
this.startDate = this.localeDayjs(new Date(date)).startOf('quarter');
this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('quarter');
} else if (viewType === ViewType.Year) {
this.startDate = this.localeDayjs(new Date(date)).startOf('year');
this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('year');
}
} else {
const start = this.localeDayjs(new Date(this.startDate));
const end = this.localeDayjs(new Date(this.endDate)).add(1, 'days');
if (this.selectDate !== undefined) {
const selectDate = this.localeDayjs(new Date(this.selectDate));
if (selectDate >= start && selectDate < end) {
date = this.selectDate;
}
}
const now = this.localeDayjs();
if (now >= start && now < end) {
date = now.startOf('day');
}
if (viewType === ViewType.Day) {
this.startDate = date;
this.endDate = this.startDate;
this.cellUnit = CellUnit.Hour;
} else if (viewType === ViewType.Week) {
this.startDate = this.localeDayjs(new Date(date)).startOf('week');
this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('week');
} else if (viewType === ViewType.Month) {
this.startDate = this.localeDayjs(new Date(date)).startOf('month');
this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('month');
} else if (viewType === ViewType.Quarter) {
this.startDate = this.localeDayjs(new Date(date)).startOf('quarter');
this.endDate = this.localeDayjs(new Date(this.startDate)).endOf('quarter');
}
}
this.viewType = viewType;
}
this._shouldReloadViewType = false;
this.events = [];
this._createHeaders();
this._createRenderData();
this.setScrollToSpecialDayjs(true);
}
}
setSchedulerMaxHeight(newSchedulerMaxHeight) {
this.config.schedulerMaxHeight = newSchedulerMaxHeight;
}
isSchedulerResponsive() {
return !!this.config.schedulerWidth.endsWith && this.config.schedulerWidth.endsWith('%');
}
toggleExpandStatus(slotId) {
let slotEntered = false;
let slotIndent = -1;
let isExpanded = false;
const expandedMap = new Map();
this.renderData.forEach(item => {
if (slotEntered === false) {
if (item.slotId === slotId && item.hasChildren) {
slotEntered = true;
isExpanded = !item.expanded;
item.expanded = isExpanded;
slotIndent = item.indent;
expandedMap.set(item.indent, {
expanded: item.expanded,
render: item.render,
});
}
} else if (item.indent > slotIndent) {
const expandStatus = expandedMap.get(item.indent - 1);
item.render = expandStatus.expanded && expandStatus.render;
if (item.hasChildren) {
expandedMap.set(item.indent, {
expanded: item.expanded,
render: item.render,
});
}
} else {
slotEntered = false;
}
});
}
isResourceViewResponsive() {
const resourceTableWidth = this.getResourceTableConfigWidth();
return !!resourceTableWidth.endsWith && resourceTableWidth.endsWith('%');
}
isContentViewResponsive() {
const contentCellWidth = this.getContentCellConfigWidth();
return !!contentCellWidth.endsWith && contentCellWidth.endsWith('%');
}
getSchedulerWidth() {
const baseWidth = this.documentWidth - this.config.besidesWidth > 0 ? this.documentWidth - this.config.besidesWidth : 0;
return this.isSchedulerResponsive() ? parseInt((baseWidth * Number(this.config.schedulerWidth.slice(0, -1))) / 100, 10) : this.config.schedulerWidth;
}
getResourceTableWidth() {
const resourceTableConfigWidth = this.getResourceTableConfigWidth();
const schedulerWidth = this.getSchedulerWidth();
let resourceTableWidth = this.isResourceViewResponsive() ? parseInt((schedulerWidth * Number(resourceTableConfigWidth.slice(0, -1))) / 100, 10) : resourceTableConfigWidth;
if (this.isSchedulerResponsive() && this.getContentTableWidth() + resourceTableWidth < schedulerWidth) resourceTableWidth = schedulerWidth - this.getContentTableWidth();
return resourceTableWidth;
}
getContentCellWidth() {
const contentCellConfigWidth = this.getContentCellConfigWidth();
const schedulerWidth = this.getSchedulerWidth();
return this.isContentViewResponsive() ? parseInt((schedulerWidth * Number(contentCellConfigWidth.slice(0, -1))) / 100, 10) : contentCellConfigWidth;
}
getContentTableWidth() {
return this.headers.length * this.getContentCellWidth();
}
getScrollToSpecialDayjs() {
if (this.config.scrollToSpecialDayjsEnabled) return this.scrollToSpecialDayjs;
return false;
}
getSlots() {
return this.isEventPerspective ? this.eventGroups : this.resources;
}
getSlotById(slotId) {
const slots = this.getSlots();
let slot;
slots.forEach(item => {
if (item.id === slotId) slot = item;
});
return slot;
}
getResourceById(resourceId) {
let resource;
this.resources.forEach(item => {
if (item.id === resourceId) resource = item;
});
return resource;
}
getTableHeaderHeight() {
return this.config.tableHeaderHeight;
}
getSchedulerContentDesiredHeight() {
let height = 0;
this.renderData.forEach(item => {
if (item.render) height += item.rowHeight;
});
return height;
}
getCellMaxEvents() {
const viewConfigMap = {
[ViewType.Week]: 'weekMaxEvents',
[ViewType.Day]: 'dayMaxEvents',
[ViewType.Month]: 'monthMaxEvents',
[ViewType.Year]: 'yearMaxEvents',
[ViewType.Quarter]: 'quarterMaxEvents',
};
const configProperty = viewConfigMap[this.viewType] || 'customMaxEvents';
return this.config[configProperty];
}
getCalendarPopoverLocale() {
return this.calendarPopoverLocale;
}
getSelectedDate() {
return this.selectDate.format(DATE_FORMAT);
}
getViewStartDate() {
return this.startDate;
}
getViewEndDate() {
return this.endDate;
}
getViewDates() {
return {
startDate: this.startDate,
endDate: this.endDate,
};
}
getDateLabel() {
const start = this.localeDayjs(new Date(this.startDate));
const end = this.localeDayjs(new Date(this.endDate));
let dateLabel = start.format('LL');
if (start !== end) dateLabel = `${start.format('LL')}-${end.format('LL')}`;
if (this.behaviors.getDateLabelFunc) dateLabel = this.behaviors.getDateLabelFunc(this, this.viewType, this.startDate, this.endDate);
return dateLabel;
}
addEvent(newEvent) {
this._attachEvent(newEvent);
if (this.eventGroupsAutoGenerated) this._generateEventGroups();
this._createRenderData();
}
updateEventStart(event, newStart) {
this._detachEvent(event);
event.start = newStart;
this._attachEvent(event);
this._createRenderData();
}
updateEventEnd(event, newEnd) {
event.end = newEnd;
this._createRenderData();
}
swapEvent(eventSource, eventDest) {
// Swap group or resource IDs
if (this.isEventPerspective) {
[eventSource.groupId, eventDest.groupId] = [eventDest.groupId, eventSource.groupId];
[eventSource.groupName, eventDest.groupName] = [eventDest.groupName, eventSource.groupName];
} else {
[eventSource.resourceId, eventDest.resourceId] = [eventDest.resourceId, eventSource.resourceId];
}
// Swap start and end times
[eventSource.start, eventDest.start] = [eventDest.start, eventSource.start];
[eventSource.end, eventDest.end] = [eventDest.end, eventSource.end];
// Update the events
this._detachEvent(eventSource);
this._detachEvent(eventDest);
this._attachEvent(eventSource);
this._attachEvent(eventDest);
this._createRenderData();
}
swapEvent2(eventSource, eventDest) {
const tempEventSource = { ...eventSource };
const tempEventDest = { ...eventDest };
this._detachEvent(eventSource);
this._detachEvent(eventDest);
if (this.isEventPerspective) {
tempEventSource.groupId = eventDest.groupId;
tempEventSource.groupName = eventDest.groupName;
tempEventDest.groupId = eventSource.groupId;
tempEventDest.groupName = eventSource.groupName;
} else {
tempEventSource.resourceId = eventDest.resourceId;
tempEventDest.resourceId = eventSource.resourceId;
}
tempEventSource.end = eventDest.end;
tempEventSource.start = eventDest.start;
tempEventDest.end = eventSource.end;
tempEventDest.start = eventSource.start;
this._attachEvent(tempEventSource);
this._attachEvent(tempEventDest);
this._createRenderData();
}
moveEvent(event, newSlotId, newSlotName, newStart, newEnd) {
this._detachEvent(event);
if (this.isEventPerspective) {
event.groupId = newSlotId;
event.groupName = newSlotName;
} else event.resourceId = newSlotId;
event.end = newEnd;
event.start = newStart;
this._attachEvent(event);
this._createRenderData();
}
isEventInTimeWindow(eventStart, eventEnd, windowStart, windowEnd) {
return eventStart < windowEnd && eventEnd > windowStart;
}
removeEvent(event) {
const index = this.events.indexOf(event);
if (index !== -1) {
this.events.splice(index, 1);
this._createRenderData();
}
}
removeEventById(eventId) {
let index = -1;
this.events.forEach((item, idx) => {
if (item.id === eventId) index = idx;
});
if (index !== -1) {
this.events.splice(index, 1);
this._createRenderData();
}
}
getResourceTableConfigWidth() {
if (this.showAgenda) {
return this.config.agendaResourceTableWidth;
}
const viewConfigMap = {
[ViewType.Week]: 'weekResourceTableWidth',
[ViewType.Day]: 'dayResourceTableWidth',
[ViewType.Month]: 'monthResourceTableWidth',
[ViewType.Year]: 'yearResourceTableWidth',
[ViewType.Quarter]: 'quarterResourceTableWidth',
};
const configProperty = viewConfigMap[this.viewType] || 'customResourceTableWidth';
return this.config[configProperty];
}
getContentCellConfigWidth() {
const viewConfigMap = {
[ViewType.Week]: 'weekCellWidth',
[ViewType.Day]: 'dayCellWidth',
[ViewType.Month]: 'monthCellWidth',
[ViewType.Year]: 'yearCellWidth',
[ViewType.Quarter]: 'quarterCellWidth',
};
const configProperty = viewConfigMap[this.viewType] || 'customCellWidth';
return this.config[configProperty];
}
_setDocumentWidth(documentWidth) {
if (documentWidth >= 0) {
this.documentWidth = documentWidth;
}
}
_detachEvent(event) {
const index = this.events.indexOf(event);
if (index !== -1) this.events.splice(index, 1);
}
_attachEvent(event) {
let pos = 0;
const eventStart = this.localeDayjs(new Date(event.start));
this.events.forEach((item, index) => {
const start = this.localeDayjs(new Date(item.start));
if (eventStart >= start) pos = index + 1;
});
this.events.splice(pos, 0, event);
}
_handleRecurringEvents() {
const recurringEvents = this.events.filter(x => !!x.rrule);
recurringEvents.forEach(item => {
this._detachEvent(item);
});
recurringEvents.forEach(item => {
const windowStart = this.startDate;
const windowEnd = this.endDate.add(1, 'days');
const oldStart = this.localeDayjs(new Date(item.start));
const oldEnd = this.localeDayjs(new Date(item.end));
let rule = rrulestr(item.rrule);
let oldDtstart;
const oldUntil = rule.origOptions.until || windowEnd.toDate();
if (rule.origOptions.dtstart) {
oldDtstart = this.localeDayjs(new Date(rule.origOptions.dtstart));
}
// rule.origOptions.dtstart = oldStart.toDate();
if (windowEnd < oldUntil) {
rule.origOptions.until = windowEnd.toDate();
}
// reload
rule = rrulestr(rule.toString());
if (item.exdates || item.exrule) {
const rruleSet = new RRuleSet();
rruleSet.rrule(rule);
if (item.exrule) {
rruleSet.exrule(rrulestr(item.exrule));
}
if (item.exdates) {
item.exdates.forEach(exdate => {
rruleSet.exdate(this.localeDayjs(exdate).toDate());
});
}
rule = rruleSet;
}
const all = rule.between(new Date(windowStart), new Date(windowEnd));
all.forEach((time, index) => {
const newEvent = {
...item,
recurringEventId: item.id,
recurringEventStart: item.start,
recurringEventEnd: item.end,
id: `${item.id}-${index}`,
start: rule.origOptions.tzid
? this.localeDayjs.utc(time).utcOffset(this.localeDayjs(new Date()).utcOffset(), true).format(DATETIME_FORMAT)
: this.localeDayjs(new Date(time)).format(DATETIME_FORMAT),
end: rule.origOptions.tzid
? this.localeDayjs
.utc(time)
.utcOffset(this.localeDayjs(new Date()).utcOffset(), true)
.add(oldEnd.diff(oldStart), 'ms')
.add(this.localeDayjs(new Date(oldUntil)).utcOffset() - this.localeDayjs(new Date(item.start)).utcOffset(), 'm')
.format(DATETIME_FORMAT)
: this.localeDayjs(new Date(time)).add(oldEnd.diff(oldStart), 'ms').format(DATETIME_FORMAT),
};
const eventStart = this.localeDayjs(newEvent.start);
const eventEnd = this.localeDayjs(newEvent.end);
if (this.isEventInTimeWindow(eventStart, eventEnd, windowStart, windowEnd) && (!oldDtstart || eventStart >= oldDtstart)) {
this._attachEvent(newEvent);
}
});
});
}
_resolveDate(num, date = undefined) {
if (date !== undefined) {
this.selectDate = this.localeDayjs(date);
}
const setStartAndEndDates = unit => {
this.startDate = date !== undefined ? this.selectDate.startOf(unit) : this.startDate.add(num, `${unit}s`);
this.endDate = this.startDate.endOf(unit);
};
switch (this.viewType) {
case ViewType.Week:
setStartAndEndDates('week');
break;
case ViewType.Day:
this.startDate = date !== undefined ? this.selectDate : this.startDate.add(num, 'days');
this.endDate = this.startDate;
break;
case ViewType.Month:
setStartAndEndDates('month');
break;
case ViewType.Quarter:
setStartAndEndDates('quarter');
break;
case ViewType.Year:
setStartAndEndDates('year');
break;
case ViewType.Custom:
case ViewType.Custom1:
case ViewType.Custom2:
if (this.behaviors.getCustomDateFunc !== undefined) {
const customDate = this.behaviors.getCustomDateFunc(this, num, date);
this.startDate = this.localeDayjs(customDate.startDate);
this.endDate = this.localeDayjs(customDate.endDate);
if (customDate.cellUnit) {
this.cellUnit = customDate.cellUnit;
}
} else {
throw new Error('This is a custom view type, set behaviors.getCustomDateFunc func to resolve the time window (startDate and endDate) yourself');
}
break;
default:
break;
}
}
// Previous Code
_createHeaders() {
const headers = [];
let start = this.localeDayjs(new Date(this.startDate));
let end = this.localeDayjs(new Date(this.endDate));
let header = start;
if (this.showAgenda) {
headers.push({ time: header.format(DATETIME_FORMAT), nonWorkingTime: false });
} else if (this.cellUnit === CellUnit.Hour) {
if (start.hour() === 0) {
start = start.add(this.config.dayStartFrom, 'hours');
}
if (end.hour() === 0) {
end = end.add(this.config.dayStopTo, 'hours');
}
header = start;
let prevHour = -1;
while (header >= start && header <= end) {
// prevent doubled hours on time change
if (header.hour() === prevHour) {
header = header.add(1, 'hours');
// eslint-disable-next-line no-continue
continue;
}
prevHour = header.hour();
const minuteSteps = this.getMinuteStepsInHour();
for (let i = 0; i < minuteSteps; i += 1) {
const hour = header.hour();
if (hour >= this.config.dayStartFrom && hour <= this.config.dayStopTo) {
const time = header.format(DATETIME_FORMAT);
const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time);
headers.push({ time, nonWorkingTime });
}
header = header.add(this.config.minuteStep, 'minutes');
}
}
} else if (this.cellUnit === CellUnit.Day) {
while (header >= start && header <= end) {
const time = header.format(DATETIME_FORMAT);
const dayOfWeek = header.day();
if (this.config.displayWeekend || (dayOfWeek !== 0 && dayOfWeek !== 6)) {
const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time);
headers.push({ time, nonWorkingTime });
}
header = header.add(1, 'days');
}
} else if (this.cellUnit === CellUnit.Week) {
while (header >= start && header <= end) {
const time = header.format(DATE_FORMAT);
headers.push({ time });
header = header.add(1, 'weeks').startOf('week');
}
} else if (this.cellUnit === CellUnit.Month) {
while (header >= start && header <= end) {
const time = header.format(DATE_FORMAT);
headers.push({ time });
header = header.add(1, 'months').startOf('month');
}
} else if (this.cellUnit === CellUnit.Year) {
while (header >= start && header <= end) {
const time = header.format(DATE_FORMAT);
headers.push({ time });
header = header.add(1, 'years').startOf('year');
}
}
this.headers = headers;
}
// Fix Optimited code
// _createHeaders() {
// const headers = [];
// const start = this.localeDayjs(new Date(this.startDate));
// const end = this.localeDayjs(new Date(this.endDate));
// const processHeader = (header, format, unit, incrementFn) => {
// let head = header;
// while (head >= start && head <= end) {
// const time = head.format(format);
// if (unit === CellUnit.Day) {
// const dayOfWeek = head.weekday();
// if (this.config.displayWeekend || (dayOfWeek !== 0 && dayOfWeek !== 6)) {
// const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time);
// headers.push({ time, nonWorkingTime });
// }
// } else {
// headers.push({ time });
// }
// head = head.add(1, incrementFn);
// }
// };
// if (this.showAgenda) {
// headers.push({ time: start.format(DATETIME_FORMAT), nonWorkingTime: false });
// } else if (this.cellUnit === CellUnit.Hour) {
// const hourIncrement = this.config.minuteStep < 60 ? 'minutes' : 'hours';
// const minuteSteps = this.getMinuteStepsInHour();
// let header = start.hour() === 0 ? start.add(this.config.dayStartFrom, 'hours') : start;
// while (header <= end) {
// const hour = header.hour();
// if (hour >= this.config.dayStartFrom && hour <= this.config.dayStopTo) {
// const time = header.format(DATETIME_FORMAT);
// const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time);
// headers.push({ time, nonWorkingTime });
// }
// header = header.add(minuteSteps, hourIncrement);
// }
// } else {
// const header = start;
// const format = this.cellUnit === CellUnit.Day ? DATETIME_FORMAT : DATE_FORMAT;
// const incrementFn = this.cellUnit === CellUnit.Day ? 'days' : `${this.cellUnit}s`;
// processHeader(header, format, this.cellUnit, incrementFn);
// }
// this.headers = headers;
// }
_createInitHeaderEvents(header) {
const start = this.localeDayjs(new Date(header.time));
const startValue = start.format(DATETIME_FORMAT);
let endValue;
if (this.showAgenda) {
const incrementUnit = {
[ViewType.Day]: 'days',
[ViewType.Week]: 'weeks',
[ViewType.Month]: 'months',
[ViewType.Year]: 'years',
[ViewType.Quarter]: 'quarters',
}[this.viewType] || 'days';
if (incrementUnit === 'days') {
endValue = this.localeDayjs(new Date(this.endDate)).add(1, 'days').format(DATETIME_FORMAT);
} else {
endValue = start.add(1, incrementUnit).format(DATETIME_FORMAT);
}
} else {
const incrementUnit = {
[CellUnit.Hour]: 'minutes',
[CellUnit.Week]: 'weeks',
[CellUnit.Month]: 'months',
[CellUnit.Year]: 'years',
}[this.cellUnit] || 'days';
endValue = start
.add(incrementUnit === 'minutes' ? this.config.minuteStep : 1, incrementUnit)
.format(this.cellUnit === CellUnit.Year || this.cellUnit === CellUnit.Month || this.cellUnit === CellUnit.Week ? DATE_FORMAT : DATETIME_FORMAT);
}
return {
time: header.time,
nonWorkingTime: header.nonWorkingTime,
start: startValue,
end: endValue,
count: 0,
addMore: 0,
addMoreIndex: 0,
events: Array(3),
};
}
_createHeaderEvent(render, span, eventItem) {
return { render, span, eventItem };
}
_getEventSlotId(event) {
return this.isEventPerspective ? this._getEventGroupId(event) : event.resourceId;
}
_getEventGroupId(event) {
return event.groupId ? event.groupId.toString() : event.id.toString();
}
_getEventGroupName(event) {
return event.groupName ? event.groupName : event.title;
}
_generateEventGroups() {
const eventGroups = [];
const set = new Set();
this.events.forEach(item => {
const groupId = this._getEventGroupId(item);
const groupName = this._getEventGroupName(item);
if (!set.has(groupId)) {
eventGroups.push({
id: groupId,
name: groupName,
state: item,
});
set.add(groupId);
}
});
this.eventGroups = eventGroups;
}
_createInitRenderData(isEventPerspective, eventGroups, resources, headers) {
const slots = isEventPerspective ? eventGroups : resources;
const slotTree = [];
const slotMap = new Map();
slots.forEach(slot => {
const headerEvents = headers.map(header => this._createInitHeaderEvents(header));
const slotRenderData = {
slotId: slot.id,
slotName: slot.name,
slotTitle: slot.title,
parentId: slot.parentId,
groupOnly: slot.groupOnly,
hasSummary: false,
rowMaxCount: 0,
rowHeight: this.config.nonAgendaSlotMinHeight !== 0 ? this.config.nonAgendaSlotMinHeight : this.config.eventItemLineHeight + 2,
headerItems: headerEvents,
indent: 0,
hasChildren: false,
expanded: true,
render: true,
};
const { id } = slot;
let value;
if (slotMap.has(id)) {
value = slotMap.get(id);
value.data = slotRenderData;
} else {
value = {
data: slotRenderData,
children: [],
};
slotMap.set(id, value);
}
const { parentId } = slot;
if (!parentId || parentId === id) {
slotTree.push(value);
} else {
let parentValue;
if (slotMap.has(parentId)) {
parentValue = slotMap.get(parentId);
} else {
parentValue = {
data: undefined,
children: [],
};
slotMap.set(parentId, parentValue);
}
parentValue.children.push(value);
}
});
const slotStack = [];
let i;
for (i = slotTree.length - 1; i >= 0; i -= 1) {
slotStack.push(slotTree[i]);
}
const initRenderData = [];
let currentNode;
while (slotStack.length > 0) {
currentNode = slotStack.pop();
if (currentNode.data.indent > 0) {
currentNode.data.render = this.config.defaultExpanded;
}
if (currentNode.children.length > 0) {
currentNode.data.hasChildren = true;
currentNode.data.expanded = this.config.defaultExpanded;
}
initRenderData.push(currentNode.data);
for (i = currentNode.children.length - 1; i >= 0; i -= 1) {
currentNode.children[i].data.indent = currentNode.data.indent + 1;
slotStack.push(currentNode.children[i]);
}
}
return initRenderData;
}
_getSpan(startTime, endTime, headers) {
if (this.showAgenda) return 1;
// function startOfWeek(date) {
// const day = date.getDay();
// const diff = date.getDate() - day;
// return new Date(date.getFullYear(), date.getMonth(), diff);
// }
const timeBetween = (date1, date2, timeIn) => {
if (timeIn === 'days' || timeIn === 'day') {
if (date1.getDate() === date2.getDate() && date1.getMonth() === date2.getMonth()) {
return 1;
}
}
let one;
switch (timeIn) {
case 'days':
case 'day':
one = 1000 * 60 * 60 * 24;
break;
case 'minutes':
case 'minute':
one = 1000 * 60;
break;
default:
return 0;
}
const date1Ms = date1.getTime();
const date2Ms = date2.getTime();
const diff = (date2Ms - date1Ms) / one;
return diff < 0 ? 0 : diff;
};
const eventStart = new Date(startTime);
const eventEnd = new Date(endTime);
let span = 0;
const windowStart = new Date(this.startDate);
const windowEnd = new Date(this.endDate);
windowStart.setHours(0, 0, 0, 0);
windowEnd.setHours(23, 59, 59);
if (this.viewType === ViewType.Day) {
if (headers.length > 0) {
const day = new Date(headers[0].time);
if (day.getDate() > eventStart.getDate() && day.getDate() < eventEnd.getDate()) {
span = 1440 / this.config.minuteStep;
} else if (day.getDate() > eventStart.getDate() && day.getDate() === eventEnd.getDate()) {
span = Math.ceil(timeBetween(day, eventEnd, 'minutes') / this.config.minuteStep);
} else if (day.getDate() === eventStart.getDate() && day.getDate() < eventEnd.getDate()) {
day.setHours(23, 59, 59);
span = Math.ceil(timeBetween(eventStart, day, 'minutes') / this.config.minuteStep);
} else if ((day.getDate() === eventStart.getDate() && day.getDate() === eventEnd.getDate()) || eventEnd.getDate() === eventStart.getDate()) {
span = Math.ceil(timeBetween(eventStart, eventEnd, 'minutes') / this.config.minuteStep);
}
}
} else if (this.viewType === ViewType.Week || this.viewType === ViewType.Month || this.viewType === ViewType.Quarter || this.viewType === ViewType.Year) {
const startDate = windowStart < eventStart ? eventStart : windowStart;
const endDate = windowEnd > eventEnd ? eventEnd : windowEnd;
span = Math.ceil(timeBetween(startDate, endDate, 'days'));
} else {
if (this.cellUnit === CellUnit.Day) {
eventEnd.setHours(23, 59, 59);
eventStart.setHours(0, 0, 0, 0);
}
const timeIn = this.cellUnit === CellUnit.Day ? 'days' : 'minutes';
const dividedBy = this.cellUnit === CellUnit.Day ? 1 : this.config.minuteStep;
if (windowStart >= eventStart && eventEnd <= windowEnd) {
span = Math.ceil(timeBetween(windowStart, eventEnd, timeIn) / dividedBy);
} else if (windowStart > eventStart && eventEnd > windowEnd) {
span = Math.ceil(timeBetween(windowStart, windowEnd, timeIn) / dividedBy);
} else if (windowStart <= eventStart && eventEnd >= windowEnd) {
span = Math.ceil(timeBetween(eventStart, windowEnd, timeIn) / dividedBy);
} else {
span = Math.ceil(timeBetween(eventStart, eventEnd, timeIn) / dividedBy);
}
}
return span;
}
_validateResource(resources) {
if (Object.prototype.toString.call(resources) !== '[object Array]') {
throw new Error('Resources should be Array object');
}
resources.forEach((item, index) => {
if (item === undefined) {
console.error(`Resource undefined: ${index}`);
throw new Error(`Resource undefined: ${index}`);
}
if (item.id === undefined || item.name === undefined) {
console.error('Resource property missed', index, item);
throw new Error(`Resource property undefined: ${index}`);
}
});
}
_validateEventGroups(eventGroups) {
if (Object.prototype.toString.call(eventGroups) !== '[object Array]') {
throw new Error('Event groups should be Array object');
}
eventGroups.forEach((item, index) => {
if (item === undefined) {
console.error(`Event group undefined: ${index}`);
throw new Error(`Event group undefined: ${index}`);
}
if (item.id === undefined || item.name === undefined) {
console.error('Event group property missed', index, item);
throw new Error(`Event group property undefined: ${index}`);
}
});
}
_validateEvents(events) {
if (Object.prototype.toString.call(events) !== '[object Array]') {
throw new Error('Events should be Array object');
}
events.forEach((e, index) => {
if (e === undefined) {
console.error(`Event undefined: ${index}`);
throw new Error(`Event undefined: ${index}`);
}
if (e.id === undefined || e.resourceId === undefined || e.title === undefined || e.start === undefined || e.end === undefined) {
console.error('Event property missed', index, e);
throw new Error(`Event property undefined: ${index}`);
}
});
}
_validateMinuteStep(minuteStep) {
if (60 % minuteStep !== 0) {
console.error('Minute step is not set properly - 60 minutes must be divisible without remainder by this number');
throw new Error('Minute step is not set properly - 60 minutes must be divisible without remainder by this number');
}
}
_compare(event1, event2) {
const start1 = this.localeDayjs(event1.start);
const start2 = this.localeDayjs(event2.start);
if (start1 !== start2) return start1 < start2 ? -1 : 1;
const end1 = this.localeDayjs(event1.end);
const end2 = this.localeDayjs(event2.end);
if (end1 !== end2) return end1 < end2 ? -1 : 1;
return event1.id < event2.id ? -1 : 1;
}
_createRenderData() {
const initRenderData = this._createInitRenderData(this.isEventPerspective, this.eventGroups, this.resources, this.headers);
// this.events.sort(this._compare);
const cellMaxEventsCount = this.getCellMaxEvents();
const cellMaxEventsCountValue = 30;
this.events.forEach(item => {
const resourceEventsList = initRenderData.filter(x => x.slotId === this._getEventSlotId(item));
if (resourceEventsList.length > 0) {
const resourceEvents = resourceEventsList[0];
const span = this._getSpan(item.start, item.end, this.headers);
const eventStart = new Date(item.start);
const eventEnd = new Date(item.end);
let pos = -1;
resourceEvents.headerItems.forEach((header, index) => {
const headerStart = new Date(header.start);
const headerEnd = new Date(header.end);
if (headerEnd > eventStart && headerStart < eventEnd) {
header.count += 1;
if (header.count > resourceEvents.rowMaxCount) {
resourceEvents.rowMaxCount = header.count;
const rowsCount = cellMaxEventsCount <= cellMaxEventsCountValue && resourceEvents.rowMaxCount > cellMaxEventsCount ? cellMaxEventsCount : resourceEvents.rowMaxCount;
const newRowHeight = rowsCount * this.config.eventItemLineHeight + (this.config.creatable && this.config.checkConflict === false ? 20 : 2);
if (newRowHeight > resourceEvents.rowHeight) resourceEvents.rowHeight = newRowHeight;
}
if (pos === -1) {
let tmp = 0;
while (header.events[tmp] !== undefined) tmp += 1;
pos = tmp;
}
let render = headerStart <= eventStart || index === 0;
if (render === false) {
const previousHeader = resourceEvents.headerItems[index - 1];
const previousHeaderStart = new Date(previousHeader.start);
const previousHeaderEnd = new Date(previousHeader.end);
if (previousHeaderEnd <= eventStart || previousHeaderStart >= eventEnd) render = true;
}
// console.log(`span: ${span}`)
header.events[pos] = this._createHeaderEvent(render, span, item);
}
});
}
});
if (cellMaxEventsCount <= cellMaxEventsCountValue || this.behaviors.getSummaryFunc !== undefined) {
initRenderData.forEach(resourceEvents => {
let hasSummary = false;
resourceEvents.headerItems.forEach(headerItem => {
if (cellMaxEventsCount <= cellMaxEventsCountValue) {
let renderItemsCount = 0;
let addMoreIndex = 0;
let index = 0;
while (index < cellMaxEventsCount - 1) {
if (headerItem.events[index] !== undefined) {
renderItemsCount += 1;
addMoreIndex = index + 1;
}
index += 1;
}
if (headerItem.events[index] !== undefined) {
if (renderItemsCount + 1 < headerItem.count) {
headerItem.addMore = headerItem.count - renderItemsCount;
headerItem.addMoreIndex = addMoreIndex;
}
} else if (renderItemsCount < headerItem.count) {
headerItem.addMore = headerItem.count - renderItemsCount;
headerItem.addMoreIndex = addMoreIndex;
}
}
if (this.behaviors.getSummaryFunc !== undefined) {
const events = [];
headerItem.events.forEach(e => {
if (!!e && !!e.eventItem) events.push(e.eventItem);
});
headerItem.summary = this.behaviors.getSummaryFunc(this, events, resourceEvents.slotId, resourceEvents.slotName, headerItem.start, headerItem.end);
if (!!headerItem.summary && headerItem.summary.text !== undefined) hasSummary = true;
}
});
resourceEvents.hasSummary = hasSummary;
if (hasSummary) {
const rowsCount = cellMaxEventsCount <= cellMaxEventsCountValue && resourceEvents.rowMaxCount > cellMaxEventsCount ? cellMaxEventsCount : resourceEvents.rowMaxCount;
const newRowHeight = (rowsCount + 1) * this.config.eventItemLineHeight + (this.config.creatable && this.config.checkConflict === false ? 20 : 2);
if (newRowHeight > resourceEvents.rowHeight) resourceEvents.rowHeight = newRowHeight;
}
});
}
this.renderData = initRenderData;
}
_startResizing() {
this.resizing = true;
}
_stopResizing() {
this.resizing = false;
}
_isResizing() {
return this.resizing;
}
}