[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:
@@ -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>
|
||||
+101
@@ -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',
|
||||
});
|
||||
+8
@@ -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>
|
||||
|
||||
+1
-1
@@ -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,
|
||||
};
|
||||
|
||||
+1
-1
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user