diff --git a/addons/crm/static/src/views/crm_kanban/crm_column_progress.js b/addons/crm/static/src/views/crm_kanban/crm_column_progress.js new file mode 100644 index 00000000000..99f8fc718a0 --- /dev/null +++ b/addons/crm/static/src/views/crm_kanban/crm_column_progress.js @@ -0,0 +1,36 @@ +/** @odoo-module */ + +import { onWillStart } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { ColumnProgress } from "@web/views/view_components/column_progress"; +import { session } from "@web/session"; + +export class CrmColumnProgress extends ColumnProgress { + static props = { + ...ColumnProgress.props, + progressAttributes: true, + }; + static template = "crm.ColumnProgress"; + setup() { + super.setup(); + this.user = useService("user"); + this.showRecurringRevenue = false; + + onWillStart(async () => { + if (this.props.progressAttributes.recurring_revenue_sum_field) { + this.showRecurringRevenue = await this.user.hasGroup("crm.group_use_recurring_revenues"); + } + }); + } + + getRecurringRevenueGroupAggregate(group) { + const rrField = this.props.progressAttributes.recurring_revenue_sum_field; + const value = group.getAggregates(rrField.name); + const title = rrField.string || this.env._t("Count"); + let currency = false; + if (value && rrField.currency_field) { + currency = session.currencies[session.company_currency_id]; + } + return { value, currency, title }; + } +} diff --git a/addons/crm/static/src/views/crm_kanban/crm_column_progress.xml b/addons/crm/static/src/views/crm_kanban/crm_column_progress.xml new file mode 100644 index 00000000000..5c7cd516490 --- /dev/null +++ b/addons/crm/static/src/views/crm_kanban/crm_column_progress.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.js b/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.js index 066034c04ff..11a6dcf47f8 100644 --- a/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.js +++ b/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.js @@ -1,33 +1,11 @@ /** @odoo-module **/ +import { CrmColumnProgress } from "./crm_column_progress"; import { KanbanRenderer } from "@web/views/kanban/kanban_renderer"; -import { session } from "@web/session"; -import { useService } from "@web/core/utils/hooks"; -const { onWillStart } = owl; - -export class CrmKanbanRenderer extends KanbanRenderer { - setup() { - super.setup(); - this.user = useService("user"); - - this.showRecurringRevenue = false; - onWillStart(async () => { - this.showRecurringRevenue = - this.props.list.model.progressAttributes.recurring_revenue_sum_field && - (await this.user.hasGroup("crm.group_use_recurring_revenues")); - }); - } - - getRecurringRevenueGroupAggregate(group) { - const rrField = this.props.list.model.progressAttributes.recurring_revenue_sum_field; - const value = group.getAggregates(rrField.name); - const title = rrField.string || this.env._t("Count"); - let currency = false; - if (value && rrField.currency_field) { - currency = session.currencies[session.company_currency_id]; - } - return { value, currency, title }; - } -} +export class CrmKanbanRenderer extends KanbanRenderer {} CrmKanbanRenderer.template = "crm.CrmKanbanRenderer"; +CrmKanbanRenderer.components = { + ...KanbanRenderer.components, + CrmColumnProgress, +}; diff --git a/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.xml b/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.xml index 06b3880f621..f90080e2218 100644 --- a/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.xml +++ b/addons/crm/static/src/views/crm_kanban/crm_kanban_renderer.xml @@ -1,24 +1,8 @@ - - + + - - - - - - + - - - - diff --git a/addons/crm/static/tests/crm_kanban_progress_bar_mrr_sum_field_tests.js b/addons/crm/static/tests/crm_kanban_progress_bar_mrr_sum_field_tests.js index 5053f5dd515..ed6e2c86914 100644 --- a/addons/crm/static/tests/crm_kanban_progress_bar_mrr_sum_field_tests.js +++ b/addons/crm/static/tests/crm_kanban_progress_bar_mrr_sum_field_tests.js @@ -9,7 +9,7 @@ import { getFixture, patchWithCleanup, } from '@web/../tests/helpers/utils'; -import { KanbanAnimatedNumber } from "@web/views/kanban/kanban_animated_number"; +import { AnimatedNumber } from "@web/views/view_components/animated_number"; const serviceRegistry = registry.category("services"); @@ -18,7 +18,7 @@ let serverData; QUnit.module('Crm Kanban Progressbar', { beforeEach: function () { - patchWithCleanup(KanbanAnimatedNumber, { enableAnimations: false }); + patchWithCleanup(AnimatedNumber, { enableAnimations: false }); serverData = { models: { 'res.users': { @@ -124,7 +124,7 @@ QUnit.module('Crm Kanban Progressbar', { `, }); - const reccurringRevenueValues = [...target.querySelectorAll('.o_kanban_counter_side:nth-child(3)')].map((elem) => elem.textContent); + const reccurringRevenueValues = [...target.querySelectorAll('.o_animated_number:nth-child(3)')].map((elem) => elem.textContent); // When no values are given in column it should return 0 and counts value if given. assert.deepEqual(reccurringRevenueValues, ["+20", "+0", "+45"], @@ -157,7 +157,7 @@ QUnit.module('Crm Kanban Progressbar', { }); //MRR before state change - let reccurringRevenueNoValues = [...target.querySelectorAll('.o_kanban_counter_side:nth-child(3)')].map((elem) => elem.textContent); + let reccurringRevenueNoValues = [...target.querySelectorAll('.o_animated_number:nth-child(3)')].map((elem) => elem.textContent); assert.deepEqual(reccurringRevenueNoValues, ['+30','+35'], "counter should display the sum of recurring_revenue_monthly values"); @@ -169,7 +169,7 @@ QUnit.module('Crm Kanban Progressbar', { ); //check MRR after drag&drop - reccurringRevenueNoValues = [...target.querySelectorAll('.o_kanban_counter_side:nth-child(3)')].map((elem) => elem.textContent); + reccurringRevenueNoValues = [...target.querySelectorAll('.o_animated_number:nth-child(3)')].map((elem) => elem.textContent); assert.deepEqual(reccurringRevenueNoValues, ['+25', '+40'], "counter should display the sum of recurring_revenue_monthly correctly after drag and drop"); @@ -177,7 +177,7 @@ QUnit.module('Crm Kanban Progressbar', { await click(target.querySelector('.o_kanban_group:nth-child(2) .progress-bar[aria-valuenow="2"]'), null); //check MRR after applying filter - reccurringRevenueNoValues = [...target.querySelectorAll('.o_kanban_counter_side:nth-child(3)')].map((elem) => elem.textContent); + reccurringRevenueNoValues = [...target.querySelectorAll('.o_animated_number:nth-child(3)')].map((elem) => elem.textContent); assert.deepEqual(reccurringRevenueNoValues, ['+25','+25'], "counter should display the sum of recurring_revenue_monthly only of overdue filter in 1st column"); }); diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index bceb38c63f5..944c972843e 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -111,6 +111,7 @@ 'web/static/src/views/kanban/**/*', 'web/static/src/views/list/**/*', 'web/static/src/views/view_button/**/*', + 'web/static/src/views/view_components/**/*', 'web/static/src/views/view_dialogs/**/*', 'web/static/src/views/widgets/**/*', 'web/static/src/webclient/**/*', diff --git a/addons/web/static/src/views/kanban/kanban_column_progressbar.scss b/addons/web/static/src/views/kanban/kanban_column_progressbar.scss index 0079be23ac8..a47ac2f166c 100644 --- a/addons/web/static/src/views/kanban/kanban_column_progressbar.scss +++ b/addons/web/static/src/views/kanban/kanban_column_progressbar.scss @@ -1,15 +1,3 @@ -@keyframes kanban-counter-grow { - 30% { - transform: scale3d(1.1, 1.1, 1.1); - } -} - -@keyframes kanban-counter-grow-huge { - 30% { - transform: scale3d(1.3, 1.3, 1.3); - } -} - @mixin o-kanban-css-filter($class, $accent-color) { // Provide CSS reordering and highlight &.o_kanban_group_show_#{$class} { @@ -44,21 +32,9 @@ .o_kanban_counter { transition: opacity 0.3s ease 0s; - > .o_kanban_counter_progress { + > .o_column_progress { height: $font-size-sm; } - - > .o_kanban_counter_side { - transform-origin: right center; - - &.o_kanban_grow { - animation: kanban-counter-grow 1s ease 0s 1 normal none running; - } - - &.o_kanban_grow_huge { - animation: kanban-counter-grow-huge 1s ease 0s 1 normal none running; - } - } } .o_kanban_group { diff --git a/addons/web/static/src/views/kanban/kanban_record.js b/addons/web/static/src/views/kanban/kanban_record.js index ad78aaf130c..c144cb04c76 100644 --- a/addons/web/static/src/views/kanban/kanban_record.js +++ b/addons/web/static/src/views/kanban/kanban_record.js @@ -69,7 +69,7 @@ function getColorName(value) { * exist * @returns {string} */ -function getImageSrcFromRecordInfo(record, model, field, idOrIds, placeholder) { +export function getImageSrcFromRecordInfo(record, model, field, idOrIds, placeholder) { const id = (Array.isArray(idOrIds) ? idOrIds[0] : idOrIds) || null; const isCurrentRecord = record.resModel === model && (record.resId === id || (!record.resId && !id)); @@ -99,7 +99,7 @@ function getImageSrcFromRecordInfo(record, model, field, idOrIds, placeholder) { * @param {string} fieldName * @returns {any} */ -function getRawValue(record, fieldName) { +export function getRawValue(record, fieldName) { const field = record.fields[fieldName]; const value = record.data[fieldName]; switch (field.type) { @@ -127,7 +127,7 @@ function getRawValue(record, fieldName) { * @param {string} fieldName * @returns {string} */ -function getValue(record, fieldName) { +export function getValue(record, fieldName) { const field = record.fields[fieldName]; const value = record.data[fieldName]; const formatter = formatters.get(field.type, String); @@ -149,7 +149,7 @@ function isBinSize(value) { * @param {string} innerHTML * @returns {boolean} true if no content found or if containing only formatting tags */ -function isHtmlEmpty(innerHTML = "") { +export function isHtmlEmpty(innerHTML = "") { const div = Object.assign(document.createElement("div"), { innerHTML }); return div.innerText.trim() === ""; } diff --git a/addons/web/static/src/views/kanban/kanban_renderer.js b/addons/web/static/src/views/kanban/kanban_renderer.js index 3e9c1a1838b..6896cf6f3a1 100644 --- a/addons/web/static/src/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/views/kanban/kanban_renderer.js @@ -14,7 +14,7 @@ import { isAllowedDateField } from "@web/views/relational_model"; import { isNull, isRelational } from "@web/views/utils"; import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog"; import { useBounceButton } from "@web/views/view_hook"; -import { KanbanAnimatedNumber } from "./kanban_animated_number"; +import { ColumnProgress } from "@web/views/view_components/column_progress"; import { KanbanColumnQuickCreate } from "./kanban_column_quick_create"; import { KanbanRecord } from "./kanban_record"; import { KanbanRecordQuickCreate } from "./kanban_record_quick_create"; @@ -616,7 +616,7 @@ KanbanRenderer.props = [ KanbanRenderer.components = { Dropdown, DropdownItem, - KanbanAnimatedNumber, + ColumnProgress, KanbanColumnQuickCreate, KanbanRecord, KanbanRecordQuickCreate, diff --git a/addons/web/static/src/views/kanban/kanban_renderer.xml b/addons/web/static/src/views/kanban/kanban_renderer.xml index c2a1faae1af..daf8ebef9c0 100644 --- a/addons/web/static/src/views/kanban/kanban_renderer.xml +++ b/addons/web/static/src/views/kanban/kanban_renderer.xml @@ -56,31 +56,7 @@
-
- -
- -
- - +
diff --git a/addons/web/static/src/views/kanban/kanban_animated_number.js b/addons/web/static/src/views/view_components/animated_number.js similarity index 72% rename from addons/web/static/src/views/kanban/kanban_animated_number.js rename to addons/web/static/src/views/view_components/animated_number.js index beb5eb900f1..cc90d038597 100644 --- a/addons/web/static/src/views/kanban/kanban_animated_number.js +++ b/addons/web/static/src/views/view_components/animated_number.js @@ -5,7 +5,24 @@ import { formatInteger } from "@web/views/fields/formatters"; import { Component, onWillUpdateProps, onWillUnmount, useState } from "@odoo/owl"; -export class KanbanAnimatedNumber extends Component { +export class AnimatedNumber extends Component { + static template = "web.AnimatedNumber"; + static props = { + value: Number, + duration: Number, + animationClass: { type: String, optional: true }, + currency: { type: [Object, Boolean], optional: true }, + title: { type: String, optional: true }, + slots: { + type: Object, + shape: { + prefix: { type: Object, optional: true }, + }, + optional: true, + }, + }; + static enableAnimations = true; + setup() { this.formatInteger = formatInteger; this.state = useState({ value: this.props.value }); @@ -37,20 +54,3 @@ export class KanbanAnimatedNumber extends Component { return this.formatInteger(value, { humanReadable: true, decimals: 0, minDigits: 3 }); } } - -KanbanAnimatedNumber.template = "web.KanbanAnimatedNumber"; -KanbanAnimatedNumber.props = { - value: Number, - duration: Number, - animationClass: { type: String, optional: true }, - currency: { type: [Object, Boolean], optional: true }, - title: { type: String, optional: true }, - slots: { - type: Object, - shape: { - prefix: { type: Object, optional: true }, - }, - optional: true, - }, -}; -KanbanAnimatedNumber.enableAnimations = true; diff --git a/addons/web/static/src/views/view_components/animated_number.scss b/addons/web/static/src/views/view_components/animated_number.scss new file mode 100644 index 00000000000..a1be54bccb3 --- /dev/null +++ b/addons/web/static/src/views/view_components/animated_number.scss @@ -0,0 +1,23 @@ +@keyframes animated-counter-grow { + 30% { + transform: scale3d(1.1, 1.1, 1.1); + } +} + +@keyframes animated-counter-grow-huge { + 30% { + transform: scale3d(1.3, 1.3, 1.3); + } +} + +.o_animated_number { + transform-origin: right center; + + &.o_animated_grow { + animation: animated-counter-grow 1s ease 0s 1 normal none running; + } + + &.o_animated_grow_huge { + animation: animated-counter-grow-huge 1s ease 0s 1 normal none running; + } +} diff --git a/addons/web/static/src/views/kanban/kanban_animated_number.xml b/addons/web/static/src/views/view_components/animated_number.xml similarity index 67% rename from addons/web/static/src/views/kanban/kanban_animated_number.xml rename to addons/web/static/src/views/view_components/animated_number.xml index ec0725fe414..23315e90289 100644 --- a/addons/web/static/src/views/kanban/kanban_animated_number.xml +++ b/addons/web/static/src/views/view_components/animated_number.xml @@ -1,8 +1,8 @@ - -
+ +
diff --git a/addons/web/static/src/views/view_components/column_progress.js b/addons/web/static/src/views/view_components/column_progress.js new file mode 100644 index 00000000000..4adca00e199 --- /dev/null +++ b/addons/web/static/src/views/view_components/column_progress.js @@ -0,0 +1,15 @@ +/** @odoo-module **/ + +import { Component } from "@odoo/owl"; +import { AnimatedNumber } from "./animated_number"; + +export class ColumnProgress extends Component { + static components = { + AnimatedNumber, + }; + static template = "web.ColumnProgress"; + static props = { + aggregate: { type: Object }, + group: { type: Object }, + }; +} diff --git a/addons/web/static/src/views/view_components/column_progress.xml b/addons/web/static/src/views/view_components/column_progress.xml new file mode 100644 index 00000000000..9d78e83d8b5 --- /dev/null +++ b/addons/web/static/src/views/view_components/column_progress.xml @@ -0,0 +1,31 @@ + + + + +
+ +
+ +
+ +
+ + diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index c9735637d1d..b68b185a8ef 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -34,7 +34,7 @@ import { tooltipService } from "@web/core/tooltip/tooltip_service"; import { nbsp } from "@web/core/utils/strings"; import { getNextTabableElement } from "@web/core/utils/ui"; import { session } from "@web/session"; -import { KanbanAnimatedNumber } from "@web/views/kanban/kanban_animated_number"; +import { AnimatedNumber } from "@web/views/view_components/animated_number"; import { KanbanController } from "@web/views/kanban/kanban_controller"; import { kanbanView } from "@web/views/kanban/kanban_view"; import { DynamicRecordList } from "@web/views/relational_model"; @@ -82,19 +82,17 @@ function getCardTexts(groupIndex) { } function getCounters() { - return [...target.querySelectorAll(".o_kanban_counter_side")].map( - (counter) => counter.innerText - ); + return [...target.querySelectorAll(".o_animated_number")].map((counter) => counter.innerText); } function getProgressBars(columnIndex) { const column = getColumn(columnIndex); - return [...column.querySelectorAll(".o_kanban_counter_progress .progress-bar")]; + return [...column.querySelectorAll(".o_column_progress .progress-bar")]; } function getTooltips(groupIndex) { const root = groupIndex >= 0 ? getColumn(groupIndex) : target; - return [...root.querySelectorAll(".o_kanban_counter_progress .progress-bar")] + return [...root.querySelectorAll(".o_column_progress .progress-bar")] .map((card) => card.dataset.tooltip) .filter(Boolean); } @@ -162,7 +160,7 @@ let target; QUnit.module("Views", (hooks) => { hooks.beforeEach(() => { - patchWithCleanup(KanbanAnimatedNumber, { enableAnimations: false }); + patchWithCleanup(AnimatedNumber, { enableAnimations: false }); serverData = { models: { partner: { @@ -8715,31 +8713,24 @@ QUnit.module("Views", (hooks) => { assert.containsN(target, ".o_kanban_group", 2); assert.deepEqual(getCounters(), ["1", "4"]); - assert.containsN( - target, - ".o_kanban_group:last-child .o_kanban_counter_progress .progress-bar", - 4 - ); + assert.containsN(target, ".o_kanban_group:last-child .o_column_progress .progress-bar", 4); assert.containsOnce( target, - ".o_kanban_group:last-child .o_kanban_counter_progress .progress-bar.bg-200", + ".o_kanban_group:last-child .o_column_progress .progress-bar.bg-200", "should have false kanban color" ); assert.hasClass( target.querySelector( - ".o_kanban_group:last-child .o_kanban_counter_progress .progress-bar.bg-200" + ".o_kanban_group:last-child .o_column_progress .progress-bar.bg-200" ), "bg-200" ); - await click( - target, - ".o_kanban_group:last-child .o_kanban_counter_progress .progress-bar.bg-200" - ); + await click(target, ".o_kanban_group:last-child .o_column_progress .progress-bar.bg-200"); assert.hasClass( target.querySelector( - ".o_kanban_group:last-child .o_kanban_counter_progress .progress-bar.bg-200" + ".o_kanban_group:last-child .o_column_progress .progress-bar.bg-200" ), "progress-bar-animated" ); @@ -8781,14 +8772,11 @@ QUnit.module("Views", (hooks) => { assert.containsN(target, ".o_kanban_group", 2); assert.deepEqual(getCounters(), ["-4", "51"]); - await click( - target, - ".o_kanban_group:last-child .o_kanban_counter_progress .progress-bar.bg-200" - ); + await click(target, ".o_kanban_group:last-child .o_column_progress .progress-bar.bg-200"); assert.hasClass( target.querySelector( - ".o_kanban_group:last-child .o_kanban_counter_progress .progress-bar.bg-200" + ".o_kanban_group:last-child .o_column_progress .progress-bar.bg-200" ), "progress-bar-animated" ); @@ -8942,7 +8930,7 @@ QUnit.module("Views", (hooks) => { groupBy: ["bar"], }); - await click(target, ".o_kanban_counter_progress .progress-bar.bg-success"); + await click(target, ".o_column_progress .progress-bar.bg-success"); assert.deepEqual(getCardTexts(), ["5"], "we should have 1 record shown"); @@ -11363,7 +11351,7 @@ QUnit.module("Views", (hooks) => { // Initial state: 2 columns, the "Yes" column contains 2 records "abc", 1 "def" and 1 "ghi" assert.deepEqual(getCounters(), ["1", "4"]); assert.containsN(getColumn(1), ".o_kanban_record", 4); - assert.containsN(getColumn(1), ".o_kanban_counter_progress .progress-bar", 3); + assert.containsN(getColumn(1), ".o_column_progress .progress-bar", 3); assert.strictEqual(getProgressBars(1)[0].style.width, "50%"); // abc: 2 assert.strictEqual(getProgressBars(1)[1].style.width, "25%"); // def: 1 assert.strictEqual(getProgressBars(1)[2].style.width, "25%"); // ghi: 1 @@ -11373,7 +11361,7 @@ QUnit.module("Views", (hooks) => { assert.deepEqual(getCounters(), ["1", "2"]); assert.containsN(getColumn(1), ".o_kanban_record", 2); - assert.containsN(getColumn(1), ".o_kanban_counter_progress .progress-bar", 3); + assert.containsN(getColumn(1), ".o_column_progress .progress-bar", 3); assert.strictEqual(getProgressBars(1)[0].style.width, "50%"); // abc: 2 assert.strictEqual(getProgressBars(1)[1].style.width, "25%"); // def: 1 assert.strictEqual(getProgressBars(1)[2].style.width, "25%"); // ghi: 1 @@ -11385,7 +11373,7 @@ QUnit.module("Views", (hooks) => { assert.deepEqual(getCounters(), ["1", "1"]); assert.containsN(getColumn(1), ".o_kanban_record", 2); - assert.containsN(getColumn(1), ".o_kanban_counter_progress .progress-bar", 3); + assert.containsN(getColumn(1), ".o_column_progress .progress-bar", 3); assert.strictEqual(getProgressBars(1)[0].style.width, "25%"); // abc: 1 assert.strictEqual(getProgressBars(1)[1].style.width, "50%"); // def: 2 assert.strictEqual(getProgressBars(1)[2].style.width, "25%"); // ghi: 1