Ctrl K
ComunidadGuías…

tine20/Calendar/js/EventEditDialog.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
 * Tine 2.0
 * 
 * @package     Calendar
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */
 
import OrganizerCombo from "./OrganizerCombo";
import formatAddress from "util/postalAddressFormater";

Ext.ns('Tine.Calendar');

import FieldTriggerPlugin from "Tinebase/js/ux/form/FieldTriggerPlugin"

require('./Printer/EventRecord');
require('./FreeTimeSearchDialog');
require('./PollPanel');

/**
 * @namespace Tine.Calendar
 * @class Tine.Calendar.EventEditDialog
 * @extends Tine.widgets.dialog.EditDialog
 * Calendar Edit Dialog <br>
 * 
 * @author      Cornelius Weiss <c.weiss@metaways.de>
 */
Tine.Calendar.EventEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {

    labelAlign: 'side',
    windowNamePrefix: 'CalEventEditWindow_',
    appName: 'Calendar',
    recordClass: Tine.Calendar.Model.Event,
    showContainerSelector: false,
    displayNotes: true,
    requiredSaveGrant: '',

    mode: 'local',

    /**
     * @cfg {Number} tabsMinHeight
     * (minimum) height of the attendee/rrule/alarm/poll tabs, the attendee grid grows with its rows
     */
    tabsMinHeight: 210,

    saveEvent: function(record, options, additionalArguments) {
        // NOTE: only mainscreen can handle busyConflicts
        additionalArguments.checkBusyConflicts = 0;

        return Tine.Calendar.Model.EventJsonBackend.prototype.saveRecord.apply(this.recordProxy, arguments);
        //
        // var cp = this.app.getMainScreen().getCenterPanel(),
        //     activePanel = cp.getCalendarPanel(cp.activeView),
        //     activeView = activePanel.getView();
        //
        // activeView.showEvent(record)
        //     .then(function() {
        //         // save via mainscreen to show fbExceptions and recurring decistions
        //         // but we would need to foreground the window and this is not possible for the browser
        //     });
    },

    /**
     * returns dialog
     * 
     * NOTE: when this method gets called, all initalisation is done.
     * @return {Object} components this.itmes definition
     */
    getFormItems: function() {
        var timeIncrement = parseInt(this.app.getRegistry().peek('preferences').get('timeIncrement'));

        return {
            xtype: 'tabpanel',
            plugins: [{
                ptype: 'ux.itemregistry',
                key: 'Calendar-Event-EditDialog-TabPanel'
            }, {
                ptype : 'ux.tabpanelkeyplugin'
            }],
            defaults: {
                hideMode: 'offsets'
            },
            plain:true,
            activeTab: 0,
            border: false,
            items:[{
                title: this.app.i18n.n_('Event', 'Events', 1),
                border: false,
                frame: true,
                layout: 'border',
                layoutConfig: {
                    enableResponsive: true,
                    stackEastLevel: 2,
                    responsiveBreakpointOverrides: [{level: 2, width: 700}]
                },
                items: [{
                    region: 'center',
                    layout: 'hfit',
                    border: false,
                    items: [{
                        layout: 'hbox',
                        style: 'margin-bottom: 4px;',
                        layoutConfig: {
                            enableResponsive: false,
                        },
                        items: [{
                            margins: '5 5 5 11',
                            width: 100,
                            xtype: 'label',
                            text: this.app.i18n._('Summary')
                        }, {
                            flex: 1,
                            xtype:'textfield',
                            name: 'summary',
                            listeners: {render: function(field){field.focus(false, 250);}},
                            allowBlank: false,
                            requiredGrant: 'editGrant',
                            maxLength: 1024
                        }]
                    }, {
                        layout: 'hbox',
                        style: 'margin-bottom: 4px;',
                        layoutConfig: {
                            enableResponsive: false,
                        },
                            hidden: !this.app.featureEnabled('featureEventType'),
                        items: [{
                            margins: '5 5 5 11',
                            width: 100,
                            // height: 30,
                            xtype: 'label',
                            text: this.app.i18n._('Event Types')
                        }, {
                            flex: 1,
                            xtype:'tinerecordspickercombobox',
                            name: 'event_types',
                            recordClass: 'Calendar.EventTypes',
                            refIdField: 'record',
                            searchComboConfig: {useEditPlugin: false},
                            editDialogConfig: {mode: 'local'},
                            isMetadataModelFor: 'event_type',
                            requiredGrant: 'editGrant',
                        }]
                    }, {
                                layout: 'hbox',
                        style: 'margin-bottom: 4px;',
                        layoutConfig: {
                            enableResponsive: false,
                        },
                                hidden: !Tine.Tinebase.featureEnabled('featureSite'),
                                items: [{
                                    margins: '5 5 5 11',
                                    width: 100,
                                    // height: 30,
                                    xtype: 'label',
                                    text: this.app.i18n._('Site')
                                }, {
                                    flex: 1,
                                    xtype:'addressbookcontactpicker',
                                    name: 'event_site',
                                    userOnly: false,
                                    useAccountRecord: false,
                                    searchComboConfig: {useEditPlugin: false},
                                    requiredGrant: 'editGrant',
                                    recordEditPluginConfig: {allowCreateNew: false},
                                    emptyText: this.app.i18n._('Search for sites...'),
                                    additionalFilterSpec: {
                                        config: {
                                            name: 'siteFilter',
                                            appName: 'Tinebase'
                                        }
                                    }
                                }]
                    }, {
                        layout: 'hbox',
                        style: 'margin-bottom: 4px;',
                        layoutConfig: {
                            enableResponsive: false,
                        },
                        items: [{
                            margins: '5 5 5 11',
                            width: 100,
                            xtype: 'label',
                            text: this.app.i18n._('View')
                        }, Ext.apply(this.perspectiveCombo, {
                            flex: 1
                        })]
                    }, {
                        layout: 'hbox',
                        autoHeight: true,
                        style: 'margin: 5px 0',
                        layoutConfig: {
                            align : 'stretchmax',
                            pack  : 'start'
                        },
                        items: [{
                            flex: 1,
                            xtype: 'fieldset',
                            margins: '0 5 0 0',
                            title: this.app.i18n._('Details'),
                            items: [{
                                xtype: 'columnform',
                                columnLayoutConfig: {
                                    enableResponsive: false, // as fields usable also in small devices
                                },
                                labelAlign: 'side',
                                labelWidth: 100,
                                formDefaults: {
                                    xtype:'textfield',
                                    anchor: '100%',
                                    labelSeparator: '',
                                    columnWidth: .7
                                },
                                items: [[{
                                        xtype:'label',
                                        width: 105,
                                        text: this.app.i18n._('Event Location'),
                                    }, {
                                        columnWidth: 1/3,
                                        hideLabel: true,
                                        name: 'location_record',
                                        requiredGrant: 'editGrant',
                                        allowBlank: true,
                                        xtype: 'addressbookcontactpicker',
                                        userOnly: false,
                                        useAccountRecord: false,
                                        blurOnSelect: true,
                                        selectOnFocus: true,
                                        readOnly: false,
                                        maxLength: 1024,
                                        recordEditPluginConfig: {allowCreateNew: false,},
                                        listeners: {
                                            scope: this,
                                            'select': async function (combo, rec) {
                                                const aStruct = await formatAddress(rec.getPreferredAddressObject());
                                                // @TODO - have some fallback for old solution?
                                                // this.form.findField('location').setValue(rec.get('n_fn'));
                                                this.form.findField('location').setValue(aStruct.join('\n'));
                                            }
                                        }
                                    }, {
                                        columnWidth: 2/3,
                                        hideLabel: true,
                                        name: 'location',
                                        xtype: 'textarea',
                                        grow: true,
                                        growMin: 18,
                                        growAppend: '',
                                        requiredGrant: 'editGrant',
                                        maxLength: 255
                                }], [{
                                    xtype: 'datetimefield',
                                    fieldLabel: this.app.i18n._('Start Time'),
                                    listeners: {scope: this, change: this.onDtStartChange},
                                    name: 'dtstart',
                                    allowBlank: false,
                                    increment: timeIncrement,
                                    requiredGrant: 'editGrant'
                                }, {
                                    columnWidth: .3,
                                    xtype: 'checkbox',
                                    hideLabel: true,
                                    boxLabel: this.app.i18n._('whole day'),
                                    listeners: {scope: this, check: this.onAllDayChange},
                                    name: 'is_all_day_event',
                                    requiredGrant: 'editGrant'
                                }], [{
                                    xtype: 'datetimefield',
                                    fieldLabel: this.app.i18n._('End Time'),
                                    listeners: {scope: this, change: this.onDtEndChange},
                                    name: 'dtend',
                                    allowBlank: false,
                                    increment: timeIncrement,
                                    requiredGrant: 'editGrant'
                                }, {
                                    columnWidth: .3,
                                    xtype: 'combo',
                                    hideLabel: true,
                                    readOnly: true,
                                    hideTrigger: true,
                                    disabled: true,
                                    name: 'originator_tz',
                                    requiredGrant: 'editGrant'
                                }], [ this.containerSelectCombo = new Tine.widgets.container.SelectionComboBox({
                                    columnWidth: 1,
                                    id: this.app.appName + 'EditDialogContainerSelector' + Ext.id(),
                                    fieldLabel: i18n._('Saved in'),
                                    ref: '../../../../../../../../containerSelect',
                                    //width: 300,
                                    //listWidth: 300,
                                    name: this.recordClass.getMeta('containerProperty'),
                                    treePanelClass: Tine.Calendar.TreePanel,
                                    recordClass: this.recordClass,
                                    containerName: this.app.i18n.n_hidden(this.recordClass.getMeta('containerName'), this.recordClass.getMeta('containersName'), 1),
                                    containersName: this.app.i18n._hidden(this.recordClass.getMeta('containersName')),
                                    appName: this.app.appName,
                                    requiredGrant: 'readGrant',
                                    requiredGrants: ['addGrant'],
                                    disabled: true
                                }), Ext.apply(this.perspectiveCombo.getAttendeeContainerField(), {
                                    columnWidth: 1
                                })]]
                            }]
                        }, {
                            width: 130,
                            xtype: 'fieldset',
                            title: this.app.i18n._('Status'),
                            items: [{
                                xtype: 'widget-keyfieldcombo',
                                app:   'Calendar',
                                keyFieldName: 'eventStatus',
                                anchor: '100%',
                                hideLabel: true,
                                value: 'CONFIRMED',
                                name: 'status',
                                requiredGrant: 'editGrant',
                                listeners: {
                                    beforeselect: (combo, status, index) => {
                                        Ext.MessageBox.confirm(
                                            this.app.i18n._('Update status for all attendees?'),
                                            this.app.i18n._('You are about to change the status of the event itself, not just your own. Do you really want to change the event status for all attendees?'), function (btn) {
                                                if (btn === 'yes') {
                                                    combo.setValue(status.id);
                                                }
                                            }, this);
                                        return false;
                                    }
                                }
                            }, {
                                xtype: 'checkbox',
                                hideLabel: true,
                                boxLabel: this.app.i18n._('non-blocking'),
                                name: 'transp',
                                requiredGrant: 'editGrant',
                                getValue: function() {
                                    var bool = Ext.form.Checkbox.prototype.getValue.call(this);
                                    return bool ? 'TRANSPARENT' : 'OPAQUE';
                                },
                                setValue: function(value) {
                                    var bool = (value == 'TRANSPARENT' || value === true);
                                    return Ext.form.Checkbox.prototype.setValue.call(this, bool);
                                }
                            }, Ext.apply(this.perspectiveCombo.getAttendeeTranspField(), {
                                hideLabel: true
                            }), {
                                xtype: 'checkbox',
                                hideLabel: true,
                                boxLabel: this.app.i18n._('Private'),
                                name: 'class',
                                requiredGrant: 'editGrant',
                                getValue: function() {
                                    var bool = Ext.form.Checkbox.prototype.getValue.call(this);
                                    return bool ? 'PRIVATE' : 'PUBLIC';
                                },
                                setValue: function(value) {
                                    var bool = (value == 'PRIVATE' || value === true);
                                    return Ext.form.Checkbox.prototype.setValue.call(this, bool);
                                }
                            }, Ext.apply(this.perspectiveCombo.getAttendeeStatusField(), {
                                anchor: '100%',
                                hideLabel: true
                            })]
                        }]
                    }, {
                        xtype: 'tabpanel',
                        deferredRender: false,
                        activeTab: 0,
                        border: false,
                        // grows with the active tab (attendee rows), the center region scrolls
                        autoHeight: true,
                        form: true,
                        items: [
                            this.attendeeGridPanel,
                            this.rrulePanel,
                            this.alarmPanel
                        ].concat(Tine.Tinebase.appMgr.get('Calendar').featureEnabled('featurePolls') ? this.pollPanel : [])
                    }]
                }, {
                    // activities and tags
                    region: 'east',
                    layout: 'ux.multiaccordion',
                    animate: true,
                    width: 200,
                    split: true,
                    collapsible: true,
                    collapseMode: 'mini',
                    header: false,
                    margins: '0 5 0 5',
                    border: true,
                    plugins: [{
                        ptype: 'ux.itemregistry',
                        key:   this.app.appName + '-' + this.recordClass.prototype.modelName + '-editDialog-eastPanel'
                    }],
                    items: [
                        new Ext.Panel({
                            // @todo generalise!
                            title: this.app.i18n._('Description'),
                            iconCls: 'descriptionIcon',
                            // url field + description filling the rest (an anchored 100% height would ignore the url field)
                            layout: 'vbox',
                            layoutConfig: {
                                align: 'stretch'
                            },
                            border: false,
                            items: [{
                                xtype:'textfield',
                                name: 'url',
                                emptyText: this.app.i18n._('URL'),
                                requiredGrant: 'editGrant'
                            }, {
                                xtype:'textarea',
                                name: 'description',
                                flex: 1,
                                margins: '4 0 0 0',
                                grow: false,
                                preventScrollbars:false,
                                emptyText: this.app.i18n._('Enter a description'),
                                requiredGrant: 'editGrant'
                            }]
                        }),
                        new Tine.widgets.tags.TagPanel({
                            app: 'Calendar',
                            border: false,
                            // bodyStyle: 'border:1px solid #B5B8C8;'
                        })
                    ]
                }]
            }, new Tine.widgets.activities.ActivitiesTabPanel({
                app: this.appName,
                record_id: (this.record) ? this.record.id : '',
                record_model: this.appName + '_Model_' + this.recordClass.getMeta('modelName'),
                getRecordId: (function() {
                    return this.record.isRecurInstance() ? this.record.get('base_event_id') : this.record.get('id');
                }).createDelegate(this)
            })]
        };
    },

    onFreeTimeSearch: function() {
        this.onRecordUpdate();
        Tine.Calendar.FreeTimeSearchDialog.openWindow({
            record: this.record,
            listeners: {
                scope: this,
                apply: this.onFreeTimeSearchApply
            }
        });
    },

    onFreeTimeSearchApply: function(dialog, recordData) {
        this.record = this.recordProxy.recordReader({responseText: recordData});
        this.onRecordLoad();
    },

    /**
     * mute first alert
     * 
     * @param {} button
     * @param {} e
     */
    onMuteNotificationOnce: function (button, e) {
        this.record.set('mute', button.pressed);
        button.setText(Tine.Tinebase.appMgr.get('Calendar').i18n._(button.pressed ?
            'Notifications are disabled' : 'Notifications are enabled'
        ));
    },

    onPrint: function(printMode) {
        this.onRecordUpdate();
        var renderer = new Tine.Calendar.Printer.EventRenderer();
        renderer.print(this);
    },

    initComponent: function() {
        this.app = Tine.Tinebase.appMgr.get('Calendar');

        this.addEvents(
            /**
             * @event dtStartChange
             * @desc  Fired when dtstart chages in UI
             * @param {Json String} oldValue, newValue
             */
            'dtStartChange'
        );

        this.recordProxy = new Tine.Calendar.Model.EventJsonBackend({
            saveRecord: this.saveEvent.createDelegate(this)
        });

        this.action_freeTimeSearch = new Ext.Action({
            text: Tine.Tinebase.appMgr.get('Calendar').i18n._('Free Time Search'),
            handler: this.onFreeTimeSearch,
            iconCls: 'cal-action_fretimesearch',
            disabled: false,
            scope: this
        });

        this.button_muteNotification =  new Ext.Button(new Ext.Action({
            text: Tine.Tinebase.appMgr.get('Calendar').i18n._('Notifications are enabled'),
            handler: this.onMuteNotificationOnce,
            iconCls: 'action_mute_noteification',
            disabled: false,
            scope: this,
            enableToggle: true,
        }));

        this.tbarItems = [new Ext.Button(this.action_freeTimeSearch), this.button_muteNotification
        , new Ext.Button(new Ext.Action({
            text: Tine.Tinebase.appMgr.get('Calendar').i18n._('Print Event'),
            handler: this.onPrint,
            iconCls:'action_print',
            disabled: false,
            scope: this
        }))];

        var organizerCombo;
        this.attendeeGridPanel = new Tine.Calendar.AttendeeGridPanel({
            editDialog: this,
            autoHeight: true,
            minHeight: this.tabsMinHeight,
            bbar: [{
                xtype: 'label',
                html: Tine.Tinebase.appMgr.get('Calendar').i18n._('Organizer') + "&nbsp;"
            }, organizerCombo = new OrganizerCombo({
                width: 300,
                forceSelection: true, // no selection of external organizers here yet
                onOrganizerSelect: (data) => {
                    _.each(data, (val, key) => { this.record.set(key, val) })
                }
            })]
        });
        
        // auto location
        this.attendeeGridPanel.on('afteredit', function(o) {
            if (o.field == 'user_id' && o.record.get('user_type') == 'resource' ) {
                var typeId = _.get(o.record, 'data.user_id.type'),
                    type = Tine.Tinebase.widgets.keyfield.StoreMgr.get('Calendar', 'resourceTypes').getById(typeId);

                if (type?.get('is_location')) {
                    this.setLocationRecord(o.record);
                }
            }

            this.checkStates();
        }, this);

        this.attendeeGridPanel.store.on('remove', function(store, record, idx) {
            if(_.get(record, 'data.user_id', false)){
                if (_.get(record, 'data.user_type') !== 'ressources') return;

                //remove location if location is location from deleted ressource
                var typeId = _.get(record, 'data.user_id.type'),
                    type = Tine.Tinebase.widgets.keyfield.StoreMgr.get('Calendar', 'resourceTypes').getById(typeId),
                    locationName = this.attendeeGridPanel.renderAttenderResourceName(record.get('user_id'), {noIcon: true}),
                    locationField = this.getForm().findField('location');

                if (type?.get('is_location') && locationName === locationField.getValue()) {
                    locationField.setValue('');
                }
            }
        }, this);
        
        this.on('render', function() {this.getForm().add(organizerCombo);}, this);

        this.pollPanel = new Tine.Calendar.PollPanel({
            editDialog : this
        });
        this.rrulePanel = new Tine.Calendar.RrulePanel({
            eventEditDialog : this,
            height: this.tabsMinHeight
        });
        this.alarmPanel = new Tine.widgets.dialog.AlarmPanel({
            height: this.tabsMinHeight
        });
        this.attendeeStore = this.attendeeGridPanel.getStore();
        this.attendeeStore.on('add', this.onAttendeeStoreChange, this);
        this.attendeeStore.on('clear', this.onAttendeeStoreChange, this);
        this.attendeeStore.on('load', this.onAttendeeStoreChange, this);
        this.attendeeStore.on('datachanged', this.onAttendeeStoreChange, this);
        this.attendeeStore.on('remove', this.onAttendeeStoreChange, this);

        // a combo with all attendee + origin/organizer
        this.perspectiveCombo = new Tine.Calendar.PerspectiveCombo({
            name: 'perspective',
            editDialog: this
        });

        this.initMessageBus();

        Tine.Calendar.EventEditDialog.superclass.initComponent.call(this);
        this.addAttendee();
        // Start with unmodified Record
        this.record.commit();
    },

    initMessageBus: function() {
        this.postalSubscriptions = [];
        this.postalSubscriptions.push(postal.subscribe({
            channel: "recordchange",
            topic: 'Calendar.Event.*',
            callback: this.onRecordChanges.createDelegate(this)
        }));
    },

    /**
     * bus notified about record changes
     */
    onRecordChanges: async function(data, e) {
        if (data?.[0] && this.recordId === data[0].recordId && data[0].verb === 'delete') {
            this.window.close(true);
        }
    },

    setLocationRecord: function(resource, overwrite = false) {
        const locationField = this.getForm().findField('location');
        const locationRecordField = this.getForm().findField('location_record');
        const siteField = this.getForm().findField('event_site');
        const locationAddress = _.get(resource, 'data.user_id.location_address');

        if (locationAddress &&(! locationField.getValue() || overwrite)) {
            locationField.setValue(locationAddress);
        }

        var relations = _.get(resource, 'data.user_id.relations'),
            locationId = relations ? _.findIndex(relations, function(k) { return k.type == 'LOCATION'; }) : -1,
            locationContact = locationId >= 0 ? relations[locationId].related_record : null,
            siteId = relations ? _.findIndex(relations, function (k) { return k.type == 'SITE'; }) : -1,
            siteContact = siteId >= 0 ? relations[siteId].related_record : null;

        if (locationContact && (!locationRecordField.getValue() || overwrite)) {
            locationRecordField.setValue(locationContact);
        } else if (siteContact && (!locationRecordField.getValue() || overwrite)) {
            locationRecordField.setValue(siteContact);
        }

        if (Tine.Tinebase.featureEnabled('featureSite') && siteContact && (!siteField.getValue() || overwrite)) {
            siteField.setValue(siteContact)
        }

        if (_.get(resource, 'data.user_id.location_address'&& (!locationRecordField.getValue() || overwrite))) {

        }
    },

    /**
     * if this addRelations is set, iterate and create attendee
     */
    addAttendee: function() {
        var attendee = this.record.get('attendee');
        var attendee = Ext.isArray(attendee) ? attendee : [];
        
        if (Ext.isArray(this.plugins)) {
            for (var index = 0; index < this.plugins.length; index++) {
                if (this.plugins[index].hasOwnProperty('addRelations')) {

                    var config = this.plugins[index].hasOwnProperty('relationConfig') ? this.plugins[index].relationConfig : {};
                    
                    for (var index2 = 0; index2 < this.plugins[index].addRelations.length; index2++) {
                        var item = this.plugins[index].addRelations[index2];
                        var attender = Ext.apply({
                            user_type: 'user',
                            role: 'REQ',
                            quantity: 1,
                            status: 'NEEDS-ACTION',
                            user_id: item
                        }, config);
                        
                        attendee.push(attender);
                    }
                }
            }
        }
        
        this.record.set('attendee', attendee);
    },
    
    /**
     * checks if form data is valid
     * 
     * @return {Boolean}
     */
    isValid: function() {
        var isValid = this.validateDtStart() && this.validateDtEnd();
        
        if (! this.rrulePanel.isValid()) {
            isValid = false;
            
            this.rrulePanel.ownerCt.setActiveTab(this.rrulePanel);
        }
        
        return isValid && Tine.Calendar.EventEditDialog.superclass.isValid.apply(this, arguments);
    },
     
    onAllDayChange: function(checkbox, isChecked) {
        var dtStartField = this.getForm().findField('dtstart');
        var dtEndField = this.getForm().findField('dtend');
        dtStartField.setDisabled(isChecked, 'time');
        dtEndField.setDisabled(isChecked, 'time');
        
        if (isChecked) {
            dtStartField.clearTime();
            var dtend = dtEndField.getValue();
            if (Ext.isDate(dtend) && dtend.format('H:i:s') != '23:59:59') {
                dtEndField.setValue(dtend.clearTime(true).add(Date.HOUR, 24).add(Date.SECOND, -1));
            }
            
        } else {
            dtStartField.undo();
            dtEndField.undo();
        }
    },
    
    onDtEndChange: function(dtEndField, newValue, oldValue) {
        this.validateDtEnd();
    },
    
    /**
     * on dt start change
     * 
     * @param {} dtStartField
     * @param {} newValue
     * @param {} oldValue
     */
    onDtStartChange: function(dtStartField, newValue, oldValue) {
        if (this.validateDtStart() == false) {
            return false;
        }
        
        if (Ext.isDate(newValue) && Ext.isDate(oldValue)) {
            var dtEndField = this.getForm().findField('dtend'),
                dtEnd = dtEndField.getValue();
                
            if (Ext.isDate(dtEnd)) {
                var duration = dtEnd.getTime() - oldValue.getTime(),
                    newDtEnd = newValue.add(Date.MILLI, duration);
                dtEndField.setValue(newDtEnd);
                this.validateDtEnd();
            }
        }

        this.fireEvent('dtStartChange', Ext.util.JSON.encode({newValue: newValue, oldValue: oldValue || newValue}));
    },
    
    /**
     * copy record
     * 
     * TODO change attender status?
     */
    doCopyRecord: function() {
        var _ = window.lodash;

        // Calendar is the only app with record based grants -> user gets edit grant for all fields when copying
        this.record.set('editGrant', true);

        // BUT: add Grant is container based!
        this.record.set('addGrant', _.get(this.record, 'data.container_id.account_grants.addGrant', false));

        if (this.record.get('status') == 'CANCELED') {
            this.record.set('status', 'CONFIRMED')
        }

        Tine.Calendar.EventEditDialog.superclass.doCopyRecord.call(this);

        const attendeeStore = Tine.Calendar.Model.Attender.getAttendeeStore(this.record.data.attendee);
        const ownAttendee = Tine.Calendar.Model.Attender.getAttendeeStore.getMyAttenderRecord(attendeeStore);
        const allAttendee = Tine.Tinebase.common.assertComparable([]);


        attendeeStore.each(attendee => {
            if(attendee !== ownAttendee) {
                attendee.set('status', 'NEEDS-ACTION');
            }

            allAttendee.push(Object.assign({... attendee.data}, {id: null}));
        });


        this.record.set('attendee', allAttendee);

        // remove event_types ids
        Ext.each(this.record.data.event_types, function(type) {
            delete type.id;
        }, this);


        Tine.log.debug('Tine.Calendar.EventEditDialog::doCopyRecord() -> record:');
        Tine.log.debug(this.record);
    },

    /**
     * executed after record got updated from proxy
     */
    onRecordLoad: function() {
        if (String(this.record.id).match(/new-ext-gen/)) {
            this.record.set('id', '');
        }

        this.omitCopyTitle = this.record.hasPoll();
        this.getForm().findField('summary').allowBlank = !this.record.get('editGrant');
        Tine.Calendar.EventEditDialog.superclass.onRecordLoad.call(this);
    },

    isNewRecord: function () {
        return !this.record || !(this.record.get && this.record.get('creation_time'))
    },

    /**
     * is called after all subpanels have been loaded
     */
    onAfterRecordLoad: function() {
        Tine.Calendar.EventEditDialog.superclass.onAfterRecordLoad.call(this);

        // disable relations panel for non persistent exceptions till we have the baseEventId
        if (this.record.isRecurInstance()) {
            this.relationsPanel.setDisabled(true);
        }
        this.attendeeGridPanel.onRecordLoad(this.record);
        this.rrulePanel.onRecordLoad(this.record);
        this.alarmPanel.onRecordLoad(this.record);

        this.perspectiveCombo.loadPerspective();
        // disable container selection combo if user has no right to edit
        this.containerSelect.setDisabled.defer(20, this.containerSelect, [(! this.record.get('editGrant'))]);
        
        // disable time selectors if this is a whole day event
        if (this.record.get('is_all_day_event')) {
            this.onAllDayChange(null, true);
        }
        this.button_muteNotification.pressed = !!+this.record.get('mute');
        this.button_muteNotification.setText(!!+this.record.get('mute') ?
            Tine.Tinebase.appMgr.get('Calendar').i18n._('Notifications are disabled') :
            Tine.Tinebase.appMgr.get('Calendar').i18n._('Notifications are enabled'));
    },

    /**
     * generic apply changes handler
     * @param {Boolean} closeWindow
     */
    onApplyChanges: function(closeWindow) {
        if (this.app.featureEnabled('featureEventNotificationConfirmation') && !+this.record.get('mute')) {
            Ext.MessageBox.confirm(
                this.app.i18n._('Send Notification?'),
                this.app.i18n._('Changes to this event may trigger notifications. Click the button labeled "Notifications are enabled" to switch to "Notifications are disabled".'),
                function (button) {
                    if (button === 'yes') {
                        Tine.Calendar.EventEditDialog.superclass.onApplyChanges.call(this,closeWindow);
                    }
                },
                this
            );
            return;
        } else {
            Tine.Calendar.EventEditDialog.superclass.onApplyChanges.call(this, closeWindow);
        }
    },

    onRecordUpdate: function() {
        Tine.Calendar.EventEditDialog.superclass.onRecordUpdate.apply(this, arguments);
        this.attendeeGridPanel.onRecordUpdate(this.record);
        this.rrulePanel.onRecordUpdate(this.record);
        this.alarmPanel.onRecordUpdate(this.record);
        this.perspectiveCombo.updatePerspective();
    },

    onAttendeeStoreChange: function() {
        // NOTE: mind the add new attendee row!
        this.action_freeTimeSearch.setDisabled(this.attendeeStore.getCount() < 2);
    },

    validateDtEnd: function() {
        var dtStart = this.getForm().findField('dtstart').getValue(),
            dtEndField = this.getForm().findField('dtend'),
            dtEnd = dtEndField.getValue(),
            endTime = this.adjustTimeToUserPreference(dtEndField.getValue(), 'daysviewendtime');
        
        if (! Ext.isDate(dtEnd)) {
            dtEndField.markInvalid(this.app.i18n._('The end date is not valid'));
            return false;
        } else if (Ext.isDate(dtStart) && dtEnd.getTime() - dtStart.getTime() <= 0) {
            dtEndField.markInvalid(this.app.i18n._('The end date must be after the start date'));
            return false;
        } else if (! Tine.Tinebase.configManager.get('daysviewallowallevents', 'Calendar')
                && this.getForm().findField('is_all_day_event').checked === false
                && !! Tine.Tinebase.configManager.get('daysviewcroptime', 'Calendar') && dtEnd > endTime)
        {
            dtEndField.markInvalid(this.app.i18n._('The end time cannot be later than the configured time.'));
            return false;
        } else {
            dtEndField.clearInvalid();
            return true;
        }
    },
    
    /**
     * adjusts given date (end/start) to user preference (hours)
     * 
     * @param {Date} dateValue
     * @param {String} prefKey
     * @return {Date}
     */
    adjustTimeToUserPreference: function(dateValue, prefKey) {
        let userPreferenceDate = dateValue;
        const prefs = this.app.getRegistry().peek('preferences');
        const hour = prefs.get(prefKey).split(':')[0];
        
        // adjust date to user preference
        userPreferenceDate.setHours(hour);
        userPreferenceDate.setMinutes(0);
        if (prefKey == 'daysviewendtime' && userPreferenceDate.format('H:i') == '00:00') {
            userPreferenceDate = userPreferenceDate.add(Date.MINUTE, -1);
        }
        
        return userPreferenceDate;
    },
    
    validateDtStart: function() {
        var dtStartField = this.getForm().findField('dtstart'),
            dtStart = dtStartField.getValue(),
            startTime = this.adjustTimeToUserPreference(dtStartField.getValue(), 'daysviewstarttime');
        
        if (! Ext.isDate(dtStart)) {
            dtStartField.markInvalid(this.app.i18n._('The start date is not valid'));
            return false;
        } else if (! Tine.Tinebase.configManager.get('daysviewallowallevents', 'Calendar')
                && this.getForm().findField('is_all_day_event').checked === false
                && !! Tine.Tinebase.configManager.get('daysviewcroptime', 'Calendar')
                && dtStart < startTime)
        {
            dtStartField.markInvalid(this.app.i18n._('The start date cannot be earlier than the configured time.'));
            return false;
        } else {
            dtStartField.clearInvalid();
            return true;
        }
    }
});

/**
 * Opens a new event edit dialog window
 * 
 * @return {Ext.ux.Window}
 */
Tine.Calendar.EventEditDialog.openWindow = function (config) {
    // record is JSON encoded here...
    var id = config.recordId ? config.recordId : 0;
    var window = Tine.WindowFactory.getWindow({
        width: 800,
        height: 505,
        name: Tine.Calendar.EventEditDialog.prototype.windowNamePrefix + id,
        contentPanelConstructor: 'Tine.Calendar.EventEditDialog',
        contentPanelConstructorConfig: config
    });
    return window;
};