summaryrefslogtreecommitdiff
path: root/addons/mail/static/src
diff options
context:
space:
mode:
authorXavierDo <xdo@odoo.com>2018-08-01 14:51:01 +0200
committerXavierDo <xdo@odoo.com>2018-08-10 15:20:37 +0200
commit1de9496f14a42d543ec9805dfc1d0e8df39017d2 (patch)
tree65ae75dd0295eeb2d151a4bb4e4c1457522967c4 /addons/mail/static/src
parent83fc5dc03ce92df9b920386f9215cdc5504c3be1 (diff)
[IMP] mail: add activity view
A simple new view to easily display all activity on a model grouped by res_id and activity_type_id. Some action are possible like sending a mail template for all record having an activity with this activity type and present in the view (depends on the filter). The view use the Kanban activity view on cells. It could be a good idea to rename this widget to something like "DropdownActivityView" (maybe after the freeze) Task: #1870662 PR: #26272
Diffstat (limited to 'addons/mail/static/src')
-rw-r--r--addons/mail/static/src/js/activity.js1
-rw-r--r--addons/mail/static/src/js/views/activity/activity_controller.js74
-rw-r--r--addons/mail/static/src/js/views/activity/activity_model.js69
-rw-r--r--addons/mail/static/src/js/views/activity/activity_renderer.js181
-rw-r--r--addons/mail/static/src/js/views/activity/activity_view.js29
-rw-r--r--addons/mail/static/src/scss/activity_view.scss54
-rw-r--r--addons/mail/static/src/scss/mail_activity.scss5
-rw-r--r--addons/mail/static/src/xml/activity_view.xml18
8 files changed, 430 insertions, 1 deletions
diff --git a/addons/mail/static/src/js/activity.js b/addons/mail/static/src/js/activity.js
index dbff89c76db..0a25fcfe194 100644
--- a/addons/mail/static/src/js/activity.js
+++ b/addons/mail/static/src/js/activity.js
@@ -146,6 +146,7 @@ var AbstractActivityField = AbstractField.extend({
context: this.record.getContext(),
}).then(
function(rslt_action) {
+ console.log(rslt_action);
self.do_action(rslt_action, {
on_close: function () {
self.trigger_up('reload');
diff --git a/addons/mail/static/src/js/views/activity/activity_controller.js b/addons/mail/static/src/js/views/activity/activity_controller.js
new file mode 100644
index 00000000000..6af4c310c01
--- /dev/null
+++ b/addons/mail/static/src/js/views/activity/activity_controller.js
@@ -0,0 +1,74 @@
+odoo.define('mail.ActivityController', function (require) {
+"use strict";
+
+var AbstractController = require('web.AbstractController');
+
+var ActivityController = AbstractController.extend({
+ custom_events: _.extend({}, AbstractController.prototype.custom_events, {
+ send_mail_template: '_onSendMailTemplate',
+ open_view_form: '_onOpenViewForm',
+ reload: '_onReload',
+ }),
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {OdooEvent} event
+ * @param {string} event.name
+ * @param {Object} event.data
+ * @param {boolean} [event.data.activity]
+ * @param {boolean} [event.data.followers]
+ * @param {boolean} [event.data.thread]
+ */
+ _onReload: function (event) {
+ event.stopPropagation();
+ var self = this;
+ this.model.reload().then(self.reload());
+ },
+
+ /**
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onSendMailTemplate: function (ev) {
+ var templateID = ev.data.templateID;
+ var activityTypeID = ev.data.activityTypeID;
+ var state = this.model.get();
+ var groupedActivities = state.data.grouped_activities;
+ var resIDS = [];
+ Object.keys(groupedActivities).forEach(function (resID) {
+ var activityByType = groupedActivities[resID];
+ var activity = activityByType[activityTypeID];
+ if (activity) {
+ resIDS.push(parseInt(resID));
+ }
+ });
+ this._rpc({
+ model: this.model.modelName,
+ method: 'activity_send_mail',
+ args: [resIDS, templateID],
+ });
+ },
+ /**
+ * @private
+ * @override
+ * @param {MouseEvent} ev
+ */
+ _onOpenViewForm: function (ev) {
+ var resID = ev.data.resID;
+ this.do_action({
+ type: 'ir.actions.act_window',
+ res_model: this.model.modelName,
+ res_id: resID,
+ views: [[false, 'form']],
+ target: 'current'
+ });
+ },
+});
+
+return ActivityController;
+
+});
diff --git a/addons/mail/static/src/js/views/activity/activity_model.js b/addons/mail/static/src/js/views/activity/activity_model.js
new file mode 100644
index 00000000000..146609bbb52
--- /dev/null
+++ b/addons/mail/static/src/js/views/activity/activity_model.js
@@ -0,0 +1,69 @@
+odoo.define('mail.ActivityModel', function (require) {
+'use strict';
+
+var AbstractModel = require('web.AbstractModel');
+
+var ActivityModel = AbstractModel.extend({
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ get: function () {
+ return this.data;
+ },
+ /**
+ * @override
+ * @param {Object} params
+ * @param {Array[]} params.domain
+ * @returns {Deferred}
+ */
+ load: function (params) {
+ this.domain = params.domain;
+ this.modelName = params.modelName;
+ this.data = {};
+ return this._fetchData();
+ },
+ /**
+ * @param {any} handle
+ * @param {Object} params
+ * @param {Array[]} params.domain
+ * @returns {Deferred}
+ */
+ reload: function (handle, params) {
+ if (params && 'domain' in params) {
+ this.domain = params.domain;
+ }
+ return this._fetchData();
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Fetch activity data.
+ *
+ * @private
+ * @returns {Deferred}
+ */
+ _fetchData: function () {
+ var self = this;
+ return this._rpc({
+ model: "mail.activity",
+ method: 'get_activity_data',
+ kwargs: {
+ res_model: this.modelName,
+ domain: this.domain,
+ }
+ }).then(function (result) {
+ self.data.data = result;
+ });
+ },
+});
+
+return ActivityModel;
+
+});
diff --git a/addons/mail/static/src/js/views/activity/activity_renderer.js b/addons/mail/static/src/js/views/activity/activity_renderer.js
new file mode 100644
index 00000000000..bba1cf86540
--- /dev/null
+++ b/addons/mail/static/src/js/views/activity/activity_renderer.js
@@ -0,0 +1,181 @@
+odoo.define('mail.ActivityRenderer', function (require) {
+"use strict";
+
+var AbstractRenderer = require('web.AbstractRenderer');
+var core = require('web.core');
+var field_registry = require('web.field_registry');
+
+var QWeb = core.qweb;
+var _t = core._t;
+
+var ActivityRenderer = AbstractRenderer.extend({
+ className: 'o_activity_view',
+ events: {
+ 'click .o_res_name_cell': '_onResNameClicked',
+ 'click .o_send_mail_template': '_onSenMailTemplateClicked',
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @private
+ */
+ _render: function () {
+ this.$el
+ .removeClass('table-responsive')
+ .empty();
+
+ if (this.state.data.activity_types.length === 0) {
+ this.$el.append($("<div>").text(_t("No schedulded activity")));
+ } else {
+ var $table = $('<table>')
+ .addClass('table-bordered')
+ .append(this._renderHeader())
+ .append(this._renderBody());
+ this.$el
+ .addClass('table-responsive')
+ .append($table);
+ }
+ return this._super();
+ },
+ /**
+ * @private
+ * @returns {jQueryElement} a jquery element <tbody>
+ */
+ _renderBody: function () {
+ var $rows = _.map(this.state.data.res_ids, this._renderRow.bind(this));
+ return $('<tbody>').append($rows);
+ },
+ /**
+ * @private
+ * @returns {jQueryElement} a jquery element <thead>
+ */
+ _renderHeader: function () {
+ var $tr = $('<tr>')
+ .append($('<th>')) //empty cell for name
+ .append(_.map(this.state.data.activity_types, this._renderHeaderCell.bind(this)));
+ return $('<thead>').append($tr);
+ },
+ /**
+ * @private
+ * @param {Object} activity_type
+ * @returns {jQueryElement} a <th> element
+ */
+ _renderHeaderCell: function (activity_type) {
+ return QWeb.render('mail.ActivityViewHeaderCell', {
+ id: activity_type[0],
+ name: activity_type[1],
+ template_list: activity_type[2] || [],
+ });
+ },
+ /**
+ * @private
+ * @param {Object} data
+ * @returns {jQueryElement} a <tr> element
+ */
+ _renderRow: function (data) {
+ var self = this;
+ var res_id = data[0];
+ var name = data[1];
+ var $nameTD = $('<td>')
+ .addClass("o_res_name_cell")
+ .html(name)
+ .data('res-id', res_id);
+ var $cells = _.map(this.state.data.activity_types, function (node) {
+ var $td = $('<td>').addClass("o_activity_summary_cell");
+ var activity_type_id = node[0];
+ var activity_group = self.state.data.grouped_activities[res_id][activity_type_id];
+ activity_group = activity_group || {count: 0, ids: [], state: false};
+ if (activity_group.state) {
+ $td.addClass(activity_group.state);
+ }
+ // we need to create a fake record in order to instanciate the KanbanActivity
+ // this is the minimal information in order to make it work
+ // AAB: move this to a function
+ var record = {
+ data: {
+ activity_ids: {
+ model: 'mail.activity',
+ res_ids: activity_group.ids,
+ },
+ activity_state: activity_group.state,
+ },
+ fields: {
+ activity_ids: {},
+ activity_state: {
+ selection: [
+ ['overdue', "Overdue"],
+ ['today', "Today"],
+ ['planned', "Planned"],
+ ],
+ },
+ },
+ fieldsInfo: {},
+ model: self.state.data.model,
+ ref: res_id, // not necessary, i think
+ type: 'record',
+ res_id: res_id,
+ getContext: function () {
+ return {}; // session.user_context
+ },
+ //todo intercept event or changes on record to update view
+ };
+ var KanbanActivity = field_registry.get('kanban_activity');
+ var widget = new KanbanActivity(self, "activity_ids", record, {});
+ widget.appendTo($td);
+ // replace clock by closest deadline
+ var $date = $('<div>');
+ var formated_date = moment(activity_group.o_closest_deadline).format('ll');
+ var current_year = (new Date()).getFullYear();
+ if (formated_date.endsWith(current_year)) { // Dummy logic to remove year (only if current year), we will maybe need to improve it
+ formated_date = formated_date.slice(0, -4);
+ formated_date = formated_date.replace(/( |,)*$/g, "");
+ }
+ $date
+ .text(formated_date)
+ .addClass('o_closest_deadline');
+ $td.find('a')
+ .empty()
+ .append($date);
+ return $td;
+ });
+ var $tr = $('<tr/>', {class: 'o_data_row'})
+ .append($nameTD)
+ .append($cells);
+ return $tr;
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+ /**
+ * @private
+ * @override
+ * @param {MouseEvent} ev
+ */
+ _onSenMailTemplateClicked: function (ev) {
+ var $target = $(ev.currentTarget);
+ var templateID = $target.data('template-id');
+ var activityTypeID = $target.closest('th').data('activity-type-id');
+ this.trigger_up('send_mail_template', {
+ activityTypeID: activityTypeID,
+ templateID: templateID,
+ });
+ },
+ /**
+ * @private
+ * @override
+ * @param {MouseEvent} ev
+ */
+ _onResNameClicked: function (ev) {
+ var resID = $(ev.currentTarget).data('res-id');
+ this.trigger_up('open_view_form', {resID: resID});
+ },
+});
+
+return ActivityRenderer;
+
+});
diff --git a/addons/mail/static/src/js/views/activity/activity_view.js b/addons/mail/static/src/js/views/activity/activity_view.js
new file mode 100644
index 00000000000..fdd258caa54
--- /dev/null
+++ b/addons/mail/static/src/js/views/activity/activity_view.js
@@ -0,0 +1,29 @@
+odoo.define('mail.ActivityView', function (require) {
+"use strict";
+
+var ActivityController = require('mail.ActivityController');
+var ActivityModel = require('mail.ActivityModel');
+var ActivityRenderer = require('mail.ActivityRenderer');
+var AbstractView = require('web.AbstractView');
+var core = require('web.core');
+var view_registry = require('web.view_registry');
+
+var _lt = core._lt;
+
+var ActivityView = AbstractView.extend({
+ accesskey: "a",
+ display_name: _lt('Activity'),
+ icon: 'fa-clock-o',
+ config: _.extend({}, AbstractView.prototype.config, {
+ Controller: ActivityController,
+ Model: ActivityModel,
+ Renderer: ActivityRenderer,
+ }),
+ viewType: 'activity',
+});
+
+view_registry.add('activity', ActivityView);
+
+return ActivityView;
+
+});
diff --git a/addons/mail/static/src/scss/activity_view.scss b/addons/mail/static/src/scss/activity_view.scss
new file mode 100644
index 00000000000..d67c6019cdf
--- /dev/null
+++ b/addons/mail/static/src/scss/activity_view.scss
@@ -0,0 +1,54 @@
+.o_activity_view {
+ height: 100%;
+ > table {
+ background-color: white;
+ }
+ .o_activity_summary_cell {
+ background-color: #FFF;
+ &.planned {
+ background-color: theme-color('success');
+ }
+ &.overdue {
+ background-color: theme-color('danger');
+ }
+ &.today {
+ background-color: theme-color('warning');
+ }
+ .o_kanban_inline_block {
+ min-height: 42px;
+ }
+ .dropdown-toggle {
+ cursor: pointer;
+ .o_closest_deadline {
+ height: 42px;
+ width: 100%;
+ color: #FFF;
+ text-align: center;
+ line-height: 42px;
+ }
+ }
+ }
+ .o_res_name_cell {
+ padding: 0 10px;
+ color: #008784;
+ &:hover {
+ color: #666;
+ }
+ }
+ .o_activity_type_cell {
+ padding:10px;
+ min-width:100px;
+ .fa-ellipsis-v {
+ cursor: pointer;
+ }
+
+ .o_template_element {
+ white-space: nowrap;
+ padding:5px;
+ cursor: pointer;
+ &:hover {
+ color: theme-color('success');
+ }
+ }
+ }
+}
diff --git a/addons/mail/static/src/scss/mail_activity.scss b/addons/mail/static/src/scss/mail_activity.scss
index 7fbe0e89c82..0582b5312f4 100644
--- a/addons/mail/static/src/scss/mail_activity.scss
+++ b/addons/mail/static/src/scss/mail_activity.scss
@@ -144,10 +144,13 @@
}
/* Kanban View */
-.o_kanban_record {
+.o_kanban_record{
.o_kanban_inline_block {
display: inline-block;
}
+}
+
+.o_kanban_record, .o_view_controller{
.o_mail_activity {
.o_activity_btn {
span.fa {
diff --git a/addons/mail/static/src/xml/activity_view.xml b/addons/mail/static/src/xml/activity_view.xml
new file mode 100644
index 00000000000..eacb4cc7ca9
--- /dev/null
+++ b/addons/mail/static/src/xml/activity_view.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+
+<t t-name="mail.ActivityViewHeaderCell">
+ <th class="o_activity_type_cell" t-att-data-activity-type-id="id">
+ <span><t t-esc="name"/></span>
+ <span t-if="template_list.length > 0" class="dropdown pull-right">
+ <i class="fa fa-ellipsis-v fa-fw" data-toggle="dropdown"/>
+ <div class="dropdown-menu">
+ <div t-foreach="template_list" t-as="template" title="This action will send an email." class="o_template_element o_send_mail_template" t-att-data-template-id="template.id">
+ <i class="fa fa-envelope fa-fw"/> <t t-esc="template.name"/>
+ </div>
+ </div>
+ </span>
+ </th>
+</t>
+
+</templates>