diff --git a/apps/common/main/lib/component/Calendar.js b/apps/common/main/lib/component/Calendar.js
new file mode 100644
index 0000000000..71b282a132
--- /dev/null
+++ b/apps/common/main/lib/component/Calendar.js
@@ -0,0 +1,491 @@
+/*
+ *
+ * (c) Copyright Ascensio System SIA 2010-2019
+ *
+ * This program is a free software product. You can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License (AGPL)
+ * version 3 as published by the Free Software Foundation. In accordance with
+ * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
+ * that Ascensio System SIA expressly excludes the warranty of non-infringement
+ * of any third-party rights.
+ *
+ * This program is distributed WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
+ * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
+ *
+ * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
+ * street, Riga, Latvia, EU, LV-1050.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of the Program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU AGPL version 3.
+ *
+ * Pursuant to Section 7(b) of the License you must retain the original Product
+ * logo when distributing the program. Pursuant to Section 7(e) we decline to
+ * grant you any rights under trademark law for use of our trademarks.
+ *
+ * All the Product's GUI elements, including illustrations and icon sets, as
+ * well as technical writing content are licensed under the terms of the
+ * Creative Commons Attribution-ShareAlike 4.0 International. See the License
+ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
+ *
+*/
+if (Common === undefined)
+ var Common = {};
+
+define([
+ 'common/main/lib/component/BaseView',
+ 'common/main/lib/util/utils'
+], function () {
+ 'use strict';
+
+ Common.UI.Calendar = Common.UI.BaseView.extend(_.extend({
+
+ template :
+ _.template([
+ '
',
+ '
',
+ '
',
+ '',
+ '',
+ '',
+ '
',
+ '
',
+ '
',
+ '
',
+ '',
+ '
'
+ ].join('')),
+
+ options: {
+ date: undefined,
+ firstday: 0 // 0 - sunday, 1 - monday
+ },
+
+ initialize : function(options) {
+ Common.UI.BaseView.prototype.initialize.call(this, options);
+
+ var me = this;
+
+ this.monthNames = [this.textJanuary, this.textFebruary, this.textMarch, this.textApril, this.textMay, this.textJune, this.textJuly, this.textAugust, this.textSeptember, this.textOctober, this.textNovember, this.textDecember];
+ this.dayNamesShort = [this.textShortSunday, this.textShortMonday, this.textShortTuesday, this.textShortWednesday, this.textShortThursday, this.textShortFriday, this.textShortSaturday];
+ this.monthShortNames = [this.textShortJanuary, this.textShortFebruary, this.textShortMarch, this.textShortApril, this.textShortMay, this.textShortJune, this.textShortJuly, this.textShortAugust, this.textShortSeptember, this.textShortOctober, this.textShortNovember, this.textShortDecember];
+
+ me.options.date = options.date;
+ if (!_.isUndefined(options.firstday) && (options.firstday === 0 || options.firstday === 1)) {
+ me.options.firstday = options.firstday;
+ }
+
+ me.enableKeyEvents= me.options.enableKeyEvents;
+
+ me._state = undefined; // 0 - month, 1 - months, 2 - years
+
+ me.render();
+ },
+
+ render: function () {
+ var me = this;
+ me.cmpEl = me.$el || $(this.el);
+ me.cmpEl.html(this.template());
+
+ me.currentDate = me.options.date || new Date();
+
+ me.btnPrev = new Common.UI.Button({
+ cls: '',
+ iconCls: 'arrow-prev img-commonctrl'
+ });
+ me.btnPrev.render(me.cmpEl.find('#prev-arrow'));
+ me.btnPrev.on('click', _.bind(me.onClickPrev, me));
+
+ me.btnNext = new Common.UI.Button({
+ cls: '',
+ iconCls: 'arrow-next img-commonctrl'
+ });
+ me.btnNext.render(me.cmpEl.find('#next-arrow'));
+ me.btnNext.on('click', _.bind(me.onClickNext, me));
+
+ me.cmpEl.on('keydown', function(e) {
+ me.trigger('calendar:keydown', me, e);
+ });
+
+ me.renderMonth(me.currentDate);
+
+ this.trigger('render:after', this);
+ return this;
+ },
+
+ onClickPrev: function () {
+ var me = this;
+ if (me._state === 0) {
+ var d = new Date(me.currentDate);
+ d.setMonth(d.getMonth() - 1);
+ if (d.getFullYear() > 0) {
+ me.renderMonth(d);
+ }
+ } else if (me._state === 1) {
+ var d = new Date(me.currentDate);
+ d.setFullYear(d.getFullYear() - 1);
+ if (d.getFullYear() > 0) {
+ me.renderMonths(d);
+ }
+ } else if (me._state === 2) {
+ var year = me.currentDate.getFullYear(),
+ newYear;
+ if (year % 10 !== 0) {
+ newYear = String(year);
+ newYear = Number(newYear.slice(0, -1) + '0') - 1;
+ } else {
+ newYear = year - 1;
+ }
+ if (newYear > 0) {
+ me.currentDate.setFullYear(newYear);
+ me.renderYears(newYear);
+ }
+ }
+ },
+
+ onClickNext: function () {
+ var me = this;
+ if (me._state === 0) {
+ var d = new Date(me.currentDate);
+ d.setMonth(d.getMonth() + 1);
+ if (d.getFullYear() > 0) {
+ me.renderMonth(d);
+ }
+ } else if (me._state === 1) {
+ var d = new Date(me.currentDate);
+ d.setFullYear(d.getFullYear() + 1);
+ if (d.getFullYear() > 0) {
+ me.renderMonths(d);
+ }
+ } else if (me._state === 2) {
+ var year = me.currentDate.getFullYear(),
+ newYear;
+ if (year % 10 !== 9) {
+ newYear = String(year);
+ newYear = Number(newYear.slice(0, -1) + '9') + 1;
+ } else {
+ newYear = year + 1;
+ }
+ if (newYear > 0) {
+ me.currentDate.setFullYear(newYear);
+ me.renderYears(newYear);
+ }
+ }
+ },
+
+ renderYears: function (year) {
+ var me = this,
+ year = _.isNumber(year) ? year : (me.currentDate ? me.currentDate.getFullYear() : new Date().getFullYear());
+
+ me._state = 2;
+
+ var firstYear = year,
+ lastYear = year;
+ if ((firstYear % 10) !== 0) {
+ var strYear = String(year);
+ firstYear = Number(strYear.slice(0, -1) + '0');
+ }
+ if ((lastYear % 10) !== 9) {
+ var strYear = String(year);
+ lastYear = Number(strYear.slice(0, -1) + '9');
+ }
+
+ me.topTitle = _.template([
+ ''
+ ].join(''));
+ me.cmpEl.find('.calendar-header .title').html(me.topTitle);
+
+ me.bottomTitle = _.template([
+ ''
+ ].join(''));
+ me.cmpEl.find('.calendar-header .bottom-row').html(me.bottomTitle);
+
+ var arrYears = [];
+ var tmpYear = firstYear - 3;
+
+ for (var i = 0; i < 16; i++) {
+ arrYears.push({
+ year: (tmpYear > 0) ? tmpYear : '',
+ isCurrentDecade: ((tmpYear >= firstYear) && (tmpYear <= lastYear)) ? true : false,
+ disabled: (tmpYear > 0) ? false : true,
+ selected: (_.isDate(me.selectedDate)) ?
+ (tmpYear === me.selectedDate.getFullYear()) :
+ (tmpYear === new Date().getFullYear())
+ });
+ tmpYear++;
+ }
+
+ if (!me.yearPicker) {
+ me.yearPicker = new Common.UI.DataView({
+ el: me.cmpEl.find('.calendar-content'),
+ store: new Common.UI.DataViewStore(arrYears),
+ itemTemplate: _.template('
<%= year %>
')
+ });
+ me.yearPicker.on('item:click', function (picker, item, record, e) {
+ var year = record.get('year'),
+ date = new Date();
+ date.setFullYear(year);
+ me.renderMonths(date);
+ });
+ me.enableKeyEvents && this.yearPicker.on('item:keydown', function(view, record, e) {
+ if (e.keyCode==Common.UI.Keys.ESC) {
+ Common.NotificationCenter.trigger('dataview:blur');
+ }
+ });
+ } else
+ me.yearPicker.store.reset(arrYears);
+
+ me.enableKeyEvents && _.delay(function() {
+ me.monthPicker.cmpEl.find('.dataview').focus();
+ }, 10);
+ },
+
+ renderMonths: function (date) {
+ var me = this,
+ curDate = (_.isDate(date)) ? date : (me.currentDate ? me.currentDate : new Date()),
+ year = curDate.getFullYear();
+
+ me._state = 1;
+ me.currentDate = curDate;
+
+ // Number of year
+ me.topTitle = _.template([
+ ''
+ ].join(''));
+ me.cmpEl.find('.calendar-header .title').html(me.topTitle);
+ me.cmpEl.find('.calendar-header .title').off();
+ me.cmpEl.find('.calendar-header .title').on('click', _.bind(me.renderYears, me));
+
+ me.bottomTitle = _.template([
+ ''
+ ].join(''));
+ me.cmpEl.find('.calendar-header .bottom-row').html(me.bottomTitle);
+
+ var arrMonths = [];
+ var today = new Date();
+
+ for (var ind = 0; ind < 12; ind++) {
+ arrMonths.push({
+ indexMonth: ind,
+ nameMonth: me.monthShortNames[ind],
+ year: year,
+ curYear: true,
+ isCurrentMonth: (ind === curDate.getMonth()),
+ selected: (_.isDate(me.selectedDate)) ?
+ (ind === me.selectedDate.getMonth() && year === me.selectedDate.getFullYear()) :
+ (ind === today.getMonth() && year === today.getFullYear())
+ });
+ }
+ year = year + 1;
+ for (var ind = 0; ind < 4; ind++) {
+ arrMonths.push({
+ indexMonth: ind,
+ nameMonth: me.monthShortNames[ind],
+ year: year,
+ curYear: false,
+ selected: (_.isDate(me.selectedDate)) ?
+ (ind === me.selectedDate.getMonth() && year === me.selectedDate.getFullYear()) :
+ (ind === today.getMonth() && year === today.getFullYear())
+ });
+ }
+
+ if (!me.monthsPicker) {
+ me.monthsPicker = new Common.UI.DataView({
+ el: me.cmpEl.find('.calendar-content'),
+ store: new Common.UI.DataViewStore(arrMonths),
+ itemTemplate: _.template('
<%= nameMonth %>
')
+ });
+ me.monthsPicker.on('item:click', function (picker, item, record, e) {
+ var month = record.get('indexMonth'),
+ year = record.get('year'),
+ date = new Date();
+ date.setFullYear(year, month);
+ me.renderMonth(date);
+ });
+ me.enableKeyEvents && this.monthsPicker.on('item:keydown', function(view, record, e) {
+ if (e.keyCode==Common.UI.Keys.ESC) {
+ Common.NotificationCenter.trigger('dataview:blur');
+ }
+ });
+ } else
+ me.monthsPicker.store.reset(arrMonths);
+
+ me.enableKeyEvents && _.delay(function() {
+ me.monthPicker.cmpEl.find('.dataview').focus();
+ }, 10);
+ },
+
+ renderMonth: function (date) {
+ var me = this;
+ me._state = 0;
+ var firstDay = me.options.firstday;
+
+ // Current date
+ var curDate = date || new Date(),
+ curMonth = curDate.getMonth(),
+ curIndexDayInWeek = curDate.getDay(),
+ curNumberDayInMonth = curDate.getDate(),
+ curYear = curDate.getFullYear();
+
+ me.currentDate = curDate;
+
+ // Name month
+ me.topTitle = _.template([
+ '