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 c75807a10f0..480bad864e7 100644 --- a/addons/mail/static/src/js/views/activity/activity_renderer.js +++ b/addons/mail/static/src/js/views/activity/activity_renderer.js @@ -157,7 +157,17 @@ var ActivityRenderer = AbstractRenderer.extend({ columnID: typeId, progressBarStates: {}, }, { - count: _.reduce(_.values(counts), function (x, y) { return x + y; }), + count: _.reduce(_.values(counts), (x, y) => x + y), + fields: { + activity_state: { + type: 'selection', + selection: [ + ['planned', _t('Planned')], + ['today', _t('Today')], + ['overdue', _t('Overdue')], + ], + }, + }, progressBarValues: { field: 'activity_state', colors: { planned: 'success', today: 'warning', overdue: 'danger' }, diff --git a/addons/mail/static/tests/activity_tests.js b/addons/mail/static/tests/activity_tests.js index 6725aab2e33..8134e6699f5 100644 --- a/addons/mail/static/tests/activity_tests.js +++ b/addons/mail/static/tests/activity_tests.js @@ -156,13 +156,13 @@ QUnit.test('activity view: simple activity rendering', async function (assert) { var $th1 = activity.$('table thead tr:first th:nth-child(2)'); assert.containsOnce($th1, 'span:first:contains(Email)', 'should contain "Email" in header of first column'); assert.containsOnce($th1, '.o_kanban_counter', 'should contain a progressbar in header of first column'); - assert.hasAttrValue($th1.find('.o_kanban_counter_progress .progress-bar:first'), 'data-original-title', '1 planned', + assert.hasAttrValue($th1.find('.o_kanban_counter_progress .progress-bar:first'), 'data-original-title', '1 Planned', 'the counter progressbars should be correctly displayed'); - assert.hasAttrValue($th1.find('.o_kanban_counter_progress .progress-bar:nth-child(2)'), 'data-original-title', '1 today', + assert.hasAttrValue($th1.find('.o_kanban_counter_progress .progress-bar:nth-child(2)'), 'data-original-title', '1 Today', 'the counter progressbars should be correctly displayed'); var $th2 = activity.$('table thead tr:first th:nth-child(3)'); assert.containsOnce($th2, 'span:first:contains(Call)', 'should contain "Call" in header of second column'); - assert.hasAttrValue($th2.find('.o_kanban_counter_progress .progress-bar:last'), 'data-original-title', '1 overdue', + assert.hasAttrValue($th2.find('.o_kanban_counter_progress .progress-bar:nth-child(3)'), 'data-original-title', '1 Overdue', 'the counter progressbars should be correctly displayed'); assert.containsNone(activity, 'table thead tr:first th:nth-child(4) .o_kanban_counter', 'should not contain a progressbar in header of 3rd column'); 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 6a9caa99a39..3e9197d3fa2 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 @@ -1,9 +1,12 @@ odoo.define('web.KanbanColumnProgressBar', function (require) { 'use strict'; -var Widget = require('web.Widget'); +const core = require('web.core'); var session = require('web.session'); var utils = require('web.utils'); +var Widget = require('web.Widget'); + +const _t = core._t; var KanbanColumnProgressBar = Widget.extend({ template: 'KanbanView.ColumnProgressBar', @@ -28,7 +31,9 @@ var KanbanColumnProgressBar = Widget.extend({ // attributes this.fieldName = columnState.progressBarValues.field; - this.colors = columnState.progressBarValues.colors; + this.colors = _.extend({}, columnState.progressBarValues.colors, { + __false: 'muted', // color to use for false value + }); this.sumField = columnState.progressBarValues.sum_field; // Previous progressBar state @@ -55,7 +60,7 @@ var KanbanColumnProgressBar = Widget.extend({ this.$bars = {}; _.each(this.colors, function (val, key) { - self.$bars[val] = self.$('.bg-' + val + '-full'); + self.$bars[key] = self.$(`.progress-bar[data-filter=${key}]`); }); this.$counter = this.$('.o_kanban_counter_side'); this.$number = this.$counter.find('b'); @@ -77,18 +82,22 @@ var KanbanColumnProgressBar = Widget.extend({ // 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._notifyState(); self._render(); }); @@ -121,7 +130,7 @@ var KanbanColumnProgressBar = Widget.extend({ }); this.trigger_up('tweak_column_records', { callback: function ($el, recordData) { - var categoryValue = recordData[self.fieldName]; + var categoryValue = recordData[self.fieldName] ? recordData[self.fieldName] : '__false'; _.each(self.colors, function (val, key) { $el.removeClass('oe_kanban_card_' + val); }); @@ -134,8 +143,9 @@ var KanbanColumnProgressBar = Widget.extend({ // Display and animate the progress bars var barNumber = 0; var barMinWidth = 6; // In % + const selection = self.columnState.fields[self.fieldName].selection; _.each(self.colors, function (val, key) { - var $bar = self.$bars[val]; + var $bar = self.$bars[key]; var count = self.subgroupCounts && self.subgroupCounts[key] || 0; if (!$bar) { @@ -143,7 +153,14 @@ var KanbanColumnProgressBar = Widget.extend({ } // Adapt tooltip - $bar.attr('data-original-title', count + ' ' + key); + let value; + if (selection) { // progressbar on a field of type selection + const option = selection.find(option => option[0] === key); + value = option && option[1] || _t('Other'); + } else { + value = key; + } + $bar.attr('data-original-title', count + ' ' + value); $bar.tooltip({ delay: 0, trigger: 'hover', @@ -181,7 +198,8 @@ var KanbanColumnProgressBar = Widget.extend({ end = 0; _.each(self.columnState.data, function (record) { var recordData = record.data; - if (self.activeFilter === recordData[self.fieldName]) { + if (self.activeFilter === recordData[self.fieldName] || + (self.activeFilter === '__false' && !recordData[self.fieldName])) { end += parseFloat(recordData[self.sumField]); } }); diff --git a/addons/web/static/src/scss/kanban_column_progressbar.scss b/addons/web/static/src/scss/kanban_column_progressbar.scss index d70644f7e4a..bfea16934c5 100644 --- a/addons/web/static/src/scss/kanban_column_progressbar.scss +++ b/addons/web/static/src/scss/kanban_column_progressbar.scss @@ -110,6 +110,7 @@ @include o-kanban-css-filter(success, theme-color('success')); @include o-kanban-css-filter(warning, theme-color('warning')); @include o-kanban-css-filter(danger, theme-color('danger')); + @include o-kanban-css-filter(muted, theme-color('dark')); &.o_kanban_group_show { display: flex; diff --git a/addons/web/static/src/scss/webclient.scss b/addons/web/static/src/scss/webclient.scss index fca898f262a..d41d1fb0ba3 100644 --- a/addons/web/static/src/scss/webclient.scss +++ b/addons/web/static/src/scss/webclient.scss @@ -78,6 +78,9 @@ div.o_boolean_toggle.custom-control.custom-checkbox { .bg-info-full { background-color: theme-color('info'); } +.bg-muted-full { + background-color: #dee2e6; +} .o_web_accesskey_overlay { font-family: $font-family-sans-serif; diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index 9e6b8ddd11d..4dbfb6d83fc 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -4624,6 +4624,78 @@ QUnit.module('Views', { kanban.destroy(); }); + QUnit.test('column progressbars: "false" bar is clickable', async function (assert) { + assert.expect(8); + + this.data.partner.records.push({id: 5, bar: true, foo: false, product_id: 5, state: "ghi"}); + var kanban = await createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: + '' + + '' + + '' + + '' + + '' + + '
' + + '' + + '
' + + '
' + + '
', + groupBy: ['bar'], + }); + + assert.containsN(kanban, '.o_kanban_group', 2); + assert.strictEqual(kanban.$('.o_kanban_counter:last .o_kanban_counter_side').text(), "4"); + assert.containsN(kanban, '.o_kanban_counter_progress:last .progress-bar', 4); + assert.containsOnce(kanban, '.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]', + "should have false kanban color"); + assert.hasClass(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]'), 'bg-muted-full'); + + await testUtils.dom.click(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]')); + + assert.hasClass(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]'), 'progress-bar-animated'); + assert.hasClass(kanban.$('.o_kanban_group:last'), 'o_kanban_group_show_muted'); + assert.strictEqual(kanban.$('.o_kanban_counter:last .o_kanban_counter_side').text(), "1"); + + kanban.destroy(); + }); + + QUnit.test('column progressbars: "false" bar with sum_field', async function (assert) { + assert.expect(4); + + this.data.partner.records.push({id: 5, bar: true, foo: false, int_field: 15, product_id: 5, state: "ghi"}); + var kanban = await createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '' + + '
' + + '
' + + '
', + groupBy: ['bar'], + }); + + assert.containsN(kanban, '.o_kanban_group', 2); + assert.strictEqual(kanban.$('.o_kanban_counter:last .o_kanban_counter_side').text(), "51"); + + await testUtils.dom.click(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]')); + + assert.hasClass(kanban.$('.o_kanban_counter_progress:last .progress-bar[data-filter="__false"]'), 'progress-bar-animated'); + assert.strictEqual(kanban.$('.o_kanban_counter:last .o_kanban_counter_side').text(), "15"); + + kanban.destroy(); + }); + QUnit.test('column progressbars should not crash in non grouped views', async function (assert) { assert.expect(3); diff --git a/doc/reference/views.rst b/doc/reference/views.rst index 3955c5242d0..efe07c48076 100644 --- a/doc/reference/views.rst +++ b/doc/reference/views.rst @@ -1076,8 +1076,8 @@ Possible children of the view element are: the progressbar ``colors`` (required) - JSON mapping the above field values to either "danger", "warning" or - "success" colors + JSON mapping the above field values to either "danger", "warning", "success" + or "muted" colors ``sum_field`` (optional) the name of the field whose column's records' values will be summed and