summaryrefslogtreecommitdiff
path: root/addons/calendar/static/src/js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/calendar/static/src/js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/calendar/static/src/js')
-rw-r--r--addons/calendar/static/src/js/base_calendar.js164
-rw-r--r--addons/calendar/static/src/js/calendar_controller.js65
-rw-r--r--addons/calendar/static/src/js/calendar_model.js21
-rw-r--r--addons/calendar/static/src/js/calendar_renderer.js118
-rw-r--r--addons/calendar/static/src/js/calendar_view.js22
-rw-r--r--addons/calendar/static/src/js/mail_activity.js60
-rw-r--r--addons/calendar/static/src/js/systray_activity_menu.js56
7 files changed, 506 insertions, 0 deletions
diff --git a/addons/calendar/static/src/js/base_calendar.js b/addons/calendar/static/src/js/base_calendar.js
new file mode 100644
index 00000000..f2c1e56b
--- /dev/null
+++ b/addons/calendar/static/src/js/base_calendar.js
@@ -0,0 +1,164 @@
+odoo.define('base_calendar.base_calendar', function (require) {
+"use strict";
+
+var BasicModel = require('web.BasicModel');
+var fieldRegistry = require('web.field_registry');
+var Notification = require('web.Notification');
+var relationalFields = require('web.relational_fields');
+var session = require('web.session');
+var WebClient = require('web.WebClient');
+
+var FieldMany2ManyTags = relationalFields.FieldMany2ManyTags;
+
+var CalendarNotification = Notification.extend({
+ template: "CalendarNotification",
+ xmlDependencies: (Notification.prototype.xmlDependencies || [])
+ .concat(['/calendar/static/src/xml/notification_calendar.xml']),
+
+ init: function(parent, params) {
+ this._super(parent, params);
+ this.eid = params.eventID;
+ this.sticky = true;
+
+ this.events = _.extend(this.events || {}, {
+ 'click .link2event': function() {
+ var self = this;
+
+ this._rpc({
+ route: '/web/action/load',
+ params: {
+ action_id: 'calendar.action_calendar_event_notify',
+ },
+ })
+ .then(function(r) {
+ r.res_id = self.eid;
+ return self.do_action(r);
+ });
+ },
+
+ 'click .link2recall': function() {
+ this.close();
+ },
+
+ 'click .link2showed': function() {
+ this._rpc({route: '/calendar/notify_ack'})
+ .then(this.close.bind(this, false), this.close.bind(this, false));
+ },
+ });
+ },
+});
+
+WebClient.include({
+ display_calendar_notif: function(notifications) {
+ var self = this;
+ var last_notif_timer = 0;
+
+ // Clear previously set timeouts and destroy currently displayed calendar notifications
+ clearTimeout(this.get_next_calendar_notif_timeout);
+ _.each(this.calendar_notif_timeouts, clearTimeout);
+ this.calendar_notif_timeouts = {};
+
+ // For each notification, set a timeout to display it
+ _.each(notifications, function(notif) {
+ var key = notif.event_id + ',' + notif.alarm_id;
+ if (key in self.calendar_notif) {
+ return;
+ }
+ self.calendar_notif_timeouts[key] = setTimeout(function () {
+ var notificationID = self.call('notification', 'notify', {
+ Notification: CalendarNotification,
+ title: notif.title,
+ message: notif.message,
+ eventID: notif.event_id,
+ onClose: function () {
+ delete self.calendar_notif[key];
+ },
+ });
+ self.calendar_notif[key] = notificationID;
+ }, notif.timer * 1000);
+ last_notif_timer = Math.max(last_notif_timer, notif.timer);
+ });
+
+ // Set a timeout to get the next notifications when the last one has been displayed
+ if (last_notif_timer > 0) {
+ this.get_next_calendar_notif_timeout = setTimeout(this.get_next_calendar_notif.bind(this), last_notif_timer * 1000);
+ }
+ },
+ get_next_calendar_notif: function() {
+ session.rpc("/calendar/notify", {}, {shadow: true})
+ .then(this.display_calendar_notif.bind(this))
+ .guardedCatch(function(reason) { //
+ var err = reason.message;
+ var ev = reason.event;
+ if(err.code === -32098) {
+ // Prevent the CrashManager to display an error
+ // in case of an xhr error not due to a server error
+ ev.preventDefault();
+ }
+ });
+ },
+ show_application: function() {
+ // An event is triggered on the bus each time a calendar event with alarm
+ // in which the current user is involved is created, edited or deleted
+ this.calendar_notif_timeouts = {};
+ this.calendar_notif = {};
+ this.call('bus_service', 'onNotification', this, function (notifications) {
+ _.each(notifications, (function (notification) {
+ if (notification[0][1] === 'calendar.alarm') {
+ this.display_calendar_notif(notification[1]);
+ }
+ }).bind(this));
+ });
+ return this._super.apply(this, arguments).then(this.get_next_calendar_notif.bind(this));
+ },
+});
+
+BasicModel.include({
+ /**
+ * @private
+ * @param {Object} record
+ * @param {string} fieldName
+ * @returns {Promise}
+ */
+ _fetchSpecialAttendeeStatus: function (record, fieldName) {
+ var context = record.getContext({fieldName: fieldName});
+ var attendeeIDs = record.data[fieldName] ? this.localData[record.data[fieldName]].res_ids : [];
+ var meetingID = _.isNumber(record.res_id) ? record.res_id : false;
+ return this._rpc({
+ model: 'res.partner',
+ method: 'get_attendee_detail',
+ args: [attendeeIDs, meetingID],
+ context: context,
+ }).then(function (result) {
+ return _.map(result, function (d) {
+ return _.object(['id', 'display_name', 'status', 'color'], d);
+ });
+ });
+ },
+});
+
+var Many2ManyAttendee = FieldMany2ManyTags.extend({
+ // as this widget is model dependant (rpc on res.partner), use it in another
+ // context probably won't work
+ // supportedFieldTypes: ['many2many'],
+ tag_template: "Many2ManyAttendeeTag",
+ specialData: "_fetchSpecialAttendeeStatus",
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @private
+ */
+ _getRenderTagsContext: function () {
+ var result = this._super.apply(this, arguments);
+ result.attendeesData = this.record.specialData.partner_ids;
+ return result;
+ },
+});
+
+fieldRegistry.add('many2manyattendee', Many2ManyAttendee);
+
+});
diff --git a/addons/calendar/static/src/js/calendar_controller.js b/addons/calendar/static/src/js/calendar_controller.js
new file mode 100644
index 00000000..190125ac
--- /dev/null
+++ b/addons/calendar/static/src/js/calendar_controller.js
@@ -0,0 +1,65 @@
+odoo.define('calendar.CalendarController', function (require) {
+ "use strict";
+
+ const Controller = require('web.CalendarController');
+ const Dialog = require('web.Dialog');
+ const { qweb, _t } = require('web.core');
+
+ const CalendarController = Controller.extend({
+
+ _askRecurrenceUpdatePolicy() {
+ return new Promise((resolve, reject) => {
+ new Dialog(this, {
+ title: _t('Edit Recurrent event'),
+ size: 'small',
+ $content: $(qweb.render('calendar.RecurrentEventUpdate')),
+ buttons: [{
+ text: _t('Confirm'),
+ classes: 'btn-primary',
+ close: true,
+ click: function () {
+ resolve(this.$('input:checked').val());
+ },
+ }],
+ }).open();
+ });
+ },
+
+ // TODO factorize duplicated code
+ /**
+ * @override
+ * @private
+ * @param {OdooEvent} event
+ */
+ async _onDropRecord(event) {
+ const _super = this._super; // reference to this._super is lost after async call
+ if (event.data.record.recurrency) {
+ const recurrenceUpdate = await this._askRecurrenceUpdatePolicy();
+ event.data = _.extend({}, event.data, {
+ 'recurrenceUpdate': recurrenceUpdate,
+ });
+ }
+ _super.apply(this, arguments);
+ },
+
+ /**
+ * @override
+ * @private
+ * @param {OdooEvent} event
+ */
+ async _onUpdateRecord(event) {
+ const _super = this._super; // reference to this._super is lost after async call
+ if (event.data.record.recurrency) {
+ const recurrenceUpdate = await this._askRecurrenceUpdatePolicy();
+ event.data = _.extend({}, event.data, {
+ 'recurrenceUpdate': recurrenceUpdate,
+ });
+ }
+ _super.apply(this, arguments);
+ },
+
+ });
+
+ return CalendarController;
+
+});
diff --git a/addons/calendar/static/src/js/calendar_model.js b/addons/calendar/static/src/js/calendar_model.js
new file mode 100644
index 00000000..8d90b543
--- /dev/null
+++ b/addons/calendar/static/src/js/calendar_model.js
@@ -0,0 +1,21 @@
+odoo.define('calendar.CalendarModel', function (require) {
+ "use strict";
+
+ const Model = require('web.CalendarModel');
+
+ const CalendarModel = Model.extend({
+
+ /**
+ * @override
+ * Transform fullcalendar event object to odoo Data object
+ */
+ calendarEventToRecord(event) {
+ const data = this._super(event);
+ return _.extend({}, data, {
+ 'recurrence_update': event.recurrenceUpdate,
+ });
+ }
+ });
+
+ return CalendarModel;
+});
diff --git a/addons/calendar/static/src/js/calendar_renderer.js b/addons/calendar/static/src/js/calendar_renderer.js
new file mode 100644
index 00000000..6aa97a40
--- /dev/null
+++ b/addons/calendar/static/src/js/calendar_renderer.js
@@ -0,0 +1,118 @@
+odoo.define('calendar.CalendarRenderer', function (require) {
+"use strict";
+
+const CalendarRenderer = require('web.CalendarRenderer');
+const CalendarPopover = require('web.CalendarPopover');
+const session = require('web.session');
+
+
+const AttendeeCalendarPopover = CalendarPopover.extend({
+ template: 'Calendar.attendee.status.popover',
+ events: _.extend({}, CalendarPopover.prototype.events, {
+ 'click .o-calendar-attendee-status .dropdown-item': '_onClickAttendeeStatus'
+ }),
+ /**
+ * @constructor
+ */
+ init: function () {
+ var self = this;
+ this._super.apply(this, arguments);
+ // Show status dropdown if user is in attendees list
+ if (this.isCurrentPartnerAttendee()) {
+ this.statusColors = {accepted: 'text-success', declined: 'text-danger', tentative: 'text-muted', needsAction: 'text-dark'};
+ this.statusInfo = {};
+ _.each(this.fields.attendee_status.selection, function (selection) {
+ self.statusInfo[selection[0]] = {text: selection[1], color: self.statusColors[selection[0]]};
+ });
+ this.selectedStatusInfo = this.statusInfo[this.event.extendedProps.record.attendee_status];
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @return {boolean}
+ */
+ isCurrentPartnerAttendee() {
+ return this.event.extendedProps.record.partner_ids.includes(session.partner_id);
+ },
+ /**
+ * @override
+ * @return {boolean}
+ */
+ isEventDeletable() {
+ return this._super() && (this._isEventPrivate() ? this.isCurrentPartnerAttendee() : true);
+ },
+ /**
+ * @override
+ * @return {boolean}
+ */
+ isEventDetailsVisible() {
+ return this._isEventPrivate() ? this.isCurrentPartnerAttendee() : this._super();
+ },
+ /**
+ * @override
+ * @return {boolean}
+ */
+ isEventEditable() {
+ return this._isEventPrivate() ? this.isCurrentPartnerAttendee() : this._super();
+ },
+ /**
+ * @return {boolean}
+ */
+ displayAttendeeAnswerChoice() {
+ // check if we are a partner and if we are the only attendee.
+ // This avoid to display attendee anwser dropdown for single user attendees
+ const isCurrentpartner = (currentValue) => currentValue === session.partner_id;
+ const onlyAttendee = this.event.extendedProps.record.partner_ids.every(isCurrentpartner);
+ return this.isCurrentPartnerAttendee() && ! onlyAttendee;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @return {boolean}
+ */
+ _isEventPrivate() {
+ return this.event.extendedProps.record.privacy === 'private';
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickAttendeeStatus: function (ev) {
+ ev.preventDefault();
+ var self = this;
+ var selectedStatus = $(ev.currentTarget).attr('data-action');
+ this._rpc({
+ model: 'calendar.event',
+ method: 'change_attendee_status',
+ args: [parseInt(this.event.id), selectedStatus],
+ }).then(function () {
+ self.event.extendedProps.record.attendee_status = selectedStatus; // FIXEME: Maybe we have to reload view
+ self.$('.o-calendar-attendee-status-text').text(self.statusInfo[selectedStatus].text);
+ self.$('.o-calendar-attendee-status-icon').removeClass(_.values(self.statusColors).join(' ')).addClass(self.statusInfo[selectedStatus].color);
+ });
+ },
+});
+
+
+const AttendeeCalendarRenderer = CalendarRenderer.extend({
+ config: _.extend({}, CalendarRenderer.prototype.config, {
+ CalendarPopover: AttendeeCalendarPopover,
+ }),
+});
+
+return AttendeeCalendarRenderer
+
+});
diff --git a/addons/calendar/static/src/js/calendar_view.js b/addons/calendar/static/src/js/calendar_view.js
new file mode 100644
index 00000000..4454dc0e
--- /dev/null
+++ b/addons/calendar/static/src/js/calendar_view.js
@@ -0,0 +1,22 @@
+odoo.define('calendar.CalendarView', function (require) {
+"use strict";
+
+var CalendarController = require('calendar.CalendarController');
+var CalendarModel = require('calendar.CalendarModel');
+const CalendarRenderer = require('calendar.CalendarRenderer');
+var CalendarView = require('web.CalendarView');
+var viewRegistry = require('web.view_registry');
+
+var AttendeeCalendarView = CalendarView.extend({
+ config: _.extend({}, CalendarView.prototype.config, {
+ Renderer: CalendarRenderer,
+ Controller: CalendarController,
+ Model: CalendarModel,
+ }),
+});
+
+viewRegistry.add('attendee_calendar', AttendeeCalendarView);
+
+return AttendeeCalendarView
+
+});
diff --git a/addons/calendar/static/src/js/mail_activity.js b/addons/calendar/static/src/js/mail_activity.js
new file mode 100644
index 00000000..3c478921
--- /dev/null
+++ b/addons/calendar/static/src/js/mail_activity.js
@@ -0,0 +1,60 @@
+odoo.define('calendar.Activity', function (require) {
+"use strict";
+
+var Activity = require('mail.Activity');
+var Dialog = require('web.Dialog');
+var core = require('web.core');
+var _t = core._t;
+
+Activity.include({
+
+ /**
+ * Override behavior to redirect to calendar event instead of activity
+ *
+ * @override
+ */
+ _onEditActivity: function (event, options) {
+ var self = this;
+ var activity_id = $(event.currentTarget).data('activity-id');
+ var activity = _.find(this.activities, function (act) { return act.id === activity_id; });
+ if (activity && activity.activity_category === 'meeting' && activity.calendar_event_id) {
+ return self._super(event, _.extend({
+ res_model: 'calendar.event',
+ res_id: activity.calendar_event_id[0],
+ }));
+ }
+ return self._super(event, options);
+ },
+
+ /**
+ * Override behavior to warn that the calendar event is about to be removed as well
+ *
+ * @override
+ */
+ _onUnlinkActivity: function (event, options) {
+ event.preventDefault();
+ var self = this;
+ var activity_id = $(event.currentTarget).data('activity-id');
+ var activity = _.find(this.activities, function (act) { return act.id === activity_id; });
+ if (activity && activity.activity_category === 'meeting' && activity.calendar_event_id) {
+ Dialog.confirm(
+ self,
+ _t("The activity is linked to a meeting. Deleting it will remove the meeting as well. Do you want to proceed ?"), {
+ confirm_callback: function () {
+ return self._rpc({
+ model: 'mail.activity',
+ method: 'unlink_w_meeting',
+ args: [[activity_id]],
+ })
+ .then(self._reload.bind(self, {activity: true}));
+ },
+ }
+ );
+ }
+ else {
+ return self._super(event, options);
+ }
+ },
+});
+
+});
diff --git a/addons/calendar/static/src/js/systray_activity_menu.js b/addons/calendar/static/src/js/systray_activity_menu.js
new file mode 100644
index 00000000..d6f98b69
--- /dev/null
+++ b/addons/calendar/static/src/js/systray_activity_menu.js
@@ -0,0 +1,56 @@
+odoo.define('calendar.systray.ActivityMenu', function (require) {
+"use strict";
+
+var ActivityMenu = require('mail.systray.ActivityMenu');
+var fieldUtils = require('web.field_utils');
+
+ActivityMenu.include({
+
+ //-----------------------------------------
+ // Private
+ //-----------------------------------------
+
+ /**
+ * parse date to server value
+ *
+ * @private
+ * @override
+ */
+ _getActivityData: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ var meeting = _.find(self._activities, {type: 'meeting'});
+ if (meeting && meeting.meetings) {
+ _.each(meeting.meetings, function (res) {
+ res.start = fieldUtils.parse.datetime(res.start, false, {isUTC: true});
+ });
+ }
+ });
+ },
+
+ //-----------------------------------------
+ // Handlers
+ //-----------------------------------------
+
+ /**
+ * @private
+ * @override
+ */
+ _onActivityFilterClick: function (ev) {
+ var $el = $(ev.currentTarget);
+ var data = _.extend({}, $el.data());
+ if (data.res_model === "calendar.event" && data.filter === "my") {
+ this.do_action('calendar.action_calendar_event', {
+ additional_context: {
+ default_mode: 'day',
+ search_default_mymeetings: 1,
+ },
+ clear_breadcrumbs: true,
+ });
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+});
+
+});