/* * Tine 2.0 * * @package Calendar * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3 * @author Cornelius Weiss * @copyright Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de) */ Ext.ns('Tine.Calendar'); /** * @namespace Tine.Calendar * @class Tine.Calendar.FreeTimeSearchDialog * @extends Ext.Panel */ Tine.Calendar.FreeTimeSearchDialog = Ext.extend(Ext.Panel, { cls: 'tw-editdialog', layout: 'fit', windowNamePrefix: 'CalFreeTimeSearchWindow_', optionsStateId: 'FreeTimeSearchOptions', canonicalName: ['', 'Calendar', 'FreeTimeSearchDialog'].join(Tine.Tinebase.CanonicalPath.separator), initComponent: function() { var _ = window.lodash; this.app = Tine.Tinebase.appMgr.get('Calendar'); this.recordClass = Tine.Calendar.Model.Event; this.recordProxy = Tine.Calendar.backend; if (Ext.isString(this.record)) { this.record = this.recordProxy.recordReader({responseText: this.record}); } var prefs = this.app.getRegistry().peek('preferences'); Ext.DatePicker.prototype.startDay = parseInt((prefs ? prefs.get('firstdayofweek') : 1), 10); this.store = new Ext.data.JsonStore({ fields: Tine.Calendar.Model.Event, proxy: Tine.Calendar.backend, reader: new Ext.data.JsonReader({}) }); this.calendarView = new Tine.Calendar.DaysView({ store: this.store, startDate: new Date(), numOfDays: 7, readOnly: true }); this.calendarView.getSelectionModel().on('selectionchange', this.onViewSelectionChange, this); this.store.on('beforeload', this.onStoreBeforeload, this); this.store.on('load', this.onStoreLoad, this); this.detailsPanel = new Tine.Calendar.EventDetailsPanel(); this.freeTimeSlots = []; this.tbar = [{ text: this.app.i18n._('Back'), minWidth: 70, ref: '../buttonBack', iconCls: 'action_previous', scope: this, disabled: true, handler: this.onButtonBack }, { text: this.app.i18n._('Next'), minWidth: 70, ref: '../buttonNext', iconCls: 'action_next', scope: this, handler: this.onButtonNext }, '-', { text: this.app.i18n._('Options'), minWidth: 70, ref: '../buttonOptions', iconCls: 'action_options', scope: this, handler: this.onButtonOptions }]; this.fbar = ['->', { text: i18n._('Cancel'), minWidth: 70, ref: '../buttonCancel', scope: this, handler: this.onButtonCancel, iconCls: 'action_cancel' }, { text: i18n._('Ok'), minWidth: 70, ref: '../buttonApply', scope: this, handler: this.onButtonApply, variant: 'primary', iconCls: 'action_saveAndClose' }]; this.items = [{ layout: 'border', border: false, items: [{ region: 'center', layout: 'fit', border: false, items: this.calendarView }, { region: 'south', border: false, collapsible: true, collapseMode: 'mini', header: false, split: true, layout: 'fit', height: this.detailsPanel.defaultHeight ? this.detailsPanel.defaultHeight : 125, items: this.detailsPanel }] }]; Tine.Calendar.FreeTimeSearchDialog.superclass.initComponent.call(this); this.constraints = Ext.state.Manager.get(this.optionsStateId, Tine.Calendar.EventFinderOptionsDialog.prototype.defaultOptions); this.doFreeTimeSearch(); }, afterRender: function() { Tine.Calendar.FreeTimeSearchDialog.superclass.afterRender.call(this); this.window.setTitle(this.app.i18n._('Free Time Search')); this.loadMask = new Ext.LoadMask(this.getEl(), {msg: this.app.i18n._("Searching for Free Time...")}); this.loadMask.show.defer(100, this.loadMask); }, applyOptions: function(constraints) { // nothing to do if (Ext.encode(this.constraints) == Ext.encode(constraints)) return; this.constraints = constraints; this.doFreeTimeSearch() }, doFreeTimeSearch: function(from) { if (this.loadMask) { this.loadMask.show(); } var _ = window.lodash, datePart = this.record.get('dtstart').add(Date.DAY, -7).format('Y-m-d '), groupedConstraints = _.groupBy(_.filter(this.constraints, {active: true}), function(constraint) { return constraint.period.from + '-' + constraint.period.until; }), constraints = _.reduce(groupedConstraints, function(result, constraintsGroup) { return result.concat({ dtstart: datePart + constraintsGroup[0].period.from, dtend: datePart + constraintsGroup[0].period.until, rrule: 'FREQ=WEEKLY;INTERVAL=1;BYDAY=' + _.map(constraintsGroup, 'id').join(',') }); }, []), options = { from: from ? from : this.record.get('dtstart'), constraints: constraints }; Tine.Calendar.searchFreeTime(this.record.data, options, this.onFreeTimeData, this); }, onFreeTimeData: function(result) { if (result.timeSearchStopped) { return this.onFreeTimeSearchTimeout(result); } var _ = window.lodash, timing = _.get(result, 'results[0]'), dtStart = Date.parseDate(timing.dtstart, Date.patterns.ISO8601Long), from = dtStart.clearTime(true).add(Date.DAY, -1 * dtStart.getDay()) .add(Date.DAY, Ext.DatePicker.prototype.startDay - (dtStart.getDay() == 0 ? 7 : 0)), dtEnd = Date.parseDate(timing.dtend, Date.patterns.ISO8601Long), until = from.add(Date.DAY, 7), period = {from: from, until: until}, filterHash = Ext.encode(this.store.baseParams.filter); this.store.remove(this.record); this.record.set('dtstart', dtStart); this.record.set('dtend', dtEnd); this.freeTimeSlot = result; // calculate period start if (Ext.DatePicker.prototype.startDay) { from = from.add(Date.DAY, Ext.DatePicker.prototype.startDay - (dtStart.getDay() == 0 ? 7 : 0)); } this.store.baseParams.filter = [ {field: 'period', operator: 'within', value: period}, {field: 'attender', operator: 'in', value: this.record.get('attendee')}, {field: 'attender_status', operator: 'notin', value: 'DECLINED'} ]; if (this.record.get('id')) { this.store.baseParams.filter.push({field: 'id', operator: 'not', value: this.record.get('id')}); } if (filterHash == Ext.encode(this.store.baseParams.filter)) { this.onStoreLoad(); } else { this.calendarView.updatePeriod(period); this.store.load(); } }, onFreeTimeSearchTimeout: function(result) { var _ = window.lodash, dtStopped = Date.parseDate(_.get(result, 'timeSearchStopped'), Date.patterns.ISO8601Long), stoppedString = Tine.Tinebase.common.dateRenderer(dtStopped); Tine.widgets.dialog.MultiOptionsDialog.openWindow({ title: this.app.i18n._('No Free Time found'), questionText: String.format(this.app.i18n._('No free timeslot could be found Until {0}. Do you want to continue?'), '' + stoppedString + '') + '

', height: 170, scope: this, options: [ {text: this.app.i18n._('Continue'), name: 'continue'}, {text: this.app.i18n._('Give up'), name: 'giveUp'} ], handler: function(option) { switch(option) { case 'giveUp': this.onButtonCancel(); break; case 'continue': this.doFreeTimeSearch(Date.parseDate(dtStopped, Date.patterns.ISO8601Long)); break; } } }); }, onStoreBeforeload: function() { if (this.loadMask) { this.loadMask.show(); } }, onStoreLoad: function() { this.store.each(function(event) { if (event.ui) { event.ui.setOpacity(0.5, 0); } }, this); this.store.add([this.record]); this.record.ui.setOpacity(1, 0); // @TODO check if event is visible and skip scrolling -> defered scrolling looks ugly // this.calendarView.scrollTo.defer(500, this.calendarView, [this.record.get('dtstart')]); this.loadMask.hide(); }, onViewSelectionChange: function(sm, selections) { this.detailsPanel.onDetailsUpdate(sm); }, onButtonBack: function() { this.onFreeTimeData(this.freeTimeSlots.pop()); this.buttonBack.setDisabled(!this.freeTimeSlots.length); }, onButtonNext: function() { this.freeTimeSlots.push(this.freeTimeSlot); this.doFreeTimeSearch(this.record.get('dtend')); this.buttonBack.enable(); }, onButtonOptions: function() { Tine.Calendar.EventFinderOptionsDialog.openWindow({ titleText: this.app.i18n._('Free Time Search Options'), stateId: this.optionsStateId, recordId: this.record.id, listeners: { scope: this, apply: this.onOptionsApply } }); }, onOptionsApply: function(dialog, options) { this.applyOptions(Ext.decode(options)); }, onButtonCancel: function() { this.fireEvent('cancel'); this.purgeListeners(); this.window.close(); }, onButtonApply: function() { this.fireEvent('apply', this, Ext.encode(this.record.data)); this.purgeListeners(); this.window.close(); } }); /** * Opens a new free time serach dialog window * * @return {Ext.ux.Window} */ Tine.Calendar.FreeTimeSearchDialog.openWindow = function (config) { var _ = window.lodash; if (! _.isString(config.record)) { config.record = Ext.encode(config.record.data); } return Tine.WindowFactory.getWindow({ width: 1024, height: 768, name: Tine.Calendar.FreeTimeSearchDialog.prototype.windowNamePrefix + _.get(config, 'record.id', 0), contentPanelConstructor: 'Tine.Calendar.FreeTimeSearchDialog', contentPanelConstructorConfig: config }); };