define("modules/crm/views/scheduler/scheduler", ["exports", "view", "vis-data", "vis-timeline", "moment", "jquery"], function (_exports, _view, _visData, _visTimeline, _moment, _jquery) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _view = _interopRequireDefault(_view); _moment = _interopRequireDefault(_moment); _jquery = _interopRequireDefault(_jquery); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2026 EspoCRM, Inc. * Website: https://www.espocrm.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ class SchedulerView extends _view.default { // language=Handlebars templateContent = `
`; rangeMarginThreshold = 12 * 3600; leftMargin = 24 * 3600; rightMargin = 48 * 3600; rangeMultiplierLeft = 3; rangeMultiplierRight = 3; setup() { this.startField = this.options.startField || 'dateStart'; this.endField = this.options.endField || 'dateEnd'; this.assignedUserField = this.options.assignedUserField || 'assignedUser'; this.startDateField = this.startField + 'Date'; this.endDateField = this.endField + 'Date'; this.colors = Espo.Utils.clone(this.getMetadata().get('clientDefs.Calendar.colors') || {}); this.colors = { ...this.colors, ...this.getHelper().themeManager.getParam('calendarColors') }; let usersFieldDefault = 'users'; if (!this.model.hasLink('users') && this.model.hasLink('assignedUsers')) { usersFieldDefault = 'assignedUsers'; } this.eventAssignedUserIsAttendeeDisabled = this.getConfig().get('eventAssignedUserIsAttendeeDisabled') || false; this.usersField = this.options.usersField || usersFieldDefault; this.userIdList = []; this.listenTo(this.model, 'change', m => { let isChanged = m.hasChanged('isAllDay') || m.hasChanged(this.startField) || m.hasChanged(this.endField) || m.hasChanged(this.endDateField) || m.hasChanged(this.usersField + 'Ids') || !this.eventAssignedUserIsAttendeeDisabled && m.hasChanged(this.assignedUserField + 'Id'); if (!isChanged) { return; } if (!m.hasChanged(this.assignedUserField + 'Id') && !m.hasChanged(this.usersField + 'Ids')) { this.initDates(true); if (!this.start || !this.end || !this.userIdList.length) { if (!this.timeline) { return; } this.showNoData(); this.trigger('no-data'); return; } this.trigger('has-data'); if (this.timeline) { this.updateEvent(); this.timeline.setWindow(this.start.toDate(), this.end.toDate()); } if (this.noDataShown) { this.reRender(); } return; } if (this.isRemoved()) { return; } this.trigger('has-data'); this.reRender(); }); this.once('remove', () => { this.destroyTimeline(); }); } destroyTimeline() { if (this.timeline) { this.timeline.destroy(); this.timeline = null; } } showNoData() { this.noDataShown = true; this.destroyTimeline(); this.$timeline.empty(); this.$timeline.append((0, _jquery.default)('
').addClass('revert-margin').text(this.translate('No Data'))); } afterRender() { let $timeline = this.$timeline = this.$el.find('.timeline'); this.noDataShown = false; this.$timeline.empty(); this.initGroupsDataSet(); this.initDates(); if (!$timeline.get(0)) { return; } $timeline.get(0).innerHTML = ''; if (!this.start || !this.end || !this.userIdList.length) { this.showNoData(); this.trigger('no-data'); return; } this.destroyTimeline(); if (this.lastHeight) { $timeline.css('min-height', this.lastHeight + 'px'); } this.fetch(this.start, this.end, eventList => { let itemsDataSet = new _visData.DataSet(eventList); // noinspection SpellCheckingInspection this.timeline = new _visTimeline.Timeline($timeline.get(0), itemsDataSet, this.groupsDataSet, { dataAttributes: 'all', start: this.start.toDate(), end: this.end.toDate(), rollingMode: { follow: false // fixes slow render }, moment: date => { let m = (0, _moment.default)(date); if (date && date.noTimeZone) { return m; } return m.tz(this.getDateTime().getTimeZone()); }, xss: { filterOptions: { onTag: (tag, html) => html } }, format: this.getFormatObject(), zoomable: false, moveable: true, orientation: 'top', groupEditable: false, editable: { add: false, updateTime: false, updateGroup: false, remove: false }, showCurrentTime: true, locales: { myLocale: { current: this.translate('current', 'labels', 'Calendar'), time: this.translate('time', 'labels', 'Calendar') } }, locale: 'myLocale', margin: { item: { vertical: 12 }, axis: 6 } }); $timeline.css('min-height', ''); // noinspection SpellCheckingInspection this.timeline.on('rangechanged', e => { e.skipClick = true; this.blockClick = true; setTimeout(function () { this.blockClick = false; }.bind(this), 100); this.start = (0, _moment.default)(e.start); this.end = (0, _moment.default)(e.end); this.updateRange(); }); setTimeout(() => { this.lastHeight = $timeline.height(); }, 500); }); } updateEvent() { let eventList = Espo.Utils.cloneDeep(this.busyEventList); let convertedEventList = this.convertEventList(eventList); this.addEvent(convertedEventList); let itemsDataSet = new _visData.DataSet(convertedEventList); this.timeline.setItems(itemsDataSet); } updateRange() { if (this.start.unix() < this.fetchedStart.unix() + this.rangeMarginThreshold || this.end.unix() > this.fetchedEnd.unix() - this.rangeMarginThreshold) { this.runFetch(); } } initDates(update) { this.start = null; this.end = null; let startS = this.model.get(this.startField); let endS = this.model.get(this.endField); if (this.model.get('isAllDay')) { startS = this.model.get(this.startDateField); endS = this.model.get(this.endDateField); } if (!startS || !endS) { return; } if (this.model.get('isAllDay')) { this.eventStart = _moment.default.tz(startS, this.getDateTime().getTimeZone()); this.eventEnd = _moment.default.tz(endS, this.getDateTime().getTimeZone()); this.eventEnd.add(1, 'day'); } else { this.eventStart = _moment.default.utc(startS).tz(this.getDateTime().getTimeZone()); this.eventEnd = _moment.default.utc(endS).tz(this.getDateTime().getTimeZone()); } let diff = this.eventEnd.diff(this.eventStart, 'hours'); this.start = this.eventStart.clone(); this.end = this.eventEnd.clone(); if (diff < 0) { this.end = this.start.clone(); } if (diff < 1) { diff = 1; } this.start.add(-diff * this.rangeMultiplierLeft, 'hours'); this.end.add(diff * this.rangeMultiplierRight, 'hours'); this.start.startOf('hour'); this.end.endOf('hour'); if (!update) { this.fetchedStart = null; this.fetchedEnd = null; } } runFetch() { this.fetch(this.start, this.end, eventList => { let itemsDataSet = new _visData.DataSet(eventList); this.timeline.setItems(itemsDataSet); }); } fetch(from, to, callback) { from = from.clone().add(-1 * this.leftMargin, 'seconds'); to = to.clone().add(this.rightMargin, 'seconds'); let fromString = from.utc().format(this.getDateTime().internalDateTimeFormat); let toString = to.utc().format(this.getDateTime().internalDateTimeFormat); let url = 'Timeline/busyRanges?from=' + fromString + '&to=' + toString + '&userIdList=' + encodeURIComponent(this.userIdList.join(',')) + '&entityType=' + this.model.entityType; if (this.model.id) { url += '&entityId=' + this.model.id; } Espo.Ajax.getRequest(url).then(data => { this.fetchedStart = from.clone(); this.fetchedEnd = to.clone(); let eventList = []; for (let userId in data) { let itemList = /** @type {Object.} */data[userId].filter(item => !item.isBusyRange).concat(data[userId].filter(item => item.isBusyRange)); itemList.forEach(item => { item.userId = userId; eventList.push(item); }); } this.busyEventList = Espo.Utils.cloneDeep(eventList); let convertedEventList = this.convertEventList(eventList); this.addEvent(convertedEventList); callback(convertedEventList); }); } addEvent(list) { this.getCurrentItemList().forEach(item => { list.push(item); }); } getCurrentItemList() { let list = []; let o = { start: this.eventStart.clone(), end: this.eventEnd.clone(), type: 'background', style: 'z-index: 4; opacity: 0.6;', className: 'event-range' }; let color = this.getColorFromScopeName(this.model.entityType); if (color) { o.style += '; border-color: ' + color; let rgb = this.hexToRgb(color); o.style += '; background-color: rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', 0.01)'; } this.userIdList.forEach(id => { let c = Espo.Utils.clone(o); c.group = id; c.id = 'event-' + id; list.push(c); }); return list; } convertEventList(list) { let resultList = []; list.forEach(item => { let event = this.convertEvent(item); if (!event) { return; } resultList.push(event); }); return resultList; } /** * @param {Object.} o * @return {Object} */ convertEvent(o) { let event; if (o.isBusyRange) { event = { className: 'busy', group: o.userId, 'date-start': o.dateStart, 'date-end': o.dateEnd, type: 'background' }; } else if (o.isWorkingRange) { event = { className: 'working', group: o.userId, 'date-start': o.dateStart, 'date-end': o.dateEnd, type: 'background' }; } else if (o.isNonWorkingRange) { event = { className: 'non-working', group: o.userId, 'date-start': o.dateStart, 'date-end': o.dateEnd, type: 'background' }; let color = this.colors['bg']; event.style = 'background-color:' + color + ';'; event.style += 'border-color:' + color + ';'; } if (o.dateStart) { if (!o.dateStartDate) { event.start = this.getDateTime().toMoment(o.dateStart); } else { event.start = _moment.default.tz(o.dateStartDate, this.getDateTime().getTimeZone()); } } if (o.dateEnd) { if (!o.dateEndDate) { event.end = this.getDateTime().toMoment(o.dateEnd); } else { event.end = _moment.default.tz(o.dateEndDate, this.getDateTime().getTimeZone()); } } if (o.isBusyRange || o.isNonWorkingRange) { return event; } } initGroupsDataSet() { let list = []; let userIdList = Espo.Utils.clone(this.model.get(this.usersField + 'Ids') || []); let assignedUserId = this.model.get(this.assignedUserField + 'Id'); let names = this.model.get(this.usersField + 'Names') || {}; if (!this.eventAssignedUserIsAttendeeDisabled && assignedUserId) { if (!~userIdList.indexOf(assignedUserId)) { userIdList.unshift(assignedUserId); } names[assignedUserId] = this.model.get(this.assignedUserField + 'Name'); } this.userIdList = userIdList; userIdList.forEach((id, i) => { list.push({ id: id, content: this.getGroupContent(id, names[id] || id), order: i }); }); this.groupsDataSet = new _visData.DataSet(list); } getGroupContent(id, name) { if (this.calendarType === 'single') { return (0, _jquery.default)('').text(name).get(0).outerHTML; } let avatarHtml = this.getAvatarHtml(id); if (avatarHtml) { avatarHtml += ' '; } return (0, _jquery.default)('').append((0, _jquery.default)(avatarHtml), (0, _jquery.default)('').attr('data-id', id).addClass('group-title').text(name)).get(0).innerHTML; } getAvatarHtml(id) { if (this.getConfig().get('avatarsDisabled')) { return ''; } let t; let cache = this.getCache(); if (cache) { t = cache.get('app', 'timestamp'); } else { t = Date.now(); } // noinspection HtmlRequiredAltAttribute,RequiredAttributes return (0, _jquery.default)('').addClass('avatar avatar-link').attr('width', '16').attr('src', this.getBasePath() + '?entryPoint=avatar&size=small&id=' + id + '&t=' + t).get(0).outerHTML; } getFormatObject() { return { minorLabels: { millisecond: 'SSS', second: 's', minute: this.getDateTime().getTimeFormat(), hour: this.getDateTime().getTimeFormat(), weekday: 'ddd D', day: 'D', month: 'MMM', year: 'YYYY' }, majorLabels: { millisecond: this.getDateTime().getTimeFormat() + ' ss', second: this.getDateTime().getReadableDateFormat() + ' HH:mm', minute: 'ddd D MMMM', hour: 'ddd D MMMM', weekday: 'MMMM YYYY', day: 'MMMM YYYY', month: 'YYYY', year: '' } }; } getColorFromScopeName(scope) { return this.getMetadata().get(['clientDefs', scope, 'color']) || this.getMetadata().get(['clientDefs', 'Calendar', 'colors', scope]); } hexToRgb(hex) { let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } } var _default = _exports.default = SchedulerView; }); define("modules/crm/views/calendar/timeline", ["exports", "view", "vis-data", "vis-timeline", "moment", "jquery", "helpers/record-modal", "crm:views/calendar/modals/shared-options"], function (_exports, _view, _visData, _visTimeline, _moment, _jquery, _recordModal, _sharedOptions) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _view = _interopRequireDefault(_view); _moment = _interopRequireDefault(_moment); _jquery = _interopRequireDefault(_jquery); _recordModal = _interopRequireDefault(_recordModal); _sharedOptions = _interopRequireDefault(_sharedOptions); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2026 EspoCRM, Inc. * Website: https://www.espocrm.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ /** @module modules/crm/views/calendar/timeline */ class TimelineView extends _view.default { template = 'crm:calendar/timeline'; eventAttributes = []; colors = {}; /** * @private * @type {string[]} */ allDayScopeList; /** * @private * @type {string[]} */ scopeList = ['Meeting', 'Call', 'Task']; /** * @private * @type {string[]} */ enabledScopeList; /** * @private * @type {string[]} */ onlyDateScopeList; header = true; modeList = []; defaultMode = 'timeline'; maxRange = 120; rangeMarginThreshold = 12 * 3600; leftMargin = 24 * 3600; rightMargin = 48 * 3600; calendarType = 'single'; calendarTypeList = ['single', 'shared']; zoomPercentage = 1; /** @type {Timeline} */ timeline; events = { /** @this TimelineView */ 'click button[data-action="today"]': function () { this.actionToday(); }, /** @this TimelineView */ 'click [data-action="mode"]': function (e) { const mode = (0, _jquery.default)(e.currentTarget).data('mode'); this.selectMode(mode); }, /** @this TimelineView */ 'click [data-action="refresh"]': function () { this.actionRefresh(); }, /** @this TimelineView */ 'click [data-action="toggleScopeFilter"]': function (e) { const $target = (0, _jquery.default)(e.currentTarget); const filterName = $target.data('name'); const $check = $target.find('.filter-check-icon'); if ($check.hasClass('hidden')) { $check.removeClass('hidden'); } else { $check.addClass('hidden'); } e.stopPropagation(e); this.toggleScopeFilter(filterName); }, /** @this TimelineView */ 'click [data-action="toggleCalendarType"]': function (e) { const $target = (0, _jquery.default)(e.currentTarget); const calendarType = $target.data('name'); $target.parent().parent().find('.calendar-type-check-icon').addClass('hidden'); const $check = $target.find('.calendar-type-check-icon'); if ($check.hasClass('hidden')) { $check.removeClass('hidden'); } $target.closest('.calendar-type-button-group').find('.calendar-type-label').text(this.getCalendarTypeLabel(calendarType)); const $showSharedCalendarOptions = this.$el.find('> .button-container button[data-action="showSharedCalendarOptions"]'); if (calendarType === 'shared') { $showSharedCalendarOptions.removeClass('hidden'); } else { $showSharedCalendarOptions.addClass('hidden'); } this.selectCalendarType(calendarType); }, /** @this TimelineView */ 'click button[data-action="showSharedCalendarOptions"]': function () { this.actionShowSharedCalendarOptions(); } }; /** * @param {{ * userId?: string, * userName?: string|null, * mode?: string|null, * date?: string|null, * $container?: JQuery, * suppressLoadingAlert?: boolean, * containerSelector?: string, * enabledScopeList?: string[], * calendarType?: string, * userList?: string[], * header?: boolean, * onSave?: function(), * }} options */ constructor(options) { super(options); this.options = options; } data() { const calendarTypeDataList = this.getCalendarTypeDataList(); return { mode: this.mode, header: this.header, calendarType: this.calendarType, calendarTypeDataList: calendarTypeDataList, calendarTypeSelectEnabled: calendarTypeDataList.length > 1, calendarTypeLabel: this.getCalendarTypeLabel(this.calendarType), isCustomViewAvailable: this.isCustomViewAvailable }; } setup() { this.date = this.options.date || this.getDateTime().getToday(); this.mode = this.options.mode || this.defaultMode; this.header = 'header' in this.options ? this.options.header : this.header; this.$container = this.options.$container; this.colors = Espo.Utils.clone(this.getMetadata().get('clientDefs.Calendar.colors') || this.colors || {}); this.modeList = this.getMetadata().get('clientDefs.Calendar.modeList') || this.modeList || []; this.scopeList = this.getConfig().get('calendarEntityList') || Espo.Utils.clone(this.scopeList); this.allDayScopeList = this.getMetadata().get('clientDefs.Calendar.allDayScopeList') ?? []; this.scopeList.forEach(scope => { if (this.getMetadata().get(`scopes.${scope}.calendarOneDay`) && !this.allDayScopeList.includes(scope)) { this.allDayScopeList.push(scope); } }); this.colors = { ...this.colors, ...this.getHelper().themeManager.getParam('calendarColors') }; this.isCustomViewAvailable = this.getAcl().getPermissionLevel('userCalendar') !== 'no'; if (this.options.userId) { this.isCustomViewAvailable = false; } const scopeList = []; this.scopeList.forEach(scope => { if (this.getAcl().check(scope)) { scopeList.push(scope); } }); this.scopeList = scopeList; if (this.header) { this.enabledScopeList = this.getStoredEnabledScopeList() || Espo.Utils.clone(this.scopeList); } else { this.enabledScopeList = this.options.enabledScopeList || Espo.Utils.clone(this.scopeList); } if (Object.prototype.toString.call(this.enabledScopeList) !== '[object Array]') { this.enabledScopeList = []; } this.enabledScopeList.forEach(item => { const color = this.getMetadata().get(['clientDefs', item, 'color']); if (color) { this.colors[item] = color; } }); this.onlyDateScopeList = this.scopeList.filter(scope => { return this.getMetadata().get(`entityDefs.${scope}.fields.dateStart.type`) === 'date'; }); if (this.options.calendarType) { this.calendarType = this.options.calendarType; } else { if (this.options.userId) { this.calendarType = 'single'; } else { this.calendarType = this.getStorage().get('calendar', 'timelineType') || 'shared'; } } if (this.getAcl().getPermissionLevel('userCalendar') === 'no') { if (this.calendarType === 'shared') { this.calendarType = 'single'; } } if (!~this.calendarTypeList.indexOf(this.calendarType)) { this.calendarType = 'single'; } if (this.header) { this.createView('modeButtons', 'crm:views/calendar/mode-buttons', { selector: '.mode-buttons', isCustomViewAvailable: this.isCustomViewAvailable, modeList: this.modeList, scopeList: this.scopeList, mode: this.mode }); } } // noinspection JSUnusedGlobalSymbols /** * @return {import('./mode-buttons').default} */ getModeButtonsView() { return this.getView('modeButtons'); } selectMode(mode) { this.trigger('change:mode', mode); } getCalendarTypeDataList() { const list = []; const o = { type: 'single', disabled: this.calendarType !== 'single', label: this.getCalendarTypeLabel('single') }; list.push(o); if (this.options.userId) { return list; } if (this.getAcl().getPermissionLevel('userCalendar') !== 'no') { list.push({ type: 'shared', label: this.getCalendarTypeLabel('shared'), disabled: this.calendarType !== 'shared' }); } return list; } getCalendarTypeLabel(type) { let label; if (type === 'single') { if (this.options.userId) { label = this.options.userName || this.options.userId; } else { label = this.getUser().get('name'); } label = this.getHelper().escapeString(label); return label; } if (type === 'shared') { return this.translate('Shared', 'labels', 'Calendar'); } } selectCalendarType(name) { this.calendarType = name; this.initUserList(); this.initGroupsDataSet(); this.timeline.setGroups(this.groupsDataSet); this.runFetch(); this.getStorage().set('calendar', 'timelineType', name); } toggleScopeFilter(name) { const index = this.enabledScopeList.indexOf(name); if (!~index) { this.enabledScopeList.push(name); } else { this.enabledScopeList.splice(index, 1); } this.storeEnabledScopeList(this.enabledScopeList); this.runFetch(); } getStoredEnabledScopeList() { const key = 'calendarEnabledScopeList'; return this.getStorage().get('state', key) || null; } storeEnabledScopeList(enabledScopeList) { const key = 'calendarEnabledScopeList'; this.getStorage().set('state', key, enabledScopeList); } getTitle() { let title = ''; if (this.options.userId && this.options.userName) { title += ' (' + this.options.userName + ')'; } title = this.getHelper().escapeString(title); return title; } /** * @param {Object.} o * @return {Object} */ convertEvent(o) { const userId = o.userId || this.userList[0].id || this.getUser().id; let event; if (o.isBusyRange) { event = { className: 'busy', group: userId, 'date-start': o.dateStart, 'date-end': o.dateEnd, type: 'background' }; } else if (o.isWorkingRange) { event = { className: 'working', group: userId, 'date-start': o.dateStart, 'date-end': o.dateEnd, type: 'background' }; } else if (o.isNonWorkingRange) { event = { className: 'non-working', group: userId, 'date-start': o.dateStart, 'date-end': o.dateEnd, type: 'background' }; } else { event = { content: this.getHelper().escapeString(o.name), title: this.getHelper().escapeString(o.name), id: userId + '-' + o.scope + '-' + o.id, group: userId, 'record-id': o.id, scope: o.scope, status: o.status, 'date-start': o.dateStart, 'date-end': o.dateEnd, type: 'range', className: 'clickable', color: o.color }; } this.eventAttributes.forEach(attr => { event[attr] = o[attr]; }); if (o.dateStart || o.dateStartDate) { if (!o.dateStartDate) { event.start = this.getDateTime().toMoment(o.dateStart); } else { event.start = _moment.default.tz(o.dateStartDate, this.getDateTime().getTimeZone()); } } if (o.dateEnd || o.dateEndDate) { if (!o.dateEndDate) { event.end = this.getDateTime().toMoment(o.dateEnd); } else { event.end = _moment.default.tz(o.dateEndDate, this.getDateTime().getTimeZone()); } } if (o.dateStartDate && !this.allDayScopeList.includes(o.scope) && event.end) { event.end = event.end.clone().add(1, 'days'); } if (o.isBusyRange) { return event; } if (this.allDayScopeList.includes(o.scope)) { event.type = 'box'; if (event.end) { if (o.dateEndDate) { event.start = event.end.clone().add(1, 'days'); } else { event.start = event.end.clone(); } } } else { if (!event.end || !event.start) return; } this.fillColor(event); if (!o.isNonWorkingRange) { this.handleStatus(event); } return event; } /** * @param {string} scope * @return {string[]} */ getEventTypeCompletedStatusList(scope) { return this.getMetadata().get(['scopes', scope, 'completedStatusList']) || []; } /** * @param {string} scope * @return {string[]} */ getEventTypeCanceledStatusList(scope) { return this.getMetadata().get(['scopes', scope, 'canceledStatusList']) || []; } fillColor(event) { let key = event.scope; if (event.className === 'non-working') { key = 'bg'; } let color = this.colors[key]; if (event.color) { color = event.color; } if (!color) { color = this.getColorFromScopeName(event.scope); } if (event.status && (this.getEventTypeCompletedStatusList(event.scope).includes(event.status) || this.getEventTypeCanceledStatusList(event.scope).includes(event.status))) { color = this.shadeColor(color, 0.4); } event.style = event.style || ''; event.style += 'background-color:' + color + ';'; event.style += 'border-color:' + color + ';'; } handleStatus(event) { if (this.getEventTypeCanceledStatusList(event.scope).includes(event.status)) { event.className += ' event-canceled'; } } shadeColor(color, percent) { if (color === 'transparent') { return color; } if (this.getThemeManager().getParam('isDark')) { percent *= -1; } const alpha = color.substring(7); const f = parseInt(color.slice(1, 7), 16), t = percent < 0 ? 0 : 255, p = percent < 0 ? percent * -1 : percent, R = f >> 16, G = f >> 8 & 0x00FF, B = f & 0x0000FF; return "#" + (0x1000000 + (Math.round((t - R) * p) + R) * 0x10000 + (Math.round((t - G) * p) + G) * 0x100 + (Math.round((t - B) * p) + B)).toString(16).slice(1) + alpha; } convertEventList(list) { const resultList = []; list.forEach(item => { const event = this.convertEvent(item); if (!event) { return; } resultList.push(event); }); return resultList; } afterRender() { if (this.options.containerSelector) { this.$container = (0, _jquery.default)(this.options.containerSelector); } const $timeline = this.$timeline = this.$el.find('div.timeline'); this.initUserList(); this.initDates(); this.initGroupsDataSet(); this.fetchEvents(this.start, this.end, eventList => { const itemsDataSet = new _visData.DataSet(eventList); this.timeline = new _visTimeline.Timeline($timeline.get(0), itemsDataSet, this.groupsDataSet, { dataAttributes: 'all', start: this.start.toDate(), end: this.end.toDate(), rollingMode: { follow: false // fixes slow render }, xss: { filterOptions: { onTag: (tag, html) => html } }, moment: /** Record */date => { const m = (0, _moment.default)(date); if (date && date.noTimeZone) { return m; } // noinspection JSUnresolvedReference return m.tz(this.getDateTime().getTimeZone()); }, format: this.getFormatObject(), zoomMax: 24 * 3600 * 1000 * this.maxRange, zoomMin: 1000 * 60 * 15, orientation: 'top', groupEditable: false, editable: { add: false, updateTime: false, updateGroup: false, remove: false }, locales: { myLocale: { current: this.translate('current', 'labels', 'Calendar'), time: this.translate('time', 'labels', 'Calendar') } }, locale: 'myLocale', margin: { item: { vertical: 12 }, axis: 6 } }); this.timeline.on('click', e => { if (this.blockClick) { return; } if (e.item) { const $item = this.$el.find('.timeline .vis-item[data-id="' + e.item + '"]'); const id = $item.attr('data-record-id'); const scope = $item.attr('data-scope'); if (id && scope) { this.viewEvent(scope, id); } return; } if (e.what === 'background' && e.group && e.time) { const dateStart = (0, _moment.default)(e.time).utc().format(this.getDateTime().internalDateTimeFormat); this.createEvent(dateStart, e.group); } }); // noinspection SpellCheckingInspection this.timeline.on('rangechanged', e => { e.skipClick = true; this.blockClick = true; setTimeout(() => { this.blockClick = false; }, 100); this.start = (0, _moment.default)(e.start); this.end = (0, _moment.default)(e.end); this.triggerView(); if (this.start.unix() < this.fetchedStart.unix() + this.rangeMarginThreshold || this.end.unix() > this.fetchedEnd.unix() - this.rangeMarginThreshold) { this.runFetch(); } }); this.once('remove', () => { this.timeline.destroy(); }); }); } /** * @private * @param {string} dateStart * @param {string} [userId] */ async createEvent(dateStart, userId) { if (!dateStart) { const time = (this.timeline.getWindow().end - this.timeline.getWindow().start) / 2 + this.timeline.getWindow().start; dateStart = (0, _moment.default)(time).utc().format(this.getDateTime().internalDateTimeFormat); if (this.date === this.getDateTime().getToday()) { dateStart = (0, _moment.default)().utc().format(this.getDateTime().internalDateTimeFormat); } } const attributes = { dateStart: dateStart }; if (userId) { let userName; this.userList.forEach(item => { if (item.id === userId) { userName = item.name; } }); attributes.assignedUserId = userId; attributes.assignedUserName = userName || userId; } const scopeList = this.enabledScopeList.filter(it => !this.onlyDateScopeList.includes(it)); Espo.Ui.notifyWait(); const view = await this.createView('dialog', 'crm:views/calendar/modals/edit', { attributes: attributes, enabledScopeList: scopeList, scopeList: this.scopeList }); this.listenTo(view, 'before:save', () => { if (this.options.onSave) { this.options.onSave(); } }); this.listenTo(view, 'after:save', () => this.runFetch()); await view.render(); Espo.Ui.notify(); } /** * * @param {string} scope * @param {string} id */ async viewEvent(scope, id) { const helper = new _recordModal.default(); /** @type {import('views/modals/detail').default} */ let modalView; modalView = await helper.showDetail(this, { entityType: scope, id: id, removeDisabled: false, beforeSave: () => { if (this.options.onSave) { this.options.onSave(); } }, beforeDestroy: () => { if (this.options.onSave) { this.options.onSave(); } }, afterSave: (model, o) => { if (!o.bypassClose) { modalView.close(); } this.runFetch(); }, afterDestroy: () => { this.runFetch(); } }); } runFetch() { this.fetchEvents(this.start, this.end, eventList => { const itemsDataSet = new _visData.DataSet(eventList); this.timeline.setItems(itemsDataSet); this.triggerView(); }); } getFormatObject() { return { minorLabels: { millisecond: 'SSS', second: 's', minute: this.getDateTime().getTimeFormat(), hour: this.getDateTime().getTimeFormat(), weekday: 'ddd D', day: 'D', month: 'MMM', year: 'YYYY' }, majorLabels: { millisecond: this.getDateTime().getTimeFormat() + ' ss', second: this.getDateTime().getReadableDateFormat() + ' HH:mm', minute: 'ddd D MMMM', hour: 'ddd D MMMM', weekday: 'MMMM YYYY', day: 'MMMM YYYY', month: 'YYYY', year: '' } }; } triggerView() { const m = this.start.clone().add(Math.round((this.end.unix() - this.start.unix()) / 2), 'seconds'); const date = m.format(this.getDateTime().internalDateFormat); this.date = date; this.trigger('view', date, this.mode); } initUserList() { if (this.options.userList) { this.userList = Espo.Utils.clone(this.options.userList); if (!this.userList.length) { this.userList.push({ id: this.getUser().id, name: this.getUser().get('name') }); } return; } this.userList = []; if (this.calendarType === 'single') { if (this.options.userId) { this.userList.push({ id: this.options.userId, name: this.options.userName || this.options.userId }); return; } this.userList.push({ id: this.getUser().id, name: this.getUser().get('name') }); return; } if (this.calendarType === 'shared') { this.getSharedCalenderUserList().forEach(item => { this.userList.push({ id: item.id, name: item.name }); }); } } storeUserList() { this.getPreferences().save({ 'sharedCalendarUserList': Espo.Utils.clone(this.userList) }, { patch: true }); } getSharedCalenderUserList() { const list = Espo.Utils.clone(this.getPreferences().get('sharedCalendarUserList')); if (list && list.length) { let isBad = false; list.forEach(item => { if (typeof item !== 'object' || !item.id || !item.name) { isBad = true; } }); if (!isBad) { return list; } } return [{ id: this.getUser().id, name: this.getUser().get('name') }]; } initDates() { if (this.date) { this.start = _moment.default.tz(this.date, this.getDateTime().getTimeZone()); } else { this.start = _moment.default.tz(this.getDateTime().getTimeZone()); } this.end = this.start.clone(); this.end.add(1, 'day'); this.fetchedStart = null; this.fetchedEnd = null; } initGroupsDataSet() { const list = []; this.userList.forEach((user, i) => { list.push({ id: user.id, content: this.getGroupContent(user.id, user.name), order: i }); }); this.groupsDataSet = new _visData.DataSet(list); } getGroupContent(id, name) { if (this.calendarType === 'single') { return (0, _jquery.default)('').text(name).get(0).outerHTML; } let avatarHtml = this.getAvatarHtml(id); if (avatarHtml) { avatarHtml += ' '; } return avatarHtml + (0, _jquery.default)('').attr('data-id', id).addClass('group-title').text(name).get(0).outerHTML; } getAvatarHtml(id) { if (this.getConfig().get('avatarsDisabled')) { return ''; } let t; const cache = this.getCache(); if (cache) { t = cache.get('app', 'timestamp'); } else { t = Date.now(); } // noinspection HtmlRequiredAltAttribute,RequiredAttributes return (0, _jquery.default)('').addClass('avatar avatar-link').attr('width', '16').attr('src', this.getBasePath() + '?entryPoint=avatar&size=small&id=' + id + '&t=' + t).get(0).outerHTML; } fetchEvents(from, to, callback) { if (!this.options.suppressLoadingAlert) { Espo.Ui.notifyWait(); } from = from.clone().add(-1 * this.leftMargin, 'seconds'); to = to.clone().add(this.rightMargin, 'seconds'); const fromString = from.utc().format(this.getDateTime().internalDateTimeFormat); const toString = to.utc().format(this.getDateTime().internalDateTimeFormat); let url = 'Timeline?from=' + fromString + '&to=' + toString; const userIdList = this.userList.map(user => { return user.id; }); if (userIdList.length === 1) { url += '&userId=' + userIdList[0]; } else { url += '&userIdList=' + encodeURIComponent(userIdList.join(',')); } url += '&scopeList=' + encodeURIComponent(this.enabledScopeList.join(',')); Espo.Ajax.getRequest(url).then(data => { this.fetchedStart = from.clone(); this.fetchedEnd = to.clone(); const eventList = []; for (const userId in data) { const userEventList = data[userId]; userEventList.forEach(item => { item.userId = userId; eventList.push(item); }); } const convertedEventList = this.convertEventList(eventList); callback(convertedEventList); Espo.Ui.notify(false); }); } async actionShowSharedCalendarOptions() { const view = new _sharedOptions.default({ users: this.userList, onApply: data => { this.userList = data.users; this.storeUserList(); this.initGroupsDataSet(); this.timeline.setGroups(this.groupsDataSet); this.runFetch(); } }); await this.assignView('modal', view); await view.render(); } actionRefresh() { this.runFetch(); const iconEl = this.element.querySelector('button[data-action="refresh"] > span'); if (iconEl) { iconEl.classList.add('animation-spin-fast'); setTimeout(() => iconEl.classList.remove('animation-spin-fast'), 500); } } getColorFromScopeName(scope) { const additionalColorList = this.getMetadata().get('clientDefs.Calendar.additionalColorList') || []; if (!additionalColorList.length) { return; } const colors = this.getMetadata().get('clientDefs.Calendar.colors') || {}; const scopeList = this.getConfig().get('calendarEntityList') || []; let index = 0; let j = 0; for (let i = 0; i < scopeList.length; i++) { if (scopeList[i] in colors) { continue; } if (scopeList[i] === scope) { index = j; break; } j++; } index = index % additionalColorList.length; this.colors[scope] = additionalColorList[index]; return this.colors[scope]; } // noinspection JSUnusedGlobalSymbols actionPrevious() { const start = this.timeline.getWindow().start; this.timeline.moveTo(start); this.triggerView(); } // noinspection JSUnusedGlobalSymbols actionNext() { const end = this.timeline.getWindow().end; this.timeline.moveTo(end); this.triggerView(); } actionToday() { this.timeline.moveTo((0, _moment.default)().toDate()); this.triggerView(); } // noinspection JSUnusedGlobalSymbols actionZoomOut() { this.timeline.zoomOut(this.zoomPercentage); this.triggerView(); } // noinspection JSUnusedGlobalSymbols actionZoomIn() { this.timeline.zoomIn(this.zoomPercentage); } } var _default = _exports.default = TimelineView; });