[IMP] web: new TimeRangeMenu widget in search view
This commit introduces a new widget 'TimeRangeMenu' used in the search view. It allows a user to easily: - search data with a given date or datetime field value in a specified period of time - compare the data of interest with data of the previous period or the previous year. The time range menu is available in the search view when one of the four following view types is instantiated: graph, pivot, cohort, or dashboard. Task Id: 1835644
This commit is contained in:
@@ -528,15 +528,22 @@ ActionManager.include({
|
||||
group_by_seq: groupbys || [],
|
||||
eval_context: this.userContext,
|
||||
});
|
||||
var groupBy = results.group_by.length ?
|
||||
results.group_by :
|
||||
(action.context.group_by || []);
|
||||
groupBy = (typeof groupBy === 'string') ? [groupBy] : groupBy;
|
||||
|
||||
if (results.error) {
|
||||
throw new Error(_.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s",
|
||||
JSON.stringify(results.error)));
|
||||
}
|
||||
var groupBy = results.group_by.length ? results.group_by : (action.context.group_by || []);
|
||||
|
||||
var context = _.omit(results.context, 'time_ranges');
|
||||
|
||||
return {
|
||||
context: results.context,
|
||||
context: context,
|
||||
domain: results.domain,
|
||||
groupBy: (typeof groupBy === 'string') ? [groupBy] : groupBy,
|
||||
groupBy: groupBy,
|
||||
};
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -170,7 +170,7 @@ var ControlPanel = Widget.extend({
|
||||
|
||||
// Update the searchview and switch buttons
|
||||
if (status.searchview || options.clear) {
|
||||
this._update_search_view(status.searchview, status.search_view_hidden, status.groupable);
|
||||
this._update_search_view(status.searchview, status.search_view_hidden, status.groupable, status.enableTimeRangeMenu);
|
||||
}
|
||||
if (status.active_view_selector) {
|
||||
this._update_switch_buttons(status.active_view_selector);
|
||||
@@ -266,12 +266,15 @@ var ControlPanel = Widget.extend({
|
||||
* @param {boolean} [groupable] visibility of the groupable menu (only
|
||||
* relevant if searchview is visible)
|
||||
*/
|
||||
_update_search_view: function (searchview, isHidden, groupable) {
|
||||
_update_search_view: function (searchview, isHidden, groupable, enableTimeRangeMenu) {
|
||||
if (searchview) {
|
||||
searchview.toggle_visibility(!isHidden);
|
||||
if (groupable !== undefined){
|
||||
searchview.groupby_menu.do_toggle(groupable);
|
||||
}
|
||||
if (enableTimeRangeMenu !== undefined){
|
||||
searchview.displayTimeRangeMenu(enableTimeRangeMenu);
|
||||
}
|
||||
}
|
||||
this.nodes.$searchview.toggle(!isHidden);
|
||||
this.$el.toggleClass('o_breadcrumb_full', !!isHidden);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
odoo.define('web.dataComparisonUtils', function (require) {
|
||||
"use strict";
|
||||
|
||||
var fieldUtils = require('web.field_utils');
|
||||
|
||||
/**
|
||||
* @param {Number} value
|
||||
* @param {Number} comparisonValue
|
||||
* @returns {Object}
|
||||
*/
|
||||
function computeVariation (value, comparisonValue) {
|
||||
var magnitude;
|
||||
var signClass;
|
||||
if (!isNaN(value) && !isNaN(comparisonValue)) {
|
||||
if (comparisonValue === 0) {
|
||||
if (value === 0) {
|
||||
magnitude = 0;
|
||||
} else if (value > 0){
|
||||
magnitude = 1;
|
||||
} else {
|
||||
magnitude = -1;
|
||||
}
|
||||
} else {
|
||||
magnitude = Math.sign(comparisonValue) * (value - comparisonValue) / comparisonValue;
|
||||
}
|
||||
if (magnitude > 0) {
|
||||
signClass = ' o_positive';
|
||||
} else if (magnitude < 0) {
|
||||
signClass = ' o_negative';
|
||||
} else if (magnitude === 0) {
|
||||
signClass = ' o_null';
|
||||
}
|
||||
return {magnitude: magnitude, signClass: signClass};
|
||||
} else {
|
||||
return {magnitude: NaN};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} variation
|
||||
* @param {Object} field
|
||||
* @param {Object} options
|
||||
* @returns {Object}
|
||||
*/
|
||||
function renderVariation (variation, field, options) {
|
||||
var $variation;
|
||||
if (!isNaN(variation.magnitude)) {
|
||||
$variation = $('<div>', {class: 'o_variation' + variation.signClass}).html(
|
||||
fieldUtils.format.percentage(variation.magnitude, field, options
|
||||
));
|
||||
} else {
|
||||
$variation = $('<div>', {class: 'o_variation'}).html('-');
|
||||
}
|
||||
return $variation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JQuery} $node
|
||||
* @param {Number} value
|
||||
* @param {Number} comparisonValue
|
||||
* @param {Object} variation (with key 'magnitude' and 'signClass')
|
||||
* @param {function} formatter
|
||||
* @param {Object} field
|
||||
* @param {Object} options
|
||||
* @returns {Object}
|
||||
*/
|
||||
function renderComparison ($node, value, comparisonValue, variation, formatter, field, options) {
|
||||
var $variation = renderVariation(variation, field, options);
|
||||
$node.append($variation);
|
||||
if (!isNaN(variation.magnitude)) {
|
||||
$node.append(
|
||||
$('<div>', {class: 'o_comparison'})
|
||||
.html(formatter(value, field, options) + ' vs ' + formatter(comparisonValue, field, options))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
computeVariation: computeVariation,
|
||||
renderComparison: renderComparison
|
||||
};
|
||||
|
||||
});
|
||||
@@ -234,78 +234,127 @@ var Domain = collections.Tree.extend({
|
||||
* @param {string} period
|
||||
* @param {string} type ('date' or 'datetime')
|
||||
* @param {boolean} forTooltip indicates if domain is used to print a tooltip
|
||||
* @param {string} comparisonPeriod
|
||||
* @returns {string} a domain in string form
|
||||
*/
|
||||
constructDomain: function (fieldName, period, type, forTooltip) {
|
||||
constructDomain: function (fieldName, period, type, forTooltip, comparisonPeriod) {
|
||||
var leftBoundaryParams, rightBoundaryParams;
|
||||
var offsetPeriodParams;
|
||||
var t = forTooltip || false;
|
||||
function makeInterval () {
|
||||
switch (comparisonPeriod) {
|
||||
case 'previous_period':
|
||||
_.map(offsetPeriodParams, function (value, key) {
|
||||
if (_.isNumber(value)) {
|
||||
leftBoundaryParams[key] = value + (leftBoundaryParams[key] || 0);
|
||||
} else {
|
||||
leftBoundaryParams[key] = value + ' + ' + (leftBoundaryParams[key] || 0);
|
||||
}
|
||||
});
|
||||
_.map(offsetPeriodParams, function (value, key) {
|
||||
if (_.isNumber(value)) {
|
||||
rightBoundaryParams[key] = value + (rightBoundaryParams[key] || 0);
|
||||
} else {
|
||||
rightBoundaryParams[key] = value + ' + ' + (rightBoundaryParams[key] || 0);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'previous_year':
|
||||
leftBoundaryParams.years = leftBoundaryParams.years ? leftBoundaryParams.years-- : -1;
|
||||
rightBoundaryParams.years = rightBoundaryParams.years ? rightBoundaryParams.years-- : -1;
|
||||
break;
|
||||
}
|
||||
|
||||
var stringifyParams = function (value, key) {
|
||||
return key + '=' + value;
|
||||
};
|
||||
var leftBoundaryStringifyParams = _.map(leftBoundaryParams, stringifyParams).join(', ');
|
||||
var rightBoundaryStringifyParams = _.map(rightBoundaryParams, stringifyParams).join(', ');
|
||||
|
||||
if (type === 'date') {
|
||||
return "['&'," +
|
||||
"('" + fieldName + "', '>=', (context_today() + relativedelta(" + leftBoundaryParams + ")).strftime('%Y-%m-%d'))," +
|
||||
"('" + fieldName + "', '<', (context_today() + relativedelta(" + rightBoundaryParams + ")).strftime('%Y-%m-%d'))"+
|
||||
"('" + fieldName + "', '>=', (context_today() + relativedelta(" + leftBoundaryStringifyParams + ")).strftime('%Y-%m-%d'))," +
|
||||
"('" + fieldName + "', '<', (context_today() + relativedelta(" + rightBoundaryStringifyParams + ")).strftime('%Y-%m-%d'))"+
|
||||
"]";
|
||||
}
|
||||
else {
|
||||
return "['&'," +
|
||||
"('" + fieldName + "', '>=', " +
|
||||
"(datetime.datetime.combine(context_today() + relativedelta(" + leftBoundaryParams + "), datetime.time(0,0,0)).to_utc()).strftime('%Y-%m-%d %H:%M:%S'))," +
|
||||
"(datetime.datetime.combine(context_today() + relativedelta(" + leftBoundaryStringifyParams + "), datetime.time(0,0,0)).to_utc()).strftime('%Y-%m-%d %H:%M:%S'))," +
|
||||
"('" + fieldName + "', '<', " +
|
||||
"(datetime.datetime.combine(context_today() + relativedelta(" + rightBoundaryParams + "), datetime.time(0,0,0)).to_utc()).strftime('%Y-%m-%d %H:%M:%S'))"+
|
||||
"(datetime.datetime.combine(context_today() + relativedelta(" + rightBoundaryStringifyParams + "), datetime.time(0,0,0)).to_utc()).strftime('%Y-%m-%d %H:%M:%S'))"+
|
||||
"]";
|
||||
}
|
||||
}
|
||||
switch (period) {
|
||||
case 'today':
|
||||
leftBoundaryParams = "";
|
||||
rightBoundaryParams = t ? "" : "days=1";
|
||||
leftBoundaryParams = {};
|
||||
rightBoundaryParams = t ? {} : {days: 1};
|
||||
offsetPeriodParams = {days: -1};
|
||||
return makeInterval();
|
||||
case 'this_week':
|
||||
leftBoundaryParams = "weeks=-1, days=1, weekday=0";
|
||||
rightBoundaryParams = t ? "weekday=6" : "days=1, weekday=0";
|
||||
leftBoundaryParams = {weeks: -1, days: 1, weekday: 0};
|
||||
rightBoundaryParams = t ? {weekday: 6} : {days: 1, weekday: 0};
|
||||
offsetPeriodParams = {weeks: -1};
|
||||
return makeInterval();
|
||||
case 'this_month':
|
||||
leftBoundaryParams = "day=1";
|
||||
rightBoundaryParams = (t ? "days=-1, " : "") + "day=1, months=1";
|
||||
leftBoundaryParams = {day: 1};
|
||||
rightBoundaryParams = _.extend(t ? {days: -1} : {},
|
||||
{day: 1, months: 1});
|
||||
offsetPeriodParams = {months: -1};
|
||||
return makeInterval();
|
||||
case 'this_quarter':
|
||||
leftBoundaryParams = "months= - (context_today().month - 1) % 3, day=1";
|
||||
rightBoundaryParams = (t ? "days=-1, " : "") + "months= 3 - (context_today().month -1 ) % 3, day=1";
|
||||
leftBoundaryParams = {months: '- (context_today().month - 1) % 3', day: 1};
|
||||
rightBoundaryParams = _.extend(t ? {days: -1} : {},
|
||||
{months: '3 - (context_today().month - 1) % 3', day: 1});
|
||||
offsetPeriodParams = {months: -3};
|
||||
return makeInterval();
|
||||
case 'this_year':
|
||||
leftBoundaryParams = "month=1, day=1";
|
||||
rightBoundaryParams = (t ? "days=-1, " : "") + "month=1, day=1, years=1";
|
||||
leftBoundaryParams = {month: 1, day: 1};
|
||||
rightBoundaryParams = _.extend(t ? {days: -1} : {},
|
||||
{month: 1, day: 1, years: 1});
|
||||
offsetPeriodParams = {years: -1};
|
||||
return makeInterval();
|
||||
case 'yesterday':
|
||||
leftBoundaryParams = "days=-1";
|
||||
rightBoundaryParams = (t ? "days=-1" : "");
|
||||
leftBoundaryParams = {days: -1};
|
||||
rightBoundaryParams = (t ? {days: -1} : {});
|
||||
offsetPeriodParams = {days: -1};
|
||||
return makeInterval();
|
||||
case 'last_week':
|
||||
leftBoundaryParams = "weeks=-2, days=1, weekday=0";
|
||||
rightBoundaryParams = t ? "weeks=-1, weekday=6" : "weeks=-1, days=1, weekday=0";
|
||||
leftBoundaryParams = {weeks: -2, days: 1, weekday: 0};
|
||||
rightBoundaryParams = t ? {weeks: -1, weekday: 6} : {weeks: -1, days: 1, weekday: 0};
|
||||
offsetPeriodParams = {weeks: -1};
|
||||
return makeInterval();
|
||||
case 'last_month':
|
||||
leftBoundaryParams = "months=-1, day=1";
|
||||
rightBoundaryParams = (t ? "days=-1 , " : "") + "day=1";
|
||||
leftBoundaryParams = {months: -1, day: 1};
|
||||
rightBoundaryParams = _.extend(t ? {days: -1} : {}, {day: 1});
|
||||
offsetPeriodParams = {months: -1};
|
||||
return makeInterval();
|
||||
case 'last_quarter':
|
||||
leftBoundaryParams = "months= - 3 - (context_today().month - 1) % 3, day=1";
|
||||
rightBoundaryParams = (t ? "days=-1 , " : "") + "months= - (context_today().month - 1) % 3, day=1";
|
||||
leftBoundaryParams = {months: '- 3 - (context_today().month - 1) % 3', day: 1};
|
||||
rightBoundaryParams = _.extend(t ? {days: -1} : {},
|
||||
{months: '- (context_today().month - 1) % 3', day: 1});
|
||||
offsetPeriodParams = {months: -3};
|
||||
return makeInterval();
|
||||
case 'last_year':
|
||||
leftBoundaryParams = "month=1, day=1, years=-1";
|
||||
rightBoundaryParams = (t ? "days=-1 , " : "") + "month=1, day=1";
|
||||
leftBoundaryParams = {month: 1, day: 1, years: -1};
|
||||
rightBoundaryParams = _.extend(t ? {days: -1} : {}, {month: 1, day: 1});
|
||||
offsetPeriodParams = {years: -1};
|
||||
return makeInterval();
|
||||
case 'last_7_days':
|
||||
leftBoundaryParams = "days=-7";
|
||||
rightBoundaryParams = t ? "days=-1" : "";
|
||||
leftBoundaryParams = {days: -7};
|
||||
rightBoundaryParams = t ? {days: -1} : {};
|
||||
offsetPeriodParams = {days: -7};
|
||||
return makeInterval();
|
||||
case 'last_30_days':
|
||||
leftBoundaryParams = "days=-30";
|
||||
rightBoundaryParams = t ? "days=-1" : "";
|
||||
leftBoundaryParams = {days: -30};
|
||||
rightBoundaryParams = t ? {days: -1} : {};
|
||||
offsetPeriodParams = {days: -30};
|
||||
return makeInterval();
|
||||
case 'last_365_days':
|
||||
leftBoundaryParams = "days=-365";
|
||||
rightBoundaryParams = t ? "days=-1" : "";
|
||||
leftBoundaryParams = {days: -365};
|
||||
rightBoundaryParams = t ? {days: -1} : {};
|
||||
offsetPeriodParams = {days: -365};
|
||||
return makeInterval();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -67,6 +67,7 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
|
||||
this.searchView = params.searchView;
|
||||
this.searchViewHidden = params.searchViewHidden;
|
||||
this.groupable = params.groupable;
|
||||
this.enableTimeRangeMenu = params.enableTimeRangeMenu;
|
||||
this.actionViews = params.actionViews;
|
||||
this.viewType = params.viewType;
|
||||
this.withControlPanel = params.withControlPanel !== false;
|
||||
@@ -428,6 +429,7 @@ var AbstractController = AbstractAction.extend(ControlPanelMixin, {
|
||||
searchview: this.searchView,
|
||||
search_view_hidden: !this.searchable || this.searchviewHidden,
|
||||
groupable: this.groupable,
|
||||
enableTimeRangeMenu: this.enableTimeRangeMenu,
|
||||
});
|
||||
|
||||
this._pushState();
|
||||
|
||||
@@ -50,7 +50,7 @@ var AbstractView = Class.extend({
|
||||
// groupby menu or not. This is useful for the views which do not support
|
||||
// grouping data.
|
||||
groupable: true,
|
||||
|
||||
enableTimeRangeMenu: false,
|
||||
config: {
|
||||
Model: AbstractModel,
|
||||
Renderer: AbstractRenderer,
|
||||
@@ -74,6 +74,11 @@ var AbstractView = Class.extend({
|
||||
* @param {number} [params.count]
|
||||
* @param {string} [params.controllerID]
|
||||
* @param {string[]} params.domain
|
||||
* @param {string[][]} params.timeRange
|
||||
* @param {string[][]} params.comparisonTimeRange
|
||||
* @param {string} params.timeRangeDescription
|
||||
* @param {string} params.comparisonTimeRangeDescription
|
||||
* @param {boolean} params.compare
|
||||
* @param {string[]} params.groupBy
|
||||
* @param {number} [params.currentId]
|
||||
* @param {boolean} params.isEmbedded
|
||||
@@ -110,6 +115,22 @@ var AbstractView = Class.extend({
|
||||
noContentHelp: params.action && params.action.help,
|
||||
};
|
||||
|
||||
var timeRangeMenuData = params.context.timeRangeMenuData;
|
||||
var timeRange = [];
|
||||
var comparisonTimeRange = [];
|
||||
var compare = false;
|
||||
var timeRangeDescription = "";
|
||||
var comparisonTimeRangeDescription = "";
|
||||
if (this.enableTimeRangeMenu && timeRangeMenuData) {
|
||||
timeRange = timeRangeMenuData.timeRange;
|
||||
comparisonTimeRange = timeRangeMenuData.comparisonTimeRange;
|
||||
compare = comparisonTimeRange.length > 0;
|
||||
timeRangeDescription = timeRangeMenuData.timeRangeDescription;
|
||||
comparisonTimeRangeDescription = timeRangeMenuData.comparisonTimeRangeDescription;
|
||||
this.rendererParams.timeRangeDescription = timeRangeDescription;
|
||||
this.rendererParams.comparisonTimeRangeDescription = comparisonTimeRangeDescription;
|
||||
}
|
||||
|
||||
this.controllerParams = {
|
||||
modelName: params.modelName,
|
||||
activeActions: {
|
||||
@@ -119,6 +140,7 @@ var AbstractView = Class.extend({
|
||||
duplicate: this.arch.attrs.duplicate ? JSON.parse(this.arch.attrs.duplicate) : true,
|
||||
},
|
||||
groupable: this.groupable,
|
||||
enableTimeRangeMenu: this.enableTimeRangeMenu,
|
||||
isEmbedded: this.isEmbedded,
|
||||
controllerID: params.controllerID,
|
||||
bannerRoute: this.arch.attrs.banner_route,
|
||||
@@ -144,6 +166,11 @@ var AbstractView = Class.extend({
|
||||
count: params.count || ((this.controllerParams.ids !== undefined) &&
|
||||
this.controllerParams.ids.length) || 0,
|
||||
domain: params.domain,
|
||||
timeRange: timeRange,
|
||||
timeRangeDescription: timeRangeDescription,
|
||||
comparisonTimeRange: comparisonTimeRange,
|
||||
comparisonTimeRangeDescription: comparisonTimeRangeDescription,
|
||||
compare: compare,
|
||||
groupedBy: params.groupBy,
|
||||
modelName: params.modelName,
|
||||
res_id: params.currentId,
|
||||
|
||||
@@ -18,10 +18,13 @@ odoo.define('web.PivotModel', function (require) {
|
||||
|
||||
var AbstractModel = require('web.AbstractModel');
|
||||
var concurrency = require('web.concurrency');
|
||||
var dataComparisonUtils = require('web.dataComparisonUtils');
|
||||
var core = require('web.core');
|
||||
var session = require('web.session');
|
||||
var utils = require('web.utils');
|
||||
|
||||
var computeVariation = dataComparisonUtils.computeVariation;
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
var PivotModel = AbstractModel.extend({
|
||||
|
||||
@@ -7,10 +7,12 @@ var Domain = require('web.Domain');
|
||||
var DropdownMenu = require('web.DropdownMenu');
|
||||
var search_filters = require('web.search_filters');
|
||||
var time = require('web.time');
|
||||
var TimeRangeMenuOptions = require('web.TimeRangeMenuOptions');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
var PERIOD_OPTIONS = TimeRangeMenuOptions.PeriodOptions;
|
||||
|
||||
var FiltersMenu = DropdownMenu.extend({
|
||||
custom_events: {
|
||||
@@ -43,6 +45,7 @@ var FiltersMenu = DropdownMenu.extend({
|
||||
* defaultOptionId: string refers to an optionId (optional)
|
||||
* currentOptionId: string refers to an optionId that is activated if item is active (optional)
|
||||
* }
|
||||
* @param {Object[]} periodOptions determines the options available for filter with date attribute
|
||||
* @param {Object} fields 'field_get' of a model: mapping from field name
|
||||
* to an object with its properties
|
||||
*/
|
||||
@@ -50,21 +53,7 @@ var FiltersMenu = DropdownMenu.extend({
|
||||
// determines where the filter menu is displayed and its style
|
||||
this.isMobile = config.device.isMobile;
|
||||
// determines list of options used by filter of type 'date'
|
||||
this.periodOptions = [
|
||||
{description: _t('Today'), optionId: 'today', groupId: 1},
|
||||
{description: _t('This Week'), optionId: 'this_week', groupId: 1},
|
||||
{description: _t('This Month'), optionId: 'this_month', groupId: 1},
|
||||
{description: _t('This Quarter'), optionId: 'this_quarter', groupId: 1},
|
||||
{description: _t('This Year'), optionId: 'this_year', groupId: 1},
|
||||
{description: _t('Yesterday'), optionId: 'yesterday', groupId: 2},
|
||||
{description: _t('Last Week'), optionId: 'last_week', groupId: 2},
|
||||
{description: _t('Last Month'), optionId: 'last_month', groupId: 2},
|
||||
{description: _t('Last Quarter'), optionId: 'last_quarter', groupId: 2},
|
||||
{description: _t('Last Year'), optionId: 'last_year', groupId: 2},
|
||||
{description: _t('Last 7 Days'), optionId: 'last_7_days', groupId: 3},
|
||||
{description: _t('Last 30 Days'), optionId: 'last_30_days', groupId: 3},
|
||||
{description: _t('Last 365 Days'), optionId: 'last_365_days', groupId: 3},
|
||||
];
|
||||
this.periodOptions = PERIOD_OPTIONS;
|
||||
// determines when the 'Add custom filter' submenu is open
|
||||
this.generatorMenuIsOpen = false;
|
||||
this.propositions = [];
|
||||
|
||||
@@ -10,9 +10,14 @@ var FiltersMenu = require('web.FiltersMenu');
|
||||
var GroupByMenu = require('web.GroupByMenu');
|
||||
var pyUtils = require('web.py_utils');
|
||||
var search_inputs = require('web.search_inputs');
|
||||
var TimeRangeMenu = require('web.TimeRangeMenu');
|
||||
var TimeRangeMenuOptions = require('web.TimeRangeMenuOptions');
|
||||
var utils = require('web.utils');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var _t = core._t;
|
||||
var ComparisonOptions = TimeRangeMenuOptions.ComparisonOptions;
|
||||
var PeriodOptions = TimeRangeMenuOptions.PeriodOptions;
|
||||
|
||||
var Backbone = window.Backbone;
|
||||
|
||||
@@ -198,7 +203,7 @@ var FacetView = Widget.extend({
|
||||
* is the current interval used
|
||||
* (necessarily the field is of type 'date' or 'datetime')
|
||||
* @param {Object} periodMapping, a key is a field name and the corresponding value
|
||||
* is the current interval used
|
||||
* is the current period used
|
||||
* (necessarily the field is of type 'date' or 'datetime')
|
||||
*/
|
||||
init: function (parent, model, intervalMapping, periodMapping) {
|
||||
@@ -248,9 +253,11 @@ var FacetValueView = Widget.extend({
|
||||
/*
|
||||
* @param {Widget} parent
|
||||
* @param {Object} model
|
||||
* @param {Object} interval (optional) is used in case the facet value
|
||||
* @param {Object} option (optional) is used in case the facet value
|
||||
* corresponds to a groupby with an associated 'date'
|
||||
* 'datetime' field.
|
||||
* @param {Object} comparison (optional) is used in case the facet value
|
||||
* comes from the time range menu and comparison is active
|
||||
*/
|
||||
init: function (parent, model, option) {
|
||||
this._super(parent);
|
||||
@@ -326,13 +333,16 @@ var SearchView = Widget.extend({
|
||||
item_option_changed: '_onItemOptionChanged',
|
||||
new_groupby: '_onNewGroupby',
|
||||
new_filters: '_onNewFilters',
|
||||
time_range_modified: '_onTimeRangeModified',
|
||||
time_range_removed: '_onTimeRangeRemoved',
|
||||
},
|
||||
defaults: _.extend({}, Widget.prototype.defaults, {
|
||||
hidden: false,
|
||||
disable_filters: false,
|
||||
disable_custom_filters: false,
|
||||
disable_groupby: false,
|
||||
disable_favorites: false,
|
||||
disable_custom_filters: false,
|
||||
disable_filters: false,
|
||||
disableTimeRangeMenu: true,
|
||||
}),
|
||||
template: "SearchView",
|
||||
|
||||
@@ -361,6 +371,15 @@ var SearchView = Widget.extend({
|
||||
|
||||
this.hasFavorites = false;
|
||||
|
||||
this.noDateFields = true;
|
||||
var field;
|
||||
for (var key in this.fields) {
|
||||
field = this.fields[key];
|
||||
if (_.contains(['date', 'datetime'], field.type) && field.sortable) {
|
||||
this.noDateFields = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.selectedGroupIds = {
|
||||
groupByCategory: [],
|
||||
filterCategory: []
|
||||
@@ -373,6 +392,8 @@ var SearchView = Widget.extend({
|
||||
|
||||
this.filters = [];
|
||||
this.groupbys = [];
|
||||
this.timeRanges = options.action && options.action.context ?
|
||||
options.action.context.time_ranges : undefined;
|
||||
var visibleSearchMenu = this.call('local_storage', 'getItem', 'visible_search_menu');
|
||||
this.visible_filters = (visibleSearchMenu !== 'false');
|
||||
this.input_subviews = []; // for user input in searchbar
|
||||
@@ -421,6 +442,12 @@ var SearchView = Widget.extend({
|
||||
.then(this.set_default_filters.bind(this))
|
||||
.then(function () {
|
||||
var menu_defs = [];
|
||||
self.timeRangeMenu = self._createTimeRangeMenu();
|
||||
menu_defs.push(self.timeRangeMenu.prependTo($buttons));
|
||||
self.timeRangeMenu.do_hide();
|
||||
var display = self.options.disableTimeRangeMenu !== undefined &&
|
||||
!self.options.disableTimeRangeMenu;
|
||||
self.displayTimeRangeMenu(display);
|
||||
if (!self.options.disable_groupby) {
|
||||
self.groupby_menu = self._createGroupByMenu();
|
||||
menu_defs.push(self.groupby_menu.prependTo($buttons));
|
||||
@@ -432,6 +459,17 @@ var SearchView = Widget.extend({
|
||||
return $.when.apply($, menu_defs);
|
||||
});
|
||||
},
|
||||
/*
|
||||
*
|
||||
* @param {boolean}
|
||||
*/
|
||||
displayTimeRangeMenu: function (b) {
|
||||
if (!b || this.noDateFields) {
|
||||
this.timeRangeMenu.do_hide();
|
||||
} else {
|
||||
this.timeRangeMenu.do_show();
|
||||
}
|
||||
},
|
||||
on_attach_callback: function () {
|
||||
this._focusInput();
|
||||
},
|
||||
@@ -448,6 +486,8 @@ var SearchView = Widget.extend({
|
||||
if (!_.isEmpty(this.search_defaults)) {
|
||||
var inputs = this.search_fields.concat(this.filters, this.groupbys);
|
||||
var search_defaults = _.invoke(inputs, 'facet_for_defaults', this.search_defaults);
|
||||
var defaultTimeRange = this._searchDefaultTimeRange();
|
||||
search_defaults.push(defaultTimeRange);
|
||||
return $.when.apply(null, search_defaults).then(function () {
|
||||
var facets = _.compact(arguments);
|
||||
self.query.reset(facets, {preventSearch: true});
|
||||
@@ -502,7 +542,7 @@ var SearchView = Widget.extend({
|
||||
if (domain) {
|
||||
domains.push(domain);
|
||||
}
|
||||
var context = field.get_context(facet);
|
||||
var context = field.get_context(facet, noDomainEvaluation);
|
||||
if (context) {
|
||||
contexts.push(context);
|
||||
}
|
||||
@@ -630,8 +670,8 @@ var SearchView = Widget.extend({
|
||||
groupByCategory: [],
|
||||
filterCategory: [],
|
||||
};
|
||||
var timeRangeMenuIsActive;
|
||||
this.query.each(function (facet) {
|
||||
|
||||
var values = facet.get('values');
|
||||
if (facet.attributes.cat === "groupByCategory") {
|
||||
selectedGroupIds.groupByCategory = _.uniq(
|
||||
@@ -665,7 +705,9 @@ var SearchView = Widget.extend({
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (facet.attributes.cat === "timeRangeCategory") {
|
||||
timeRangeMenuIsActive = true;
|
||||
}
|
||||
var f = new FacetView(this, facet, this.intervalMapping, this.periodMapping);
|
||||
started.push(f.appendTo(self.$('.o_searchview_input_container')));
|
||||
self.input_subviews.push(f);
|
||||
@@ -692,6 +734,9 @@ var SearchView = Widget.extend({
|
||||
if (self.filters_menu) {
|
||||
self._unsetUnusedFilters(selectedGroupIds.filterCategory);
|
||||
}
|
||||
if (self.timeRangeMenu && !timeRangeMenuIsActive) {
|
||||
self.timeRangeMenu.deactivate();
|
||||
}
|
||||
});
|
||||
},
|
||||
childFocused: function () {
|
||||
@@ -747,7 +792,7 @@ var SearchView = Widget.extend({
|
||||
}
|
||||
var current_group = [],
|
||||
current_category = 'filters',
|
||||
categories = {filters: this.filters, group_by: this.groupbys};
|
||||
categories = {filters: this.filters, group_by: this.groupbys, timeRanges: this.timeRanges};
|
||||
|
||||
_.each(filters.concat({category:'filters', item: 'separator'}), function (filter) {
|
||||
if (filter.item.tag === 'filter' && filter.category === current_category) {
|
||||
@@ -921,6 +966,15 @@ var SearchView = Widget.extend({
|
||||
});
|
||||
return new GroupByMenu(this, groupbys, this.fields);
|
||||
},
|
||||
/**
|
||||
* Create a time range menu.
|
||||
*
|
||||
* @private
|
||||
* @returns {Widget} the range menu
|
||||
*/
|
||||
_createTimeRangeMenu: function () {
|
||||
return new TimeRangeMenu(this, this.fields, this.timeRanges);
|
||||
},
|
||||
/**
|
||||
* Processes a fieldsView in place. In particular, parses its arch.
|
||||
*
|
||||
@@ -936,6 +990,81 @@ var SearchView = Widget.extend({
|
||||
fv.arch = utils.xml_to_json(doc, true);
|
||||
return fv;
|
||||
},
|
||||
_searchDefaultTimeRange: function () {
|
||||
if (this.timeRanges) {
|
||||
var timeRange = "[]";
|
||||
var timeRangeDescription;
|
||||
var comparisonTimeRange = "[]";
|
||||
var comparisonTimeRangeDescription;
|
||||
|
||||
var dateField = {
|
||||
name: this.timeRanges.field,
|
||||
type: this.fields[this.timeRanges.field].type,
|
||||
description: this.fields[this.timeRanges.field].string,
|
||||
};
|
||||
|
||||
timeRange = Domain.prototype.constructDomain(
|
||||
dateField.name,
|
||||
this.timeRanges.range,
|
||||
dateField.type
|
||||
);
|
||||
timeRangeDescription = _.findWhere(
|
||||
PeriodOptions,
|
||||
{optionId: this.timeRanges.range}
|
||||
).description;
|
||||
|
||||
if (this.timeRanges.comparison_range) {
|
||||
comparisonTimeRange = Domain.prototype.constructDomain(
|
||||
dateField.name,
|
||||
this.timeRanges.range,
|
||||
dateField.type,
|
||||
null,
|
||||
this.timeRanges.comparison_range
|
||||
);
|
||||
comparisonTimeRangeDescription = _.findWhere(
|
||||
ComparisonOptions,
|
||||
{optionId: this.timeRanges.comparison_range}
|
||||
).description;
|
||||
}
|
||||
|
||||
return $.when({
|
||||
cat: 'timeRangeCategory',
|
||||
category: _t("Time Range"),
|
||||
icon: 'fa fa-calendar',
|
||||
field: {
|
||||
get_context: function (facet, noDomainEvaluation) {
|
||||
if (!noDomainEvaluation) {
|
||||
timeRange = Domain.prototype.stringToArray(timeRange);
|
||||
comparisonTimeRange = Domain.prototype.stringToArray(comparisonTimeRange);
|
||||
}
|
||||
return {
|
||||
timeRangeMenuData: {
|
||||
timeRange: timeRange,
|
||||
timeRangeDescription: timeRangeDescription,
|
||||
comparisonTimeRange: comparisonTimeRange,
|
||||
comparisonTimeRangeDescription: comparisonTimeRangeDescription,
|
||||
}
|
||||
};
|
||||
},
|
||||
get_groupby: function () {},
|
||||
get_domain: function () {}
|
||||
},
|
||||
isRange: true,
|
||||
values: [{
|
||||
label: dateField.description + ': ' + timeRangeDescription +
|
||||
(
|
||||
comparisonTimeRangeDescription ?
|
||||
(' / ' + comparisonTimeRangeDescription) :
|
||||
''
|
||||
),
|
||||
value: null,
|
||||
}],
|
||||
});
|
||||
} else {
|
||||
return $.when();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This function ask the groupby menu to deactive all groupbys if no
|
||||
* groupby is used.
|
||||
@@ -1115,6 +1244,31 @@ var SearchView = Widget.extend({
|
||||
this.query.trigger('reset');
|
||||
}
|
||||
},
|
||||
/*
|
||||
* @private
|
||||
* @param {JQueryEvent} event
|
||||
*/
|
||||
_onTimeRangeModified: function () {
|
||||
var facet = this.timeRangeMenu.facetFor();
|
||||
var current = this.query.find(function (facet) {
|
||||
return facet.get('cat') === 'timeRangeCategory';
|
||||
});
|
||||
if (current) {
|
||||
this.query.remove(current, {silent: true});
|
||||
}
|
||||
this.query.add(facet);
|
||||
},
|
||||
/*
|
||||
* @private
|
||||
*/
|
||||
_onTimeRangeRemoved: function () {
|
||||
var current = this.query.find(function (facet) {
|
||||
return facet.get('cat') === 'timeRangeCategory';
|
||||
});
|
||||
if (current) {
|
||||
this.query.remove(current);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
_.extend(SearchView, {
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
odoo.define('web.TimeRangeMenu', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var Domain = require('web.Domain');
|
||||
var TimeRangeMenuOptions = require('web.TimeRangeMenuOptions');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var _t = core._t;
|
||||
var ComparisonOptions = TimeRangeMenuOptions.ComparisonOptions;
|
||||
var PeriodOptions = TimeRangeMenuOptions.PeriodOptions;
|
||||
|
||||
var TimeRangeMenu = Widget.extend({
|
||||
template: 'web.TimeRangeMenu',
|
||||
events: {
|
||||
'click .o_apply_range': '_onApplyButtonClick',
|
||||
'click .o_comparison_checkbox': '_onCheckBoxClick',
|
||||
},
|
||||
|
||||
/**
|
||||
* override
|
||||
* @param {Widget} parent
|
||||
* @param {Object} fields
|
||||
* @param {Object} configuration
|
||||
*
|
||||
*/
|
||||
init: function(parent, fields, configuration) {
|
||||
var self = this;
|
||||
this._super(parent);
|
||||
this.dateFields = [];
|
||||
_.each(fields, function (field, name) {
|
||||
if (field.sortable && _.contains(['date', 'datetime'], field.type)) {
|
||||
self.dateFields.push(_.extend({}, field, {
|
||||
name: name,
|
||||
}));
|
||||
}
|
||||
});
|
||||
this.periodOptions = PeriodOptions;
|
||||
this.comparisonOptions = ComparisonOptions;
|
||||
|
||||
// Following steps determine initial configuration
|
||||
this.isActive = false;
|
||||
this.timeRangeId = undefined;
|
||||
this.comparisonIsSelected = false;
|
||||
this.comparisonTimeRangeId = undefined;
|
||||
this.dateField = {};
|
||||
if (configuration && configuration.field && configuration.range) {
|
||||
this.isActive = true;
|
||||
var dateField = _.findWhere(this.dateFields, {name: configuration.field});
|
||||
this.dateField = {
|
||||
name: dateField.name,
|
||||
description: dateField.string,
|
||||
type: dateField.type,
|
||||
};
|
||||
this.timeRangeId = configuration.range;
|
||||
if (configuration.comparison_range) {
|
||||
this.comparisonIsSelected = true;
|
||||
this.comparisonTimeRangeId = configuration.comparison_range;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
deactivate: function () {
|
||||
this.isActive = false;
|
||||
this.comparisonIsSelected = false;
|
||||
this.renderElement();
|
||||
},
|
||||
/**
|
||||
* Generates a :js:class:`~instance.web.search.Facet` descriptor from a
|
||||
* filter descriptor
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
facetFor: function () {
|
||||
var fieldDescription;
|
||||
var timeRange = "[]";
|
||||
var timeRangeDescription;
|
||||
var comparisonTimeRange = "[]";
|
||||
var comparisonTimeRangeDescription;
|
||||
|
||||
if (this.isActive) {
|
||||
fieldDescription = this.dateField.description;
|
||||
if (this.timeRangeId !== 'custom') {
|
||||
timeRange = Domain.prototype.constructDomain(
|
||||
this.dateField.name,
|
||||
this.timeRangeId,
|
||||
this.dateField.type
|
||||
);
|
||||
timeRangeDescription = _.findWhere(
|
||||
this.periodOptions,
|
||||
{optionId: this.timeRangeId}
|
||||
).description;
|
||||
}
|
||||
if (this.comparisonIsSelected) {
|
||||
comparisonTimeRange = Domain.prototype.constructDomain(
|
||||
this.dateField.name,
|
||||
this.timeRangeId,
|
||||
this.dateField.type,
|
||||
null,
|
||||
this.comparisonTimeRangeId
|
||||
);
|
||||
comparisonTimeRangeDescription = _.findWhere(
|
||||
this.comparisonOptions,
|
||||
{optionId: this.comparisonTimeRangeId}
|
||||
).description;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
cat: 'timeRangeCategory',
|
||||
category: _t("Time Range"),
|
||||
icon: 'fa fa-calendar',
|
||||
field: {
|
||||
get_context: function (facet, noDomainEvaluation) {
|
||||
if (!noDomainEvaluation) {
|
||||
timeRange = Domain.prototype.stringToArray(timeRange);
|
||||
comparisonTimeRange = Domain.prototype.stringToArray(comparisonTimeRange);
|
||||
}
|
||||
return {
|
||||
timeRangeMenuData: {
|
||||
timeRange: timeRange,
|
||||
timeRangeDescription: timeRangeDescription,
|
||||
comparisonTimeRange: comparisonTimeRange,
|
||||
comparisonTimeRangeDescription: comparisonTimeRangeDescription,
|
||||
}
|
||||
};
|
||||
},
|
||||
get_groupby: function () {},
|
||||
get_domain: function () {}
|
||||
},
|
||||
isRange: true,
|
||||
values: [{
|
||||
label: fieldDescription + ': ' + timeRangeDescription +
|
||||
(
|
||||
comparisonTimeRangeDescription ?
|
||||
(' / ' + comparisonTimeRangeDescription) :
|
||||
''
|
||||
),
|
||||
value: null,
|
||||
}],
|
||||
};
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onApplyButtonClick: function () {
|
||||
this.isActive = true;
|
||||
var dateFieldName = this.$('.o_date_field_selector').val();
|
||||
this.timeRangeId = this.$('.o_time_range_selector').val();
|
||||
if (this.comparisonIsSelected) {
|
||||
this.comparisonTimeRangeId = this.$('.o_comparison_time_range_selector').val();
|
||||
}
|
||||
this.dateField = {
|
||||
name: dateFieldName,
|
||||
type: _.findWhere(this.dateFields, {name: dateFieldName}).type,
|
||||
description: _.findWhere(this.dateFields, {name: dateFieldName}).string,
|
||||
};
|
||||
|
||||
this.renderElement();
|
||||
this.trigger_up('time_range_modified');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param {JQueryEvent} ev
|
||||
*/
|
||||
_onCheckBoxClick: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.comparisonIsSelected = this.$('.o_comparison_checkbox').prop('checked');
|
||||
this.$('.o_comparison_time_range_selector').toggleClass('o_hidden');
|
||||
this.$el.addClass('open');
|
||||
}
|
||||
});
|
||||
|
||||
return TimeRangeMenu;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
odoo.define('web.TimeRangeMenuOptions', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var _lt = core._lt;
|
||||
|
||||
var PeriodOptions = [
|
||||
{description: _lt('Today'), optionId: 'today', groupId: 1},
|
||||
{description: _lt('This Week'), optionId: 'this_week', groupId: 1},
|
||||
{description: _lt('This Month'), optionId: 'this_month', groupId: 1},
|
||||
{description: _lt('This Quarter'), optionId: 'this_quarter', groupId: 1},
|
||||
{description: _lt('This Year'), optionId: 'this_year', groupId: 1},
|
||||
{description: _lt('Yesterday'), optionId: 'yesterday', groupId: 2},
|
||||
{description: _lt('Last Week'), optionId: 'last_week', groupId: 2},
|
||||
{description: _lt('Last Month'), optionId: 'last_month', groupId: 2},
|
||||
{description: _lt('Last Quarter'), optionId: 'last_quarter', groupId: 2},
|
||||
{description: _lt('Last Year'), optionId: 'last_year', groupId: 2},
|
||||
{description: _lt('Last 7 Days'), optionId: 'last_7_days', groupId: 3},
|
||||
{description: _lt('Last 30 Days'), optionId: 'last_30_days', groupId: 3},
|
||||
{description: _lt('Last 365 Days'), optionId: 'last_365_days', groupId: 3},
|
||||
];
|
||||
|
||||
var ComparisonOptions = [
|
||||
{description: _lt('Previous Period'), optionId: 'previous_period'},
|
||||
{description: _lt('Previous Year'), optionId: 'previous_year'}
|
||||
];
|
||||
|
||||
return {
|
||||
PeriodOptions: PeriodOptions,
|
||||
ComparisonOptions: ComparisonOptions,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
min-width: 150px;
|
||||
max-height: calc(100vh - 125px); // FIXME
|
||||
overflow: auto;
|
||||
|
||||
|
||||
@@ -142,11 +142,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Filters
|
||||
.o_filters_menu {
|
||||
.o_filters_menu, .o_group_by_menu, .o_time_range_menu {
|
||||
max-height: calc(100vh - 125px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
// Filters
|
||||
.o_filters_menu {
|
||||
.o_filter_condition {
|
||||
@include o-search-options-dropdown-custom-item;
|
||||
margin-bottom: 8px;
|
||||
@@ -182,8 +184,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Favorites
|
||||
.o_favorites_menu {
|
||||
max-height: 75vh;
|
||||
@@ -211,3 +211,10 @@
|
||||
@include o-search-options-dropdown-custom-item;
|
||||
}
|
||||
}
|
||||
|
||||
// Time Ranges
|
||||
.o_time_range_menu {
|
||||
.dropdown-item-text > label, .custom-control-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,6 +1127,72 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<div t-name="web.TimeRangeMenu" class="btn-group o_dropdown">
|
||||
<button class="o_dropdown_toggler_btn btn btn-secondary dropdown-toggle o_time_range_menu_button" data-toggle="dropdown"
|
||||
aria-expanded="false" tabindex="-1">
|
||||
<span class="fa fa-calendar"/> Time Ranges <span class="caret"/>
|
||||
</button>
|
||||
<div class="dropdown-menu o_dropdown_menu o_time_range_menu" role="menu">
|
||||
<div class="dropdown-item-text">
|
||||
<label for="date_field_selector">Date Field</label>
|
||||
<select id="date_field_selector" class="o_input o_date_field_selector">
|
||||
<t t-foreach="widget.dateFields" t-as="field">
|
||||
<t t-if="widget.dateField.name === field.name">
|
||||
<option t-att-value="field.name" selected="selected">
|
||||
<t t-esc="field.string"/>
|
||||
</option>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<option t-att-value="field.name">
|
||||
<t t-esc="field.string"/>
|
||||
</option>
|
||||
</t>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="dropdown-item-text">
|
||||
<label for="time_range_selector">Date Range</label>
|
||||
<select id="time_range_selector" class="o_input o_time_range_selector">
|
||||
<t t-foreach="widget.periodOptions" t-as="option">
|
||||
<t t-if="widget.timeRangeId === option.optionId">
|
||||
<option t-att-value="option.optionId" selected="selected">
|
||||
<t t-esc="option.description"/>
|
||||
</option>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<option t-att-value="option.optionId">
|
||||
<t t-esc="option.description"/>
|
||||
</option>
|
||||
</t>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="dropdown-item-text">
|
||||
<t t-set="isSelected" t-value="widget.comparisonIsSelected"/>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" id="comparison_checkbox" class="custom-control-input o_comparison_checkbox" t-att-checked="isSelected ? 'checked' : undefined"/>
|
||||
<label for="comparison_checkbox" class="custom-control-label">Compare To</label>
|
||||
</div>
|
||||
<select t-att-class="'o_input o_comparison_time_range_selector' + (widget.comparisonIsSelected ? '' : ' o_hidden')">
|
||||
<t t-foreach="widget.comparisonOptions" t-as="option">
|
||||
<t t-if="widget.comparisonTimeRangeId === option.optionId">
|
||||
<option t-att-value="option.optionId" selected="selected">
|
||||
<t t-esc="option.description"/>
|
||||
</option>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<option t-att-value="option.optionId">
|
||||
<t t-esc="option.description"/>
|
||||
</option>
|
||||
</t>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="dropdown-item-text">
|
||||
<button class="btn btn-primary o_apply_range" type="button">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div t-name="SearchView" class="o_searchview" role="search" aria-autocomplete="list">
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/services/config.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/abstract_service.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/abstract_storage_service.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/data_comparison_utils.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/class.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/collections.js"/>
|
||||
<script type="text/javascript" src="/web/static/src/js/core/translation.js"></script>
|
||||
@@ -239,6 +240,8 @@
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/search_inputs.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/favorites_menu.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/groupby_menu.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/time_range_menu.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/time_range_menu_options.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/groupby_menu_interface_mixin.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/widgets/dropdown_menu.js"></script>
|
||||
<script type="text/javascript" src="/web/static/src/js/views/search/filters_menu.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user