summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/components/activity_mark_done_popover
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/mail/static/src/components/activity_mark_done_popover
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mail/static/src/components/activity_mark_done_popover')
-rw-r--r--addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js122
-rw-r--r--addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss20
-rw-r--r--addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml23
-rw-r--r--addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js297
4 files changed, 462 insertions, 0 deletions
diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js
new file mode 100644
index 00000000..de1ea5ce
--- /dev/null
+++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js
@@ -0,0 +1,122 @@
+odoo.define('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js', function (require) {
+'use strict';
+
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
+const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
+
+const { Component } = owl;
+const { useRef } = owl.hooks;
+
+class ActivityMarkDonePopover extends Component {
+
+ /**
+ * @override
+ */
+ constructor(...args) {
+ super(...args);
+ useShouldUpdateBasedOnProps();
+ useStore(props => {
+ const activity = this.env.models['mail.activity'].get(props.activityLocalId);
+ return {
+ activity: activity ? activity.__state : undefined,
+ };
+ });
+ this._feedbackTextareaRef = useRef('feedbackTextarea');
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ mounted() {
+ this._feedbackTextareaRef.el.focus();
+ if (this.activity.feedbackBackup) {
+ this._feedbackTextareaRef.el.value = this.activity.feedbackBackup;
+ }
+ }
+
+ /**
+ * @returns {mail.activity}
+ */
+ get activity() {
+ return this.env.models['mail.activity'].get(this.props.activityLocalId);
+ }
+
+ /**
+ * @returns {string}
+ */
+ get DONE_AND_SCHEDULE_NEXT() {
+ return this.env._t("Done & Schedule Next");
+ }
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _close() {
+ this.trigger('o-popover-close');
+ }
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onBlur() {
+ this.activity.update({
+ feedbackBackup: this._feedbackTextareaRef.el.value,
+ });
+ }
+
+ /**
+ * @private
+ */
+ _onClickDiscard() {
+ this._close();
+ }
+
+ /**
+ * @private
+ */
+ async _onClickDone() {
+ await this.activity.markAsDone({
+ feedback: this._feedbackTextareaRef.el.value,
+ });
+ this.trigger('reload', { keepChanges: true });
+ }
+
+ /**
+ * @private
+ */
+ _onClickDoneAndScheduleNext() {
+ this.activity.markAsDoneAndScheduleNext({
+ feedback: this._feedbackTextareaRef.el.value,
+ });
+ }
+
+ /**
+ * @private
+ */
+ _onKeydown(ev) {
+ if (ev.key === 'Escape') {
+ this._close();
+ }
+ }
+
+}
+
+Object.assign(ActivityMarkDonePopover, {
+ props: {
+ activityLocalId: String,
+ },
+ template: 'mail.ActivityMarkDonePopover',
+});
+
+return ActivityMarkDonePopover;
+
+});
diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss
new file mode 100644
index 00000000..3479ffc3
--- /dev/null
+++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.scss
@@ -0,0 +1,20 @@
+// ------------------------------------------------------------------
+// Layout
+// ------------------------------------------------------------------
+
+.o_ActivityMarkDonePopover {
+ min-height: 100px;
+}
+
+.o_ActivityMarkDonePopover_buttons {
+ margin-top: map-get($spacers, 2);
+}
+
+.o_ActivityMarkDonePopover_doneButton {
+ margin: map-get($spacers, 0) map-get($spacers, 2);
+}
+
+.o_ActivityMarkDonePopover_feedback {
+ min-height: 70px;
+}
+
diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml
new file mode 100644
index 00000000..357ab59b
--- /dev/null
+++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+
+ <t t-name="mail.ActivityMarkDonePopover" owl="1">
+ <div class="o_ActivityMarkDonePopover" t-on-keydown="_onKeydown">
+ <t t-if="activity">
+ <textarea class="form-control o_ActivityMarkDonePopover_feedback" rows="3" placeholder="Write Feedback" t-on-blur="_onBlur" t-ref="feedbackTextarea"/>
+ <div class="o_ActivityMarkDonePopover_buttons">
+ <button type="button" class="o_ActivityMarkDonePopover_doneScheduleNextButton btn btn-sm btn-primary" t-on-click="_onClickDoneAndScheduleNext" t-esc="DONE_AND_SCHEDULE_NEXT"/>
+ <t t-if="!activity.force_next">
+ <button type="button" class="o_ActivityMarkDonePopover_doneButton btn btn-sm btn-primary" t-on-click="_onClickDone">
+ Done
+ </button>
+ </t>
+ <button type="button" class="o_ActivityMarkDonePopover_discardButton btn btn-sm btn-link" t-on-click="_onClickDiscard">
+ Discard
+ </button>
+ </div>
+ </t>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js
new file mode 100644
index 00000000..0c019b2b
--- /dev/null
+++ b/addons/mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js
@@ -0,0 +1,297 @@
+odoo.define('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover_tests.js', function (require) {
+'use strict';
+
+const components = {
+ ActivityMarkDonePopover: require('mail/static/src/components/activity_mark_done_popover/activity_mark_done_popover.js'),
+};
+
+const {
+ afterEach,
+ afterNextRender,
+ beforeEach,
+ createRootComponent,
+ start,
+} = require('mail/static/src/utils/test_utils.js');
+
+const Bus = require('web.Bus');
+
+QUnit.module('mail', {}, function () {
+QUnit.module('components', {}, function () {
+QUnit.module('activity_mark_done_popover', {}, function () {
+QUnit.module('activity_mark_done_popover_tests.js', {
+ beforeEach() {
+ beforeEach(this);
+
+ this.createActivityMarkDonePopoverComponent = async activity => {
+ await createRootComponent(this, components.ActivityMarkDonePopover, {
+ props: { activityLocalId: activity.localId },
+ target: this.widget.el,
+ });
+ };
+
+ this.start = async params => {
+ const { env, widget } = await start(Object.assign({}, params, {
+ data: this.data,
+ }));
+ this.env = env;
+ this.widget = widget;
+ };
+ },
+ afterEach() {
+ afterEach(this);
+ },
+});
+
+QUnit.test('activity mark done popover simplest layout', async function (assert) {
+ assert.expect(6);
+
+ await this.start();
+ const activity = this.env.models['mail.activity'].create({
+ canWrite: true,
+ category: 'not_upload_file',
+ id: 12,
+ thread: [['insert', { id: 42, model: 'res.partner' }]],
+ });
+ await this.createActivityMarkDonePopoverComponent(activity);
+
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover',
+ "Popover component should be present"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover_feedback',
+ "Popover component should contain the feedback textarea"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover_buttons',
+ "Popover component should contain the action buttons"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover_doneScheduleNextButton',
+ "Popover component should contain the done & schedule next button"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover_doneButton',
+ "Popover component should contain the done button"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover_discardButton',
+ "Popover component should contain the discard button"
+ );
+});
+
+QUnit.test('activity with force next mark done popover simplest layout', async function (assert) {
+ assert.expect(6);
+
+ await this.start();
+ const activity = this.env.models['mail.activity'].create({
+ canWrite: true,
+ category: 'not_upload_file',
+ force_next: true,
+ id: 12,
+ thread: [['insert', { id: 42, model: 'res.partner' }]],
+ });
+ await this.createActivityMarkDonePopoverComponent(activity);
+
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover',
+ "Popover component should be present"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover_feedback',
+ "Popover component should contain the feedback textarea"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover_buttons',
+ "Popover component should contain the action buttons"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover_doneScheduleNextButton',
+ "Popover component should contain the done & schedule next button"
+ );
+ assert.containsNone(
+ document.body,
+ '.o_ActivityMarkDonePopover_doneButton',
+ "Popover component should NOT contain the done button"
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ActivityMarkDonePopover_discardButton',
+ "Popover component should contain the discard button"
+ );
+});
+
+QUnit.test('activity mark done popover mark done without feedback', async function (assert) {
+ assert.expect(7);
+
+ await this.start({
+ async mockRPC(route, args) {
+ if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
+ assert.step('action_feedback');
+ assert.strictEqual(args.args.length, 1);
+ assert.strictEqual(args.args[0].length, 1);
+ assert.strictEqual(args.args[0][0], 12);
+ assert.strictEqual(args.kwargs.attachment_ids.length, 0);
+ assert.notOk(args.kwargs.feedback);
+ return;
+ }
+ if (route === '/web/dataset/call_kw/mail.activity/unlink') {
+ // 'unlink' on non-existing record raises a server crash
+ throw new Error("'unlink' RPC on activity must not be called (already unlinked from mark as done)");
+ }
+ return this._super(...arguments);
+ },
+ });
+ const activity = this.env.models['mail.activity'].create({
+ canWrite: true,
+ category: 'not_upload_file',
+ id: 12,
+ thread: [['insert', { id: 42, model: 'res.partner' }]],
+ });
+ await this.createActivityMarkDonePopoverComponent(activity);
+
+ document.querySelector('.o_ActivityMarkDonePopover_doneButton').click();
+ assert.verifySteps(
+ ['action_feedback'],
+ "Mark done and schedule next button should call the right rpc"
+ );
+});
+
+QUnit.test('activity mark done popover mark done with feedback', async function (assert) {
+ assert.expect(7);
+
+ await this.start({
+ async mockRPC(route, args) {
+ if (route === '/web/dataset/call_kw/mail.activity/action_feedback') {
+ assert.step('action_feedback');
+ assert.strictEqual(args.args.length, 1);
+ assert.strictEqual(args.args[0].length, 1);
+ assert.strictEqual(args.args[0][0], 12);
+ assert.strictEqual(args.kwargs.attachment_ids.length, 0);
+ assert.strictEqual(args.kwargs.feedback, 'This task is done');
+ return;
+ }
+ if (route === '/web/dataset/call_kw/mail.activity/unlink') {
+ // 'unlink' on non-existing record raises a server crash
+ throw new Error("'unlink' RPC on activity must not be called (already unlinked from mark as done)");
+ }
+ return this._super(...arguments);
+ },
+ });
+ const activity = this.env.models['mail.activity'].create({
+ canWrite: true,
+ category: 'not_upload_file',
+ id: 12,
+ thread: [['insert', { id: 42, model: 'res.partner' }]],
+ });
+ await this.createActivityMarkDonePopoverComponent(activity);
+
+ let feedbackTextarea = document.querySelector('.o_ActivityMarkDonePopover_feedback');
+ feedbackTextarea.focus();
+ document.execCommand('insertText', false, 'This task is done');
+ document.querySelector('.o_ActivityMarkDonePopover_doneButton').click();
+ assert.verifySteps(
+ ['action_feedback'],
+ "Mark done and schedule next button should call the right rpc"
+ );
+});
+
+QUnit.test('activity mark done popover mark done and schedule next', async function (assert) {
+ assert.expect(6);
+
+ const bus = new Bus();
+ bus.on('do-action', null, payload => {
+ assert.step('activity_action');
+ throw new Error("The do-action event should not be triggered when the route doesn't return an action");
+ });
+ await this.start({
+ async mockRPC(route, args) {
+ if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') {
+ assert.step('action_feedback_schedule_next');
+ assert.strictEqual(args.args.length, 1);
+ assert.strictEqual(args.args[0].length, 1);
+ assert.strictEqual(args.args[0][0], 12);
+ assert.strictEqual(args.kwargs.feedback, 'This task is done');
+ return false;
+ }
+ if (route === '/web/dataset/call_kw/mail.activity/unlink') {
+ // 'unlink' on non-existing record raises a server crash
+ throw new Error("'unlink' RPC on activity must not be called (already unlinked from mark as done)");
+ }
+ return this._super(...arguments);
+ },
+ env: { bus },
+ });
+ const activity = this.env.models['mail.activity'].create({
+ canWrite: true,
+ category: 'not_upload_file',
+ id: 12,
+ thread: [['insert', { id: 42, model: 'res.partner' }]],
+ });
+ await this.createActivityMarkDonePopoverComponent(activity);
+
+ let feedbackTextarea = document.querySelector('.o_ActivityMarkDonePopover_feedback');
+ feedbackTextarea.focus();
+ document.execCommand('insertText', false, 'This task is done');
+ await afterNextRender(() => {
+ document.querySelector('.o_ActivityMarkDonePopover_doneScheduleNextButton').click();
+ });
+ assert.verifySteps(
+ ['action_feedback_schedule_next'],
+ "Mark done and schedule next button should call the right rpc and not trigger an action"
+ );
+});
+
+QUnit.test('[technical] activity mark done & schedule next with new action', async function (assert) {
+ assert.expect(3);
+
+ const bus = new Bus();
+ bus.on('do-action', null, payload => {
+ assert.step('activity_action');
+ assert.deepEqual(
+ payload.action,
+ { type: 'ir.actions.act_window' },
+ "The content of the action should be correct"
+ );
+ });
+ await this.start({
+ async mockRPC(route, args) {
+ if (route === '/web/dataset/call_kw/mail.activity/action_feedback_schedule_next') {
+ return { type: 'ir.actions.act_window' };
+ }
+ return this._super(...arguments);
+ },
+ env: { bus },
+ });
+ const activity = this.env.models['mail.activity'].create({
+ canWrite: true,
+ category: 'not_upload_file',
+ id: 12,
+ thread: [['insert', { id: 42, model: 'res.partner' }]],
+ });
+ await this.createActivityMarkDonePopoverComponent(activity);
+
+ await afterNextRender(() => {
+ document.querySelector('.o_ActivityMarkDonePopover_doneScheduleNextButton').click();
+ });
+ assert.verifySteps(
+ ['activity_action'],
+ "The action returned by the route should be executed"
+ );
+});
+
+});
+});
+});
+
+});