mirror of
https://github.com/bringout/oca-ocb-web.git
synced 2026-04-25 15:12:00 +02:00
Initial commit: Web packages
This commit is contained in:
commit
cd458d4b85
791 changed files with 410049 additions and 0 deletions
|
|
@ -0,0 +1,100 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { loadJS } from "@web/core/assets";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { formatFloat } from "@web/views/fields/formatters";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
import { Component, onWillStart, useEffect, useRef } from "@odoo/owl";
|
||||
|
||||
export class GaugeField extends Component {
|
||||
setup() {
|
||||
this.chart = null;
|
||||
this.canvasRef = useRef("canvas");
|
||||
|
||||
onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js"));
|
||||
|
||||
useEffect(() => {
|
||||
this.renderChart();
|
||||
return () => {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
get formattedValue() {
|
||||
return formatFloat(this.props.value, { humanReadable: true, decimals: 1 });
|
||||
}
|
||||
|
||||
renderChart() {
|
||||
const gaugeValue = this.props.value;
|
||||
let maxValue = Math.max(gaugeValue, this.props.record.data[this.props.maxValueField] || this.props.maxValue);
|
||||
let maxLabel = maxValue;
|
||||
if (gaugeValue === 0 && maxValue === 0) {
|
||||
maxValue = 1;
|
||||
maxLabel = 0;
|
||||
}
|
||||
const config = {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
data: [gaugeValue, maxValue - gaugeValue],
|
||||
backgroundColor: ["#1f77b4", "#dddddd"],
|
||||
label: this.props.title,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
circumference: Math.PI,
|
||||
rotation: -Math.PI,
|
||||
responsive: true,
|
||||
tooltips: {
|
||||
displayColors: false,
|
||||
callbacks: {
|
||||
label: function (tooltipItems) {
|
||||
if (tooltipItems.index === 0) {
|
||||
return _t("Value: ") + gaugeValue;
|
||||
}
|
||||
return _t("Max: ") + maxLabel;
|
||||
},
|
||||
},
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: this.props.title,
|
||||
padding: 4,
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
bottom: 5,
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
cutoutPercentage: 70,
|
||||
},
|
||||
};
|
||||
this.chart = new Chart(this.canvasRef.el, config);
|
||||
}
|
||||
}
|
||||
|
||||
GaugeField.template = "web.GaugeField";
|
||||
GaugeField.props = {
|
||||
...standardFieldProps,
|
||||
maxValueField: { type: String },
|
||||
title: { type: String },
|
||||
maxValue: { type: Number },
|
||||
};
|
||||
|
||||
GaugeField.extractProps = ({ attrs, field }) => {
|
||||
return {
|
||||
maxValueField: attrs.options.max_field || "",
|
||||
title: attrs.options.title || field.string,
|
||||
maxValue: attrs.options.max_value || 100,
|
||||
};
|
||||
};
|
||||
|
||||
registry.category("fields").add("gauge", GaugeField);
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.GaugeField" owl="1">
|
||||
<div class="oe_gauge position-relative">
|
||||
<canvas t-ref="canvas"/>
|
||||
<span class="o_gauge_value position-absolute start-0 end-0 bottom-0 text-center" t-esc="props.value"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
odoo.define('web_kanban_gauge.widget', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractField = require('web.AbstractField');
|
||||
var core = require('web.core');
|
||||
var field_registry = require('web.field_registry');
|
||||
var utils = require('web.utils');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
/**
|
||||
* options
|
||||
*
|
||||
* - max_value: maximum value of the gauge [default: 100]
|
||||
* - max_field: get the max_value from the field that must be present in the
|
||||
* view; takes over max_value
|
||||
* - gauge_value_field: if set, the value displayed below the gauge is taken
|
||||
* from this field instead of the base field used for
|
||||
* the gauge. This allows to display a number different
|
||||
* from the gauge.
|
||||
* - label: lable of the gauge, displayed below the gauge value
|
||||
* - label_field: get the label from the field that must be present in the
|
||||
* view; takes over label
|
||||
* - title: title of the gauge, displayed on top of the gauge
|
||||
* - style: custom style
|
||||
*/
|
||||
|
||||
var GaugeWidget = AbstractField.extend({
|
||||
className: "oe_gauge",
|
||||
jsLibs: [
|
||||
'/web/static/lib/Chart/Chart.js',
|
||||
],
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_render: function () {
|
||||
// current value
|
||||
var val = this.value;
|
||||
if (_.isArray(JSON.parse(val))) {
|
||||
val = JSON.parse(val);
|
||||
}
|
||||
var gauge_value = _.isArray(val) && val.length ? val[val.length-1].value : val;
|
||||
if (this.nodeOptions.gauge_value_field) {
|
||||
gauge_value = this.recordData[this.nodeOptions.gauge_value_field];
|
||||
}
|
||||
|
||||
// max_value
|
||||
var max_value = this.nodeOptions.max_value || 100;
|
||||
if (this.nodeOptions.max_field) {
|
||||
max_value = this.recordData[this.nodeOptions.max_field];
|
||||
}
|
||||
max_value = Math.max(gauge_value, max_value);
|
||||
|
||||
// title
|
||||
var title = this.nodeOptions.title || this.field.string;
|
||||
|
||||
var maxLabel = max_value;
|
||||
if (gauge_value === 0 && max_value === 0) {
|
||||
max_value = 1;
|
||||
maxLabel = 0;
|
||||
}
|
||||
var config = {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
datasets: [{
|
||||
data: [
|
||||
gauge_value,
|
||||
max_value - gauge_value
|
||||
],
|
||||
backgroundColor: [
|
||||
"#1f77b4", "#dddddd"
|
||||
],
|
||||
label: title
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
circumference: Math.PI,
|
||||
rotation: -Math.PI,
|
||||
responsive: true,
|
||||
tooltips: {
|
||||
displayColors: false,
|
||||
callbacks: {
|
||||
label: function(tooltipItems) {
|
||||
if (tooltipItems.index === 0) {
|
||||
return _t('Value: ') + gauge_value;
|
||||
}
|
||||
return _t('Max: ') + maxLabel;
|
||||
},
|
||||
},
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
padding: 4,
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
bottom: 5
|
||||
}
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
cutoutPercentage: 70,
|
||||
}
|
||||
};
|
||||
this.$canvas = $('<canvas/>');
|
||||
this.$el.empty();
|
||||
this.$el.append(this.$canvas);
|
||||
this.$el.attr('style', this.nodeOptions.style);
|
||||
this.$el.css({position: 'relative'});
|
||||
var context = this.$canvas[0].getContext('2d');
|
||||
this.chart = new Chart(context, config);
|
||||
|
||||
var humanValue = utils.human_number(gauge_value, 1);
|
||||
var $value = $('<span class="o_gauge_value">').text(humanValue);
|
||||
$value.css({'text-align': 'center', position: 'absolute', left: 0, right: 0, bottom: '6px', 'font-weight': 'bold'});
|
||||
this.$el.append($value);
|
||||
},
|
||||
});
|
||||
|
||||
field_registry.add("gauge", GaugeWidget);
|
||||
|
||||
return GaugeWidget;
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { getFixture, getNodesTextContent } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
QUnit.module("Fields", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
int_field: {
|
||||
string: "int_field",
|
||||
type: "integer",
|
||||
},
|
||||
another_int_field: {
|
||||
string: "another_int_field",
|
||||
type: "integer",
|
||||
},
|
||||
},
|
||||
records: [
|
||||
{ id: 1, int_field: 10, another_int_field: 45 },
|
||||
{ id: 2, int_field: 4, another_int_field: 10 },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("GaugeField");
|
||||
|
||||
QUnit.test("GaugeField in kanban view", async function (assert) {
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<field name="another_int_field"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="int_field" widget="gauge" options="{'max_field': 'another_int_field'}"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
|
||||
assert.containsN(target, ".o_kanban_record:not(.o_kanban_ghost)", 2);
|
||||
assert.containsN(target, ".o_field_widget[name=int_field] .oe_gauge canvas", 2);
|
||||
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_gauge_value")), [
|
||||
"10",
|
||||
"4",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
odoo.define('web_kanban_gauge.gauge_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var KanbanView = require('web.KanbanView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
|
||||
QUnit.module('fields', {}, function () {
|
||||
|
||||
QUnit.module('basic_fields', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
partner: {
|
||||
fields: {
|
||||
int_field: {string: "int_field", type: "integer", sortable: true},
|
||||
},
|
||||
records: [
|
||||
{id: 1, int_field: 10},
|
||||
{id: 2, int_field: 4},
|
||||
]
|
||||
},
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
|
||||
QUnit.module('gauge widget');
|
||||
|
||||
QUnit.test('basic rendering', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
var kanban = await createView({
|
||||
View: KanbanView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<kanban><templates><t t-name="kanban-box">' +
|
||||
'<div><field name="int_field" widget="gauge"/></div>' +
|
||||
'</t></templates></kanban>',
|
||||
});
|
||||
|
||||
assert.containsOnce(kanban, '.o_kanban_record:first .oe_gauge canvas',
|
||||
"should render the gauge widget");
|
||||
|
||||
kanban.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { getFixture, getNodesTextContent } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
QUnit.module("Fields", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
int_field: {
|
||||
string: "int_field",
|
||||
type: "integer",
|
||||
},
|
||||
},
|
||||
records: [
|
||||
{ id: 1, int_field: 10 },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("GaugeValue");
|
||||
|
||||
QUnit.test("GaugeValue in kanban view", async function (assert) {
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<kanban>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="int_field" widget="gauge" options="{'max_value': 120}"/>
|
||||
<field name="int_field" widget="gauge"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>`,
|
||||
});
|
||||
|
||||
assert.containsN(target, ".o_field_widget[name=int_field] .oe_gauge canvas", 2);
|
||||
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_gauge_value")), [
|
||||
"10",
|
||||
"10",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue