From 05084c67dba130560b514cff5f354ca14443a07c Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 4 Jul 2023 11:57:04 +0300 Subject: [PATCH] ref --- .../crm/src/views/calendar/timeline.js | 2071 +++++++++-------- .../crm/src/views/scheduler/scheduler.js | 995 ++++---- 2 files changed, 1543 insertions(+), 1523 deletions(-) diff --git a/client/modules/crm/src/views/calendar/timeline.js b/client/modules/crm/src/views/calendar/timeline.js index f5b893c03e..4dc409d0ef 100644 --- a/client/modules/crm/src/views/calendar/timeline.js +++ b/client/modules/crm/src/views/calendar/timeline.js @@ -26,1172 +26,1187 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('crm:views/calendar/timeline', ['view', 'lib!vis'], function (Dep, Vis) { +import View from 'view'; +import Vis from 'vis'; +import moment from 'moment'; +import $ from 'jquery'; - return Dep.extend({ +class TimelineView extends View { - template: 'crm:calendar/timeline', + template = 'crm:calendar/timeline' - eventAttributes: [], - colors: {}, - scopeList: [], - header: true, - modeList: [], - defaultMode: 'timeline', - maxRange: 120, - rangeMarginThreshold: 12 * 3600, - leftMargin: 24 * 3600, - rightMargin: 48 * 3600, - calendarType: 'single', - calendarTypeList: ['single', 'shared'], - zoomPercentage: 1, + eventAttributes = [] + colors = {} + scopeList = [] + header = true + modeList = [] + defaultMode = 'timeline' + maxRange = 120 + rangeMarginThreshold = 12 * 3600 + leftMargin = 24 * 3600 + rightMargin = 48 * 3600 + calendarType = 'single' + calendarTypeList = ['single', 'shared'] + zoomPercentage = 1 - data: function () { - var 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, - }; + events = { + /** @this TimelineView */ + 'click button[data-action="today"]': function () { + this.actionToday(); }, + /** @this TimelineView */ + 'click [data-action="mode"]': function (e) { + let mode = $(e.currentTarget).data('mode'); - events: { - 'click button[data-action="today"]': function () { - this.actionToday(); - }, - 'click [data-action="mode"]': function (e) { - let mode = $(e.currentTarget).data('mode'); + this.selectMode(mode) + }, + /** @this TimelineView */ + 'click [data-action="refresh"]': function () { + this.actionRefresh(); + }, + /** @this TimelineView */ + 'click [data-action="toggleScopeFilter"]': function (e) { + let $target = $(e.currentTarget); + let filterName = $target.data('name'); - this.selectMode(mode) - }, - 'click [data-action="refresh"]': function () { - this.actionRefresh(); - }, - 'click [data-action="toggleScopeFilter"]': function (e) { - let $target = $(e.currentTarget); - let filterName = $target.data('name'); + let $check = $target.find('.filter-check-icon'); - let $check = $target.find('.filter-check-icon'); - - if ($check.hasClass('hidden')) { - $check.removeClass('hidden'); - } else { - $check.addClass('hidden'); - } - - e.stopPropagation(e); - - this.toggleScopeFilter(filterName); - }, - 'click [data-action="toggleCalendarType"]': function (e) { - let $target = $(e.currentTarget); - let calendarType = $target.data('name'); - - $target.parent().parent().find('.calendar-type-check-icon').addClass('hidden'); - - let $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)); - - let $showSharedCalendarOptions = this.$el - .find('> .button-container button[data-action="showSharedCalendarOptions"]'); - - if (calendarType === 'shared') { - $showSharedCalendarOptions.removeClass('hidden'); - } else { - $showSharedCalendarOptions.addClass('hidden'); - } - - this.selectCalendarType(calendarType); - }, - 'click button[data-action="addUser"]': function () { - this.actionAddUser(); - }, - 'click button[data-action="showSharedCalendarOptions"]': function () { - this.actionShowSharedCalendarOptions(); + 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) { + let $target = $(e.currentTarget); + let calendarType = $target.data('name'); - setup: function () { - 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; + $target.parent().parent().find('.calendar-type-check-icon').addClass('hidden'); - this.$container = this.options.$container; + let $check = $target.find('.calendar-type-check-icon'); - 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.allDayScopeList || []; + if ($check.hasClass('hidden')) { + $check.removeClass('hidden'); + } - this.colors = {...this.colors, ...this.getHelper().themeManager.getParam('calendarColors')}; + $target.closest('.calendar-type-button-group') + .find('.calendar-type-label') + .text(this.getCalendarTypeLabel(calendarType)); - this.isCustomViewAvailable = this.getAcl().get('userPermission') !== 'no'; + let $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="addUser"]': function () { + this.actionAddUser(); + }, + /** @this TimelineView */ + 'click button[data-action="showSharedCalendarOptions"]': function () { + this.actionShowSharedCalendarOptions(); + }, + } + + data() { + let 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.allDayScopeList || []; + + this.colors = {...this.colors, ...this.getHelper().themeManager.getParam('calendarColors')}; + + this.isCustomViewAvailable = this.getAcl().getPermissionLevel('userPermission') !== 'no'; + + if (this.options.userId) { + this.isCustomViewAvailable = false; + } + + let 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 => { + let color = this.getMetadata().get(['clientDefs', item, 'color']); + + if (color) { + this.colors[item] = color; + } + }); + + if (this.options.calendarType) { + this.calendarType = this.options.calendarType; + } else { if (this.options.userId) { - this.isCustomViewAvailable = false; - } - - let 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); + this.calendarType = 'single'; } else { - this.enabledScopeList = this.options.enabledScopeList || Espo.Utils.clone(this.scopeList); + this.calendarType = this.getStorage().get('calendar', 'timelineType') || 'shared'; } + } - if (Object.prototype.toString.call(this.enabledScopeList) !== '[object Array]') { - this.enabledScopeList = []; - } - - this.enabledScopeList.forEach(item => { - var color = this.getMetadata().get(['clientDefs', item, 'color']); - - if (color) { - this.colors[item] = color; - } - }); - - 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().get('userPermission' === 'no')) { - if (this.calendarType === 'shared') { - this.calendarType = 'single'; - } - } - - if (!~this.calendarTypeList.indexOf(this.calendarType)) { + if (this.getAcl().get('userPermission' === 'no')) { + if (this.calendarType === 'shared') { this.calendarType = 'single'; } + } - if (this.header) { - this.createView('modeButtons', 'crm:views/calendar/mode-buttons', { - el: this.getSelector() + ' .mode-buttons', - isCustomViewAvailable: this.isCustomViewAvailable, - modeList: this.modeList, - scopeList: this.scopeList, - mode: this.mode, - }); - } - }, + if (!~this.calendarTypeList.indexOf(this.calendarType)) { + this.calendarType = 'single'; + } - selectMode: function (mode) { - this.trigger('change:mode', mode); - }, - - getModeDataList: function () { - var list = []; - - this.modeList.forEach(name => { - var o = { - mode: name, - label: this.translate(name, 'modes', 'Calendar'), - labelShort: this.translate(name, 'modes', 'Calendar').substring(0, 2), - }; - - list.push(o); + if (this.header) { + this.createView('modeButtons', 'crm:views/calendar/mode-buttons', { + el: this.getSelector() + ' .mode-buttons', + isCustomViewAvailable: this.isCustomViewAvailable, + modeList: this.modeList, + scopeList: this.scopeList, + mode: this.mode, }); + } + } - if (this.isCustomViewAvailable) { - (this.getPreferences().get('calendarViewDataList') || []).forEach((item) => { - item = Espo.Utils.clone(item); + selectMode(mode) { + this.trigger('change:mode', mode); + } - item.mode = 'view-' + item.id; - item.label = item.name; - item.labelShort = (item.name || '').substring(0, 2); + /*getModeDataList() { + let list = []; - list.push(item); - }); - } - - var currentIndex = -1; - - list.forEach((item, i) => { - if (item.mode === this.mode) { - currentIndex = i; - } - }); - - if (currentIndex >= this.visibleModeListCount) { - var tmp = list[this.visibleModeListCount - 1]; - - list[this.visibleModeListCount - 1] = list[currentIndex]; - list[currentIndex] = tmp; - } - - return list; - }, - - getVisibleModeDataList: function () { - var fullList = this.getModeDataList(); - - var list = []; - - fullList.forEach((o, i) => { - if (i >= this.visibleModeListCount) { - return; - } - - list.push(o); - }); - - return list; - }, - - getHiddenModeDataList: function () { - var fullList = this.getModeDataList(); - - var list = []; - - fullList.forEach((o, i) => { - if (i < this.visibleModeListCount) { - return; - } - - list.push(o); - }); - - return list; - }, - - getCalendarTypeDataList: function () { - var list = []; - - var o = { - type: 'single', - disabled: this.calendarType !== 'single', - label: this.getCalendarTypeLabel('single') + this.modeList.forEach(name => { + let o = { + mode: name, + label: this.translate(name, 'modes', 'Calendar'), + labelShort: this.translate(name, 'modes', 'Calendar').substring(0, 2), }; list.push(o); + }); - if (this.options.userId) { - return list; + if (this.isCustomViewAvailable) { + (this.getPreferences().get('calendarViewDataList') || []).forEach((item) => { + item = Espo.Utils.clone(item); + + item.mode = 'view-' + item.id; + item.label = item.name; + item.labelShort = (item.name || '').substring(0, 2); + + list.push(item); + }); + } + + let currentIndex = -1; + + list.forEach((item, i) => { + if (item.mode === this.mode) { + currentIndex = i; } + }); - if (this.getAcl().get('userPermission') !== 'no') { - list.push({ - type: 'shared', - label: this.getCalendarTypeLabel('shared'), - disabled: this.calendarType !== 'shared' - }); - } + if (currentIndex >= this.visibleModeListCount) { + let tmp = list[this.visibleModeListCount - 1]; - return list; - }, + list[this.visibleModeListCount - 1] = list[currentIndex]; + list[currentIndex] = tmp; + } - getCalendarTypeLabel: function (type) { - var label; + return list; + }*/ - 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); + /*getVisibleModeDataList() { + let fullList = this.getModeDataList(); + let list = []; - return label; - } - - if (type === 'shared') { - return this.translate('Shared', 'labels', 'Calendar'); - } - }, - - selectCalendarType: function (name) { - this.calendarType = name; - - this.initUserList(); - this.initGroupsDataSet(); - this.timeline.setGroups(this.groupsDataSet); - - this.runFetch(); - - this.getStorage().set('calendar', 'timelineType', name); - }, - - toggleScopeFilter: function (name) { - var index = this.enabledScopeList.indexOf(name); - - if (!~index) { - this.enabledScopeList.push(name); - } else { - this.enabledScopeList.splice(index, 1); - } - - this.storeEnabledScopeList(this.enabledScopeList); - this.runFetch(); - }, - - getStoredEnabledScopeList: function () { - var key = 'calendarEnabledScopeList'; - - return this.getStorage().get('state', key) || null; - }, - - storeEnabledScopeList: function (enabledScopeList) { - var key = 'calendarEnabledScopeList'; - - this.getStorage().set('state', key, enabledScopeList); - }, - - updateDate: function () { - if (!this.header) { + fullList.forEach((o, i) => { + if (i >= this.visibleModeListCount) { return; } - var title = this.getTitle(); - this.$el.find('.date-title h4 span').text(title); - }, + list.push(o); + }); - getTitle: function () { - var title = ''; + return list; + }*/ - if (this.options.userId && this.options.userName) { - title += ' (' + this.options.userName + ')'; + /*getHiddenModeDataList() { + let fullList = this.getModeDataList(); + let list = []; + + fullList.forEach((o, i) => { + if (i < this.visibleModeListCount) { + return; } - title = this.getHelper().escapeString(title); + list.push(o); + }); - return title; - }, + return list; + }*/ - convertEvent: function (o) { - let userId = o.userId || this.userList[0].id || this.getUser().id; + getCalendarTypeDataList() { + let list = []; - let event; + let o = { + type: 'single', + disabled: this.calendarType !== 'single', + label: this.getCalendarTypeLabel('single'), + }; - 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', - }; + list.push(o); + + if (this.options.userId) { + return list; + } + + if (this.getAcl().get('userPermission') !== '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'); } - 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, - }; + 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) { + let index = this.enabledScopeList.indexOf(name); + + if (!~index) { + this.enabledScopeList.push(name); + } else { + this.enabledScopeList.splice(index, 1); + } + + this.storeEnabledScopeList(this.enabledScopeList); + this.runFetch(); + } + + getStoredEnabledScopeList() { + let key = 'calendarEnabledScopeList'; + + return this.getStorage().get('state', key) || null; + } + + storeEnabledScopeList(enabledScopeList) { + let key = 'calendarEnabledScopeList'; + + this.getStorage().set('state', key, enabledScopeList); + } + + /*updateDate() { + if (!this.header) { + return; + } + + let title = this.getTitle(); + + this.$el.find('.date-title h4 span').text(title); + }*/ + + getTitle() { + let title = ''; + + if (this.options.userId && this.options.userName) { + title += ' (' + this.options.userName + ')'; + } + + title = this.getHelper().escapeString(title); + + return title; + } + + convertEvent(o) { + let 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) { + if (!o.dateStartDate) { + event.start = this.getDateTime().toMoment(o.dateStart); + } else { + event.start = moment.tz(o.dateStartDate, this.getDateTime().getTimeZone()); + } + } + + if (o.dateEnd) { + if (!o.dateEndDate) { + event.end = this.getDateTime().toMoment(o.dateEnd); + } else { + event.end = moment.tz(o.dateEndDate, this.getDateTime().getTimeZone()); + } + } + + if (o.dateStartDate && !~this.allDayScopeList.indexOf(o.scope)) { + event.end = event.end.clone().add(1, 'days'); + } + + if (o.isBusyRange) { + return event; + } + + if (~this.allDayScopeList.indexOf(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; + } + + let alpha = color.substring(7); + + let 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) { + let resultList = []; + + list.forEach(item => { + let event = this.convertEvent(item); + + if (!event) { + return; } - this.eventAttributes.forEach(attr => { - event[attr] = o[attr]; + resultList.push(event); + }); + + return resultList; + } + + /*convertTime(d) { + let format = this.getDateTime().internalDateTimeFormat; + let timeZone = this.getDateTime().timeZone; + let string = d.format(format); + + let m; + + m = timeZone ? + moment.tz(string, format, timeZone).utc() : + moment.utc(string, format); + + return m.format(format) + ':00'; + }*/ + + afterRender() { + if (this.options.containerSelector) { + this.$container = $(this.options.containerSelector); + } + + let $timeline = this.$timeline = this.$el.find('div.timeline'); + + this.initUserList(); + this.initDates(); + this.initGroupsDataSet(); + + this.fetchEvents(this.start, this.end, (eventList) => { + let itemsDataSet = new Vis.DataSet(eventList); + + let timeline = this.timeline = new Vis.Timeline($timeline.get(0), itemsDataSet, this.groupsDataSet, { + dataAttributes: 'all', + start: this.start.toDate(), + end: this.end.toDate(), + moment: date => { + let m = moment(date); + + if (date && date.noTimeZone) { + return m; + } + + 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, + }, }); - if (o.dateStart) { - if (!o.dateStartDate) { - event.start = this.getDateTime().toMoment(o.dateStart); - } else { - event.start = moment.tz(o.dateStartDate, this.getDateTime().getTimeZone()); - } - } - - if (o.dateEnd) { - if (!o.dateEndDate) { - event.end = this.getDateTime().toMoment(o.dateEnd); - } else { - event.end = moment.tz(o.dateEndDate, this.getDateTime().getTimeZone()); - } - } - - if (o.dateStartDate && !~this.allDayScopeList.indexOf(o.scope)) { - event.end = event.end.clone().add(1, 'days'); - } - - if (o.isBusyRange) { - return event; - } - - if (~this.allDayScopeList.indexOf(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: function (scope) { - return this.getMetadata().get(['scopes', scope, 'completedStatusList']) || []; - }, - - /** - * @param {string} scope - * @return {string[]} - */ - getEventTypeCanceledStatusList: function (scope) { - return this.getMetadata().get(['scopes', scope, 'canceledStatusList']) || []; - }, - - fillColor: function (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: function (event) { - if (this.getEventTypeCanceledStatusList(event.scope).includes(event.status)) { - event.className += ' event-canceled'; - } - }, - - shadeColor: function (color, percent) { - if (color === 'transparent') { - return color; - } - - if (this.getThemeManager().getParam('isDark')) { - percent *= -1; - } - - let alpha = color.substring(7); - - let 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: function (list) { - var resultList = []; - - list.forEach(item => { - var event = this.convertEvent(item); - - if (!event) { + timeline.on('click', (e) => { + if (this.blockClick) { return; } - resultList.push(event); + if (e.item) { + let $item = this.$el.find('.timeline .vis-item[data-id="'+e.item+'"]'); + let id = $item.attr('data-record-id'); + let scope = $item.attr('data-scope'); + + if (id && scope) { + this.viewEvent(scope, id); + } + + return; + } + + if (e.what === 'background' && e.group && e.time) { + let dateStart = moment(e.time).utc().format(this.getDateTime().internalDateTimeFormat); + + this.createEvent(dateStart, e.group); + } }); - return resultList; - }, + timeline.on('rangechanged', (e) =>{ + e.skipClick = true; - convertTime: function (d) { - var format = this.getDateTime().internalDateTimeFormat; - var timeZone = this.getDateTime().timeZone; - var string = d.format(format); + this.blockClick = true; - var m; + setTimeout(() => { + this.blockClick = false; + }, 100); - if (timeZone) { - m = moment.tz(string, format, timeZone).utc(); - } else { - m = moment.utc(string, format); - } + this.start = moment(e.start); + this.end = moment(e.end); - return m.format(format) + ':00'; - }, + this.triggerView(); - afterRender: function () { - if (this.options.containerSelector) { - this.$container = $(this.options.containerSelector); - } - - var $timeline = this.$timeline = this.$el.find('div.timeline'); - - this.initUserList(); - this.initDates(); - this.initGroupsDataSet(); - - this.fetchEvents(this.start, this.end, (eventList) => { - var itemsDataSet = new Vis.DataSet(eventList); - var timeline = this.timeline = new Vis.Timeline($timeline.get(0), itemsDataSet, this.groupsDataSet, { - dataAttributes: 'all', - start: this.start.toDate(), - end: this.end.toDate(), - moment: (date) => { - var m = moment(date); - - if (date && date.noTimeZone) { - return m; - } - - 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, - }, - }); - - timeline.on('click', (e) => { - if (this.blockClick) { - return; - } - - if (e.item) { - let $item = this.$el.find('.timeline .vis-item[data-id="'+e.item+'"]'); - let id = $item.attr('data-record-id'); - let scope = $item.attr('data-scope'); - - if (id && scope) { - this.viewEvent(scope, id); - } - - return; - } - - if (e.what === 'background' && e.group && e.time) { - let dateStart = moment(e.time).utc().format(this.getDateTime().internalDateTimeFormat); - - this.createEvent(dateStart, e.group); - } - }); - - timeline.on('rangechanged', (e) =>{ - e.skipClick = true; - - this.blockClick = true; - - setTimeout(() => { - this.blockClick = false; - }, 100); - - this.start = moment(e.start); - this.end = moment(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', () => { - timeline.destroy(); - }); + if ( + (this.start.unix() < this.fetchedStart.unix() + this.rangeMarginThreshold) || + (this.end.unix() > this.fetchedEnd.unix() - this.rangeMarginThreshold) + ) { + this.runFetch(); + } }); - }, - createEvent: function (dateStart, userId) { - if (!dateStart) { - let time = (this.timeline.range.end - this.timeline.range.start) / 2 + - this.timeline.range.start; + this.once('remove', () => { + timeline.destroy(); + }); + }); + } - dateStart = moment(time) + createEvent(dateStart, userId) { + if (!dateStart) { + let time = (this.timeline.range.end - this.timeline.range.start) / 2 + + this.timeline.range.start; + + dateStart = moment(time) + .utc() + .format(this.getDateTime().internalDateTimeFormat); + + if (this.date === this.getDateTime().getToday()) { + dateStart = moment() .utc() .format(this.getDateTime().internalDateTimeFormat); - - if (this.date === this.getDateTime().getToday()) { - dateStart = moment() - .utc() - .format(this.getDateTime().internalDateTimeFormat); - } } + } - let attributes = {dateStart: dateStart}; + let attributes = {dateStart: dateStart}; - if (userId) { - var userName; + if (userId) { + let userName; - this.userList.forEach((item) => { - if (item.id === userId) { - userName = item.name; - } - }); + this.userList.forEach((item) => { + if (item.id === userId) { + userName = item.name; + } + }); - attributes.assignedUserId = userId; - attributes.assignedUserName = userName || userId; + attributes.assignedUserId = userId; + attributes.assignedUserName = userName || userId; + } + + Espo.Ui.notify(' ... '); + + this.createView('quickEdit', 'crm:views/calendar/modals/edit', { + attributes: attributes, + enabledScopeList: this.enabledScopeList, + scopeList: this.scopeList + }, (view) => { + view.render(); + view.notify(false); + + this.listenTo(view, 'after:save', () => { + this.runFetch(); + }); + }); + } + + viewEvent(scope, id) { + Espo.Ui.notify(' ... '); + + let viewName = this.getMetadata().get(['clientDefs', scope, 'modalViews', 'detail']) || + 'views/modals/detail'; + + this.createView('quickView', viewName, { + scope: scope, + id: id, + removeDisabled: false + }, (view) => { + view.render(); + view.notify(false); + + this.listenToOnce(view, 'after:destroy', () => { + this.runFetch(); + }); + + this.listenTo(view, 'after:save', (m, o) => { + o = o || {}; + + if (!o.bypassClose) { + view.close(); + } + + this.runFetch(); + }); + }); + } + + runFetch() { + this.fetchEvents(this.start, this.end, eventList => { + let itemsDataSet = new Vis.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: '', } + }; + } - Espo.Ui.notify(' ... '); + triggerView() { + let m = this.start.clone().add(Math.round((this.end.unix() - this.start.unix()) / 2), 'seconds'); - this.createView('quickEdit', 'crm:views/calendar/modals/edit', { - attributes: attributes, - enabledScopeList: this.enabledScopeList, - scopeList: this.scopeList - }, (view) => { - view.render(); - view.notify(false); + let date = m.format(this.getDateTime().internalDateFormat); - this.listenTo(view, 'after:save', () => { - this.runFetch(); - }); - }); - }, + this.date = date; - viewEvent: function (scope, id) { - Espo.Ui.notify(' ... '); + this.trigger('view', date, this.mode); + } - var viewName = this.getMetadata().get(['clientDefs', scope, 'modalViews', 'detail']) || - 'views/modals/detail'; - - this.createView('quickView', viewName, { - scope: scope, - id: id, - removeDisabled: false - }, (view) => { - view.render(); - view.notify(false); - - this.listenToOnce(view, 'after:destroy', (m) => { - this.runFetch(); - }); - - this.listenTo(view, 'after:save', (m, o) => { - o = o || {}; - - if (!o.bypassClose) { - view.close(); - } - - this.runFetch(); - }); - }); - }, - - runFetch: function () { - this.fetchEvents(this.start, this.end, eventList => { - let itemsDataSet = new Vis.DataSet(eventList); - - this.timeline.setItems(itemsDataSet); - this.triggerView(); - }); - }, - - getFormatObject: function () { - 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: function () { - let m = this.start.clone().add(Math.round((this.end.unix() - this.start.unix()) / 2), 'seconds'); - - let date = m.format(this.getDateTime().internalDateFormat); - - this.date = date; - - this.trigger('view', date, this.mode); - }, - - initUserList: function () { - 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; - } + 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; } - if (this.calendarType === 'shared') { - this.getSharedCalenderUserList().forEach(item => { - this.userList.push({ - id: item.id, - name: item.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 }); - } - }, - orderUserList: function (list) { - var userList = []; - - list.forEach(id => { - this.userList.forEach(item => { - if (id === item.id) { - userList.push(item); - } - }); - }); - - this.userList = userList; - - this.storeUserList(); - }, - - storeUserList: function () { - this.getPreferences().save({ - 'sharedCalendarUserList': Espo.Utils.clone(this.userList) - }, {patch: true}); - }, - - addSharedCalenderUser: function (id, name, skipStore) { - var isMet = false; - - this.userList.forEach(item => { - if (item.id === id) { - isMet = true; - } - }); - - if (isMet) { return; } this.userList.push({ - id: id, - name: name - }); - - if (!skipStore) { - this.storeUserList(); - } - }, - - removeSharedCalendarUser: function (id) { - var index = -1; - - this.userList.forEach((item, i) => { - if (item.id === id) { - index = i; - } - }); - - if (~index) { - this.userList.splice(index, 1); - this.storeUserList(); - } - }, - - getSharedCalenderUserList: function () { - var list = Espo.Utils.clone(this.getPreferences().get('sharedCalendarUserList')); - - if (list && list.length) { - var 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: function () { - if (this.date) { - this.start = moment.tz(this.date, this.getDateTime().getTimeZone()); - } else { - this.start = moment.tz(this.getDateTime().getTimeZone()); - } - - this.end = this.start.clone(); - this.end.add(1, 'day'); - - this.fetchedStart = null; - this.fetchedEnd = null; - }, - - initGroupsDataSet: function () { - var list = []; - - this.userList.forEach((user, i) =>{ - list.push({ - id: user.id, - content: this.getGroupContent(user.id, user.name), - order: i, - }); }); - this.groupsDataSet = new Vis.DataSet(list); - }, - getGroupContent: function (id, name) { + return; + } - if (this.calendarType === 'single') { - return $('') - .text(name) - .get(0).outerHTML; + if (this.calendarType === 'shared') { + this.getSharedCalenderUserList().forEach(item => { + this.userList.push({ + id: item.id, + name: item.name, + }); + }); + } + } + + /*orderUserList(list) { + let userList = []; + + list.forEach(id => { + this.userList.forEach(item => { + if (id === item.id) { + userList.push(item); + } + }); + }); + + this.userList = userList; + + this.storeUserList(); + }*/ + + storeUserList() { + this.getPreferences().save({ + 'sharedCalendarUserList': Espo.Utils.clone(this.userList), + }, {patch: true}); + } + + addSharedCalenderUser(id, name, skipStore) { + let isMet = false; + + this.userList.forEach(item => { + if (item.id === id) { + isMet = true; } + }); - let avatarHtml = this.getAvatarHtml(id); + if (isMet) { + return; + } - if (avatarHtml) { - avatarHtml += ' '; + this.userList.push({ + id: id, + name: name, + }); + + if (!skipStore) { + this.storeUserList(); + } + } + + /*removeSharedCalendarUser(id) { + let index = -1; + + this.userList.forEach((item, i) => { + if (item.id === id) { + index = i; } + }); - return avatarHtml + - $('') - .attr('data-id', id) - .addClass('group-title') - .text(name) - .get(0).outerHTML; - }, + if (~index) { + this.userList.splice(index, 1); + this.storeUserList(); + } + }*/ - getAvatarHtml: function (id) { - if (this.getConfig().get('avatarsDisabled')) { - return ''; + getSharedCalenderUserList() { + let 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; } + } - var t; - var cache = this.getCache(); + return [{ + id: this.getUser().id, + name: this.getUser().get('name'), + }]; + } - if (cache) { - t = cache.get('app', 'timestamp'); - } else { - t = Date.now(); - } + initDates() { + if (this.date) { + this.start = moment.tz(this.date, this.getDateTime().getTimeZone()); + } else { + this.start = moment.tz(this.getDateTime().getTimeZone()); + } - return $('') - .addClass('avatar avatar-link') - .attr('width', '14') - .attr('src', this.getBasePath() + '?entryPoint=avatar&size=small&id=' + id + '&t=' + t) + this.end = this.start.clone(); + this.end.add(1, 'day'); + + this.fetchedStart = null; + this.fetchedEnd = null; + } + + initGroupsDataSet() { + let list = []; + + this.userList.forEach((user, i) =>{ + list.push({ + id: user.id, + content: this.getGroupContent(user.id, user.name), + order: i, + }); + }); + + this.groupsDataSet = new Vis.DataSet(list); + } + + getGroupContent(id, name) { + if (this.calendarType === 'single') { + return $('') + .text(name) .get(0).outerHTML; - }, + } - initItemsDataSet: function () { - this.itemsDataSet = new Vis.DataSet(list); - }, + let avatarHtml = this.getAvatarHtml(id); - fetchEvents: function (from, to, callback) { - if (!this.options.noFetchLoadingMessage) { - Espo.Ui.notify(' ... '); - } + if (avatarHtml) { + avatarHtml += ' '; + } - from = from.clone().add((-1) * this.leftMargin, 'seconds'); - to = to.clone().add(this.rightMargin, 'seconds'); + return avatarHtml + + $('') + .attr('data-id', id) + .addClass('group-title') + .text(name) + .get(0).outerHTML; + } - var fromString = from.utc().format(this.getDateTime().internalDateTimeFormat); - var toString = to.utc().format(this.getDateTime().internalDateTimeFormat); + getAvatarHtml(id) { + if (this.getConfig().get('avatarsDisabled')) { + return ''; + } - var url = 'Timeline?from=' + fromString + '&to=' + toString; + let t; + let cache = this.getCache(); - var userIdList = this.userList.map(user => { - return user.id - }); + if (cache) { + t = cache.get('app', 'timestamp'); + } else { + t = Date.now(); + } - if (userIdList.length === 1) { - url += '&userId=' + userIdList[0]; - } else { - url += '&userIdList=' + encodeURIComponent(userIdList.join(',')); - } + // noinspection HtmlRequiredAltAttribute,RequiredAttributes + return $('') + .addClass('avatar avatar-link') + .attr('width', '14') + .attr('src', this.getBasePath() + '?entryPoint=avatar&size=small&id=' + id + '&t=' + t) + .get(0).outerHTML; + } - url += '&scopeList=' + encodeURIComponent(this.enabledScopeList.join(',')); - - Espo.Ajax.getRequest(url).then(data => { - this.fetchedStart = from.clone(); - this.fetchedEnd = to.clone(); - - let eventList = []; - - for (let userId in data) { - let userEventList = data[userId]; - - userEventList.forEach(item => { - item.userId = userId; - - eventList.push(item); - }); - } - - let convertedEventList = this.convertEventList(eventList); - - callback(convertedEventList); - - Espo.Ui.notify(false); - }); - }, - - removeUser: function (id) { - this.removeSharedCalendarUser(id); - this.initGroupsDataSet(); - this.timeline.setGroups(this.groupsDataSet); - }, - - actionShowSharedCalendarOptions: function () { - this.createView('dialog', 'crm:views/calendar/modals/shared-options', { - userList: this.userList - }, view => { - view.render(); - - this.listenToOnce(view, 'save', data => { - this.userList = data.userList; - this.storeUserList(); - this.initGroupsDataSet(); - this.timeline.setGroups(this.groupsDataSet); - this.runFetch(); - }); - }); - }, - - actionAddUser: function () { - var boolFilterList = []; - - if (this.getAcl().get('userPermission') === 'team') { - boolFilterList.push('onlyMyTeam'); - } - - var viewName = this.getMetadata().get('clientDefs.' + this.foreignScope + '.modalViews.select') || - 'views/modals/select-records'; + /*initItemsDataSet() { + this.itemsDataSet = new Vis.DataSet(list); + }*/ + fetchEvents(from, to, callback) { + if (!this.options.noFetchLoadingMessage) { Espo.Ui.notify(' ... '); + } - this.createView('dialog', viewName, { - scope: 'User', - createButton: false, - boolFilterList: boolFilterList, - multiple: true, - }, view => { - view.render(); - Espo.Ui.notify(false); + from = from.clone().add((-1) * this.leftMargin, 'seconds'); + to = to.clone().add(this.rightMargin, 'seconds'); - this.listenToOnce(view, 'select', modelList => { - modelList.forEach(model => { - this.addSharedCalenderUser(model.id, model.get('name')); - }); + let fromString = from.utc().format(this.getDateTime().internalDateTimeFormat); + let toString = to.utc().format(this.getDateTime().internalDateTimeFormat); - this.storeUserList(); - this.initGroupsDataSet(); + let url = 'Timeline?from=' + fromString + '&to=' + toString; - this.timeline.setGroups(this.groupsDataSet); + let userIdList = this.userList.map(user => { + return user.id; + }); - this.runFetch(); + 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(); + + let eventList = []; + + for (let userId in data) { + let userEventList = data[userId]; + + userEventList.forEach(item => { + item.userId = userId; + + eventList.push(item); }); + } + + let convertedEventList = this.convertEventList(eventList); + + callback(convertedEventList); + + Espo.Ui.notify(false); + }); + } + + /*removeUser(id) { + this.removeSharedCalendarUser(id); + this.initGroupsDataSet(); + this.timeline.setGroups(this.groupsDataSet); + }*/ + + actionShowSharedCalendarOptions() { + this.createView('dialog', 'crm:views/calendar/modals/shared-options', { + userList: this.userList, + }, view => { + view.render(); + + this.listenToOnce(view, 'save', data => { + this.userList = data.userList; + this.storeUserList(); + this.initGroupsDataSet(); + this.timeline.setGroups(this.groupsDataSet); + this.runFetch(); }); - }, + }); + } - actionRefresh: function () { - this.runFetch(); - }, + actionAddUser() { + let boolFilterList = []; - getColorFromScopeName: function (scope) { - var additionalColorList = this.getMetadata().get('clientDefs.Calendar.additionalColorList') || []; + if (this.getAcl().get('userPermission') === 'team') { + boolFilterList.push('onlyMyTeam'); + } - if (!additionalColorList.length) { - return; + let viewName = this.getMetadata().get('clientDefs.' + this.foreignScope + '.modalViews.select') || + 'views/modals/select-records'; + + Espo.Ui.notify(' ... '); + + this.createView('dialog', viewName, { + scope: 'User', + createButton: false, + boolFilterList: boolFilterList, + multiple: true, + }, view => { + view.render(); + Espo.Ui.notify(false); + + this.listenToOnce(view, 'select', modelList => { + modelList.forEach(model => { + this.addSharedCalenderUser(model.id, model.get('name')); + }); + + this.storeUserList(); + this.initGroupsDataSet(); + + this.timeline.setGroups(this.groupsDataSet); + + this.runFetch(); + }); + }); + } + + actionRefresh() { + this.runFetch(); + } + + getColorFromScopeName(scope) { + let additionalColorList = this.getMetadata().get('clientDefs.Calendar.additionalColorList') || []; + + if (!additionalColorList.length) { + return; + } + + let colors = this.getMetadata().get('clientDefs.Calendar.colors') || {}; + let 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; } - var colors = this.getMetadata().get('clientDefs.Calendar.colors') || {}; - var scopeList = this.getConfig().get('calendarEntityList') || []; + if (scopeList[i] === scope) { + index = j; - var index = 0; - var j = 0; - - for (let i = 0; i < scopeList.length; i++) { - if (scopeList[i] in colors) { - continue; - } - - if (scopeList[i] === scope) { - index = j; - - break; - } - - j++; + break; } - index = index % additionalColorList.length; - this.colors[scope] = additionalColorList[index]; + j++; + } - return this.colors[scope]; - }, + index = index % additionalColorList.length; + this.colors[scope] = additionalColorList[index]; - actionPrevious: function () { - this.timeline.moveTo(this.timeline.range.start); - this.triggerView(); - }, + return this.colors[scope]; + } - actionNext: function () { - this.timeline.moveTo(this.timeline.range.end); - this.triggerView(); - }, + actionPrevious() { + this.timeline.moveTo(this.timeline.range.start); - actionToday: function () { - this.timeline.moveTo(moment()); - this.triggerView(); - }, + this.triggerView(); + } - actionZoomOut: function () { - this.timeline.zoomOut(this.zoomPercentage); - this.triggerView(); - }, + actionNext() { + this.timeline.moveTo(this.timeline.range.end); - actionZoomIn: function () { - this.timeline.zoomIn(this.zoomPercentage); - }, - }); -}); + this.triggerView(); + } + + actionToday() { + this.timeline.moveTo(moment()); + + this.triggerView(); + } + + // noinspection JSUnusedGlobalSymbols + actionZoomOut() { + this.timeline.zoomOut(this.zoomPercentage); + + this.triggerView(); + } + + // noinspection JSUnusedGlobalSymbols + actionZoomIn() { + this.timeline.zoomIn(this.zoomPercentage); + } +} + +export default TimelineView; diff --git a/client/modules/crm/src/views/scheduler/scheduler.js b/client/modules/crm/src/views/scheduler/scheduler.js index 58dbf207ab..84c50f30b7 100644 --- a/client/modules/crm/src/views/scheduler/scheduler.js +++ b/client/modules/crm/src/views/scheduler/scheduler.js @@ -26,563 +26,568 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis) { +import View from 'view'; +import Vis from 'vis'; +import moment from 'moment'; +import $ from 'jquery'; - return Dep.extend({ +class SchedulerView extends View { - templateContent: ` -
- - `, + // language=Handlebars + templateContent = ` +
+ + ` - rangeMarginThreshold: 12 * 3600, + rangeMarginThreshold = 12 * 3600 + leftMargin = 24 * 3600 + rightMargin = 48 * 3600 + rangeMultiplierLeft = 3 + rangeMultiplierRight = 3 - leftMargin: 24 * 3600, - rightMargin: 48 * 3600, + setup() { + this.startField = this.options.startField || 'dateStart'; + this.endField = this.options.endField || 'dateEnd'; + this.assignedUserField = this.options.assignedUserField || 'assignedUser'; - rangeMultiplierLeft: 3, - rangeMultiplierRight: 3, + this.colors = Espo.Utils.clone(this.getMetadata().get('clientDefs.Calendar.colors') || {}); - setup: function () { - this.startField = this.options.startField || 'dateStart'; - this.endField = this.options.endField || 'dateEnd'; - this.assignedUserField = this.options.assignedUserField || 'assignedUser'; + this.colors = {...this.colors, ...this.getHelper().themeManager.getParam('calendarColors')}; - this.colors = Espo.Utils.clone(this.getMetadata().get('clientDefs.Calendar.colors') || {}); + let usersFieldDefault = 'users'; - this.colors = {...this.colors, ...this.getHelper().themeManager.getParam('calendarColors')}; + if (!this.model.hasLink('users') && this.model.hasLink('assignedUsers')) { + usersFieldDefault = 'assignedUsers'; + } - let usersFieldDefault = 'users'; + this.eventAssignedUserIsAttendeeDisabled = + this.getConfig().get('eventAssignedUserIsAttendeeDisabled') || false; - if (!this.model.hasLink('users') && this.model.hasLink('assignedUsers')) { - usersFieldDefault = 'assignedUsers'; + this.usersField = this.options.usersField || usersFieldDefault; + this.userIdList = []; + + this.Vis = Vis; + + this.listenTo(this.model, 'change', (m) => { + let isChanged = + m.hasChanged('isAllDay') || + m.hasChanged(this.startField) || + m.hasChanged(this.endField) || + m.hasChanged(this.usersField + 'Ids') || + !this.eventAssignedUserIsAttendeeDisabled && m.hasChanged(this.assignedUserField + 'Id'); + + if (!isChanged) { + return; } - this.eventAssignedUserIsAttendeeDisabled = - this.getConfig().get('eventAssignedUserIsAttendeeDisabled') || false; - - this.usersField = this.options.usersField || usersFieldDefault; - - this.userIdList = []; - - this.Vis = Vis; - - this.listenTo(this.model, 'change', (m) => { - let isChanged = - m.hasChanged('isAllDay') || - m.hasChanged(this.startField) || - m.hasChanged(this.endField) || - 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'); + 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.trigger('has-data'); + this.showNoData(); + this.trigger('no-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(); - }); - }, + if (this.timeline) { + this.updateEvent(); - destroyTimeline: function () { - if (this.timeline) { - this.timeline.destroy(); - this.timeline = null; - } - }, + this.timeline.setWindow( + this.start.toDate(), + this.end.toDate() + ); + } - showNoData: function () { - this.noDataShown = true; - - this.destroyTimeline(); - - this.$timeline.empty(); - this.$timeline.append( - $('
') - .addClass('revert-margin') - .text(this.translate('No Data')) - ); - }, - - afterRender: function () { - 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'); + if (this.noDataShown) { + this.reRender(); + } return; } - this.destroyTimeline(); - - if (this.lastHeight) { - $timeline.css('min-height', this.lastHeight + 'px'); + if (this.isRemoved()) { + return; } - this.fetch(this.start, this.end, eventList => { - let itemsDataSet = new Vis.DataSet(eventList); + this.trigger('has-data'); + this.reRender(); + }); - let timeline = this.timeline =new Vis.Timeline($timeline.get(0), itemsDataSet, this.groupsDataSet, { - dataAttributes: 'all', - start: this.start.toDate(), - end: this.end.toDate(), - moment: date => { - let m = moment(date); + this.once('remove', () => { + this.destroyTimeline(); + }); + } - if (date && date.noTimeZone) { - return m; - } + destroyTimeline() { + if (this.timeline) { + this.timeline.destroy(); + this.timeline = null; + } + } - return m.tz(this.getDateTime().getTimeZone()); + showNoData() { + this.noDataShown = true; + + this.destroyTimeline(); + + this.$timeline.empty(); + this.$timeline.append( + $('
') + .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 Vis.DataSet(eventList); + + let timeline = this.timeline =new Vis.Timeline($timeline.get(0), itemsDataSet, this.groupsDataSet, { + dataAttributes: 'all', + start: this.start.toDate(), + end: this.end.toDate(), + moment: date => { + let m = moment(date); + + if (date && date.noTimeZone) { + return m; + } + + return m.tz(this.getDateTime().getTimeZone()); + }, + 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, }, - 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', ''); - - timeline.on('rangechanged', (e) => { - e.skipClick = true; - - this.blockClick = true; - - setTimeout(function () {this.blockClick = false}.bind(this), 100); - - this.start = moment(e.start); - this.end = moment(e.end); - - this.updateRange(); - }); - - setTimeout(() => { - this.lastHeight = $timeline.height(); - }, 500); + axis: 6, + }, }); - }, - updateEvent: function () { - let eventList = Espo.Utils.cloneDeep(this.busyEventList); + $timeline.css('min-height', ''); - let convertedEventList = this.convertEventList(eventList); - this.addEvent(convertedEventList); + timeline.on('rangechanged', (e) => { + e.skipClick = true; - let itemsDataSet = new this.Vis.DataSet(convertedEventList); + this.blockClick = true; + + setTimeout(function () {this.blockClick = false}.bind(this), 100); + + this.start = moment(e.start); + this.end = moment(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 this.Vis.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.startField + 'Date'); + endS = this.model.get(this.endField + 'Date'); + } + + if (!startS || !endS) { + return; + } + + if (this.model.get('isAllDay')) { + this.eventStart = moment.tz(startS, this.getDateTime().getTimeZone()); + this.eventEnd = moment.tz(endS, this.getDateTime().getTimeZone()); + this.eventEnd.add(1, 'day'); + } else { + this.eventStart = moment.utc(startS).tz(this.getDateTime().getTimeZone()); + this.eventEnd = moment.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 this.Vis.DataSet(eventList); this.timeline.setItems(itemsDataSet); - }, + }); + } - updateRange: function () { - if ( - (this.start.unix() < this.fetchedStart.unix() + this.rangeMarginThreshold) || - (this.end.unix() > this.fetchedEnd.unix() - this.rangeMarginThreshold) - ) { - this.runFetch(); - } - }, + fetch(from, to, callback) { + from = from.clone().add((-1) * this.leftMargin, 'seconds'); + to = to.clone().add(this.rightMargin, 'seconds'); - initDates: function (update) { - this.start = null; - this.end = null; + let fromString = from.utc().format(this.getDateTime().internalDateTimeFormat); + let toString = to.utc().format(this.getDateTime().internalDateTimeFormat); - let startS = this.model.get(this.startField); - let endS = this.model.get(this.endField); + let url = + 'Timeline/busyRanges?from=' + fromString + '&to=' + toString + + '&userIdList=' + encodeURIComponent(this.userIdList.join(',')) + + '&entityType=' + this.model.entityType; - if (this.model.get('isAllDay')) { - startS = this.model.get(this.startField + 'Date'); - endS = this.model.get(this.endField + 'Date'); + 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 = data[userId] + .filter(item => !item.isBusyRange) + .concat( + data[userId].filter(item => item.isBusyRange) + ); + + itemList.forEach(item => { + item.userId = userId; + + eventList.push(item); + }); } - if (!startS || !endS) { + 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; } - if (this.model.get('isAllDay')) { - this.eventStart = moment.tz(startS, this.getDateTime().getTimeZone()); - this.eventEnd = moment.tz(endS, this.getDateTime().getTimeZone()); - this.eventEnd.add(1, 'day'); - } else { - this.eventStart = moment.utc(startS).tz(this.getDateTime().getTimeZone()); - this.eventEnd = moment.utc(endS).tz(this.getDateTime().getTimeZone()); - } + resultList.push(event); + }); - let diff = this.eventEnd.diff(this.eventStart, 'hours'); + return resultList; + } - this.start = this.eventStart.clone(); - this.end = this.eventEnd.clone(); + convertEvent(o) { + let event; - 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: function () { - this.fetch(this.start, this.end, eventList => { - let itemsDataSet = new this.Vis.DataSet(eventList); - - this.timeline.setItems(itemsDataSet); - }); - }, - - fetch: function (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 = 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: function (list) { - this.getCurrentItemList().forEach(item => { - list.push(item); - }); - }, - - getCurrentItemList: function () { - let list = []; - - let o = { - start: this.eventStart.clone(), - end: this.eventEnd.clone(), + 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', - style: 'z-index: 4; opacity: 0.6;', - className: 'event-range', }; - let color = this.getColorFromScopeName(this.model.entityType); + let color = this.colors['bg']; - if (color) { - o.style += '; border-color: ' + color; + event.style = 'background-color:' + color + ';'; + event.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: function (list) { - let resultList = []; - - list.forEach(item => { - let event = this.convertEvent(item); - - if (!event) { - return; - } - - resultList.push(event); - }); - - return resultList; - }, - - convertEvent: function (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.tz(o.dateStartDate, this.getDateTime().getTimeZone()); - } - } - - if (o.dateEnd) { - if (!o.dateEndDate) { - event.end = this.getDateTime().toMoment(o.dateEnd); - } else { - event.end = moment.tz(o.dateEndDate, this.getDateTime().getTimeZone()); - } - } - - if (o.isBusyRange || o.isNonWorkingRange) { - return event; - } - }, - - initGroupsDataSet: function () { - 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 this.Vis.DataSet(list); - }, - - getGroupContent: function (id, name) { - if (this.calendarType === 'single') { - return $('') - .text(name) - .get(0).outerHTML; - } - - let avatarHtml = this.getAvatarHtml(id); - - if (avatarHtml) { - avatarHtml += ' '; - } - - return $('') - .append( - avatarHtml, - $('') - .attr('data-id', id) - .addClass('group-title') - .text(name) - ) - .get(0).innerHTML; - }, - - getAvatarHtml: function (id) { - if (this.getConfig().get('avatarsDisabled')) { - return ''; - } - - let t; - let cache = this.getCache(); - - if (cache) { - t = cache.get('app', 'timestamp'); + if (o.dateStart) { + if (!o.dateStartDate) { + event.start = this.getDateTime().toMoment(o.dateStart); } else { - t = Date.now(); + event.start = moment.tz(o.dateStartDate, this.getDateTime().getTimeZone()); + } + } + + if (o.dateEnd) { + if (!o.dateEndDate) { + event.end = this.getDateTime().toMoment(o.dateEnd); + } else { + event.end = moment.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); } - return $('') - .addClass('avatar avatar-link') - .attr('width', '14') - .attr('src', this.getBasePath() + '?entryPoint=avatar&size=small&id=' + id + '&t=' + t) + 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 this.Vis.DataSet(list); + } + + getGroupContent(id, name) { + if (this.calendarType === 'single') { + return $('') + .text(name) .get(0).outerHTML; - }, + } - getFormatObject: function () { - 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: '' - } - }; - }, + let avatarHtml = this.getAvatarHtml(id); - getColorFromScopeName: function (scope) { - return this.getMetadata().get(['clientDefs', scope, 'color']) || - this.getMetadata().get(['clientDefs', 'Calendar', 'colors', scope]); - }, + if (avatarHtml) { + avatarHtml += ' '; + } - hexToRgb: function (hex) { - let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return $('') + .append( + avatarHtml, + $('') + .attr('data-id', id) + .addClass('group-title') + .text(name) + ) + .get(0).innerHTML; + } - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16), - } : null; - }, - }); -}); + 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 $('') + .addClass('avatar avatar-link') + .attr('width', '14') + .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; + } +} + +export default SchedulerView;