[IMP] mail: ease activities management by adding a shortcut-counter in top menu
This commit improves the use of activities through various applications by adding a counter of activities displaying next activities grouped by application. For each application activities are divided into late, today and future activities in order to achieve a better management. This commit also merges Inbox and Messaging icons in topbar to keep only one displaying All / Chat / Channels messages.
This commit is contained in:
committed by
Thibault Delavallée
parent
f5c8bd30a3
commit
07f1e9025d
@@ -132,16 +132,35 @@ class MailActivity(models.Model):
|
||||
def create(self, values):
|
||||
activity = super(MailActivity, self).create(values)
|
||||
self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id])
|
||||
self.env['bus.bus'].sendone(
|
||||
(self._cr.dbname, 'res.partner', activity.user_id.partner_id.id),
|
||||
{'type': 'activity_updated', 'activity_created': True})
|
||||
return activity
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
res = super(MailActivity, self).write(values)
|
||||
if values.get('user_id'):
|
||||
pre_responsibles = self.mapped('user_id.partner_id')
|
||||
for activity in self:
|
||||
self.env[activity.res_model].browse(activity.res_id).message_subscribe(partner_ids=[activity.user_id.partner_id.id])
|
||||
self.env['bus.bus'].sendone(
|
||||
(self._cr.dbname, 'res.partner', activity.user_id.partner_id.id),
|
||||
{'type': 'activity_updated', 'activity_created': True})
|
||||
for partner in pre_responsibles:
|
||||
self.env['bus.bus'].sendone(
|
||||
(self._cr.dbname, 'res.partner', partner.id),
|
||||
{'type': 'activity_updated', 'activity_deleted': True})
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for activity in self:
|
||||
self.env['bus.bus'].sendone(
|
||||
(self._cr.dbname, 'res.partner', activity.user_id.partner_id.id),
|
||||
{'type': 'activity_updated', 'activity_deleted': True})
|
||||
return super(MailActivity, self).unlink()
|
||||
|
||||
@api.multi
|
||||
def action_done(self, feedback=False):
|
||||
message = self.env['mail.message']
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
|
||||
from email.utils import formataddr
|
||||
|
||||
from odoo import _, api, fields, models, SUPERUSER_ID, tools
|
||||
from odoo import _, api, fields, models, modules, SUPERUSER_ID, tools
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import pycompat
|
||||
@@ -420,6 +420,8 @@ class Message(models.Model):
|
||||
for message in message_values:
|
||||
message['is_note'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['internal']
|
||||
message['subtype_description'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['description']
|
||||
if message['model']:
|
||||
message['module_icon'] = modules.module.get_module_icon(self.env[message['model']]._original_module)
|
||||
return message_values
|
||||
|
||||
#------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, exceptions, fields, models
|
||||
from odoo import _, api, exceptions, fields, models, modules
|
||||
from odoo.tools import pycompat
|
||||
|
||||
|
||||
@@ -119,6 +119,36 @@ class Users(models.Model):
|
||||
def message_get_suggested_recipients(self):
|
||||
return dict((res_id, list()) for res_id in self._ids)
|
||||
|
||||
@api.model
|
||||
def activity_user_count(self):
|
||||
query = """SELECT m.name, count(*), act.res_model as model,
|
||||
CASE
|
||||
WHEN now()::date - act.date_deadline::date = 0 Then 'today'
|
||||
WHEN now()::date - act.date_deadline::date > 0 Then 'overdue'
|
||||
WHEN now()::date - act.date_deadline::date < 0 Then 'planned'
|
||||
END AS states
|
||||
FROM mail_activity AS act
|
||||
JOIN ir_model AS m ON act.res_model_id = m.id
|
||||
WHERE user_id = %s
|
||||
GROUP BY m.name, states, act.res_model;
|
||||
"""
|
||||
self.env.cr.execute(query, [self.env.uid])
|
||||
activity_data = self.env.cr.dictfetchall()
|
||||
|
||||
user_activities = {}
|
||||
for activity in activity_data:
|
||||
if not user_activities.get(activity['model']):
|
||||
user_activities[activity['model']] = {
|
||||
'name': activity['name'],
|
||||
'model': activity['model'],
|
||||
'icon': modules.module.get_module_icon(self.env[activity['model']]._original_module),
|
||||
'total_count': 0, 'today_count': 0, 'overdue_count': 0, 'planned_count': 0,
|
||||
}
|
||||
user_activities[activity['model']]['%s_count' % activity['states']] += activity['count']
|
||||
user_activities[activity['model']]['total_count'] += activity['count']
|
||||
|
||||
return user_activities.values()
|
||||
|
||||
|
||||
class res_groups_mail_channel(models.Model):
|
||||
""" Update of res.groups class
|
||||
|
||||
@@ -137,6 +137,7 @@ function make_message (data) {
|
||||
model: data.model,
|
||||
res_id: data.res_id,
|
||||
url: session.url("/mail/view?message_id=" + data.id),
|
||||
module_icon:data.module_icon,
|
||||
};
|
||||
|
||||
_.each(_.keys(emoji_substitutions), function (key) {
|
||||
@@ -476,6 +477,8 @@ function on_partner_notification (data) {
|
||||
on_channel_seen_notification(data);
|
||||
} else if (data.info === 'transient_message') {
|
||||
on_transient_message_notification(data);
|
||||
} else if (data.type === 'activity_updated') {
|
||||
onActivityUpdateNodification(data);
|
||||
} else {
|
||||
on_chat_session_notification(data);
|
||||
}
|
||||
@@ -599,6 +602,9 @@ function on_transient_message_notification (data) {
|
||||
add_message(data);
|
||||
}
|
||||
|
||||
function onActivityUpdateNodification (data) {
|
||||
chat_manager.bus.trigger('activity_updated', data);
|
||||
}
|
||||
// Public interface
|
||||
//----------------------------------------------------------------------------------
|
||||
var ChatManager = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
|
||||
@@ -1128,6 +1134,20 @@ var ChatManager = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, {
|
||||
|
||||
get_channels_preview: function (channels) {
|
||||
var channels_preview = _.map(channels, function (channel) {
|
||||
if (channel.channel_ids && _.contains(channel.channel_ids,"channel_inbox")) {
|
||||
// map inbox(mail_message) data with existing channel/chat template
|
||||
var info = _.pick(channel, 'id', 'body', 'avatar_src', 'res_id', 'model', 'module_icon', 'subject','date', 'record_name', 'status', 'displayed_author', 'email_from', 'unread_counter');
|
||||
info.last_message = {
|
||||
body: info.body,
|
||||
date: info.date,
|
||||
displayed_author: info.displayed_author || info.email_from,
|
||||
};
|
||||
info.name = info.record_name || info.subject || info.displayed_author;
|
||||
info.image_src = info.module_icon || info.avatar_src;
|
||||
info.message_id = info.id;
|
||||
info.id = 'channel_inbox';
|
||||
return info;
|
||||
}
|
||||
var info = _.pick(channel, 'id', 'is_chat', 'name', 'status', 'unread_counter');
|
||||
info.last_message = _.last(channel.cache['[]'].messages);
|
||||
if (!info.is_chat) {
|
||||
|
||||
@@ -268,6 +268,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
|
||||
chat_manager.bus.on('update_starred', self, self.throttled_renderSidebar);
|
||||
chat_manager.bus.on('update_channel_unread_counter', self, self.throttled_renderSidebar);
|
||||
chat_manager.bus.on('update_dm_presence', self, self.throttled_renderSidebar);
|
||||
chat_manager.bus.on('activity_updated', self, self.throttled_renderSidebar);
|
||||
self.thread.$el.on("scroll", null, _.debounce(function () {
|
||||
if (self.thread.is_at_bottom()) {
|
||||
chat_manager.mark_channel_as_seen(self.channel);
|
||||
|
||||
@@ -2,6 +2,8 @@ odoo.define('mail.systray', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var framework = require('web.framework');
|
||||
var session = require('web.session');
|
||||
var SystrayMenu = require('web.SystrayMenu');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
@@ -9,39 +11,6 @@ var chat_manager = require('mail.chat_manager');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
|
||||
/**
|
||||
* Menu item appended in the systray part of the navbar, redirects to the Inbox in Discuss
|
||||
* Also displays the needaction counter (= Inbox counter)
|
||||
*/
|
||||
var InboxItem = Widget.extend({
|
||||
template:'mail.chat.InboxItem',
|
||||
events: {
|
||||
"click": "on_click",
|
||||
},
|
||||
start: function () {
|
||||
this.$needaction_counter = this.$('.o_notification_counter');
|
||||
chat_manager.bus.on("update_needaction", this, this.update_counter);
|
||||
chat_manager.is_ready.then(this.update_counter.bind(this));
|
||||
return this._super();
|
||||
},
|
||||
update_counter: function () {
|
||||
var counter = chat_manager.get_needaction_counter();
|
||||
this.$needaction_counter.text(counter);
|
||||
this.$el.toggleClass('o_no_notification', !counter);
|
||||
},
|
||||
on_click: function (event) {
|
||||
event.preventDefault();
|
||||
chat_manager.is_ready.then(this.discuss_redirect.bind(this));
|
||||
},
|
||||
discuss_redirect: _.debounce(function () {
|
||||
var self = this;
|
||||
this.do_action('mail.mail_channel_action_client_chat', {clear_breadcrumbs: true}).then(function () {
|
||||
self.trigger_up('hide_app_switcher');
|
||||
core.bus.trigger('change_menu_section', chat_manager.get_discuss_menu_id());
|
||||
});
|
||||
}, 1000, true),
|
||||
});
|
||||
|
||||
/**
|
||||
* Menu item appended in the systray part of the navbar
|
||||
*
|
||||
@@ -56,12 +25,13 @@ var MessagingMenu = Widget.extend({
|
||||
"click": "on_click",
|
||||
"click .o_filter_button": "on_click_filter_button",
|
||||
"click .o_new_message": "on_click_new_message",
|
||||
"click .o_mail_channel_preview": "on_click_channel",
|
||||
"click .o_mail_channel_preview": "_onClickChannel",
|
||||
},
|
||||
start: function () {
|
||||
this.$filter_buttons = this.$('.o_filter_button');
|
||||
this.$channels_preview = this.$('.o_mail_navbar_dropdown_channels');
|
||||
this.filter = false;
|
||||
chat_manager.bus.on("update_needaction", this, this.update_counter);
|
||||
chat_manager.bus.on("update_channel_unread_counter", this, this.update_counter);
|
||||
chat_manager.is_ready.then(this.update_counter.bind(this));
|
||||
return this._super();
|
||||
@@ -70,10 +40,9 @@ var MessagingMenu = Widget.extend({
|
||||
return this.$el.hasClass('open');
|
||||
},
|
||||
update_counter: function () {
|
||||
var counter = chat_manager.get_unread_conversation_counter();
|
||||
var counter = chat_manager.get_needaction_counter() + chat_manager.get_unread_conversation_counter();
|
||||
this.$('.o_notification_counter').text(counter);
|
||||
this.$el.toggleClass('o_no_notification', !counter);
|
||||
this.$el.toggleClass('o_unread_chat', !!chat_manager.get_chat_unread_counter());
|
||||
if (this.is_open()) {
|
||||
this.update_channels_preview();
|
||||
}
|
||||
@@ -94,8 +63,23 @@ var MessagingMenu = Widget.extend({
|
||||
return channel.type !== 'static';
|
||||
}
|
||||
});
|
||||
|
||||
chat_manager.get_channels_preview(channels).then(self._render_channels_preview.bind(self));
|
||||
chat_manager.get_messages({channel_id: 'channel_inbox'}).then(function(result) {
|
||||
var res = [];
|
||||
_.each(result, function (message) {
|
||||
message.unread_counter = 1;
|
||||
var duplicatedMessage = _.findWhere(res, {model: message.model, 'res_id': message.res_id});
|
||||
if (message.model && message.res_id && duplicatedMessage) {
|
||||
message.unread_counter = duplicatedMessage.unread_counter + 1;
|
||||
res[_.findIndex(res, duplicatedMessage)] = message;
|
||||
} else {
|
||||
res.push(message);
|
||||
}
|
||||
});
|
||||
if (self.filter === 'channel_inbox' || !self.filter) {
|
||||
channels = _.union(channels, res);
|
||||
}
|
||||
chat_manager.get_channels_preview(channels).then(self._render_channels_preview.bind(self));
|
||||
});
|
||||
});
|
||||
},
|
||||
_render_channels_preview: function (channels_preview) {
|
||||
@@ -136,16 +120,172 @@ var MessagingMenu = Widget.extend({
|
||||
on_click_new_message: function () {
|
||||
chat_manager.bus.trigger('open_chat');
|
||||
},
|
||||
on_click_channel: function (event) {
|
||||
var channel_id = $(event.currentTarget).data('channel_id');
|
||||
var channel = chat_manager.get_channel(channel_id);
|
||||
if (channel) {
|
||||
chat_manager.open_channel(channel);
|
||||
|
||||
// Handlers
|
||||
|
||||
/**
|
||||
* When a channel is clicked on, we want to open chat/channel window
|
||||
* If channel is inbox then redirect to that record view
|
||||
* If record not linked redirect to Inbox
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onClickChannel: function (event) {
|
||||
var channelID = $(event.currentTarget).data('channel_id');
|
||||
if (channelID == 'channel_inbox') {
|
||||
var resID = $(event.currentTarget).data('res_id');
|
||||
var resModel = $(event.currentTarget).data('res_model');
|
||||
if (resModel && resID) {
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: resModel,
|
||||
views: [[false, 'form'], [false, 'kanban']],
|
||||
res_id: resID
|
||||
});
|
||||
} else {
|
||||
// if no model linked redirect to inbox
|
||||
framework.redirect('mail/view?message_id=channel_inbox');
|
||||
}
|
||||
} else {
|
||||
var channel = chat_manager.get_channel(channelID);
|
||||
if (channel) {
|
||||
chat_manager.open_channel(channel);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
SystrayMenu.Items.push(MessagingMenu);
|
||||
SystrayMenu.Items.push(InboxItem);
|
||||
/**
|
||||
* Menu item appended in the systray part of the navbar, redirects to the next activities of all app
|
||||
*/
|
||||
var ActivityMenu = Widget.extend({
|
||||
template:'mail.chat.ActivityMenu',
|
||||
events: {
|
||||
"click": "_onActivityMenuClick",
|
||||
"click .o_activity_filter_button, .o_mail_channel_preview": "_onActivityFilterClick",
|
||||
},
|
||||
start: function () {
|
||||
this.$activities_preview = this.$('.o_mail_navbar_dropdown_channels');
|
||||
chat_manager.bus.on("activity_updated", this, this._updateCounter);
|
||||
chat_manager.is_ready.then(this._updateCounter.bind(this));
|
||||
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: 'activity_user_count',
|
||||
}).then(function (data) {
|
||||
self.isCounterUpdated = false;
|
||||
self.activities = data;
|
||||
self.activityCounter = _.reduce(data, function(total_count, p_data){ return total_count + p_data.total_count; }, 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'
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Check wether activity systray dropdown is open or not
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isOpen: function () {
|
||||
return this.$el.hasClass('open');
|
||||
},
|
||||
/**
|
||||
* Update(render) activity system tray view on activity updation.
|
||||
* @private
|
||||
*/
|
||||
_updateActivityPreview: function () {
|
||||
var self = this;
|
||||
self._getActivityData().then(function (){
|
||||
self.$activities_preview.html(QWeb.render('mail.chat.ActivityMenuPreview', {
|
||||
activities : self.activities
|
||||
}));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 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.isCounterUpdated = true;
|
||||
this.$('.o_notification_counter').text(this.activityCounter);
|
||||
this.$el.toggleClass('o_no_notification', !this.activityCounter);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Handlers
|
||||
|
||||
/**
|
||||
* Redirect to particular model view
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onActivityFilterClick: function (event) {
|
||||
event.stopPropagation();
|
||||
var $target = $(event.currentTarget);
|
||||
var context = {};
|
||||
context['search_default_activities_' + $target.data('filter')] = 1;
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
name: $target.data('model_name'),
|
||||
res_model: $target.data('res_model'),
|
||||
views: [[false, 'kanban'], [false, 'form']],
|
||||
search_view_id: [false],
|
||||
domain: [['activity_user_id', '=', session.uid]],
|
||||
context:context,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* When menu clicked update activity preview if counter updated
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onActivityMenuClick: function () {
|
||||
if (!this._isOpen() && this.isCounterUpdated) {
|
||||
this._updateActivityPreview();
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
SystrayMenu.Items.push(MessagingMenu);
|
||||
SystrayMenu.Items.push(ActivityMenu);
|
||||
|
||||
// to test activity menu in qunit test cases we need it
|
||||
return {
|
||||
ActivityMenu: ActivityMenu,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -12,14 +12,19 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.o_unread_chat > a > i {
|
||||
color: @odoo-brand-optional;
|
||||
}
|
||||
|
||||
&.open .o_mail_navbar_dropdown {
|
||||
.o-flex-display();
|
||||
.o-flex-flow(column, nowrap);
|
||||
}
|
||||
.o_notification_counter {
|
||||
background: @odoo-brand-optional;
|
||||
color: white;
|
||||
padding: 0em 0.3em;
|
||||
font-size: 0.7em;
|
||||
top: 7%;
|
||||
position: absolute;
|
||||
margin: 0px 0px 0px 8px;
|
||||
}
|
||||
.o_mail_navbar_dropdown {
|
||||
width: 350px;
|
||||
padding: 0;
|
||||
@@ -107,12 +112,24 @@
|
||||
}
|
||||
.o_last_message_preview {
|
||||
width: 100%;
|
||||
max-height: 20px;
|
||||
color: @odoo-main-color-muted;
|
||||
.o-text-overflow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_activity_filter_button {
|
||||
.btn-link;
|
||||
padding: 2px;
|
||||
}
|
||||
.o_no_activity {
|
||||
cursor: initial;
|
||||
.o-align-items(center);
|
||||
color: grey;
|
||||
opacity: 0.5;
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
|
||||
<t t-name="mail.chat.InboxItem">
|
||||
<li class="o_mail_navbar_item">
|
||||
<a href="#" title="Inbox">
|
||||
<i class="fa fa-at"/> <span class="o_notification_counter"/>
|
||||
</a>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.chat.MessagingMenu">
|
||||
<li class="o_mail_navbar_item">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" title="Conversations" href="#">
|
||||
<i class="fa fa-comments"/> <span class="o_notification_counter"/>
|
||||
<i class="fa fa-comments"/> <span class="o_notification_counter badge"/>
|
||||
</a>
|
||||
<ul class="o_mail_navbar_dropdown dropdown-menu" role="menu">
|
||||
<li class="o_mail_navbar_dropdown_top">
|
||||
@@ -29,9 +21,14 @@
|
||||
</t>
|
||||
|
||||
<t t-name="mail.chat.ChannelsPreview">
|
||||
<t t-if="_.isEmpty(channels)">
|
||||
<li class="text-center o_no_activity mt16">
|
||||
<span>No discussion yet...</span>
|
||||
</li>
|
||||
</t>
|
||||
<t t-foreach="channels" t-as="channel">
|
||||
<div t-attf-class="o_mail_channel_preview #{channel.unread_counter ? 'o_channel_unread' : ''}"
|
||||
t-att-data-channel_id="channel.id">
|
||||
t-att-data-channel_id="channel.id" t-att-data-res_id="channel.res_id" t-att-data-res_model="channel.model">
|
||||
<img class="o_channel_image" t-att-src="channel.image_src"/>
|
||||
<div class="o_channel_info">
|
||||
<div class="o_channel_title">
|
||||
@@ -53,5 +50,43 @@
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.chat.ActivityMenuPreview">
|
||||
<t t-if="_.isEmpty(activities)">
|
||||
<li class="text-center o_no_activity">
|
||||
<span>No activities planned.</span>
|
||||
</li>
|
||||
</t>
|
||||
<t t-foreach="activities" t-as="activity">
|
||||
<div class="o_mail_channel_preview" t-att-data-res_model="activity.model" t-att-data-model_name="activity.name" data-filter='my'>
|
||||
<img class="o_channel_image" t-att-src="activity.icon"/>
|
||||
<div class="o_channel_info">
|
||||
<div class="o_channel_title">
|
||||
<span class="o_channel_name">
|
||||
<t t-esc="activity.name"/>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<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 pull-right mr16" 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 pull-right mr16">0 Future</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.chat.ActivityMenu">
|
||||
<li class="o_mail_navbar_item">
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false" title="Activities" href="#">
|
||||
<i class="fa fa-check-square-o"/> <span class="o_notification_counter badge"/>
|
||||
</a>
|
||||
<ul class="o_mail_navbar_dropdown dropdown-menu" role="menu">
|
||||
<li class="o_mail_navbar_dropdown_channels"/>
|
||||
</ul>
|
||||
</li>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
odoo.define('mail.systray_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var systray = require('mail.systray');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
|
||||
QUnit.module('mail', {}, function () {
|
||||
|
||||
QUnit.module('ActivityMenu', {
|
||||
beforeEach: function () {
|
||||
|
||||
this.data = {
|
||||
'mail.activity.menu': {
|
||||
fields: {
|
||||
name: { type: "char" },
|
||||
res_model: { type: "char" },
|
||||
planned_count: { type: "integer"},
|
||||
today_count: { type: "integer"},
|
||||
overdue_count: { type: "integer"},
|
||||
total_count: { type: "integer"},
|
||||
},
|
||||
records: [{
|
||||
name: "Contact",
|
||||
res_model: "res.partner",
|
||||
planned_count: 0,
|
||||
today_count: 1,
|
||||
overdue_count: 0,
|
||||
total_count: 1,
|
||||
},
|
||||
{
|
||||
name: "Task",
|
||||
res_model: "project.task",
|
||||
planned_count: 1,
|
||||
today_count: 0,
|
||||
overdue_count: 0,
|
||||
total_count: 1,
|
||||
},
|
||||
{
|
||||
name: "Issue",
|
||||
res_model: "project.issue",
|
||||
planned_count: 1,
|
||||
today_count: 1,
|
||||
overdue_count: 0,
|
||||
total_count: 2,
|
||||
}],
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('activity menu widget: menu with no records', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var activityMenu = new systray.ActivityMenu();
|
||||
testUtils.addMockEnvironment(activityMenu, {
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'activity_user_count') {
|
||||
return $.when([]);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
activityMenu.appendTo($('#qunit-fixture'));
|
||||
assert.ok(activityMenu.$('.o_no_activity').hasClass('o_no_activity'), "should not have instance of widget");
|
||||
activityMenu.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('activity menu widget: activity menu with 3 records', function (assert) {
|
||||
assert.expect(4);
|
||||
var self = this;
|
||||
var activityMenu = new systray.ActivityMenu();
|
||||
|
||||
testUtils.addMockEnvironment(activityMenu, {
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'activity_user_count') {
|
||||
return $.when(self.data['mail.activity.menu']['records']);
|
||||
}
|
||||
return this._super(route, args);
|
||||
},
|
||||
});
|
||||
activityMenu.appendTo($('#qunit-fixture'));
|
||||
assert.ok(activityMenu.$el.hasClass('o_mail_navbar_item'), 'should be the instance of widget');
|
||||
assert.ok(activityMenu.$('.o_mail_channel_preview').hasClass('o_mail_channel_preview'), "should instance of widget");
|
||||
assert.ok(activityMenu.$('.o_notification_counter').hasClass('o_notification_counter'), "widget should have notification counter");
|
||||
assert.strictEqual(parseInt(activityMenu.el.innerText), 4, "widget should have 4 notification counter");
|
||||
activityMenu.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -40,6 +40,7 @@
|
||||
<template id="qunit_suite" name="mail_tests" inherit_id="web.qunit_suite">
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<script type="text/javascript" src="/mail/static/tests/chatter_tests.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/tests/systray_tests.js"></script>
|
||||
<script type="text/javascript" src="/mail/static/tests/chat_window_test.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user