[REF] mail: activities list, ‘mark as done’ sliding panel
Refactor ‘mark as done’ popup JS in order to use a sliding panel layout instead. Sliding panels are currently needed in kanban views, where activities are listed in a dropdown that can't handle popups because of CSS overflow and lack of space. Since this new system check for default bootstrap data-toggle attributes, it could be easily used elsewhere changing the buttons's data-toggle and providing the necessary container for the “mark as done” form.
This commit is contained in:
@@ -271,39 +271,55 @@ var BasicActivity = AbstractField.extend({
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
var self = this;
|
||||
var $popoverElement = $(ev.currentTarget);
|
||||
var activityID = $popoverElement.data('activity-id');
|
||||
var previousActivityTypeID = $popoverElement.data('previous-activity-type-id');
|
||||
var forceNextActivity = $popoverElement.data('force-next-activity');
|
||||
if (!$popoverElement.data('bs.popover')) {
|
||||
$popoverElement.popover({
|
||||
var $markDoneBtn = $(ev.currentTarget);
|
||||
var activityID = $markDoneBtn.data('activity-id');
|
||||
var previousActivityTypeID = $markDoneBtn.data('previous-activity-type-id');
|
||||
var forceNextActivity = $markDoneBtn.data('force-next-activity');
|
||||
|
||||
if ($markDoneBtn.data('toggle') == 'collapse') {
|
||||
var $actLi = $markDoneBtn.parents('.o_log_activity');
|
||||
var $panel = self.$('#o_activity_form_' + activityID);
|
||||
|
||||
if (!$panel.data('bs.collapse')) {
|
||||
var $form = $(QWeb.render('mail.activity_feedback_form', { previous_activity_type_id: previousActivityTypeID, force_next: forceNextActivity}));
|
||||
$panel.append($form);
|
||||
self._onMarkActivityDoneActions($markDoneBtn, $form, activityID);
|
||||
|
||||
// Close and reset any other open panels
|
||||
_.each($panel.siblings('.o_activity_form'), function (el) {
|
||||
if ($(el).data('bs.collapse')) {
|
||||
$(el).empty().collapse('dispose').removeClass('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Scroll to selected activity
|
||||
$markDoneBtn.parents('.o_activity_log_container').scrollTo($actLi.position().top, 100);
|
||||
}
|
||||
|
||||
// Empty and reset panel on close
|
||||
$panel.on('hidden.bs.collapse', function () {
|
||||
if ($panel.data('bs.collapse')) {
|
||||
$actLi.removeClass('o_activity_selected');
|
||||
$panel.collapse('dispose');
|
||||
$panel.empty();
|
||||
}
|
||||
});
|
||||
|
||||
this.$('.o_activity_selected').removeClass('o_activity_selected');
|
||||
$actLi.toggleClass('o_activity_selected');
|
||||
$panel.collapse('toggle');
|
||||
|
||||
} else if (!$markDoneBtn.data('bs.popover')) {
|
||||
$markDoneBtn.popover({
|
||||
template: $(Popover.Default.template).addClass('o_mail_activity_feedback')[0].outerHTML, // Ugly but cannot find another way
|
||||
container: $popoverElement,
|
||||
container: $markDoneBtn,
|
||||
title : _t("Feedback"),
|
||||
html: true,
|
||||
trigger:'click',
|
||||
placement: 'right', // FIXME: this should work, maybe a bug in the popper lib
|
||||
content : function () {
|
||||
var $popover = $(QWeb.render('mail.activity_feedback_form', { previous_activity_type_id: previousActivityTypeID, force_next: forceNextActivity}));
|
||||
$popover.find('#activity_feedback').val(self._draftFeedback[activityID]);
|
||||
$popover.on('click', '.o_activity_popover_done', function (ev) {
|
||||
ev.stopPropagation();
|
||||
self._markActivityDone({
|
||||
activityID: activityID,
|
||||
feedback: _.escape($popover.find('#activity_feedback').val()),
|
||||
});
|
||||
});
|
||||
$popover.on('click', '.o_activity_popover_done_next', function (ev) {
|
||||
ev.stopPropagation();
|
||||
self._markActivityDoneAndScheduleNext({
|
||||
activityID: activityID,
|
||||
feedback: _.escape($popover.find('#activity_feedback').val()),
|
||||
});
|
||||
});
|
||||
$popover.on('click', '.o_activity_popover_discard', function (ev) {
|
||||
ev.stopPropagation();
|
||||
$popoverElement.popover('hide');
|
||||
});
|
||||
self._onMarkActivityDoneActions($markDoneBtn, $popover, activityID);
|
||||
return $popover;
|
||||
},
|
||||
}).on('shown.bs.popover', function () {
|
||||
@@ -315,6 +331,39 @@ var BasicActivity = AbstractField.extend({
|
||||
}).popover('show');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Bind all necessary actions to the 'mark as done' form
|
||||
*
|
||||
* @private
|
||||
* @param {Object} $form
|
||||
* @param {integer} activityID
|
||||
*/
|
||||
_onMarkActivityDoneActions: function ($btn, $form, activityID) {
|
||||
var self = this;
|
||||
$form.find('#activity_feedback').val(self._draftFeedback[activityID]);
|
||||
$form.on('click', '.o_activity_popover_done', function (ev) {
|
||||
ev.stopPropagation();
|
||||
self._markActivityDone({
|
||||
activityID: activityID,
|
||||
feedback: _.escape($form.find('#activity_feedback').val()),
|
||||
});
|
||||
});
|
||||
$form.on('click', '.o_activity_popover_done_next', function (ev) {
|
||||
ev.stopPropagation();
|
||||
self._markActivityDoneAndScheduleNext({
|
||||
activityID: activityID,
|
||||
feedback: _.escape($form.find('#activity_feedback').val()),
|
||||
});
|
||||
});
|
||||
$form.on('click', '.o_activity_popover_discard', function (ev) {
|
||||
ev.stopPropagation();
|
||||
if ($btn.data('bs.popover')) {
|
||||
$btn.popover('hide');
|
||||
} else if ($btn.data('toggle') == 'collapse') {
|
||||
self.$('#o_activity_form_' + activityID).collapse('hide');
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
|
||||
@@ -123,13 +123,13 @@
|
||||
}
|
||||
|
||||
.o_activity_color_planned {
|
||||
color: theme-color('success');
|
||||
color: darken(theme-color('success'), 10%);
|
||||
}
|
||||
.o_activity_color_overdue {
|
||||
color: theme-color('danger');
|
||||
color: darken(theme-color('danger'), 10%);
|
||||
}
|
||||
.o_activity_color_today {
|
||||
color: theme-color('warning');
|
||||
color: darken(theme-color('warning'), 10%);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,62 +160,45 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o_activity_link_kanban {
|
||||
visibility: hidden;
|
||||
@include media-breakpoint-down(sm) {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.o_activity_template_preview,.o_activity_template_send {
|
||||
font-weight: bold;
|
||||
color: $o-brand-primary;
|
||||
cursor: pointer;
|
||||
&:hover:not(.active) {
|
||||
color: darken($o-brand-primary, 15%);
|
||||
border-color: darken($o-brand-primary, 15%);
|
||||
}
|
||||
}
|
||||
|
||||
div.o_activity {
|
||||
min-width: 290px;
|
||||
padding: 0px 0px;
|
||||
|
||||
> :last-child {
|
||||
background-color: $o-brand-lightsecondary;
|
||||
line-height: 3em;
|
||||
}
|
||||
ul.o_activity_log {
|
||||
.o_activity_log_container {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
ul.o_activity_log {
|
||||
color: $body-color;
|
||||
|
||||
li {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
|
||||
&.o_activity_label {
|
||||
line-height: 2.5em;
|
||||
padding: 0px 5px;
|
||||
background-color: $o-brand-lightsecondary;
|
||||
.o_activity_title_entry {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
&.o_log_activity {
|
||||
padding: 8px 10px 8px 0px;
|
||||
vertical-align: middle;
|
||||
&:hover {
|
||||
background-color: theme-color('light');
|
||||
.o_activity_link_kanban {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.o_edit_button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.o_activity_title {
|
||||
width: 85%;
|
||||
padding: 0px 5px 0px 10px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
&:hover .o_edit_button{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.o_activity_link_kanban {
|
||||
font-size: 1.5em;
|
||||
@include o-hover-text-color($text-muted, theme-color('success'));
|
||||
@include o-hover-opacity(0.5, 1);
|
||||
}
|
||||
|
||||
&.o_activity_selected {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&.o_activity_form > div {
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,58 +20,65 @@
|
||||
<span role="menuitem" t-if="_.isEmpty(records)" class="dropdown-item-text text-center o_no_activity">
|
||||
No activities planned.
|
||||
</span>
|
||||
<div t-else="" aria-haspopup="true" role="menu">
|
||||
<ul class="nav o_activity_log" role="menu">
|
||||
<div t-else="" aria-haspopup="true" role="menu" class="o_activity_log_container dropdown-item bg-100 p-0">
|
||||
<ul class="o_activity_log list-group list-group-flush mb-2" role="menu">
|
||||
<t t-foreach="_.keys(records)" t-as="key">
|
||||
<t t-set="logs" t-value="records[key]" />
|
||||
<li role="menuitem" class="nav-item o_activity_label">
|
||||
<strong t-attf-class="o_activity_color_#{key}">
|
||||
<t t-esc="selection[key]" /> (<t t-esc="logs.length"/>)
|
||||
</strong>
|
||||
<t t-set="contextual_class" t-value="key == 'planned' ? 'success' : (key == 'today' ? 'warning' : 'danger') "/>
|
||||
|
||||
<li role="menuitem" t-attf-class="o_activity_label list-group-item list-group-item list-group-item-light d-flex justify-content-between align-items-center o_activity_color_{{key}} {{!key_first ? 'mt-2' : ''}}">
|
||||
<strong><t t-esc="selection[key]"/></strong>
|
||||
<span t-attf-class="badge badge-pill badge-{{contextual_class}} border-0 mr-0"><t t-esc="logs.length"/></span>
|
||||
</li>
|
||||
<t t-foreach="logs" t-as="log">
|
||||
<t t-set="edit_class" t-value="'o_edit_activity'"/>
|
||||
<t t-if="log.force_next">
|
||||
<t t-set="edit_class" t-value=""/>
|
||||
</t>
|
||||
<li t-att-data-activity-id="log.id" t-attf-class="nav-item o_log_activity #{edit_class}" role="menuitem">
|
||||
<div class="o_activity_title float-left">
|
||||
<span t-attf-class="fa #{log.icon} fa-fw" role="img" aria-label="Log" title="Log"/>
|
||||
<strong>
|
||||
<t t-esc="log.title_action or log.activity_type_id[1]" />
|
||||
</strong>
|
||||
<div>
|
||||
<span class="fa fa-clock-o fa-fw" role="img" aria-label="Deadline" title="Deadline"/>
|
||||
<span t-att-title="log.date_deadline"><t t-esc="log.label_delay" /></span>
|
||||
<div class="mt16" t-att-data-activity-id="log.id" t-att-data-force-next-activity="log.force_next" t-att-data-previous-activity-type-id="log.activity_type_id[0]">
|
||||
<t t-if="log.mail_template_ids">
|
||||
<t t-foreach="log.mail_template_ids" t-as="mail_template">
|
||||
<div>
|
||||
<i class="fa fa-envelope-o" aria-label="Mail" title="Mail" role="img"></i>
|
||||
<span t-esc="mail_template.name"/>:
|
||||
<span class="o_activity_template_preview" t-att-data-template-id="mail_template.id">Preview</span>
|
||||
<span class="text-muted">or</span>
|
||||
<span class="o_activity_template_send" t-att-data-template-id="mail_template.id">Send Now</span>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<li t-att-data-activity-id="log.id" t-attf-class="list-group-item o_log_activity d-flex #{edit_class} #{log_last ? 'border-bottom' : ''}" role="menuitem">
|
||||
<div class="o_activity_title">
|
||||
<div t-attf-class="o_activity_title_entry {{! log.force_next ? 'align-items-center' : 'mb-1'}}">
|
||||
<span t-attf-class="fa #{log.icon ? log.icon : 'fa-bell' } fa-fw mr-2 text-center text-muted" role="img" aria-label="Log" title="Log"/>
|
||||
<strong class="text-dark"><t t-esc="log.title_action or log.activity_type_id[1]"/></strong>
|
||||
<button t-if="! log.force_next" class="btn btn-sm btn-link py-0 o_edit_button"><i class="fa fa-pencil"/></button>
|
||||
</div>
|
||||
<div class="o_activity_title_entry mt-1" t-if="log.state != 'today'">
|
||||
<span class="fa fa-clock-o fa-fw mr-2 text-center text-muted" role="img" aria-label="Deadline" title="Deadline"/>
|
||||
<small t-att-title="log.date_deadline"><t t-esc="log.label_delay" /></small>
|
||||
</div>
|
||||
<t t-if="log.mail_template_ids">
|
||||
<div t-foreach="log.mail_template_ids" t-as="mail_template" class="o_activity_title_entry mt-2" t-att-data-activity-id="log.id" t-att-data-force-next-activity="log.force_next" t-att-data-previous-activity-type-id="log.activity_type_id[0]">
|
||||
<i class="fa fa-envelope-o fa-fw mr-2 text-center text-muted" aria-label="Mail" title="Mail" role="img"></i>
|
||||
<small>
|
||||
<div class="mb-1" t-esc="mail_template.name + ':'"/>
|
||||
<a class="o_activity_template_preview" t-att-data-template-id="mail_template.id" href="#"><b>Preview</b></a>
|
||||
<small>or</small>
|
||||
<a class="o_activity_template_send" t-att-data-template-id="mail_template.id" href="#"><b>Send Now</b></a>
|
||||
</small>
|
||||
</div>
|
||||
<t t-if="log.user_id[0] != uid">
|
||||
<span class="ml4 fa fa-user" role="img" aria-label="User" title="User"/>
|
||||
<span><t t-esc="log.user_id[1]" /></span>
|
||||
</t>
|
||||
</t>
|
||||
<div t-if="log.user_id[0] != uid and log.mail_template_ids" class="o_activity_title_entry">
|
||||
<span class="fa fa-user fa-fw mr-2 text-center text-muted" role="img" aria-label="User" title="User"/>
|
||||
<span><t t-esc="log.user_id[1]" /></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="float-right">
|
||||
<span class="o_mark_as_done o_activity_link o_activity_link_kanban fa fa-check-circle fa-2x mt4" t-att-data-force-next-activity="log.force_next" t-att-data-previous-activity-type-id="log.activity_type_id[0]" t-att-data-activity-id="log.id" title="Mark as done" role="img" aria-label="Mark as done"/>
|
||||
<div class="flex-grow-1 text-right">
|
||||
<a t-att-data-force-next-activity="log.force_next"
|
||||
t-att-data-previous-activity-type-id="log.activity_type_id[0]"
|
||||
t-att-data-activity-id="log.id"
|
||||
t-attf-href="#o_mark_done_form{{log.id}}"
|
||||
class="o_mark_as_done o_activity_link o_activity_link_kanban fa fa-check-circle"
|
||||
data-toggle="collapse" title="Mark as done" role="img" aria-label="Mark as done"/>
|
||||
</div>
|
||||
</li>
|
||||
<li t-attf-id="o_activity_form_{{log.id}}" class="o_activity_form list-group-item border-top-0 py-0 mb-2 collapse"></li>
|
||||
</t>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
<div role="menuitem" class="o_schedule_activity text-center">
|
||||
<a role="button" class="btn btn-link btn-block">
|
||||
<div class="dropdown-divider mt-0"/>
|
||||
<div role="menuitem" class="o_schedule_activity dropdown-header py-1 text-center">
|
||||
<a role="button" class="btn btn-secondary mb-1">
|
||||
<i class="fa fa-plus fa-fw"></i><strong>Schedule an activity</strong>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -308,13 +308,13 @@ QUnit.test('activity view: activity widget', function (assert) {
|
||||
today.find('.o_closest_deadline').click();
|
||||
assert.ok(dropdown.hasClass('show'), "dropdown should be displayed");
|
||||
assert.ok(dropdown.find('.o_activity_color_today:contains(Today)').length, "Title should be today");
|
||||
assert.ok(dropdown.find('.o_activity_title div div div:first span:contains(template8)').length,
|
||||
assert.ok(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first div:contains(template8)').length,
|
||||
"template8 should be available");
|
||||
assert.ok(dropdown.find('.o_activity_title div div div:nth-child(2) span:contains(template9)').length,
|
||||
assert.ok(dropdown.find('.o_activity_title_entry[data-activity-id="2"]:eq(1) div:contains(template9)').length,
|
||||
"template9 should be available");
|
||||
|
||||
dropdown.find('.o_activity_title div div div:first .o_activity_template_preview').click();
|
||||
dropdown.find('.o_activity_title div div div:first .o_activity_template_send').click();
|
||||
dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first .o_activity_template_preview').click();
|
||||
dropdown.find('.o_activity_title_entry[data-activity-id="2"]:first .o_activity_template_send').click();
|
||||
var overdue = activity.$('table tbody tr:first td:nth-child(3).overdue');
|
||||
overdue.find('.o_closest_deadline').click();
|
||||
dropdown = overdue.find('.dropdown-menu.o_activity');
|
||||
|
||||
@@ -492,7 +492,7 @@ QUnit.test('kanban activity widget with no activity', function (assert) {
|
||||
});
|
||||
|
||||
QUnit.test('kanban activity widget with an activity', function (assert) {
|
||||
assert.expect(11);
|
||||
assert.expect(12);
|
||||
|
||||
this.data.partner.records[0].activity_ids = [1];
|
||||
this.data.partner.records[0].activity_state = 'today';
|
||||
@@ -539,8 +539,9 @@ QUnit.test('kanban activity widget with an activity', function (assert) {
|
||||
$record.find('.o_activity_btn').click();
|
||||
assert.strictEqual(rpcCount, 2, 'a read should have been done to fetch the activity details');
|
||||
assert.strictEqual($record.find('.o_activity_title').length, 1, "should have an activity scheduled");
|
||||
var label_text = $record.find('.o_activity_label .o_activity_color_today').text();
|
||||
assert.ok(label_text.indexOf('Today (1)') >= 0, "should display the correct label and count");
|
||||
var label = $record.find('.o_activity_log .o_activity_color_today');
|
||||
assert.strictEqual(label.find('strong').text(), "Today", "should display the correct label");
|
||||
assert.strictEqual(label.find('.badge-warning').text(), "1", "should display the correct count");
|
||||
|
||||
// click on the activity button to close the dropdown
|
||||
$record.find('.o_activity_btn').click();
|
||||
|
||||
Reference in New Issue
Block a user