[IMP] mail, *: remove legacy code from activity systray
* = calendar, crm, im_livechat, mass_mailing_sms, note, test_discuss_full,
test_mail
task-2920645
closes odoo/odoo#96114
Related: odoo/enterprise#29794
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -47,7 +47,6 @@ If you need to manage your meetings, you should install the CRM module.
|
||||
'calendar/static/src/js/calendar_controller.js',
|
||||
'calendar/static/src/js/calendar_model.js',
|
||||
'calendar/static/src/js/calendar_view.js',
|
||||
'calendar/static/src/js/systray_activity_menu.js',
|
||||
'calendar/static/src/js/services/calendar_notification_service.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
|
||||
@@ -44,11 +44,12 @@ class Users(models.Model):
|
||||
if meetings_lines:
|
||||
meeting_label = _("Today's Meetings")
|
||||
meetings_systray = {
|
||||
'id': self.env['ir.model']._get('calendar.event').id,
|
||||
'type': 'meeting',
|
||||
'name': meeting_label,
|
||||
'model': 'calendar.event',
|
||||
'icon': modules.module.get_module_icon(self.env['calendar.event']._original_module),
|
||||
'meetings': meetings_lines,
|
||||
'meetings': [('insert-and-replace', meetings_lines)],
|
||||
}
|
||||
res.insert(0, meetings_systray)
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.ActivityMenuView" t-inherit-mode="extension">
|
||||
<xpath expr="//*[hasclass('o_ActivityMenuView_activityGroupTitle')]" position="after">
|
||||
<div t-if="activityGroupView.activityGroup.type == 'meeting'">
|
||||
<t t-set="is_next_meeting" t-value="true"/>
|
||||
<t t-foreach="activityGroupView.activityGroup.meetings" t-as="meeting" t-key="meeting.localId">
|
||||
<div class="d-flex">
|
||||
<span class="flex-grow-1" t-att-class="!meeting.allday and is_next_meeting ? 'o_meeting_filter o_meeting_bold' : 'o_meeting_filter'" t-att-data-res_model="activityGroupView.activityGroup.irModel.model" t-att-data-res_id="meeting.id" t-att-data-model_name="activityGroupView.activityGroup.irModel.name" t-att-title="meeting.name">
|
||||
<span><t t-esc="meeting.name"/></span>
|
||||
</span>
|
||||
<span t-if="meeting.formattedStart">
|
||||
<t t-if="meeting.allday">All Day</t>
|
||||
<t t-else=''>
|
||||
<t t-set="is_next_meeting" t-value="false"/>
|
||||
<t t-esc="meeting.formattedStart"/>
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -1,53 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import ActivityMenu from '@mail/js/systray/systray_activity_menu';
|
||||
import fieldUtils from 'web.field_utils';
|
||||
|
||||
ActivityMenu.include({
|
||||
|
||||
//-----------------------------------------
|
||||
// Private
|
||||
//-----------------------------------------
|
||||
|
||||
/**
|
||||
* parse date to server value
|
||||
*
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_getActivityData: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
var meeting = _.find(self._activities, {type: 'meeting'});
|
||||
if (meeting && meeting.meetings) {
|
||||
_.each(meeting.meetings, function (res) {
|
||||
res.start = fieldUtils.parse.datetime(res.start, false, {isUTC: true});
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
//-----------------------------------------
|
||||
// Handlers
|
||||
//-----------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_onActivityFilterClick: function (ev) {
|
||||
var $el = $(ev.currentTarget);
|
||||
var data = _.extend({}, $el.data());
|
||||
if (data.res_model === "calendar.event" && data.filter === "my") {
|
||||
this.do_action('calendar.action_calendar_event', {
|
||||
additional_context: {
|
||||
default_mode: 'day',
|
||||
search_default_mymeetings: 1,
|
||||
},
|
||||
clear_breadcrumbs: true,
|
||||
});
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { addFields, addOnChanges, addRecordMethods, patchModelMethods } from '@mail/model/model_core';
|
||||
import { many } from '@mail/model/model_field';
|
||||
import { OnChange } from '@mail/model/model_onchange';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/activity_group';
|
||||
|
||||
patchModelMethods('ActivityGroup', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
convertData(data) {
|
||||
const data2 = this._super(data);
|
||||
data2.meetings = data.meetings;
|
||||
return data2;
|
||||
},
|
||||
});
|
||||
|
||||
addRecordMethods('ActivityGroup', {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeMeetings() {
|
||||
if (this.type === 'meeting' && this.meetings.length === 0) {
|
||||
this.delete();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
addFields('ActivityGroup', {
|
||||
meetings: many('calendar.event'),
|
||||
});
|
||||
|
||||
addOnChanges('ActivityGroup', [
|
||||
new OnChange({
|
||||
dependencies: ['meetings', 'type'],
|
||||
methodName: '_onChangeMeetings',
|
||||
}),
|
||||
]);
|
||||
@@ -0,0 +1,27 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patchRecordMethods } from '@mail/model/model_core';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/activity_group_view';
|
||||
|
||||
patchRecordMethods('ActivityGroupView', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onClickFilterButton(ev) {
|
||||
const $el = $(ev.currentTarget);
|
||||
const data = _.extend({}, $el.data());
|
||||
if (data.res_model === "calendar.event" && data.filter === "my") {
|
||||
this.activityMenuViewOwner.update({ isOpen: false });
|
||||
this.env.services['action'].doAction('calendar.action_calendar_event', {
|
||||
additionalContext: {
|
||||
default_mode: 'day',
|
||||
search_default_mymeetings: 1,
|
||||
},
|
||||
clearBreadcrumbs: true,
|
||||
});
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
|
||||
import fieldUtils from 'web.field_utils';
|
||||
import { getLangTimeFormat } from 'web.time';
|
||||
|
||||
registerModel({
|
||||
name: 'calendar.event',
|
||||
identifyingFields: ['id'],
|
||||
recordMethods: {
|
||||
_computeFormattedStart() {
|
||||
if (!this.start) {
|
||||
return clear();
|
||||
}
|
||||
return moment(fieldUtils.parse.datetime(this.start, false, { isUTC: true })).local().format(getLangTimeFormat());
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
allday: attr(),
|
||||
attendee_status: attr(),
|
||||
formattedStart: attr({
|
||||
compute: '_computeFormattedStart',
|
||||
}),
|
||||
id: attr({
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
name: attr(),
|
||||
start: attr(),
|
||||
},
|
||||
});
|
||||
@@ -57,7 +57,7 @@
|
||||
}
|
||||
.o_meeting_filter {
|
||||
@include o-text-overflow();
|
||||
width: 64%;
|
||||
min-width: 0;
|
||||
color: grey;
|
||||
vertical-align: top;
|
||||
&.o_meeting_bold {
|
||||
|
||||
@@ -25,28 +25,6 @@
|
||||
|
||||
</div>
|
||||
|
||||
<t t-extend="mail.systray.ActivityMenu.Previews">
|
||||
<t t-jquery="div.o_preview_title" t-operation="after">
|
||||
<div t-if="activity and activity.type == 'meeting'">
|
||||
<t t-set="is_next_meeting" t-value="true"/>
|
||||
<t t-foreach="activity.meetings" t-as="meeting">
|
||||
<div>
|
||||
<span t-att-class="!meeting.allday and is_next_meeting ? 'o_meeting_filter o_meeting_bold' : 'o_meeting_filter'" t-att-data-res_model="activity.model" t-att-data-res_id="meeting.id" t-att-data-model_name="activity.name" t-att-title="meeting.name">
|
||||
<span><t t-esc="meeting.name"/></span>
|
||||
</span>
|
||||
<span t-if="meeting.start" class="float-end">
|
||||
<t t-if="meeting.allday">All Day</t>
|
||||
<t t-else=''>
|
||||
<t t-set="is_next_meeting" t-value="false"/>
|
||||
<t t-esc="moment(meeting.start).local().format(Time.strftime_to_moment_format(_t.database.parameters.time_format))"/>
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="Calendar.attendee.status.popover" t-extend="CalendarView.event.popover">
|
||||
<t t-jquery=".o_cw_popover_delete" t-operation="after">
|
||||
<div t-if="widget.displayAttendeeAnswerChoice()" class="btn-group o-calendar-attendee-status ms-2">
|
||||
|
||||
@@ -81,7 +81,8 @@ patch(MockServer.prototype, 'calendar', {
|
||||
).filter(meetingLine => meetingLine['attendee_status'] !== 'declined');
|
||||
if (meetingsLines.length) {
|
||||
activities.unshift({
|
||||
meetings: meetingsLines,
|
||||
id: 'calendar.event', // for simplicity
|
||||
meetings: [['insert-and-replace', meetingsLines]],
|
||||
model: 'calendar.event',
|
||||
name: 'Today\'s Meetings',
|
||||
type: 'meeting',
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import testUtils from 'web.test_utils';
|
||||
import { patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
|
||||
QUnit.module('calendar', {}, function () {
|
||||
@@ -28,21 +27,22 @@ QUnit.test('activity menu widget:today meetings', async function (assert) {
|
||||
attendee_ids: [calendarAttendeeId1],
|
||||
},
|
||||
]);
|
||||
const { env } = await start();
|
||||
assert.containsOnce(document.body, '.o_mail_systray_item', 'should contain an instance of widget');
|
||||
const { click, env } = await start();
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView', 'should contain an instance of widget');
|
||||
|
||||
await testUtils.dom.click(document.querySelector('.dropdown-toggle[title="Activities"]'));
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
|
||||
patchWithCleanup(env.services.action, {
|
||||
doAction(action) {
|
||||
assert.strictEqual(action, "calendar.action_calendar_event", 'should open meeting calendar view in day mode');
|
||||
},
|
||||
});
|
||||
await testUtils.dom.click(document.querySelector('.o_mail_preview'));
|
||||
|
||||
assert.ok(document.querySelector('.o_meeting_filter'), "should be a meeting");
|
||||
assert.containsN(document.body, '.o_meeting_filter', 2, 'there should be 2 meetings');
|
||||
assert.hasClass(document.querySelector('.o_meeting_filter'), 'o_meeting_bold', 'this meeting is yet to start');
|
||||
assert.doesNotHaveClass(document.querySelectorAll('.o_meeting_filter')[1], 'o_meeting_bold', 'this meeting has been started');
|
||||
|
||||
await click('.o_ActivityMenuView_activityGroup');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,6 +70,9 @@
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'assets': {
|
||||
'mail.assets_messaging': [
|
||||
'crm/static/src/models/*.js',
|
||||
],
|
||||
'web.assets_qweb': [
|
||||
'crm/static/src/xml/crm_kanban.xml',
|
||||
'crm/static/src/xml/forecast_kanban.xml',
|
||||
@@ -78,7 +81,6 @@
|
||||
'crm/static/src/js/crm_form.js',
|
||||
'crm/static/src/js/crm_kanban.js',
|
||||
'crm/static/src/js/forecast/*',
|
||||
'crm/static/src/js/systray_activity_menu.js',
|
||||
'crm/static/src/js/tours/crm.js',
|
||||
'crm/static/src/scss/crm.scss',
|
||||
'crm/static/src/scss/crm_team_member_views.scss',
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import ActivityMenu from '@mail/js/systray/systray_activity_menu';
|
||||
|
||||
ActivityMenu.include({
|
||||
|
||||
//--------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_getViewsList(model) {
|
||||
if (model === "crm.lead") {
|
||||
return [[false, 'list'], [false, 'kanban'],
|
||||
[false, 'form'], [false, 'calendar'],
|
||||
[false, 'pivot'], [false, 'graph'],
|
||||
[false, 'activity']
|
||||
];
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
//-----------------------------------------
|
||||
// Handlers
|
||||
//-----------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_onActivityFilterClick: function (event) {
|
||||
// fetch the data from the button otherwise fetch the ones from the parent (.o_mail_preview).
|
||||
var data = _.extend({}, $(event.currentTarget).data(), $(event.target).data());
|
||||
var context = {};
|
||||
if (data.res_model === "crm.lead") {
|
||||
if (data.filter === 'my') {
|
||||
context['search_default_activities_overdue'] = 1;
|
||||
context['search_default_activities_today'] = 1;
|
||||
} else {
|
||||
context['search_default_activities_' + data.filter] = 1;
|
||||
}
|
||||
// Necessary because activity_ids of mail.activity.mixin has auto_join
|
||||
// So, duplicates are faking the count and "Load more" doesn't show up
|
||||
context['force_search_count'] = 1;
|
||||
this.do_action('crm.crm_lead_action_my_activities', {
|
||||
additional_context: context,
|
||||
clear_breadcrumbs: true,
|
||||
});
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patchRecordMethods } from '@mail/model/model_core';
|
||||
// ensure that the model definition is loaded before the patch
|
||||
import '@mail/models/activity_group_view';
|
||||
|
||||
patchRecordMethods('ActivityGroupView', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onClickFilterButton(ev) {
|
||||
// fetch the data from the button otherwise fetch the ones from the parent (.o_ActivityMenuView_activityGroup).
|
||||
var data = _.extend({}, $(ev.currentTarget).data(), $(ev.target).data());
|
||||
var context = {};
|
||||
if (data.res_model === "crm.lead") {
|
||||
this.activityMenuViewOwner.update({ isOpen: false });
|
||||
if (data.filter === 'my') {
|
||||
context['search_default_activities_overdue'] = 1;
|
||||
context['search_default_activities_today'] = 1;
|
||||
} else {
|
||||
context['search_default_activities_' + data.filter] = 1;
|
||||
}
|
||||
// Necessary because activity_ids of mail.activity.mixin has auto_join
|
||||
// So, duplicates are faking the count and "Load more" doesn't show up
|
||||
context['force_search_count'] = 1;
|
||||
this.env.services['action'].doAction('crm.crm_lead_action_my_activities', {
|
||||
additionalContext: context,
|
||||
clearBreadcrumbs: true,
|
||||
});
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { patchRecordMethods } from "@mail/model/model_core";
|
||||
import "@mail/models/ir_model"; // ensure the model definition is loaded before the patch
|
||||
|
||||
patchRecordMethods("ir.model", {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeAvailableWebViews() {
|
||||
if (this.model === "crm.lead") {
|
||||
return [
|
||||
'list',
|
||||
'kanban',
|
||||
'form',
|
||||
'calendar',
|
||||
'pivot',
|
||||
'graph',
|
||||
'activity',
|
||||
];
|
||||
}
|
||||
return this._super();
|
||||
},
|
||||
});
|
||||
@@ -96,7 +96,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
|
||||
|
||||
.o_thread_window_buttons {
|
||||
flex: 0 0 auto;
|
||||
.o_thread_window_close, .o_thread_window_expand {
|
||||
.o_thread_window_close {
|
||||
color: white;
|
||||
padding: 0px 3px;
|
||||
margin-left: 5px;
|
||||
@@ -161,7 +161,7 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
|
||||
font-size: smaller;
|
||||
}
|
||||
}
|
||||
&.show .o_thread_window_dropdown_toggler .o_total_unread_counter, .o_thread_window_expand {
|
||||
&.show .o_thread_window_dropdown_toggler .o_total_unread_counter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -193,10 +193,6 @@ $o-mail-thread-window-zindex: $zindex-modal + 1 !default;
|
||||
z-index: 1101; // blockUI's z-index is 1100
|
||||
}
|
||||
|
||||
.o_no_thread_window .o_thread_window {
|
||||
display: none;
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
// legacy: mail/thread.scss
|
||||
//-------------------------------------
|
||||
|
||||
@@ -226,6 +226,7 @@ class Users(models.Model):
|
||||
module = self.env[activity['model']]._original_module
|
||||
icon = module and modules.module.get_module_icon(module)
|
||||
user_activities[activity['model']] = {
|
||||
'id': activity['id'],
|
||||
'name': model_names[activity['id']],
|
||||
'model': activity['model'],
|
||||
'type': 'activity',
|
||||
|
||||
+16
-15
@@ -1,26 +1,27 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import ActivityMenu from '@mail/js/systray/systray_activity_menu';
|
||||
|
||||
import { ComponentAdapter } from "web.OwlCompatibility";
|
||||
// ensure components are registered beforehand.
|
||||
import '@mail/components/activity_menu_view/activity_menu_view';
|
||||
import { getMessagingComponent } from "@mail/utils/messaging_component";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
class ActivityMenuAdapter extends ComponentAdapter {
|
||||
setup() {
|
||||
this.env = owl.Component.env;
|
||||
super.setup();
|
||||
}
|
||||
}
|
||||
|
||||
export class ActivityMenuContainer extends Component {
|
||||
get activityMenuWidget() {
|
||||
return ActivityMenu;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
this.env.services.messaging.modelManager.messagingCreatedPromise.then(() => {
|
||||
this.activityMenuView = this.env.services.messaging.modelManager.messaging.models['ActivityMenuView'].insert();
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(ActivityMenuContainer, {
|
||||
components: { ActivityMenuAdapter },
|
||||
components: { ActivityMenuView: getMessagingComponent('ActivityMenuView') },
|
||||
template: 'mail.ActivityMenuContainer',
|
||||
});
|
||||
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.ActivityMenuContainer" owl="1">
|
||||
<ActivityMenuAdapter Component="activityMenuWidget"/>
|
||||
<t t-if="activityMenuView">
|
||||
<ActivityMenuView record="activityMenuView"/>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
export class ActivityMenuView extends Component {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component' });
|
||||
}
|
||||
/**
|
||||
* @returns {ActivityMenuView}
|
||||
*/
|
||||
get activityMenuView() {
|
||||
return this.props.record;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(ActivityMenuView, {
|
||||
props: { record: Object },
|
||||
template: 'mail.ActivityMenuView',
|
||||
});
|
||||
|
||||
registerMessagingComponent(ActivityMenuView);
|
||||
@@ -0,0 +1,137 @@
|
||||
.o_ActivityMenuView_activityGroups {
|
||||
flex: 0 1 auto;
|
||||
max-height: 400px;
|
||||
min-height: 50px;
|
||||
overflow-y: auto;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
max-height: none;
|
||||
padding-bottom: 52px; // leave space for tabs
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroup {
|
||||
display: flex;
|
||||
background-color: transparent;
|
||||
color: $o-main-text-color;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
&:hover {
|
||||
background-color: map-get($theme-colors, 'light');
|
||||
.o_ActivityMenuView_activityGroupName {
|
||||
color: $headings-color;
|
||||
}
|
||||
}
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid map-get($grays, '400');
|
||||
}
|
||||
@include media-breakpoint-down(md) {
|
||||
padding: $o-mail-chatter-mobile-gap;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
min-height: 50px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupActionButtons {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-flow: row-reverse wrap;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupActionButton {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupFilterButton {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupIconContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
> img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.fa-circle-o {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupInfo {
|
||||
flex: 1 1 100%;
|
||||
overflow: hidden;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-left: $o-mail-chatter-mobile-gap;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupName {
|
||||
flex: 0 1 auto;
|
||||
@include o-text-overflow;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupNoCount {
|
||||
cursor: initial;
|
||||
align-items: center;
|
||||
color: grey;
|
||||
opacity: 0.5;
|
||||
padding: 3px;
|
||||
min-height: inherit;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_activityGroupTitle {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_dropdownMenu {
|
||||
direction: ltr;
|
||||
width: 350px;
|
||||
padding: 0;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
position: fixed;
|
||||
top: $o-mail-chat-window-header-height-mobile;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
.o_ActivityMenuView_noActivity {
|
||||
cursor: initial;
|
||||
align-items: center;
|
||||
color: grey;
|
||||
opacity: 0.5;
|
||||
padding: 3px;
|
||||
min-height: inherit;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.ActivityMenuView" owl="1">
|
||||
<div class="o_ActivityMenuView dropdown" t-ref="root">
|
||||
<a class="o_ActivityMenuView_dropdownToggle dropdown-toggle o-no-caret o-dropdown--narrow" t-att-aria-expanded="activityMenuView.isOpen ? 'true' : 'false'" title="Activities" href="#" role="button" t-on-click="activityMenuView.onClickDropdownToggle">
|
||||
<i class="fa fa-lg fa-clock-o" role="img" aria-label="Activities"/> <span t-if="activityMenuView.counter > 0" class="o_ActivityMenuView_counter badge" t-esc="activityMenuView.counter"/>
|
||||
</a>
|
||||
<div t-if="activityMenuView.isOpen" class="o_ActivityMenuView_dropdownMenu o-dropdown-menu dropdown-menu-end show" role="menu">
|
||||
<div class="o_ActivityMenuView_activityGroups">
|
||||
<t t-if="activityMenuView.activityGroupViews.length === 0">
|
||||
<div class="o_ActivityMenuView_noActivity dropdown-item-text text-center d-flex justify-content-center">
|
||||
<span>Congratulations, you're done with your activities.</span>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="activityMenuView.activityGroupViews" t-as="activityGroupView" t-key="activityGroupView.localId" name="activityGroupLoop">
|
||||
<div class="o_ActivityMenuView_activityGroup" t-att-data-res_model="activityGroupView.activityGroup.irModel.model" t-att-data-model_name="activityGroupView.activityGroup.irModel.name" t-att-data-domain="activityGroupView.activityGroup.domain" data-filter='my' t-att-data-activity-group-view-local-id="activityGroupView.localId" t-on-click="activityGroupView.onClickFilterButton">
|
||||
<div t-if="activityGroupView.activityGroup.irModel.iconUrl" class="o_ActivityMenuView_activityGroupIconContainer">
|
||||
<img t-att-src="activityGroupView.activityGroup.irModel.iconUrl" alt="Activity"/>
|
||||
</div>
|
||||
<div class="o_ActivityMenuView_activityGroupInfo">
|
||||
<div class="o_ActivityMenuView_activityGroupTitle">
|
||||
<span class="o_ActivityMenuView_activityGroupName">
|
||||
<t t-esc="activityGroupView.activityGroup.irModel.name"/>
|
||||
</span>
|
||||
<div t-if="activityGroupView.activityGroup.actions" class="o_ActivityMenuView_activityGroupActionButtons">
|
||||
<t t-foreach="activityGroupView.activityGroup.actions" t-as="action" t-key="action.name">
|
||||
<button type="button"
|
||||
t-att-title="action.name"
|
||||
t-att-class="'o_ActivityMenuView_activityGroupActionButton btn btn-link fa ' + action.icon"
|
||||
t-att-data-action_xmlid="action.action_xmlid"
|
||||
t-att-data-res_model="activityGroupView.activityGroup.irModel.model"
|
||||
t-att-data-model_name="activityGroupView.activityGroup.irModel.name"
|
||||
t-att-data-domain="activityGroupView.activityGroup.domain"
|
||||
t-on-click="activityGroupView.onClick"
|
||||
>
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="activityGroupView.activityGroup.type == 'activity'">
|
||||
<button t-if="activityGroupView.activityGroup.overdue_count" type="button" class="o_ActivityMenuView_activityGroupFilterButton btn btn-link mr16" t-att-data-res_model="activityGroupView.activityGroup.irModel.model" t-att-data-model_name="activityGroupView.activityGroup.irModel.name" data-filter='overdue'><t t-esc="activityGroupView.activityGroup.overdue_count"/> Late </button>
|
||||
<span t-if="!activityGroupView.activityGroup.overdue_count" class="o_ActivityMenuView_activityGroupNoCount mr16">0 Late </span>
|
||||
<button t-if="activityGroupView.activityGroup.today_count" type="button" class="o_ActivityMenuView_activityGroupFilterButton btn btn-link mr16" t-att-data-res_model="activityGroupView.activityGroup.irModel.model" t-att-data-model_name="activityGroupView.activityGroup.irModel.name" data-filter='today'> <t t-esc="activityGroupView.activityGroup.today_count"/> Today </button>
|
||||
<span t-if="!activityGroupView.activityGroup.today_count" class="o_ActivityMenuView_activityGroupNoCount mr16">0 Today </span>
|
||||
<button t-if="activityGroupView.activityGroup.planned_count" type="button" class="o_ActivityMenuView_activityGroupFilterButton btn btn-link float-end" t-att-data-res_model="activityGroupView.activityGroup.irModel.model" t-att-data-model_name="activityGroupView.activityGroup.irModel.name" data-filter='upcoming_all'> <t t-esc="activityGroupView.activityGroup.planned_count"/> Future </button>
|
||||
<span t-if="!activityGroupView.activityGroup.planned_count" class="o_ActivityMenuView_activityGroupNoCount float-end">0 Future</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,199 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { qweb as QWeb } from 'web.core';
|
||||
import config from "web.config";
|
||||
import session from 'web.session';
|
||||
import Widget from 'web.Widget';
|
||||
import Time from 'web.time';
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
/**
|
||||
* Menu item appended in the systray part of the navbar, redirects to the next
|
||||
* activities of all app
|
||||
*/
|
||||
var ActivityMenu = Widget.extend({
|
||||
name: 'activity_menu',
|
||||
template:'mail.systray.ActivityMenu',
|
||||
events: {
|
||||
'click .o_mail_activity_action': '_onActivityActionClick',
|
||||
'click .o_mail_preview': '_onActivityFilterClick',
|
||||
'show.bs.dropdown': '_onActivityMenuShow',
|
||||
'hide.bs.dropdown': '_onActivityMenuHide',
|
||||
},
|
||||
start: function () {
|
||||
if (config.device.isMobile) {
|
||||
this.el.querySelector('.dropdown-toggle').dataset.bsDisplay = "static";
|
||||
}
|
||||
this._$activitiesPreview = this.$('.o_mail_systray_dropdown_items');
|
||||
Component.env.bus.on('activity_updated', this, this._updateCounter);
|
||||
this._updateCounter();
|
||||
this._updateActivityPreview();
|
||||
return this._super();
|
||||
},
|
||||
//--------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------
|
||||
/**
|
||||
* Make RPC and get current user's activity details
|
||||
* @private
|
||||
*/
|
||||
_getActivityData: function () {
|
||||
var self = this;
|
||||
|
||||
return self._rpc({
|
||||
model: 'res.users',
|
||||
method: 'systray_get_activities',
|
||||
args: [],
|
||||
kwargs: {context: session.user_context},
|
||||
}).then(function (data) {
|
||||
self._activities = data;
|
||||
self.activityCounter = _.reduce(data, function (total_count, p_data) { return total_count + p_data.total_count || 0; }, 0);
|
||||
self.$('.o_notification_counter').text(self.activityCounter);
|
||||
self.$el.toggleClass('o_no_notification', !self.activityCounter);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Get particular model view to redirect on click of activity scheduled on that model.
|
||||
* @private
|
||||
* @param {string} model
|
||||
*/
|
||||
_getActivityModelViewID: function (model) {
|
||||
return this._rpc({
|
||||
model: model,
|
||||
method: 'get_activity_view_id'
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Return views to display when coming from systray depending on the model.
|
||||
*
|
||||
* @private
|
||||
* @param {string} model
|
||||
* @returns {Array[]} output the list of views to display.
|
||||
*/
|
||||
_getViewsList(model) {
|
||||
return [[false, 'kanban'], [false, 'list'], [false, 'form']];
|
||||
},
|
||||
/**
|
||||
* Update(render) activity system tray view on activity updation.
|
||||
* @private
|
||||
*/
|
||||
_updateActivityPreview: function () {
|
||||
var self = this;
|
||||
self._getActivityData().then(function (){
|
||||
self._$activitiesPreview.html(QWeb.render('mail.systray.ActivityMenu.Previews', {
|
||||
widget: self,
|
||||
Time: Time
|
||||
}));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* update counter based on activity status(created or Done)
|
||||
* @private
|
||||
* @param {Object} [data] key, value to decide activity created or deleted
|
||||
* @param {String} [data.type] notification type
|
||||
* @param {Boolean} [data.activity_deleted] when activity deleted
|
||||
* @param {Boolean} [data.activity_created] when activity created
|
||||
*/
|
||||
_updateCounter: function (data) {
|
||||
if (data) {
|
||||
if (data.activity_created) {
|
||||
this.activityCounter ++;
|
||||
}
|
||||
if (data.activity_deleted && this.activityCounter > 0) {
|
||||
this.activityCounter --;
|
||||
}
|
||||
this.$('.o_notification_counter').text(this.activityCounter);
|
||||
this.$el.toggleClass('o_no_notification', !this.activityCounter);
|
||||
}
|
||||
},
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Handlers
|
||||
//------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Redirect to specific action given its xml id or to the activity
|
||||
* view of the current model if no xml id is provided
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onActivityActionClick: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.$('.dropdown-toggle').dropdown('toggle');
|
||||
var targetAction = $(ev.currentTarget);
|
||||
var actionXmlid = targetAction.data('action_xmlid');
|
||||
if (actionXmlid) {
|
||||
this.do_action(actionXmlid);
|
||||
} else {
|
||||
var domain = [['activity_ids.user_id', '=', session.uid]]
|
||||
if (targetAction.data('domain')) {
|
||||
domain = domain.concat(targetAction.data('domain'))
|
||||
}
|
||||
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
name: targetAction.data('model_name'),
|
||||
views: [[false, 'activity'], [false, 'kanban'], [false, 'list'], [false, 'form']],
|
||||
view_mode: 'activity',
|
||||
res_model: targetAction.data('res_model'),
|
||||
domain: domain,
|
||||
}, {
|
||||
clear_breadcrumbs: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Redirect to particular model view
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onActivityFilterClick: function (event) {
|
||||
// fetch the data from the button otherwise fetch the ones from the parent (.o_mail_preview).
|
||||
var data = _.extend({}, $(event.currentTarget).data(), $(event.target).data());
|
||||
var context = {};
|
||||
if (data.filter === 'my') {
|
||||
context['search_default_activities_overdue'] = 1;
|
||||
context['search_default_activities_today'] = 1;
|
||||
} else {
|
||||
context['search_default_activities_' + data.filter] = 1;
|
||||
}
|
||||
// Necessary because activity_ids of mail.activity.mixin has auto_join
|
||||
// So, duplicates are faking the count and "Load more" doesn't show up
|
||||
context['force_search_count'] = 1;
|
||||
|
||||
var domain = [['activity_ids.user_id', '=', session.uid]]
|
||||
if (data.domain) {
|
||||
domain = domain.concat(data.domain)
|
||||
}
|
||||
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
name: data.model_name,
|
||||
res_model: data.res_model,
|
||||
views: this._getViewsList(data.res_model),
|
||||
search_view_id: [false],
|
||||
domain: domain,
|
||||
context:context,
|
||||
}, {
|
||||
clear_breadcrumbs: true,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onActivityMenuShow: function () {
|
||||
document.body.classList.add('modal-open');
|
||||
this._updateActivityPreview();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onActivityMenuHide: function () {
|
||||
document.body.classList.remove('modal-open');
|
||||
},
|
||||
});
|
||||
|
||||
export default ActivityMenu;
|
||||
@@ -895,6 +895,9 @@ export class ModelManager {
|
||||
listener,
|
||||
reason: `first call on ${record}`,
|
||||
});
|
||||
if (!record.exists()) {
|
||||
break; // onChange might have deleted the record, other onChange shouldn't be executed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,9 @@ registerModel({
|
||||
inverse: 'activity',
|
||||
isCausal: true,
|
||||
}),
|
||||
assignee: one('User'),
|
||||
assignee: one('User', {
|
||||
inverse: 'activitiesAsAssignee',
|
||||
}),
|
||||
attachments: many('Attachment', {
|
||||
inverse: 'activities',
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, many, one } from '@mail/model/model_field';
|
||||
import { insertAndReplace } from '@mail/model/model_field_command';
|
||||
import { OnChange } from '@mail/model/model_onchange';
|
||||
|
||||
registerModel({
|
||||
name: 'ActivityGroup',
|
||||
identifyingFields: ['irModel'],
|
||||
modelMethods: {
|
||||
convertData(data) {
|
||||
return {
|
||||
actions: data.actions,
|
||||
domain: data.domain,
|
||||
irModel: insertAndReplace({
|
||||
iconUrl: data.icon,
|
||||
id: data.id,
|
||||
model: data.model,
|
||||
name: data.name,
|
||||
}),
|
||||
overdue_count: data.overdue_count,
|
||||
planned_count: data.planned_count,
|
||||
today_count: data.today_count,
|
||||
total_count: data.total_count,
|
||||
type: data.type,
|
||||
};
|
||||
},
|
||||
},
|
||||
recordMethods: {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeTotalCount() {
|
||||
if (this.type === 'activity' && this.total_count === 0) {
|
||||
this.delete();
|
||||
}
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
actions: attr(),
|
||||
activityGroupViews: many('ActivityGroupView', {
|
||||
inverse: 'activityGroup',
|
||||
isCausal: true,
|
||||
}),
|
||||
domain: attr(),
|
||||
irModel: one('ir.model', {
|
||||
inverse: 'activityGroup',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
overdue_count: attr({
|
||||
default: 0,
|
||||
}),
|
||||
planned_count: attr({
|
||||
default: 0,
|
||||
}),
|
||||
today_count: attr({
|
||||
default: 0,
|
||||
}),
|
||||
total_count: attr({
|
||||
default: 0,
|
||||
}),
|
||||
type: attr(),
|
||||
},
|
||||
onChanges: [
|
||||
new OnChange({
|
||||
dependencies: ['total_count', 'type'],
|
||||
methodName: '_onChangeTotalCount',
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { one } from '@mail/model/model_field';
|
||||
|
||||
import session from 'web.session';
|
||||
|
||||
registerModel({
|
||||
name: 'ActivityGroupView',
|
||||
identifyingFields: ['activityMenuViewOwner', 'activityGroup'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClick(ev) {
|
||||
ev.stopPropagation();
|
||||
this.activityMenuViewOwner.update({ isOpen: false });
|
||||
const targetAction = $(ev.currentTarget);
|
||||
const actionXmlid = targetAction.data('action_xmlid');
|
||||
if (actionXmlid) {
|
||||
this.env.services.action.doAction(actionXmlid);
|
||||
} else {
|
||||
let domain = [['activity_ids.user_id', '=', session.uid]];
|
||||
if (targetAction.data('domain')) {
|
||||
domain = domain.concat(targetAction.data('domain'));
|
||||
}
|
||||
this.env.services['action'].doAction(
|
||||
{
|
||||
domain,
|
||||
name: targetAction.data('model_name'),
|
||||
res_model: targetAction.data('res_model'),
|
||||
type: 'ir.actions.act_window',
|
||||
views: this.activityGroup.irModel.availableWebViews.map(viewName => [false, viewName]),
|
||||
},
|
||||
{
|
||||
clearBreadcrumbs: true,
|
||||
viewType: 'activity',
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickFilterButton(ev) {
|
||||
this.activityMenuViewOwner.update({ isOpen: false });
|
||||
// fetch the data from the button otherwise fetch the ones from the parent (.o_ActivityMenuView_activityGroup).
|
||||
const data = _.extend({}, $(ev.currentTarget).data(), $(ev.target).data());
|
||||
const context = {};
|
||||
if (data.filter === 'my') {
|
||||
context['search_default_activities_overdue'] = 1;
|
||||
context['search_default_activities_today'] = 1;
|
||||
} else {
|
||||
context['search_default_activities_' + data.filter] = 1;
|
||||
}
|
||||
// Necessary because activity_ids of mail.activity.mixin has auto_join
|
||||
// So, duplicates are faking the count and "Load more" doesn't show up
|
||||
context['force_search_count'] = 1;
|
||||
let domain = [['activity_ids.user_id', '=', session.uid]];
|
||||
if (data.domain) {
|
||||
domain = domain.concat(data.domain);
|
||||
}
|
||||
this.env.services['action'].doAction(
|
||||
{
|
||||
context,
|
||||
domain,
|
||||
name: data.model_name,
|
||||
res_model: data.res_model,
|
||||
search_view_id: [false],
|
||||
type: 'ir.actions.act_window',
|
||||
views: this.activityGroup.irModel.availableWebViews.map(viewName => [false, viewName]),
|
||||
},
|
||||
{
|
||||
clearBreadcrumbs: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activityGroup: one('ActivityGroup', {
|
||||
inverse: 'activityGroupViews',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
activityMenuViewOwner: one('ActivityMenuView', {
|
||||
inverse: 'activityGroupViews',
|
||||
readonly: true,
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, many } from '@mail/model/model_field';
|
||||
import { insertAndReplace, replace } from '@mail/model/model_field_command';
|
||||
|
||||
import session from 'web.session';
|
||||
|
||||
registerModel({
|
||||
name: 'ActivityMenuView',
|
||||
identifyingFields: [],
|
||||
lifecycleHooks: {
|
||||
_created() {
|
||||
this.fetchData();
|
||||
document.addEventListener('click', this._onClickCaptureGlobal, true);
|
||||
},
|
||||
_willDelete() {
|
||||
document.removeEventListener('click', this._onClickCaptureGlobal, true);
|
||||
},
|
||||
},
|
||||
recordMethods: {
|
||||
close() {
|
||||
this.update({ isOpen: false });
|
||||
},
|
||||
async fetchData() {
|
||||
const data = await this.messaging.rpc({
|
||||
model: 'res.users',
|
||||
method: 'systray_get_activities',
|
||||
args: [],
|
||||
kwargs: { context: session.user_context },
|
||||
});
|
||||
this.update({
|
||||
activityGroups: insertAndReplace(data.map(vals => this.messaging.models['ActivityGroup'].convertData(vals))),
|
||||
extraCount: 0,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickDropdownToggle(ev) {
|
||||
ev.preventDefault();
|
||||
if (this.isOpen) {
|
||||
this.update({ isOpen: false });
|
||||
} else {
|
||||
this.update({ isOpen: true });
|
||||
this.fetchData();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Closes the menu when clicking outside, if appropriate.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onClickCaptureGlobal(ev) {
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
if (!this.component || !this.component.root.el) {
|
||||
return;
|
||||
}
|
||||
if (this.component.root.el.contains(ev.target)) {
|
||||
return;
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {FieldCommand}
|
||||
*/
|
||||
_computeActivityGroupViews() {
|
||||
return insertAndReplace(this.activityGroups.map(activityGroup => {
|
||||
return {
|
||||
activityGroup: replace(activityGroup),
|
||||
};
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_computeCounter() {
|
||||
return this.activityGroups.reduce((total, group) => total + group.total_count, this.extraCount);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Array[]}
|
||||
*/
|
||||
_sortActivityGroups() {
|
||||
return [
|
||||
['smaller-first', 'irModel.id'],
|
||||
];
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activityGroups: many('ActivityGroup', {
|
||||
sort: '_sortActivityGroups',
|
||||
}),
|
||||
activityGroupViews: many('ActivityGroupView', {
|
||||
compute: '_computeActivityGroupViews',
|
||||
inverse: 'activityMenuViewOwner',
|
||||
isCausal: true,
|
||||
}),
|
||||
component: attr(),
|
||||
counter: attr({
|
||||
compute: '_computeCounter',
|
||||
}),
|
||||
/**
|
||||
* Determines the number of activities that have been added in the
|
||||
* system but not yet taken into account in each activity group counter.
|
||||
*
|
||||
* @deprecated this field should be replaced by directly updating the
|
||||
* counter of each group.
|
||||
*/
|
||||
extraCount: attr(),
|
||||
isOpen: attr({
|
||||
default: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
|
||||
registerModel({
|
||||
name: 'ir.model',
|
||||
identifyingFields: ['id'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @private
|
||||
* @returns {string[]}
|
||||
*/
|
||||
_computeAvailableWebViews() {
|
||||
return ['kanban', 'list', 'form', 'activity'];
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
/**
|
||||
* Determines the name of the views that are available for this model.
|
||||
*/
|
||||
availableWebViews: attr({
|
||||
compute: '_computeAvailableWebViews',
|
||||
}),
|
||||
activityGroup: one('ActivityGroup', {
|
||||
inverse: 'irModel',
|
||||
isCausal: true,
|
||||
}),
|
||||
iconUrl: attr(),
|
||||
id: attr({
|
||||
required: true,
|
||||
readonly: true,
|
||||
}),
|
||||
model: attr({
|
||||
required: true,
|
||||
}),
|
||||
name: attr(),
|
||||
},
|
||||
});
|
||||
@@ -89,8 +89,17 @@ registerModel({
|
||||
return this._handleNotificationChannelLeave(message.payload);
|
||||
case 'res.users/connection':
|
||||
return this._handleNotificationPartnerUserConnection(message.payload);
|
||||
case 'mail.activity/updated':
|
||||
return owl.Component.env.bus.trigger('activity_updated', message.payload);
|
||||
case 'mail.activity/updated': {
|
||||
for (const activityMenuView of this.messaging.models['ActivityMenuView'].all()) {
|
||||
if (message.payload.activity_created) {
|
||||
activityMenuView.update({ extraCount: increment() });
|
||||
}
|
||||
if (message.payload.activity_deleted) {
|
||||
activityMenuView.update({ extraCount: decrement() });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'mail.channel/unpin':
|
||||
return this._handleNotificationChannelUnpin(message.payload);
|
||||
case 'mail.channel/joined':
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { attr, many, one } from '@mail/model/model_field';
|
||||
import { clear, insert } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
@@ -173,6 +173,9 @@ registerModel({
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activitiesAsAssignee: many('Activity', {
|
||||
inverse: 'assignee',
|
||||
}),
|
||||
id: attr({
|
||||
readonly: true,
|
||||
required: true,
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
font-size: 1.3rem;
|
||||
font-family: emojifont;
|
||||
}
|
||||
.o_mail_preview .o_mail_emoji {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
@mixin o-viewer-black-btn {
|
||||
background-color: rgba(black, 0.4);
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
// ------------------------------------------------------------------
|
||||
// Layout
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_mail_user_status {
|
||||
font-size: 1em;
|
||||
position: relative;
|
||||
&.o_user_online {
|
||||
color: $o-enterprise-primary-color;
|
||||
}
|
||||
&.fa-stack {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
line-height: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Thread preview: shared between discuss (mobile) and systray
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o_mail_preview {
|
||||
display: flex;
|
||||
background-color: map-get($theme-colors, 'light');
|
||||
color: $o-main-text-color;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
&:hover {
|
||||
background-color: map-get($grays, '300');
|
||||
.o_preview_name {
|
||||
color: $headings-color;
|
||||
}
|
||||
.o_discuss_icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid map-get($grays, '400');
|
||||
}
|
||||
.o_mail_preview_image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
> img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
&.o_mail_preview_app > img {
|
||||
border-radius: 2px;
|
||||
}
|
||||
.o_mail_user_status {
|
||||
@include o-position-absolute($bottom: 0px, $right: 0px);
|
||||
}
|
||||
}
|
||||
.o_preview_info {
|
||||
flex: 1 1 100%;
|
||||
overflow: hidden;
|
||||
.o_preview_title {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
.o_preview_name {
|
||||
flex: 0 1 auto;
|
||||
@include o-text-overflow;
|
||||
}
|
||||
.o_mail_activity_action_buttons {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-flow: row-reverse wrap;
|
||||
}
|
||||
.o_mail_activity_action {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.o_preview_counter {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
.o_thread_window_expand {
|
||||
margin: 0px 6px;
|
||||
}
|
||||
.o_last_message_date {
|
||||
flex: 0 0 auto;
|
||||
color: $o-main-color-muted;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.o_last_message_preview {
|
||||
width: 94%;
|
||||
max-height: 20px;
|
||||
color: $o-main-color-muted;
|
||||
@include o-text-overflow;
|
||||
}
|
||||
}
|
||||
.o_discuss_icon {
|
||||
opacity: 0;
|
||||
}
|
||||
&.o_preview_unread {
|
||||
background-color: transparent;
|
||||
&:hover {
|
||||
background-color: map-get($theme-colors, 'light');
|
||||
}
|
||||
.o_preview_info {
|
||||
.o_preview_title {
|
||||
.o_preview_name, .o_preview_counter {
|
||||
font-weight: 700;
|
||||
}
|
||||
.o_last_message_date {
|
||||
color: $o-brand-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.o_systray_activity {
|
||||
background-color: transparent;
|
||||
&:hover {
|
||||
background-color: map-get($theme-colors, 'light');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
|
||||
.o_main_navbar > ul.o_menu_systray > li .dropdown-menu.show {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.o_mail_preview {
|
||||
padding: $o-mail-chatter-mobile-gap;
|
||||
|
||||
.o_preview_info, .o_last_message_date {
|
||||
margin-left: $o-mail-chatter-mobile-gap;
|
||||
}
|
||||
|
||||
.o_preview_name {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.o_last_message_date {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.o_last_message_preview {
|
||||
margin-top: $o-mail-chatter-mobile-gap*0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.o_mail_mobile_tabs {
|
||||
display: flex;
|
||||
box-shadow: 0 0 8px map-get($grays, '400');
|
||||
|
||||
.o_mail_mobile_tab {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
width: 20%;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: space-between;
|
||||
padding: $o-mail-chatter-mobile-gap $o-mail-chatter-mobile-gap*2;
|
||||
box-shadow: 1px 0 0 map-get($grays, '400');
|
||||
text-align: center;
|
||||
|
||||
> span {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
font-size: 10px;
|
||||
|
||||
&.fa {
|
||||
padding-bottom: $o-mail-chatter-mobile-gap*2;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
}
|
||||
|
||||
&.active > span {
|
||||
color: $o-brand-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
// Systray icon and dropdown
|
||||
.o_mail_systray_item {
|
||||
&.o_no_notification .o_notification_counter {
|
||||
display: none;
|
||||
}
|
||||
&.show .o_mail_systray_dropdown {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
.o_mail_systray_dropdown {
|
||||
direction: ltr;
|
||||
width: 350px;
|
||||
padding: 0;
|
||||
|
||||
.o_spinner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: $o-main-text-color;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.o_mail_systray_dropdown_top {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid map-get($grays, '400');
|
||||
box-shadow: 0 0 2px map-get($grays, '400');
|
||||
.o_filter_button, .o_new_message {
|
||||
padding: 5px;
|
||||
}
|
||||
.o_filter_button {
|
||||
color: $o-main-color-muted;
|
||||
&:hover, &.active {
|
||||
color: $o-brand-primary;
|
||||
}
|
||||
&.active {
|
||||
cursor: default;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_mail_systray_dropdown_items {
|
||||
flex: 0 1 auto;
|
||||
max-height: 400px;
|
||||
min-height: 50px;
|
||||
overflow-y: auto;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.o_mail_preview {
|
||||
min-height: 50px;
|
||||
padding: 5px;
|
||||
.o_mail_preview_image .fa-circle-o {
|
||||
display: none;
|
||||
}
|
||||
.o_preview_info {
|
||||
margin-left: 10px;
|
||||
.o_preview_title {
|
||||
.o_last_message_date {
|
||||
padding-top: 2px;
|
||||
font-size: x-small;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_activity_filter_button {
|
||||
padding: 2px;
|
||||
}
|
||||
.o_no_activity {
|
||||
cursor: initial;
|
||||
align-items: center;
|
||||
color: grey;
|
||||
opacity: 0.5;
|
||||
padding: 3px;
|
||||
min-height: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_no_thread_window .o_mail_systray_dropdown .o_new_message {
|
||||
display: none; // hide 'new message' button if chat windows are disabled
|
||||
}
|
||||
|
||||
// Mobile rules
|
||||
// Goal: mock the design of Discuss in mobile
|
||||
@include media-breakpoint-down(md) {
|
||||
.o_mail_systray_item {
|
||||
.o_mail_systray_dropdown {
|
||||
position: fixed;
|
||||
top: $o-mail-chat-window-header-height-mobile;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
max-height: none;
|
||||
|
||||
.o_mail_systray_dropdown_top {
|
||||
padding: 5px;
|
||||
}
|
||||
.o_mail_systray_mobile_header {
|
||||
padding: 5px;
|
||||
height: 44px;
|
||||
border-bottom: 1px solid #ebebeb;
|
||||
box-shadow: 0 0 2px map-get($grays, '400');
|
||||
}
|
||||
.o_mail_systray_dropdown_items {
|
||||
max-height: none;
|
||||
padding-bottom: 52px; // leave space for tabs
|
||||
}
|
||||
.o_mail_mobile_tabs {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
background-color: white;
|
||||
color: $o-main-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,15 +38,6 @@
|
||||
.o_updatable_im_status {
|
||||
width: $o-mail-thread-avatar-size;
|
||||
}
|
||||
.o_mail_user_status {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
&.fa-circle-o {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<!--
|
||||
AKU FIXME: use mail/static/src/components/persona_im_status_icon/persona_im_status_icon.js component instead
|
||||
@param {string} status
|
||||
-->
|
||||
<t t-name="mail.widgets.UserStatus">
|
||||
<span>
|
||||
<t t-if="status == 'online'">
|
||||
<i class="o_mail_user_status o_user_online fa fa-circle" title="Online" role="img" aria-label="User is online"/>
|
||||
</t>
|
||||
<t t-if="status == 'away'">
|
||||
<i class="fa fa-circle o_mail_user_status o_user_idle text-warning" title="Idle" role="img" aria-label="User is idle"/>
|
||||
</t>
|
||||
<t t-if="status == 'offline'">
|
||||
<i class="o_mail_user_status fa fa-circle-o" title="Offline" role="img" aria-label="User is offline"/>
|
||||
</t>
|
||||
<t t-if="status == 'bot'">
|
||||
<i class="o_mail_user_status o_user_online fa fa-heart" title="Bot" role="img" aria-label="User is a bot"/>
|
||||
</t>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
|
||||
<!--
|
||||
@param {mail.systray.ActivityMenu} widget
|
||||
@param {Object[]} widget.activities
|
||||
-->
|
||||
<t t-name="mail.systray.ActivityMenu.Previews">
|
||||
<t t-set="activities" t-value="widget._activities"/>
|
||||
<t t-if="_.isEmpty(activities)">
|
||||
<div class="dropdown-item-text text-center o_no_activity d-flex justify-content-center">
|
||||
<span>Congratulations, you're done with your activities.</span>
|
||||
</div>
|
||||
</t>
|
||||
<t t-foreach="activities" t-as="activity">
|
||||
<div class="o_mail_preview o_systray_activity" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" t-att-data-domain="activity.domain" data-filter='my'>
|
||||
<div t-if="activity.icon" class="o_mail_preview_image o_mail_preview_app">
|
||||
<img t-att-src="activity.icon" alt="Activity"/>
|
||||
</div>
|
||||
<div class="o_preview_info">
|
||||
<div class="o_preview_title">
|
||||
<span class="o_preview_name">
|
||||
<t t-esc="activity.name"/>
|
||||
</span>
|
||||
<div t-if="activity.actions" class="o_mail_activity_action_buttons">
|
||||
<t t-foreach="activity.actions" t-as="action">
|
||||
<button type="button"
|
||||
t-att-title="action.name"
|
||||
t-att-class="'o_mail_activity_action btn btn-link fa ' + action.icon"
|
||||
t-att-data-action_xmlid="action.action_xmlid"
|
||||
t-att-data-res_model="activity.model"
|
||||
t-att-data-model_name="activity.name"
|
||||
t-att-data-domain="activity.domain">
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="activity and activity.type == 'activity'">
|
||||
<button t-if="activity.overdue_count" type="button" class="btn btn-link o_activity_filter_button mr16" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='overdue'><t t-esc="activity.overdue_count"/> Late </button>
|
||||
<span t-if="!activity.overdue_count" class="o_no_activity mr16">0 Late </span>
|
||||
<button t-if="activity.today_count" type="button" class="btn btn-link o_activity_filter_button mr16" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='today'> <t t-esc="activity.today_count"/> Today </button>
|
||||
<span t-if="!activity.today_count" class="o_no_activity mr16">0 Today </span>
|
||||
<button t-if="activity.planned_count" type="button" class="btn btn-link o_activity_filter_button float-end" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='upcoming_all'> <t t-esc="activity.planned_count"/> Future </button>
|
||||
<span t-if="!activity.planned_count" class="o_no_activity float-end">0 Future</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.systray.ActivityMenu">
|
||||
<div class="o_mail_systray_item dropdown">
|
||||
<a class="dropdown-toggle o-no-caret o-dropdown--narrow" data-bs-toggle="dropdown" data-bs-offset="0,0" aria-expanded="false" title="Activities" href="#" role="button">
|
||||
<i class="fa fa-lg fa-clock-o" role="img" aria-label="Activities"/> <span class="o_notification_counter badge"/>
|
||||
</a>
|
||||
<div class="o_mail_systray_dropdown dropdown-menu dropdown-menu-end" role="menu">
|
||||
<div class="o_mail_systray_dropdown_items"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -2193,6 +2193,7 @@ patch(MockServer.prototype, 'mail', {
|
||||
const modelName = activity['res_model'];
|
||||
if (!userActivitiesByModelName[modelName]) {
|
||||
userActivitiesByModelName[modelName] = {
|
||||
id: modelName, // for simplicity
|
||||
model: modelName,
|
||||
name: modelName,
|
||||
overdue_count: 0,
|
||||
|
||||
@@ -49,6 +49,7 @@ class Users(models.Model):
|
||||
icon = module and modules.module.get_module_icon(module)
|
||||
res_ids = set()
|
||||
user_activities[act['mailing_type']] = {
|
||||
'id': self.env['ir.model']._get('mailing.mailing').id,
|
||||
'name': name,
|
||||
'model': 'mailing.mailing',
|
||||
'type': 'activity',
|
||||
|
||||
@@ -25,15 +25,18 @@
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'assets': {
|
||||
'mail.assets_messaging': [
|
||||
'note/static/src/models/*.js',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'note/static/src/components/*/*.js',
|
||||
'note/static/src/scss/note.scss',
|
||||
'note/static/src/js/systray_activity_menu.js',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'note/static/tests/**/*',
|
||||
],
|
||||
'web.assets_qweb': [
|
||||
'note/static/src/xml/**/*',
|
||||
'note/static/src/components/*/*.xml',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -61,6 +61,7 @@ GROUP BY id"""
|
||||
activities[note_index]['name'] = note_label
|
||||
else:
|
||||
activities.append({
|
||||
'id': self.env['ir.model']._get('note.note').id,
|
||||
'type': 'activity',
|
||||
'name': note_label,
|
||||
'model': 'note.note',
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
|
||||
import { useUpdateToModel } from '@mail/component_hooks/use_update_to_model';
|
||||
import '@mail/components/activity_menu_view/activity_menu_view'; // ensure components are registered beforehand.
|
||||
import { getMessagingComponent } from "@mail/utils/messaging_component";
|
||||
|
||||
import { DatePicker } from '@web/core/datepicker/datepicker';
|
||||
import { patch } from 'web.utils';
|
||||
|
||||
const ActivityMenuView = getMessagingComponent('ActivityMenuView');
|
||||
|
||||
patch(ActivityMenuView.prototype, 'note', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
this._super();
|
||||
useRefToModel({ fieldName: 'noteInputRef', refName: 'noteInput', });
|
||||
useUpdateToModel({ methodName: 'onComponentUpdate' });
|
||||
},
|
||||
});
|
||||
|
||||
Object.assign(ActivityMenuView.components, {
|
||||
DatePicker,
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.ActivityMenuView" t-inherit-mode="extension">
|
||||
<xpath expr="//*[@name='activityGroupLoop']" position="after">
|
||||
<div t-if="!activityMenuView.isAddingNote" class="o_note_show d-grid" t-on-click="activityMenuView.onClickAddNote">
|
||||
<a role="button" class="btn text-center">Add new note</a>
|
||||
</div>
|
||||
<div t-if="activityMenuView.isAddingNote" class="o_note o_ActivityMenuView_activityGroup">
|
||||
<div class="o_ActivityMenuView_activityGroupIconContainer">
|
||||
<img src="/note/static/description/icon.svg" alt="Channel"/>
|
||||
</div>
|
||||
<div class="o_ActivityMenuView_activityGroupInfo">
|
||||
<div class="o_ActivityMenuView_activityGroupTitle">
|
||||
<span class="o_ActivityMenuView_activityGroupName"><strong>Add a note</strong></span>
|
||||
<DatePicker
|
||||
date="activityMenuView.addingNoteDate"
|
||||
onDateTimeChanged="activityMenuView.onDateTimeChanged"
|
||||
placeholder="activityMenuView.addingNoteDatePlaceholder"
|
||||
/>
|
||||
</div>
|
||||
<div class="o_note_input_box">
|
||||
<p><input class="o_note_input" type="text" placeholder="Remember..." t-on-keydown="activityMenuView.onKeydownNoteInput" t-ref="noteInput"/></p>
|
||||
<span class="ml8 mr4">
|
||||
<a class="o_note_save" t-on-click="activityMenuView.onClickSaveNote">SAVE</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -1,133 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import ActivityMenu from '@mail/js/systray/systray_activity_menu';
|
||||
|
||||
import { _t } from 'web.core';
|
||||
import datepicker from 'web.datepicker';
|
||||
|
||||
ActivityMenu.include({
|
||||
events: _.extend({}, ActivityMenu.prototype.events, {
|
||||
'click .o_note_show': '_onAddNoteClick',
|
||||
'click .o_note_save': '_onNoteSaveClick',
|
||||
'click .o_note_set_datetime': '_onNoteDateTimeSetClick',
|
||||
'keydown input.o_note_input': '_onNoteInputKeyDown',
|
||||
'click .o_note': '_onNewNoteClick',
|
||||
}),
|
||||
//--------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------
|
||||
/**
|
||||
* Moving notes at first place
|
||||
* @override
|
||||
*/
|
||||
_getActivityData: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
var reminderIndex = _.findIndex(self.activities, function (val) {
|
||||
return val.model === 'note.note';
|
||||
});
|
||||
if (reminderIndex > 0) {
|
||||
self.activities.splice(0, 0, self.activities.splice(reminderIndex, 1)[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Save the note to database using datepicker date and field as note
|
||||
* By default, when no datetime is set, it uses the current datetime.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_saveNote: function () {
|
||||
var note = this.$('.o_note_input').val().trim();
|
||||
if (! note) {
|
||||
return;
|
||||
}
|
||||
var params = {'note': note};
|
||||
var noteDateTime = this.noteDateTimeWidget.getValue();
|
||||
if (noteDateTime) {
|
||||
params = _.extend(params, {'date_deadline': noteDateTime});
|
||||
} else {
|
||||
params = _.extend(params, {'date_deadline': moment()});
|
||||
}
|
||||
this.$('.o_note_show').removeClass('d-none');
|
||||
this.$('.o_note').addClass('d-none');
|
||||
this._rpc({
|
||||
route: '/note/new',
|
||||
params: params,
|
||||
}).then(this._updateActivityPreview.bind(this));
|
||||
},
|
||||
//-----------------------------------------
|
||||
// Handlers
|
||||
//-----------------------------------------
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onActivityFilterClick: function (ev) {
|
||||
var $el = $(ev.currentTarget);
|
||||
if (!$el.hasClass("o_note")) {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* When add new note button clicked, toggling quick note create view inside
|
||||
* Systray activity view
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onAddNoteClick: function (ev) {
|
||||
var self = this;
|
||||
ev.stopPropagation();
|
||||
if (!this.noteDateTimeWidget){
|
||||
this.noteDateTimeWidget = new datepicker.DateWidget(this, {useCurrent: true});
|
||||
}
|
||||
this.noteDateTimeWidget.appendTo(this.$('.o_note_datetime')).then(function() {
|
||||
self.noteDateTimeWidget.$input.attr('placeholder', _t("Today"));
|
||||
self.noteDateTimeWidget.setValue(false);
|
||||
self.$('.o_note_show, .o_note').toggleClass('d-none');
|
||||
self.$('.o_note_input').val('').focus();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* When focusing on input for new quick note systerm tray must be open.
|
||||
* Preventing to close
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onNewNoteClick: function (ev) {
|
||||
ev.stopPropagation();
|
||||
},
|
||||
/**
|
||||
* Opens datetime picker for note.
|
||||
* Quick FIX due to no option for set custom icon instead of caret in datepicker.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onNoteDateTimeSetClick: function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.noteDateTimeWidget.$input.click();
|
||||
},
|
||||
/**
|
||||
* Saving note (quick create) and updating activity preview
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onNoteSaveClick: function (ev) {
|
||||
this._saveNote();
|
||||
},
|
||||
/**
|
||||
* Handling Enter key for quick create note.
|
||||
*
|
||||
* @private
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
_onNoteInputKeyDown: function (ev) {
|
||||
if (ev.which === $.ui.keyCode.ENTER) {
|
||||
this._saveNote();
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { addFields, addRecordMethods } from '@mail/model/model_core';
|
||||
import { attr } from '@mail/model/model_field';
|
||||
import '@mail/models/activity_group'; // ensure the model definition is loaded before the patch
|
||||
|
||||
addRecordMethods('ActivityGroup', {
|
||||
_computeIsNote() {
|
||||
return this.irModel.model === 'note.note';
|
||||
},
|
||||
});
|
||||
|
||||
addFields('ActivityGroup', {
|
||||
isNote: attr({
|
||||
compute: '_computeIsNote',
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { addFields, addRecordMethods, patchRecordMethods } from '@mail/model/model_core';
|
||||
import { attr } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
import '@mail/models/activity_menu_view'; // ensure the model definition is loaded before the patch
|
||||
|
||||
const { DateTime } = luxon;
|
||||
|
||||
addRecordMethods('ActivityMenuView', {
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickAddNote(ev) {
|
||||
this.update({
|
||||
addingNoteDoFocus: true,
|
||||
isAddingNote: true,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickSaveNote(ev) {
|
||||
this.saveNote();
|
||||
},
|
||||
onComponentUpdate() {
|
||||
if (this.addingNoteDoFocus && this.noteInputRef.el) {
|
||||
this.noteInputRef.el.focus();
|
||||
this.update({ addingNoteDoFocus: clear() });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {DateTime|string} date
|
||||
*/
|
||||
onDateTimeChanged(date) {
|
||||
this.update({ addingNoteDate: date ? date : clear() });
|
||||
},
|
||||
/**
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
onKeydownNoteInput(ev) {
|
||||
if (ev.key === 'Enter') {
|
||||
this.saveNote();
|
||||
}
|
||||
},
|
||||
async saveNote() {
|
||||
const note = this.noteInputRef.el.value.trim();
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
this.update({ isAddingNote: false });
|
||||
await this.messaging.rpc({
|
||||
route: '/note/new',
|
||||
params: {
|
||||
'note': note,
|
||||
'date_deadline': this.addingNoteDate ? this.addingNoteDate : new DateTime.local(),
|
||||
},
|
||||
});
|
||||
this.fetchData();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
_computeAddingNoteDatePlaceholder() {
|
||||
return this.env._t("Today");
|
||||
},
|
||||
});
|
||||
|
||||
patchRecordMethods('ActivityMenuView', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
close() {
|
||||
this.update({
|
||||
addingNoteDoFocus: clear(),
|
||||
isAddingNote: false,
|
||||
});
|
||||
this._super();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onClickCaptureGlobal(ev) {
|
||||
if (ev.target.closest('.bootstrap-datetimepicker-widget')) {
|
||||
return;
|
||||
}
|
||||
this._super(ev);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_sortActivityGroups(ev) {
|
||||
const res = this._super();
|
||||
res.unshift(['truthy-first', 'isNote']);
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
||||
addFields('ActivityMenuView', {
|
||||
addingNoteDate: attr(),
|
||||
addingNoteDoFocus: attr({
|
||||
default: false,
|
||||
}),
|
||||
addingNoteDatePlaceholder: attr({
|
||||
compute: '_computeAddingNoteDatePlaceholder',
|
||||
}),
|
||||
isAddingNote: attr({
|
||||
default: false,
|
||||
}),
|
||||
noteInputRef: attr(),
|
||||
});
|
||||
@@ -51,11 +51,11 @@
|
||||
}
|
||||
|
||||
// Quick create notes from systray
|
||||
.o_note.o_mail_preview {
|
||||
.o_note.o_ActivityMenuView_activityGroup {
|
||||
background-color: white;
|
||||
.o_preview_info {
|
||||
.o_preview_title {
|
||||
.o_preview_name {
|
||||
.o_ActivityMenuView_activityGroupInfo {
|
||||
.o_ActivityMenuView_activityGroupTitle {
|
||||
.o_ActivityMenuView_activityGroupName {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
@@ -74,20 +74,4 @@
|
||||
.o_note_input {
|
||||
border: none;
|
||||
}
|
||||
.o_note_datetime {
|
||||
.o_datepicker {
|
||||
.o_datepicker_input {
|
||||
float: right;
|
||||
text-align: right;
|
||||
border: none;
|
||||
font-size: 11px;
|
||||
}
|
||||
.o_datepicker_button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_note_set_datetime {
|
||||
color: $text-muted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-extend="mail.systray.ActivityMenu.Previews">
|
||||
<t t-jquery="t[t-foreach*='activities'][t-as*='activity']" t-operation="after">
|
||||
<div class="o_note_show d-grid">
|
||||
<a role="button" class="btn text-center">Add new note</a>
|
||||
</div>
|
||||
<div class="o_note o_mail_preview d-none">
|
||||
<div class="o_mail_preview_image o_mail_preview_app">
|
||||
<img src="/note/static/description/icon.svg" alt="Channel"/>
|
||||
</div>
|
||||
<div class="o_preview_info">
|
||||
<div class="o_preview_title">
|
||||
<span class="o_preview_name"><strong>Add a note</strong></span>
|
||||
<div class="o_note_datetime"/>
|
||||
<span class="ml4">
|
||||
<a class="o_note_set_datetime">
|
||||
<span class="fa fa-calendar" role="img" aria-label="Set date and time" title="Set date and time"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="o_note_input_box">
|
||||
<p><input class="o_note_input" type="text" placeholder="Remember..." /></p>
|
||||
<span class="ml8 mr4">
|
||||
<a class="o_note_save">SAVE</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -61,6 +61,7 @@ patch(MockServer.prototype, 'note', {
|
||||
activities[noteIndex]['name'] = 'Notes';
|
||||
} else {
|
||||
activities.push({
|
||||
id: 'note.note', // for simplicity
|
||||
type: 'activity',
|
||||
name: 'Notes',
|
||||
model: 'note.note',
|
||||
|
||||
@@ -10,53 +10,52 @@ QUnit.module("ActivityMenu");
|
||||
QUnit.test('note activity menu widget: create note from activity menu', async function (assert) {
|
||||
assert.expect(15);
|
||||
|
||||
await start();
|
||||
assert.containsOnce(document.body, '.o_mail_systray_item',
|
||||
const { click } = await start();
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView',
|
||||
'should contain an instance of widget');
|
||||
await testUtils.nextTick();
|
||||
assert.strictEqual(document.querySelector('.o_notification_counter').innerText, '0',
|
||||
assert.containsNone(document.body, '.o_ActivityMenuView_counter',
|
||||
"should not have any activity notification initially");
|
||||
|
||||
// toggle quick create for note
|
||||
await testUtils.dom.click(document.querySelector('.dropdown-toggle[title="Activities"]'));
|
||||
assert.containsOnce(document.body, '.o_no_activity',
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_noActivity',
|
||||
"should not have any activity preview");
|
||||
assert.doesNotHaveClass(document.querySelector('.o_note_show'), 'd-none',
|
||||
assert.containsOnce(document.body, '.o_note_show',
|
||||
'ActivityMenu should have Add new note CTA');
|
||||
await testUtils.dom.click(document.querySelector('.o_note_show'));
|
||||
assert.hasClass(document.querySelector('.o_note_show'), 'd-none',
|
||||
await click('.o_note_show');
|
||||
assert.containsNone(document.body, '.o_note_show',
|
||||
'ActivityMenu should hide CTA when entering a new note');
|
||||
assert.doesNotHaveClass(document.querySelector('.o_note'), 'd-none',
|
||||
assert.containsOnce(document.body, '.o_note',
|
||||
'ActivityMenu should display input for new note');
|
||||
|
||||
// creating quick note without date
|
||||
await testUtils.fields.editInput(document.querySelector("input.o_note_input"), "New Note");
|
||||
await testUtils.dom.click(document.querySelector(".o_note_save"));
|
||||
assert.strictEqual(document.querySelector('.o_notification_counter').innerText, '1',
|
||||
await click('.o_note_save');
|
||||
assert.strictEqual(document.querySelector('.o_ActivityMenuView_counter').innerText, '1',
|
||||
"should increment activity notification counter after creating a note");
|
||||
assert.containsOnce(document.body, '.o_mail_preview[data-res_model="note.note"]',
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_activityGroup[data-res_model="note.note"]',
|
||||
"should have an activity preview that is a note");
|
||||
assert.strictEqual(document.querySelector('.o_activity_filter_button[data-filter="today"]').innerText.trim(),
|
||||
assert.strictEqual(document.querySelector('.o_ActivityMenuView_activityGroupFilterButton[data-filter="today"]').innerText.trim(),
|
||||
"1 Today",
|
||||
"should display one note for today");
|
||||
|
||||
assert.doesNotHaveClass(document.querySelector('.o_note_show'), 'd-none',
|
||||
'ActivityMenu add note button should be displayed');
|
||||
assert.hasClass(document.querySelector('.o_note'), 'd-none',
|
||||
assert.containsNone(document.body, '.o_note',
|
||||
'ActivityMenu add note input should be hidden');
|
||||
|
||||
// creating quick note with date
|
||||
await testUtils.dom.click(document.querySelector('.o_note_show'));
|
||||
await click('.o_note_show');
|
||||
document.querySelector('input.o_note_input').value = "New Note";
|
||||
await testUtils.dom.click(document.querySelector(".o_note_save"));
|
||||
assert.strictEqual(document.querySelector('.o_notification_counter').innerText, '2',
|
||||
await click(".o_note_save");
|
||||
assert.strictEqual(document.querySelector('.o_ActivityMenuView_counter').innerText, '2',
|
||||
"should increment activity notification counter after creating a second note");
|
||||
assert.strictEqual(document.querySelector('.o_activity_filter_button[data-filter="today"]').innerText.trim(),
|
||||
assert.strictEqual(document.querySelector('.o_ActivityMenuView_activityGroupFilterButton[data-filter="today"]').innerText.trim(),
|
||||
"2 Today",
|
||||
"should display 2 notes for today");
|
||||
assert.doesNotHaveClass(document.querySelector('.o_note_show'), 'd-none',
|
||||
'ActivityMenu add note button should be displayed');
|
||||
assert.hasClass(document.querySelector('.o_note'), 'd-none',
|
||||
assert.containsNone(document.body, '.o_note',
|
||||
'ActivityMenu add note input should be hidden');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,11 +9,14 @@
|
||||
'summary': 'Test of Discuss with all possible overrides installed.',
|
||||
'description': """Test of Discuss with all possible overrides installed, including feature and performance tests.""",
|
||||
'depends': [
|
||||
'calendar',
|
||||
'crm',
|
||||
'crm_livechat',
|
||||
'hr_holidays',
|
||||
'im_livechat',
|
||||
'mail',
|
||||
'mail_bot',
|
||||
'note',
|
||||
'website_livechat',
|
||||
],
|
||||
'installable': True,
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import session from 'web.session';
|
||||
import testUtils from 'web.test_utils';
|
||||
import { date_to_str } from 'web.time';
|
||||
import { patchWithCleanup } from '@web/../tests/helpers/utils';
|
||||
|
||||
@@ -25,7 +24,7 @@ QUnit.module('systray_activity_menu_tests.js', {
|
||||
{ res_id: resPartnerId1, res_model: 'res.partner' },
|
||||
{ res_id: mailTestActivityIds[0], res_model: 'mail.test.activity' },
|
||||
{ date_deadline: date_to_str(tomorrow), res_id: mailTestActivityIds[1], res_model: 'mail.test.activity' },
|
||||
{ date_deadline: date_to_str(tomorrow), res_id: mailTestActivityIds[2], res_model: 'mail.test.activity' },
|
||||
{ date_deadline: date_to_str(tomorrow), res_id: mailTestActivityIds[2], res_model: 'mail.test.activity' },
|
||||
{ date_deadline: date_to_str(yesterday), res_id: mailTestActivityIds[3], res_model: 'mail.test.activity' },
|
||||
]);
|
||||
},
|
||||
@@ -34,28 +33,27 @@ QUnit.module('systray_activity_menu_tests.js', {
|
||||
QUnit.test('activity menu widget: menu with no records', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
await start({
|
||||
const { click } = await start({
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'systray_get_activities') {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
},
|
||||
});
|
||||
await testUtils.nextTick();
|
||||
assert.containsOnce(document.body, '.o_no_activity');
|
||||
await click('.o_ActivityMenuView_dropdownToggle');
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_noActivity');
|
||||
});
|
||||
|
||||
QUnit.test('activity menu widget: activity menu with 2 models', async function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
const { env } = await start();
|
||||
const { click, env } = await start();
|
||||
|
||||
await testUtils.nextTick();
|
||||
assert.containsOnce(document.body, '.o_mail_systray_item', 'should contain an instance of widget');
|
||||
// the assertion below has not been replace because there are includes of ActivityMenu that modify the length.
|
||||
assert.ok(document.querySelectorAll('.o_mail_preview').length);
|
||||
assert.containsOnce(document.body, '.o_notification_counter', "widget should have notification counter");
|
||||
assert.strictEqual(parseInt(document.querySelector('.o_notification_counter').innerText), 5, "widget should have 5 notification counter");
|
||||
await click('.o_ActivityMenuView_dropdownToggle');
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView', 'should contain an instance of widget');
|
||||
assert.ok(document.querySelectorAll('.o_ActivityMenuView_activityGroup').length);
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_counter', "widget should have notification counter");
|
||||
assert.strictEqual(parseInt(document.querySelector('.o_ActivityMenuView_counter').innerText), 5, "widget should have 5 notification counter");
|
||||
|
||||
var context = {};
|
||||
patchWithCleanup(env.services.action, {
|
||||
@@ -69,45 +67,45 @@ QUnit.test('activity menu widget: activity menu with 2 models', async function (
|
||||
force_search_count: 1,
|
||||
search_default_activities_overdue: 1,
|
||||
};
|
||||
await testUtils.dom.click(document.querySelector('.dropdown-toggle[title="Activities"]'));
|
||||
assert.containsOnce(document.body, '.o_mail_systray_dropdown.show', 'ActivityMenu should be open');
|
||||
await testUtils.dom.click(document.querySelector(".o_activity_filter_button[data-model_name='mail.test.activity'][data-filter='overdue']"));
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_dropdownMenu.show', 'ActivityMenu should be open');
|
||||
await click('.o_ActivityMenuView_activityGroupFilterButton[data-model_name="mail.test.activity"][data-filter="overdue"]');
|
||||
assert.containsNone(document.body, '.show', 'ActivityMenu should be closed');
|
||||
// case 2: click on "today"
|
||||
context = {
|
||||
force_search_count: 1,
|
||||
search_default_activities_today: 1,
|
||||
};
|
||||
await testUtils.dom.click(document.querySelector('.dropdown-toggle[title="Activities"]'));
|
||||
await testUtils.dom.click(document.querySelector(".o_activity_filter_button[data-model_name='mail.test.activity'][data-filter='today']"));
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
await click('.o_ActivityMenuView_activityGroupFilterButton[data-model_name="mail.test.activity"][data-filter="today"]');
|
||||
// case 3: click on "future"
|
||||
context = {
|
||||
force_search_count: 1,
|
||||
search_default_activities_upcoming_all: 1,
|
||||
};
|
||||
await testUtils.dom.click(document.querySelector('.dropdown-toggle[title="Activities"]'));
|
||||
await testUtils.dom.click(document.querySelector(".o_activity_filter_button[data-model_name='mail.test.activity'][data-filter='upcoming_all']"));
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
await click('.o_ActivityMenuView_activityGroupFilterButton[data-model_name="mail.test.activity"][data-filter="upcoming_all"]');
|
||||
// case 4: click anywere else
|
||||
context = {
|
||||
force_search_count: 1,
|
||||
search_default_activities_overdue: 1,
|
||||
search_default_activities_today: 1,
|
||||
};
|
||||
await testUtils.dom.click(document.querySelector('.dropdown-toggle[title="Activities"]'));
|
||||
await testUtils.dom.click(document.querySelector(".o_mail_systray_dropdown_items > div[data-model_name='mail.test.activity']"));
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
await click('.o_ActivityMenuView_activityGroups > div[data-model_name="mail.test.activity"]');
|
||||
});
|
||||
|
||||
QUnit.test('activity menu widget: activity view icon', async function (assert) {
|
||||
assert.expect(14);
|
||||
|
||||
patchWithCleanup(session, { uid: 10 });
|
||||
const { env } = await start();
|
||||
await testUtils.nextTick();
|
||||
assert.containsN(document.body, '.o_mail_activity_action', 2,
|
||||
const { click, env } = await start();
|
||||
|
||||
await click('.o_ActivityMenuView_dropdownToggle');
|
||||
assert.containsN(document.body, '.o_ActivityMenuView_activityGroupActionButton', 2,
|
||||
"widget should have 2 activity view icons");
|
||||
|
||||
var first = document.querySelector('.o_mail_activity_action');
|
||||
var second = document.querySelectorAll('.o_mail_activity_action')[1];
|
||||
var first = document.querySelector('.o_ActivityMenuView_activityGroupActionButton');
|
||||
var second = document.querySelectorAll('.o_ActivityMenuView_activityGroupActionButton')[1];
|
||||
assert.strictEqual(first.getAttribute('data-model_name'), "res.partner",
|
||||
"first activity action should link to 'res.partner'");
|
||||
assert.hasClass(first, 'fa-clock-o', "should display the activity action icon");
|
||||
@@ -129,18 +127,16 @@ QUnit.test('activity menu widget: activity view icon', async function (assert) {
|
||||
},
|
||||
});
|
||||
|
||||
// click on the "mail.test.activity" activity icon
|
||||
await testUtils.dom.click(document.querySelector('.dropdown-toggle[title="Activities"]'));
|
||||
assert.hasClass(document.querySelector('.dropdown-menu'), 'show',
|
||||
assert.hasClass(document.querySelector('.o-dropdown-menu'), 'show',
|
||||
"dropdown should be expanded");
|
||||
|
||||
await testUtils.dom.click(document.querySelector(".o_mail_activity_action[data-model_name='mail.test.activity']"));
|
||||
assert.doesNotHaveClass(document.querySelector('.dropdown-menu'), 'show',
|
||||
await click('.o_ActivityMenuView_activityGroupActionButton[data-model_name="mail.test.activity"]');
|
||||
assert.containsNone(document.body, '.o-dropdown-menu',
|
||||
"dropdown should be collapsed");
|
||||
|
||||
// click on the "res.partner" activity icon
|
||||
await testUtils.dom.click(document.querySelector('.dropdown-toggle[title="Activities"]'));
|
||||
await testUtils.dom.click(document.querySelector(".o_mail_activity_action[data-model_name='res.partner']"));
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
await click('.o_ActivityMenuView_activityGroupActionButton[data-model_name="res.partner"]');
|
||||
|
||||
assert.verifySteps([
|
||||
'do_action:mail.test.activity',
|
||||
@@ -152,19 +148,18 @@ QUnit.test('activity menu widget: close on messaging menu click', async function
|
||||
assert.expect(2);
|
||||
|
||||
const { click } = await start();
|
||||
await testUtils.nextTick();
|
||||
|
||||
await testUtils.dom.click(document.querySelector('.dropdown-toggle[title="Activities"]'));
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
assert.hasClass(
|
||||
document.querySelector('.o_mail_systray_dropdown'),
|
||||
document.querySelector('.o_ActivityMenuView_dropdownMenu'),
|
||||
'show',
|
||||
"activity menu should be shown after click on itself"
|
||||
);
|
||||
|
||||
await click(`.o_MessagingMenu_toggler`);
|
||||
assert.doesNotHaveClass(
|
||||
document.querySelector('.o_mail_systray_dropdown'),
|
||||
'show',
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_ActivityMenuView_dropdownMenu',
|
||||
"activity menu should be hidden after click on messaging menu"
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user