diff options
Diffstat (limited to 'addons/web/static/src')
9 files changed, 593 insertions, 18 deletions
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 779073b51ed..bcccefbb984 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1576,7 +1576,7 @@ var BasicModel = AbstractModel.extend({ if (change.id) { relRecord = self.localData[change.id]; } - switch(change.operation) { + switch (change.operation) { case 'ADD': list.count++; var resID = relRecord ? relRecord.res_id : change.resID; @@ -1666,7 +1666,7 @@ var BasicModel = AbstractModel.extend({ var result = {}; var self = this; var evalContext; - function evalModifier (mod) { + function evalModifier(mod) { if (mod === undefined || mod === false || mod === true) { return !!mod; } @@ -2775,7 +2775,7 @@ var BasicModel = AbstractModel.extend({ var context = _.extend({}, record.data, record._changes); // calls _generateX2ManyCommands for a given field, and returns the array of commands - function _generateX2ManyCommands (fieldName) { + function _generateX2ManyCommands(fieldName) { var commands = self._generateX2ManyCommands(record, {fieldNames: [fieldName]}); return commands[fieldName]; } diff --git a/addons/web/static/src/js/views/kanban/kanban_column.js b/addons/web/static/src/js/views/kanban/kanban_column.js index ab071d0e930..dac28c536dd 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column.js +++ b/addons/web/static/src/js/views/kanban/kanban_column.js @@ -8,6 +8,7 @@ var kanban_quick_create = require('web.kanban_quick_create'); var KanbanRecord = require('web.KanbanRecord'); var view_dialogs = require('web.view_dialogs'); var Widget = require('web.Widget'); +var KanbanColumnProgressBar = require('web.KanbanColumnProgressBar'); var _t = core._t; var QWeb = core.qweb; @@ -19,6 +20,8 @@ var KanbanColumn = Widget.extend({ cancel_quick_create: '_onCancelQuickCreate', kanban_record_delete: '_onDeleteRecord', quick_create_add_record: '_onQuickCreateAddRecord', + tweak_column: '_onTweakColumn', + tweak_column_records: '_onTweakColumnRecords', }, events: { 'click .o_column_edit': '_onEditColumn', @@ -58,6 +61,13 @@ var KanbanColumn = Widget.extend({ this.offset = 0; this.remaining = this.size - this.data_records.length; + if (options.hasProgressBar) { + this.barOptions = { + columnID: this.db_id, + progressBarStates: options.progressBarStates, + }; + } + this.record_options = _.clone(recordOptions); if (options.grouped_by_m2o) { @@ -81,6 +91,7 @@ var KanbanColumn = Widget.extend({ */ start: function () { var self = this; + var defs = [this._super.apply(this, arguments)]; this.$header = this.$('.o_kanban_header'); for (var i = 0; i < this.data_records.length; i++) { @@ -128,9 +139,12 @@ var KanbanColumn = Widget.extend({ self._onToggleFold(event); } }); - this._update(); + if (this.barOptions) { + this.progressBar = new KanbanColumnProgressBar(this, this.barOptions, this.data); + defs.push(this.progressBar.appendTo(this.$header)); + } - return this._super.apply(this, arguments); + return $.when.apply($, defs).then(this._update.bind(this)); }, //-------------------------------------------------------------------------- @@ -180,6 +194,18 @@ var KanbanColumn = Widget.extend({ isEmpty: function () { return !this.records.length; }, + /** + * Updates the column progressBar and sets the new data. New data are + * supposed to already match column rendering (except for the progressBar). + * + * @param {Object} data + */ + updateProgressBar: function (data) { + this.data = data; + if (!this.folded && this.progressBar) { + this.progressBar.update(this.data); + } + }, //-------------------------------------------------------------------------- // Private @@ -221,6 +247,7 @@ var KanbanColumn = Widget.extend({ } else { this.$('.o_kanban_load_more').html(QWeb.render('KanbanView.LoadMore', {widget: this})); } + this.updateProgressBar(this.data); }, //-------------------------------------------------------------------------- @@ -321,6 +348,22 @@ var KanbanColumn = Widget.extend({ }, /** * @private + * @param {OdooEvent} ev + */ + _onTweakColumn: function (ev) { + ev.data.callback(this.$el); + }, + /** + * @private + * @param {OdooEvent} ev + */ + _onTweakColumnRecords: function (ev) { + _.each(this.records, function (record) { + ev.data.callback(record.$el, record.state.data); + }); + }, + /** + * @private * @param {MouseEvent} event */ _onUnarchiveRecords: function (event) { 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 new file mode 100644 index 00000000000..c4a62989b97 --- /dev/null +++ b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js @@ -0,0 +1,247 @@ +odoo.define('web.KanbanColumnProgressBar', function (require) { +'use strict'; + +var Widget = require('web.Widget'); +var session = require('web.session'); +var utils = require('web.utils'); + +var KanbanColumnProgressBar = Widget.extend({ + template: 'KanbanView.ColumnProgressBar', + events: { + 'click .o_kanban_counter_progress': '_onProgressBarParentClick', + 'click .progress-bar': '_onProgressBarClick', + }, + /** + * Allows to disable animations for tests. + * @type {boolean} + */ + ANIMATE: true, + + /** + * @constructor + */ + init: function (parent, options, columnState) { + this._super.apply(this, arguments); + + this.columnID = options.columnID; + + // <progressbar/> attributes + this.fieldName = columnState.progressBarValues.field; + this.colors = columnState.progressBarValues.colors; + this.sumField = columnState.progressBarValues.sum; + + // Previous progressBar state + var state = options.progressBarStates[this.columnID]; + this.groupCount = state ? state.groupCount : 0; + this.subgroupCounts = state ? state.subgroupCounts : {}; + this.totalCounterValue = state ? state.totalCounterValue : 0; + this.activeFilter = state ? state.activeFilter : false; + + // Prepare currency (TODO this should be automatic... use a field ?) + this.counterPrefix = ''; + this.counterSuffix = ''; + var sumFieldInfo = this.sumField && columnState.fieldsInfo.kanban[this.sumField]; + var currencyField = sumFieldInfo && sumFieldInfo.options && sumFieldInfo.options.currency_field; + if (currencyField && columnState.data.length) { + var currency = session.currencies[columnState.data[0].data[currencyField].res_id]; + if (currency.position === 'before') { + this.counterPrefix = currency.symbol + ' '; + } else { + this.counterSuffix = ' ' + currency.symbol; + } + } + }, + /** + * @override + */ + start: function () { + var self = this; + + this.$bars = {}; + _.each(this.colors, function (val, key) { + self.$bars[val] = self.$('.bg-' + val + '-full'); + }); + this.$counter = this.$('.o_kanban_counter_side'); + + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Updates internal data and rendering according to new received column + * state. + * + * @param {Object} columnState + */ + update: function (columnState) { + var self = this; + + var subgroupCounts = {}; + _.each(self.colors, function (val, key) { + var subgroupCount = columnState.progressBarValues.counts[key] || 0; + if (self.activeFilter === val && subgroupCount === 0) { + self.activeFilter = false; + } + subgroupCounts[key] = subgroupCount; + }); + + this.groupCount = columnState.count; + this.subgroupCounts = subgroupCounts; + this.prevTotalCounterValue = this.totalCounterValue; + this.totalCounterValue = this.sumField ? (columnState.aggregateValues[this.sumField] || 0) : columnState.count; + this._notifyState(); + this._render(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Updates the rendering according to internal data. This is done without + * qweb rendering because there are animations. + * + * @private + */ + _render: function () { + var self = this; + + // Update column display according to active filter + this.trigger_up('tweak_column', { + callback: function ($el) { + $el.removeClass('o_kanban_group_show'); + _.each(self.colors, function (val, key) { + $el.removeClass('o_kanban_group_show_' + val); + }); + if (self.activeFilter) { + $el.addClass('o_kanban_group_show o_kanban_group_show_' + self.colors[self.activeFilter]); + } + }, + }); + this.trigger_up('tweak_column_records', { + callback: function ($el, recordData) { + var categoryValue = recordData[self.fieldName]; + _.each(self.colors, function (val, key) { + $el.removeClass('oe_kanban_card_' + val); + }); + if (self.colors[categoryValue]) { + $el.addClass('oe_kanban_card_' + self.colors[categoryValue]); + } + }, + }); + + // Display and animate the progress bars + _.each(self.colors, function (val, key) { + var $bar = self.$bars[val]; + var count = self.subgroupCounts[key]; + + if (!$bar) { + return; + } + + // Adapt tooltip + $bar.attr('data-original-title', count + ' ' + key); + $bar.tooltip({ + delay: '0', + trigger:'hover', + placement: 'top' + }); + + // Adapt active state + $bar.toggleClass('active progress-bar-striped', key === self.activeFilter); + + // Adapt width + $bar.removeClass('o_bar_has_records transition-off'); + window.getComputedStyle($bar[0]).getPropertyValue('width'); // Force reflow so that animations work + if (count > 0) { + $bar.addClass('o_bar_has_records'); + $bar.css('width', (count * 100 / self.groupCount) + '%'); + } else { + $bar.css('width', ''); + } + }); + + // Display and animate the counter number + var humanNumber; + var suffix = this.counterSuffix; + var end = this.totalCounterValue; + if (end >= 10000) { + humanNumber = utils.human_number(end, 1); + end = parseFloat(humanNumber.substr(0, humanNumber.length - 1)); + suffix = humanNumber[humanNumber.length - 1] + suffix; + } + var start = this.prevTotalCounterValue || 0; + if (start >= 10000) { + humanNumber = utils.human_number(start, 1); + start = parseFloat(humanNumber.substr(0, humanNumber.length - 1)); + } + if (end > start && this.ANIMATE) { + $({currentValue: start}).animate({currentValue: end}, { + duration: 1000, + start: function () { + self.$counter.addClass('o_kanban_grow'); + }, + step: function () { + self.$counter.html(self.counterPrefix + Math.round(this.currentValue) + suffix); + }, + complete: function () { + self.$counter.html(self.counterPrefix + this.currentValue + suffix); + self.$counter.removeClass('o_kanban_grow'); + }, + }); + } else { + this.$counter.html(this.counterPrefix + end + suffix); + } + }, + /** + * Notifies the new progressBar state so that if a full rerender occurs, the + * new progressBar that would replace this one will be initialized with + * current state, so that animations are correct. + * + * @private + */ + _notifyState: function () { + this.trigger_up('set_progress_bar_state', { + columnID: this.columnID, + values: { + groupCount: this.groupCount, + subgroupCounts: this.subgroupCounts, + totalCounterValue: this.totalCounterValue, + activeFilter: this.activeFilter, + }, + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Event} ev + */ + _onProgressBarClick: function (ev) { + this.$clickedBar = $(ev.currentTarget); + var filter = this.$clickedBar.data('filter'); + this.activeFilter = (this.activeFilter === filter ? false : filter); + this._notifyState(); + this._render(); + }, + /** + * @private + * @param {Event} ev + */ + _onProgressBarParentClick: function (ev) { + if (ev.target !== ev.currentTarget) { + return; + } + this.activeFilter = false; + this._notifyState(); + this._render(); + }, +}); +return KanbanColumnProgressBar; +}); diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 7e018327060..8ba0e3fb7c3 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -78,6 +78,12 @@ var KanbanController = BasicController.extend({ * @returns {Deferred} */ _confirmSave: function (id) { + var data = this.model.get(this.handle, {raw: true}); + var grouped = data.groupedBy.length; + if (grouped) { + var columnState = this.model.getColumn(id); + return this.renderer.updateColumn(columnState.id, columnState); + } return this.renderer.updateRecord(this.model.get(id)); }, /** @@ -164,7 +170,7 @@ var KanbanController = BasicController.extend({ var data = self.model.get(db_id); self.renderer.updateColumn(db_id, data); }); - }); + }); }).fail(this.reload.bind(this)); }, /** @@ -305,7 +311,7 @@ var KanbanController = BasicController.extend({ context: _.extend({default_name: name}, context), title: _t("Create"), disable_multiple_selection: true, - on_saved: function(record) { + on_saved: function (record) { add_record([record.res_id]); }, }).open(); diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js index f714fc519df..400d73c0b7d 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -98,9 +98,25 @@ var KanbanModel = BasicModel.extend({ if (dp && dp.tooltipData) { result.tooltipData = $.extend(true, {}, dp.tooltipData); } + if (dp && dp.progressBarValues) { + result.progressBarValues = $.extend(true, {}, dp.progressBarValues); + } return result; }, /** + * Same as @see get but getting the parent element whose ID is given. + * + * @param {string} id + * @returns {Object} + */ + getColumn: function (id) { + var element = this.localData[id]; + if (element) { + return this.get(element.parentID); + } + return null; + }, + /** * @override */ load: function (params) { @@ -148,7 +164,8 @@ var KanbanModel = BasicModel.extend({ // Manually updates groups data. Note: this is done before the actual // save as it might need to perform a read group in some cases so those // updated data might be overriden again. - var resID = self.localData[recordID].res_id; + var record = self.localData[recordID]; + var resID = record.res_id; // Remove record from its current group var old_group; for (var i = 0; i < parent.data.length; i++) { @@ -170,6 +187,7 @@ var KanbanModel = BasicModel.extend({ return this.notifyChanges(recordID, changes).then(function () { return self.save(recordID); }).then(function () { + record.parentID = new_group.id; return [old_group.id, new_group.id]; }); }, @@ -182,7 +200,15 @@ var KanbanModel = BasicModel.extend({ if (options && options.groupBy && !options.groupBy.length) { options.groupBy = this.defaultGroupedBy; } - return this._super(id, options); + var def = this._super(id, options); + return this._reloadProgressBarGroupFromRecord(id, def); + }, + /** + * @override + */ + save: function (recordID) { + var def = this._super.apply(this, arguments); + return this._reloadProgressBarGroupFromRecord(recordID, def); }, //-------------------------------------------------------------------------- @@ -190,6 +216,25 @@ var KanbanModel = BasicModel.extend({ //-------------------------------------------------------------------------- /** + * @override + */ + _makeDataPoint: function (params) { + var dataPoint = this._super.apply(this, arguments); + if (params.progressBar) { + dataPoint.progressBar = params.progressBar; + } + return dataPoint; + }, + /** + * @override + */ + _load: function (dataPoint, options) { + if (dataPoint.progressBar) { + return this._readProgressBarGroup(dataPoint, options); + } + return this._super.apply(this, arguments); + }, + /** * Ensures that there is no nested groups in Kanban (only the first grouping * level is taken into account). * @@ -207,6 +252,34 @@ var KanbanModel = BasicModel.extend({ }); }, /** + * @private + * @param {Object} dataPoint + * @returns {Deferred<Object>} + */ + _readProgressBarGroup: function (list, options) { + var self = this; + var groupsDef = this._readGroup(list, options); + var progressBarDef = this._rpc({ + model: list.model, + method: 'read_progress_bar', + kwargs: { + domain: list.domain, + group_by: list.groupedBy[0], + progress_bar: list.progressBar, + context: list.context, + }, + }); + return $.when(groupsDef, progressBarDef).then(function (a, data) { + _.each(list.data, function (groupID) { + var group = self.localData[groupID]; + group.progressBarValues = _.extend({ + counts: data[group.value] || {}, + }, list.progressBar); + }); + return list; + }); + }, + /** * Fetches tooltip specific fields on the group by relation and stores it in * the column datapoint in a special key `tooltipData`. * Data for the tooltips (group_by_tooltip) are fetched in batch for all @@ -253,8 +326,37 @@ var KanbanModel = BasicModel.extend({ } return $.when(); }, -}); + /** + * Reloads all progressbar data if the given id is a record's one. This is + * done after given deferred and insures that the given deferred's result is + * not lost. + * + * @private + * @param {string} recordID + * @param {Deferred} def + * @returns {Deferred} + */ + _reloadProgressBarGroupFromRecord: function (recordID, def) { + var element = this.localData[recordID]; + if (element.type !== 'record') { + return def; + } + // If we updated a record, then we must potentially update columns' + // progressbars, so we need to load groups info again + var self = this; + while (element) { + if (element.progressBar) { + return def.then(function (data) { + return self._load(element, {onlyGroups: true}).then(function () { + return data; + }); + }); + } + element = this.localData[element.parentID]; + } + return def; + }, +}); return KanbanModel; - }); diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index 5d7be3d3636..49752ca2678 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -83,6 +83,10 @@ function transformQwebTemplate(node, fields) { var KanbanRenderer = BasicRenderer.extend({ className: 'o_kanban_view', + custom_events: _.extend({}, BasicRenderer.prototype.custom_events || {}, { + 'set_progress_bar_state': '_onSetProgressBarState', + }), + /** * @override */ @@ -100,6 +104,9 @@ var KanbanRenderer = BasicRenderer.extend({ viewType: 'kanban', }); this.columnOptions = _.extend({}, params.column_options, { qweb: this.qweb }); + if (this.columnOptions.hasProgressBar) { + this.columnOptions.progressBarStates = {}; + } this._setState(state); }, @@ -336,6 +343,24 @@ var KanbanRenderer = BasicRenderer.extend({ }); this.createColumnEnabled = this.groupedByM2O && this.columnOptions.group_creatable; }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Updates progressbar internal states (necessary for animations) with + * received data. + * + * @private + * @param {OdooEvent} ev + */ + _onSetProgressBarState: function (ev) { + if (!this.columnOptions.progressBarStates[ev.data.columnID]) { + this.columnOptions.progressBarStates[ev.data.columnID] = {}; + } + _.extend(this.columnOptions.progressBarStates[ev.data.columnID], ev.data.values); + }, }); return KanbanRenderer; diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js index 55a90e83242..3e808de72e3 100644 --- a/addons/web/static/src/js/views/kanban/kanban_view.js +++ b/addons/web/static/src/js/views/kanban/kanban_view.js @@ -6,6 +6,7 @@ var core = require('web.core'); var KanbanModel = require('web.KanbanModel'); var KanbanRenderer = require('web.KanbanRenderer'); var KanbanController = require('web.KanbanController'); +var utils = require('web.utils'); var _lt = core._lt; @@ -21,6 +22,10 @@ var KanbanView = BasicView.extend({ Renderer: KanbanRenderer, }, viewType: 'kanban', + + /** + * @constructor + */ init: function (viewInfo, params) { this._super.apply(this, arguments); @@ -29,8 +34,20 @@ var KanbanView = BasicView.extend({ this.loadParams.limit = this.loadParams.limit || 40; this.loadParams.openGroupByDefault = true; this.loadParams.type = 'list'; - this.loadParams.groupBy = arch.attrs.default_group_by ? [arch.attrs.default_group_by] : (params.groupBy || []); + var progressBar; + utils.traverse(arch, function (n) { + var isProgressBar = (n.tag === 'progressbar'); + if (isProgressBar) { + progressBar = _.clone(n.attrs); + progressBar.colors = JSON.parse(progressBar.colors); + progressBar.sum = progressBar.sum || false; + } + return !isProgressBar; + }); + if (progressBar) { + this.loadParams.progressBar = progressBar; + } var activeActions = this.controllerParams.activeActions; activeActions = _.extend(activeActions, { @@ -44,6 +61,7 @@ var KanbanView = BasicView.extend({ deletable: activeActions.group_delete, group_creatable: activeActions.group_create, quick_create: params.isQuickCreateEnabled || this._isQuickCreateEnabled(viewInfo), + hasProgressBar: !!progressBar, }; this.rendererParams.record_options = { editable: activeActions.edit, @@ -56,10 +74,19 @@ var KanbanView = BasicView.extend({ this.controllerParams.readOnlyMode = false; this.controllerParams.hasButtons = true; }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Object} viewInfo + */ _isQuickCreateEnabled: function (viewInfo) { var groupBy = this.loadParams.groupBy[0]; groupBy = groupBy !== undefined ? groupBy.split(':')[0] : undefined; - if(groupBy !== undefined && !_.contains(['char', 'boolean', 'many2one'], viewInfo.fields[groupBy].type)) { + if (groupBy !== undefined && !_.contains(['char', 'boolean', 'many2one'], viewInfo.fields[groupBy].type)) { return false; } if (!this.controllerParams.activeActions.create) { @@ -70,11 +97,6 @@ var KanbanView = BasicView.extend({ } return true; } - - }); - return KanbanView; - }); - diff --git a/addons/web/static/src/less/kanban_column_progressbar.less b/addons/web/static/src/less/kanban_column_progressbar.less new file mode 100644 index 00000000000..c1cf86adc81 --- /dev/null +++ b/addons/web/static/src/less/kanban_column_progressbar.less @@ -0,0 +1,115 @@ + +.o-kanban-css-filter(@class, @accent-color) { + // Provide CSS reordering and highlight + &.o_kanban_group_show_@{class} { + @mix-soft: mix(@accent-color, @odoo-webclient-background-color, 5%); + @mix-full: mix(@accent-color, @odoo-webclient-background-color); + + &, .o_kanban_header { + background-color: @mix-soft; + border-color: @mix-full; + } + + .progress-bar.bg-@{class}-full { + border: 1px solid white; + } + + .oe_kanban_card_@{class} { + order: 1; + margin-bottom: @o-kanban-inside-vgutter*0.5; + + ~ .oe_kanban_card_@{class} { + margin-top: -@o-kanban-inside-vgutter*0.5 - 1px; + } + } + + .o_kanban_load_more { + order: 2; + padding: @o-kanban-inside-vgutter*0.5 0 @o-kanban-inside-vgutter; + } + + .o_kanban_record:not(.oe_kanban_card_@{class}) { + order: 3; + opacity: 0.5; + box-shadow: none; + + &:hover { + opacity: 1; + } + } + } +} + +.o_kanban_counter { + position: relative; + .o-flex-display(); + .o-align-items(center); + height: 30px; + margin-bottom: @o-kanban-record-margin*2; + + > .o_kanban_counter_progress { + width: 76%; + height: @font-size-base; + margin-bottom: 0; + background: fade(black, 7%); + box-shadow: none; + + .progress-bar { + margin-bottom: 0; + box-shadow: none; + cursor: pointer; + + // Be sure that a bar with data has a minimun width + &.o_bar_has_records { + min-width: 10px; + } + } + + .o_kanban_counter_label { + font-size: 10px; + .user-select(none); + } + } + + > .o_kanban_counter_side { + width: 21%; + margin-left: 3%; + color: @headings-color; + font-weight: bold; + text-align: right; + + &.o_kanban_grow { + .o-animation(grow, 400ms, ease-in, 200ms); + } + } +} +.o_column_folded .o_kanban_counter { + display: none; +} + +.o_kanban_view .o_kanban_group { + .o-kanban-css-filter(success, @brand-success); + .o-kanban-css-filter(warning, @brand-warning); + .o-kanban-css-filter(danger, @brand-danger); + + &.o_kanban_group_show { + .o-flex-display(); + .o-flex-flow(column, nowrap); + + > * { + .o-flex(0, 0, auto); + } + } +} + +@keyframes grow { + 0% { + transform: scale3d(1, 1, 1); + } + 50% { + transform: scale3d(1.5, 1.5, 1.5); + } + 100% { + transform: scale3d(1, 1, 1); + } +} diff --git a/addons/web/static/src/xml/kanban.xml b/addons/web/static/src/xml/kanban.xml index c7e7c83e836..acde7fcc739 100644 --- a/addons/web/static/src/xml/kanban.xml +++ b/addons/web/static/src/xml/kanban.xml @@ -91,4 +91,19 @@ </tbody> </table> </t> + +<t t-name="KanbanView.ColumnProgressBar"> + <div class="o_kanban_counter"> + <div class="o_kanban_counter_progress progress"> + <t t-foreach="widget.colors" t-as="color"> + <t t-set="count" t-value="widget.subgroupCounts[color] || 0"/> + <div t-attf-class="progress-bar transition-off bg-#{color_value}-full#{count ? ' o_bar_has_records' : ''}#{widget.activeFilter ? ' active progress-bar-striped' : ''}" + t-att-data-filter="color" + t-attf-data-original-title="#{count} #{color}" + t-attf-style="width: #{count ? (count * 100 / widget.groupCount) : 0}%;"/> + </t> + </div> + <div class="o_kanban_counter_side"><t t-esc="widget.totalCounterValue"/></div> + </div> +</t> </template> |
