[IMP] mail, test_mail: convert legacy code from activity view cell

task-3026018

closes odoo/odoo#103326

Related: odoo/enterprise#32853
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Sébastien Theys
2022-10-27 12:40:09 +02:00
parent e2fe60e717
commit bade59a3a7
22 changed files with 353 additions and 173 deletions
@@ -0,0 +1,25 @@
/** @odoo-module **/
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
import { registerMessagingComponent } from '@mail/utils/messaging_component';
const { Component } = owl;
export class ActivityCellView extends Component {
setup() {
useRefToModel({ fieldName: 'contentRef', refName: 'content' });
}
get activityCellView() {
return this.props.record;
}
}
Object.assign(ActivityCellView, {
props: { record: Object },
template: 'mail.ActivityCellView',
});
registerMessagingComponent(ActivityCellView);
@@ -0,0 +1,3 @@
.o_ActivityCellView_activityCount {
@include o-position-absolute($bottom: 0, $right: 0);
}
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ActivityCellView" owl="1">
<div class="o_ActivityCellView h-100 cursor-pointer p-3" t-on-click="activityCellView.onClick" t-ref="root">
<div class="o_ActivityCellView_content h-100 d-flex align-items-center justify-content-center position-relative" t-ref="content">
<div class="o_ActivityCellView_closestDeadline text-white" t-out="activityCellView.closestDeadlineFormatted"/>
<span t-if="activityCellView.filteredActivities.length > 1" class="o_ActivityCellView_activityCount badge bg-light rounded-pill border-0" t-out="activityCellView.filteredActivities.length"/>
</div>
</div>
</t>
</templates>
@@ -0,0 +1,101 @@
/** @odoo-module **/
// ensure components are registered beforehand.
import '@mail/backend_components/activity_cell_view/activity_cell_view';
import { insert } from '@mail/model/model_field_command';
import { getMessagingComponent } from '@mail/utils/messaging_component';
const { Component, onWillDestroy, onWillUpdateProps } = owl;
const getNextId = (function () {
let tmpId = 0;
return () => {
tmpId += 1;
return tmpId;
};
})();
/**
* Container for messaging component ActivityCellView ensuring messaging
* records are ready before rendering ActivityCellView component.
*/
export class ActivityCellViewContainer extends Component {
/**
* @override
*/
setup() {
super.setup();
this.activityCellView = undefined;
this.activityCellViewId = getNextId();
this._insertFromProps(this.props);
onWillUpdateProps(nextProps => this._insertFromProps(nextProps));
onWillDestroy(() => this._deleteRecord());
}
/**
* @private
*/
_deleteRecord() {
if (this.activityCellView) {
if (this.activityCellView.exists()) {
this.activityCellView.delete();
}
this.activityCellView = undefined;
}
}
/**
* @private
*/
async _insertFromProps(props) {
const messaging = await this.env.services.messaging.get();
if (owl.status(this) === "destroyed") {
this._deleteRecord();
return;
}
const activityCellView = messaging.models['ActivityCellView'].insert({
activityType: {
id: props.activityTypeId,
},
closestDeadline: props.closestDeadline,
id: this.activityCellViewId,
reloadFunc: props.reloadFunc,
thread: {
activities: insert(props.activityIds.map(activityId => {
return {
id: activityId,
type: {
id: props.activityTypeId,
},
};
})),
hasActivities: true,
id: props.resId,
model: props.resModel,
},
});
if (activityCellView !== this.activityCellView) {
this._deleteRecord();
this.activityCellView = activityCellView;
}
this.render();
}
}
Object.assign(ActivityCellViewContainer, {
components: { ActivityCellView: getMessagingComponent('ActivityCellView') },
props: {
activityIds: {
type: Array,
elements: Number,
},
activityTypeId: Number,
closestDeadline: String,
reloadFunc: Function,
resId: Number,
resModel: String,
},
template: 'mail.ActivityCellViewContainer',
});
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ActivityCellViewContainer" owl="1">
<ActivityCellView t-if="activityCellView" record="activityCellView"/>
</t>
</templates>
@@ -8,7 +8,7 @@
</t>
<t t-if="activityListView.overdueActivityListViewItems.length > 0">
<div class="d-flex bg-100 py-2 border-bottom">
<span class="text-danger fw-bold mx-3">Overdue</span>
<span class="o_ActivityListView_overdueTitle text-danger fw-bold mx-3">Overdue</span>
<span class="flex-grow-1"/>
<span class="badge rounded-pill text-bg-danger mx-3 align-self-center" t-esc="activityListView.overdueActivityListViewItems.length"/>
</div>
@@ -18,7 +18,7 @@
</t>
<t t-if="activityListView.todayActivityListViewItems.length > 0">
<div class="d-flex bg-100 py-2 border-bottom">
<span class="text-warning fw-bold mx-3">Today</span>
<span class="o_ActivityListView_todayTitle text-warning fw-bold mx-3">Today</span>
<span class="flex-grow-1"/>
<span class="badge rounded-pill text-bg-warning mx-3 align-self-center" t-esc="activityListView.todayActivityListViewItems.length"/>
</div>
@@ -28,7 +28,7 @@
</t>
<t t-if="activityListView.plannedActivityListViewItems.length > 0">
<div class="d-flex bg-100 py-2 border-bottom">
<span class="text-success fw-bold mx-3">Planned</span>
<span class="o_ActivityListView_plannedTitle text-success fw-bold mx-3">Planned</span>
<span class="flex-grow-1"/>
<span class="badge rounded-pill text-bg-success mx-3 align-self-center" t-esc="activityListView.plannedActivityListViewItems.length"/>
</div>
@@ -59,7 +59,7 @@ export class KanbanFieldActivityViewContainer extends Component {
const kanbanFieldActivityView = messaging.models['KanbanFieldActivityView'].insert({
id: this.kanbanFieldActivityViewId,
thread: {
activities: this.props.value.records.map(activityData => {
activities: props.value.records.map(activityData => {
return {
id: activityData.resId,
};
@@ -59,7 +59,7 @@ export class ListFieldActivityViewContainer extends Component {
const listFieldActivityView = messaging.models['ListFieldActivityView'].insert({
id: this.listFieldActivityViewId,
thread: {
activities: this.props.value.records.map(activityData => {
activities: props.value.records.map(activityData => {
return {
id: activityData.resId,
};
+9 -31
View File
@@ -45,7 +45,6 @@ const KanbanActivity = AbstractField.extend({
this.selection[key] = value;
}
this.defaultActivityType = options.activityType;
this.isActivityViewCell = options.isActivityViewCell;
},
async willStart() {
await this._super();
@@ -379,38 +378,17 @@ const KanbanActivity = AbstractField.extend({
* @private
*/
_render() {
if (this.isActivityViewCell) {
// replace clock by closest deadline
const $date = $('<div class="o_closest_deadline">');
const date = moment(this.record.data.closest_deadline).toDate();
// 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 bg-light rounded-pill border-0 ' + this.record.data.activity_state,
text: this.record.data.activity_ids.res_ids.length,
}));
}
// span classes need to be updated manually because the template cannot
// be re-rendered eaasily (because of the dropdown state)
const spanClasses = ['fa', 'fa-lg', 'fa-fw'];
spanClasses.push('o_activity_color_' + (this.record.data.activity_state || 'default'));
if (this.recordData.activity_exception_decoration) {
spanClasses.push('text-' + this.recordData.activity_exception_decoration);
spanClasses.push(this.recordData.activity_exception_icon);
} else {
// span classes need to be updated manually because the template cannot
// be re-rendered eaasily (because of the dropdown state)
const spanClasses = ['fa', 'fa-lg', 'fa-fw'];
spanClasses.push('o_activity_color_' + (this.record.data.activity_state || 'default'));
if (this.recordData.activity_exception_decoration) {
spanClasses.push('text-' + this.recordData.activity_exception_decoration);
spanClasses.push(this.recordData.activity_exception_icon);
} else {
spanClasses.push('fa-clock-o');
}
this.$('.o_activity_btn > span').removeClass().addClass(spanClasses.join(' '));
spanClasses.push('fa-clock-o');
}
this.$('.o_activity_btn > span').removeClass().addClass(spanClasses.join(' '));
if (this.$el.hasClass('show')) {
// note: this part of the rendering might be asynchronous
this._renderDropdown();
@@ -14,6 +14,7 @@ var _t = core._t;
var ActivityController = BasicController.extend({
custom_events: _.extend({}, BasicController.prototype.custom_events, {
empty_cell_clicked: '_onEmptyCell',
reload_data: '_onReloadData',
send_mail_template: '_onSendMailTemplate',
schedule_activity: '_onScheduleActivity',
}),
@@ -53,6 +54,12 @@ var ActivityController = BasicController.extend({
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onReloadData() {
this.trigger_up('reload');
},
/**
* @private
*/
@@ -20,44 +20,10 @@ const 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, {getKanbanActivityData: this.getKanbanActivityData});
_.extend(result, this.additionalData);
}
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,10 +1,10 @@
/** @odoo-module **/
import ActivityRecord from '@mail/js/views/activity/activity_record';
import { ActivityCellViewContainer } from '@mail/backend_components/activity_cell_view/activity_cell_view_container';
import AbstractRendererOwl from 'web.AbstractRendererOwl';
import core from 'web.core';
import field_registry from 'web.field_registry';
import KanbanColumnProgressBar from 'web.KanbanColumnProgressBar';
import { ComponentAdapter } from 'web.OwlCompatibility';
import QWeb from 'web.QWeb';
@@ -13,7 +13,6 @@ import utils from 'web.utils';
const { useState } = owl;
const _t = core._t;
const KanbanActivity = field_registry.get('kanban_activity');
/**
* Owl Component Adapter for ActivityRecord which is KanbanRecord (Odoo Widget)
@@ -31,21 +30,6 @@ class ActivityRecordAdapter extends ComponentAdapter {
}
}
/**
* Owl Component Adapter for KanbanActivity.
* TODO: Remove this adapter when KanbanActivity is a Component.
*/
class ActivityCellAdapter extends ComponentAdapter {
renderWidget() {
this.widget._render();
}
updateWidget(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
@@ -94,7 +78,6 @@ class ActivityRenderer extends AbstractRendererOwl {
});
this.widgetComponents = {
ActivityRecord,
KanbanActivity,
KanbanColumnProgressBar,
};
}
@@ -209,8 +192,8 @@ class ActivityRenderer extends AbstractRendererOwl {
}
ActivityRenderer.components = {
ActivityCellViewContainer,
ActivityRecordAdapter,
ActivityCellAdapter,
KanbanColumnProgressBarAdapter,
};
ActivityRenderer.template = 'mail.ActivityRenderer';
+11 -11
View File
@@ -179,7 +179,6 @@ registerModel({
/**
* @param {Object} param0
* @param {string} param0.feedback
* @returns {Object}
*/
async markAsDoneAndScheduleNext({ feedback }) {
const thread = this.thread;
@@ -198,17 +197,18 @@ registerModel({
if (!action) {
return;
}
this.env.services.action.doAction(
action,
{
onClose: () => {
if (!thread.exists()) {
return;
}
thread.fetchData(['activities']);
await new Promise(resolve => {
this.env.services.action.doAction(
action,
{
onClose: resolve,
},
},
);
);
});
if (!thread.exists()) {
return;
}
thread.fetchData(['activities']);
},
},
fields: {
@@ -0,0 +1,53 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'ActivityCellView',
recordMethods: {
onClick() {
this.update({ activityListPopoverView: this.activityListPopoverView ? clear() : {} });
},
},
fields: {
activityListPopoverView: one('PopoverView', {
inverse: 'activityCellViewOwnerAsActivityList',
}),
activityType: one('ActivityType', {
required: true,
}),
closestDeadline: attr({
required: true,
}),
closestDeadlineFormatted: attr({
compute() {
const date = moment(this.closestDeadline).toDate();
// To remove year only if current year
if (moment().year() === moment(date).year()) {
return date.toLocaleDateString(moment().locale(), {
day: 'numeric', month: 'short'
});
} else {
return moment(date).format('ll');
}
},
}),
contentRef: attr(),
filteredActivities: many('Activity', {
compute() {
return this.thread.activities.filter(activity => activity.type === this.activityType);
},
}),
id: attr({
identifying: true,
}),
reloadFunc: attr({
required: true,
}),
thread: one('Thread', {
required: true,
}),
},
});
@@ -2,6 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
import session from 'web.session';
@@ -29,13 +30,22 @@ registerModel({
},
recordMethods: {
onClickAddActivityButton() {
const reloadFunc = this.reloadFunc;
const thread = this.thread;
const webRecord = this.webRecord;
this.messaging.openActivityForm({
defaultActivityTypeId: this.popoverViewOwner.activityCellViewOwnerAsActivityList
? this.popoverViewOwner.activityCellViewOwnerAsActivityList.activityType.id
: undefined,
thread,
}).then(() => {
thread.fetchData(['activities']);
webRecord.model.load({ resId: thread.id });
if (reloadFunc) {
reloadFunc();
}
if (webRecord) {
webRecord.model.load({ resId: thread.id });
}
});
this.popoverViewOwner.delete();
},
@@ -43,6 +53,9 @@ registerModel({
fields: {
activities: many('Activity', {
compute() {
if (this.popoverViewOwner.activityCellViewOwnerAsActivityList) {
return this.popoverViewOwner.activityCellViewOwnerAsActivityList.filteredActivities;
}
return this.thread && this.thread.activities;
},
sort: [
@@ -70,9 +83,20 @@ registerModel({
identifying: true,
inverse: 'activityListView',
}),
reloadFunc: attr({
compute() {
return this.popoverViewOwner.activityCellViewOwnerAsActivityList ? this.popoverViewOwner.activityCellViewOwnerAsActivityList.reloadFunc : clear();
},
}),
thread: one('Thread', {
compute() {
return this.popoverViewOwner.activityButtonViewOwnerAsActivityList.thread;
if (this.popoverViewOwner.activityButtonViewOwnerAsActivityList) {
return this.popoverViewOwner.activityButtonViewOwnerAsActivityList.thread;
}
if (this.popoverViewOwner.activityCellViewOwnerAsActivityList) {
return this.popoverViewOwner.activityCellViewOwnerAsActivityList.thread;
}
return clear();
},
required: true,
}),
@@ -81,9 +105,11 @@ registerModel({
}),
webRecord: attr({
compute() {
return this.popoverViewOwner.activityButtonViewOwnerAsActivityList.webRecord;
if (this.popoverViewOwner.activityButtonViewOwnerAsActivityList) {
return this.popoverViewOwner.activityButtonViewOwnerAsActivityList.webRecord;
}
return clear();
},
required: true,
}),
},
});
@@ -12,10 +12,16 @@ registerModel({
recordMethods: {
onClickEditActivityButton() {
const popoverViewOwner = this.activityListViewOwner.popoverViewOwner;
const reloadFunc = this.reloadFunc;
const webRecord = this.webRecord;
const thread = this.activity.thread;
this.activity.edit().then(() => {
webRecord.model.load({ resId: thread.id });
if (reloadFunc) {
reloadFunc();
}
if (webRecord) {
webRecord.model.load({ resId: thread.id });
}
});
popoverViewOwner.delete();
},
@@ -117,11 +123,15 @@ registerModel({
markDoneView: one('ActivityMarkDonePopoverContentView', {
inverse: 'activityListViewItemOwner',
}),
reloadFunc: attr({
compute() {
return this.activityListViewOwner.reloadFunc ? this.activityListViewOwner.reloadFunc : clear();
},
}),
webRecord: attr({
compute() {
return this.activityListViewOwner.webRecord;
return this.activityListViewOwner.webRecord ? this.activityListViewOwner.webRecord : clear();
},
required: true,
}),
},
});
@@ -34,6 +34,7 @@ registerModel({
*/
async onClickDone() {
const chatter = this.activityViewOwner && this.activityViewOwner.activityBoxView.chatter;
const reloadFunc = this.reloadFunc;
const webRecord = this.webRecord;
const thread = this.activity.thread;
await this.activity.markAsDone({
@@ -42,6 +43,9 @@ registerModel({
if (chatter && chatter.exists() && chatter.component) {
chatter.reloadParentView();
}
if (reloadFunc) {
reloadFunc();
}
if (webRecord) {
webRecord.model.load({ resId: thread.id });
}
@@ -51,21 +55,25 @@ registerModel({
*/
async onClickDoneAndScheduleNext() {
const chatter = this.activityViewOwner && this.activityViewOwner.activityBoxView.chatter;
const reloadFunc = this.reloadFunc;
const webRecord = this.webRecord;
const thread = this.activity.thread;
const activityListViewOwner = this.activityListViewItemOwner && this.activityListViewItemOwner.activityListViewOwner;
await this.activity.markAsDoneAndScheduleNext({
feedback: this.feedbackTextareaRef.el.value,
});
const activity = this.activity;
const feedback = this.feedbackTextareaRef.el.value;
if (activityListViewOwner && activityListViewOwner.exists()) {
activityListViewOwner.popoverViewOwner.delete();
}
await activity.markAsDoneAndScheduleNext({ feedback });
if (chatter && chatter.exists() && chatter.component) {
chatter.reloadParentView();
}
if (reloadFunc) {
reloadFunc();
}
if (webRecord) {
webRecord.model.load({ resId: thread.id });
}
if (activityListViewOwner && activityListViewOwner.exists()) {
activityListViewOwner.popoverViewOwner.delete();
}
},
/**
* Handles keydown on this activity mark done.
@@ -141,6 +149,14 @@ registerModel({
identifying: true,
inverse: 'activityMarkDonePopoverContentView',
}),
reloadFunc: attr({
compute() {
if (this.activityListViewItemOwner) {
return this.activityListViewItemOwner.reloadFunc;
}
return clear();
},
}),
webRecord: attr({
compute() {
if (this.activityListViewItemOwner) {
@@ -94,6 +94,7 @@ registerModel({
* @returns {Promise}
*/
async _performUpload({ files }) {
const reloadFunc = this.activityListViewItemOwner && this.activityListViewItemOwner.reloadFunc;
const webRecord = this.activityListViewItemOwner && this.activityListViewItemOwner.webRecord;
const composer = this.composerView && this.composerView.composer; // save before async
const thread = this.thread; // save before async
@@ -149,15 +150,18 @@ registerModel({
}
}
}
if (activity && activity.exists()) {
await activity.markAsDone({ attachments });
}
if (reloadFunc) {
reloadFunc();
}
if (webRecord) {
webRecord.model.load({ resId: thread.id });
}
if (chatter && chatter.exists() && chatter.shouldReloadParentFromFileChanged) {
chatter.reloadParentView();
}
if (activity && activity.exists()) {
activity.markAsDone({ attachments });
}
},
},
fields: {
+13 -3
View File
@@ -50,9 +50,13 @@ registerModel({
identifying: true,
inverse: 'activityListPopoverView',
}),
activityCellViewOwnerAsActivityList: one('ActivityCellView', {
identifying: true,
inverse: 'activityListPopoverView',
}),
activityListView: one('ActivityListView', {
compute() {
return this.activityButtonViewOwnerAsActivityList ? {} : clear();
return this.activityButtonViewOwnerAsActivityList || this.activityCellViewOwnerAsActivityList ? {} : clear();
},
inverse: 'popoverViewOwner',
}),
@@ -92,6 +96,9 @@ registerModel({
if (this.activityButtonViewOwnerAsActivityList) {
return this.activityButtonViewOwnerAsActivityList.buttonRef;
}
if (this.activityCellViewOwnerAsActivityList) {
return this.activityCellViewOwnerAsActivityList.contentRef;
}
if (this.messageActionViewOwnerAsReaction) {
return this.messageActionViewOwnerAsReaction.actionRef;
}
@@ -171,7 +178,7 @@ registerModel({
if (this.emojiPickerView) {
return this.emojiPickerView;
}
if (this.activityButtonViewOwnerAsActivityList) {
if (this.activityButtonViewOwnerAsActivityList || this.activityCellViewOwnerAsActivityList) {
return this.activityListView;
}
if (this.messageNotificationPopoverContentView) {
@@ -217,7 +224,7 @@ registerModel({
if (this.emojiPickerView) {
return 'EmojiPickerView';
}
if (this.activityButtonViewOwnerAsActivityList) {
if (this.activityButtonViewOwnerAsActivityList || this.activityCellViewOwnerAsActivityList) {
return 'ActivityListView';
}
if (this.messageNotificationPopoverContentView) {
@@ -296,6 +303,9 @@ registerModel({
if (this.activityButtonViewOwnerAsActivityList) {
return 'bottom-start';
}
if (this.activityCellViewOwnerAsActivityList) {
return 'bottom-start';
}
if (this.messageActionViewOwnerAsReaction) {
return 'top';
}
+6 -29
View File
@@ -4,11 +4,10 @@
thead > tr > th:first-of-type {
min-width: 300px;
}
tbody > tr > td, tfoot > tr > td {
cursor: pointer;
}
}
.o_activity_summary_cell {
padding: 0; // to cancel unwanted padding of <td> coming from bootstrap
&.planned {
background-color: map-get($theme-colors, 'success');
}
@@ -18,19 +17,6 @@
&.today {
background-color: map-get($theme-colors, '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_activity_empty_cell {
> i {
display: none;
@@ -44,19 +30,6 @@
}
}
}
.o_activity_btn > .badge {
@include o-position-absolute($bottom: 0, $right: 0);
&.planned {
@extend .text-success;
}
&.overdue {
@extend .text-danger;
}
&.today {
@extend .text-warning;
}
}
}
// it contains a kanban card representing the record
@@ -170,3 +143,7 @@
}
}
}
.o_activity_view_table {
height: 1px; // this value is ignored on the table but it is necessary to ensure h-100 works on descendants to vertically center content inside cells
}
+8 -9
View File
@@ -15,7 +15,7 @@
<div class="dropdown-menu">
<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"
class="o_template_element o_send_mail_template cursor-pointer"
t-att-data-activity-type-id="type[0]"
t-att-data-template-id="template.id"
t-on-click="_onSendMailTemplateClicked">
@@ -44,7 +44,7 @@
</t>
<t t-name="mail.ActivityViewRow" owl="1">
<tr class="o_data_row" t-att-data-res-id="resId">
<tr class="o_data_row h-100" 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.value : '' }}">
<ActivityRecordAdapter Component="widgetComponents.ActivityRecord"
@@ -60,22 +60,21 @@
<t t-set="activityGroup" t-value="props.grouped_activities[resId] and props.grouped_activities[resId][type[0]] or {count: 0, ids: [], state: false}"/>
<t t-set="isCellHidden" t-value="activeFilter.resIds.length and !activeFilter.resIds.includes(resId) and activeFilter.activityTypeId === type[0]"/>
<td t-if="activityGroup.state and !isCellHidden" 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.value : '' }}">
<ActivityCellAdapter Component="widgetComponents.KanbanActivity"
widgetArgs="['activity_ids', props.getKanbanActivityData(activityGroup, resId), { activityType: type[0], isActivityViewCell: true }]"/>
t-attf-class="o_activity_summary_cell h-100 {{activityGroup.state}} {{ activeFilter.resIds.includes(resId) ? 'o_activity_filter_' + activeFilter.state.value : '' }}">
<ActivityCellViewContainer activityIds="activityGroup.ids" activityTypeId="type[0]" closestDeadline="activityGroup.o_closest_deadline" reloadFunc.bind="ev => this.trigger('reload_data')" resId="record.res_id" resModel="record.model"/>
</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"
class="o_activity_summary_cell o_activity_empty_cell cursor-pointer"
t-attf-class="{{ activeFilter.resIds.includes(resId) ? 'o_activity_filter_' + activeFilter.state.value : '' }}"
t-on-click.prevent.stop="_onEmptyCellClicked">
<i title="Create" class="text-center fa fa-plus"/>
<i title="Create" class="text-center fa fa-plus mt-2"/>
</td>
</t>
<t t-name="mail.ActivityViewFooter" owl="1">
<tfoot>
<tr class="o_data_row">
<td class="o_record_selector p-3" t-on-click.prevent.stop="() => this.trigger('schedule_activity')">
<td class="o_record_selector p-3 cursor-pointer" t-on-click.prevent.stop="() => this.trigger('schedule_activity')">
<span class="btn btn-link"><i class="fa fa-plus pe-2"/><span>Schedule activity</span></span>
</td>
</tr>
@@ -84,7 +83,7 @@
<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 table-bordered mb-5 bg-view">
<table t-else="" class="table table-bordered mb-5 bg-view o_activity_view_table">
<t t-call="mail.ActivityViewHeader"/>
<t t-call="mail.ActivityViewBody"/>
<t t-call="mail.ActivityViewFooter"/>
+16 -15
View File
@@ -135,7 +135,7 @@ QUnit.test('activity view: simple activity rendering', async function (assert) {
var today = activityDateFormat(new Date());
assert.ok($activity.find('table tbody tr:first td:nth-child(2).today .o_closest_deadline:contains(' + today + ')').length,
assert.ok($activity.find('table tbody tr:first td:nth-child(2).today .o_ActivityCellView_closestDeadline:contains(' + today + ')').length,
'should contain an activity for today in second cell of first line ' + today);
var td = 'table tbody tr:nth-child(1) td.o_activity_empty_cell';
assert.containsN($activity, td, 2, 'should contain an empty cell as no activity scheduled yet.');
@@ -263,26 +263,27 @@ QUnit.test('activity view: activity widget', async function (assert) {
},
});
await testUtils.dom.click(document.querySelector('.today .o_closest_deadline'));
assert.hasClass(document.querySelector('.today .dropdown-menu.o_activity'), 'show', "dropdown should be displayed");
assert.ok(document.querySelector('.o_activity_color_today').textContent.includes('Today'), "Title should be today");
assert.ok([...document.querySelectorAll('.today .o_activity_title_entry')].filter(el => el.textContent.includes('Template1')).length,
await testUtils.dom.click(document.querySelector('.today .o_ActivityCellView_closestDeadline'));
assert.containsOnce(document.body, '.o_ActivityListView', "dropdown should be displayed");
assert.ok(document.querySelector('.o_ActivityListView_todayTitle').textContent.includes('Today'), "Title should be today");
assert.ok([...document.querySelectorAll('.o_MailTemplate_name')].filter(el => el.textContent.includes('Template1')).length,
"Template1 should be available");
assert.ok([...document.querySelectorAll('.today .o_activity_title_entry')].filter(el => el.textContent.includes('Template2')).length,
assert.ok([...document.querySelectorAll('.o_MailTemplate_name')].filter(el => el.textContent.includes('Template2')).length,
"Template2 should be available");
await testUtils.dom.click(document.querySelector('.o_activity_title_entry[data-activity-id="2"] .o_activity_template_preview'));
await testUtils.dom.click(document.querySelector('.o_activity_title_entry[data-activity-id="2"] .o_activity_template_send'));
await testUtils.dom.click(document.querySelector('.overdue .o_closest_deadline'));
assert.notOk(document.querySelector('.overdue .o_activity_template_preview'),
await testUtils.dom.click(document.querySelector('.o_MailTemplate_preview'));
await testUtils.dom.click(document.querySelector('.today .o_ActivityCellView_closestDeadline'));
await testUtils.dom.click(document.querySelector('.o_MailTemplate_send'));
await testUtils.dom.click(document.querySelector('.overdue .o_ActivityCellView_closestDeadline'));
assert.containsNone(document.body, '.o_MailTemplate_name',
"No template should be available");
await testUtils.dom.click(document.querySelector('.overdue .o_schedule_activity'));
await testUtils.dom.click(document.querySelector('.overdue .o_closest_deadline'));
await testUtils.dom.click(document.querySelector('.overdue .o_mark_as_done'));
document.querySelector('.overdue #activity_feedback').value = "feedback2";
await testUtils.dom.click(document.querySelector('.o_ActivityListView_addActivityButton'));
await testUtils.dom.click(document.querySelector('.overdue .o_ActivityCellView_closestDeadline'));
await testUtils.dom.click(document.querySelector('.o_ActivityListViewItem_markAsDone'));
document.querySelector('.o_ActivityMarkDonePopoverContent_feedback').value = "feedback2";
await testUtils.dom.click(document.querySelector('.overdue .o_activity_popover_done_next'));
await testUtils.dom.click(document.querySelector('.o_ActivityMarkDonePopoverContent_doneScheduleNextButton'));
assert.verifySteps([
"do_action_compose",
"activity_send_mail",