@ -0,0 +1,47 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
Advanced Dynamic Dashboard |
|||
================================================ |
|||
* Dynamically Arrange the dashboard to get the information that are relevant to your business, department, or a specific process or need. |
|||
|
|||
Installation |
|||
============ |
|||
- www.odoo.com/documentation/16.0/setup/install.html |
|||
- Install our custom addon |
|||
|
|||
License |
|||
------- |
|||
GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL v3) |
|||
(https://www.odoo.com/documentation/user/16.0/legal/licenses/licenses.html) |
|||
|
|||
Company |
|||
------- |
|||
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
|||
|
|||
Credits |
|||
------- |
|||
* Developer: |
|||
(V16) Robin @Cybrosys, Afra MP @Cybrosys |
|||
|
|||
Contacts |
|||
-------- |
|||
* Mail Contact : odoo@cybrosys.com |
|||
|
|||
Bug Tracker |
|||
----------- |
|||
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. |
|||
|
|||
Maintainer |
|||
========== |
|||
.. image:: https://cybrosys.com/images/logo.png |
|||
:target: https://cybrosys.com |
|||
|
|||
This module is maintained by Cybrosys Technologies. |
|||
|
|||
For support and more information, please visit https://www.cybrosys.com |
|||
|
|||
Further information |
|||
=================== |
|||
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,24 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Robin, Afra MP (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from . import controllers |
|||
from . import models |
|||
|
@ -0,0 +1,65 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Robin, Afra MP (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
{ |
|||
'name': "Advanced Dynamic Dashboard", |
|||
'version': '16.0.1.0.0', |
|||
'category': 'Productivity', |
|||
'summary': """Create Configurable Dashboards Easily""", |
|||
'description': """Create Configurable Advanced Dynamic Dashboard to get the |
|||
information that are relevant to your |
|||
business, department, or a specific process or need""", |
|||
'author': 'Cybrosys Techno Solutions', |
|||
'website': "https://www.cybrosys.com", |
|||
'company': 'Cybrosys Techno Solutions', |
|||
'maintainer': 'Cybrosys Techno Solutions', |
|||
'depends': ['base', 'web'], |
|||
'data': [ |
|||
'security/ir.model.access.csv', |
|||
'views/dashboard_views.xml', |
|||
'views/dynamic_block_views.xml', |
|||
'views/dashboard_menu_views.xml', |
|||
], |
|||
'assets': { |
|||
'web.assets_backend': [ |
|||
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css", |
|||
'advanced_dynamic_dashboard/static/lib/css/gridstack.min.css', |
|||
'advanced_dynamic_dashboard/static/src/css/dynamic_dashboard.css', |
|||
'advanced_dynamic_dashboard/static/src/scss/dynamic_dashboard.scss', |
|||
"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css", |
|||
'https://cdnjs.cloudflare.com/ajax/libs/gridstack.js/0.2.6/gridstack.min.js', |
|||
'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.js', |
|||
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js", |
|||
"https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js", |
|||
"https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js", |
|||
'https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js', |
|||
'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js', |
|||
'advanced_dynamic_dashboard/static/src/js/dynamic_dashboard.js', |
|||
'advanced_dynamic_dashboard/static/src/xml/dynamic_dashboard_template.xml', |
|||
'https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700' |
|||
], |
|||
}, |
|||
'images': ['static/description/banner.png'], |
|||
'license': "AGPL-3", |
|||
'installable': True, |
|||
'auto_install': False, |
|||
'application': True, |
|||
} |
@ -0,0 +1,22 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Robin, Afra MP (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from . import advanced_dynamic_dashboard |
@ -0,0 +1,44 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Robin, Afra MP (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import http |
|||
from odoo.http import request |
|||
|
|||
|
|||
class DynamicDashboard(http.Controller): |
|||
"""Class to search and filter values in dashboard""" |
|||
|
|||
@http.route('/tile/details', type='json', auth='user') |
|||
def tile_details(self, **kw): |
|||
"""Function to get tile details""" |
|||
tile_id = request.env['dashboard.block'].sudo().browse(int(kw.get('id'))) |
|||
if tile_id: |
|||
return {'model': tile_id.model_id.model, 'filter': tile_id.filter, |
|||
'model_name': tile_id.model_id.name} |
|||
else: |
|||
return False |
|||
|
|||
@http.route('/custom_dashboard/search_input_chart', type='json', |
|||
auth="public", website=True) |
|||
def dashboard_search_input_chart(self, search_input): |
|||
"""Function to filter search input in dashboard""" |
|||
return request.env['dashboard.block'].search([ |
|||
('name', 'ilike', search_input)]).ids |
@ -0,0 +1,6 @@ |
|||
## Module <advanced_dynamic_dashboard> |
|||
|
|||
#### 15.07.2023 |
|||
#### Version 16.0.1.0.0 |
|||
#### ADD |
|||
- Initial commit for Advanced Dynamic Dashboard |
@ -0,0 +1,24 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Robin, Afra MP (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from . import dashboard_block |
|||
from . import dashboard_menu |
|||
from . import domain_to_sql |
@ -0,0 +1,155 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Robin, Afra MP (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from ast import literal_eval |
|||
from odoo import fields, models |
|||
from odoo.osv import expression |
|||
|
|||
|
|||
class DashboardBlock(models.Model): |
|||
"""Class is used to create charts and tiles in dashboard""" |
|||
_name = "dashboard.block" |
|||
_description = "Dashboard Blocks" |
|||
|
|||
def get_default_action(self): |
|||
"""Function to get values from dashboard if action_id is true return |
|||
id else return false""" |
|||
action_id = self.env.ref( |
|||
'advanced_dynamic_dashboard.dashboard_view_action') |
|||
if action_id: |
|||
return action_id.id |
|||
else: |
|||
return False |
|||
|
|||
name = fields.Char(string="Name", help='Name of the block') |
|||
fa_icon = fields.Char(string="Icon", help="Add icon for tile") |
|||
graph_size = fields.Selection( |
|||
selection=[("col-lg-4", "Small"), ("col-lg-6", "Medium"), |
|||
("col-lg-12", "Large")], |
|||
string="Graph Size", default='col-lg-4', help="Select the graph size") |
|||
operation = fields.Selection( |
|||
selection=[("sum", "Sum"), ("avg", "Average"), ("count", "Count")], |
|||
string="Operation", |
|||
help='Tile Operation that needs to bring values for tile', |
|||
required=True) |
|||
graph_type = fields.Selection( |
|||
selection=[("bar", "Bar"), ("radar", "Radar"), ("pie", "Pie"), |
|||
("polarArea", "polarArea"), ("line", "Line"), |
|||
("doughnut", "Doughnut")], |
|||
string="Chart Type", help='Type of Chart') |
|||
measured_field_id = fields.Many2one("ir.model.fields", |
|||
string="Measured Field", |
|||
help="Select the Measured") |
|||
client_action_id = fields.Many2one('ir.actions.client', |
|||
string="Client action", |
|||
default=get_default_action, |
|||
help="Client action") |
|||
type = fields.Selection( |
|||
selection=[("graph", "Chart"), ("tile", "Tile")], string="Type", |
|||
help='Type of Block ie, Chart or Tile') |
|||
x_axis = fields.Char(string="X-Axis", help="Chart X-axis") |
|||
y_axis = fields.Char(string="Y-Axis", help="Chart Y-axis") |
|||
x_pos = fields.Integer(string="X-Position", help="Chart X-axis position") |
|||
y_pos = fields.Integer(string="X-Position", help="Chart Y-axis position") |
|||
height = fields.Integer(string="height", help="Chart height") |
|||
width = fields.Integer(string="width", help="Chart width") |
|||
group_by_id = fields.Many2one("ir.model.fields", store=True, |
|||
string="Group by(Y-Axis)", |
|||
help='Field value for Y-Axis') |
|||
tile_color = fields.Char(string="Tile Color", help='Primary Color of Tile') |
|||
text_color = fields.Char(string="Text Color", help='Text Color of Tile') |
|||
val_color = fields.Char(string="Value Color", help='Value Color of Tile') |
|||
fa_color = fields.Char(string="Icon Color", help='Icon Color of Tile') |
|||
filter = fields.Char(string="Filter", help="Add filter") |
|||
model_id = fields.Many2one('ir.model', string='Model', |
|||
help="Select the module name") |
|||
model_name = fields.Char(related='model_id.model', string="Model Name", |
|||
help="Added model_id model") |
|||
edit_mode = fields.Boolean(string="Edit Mode", |
|||
help="Enable to edit chart and tile", |
|||
default=False, invisible=True) |
|||
|
|||
def get_dashboard_vals(self, action_id): |
|||
"""Fetch block values from js and create chart""" |
|||
block_id = [] |
|||
for rec in self.env['dashboard.block'].sudo().search( |
|||
[('client_action_id', '=', int(action_id))]): |
|||
vals = {'id': rec.id, 'name': rec.name, 'type': rec.type, |
|||
'graph_type': rec.graph_type, 'icon': rec.fa_icon, |
|||
'cols': rec.graph_size, |
|||
'color': 'background-color: %s;' % rec.tile_color if rec.tile_color else '#1f6abb;', |
|||
'text_color': 'color: %s;' % rec.text_color if rec.text_color else '#FFFFFF;', |
|||
'val_color': 'color: %s;' % rec.val_color if rec.val_color else '#FFFFFF;', |
|||
'icon_color': 'color: %s;' % rec.tile_color if rec.tile_color else '#1f6abb;', |
|||
'x_pos': rec.x_pos,'y_pos': rec.y_pos, 'height': rec.height, |
|||
'width': rec.width} |
|||
domain = [] |
|||
if rec.filter: |
|||
domain = expression.AND([literal_eval(rec.filter)]) |
|||
if rec.model_name: |
|||
if rec.type == 'graph': |
|||
self._cr.execute(self.env[rec.model_name].get_query(domain, |
|||
rec.operation, |
|||
rec.measured_field_id, |
|||
group_by=rec.group_by_id)) |
|||
records = self._cr.dictfetchall() |
|||
x_axis = [] |
|||
for record in records: |
|||
if record.get('name') and type( |
|||
record.get('name')) == dict: |
|||
x_axis.append(record.get('name')[self._context.get('lang') or 'en_US']) |
|||
else: |
|||
x_axis.append(record.get(rec.group_by_id.name)) |
|||
y_axis = [] |
|||
for record in records: |
|||
y_axis.append(record.get('value')) |
|||
vals.update({'x_axis': x_axis, 'y_axis': y_axis}) |
|||
else: |
|||
self._cr.execute(self.env[rec.model_name].get_query(domain, |
|||
rec.operation, |
|||
rec.measured_field_id)) |
|||
records = self._cr.dictfetchall() |
|||
magnitude = 0 |
|||
total = records[0].get('value') |
|||
while abs(total) >= 1000: |
|||
magnitude += 1 |
|||
total /= 1000.0 |
|||
# add more suffixes if you need them |
|||
val = '%.2f%s' % ( |
|||
total, ['', 'K', 'M', 'G', 'T', 'P'][magnitude]) |
|||
records[0]['value'] = val |
|||
vals.update(records[0]) |
|||
block_id.append(vals) |
|||
return block_id |
|||
|
|||
def get_save_layout(self, act_id, grid_data_list): |
|||
"""Function fetch edited values while edit layout of the chart or tile |
|||
and save values in a database""" |
|||
for block in self.env['dashboard.block'].sudo().search( |
|||
[('client_action_id', '=', int(act_id))]): |
|||
for data in grid_data_list: |
|||
if block['id'] == data['id']: |
|||
block.write({ |
|||
'x_pos': int(data['x']), |
|||
'y_pos': int(data['y']), |
|||
'height': int(data['height']), |
|||
'width': int(data['width']), |
|||
}) |
@ -0,0 +1,78 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Robin, Afra MP (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class DashboardMenu(models.Model): |
|||
"""Class to create new dashboard menu""" |
|||
_name = "dashboard.menu" |
|||
_description = "Dashboard Menu" |
|||
|
|||
name = fields.Char(string="Name", ondelete='cascade', |
|||
help="Enter a name for the dashboard menu") |
|||
menu_id = fields.Many2one('ir.ui.menu', string="Parent Menu", |
|||
help="Parent Menu Location of New Dashboard", |
|||
ondelete='cascade') |
|||
group_ids = fields.Many2many('res.groups', string='Groups', |
|||
related='menu_id.groups_id', |
|||
help="User need to be at least in one of these groups to see the menu") |
|||
client_action_id = fields.Many2one('ir.actions.client', |
|||
string="Client Action", |
|||
help="Client action") |
|||
|
|||
@api.model |
|||
def create(self, vals): |
|||
"""Function to create new dashboard menu""" |
|||
action_id = self.env['ir.actions.client'].create({ |
|||
'name': vals['name'], |
|||
'tag': 'advanced_dynamic_dashboard', |
|||
}) |
|||
vals['client_action_id'] = action_id.id |
|||
self.env['ir.ui.menu'].create({ |
|||
'name': vals['name'], |
|||
'parent_id': vals['menu_id'], |
|||
'action': 'ir.actions.client,%d' % (action_id.id,) |
|||
}) |
|||
return super(DashboardMenu, self).create(vals) |
|||
|
|||
def write(self, vals): |
|||
"""Function to save edited data in dashboard menu""" |
|||
for rec in self: |
|||
client_act_id = rec['client_action_id'].id |
|||
self.env['ir.ui.menu'].search( |
|||
[('parent_id', '=', rec['menu_id'].id), |
|||
('action', '=', f'ir.actions.client,{client_act_id}')]).write({ |
|||
'name': vals['name'] if 'name' in vals.keys() else rec['name'], |
|||
'parent_id': vals['menu_id'] if 'menu_id' in vals.keys() else |
|||
rec['menu_id'], |
|||
'action': f'ir.actions.client,{client_act_id}' |
|||
}) |
|||
return super(DashboardMenu, self).write(vals) |
|||
|
|||
def unlink(self): |
|||
"""Delete dashboard along with menu item""" |
|||
for rec in self: |
|||
self.env['ir.ui.menu'].search( |
|||
[('parent_id', '=', rec['menu_id'].id), |
|||
('action', '=', |
|||
f'ir.actions.client,{rec["client_action_id"].id}')]).unlink() |
|||
return super(DashboardMenu, self).unlink() |
@ -0,0 +1,68 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Robin, Afra MP (odoo@cybrosys.com) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import models |
|||
|
|||
|
|||
def get_query(self, args, operation, field, group_by=False, apply_ir_rules=False): |
|||
"""Dashboard block Query Creation""" |
|||
query = self._where_calc(args) |
|||
if apply_ir_rules: |
|||
self._apply_ir_rules(query, 'read') |
|||
if operation and field: |
|||
data = 'COALESCE(%s("%s".%s),0) AS value' % (operation.upper(), self._table, field.name) |
|||
join = '' |
|||
group_by_str = '' |
|||
if group_by: |
|||
if group_by.ttype == 'many2one': |
|||
relation_model = group_by.relation.replace('.', '_') |
|||
join = ' INNER JOIN %s on "%s".id = "%s".%s' % ( |
|||
relation_model, relation_model, self._table, group_by.name) |
|||
rec_name = self.env[group_by.relation]._rec_name_fallback() |
|||
data = data + ',"%s".%s AS %s' % (relation_model, rec_name, group_by.name) |
|||
group_by_str = ' Group by "%s".%s' % (relation_model, rec_name) |
|||
else: |
|||
data = data + ',"%s".%s' % (self._table, group_by.name) |
|||
group_by_str = ' Group by "%s".%s' % (self._table, str(group_by.name)) |
|||
else: |
|||
data = '"%s".id' % (self._table) |
|||
|
|||
from_clause, where_clause, where_clause_params = query.get_sql() |
|||
where_str = where_clause and (" WHERE %s" % where_clause) or '' |
|||
if 'company_id' in self._fields: |
|||
if len(self.env.companies.ids) > 1: |
|||
operator = 'in' |
|||
company = str(tuple(self.env.companies.ids)) |
|||
else: |
|||
operator = '=' |
|||
company = self.env.companies.ids[0] |
|||
if where_str == '': |
|||
add = ' where' |
|||
else: |
|||
add = ' and' |
|||
multicompany_condition = '%s "%s".company_id %s %s' % (add, self._table, operator, company) |
|||
else: |
|||
multicompany_condition = '' |
|||
query_str = 'SELECT %s FROM ' % data + from_clause + join + where_str + multicompany_condition + group_by_str |
|||
return query_str % tuple(map(lambda x: "'" + str(x) + "'", where_clause_params)) |
|||
|
|||
|
|||
models.BaseModel.get_query = get_query |
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 4.1 MiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 300 KiB |
After Width: | Height: | Size: 376 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 300 KiB |
After Width: | Height: | Size: 300 KiB |
After Width: | Height: | Size: 265 KiB |
After Width: | Height: | Size: 285 KiB |
After Width: | Height: | Size: 261 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 308 KiB |
After Width: | Height: | Size: 100 KiB |
After Width: | Height: | Size: 263 KiB |
After Width: | Height: | Size: 288 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 288 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 1.6 MiB |
@ -0,0 +1,386 @@ |
|||
.row { |
|||
margin: 1rem; |
|||
} |
|||
|
|||
.o_dynamic_navbar { |
|||
margin: 1rem 0 1rem 0; |
|||
} |
|||
|
|||
.card { |
|||
background-color: transparent !important; |
|||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|||
border-radius: 5px !important; |
|||
cursor: pointer; |
|||
transition: transform 0.2s; |
|||
} |
|||
|
|||
.grid-stack-item { |
|||
position: absolute !important; |
|||
font-size: 100%; |
|||
overflow:hidden; |
|||
} |
|||
|
|||
h2, h3 { |
|||
font-size: 150% !important; |
|||
} |
|||
|
|||
.card:hover { |
|||
transform: scale(1.05); |
|||
} |
|||
|
|||
.card-header { |
|||
border-radius: 0.5rem 0.5rem 0 0 !important; |
|||
} |
|||
|
|||
.container { |
|||
max-width: 100% !important; |
|||
} |
|||
|
|||
.tile-container { |
|||
padding: 0 0 0 10px; |
|||
border-radius: 0.5rem !important; |
|||
} |
|||
|
|||
.tile { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|||
border-radius: 5px; |
|||
cursor: pointer; |
|||
transition: transform 0.2s; |
|||
} |
|||
|
|||
.tile:hover { |
|||
transform: scale(1.1); |
|||
} |
|||
|
|||
#search-input-chart { |
|||
margin-left: auto; |
|||
height: 2.4rem; |
|||
} |
|||
|
|||
#searchclear { |
|||
position: absolute; |
|||
margin: 0.3rem 0 0 16.5rem; |
|||
} |
|||
|
|||
.navbar-collapse { |
|||
margin-bottom: 8px !important; |
|||
} |
|||
|
|||
div.card-header { |
|||
color: #383838; |
|||
background-color: #70659647 !important; |
|||
} |
|||
|
|||
/* The toggle-btn - the box around the slider */ |
|||
.layout-switch { |
|||
font-size: 15px; |
|||
position: absolute; |
|||
left: 10.5em; |
|||
display: inline-block; |
|||
padding: 0 0 0 0; |
|||
border-radius: 0.5rem; |
|||
} |
|||
|
|||
.toggle-btn { |
|||
font-size: 15px; |
|||
position: absolute; |
|||
left: 18.2em; |
|||
display: inline-block; |
|||
padding: 4px 0 0 0; |
|||
border-radius: 0.5rem; |
|||
} |
|||
|
|||
.theme-text { |
|||
font-family: Arial, sans-serif; |
|||
font-size: 1em; |
|||
opacity: 70%; |
|||
} |
|||
|
|||
.search-group { |
|||
position: absolute; |
|||
right: 1.7em; |
|||
} |
|||
|
|||
/*!* Hide default HTML checkbox *!*/ |
|||
.toggle-btn input { |
|||
opacity: 0; |
|||
width: 0; |
|||
height: 0; |
|||
} |
|||
|
|||
/*!* The slider *!*/ |
|||
.slider { |
|||
position: absolute; |
|||
cursor: pointer; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: #ccc; |
|||
-webkit-transition: .4s; |
|||
transition: .4s; |
|||
} |
|||
|
|||
.theme_icon { |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.theme_icon:hover { |
|||
text-shadow: 2px 1px 20px #795db3; |
|||
} |
|||
|
|||
.block_setting { |
|||
position: absolute; |
|||
top: 7px; |
|||
left: 10px; |
|||
} |
|||
|
|||
.block_delete { |
|||
position: absolute; |
|||
top: 7px; |
|||
right: 20px; |
|||
} |
|||
|
|||
.export_option { |
|||
position: absolute; |
|||
top: 9px; |
|||
right: 10px; |
|||
font-size: 15px; |
|||
} |
|||
|
|||
#ExportMenu::after { |
|||
display: none; |
|||
} |
|||
|
|||
.tile_edit { |
|||
color: #d2d2d2; |
|||
} |
|||
|
|||
.block_edit { |
|||
color: #3f3f3f; |
|||
} |
|||
|
|||
.dropdown-export { |
|||
right: 0; |
|||
top: 1em; |
|||
} |
|||
|
|||
.block_setting, .block_delete, .block_export { |
|||
display: none; |
|||
} |
|||
|
|||
.grid-stack-item:hover .block_setting, |
|||
.grid-stack-item:hover .block_export, |
|||
.grid-stack-item:hover .block_delete { |
|||
display: block; |
|||
} |
|||
|
|||
.chart-edit { |
|||
position: absolute; |
|||
top: 0px; |
|||
left: 3px; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.chart-setting { |
|||
position: absolute; |
|||
top: 0px; |
|||
right: 3px; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.chart_title { |
|||
padding-top: 0.7em; |
|||
text-align: center; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
input:checked + .slider:before { |
|||
-webkit-transform: translateX(26px); |
|||
-ms-transform: translateX(26px); |
|||
transform: translateX(26px); |
|||
} |
|||
|
|||
.move_slider { |
|||
-webkit-transform: translateX(26px); |
|||
-ms-transform: translateX(26px); |
|||
transform: translateX(26px); |
|||
} |
|||
|
|||
/* Rounded sliders */ |
|||
.slider.round { |
|||
border-radius: 34px; |
|||
margin: -1px 6px 11px 0; |
|||
} |
|||
|
|||
.slider.round:before { |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.o_kanban_record { |
|||
background-color: #e8e8e8 !important; |
|||
} |
|||
|
|||
.oe_module_name { |
|||
background-color: #ffffff !important; |
|||
} |
|||
|
|||
.bootbox .modal-footer .btn-danger:hover { |
|||
background-color: #ff595c; |
|||
} |
|||
|
|||
.bootbox .modal-footer .btn-danger { |
|||
box-shadow: none; |
|||
margin-left: 0.5rem; |
|||
} |
|||
|
|||
.bootbox .modal-header { |
|||
text-shadow: -1px 3px 5px #b4b4b4; |
|||
} |
|||
|
|||
.bootbox .modal-body { |
|||
font-size: 14px; |
|||
text-shadow: -1px 3px 5px #b4b4b4; |
|||
color: #000000; |
|||
} |
|||
|
|||
.dropdown-addblock, .o-dropdown-menu { |
|||
left: 2.5em; |
|||
top: 2.5em; |
|||
} |
|||
.navbar { |
|||
padding: 1.2rem 0 2.6rem 0 !important; |
|||
border-bottom: 1px solid #3f3f3f1a !important; |
|||
color: #444444; |
|||
background-color: #e1e2e26e !important; |
|||
border-bottom: 5px solid #eef0f0 !important; |
|||
} |
|||
|
|||
.theme_icon:hover { |
|||
text-shadow: 0px 0px 5px #9388ff; |
|||
} |
|||
|
|||
.navbar-style { |
|||
margin-top: 1.2em; |
|||
border-radius: 0.5em; |
|||
} |
|||
|
|||
.grid-stack > .grid-stack-item > .grid-stack-item-content { |
|||
|
|||
overflow-x: unset !important; |
|||
overflow-y: unset !important; |
|||
} |
|||
|
|||
|
|||
.dropdown-add-items { |
|||
position: absolute; |
|||
color: #e4e4e4; |
|||
left: 2em; |
|||
font-size: 16px; |
|||
text-transform: uppercase; |
|||
background-color: #71639e; |
|||
border: 1px solid transparent; |
|||
padding: 0.375rem 0.75rem; |
|||
font-size: 1.08333333rem; |
|||
border-radius: 0.25rem; |
|||
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; |
|||
} |
|||
|
|||
.dropdown-add-items:hover { |
|||
color: white; |
|||
background-color: #59507b; |
|||
} |
|||
|
|||
|
|||
@media (max-width: 767px) { |
|||
.navbar { |
|||
padding: 1.2rem 0 1.2rem 0; |
|||
} |
|||
|
|||
.navbar:focus, .navbar:active { |
|||
padding: 1.2rem 0 2rem 0; |
|||
} |
|||
|
|||
.search-group { |
|||
width: 50%; |
|||
position: absolute; |
|||
right: 3.7em; |
|||
} |
|||
|
|||
.o_web_client.o_touch_device .btn, .o_web_client.o_touch_device .btn .btn-sm, .o_web_client.o_touch_device .btn .btn-group-sm > .btn { |
|||
font-size: 1.08333333rem; |
|||
padding: 6px 8px; |
|||
margin-top: 39px; |
|||
margin-left: -39px; |
|||
} |
|||
|
|||
.grid-stack-item-content { |
|||
cursor: move; |
|||
width: 211px; |
|||
height: 63px; |
|||
} |
|||
|
|||
/* !* styles for mobile devices *!*/ |
|||
.grid-stack.grid-stack-one-column-mode > .grid-stack-item { |
|||
width: 100% !important; |
|||
} |
|||
|
|||
.dropdown-item { |
|||
font-size: 1.08333333rem; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.dropdown-addblock, .o-dropdown-menu { |
|||
left: -3.5em; |
|||
padding: 0em 7em; |
|||
margin-top: 38px; |
|||
width: 276px; |
|||
} |
|||
|
|||
.toggle-btn { |
|||
position: absolute; |
|||
margin-top: -16px; |
|||
margin-left: 56px; |
|||
left: 1.2em; |
|||
top: 2.3em; |
|||
height: 10px; |
|||
} |
|||
|
|||
#edit_layout { |
|||
display: none; |
|||
} |
|||
|
|||
#search-button { |
|||
position: relative; |
|||
left: 3.5em; |
|||
} |
|||
|
|||
#searchclear { |
|||
position: absolute; |
|||
margin: 0.3rem 0 0 9.9rem; |
|||
} |
|||
|
|||
.search-box { |
|||
top: -3em; |
|||
left: 7.5em; |
|||
margin: 0em -1.9em; |
|||
width: 79%; |
|||
} |
|||
|
|||
|
|||
.navbar-toggler { |
|||
margin: 0.2em 1em; |
|||
padding: 0.25rem 0.5rem; |
|||
} |
|||
|
|||
.dropdown-add-items { |
|||
position: absolute; |
|||
color: #e4e4e4; |
|||
top: -2.6em; |
|||
left: 3.5em; |
|||
} |
|||
} |
@ -0,0 +1,510 @@ |
|||
odoo.define('advanced_dynamic_dashboard.Dashboard', function (require) { |
|||
"use strict"; |
|||
var AbstractAction = require('web.AbstractAction'); |
|||
var ajax = require('web.ajax'); |
|||
var core = require('web.core'); |
|||
var rpc = require('web.rpc'); |
|||
var QWeb = core.qweb; |
|||
var Dialog = require('web.Dialog'); |
|||
var DynamicDashboard = AbstractAction.extend({ |
|||
template: 'advanced_dynamic_dashboard', |
|||
events: { |
|||
'click .add_block': '_onClick_add_block', |
|||
'click .block_setting': '_onClick_block_setting', |
|||
'click .block_delete': '_onClick_block_delete', |
|||
'click #search-button': 'search_chart', |
|||
'click #searchclear': 'clear_search', |
|||
'click #dropdownNavbar': 'navbar_toggle', |
|||
'click #dropdownMenuButton': 'dropdown_toggle', |
|||
'click .chart_item_export': 'export_item', |
|||
'click #edit_layout': '_onClick_edit_layout', |
|||
'click #save_layout': '_onClick_save_layout', |
|||
'change #theme-toggle': 'switch_mode', |
|||
'mouseenter #theme-change-icon': 'show_mode_text', |
|||
'mouseleave #theme-change-icon': 'hide_mode_text', |
|||
'click .tile': '_onClick_tile', |
|||
}, |
|||
init: function (parent, context) {//Function to Initializes all the values while loading the file
|
|||
this.action_id = context['id']; |
|||
this._super(parent, context); |
|||
this.block_ids = []; |
|||
}, |
|||
willStart: function () {//Returns the function fetch_data when page load.
|
|||
var self = this; |
|||
return $.when(this._super()).then(function () { |
|||
return self.fetch_data(); |
|||
}); |
|||
}, |
|||
start: function () {//Function return render_dashboards() and gridstack_init()
|
|||
self = this; |
|||
this.set("title", 'Dashboard'); |
|||
return this._super().then(function () { |
|||
self.render_dashboards(); |
|||
self.gridstack_init(self); |
|||
}); |
|||
}, |
|||
fetch_data: function () {//Fetch data and call rpc query to create chart or tile. return block_ids
|
|||
self = this; |
|||
var def1 = this._rpc({ |
|||
model: 'dashboard.block', |
|||
method: 'get_dashboard_vals', |
|||
args: [[], this.action_id] |
|||
}).then(function (result) { |
|||
self.block_ids = result; |
|||
}); |
|||
return $.when(def1); |
|||
}, |
|||
show_mode_text: function () {//Function change text of dark and light mode while clicking the dark and light button.
|
|||
this.$el.find('.theme_icon').next(this.el.querySelector('.theme-text')).remove(); |
|||
if ( this.$el.find('#theme-toggle').is(':checked')) {//Set text "Light Mode"
|
|||
this.$el.find('.theme_icon').after('<span style="color: #d6e7ff" class="theme-text">⠀Light Mode</span>'); |
|||
} else {//Set text "Dark Mode"
|
|||
this.$el.find('.theme_icon').after('<span style="color: #000000" class="theme-text">⠀Dark Mode</span>'); |
|||
} |
|||
this.$el.find('.theme_icon').next(this.el.querySelector('.theme-text')).fadeIn(); |
|||
}, |
|||
hide_mode_text: function () {//While click button, hide the mode icon and text
|
|||
this.$el.find('.theme_icon').next(this.el.querySelector('.theme-text')).fadeOut(function () { |
|||
$(this).remove(); |
|||
}); |
|||
}, |
|||
switch_mode: function (ev) {//Function to change dashboard theme dark and light mode.
|
|||
this.$el.find('.theme_icon').next('.theme-text').remove(); |
|||
const isDarkTheme = this.$el.find('#theme-toggle').is(':checked'); |
|||
$(this.el.parentElement).toggleClass('dark-theme', isDarkTheme); |
|||
this.$el.find('.theme_icon').toggleClass('bi-sun-fill', isDarkTheme); |
|||
this.$el.find('.theme_icon').toggleClass('bi-moon-stars-fill', !isDarkTheme); |
|||
this.$el.find('.dropdown-export').toggleClass('dropdown-menu-dark', isDarkTheme); |
|||
}, |
|||
get_colors: function (x_axis) {//Function fetch random color values and set chart color
|
|||
return x_axis.map(() => `rgb(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)})`); |
|||
}, |
|||
|
|||
get_values_bar: function (block) {//Set bar chart label, color, data and options. And return data and options
|
|||
var data = { |
|||
labels: block.x_axis, |
|||
datasets: [{ |
|||
data: block.y_axis, |
|||
backgroundColor: this.get_colors(block.x_axis), |
|||
borderColor: 'rgba(200, 200, 200, 0.75)', |
|||
borderWidth: 1 |
|||
}] |
|||
}; |
|||
var options = { |
|||
scales: { |
|||
y: { |
|||
beginAtZero: true |
|||
} |
|||
} |
|||
}; |
|||
return [data, options]; |
|||
}, |
|||
get_values_pie: function (block) {//Set pie chart data and options. And return data and options.
|
|||
var data = { |
|||
labels: block['x_axis'], |
|||
datasets: [{ |
|||
label: '', |
|||
data: block['y_axis'], |
|||
backgroundColor: this.get_colors(block['x_axis']), |
|||
hoverOffset: 4 |
|||
}] |
|||
}; |
|||
return [data, {}]; |
|||
}, |
|||
get_values_line: function (block) {//Set line chart label, data and options. And return data and options.
|
|||
var data = { |
|||
labels: block['x_axis'], |
|||
datasets: [{ |
|||
label: '', |
|||
data: block['y_axis'], |
|||
fill: false, |
|||
borderColor: 'rgb(75, 192, 192)', |
|||
tension: 0.1 |
|||
}] |
|||
}; |
|||
return [data, {}]; |
|||
}, |
|||
get_values_doughnut: function (block) {// Set doughnut chart data and options. And return data and options.
|
|||
var data = { |
|||
labels: block['x_axis'], |
|||
datasets: [{ |
|||
label: '', |
|||
data: block['y_axis'], |
|||
backgroundColor: this.get_colors(block['x_axis']), |
|||
hoverOffset: 4 |
|||
}] |
|||
}; |
|||
return [data, {}]; |
|||
}, |
|||
get_values_polarArea: function (block) {// Set polarArea chart data and options. And return data and options.
|
|||
var data = { |
|||
labels: block['x_axis'], |
|||
datasets: [{ |
|||
label: '', |
|||
data: block['y_axis'], |
|||
backgroundColor: this.get_colors(block['x_axis']), |
|||
hoverOffset: 4 |
|||
}] |
|||
}; |
|||
return [data, {}]; |
|||
}, |
|||
get_values_radar: function (block) {// Set radar chart data and options. And return data and options.
|
|||
var data = { |
|||
labels: block['x_axis'], |
|||
datasets: [{ |
|||
label: '', |
|||
data: block['y_axis'], |
|||
fill: true, |
|||
backgroundColor: 'rgba(255, 99, 132, 0.2)', |
|||
borderColor: 'rgb(255, 99, 132)', |
|||
pointBackgroundColor: 'rgb(255, 99, 132)', |
|||
pointBorderColor: '#fff', |
|||
pointHoverBackgroundColor: '#fff', |
|||
pointHoverBorderColor: 'rgb(255, 99, 132)' |
|||
}] |
|||
}; |
|||
var options = { |
|||
elements: { |
|||
line: { |
|||
borderWidth: 3 |
|||
} |
|||
} |
|||
} |
|||
return [data, options]; |
|||
}, |
|||
gridstack_init: function (self) {// Used gridstack to drag and resize chart and tile.
|
|||
self.$('.grid-stack').gridstack({ |
|||
animate: true, |
|||
duration: 200, |
|||
handle: '.grid-stack-item-content', |
|||
draggable: { |
|||
handle: '.grid-stack-item-content', |
|||
scroll: true |
|||
}, |
|||
resizable:{ |
|||
aspectRatio:20/18, |
|||
}, |
|||
alwaysShowResizeHandle: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), |
|||
float: true |
|||
}); |
|||
self.gridstack_off(self); |
|||
}, |
|||
gridstack_on: function (self) {// Enable move and resize functionality
|
|||
var gridstack = self.$('.grid-stack').data('gridstack'); |
|||
gridstack.enableMove(true); |
|||
gridstack.enableResize(true); |
|||
}, |
|||
gridstack_off: function (self) {// Disable move and resize functionality
|
|||
var gridstack = self.$('.grid-stack').data('gridstack'); |
|||
gridstack.enableMove(false); |
|||
gridstack.enableResize(false); |
|||
}, |
|||
render_dashboards: function () { |
|||
self = this; |
|||
self.$("#save_layout").hide();//Hide save_layout button
|
|||
_.each(this.block_ids, function (block) {//Loop all chart and tile
|
|||
if (block['type'] == 'tile') { |
|||
self.$('.o_dynamic_dashboard').append(QWeb.render('DynamicDashboardTile', {widget: block})); |
|||
} else {//Block type = 'chart'
|
|||
self.$('.o_dynamic_dashboard').append(QWeb.render('DynamicDashboardChart', {widget: block})); |
|||
if (!('x_axis' in block)) { |
|||
return false |
|||
} |
|||
var type = block['graph_type'] |
|||
var chart_type = 'self.get_values_' + `${type}(block)` |
|||
new Chart(self.$('.chart_graphs').last(), { |
|||
type: type, |
|||
data: eval(chart_type)[0], |
|||
options: eval(chart_type)[1] |
|||
}); |
|||
} |
|||
}); |
|||
// Toggling dropdown for exporting, clicked item, closing all others
|
|||
// When clicked on one, also when mouse leaves parent.
|
|||
self.$(".block_export").on({ |
|||
click: function () {//Show the export dropdown.
|
|||
if ($(this).next(".dropdown-export").is(':visible')) { |
|||
$(this).next(".dropdown-export").hide(); |
|||
} else { |
|||
$(this).next('.dropdown-export').hide(); |
|||
$(this).next(".dropdown-export").show(); |
|||
} |
|||
} |
|||
}); |
|||
self.$(".grid-stack-item").on({//Function to hide dropdown-export list while mouse leave the block.
|
|||
mouseleave: function () { |
|||
self.$('.dropdown-export').hide(); |
|||
} |
|||
}); |
|||
self.$(".dropdown-addblock").on({//Function to hide dropdown-addblock list if mouse leave dropdown list.
|
|||
mouseleave: function () { |
|||
self.$(".dropdown-addblock").hide(); |
|||
} |
|||
}); |
|||
self.gridstack_init(self); |
|||
if (localStorage.getItem("toggleState") == 'true') { |
|||
self.$(".toggle").prop('checked', true) |
|||
$(self.el.parentElement).addClass('dark-theme'); |
|||
self.$(".theme_icon").removeClass('bi-moon-stars-fill'); |
|||
self.$(".theme_icon").addClass('bi-sun-fill'); |
|||
self.$(".dropdown-export").addClass('dropdown-menu-dark'); |
|||
} else { |
|||
$(self.el.parentElement).removeClass('dark-theme'); |
|||
self.$(".theme_icon").removeClass('bi-sun-fill'); |
|||
self.$(".theme_icon").addClass('bi-moon-stars-fill'); |
|||
self.$(".dropdown-export").removeClass('dropdown-menu-dark'); |
|||
} |
|||
}, |
|||
navbar_toggle: function () {//Function to toggle the navbar.
|
|||
this.$('.navbar-collapse').toggle(); |
|||
}, |
|||
export_item: function (e) {//Function to export chart into jpg, png or csv formate.
|
|||
var type = $(e.currentTarget).attr('data-type'); |
|||
var canvas = $(e.currentTarget).closest('.export_option').siblings('.row').find('#canvas')[0]; |
|||
var dataTitle = canvas.getAttribute("data-title"); |
|||
// Create a new canvas with a white background
|
|||
var bgCanvas = document.createElement("canvas"); |
|||
bgCanvas.width = canvas.width; |
|||
bgCanvas.height = canvas.height; |
|||
var bgCtx = bgCanvas.getContext("2d"); |
|||
bgCtx.fillStyle = "white"; |
|||
bgCtx.fillRect(0, 0, canvas.width, canvas.height); |
|||
// Draw the chart onto the new canvas
|
|||
bgCtx.drawImage(canvas, 0, 0); |
|||
// Export the new canvas as an image
|
|||
var imgData = bgCanvas.toDataURL("image/png"); |
|||
if (type === 'png') { |
|||
this.$el.find('.chart_png_export').attr({ |
|||
href: imgData, |
|||
download: `${dataTitle}.png` |
|||
}); |
|||
} |
|||
if (type === 'pdf') { |
|||
var pdf = new jsPDF(); |
|||
pdf.addImage(bgCanvas.toDataURL("image/png"), 'PNG', 0, 0); |
|||
pdf.save(`${dataTitle}.pdf`); |
|||
|
|||
} |
|||
if (type === 'csv') { |
|||
var rows = []; |
|||
// Check if the id inside the object is equal to this id
|
|||
for (var obj of this.block_ids) { |
|||
if (obj.id == $(e.currentTarget).attr('data-id')) { |
|||
rows.push(obj.x_axis); |
|||
rows.push(obj.y_axis); |
|||
} |
|||
} |
|||
let csvContent = "data:text/csv;charset=utf-8,"; |
|||
rows.forEach(function (rowArray) { |
|||
let row = rowArray.join(","); |
|||
csvContent += row + "\r\n"; |
|||
}); |
|||
var link = document.createElement("a"); |
|||
link.setAttribute("href", encodeURI(csvContent)); |
|||
link.setAttribute("download", `${dataTitle}.csv`); |
|||
document.body.appendChild(link); // Required for FF
|
|||
link.click(); |
|||
} |
|||
}, |
|||
dropdown_toggle: function () {//Function to toggle the button Add Items.
|
|||
this.$el.find('.dropdown-addblock').toggle(); |
|||
}, |
|||
on_reverse_breadcrumb: function () {//Function return all block in exact position.
|
|||
self = this; |
|||
this.fetch_data().then(function () {//Fetch all datas
|
|||
self.render_dashboards(); |
|||
self.gridstack_init(self); |
|||
location.reload(); |
|||
}); |
|||
}, |
|||
search_chart: function (e) {// Fetch search input value and filter the chart and tile.
|
|||
e.stopPropagation() |
|||
self = this; |
|||
$(this).next("#theme-change-icon").hide(); |
|||
this.$("#edit_layout").hide(); |
|||
this.$("#save_layout").hide(); |
|||
this.myDiv = this.$('.o_dynamic_dashboard'); |
|||
this.$('.o_dynamic_dashboard').empty(); |
|||
ajax.jsonRpc("/custom_dashboard/search_input_chart", 'call', {//Ajax call to get filtered data
|
|||
'search_input': self.$("#search-input-chart").val() |
|||
}).then(function (res) { |
|||
_.each(self.block_ids, function (block) { |
|||
if (res.includes(block['id'])) { |
|||
if (block['type'] == 'tile') { |
|||
self.$('.o_dynamic_dashboard').append(QWeb.render('DynamicDashboardTile', {widget: block})); |
|||
} else { |
|||
self.$('.o_dynamic_dashboard').append(QWeb.render('DynamicDashboardChart', {widget: block})); |
|||
if (!('x_axis' in block)) { |
|||
return false |
|||
} |
|||
var chart_type = 'self.get_values_' + `${block['graph_type']}(block)` |
|||
new Chart(self.$('.chart_graphs').last(), { |
|||
type: block['graph_type'], |
|||
data: eval(chart_type)[0], |
|||
options: eval(chart_type)[1] |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
}, |
|||
clear_search: function () {//Function to clear search box and call the functon on_reverse_breadcrumb().
|
|||
self = this; |
|||
self.$("#search-input-chart").val(""); |
|||
self.$("#theme-change-icon").show(); |
|||
self.$("#edit_layout").show(); |
|||
self.$("#save_layout").hide(); |
|||
this.block_ids = []; |
|||
self.on_reverse_breadcrumb(); |
|||
}, |
|||
_onClick_block_setting: function (event) {//Function to edit blocks and redirect to the model dashboard.block
|
|||
event.stopPropagation(); |
|||
self = this; |
|||
this.do_action({ |
|||
type: 'ir.actions.act_window', |
|||
res_model: 'dashboard.block', |
|||
view_mode: 'form', |
|||
res_id: parseInt($(event.currentTarget).closest('.block').attr('data-id')), |
|||
views: [[false, 'form']], |
|||
context: {'form_view_initial_mode': 'edit'}, |
|||
}, {on_reverse_breadcrumb: self.on_reverse_breadcrumb}) |
|||
}, |
|||
_onClick_block_delete: function (event) {//While click on cross icon, the block will be deleted.
|
|||
event.stopPropagation(); |
|||
self = this; |
|||
bootbox.confirm({//Popup to conform delete
|
|||
message: "Are you sure you want to delete this item?", |
|||
title: "Delete confirmation", |
|||
buttons: { |
|||
cancel: { |
|||
label: 'NO, GO BACK', |
|||
className: 'btn-primary' |
|||
}, |
|||
confirm: { |
|||
label: 'YES, I\'M SURE', |
|||
className: 'btn-danger' |
|||
} |
|||
}, |
|||
callback: function (result) {//Function to unlink block
|
|||
if (result) { |
|||
rpc.query({ |
|||
model: 'dashboard.block', |
|||
method: 'unlink', |
|||
args: [parseInt($(event.currentTarget).closest('.block').attr('data-id'))], // ID of the record to unlink
|
|||
}).then(function (result) { |
|||
location.reload() |
|||
self.on_reverse_breadcrumb(); |
|||
}).catch(function (error) { |
|||
console.log('Error unlinking record: ', error); |
|||
}); |
|||
} else { |
|||
// Do nothing
|
|||
} |
|||
} |
|||
}); |
|||
}, |
|||
_onClick_add_block: function (e) {//Fetch data and create chart or tile
|
|||
self = this; |
|||
var type = $(e.currentTarget).attr('data-type'); |
|||
if (type == 'graph') { |
|||
var chart_type = $(e.currentTarget).attr('data-chart_type'); |
|||
} |
|||
if (type === 'tile') { |
|||
var randomColor = '#' + ('000000' + Math.floor(Math.random() * 16777216).toString(16)).slice(-6); |
|||
this.do_action({// Redirect to dashboard.block
|
|||
type: 'ir.actions.act_window', |
|||
res_model: 'dashboard.block', |
|||
view_mode: 'form', |
|||
views: [[false, 'form']], |
|||
context: { |
|||
'form_view_initial_mode': 'edit', |
|||
'default_name': 'New Tile', |
|||
'default_type': type, |
|||
'default_height': 2, |
|||
'default_width': 2, |
|||
'default_tile_color': randomColor, |
|||
'default_text_color': '#FFFFFF', |
|||
'default_fa_icon': 'fa fa-bar-chart', |
|||
'default_client_action_id': parseInt(self.action_id) |
|||
}, |
|||
on_close: function () { |
|||
window.location.reload(); |
|||
} |
|||
}); |
|||
} else { |
|||
this.do_action({ |
|||
type: 'ir.actions.act_window', |
|||
res_model: 'dashboard.block', |
|||
view_mode: 'form', |
|||
views: [[false, 'form']], |
|||
context: { |
|||
'form_view_initial_mode': 'edit', |
|||
'default_name': 'New ' + chart_type, |
|||
'default_type': type, |
|||
'default_height': 5, |
|||
'default_width': 4, |
|||
'default_graph_type': chart_type, |
|||
'default_graph_size': 'col-lg-4', |
|||
'default_fa_icon': 'fa fa-bar-chart', |
|||
'default_client_action_id': parseInt(self.action_id) |
|||
}, |
|||
on_close: function(){ |
|||
window.location.reload(); |
|||
} |
|||
}); |
|||
} |
|||
// FETCHING SAVED LAYOUT FROM LOCAL STORAGE MEMORY
|
|||
}, |
|||
_onClick_edit_layout: function (e) {// Function to hide edit_layout button and show save_layout button. and also work the function gridstack_on(self)
|
|||
e.stopPropagation(); |
|||
self = this; |
|||
self.$("#edit_layout").hide(); |
|||
self.$("#save_layout").show(); |
|||
self.gridstack_on(self); |
|||
}, |
|||
_onClick_save_layout: function (e) {//Function to save the edited value
|
|||
e.stopPropagation(); |
|||
self = this; |
|||
self.$("#edit_layout").show(); |
|||
self.$("#save_layout").hide(); |
|||
var grid_data_list = []; |
|||
this.$el.find('.grid-stack-item').each(function () { |
|||
grid_data_list.push({ |
|||
'id': $(this).data('id'), |
|||
'x': $(this).data('gs-x'), |
|||
'y': $(this).data('gs-y'), |
|||
'width': $(this).data('gs-width'), |
|||
'height': $(this).data('gs-height') |
|||
}) |
|||
}); |
|||
this._rpc({ |
|||
model: 'dashboard.block', |
|||
method: 'get_save_layout', |
|||
args: [[], this.action_id, grid_data_list] |
|||
}); |
|||
self.gridstack_off(self); |
|||
}, |
|||
_onClick_tile: function (e) {// Function to view the tree view of the tile.
|
|||
e.stopPropagation(); |
|||
self = this; |
|||
ajax.jsonRpc('/tile/details', 'call', { |
|||
'id': $(e.currentTarget).attr('data-id') |
|||
}).then(function (result) { |
|||
if (result['model_name']) { |
|||
self.do_action({ |
|||
name: result['model_name'], |
|||
type: 'ir.actions.act_window', |
|||
res_model: result['model'], |
|||
view_mode: 'tree,form', |
|||
views: [[false, 'list'], [false, 'form']], |
|||
domain: result['filter'] |
|||
}); |
|||
} else { |
|||
Dialog.alert(this, "Configure the tile's model and parameters."); |
|||
} |
|||
}); |
|||
}, |
|||
}); |
|||
core.action_registry.add('advanced_dynamic_dashboard', DynamicDashboard); |
|||
return DynamicDashboard; |
|||
}); |
@ -0,0 +1,179 @@ |
|||
|
|||
:root { |
|||
/* Colors */ |
|||
--green: #00C689; |
|||
--blue: #3DA5F4; |
|||
--red: #F1536E; |
|||
--yellow: #FDA006; |
|||
/*Fonts*/ |
|||
--primary-font: 'Roboto', sans-serif; |
|||
} |
|||
|
|||
html .o_web_client > .o_action_manager { |
|||
overflow: auto; |
|||
} |
|||
|
|||
.bg-green { |
|||
background-color: var(--green); |
|||
} |
|||
|
|||
.bg-blue { |
|||
background-color: var(--blue); |
|||
} |
|||
|
|||
.bg-red { |
|||
background-color: var(--red); |
|||
} |
|||
|
|||
.bg-yellow { |
|||
background-color: var(--yellow); |
|||
} |
|||
|
|||
.text-color-yellow { |
|||
color: var(--yellow); |
|||
} |
|||
|
|||
.text-color-green { |
|||
color: var(--green); |
|||
} |
|||
|
|||
.text-color-blue { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.text-color-red { |
|||
color: var(--red); |
|||
} |
|||
|
|||
.text-color-yellow { |
|||
color: var(--yellow); |
|||
} |
|||
|
|||
.tile-container__icon-container { |
|||
border-radius: 50%; |
|||
width: 4.75rem; |
|||
height: 4.75rem; |
|||
font-size: 28px; |
|||
} |
|||
|
|||
.tile-container__status-container { |
|||
margin-left: 2em; |
|||
} |
|||
|
|||
.status-container__title { |
|||
font-family: var(--primary-font); |
|||
font-weight: 500; |
|||
font-size: 1.5rem; |
|||
line-height: 1.5rem; |
|||
} |
|||
|
|||
.status-container__figures { |
|||
font-family: var(--primary-font); |
|||
} |
|||
|
|||
.status-container__figures > h3 { |
|||
font-weight: 700; |
|||
font-size: 1.5rem; |
|||
line-height: 1.813rem; |
|||
} |
|||
|
|||
.tile-container__setting-icon { |
|||
top: 0.638rem; |
|||
right: 1rem; |
|||
} |
|||
|
|||
.dark-theme { |
|||
/* Add dark theme styles here */ |
|||
.block_edit { |
|||
color: #b7b7b7 !important; |
|||
} |
|||
|
|||
.navbar { |
|||
padding: 1.2rem 0 2.6rem 0 !important; |
|||
border-bottom: 1px solid #3f3f3f1a !important; |
|||
} |
|||
|
|||
.theme_icon:hover { |
|||
text-shadow: 0px 0px 5px #9388ff; |
|||
} |
|||
.add_block{ |
|||
color: #dfdfdf; |
|||
} |
|||
#ExportMenu { |
|||
color: #626262; |
|||
} |
|||
|
|||
.dropdown-export { |
|||
background-color: #2a2a2a; |
|||
color: #dfdfdf; |
|||
} |
|||
|
|||
.chart_title { |
|||
color: #dfdfdf; |
|||
margin: -0.5em 0 2em 0; |
|||
} |
|||
|
|||
.btn-search_edit, { |
|||
color: #313131; |
|||
border: none; |
|||
background-color: #979797 !important; |
|||
} |
|||
|
|||
#search-input-chart { |
|||
color: #ffffff; |
|||
border: 1px solid #4e4e4e; |
|||
background-color: #2A2A2A !important; |
|||
} |
|||
|
|||
#search-clear { |
|||
color: #1f1f1f !important; |
|||
} |
|||
|
|||
div.navbar { |
|||
color: #909090; |
|||
background-color: #2A2A2A !important; |
|||
} |
|||
|
|||
.navbar-collapse { |
|||
margin-bottom: 12px !important; |
|||
} |
|||
|
|||
h3 { |
|||
color: #909090; |
|||
} |
|||
|
|||
div.dropdown-addblock { |
|||
color: #909090; |
|||
background-color: #2A2A2A !important; |
|||
} |
|||
|
|||
#dropdownMenuButton { |
|||
color: #313131; |
|||
background-color: #979797 !important; |
|||
} |
|||
|
|||
div.card-body { |
|||
border-radius: 0.5em !important; |
|||
background-color: #2a2a2a !important; |
|||
} |
|||
|
|||
.o_kanban_record { |
|||
background-color: #e8e8e8 !important; |
|||
} |
|||
|
|||
.o_kanban_renderer { |
|||
background-color: #e8e8e8 !important; |
|||
} |
|||
|
|||
.oe_module_name { |
|||
background-color: #ffffff !important; |
|||
} |
|||
|
|||
background-color: #1C1B1B; |
|||
@media (max-width: 767px) { |
|||
.navbar-light .navbar-toggler { |
|||
color: #A7A7A72D; |
|||
border-color: #F6F6F621; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,196 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<templates id="template" xml:space="preserve"> |
|||
<!--DASHBOARD VIEW WITH NAVIGATION-BAR, GRID_STACK TEMPLATE--> |
|||
<t t-name="advanced_dynamic_dashboard"> |
|||
<div class="container mx-auto"> |
|||
<div class="navbar navbar-expand-md navbar-light bg-light mb-4 navbar-style border-bottom" |
|||
role="navigation"> |
|||
<button class="navbar-toggler" id="dropdownNavbar" type="button" |
|||
data-toggle="collapse" |
|||
data-target="#navbarCollapse" |
|||
aria-controls="navbarCollapse" aria-expanded="false" |
|||
aria-label="Toggle navigation"> |
|||
<span class="navbar-toggler-icon"/> |
|||
</button> |
|||
<div class="collapse navbar-collapse" |
|||
aria-labelledby="dropdownNavbar"> |
|||
<ul class="navbar-nav mr-auto"> |
|||
<label class="navbar-items dropdown drop-down-add"> |
|||
<button class="btn dropdown-add-items dropdown-toggle" |
|||
type="button" id="dropdownMenuButton" |
|||
data-toggle="dropdown" aria-haspopup="true" |
|||
aria-expanded="false"> |
|||
<i class="bi bi-plus-circle-fill"/> |
|||
<span>⠀Add Items</span> |
|||
|
|||
</button> |
|||
<div class="dropdown-menu dropdown-addblock" |
|||
aria-labelledby="dropdownMenuButton"> |
|||
<a class="dropdown-item add_block" |
|||
data-type="tile">Tile</a> |
|||
<a class="dropdown-item add_block" |
|||
data-type="graph" data-chart_type="bar">Bar Chart</a> |
|||
<a class="dropdown-item add_block" |
|||
data-type="graph" data-chart_type="doughnut">Doughnut Chart</a> |
|||
<a class="dropdown-item add_block" |
|||
data-type="graph" |
|||
data-chart_type="line">Line Chart</a> |
|||
<a class="dropdown-item add_block" |
|||
data-type="graph" data-chart_type="pie">Pie Chart</a> |
|||
<a class="dropdown-item add_block" |
|||
data-type="graph" |
|||
data-chart_type="polarArea">Polar Area Chart</a> |
|||
<a class="dropdown-item add_block" |
|||
data-type="graph" |
|||
data-chart_type="radar">Radar Chart</a> |
|||
</div> |
|||
</label> |
|||
<label class="navbar-items layout-switch" |
|||
id="edit-layout-label"> |
|||
<button class="navbar-items btn-search_edit btn btn-primary my-2 mx-2 my-sm-0" |
|||
type="button" |
|||
id="edit_layout">Edit |
|||
Layout |
|||
</button> |
|||
<button class="navbar-items btn-search_edit btn btn-primary my-2 mx-2 my-sm-0" |
|||
type="button" |
|||
id="save_layout">Save Layout |
|||
</button> |
|||
</label> |
|||
<label class="navbar-items toggle-btn" |
|||
id="theme-change-icon"> |
|||
<input type="checkbox" class="toggle" |
|||
id="theme-toggle"/> |
|||
<i class="theme_icon bi bi-moon-stars-fill"/> |
|||
</label> |
|||
<label class="search-group"> |
|||
<div class="navbar-items btn-group search-box"> |
|||
<input class="form-control mr-sm-2" type="text" |
|||
placeholder="Search" |
|||
id="search-input-chart" |
|||
aria-label="Search"/> |
|||
<span id="searchclear"> |
|||
<i class='fa fa-times search-clear'/> |
|||
</span> |
|||
</div> |
|||
<button class="navbar-items btn-search_edit btn btn-outline-primary my-2 my-sm-0 search-btn" |
|||
type="button" |
|||
id="search-button">Search</button> |
|||
|
|||
</label> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
<div class="o_dynamic_dashboard row m-2 grid-stack" |
|||
name="gridstack"> |
|||
<!--CONTAINER FOR CONTENT GENERATION :TILE & CHART(FROM DynamicDashboardTile & DynamicDashboardChart--> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
|
|||
<t t-name="DynamicDashboardTile"> |
|||
<!-- TILE BUILDING TEMPLATE--> |
|||
<div t-att-class="'grid-stack-item ' + widget.name" |
|||
t-att-data-gs-y="widget.y_pos" |
|||
t-att-data-gs-x="widget.x_pos" t-att-data-gs-width="widget.width" |
|||
t-att-data-gs-height="widget.height" |
|||
t-att-data-id="widget.id"> |
|||
<div class="grid-stack-item-content tile block" |
|||
t-att-data-id="widget.id" |
|||
t-att-style="widget.color+widget.text_color"> |
|||
<div t-att-style="widget.color+widget.text_color" |
|||
class="tile-container d-flex align-items-center w-100 my-3"> |
|||
<a class="block_setting tile_edit tile-container__setting-icon"> |
|||
<i class="fa fa-edit"/> |
|||
</a> |
|||
<a class="block_delete tile_edit tile-container__delete-icon"> |
|||
<i class="fa fa-times"/> |
|||
</a> |
|||
<div> |
|||
|
|||
</div> |
|||
<div t-att-style="widget.icon_color" |
|||
class="tile-container__icon-container bg-white d-flex justify-content-center align-items-center"> |
|||
<i t-att-class="widget.icon"/> |
|||
</div> |
|||
<div t-att-style="widget.text_color" |
|||
class="tile-container__status-container"> |
|||
<h2 t-att-style="widget.text_color" |
|||
class="status-container__title"> |
|||
<t t-esc="widget.name"/> |
|||
</h2> |
|||
<div class="status-container__figures d-flex flex-wrap align-items-baseline"> |
|||
<h3 class="mb-0 mb-md-1 mb-lg-0 mr-1" |
|||
t-att-style="widget.val_color"> |
|||
<t t-esc="widget.value"/> |
|||
</h3> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
|
|||
<t t-name="DynamicDashboardChart"> |
|||
<!-- CHART BUILDING TEMPLATE--> |
|||
<div t-att-class="'grid-stack-item ' + widget.name" |
|||
t-att-data-gs-x="widget.x_pos" |
|||
t-att-data-gs-y="widget.y_pos" t-att-data-gs-width="widget.width" |
|||
t-att-data-gs-height="widget.height" |
|||
t-att-data-id="widget.id"> |
|||
<div class="grid-stack-item-content block card" |
|||
t-att-data-id="widget.id"> |
|||
<div class="card-body mt-1" id="in_ex_body_hide"> |
|||
<div class="block_edit block_setting"> |
|||
<i title="Configuration" |
|||
class="fa fa-pencil block_setting chart-edit"/> |
|||
</div> |
|||
<div class="block_edit block_delete"> |
|||
<i title="Delete" |
|||
class="fa fa-times block_delete chart-setting"/> |
|||
</div> |
|||
<div class="dropdown export_option"> |
|||
<div class="block_edit fa fa-ellipsis-v block_export dropdown-toggle" |
|||
type="button" |
|||
id="ExportMenu" data-toggle="dropdown" |
|||
aria-haspopup="true" aria-expanded="false"> |
|||
</div> |
|||
<div class="dropdown-menu dropdown-export" |
|||
aria-labelledby="ExportMenu"> |
|||
<div class="m-2 chart_export_menu_header"> |
|||
<span>Export</span> |
|||
</div> |
|||
<a class="dropdown-item chart_item_export chart_png_export" |
|||
data-type="png"> |
|||
<i class="fa fa-file-image-o"/> |
|||
<span>Save as Image</span> |
|||
</a> |
|||
<button class="dropdown-item chart_pdf_export chart_item_export" |
|||
data-type="pdf"> |
|||
<i class="fa fa-file-pdf-o"/> |
|||
<span>Save as PDF</span> |
|||
</button> |
|||
<button class="dropdown-item chart_csv_export chart_item_export" |
|||
data-type="csv" |
|||
t-att-data-id="widget.id"> |
|||
<i class="fa fa-file-code-o"/> |
|||
<span>Export to CSV</span> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
<h3 class="chart_title"> |
|||
<t t-esc="widget.name"/> |
|||
</h3> |
|||
<div class="row"> |
|||
<div class="col-md-12 chart_canvas" id="chart_canvas" |
|||
t-att-data-id="widget.id"> |
|||
<canvas id="canvas" class="chart_graphs" width="300" |
|||
height="200" |
|||
t-att-data-title="widget.name"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
</templates> |
@ -0,0 +1,62 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<!-- Action specified for the dashboard menu--> |
|||
<record id="dashboard_menu_view_action" model="ir.actions.act_window"> |
|||
<field name="name">Dashboards Menu</field> |
|||
<field name="type">ir.actions.act_window</field> |
|||
<field name="res_model">dashboard.menu</field> |
|||
<field name="view_mode">kanban,form</field> |
|||
</record> |
|||
<!--Kanban view of the dashboard menu--> |
|||
<record id="dashboard_menu_view_kanban" model="ir.ui.view"> |
|||
<field name="name">dashboard.menu.view.kanban</field> |
|||
<field name="model">dashboard.menu</field> |
|||
<field name="arch" type="xml"> |
|||
<kanban> |
|||
<field name="name"/> |
|||
<templates> |
|||
<t t-name="kanban-box"> |
|||
<div t-attf-class="oe_kanban_global_click"> |
|||
<h3 class="my-2 ms-3"> |
|||
Name: |
|||
<field name="name"/> |
|||
</h3> |
|||
<div class="row"> |
|||
<hr class="mt4 mb4"/> |
|||
<div class="col-6 text-center"> |
|||
<strong>Parent:</strong> |
|||
</div> |
|||
<div class="col-6 text-center"> |
|||
<field name="menu_id"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
</templates> |
|||
</kanban> |
|||
</field> |
|||
</record> |
|||
<!--Form view of the dashboard menu--> |
|||
<record id="dashboard_menu_view_form" model="ir.ui.view"> |
|||
<field name="name">dashboard.menu.view.form</field> |
|||
<field name="model">dashboard.menu</field> |
|||
<field name="arch" type="xml"> |
|||
<form> |
|||
<sheet> |
|||
<group> |
|||
<group> |
|||
<field name="name" class="oe_inline"/> |
|||
<field name="menu_id" class="oe_inline"/> |
|||
<field name="group_ids" widget="many2many_tags" |
|||
invisible="1"/> |
|||
<field name="client_action_id" invisible="1"/> |
|||
</group> |
|||
</group> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
<menuitem name="Dashboards" id="dashboard_menu_view_menu_action" |
|||
parent="advanced_dynamic_dashboard.dashboard_menu_action" |
|||
sequence="0" action="dashboard_menu_view_action"/> |
|||
</odoo> |
@ -0,0 +1,12 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<!-- The client action record to view the Dashboard--> |
|||
<record id="dashboard_view_action" model="ir.actions.client"> |
|||
<field name="name">Dashboard</field> |
|||
<field name="tag">advanced_dynamic_dashboard</field> |
|||
</record> |
|||
<menuitem name="Dynamic Dashboards" action="dashboard_view_action" |
|||
id="dashboard_menu_action" |
|||
web_icon="advanced_dynamic_dashboard,static/description/icon.png" |
|||
sequence="-1"/> |
|||
</odoo> |
@ -0,0 +1,82 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<!--Form view of the dashboard block--> |
|||
<record id="dashboard_block_view_form" model="ir.ui.view"> |
|||
<field name="name">dashboard.block.view.form</field> |
|||
<field name="model">dashboard.block</field> |
|||
<field name="arch" type="xml"> |
|||
<form> |
|||
<sheet> |
|||
<group> |
|||
<group> |
|||
<div> |
|||
<field name="name" class="oe_inline" |
|||
style="font-size: 30px;" |
|||
placeholder="Block Name" required="1"/> |
|||
</div> |
|||
</group> |
|||
</group> |
|||
<group> |
|||
<group> |
|||
<field name="model_id" |
|||
attrs="{'required':[('edit_mode','=', True)]}"/> |
|||
<field name="client_action_id" invisible="1"/> |
|||
<field name="model_name" invisible="1"/> |
|||
<field name="edit_mode" invisible="1"/> |
|||
<field name="operation" |
|||
attrs="{'required':[('edit_mode','=', True)]}"/> |
|||
<field name="measured_field_id" required="1" |
|||
domain="[('model_id','=',model_id), ('ttype','in',['float','integer','monetary']), ('store', '=', True)]" |
|||
attrs="{'required':[('edit_mode','=', True)]}"/> |
|||
<field name="filter" widget="domain" |
|||
options="{'model': 'model_name'}"/> |
|||
</group> |
|||
</group> |
|||
<group string="Block Information"> |
|||
<group> |
|||
<field name="type" required="1"/> |
|||
<field name="graph_type" |
|||
attrs="{'invisible': [('type','not in', 'graph')]}"/> |
|||
<field name="graph_size" |
|||
attrs="{'invisible': [('type','not in', 'graph')]}"/> |
|||
<field name="fa_icon" |
|||
attrs="{'invisible': [('type','not in', 'tile')]}"/> |
|||
<field name="group_by_id" |
|||
attrs="{'invisible': [('type','not in', 'graph')], 'required':[('edit_mode','=', True),('type','=','graph')]}" |
|||
domain="[('model_id','=',model_id), ('ttype','!=','one2many'), ('store', '=', True)]"/> |
|||
<field name="tile_color" |
|||
attrs="{'invisible': [('type','not in', 'tile')]}" |
|||
widget="color"/> |
|||
<field name="val_color" |
|||
attrs="{'invisible': [('type','not in', 'tile')]}" |
|||
widget="color"/> |
|||
<field name="text_color" |
|||
attrs="{'invisible': [('type','not in', 'tile')]}" |
|||
widget="color"/> |
|||
</group> |
|||
</group> |
|||
</sheet> |
|||
</form> |
|||
</field> |
|||
</record> |
|||
<!--Tree view of the dashboard block--> |
|||
<record id="dashboard_block_view_tree" model="ir.ui.view"> |
|||
<field name="name">dashboard.block.view.tree</field> |
|||
<field name="model">dashboard.block</field> |
|||
<field name="arch" type="xml"> |
|||
<tree> |
|||
<field name="name"/> |
|||
<field name="model_id"/> |
|||
<field name="type"/> |
|||
</tree> |
|||
</field> |
|||
</record> |
|||
<!-- Action specified for the dashboard block--> |
|||
<record id="dashboard_block_action" model="ir.actions.act_window"> |
|||
<field name="name">Dashboard Block</field> |
|||
<field name="type">ir.actions.act_window</field> |
|||
<field name="res_model">dashboard.block</field> |
|||
<field name="view_mode">tree,form</field> |
|||
<field name="context">{'default_edit_mode' : True}</field> |
|||
</record> |
|||
</odoo> |