[REF] mail: migrate activity renderer to owl
In order to prepare the future, we want to convert everything in Owl. This commit converts the activity renderer. closes odoo/odoo#42278 Task: 2149408 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
committed by
Michael Mattiello (mcm)
parent
7e405102d2
commit
5aa0822a9d
@@ -0,0 +1,42 @@
|
||||
odoo.define("mail.ActivityCell", function (require) {
|
||||
"use strict";
|
||||
|
||||
require("mail.Activity");
|
||||
const field_registry = require('web.field_registry');
|
||||
|
||||
const KanbanActivity = field_registry.get('kanban_activity');
|
||||
|
||||
const ActivityCell = KanbanActivity.extend({
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_render() {
|
||||
// replace clock by closest deadline
|
||||
const $date = $('<div class="o_closest_deadline">');
|
||||
const date = new Date(this.record.data.closest_deadline);
|
||||
// To remove year only if current year
|
||||
if (moment().year() === moment(date).year()) {
|
||||
$date.text(date.toLocaleDateString(moment().locale(), {
|
||||
day: 'numeric', month: 'short'
|
||||
}));
|
||||
} else {
|
||||
$date.text(moment(date).format('ll'));
|
||||
}
|
||||
this.$('a').html($date);
|
||||
if (this.record.data.activity_ids.res_ids.length > 1) {
|
||||
this.$('a').append($('<span>', {
|
||||
class: 'badge badge-light badge-pill border-0 ' + this.record.data.activity_state,
|
||||
text: this.record.data.activity_ids.res_ids.length,
|
||||
}));
|
||||
}
|
||||
if (this.$el.hasClass('show')) {
|
||||
// note: this part of the rendering might be asynchronous
|
||||
this._renderDropdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ActivityCell;
|
||||
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
odoo.define('mail.ActivityController', function (require) {
|
||||
"use strict";
|
||||
|
||||
require('mail.Activity');
|
||||
var BasicController = require('web.BasicController');
|
||||
var core = require('web.core');
|
||||
var field_registry = require('web.field_registry');
|
||||
@@ -20,6 +21,18 @@ var ActivityController = BasicController.extend({
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param parent
|
||||
* @param model
|
||||
* @param renderer
|
||||
* @param {Object} params
|
||||
* @param {String} params.title The title used in schedule activity dialog
|
||||
*/
|
||||
init: function (parent, model, renderer, params) {
|
||||
this._super.apply(this, arguments);
|
||||
this.title = params.title;
|
||||
},
|
||||
/**
|
||||
* Overridden to remove the pager as it makes no sense in this view.
|
||||
*
|
||||
@@ -38,17 +51,16 @@ var ActivityController = BasicController.extend({
|
||||
*/
|
||||
_onScheduleActivity: function () {
|
||||
var self = this;
|
||||
|
||||
var state = this.model.get(this.handle);
|
||||
new ViewDialogs.SelectCreateDialog(this, {
|
||||
res_model: state.model,
|
||||
domain: this.model.originalDomain,
|
||||
title: _.str.sprintf(_t("Search: %s"), this.renderer.arch.attrs.string),
|
||||
title: _.str.sprintf(_t("Search: %s"), this.title),
|
||||
no_create: !this.activeActions.create,
|
||||
disable_multiple_selection: true,
|
||||
context: state.context,
|
||||
on_selected: function (record) {
|
||||
var fakeRecord = self.renderer.getKanbanActivityData({}, record[0]);
|
||||
var fakeRecord = state.getKanbanActivityData({}, record[0]);
|
||||
var widget = new KanbanActivity(self, 'activity_ids', fakeRecord, {});
|
||||
widget.scheduleActivity();
|
||||
},
|
||||
@@ -79,7 +91,7 @@ var ActivityController = BasicController.extend({
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
_onSendMailTemplate: function (ev) {
|
||||
var templateID = ev.data.templateID;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
odoo.define('mail.ActivityModel', function (require) {
|
||||
'use strict';
|
||||
|
||||
var BasicModel = require('web.BasicModel');
|
||||
var session = require('web.session');
|
||||
const BasicModel = require('web.BasicModel');
|
||||
const session = require('web.session');
|
||||
|
||||
var ActivityModel = BasicModel.extend({
|
||||
const ActivityModel = BasicModel.extend({
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Add the following (activity specific) keys when performing a `get` on the
|
||||
* main list datapoint:
|
||||
@@ -22,10 +21,44 @@ var ActivityModel = BasicModel.extend({
|
||||
get: function () {
|
||||
var result = this._super.apply(this, arguments);
|
||||
if (result && result.model === this.modelName && result.type === 'list') {
|
||||
_.extend(result, this.additionalData);
|
||||
_.extend(result, this.additionalData, {getKanbanActivityData: this.getKanbanActivityData});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* @param {Object} activityGroup
|
||||
* @param {integer} resId
|
||||
* @returns {Object}
|
||||
*/
|
||||
getKanbanActivityData(activityGroup, resId) {
|
||||
return {
|
||||
data: {
|
||||
activity_ids: {
|
||||
model: 'mail.activity',
|
||||
res_ids: activityGroup.ids,
|
||||
},
|
||||
activity_state: activityGroup.state,
|
||||
closest_deadline: activityGroup.o_closest_deadline,
|
||||
},
|
||||
fields: {
|
||||
activity_ids: {},
|
||||
activity_state: {
|
||||
selection: [
|
||||
['overdue', "Overdue"],
|
||||
['today', "Today"],
|
||||
['planned', "Planned"],
|
||||
],
|
||||
},
|
||||
},
|
||||
fieldsInfo: {},
|
||||
model: this.model,
|
||||
type: 'record',
|
||||
res_id: resId,
|
||||
getContext: function () {
|
||||
return {};
|
||||
},
|
||||
};
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @param {Array[]} params.domain
|
||||
|
||||
@@ -1,163 +1,141 @@
|
||||
odoo.define('mail.ActivityRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractRenderer = require('web.AbstractRenderer');
|
||||
var ActivityRecord = require('mail.ActivityRecord');
|
||||
var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
var field_registry = require('web.field_registry');
|
||||
var KanbanColumnProgressBar = require('web.KanbanColumnProgressBar');
|
||||
var qweb = require('web.QWeb');
|
||||
var session = require('web.session');
|
||||
var utils = require('web.utils');
|
||||
const AbstractRendererOwl = require('web.AbstractRendererOwl');
|
||||
const ActivityCell = require('mail.ActivityCell');
|
||||
const ActivityRecord = require('mail.ActivityRecord');
|
||||
const { ComponentAdapter } = require('web.OwlCompatibility');
|
||||
const core = require('web.core');
|
||||
const KanbanColumnProgressBar = require('web.KanbanColumnProgressBar');
|
||||
const QWeb = require('web.QWeb');
|
||||
const session = require('web.session');
|
||||
const utils = require('web.utils');
|
||||
|
||||
var KanbanActivity = field_registry.get('kanban_activity');
|
||||
var _t = core._t;
|
||||
var QWeb = core.qweb;
|
||||
const _t = core._t;
|
||||
|
||||
var ActivityRenderer = AbstractRenderer.extend({
|
||||
className: 'o_activity_view',
|
||||
custom_events: {
|
||||
'set_progress_bar_state': '_onSetProgressBarState',
|
||||
},
|
||||
events: {
|
||||
'click .o_send_mail_template': '_onSenMailTemplateClicked',
|
||||
'click .o_activity_empty_cell': '_onEmptyCell',
|
||||
'click .o_record_selector': '_onRecordSelector',
|
||||
},
|
||||
const { useState } = owl.hooks;
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} params.templates
|
||||
*/
|
||||
init: function (parent, state, params) {
|
||||
this._super.apply(this, arguments);
|
||||
/**
|
||||
* Owl Component Adapter for ActivityRecord which is KanbanRecord (Odoo Widget)
|
||||
* TODO: Remove this adapter when ActivityRecord is a Component
|
||||
*/
|
||||
class ActivityRecordAdapter extends ComponentAdapter {
|
||||
render() {
|
||||
_.invoke(_.pluck(this.widget.subWidgets, '$el'), 'detach');
|
||||
this.widget._render();
|
||||
}
|
||||
|
||||
this.qweb = new qweb(config.isDebug(), {_s: session.origin});
|
||||
this.qweb.add_template(utils.json_node_to_xml(params.templates));
|
||||
},
|
||||
update(nextProps) {
|
||||
const state = nextProps.widgetArgs[0];
|
||||
this.widget._setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owl Component Adapter for ActivityCell which is BasicActivity (AbstractField)
|
||||
* TODO: Remove this adapter when ActivityCell is a Component
|
||||
*/
|
||||
class ActivityCellAdapter extends ComponentAdapter {
|
||||
render() {
|
||||
this.widget._render();
|
||||
}
|
||||
|
||||
update(nextProps) {
|
||||
const record = nextProps.widgetArgs[1];
|
||||
this.widget._reset(record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Owl Component Adapter for KanbanColumnProgressBar (Odoo Widget)
|
||||
* TODO: Remove this adapter when KanbanColumnProgressBar is a Component
|
||||
*/
|
||||
class KanbanColumnProgressBarAdapter extends ComponentAdapter {
|
||||
render() {
|
||||
this.widget._render();
|
||||
}
|
||||
|
||||
update(nextProps) {
|
||||
const options = nextProps.widgetArgs[0];
|
||||
const columnState = nextProps.widgetArgs[1];
|
||||
|
||||
const columnId = options.columnID;
|
||||
const nextActiveFilter = options.progressBarStates[columnId].activeFilter;
|
||||
this.widget.activeFilter = nextActiveFilter ? this.widget.activeFilter : false;
|
||||
this.widget.columnState = columnState;
|
||||
this.widget.computeCounters();
|
||||
}
|
||||
|
||||
_trigger_up(ev) {
|
||||
// KanbanColumnProgressBar triggers 3 events before being mounted
|
||||
// but we don't need to listen to them in our case.
|
||||
if (this.el) {
|
||||
super._trigger_up(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ActivityRenderer extends AbstractRendererOwl {
|
||||
constructor(parent, props) {
|
||||
super(...arguments);
|
||||
this.qweb = new QWeb(this.env.isDebug(), {_s: session.origin});
|
||||
this.qweb.add_template(utils.json_node_to_xml(props.templates));
|
||||
this.activeFilter = useState({
|
||||
state: null,
|
||||
activityTypeId: null,
|
||||
resIds: []
|
||||
});
|
||||
this.widgetComponents = {
|
||||
ActivityRecord,
|
||||
ActivityCell,
|
||||
KanbanColumnProgressBar,
|
||||
};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @param {Object} activityGroup
|
||||
* @param {integer} resID
|
||||
* @returns {Object}
|
||||
* Gets all activity resIds in the view.
|
||||
*
|
||||
* @returns filtered resIds first then the rest.
|
||||
*/
|
||||
getKanbanActivityData: function (activityGroup, resID) {
|
||||
get activityResIds() {
|
||||
const copiedActivityResIds = Array.from(this.props.activity_res_ids)
|
||||
return copiedActivityResIds.sort((a, b) => this.activeFilter.resIds.includes(a) ? -1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all existing activity type ids.
|
||||
*/
|
||||
get activityTypeIds() {
|
||||
const activities = Object.values(this.props.grouped_activities);
|
||||
const activityIds = activities.flatMap(Object.keys);
|
||||
const uniqueIds = Array.from(new Set(activityIds));
|
||||
return uniqueIds.map(Number);
|
||||
}
|
||||
|
||||
getProgressBarOptions(typeId) {
|
||||
return {
|
||||
data: {
|
||||
activity_ids: {
|
||||
model: 'mail.activity',
|
||||
res_ids: activityGroup.ids,
|
||||
},
|
||||
activity_state: activityGroup.state,
|
||||
},
|
||||
fields: {
|
||||
activity_ids: {},
|
||||
activity_state: {
|
||||
selection: [
|
||||
['overdue', "Overdue"],
|
||||
['today', "Today"],
|
||||
['planned', "Planned"],
|
||||
],
|
||||
},
|
||||
},
|
||||
fieldsInfo: {},
|
||||
model: this.state.model,
|
||||
type: 'record',
|
||||
res_id: resID,
|
||||
getContext: function () {
|
||||
return {}; // session.user_context
|
||||
},
|
||||
//todo intercept event or changes on record to update view
|
||||
};
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_getRecord: function (recordId) {
|
||||
return _.findWhere(this.state.data, { res_id: recordId });
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_render: function () {
|
||||
var self = this;
|
||||
this.$el.addClass('table-responsive');
|
||||
this.$el.html(QWeb.render('mail.ActivityView', { isEmpty: !this.state.activity_types.length }));
|
||||
return Promise.all([
|
||||
this._super.apply(this, arguments),
|
||||
this._renderHeader(),
|
||||
this._renderBody(),
|
||||
]).then(function (result) {
|
||||
self.$('table')
|
||||
.append(result[1])
|
||||
.append(result[2])
|
||||
.append(self._renderFooter());
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Promise<jQueryElement>} a jquery element <tbody>
|
||||
*/
|
||||
_renderBody: function () {
|
||||
var defs = _.map(this.state.activity_res_ids, this._renderRow.bind(this));
|
||||
return Promise.all(defs).then(function ($rows) {
|
||||
return $('<tbody>').append($rows);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {jQueryElement} a <tfoot> element
|
||||
*/
|
||||
_renderFooter: function () {
|
||||
return QWeb.render('mail.ActivityViewFooter');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Promise<jQueryElement>}
|
||||
*/
|
||||
_renderHeader: function () {
|
||||
var self = this;
|
||||
var activityTypeIds = _.unique(_.flatten(_.map(this.state.grouped_activities, function (act) { return _.keys(act); })));
|
||||
var $thead = $(QWeb.render('mail.ActivityViewHeader', {
|
||||
types: this.state.activity_types,
|
||||
activityTypeIDs: _.map(activityTypeIds, Number),
|
||||
}));
|
||||
var defs = [];
|
||||
activityTypeIds.forEach(function (typeId) {
|
||||
defs.push(self._renderProgressBar($thead, typeId));
|
||||
});
|
||||
return Promise.all(defs).then(function () {
|
||||
return $thead;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_renderProgressBar: function ($thead, typeId) {
|
||||
var counts = { planned: 0, today: 0, overdue: 0 };
|
||||
_.each(this.state.grouped_activities, function (act) {
|
||||
if (_.contains(_.keys(act), typeId.toString())) {
|
||||
counts[act[typeId].state] += 1;
|
||||
}
|
||||
});
|
||||
var progressBar = new KanbanColumnProgressBar(this, {
|
||||
columnID: typeId,
|
||||
progressBarStates: {},
|
||||
}, {
|
||||
count: _.reduce(_.values(counts), (x, y) => x + y),
|
||||
progressBarStates: {
|
||||
[typeId]: {
|
||||
activeFilter: this.activeFilter.activityTypeId === typeId,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getProgressBarColumnState(typeId) {
|
||||
const counts = { planned: 0, today: 0, overdue: 0 };
|
||||
for (let activities of Object.values(this.props.grouped_activities)) {
|
||||
if (typeId in activities) {
|
||||
counts[activities[typeId].state] += 1;
|
||||
}
|
||||
}
|
||||
return {
|
||||
count: Object.values(counts).reduce((x, y) => x + y),
|
||||
fields: {
|
||||
activity_state: {
|
||||
type: 'selection',
|
||||
@@ -173,141 +151,58 @@ var ActivityRenderer = AbstractRenderer.extend({
|
||||
colors: { planned: 'success', today: 'warning', overdue: 'danger' },
|
||||
counts: counts,
|
||||
},
|
||||
});
|
||||
return progressBar.appendTo($thead.find('th[data-activity-type-id=' + typeId + ']'));
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {integer} resId
|
||||
* @returns {Promise<jQueryElement>}
|
||||
*/
|
||||
_renderRow: function (resId) {
|
||||
var self = this;
|
||||
var defs = [];
|
||||
var record = this._getRecord(resId);
|
||||
var $nameTD = $('<td>', {
|
||||
class: _.contains(this.filteredResIDs, resId) ? 'o_activity_filter_' + this.activeFilter : '',
|
||||
});
|
||||
var activityRecord = new ActivityRecord(this, record, { qweb: this.qweb });
|
||||
defs.push(activityRecord.appendTo($nameTD));
|
||||
|
||||
var $cells = _.map(this.state.activity_types, function (node) {
|
||||
var activity_type_id = node[0];
|
||||
var activity_group = self.state.grouped_activities[resId];
|
||||
activity_group = activity_group && activity_group[activity_type_id] || {count: 0, ids: [], state: false};
|
||||
|
||||
var $td = $(QWeb.render('mail.ActivityViewRow', {
|
||||
resId: resId,
|
||||
activityGroup: activity_group,
|
||||
activityTypeId: activity_type_id,
|
||||
widget: self,
|
||||
}));
|
||||
if (activity_group.state) {
|
||||
var record = self.getKanbanActivityData(activity_group, resId);
|
||||
var widget = new KanbanActivity(self, "activity_ids", record, {});
|
||||
var def = widget.appendTo($td).then(function () {
|
||||
// replace clock by closest deadline
|
||||
var $date = $('<div class="o_closest_deadline">');
|
||||
// split the year/month/day to create a date (to have a date, no tz, instead of a datetime, tz dependant)
|
||||
var splitDate = activity_group.o_closest_deadline.split('-');
|
||||
// the value representing the month, ranges from 0 for January up to 11 for December.
|
||||
var date = new Date(splitDate[0], splitDate[1]-1, splitDate[2]);
|
||||
// To remove year only if current year
|
||||
if (moment().year() === moment(date).year()) {
|
||||
$date.text(date.toLocaleDateString(moment().locale(), { day: 'numeric', month: 'short' }));
|
||||
} else {
|
||||
$date.text(moment(date).format('ll'));
|
||||
}
|
||||
$td.find('a').html($date);
|
||||
if (activity_group.count > 1) {
|
||||
$td.find('a').append($('<span>', {
|
||||
class: 'badge badge-light badge-pill border-0 ' + activity_group.state,
|
||||
text: activity_group.count,
|
||||
}));
|
||||
}
|
||||
});
|
||||
defs.push(def);
|
||||
}
|
||||
return $td;
|
||||
});
|
||||
return Promise.all(defs).then(function () {
|
||||
var $tr = $('<tr/>', {class: 'o_data_row'}).attr('data-res-id', resId)
|
||||
.append($nameTD)
|
||||
.append($cells);
|
||||
return $tr;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onEmptyCell: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
var data = $(ev.currentTarget).data();
|
||||
this.trigger_up('empty_cell_clicked', data);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onRecordSelector: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.trigger_up('schedule_activity');
|
||||
},
|
||||
/**
|
||||
* @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,
|
||||
_onEmptyCellClicked(ev) {
|
||||
this.trigger('empty_cell_clicked', {
|
||||
resId: parseInt(ev.currentTarget.dataset.resId, 10),
|
||||
activityTypeId: parseInt(ev.currentTarget.dataset.activityTypeId, 10),
|
||||
});
|
||||
},
|
||||
}
|
||||
/**
|
||||
* Rearrange body part of table based on active filter.
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onSetProgressBarState: function (ev) {
|
||||
var self = this;
|
||||
|
||||
this.$('th[class*="o_activity_filter_"]').attr('class', 'o_activity_type_cell');
|
||||
this.$('.o_kanban_counter_progress div').removeClass('active progress-bar-striped');
|
||||
|
||||
var data = ev.data;
|
||||
var arrangedRecords = this.state.activity_res_ids;
|
||||
this.activeFilter = data.values.activeFilter;
|
||||
if (this.activeFilter) {
|
||||
var filteredResIds = _.map(_.keys(_.pick(this.state.grouped_activities, function (act) {
|
||||
return act[data.columnID] && act[data.columnID].state === self.activeFilter;
|
||||
})), Number);
|
||||
arrangedRecords = _.union(_.intersection(this.state.activity_res_ids, filteredResIds), this.state.activity_res_ids);
|
||||
this.filteredResIDs = filteredResIds;
|
||||
}
|
||||
var defs = _.map(arrangedRecords, this._renderRow.bind(this));
|
||||
Promise.all(defs).then(function ($rows) {
|
||||
self.$('tbody').html($rows);
|
||||
_onSendMailTemplateClicked(ev) {
|
||||
this.trigger('send_mail_template', {
|
||||
activityTypeID: parseInt(ev.currentTarget.dataset.activityTypeId, 10),
|
||||
templateID: parseInt(ev.currentTarget.dataset.templateId, 10),
|
||||
});
|
||||
|
||||
if (this.activeFilter) {
|
||||
var $header = this.$('th.o_activity_type_cell[data-activity-type-id=' + data.columnID + ']');
|
||||
$header.addClass('o_activity_filter_' + this.activeFilter);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {CustomEvent} ev
|
||||
*/
|
||||
_onSetProgressBarState(ev) {
|
||||
if (ev.detail.values.activeFilter) {
|
||||
this.activeFilter.state = ev.detail.values.activeFilter;
|
||||
this.activeFilter.activityTypeId = ev.detail.columnID;
|
||||
this.activeFilter.resIds = Object.entries(this.props.grouped_activities)
|
||||
.filter(([, resIds]) => ev.detail.columnID in resIds &&
|
||||
resIds[ev.detail.columnID].state === ev.detail.values.activeFilter)
|
||||
.map(([key]) => parseInt(key));
|
||||
} else {
|
||||
this.activeFilter.state = null;
|
||||
this.activeFilter.activityTypeId = null;
|
||||
this.activeFilter.resIds = [];
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ActivityRenderer.components = {
|
||||
ActivityRecordAdapter,
|
||||
ActivityCellAdapter,
|
||||
KanbanColumnProgressBarAdapter,
|
||||
};
|
||||
ActivityRenderer.template = 'mail.ActivityRenderer';
|
||||
|
||||
return ActivityRenderer;
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
odoo.define('mail.ActivityView', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ActivityController = require('mail.ActivityController');
|
||||
var ActivityModel = require('mail.ActivityModel');
|
||||
var ActivityRenderer = require('mail.ActivityRenderer');
|
||||
var BasicView = require('web.BasicView');
|
||||
var core = require('web.core');
|
||||
var view_registry = require('web.view_registry');
|
||||
const ActivityController = require('mail.ActivityController');
|
||||
const ActivityModel = require('mail.ActivityModel');
|
||||
const ActivityRenderer = require('mail.ActivityRenderer');
|
||||
const BasicView = require('web.BasicView');
|
||||
const core = require('web.core');
|
||||
const RendererWrapper = require('web.RendererWrapper');
|
||||
const view_registry = require('web.view_registry');
|
||||
|
||||
var _lt = core._lt;
|
||||
const _lt = core._lt;
|
||||
|
||||
var ActivityView = BasicView.extend({
|
||||
const ActivityView = BasicView.extend({
|
||||
accesskey: "a",
|
||||
display_name: _lt('Activity'),
|
||||
icon: 'fa-clock-o',
|
||||
@@ -33,6 +34,15 @@ var ActivityView = BasicView.extend({
|
||||
this.loadParams.limit = false;
|
||||
|
||||
this.rendererParams.templates = _.findWhere(this.arch.children, { 'tag': 'templates' });
|
||||
this.controllerParams.title = this.arch.attrs.string;
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
getRenderer(parent, state) {
|
||||
state = Object.assign({}, state, this.rendererParams);
|
||||
return new RendererWrapper(null, this.config.Renderer, state);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,54 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ActivityViewHeader">
|
||||
<t t-name="mail.ActivityViewHeader" owl="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th t-foreach="types" t-as="type" class="o_activity_type_cell" t-att-data-activity-type-id="type[0]" t-attf-width="#{100/types.length}%">
|
||||
<th t-foreach="props.activity_types" t-as="type" t-key="type[0]"
|
||||
class="o_activity_type_cell" t-attf-class="{{ activeFilter.activityTypeId === type[0] ? 'o_activity_filter_' + activeFilter.state : '' }}"
|
||||
t-att-data-activity-type-id="type[0]" t-attf-width="{{100/props.activity_types.length}}%">
|
||||
<div>
|
||||
<span><t t-esc="type[1]"/></span>
|
||||
<span t-esc="type[1]"/>
|
||||
<span t-if="type[2].length > 0" class="dropdown pull-right">
|
||||
<i class="fa fa-ellipsis-v fa-fw" data-toggle="dropdown"/>
|
||||
<div class="dropdown-menu">
|
||||
<div t-foreach="type[2]" 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>
|
||||
<t t-foreach="type[2]" t-as="template" t-key="template.id">
|
||||
<div title="This action will send an email."
|
||||
class="o_template_element o_send_mail_template"
|
||||
t-att-data-activity-type-id="type[0]"
|
||||
t-att-data-template-id="template.id"
|
||||
t-on-click="_onSendMailTemplateClicked">
|
||||
<i class="fa fa-envelope fa-fw"/> <t t-esc="template.name"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div t-if="activityTypeIDs.length and !_.contains(activityTypeIDs, type[0])" class="mt24"/>
|
||||
<KanbanColumnProgressBarAdapter Component="widgetComponents.KanbanColumnProgressBar"
|
||||
widgetArgs="[getProgressBarOptions(type[0]), getProgressBarColumnState(type[0])]"
|
||||
t-if="activityTypeIds.includes(type[0])"
|
||||
t-on-set-progress-bar-state="_onSetProgressBarState"/>
|
||||
<div t-else="" class="mt24"/>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.ActivityViewRow">
|
||||
<td t-att-data-res-id="resId" t-att-data-activity-type-id="activityTypeId"
|
||||
t-attf-class="o_activity_summary_cell #{activityGroup.state ? activityGroup.state : 'o_activity_empty_cell'} #{_.contains(widget.filteredResIDs, resId) ? 'o_activity_filter_' + widget.activeFilter : ''}">
|
||||
<i t-if="!activityGroup.state" title="Create" class="text-center fa fa-plus"/>
|
||||
<t t-name="mail.ActivityViewBody" owl="1">
|
||||
<tbody>
|
||||
<t t-foreach="activityResIds" t-as="resId" t-key="resId">
|
||||
<t t-call="mail.ActivityViewRow"/>
|
||||
</t>
|
||||
</tbody>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.ActivityViewRow" owl="1">
|
||||
<tr class="o_data_row" t-att-data-res-id="resId">
|
||||
<t t-set="record" t-value="props.data.find(data => data.res_id === resId)"/>
|
||||
<td t-attf-class="{{ activeFilter.resIds.includes(resId) ? 'o_activity_filter_' + activeFilter.state : '' }}">
|
||||
<ActivityRecordAdapter Component="widgetComponents.ActivityRecord"
|
||||
widgetArgs="[record, { qweb: qweb }]"/>
|
||||
</td>
|
||||
<t t-foreach="props.activity_types" t-as="type" t-key="type[0]">
|
||||
<t t-call="mail.ActivityViewCell"/>
|
||||
</t>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.ActivityViewCell" owl="1">
|
||||
<t t-set="activityGroup" t-value="props.grouped_activities[resId] and props.grouped_activities[resId][type[0]] or {count: 0, ids: [], state: false}"/>
|
||||
<td t-if="activityGroup.state" t-att-data-res-id="resId" t-att-data-activity-type-id="type[0]"
|
||||
t-attf-class="o_activity_summary_cell {{activityGroup.state}} {{ activeFilter.resIds.includes(resId) ? 'o_activity_filter_' + activeFilter.state : '' }}">
|
||||
<ActivityCellAdapter Component="widgetComponents.ActivityCell"
|
||||
widgetArgs="['activity_ids', props.getKanbanActivityData(activityGroup, resId)]"/>
|
||||
</td>
|
||||
<td t-else="" t-att-data-res-id="resId" t-att-data-activity-type-id="type[0]"
|
||||
class="o_activity_summary_cell o_activity_empty_cell"
|
||||
t-attf-class="{{ activeFilter.resIds.includes(resId) ? 'o_activity_filter_' + activeFilter.state : '' }}"
|
||||
t-on-click.prevent.stop="_onEmptyCellClicked">
|
||||
<i title="Create" class="text-center fa fa-plus"/>
|
||||
</td>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.ActivityViewFooter">
|
||||
<t t-name="mail.ActivityViewFooter" owl="1">
|
||||
<tfoot>
|
||||
<tr class="o_data_row">
|
||||
<td class="o_record_selector p-3">
|
||||
<td class="o_record_selector p-3" t-on-click.prevent.stop="trigger('schedule_activity')">
|
||||
<span class="fa fa-plus pr-2"/><span>Schedule activity</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.ActivityView">
|
||||
<div t-if="isEmpty" class="o_view_nocontent" role="alert">
|
||||
<div class="o_nocontent_help">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No data to display
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<table t-else="" class="table-bordered mb-5"/>
|
||||
</t>
|
||||
<div t-name="mail.ActivityRenderer" class="o_activity_view" owl="1">
|
||||
<t t-if="!props.activity_types.length" t-call="web.NoContentHelper"/>
|
||||
<table t-else="" class="table-bordered mb-5">
|
||||
<t t-call="mail.ActivityViewHeader"/>
|
||||
<t t-call="mail.ActivityViewBody"/>
|
||||
<t t-call="mail.ActivityViewFooter"/>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -263,7 +263,8 @@ QUnit.test('activity view: batch send mail on activity', async function (assert)
|
||||
|
||||
QUnit.test('activity view: activity widget', async function (assert) {
|
||||
assert.expect(16);
|
||||
var activity = await createView({
|
||||
|
||||
const params = {
|
||||
View: ActivityView,
|
||||
model: 'task',
|
||||
data: this.data,
|
||||
@@ -313,7 +314,10 @@ QUnit.test('activity view: activity widget', async function (assert) {
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
owl.Component.env = testUtils.mock.getMockedOwlEnv(params);
|
||||
var activity = await createView(params);
|
||||
var today = activity.$('table tbody tr:first td:nth-child(2).today');
|
||||
var dropdown = today.find('.dropdown-menu.o_activity');
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
<script type="text/javascript" src="/mail/static/src/js/views/activity/activity_controller.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/views/activity/activity_renderer.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/views/activity/activity_record.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/src/js/views/activity/activity_cell.js"></script>
|
||||
<!-- Attachment box -->
|
||||
<script type="text/javascript" src="/mail/static/src/js/attachment_box.js"></script>
|
||||
<!-- Emojis -->
|
||||
|
||||
@@ -80,28 +80,32 @@ var KanbanColumnProgressBar = Widget.extend({
|
||||
// This should be executed when the progressbar is fully rendered
|
||||
// and is in the DOM, this happens to be always the case with
|
||||
// current use of progressbars
|
||||
|
||||
var subgroupCounts = {};
|
||||
let allSubgroupCount = 0;
|
||||
_.each(self.colors, function (val, key) {
|
||||
var subgroupCount = self.columnState.progressBarValues.counts[key] || 0;
|
||||
if (self.activeFilter === key && subgroupCount === 0) {
|
||||
self.activeFilter = false;
|
||||
}
|
||||
subgroupCounts[key] = subgroupCount;
|
||||
allSubgroupCount += subgroupCount;
|
||||
});
|
||||
subgroupCounts.__false = self.columnState.count - allSubgroupCount;
|
||||
|
||||
self.groupCount = self.columnState.count;
|
||||
self.subgroupCounts = subgroupCounts;
|
||||
self.prevTotalCounterValue = self.totalCounterValue;
|
||||
self.totalCounterValue = self.sumField ? (self.columnState.aggregateValues[self.sumField] || 0) : self.columnState.count;
|
||||
|
||||
self.computeCounters();
|
||||
self._notifyState();
|
||||
self._render();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Computes the count of each sub group and the total count
|
||||
*/
|
||||
computeCounters() {
|
||||
const subgroupCounts = {};
|
||||
let allSubgroupCount = 0;
|
||||
for (const key of Object.keys(this.colors)) {
|
||||
const subgroupCount = this.columnState.progressBarValues.counts[key] || 0;
|
||||
if (this.activeFilter === key && subgroupCount === 0) {
|
||||
this.activeFilter = false;
|
||||
}
|
||||
subgroupCounts[key] = subgroupCount;
|
||||
allSubgroupCount += subgroupCount;
|
||||
};
|
||||
subgroupCounts.__false = this.columnState.count - allSubgroupCount;
|
||||
|
||||
this.groupCount = this.columnState.count;
|
||||
this.subgroupCounts = subgroupCounts;
|
||||
this.prevTotalCounterValue = this.totalCounterValue;
|
||||
this.totalCounterValue = this.sumField ? (this.columnState.aggregateValues[this.sumField] || 0) : this.columnState.count;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -187,6 +191,7 @@ var KanbanColumnProgressBar = Widget.extend({
|
||||
$bar.css('width', '');
|
||||
}
|
||||
});
|
||||
this.$('.progress-bar').css('min-width', '');
|
||||
this.$('.progress-bar.o_bar_has_records').css('min-width', barMinWidth + '%');
|
||||
|
||||
// Display and animate the counter number
|
||||
|
||||
@@ -58,6 +58,7 @@ odoo.define('web.test_env', async function (require) {
|
||||
device: Object.assign({
|
||||
isMobile: false,
|
||||
}, env.device),
|
||||
isDebug: env.isDebug || (() => false),
|
||||
qweb: new owl.QWeb({ templates: session.owlTemplates }),
|
||||
services: Object.assign({
|
||||
ajax: { // for legacy subwidgets
|
||||
|
||||
Reference in New Issue
Block a user