diga
5 years ago
No known key found for this signature in database
GPG Key ID: 47D77E95CE2E6B8A
14 changed files with 4013 additions and 0 deletions
-
1web_widget_heatmap/__init__.py
-
30web_widget_heatmap/__manifest__.py
-
1web_widget_heatmap/readme/CONTRIBUTORS.rst
-
3web_widget_heatmap/readme/CREDITS.rst
-
3web_widget_heatmap/readme/DESCRIPTION.rst
-
35web_widget_heatmap/readme/USAGE.rst
-
BINweb_widget_heatmap/static/description/icon.png
-
75web_widget_heatmap/static/description/index.html
-
145web_widget_heatmap/static/lib/css/cal-heatmap.css
-
3487web_widget_heatmap/static/lib/js/cal-heatmap.js
-
12web_widget_heatmap/static/src/css/heatmap_widget.css
-
194web_widget_heatmap/static/src/js/heatmap_widget.js
-
14web_widget_heatmap/static/src/xml/base.xml
-
13web_widget_heatmap/views/web_widget_heatmap_template.xml
@ -0,0 +1 @@ |
|||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). |
@ -0,0 +1,30 @@ |
|||||
|
# Copyright 2019 Dinar Gabbasov <https://it-projects.info/team/GabbasovDinar> |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). |
||||
|
{ |
||||
|
"name": "Web Widget HeatMap", |
||||
|
"summary": "Displaying your records in calendar HeatMap", |
||||
|
"version": "11.0.1.0.0", |
||||
|
"development_status": "Stable", |
||||
|
"category": "Extra Tools", |
||||
|
"website": "https://github.com/OCA/web/tree/11.0/web_widget_heatmap", |
||||
|
"author": "Dinar Gabbasov, Odoo Community Association (OCA)", |
||||
|
"maintainers": ["GabbasovDinar"], |
||||
|
"license": "AGPL-3", |
||||
|
"application": False, |
||||
|
"installable": True, |
||||
|
"preloadable": True, |
||||
|
"external_dependencies": { |
||||
|
"python": [], |
||||
|
"bin": [], |
||||
|
}, |
||||
|
"depends": [ |
||||
|
"web", |
||||
|
], |
||||
|
"data": [ |
||||
|
"views/web_widget_heatmap_template.xml", |
||||
|
], |
||||
|
"demo": [], |
||||
|
"qweb": [ |
||||
|
"static/src/xml/base.xml" |
||||
|
] |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
* Dinar Gabbasov <gabbasov@it-projects.info> |
@ -0,0 +1,3 @@ |
|||||
|
The development of this module has been financially supported by: |
||||
|
|
||||
|
* Georg A. G. Notter <georg.notter@agenterp.com> |
@ -0,0 +1,3 @@ |
|||||
|
This module aims to add a heatmap calendar to Odoo. |
||||
|
|
||||
|
It's a `cal-heatmap <https://cal-heatmap.com/>`_ lib integration. |
@ -0,0 +1,35 @@ |
|||||
|
To use this module, you need to declare a One2many or Many2many field:: |
||||
|
|
||||
|
sale_order_ids = fields.One2many( |
||||
|
"sale.order", |
||||
|
"partner_id", |
||||
|
string="Sales Order", |
||||
|
) |
||||
|
|
||||
|
In the view declaration, put widget="heatmap" attribute in the field tag:: |
||||
|
|
||||
|
... |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="View name"> |
||||
|
... |
||||
|
<field name="sale_order_ids" widget="heatmap"/> |
||||
|
... |
||||
|
</form> |
||||
|
</field> |
||||
|
... |
||||
|
|
||||
|
Widget Options:: |
||||
|
|
||||
|
cellSize - Size of each subDomain cell, in pixel. |
||||
|
... |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="View name"> |
||||
|
... |
||||
|
<field name="sale_order_ids" widget="heatmap" options="{'cellSize': 15}"/> |
||||
|
... |
||||
|
</form> |
||||
|
</field> |
||||
|
... |
||||
|
|
||||
|
|
||||
|
* All widget options you can find in the official `documentation <https://cal-heatmap.com/#options>`_ of the cal-heatmap lib. |
After Width: 100 | Height: 100 | Size: 3.0 KiB |
@ -0,0 +1,75 @@ |
|||||
|
<section class="oe_container"> |
||||
|
<div class="oe_row oe_spaced"> |
||||
|
<div class="oe_span12"> |
||||
|
<h2 class="oe_slogan">Web Widget HeatMap</h2> |
||||
|
<p>This module aims to add a heatmap calendar to Odoo.</p> |
||||
|
<p>It's a <a class="reference external" href="https://cal-heatmap.com/">cal-heatmap</a> lib integration.</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section class="oe_container oe_dark"> |
||||
|
<div class="oe_row oe_spaced"> |
||||
|
<div class="oe_span12"> |
||||
|
<h2 class="oe_slogan">Installation</h2> |
||||
|
</div> |
||||
|
<div class="oe_span12"> |
||||
|
<p class="oe_mt32">Install this module in a usual way</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section class="oe_container oe_dark"> |
||||
|
<div class="oe_row oe_spaced"> |
||||
|
<div class="oe_span12"> |
||||
|
<h2 class="oe_slogan">Usage</h2> |
||||
|
</div> |
||||
|
<div class="oe_span12"> |
||||
|
<p class="oe_mt32">You need to declare a One2many or Many2many field: |
||||
|
<pre class="literal-block"> |
||||
|
sale_order_ids = fields.One2many("sale.order", "partner_id", "Sales Order") |
||||
|
</pre> |
||||
|
<p>In the view declaration, put widget="heatmap" attribute in the field tag:</p> |
||||
|
<pre class="literal-block"> |
||||
|
... |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="View name"> |
||||
|
... |
||||
|
<field name="sale_order_ids" widget="heatmap"/> |
||||
|
... |
||||
|
</form> |
||||
|
</field> |
||||
|
... |
||||
|
</pre> |
||||
|
</p> |
||||
|
<p class="oe_mt32">For further information, please visit: |
||||
|
<ul> |
||||
|
<li><a href="https://www.odoo.com/forum/help-1">https://www.odoo.com/forum/help-1</a></li> |
||||
|
</ul> |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section class="oe_container oe_dark"> |
||||
|
<div class="oe_row"> |
||||
|
<div class="oe_span12"> |
||||
|
<h2 class="oe_slogan">Credits</h2> |
||||
|
</div> |
||||
|
<div class="oe_span12"> |
||||
|
<h3>Contributors</h3> |
||||
|
<ul> |
||||
|
<li>Dinar Gabbasov <<a href="mailto:gabbasov@it-projects.info">gabbasov@it-projects.info</a>></li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div class="oe_span12"> |
||||
|
<h3>Maintainer</h3> |
||||
|
<p> |
||||
|
This module is maintained by the OCA.<br/> |
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.<br/> |
||||
|
To contribute to this module, please visit <a href="http://odoo-community.org">http://odoo-community.org</a>.<br/> |
||||
|
<a href="http://odoo-community.org"><img class="oe_picture oe_centered" src="http://odoo-community.org/logo.png"></a> |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
@ -0,0 +1,145 @@ |
|||||
|
/* Cal-HeatMap CSS */ |
||||
|
|
||||
|
.cal-heatmap-container { |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .graph |
||||
|
{ |
||||
|
font-family: "Lucida Grande", Lucida, Verdana, sans-serif; |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .graph-label |
||||
|
{ |
||||
|
fill: #999; |
||||
|
font-size: 10px |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .graph, .cal-heatmap-container .graph-legend rect { |
||||
|
shape-rendering: crispedges |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .graph-rect |
||||
|
{ |
||||
|
fill: #ededed |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .graph-subdomain-group rect:hover |
||||
|
{ |
||||
|
stroke: #000; |
||||
|
stroke-width: 1px |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .subdomain-text { |
||||
|
font-size: 8px; |
||||
|
fill: #999; |
||||
|
pointer-events: none |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .hover_cursor:hover { |
||||
|
cursor: pointer |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .qi { |
||||
|
background-color: #999; |
||||
|
fill: #999 |
||||
|
} |
||||
|
|
||||
|
/* |
||||
|
Remove comment to apply this style to date with value equal to 0 |
||||
|
.q0 |
||||
|
{ |
||||
|
background-color: #fff; |
||||
|
fill: #fff; |
||||
|
stroke: #ededed |
||||
|
} |
||||
|
*/ |
||||
|
|
||||
|
.cal-heatmap-container .q1 |
||||
|
{ |
||||
|
background-color: #dae289; |
||||
|
fill: #dae289 |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .q2 |
||||
|
{ |
||||
|
background-color: #cedb9c; |
||||
|
fill: #9cc069 |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .q3 |
||||
|
{ |
||||
|
background-color: #b5cf6b; |
||||
|
fill: #669d45 |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .q4 |
||||
|
{ |
||||
|
background-color: #637939; |
||||
|
fill: #637939 |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .q5 |
||||
|
{ |
||||
|
background-color: #3b6427; |
||||
|
fill: #3b6427 |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container rect.highlight |
||||
|
{ |
||||
|
stroke:#444; |
||||
|
stroke-width:1 |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container text.highlight |
||||
|
{ |
||||
|
fill: #444 |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container rect.highlight-now |
||||
|
{ |
||||
|
stroke: red |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container text.highlight-now |
||||
|
{ |
||||
|
fill: red; |
||||
|
font-weight: 800 |
||||
|
} |
||||
|
|
||||
|
.cal-heatmap-container .domain-background { |
||||
|
fill: none; |
||||
|
shape-rendering: crispedges |
||||
|
} |
||||
|
|
||||
|
.ch-tooltip { |
||||
|
padding: 10px; |
||||
|
background: #222; |
||||
|
color: #bbb; |
||||
|
font-size: 12px; |
||||
|
line-height: 1.4; |
||||
|
width: 140px; |
||||
|
position: absolute; |
||||
|
z-index: 99999; |
||||
|
text-align: center; |
||||
|
border-radius: 2px; |
||||
|
box-shadow: 2px 2px 2px rgba(0,0,0,0.2); |
||||
|
display: none; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.ch-tooltip::after{ |
||||
|
position: absolute; |
||||
|
width: 0; |
||||
|
height: 0; |
||||
|
border-color: transparent; |
||||
|
border-style: solid; |
||||
|
content: ""; |
||||
|
padding: 0; |
||||
|
display: block; |
||||
|
bottom: -6px; |
||||
|
left: 50%; |
||||
|
margin-left: -6px; |
||||
|
border-width: 6px 6px 0; |
||||
|
border-top-color: #222; |
||||
|
} |
3487
web_widget_heatmap/static/lib/js/cal-heatmap.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,12 @@ |
|||||
|
/* Copyright 2019 Dinar Gabbasov <https://it-projects.info/team/GabbasovDinar> |
||||
|
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ |
||||
|
.heatmap_widget { |
||||
|
width: 100%; |
||||
|
padding: 10px 0px; |
||||
|
} |
||||
|
.o_field_heatmap { |
||||
|
width: 100%; |
||||
|
} |
||||
|
.o_field_heatmap .cal-heatmap-container { |
||||
|
margin: 0 auto; |
||||
|
} |
@ -0,0 +1,194 @@ |
|||||
|
/* |
||||
|
Copyright 2019 Dinar Gabbasov <https://it-projects.info/team/GabbasovDinar>
|
||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
*/ |
||||
|
|
||||
|
odoo.define('web_widget_heatmap.widget', function (require) { |
||||
|
"use strict"; |
||||
|
|
||||
|
var AbstractField = require('web.AbstractField'); |
||||
|
var registry = require('web.field_registry'); |
||||
|
|
||||
|
|
||||
|
var HeatMapWidget = AbstractField.extend({ |
||||
|
|
||||
|
fieldsToFetch: { |
||||
|
display_name: {type: 'char'}, |
||||
|
create_date: {type: 'datetime'}, |
||||
|
}, |
||||
|
|
||||
|
template: 'HeatMapWidget', |
||||
|
|
||||
|
supportedFieldTypes: ['one2many', 'many2many'], |
||||
|
|
||||
|
description: "", |
||||
|
|
||||
|
cssLibs: [ |
||||
|
'/web/static/lib/nvd3/nv.d3.css', |
||||
|
'/web_widget_heatmap/static/lib/css/cal-heatmap.css' |
||||
|
], |
||||
|
|
||||
|
jsLibs: [ |
||||
|
'/web/static/lib/nvd3/d3.v3.js', |
||||
|
'/web/static/lib/nvd3/nv.d3.js', |
||||
|
'/web/static/src/js/libs/nvd3.js', |
||||
|
'/web_widget_heatmap/static/lib/js/cal-heatmap.js' |
||||
|
], |
||||
|
|
||||
|
/** |
||||
|
* Calculate and get the maximum date (up to which date the grid will be built). |
||||
|
* Display of control buttons depends on the maximum date. |
||||
|
* @param {Object} options |
||||
|
* @returns {Date} max_date |
||||
|
*/ |
||||
|
get_max_date: function(options) { |
||||
|
var max_date = new Date(options.start_date); |
||||
|
var range = this.nodeOptions ? this.nodeOptions.range : options.range; |
||||
|
var domain = this.nodeOptions ? this.nodeOptions.domain : options.domain; |
||||
|
if (domain === 'hour') { |
||||
|
max_date.setHours(max_date.getHours() + range); |
||||
|
} else if (domain === 'day') { |
||||
|
max_date.setDate(max_date.getDate() + range); |
||||
|
} else if (domain === 'week') { |
||||
|
max_date.setDate(max_date.getDate() + (range * 7)); |
||||
|
} else if (domain === 'month') { |
||||
|
max_date.setMonth(max_date.getMonth() + range); |
||||
|
} else if (domain === 'year') { |
||||
|
max_date.setFullYear(max_date.getFullYear() + range); |
||||
|
} |
||||
|
if (max_date > options.end_date) { |
||||
|
return max_date; |
||||
|
} |
||||
|
return options.end_date; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Generate widget options by field data |
||||
|
* @param {Array} elements |
||||
|
* @returns {Object} widget options |
||||
|
*/ |
||||
|
generate_element_options: function(elements) { |
||||
|
var start_date = elements.length ? elements[0].create_date.toDate() : new Date(); |
||||
|
var timestamps = elements.map(function(el) { |
||||
|
return el.create_date.unix(); |
||||
|
}); |
||||
|
var domain = "day"; |
||||
|
var range = 16; |
||||
|
var end_date = elements.length ? elements[elements.length - 1].create_date.toDate() : null; |
||||
|
var max_date = this.get_max_date({ |
||||
|
start_date: start_date, |
||||
|
end_date: end_date, |
||||
|
domain: domain, |
||||
|
range: range |
||||
|
}); |
||||
|
var controls = false; |
||||
|
if (max_date < end_date) { |
||||
|
controls = true; |
||||
|
} |
||||
|
return { |
||||
|
start: start_date, |
||||
|
data: _.chain(timestamps).countBy().value(), |
||||
|
dataType: 'json', |
||||
|
minDate: start_date, |
||||
|
maxDate: max_date, |
||||
|
controls: controls, |
||||
|
range: range, |
||||
|
domain: domain, |
||||
|
subDomain: "hour", |
||||
|
domainGutter: 0, |
||||
|
highlight: "now", |
||||
|
onClick: this.onClickHeatMap.bind(this), |
||||
|
label: { |
||||
|
position: "top" |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Render the view |
||||
|
* @private |
||||
|
*/ |
||||
|
_render: function () { |
||||
|
var self = this; |
||||
|
var elements = this.value ? _.pluck(this.value.data, 'data') : []; |
||||
|
var options = this.generate_element_options(elements); |
||||
|
this.controls = options.controls; |
||||
|
this.renderElement(); |
||||
|
var nodeOptions = this.nodeOptions || {}; |
||||
|
this.heatmap_options = _.extend(options, {itemSelector: this.$el.find('.o_field_heatmap')[0]}); |
||||
|
_.each(nodeOptions, function(value, key) { |
||||
|
self.heatmap_options[key] = value; |
||||
|
}); |
||||
|
this.heatmap = new CalHeatMap(); |
||||
|
this.heatmap.init(this.heatmap_options); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Renders the element and add new events. |
||||
|
*/ |
||||
|
renderElement: function() { |
||||
|
this._super(); |
||||
|
this.$el.find('.next').click(this.nextHeatMap.bind(this)); |
||||
|
this.$el.find('.previous').click(this.previousHeatMap.bind(this)); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Show next heatmap grid |
||||
|
*/ |
||||
|
nextHeatMap: function() { |
||||
|
this.heatmap.next(); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Show previous heatmap grid |
||||
|
*/ |
||||
|
previousHeatMap: function() { |
||||
|
this.heatmap.previous(); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Find the records that match the selected date and show in the tree view |
||||
|
* @param {Date} date |
||||
|
* @returns {jQuery.Deferred} Action loaded |
||||
|
*/ |
||||
|
onClickHeatMap: function(date) { |
||||
|
date = moment(date); |
||||
|
var options = this.heatmap_options; |
||||
|
var elements = this.value ? _.pluck(this.value.data, 'data') : []; |
||||
|
if (elements && elements.length) { |
||||
|
// TODO: make faster
|
||||
|
var current_elements_ids = elements.filter(function(el) { |
||||
|
var duration = moment.duration(date.diff(el.create_date)); |
||||
|
if (options.domain === 'hour') { |
||||
|
return duration.years() === 0 && duration.months() === 0 && duration.weeks() === 0 && duration.days() === 0 && duration.hours() === 0; |
||||
|
} else if (options.domain === 'day') { |
||||
|
return duration.years() === 0 && duration.months() === 0 && duration.weeks() === 0 && duration.days() === 0; |
||||
|
} else if (options.domain === 'week') { |
||||
|
return duration.years() === 0 && duration.months() === 0 && duration.weeks() === 0; |
||||
|
} else if (options.domain === 'month') { |
||||
|
return duration.years() === 0 && duration.months() === 0; |
||||
|
} else if (options.domain === 'year') { |
||||
|
return duration.years() === 0; |
||||
|
} |
||||
|
return false; |
||||
|
}).map(function(el) { |
||||
|
return el.id; |
||||
|
}); |
||||
|
var action = { |
||||
|
name: date.format('YYYY-MM-DD HH:mm'), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: this.value.model, |
||||
|
view_mode: 'list,form', |
||||
|
views: [[false, 'list'], [false, 'form']], |
||||
|
view_type: 'list', |
||||
|
domain: [['id', 'in', current_elements_ids]], |
||||
|
}; |
||||
|
return this.do_action(action); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
registry.add('heatmap', HeatMapWidget); |
||||
|
|
||||
|
return HeatMapWidget; |
||||
|
}); |
@ -0,0 +1,14 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!-- Copyright 2019 Dinar Gabbasov <https://it-projects.info/team/GabbasovDinar> |
||||
|
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> |
||||
|
<templates id="template" xml:space="preserve"> |
||||
|
<t t-name="HeatMapWidget"> |
||||
|
<div class="heatmap_widget"> |
||||
|
<span class="controls" t-if="widget.controls"> |
||||
|
<button class="btn btn-sm btn-default previous fa fa-chevron-left"></button> |
||||
|
<button class="btn btn-sm btn-default next fa fa-chevron-right"></button> |
||||
|
</span> |
||||
|
<span class="o_field_heatmap"></span> |
||||
|
</div> |
||||
|
</t> |
||||
|
</templates> |
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0"?> |
||||
|
<!-- Copyright 2019 Dinar Gabbasov <https://it-projects.info/team/GabbasovDinar> |
||||
|
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> |
||||
|
<odoo> |
||||
|
|
||||
|
<template id="assets_backend" inherit_id="web.assets_backend"> |
||||
|
<xpath expr="." position="inside"> |
||||
|
<script type="text/javascript" src="/web_widget_heatmap/static/src/js/heatmap_widget.js"></script> |
||||
|
<link rel="stylesheet" type="text/css" href="/web_widget_heatmap/static/src/css/heatmap_widget.css"/> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
|
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue