Browse Source

July 22: [FIX] Bug Fixed 'odoo_dynamic_dashboard'

pull/331/head
RisvanaCybro 9 months ago
parent
commit
98867a750b
  1. 25
      odoo_dynamic_dashboard/README.rst
  2. 3
      odoo_dynamic_dashboard/__init__.py
  3. 34
      odoo_dynamic_dashboard/__manifest__.py
  4. 41
      odoo_dynamic_dashboard/controllers/odoo_dynamic_dashboard.py
  5. 12
      odoo_dynamic_dashboard/data/dashboard_theme_data.xml
  6. 12
      odoo_dynamic_dashboard/doc/RELEASE_NOTES.md
  7. 2
      odoo_dynamic_dashboard/models/__init__.py
  8. 174
      odoo_dynamic_dashboard/models/dashboard_block.py
  9. 93
      odoo_dynamic_dashboard/models/dashboard_menu.py
  10. 53
      odoo_dynamic_dashboard/models/dashboard_theme.py
  11. 45
      odoo_dynamic_dashboard/models/domain_to_sql.py
  12. 3
      odoo_dynamic_dashboard/security/ir.model.access.csv
  13. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/1.png
  14. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/10.png
  15. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/11.png
  16. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/2.png
  17. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/3.png
  18. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/4.png
  19. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/5.png
  20. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/6.png
  21. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/7.png
  22. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/8.png
  23. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/9.png
  24. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/Tiles.png
  25. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/additems.png
  26. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/blocks.png
  27. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/charts.png
  28. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/darkmode.png
  29. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/dashboard_create.png
  30. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/delete.png
  31. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/editlayout.png
  32. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/email.png
  33. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/email_report.png
  34. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/hero.gif
  35. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/mainpage.png
  36. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/newbar.png
  37. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/newtile.png
  38. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/pdf.png
  39. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/pdf_and_mail.png
  40. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/redirect.png
  41. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/sales_dashboard.png
  42. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/savetypes.png
  43. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/themes.png
  44. BIN
      odoo_dynamic_dashboard/static/description/assets/screenshots/themeselect.png
  45. 487
      odoo_dynamic_dashboard/static/description/index.html
  46. 4
      odoo_dynamic_dashboard/static/lib/js/interactjs.js
  47. 344
      odoo_dynamic_dashboard/static/src/css/dynamic_dashboard.css
  48. 54
      odoo_dynamic_dashboard/static/src/js/DynamicDashboard.js
  49. 82
      odoo_dynamic_dashboard/static/src/js/DynamicDashboardChart.js
  50. 52
      odoo_dynamic_dashboard/static/src/js/DynamicDashboardTile.js
  51. 391
      odoo_dynamic_dashboard/static/src/js/dynamic_dashboard.js
  52. 215
      odoo_dynamic_dashboard/static/src/js/dynamic_dashboard_chart.js
  53. 89
      odoo_dynamic_dashboard/static/src/js/dynamic_dashboard_tile.js
  54. 208
      odoo_dynamic_dashboard/static/src/scss/dynamic_dashboard.scss
  55. 109
      odoo_dynamic_dashboard/static/src/scss/style.scss
  56. 128
      odoo_dynamic_dashboard/static/src/xml/dynamic_dashboard_template.xml
  57. 46
      odoo_dynamic_dashboard/views/dashboard_menu_view.xml
  58. 66
      odoo_dynamic_dashboard/views/dashboard_menu_views.xml
  59. 56
      odoo_dynamic_dashboard/views/dashboard_theme_views.xml
  60. 13
      odoo_dynamic_dashboard/views/dashboard_view.xml
  61. 14
      odoo_dynamic_dashboard/views/dashboard_views.xml
  62. 56
      odoo_dynamic_dashboard/views/dynamic_block_views.xml
  63. 13
      odoo_dynamic_dashboard/wizard/__init__.py
  64. 75
      odoo_dynamic_dashboard/wizard/dashboard_mail.py
  65. 22
      odoo_dynamic_dashboard/wizard/dashboard_mail_views.xml

25
odoo_dynamic_dashboard/README.rst

@ -3,33 +3,32 @@
:alt: License: AGPL-3
Odoo Dynamic Dashboard
======================
==========================
* Dynamically Arrange the dashboard to get the information that are relevant to your business, department, or a specific process or need.
Configuration
=============
* No additional configurations needed.
- No configuration needed
License
=======
Affero General Public License v3.0 (AGPL v3)
(https://www.gnu.org/licenses/agpl-3.0-standalone.html)
Company
-------
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__
License
-------
Affero General Public License, Version 3 (AGPL-3).
(https://www.gnu.org/licenses/agpl-3.0-standalone.html)
Credits
-------
* Developers : Irfan, Afras,
(V16) Amal Prasad,
(V17) Arjun S,
* Developers: (V17) Arjun S,
(V16) Robin, Afra MP
* Contact: odoo@cybrosys.com
Contacts
--------
* Mail Contact : odoo@cybrosys.com
* Website : https://cybrosys.com
* Website : http://www.cybrosys.com
Bug Tracker
-----------
@ -39,10 +38,8 @@ 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 `Our Website <https://cybrosys.com/>`__
For support and more information, please visit https://www.cybrosys.com
Further information
===================

3
odoo_dynamic_dashboard/__init__.py

@ -19,5 +19,6 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from . import models
from . import controllers
from . import models
from . import wizard

34
odoo_dynamic_dashboard/__manifest__.py

@ -21,34 +21,42 @@
#############################################################################
{
'name': "Odoo Dynamic Dashboard",
'version': '17.0.1.0.0',
'category': 'Extra Tools ',
'summary': """Odoo Dynamic Dashboard, Dynamic Dashboard, Odoo Dashboard, Dynamic Dashbaord, AI Dashboard, Odoo17 Dashboard, Dashboard, Odoo17, Configurable Dashboard""",
'description': """Create Configurable Dashboard Dynamically to get the
information that are relevant to your business, department,
or a specific process or need, Dynamic Dashboard, Dashboard ,
Dashboard Odoo""",
'version': '17.0.2.0.0',
'category': 'Productivity',
'summary': """Create Configurable Dashboards Easily""",
'description': """Create Configurable Odoo Dynamic Dashboard to get the
information that are relevant to your business, department, or a specific
process or need""",
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions',
'website': 'https://www.cybrosys.com',
'website': "https://www.cybrosys.com",
'depends': ['web'],
'data': [
'security/ir.model.access.csv',
'views/dashboard_view.xml',
'views/dashboard_menu_view.xml',
'views/dynamic_block_view.xml',
'data/dashboard_theme_data.xml',
'views/dashboard_views.xml',
'views/dynamic_block_views.xml',
'views/dashboard_menu_views.xml',
'views/dashboard_theme_views.xml',
'wizard/dashboard_mail_views.xml',
],
'assets': {
'web.assets_backend': [
'odoo_dynamic_dashboard/static/src/js/**/*.js',
'odoo_dynamic_dashboard/static/src/css/**/*.css',
'odoo_dynamic_dashboard/static/src/scss/**/*.scss',
'odoo_dynamic_dashboard/static/src/js/**/*.js',
'odoo_dynamic_dashboard/static/src/xml/**/*.xml',
'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css',
'odoo_dynamic_dashboard/static/lib/js/interactjs.js',
],
},
'images': ['static/description/banner.png'],
'images': ['static/description/banner.jpg'],
'license': "AGPL-3",
'installable': True,
'auto_install': False,
'application': True,
}

41
odoo_dynamic_dashboard/controllers/odoo_dynamic_dashboard.py

@ -24,38 +24,11 @@ from odoo.http import request
class DynamicDashboard(http.Controller):
"""
This is the class DynamicDashboard which is the subclass of the class
http.Controller
"""
"""Class to search and filter values in dashboard"""
@http.route('/create/tile', type='json', auth='user')
def tile_creation(self, **kw):
"""This is the method to create the tile when create on the button
ADD BLOCK"""
tile_type = kw.get('type')
action_id = kw.get('action_id')
request.env['dashboard.block'].get_dashboard_vals(action_id)
tile_id = request.env['dashboard.block'].sudo().create({
'name': 'New Block',
'type': tile_type,
'tile_color': '#1f6abb',
'text_color': '#FFFFFF',
'fa_icon': 'fa fa-money',
'fa_color': '#132e45',
'edit_mode': True,
'client_action': int(action_id),
})
return {'id': tile_id.id, 'name': tile_id.name, 'type': tile_type, 'icon': 'fa fa-money',
'color': '#1f6abb',
'tile_color': '#1f6abb',
'text_color': '#FFFFFF',
'icon_color': '#1f6abb'}
@http.route('/get/values', type='json', auth='user')
def get_value(self, **kw):
"""This is the method get_value which will get the records inside the
tile"""
action_id = kw.get('action_id')
datas = request.env['dashboard.block'].get_dashboard_vals(action_id)
return datas
@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

12
odoo_dynamic_dashboard/data/dashboard_theme_data.xml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<!-- Demo Dashboard Theme -->
<record id="demo_theme" model="dashboard.theme">
<field name="name">Demo</field>
<field name="color_x">#4158D0</field>
<field name="color_y">#C850C0</field>
<field name="color_z">#FFCC70</field>
</record>
</data>
</odoo>

12
odoo_dynamic_dashboard/doc/RELEASE_NOTES.md

@ -1,7 +1,13 @@
## Module <odoo_dynamic_dashboard>
#### 02.03.2024
#### 18.05.2024
#### Version 17.0.1.0.0
#### ADD
##### ADD
- Initial commit for Odoo Dynamic Dashboard
- Initial Commit for Odoo Dynamic Dashboard
## Module <odoo_dynamic_dashboard>
#### 20.07.2024
#### Version 17.0.2.0.0
##### UPDT
- New Features has been Added.

2
odoo_dynamic_dashboard/models/__init__.py

@ -20,6 +20,6 @@
#
#############################################################################
from . import dashboard_block
from . import dashboard_block_line
from . import dashboard_menu
from . import dashboard_theme
from . import domain_to_sql

174
odoo_dynamic_dashboard/models/dashboard_block.py

@ -19,140 +19,164 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import models, fields, _
from odoo.exceptions import ValidationError
from odoo.osv import expression
from ast import literal_eval
from odoo import api, fields, models
from odoo.osv import expression
class DashboardBlock(models.Model):
"""Creates the model Dashboard Blocks"""
"""Class is used to create charts and tiles in dashboard"""
_name = "dashboard.block"
_description = "Dashboard Blocks"
_description = "Dashboard Block"
def get_default_action(self):
"""This is the method get_default_action which will return the default
action id."""
"""Function to get values from dashboard if action_id is true return
id else return false"""
action_id = self.env.ref(
'odoo_dynamic_dashboard.dynamic_dashboard_action')
'odoo_dynamic_dashboard.dashboard_view_action')
if action_id:
return action_id.id
return False
name = fields.Char(string="Name", help='Name of the block')
field_id = fields.Many2one('ir.model.fields', string='Measured Field',
domain="[('store', '=', True), ('model_id', '=', model_id), ('ttype', 'in', ['float','integer','monetary'])]",
help='Measured field for the block')
fa_icon = fields.Char(string="Icon", help='Icon for the block')
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="Size of the graph")
fa_icon = fields.Char(string="Icon", help="Add icon for tile")
operation = fields.Selection(
selection=[("sum", "Sum"), ("avg", "Average"), ("count", "Count")],
string="Operation",
help='Tile Operation that needs to bring values for tile')
help='Tile Operation that needs to bring values for tile',
required=True)
graph_type = fields.Selection(
selection=[("bar", "Bar"), ("radar", "Radar"), ("pie", "Pie"),
("line", "Line"), ("doughnut", "Doughnut")],
("polarArea", "polarArea"), ("line", "Line"),
("doughnut", "Doughnut")],
string="Chart Type", help='Type of Chart')
measured_field = fields.Many2one("ir.model.fields", string="Measured Field",
help='Measure field for the chart')
client_action = fields.Many2one('ir.actions.client',
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,
string="Client Action",
help='Client Action for the dashboard '
'block')
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="X-axis for the chart")
y_axis = fields.Char(string="Y-Axis", help="Y-axis for the chart")
group_by = fields.Many2one("ir.model.fields", store=True,
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")
height = fields.Char(string="Height ", help="Height of the block")
width = fields.Char(string="Width", help="Width of the block")
translate_x = fields.Char(string="Translate_X",
help="x value for the style transform translate")
translate_y = fields.Char(string="Translate_Y",
help="y value for the style transform translate")
data_x = fields.Char(string="Data_X", help="Data x value for resize")
data_y = fields.Char(string="Data_Y", help="Data y value for resize")
group_by_id = fields.Many2one("ir.model.fields", store=True,
string="Group by(Y-Axis)",
help='Field value for Y-Axis',
domain="[('store', '=', True)]")
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='Filter for Tile')
filter = fields.Char(string="Filter", help="Add filter")
model_id = fields.Many2one('ir.model', string='Model',
help='Model for Tile')
model_name = fields.Char(related='model_id.model', readonly=True,
string="Model Name", help='Model Name of Tile')
filter_by = fields.Many2one("ir.model.fields", string=" Filter By",
help="Filter By for Tile")
filter_values = fields.Char(string="Filter Values",
help="Filter Values for tiles accordingly")
sequence = fields.Integer(string="Sequence",
help="sequence of the dashboard")
edit_mode = fields.Boolean(default=False, invisible=True,
string="Edit Mode", help="Edit mode of the tile")
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)
@api.onchange('model_id')
def _onchange_model_id(self):
if self.operation or self.measured_field_id:
self.operation = False
self.measured_field_id = False
def get_dashboard_vals(self, action_id):
"""Dashboard block values"""
def get_dashboard_vals(self, action_id, start_date=None, end_date=None):
"""Fetch block values from js and create chart"""
block_id = []
for rec in self.env['dashboard.block'].sudo().search(
[('client_action', '=', int(action_id))]):
[('client_action_id', '=', int(action_id))]):
if rec.filter is False:
rec.filter = "[]"
filter_list = literal_eval(rec.filter)
filter_list = [filter_item for filter_item in filter_list if not (
isinstance(filter_item, tuple) and filter_item[
0] == 'create_date')]
vals = {
'id': rec.id,
'name': rec.name,
'type': rec.type,
'graph_type': rec.graph_type,
'icon': rec.fa_icon,
'cols': rec.graph_size,
'color': rec.tile_color if rec.tile_color else '#1f6abb;',
'text_color': rec.text_color if rec.text_color else '#FFFFFF;',
'icon_color': rec.fa_color if rec.fa_color else '#1f6abb;',
'tile_color': rec.tile_color if rec.tile_color else '#FFFFFF;',
rec.filter = repr(filter_list)
vals = {'id': rec.id, 'name': rec.name, 'type': rec.type,
'graph_type': rec.graph_type, 'icon': rec.fa_icon,
'model_name': rec.model_name,
'measured_field': rec.measured_field.field_description if rec.measured_field else None,
'y_field': rec.measured_field.name,
'x_field': rec.group_by.name,
'operation': rec.operation,
'domain': filter_list
'color': f'background-color: {rec.tile_color};' if rec.tile_color else '#1f6abb;',
'text_color': f'color: {rec.text_color};' if rec.text_color else '#FFFFFF;',
'val_color': f'color: {rec.val_color};' if rec.val_color else '#FFFFFF;',
'icon_color': f'color: {rec.tile_color};' if rec.tile_color else '#1f6abb;',
'height': rec.height,
'width': rec.width,
'translate_x': rec.translate_x,
'translate_y': rec.translate_y,
'data_x': rec.data_x,
'data_y': rec.data_y,
'domain': filter_list,
}
domain = []
if rec.filter:
domain = expression.AND([literal_eval(rec.filter)])
if rec.model_name:
if rec.type == 'graph':
query = self.env[rec.model_name].get_query(domain,
self._cr.execute(self.env[rec.model_name].get_query(domain,
rec.operation,
rec.measured_field,
group_by=rec.group_by)
try:
self._cr.execute(query)
except Exception as exc:
raise ValidationError(
_(f"Could'nt fetch data try another group by field for {rec.name} block")) from exc
rec.measured_field_id,
start_date,
end_date,
group_by=rec.group_by_id))
records = self._cr.dictfetchall()
x_axis = []
for record in records:
x_axis.append(record.get(rec.group_by.name))
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:
query = self.env[rec.model_name].get_query(domain,
self._cr.execute(self.env[rec.model_name].get_query(domain,
rec.operation,
rec.measured_field)
self._cr.execute(query)
rec.measured_field_id,
start_date,
end_date))
records = self._cr.dictfetchall()
magnitude = 0
total = records[0].get('value')
while abs(total) >= 1000:
magnitude += 1
total /= 1000.0
val = f'{total:.2f}{" KMGTP"[magnitude]}' if magnitude else f'{total:.2f}'
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, grid_data_list):
"""Function fetch edited values while edit layout of the chart or tile
and save values in a database"""
for data in grid_data_list:
block = self.browse(int(data['id']))
if data.get('data-x'):
block.write({
'translate_x': f"{data['data-x']}px",
'translate_y': f"{data['data-y']}px",
'data_x': data['data-x'],
'data_y': data['data-y'],
})
if data.get('height'):
block.write({
'height': f"{data['height']}px",
'width': f"{data['width']}px",
})
return True

93
odoo_dynamic_dashboard/models/dashboard_menu.py

@ -23,77 +23,58 @@ from odoo import api, fields, models
class DashboardMenu(models.Model):
"""
This is the class DashboardMenu which is the subclass of the class Model
which is here used to create the model dashboard.menu.
"""
"""Class to create new dashboard menu"""
_name = "dashboard.menu"
_description = "Dashboard Menu"
name = fields.Char(string="Name", help="Name of the dashboard")
parent_id = fields.Many2one('ir.ui.menu', string="Menu",
help="Parent of the dashboard")
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='parent_id.groups_id',
help="User need to be at least in one "
"of these groups to see the menu")
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 Related "
"to the dashboard")
menu_id = fields.Many2one('ir.ui.menu', string="Created Menu",
help="Created menu")
string="Client Action",
help="Client action of the "
"corresponding dashboard menu")
@api.model
def create(self, vals):
"""
Summary:
This is the create function of the model DashboardMenu which is
triggered when creating a new record in this model.
Args:
vals:
The values when the user creating a new record.
Returns:
res:
Returns the created record at the end
"""
values = {
"""Function to create new dashboard menu"""
action_id = self.env['ir.actions.client'].create({
'name': vals['name'],
'tag': 'owl.dynamic_dashboard',
}
action_id = self.env['ir.actions.client'].create(values)
'tag': 'OdooDynamicDashboard',
})
vals['client_action_id'] = action_id.id
menu_id = self.env['ir.ui.menu'].create({
self.env['ir.ui.menu'].create({
'name': vals['name'],
'parent_id': vals['parent_id'],
'action': f'ir.actions.client,{action_id.id}'
'parent_id': vals['menu_id'],
'action': 'ir.actions.client,%d' % (action_id.id,)
})
res = super(DashboardMenu, self).create(vals)
res.menu_id = menu_id.id
return res
return super(DashboardMenu, self).create(vals)
def write(self, vals):
"""
Summary:
This is the write function of the model DashboardMenu which is
triggered when changing any value in the corresponding record.
Args:
vals:
The values when the user creating a editing an record.
Returns:
Returns the updated record at the end
"""
if self.menu_id:
self.menu_id.update(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):
"""
Summary:
This is the unlink function of the model DashboardMenu which is
triggered when unlinking any record in this model.
Returns:
Returns the record delete.
"""
self.menu_id.unlink()
"""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()

53
odoo_dynamic_dashboard/models/dashboard_theme.py

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.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 DashboardTheme(models.Model):
_name = 'dashboard.theme'
_description = 'Dashboard Theme'
name = fields.Char(string='Theme Name', help='Name of the theme')
color_x = fields.Char(string='Color X', help='Select the color_x for theme',
default='#4158D0')
color_y = fields.Char(string='Color Y', help='Select the color_y for theme',
default='#C850C0')
color_z = fields.Char(string='Color Z', help='Select the color_z for theme',
default='#FFCC70')
body = fields.Html(string='Body', help='Preview of the theme will be shown')
style = fields.Char(string='Style',
help='It store the style of the gradient')
@api.constrains('name', 'color_x', 'color_y', 'color_z')
def save_record(self):
"""
Function for saving the datas including body and style
"""
self.body = f"<div style='width:300px; height:300px;background-image: linear-gradient(50deg, {self.color_x} 0%, {self.color_y} 46%, {self.color_z} 100%);'/>"
self.style = f"background-image: linear-gradient(50deg, {self.color_x} 0%, {self.color_y} 46%, {self.color_z} 100%);"
def get_records(self):
"""
Function for returning all records with fields name and style
"""
records = self.search_read([], ['name', 'style'])
return records

45
odoo_dynamic_dashboard/models/domain_to_sql.py

@ -22,13 +22,15 @@
from odoo import models
def get_query(self, args, operation, field, group_by=False, apply_ir_rules=False):
"""Dashboard block Query Creation"""
def get_query(self, args, operation, field, start_date=None, end_date=None,
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)
data = 'COALESCE(%s("%s".%s),0) AS value' % (
operation.upper(), self._table, field.name)
join = ''
group_by_str = ''
if group_by:
@ -37,31 +39,36 @@ def get_query(self, args, operation, field, group_by=False, apply_ir_rules=False
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)
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))
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))
if start_date and start_date != 'null':
start_date_query = f' AND ({from_clause}."create_date" >= \'{start_date}\')'
else:
operator = '='
company = self.env.companies.ids[0]
if where_str == '':
add = ' where'
start_date_query = ''
if end_date and end_date != 'null':
end_date_query = f' AND ({from_clause}."create_date" <= \'{end_date}\')'
else:
add = ' and'
multicompany_condition = '%s "%s".company_id %s %s' % (add, self._table, operator, company)
end_date_query = ''
query_str = 'SELECT %s FROM ' % data + from_clause + join + where_str + start_date_query + end_date_query + group_by_str
def format_param(x):
if not isinstance(x, tuple):
return "'" + str(x) + "'"
elif isinstance(x, tuple) and len(x) == 1:
return "(" + str(x[0]) + ")"
else:
multicompany_condition = ''
return str(x)
exact_query = query_str % tuple(map(format_param, where_clause_params))
return exact_query
query_str = 'SELECT %s FROM ' % data + from_clause + join + where_str + multicompany_condition + group_by_str
where_clause_params = map(lambda x: "'" + str(x) + "'", where_clause_params)
return query_str % tuple(where_clause_params)
models.BaseModel.get_query = get_query

3
odoo_dynamic_dashboard/security/ir.model.access.csv

@ -1,4 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_dashboard_block,access.dashboard.block,model_dashboard_block,base.group_user,1,1,1,1
access_dashboard_menu,access.dashboard.menu,model_dashboard_menu,base.group_user,1,1,1,1
access_dashboard_block_line,access.dashboard.block.line,model_dashboard_block_line,base.group_user,1,1,1,1
access_dashboard_theme,access.dashboard.theme,model_dashboard_theme,base.group_user,1,1,1,1
access_dashboard_mail,access.dashboard.mail,model_dashboard_mail,base.group_user,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_dashboard_block access.dashboard.block model_dashboard_block base.group_user 1 1 1 1
3 access_dashboard_menu access.dashboard.menu model_dashboard_menu base.group_user 1 1 1 1
4 access_dashboard_block_line access_dashboard_theme access.dashboard.block.line access.dashboard.theme model_dashboard_block_line model_dashboard_theme base.group_user 1 1 1 1
5 access_dashboard_mail access.dashboard.mail model_dashboard_mail base.group_user 1 1 1 1

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/10.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/11.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/5.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/6.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/7.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/8.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/Tiles.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/additems.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/blocks.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/charts.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/darkmode.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/dashboard_create.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/delete.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/editlayout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/email.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/email_report.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/hero.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 370 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/mainpage.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/newbar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/newtile.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/pdf.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/pdf_and_mail.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/redirect.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/sales_dashboard.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/savetypes.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/themes.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
odoo_dynamic_dashboard/static/description/assets/screenshots/themeselect.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

487
odoo_dynamic_dashboard/static/description/index.html

@ -7,12 +7,9 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N"
crossorigin="anonymous">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<style>
.nav-tabs li .active {
background-color: #a79822;
@ -46,24 +43,6 @@
.collapsed:after {
content: '\002B';
}
</style>
<script>
var acc = document.getElementsByClassName("accordion");
@ -79,33 +58,14 @@
}
});
}
</script>
</head>
<body>
<!-- HERO -->
<div class="container" style="width:84vw">
<!-- HERO -->
<div class="container" style="width:84vw">
<!--Slide-->
<div class="row"
style="padding: 4rem 2.5rem 0 !important; background-color: #fff !important;">
<div class="row" style="padding: 4rem 2.5rem 0 !important; background-color: #fff !important;">
<div class="col-lg-12 d-flex flex-column align-items-center">
<h1 class="text-center text-uppercase"
style="font-family: Montserrat, 'sans-serif' !important; font-size: 67px !important; color: #5154a5; font-weight: 900 !important;">
@ -113,21 +73,19 @@
</h1>
<p class="my-1 text-center text-uppercase"
style="letter-spacing: 4px !important; color: #74788D !important;">
Advanced Dashboard Module For Odoo17</p>
This Module Helps To Create Configurable Dashboards Easily.</p>
</div>
<div class="col-lg-12 d-flex flex-column justify-content-center">
<p class="my-1 text-center"
style="font-family: Montserrat, 'sans-serif' !important; color: #212121 !important;">
Dynamically Arrange the dashboard to get the information that
are relevant to your business, department, or a specific process
or need.
Create Configurable Odoo Dynamic Dashboard to get the
information that are relevant to your business, department, or a specific
process or need.
</p>
</div>
<div class="col-lg-12 d-flex justify-content-center align-items-center"
style="margin: 2rem 0;">
<img src="./assets/cybro-odoo.png" width="100%" height="auto"
style="width:50%;" class="img-responsive">
<div class="col-lg-12 d-flex justify-content-center align-items-center" style="margin: 2rem 0;">
<img src="./assets/cybro-odoo.png" width="100%" height="auto" style="width:50%;" class="img-responsive">
</div>
@ -135,25 +93,19 @@
<a href="mailto:odoo@cybrosys.com" target="_blank"
style="background-color:#5154a5; border-radius:35px; font-family:Montserrat; display:inline-block; padding:7px 33px; border:1px solid #5154a5"
class="mx-1 mb-2 deep-1 deep_hover">
<img class="img" style="width:30px"
src="assets/icons/support.png">
<span class="pl-2"
style="color:#fff; font-size:16px; vertical-align:middle">Email Us</span>
<img class="img" style="width:30px" src="assets/icons/support.png">
<span class="pl-2" style="color:#fff; font-size:16px; vertical-align:middle">Email Us</span>
</a>
<a href="cybroopenerp" target="_blank"
style="background-color:#7f289b; font-family:Montserrat; display:inline-block; padding:7px 33px; border:1px solid #7f289b; border-radius:35px"
class="mx-1 mb-2 deep-1 deep_hover">
<img class="img" style="width:27px"
src="assets/icons/skype.png">
<span class="pl-2"
style="color:#fff; font-size:16px; vertical-align:middle">Skype Us</span>
<img class="img" style="width:27px" src="assets/icons/skype.png">
<span class="pl-2" style="color:#fff; font-size:16px; vertical-align:middle">Skype Us</span>
</a>
</div>
</div>
<div class="col-lg-12 d-flex justify-content-center align-items-center"
style="margin:2rem 0">
<img src="assets/screenshots/hero.gif" width="100%" height="auto"
class="img-responsive">
<div class="col-lg-12 d-flex justify-content-center align-items-center" style="margin:2rem 0">
<img src="assets/screenshots/hero.gif" width="100%" height="auto" class="img-responsive">
</div>
<!-- END OF slide -->
@ -168,51 +120,39 @@
<div class="col-md-6 col-sm-12 mt32">
<div class="container shadow d-flex align-items-center justify-content-center"
style="border-radius: 5px;padding: 33px 0px;min-height: 180px; margin: 15px auto;">
<div class="col-md-3 d-flex align-items-center justify-content-center"
style="float:left">
<img class="img img-responsive"
src="assets/icons/test-1.png">
<div class="col-md-3 d-flex align-items-center justify-content-center" style="float:left">
<img class="img img-responsive" src="assets/icons/test-1.png">
</div>
<div class="col-md-9"
style="padding-left:0; float:left; width:70%">
<div class="col-md-9" style="padding-left:0; float:left; width:70%">
<h3 class="mt16 mb0"
style="font-family:Roboto; font-weight:500; font-size:22px; color: #781d96;">
Dynamic Dashboard for configuring new chart &
tiles</h3>
Easily Create Dynamic Charts And Tiles</h3>
</div>
</div>
</div>
<div class="col-md-6 col-sm-12 mt32">
<div class="container shadow d-flex align-items-center justify-content-center"
style="border-radius: 5px;padding: 33px 0px;min-height: 180px; margin: 15px auto;">
<div class="col-md-3 d-flex align-items-center justify-content-center"
style="float:left">
<img class="img img-responsive"
src="assets/icons/test-1.png">
<div class="col-md-3 d-flex align-items-center justify-content-center" style="float:left">
<img class="img img-responsive" src="assets/icons/test-1.png">
</div>
<div class="col-md-9"
style="padding-left:0; float:left; width:70%">
<div class="col-md-9" style="padding-left:0; float:left; width:70%">
<h3 class="mt16 mb0"
style="font-family:Roboto; font-weight:500; font-size:22px; color: #781d96;">
Adding background and font color for tile,
option for selecting FontAwesome icon</h3>
Create A Dynamic Dashboard Menu In Any Model</h3>
</div>
</div>
</div>
<div class="col-md-6 col-sm-12 mt32">
<div class="container shadow d-flex align-items-center justify-content-center"
style="border-radius: 5px;padding: 33px 0px;min-height: 180px; margin: 15px auto;">
<div class="col-md-3 d-flex align-items-center justify-content-center"
style="float:left">
<img class="img img-responsive"
src="assets/icons/test-1.png">
<div class="col-md-3 d-flex align-items-center justify-content-center" style="float:left">
<img class="img img-responsive" src="assets/icons/test-1.png">
</div>
<div class="col-md-9"
style="padding-left:0; float:left; width:70%">
<div class="col-md-9" style="padding-left:0; float:left; width:70%">
<h3 class="mt16 mb0"
style="font-family:Roboto; font-weight:500; font-size:22px; color: #781d96;">
Chart types: Bar, Line, Radar, Pie, Doughnut,
option to select the chart sizes
Charts Can Export Into Image, PDF, XLSX And CSV
</h3>
</div>
</div>
@ -220,16 +160,13 @@
<div class="col-md-6 col-sm-12 mt32">
<div class="container shadow d-flex align-items-center justify-content-center"
style="border-radius: 5px;padding: 33px 0px;min-height: 180px; margin: 15px auto;">
<div class="col-md-3 d-flex align-items-center justify-content-center"
style="float:left">
<img class="img img-responsive"
src="assets/icons/test-1.png">
<div class="col-md-3 d-flex align-items-center justify-content-center" style="float:left">
<img class="img img-responsive" src="assets/icons/test-1.png">
</div>
<div class="col-md-9"
style="padding-left:0; float:left; width:70%">
<div class="col-md-9" style="padding-left:0; float:left; width:70%">
<h3 class="mt16 mb0"
style="font-family:Roboto; font-weight:500; font-size:22px; color: #781d96;">
Lets you create dynamic menu's easily</h3>
Edit And Configure Charts And Tiles</h3>
</div>
</div>
</div>
@ -257,33 +194,30 @@
<div class="col-sm-12 py-4">
<h5 style="text-indent: 2em; text-align: justify;">
This module helps to create configurable dashboards easily.
The Odoo Dynamic Dashboard module allows you to arrange the
dashboard to display information relevant to your business,
department, or specific processes and needs.
Odoo Dynamic Dashboard module helps to Arrange the dashboard to
get the information that are relevant to your business,
department, or a specific process or need.
</h5>
</div>
</div>
</section>
<!--End of Overview-->
</div>
</div>
<!-- New section -->
<!-- New section -->
<div class="container" style="width:84vw">
<div class="row"
style="padding: 4rem 2.5rem 0 !important; background-color: #fff !important;">
<div class="container" style="width:84vw">
<div class="row" style="padding: 4rem 2.5rem 0 !important; background-color: #fff !important;">
<!-- tabs -->
<div class="col-md-12">
<ul role="tablist" role="tablist"
class="nav nav-tabs justify-content-center d-flex justify-content-center"
data-tabs="tabs"
class="nav nav-tabs justify-content-center d-flex justify-content-center" data-tabs="tabs"
style="border:none; background-color:unset; margin:0 auto">
<li class="nav-item"
style="border-top-right-radius:10px; border-top-left-radius:10px; background-color:#ffe305; margin-right:10px; border:1px solid #ddd; border-bottom:0">
<a href="#Screenshot" data-bs-toggle="tab"
aria-expanded="true" class="show"
<a href="#Screenshot" data-bs-toggle="tab" aria-expanded="true" class="show"
aria-expanded="true" class="show active"
style="font-family:Roboto; text-transform:uppercase; font-weight:600; font-size:15px; letter-spacing:1px; padding:11px 20px; border-top-left-radius:10px; border-top-right-radius:10px; color:#2b2b2b; border:1px solid transparent">
<img src="assets/icons/screenshot.png"
@ -297,8 +231,7 @@
<li class="nav-item"
style="border-top-right-radius:10px; border-top-left-radius:10px; background-color:#ffe305; margin-right:10px; border:1px solid #ddd; border-bottom:0">
<a href="#Features" data-bs-toggle="tab"
aria-expanded="true" class="show"
<a href="#Features" data-bs-toggle="tab" aria-expanded="true" class="show"
style="font-family:Roboto; text-transform:uppercase; font-weight:600; font-size:15px; letter-spacing:1px; padding:11px 20px; border-top-left-radius:10px; border-top-right-radius:10px; color:#2b2b2b; border:1px solid transparent">
<img src="assets/icons/feature.png"
style="width: 100%; width: 25px; margin-right: 10px;">Features
@ -311,8 +244,7 @@
<li class="nav-item"
style="border-top-right-radius:10px; border-top-left-radius:10px; background-color:#ffe305; margin-right:10px; border:1px solid #ddd; border-bottom:0">
<a href="#ReleaseNotes" data-bs-toggle="tab"
aria-expanded="true" class="show"
<a href="#ReleaseNotes" data-bs-toggle="tab" aria-expanded="true" class="show"
style="font-family:Roboto; text-transform:uppercase; font-weight:600; font-size:15px; letter-spacing:1px; padding:11px 20px; border-top-left-radius:10px; border-top-right-radius:10px; color:#2b2b2b; border:1px solid transparent">
<img src="assets/icons/notes.png"
style="width: 100%; width: 25px; margin-right: 10px;">Release
@ -329,10 +261,8 @@
<div class="col-md-12 tab-content ui-front"
style="border-top-right-radius:15px;border-bottom-right-radius:15px;height:auto;">
<div class="tab-pane fade active show" id="Screenshot"
role="tabpanel" aria-labelledby="screenshot">
<div class="row"
style="padding:4rem 2.5rem 0 !important; background-color:#fff !important">
<div class="tab-pane fade active show" id="Screenshot" role="tabpanel" aria-labelledby="screenshot">
<div class="row" style="padding:4rem 2.5rem 0 !important; background-color:#fff !important">
<div class="col-lg-12 d-flex flex-column align-items-center">
<h1 class="text-center text-uppercase"
@ -352,7 +282,7 @@
Odoo Dynamic Dashboard View
</h3>
<div style="margin:2rem">
<img src="assets/screenshots/1.png"
<img src="assets/screenshots/mainpage.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
@ -363,12 +293,10 @@
Dashboard Tiles View
</h3>
<p style="font-family:Roboto ; color:#280135">
The tiled view of the records in Odoo is also
clickable; when you click on a record, you will be
directed to the records tree view.
The tiled view of the records in Odoo is also clickable; when you click on a record, you will be directed to the records tree view.
</p>
<div style="margin:2rem">
<img src="assets/screenshots/2.png"
<img src="assets/screenshots/Tiles.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
@ -379,10 +307,21 @@
Dashboard Charts View
</h3>
<p style="font-family:Roboto ; color:#280135">
The chart view of the records in Odoo is provided in
the dashboard based on the filtering.</p>
The chart view of the records in Odoo is provided in the dashboard based on the filtering.</p>
<div style="margin:2rem">
<img src="assets/screenshots/3.png"
<img src="assets/screenshots/charts.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
<p style="font-family:Roboto ; color:#280135">
Clicking on various icons generates different types of chart data, which can be exported as image, PDF, CSV, and XLSX formats.</p>
<div style="margin:2rem">
<img src="assets/screenshots/savetypes.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
<p style="font-family:Roboto ; color:#280135">
By clicking cross icon you can delete the chart and also the same for tile.</p>
<div style="margin:2rem">
<img src="assets/screenshots/delete.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
@ -390,65 +329,97 @@
<div class="mt-5 col-lg-12 d-flex flex-column align-items-center">
<h3 class="mt16 mb0"
style="mt-3 font-family:Roboto; font-weight:500; font-size:22px; color:#781d96">
Creating New Tile Block
Select Type Of The Item
</h3>
<div style="margin:2rem">
<img src="assets/screenshots/additems.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
<p style="font-family:Roboto ; color:#280135">
When you click the 'Add Block' button on the
dashboard, a new empty tile block will be added. You
can configure this tile block using the button in
the top right corner. You can also use this button
to edit existing tile blocks.</p>
When choosing the "Tile" option, input a name for the tile,
select a model, operation type, and measured field.
Additionally, customize the tile with an icon and color.</p>
<div style="margin:2rem">
<img src="assets/screenshots/4.png"
<img src="assets/screenshots/newtile.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
<p style="font-family:Roboto ; color:#280135">You will
then be redirected to the form view to configure the
tile block. Here, you can add a name to the tile and
specify the model from which records should be
displayed. You can choose an operation—such as sum,
count, or average—to be performed on the data shown
in the field on the dashboard. Additionally, you can
add a measurement field to determine the value
displayed in the tile. There is also an option to
set a filter, which will dictate the data shown in
the tile. Finally, in the tile information section,
you can set the tile type, tile icon, icon color,
tile color, and text color.
<p style="font-family:Roboto ; color:#280135">
When opting for "Charts," provide a name for the chart,
select a model, operation type, and measured field, along with filters.
Additionally, choose the chart type, set its size,
and specify the grouping for the Y-axis.</p>
<div style="margin:2rem">
<img src="assets/screenshots/newbar.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
<div class="mt-5 col-lg-12 d-flex flex-column align-items-center">
<h3 class="mt16 mb0"
style="mt-3 font-family:Roboto; font-weight:500; font-size:22px; color:#781d96">
Edit Layout
</h3>
<p style="font-family:Roboto ; color:#280135">
You can change the position of the item and resize the item.
</p>
<div style="margin:2rem">
<img src="assets/screenshots/5.png"
<img src="assets/screenshots/editlayout.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
<div class="mt-5 col-lg-12 d-flex flex-column align-items-center">
<p style="font-family:Roboto ; color:#280135">
By double-clicking the tile, it will redirect to the corresponding tree view.
</p>
<div style="margin:2rem">
<img src="assets/screenshots/redirect.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
<div class="mt-5 col-lg-12 d-flex flex-column align-items-center">
<h3 class="mt16 mb0"
style="mt-3 font-family:Roboto; font-weight:500; font-size:22px; color:#781d96">
Change The Mode
</h3>
<p style="font-family:Roboto ; color:#280135">
Dark mode
</p>
<div style="margin:2rem">
<img src="assets/screenshots/darkmode.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
<div class="mt-5 col-lg-12 d-flex flex-column align-items-center">
<h3 class="mt16 mb0"
style="mt-3 font-family:Roboto; font-weight:500; font-size:22px; color:#781d96">
Creating New Chart Block
Dashboard Menu
</h3>
<p style="font-family:Roboto ; color:#280135">
Click the 'Add Graph' button to add a new block.
From here, you can click the configuration button in
the top right corner to make the necessary
configurations for the chart.</p>
You can Add dashboard menu in any module. Add a name for the menu and select the parent
menu also
</p>
<div style="margin:2rem">
<img src="assets/screenshots/6.png"
<img src="assets/screenshots/dashboard_create.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
<div style="margin:2rem">
<img src="assets/screenshots/sales_dashboard.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
<div class="mt-5 col-lg-12 d-flex flex-column align-items-center">
<h3 class="mt16 mb0"
style="mt-3 font-family:Roboto; font-weight:500; font-size:22px; color:#781d96">
Dashboard Block
</h3>
<p style="font-family:Roboto ; color:#280135">
From here, you will be redirected to the form view
to configure the chart. You can set the name,
operation, measurement field, and filter, similar to
the tile block. However, in the block information
section for the chart, you have additional options
to set the block type, chart type, chart size, and
the reference field to display in the charts.
You can see all the tiles and charts created in the Dashboard Block menu.
</p>
<div style="margin:2rem">
<img src="assets/screenshots/7.png"
<img src="assets/screenshots/blocks.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
@ -456,16 +427,18 @@
<div class="mt-5 col-lg-12 d-flex flex-column align-items-center">
<h3 class="mt16 mb0"
style="mt-3 font-family:Roboto; font-weight:500; font-size:22px; color:#781d96">
Blocks
Dashboard Theme
</h3>
<p style="font-family:Roboto ; color:#280135">
By clicking on the 'Blocks' menu from the Dashboard
module, you can see the blocks created in the
dashboard. From here, you can configure, delete, or
create new dashboard blocks.
Customize the Dashboard Theme via Configuration > Dashboard Theme.
Then, choose the configured theme for your dashboard.
</p>
<div style="margin:2rem">
<img src="assets/screenshots/8.png"
<img src="assets/screenshots/themes.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
<div style="margin:2rem">
<img src="assets/screenshots/themeselect.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
@ -473,44 +446,46 @@
<div class="mt-5 col-lg-12 d-flex flex-column align-items-center">
<h3 class="mt16 mb0"
style="mt-3 font-family:Roboto; font-weight:500; font-size:22px; color:#781d96">
Dynamic Dashboard
Print PDF
</h3>
<p style="font-family:Roboto ; color:#280135">
From the configuration menu, select 'Dashboards' to
view the dynamic dashboards that have been created.
Here, you can create, remove, or edit dynamic
dashboards. To create a new dynamic dashboard, click
the 'NEW' button.
Print & Mail whole dashboard in PDF format by clicking the PDF or Mail icon.
</p>
<div style="margin:2rem">
<img src="assets/screenshots/9.png"
<img src="assets/screenshots/pdf_and_mail.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
<div style="margin:2rem">
<img src="assets/screenshots/pdf.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
<div class="mt-5 col-lg-12 d-flex flex-column align-items-center">
<h3 class="mt16 mb0"
style="mt-3 font-family:Roboto; font-weight:500; font-size:22px; color:#781d96">
Send Mail
</h3>
<p style="font-family:Roboto ; color:#280135">
This is the form view for configuring the dynamic
dashboard. Here, you can set the name of the
dashboard menu and specify the parent menu under
which this dashboard menu should appear.
Clicking the Mail icon brings up a wizard,
where you can select users and then click the SEND button.
</p>
<div style="margin:2rem">
<img src="assets/screenshots/10.png"
<img src="assets/screenshots/email.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
<p style="font-family:Roboto ; color:#280135">
From the parent menu, you can see that the new
dynamic dashboard has been created as specified in
the configuration.
Demo Mail
</p>
<div style="margin:2rem">
<img src="assets/screenshots/11.png"
<img src="assets/screenshots/email_report.png"
class="col-lg-12 d-flex flex-column align-items-center">
</div>
</div>
</div>
</div>
<div class="tab-pane fade " id="Features" role="tabpanel"
aria-labelledby="features">
<div class="tab-pane fade " id="Features" role="tabpanel" aria-labelledby="features">
<section class="oe_container pt16 mt16 pb-5">
<div class="row ">
@ -528,14 +503,12 @@
</h1>
<p class="my-1 text-center text-uppercase"
style="letter-spacing:4px !important; color:#74788D !important">
Comprehensive Features of ADVANCED DYNAMIC
DASHBOARD</p>
Comprehensive Features of ODOO DYNAMIC DASHBOARD</p>
</div>
<div class="row">
<div class="col-md-4 col-sm-6 mt48"
style="padding-bottom: 10px">
<div class="col-md-4 col-sm-6 mt48" style="padding-bottom: 10px">
<div class="bg-white shadow card"
style="border-radius:10px; padding:5px; height: 100%">
<div class="text-center d-flex align-items-center card-body"
@ -548,16 +521,14 @@
<h5 style="margin-bottom: 0px !important; color:#0e1e40; font-size: 18px; text-transform:capitalize"
class="mb2 text-left mb-0">
Edit And Configure Charts And
Tiles
Edit And Configure Charts And Tiles.
</h5>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6 mt48"
style="padding-bottom: 10px">
<div class="col-md-4 col-sm-6 mt48" style="padding-bottom: 10px">
<div class="bg-white shadow card"
style="border-radius:10px; padding:5px; height: 100%">
<div class="text-center d-flex align-items-center card-body"
@ -570,16 +541,14 @@
<h5 style="margin-bottom: 0px !important; color:#0e1e40; font-size: 18px; text-transform:capitalize"
class="mb2 text-left mb-0">
Easily Create Dynamic Charts And
Tiles
Easily Create Dynamic Charts And Tiles.
</h5>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6 mt48"
style="padding-bottom: 10px">
<div class="col-md-4 col-sm-6 mt48" style="padding-bottom: 10px">
<div class="bg-white shadow card"
style="border-radius:10px; padding:5px; height: 100%">
<div class="text-center d-flex align-items-center card-body"
@ -592,15 +561,14 @@
<h5 style="margin-bottom: 0px !important; color:#0e1e40; font-size: 18px; text-transform:capitalize"
class="mb2 text-left mb-0">
Multiple way to arrange your dashboard
Available Dark Mode And Light Mode.
</h5>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6 mt48"
style="padding-bottom: 10px">
<div class="col-md-4 col-sm-6 mt48" style="padding-bottom: 10px">
<div class="bg-white shadow card"
style="border-radius:10px; padding:5px; height: 100%">
<div class="text-center d-flex align-items-center card-body"
@ -613,16 +581,14 @@
<h5 style="margin-bottom: 0px !important; color:#0e1e40; font-size: 18px; text-transform:capitalize"
class="mb2 text-left mb-0">
Create a Dynamic
Dashboard Menu In Any Model
Create A Odoo Dynamic Dashboard Menu In Any Model.
</h5>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6 mt48"
style="padding-bottom: 10px">
<div class="col-md-4 col-sm-6 mt48" style="padding-bottom: 10px">
<div class="bg-white shadow card"
style="border-radius:10px; padding:5px; height: 100%">
<div class="text-center d-flex align-items-center card-body"
@ -635,15 +601,14 @@
<h5 style="margin-bottom: 0px !important; color:#0e1e40; font-size: 18px; text-transform:capitalize"
class="mb2 text-left mb-0">
Charts Configuration
Charts Can Export Into Image, PDF,XLSX And CSV.
</h5>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6 mt48"
style="padding-bottom: 10px">
<div class="col-md-4 col-sm-6 mt48" style="padding-bottom: 10px">
<div class="bg-white shadow card"
style="border-radius:10px; padding:5px; height: 100%">
<div class="text-center d-flex align-items-center card-body"
@ -656,7 +621,7 @@
<h5 style="margin-bottom: 0px !important; color:#0e1e40; font-size: 18px; text-transform:capitalize"
class="mb2 text-left mb-0">
Tiles Configuration
Drag And Resize The Chart And Tile.
</h5>
</div>
</div>
@ -668,10 +633,8 @@
</section>
</div>
<div class="tab-pane fade " id="ReleaseNotes" role="tabpanel"
aria-labelledby="video-1">
<div class="row"
style="padding:4rem 2.5rem 0 !important; background-color:#fff !important">
<div class="tab-pane fade " id="ReleaseNotes" role="tabpanel" aria-labelledby="video-1">
<div class="row" style="padding:4rem 2.5rem 0 !important; background-color:#fff !important">
<div class="col-lg-12 d-flex flex-column align-items-center">
<h1 class="text-center text-uppercase"
style="font-family:Montserrat, 'sans-serif' !important; font-size:40px !important; color:#791d97; font-weight:900 !important">
@ -686,9 +649,8 @@
<div class="relese-note w-100 pt-3 pb-2 pl-3 mb-4 pr-3 float-left"
style="border:1px solid #ccc; box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.75);">
<p><b> Version 17.0.1 I<span
style="color:#3363c1; font-size: 14px;"> Released
on : 11th May 2024</span></b>
<p><b> Version 17.0.1 I<span style="color:#3363c1; font-size: 14px;"> Released
on : 18th May 2024</span></b>
</p>
<p> Initial commit for
odoo_dynamic_dashboard </p>
@ -705,7 +667,6 @@
</div>
</div>
<!-- New section -->
<section style="margin-top:140px;">
@ -720,71 +681,57 @@
<!-- The slideshow -->
<div class="carousel-inner" style="padding:30px">
<div class="carousel-item" style="min-height:198.656px">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/17.0/base_accounting_kit/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius:0px"
<img class="img img-responsive center-block" style="border-radius:0px"
src="assets/modules/1.gif">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/17.0/employee_dynamic_fields/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius:0px"
<img class="img img-responsive center-block" style="border-radius:0px"
src="assets/modules/2.jpg">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/17.0/product_barcode/"
target="_blank">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/17.0/product_barcode/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius:0px"
<img class="img img-responsive center-block" style="border-radius:0px"
src="assets/modules/5.png">
</div>
</a>
</div>
</div>
<div class="carousel-item active"
style="min-height:198.656px">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<div class="carousel-item active" style="min-height:198.656px">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/17.0/invoice_format_editor/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius:0px"
<img class="img img-responsive center-block" style="border-radius:0px"
src="assets/modules/3.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/17.0/login_user_detail/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius:0px"
<img class="img img-responsive center-block" style="border-radius:0px"
src="assets/modules/4.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/17.0/whatsapp_redirect/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-radius:0px"
<img class="img img-responsive center-block" style="border-radius:0px"
src="assets/modules/6.jpg">
</div>
</a>
@ -792,16 +739,12 @@
</div>
</div>
<!-- Left and right controls -->
<a class="carousel-control-prev" href="#demo1"
data-slide="prev" style="width:35px; color:#000">
<span class="carousel-control-prev-icon"><i
class="fa fa-chevron-left"
<a class="carousel-control-prev" href="#demo1" data-slide="prev" style="width:35px; color:#000">
<span class="carousel-control-prev-icon"><i class="fa fa-chevron-left"
style="font-size:24px"></i></span>
</a> <a class="carousel-control-next" href="#demo1"
data-slide="next"
</a> <a class="carousel-control-next" href="#demo1" data-slide="next"
style="width:35px; color:#000">
<span class="carousel-control-next-icon"><i
class="fa fa-chevron-right"
<span class="carousel-control-next-icon"><i class="fa fa-chevron-right"
style="font-size:24px"></i></span>
</a>
</div>
@ -812,14 +755,12 @@
<section>
<div class="d-flex align-items-center"
style="border-bottom:2px solid #714B67; padding:15px 0px">
<div class="d-flex align-items-center" style="border-bottom:2px solid #714B67; padding:15px 0px">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color:#F5F5F5; border-radius:0px; width:40px; height:40px">
<img src="//apps.odoocdn.com/apps/assets/16.0/export_stockinfo_xls/assets/misc/star.png?aedef96">
</div>
<h2 class="mt-2"
style="font-family:'Montserrat', sans-serif; font-size:24px; font-weight:bold">
<h2 class="mt-2" style="font-family:'Montserrat', sans-serif; font-size:24px; font-weight:bold">
Our Services
</h2>
</div>
@ -1071,12 +1012,10 @@
<hr>
</div>
<div class="col-sm-12 col-md-4">
<div class="d-flex align-items-center"
style="background-color:#F6F8F9; padding:22px">
<div class="d-flex align-items-center" style="background-color:#F6F8F9; padding:22px">
<div class="mr-4 d-flex justify-content-center align-items-center"
style="background-color:#714B67; height:70px; width:70px">
<img src="assets/icons/support.png" height="48"
width="48" style="width:42px; height:42px">
<img src="assets/icons/support.png" height="48" width="48" style="width:42px; height:42px">
</div>
<div>
<h4>Need Help?</h4>
@ -1091,12 +1030,10 @@
</div>
</div>
<div class="col-sm-12 col-md-4">
<div class="d-flex align-items-center"
style="background-color:#F6F8F9; padding:30px">
<div class="d-flex align-items-center" style="background-color:#F6F8F9; padding:30px">
<div class="mr-4 d-flex justify-content-center align-items-center"
style="background-color:#2AC44D; height:70px; width:70px">
<img src="assets/icons/whatsapp.png" height="52"
width="52" style="width:52px; height:52px">
<img src="assets/icons/whatsapp.png" height="52" width="52" style="width:52px; height:52px">
</div>
<div>
<h4>WhatsApp</h4>
@ -1113,12 +1050,10 @@
<div class="col-sm-12 col-md-4">
<div class="d-flex align-items-center"
style="background-color:#F6F8F9; padding:30px">
<div class="d-flex align-items-center" style="background-color:#F6F8F9; padding:30px">
<div class="mr-4 d-flex justify-content-center align-items-center"
style="background-color:#03a9f4; height:70px; width:70px">
<img src="assets/icons/skype.png" height="52" width="52"
style="width:52px; height:52px">
<img src="assets/icons/skype.png" height="52" width="52" style="width:52px; height:52px">
</div>
<div>
<h4>Skype</h4>
@ -1136,27 +1071,23 @@
</div>
<!-- Footer -->
<section class="oe_container"
style="padding: 2rem 3rem 1rem; background-color: #fff !important;">
<div class="row"
style="max-width:1540px; margin: 0 auto; margin-right: 3rem; ">
<section class="oe_container" style="padding: 2rem 3rem 1rem; background-color: #fff !important;">
<div class="row" style="max-width:1540px; margin: 0 auto; margin-right: 3rem; ">
<!-- Logo -->
<div class="col-lg-12 d-flex justify-content-center align-items-center"
style="margin-top: 3rem;">
<img src="https://www.cybrosys.com/images/logo.png"
width="200px" height="auto"/>
<div class="col-lg-12 d-flex justify-content-center align-items-center" style="margin-top: 3rem;">
<img src="https://www.cybrosys.com/images/logo.png" width="200px" height="auto" />
</div>
<!-- End of Logo -->
</div>
</section>
<!-- END OF FOOTER -->
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-Fy6S3B9q64WdZWQUiU+q4/2Lc9npb8tCaSX9FK7E8HnRr0Jz8D6OP9dO5Vg3Q9ct"
crossorigin="anonymous"></script>
</body>

4
odoo_dynamic_dashboard/static/lib/js/interactjs.js

File diff suppressed because one or more lines are too long

344
odoo_dynamic_dashboard/static/src/css/dynamic_dashboard.css

@ -0,0 +1,344 @@
.row {
margin: 1rem;
}
.resize-drag {
border-radius: 8px;
padding: 20px;
margin: 1rem;
color: white;
font-size: 20px;
font-family: sans-serif;
touch-action: none;
box-sizing: border-box;
}
.theme {
position: absolute;
right: 60.5em;
padding-top:inherit;
}
.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;
}
.dashboard_pdf{
position: absolute;
right: 26.5em;
padding-top:inherit;
}
.dashboard_mail{
position: absolute;
right: 24.5em;
padding-top:inherit;
}
.resize-drag {
position: relative !important;
font-size: 100%;
overflow:hidden;
}
h2, h3 {
font-size: 150% !important;
}
.container {
max-width: 100% !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;
}
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;
}
.modal-header .btn-close {
display: none;
}
.modal-title{
margin-inline-end: auto;
}
.theme-text {
font-family: Arial, sans-serif;
font-size: 1em;
opacity: 70%;
}
.search-group {
position: absolute;
right: 1.7em;
}
.block_setting {
position: absolute;
top: 7px;
left: 10px;
}
.block_delete {
position: absolute;
top: 7px;
right: 10px;
}
.tile_edit {
color: #d2d2d2;
}
.block_edit {
color: #3f3f3f;
}
.block_setting, .block_delete, .block_image, .block_pdf, .block_xlsx, .block_csv, .block_export {
display: none;
}
.resize-drag:hover .block_setting,
.resize-drag:hover .block_xlsx,
.resize-drag:hover .block_csv,
.resize-drag:hover .block_image,
.resize-drag:hover .block_pdf,
.resize-drag:hover .block_export,
.resize-drag:hover .block_delete {
display: block;
}
.chart-edit {
position: absolute;
top: 0px;
left: 3px;
font-size: 16px;
}
.chart-image {
position: absolute;
top: 3px;
right: 106px;
font-size: 16px;
}
.chart-pdf {
position: absolute;
top: 3px;
right: 82px;
font-size: 16px;
}
.chart-csv {
position: absolute;
top: 3px;
right: 58px;
font-size: 16px;
}
.chart-xlsx {
position: absolute;
top: 6px;
right: 34px;
font-size: 15px;
}
.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 {
top:10px;
padding: 1.2rem 0 2.6rem 0 !important;
border-bottom: 1px solid #3f3f3f1a !important;
color: #444444;
background-color: #ffffff !important;
}
.theme_icon:hover {
text-shadow: 0px 0px 5px #9388ff;
}
.navbar-style {
margin-top: 1.2em;
border-radius: 0.5em;
}
.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;
}
.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;
}
.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;
border-radius: inherit;
}
#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;
}
}

54
odoo_dynamic_dashboard/static/src/js/DynamicDashboard.js

@ -1,54 +0,0 @@
/** @odoo-module */
import { registry} from '@web/core/registry';
import { DynamicDashboardTile} from './DynamicDashboardTile'
import { DynamicDashboardChart} from './DynamicDashboardChart'
import { useService } from "@web/core/utils/hooks";
const { Component, mount} = owl
export class DynamicDashboard extends Component {
setup(){
this.action = useService("action");
this.rpc = this.env.services.rpc
this.renderDashboard()
}
async renderDashboard() {
const action = this.action
const rpc = this.rpc
await this.rpc('/get/values', {'action_id': this.props.actionId}).then(function(response){
if ($('.o_dynamic_dashboard')[0]){
for (let i = 0; i < response.length; i++) {
if (response[i].type === 'tile'){
mount(DynamicDashboardTile, $('.o_dynamic_tile')[0], { props: {
widget: response[i], doAction: action
}});
}
else{
mount(DynamicDashboardChart, $('.o_dynamic_graph')[0], { props: {
widget: response[i], doAction: action, rpc: rpc
}});
}
}
}
})
}
async _onClick_add_block(e){
var self = this;
var self_props = this.props;
var self_env = self.env;
var type = $(e.target).attr('data-type');
await this.rpc('/create/tile',{'type' : type, 'action_id': self.props.actionId}).then(function(response){
if(response['type'] == 'tile'){
mount(DynamicDashboardTile, $('.o_dynamic_tile')[0], { props: {
widget: response, doAction: self.action
}});
}
else{
mount(DynamicDashboardChart, $('.o_dynamic_graph')[0], { props: {
widget: response, doAction: self.action
}});
}
})
}
}
DynamicDashboard.template = "owl.dynamic_dashboard"
registry.category("actions").add("owl.dynamic_dashboard", DynamicDashboard)

82
odoo_dynamic_dashboard/static/src/js/DynamicDashboardChart.js

@ -1,82 +0,0 @@
/** @odoo-module */
import { registry} from '@web/core/registry';
import { loadJS} from '@web/core/assets';
import { getColor } from "@web/core/colors/colors";
const { Component, xml, onWillStart, useRef, onMounted } = owl
export class DynamicDashboardChart extends Component {
setup() {
this.doAction = this.props.doAction.doAction
this.chartRef = useRef("chart")
onWillStart(async () => {
await loadJS("https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js")
})
onMounted(()=> this.renderChart())
}
renderChart(){
if (this.props.widget.graph_type){
const x_axis = this.props.widget.x_axis
const y_axis = this.props.widget.y_axis
const data = []
for (let i = 0; i < x_axis.length; i++) {
const value = { key: x_axis[i], value: y_axis[i] }
data.push(value);
}
new Chart(
this.chartRef.el,
{
type: this.props.widget.graph_type || 'bar',
data: {
labels: data.map(row => row.key),
datasets: [
{
label: this.props.widget.measured_field,
data: data.map(row => row.value),
backgroundColor: data.map((_, index) => getColor(index)),
hoverOffset : 4
}
]
},
}
);
}
}
async getConfiguration(){
var id = this.props.widget.id
await this.doAction({
type: 'ir.actions.act_window',
res_model: 'dashboard.block',
res_id: id,
view_mode: 'form',
views: [[false, "form"]]
});
}
}
DynamicDashboardChart.template = xml `
<div style="padding-bottom:30px" t-att-class="this.props.widget.cols +' col-4 block'" t-att-data-id="this.props.widget.id">
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<h3><t t-esc="this.props.widget.name"/></h3>
</div>
<div class="col">
<div style="float:right;"><i title="Configuration" class="fa fa-cog block_setting fa-2x cursor-pointer" t-on-click="getConfiguration"/></div>
</div>
</div>
</div>
<div class="card-body" id="in_ex_body_hide">
<div class="row">
<div class="col-md-12 chart_canvas">
<div id="chart_canvas">
<canvas t-ref="chart"/>
</div>
</div>
</div>
</div>
</div>
</div>
`

52
odoo_dynamic_dashboard/static/src/js/DynamicDashboardTile.js

@ -1,52 +0,0 @@
/** @odoo-module */
import { registry} from '@web/core/registry';
const { Component, xml } = owl
export class DynamicDashboardTile extends Component {
setup() {
super.setup(...arguments);
this.action = this.props.doAction
}
async getRecord() {
var model_name = this.props.widget.model_name
if (model_name){
await this.action.doAction({
type: 'ir.actions.act_window',
res_model: model_name,
view_mode: 'tree',
views: [[false, "tree"]],
domain: this.props.widget.domain,
});
}
}
async getConfiguration() {
var id = this.props.widget.id
await this.action.doAction({
type: 'ir.actions.act_window',
res_model: 'dashboard.block',
res_id: id,
view_mode: 'form',
views: [[false, "form"]]
});
}
}
DynamicDashboardTile.template = xml`<div class="col-sm-12 col-md-12 col-lg-3 tile block"
t-att-data-id="this.props.widget.id">
<div draggable="true" t-att-style="'background: ' + this.props.widget.tile_color " class="tile-container d-flex justify-content-around align-items-center position-relative w-100 h-auto my-3">
<a t-on-click="getConfiguration" class="block_setting position-absolute tile-container__setting-icon cursor-pointer">
<i class="fa fa-cog"></i>
</a>
<div t-on-click="getRecord" class="d-flex cursor-pointer">
<div t-att-style="'color: ' + this.props.widget.icon_color " class="tile-container__icon-container bg-white d-flex justify-content-center align-items-center">
<i t-att-class="this.props.widget.icon" aria-hidden="true"></i>
</div>
<div class="tile-container__status-container" t-att-style="'color: ' + this.props.widget.text_color ">
<h2 class="status-container__title" t-att-style="'color: ' + this.props.widget.text_color "><t t-esc="this.props.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="'color: ' + this.props.widget.text_color "><t t-esc="this.props.widget.value"/></h3>
</div>
</div>
</div>
</div>
</div>`

391
odoo_dynamic_dashboard/static/src/js/dynamic_dashboard.js

@ -0,0 +1,391 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { loadJS } from '@web/core/assets';
import { DynamicDashboardTile} from './dynamic_dashboard_tile';
import { DynamicDashboardChart} from './dynamic_dashboard_chart';
import { useService } from "@web/core/utils/hooks";
const { Component, useRef, mount, onWillStart, onMounted} = owl;
export class OdooDynamicDashboard extends Component {
// Setup function to run when the template of the class OdooDynamicDashboard renders
setup() {
this.ThemeSelector = useRef('ThemeSelector');
this.action = useService("action");
this.orm = useService("orm");
this.dialog = useService("dialog");
this.actionId = this.props.actionId
this.rpc = useService("rpc");
onWillStart(async () => {
await loadJS("https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js")
await loadJS("https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js")
await loadJS("https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js")
})
onMounted(()=>{
this.renderDashboard();
})
}
onChangeTheme(){
/* Function for changing color of the theme of the dashboard */
$(".container").attr('style', this.ThemeSelector.el.value + 'min-height:-webkit-fill-available;')
}
ResizeDrag() {
/* Function for resizing and dragging the div resize-drag */
$('.items .resize-drag').each(function(index, element) {
interact(element).resizable({
edges: { left: true, right: true, bottom: true, top: true },
listeners: {
move (event) {
var target = event.target
var x = (parseFloat(target.getAttribute('data-x')) || 0)
var y = (parseFloat(target.getAttribute('data-y')) || 0)
// update the element's style
target.style.width = event.rect.width + 'px'
target.style.height = event.rect.height + 'px'
// translate when resizing from top or left edges
x += event.deltaRect.left
y += event.deltaRect.top
}
},
modifiers: [
// keep the edges inside the parent
interact.modifiers.restrictEdges({
outer: 'parent'
}),
// minimum size
interact.modifiers.restrictSize({
min: { width: 100, height: 50 }
})
],
inertia: true
}).draggable({
listeners: {move: dragMoveListener},
inertia: true,
modifiers: [
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true
})
]
})
function dragMoveListener (event) {
var target = event.target
// keep the dragged position in the data-x/data-y attributes
var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy
// translate the element
target.style.transform = 'translate(' + x + 'px, ' + y + 'px)'
// update the posiion attributes
target.setAttribute('data-x', x)
target.setAttribute('data-y', y)
}
// this function is used later in the resizing
window.dragMoveListener = dragMoveListener
});
}
async renderDashboard(){
/* Function for rendering the dashboard */
var self = this;
$("#save_layout").hide();
await this.orm.call('dashboard.theme', 'get_records', [[]]).then(function (response) {
response.forEach((ev) => {
const options = document.createElement("option");
options.value = ev.style;
options.text = ev.name;
self.ThemeSelector.el.append(options)
});
})
await this.orm.call("dashboard.block", "get_dashboard_vals", [[], this.actionId]).then( function (response){
for (let i = 0; i < response.length; i++) {
if (response[i].type === 'tile'){
mount(DynamicDashboardTile, $('.items')[0], { props: {
widget: response[i], doAction: self.action, dialog:self.dialog, orm: self.orm
}});
}
else{
mount(DynamicDashboardChart, $('.items')[0], { props: {
widget: response[i], doAction: self.action, rpc: self.rpc, dialog:self.dialog, orm: self.orm
}});
}
}
})
}
editLayout(ev) {
/* Function for editing the layout , it enables resizing and dragging functionality */
$('.items .resize-drag').each(function(index, element) {
interact(element).draggable(true)
interact(element).resizable(true)
});
ev.stopPropagation();
ev.preventDefault();
$("#edit_layout").hide();
$("#save_layout").show();
this.ResizeDrag()
}
saveLayout(ev){
/* Function for saving the layout */
var self = this;
ev.stopPropagation();
ev.preventDefault();
$("#edit_layout").show();
$("#save_layout").hide();
var data_list = []
$('.items .resize-drag').each(function(index, element) {
interact(element).draggable(false)
interact(element).resizable(false)
data_list.push({
'id' : element.dataset['id'],
'data-x': element.dataset['x'],
'data-y': element.dataset['y'],
'height': element.clientHeight,
'width': element.clientWidth,
})
});
self.orm.call('dashboard.block','get_save_layout', [[], data_list]).then( function (response){
window.location.reload();
});
}
changeViewMode(ev){
/* Function for changing the mode of the view */
ev.stopPropagation();
ev.preventDefault();
const currentMode = $(".mode").attr("mode");
if (currentMode == "light"){
$('.theme').attr('style','display: none;')
$(".container").attr('style', 'background-color: #383E45;min-height:-webkit-fill-available; !important')
$(".mode").attr("mode", "dark")
$(".bi-moon-stars-fill").attr('class', 'bi bi-cloud-sun-fill view-mode-icon')
$(".bi-cloud-sun-fill").attr('style', 'color:black;margin-left:10px;')
$(".mode").attr('style','display: none !important');
$("#search-input-chart").attr('style', 'background-color: white; !important')
$("#search-button").attr('style', 'background-color: #BB86FC; !important')
$("#dropdownMenuButton").attr('style', 'background-color: #03DAC5;margin-top:-4px; !important')
$("#text_add").attr('style', 'color: black; !important')
$(".date-label").attr('style', 'color: black;font-family:monospace; !important')
$(".block_setting").attr('style', 'color: white; !important')
$(".block_delete").attr('style', 'color: white; !important')
$(".block_image").attr('style', 'color: #03DAC5; !important')
$(".block_pdf").attr('style', 'color: #03DAC5; !important')
$(".block_csv").attr('style', 'color: #03DAC5; !important')
$(".block_xlsx").attr('style', 'color: #03DAC5; !important')
}
else {
$('.theme').attr('style','display: block;')
$(".container").attr('style', this.ThemeSelector.el.value + 'min-height:-webkit-fill-available;')
$(".mode").attr("mode", "light")
$(".bi-cloud-sun-fill").attr('class', 'bi bi-moon-stars-fill view-mode-icon')
$(".view-mode-icon").attr('style', 'color:black;margin-left:10px; !important')
$(".mode").attr('style','display: none !important');
$(".mode").attr('style','color: white !important');
$("#search-input-chart").attr('style', 'background-color: none; !important')
$("#search-button").attr('style', 'background-color: none; !important')
$("#dropdownMenuButton").attr('style', 'background-color: none;margin-top:-4px; !important')
$("#text_add").attr('style', 'color: white; !important')
$(".date-label").attr('style', 'color: black; !important;font-family:monospace; !important')
$(".block_setting").attr('style', 'color: black; !important')
$(".block_delete").attr('style', 'color: black; !important')
$(".block_image").attr('style', 'color: black; !important')
$(".block_pdf").attr('style', 'color: black; !important')
$(".block_csv").attr('style', 'color: black; !important')
$(".block_xlsx").attr('style', 'color: black; !important')
}
}
onClickAdd(event){
/* For enabling the toggle button */
event.stopPropagation();
event.preventDefault();
$(".dropdown-addblock").toggle()
}
onClickAddItem(event){
/* Function for adding tiles and charts */
event.stopPropagation();
event.preventDefault();
self = this;
var type = event.target.getAttribute('data-type');
if (type == 'graph'){
var chart_type = event.target.getAttribute('data-chart_type');
}
if (type == 'tile'){
var randomColor = '#' + ('000000' + Math.floor(Math.random() * 16777216).toString(16)).slice(-6);
this.action.doAction({
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': '155px',
'default_width': '300px',
'default_tile_color': randomColor,
'default_text_color': '#FFFFFF',
'default_val_color': '#F3F3F3',
'default_fa_icon': 'fa fa-bar-chart',
'default_client_action_id': parseInt(self.actionId)
}
})
}
else{
this.action.doAction({
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': '565px',
'default_width': '588px',
'default_graph_type': chart_type,
'default_fa_icon': 'fa fa-bar-chart',
'default_client_action_id': parseInt(self.actionId)
},
})
}
}
dateFilter(){
/* Function for filtering the data based on the creation date */
$(".items").empty();
var start_date = $("#start-date").val();
var end_date = $("#end-date").val();
var self = this;
if (!start_date){
start_date = "null"
}
if (!end_date){
end_date = "null"
}
this.orm.call("dashboard.block", "get_dashboard_vals", [[], this.actionId, start_date, end_date]).then( function (response){
for (let i = 0; i < response.length; i++) {
if (response[i].type === 'tile'){
mount(DynamicDashboardTile, $('.items')[0], { props: {
widget: response[i], doAction: self.action, dialog:self.dialog, orm: self.orm
}});
}
else{
mount(DynamicDashboardChart, $('.items')[0], { props: {
widget: response[i], doAction: self.action, rpc: self.rpc, dialog:self.dialog, orm: self.orm
}});
}
}
})
}
async clickSearch(){
/* Function for searching the blocks with their names */
var input = $("#search-input-chart").val();
await this.rpc('/custom_dashboard/search_input_chart', {'search_input': input}).then(function (response) {
var blocks = $(".items .resize-drag");
blocks.each(function(index, element){
var dataId = $(element).data('id');
if (response.includes(dataId)){
$(element).css("visibility", "visible");
}
else{
$(element).css("visibility", "hidden");
}
})
})
}
showViewMode(ev){
/* Function for showing the mode text */
const currentMode = $(".mode").attr("mode");
if (currentMode == "light"){
$(".mode").text("Dark Mode")
$(".mode").attr('style','display: inline-block !important; color: black !important');
}
else{
$(".mode").text("Light Mode")
$(".mode").attr('style','display: inline-block !important; color: black !important');
}
}
hideViewMode(ev){
/* Function for hiding the mode text */
$(".mode").fadeOut(2000);
}
clearSearch(){
/* Function for clearing the search input */
$("#search-input-chart").val('');
var blocks = $(".items .resize-drag");
blocks.each(function(index, element){
$(element).css("visibility", "visible");
})
}
async printPdf() {
/* Function for printing whole dashboard in pdf format */
var elements = $('.items .resize-drag')
var newElement = document.createElement('div');
newElement.className = 'pdf';
elements.each(function(index, elem){
newElement.appendChild(elem);
});
for (var x=0; x< $(newElement)[0].children.length; x++){
$($(newElement)[0].children[x])[0].style.transform = ""
}
var opt = {
margin: 0.3,
filename: 'Dashboard.pdf',
image: { type: 'jpeg', quality: 1 },
html2canvas: { scale: 1 },
jsPDF: { unit: 'mm', format: 'a3', orientation: 'portrait' }
};
html2pdf().set(opt).from(newElement).save().then(()=>{
window.location.reload()
})
}
async createPDF(){
/* Function for getting pdf data in string format */
var elements = $('.items .resize-drag')
var newElement = document.createElement('div');
newElement.className = 'pdf';
elements.each(function(index, elem){
newElement.appendChild(elem);
});
for (var x=0; x< $(newElement)[0].children.length; x++){
$($(newElement)[0].children[x])[0].style.transform = ""
}
var opt = {
margin: 0.3,
filename: 'Dashboard.pdf',
image: { type: 'jpeg', quality: 1 },
html2canvas: { scale: 1 },
jsPDF: { unit: 'mm', format: 'a3', orientation: 'portrait' }
};
var pdf = html2pdf().set(opt).from(newElement).toPdf()
var pdfOutput = await pdf.output('datauristring');
console.log(pdfOutput)
return pdfOutput
}
async sendMail(){
/* Function for creating pdf and sending mail to the selected users */
/* This function calls the createPDF() function and returns the pdf datas */
var created_pdf = await this.createPDF();
var base64code = created_pdf.split(',')[1];
this.action.doAction({
type: 'ir.actions.act_window',
name: 'SEND MAIL',
res_model: 'dashboard.mail',
view_mode: 'form',
views: [[false, 'form']],
target: 'new',
context: {
'default_base64code': base64code,
}
})
}
}
OdooDynamicDashboard.template = "owl.OdooDynamicDashboard"
registry.category("actions").add("OdooDynamicDashboard", OdooDynamicDashboard)

215
odoo_dynamic_dashboard/static/src/js/dynamic_dashboard_chart.js

@ -0,0 +1,215 @@
/** @odoo-module **/
import { loadJS } from '@web/core/assets';
import { getColor } from "@web/core/colors/colors";
import { _t } from "@web/core/l10n/translation";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
const { Component, xml, onWillStart, useRef, onMounted } = owl
export class DynamicDashboardChart extends Component {
// Setup function of the class DynamicDashboardChart
setup() {
this.doAction = this.props.doAction.doAction;
this.chartRef = useRef("chart");
this.dialog = this.props.dialog;
onWillStart(async () => {
await loadJS("https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js")
await loadJS("https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js")
await loadJS("https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js")
})
onMounted(()=> this.renderChart())
}
// Function to export the chart in pdf, image, xlsx and csv format
exportItem(ev){
ev.stopPropagation();
ev.preventDefault();
var type = $(ev.currentTarget).attr('data-type');
var canvas = $($($(ev.currentTarget)[0].offsetParent)[0].children[0].lastChild).find("#canvas")[0]
var dataTitle = $(canvas).attr('data-title')
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);
bgCtx.drawImage(canvas, 0, 0);
var imgData = bgCanvas.toDataURL("image/png");
if (type === 'png') {
var downloadLink = document.createElement('a');
downloadLink.href = imgData;
downloadLink.download = `${dataTitle}.png`;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
if (type === 'pdf') {
var pdf = new jsPDF();
pdf.addImage(imgData, 'PNG', 0, 0);
pdf.save(`${dataTitle}.pdf`);
}
if (type === 'xlsx'){
var rows = [];
var items = $('.resize-drag');
for (let i = 0; i < items.length; i++) {
if ($(items[i]).attr('data-id') === $(ev.currentTarget).attr('data-id')) {
rows.push(this.props.widget.x_axis);
rows.push(this.props.widget.y_axis);
}
}
// Prepare the workbook
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('My Sheet');
for(let i = 0; i < rows.length; i++){
worksheet.addRow(rows[i]);
}
const image = workbook.addImage({
base64: imgData,
extension: 'png',
});
worksheet.addImage(image, {
tl: { col: 0, row: 4 },
ext: { width: canvas.width, height: canvas.height }
});
// Save workbook to a file
workbook.xlsx.writeBuffer()
.then((buffer) => {
// Create a Blob object from the buffer
let blob = new Blob([buffer], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.setAttribute("download", `${dataTitle}.xlsx`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
}
if (type === 'csv') {
var rows = [];
var items = $('.resize-drag')
for (let i = 0; i < items.length; i++) {
if ($(items[i]).attr('data-id') === $(ev.currentTarget).attr('data-id')) {
rows.push(this.props.widget.x_axis);
rows.push(this.props.widget.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);
link.click();
document.body.removeChild(link);
}
}
// Function to get the configuration of the chart
async getConfiguration(ev){
ev.stopPropagation();
ev.preventDefault();
var id = this.props.widget.id
await this.doAction({
type: 'ir.actions.act_window',
res_model: 'dashboard.block',
res_id: id,
view_mode: 'form',
views: [[false, "form"]]
});
}
// Function to remove the chart
async removeTile(ev){
ev.stopPropagation();
ev.preventDefault();
this.dialog.add(ConfirmationDialog, {
title: _t("Delete Confirmation"),
body: _t("Are you sure you want to delete this item?"),
confirmLabel: _t("YES, I'M SURE"),
cancelLabel: _t("NO, GO BACK"),
confirm: async () => {
await this.props.orm.unlink("dashboard.block", [this.props.widget.id]);
location.reload();
},
cancel: () => {},
});
}
// Function to render the chart
renderChart(){
if (this.props.widget.graph_type){
const x_axis = this.props.widget.x_axis
const y_axis = this.props.widget.y_axis
const data = []
for (let i = 0; i < x_axis.length; i++) {
const value = { key: x_axis[i], value: y_axis[i] }
data.push(value);
}
new Chart(
this.chartRef.el,
{
type: this.props.widget.graph_type || 'bar',
data: {
labels: data.map(row => row.key),
datasets: [
{
label: this.props.widget.measured_field || 'Data',
data: data.map(row => row.value),
backgroundColor: data.map((_, index) => getColor(index)),
hoverOffset : 4
}
]
},
}
);
}
}
}
DynamicDashboardChart.template = xml`
<div class="resize-drag block card"
t-att-data-x="this.props.widget.data_x"
t-att-data-y="this.props.widget.data_y"
t-att-style="'height:'+this.props.widget.height+'; width:'+ this.props.widget.width+ '; transform: translate('+ this.props.widget.translate_x +', '+ this.props.widget.translate_y +');'"
t-att-data-id="this.props.widget.id">
<div class="card-body mt-1" id="in_ex_body_hide">
<div class="block_edit block_setting" t-on-click="(ev) => this.getConfiguration(ev)">
<i title="Configuration"
class="fa fa-pencil block_setting chart-edit"/>
</div>
<div class="block_edit block_image" data-type="png" t-on-click="(ev) => this.exportItem(ev)">
<i title="Save As Image"
class="bi bi-image block_image chart-image"/>
</div>
<div class="block_edit block_pdf" data-type="pdf" t-on-click="(ev) => this.exportItem(ev)">
<i title="Export to PDF"
class="bi bi-file-earmark-pdf block_pdf chart-pdf"/>
</div>
<div class="block_edit block_csv" t-att-data-id="this.props.widget.id" data-type="csv" t-on-click="(ev) => this.exportItem(ev)">
<i title="Export to CSV"
class="bi bi-filetype-csv block_csv chart-csv"/>
</div>
<div class="block_edit block_xlsx" t-att-data-id="this.props.widget.id" data-type="xlsx" t-on-click="(ev) => this.exportItem(ev)">
<i title="Export to XLSX"
class="fa fa-file-excel-o block_xlsx chart-xlsx"/>
</div>
<div class="block_edit block_delete" t-on-click="(ev) => this.removeTile(ev)">
<i title="Delete"
class="fa fa-times block_delete chart-setting"/>
</div>
<h3 class="chart_title">
<t t-esc="this.props.widget.name"/>
</h3>
<div class="row">
<div class="col-md-12 chart_canvas" id="chart_canvas"
t-att-data-id="this.props.widget.id">
<canvas id="canvas" t-ref="chart" t-att-data-title="this.props.widget.name"/>
</div>
</div>
</div>
</div>
`

89
odoo_dynamic_dashboard/static/src/js/dynamic_dashboard_tile.js

@ -0,0 +1,89 @@
/** @odoo-module **/
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { _t } from "@web/core/l10n/translation";
const { Component, xml } = owl;
export class DynamicDashboardTile extends Component {
// Setup function of the class DynamicDashboardTile
setup() {
this.doAction = this.props.doAction.doAction;
this.dialog = this.props.dialog;
this.orm = this.props.orm;
}
// Function to get the configuration of the tile
async getConfiguration(ev){
ev.stopPropagation();
ev.preventDefault();
var id = this.props.widget.id
await this.doAction({
type: 'ir.actions.act_window',
res_model: 'dashboard.block',
res_id: id,
view_mode: 'form',
views: [[false, "form"]]
});
}
// Function to remove the tile
async removeTile(ev){
ev.stopPropagation();
ev.preventDefault();
this.dialog.add(ConfirmationDialog, {
title: _t("Delete Confirmation"),
body: _t("Are you sure you want to delete this item?"),
confirmLabel: _t("YES, I'M SURE"),
cancelLabel: _t("NO, GO BACK"),
confirm: async () => {
await this.orm.unlink("dashboard.block", [this.props.widget.id]);
location.reload();
},
cancel: () => {},
});
}
// Function for getting records by double click
async getRecords(){
var model_name = this.props.widget.model_name;
if (model_name){
await this.doAction({
type: 'ir.actions.act_window',
res_model: model_name,
view_mode: 'tree',
views: [[false, "tree"]],
domain: this.props.widget.domain,
});
}
}
}
DynamicDashboardTile.template = xml `
<div class="resize-drag tile"
t-on-dblclick="getRecords"
t-att-data-id="this.props.widget.id"
t-att-data-x="this.props.widget.data_x"
t-att-data-y="this.props.widget.data_y"
t-att-style="this.props.widget.color+this.props.widget.text_color+ 'height:'+this.props.widget.height+';width:'+this.props.widget.width + '; transform: translate('+ this.props.widget.translate_x +', '+ this.props.widget.translate_y +');'">
<div t-att-style="this.props.widget.color+this.props.widget.text_color"
class="d-flex align-items-center w-100 my-3">
<a class="block_setting tile_edit tile-container__setting-icon" style="color:black;" t-on-click="(ev) => this.getConfiguration(ev)" >
<i class="fa fa-edit"/>
</a>
<a class="block_delete tile_edit tile-container__delete-icon" style="color:black;" t-on-click="(ev) => this.removeTile(ev)">
<i class="fa fa-times"/>
</a>
<div t-att-style="this.props.widget.icon_color"
class="tile-container__icon-container bg-white d-flex justify-content-center align-items-center">
<i t-att-class="this.props.widget.icon"/>
</div>
<div t-att-style="this.props.widget.text_color"
class="tile-container__status-container">
<h2 t-att-style="this.props.widget.text_color"
class="status-container__title">
<t t-esc="this.props.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="this.props.widget.val_color">
<t t-esc="this.props.widget.value"/>
</h3>
</div>
</div>
</div>
</div>`

208
odoo_dynamic_dashboard/static/src/scss/dynamic_dashboard.scss

@ -0,0 +1,208 @@
: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;
margin-left: 15px;
}
.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;
}
.dark-theme {
/* Add dark theme styles here */
.block_edit {
color: #b7b7b7 !important;
}
.theme_icon:hover {
text-shadow: 0px 0px 5px #9388ff;
}
.add_block{
color: #dfdfdf;
}
#ExportMenu {
color: #626262;
}
.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;
}
}
}
.btn-align-items{
width: 134px;
font-size: small;
border-radius: revert;
height: 33px;
}
#edit_layout{
background-color: #0c8444;
}
#save_layout{
background-color: #b53c5d;
width: 134px;
height: 33px;
font-size: small;
border-radius: revert;
}
#search-button{
width: 69px;
margin-left: 5px;
}
#search-input-chart{
width: 206px;
height: 34px;
border: 1px solid black;
}
.search-clear{
margin-left: -80px;
}
label input{
appearance: none;
}
.mode{
padding-left: 7px;
font-family: 'odoo_ui_icons';
display: none;
}
.view-mode-icon{
font-size: x-large;
}

109
odoo_dynamic_dashboard/static/src/scss/style.scss

@ -1,109 +0,0 @@
.card{
border: none;
border-radius: 0px;
box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
-webkit-box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
-moz-box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
}
.card-header{
background-color: transparent;
border: none;
}
: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 {
padding: 3.2rem 1.5rem;
border-radius: 2rem;
}
.tile-container__icon-container {
border-radius: 50%;
width: 4.75rem;
height: 4.75rem;
font-size: 28px;
}
.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;
}
// Main Navbar Dropdown menu location override
.o_menu_systray > .o_user_menu > .o-dropdown--menu{
left: auto !important;
right: 0px !important;
}
.button-container{
padding-top: 5px;
}
.add_block{
margin-left: 6px;
}
.tile-container__status-container{
padding-left: 7px;
}

128
odoo_dynamic_dashboard/static/src/xml/dynamic_dashboard_template.xml

@ -1,21 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="owl.dynamic_dashboard" owl="1">
<div class="container">
<div class="button-container">
<button class="btn btn-primary" data-type="tile"
type="button" t-on-click="_onClick_add_block">Add Block
<!--DASHBOARD VIEW WITH NAVIGATION-BAR, INTERACTJS TEMPLATE-->
<t t-name="owl.OdooDynamicDashboard" owl="1">
<div class="container" style="min-height:-webkit-fill-available;">
<div class="navbar navbar-expand-md navbar-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>
<button class="btn btn-primary add_block" data-type="graph"
type="button" t-on-click="_onClick_add_block">Add Graph
<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 btn-align-items dropdown-add-items dropdown-toggle"
style="margin-top:-4px;"
type="selection" id="dropdownMenuButton"
data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false"
t-on-click="onClickAdd">
<i class="fa fa-plus-circle"/>
<span id="text_add">⠀Add Items</span>
</button>
<div class="dropdown-menu dropdown-addblock"
aria-labelledby="dropdownMenuButton">
<a class="dropdown-item add_block"
data-type="tile"
t-on-click="(ev) => this.onClickAddItem(ev)">Tile</a>
<a class="dropdown-item add_block"
data-type="graph" data-chart_type="bar"
t-on-click="(ev) => this.onClickAddItem(ev)">Bar Chart</a>
<a class="dropdown-item add_block"
data-type="graph" data-chart_type="doughnut"
t-on-click="(ev) => this.onClickAddItem(ev)">Doughnut Chart</a>
<a class="dropdown-item add_block"
data-type="graph"
data-chart_type="line"
t-on-click="(ev) => this.onClickAddItem(ev)">Line Chart</a>
<a class="dropdown-item add_block"
data-type="graph" data-chart_type="pie"
t-on-click="(ev) => this.onClickAddItem(ev)">Pie Chart</a>
<a class="dropdown-item add_block"
data-type="graph"
data-chart_type="polarArea"
t-on-click="(ev) => this.onClickAddItem(ev)">Polar Area Chart</a>
<a class="dropdown-item add_block"
data-type="graph"
data-chart_type="radar"
t-on-click="(ev) => this.onClickAddItem(ev)">Radar Chart</a>
</div>
<div class="o_dynamic_dashboard row">
<div class="o_dynamic_tile row">
</label>
</ul>
</div>
<div class="o_dynamic_graph w3-container row">
<label class="navbar-items layout-switch"
style="padding-top:20px;"
id="edit-layout-label">
<button class="navbar-items btn-search_edit btn-align-items btn btn-primary my-2 mx-2 my-sm-0"
type="button"
id="edit_layout"
t-on-click="(ev) => this.editLayout(ev)">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"
t-on-click="(ev) => this.saveLayout(ev)">Save Layout</button>
<label for="view_mode"
t-on-mouseover="(ev) => this.showViewMode(ev)"
t-on-mouseout="(ev) => this.hideViewMode(ev)"
t-on-click="(ev) => this.changeViewMode(ev)"
style="margin-left: 6px;">
<input type="checkbox" id="view_mode"/>
<span><i class="bi bi-moon-stars-fill view-mode-icon" style="margin-left:10px"/></span>
</label>
<span class="mode" mode="light">Dark mode</span>
</label>
<div class="search-group" style="margin-right: 30px;padding-top:20px;">
<!-- Search Bar -->
<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" t-on-click="clearSearch">
<i class="fa fa-times search-clear"
style="margin-left:-25px;margin-top:9px;"/>
</span>
</div>
<button class="btn btn-outline-success my-2 my-sm-0"
t-on-click="clickSearch"
style="margin-left:10px;"
type="button">Search
</button>
</div>
<!-- Date Inputs -->
<div class="date-inputs"
style="position: absolute; right: 34.5em; font-size: smaller;font-family: monospace; padding-top:inherit; ">
<label for="start-date" class="date-label"
style="color: black;">Start Date:</label>
<input type="date" id="start-date" name="start-date"
t-on-change="dateFilter"
style="color: black; border: 1px solid #4e4e4e; background-color: white; padding: 5px 10px; border-radius: 5px;"/>
<label for="end-date" class="date-label"
style="color: black; margin-left: 10px;">End Date:</label>
<input type="date" id="end-date" name="end-date"
t-on-change="dateFilter"
style="color: black; border: 1px solid #4e4e4e; background-color: white; padding: 5px 10px; border-radius: 5px;"/>
</div>
<div class="o-dropdown dropdown theme">
<select class="form-select"
t-ref="ThemeSelector"
t-on-change="onChangeTheme">
<option value="0">Select Theme</option>
</select>
</div>
<div class="dashboard_pdf" t-on-click="printPdf">
<i class="bi bi-filetype-pdf" style="font-size:24px;"/>
</div>
<div class="dashboard_mail" t-on-click="sendMail">
<i class="bi bi-envelope-fill" style="font-size:24px;"/>
</div>
</div>
<div class="all_items" style="display:grid;">
<div class="items"/>
</div>
<!--CONTAINER FOR CONTENT GENERATION :TILE & CHART(FROM DynamicDashboardTile & DynamicDashboardChart-->
</div>
</t>
</templates>

46
odoo_dynamic_dashboard/views/dashboard_menu_view.xml

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Form view of the model 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"/>
<field name="parent_id"/>
<field name="group_ids" widget="many2many_tags" invisible="1"/>
<field name="client_action_id" invisible="1"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Tree view of the model dashboard menu -->
<record id="dashboard_menu_view_tree" model="ir.ui.view">
<field name="name">dashboard.menu.view.tree</field>
<field name="model">dashboard.menu</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="parent_id"/>
</tree>
</field>
</record>
<!-- Action for the model dashboard menu -->
<record id="dashboard_menu_action" model="ir.actions.act_window">
<field name="name">Dashboard Menu</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">dashboard.menu</field>
<field name="view_mode">tree,form</field>
</record>
<!-- Menu item to show the configuration in module dynamic dashboard-->
<menuitem name="Configuration" id="menu_dynamic_dashboard_configuration" parent="odoo_dynamic_dashboard.menu_dashboard"
sequence="3"/>
<!-- Menu item to show the dynamic menus in module dynamic dashboard-->
<menuitem name="Dashboards" id="menu_dynamic_dashboard_menu" parent="odoo_dynamic_dashboard.menu_dynamic_dashboard_configuration"
sequence="3" action="dashboard_menu_action"/>
</odoo>

66
odoo_dynamic_dashboard/views/dashboard_menu_views.xml

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--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">
<center>
<h3 class="my-2 ms-3">
Name:
<field name="name"/>
</h3>
</center>
<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>
<!-- Action specified for the dashboard menu-->
<record id="dashboard_menu_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>
<!--Menu Item of the model Dashboard Menu-->
<menuitem name="Dashboard Menu" id="dashboard_menu_view_action"
parent="odoo_dynamic_dashboard.menu_dashboard"
sequence="10" action="dashboard_menu_action"/>
</odoo>

56
odoo_dynamic_dashboard/views/dashboard_theme_views.xml

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--Form view of the dashboard block theme-->
<record id="dashboard_theme_view_form" model="ir.ui.view">
<field name="name">dashboard.theme.view.form</field>
<field name="model">dashboard.theme</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<div>
<field name="name" class="oe_inline"
style="font-size: 30px;"
placeholder="Theme Name" required="1"/>
</div>
</group>
<group>
<field name="color_x" widget="color"/>
<field name="color_y" widget="color"/>
<field name="color_z" widget="color"/>
</group>
<notebook>
<page string="Color Gradient">
<field name="body" type="html" readonly="1"/>
<field name="style" invisible="1"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!--Tree view of the dashboard block theme-->
<record id="dashboard_theme_view_tree" model="ir.ui.view">
<field name="name">dashboard.theme.view.tree</field>
<field name="model">dashboard.theme</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
</tree>
</field>
</record>
<!-- Action specified for the dashboard block theme -->
<record id="dashboard_theme_action" model="ir.actions.act_window">
<field name="name">Dashboard Theme</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">dashboard.theme</field>
<field name="view_mode">tree,form</field>
</record>
<!--Menu Item for the model Dashboard Blocks-->
<menuitem name="Configuration" id="odoo_dynamic_configuration"
parent="odoo_dynamic_dashboard.menu_dashboard"/>
<menuitem name="Dashboard Theme" id="dashboard_theme_menu"
parent="odoo_dynamic_dashboard.odoo_dynamic_configuration"
sequence="45" action="dashboard_theme_action"/>
</odoo>

13
odoo_dynamic_dashboard/views/dashboard_view.xml

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Action to show the dashboard-->
<record id="dynamic_dashboard_action" model="ir.actions.client">
<field name="name">Dashboard</field>
<field name="tag">owl.dynamic_dashboard</field>
</record>
<!-- Menu Item to show the dashboard module-->
<menuitem name="Dashboard" id="menu_dashboard" sequence="0" web_icon="odoo_dynamic_dashboard,static/description/icon.png"/>
<!-- Menu Item to show the dashboards menu in the dashboard module-->
<menuitem name="Dashboards" id="menu_dynamic_dashboard" parent="odoo_dynamic_dashboard.menu_dashboard"
sequence="1" action="dynamic_dashboard_action"/>
</odoo>

14
odoo_dynamic_dashboard/views/dashboard_views.xml

@ -0,0 +1,14 @@
<?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">OdooDynamicDashboard</field>
</record>
<menuitem name="Dashboards" id="menu_dashboard" sequence="-1"
web_icon="odoo_dynamic_dashboard,static/description/icon.png"/>
<menuitem name="Dashboard" action="dashboard_view_action"
id="dashboard_root_menu"
parent="odoo_dynamic_dashboard.menu_dashboard"
sequence="0"/>
</odoo>

56
odoo_dynamic_dashboard/views/dynamic_block_view.xml → odoo_dynamic_dashboard/views/dynamic_block_views.xml

@ -1,50 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- form view of the model dashboard block-->
<!--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>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
<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"
required="[('edit_mode','=', True)]"/>
<field name="client_action" invisible="1"/>
required="[('edit_mode','=', True)]"
options="{'no_create_edit':True,'no_create': True}"/>
<field name="client_action_id" invisible="1"/>
<field name="model_name" invisible="1"/>
<field name="edit_mode" invisible="1"/>
<field name="operation"
required="[('edit_mode','=', True)]"/>
<field name="measured_field"
domain="[('model_id','=',model_id), ('ttype','in',['float','integer','monetary']), ('store', '=', True)]"
required="[('edit_mode','=', True)]"/>
<field name="measured_field_id"
required="[('edit_mode','=', True)]"
options="{'no_create_edit':True, 'no_create': True }"
domain="[('model_id','=',model_id), ('ttype','in',['float','integer','monetary']), ('store', '=', True)]"/>
<field name="filter" widget="domain"
options="{'model': 'model_name'}"/>
</group>
</group>
<group string="Block Information">
<group>
<field name="sequence" invisible="1"/>
<field name="type" required="1"/>
<field name="graph_type"
invisible="type == 'tile'"/>
<field name="graph_size"
invisible="type == 'tile'"/>
<field name="fa_icon" invisible="type == 'graph'"/>
<field name="fa_color" invisible="type == 'graph'"
widget="color"/>
<field name="group_by" invisible="type == 'tile'"
<field name="fa_icon"
invisible="type == 'graph'"/>
<field name="group_by_id" invisible="type == 'tile'"
options="{'no_create_edit':True, 'no_create': True}"
required="[('edit_mode','=', True),('type','=','graph')]"
domain="[('model_id','=',model_id), ('ttype','!=','one2many'), ('store', '=', True)]"/>
<field name="tile_color" invisible="type == 'graph'"
<field name="tile_color"
invisible="type == 'graph'"
widget="color"/>
<field name="val_color"
invisible="type == 'graph'"
widget="color"/>
<field name="text_color" invisible="type == 'graph'"
<field name="text_color"
invisible="type == 'graph'"
widget="color"/>
</group>
</group>
@ -52,7 +60,7 @@
</form>
</field>
</record>
<!-- tree view of the model dashboard block-->
<!--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>
@ -64,7 +72,7 @@
</tree>
</field>
</record>
<!-- Action of the model dashboard block-->
<!-- 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>
@ -72,8 +80,8 @@
<field name="view_mode">tree,form</field>
<field name="context">{'default_edit_mode' : True}</field>
</record>
<!-- Menu item of the model dashboard block-->
<menuitem name="Blocks" id="menu_dynamic_dashboard_blocks"
<!--Menu Item for the model Dashboard Blocks-->
<menuitem name="Dashboard Blocks" id="dashboard_block_menu"
parent="odoo_dynamic_dashboard.menu_dashboard"
sequence="1" action="dashboard_block_action"/>
sequence="5" action="dashboard_block_action"/>
</odoo>

13
odoo_dynamic_dashboard/models/dashboard_block_line.py → odoo_dynamic_dashboard/wizard/__init__.py

@ -19,15 +19,4 @@
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from odoo import fields, models
class DashboardBlockLine(models.Model):
""" Creates the model Dashboard Block Line"""
_name = "dashboard.block.line"
_description = "Dashboard Block Line"
sequence = fields.Integer(string="Sequence",
help="Sequence of the block lines")
block_size = fields.Integer(string="Block size",
help="Block size of the dashboard block line")
from . import dashboard_mail

75
odoo_dynamic_dashboard/wizard/dashboard_mail.py

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.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 fields, models
class DashboardMail(models.TransientModel):
_name = 'dashboard.mail'
_description = 'Dashboard Mail'
user_ids = fields.Many2many('res.users', string="Users",
domain="[('id','!=', uid)]",
help="Select User")
base64code = fields.Char(string='Base 64', help='Base64 Code of the pdf')
def send_mail(self):
"""
Function for sending mail to the selected users
"""
for user in self.user_ids:
mail_content = (
'Hi %s, <br/> '
'I hope this mail finds you well. I am pleased to share the <b>Dashboard Report</b> with you.<br/>'
'Please find the attachment<br/>') % user.name
mail_values = {
'subject': 'Dashboard Report',
'author_id': self.env.user.partner_id.id,
'body_html': mail_content,
'email_to': user.email,
}
mail_id = self.env['mail.mail'].create(mail_values)
attachment_values = {
'name': 'Dashboard.pdf',
'datas': self.base64code,
'type': 'binary',
'res_model': 'mail.mail',
'res_id': mail_id.id,
}
attachment_id = self.env['ir.attachment'].create(attachment_values)
mail_id.write({
'attachment_ids': [(4, attachment_id.id)]
})
mail_id.send()
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
def cancel_mail(self):
"""
Function for refreshing the page while clicking cancel
"""
return {
'type': 'ir.actions.client',
'tag': 'reload',
}

22
odoo_dynamic_dashboard/wizard/dashboard_mail_views.xml

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Sending dashboard pdf to users -->
<record id="dashboard_mail_view_form" model="ir.ui.view">
<field name="name">dashboard.mail.view.form</field>
<field name="model">dashboard.mail</field>
<field name="arch" type="xml">
<form string="Sent Mail">
<group>
<field name="user_ids" widget="many2many_tags"/>
<field name="base64code" invisible="1"/>
</group>
<footer>
<button name="send_mail" string="SEND"
class="btn-primary" type="object"/>
<button string="Cancel" class="btn-secondary"
name="cancel_mail" type="object"/>
</footer>
</form>
</field>
</record>
</odoo>
Loading…
Cancel
Save