[IMP] web,mail: improve search view for mobile devices

With this commit, we introduce a better screen to manipulate the search
view in a mobile device.

Note: most of this work was initially done by suh-odoo, then was adapted
and moved to community by myself.

task 31464
This commit is contained in:
Géry Debongnie
2018-05-23 10:48:36 +02:00
parent 4232eb7bcd
commit d01787c19d
15 changed files with 403 additions and 136 deletions
@@ -14,7 +14,6 @@ if (!config.device.isMobile) {
Discuss.include({
template: 'mail.discuss_mobile',
need_control_panel: false, // in mobile, we use a custom control panel
events: _.extend(Discuss.prototype.events, {
'click .o_mail_mobile_tab': '_onMobileTabClicked',
'click .o_channel_inbox_item': '_onMobileInboxButtonClicked',
@@ -122,14 +121,6 @@ Discuss.include({
this._super.apply(this, arguments);
}
},
/**
* @private
*/
_toggleSearchView: function () {
this.searchviewDisplayed = !this.searchviewDisplayed;
this.searchview.$el.toggleClass('o_hidden', !this.searchviewDisplayed);
this.$buttons.toggleClass('o_hidden', this.searchviewDisplayed);
},
/**
* Overrides to toggle the visibility of the tabs when a message is unselected
*
@@ -198,10 +189,6 @@ Discuss.include({
self.$buttons.find('.o_mail_chat_button_' + type).removeClass('o_hidden');
self.$buttons.find('.o_mail_chat_button_mark_read').toggleClass('o_hidden', type !== 'channel_inbox');
self.$buttons.find('.o_mail_chat_button_unstar_all').toggleClass('o_hidden', type !== 'channel_starred');
self.$('.o_enable_searchview').toggleClass('o_hidden', !inInbox);
if (!inInbox && self.searchviewDisplayed) {
self._toggleSearchView(); // close the searchview when leaving Inbox
}
// update Inbox page buttons
if (inInbox) {
@@ -219,17 +206,6 @@ Discuss.include({
self.$('.o_mail_mobile_tab[data-type=' + type + ']').addClass('active');
});
},
/**
* @override
*/
_updateControlPanel: function () {
this.$buttons.appendTo(this.$('.o_mail_chat_mobile_control_panel'));
this.searchview.$el.appendTo(this.$('.o_mail_chat_mobile_control_panel'));
var $enable_searchview = $('<button/>', {type: 'button'})
.addClass('o_enable_searchview btn fa fa-search')
.on('click', this._toggleSearchView.bind(this));
$enable_searchview.insertAfter(this.searchview.$el);
},
//--------------------------------------------------------------------------
// Handlers
-29
View File
@@ -271,35 +271,6 @@
@include o-flex-flow(column, nowrap);
background: white;
.o_mail_chat_mobile_control_panel {
@include o-flex(0, 0, auto);
padding: 5px;
min-height: 44px;
border-bottom: 1px solid #ebebeb;
box-shadow: 0px 0px 10px #afafaf;
z-index: 1; // so that box shaddow is above thread's images
.o_searchview {
border: none;
padding: 5px 5px 0px 5px;
.o_searchview_input {
font-size: 15px;
}
.o_searchview_more {
display: none;
}
}
.o_enable_searchview, .o_enable_searchview:focus,
.o_enable_searchview:hover, .o_enable_searchview:active {
@include o-position-absolute($right: 5px, $top: 5px);
color: gray;
background: none;
font-size: 15px;
&:active {
box-shadow: none;
}
}
}
.o_mail_chat_mobile_inbox_buttons {
@include o-flex-display;
@include o-flex(0, 0, auto);
-1
View File
@@ -149,7 +149,6 @@
<!-- Mobile templates -->
<t t-name="mail.discuss_mobile">
<div class="o_mail_chat">
<div class="o_mail_chat_mobile_control_panel"/>
<div class="o_mail_chat_mobile_inbox_buttons">
<button type="button" class="btn btn-primary btn-sm visible-xs-inline o_channel_inbox_item" title="Inbox" data-type="channel_inbox">
Inbox
@@ -26,7 +26,7 @@ QUnit.test('mobile basic rendering', function (assert) {
// /mail/client_action route is always called when the test suite is
// launched), and we must wait for this RPC to be done before starting to
// test the interface. This should be refactored to facilitate the testing.
assert.expect(9);
assert.expect(8);
var done = assert.async();
createDiscuss({
@@ -37,8 +37,6 @@ QUnit.test('mobile basic rendering', function (assert) {
services: this.services,
}).then(function (discuss) {
// test basic rendering in mobile
assert.strictEqual(discuss.$('.o_mail_chat_mobile_control_panel').length, 1,
"should have rendered a control panel");
assert.strictEqual(discuss.$('.o_mail_chat_content .o_mail_no_content').length, 1,
"should display the no content message");
assert.strictEqual(discuss.$('.o_mail_mobile_tabs').length, 1,
@@ -56,7 +54,7 @@ QUnit.test('mobile basic rendering', function (assert) {
"should be in DMs tab");
assert.strictEqual(discuss.$('.o_mail_chat_content .o_mail_no_content').length, 0,
"should display the no content message");
discuss.$('.o_mail_chat_button_dm').click(); // click to add a channel
$('.o_mail_chat_button_dm').click(); // click to add a channel
assert.strictEqual(discuss.$('.o_mail_add_channel input:visible').length, 1,
"should display the input to add a channel");
@@ -1,6 +1,7 @@
odoo.define('web.FavoriteMenu', function (require) {
"use strict";
var config = require('web.config');
var core = require('web.core');
var data_manager = require('web.data_manager');
var pyeval = require('web.pyeval');
@@ -38,6 +39,7 @@ return Widget.extend({
this.action = action;
this.action_id = action.id;
this.filters = {};
this.isMobile = config.device.isMobile;
_.each(filters, this.add_filter.bind(this));
},
start: function () {
@@ -305,6 +307,7 @@ return Widget.extend({
odoo.define('web.FilterMenu', function (require) {
"use strict";
var config = require('web.config');
var search_filters = require('web.search_filters');
var search_inputs = require('web.search_inputs');
var Widget = require('web.Widget');
@@ -333,6 +336,7 @@ return Widget.extend({
},
init: function (parent, filters, fields) {
this._super(parent);
this.isMobile = config.device.isMobile;
this.filters = filters || [];
this.searchview = parent;
this.propositions = [];
@@ -409,6 +413,7 @@ return Widget.extend({
odoo.define('web.GroupByMenu', function (require) {
"use strict";
var config = require('web.config');
var core = require('web.core');
var search_inputs = require('web.search_inputs');
var Widget = require('web.Widget');
@@ -433,6 +438,7 @@ return Widget.extend({
var self = this;
this._super(parent);
this.searchview = parent;
this.isMobile = config.device.isMobile;
this.groups = groups || [];
this.groupableFields = [];
var groupable_types = ['many2one', 'char', 'boolean', 'selection', 'date', 'datetime'];
+26 -8
View File
@@ -326,18 +326,19 @@ var SearchView = Widget.extend({
.toggleClass('fa-search-plus', !this.visible_filters);
var menu_defs = [];
this.prepare_search_inputs();
if (this.$buttons) {
var $buttons = this._getButtonsElement();
if ($buttons) {
if (!this.options.disable_filters) {
this.filter_menu = new FilterMenu(this, this.filters, this.fields);
menu_defs.push(this.filter_menu.appendTo(this.$buttons));
menu_defs.push(this.filter_menu.appendTo($buttons));
}
if (!this.options.disable_groupby) {
this.groupby_menu = new GroupByMenu(this, this.groupbys, this.fields);
menu_defs.push(this.groupby_menu.appendTo(this.$buttons));
menu_defs.push(this.groupby_menu.appendTo($buttons));
}
if (!this.options.disable_favorites) {
this.favorite_menu = new FavoriteMenu(this, this.query, this.dataset.model, this.action, this.favorite_filters);
menu_defs.push(this.favorite_menu.appendTo(this.$buttons));
menu_defs.push(this.favorite_menu.appendTo($buttons));
}
}
return $.when.apply($, menu_defs).then(this.set_default_filters.bind(this));
@@ -454,7 +455,7 @@ var SearchView = Widget.extend({
return self.$('.o_searchview_input').val().trim();
},
});
this.autocomplete.appendTo(this.$el);
this.autocomplete.appendTo(this.$('.o_searchview_input_container'));
},
/**
* Provide auto-completion result for req.term (an array to `resp`)
@@ -528,11 +529,11 @@ var SearchView = Widget.extend({
this.query.each(function (facet) {
var f = new FacetView(this, facet);
started.push(f.appendTo(self.$el));
started.push(f.appendTo(self.$('.o_searchview_input_container')));
self.input_subviews.push(f);
}, this);
var i = new InputView(this);
started.push(i.appendTo(self.$el));
started.push(i.appendTo(self.$('.o_searchview_input_container')));
self.input_subviews.push(i);
_.each(this.input_subviews, function (childView) {
childView.on('focused', self, self.proxy('childFocused'));
@@ -540,7 +541,12 @@ var SearchView = Widget.extend({
});
$.when.apply(null, started).then(function () {
_.last(self.input_subviews).$el.focus();
if (!config.device.isMobile) {
// in mobile mode, we would rathor not focusing manually the
// input, because it opens up the integrated keyboard, which is
// not what you expect when you just selected a filter.
_.last(self.input_subviews).$el.focus();
}
});
},
childFocused: function () {
@@ -666,6 +672,18 @@ var SearchView = Widget.extend({
// Private
//--------------------------------------------------------------------------
/**
* Will return $element where Filters, Group By and Favorite buttons are
* going to be pushed. This method is overriden by the mobile search view
* to add these buttons somewhere else in the dom.
*
* @private
* @returns {jQueryElement}
*/
_getButtonsElement: function () {
return this.$buttons;
},
/**
* Processes a fieldsView in place. In particular, parses its arch.
*
@@ -0,0 +1,96 @@
odoo.define('web.SearchViewMobile', function (require) {
"use strict";
var config = require('web.config');
var SearchView = require('web.SearchView');
if (!config.device.isMobile) {
return;
}
SearchView.include({
template:'SearchViewMobile',
events:_.extend({}, SearchView.prototype.events, {
'click .o_mobile_search_close, .o_mobile_search_show_result, .o_enable_searchview': '_toggleMobileSearchView',
'click': '_onOpenMobileSearchView',
'click .o_mobile_search_clear_facets': '_onEmptyAll',
'show.bs.dropdown .o_mobile_search_filter .o_dropdown': '_onDropdownToggle',
'hide.bs.dropdown .o_mobile_search_filter .o_dropdown': '_onDropdownToggle',
}),
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
renderFacets: function () {
this._super.apply(this, arguments);
this.$('.o_mobile_search_clear_facets')
.toggleClass('o_hidden', !this.query.length);
},
/**
* @override
*/
toggle_visibility: function (is_visible) {
// Do not do anything, toggling visibility of searchview is handled
// explicitly for mobile
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_getButtonsElement: function () {
return this.$('.o_mobile_search_filter');
},
/**
* Toggle mobile search view screen
*
* @private
*/
_toggleMobileSearchView: function () {
this.$('.o_enable_searchview').toggleClass('btn-default', !!this.query.length);
this.$('.o_mobile_search').toggleClass('o_hidden');
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Toggle the little arrow in main buttons
*
* @private
* @param {BootstrapEvent} ev
*/
_onDropdownToggle: function (ev) {
$(ev.currentTarget).find('.fa-chevron-right').toggleClass('fa-chevron-down');
},
/**
* Clears all filters from the search view
*
* @private
* @param {MouseEvent} event
*/
_onEmptyAll: function () {
this.query.reset();
},
/**
* Open the mobile search view screen
*
* @private
* @param {MouseEvent} ev
*/
_onOpenMobileSearchView: function (ev) {
if (ev.target === this.el) {
this._toggleMobileSearchView();
}
},
});
});
@@ -30,9 +30,9 @@
}
.btn.o_enable_searchview {
width: 10%;
margin-left: 2%;
padding: 0px;
background: white;
}
> .o_cp_left {
+70 -64
View File
@@ -5,84 +5,90 @@
}
.o_searchview {
@include o-flex-display;
@include o-flex-flow(row, wrap);
@include o-align-items(flex-end);
position: relative;
padding: 0 20px 1px 0;
.o_searchview_facet {
$o-searchview-facet-remove-width: 18px;
@include o-flex(0, 0, auto);
max-width: 100%;
@include o-flex-display;
position: relative;
margin: 1px 3px 0 0;
.o_searchview_facet_label {
@include o-flex(0, 0, auto);
@include o-text-overflow;
padding: 0 3px;
color: white;
@include o-flex-display;
@include o-align-items(center);
}
.o_facet_values {
padding: 0 $o-searchview-facet-remove-width 0 5px;
.o_facet_values_sep {
font-style: italic;
}
}
.o_facet_remove {
@include o-flex(0, 0, auto);
@include o-flex-display;
@include o-justify-content(center);
@include o-align-items(center);
@include o-position-absolute(0, 0, 0);
width: $o-searchview-facet-remove-width;
cursor: pointer;
}
}
.o_searchview_input {
width: 100px;
@include o-flex(1, 0, auto);
}
.o_searchview_more {
font-size: 16px;
cursor: pointer;
}
.o_searchview_autocomplete {
@include o-position-absolute(100%, $left: auto);
width: 100%;
.o_searchview_input_container {
@include o-flex-display;
@include o-flex-flow(row, wrap);
li {
padding-left: 25px;
.o_searchview_facet {
$o-searchview-facet-remove-width: 18px;
@include o-flex(0, 0, auto);
max-width: 100%;
@include o-flex-display;
position: relative;
&.o-indent {
padding-left: 50px;
margin: 1px 3px 0 0;
.o_searchview_facet_label {
@include o-flex(0, 0, auto);
@include o-text-overflow;
padding: 0 3px;
color: white;
@include o-flex-display;
@include o-align-items(center);
}
a {
display: inline-block;
padding-left: 0px;
padding-right: 0px;
&:hover {
background-color: inherit;
.o_facet_values {
padding: 0 $o-searchview-facet-remove-width 0 5px;
.o_facet_values_sep {
font-style: italic;
}
&.o-expand, &.o-expanded { // caret
@include o-position-absolute($left: 6px);
padding: 3px;
}
.o_facet_remove {
@include o-flex(0, 0, auto);
@include o-flex-display;
@include o-justify-content(center);
@include o-align-items(center);
@include o-position-absolute(0, 0, 0);
width: $o-searchview-facet-remove-width;
cursor: pointer;
}
}
.o_searchview_input {
width: 100px;
@include o-flex(1, 0, auto);
}
.o_searchview_more {
font-size: 16px;
cursor: pointer;
}
.o_searchview_autocomplete {
@include o-position-absolute(100%, $left: auto);
width: 100%;
li {
padding-left: 25px;
position: relative;
&.o-indent {
padding-left: 50px;
}
&.o-expand:before {
@include o-caret-right;
}
&.o-expanded:before {
@include o-caret-down;
a {
display: inline-block;
padding-left: 0px;
padding-right: 0px;
&:hover {
background-color: inherit;
}
&.o-expand, &.o-expanded { // caret
@include o-position-absolute($left: 6px);
padding: 3px;
}
&.o-expand:before {
@include o-caret-right;
}
&.o-expanded:before {
@include o-caret-down;
}
}
}
}
@@ -0,0 +1,88 @@
.o_web_client.o_touch_device {
.o_mobile_search {
position: fixed;
top: 0;
left: 0;
bottom: 0;
padding: 0;
width: 100%;
background-color: white;
z-index: 1000;
overflow: auto;
.o_mobile_search_header {
height: 46px;
margin-bottom: 10px;
width: 100%;
background-color: $o-brand-primary;
color: white;
span:active {
background-color: darken($o-brand-primary, 10%);
}
}
.o_searchview_input_container {
display: flex;
padding: 15px 20px 0 20px;
position: relative;
.o_searchview_input {
width: 100%;
margin-bottom: 15px;
border-bottom: 1px solid $o-brand-secondary;
}
.o_searchview_facet {
border-radius: 10px;
display: inline-flex;
order:1;
.o_searchview_facet_label {
border-radius: 2em 0em 0em 2em;
}
}
.o_searchview_autocomplete {
top:100%;
> li {
margin: 5px 0px;
}
}
}
.o_mobile_search_filter {
padding-bottom: 15%;
.o_dropdown {
width: 100%;
margin: 15px 5px 0px 5px;
border: solid 1px darken($gray-lighter, 20%);
}
.o_dropdown_toggler_btn {
width: 100%;
text-align: left;
}
// We disable the backdrop in this case because it prevents any
// interaction outside of a dropdown while it is open.
.dropdown-backdrop {
z-index: -1;
}
ul {
position: relative;
width: 100%;
box-shadow: none;
border: none;
color: $gray-light;
.divider {
margin: 0px;
}
> li > a {
padding: 10px 26px;
}
}
}
.o_mobile_search_show_result {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 15px;
font-size: 17px;
z-index: 1000;
}
}
}
+23 -4
View File
@@ -1013,7 +1013,26 @@
</div>
<div t-name="SearchView" class="o_searchview">
<span class="o_searchview_more fa" title="Advanced Search..."/>
<div class="o_searchview_input_container"/>
</div>
<t t-name="SearchViewMobile">
<div class="o_searchview">
<button class="o_enable_searchview btn btn-sm fa fa-search"/>
<div class="o_mobile_search o_hidden">
<div class="o_mobile_search_header">
<span class="o_mobile_search_close pull-left mt16 mb16 mr8 ml16">
<i class="fa fa-arrow-left"/>
<strong class="pull-right ml8">FILTER</strong>
</span>
<span class="pull-right o_mobile_search_clear_facets mt16 mr16">CLEAR</span>
</div>
<div class="o_searchview_input_container"/>
<div class="o_mobile_search_filter o_search_options mb8 mt8 ml16 mr16"/>
<div class="btn btn-primary o_mobile_search_show_result">SEE RESULT</div>
</div>
</div>
</t>
<input t-name="SearchView.InputView" type="text" class="o_searchview_input" accesskey="Q" placeholder="Search..."/>
<div t-name="SearchView.FacetView" tabindex="0" class="o_searchview_facet">
<span t-if="widget.model.has('icon')" t-att-class="'fa ' + widget.model.get('icon') + ' o_searchview_facet_label'"/>
@@ -1031,7 +1050,7 @@
</t>
<div t-name="SearchView.FilterMenu" class="btn-group o_dropdown">
<button class="o_dropdown_toggler_btn btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false" tabindex="-1">
<span class="fa fa-filter"/> Filters <span class="caret"/>
<span class="fa fa-filter"/> Filters <span t-attf-class="#{widget.isMobile ? 'fa fa-chevron-right pull-right mt4' : 'caret'}"/>
</button>
<ul class="dropdown-menu o_filters_menu" role="menu">
<li class="o_add_filter o_closed_menu">
@@ -1141,9 +1160,9 @@
</option>
</select>
</t>
<div t-name="SearchView.GroupByMenu" class="btn-group hidden-xs o_dropdown">
<div t-name="SearchView.GroupByMenu" class="btn-group o_dropdown">
<button class="o_dropdown_toggler_btn btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false" tabindex="-1">
<span class="fa fa-bars"/> Group By <span class="caret"/>
<span class="fa fa-bars"/> Group By <span t-attf-class="#{widget.isMobile ? 'fa fa-chevron-right pull-right mt4' : 'caret'}"/>
</button>
<ul class="dropdown-menu o_group_by_menu" role="menu">
<li class="divider"/>
@@ -1164,7 +1183,7 @@
</t>
<div t-name="SearchView.FavoriteMenu" class="btn-group o_dropdown">
<button class="o_dropdown_toggler_btn btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false" tabindex="-1">
<span class="fa fa-star"/> Favorites <span class="caret"/>
<span class="fa fa-star"/> Favorites <span t-attf-class="#{widget.isMobile ? 'fa fa-chevron-right pull-right mt4' : 'caret'}"/>
</button>
<ul class="dropdown-menu o_favorites_menu" role="menu">
<li class="divider user_filter"/>
@@ -0,0 +1,83 @@
odoo.define('web.search_tests', function (require) {
"use strict";
var testUtils = require('web.test_utils');
var createActionManager = testUtils.createActionManager;
QUnit.module('Mobile Search view Screen', {
beforeEach: function () {
this.data = {
partner: {
fields: {
foo: {string: "Foo", type: "char"},
boolean_field: {string: "I am a boolean", type: "boolean"},
},
records: [
{id: 1, display_name: "First record", foo: "yop"},
],
},
};
this.actions = [{
id: 1,
name: 'Partners Action 1',
res_model: 'partner',
type: 'ir.actions.act_window',
views: [[false, 'list']],
}];
this.archs = {
// list views
'partner,false,list': '<tree><field name="foo"/></tree>',
// search views
'partner,false,search': '<search>' +
'<filter string="Active" name="my_projects" domain="[(\'boolean_field\', \'=\', True)]"/>' +
'<field name="foo" string="Foo"/>' +
'</search>',
};
},
}, function () {
QUnit.test('can activate a filter with mobile search view', function (assert) {
assert.expect(3);
var filterActiveFlag = false;
var actionManager = createActionManager({
actions: this.actions,
archs: this.archs,
data: this.data,
mockRPC: function (route, args) {
if (filterActiveFlag) {
assert.deepEqual(args.domain, [['boolean_field', '=', true]],
"domain should have been properly transferred to list view");
}
return this._super.apply(this, arguments);
},
});
actionManager.doAction(1);
assert.ok(!$('.o_mobile_search').is(':visible'),
'mobile search view is not visible');
// open the search view
$('button.o_enable_searchview').click();
assert.ok($('.o_mobile_search').is(':visible'),
'mobile search view is visible');
// open filter sub menu
$('button.o_dropdown_toggler_btn').first().click();
filterActiveFlag = true;
// click on Active filter
$('.o_filters_menu a:contains(Active)').click();
actionManager.destroy();
});
});
});
@@ -100,6 +100,9 @@ var createActionManager = function (params) {
addMockEnvironment(widget, _.defaults(params, {debounce: false}));
widget.appendTo($target);
widget.$el.addClass('o_web_client');
if (config.device.isMobile) {
widget.$el.addClass('o_touch_device');
}
var userContext = params.context && params.context.user_context || {};
var actionManager = new ActionManager(widget, userContext);
+3
View File
@@ -155,6 +155,7 @@
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/kanban_view_mobile.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/web_calendar.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/search_view.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/search_view_mobile.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/search_view_extra.scss"/>
<link rel="stylesheet" type="text/scss" href="/web/static/src/scss/data_export.scss"/>
@@ -211,6 +212,7 @@
<script type="text/javascript" src="/web/static/src/js/chrome/user_menu.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/menu.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/search_view.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/search_view_mobile.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/search_filters.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/search_inputs.js"></script>
<script type="text/javascript" src="/web/static/src/js/chrome/search_menus.js"></script>
@@ -579,6 +581,7 @@
<script type="text/javascript" src="/base/static/tests/base_settings_mobile_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/chrome/action_manager_mobile_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/chrome/search_view_mobile_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/views/calendar_mobile_tests.js"></script>
<script type="text/javascript" src="/web/static/tests/views/kanban_mobile_tests.js"></script>
</t>
@@ -61,7 +61,8 @@
</div>
<t t-name="website.DateRangeButtons">
<div class="btn-group btn-group-sm o_date_range_buttons pull-right">
<!-- TODO: Applied hidden-xs class as it is going to push in control panel and it breaks UI, maybe we will improve it in future -->
<div class="btn-group btn-group-sm o_date_range_buttons hidden-xs pull-right">
<button class="btn btn-sm btn-default js_date_range active" data-date="week">Last Week</button>
<button class="btn btn-sm btn-default js_date_range" data-date="month">Last Month</button>
<button class="btn btn-sm btn-default js_date_range" data-date="year">Last Year</button>