diff options
| author | XavierDo <xdo@odoo.com> | 2018-08-01 14:51:01 +0200 |
|---|---|---|
| committer | XavierDo <xdo@odoo.com> | 2018-08-10 15:20:37 +0200 |
| commit | 1de9496f14a42d543ec9805dfc1d0e8df39017d2 (patch) | |
| tree | 65ae75dd0295eeb2d151a4bb4e4c1457522967c4 /addons/mail/static/src | |
| parent | 83fc5dc03ce92df9b920386f9215cdc5504c3be1 (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')
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> |
