diff --git a/addons/mail/static/src/js/views/activity/activity_cell.js b/addons/mail/static/src/js/views/activity/activity_cell.js new file mode 100644 index 00000000000..d1cdca9433e --- /dev/null +++ b/addons/mail/static/src/js/views/activity/activity_cell.js @@ -0,0 +1,42 @@ +odoo.define("mail.ActivityCell", function (require) { + "use strict"; + + require("mail.Activity"); + const field_registry = require('web.field_registry'); + + const KanbanActivity = field_registry.get('kanban_activity'); + + const ActivityCell = KanbanActivity.extend({ + /** + * @override + * @private + */ + _render() { + // replace clock by closest deadline + const $date = $('
'); + const date = new Date(this.record.data.closest_deadline); + // To remove year only if current year + if (moment().year() === moment(date).year()) { + $date.text(date.toLocaleDateString(moment().locale(), { + day: 'numeric', month: 'short' + })); + } else { + $date.text(moment(date).format('ll')); + } + this.$('a').html($date); + if (this.record.data.activity_ids.res_ids.length > 1) { + this.$('a').append($('', { + class: 'badge badge-light badge-pill border-0 ' + this.record.data.activity_state, + text: this.record.data.activity_ids.res_ids.length, + })); + } + if (this.$el.hasClass('show')) { + // note: this part of the rendering might be asynchronous + this._renderDropdown(); + } + } + }); + + return ActivityCell; + +}); diff --git a/addons/mail/static/src/js/views/activity/activity_controller.js b/addons/mail/static/src/js/views/activity/activity_controller.js index d34893cb2e3..8de28d65cc9 100644 --- a/addons/mail/static/src/js/views/activity/activity_controller.js +++ b/addons/mail/static/src/js/views/activity/activity_controller.js @@ -1,6 +1,7 @@ odoo.define('mail.ActivityController', function (require) { "use strict"; +require('mail.Activity'); var BasicController = require('web.BasicController'); var core = require('web.core'); var field_registry = require('web.field_registry'); @@ -20,6 +21,18 @@ var ActivityController = BasicController.extend({ // Public //-------------------------------------------------------------------------- + /** + * @override + * @param parent + * @param model + * @param renderer + * @param {Object} params + * @param {String} params.title The title used in schedule activity dialog + */ + init: function (parent, model, renderer, params) { + this._super.apply(this, arguments); + this.title = params.title; + }, /** * Overridden to remove the pager as it makes no sense in this view. * @@ -38,17 +51,16 @@ var ActivityController = BasicController.extend({ */ _onScheduleActivity: function () { var self = this; - var state = this.model.get(this.handle); new ViewDialogs.SelectCreateDialog(this, { res_model: state.model, domain: this.model.originalDomain, - title: _.str.sprintf(_t("Search: %s"), this.renderer.arch.attrs.string), + title: _.str.sprintf(_t("Search: %s"), this.title), no_create: !this.activeActions.create, disable_multiple_selection: true, context: state.context, on_selected: function (record) { - var fakeRecord = self.renderer.getKanbanActivityData({}, record[0]); + var fakeRecord = state.getKanbanActivityData({}, record[0]); var widget = new KanbanActivity(self, 'activity_ids', fakeRecord, {}); widget.scheduleActivity(); }, @@ -79,7 +91,7 @@ var ActivityController = BasicController.extend({ }, /** * @private - * @param {OdooEvent} ev + * @param {CustomEvent} ev */ _onSendMailTemplate: function (ev) { var templateID = ev.data.templateID; diff --git a/addons/mail/static/src/js/views/activity/activity_model.js b/addons/mail/static/src/js/views/activity/activity_model.js index 3a511c0ae57..7099bf4a44a 100644 --- a/addons/mail/static/src/js/views/activity/activity_model.js +++ b/addons/mail/static/src/js/views/activity/activity_model.js @@ -1,15 +1,14 @@ odoo.define('mail.ActivityModel', function (require) { 'use strict'; -var BasicModel = require('web.BasicModel'); -var session = require('web.session'); +const BasicModel = require('web.BasicModel'); +const session = require('web.session'); -var ActivityModel = BasicModel.extend({ +const ActivityModel = BasicModel.extend({ //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- - /** * Add the following (activity specific) keys when performing a `get` on the * main list datapoint: @@ -22,10 +21,44 @@ var ActivityModel = BasicModel.extend({ get: function () { var result = this._super.apply(this, arguments); if (result && result.model === this.modelName && result.type === 'list') { - _.extend(result, this.additionalData); + _.extend(result, this.additionalData, {getKanbanActivityData: this.getKanbanActivityData}); } return result; }, + /** + * @param {Object} activityGroup + * @param {integer} resId + * @returns {Object} + */ + getKanbanActivityData(activityGroup, resId) { + return { + data: { + activity_ids: { + model: 'mail.activity', + res_ids: activityGroup.ids, + }, + activity_state: activityGroup.state, + closest_deadline: activityGroup.o_closest_deadline, + }, + fields: { + activity_ids: {}, + activity_state: { + selection: [ + ['overdue', "Overdue"], + ['today', "Today"], + ['planned', "Planned"], + ], + }, + }, + fieldsInfo: {}, + model: this.model, + type: 'record', + res_id: resId, + getContext: function () { + return {}; + }, + }; + }, /** * @override * @param {Array[]} params.domain diff --git a/addons/mail/static/src/js/views/activity/activity_renderer.js b/addons/mail/static/src/js/views/activity/activity_renderer.js index 7e106a4418a..3e697dd11e6 100644 --- a/addons/mail/static/src/js/views/activity/activity_renderer.js +++ b/addons/mail/static/src/js/views/activity/activity_renderer.js @@ -1,163 +1,141 @@ odoo.define('mail.ActivityRenderer', function (require) { "use strict"; -var AbstractRenderer = require('web.AbstractRenderer'); -var ActivityRecord = require('mail.ActivityRecord'); -var config = require('web.config'); -var core = require('web.core'); -var field_registry = require('web.field_registry'); -var KanbanColumnProgressBar = require('web.KanbanColumnProgressBar'); -var qweb = require('web.QWeb'); -var session = require('web.session'); -var utils = require('web.utils'); +const AbstractRendererOwl = require('web.AbstractRendererOwl'); +const ActivityCell = require('mail.ActivityCell'); +const ActivityRecord = require('mail.ActivityRecord'); +const { ComponentAdapter } = require('web.OwlCompatibility'); +const core = require('web.core'); +const KanbanColumnProgressBar = require('web.KanbanColumnProgressBar'); +const QWeb = require('web.QWeb'); +const session = require('web.session'); +const utils = require('web.utils'); -var KanbanActivity = field_registry.get('kanban_activity'); -var _t = core._t; -var QWeb = core.qweb; +const _t = core._t; -var ActivityRenderer = AbstractRenderer.extend({ - className: 'o_activity_view', - custom_events: { - 'set_progress_bar_state': '_onSetProgressBarState', - }, - events: { - 'click .o_send_mail_template': '_onSenMailTemplateClicked', - 'click .o_activity_empty_cell': '_onEmptyCell', - 'click .o_record_selector': '_onRecordSelector', - }, +const { useState } = owl.hooks; - /** - * @override - * @param {Object} params.templates - */ - init: function (parent, state, params) { - this._super.apply(this, arguments); +/** + * Owl Component Adapter for ActivityRecord which is KanbanRecord (Odoo Widget) + * TODO: Remove this adapter when ActivityRecord is a Component + */ +class ActivityRecordAdapter extends ComponentAdapter { + render() { + _.invoke(_.pluck(this.widget.subWidgets, '$el'), 'detach'); + this.widget._render(); + } - this.qweb = new qweb(config.isDebug(), {_s: session.origin}); - this.qweb.add_template(utils.json_node_to_xml(params.templates)); - }, + update(nextProps) { + const state = nextProps.widgetArgs[0]; + this.widget._setState(state); + } +} + +/** + * Owl Component Adapter for ActivityCell which is BasicActivity (AbstractField) + * TODO: Remove this adapter when ActivityCell is a Component + */ +class ActivityCellAdapter extends ComponentAdapter { + render() { + this.widget._render(); + } + + update(nextProps) { + const record = nextProps.widgetArgs[1]; + this.widget._reset(record); + } +} + +/** + * Owl Component Adapter for KanbanColumnProgressBar (Odoo Widget) + * TODO: Remove this adapter when KanbanColumnProgressBar is a Component + */ +class KanbanColumnProgressBarAdapter extends ComponentAdapter { + render() { + this.widget._render(); + } + + update(nextProps) { + const options = nextProps.widgetArgs[0]; + const columnState = nextProps.widgetArgs[1]; + + const columnId = options.columnID; + const nextActiveFilter = options.progressBarStates[columnId].activeFilter; + this.widget.activeFilter = nextActiveFilter ? this.widget.activeFilter : false; + this.widget.columnState = columnState; + this.widget.computeCounters(); + } + + _trigger_up(ev) { + // KanbanColumnProgressBar triggers 3 events before being mounted + // but we don't need to listen to them in our case. + if (this.el) { + super._trigger_up(ev); + } + } +} + +class ActivityRenderer extends AbstractRendererOwl { + constructor(parent, props) { + super(...arguments); + this.qweb = new QWeb(this.env.isDebug(), {_s: session.origin}); + this.qweb.add_template(utils.json_node_to_xml(props.templates)); + this.activeFilter = useState({ + state: null, + activityTypeId: null, + resIds: [] + }); + this.widgetComponents = { + ActivityRecord, + ActivityCell, + KanbanColumnProgressBar, + }; + } //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- /** - * @param {Object} activityGroup - * @param {integer} resID - * @returns {Object} + * Gets all activity resIds in the view. + * + * @returns filtered resIds first then the rest. */ - getKanbanActivityData: function (activityGroup, resID) { + get activityResIds() { + const copiedActivityResIds = Array.from(this.props.activity_res_ids) + return copiedActivityResIds.sort((a, b) => this.activeFilter.resIds.includes(a) ? -1 : 0); + } + + /** + * Gets all existing activity type ids. + */ + get activityTypeIds() { + const activities = Object.values(this.props.grouped_activities); + const activityIds = activities.flatMap(Object.keys); + const uniqueIds = Array.from(new Set(activityIds)); + return uniqueIds.map(Number); + } + + getProgressBarOptions(typeId) { return { - data: { - activity_ids: { - model: 'mail.activity', - res_ids: activityGroup.ids, - }, - activity_state: activityGroup.state, - }, - fields: { - activity_ids: {}, - activity_state: { - selection: [ - ['overdue', "Overdue"], - ['today', "Today"], - ['planned', "Planned"], - ], - }, - }, - fieldsInfo: {}, - model: this.state.model, - type: 'record', - res_id: resID, - getContext: function () { - return {}; // session.user_context - }, - //todo intercept event or changes on record to update view - }; - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @override - * @private - */ - _getRecord: function (recordId) { - return _.findWhere(this.state.data, { res_id: recordId }); - }, - /** - * @override - * @private - */ - _render: function () { - var self = this; - this.$el.addClass('table-responsive'); - this.$el.html(QWeb.render('mail.ActivityView', { isEmpty: !this.state.activity_types.length })); - return Promise.all([ - this._super.apply(this, arguments), - this._renderHeader(), - this._renderBody(), - ]).then(function (result) { - self.$('table') - .append(result[1]) - .append(result[2]) - .append(self._renderFooter()); - }); - }, - /** - * @private - * @returns {Promise} a jquery element - */ - _renderBody: function () { - var defs = _.map(this.state.activity_res_ids, this._renderRow.bind(this)); - return Promise.all(defs).then(function ($rows) { - return $('').append($rows); - }); - }, - /** - * @private - * @returns {jQueryElement} a element - */ - _renderFooter: function () { - return QWeb.render('mail.ActivityViewFooter'); - }, - /** - * @private - * @returns {Promise} - */ - _renderHeader: function () { - var self = this; - var activityTypeIds = _.unique(_.flatten(_.map(this.state.grouped_activities, function (act) { return _.keys(act); }))); - var $thead = $(QWeb.render('mail.ActivityViewHeader', { - types: this.state.activity_types, - activityTypeIDs: _.map(activityTypeIds, Number), - })); - var defs = []; - activityTypeIds.forEach(function (typeId) { - defs.push(self._renderProgressBar($thead, typeId)); - }); - return Promise.all(defs).then(function () { - return $thead; - }); - }, - /** - * @private - * @returns {Promise} - */ - _renderProgressBar: function ($thead, typeId) { - var counts = { planned: 0, today: 0, overdue: 0 }; - _.each(this.state.grouped_activities, function (act) { - if (_.contains(_.keys(act), typeId.toString())) { - counts[act[typeId].state] += 1; - } - }); - var progressBar = new KanbanColumnProgressBar(this, { columnID: typeId, - progressBarStates: {}, - }, { - count: _.reduce(_.values(counts), (x, y) => x + y), + progressBarStates: { + [typeId]: { + activeFilter: this.activeFilter.activityTypeId === typeId, + }, + }, + }; + } + + getProgressBarColumnState(typeId) { + const counts = { planned: 0, today: 0, overdue: 0 }; + for (let activities of Object.values(this.props.grouped_activities)) { + if (typeId in activities) { + counts[activities[typeId].state] += 1; + } + } + return { + count: Object.values(counts).reduce((x, y) => x + y), fields: { activity_state: { type: 'selection', @@ -173,141 +151,58 @@ var ActivityRenderer = AbstractRenderer.extend({ colors: { planned: 'success', today: 'warning', overdue: 'danger' }, counts: counts, }, - }); - return progressBar.appendTo($thead.find('th[data-activity-type-id=' + typeId + ']')); - }, - /** - * @private - * @param {integer} resId - * @returns {Promise} - */ - _renderRow: function (resId) { - var self = this; - var defs = []; - var record = this._getRecord(resId); - var $nameTD = $('', { - class: _.contains(this.filteredResIDs, resId) ? 'o_activity_filter_' + this.activeFilter : '', - }); - var activityRecord = new ActivityRecord(this, record, { qweb: this.qweb }); - defs.push(activityRecord.appendTo($nameTD)); - - var $cells = _.map(this.state.activity_types, function (node) { - var activity_type_id = node[0]; - var activity_group = self.state.grouped_activities[resId]; - activity_group = activity_group && activity_group[activity_type_id] || {count: 0, ids: [], state: false}; - - var $td = $(QWeb.render('mail.ActivityViewRow', { - resId: resId, - activityGroup: activity_group, - activityTypeId: activity_type_id, - widget: self, - })); - if (activity_group.state) { - var record = self.getKanbanActivityData(activity_group, resId); - var widget = new KanbanActivity(self, "activity_ids", record, {}); - var def = widget.appendTo($td).then(function () { - // replace clock by closest deadline - var $date = $('
'); - // split the year/month/day to create a date (to have a date, no tz, instead of a datetime, tz dependant) - var splitDate = activity_group.o_closest_deadline.split('-'); - // the value representing the month, ranges from 0 for January up to 11 for December. - var date = new Date(splitDate[0], splitDate[1]-1, splitDate[2]); - // To remove year only if current year - if (moment().year() === moment(date).year()) { - $date.text(date.toLocaleDateString(moment().locale(), { day: 'numeric', month: 'short' })); - } else { - $date.text(moment(date).format('ll')); - } - $td.find('a').html($date); - if (activity_group.count > 1) { - $td.find('a').append($('', { - class: 'badge badge-light badge-pill border-0 ' + activity_group.state, - text: activity_group.count, - })); - } - }); - defs.push(def); - } - return $td; - }); - return Promise.all(defs).then(function () { - var $tr = $('', {class: 'o_data_row'}).attr('data-res-id', resId) - .append($nameTD) - .append($cells); - return $tr; - }); - }, + }; + } //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- - /** * @private * @param {MouseEvent} ev */ - _onEmptyCell: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - - var data = $(ev.currentTarget).data(); - this.trigger_up('empty_cell_clicked', data); - }, - /** - * @private - * @param {MouseEvent} ev - */ - _onRecordSelector: function (ev) { - ev.stopPropagation(); - this.trigger_up('schedule_activity'); - }, - /** - * @private - * @override - * @param {MouseEvent} ev - */ - _onSenMailTemplateClicked: function (ev) { - var $target = $(ev.currentTarget); - var templateID = $target.data('template-id'); - var activityTypeID = $target.closest('th').data('activity-type-id'); - this.trigger_up('send_mail_template', { - activityTypeID: activityTypeID, - templateID: templateID, + _onEmptyCellClicked(ev) { + this.trigger('empty_cell_clicked', { + resId: parseInt(ev.currentTarget.dataset.resId, 10), + activityTypeId: parseInt(ev.currentTarget.dataset.activityTypeId, 10), }); - }, + } /** - * Rearrange body part of table based on active filter. - * * @private - * @param {OdooEvent} ev + * @param {MouseEvent} ev */ - _onSetProgressBarState: function (ev) { - var self = this; - - this.$('th[class*="o_activity_filter_"]').attr('class', 'o_activity_type_cell'); - this.$('.o_kanban_counter_progress div').removeClass('active progress-bar-striped'); - - var data = ev.data; - var arrangedRecords = this.state.activity_res_ids; - this.activeFilter = data.values.activeFilter; - if (this.activeFilter) { - var filteredResIds = _.map(_.keys(_.pick(this.state.grouped_activities, function (act) { - return act[data.columnID] && act[data.columnID].state === self.activeFilter; - })), Number); - arrangedRecords = _.union(_.intersection(this.state.activity_res_ids, filteredResIds), this.state.activity_res_ids); - this.filteredResIDs = filteredResIds; - } - var defs = _.map(arrangedRecords, this._renderRow.bind(this)); - Promise.all(defs).then(function ($rows) { - self.$('tbody').html($rows); + _onSendMailTemplateClicked(ev) { + this.trigger('send_mail_template', { + activityTypeID: parseInt(ev.currentTarget.dataset.activityTypeId, 10), + templateID: parseInt(ev.currentTarget.dataset.templateId, 10), }); - - if (this.activeFilter) { - var $header = this.$('th.o_activity_type_cell[data-activity-type-id=' + data.columnID + ']'); - $header.addClass('o_activity_filter_' + this.activeFilter); + } + /** + * @private + * @param {CustomEvent} ev + */ + _onSetProgressBarState(ev) { + if (ev.detail.values.activeFilter) { + this.activeFilter.state = ev.detail.values.activeFilter; + this.activeFilter.activityTypeId = ev.detail.columnID; + this.activeFilter.resIds = Object.entries(this.props.grouped_activities) + .filter(([, resIds]) => ev.detail.columnID in resIds && + resIds[ev.detail.columnID].state === ev.detail.values.activeFilter) + .map(([key]) => parseInt(key)); + } else { + this.activeFilter.state = null; + this.activeFilter.activityTypeId = null; + this.activeFilter.resIds = []; } - }, -}); + } +} + +ActivityRenderer.components = { + ActivityRecordAdapter, + ActivityCellAdapter, + KanbanColumnProgressBarAdapter, +}; +ActivityRenderer.template = 'mail.ActivityRenderer'; return ActivityRenderer; diff --git a/addons/mail/static/src/js/views/activity/activity_view.js b/addons/mail/static/src/js/views/activity/activity_view.js index b21a68215d1..e2e3ededfe3 100644 --- a/addons/mail/static/src/js/views/activity/activity_view.js +++ b/addons/mail/static/src/js/views/activity/activity_view.js @@ -1,16 +1,17 @@ odoo.define('mail.ActivityView', function (require) { "use strict"; -var ActivityController = require('mail.ActivityController'); -var ActivityModel = require('mail.ActivityModel'); -var ActivityRenderer = require('mail.ActivityRenderer'); -var BasicView = require('web.BasicView'); -var core = require('web.core'); -var view_registry = require('web.view_registry'); +const ActivityController = require('mail.ActivityController'); +const ActivityModel = require('mail.ActivityModel'); +const ActivityRenderer = require('mail.ActivityRenderer'); +const BasicView = require('web.BasicView'); +const core = require('web.core'); +const RendererWrapper = require('web.RendererWrapper'); +const view_registry = require('web.view_registry'); -var _lt = core._lt; +const _lt = core._lt; -var ActivityView = BasicView.extend({ +const ActivityView = BasicView.extend({ accesskey: "a", display_name: _lt('Activity'), icon: 'fa-clock-o', @@ -33,6 +34,15 @@ var ActivityView = BasicView.extend({ this.loadParams.limit = false; this.rendererParams.templates = _.findWhere(this.arch.children, { 'tag': 'templates' }); + this.controllerParams.title = this.arch.attrs.string; + }, + /** + * + * @override + */ + getRenderer(parent, state) { + state = Object.assign({}, state, this.rendererParams); + return new RendererWrapper(null, this.config.Renderer, state); }, }); diff --git a/addons/mail/static/src/xml/activity_view.xml b/addons/mail/static/src/xml/activity_view.xml index 2db197373b7..c40d6d73b7d 100644 --- a/addons/mail/static/src/xml/activity_view.xml +++ b/addons/mail/static/src/xml/activity_view.xml @@ -1,54 +1,93 @@ - + - +
- +
-
+ +
- - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + Schedule activity - - - - +
+ +
+ + + +
+
diff --git a/addons/mail/static/tests/activity_tests.js b/addons/mail/static/tests/activity_tests.js index 6c3c297ba5a..c32702d8c45 100644 --- a/addons/mail/static/tests/activity_tests.js +++ b/addons/mail/static/tests/activity_tests.js @@ -263,7 +263,8 @@ QUnit.test('activity view: batch send mail on activity', async function (assert) QUnit.test('activity view: activity widget', async function (assert) { assert.expect(16); - var activity = await createView({ + + const params = { View: ActivityView, model: 'task', data: this.data, @@ -313,7 +314,10 @@ QUnit.test('activity view: activity widget', async function (assert) { } }, }, - }); + }; + + owl.Component.env = testUtils.mock.getMockedOwlEnv(params); + var activity = await createView(params); var today = activity.$('table tbody tr:first td:nth-child(2).today'); var dropdown = today.find('.dropdown-menu.o_activity'); diff --git a/addons/mail/views/mail_templates.xml b/addons/mail/views/mail_templates.xml index 24fab6e97b4..f2f5ee4e905 100644 --- a/addons/mail/views/mail_templates.xml +++ b/addons/mail/views/mail_templates.xml @@ -79,6 +79,7 @@ + diff --git a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js index 3e9197d3fa2..752d2b2dc6c 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js +++ b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js @@ -80,28 +80,32 @@ var KanbanColumnProgressBar = Widget.extend({ // This should be executed when the progressbar is fully rendered // and is in the DOM, this happens to be always the case with // current use of progressbars - - var subgroupCounts = {}; - let allSubgroupCount = 0; - _.each(self.colors, function (val, key) { - var subgroupCount = self.columnState.progressBarValues.counts[key] || 0; - if (self.activeFilter === key && subgroupCount === 0) { - self.activeFilter = false; - } - subgroupCounts[key] = subgroupCount; - allSubgroupCount += subgroupCount; - }); - subgroupCounts.__false = self.columnState.count - allSubgroupCount; - - self.groupCount = self.columnState.count; - self.subgroupCounts = subgroupCounts; - self.prevTotalCounterValue = self.totalCounterValue; - self.totalCounterValue = self.sumField ? (self.columnState.aggregateValues[self.sumField] || 0) : self.columnState.count; - + self.computeCounters(); self._notifyState(); self._render(); }); }, + /** + * Computes the count of each sub group and the total count + */ + computeCounters() { + const subgroupCounts = {}; + let allSubgroupCount = 0; + for (const key of Object.keys(this.colors)) { + const subgroupCount = this.columnState.progressBarValues.counts[key] || 0; + if (this.activeFilter === key && subgroupCount === 0) { + this.activeFilter = false; + } + subgroupCounts[key] = subgroupCount; + allSubgroupCount += subgroupCount; + }; + subgroupCounts.__false = this.columnState.count - allSubgroupCount; + + this.groupCount = this.columnState.count; + this.subgroupCounts = subgroupCounts; + this.prevTotalCounterValue = this.totalCounterValue; + this.totalCounterValue = this.sumField ? (this.columnState.aggregateValues[this.sumField] || 0) : this.columnState.count; + }, //-------------------------------------------------------------------------- // Private @@ -187,6 +191,7 @@ var KanbanColumnProgressBar = Widget.extend({ $bar.css('width', ''); } }); + this.$('.progress-bar').css('min-width', ''); this.$('.progress-bar.o_bar_has_records').css('min-width', barMinWidth + '%'); // Display and animate the counter number diff --git a/addons/web/static/tests/helpers/test_env.js b/addons/web/static/tests/helpers/test_env.js index fabe52ff444..d934bd3ac84 100644 --- a/addons/web/static/tests/helpers/test_env.js +++ b/addons/web/static/tests/helpers/test_env.js @@ -58,6 +58,7 @@ odoo.define('web.test_env', async function (require) { device: Object.assign({ isMobile: false, }, env.device), + isDebug: env.isDebug || (() => false), qweb: new owl.QWeb({ templates: session.owlTemplates }), services: Object.assign({ ajax: { // for legacy subwidgets