mirror of
https://github.com/OCA/reporting-engine.git
synced 2025-02-16 16:30:38 +02:00
[12.0][ADD] kpi_dashboard
This commit is contained in:
76
kpi_dashboard/static/src/js/dashboard_controller.js
Normal file
76
kpi_dashboard/static/src/js/dashboard_controller.js
Normal file
@@ -0,0 +1,76 @@
|
||||
odoo.define('kpi_dashboard.DashboardController', function (require) {
|
||||
"use strict";
|
||||
|
||||
var BasicController = require('web.BasicController');
|
||||
var core = require('web.core');
|
||||
var qweb = core.qweb;
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
var DashboardController = BasicController.extend({
|
||||
custom_events: _.extend({}, BasicController.prototype.custom_events, {
|
||||
addDashboard: '_addDashboard',
|
||||
}),
|
||||
renderPager: function ($node, options) {
|
||||
options = _.extend({}, options, {
|
||||
validate: this.canBeDiscarded.bind(this),
|
||||
});
|
||||
this._super($node, options);
|
||||
},
|
||||
_pushState: function (state) {
|
||||
state = state || {};
|
||||
var env = this.model.get(this.handle, {env: true});
|
||||
state.id = env.currentId;
|
||||
this._super(state);
|
||||
},
|
||||
_addDashboard: function () {
|
||||
var self = this;
|
||||
var action = self.initialState.specialData.action_id;
|
||||
var name = self.initialState.specialData.name;
|
||||
if (! action) {
|
||||
self.do_warn(_t("First you must create the Menu"));
|
||||
}
|
||||
return self._rpc({
|
||||
route: '/board/add_to_dashboard',
|
||||
params: {
|
||||
action_id: action,
|
||||
context_to_save: {'res_id': self.initialState.res_id},
|
||||
domain: [('id', '=', self.initialState.res_id)],
|
||||
view_mode: 'dashboard',
|
||||
name: name,
|
||||
},
|
||||
})
|
||||
.then(function (r) {
|
||||
if (r) {
|
||||
self.do_notify(
|
||||
_.str.sprintf(_t("'%s' added to dashboard"), name),
|
||||
_t('Please refresh your browser for the changes to take effect.')
|
||||
);
|
||||
} else {
|
||||
self.do_warn(_t("Could not add KPI dashboard to dashboard"));
|
||||
}
|
||||
});
|
||||
},
|
||||
_updateButtons: function () {
|
||||
// HOOK Function
|
||||
this.$buttons.on(
|
||||
'click', '.o_dashboard_button_add',
|
||||
this._addDashboard.bind(this));
|
||||
},
|
||||
renderButtons: function ($node) {
|
||||
if (! $node) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$buttons = $('<div/>');
|
||||
this.$buttons.append(qweb.render(
|
||||
"kpi_dashboard.buttons", {widget: this}));
|
||||
|
||||
this._updateButtons();
|
||||
this.$buttons.appendTo($node);
|
||||
},
|
||||
});
|
||||
|
||||
return DashboardController;
|
||||
|
||||
});
|
||||
23
kpi_dashboard/static/src/js/dashboard_model.js
Normal file
23
kpi_dashboard/static/src/js/dashboard_model.js
Normal file
@@ -0,0 +1,23 @@
|
||||
odoo.define('kpi_dashboard.DashboardModel', function (require) {
|
||||
"use strict";
|
||||
|
||||
var BasicModel = require('web.BasicModel');
|
||||
|
||||
var DashboardModel = BasicModel.extend({
|
||||
_fetchRecord: function (record, options) {
|
||||
return this._rpc({
|
||||
model: record.model,
|
||||
method: 'read_dashboard',
|
||||
args: [[record.res_id]],
|
||||
context: _.extend({}, record.getContext(), {bin_size: true}),
|
||||
})
|
||||
.then(function (result) {
|
||||
record.specialData = result;
|
||||
return result
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
return DashboardModel;
|
||||
|
||||
});
|
||||
74
kpi_dashboard/static/src/js/dashboard_renderer.js
Normal file
74
kpi_dashboard/static/src/js/dashboard_renderer.js
Normal file
@@ -0,0 +1,74 @@
|
||||
odoo.define('kpi_dashboard.DashboardRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
var BasicRenderer = require('web.BasicRenderer');
|
||||
var core = require('web.core');
|
||||
var registry = require('kpi_dashboard.widget_registry');
|
||||
var BusService = require('bus.BusService');
|
||||
var qweb = core.qweb;
|
||||
|
||||
var DashboardRenderer= BasicRenderer.extend({
|
||||
className: "o_dashboard_view",
|
||||
_getDashboardWidget: function (kpi) {
|
||||
var Widget = registry.getAny([
|
||||
kpi.widget, 'abstract',
|
||||
]);
|
||||
var widget = new Widget(this, kpi);
|
||||
return widget;
|
||||
},
|
||||
_renderView: function () {
|
||||
this.$el.html($(qweb.render('dashboard_kpi.dashboard')));
|
||||
this.$el.css(
|
||||
'background-color', this.state.specialData.background_color);
|
||||
this.$el.find('.gridster')
|
||||
.css('width', this.state.specialData.width);
|
||||
this.$grid = this.$el.find('.gridster ul');
|
||||
var self = this;
|
||||
this.kpi_widget = {};
|
||||
_.each(this.state.specialData.item_ids, function (kpi) {
|
||||
var element = $(qweb.render(
|
||||
'kpi_dashboard.kpi', {widget: kpi}));
|
||||
element.css('background-color', kpi.color);
|
||||
element.css('color', kpi.font_color);
|
||||
self.$grid.append(element);
|
||||
self.kpi_widget[kpi.id] = self._getDashboardWidget(kpi);
|
||||
self.kpi_widget[kpi.id].appendTo(element);
|
||||
});
|
||||
this.$grid.gridster({
|
||||
widget_margins: [
|
||||
this.state.specialData.margin_x,
|
||||
this.state.specialData.margin_y,
|
||||
],
|
||||
widget_base_dimensions: [
|
||||
this.state.specialData.widget_dimension_x,
|
||||
this.state.specialData.widget_dimension_y,
|
||||
],
|
||||
cols: this.state.specialData.max_cols,
|
||||
}).data('gridster').disable();
|
||||
this.channel = 'kpi_dashboard_' + this.state.res_id;
|
||||
this.call(
|
||||
'bus_service', 'addChannel', this.channel);
|
||||
this.call('bus_service', 'startPolling');
|
||||
this.call(
|
||||
'bus_service', 'onNotification',
|
||||
this, this._onNotification
|
||||
);
|
||||
return $.when();
|
||||
},
|
||||
_onNotification: function (notifications) {
|
||||
var self = this;
|
||||
_.each(notifications, function (notification) {
|
||||
var channel = notification[0];
|
||||
var message = notification[1];
|
||||
if (channel === self.channel && message) {
|
||||
var widget = self.kpi_widget[message.id];
|
||||
if (widget !== undefined) {
|
||||
widget._fillWidget(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return DashboardRenderer;
|
||||
});
|
||||
44
kpi_dashboard/static/src/js/dashboard_view.js
Normal file
44
kpi_dashboard/static/src/js/dashboard_view.js
Normal file
@@ -0,0 +1,44 @@
|
||||
odoo.define('kpi_dashboard.DashboardView', function (require) {
|
||||
"use strict";
|
||||
|
||||
var BasicView = require('web.BasicView');
|
||||
var DashboardController = require('kpi_dashboard.DashboardController');
|
||||
var DashboardModel = require('kpi_dashboard.DashboardModel');
|
||||
var DashboardRenderer = require('kpi_dashboard.DashboardRenderer');
|
||||
var view_registry = require('web.view_registry');
|
||||
var core = require('web.core');
|
||||
|
||||
var _lt = core._lt;
|
||||
|
||||
var DashboardView = BasicView.extend({
|
||||
jsLibs: [
|
||||
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.js',
|
||||
],
|
||||
cssLibs: [
|
||||
'/kpi_dashboard/static/lib/gridster/jquery.dsmorse-gridster.min.css',
|
||||
],
|
||||
accesskey: "d",
|
||||
display_name: _lt("Dashboard"),
|
||||
icon: 'fa-tachometer',
|
||||
viewType: 'dashboard',
|
||||
config: _.extend({}, BasicView.prototype.config, {
|
||||
Controller: DashboardController,
|
||||
Renderer: DashboardRenderer,
|
||||
Model: DashboardModel,
|
||||
}),
|
||||
multi_record: false,
|
||||
searchable: false,
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.controllerParams.mode = 'readonly';
|
||||
this.loadParams.type = 'record';
|
||||
if (! this.loadParams.res_id && this.loadParams.context.res_id) {
|
||||
this.loadParams.res_id = this.loadParams.context.res_id;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
view_registry.add('dashboard', DashboardView);
|
||||
|
||||
return DashboardView;
|
||||
});
|
||||
91
kpi_dashboard/static/src/js/widget/abstract_widget.js
Normal file
91
kpi_dashboard/static/src/js/widget/abstract_widget.js
Normal file
@@ -0,0 +1,91 @@
|
||||
odoo.define('kpi_dashboard.AbstractWidget', function (require) {
|
||||
"use strict";
|
||||
var Widget = require('web.Widget');
|
||||
var field_utils = require('web.field_utils');
|
||||
var time = require('web.time');
|
||||
var ajax = require('web.ajax');
|
||||
var registry = require('kpi_dashboard.widget_registry');
|
||||
|
||||
var AbstractWidget = Widget.extend({
|
||||
template: 'kpi_dashboard.base_widget', // Template used by the widget
|
||||
cssLibs: [], // Specific css of the widget
|
||||
jsLibs: [], // Specific Javascript libraries of the widget
|
||||
events: {
|
||||
'click .o_kpi_dashboard_toggle_button': '_onClickToggleButton',
|
||||
'click .direct_action': '_onClickDirectAction',
|
||||
},
|
||||
init: function (parent, kpi_values) {
|
||||
this._super(parent);
|
||||
this.col = kpi_values.col;
|
||||
this.row = kpi_values.row;
|
||||
this.sizex = kpi_values.sizex;
|
||||
this.sizey = kpi_values.sizey;
|
||||
this.color = kpi_values.color;
|
||||
this.values = kpi_values;
|
||||
this.margin_x = parent.state.specialData.margin_x;
|
||||
this.margin_y = parent.state.specialData.margin_y;
|
||||
this.widget_dimension_x = parent.state.specialData.widget_dimension_x;
|
||||
this.widget_dimension_y = parent.state.specialData.widget_dimension_y;
|
||||
this.prefix = kpi_values.prefix;
|
||||
this.suffix = kpi_values.suffix;
|
||||
this.actions = kpi_values.actions;
|
||||
this.widget_size_x = this.widget_dimension_x * this.sizex +
|
||||
(this.sizex - 1) * this.margin_x;
|
||||
this.widget_size_y = this.widget_dimension_y * this.sizey +
|
||||
(this.sizey - 1) * this.margin_y;
|
||||
},
|
||||
willStart: function () {
|
||||
// We need to load the libraries before the start
|
||||
return $.when(ajax.loadLibs(this), this._super.apply(this, arguments));
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
self._fillWidget(self.values);
|
||||
});
|
||||
},
|
||||
_onClickToggleButton: function (event) {
|
||||
event.preventDefault();
|
||||
this.$el.toggleClass('o_dropdown_open');
|
||||
},
|
||||
_fillWidget: function (values) {
|
||||
// This function fills the widget values
|
||||
if (this.$el === undefined)
|
||||
return;
|
||||
this.fillWidget(values);
|
||||
var item = this.$el.find('[data-bind="value_last_update_display"]');
|
||||
if (item && values.value_last_update !== undefined) {
|
||||
var value = field_utils.parse.datetime(values.value_last_update);
|
||||
item.text(value.clone().add(
|
||||
this.getSession().getTZOffset(value), 'minutes').format(
|
||||
time.getLangDatetimeFormat()
|
||||
));
|
||||
}
|
||||
var $manage = this.$el.find('.o_kpi_dashboard_manage');
|
||||
if ($manage && this.showManagePanel(values))
|
||||
$manage.toggleClass('hidden', false);
|
||||
},
|
||||
showManagePanel: function (values) {
|
||||
// Hook for extensions
|
||||
return (values.actions !== undefined);
|
||||
},
|
||||
fillWidget: function (values) {
|
||||
// Specific function that will be changed by specific widget
|
||||
var value = values.value;
|
||||
var self = this;
|
||||
_.each(value, function (val, key) {
|
||||
var item = self.$el.find('[data-bind=' + key + ']')
|
||||
if (item)
|
||||
item.text(val);
|
||||
})
|
||||
},
|
||||
_onClickDirectAction: function(event) {
|
||||
event.preventDefault();
|
||||
var $data = $(event.currentTarget).closest('a');
|
||||
return this.do_action($($data).data('id'));
|
||||
}
|
||||
});
|
||||
|
||||
registry.add('abstract', AbstractWidget);
|
||||
return AbstractWidget;
|
||||
});
|
||||
108
kpi_dashboard/static/src/js/widget/graph_widget.js
Normal file
108
kpi_dashboard/static/src/js/widget/graph_widget.js
Normal file
@@ -0,0 +1,108 @@
|
||||
odoo.define('kpi_dashboard.GraphWidget', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractWidget = require('kpi_dashboard.AbstractWidget');
|
||||
var registry = require('kpi_dashboard.widget_registry');
|
||||
var core = require('web.core');
|
||||
var qweb = core.qweb;
|
||||
|
||||
|
||||
var GraphWidget = AbstractWidget.extend({
|
||||
template: 'kpi_dashboard.graph',
|
||||
jsLibs: [
|
||||
'/web/static/lib/nvd3/d3.v3.js',
|
||||
'/web/static/lib/nvd3/nv.d3.js',
|
||||
'/web/static/src/js/libs/nvd3.js',
|
||||
],
|
||||
cssLibs: [
|
||||
'/web/static/lib/nvd3/nv.d3.css',
|
||||
],
|
||||
start: function () {
|
||||
this._onResize = this._onResize.bind(this);
|
||||
nv.utils.windowResize(this._onResize);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
destroy: function () {
|
||||
if ('nv' in window && nv.utils && nv.utils.offWindowResize) {
|
||||
// if the widget is destroyed before the lazy loaded libs (nv) are
|
||||
// actually loaded (i.e. after the widget has actually started),
|
||||
// nv is undefined, but the handler isn't bound yet anyway
|
||||
nv.utils.offWindowResize(this._onResize);
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
_getChartOptions: function (values) {
|
||||
return {
|
||||
x: function (d, u) { return u; },
|
||||
margin: {'left': 0, 'right': 0, 'top': 5, 'bottom': 0},
|
||||
showYAxis: false,
|
||||
showXAxis: false,
|
||||
showLegend: false,
|
||||
height: this.widget_size_y - 90,
|
||||
width: this.widget_size_x - 20,
|
||||
};
|
||||
},
|
||||
_chartConfiguration: function (values) {
|
||||
|
||||
this.chart.forceY([0]);
|
||||
this.chart.xAxis.tickFormat(function (d) {
|
||||
var label = '';
|
||||
_.each(values.value.graphs, function (v) {
|
||||
if (v.values[d] && v.values[d].x) {
|
||||
label = v.values[d].x;
|
||||
}
|
||||
});
|
||||
return label;
|
||||
});
|
||||
this.chart.yAxis.tickFormat(d3.format(',.2f'));
|
||||
|
||||
this.chart.tooltip.contentGenerator(function (key) {
|
||||
return qweb.render('GraphCustomTooltip', {
|
||||
'color': key.point.color,
|
||||
'key': key.series[0].title,
|
||||
'value': d3.format(',.2f')(key.point.y)
|
||||
});
|
||||
});
|
||||
},
|
||||
_addGraph: function (values) {
|
||||
var data = values.value.graphs;
|
||||
this.$svg.addClass('o_graph_linechart');
|
||||
this.chart = nv.models.lineChart();
|
||||
this.chart.options(
|
||||
this._getChartOptions(values)
|
||||
);
|
||||
this._chartConfiguration(values);
|
||||
d3.select(this.$('svg')[0])
|
||||
.datum(data)
|
||||
.transition().duration(600)
|
||||
.call(this.chart);
|
||||
this.$('svg').css('height', this.widget_size_y - 90);
|
||||
this._customizeChart();
|
||||
},
|
||||
fillWidget: function (values) {
|
||||
var self = this;
|
||||
var element = this.$el.find('[data-bind="value"]');
|
||||
element.empty();
|
||||
element.css('padding-left', 10).css('padding-right', 10);
|
||||
this.chart = null;
|
||||
nv.addGraph(function () {
|
||||
self.$svg = self.$el.find(
|
||||
'[data-bind="value"]'
|
||||
).append('<svg width=' + (self.widget_size_x - 20) + '>');
|
||||
self._addGraph(values);
|
||||
});
|
||||
},
|
||||
_customizeChart: function () {
|
||||
// Hook function
|
||||
},
|
||||
_onResize: function () {
|
||||
if (this.chart) {
|
||||
this.chart.update();
|
||||
this._customizeChart();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
registry.add('graph', GraphWidget);
|
||||
return GraphWidget;
|
||||
});
|
||||
39
kpi_dashboard/static/src/js/widget/meter_widget.js
Normal file
39
kpi_dashboard/static/src/js/widget/meter_widget.js
Normal file
@@ -0,0 +1,39 @@
|
||||
odoo.define('kpi_dashboard.MeterWidget', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractWidget = require('kpi_dashboard.AbstractWidget');
|
||||
var registry = require('kpi_dashboard.widget_registry');
|
||||
|
||||
|
||||
var MeterWidget = AbstractWidget.extend({
|
||||
template: 'kpi_dashboard.meter',
|
||||
jsLibs: [
|
||||
'/kpi_dashboard/static/lib/gauge/GaugeMeter.js',
|
||||
],
|
||||
fillWidget: function (values) {
|
||||
var input = this.$el.find('[data-bind="value"]');
|
||||
var options = this._getMeterOptions(values);
|
||||
var margin = (this.widget_dimension_x - options.size)/2;
|
||||
input.gaugeMeter(options);
|
||||
input.parent().css('padding-left', margin);
|
||||
},
|
||||
_getMeterOptions: function (values) {
|
||||
var size = Math.min(
|
||||
this.widget_size_x,
|
||||
this.widget_size_y - 40) - 10;
|
||||
return {
|
||||
percent: values.value.value,
|
||||
style: 'Arch',
|
||||
width: 10,
|
||||
size: size,
|
||||
prepend: values.prefix !== undefined ? values.prefix : '',
|
||||
append: values.suffix !== undefined ? values.suffix : '',
|
||||
color: values.font_color,
|
||||
animate_text_colors: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
registry.add('meter', MeterWidget);
|
||||
return MeterWidget;
|
||||
});
|
||||
72
kpi_dashboard/static/src/js/widget/number_widget.js
Normal file
72
kpi_dashboard/static/src/js/widget/number_widget.js
Normal file
@@ -0,0 +1,72 @@
|
||||
odoo.define('kpi_dashboard.NumberWidget', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractWidget = require('kpi_dashboard.AbstractWidget');
|
||||
var registry = require('kpi_dashboard.widget_registry');
|
||||
var field_utils = require('web.field_utils');
|
||||
|
||||
|
||||
var NumberWidget = AbstractWidget.extend({
|
||||
template: 'kpi_dashboard.number',
|
||||
shortNumber: function (num) {
|
||||
if (Math.abs(num) >= 1000000000000) {
|
||||
return field_utils.format.integer(num / 1000000000000, false, {
|
||||
digits: [3, 1]}) + 'T';
|
||||
}
|
||||
if (Math.abs(num) >= 1000000000) {
|
||||
return field_utils.format.integer(num / 1000000000, false, {
|
||||
digits: [3,1]}) + 'G';
|
||||
}
|
||||
if (Math.abs(num) >= 1000000) {
|
||||
return field_utils.format.integer(num / 1000000, false, {
|
||||
digits: [3, 1]}) + 'M';
|
||||
}
|
||||
if (Math.abs(num) >= 1000) {
|
||||
return field_utils.format.float(num / 1000, false, {
|
||||
digits: [3, 1]}) + 'K';
|
||||
}
|
||||
if (Math.abs(num) >= 10) {
|
||||
return field_utils.format.float(num, false, {
|
||||
digits: [3, 1]});
|
||||
}
|
||||
return field_utils.format.float(num, false, {
|
||||
digits: [3, 2]});
|
||||
},
|
||||
fillWidget: function (values) {
|
||||
var widget = this.$el;
|
||||
var value = values.value.value;
|
||||
if (value === undefined) {
|
||||
value = 0;
|
||||
}
|
||||
var item = widget.find('[data-bind="value"]');
|
||||
if (item) {
|
||||
item.text(this.shortNumber(value));
|
||||
}
|
||||
var previous = values.value.previous;
|
||||
|
||||
var $change_rate = widget.find('.change-rate');
|
||||
if (previous === undefined) {
|
||||
$change_rate.toggleClass('active', false);
|
||||
} else {
|
||||
var difference = 0;
|
||||
if (previous !== 0) {
|
||||
difference = field_utils.format.integer(
|
||||
(100 * value / previous) - 100) + '%';
|
||||
}
|
||||
$change_rate.toggleClass('active', true);
|
||||
var $difference = widget.find('[data-bind="difference"]');
|
||||
$difference.text(difference);
|
||||
var $arrow = widget.find('[data-bind="arrow"]');
|
||||
if (value < previous) {
|
||||
$arrow.toggleClass('fa-arrow-up', false);
|
||||
$arrow.toggleClass('fa-arrow-down', true);
|
||||
} else {
|
||||
$arrow.toggleClass('fa-arrow-up', true);
|
||||
$arrow.toggleClass('fa-arrow-down', false);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
registry.add('number', NumberWidget);
|
||||
return NumberWidget;
|
||||
});
|
||||
17
kpi_dashboard/static/src/js/widget/text_widget.js
Normal file
17
kpi_dashboard/static/src/js/widget/text_widget.js
Normal file
@@ -0,0 +1,17 @@
|
||||
odoo.define('kpi_dashboard.TextWidget', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractWidget = require('kpi_dashboard.AbstractWidget');
|
||||
var registry = require('kpi_dashboard.widget_registry');
|
||||
|
||||
|
||||
var TextWidget = AbstractWidget.extend({
|
||||
template: 'kpi_dashboard.base_text',
|
||||
fillWidget: function () {
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
registry.add('base_text', TextWidget);
|
||||
return TextWidget;
|
||||
});
|
||||
7
kpi_dashboard/static/src/js/widget_registry.js
Normal file
7
kpi_dashboard/static/src/js/widget_registry.js
Normal file
@@ -0,0 +1,7 @@
|
||||
odoo.define('kpi_dashboard.widget_registry', function (require) {
|
||||
"use strict";
|
||||
|
||||
var Registry = require('web.Registry');
|
||||
|
||||
return new Registry();
|
||||
});
|
||||
112
kpi_dashboard/static/src/scss/kpi_dashboard.scss
Normal file
112
kpi_dashboard/static/src/scss/kpi_dashboard.scss
Normal file
@@ -0,0 +1,112 @@
|
||||
.o_dashboard_view {
|
||||
height: 100%;
|
||||
@include o-webclient-padding($top: $o-horizontal-padding/2, $bottom: $o-horizontal-padding/2);
|
||||
display: flex;
|
||||
>.gridster {
|
||||
margin: 0 auto;
|
||||
>ul {
|
||||
>li {
|
||||
text-align: center;
|
||||
list-style: none outside none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.updated_at {
|
||||
font-size: 15px;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.gs-w {
|
||||
padding: 10px;
|
||||
}
|
||||
.centered {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.numbervalue {
|
||||
text-transform: uppercase;
|
||||
font-size: 54px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.change-rate {
|
||||
font-weight: 500;
|
||||
font-size: 30px;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.o_kpi_dashboard_toggle_button {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
margin: -1px -1px auto auto;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: none;
|
||||
height: 35px;
|
||||
}
|
||||
.o_kpi_dashboard_manage_panel {
|
||||
@include o-position-absolute($right: -1px, $top: 34px);
|
||||
margin-top: -1px;
|
||||
&.container {
|
||||
width: 95%;
|
||||
max-width: 400px;
|
||||
}
|
||||
.o_kpi_dashboard_manage_section {
|
||||
border-bottom: 1px solid gray('300');
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
> div {
|
||||
padding: 3px 0 3px 20px;
|
||||
visibility: visible;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
.o_dropdown_open {
|
||||
.o_kpi_dashboard_manage_panel {
|
||||
display: block;
|
||||
}
|
||||
.o_kpi_dashboard_toggle_button {
|
||||
background: white;
|
||||
border-color: gray('400');
|
||||
z-index: $zindex-dropdown + 1;
|
||||
}
|
||||
}
|
||||
.GaugeMeter {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
span, b{
|
||||
margin: 0 23%;
|
||||
width: 54%;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
font-height: 100;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
[data-style="Semi"] B{
|
||||
Margin: 0 10%;
|
||||
Width: 80%;
|
||||
}
|
||||
S, U{
|
||||
Text-Decoration:None;
|
||||
font-height: 100;
|
||||
}
|
||||
B{
|
||||
Color: Black;
|
||||
Font-Weight: 200;
|
||||
Font-Size: 0.85em;
|
||||
Opacity: .8;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
76
kpi_dashboard/static/src/xml/dashboard.xml
Normal file
76
kpi_dashboard/static/src/xml/dashboard.xml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<template>
|
||||
<t t-name="dashboard_kpi.dashboard">
|
||||
<div class="gridster kpi_dashboard">
|
||||
<ul/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="kpi_dashboard.kpi">
|
||||
<li t-att-data-row="widget.row"
|
||||
t-att-data-col="widget.col"
|
||||
t-att-data-sizex="widget.sizex"
|
||||
t-att-data-sizey="widget.sizey"
|
||||
/>
|
||||
</t>
|
||||
<t t-name="kpi_dashboard.base_text">
|
||||
<div class="kpi">
|
||||
<h1 class="title" t-esc="widget.values.name"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="kpi_dashboard.ManagePanel">
|
||||
<t t-if="widget.actions" >
|
||||
<t t-foreach="widget.actions" t-as="action">
|
||||
<div role="menuitem" class="">
|
||||
<a role="menuitem" href="#" class="direct_action" t-att-data-id="action.id" t-att-data-type="action.type">Go to <t t-esc="action.name"/></a>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="kpi_dashboard.base_widget">
|
||||
<div class="kpi">
|
||||
<div class="o_kpi_dashboard_manage hidden">
|
||||
<a class="o_kpi_dashboard_toggle_button" href="#">
|
||||
<i class="fa fa-ellipsis-v" aria-label="Selection" role="img" title="Selection"/>
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="title" t-esc="widget.values.name"/>
|
||||
<p class="updated_at" data-bind="value_last_update_display"/>
|
||||
<div class="container o_kpi_dashboard_manage_panel dropdown-menu">
|
||||
<t t-call="kpi_dashboard.ManagePanel"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="kpi_dashboard.number" t-extend="kpi_dashboard.base_widget">
|
||||
<t t-jquery="h1" t-operation="after">
|
||||
<h2 class="numbervalue">
|
||||
<span t-esc="widget.prefix"/><span data-bind="value"/><span t-esc="widget.suffix"/>
|
||||
</h2>
|
||||
<p class="change-rate">
|
||||
<i class="fa" data-bind="arrow"/>
|
||||
<span data-bind="difference"/>
|
||||
</p>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="kpi_dashboard.meter" t-extend="kpi_dashboard.base_widget">
|
||||
<t t-jquery="h1" t-operation="after">
|
||||
<div class="centered">
|
||||
<div class="GaugeMeter" data-bind="value"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="kpi_dashboard.graph" t-extend="kpi_dashboard.base_widget">
|
||||
<t t-jquery="h1" t-operation="after">
|
||||
<div class="centered">
|
||||
<div data-bind="value"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="kpi_dashboard.buttons">
|
||||
<div class="o_dashboard_buttons" role="toolbar" aria-label="Main actions">
|
||||
<button type="button"
|
||||
class="btn btn-primary o_dashboard_button_add" accesskey="d">
|
||||
Add to Dashboard
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
Reference in New Issue
Block a user