[ADD] Web Gantt Chart

This commit is contained in:
Rohit Prajapati authored and Parthiv Patel committed 2021-01-08 13:25:26 +00:00
1 parent 0088cf48d6
commit 7d98387f0d
17 files changed
+1029

No files matched your search

+1
View File
@@ -0,0 +1 @@
from . import models
+18
View File
@@ -0,0 +1,18 @@
{
'name': 'Project Gantt Chart Demo',
'category': 'Project',
'version': '2.0',
'summary': 'Project Gantt Chart Demo',
'depends': [
'web_gantt',
'project'
],
'data': [
'views/project_task_inherit.xml',
],
'demo': ['data/project_task_demo.xml'],
'qweb': [],
'installable': True,
'auto_install': False,
'application': False,
}
@@ -0,0 +1,119 @@
<flectra>
<data noupdate="1">
<record id="project.project_task_1" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">9</field>
</record>
<record id="project.project_task_2" model="project.task">
<field name="start_date" eval="(DateTime.today())"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">7</field>
</record>
<record id="project.project_task_3" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today())"/>
<field name="progress">5</field>
</record>
<record id="project.project_task_4" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=2))"/>
<field name="progress">5</field>
</record>
<record id="project.project_task_5" model="project.task">
<field name="start_date" eval="(DateTime.today())"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=2))"/>
<field name="progress">4</field>
</record>
<record id="project.project_task_6" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=3))"/>
<field name="progress">9</field>
</record>
<record id="project.project_task_7" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">8</field>
</record>
<record id="project.project_task_8" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=3))"/>
<field name="progress">10</field>
</record>
<record id="project.project_task_9" model="project.task">
<field name="start_date" eval="(DateTime.today())"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">0</field>
</record>
<record id="project.project_task_10" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">7</field>
</record>
<record id="project.project_task_11" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=3))"/>
<field name="progress">1</field>
</record>
<record id="project.project_task_12" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">2</field>
</record>
<record id="project.project_task_19" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=2))"/>
<field name="progress">3</field>
</record>
<record id="project.project_task_20" model="project.task">
<field name="start_date" eval="(DateTime.today())"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=3))"/>
<field name="progress">3</field>
</record>
<record id="project.project_task_21" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today())"/>
<field name="progress">4</field>
</record>
<record id="project.project_task_22" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=2))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">7</field>
</record>
<record id="project.project_task_24" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=1))"/>
<field name="progress">4</field>
</record>
<record id="project.project_task_25" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today() + relativedelta(days=2))"/>
<field name="progress">2</field>
</record>
<record id="project.project_task_26" model="project.task">
<field name="start_date" eval="(DateTime.today() - relativedelta(days=1))"/>
<field name="end_date" eval="(DateTime.today())"/>
<field name="progress">10</field>
</record>
</data>
</flectra>
+1
View File
@@ -0,0 +1 @@
from . import project_task
@@ -0,0 +1,9 @@
from flectra import fields, models
class ProjectTaskInherit(models.Model):
_inherit = 'project.task'
start_date = fields.Date(string='Start Date')
end_date = fields.Date(string='End Date')
progress = fields.Integer(string='Progress')
@@ -0,0 +1,66 @@
<flectra>
<record id="project_task_view_gantt_inherit" model="ir.ui.view">
<field name="name">project.task.form.view</field>
<field name="model">project.task</field>
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='partner_id']" position="after">
<field name="start_date"/>
<field name="end_date"/>
<field name="progress"/>
</xpath>
</field>
</record>
<record id="view_project_task_gantt" model="ir.ui.view">
<field name="name">project.task.gantt</field>
<field name="model">project.task</field>
<field name="arch" type="xml">
<gantt string="Project Tasks" mode="day">
<field name="progress" type="progress" scale="10"/>
<field name="start_date" type="start"/>
<field name="end_date" type="end"/>
<!-- <field name="child_task_ids" type="child" start="sd" end="ed"/>-->
</gantt>
</field>
</record>
<record id="action_view_all_task_tree_action_inherit" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="project.view_task_tree2"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
<record id="action_view_all_task_kanban_action_inherit" model="ir.actions.act_window.view">
<field eval="2" name="sequence"/>
<field name="view_mode">kanban</field>
<field name="view_id" ref="project.view_task_kanban"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
<record id="action_view_all_task_form_action_inherit" model="ir.actions.act_window.view">
<field eval="3" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="project.view_task_form2"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
<record id="action_view_all_task_calendar_action_inherit" model="ir.actions.act_window.view">
<field eval="4" name="sequence"/>
<field name="view_mode">calendar</field>
<field name="view_id" ref="project.view_task_calendar"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
<record id="action_view_all_task_gantt_chart_inherit" model="ir.actions.act_window.view">
<field eval="5" name="sequence"/>
<field name="view_mode">gantt</field>
<field name="view_id" ref="project_gantt.view_project_task_gantt"/>
<field name="act_window_id" ref="project.action_view_all_task"/>
</record>
</flectra>
View File
Whitespace-only changes.
+18
View File
@@ -0,0 +1,18 @@
{
'name': 'Web Gantt Chart',
'category': 'Project',
'version': '2.0',
'summary': 'Gantt Chart',
'depends': [
'web'
],
'data': [
'views/assets.xml',
],
'qweb': [
'static/src/xml/*.xml'
],
'installable': True,
'auto_install': False,
'application': False,
}
+12
View File
@@ -0,0 +1,12 @@
.o_gantt_renderer{
height: 100%;
}
.gantt_grid_head_add, .gantt_add{
display: none;
}
.gantt_task_progress_drag{
display: none !important;
}
.gantt_scale_line > .gantt_scale_cell, .gantt_grid_scale > .gantt_grid_head_cell{
font-weight: bold !important;
}
@@ -0,0 +1 @@
.gridHoverStyle,.gridSelection,.timelineSelection{background-color:#fff3a1}.gantt_grid_scale .gantt_grid_head_cell{color:#a6a6a6;border-top:none!important;border-right:none!important}.gantt_grid_data .gantt_cell{border-right:none;color:#454545}.gantt_task_link .gantt_link_arrow_right{border-width:6px;margin-top:-3px}.gantt_task_link .gantt_link_arrow_left{border-width:6px;margin-left:-6px;margin-top:-3px}.gantt_task_link .gantt_link_arrow_down,.gantt_task_link .gantt_link_arrow_up{border-width:6px}.gantt_task_line .gantt_task_progress_drag{bottom:-4px;height:10px;margin-left:-8px;width:16px}.chartHeaderBg{background-color:#fff}.gantt_task .gantt_task_scale .gantt_scale_cell{color:#a6a6a6;border-right:1px solid #ebebeb}.gantt_row.gantt_project,.gantt_row.odd.gantt_project{background-color:#edffef}.gantt_task_row.gantt_project,.gantt_task_row.odd.gantt_project{background-color:#f5fff6}.gantt_task_line.gantt_project{background-color:#65c16f;border:1px solid #3c9445}.gantt_task_line.gantt_project .gantt_task_progress{background-color:#46ad51}.buttonBg{background:#fff}.gantt_cal_light .gantt_btn_set{margin:5px 10px}.gantt_btn_set.gantt_cancel_btn_set{background:#fff;color:#454545;border:1px solid #cecece}.gantt_btn_set.gantt_save_btn_set{background:#3db9d3;text-shadow:0 -1px 0 #248a9f;color:#fff}.gantt_btn_set.gantt_delete_btn_set{text-shadow:0 -1px 0 #6f6f6f;background:#ec8e00;text-shadow:0 -1px 0 #a60;color:#fff}.gantt_cal_light_wide{padding-left:0!important;padding-right:0!important}.gantt_cal_light_wide .gantt_cal_larea{border-left:none!important;border-right:none!important}.gantt_popup_button.gantt_ok_button{background:#3db9d3;text-shadow:0 -1px 0 #248a9f;color:#fff;font-weight:700;border-width:0}.gantt_popup_button.gantt_cancel_button{font-weight:700;color:#454544}.gantt_popup_title{background-color:#fff}.gantt_popup_shadow{box-shadow:3px 3px 3px rgba(0,0,0,.07)}.gantt_qi_big_icon.icon_edit{color:#454545;background:#fff}.gantt_qi_big_icon.icon_delete{text-shadow:0 -1px 0 #a60;background:#ec8e00;color:#fff;border-width:0}.gantt_tooltip{box-shadow:3px 3px 3px rgba(0,0,0,.07);border-left:1px solid rgba(0,0,0,.07);border-top:1px solid rgba(0,0,0,.07);font-size:8pt;color:#454545}.gantt_container,.gantt_tooltip{background-color:#fff;font-family:Arial}.gantt_container{font-size:13px;border:1px solid #cecece;position:relative;white-space:nowrap;overflow-x:hidden;overflow-y:hidden}.gantt_touch_active{overscroll-behavior:none}.gantt_task_scroll{overflow-x:scroll}.gantt_grid,.gantt_task{position:relative;overflow-x:hidden;overflow-y:hidden;display:inline-block;vertical-align:top}.gantt_grid_scale,.gantt_task_scale{color:#6b6b6b;font-size:12px;border-bottom:1px solid #cecece;box-sizing:border-box}.gantt_grid_scale,.gantt_task_scale,.gantt_task_vscroll{background-color:#fff}.gantt_scale_line{box-sizing:border-box;-moz-box-sizing:border-box;border-top:1px solid #cecece}.gantt_scale_line:first-child{border-top:none}.gantt_grid_head_cell{display:inline-block;vertical-align:top;border-right:1px solid #cecece;text-align:center;position:relative;cursor:default;height:100%;box-sizing:border-box;-moz-box-sizing:border-box;line-height:33px;-moz-user-select:-moz-none;-webkit-user-select:none;user-select:none;overflow:hidden}.gantt_scale_line{clear:both}.gantt_grid_data{width:100%;overflow:hidden;position:relative}.gantt_row{position:relative;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_add,.gantt_grid_head_add{width:100%;height:100%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTQ3MjMyMENDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTQ3MjMyMERDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1NDcyMzIwQUM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1NDcyMzIwQkM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PshZT8UAAABbSURBVHjaYrTdeZmBEsCER+4wEP+H4sPkGGCDg020ARR7gb4GIAcYDKMDdPnDyAbYkGG5DVW9cIQMvUdBBAuUY4vDz8iAcZinA2zgCHqAYQMseAywJcYFAAEGAM+UFGuohFczAAAAAElFTkSuQmCC);background-position:50%;background-repeat:no-repeat;cursor:pointer;position:relative;-moz-opacity:.3;opacity:.3Line truncated
@@ -0,0 +1,14 @@
/*
@license
dhtmlxGantt v.7.0.11 Standard
This version of dhtmlxGantt is distributed under GPL 2.0 license and can be legally used in GPL projects.
To use dhtmlxGantt in non-GPL projects (and get Pro version of the product), please obtain Commercial/Enterprise or Ultimate license on our site https://dhtmlx.com/docs/products/dhtmlxGantt/#licensing or contact us at sales@dhtmlx.com
(c) XB Software Ltd.
*/
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("dhtmlxgantt",[],e):"object"==typeof exports?exports.dhtmlxgantt=e():t.dhtmlxgantt=e()}(window,function(){return function(t){var e={};function n(i){if(e[i])return e[i].exports;var r=e[i]={i:i,l:!1,exports:{}};return t[i].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)n.d(i,r,function(e){return t[e]}.bind(null,r));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="/codebase/",n(n.s=245)}([function(t,e,n){var i,r=n(3);t.exports={copy:function t(e){var n,i;if(e&&"object"==typeof e)switch(!0){case r.isDate(e):i=new Date(e);break;case r.isArray(e):for(i=new Array(e.length),n=0;n<e.length;n++)i[n]=t(e[n]);break;case r.isStringObject(e):i=new String(e);break;case r.isNumberObject(e):i=new Number(e);break;case r.isBooleanObject(e):i=new Boolean(e);break;default:for(n in i=function(t){return t.constructor.toString()!=={}.constructor.toString()}(e)?Object.create(e):{},e)Object.prototype.hasOwnProperty.apply(e,[n])&&(i[n]=t(e[n]))}return i||e},defined:function(t){return void 0!==t},mixin:function(t,e,n){for(var i in e)(void 0===t[i]||n)&&(t[i]=e[i]);return t},uid:function(){return i||(i=(new Date).valueOf()),++i},bind:function(t,e){return t.bind?t.bind(e):function(){return t.apply(e,arguments)}},event:function(t,e,n,i){t.addEventListener?t.addEventListener(e,n,void 0!==i&&i):t.attachEvent&&t.attachEvent("on"+e,n)},eventRemove:function(t,e,n,i){t.removeEventListener?t.removeEventListener(e,n,void 0!==i&&i):t.detachEvent&&t.detachEvent("on"+e,n)}}},function(t,e){function n(t){var e=0,n=0,i=0,r=0;if(t.getBoundingClientRect){var a=t.getBoundingClientRect(),o=document.body,s=document.documentElement||document.body.parentNode||document.body,l=window.pageYOffset||s.scrollTop||o.scrollTop,c=window.pageXOffset||s.scrollLeft||o.scrollLeft,u=s.clientTop||o.clientTop||0,d=s.clientLeft||o.clientLeft||0;e=a.top+l-u,n=a.left+c-d,i=document.body.offsetWidth-a.right,r=document.body.offsetHeight-a.bottom}else{for(;t;)e+=parseInt(t.offsetTop,10),n+=parseInt(t.offsetLeft,10),t=t.offsetParent;i=document.body.offsetWidth-t.offsetWidth-n,r=document.body.offsetHeight-t.offsetHeight-e}return{y:Math.round(e),x:Math.round(n),width:t.offsetWidth,height:t.offsetHeight,right:Math.round(i),bottom:Math.round(r)}}function i(t){var e=!1,n=!1;if(window.getComputedStyle){var i=window.getComputedStyle(t,null);e=i.display,n=i.visibility}else t.currentStyle&&(e=t.currentStyle.display,n=t.currentStyle.visibility);return"none"!=e&&"hidden"!=n}function r(t){return!isNaN(t.getAttribute("tabindex"))&&1*t.getAttribute("tabindex")>=0}function a(t){return!{a:!0,area:!0}[t.nodeName.loLowerCase()]||!!t.getAttribute("href")}function o(t){return!{input:!0,select:!0,textarea:!0,button:!0,object:!0}[t.nodeName.toLowerCase()]||!t.hasAttribute("disabled")}function s(t){if(!t)return"";var e=t.className||"";return e.baseVal&&(e=e.baseVal),e.indexOf||(e=""),u(e)}var l;function c(t){return t.tagName?t:(t=t||window.event).target||t.srcElement}function u(t){return(String.prototype.trim||function(){return this.replace(/^\s+|\s+$/g,"")}).apply(t)}t.exports={getNodePosition:n,getFocusableNodes:function(t){for(var e=t.querySelectorAll(["a[href]","area[href]","input","select","textarea","button","iframe","object","embed","[tabindex]","[contenteditable]"].join(", ")),n=Array.prototype.slice.call(e,0),s=0;s<n.length;s++){var l=n[s];(r(l)||o(l)||a(l))&&i(l)||(n.splice(s,1),s--)}return n},getScrollSize:function(){var t=document.createElement("div");t.style.cssText="visibility:hidden;position:absolute;left:-1000px;width:100px;padding:0px;margin:0px;height:110px;min-height:100px;overflow-y:scroll;",document.body.appendChild(t);var e=t.offsetWidth-t.clientWidth;return document.body.removeChild(t),e},getClassName:s,addClassName:function(t,e){e&&-1===t.className.indexOf(e)&&(t.className+=" "+e)},removeClassName:function(t,e){e=e.split(" ");for(var n=0;n<e.length;n++){var i=new RegExp("\\s?\\b"+e[n]+"\\b(?![-_.])","");t.className=t.className.replace(i,"")}},insertNode:function(t,e){l||(l=document.createElement("div")),l.innerHTML=e;var n=l.firstChild;return t.appendChild(n),n},removeNode:function(t){t&&t.parentNode&&t.parentNode.removeChild(t)},getChildNodes:function(t,e){for(var n=t.childNodes,i=n.length,r=[],a=0;a<i;a++){vaLine truncated
//# sourceMappingURL=dhtmlxgantt.js.map
@@ -0,0 +1,147 @@
flectra.define('web_gantt.GanttController', function (require) {
"use strict";
const AbstractController = require('web.AbstractController');
const { ComponentWrapper } = require('web.OwlCompatibility');
const DropdownMenu = require('web.DropdownMenu');
const { DEFAULT_INTERVAL, INTERVAL_OPTIONS } = require('web.searchUtils');
const { qweb } = require('web.core');
var view_dialogs = require('web.view_dialogs');
class CarretDropdownMenu extends DropdownMenu {
/**
* @override
*/
get displayCaret() {
return true;
}
}
var GanttChartController = AbstractController.extend({
events: _.extend({}, AbstractController.prototype.events, {
'click .gantt_bar_task': '_onTaskClicked',
}),
custom_events: _.extend({}, AbstractController.prototype.custom_events, {
item_selected: '_onItemSelected',
open_view: '_onOpenView',
}),
/**
* @override
* @param {Widget} parent
* @param {GraphModel} model
* @param {GraphRenderer} renderer
* @param {Object} params
* @param {string[]} params.measures
* @param {boolean} params.isEmbedded
* @param {string[]} params.groupableFields,
*/
init: function (parent, model, renderer, params) {
this._super.apply(this, arguments);
this.measures = params.measures;
this.child = params.child;
this.childExist = params.childExist;
// this parameter condition the appearance of a 'Group By'
// button in the control panel owned by the graph view.
this.isEmbedded = params.isEmbedded;
this.withButtons = params.withButtons;
// views to use in the action triggered when the graph is clicked
this.views = params.views;
this.title = params.title;
// this parameter determines what is the list of fields
// that may be used within the groupby menu available when
// the view is embedded
this.groupableFields = params.groupableFields;
this.buttonDropdownPromises = [];
},
/**
* @override
*/
start: function () {
this.$el.addClass('o_gantt_controller');
return this._super.apply(this, arguments);
},
getOwnedQueryParams: function () {
var state = this.model.get();
return {
context: {
gantt_mode: state.mode,
}
};
},
destroy: function () {
this._super.apply(this, arguments);
},
_onTaskClicked:function(ev){
var $target = $(ev.currentTarget);
if($target.attr('task_id').startsWith('child_')){
let id = $target.attr('task_id').substring("child_".length);
return new view_dialogs.FormViewDialog(this, {
title: $target.find('.gantt_task_content').text(),
res_model: this.child.model,
view_id: false,
res_id: parseInt(id),
on_saved: this.reload.bind(this, {}),
}).open();
}else{
return new view_dialogs.FormViewDialog(this, {
title: $target.find('.gantt_task_content').text(),
res_model: this.modelName,
view_id: false,
res_id: parseInt($target.attr('task_id')),
on_saved: this.reload.bind(this, {}),
}).open();
}
},
_onButtonClick:function(ev){
var $target = $(ev.target);
if($target.hasClass('o_gantt_button')){
this.update({ mode: $target.data('mode') });
}else{
return new view_dialogs.FormViewDialog(this, {
res_model: this.modelName,
view_id: false,
res_id: false,
on_saved: this.reload.bind(this, {}),
}).open();
}
},
updateButtons: function () {
if (!this.$buttons) {
return;
}
var state = this.model.get();
this.$buttons.find('.o_gantt_button').removeClass('active');
this.$buttons
.find('.o_gantt_button[data-mode="' + state.mode + '"]')
.addClass('active');
},
formatDate: function(date){
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
return [day, month, year].join('-');
},
reload: async function () {
const promises = [this._super(...arguments)];
return Promise.all(promises);
},
renderButtons: function ($node) {
this.$buttons = $(qweb.render('GanttView.buttons'));
this.$buttons.click(ev => this._onButtonClick(ev));
},
});
return GanttChartController;
});
@@ -0,0 +1,232 @@
flectra.define('web_gantt.GanttModel', function (require) {
"use strict";
var core = require('web.core');
const { DEFAULT_INTERVAL, rankInterval } = require('web.searchUtils');
var _t = core._t;
var AbstractModel = require('web.AbstractModel');
return AbstractModel.extend({
/**
* @override
* @param {Widget} parent
*/
init: function () {
this._super.apply(this, arguments);
this.chart = null;
this.GanttData = [];
},
__get: function () {
return Object.assign({ isSample: this.isSampleModel }, this.chart);
},
__load: function (params) {
var groupBys = params.context.graph_groupbys || params.groupBys;
this.initialGroupBys = groupBys;
this.fields = params.fields;
this.modelName = params.modelName;
this.chart = Object.assign({
context: params.context,
dataPoints: [],
domain: params.domain,
groupBy: params.groupedBy.length ? params.groupedBy : groupBys,
measure: params.context.graph_measure || params.measure,
mode: params.context.graph_mode || params.mode,
origins: [],
stacked: params.stacked,
timeRanges: params.timeRanges,
orderBy: params.orderBy,
startDate: params.startDate,
endDate: params.endDate,
child: params.child,
childExist: params.childExist,
progress: params.progress,
titleField: params.titleField,
});
this._computeDerivedParams();
return this._loadGraph();
},
__reload: function (handle, params) {
if ('context' in params) {
this.chart.context = params.context;
this.chart.groupBy = params.context.graph_groupbys || this.chart.groupBy;
this.chart.measure = params.context.graph_measure || this.chart.measure;
this.chart.mode = params.context.graph_mode || this.chart.mode;
}
if ('domain' in params) {
this.chart.domain = params.domain;
}
if ('groupBy' in params) {
this.chart.groupBy = params.groupBy.length ? params.groupBy : this.initialGroupBys;
}
if ('measure' in params) {
this.chart.measure = params.measure;
}
if ('timeRanges' in params) {
this.chart.timeRanges = params.timeRanges;
}
this._computeDerivedParams();
if ('mode' in params) {
this.chart.mode = params.mode;
return Promise.resolve();
}
if ('stacked' in params) {
this.chart.stacked = params.stacked;
return Promise.resolve();
}
if ('orderBy' in params) {
this.chart.orderBy = params.orderBy;
return Promise.resolve();
}
return this._loadGraph();
},
_computeDerivedParams: function () {
this.chart.processedGroupBy = this._processGroupBy(this.chart.groupBy);
const { range, rangeDescription, comparisonRange, comparisonRangeDescription, fieldName } = this.chart.timeRanges;
if (range) {
this.chart.domains = [
this.chart.domain.concat(range),
this.chart.domain.concat(comparisonRange),
];
this.chart.origins = [rangeDescription, comparisonRangeDescription];
const groupBys = this.chart.processedGroupBy.map(function (gb) {
return gb.split(":")[0];
});
this.chart.comparisonFieldIndex = groupBys.indexOf(fieldName);
} else {
this.chart.domains = [this.chart.domain];
this.chart.origins = [""];
this.chart.comparisonFieldIndex = -1;
}
},
_loadGraph: function () {
var self = this;
this.chart.dataPoints = [];
this.chart.GanttData = [];
var groupBy = this.chart.processedGroupBy;
var fields = _.map(groupBy, function (groupBy) {
return groupBy.split(':')[0];
});
if (this.chart.measure !== '__count__') {
if (this.fields[this.chart.measure].type === 'many2one') {
fields = fields.concat(this.chart.measure + ":count_distinct");
}
else {
}
}
fields = fields.concat(this.chart.startDate);
fields = fields.concat(this.chart.endDate);
fields = fields.concat(this.chart.titleField);
fields = fields.concat(this.chart.progress);
if(this.chart.childExist){
fields = fields.concat(this.chart.child.name);
}
var context = _.extend({fill_temporal: true}, this.chart.context);
var proms = [];
proms.push(this._rpc({
route: '/web/dataset/search_read',
model: this.modelName,
fields: fields,
context: context,
domain: this.chart.domain,
}).then(self._processGanttData.bind(self)));
return Promise.all(proms);
},
_processGanttData: async function(rawData) {
var self = this;
var childFields = ['display_name'];
var proms = [];
if(this.chart.childExist){
childFields = childFields.concat(this.chart.child.startDate);
childFields = childFields.concat(this.chart.child.endDate);
}
rawData.records.forEach(function (dataPt){
if(dataPt[self.chart.startDate] != false && dataPt[self.chart.endDate] != false){
var start_date = self.formatDate(dataPt[self.chart.startDate]);
var end_date = self.formatDate(dataPt[self.chart.endDate]);
var type = '';
if(self.chart.childExist){
type = dataPt[self.chart.child.name].length != 0 ? "project" : ''
}
self.chart.GanttData.push({
resId: dataPt.id,
startDate: start_date,
type: type,
endDate: end_date,
titleField: dataPt[self.chart.titleField],
progress: dataPt[self.chart.progress],
});
}
if(self.chart.childExist && dataPt[self.chart.child.name].length != 0){
proms.push(self._rpc({
route: '/web/dataset/search_read',
model: self.chart.child.model,
fields: childFields,
domain: [['id', 'in', dataPt[self.chart.child.name]]],
}).then(self._processGanttChildData.bind(self,dataPt)));
}
});
return Promise.all(proms);
},
_processGanttChildData: async function(data,rawData){
var self = this;
rawData.records.forEach(function (dataPt){
var start_date = self.formatDate(dataPt[self.chart.child.startDate]);
var end_date = self.formatDate(dataPt[self.chart.child.endDate]);
self.chart.GanttData.push({
resId: 'child_' + dataPt.id,
startDate: start_date,
endDate: end_date,
titleField: dataPt['display_name'],
parent: data.id,
});
});
},
formatDate: function(date){
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
return [day, month, year].join('-');
},
_processGroupBy: function(groupBy) {
const groupBysMap = new Map();
for (const gb of groupBy) {
let [fieldName, interval] = gb.split(':');
const field = this.fields[fieldName];
if (['date', 'datetime'].includes(field.type)) {
interval = interval || DEFAULT_INTERVAL;
}
if (groupBysMap.has(fieldName)) {
const registeredInterval = groupBysMap.get(fieldName);
if (rankInterval(registeredInterval) < rankInterval(interval)) {
groupBysMap.set(fieldName, interval);
}
} else {
groupBysMap.set(fieldName, interval);
}
}
return [...groupBysMap].map(([fieldName, interval]) => {
if (interval) {
return `${fieldName}:${interval}`;
}
return fieldName;
});
},
});
});
@@ -0,0 +1,167 @@
flectra.define('web_gantt.GanttRenderer', function (require) {
"use strict";
var AbstractRenderer = require('web.AbstractRenderer');
var config = require('web.config');
var core = require('web.core');
var dataComparisonUtils = require('web.dataComparisonUtils');
var fieldUtils = require('web.field_utils');
var _t = core._t;
var DateClasses = dataComparisonUtils.DateClasses;
var qweb = core.qweb;
return AbstractRenderer.extend({
className: "o_gantt_renderer",
id: "o_gantt_renderer",
sampleDataTargets: ['.o_graph_canvas_container'],
/**
* @override
* @param {Widget} parent
* @param {Object} state
* @param {Object} params
* @param {boolean} [params.isEmbedded]
* @param {Object} [params.fields]
* @param {string} [params.title]
*/
init: function (parent, state, params) {
this._super.apply(this, arguments);
this.isEmbedded = params.isEmbedded || false;
this.title = params.title || '';
this.fields = params.fields || {};
this.modelName = params.modelName;
this.startDate = params.startDate;
this.endDate = params.endDate;
this.titleField = params.titleField;
this.progress_scale = params.progress_scale;
this.child = params.child;
this.childExist = params.childExist;
this.disableLinking = params.disableLinking;
this.chart = null;
this.ganttId = _.uniqueId('gantt');
this.$legendTooltip = null;
this.$tooltip = null;
this.rtlEnable = _t.database.parameters.direction == "rtl" ? true : false;
},
on_attach_callback: function () {
this._super.apply(this, arguments);
this.isInDOM = true;
this._render();
},
/**
* @override
*/
on_detach_callback: function () {
this._super.apply(this, arguments);
this.isInDOM = false;
},
_prepareGanttData: function(){
var self = this;
this.GanttChartData = {"data": [],"links": []};
this.state.GanttData.forEach(function (data){
var progress_bar = data.progress;
if(self.progress_scale){
progress_bar = (data.progress) / self.progress_scale;
}
self.GanttChartData.data.push({
id: data.resId,
start_date: data.startDate,
end_date: data.endDate,
text: data.titleField,
type: data.type || '',
progress: progress_bar ? progress_bar : 0,
parent: data.parent ? data.parent : false,
});
});
},
async _renderView() {
var self = this;
this._prepareGanttData();
if (this.chart) {
this.chart.clearAll();
this.chart.parse(this.GanttChartData);
this._changeState();
}else{
if (this.isInDOM) {
this._renderTitle();
gantt.plugins({
tooltip: true
});
gantt.config.show_links = false;
if(this.rtlEnable){
gantt.config.rtl = true;
}
gantt.config.date_grid = _t.database.parameters.date_format;
gantt.config.auto_scheduling = true;
gantt.config.auto_scheduling_strict = true;
gantt.i18n.setLocale(moment.locale());
gantt.init('o_gantt_renderer');
gantt.parse(this.GanttChartData);
this.chart = gantt;
this.chart.attachEvent("onBeforeLightbox", function(){
return false;
});
this.chart.attachEvent("onAfterTaskDrag", function(id, mode, e){
var currentTask = this.getTask(id);
self._updateData(currentTask);
});
this._changeState();
}
}
},
_renderTitle: function () {
if (this.title) {
this.$el.prepend($('<label/>', {
text: this.title,
}));
}
},
_changeState: function(){
if(this.state.mode == 'month'){
this.chart.config.scale_unit = "month";
this.chart.config.date_scale = "%F, %Y";
}else if(this.state.mode == 'week'){
this.chart.config.scale_unit = "week";
this.chart.config.date_scale = "Week #%W";
}else{
this.chart.config.scale_unit = "day";
this.chart.config.date_scale = "%d %M";
}
this.chart.render();
},
_updateData: function(data){
var updatedData = {};
if(data.id.toString().startsWith('child_')){
updatedData[this.child.startDate] = this.formatDate(data.start_date);
updatedData[this.child.endDate] = this.formatDate(data.end_date);
return this._rpc({
model: this.child.model,
method: 'write',
args: [parseInt(data.id.substring("child_".length)), updatedData],
});
}else{
updatedData[this.startDate] = this.formatDate(data.start_date);
updatedData[this.endDate] = this.formatDate(data.end_date);
return this._rpc({
model: this.modelName,
method: 'write',
args: [data.id, updatedData],
});
}
},
formatDate: function(date){
var d = new Date(date),
month = '' + (d.getMonth() + 1),
day = '' + d.getDate(),
year = d.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
return [year, month, day].join('-');
},
});
});
@@ -0,0 +1,171 @@
flectra.define('web_gantt.GanttView', function (require) {
"use strict";
var AbstractView = require('web.AbstractView');
var core = require('web.core');
var GraphModel = require('web.GraphModel');
var Controller = require('web_gantt.GanttController');
var GanttRenderer = require('web_gantt.GanttRenderer');
var GanttModel = require('web_gantt.GanttModel');
var view_registry = require('web.view_registry');
var _t = core._t;
var _lt = core._lt;
var searchUtils = require('web.searchUtils');
var GanttView = AbstractView.extend({
display_name: _lt('Gantt'),
icon: 'fa-tasks',
config: _.extend({}, AbstractView.prototype.config, {
Model: GanttModel,
Controller: Controller,
Renderer: GanttRenderer,
}),
viewType: 'gantt',
searchMenuTypes: ['filter', 'groupBy', 'comparison', 'favorite'],
/**
* @override
*/
init: function (viewInfo, params) {
this._super.apply(this, arguments);
const additionalMeasures = params.additionalMeasures || [];
let title;
let startDate;
let endDate;
let progress;
let measure;
let progress_scale;
let childExist = false;
let child = {};
const measures = {};
const measureStrings = {};
let groupBys = [];
const groupableFields = {};
this.fields.__count__ = { string: _t("Count"), type: 'integer' };
this.arch.children.forEach(field => {
let fieldName = field.attrs.name;
if (fieldName === "id") {
return;
}
const interval = field.attrs.interval;
if (interval) {
fieldName = fieldName + ':' + interval;
}
if (field.attrs.type === 'start') {
startDate = fieldName;
}
if(field.attrs.type === 'end'){
endDate = fieldName;
}
if(field.attrs.type === 'title'){
title = fieldName;
}
if(field.attrs.type === 'progress'){
progress = fieldName;
if(field.attrs.scale){
progress_scale = parseInt(field.attrs.scale);
}
}
if(field.attrs.type === 'child'){
if(this.fields[fieldName].type == 'many2many' || this.fields[fieldName].type == 'one2many'){
if(field.attrs.start && field.attrs.end){
child['name'] = fieldName;
child['startDate'] = field.attrs.start;
child['endDate'] = field.attrs.end;
child['model'] = this.fields[fieldName].relation;
childExist = true;
}
}
}
if (field.attrs.string) {
measureStrings[fieldName] = field.attrs.string;
}
});
for (const name in measureStrings) {
if (measures[name]) {
measures[name].description = measureStrings[name];
}
}
// Remove invisible fields from the measures
this.arch.children.forEach(field => {
let fieldName = field.attrs.name;
if (field.attrs.invisible && py.eval(field.attrs.invisible)) {
groupBys = groupBys.filter(groupBy => groupBy !== fieldName);
if (fieldName in groupableFields) {
delete groupableFields[fieldName];
}
if (!additionalMeasures.includes(fieldName)) {
delete measures[fieldName];
}
}
});
const sortedMeasures = Object.values(measures).sort((a, b) => {
const descA = a.description.toLowerCase();
const descB = b.description.toLowerCase();
return descA > descB ? 1 : descA < descB ? -1 : 0;
});
const countMeasure = {
description: _t("Count"),
fieldName: '__count__',
groupNumber: 1,
isActive: false,
itemType: 'measure',
};
this.controllerParams.withButtons = params.withButtons !== false;
this.controllerParams.measures = [...sortedMeasures, countMeasure];
this.controllerParams.groupableFields = groupableFields;
this.controllerParams.child = child;
this.controllerParams.childExist = childExist;
this.controllerParams.title = params.title || this.arch.attrs.string || _t("Untitled");
// retrieve form and list view ids from the action to open those views
// when the graph is clicked
function _findView(views, viewType) {
const view = views.find(view => {
return view.type === viewType;
});
return [view ? view.viewID : false, viewType];
}
this.controllerParams.views = [
_findView(params.actionViews, 'list'),
_findView(params.actionViews, 'form'),
];
this.rendererParams.fields = this.fields;
this.rendererParams.modelName = params.modelName;
this.rendererParams.startDate = startDate;
this.rendererParams.endDate = endDate;
this.rendererParams.progress = progress;
this.rendererParams.child = child;
this.rendererParams.childExist = childExist;
this.rendererParams.progress_scale = progress_scale;
this.rendererParams.titleField = 'display_name';
this.rendererParams.title = this.arch.attrs.title; // TODO: use attrs.string instead
this.rendererParams.disableLinking = !!JSON.parse(this.arch.attrs.disable_linking || '0');
this.loadParams.startDate = startDate;
this.loadParams.endDate = endDate;
this.loadParams.progress = progress;
this.loadParams.child = child;
this.loadParams.childExist = childExist;
this.loadParams.titleField = 'display_name';
this.loadParams.mode = this.arch.attrs.type || 'day';
this.loadParams.orderBy = this.arch.attrs.order;
this.loadParams.measure = measure || '__count__';
this.loadParams.groupBys = groupBys;
this.loadParams.fields = this.fields;
this.loadParams.comparisonDomain = params.comparisonDomain;
this.loadParams.stacked = this.arch.attrs.stacked !== "False";
},
});
view_registry.add('gantt', GanttView);
return GanttView;
});
@@ -0,0 +1,34 @@
<templates>
<t t-name="gantt_chart_popover_template">
<div class="oe_tooltip_string" role="tooltip">
<t t-esc="title"/>
</div>
<ul class="oe_tooltip_technical" role="tooltip">
<li>
<span class="oe_tooltip_technical_title">Start Date:</span>
<t t-esc="startDate"/>
</li>
<li data-item="object">
<span class="oe_tooltip_technical_title">End Date:</span>
<t t-esc="endDate"/>
</li>
</ul>
</t>
<t t-name="GanttView.buttons">
<button class="btn btn-primary">Add</button>
<div class="btn-group" role="toolbar" aria-label="Change Mode">
<button class="btn btn-secondary o_gantt_button" title="Month Mode" aria-label="Month Modet" data-mode="month">
Month
</button>
<button class="btn btn-secondary o_gantt_button" title="Week Mode" aria-label="Week Mode" data-mode="week">
Week
</button>
<button class="btn btn-secondary o_gantt_button" title="Day Mode" aria-label="Day Mode" data-mode="day">
Day
</button>
</div>
</t>
</templates>
+19
View File
@@ -0,0 +1,19 @@
<flectra>
<template id="assets_backend_gantt_chart" inherit_id="web.assets_backend" name="Website Backend Assets Gantt Chart">
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/web_gantt/static/src/dhtmlx/js/dhtmlxgantt.js"/>
<script type="text/javascript" src="/web_gantt/static/src/js/gantt_controller.js"/>
<script type="text/javascript" src="/web_gantt/static/src/js/gantt_renderer.js"/>
<script type="text/javascript" src="/web_gantt/static/src/js/gantt_model.js"/>
<script type="text/javascript" src="/web_gantt/static/src/js/gantt_view.js"/>
</xpath>
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/web_gantt/static/src/dhtmlx/css/dhtmlxgantt.css"/>
<link rel="stylesheet" type="text/scss" href="/web_gantt/static/src/css/style.css"/>
</xpath>
</template>
</flectra>