Browse Source

[ADD] Initial Commit

pull/195/head
Ajmal Cybro 4 years ago
parent
commit
dae82a326d
  1. 40
      code_backend_theme/README.rst
  2. 22
      code_backend_theme/__init__.py
  3. 71
      code_backend_theme/__manifest__.py
  4. 7
      code_backend_theme/doc/RELEASE_NOTES.md
  5. 292
      code_backend_theme/hooks.py
  6. BIN
      code_backend_theme/static/description/assets/all_screens.png
  7. BIN
      code_backend_theme/static/description/assets/easily-access-menu.gif
  8. BIN
      code_backend_theme/static/description/assets/hero.png
  9. BIN
      code_backend_theme/static/description/assets/icons/check.png
  10. BIN
      code_backend_theme/static/description/assets/icons/chevron.png
  11. BIN
      code_backend_theme/static/description/assets/icons/cogs.png
  12. BIN
      code_backend_theme/static/description/assets/icons/consultation.png
  13. BIN
      code_backend_theme/static/description/assets/icons/ecom-black.png
  14. BIN
      code_backend_theme/static/description/assets/icons/education-black.png
  15. BIN
      code_backend_theme/static/description/assets/icons/hotel-black.png
  16. BIN
      code_backend_theme/static/description/assets/icons/license.png
  17. BIN
      code_backend_theme/static/description/assets/icons/lifebuoy.png
  18. BIN
      code_backend_theme/static/description/assets/icons/manufacturing-black.png
  19. BIN
      code_backend_theme/static/description/assets/icons/pos-black.png
  20. BIN
      code_backend_theme/static/description/assets/icons/puzzle.png
  21. BIN
      code_backend_theme/static/description/assets/icons/restaurant-black.png
  22. BIN
      code_backend_theme/static/description/assets/icons/service-black.png
  23. BIN
      code_backend_theme/static/description/assets/icons/trading-black.png
  24. BIN
      code_backend_theme/static/description/assets/icons/training.png
  25. BIN
      code_backend_theme/static/description/assets/icons/update.png
  26. BIN
      code_backend_theme/static/description/assets/icons/user.png
  27. BIN
      code_backend_theme/static/description/assets/icons/wrench.png
  28. BIN
      code_backend_theme/static/description/assets/logo.png
  29. BIN
      code_backend_theme/static/description/assets/menu_focus.png
  30. BIN
      code_backend_theme/static/description/assets/resp-gif.gif
  31. BIN
      code_backend_theme/static/description/assets/responsive.jpg
  32. BIN
      code_backend_theme/static/description/assets/screenshots/10.newlookoftabs.png
  33. BIN
      code_backend_theme/static/description/assets/screenshots/11.recruitment.png
  34. BIN
      code_backend_theme/static/description/assets/screenshots/12.saleskanban.png
  35. BIN
      code_backend_theme/static/description/assets/screenshots/13.modified kanban employee.png
  36. BIN
      code_backend_theme/static/description/assets/screenshots/15.sidebarwithlistview.png
  37. BIN
      code_backend_theme/static/description/assets/screenshots/16grapghview.png
  38. BIN
      code_backend_theme/static/description/assets/screenshots/17.attendanceview.png
  39. BIN
      code_backend_theme/static/description/assets/screenshots/2.groupbyview.png
  40. BIN
      code_backend_theme/static/description/assets/screenshots/3.settings page.png
  41. BIN
      code_backend_theme/static/description/assets/screenshots/4.discusspage.png
  42. BIN
      code_backend_theme/static/description/assets/screenshots/5.productskanaban.png
  43. BIN
      code_backend_theme/static/description/assets/screenshots/6.purchase view.png
  44. BIN
      code_backend_theme/static/description/assets/screenshots/7.productviewsmartbuttons.png
  45. BIN
      code_backend_theme/static/description/assets/screenshots/8error.png
  46. BIN
      code_backend_theme/static/description/assets/screenshots/Form view.png
  47. BIN
      code_backend_theme/static/description/assets/screenshots/kanabangroupview.png
  48. BIN
      code_backend_theme/static/description/assets/screenshots/listview.png
  49. BIN
      code_backend_theme/static/description/assets/screenshots/login.png
  50. BIN
      code_backend_theme/static/description/assets/screenshots/modal.png
  51. BIN
      code_backend_theme/static/description/banner.png
  52. BIN
      code_backend_theme/static/description/icon.png
  53. 956
      code_backend_theme/static/description/index.html
  54. BIN
      code_backend_theme/static/description/theme_screenshot.png
  55. BIN
      code_backend_theme/static/src/img/code_logo.png
  56. BIN
      code_backend_theme/static/src/img/icons/Attendances.png
  57. BIN
      code_backend_theme/static/src/img/icons/CRM.png
  58. BIN
      code_backend_theme/static/src/img/icons/Calendar.png
  59. BIN
      code_backend_theme/static/src/img/icons/Contacts.png
  60. BIN
      code_backend_theme/static/src/img/icons/Dashboards.png
  61. BIN
      code_backend_theme/static/src/img/icons/Email Marketing.png
  62. BIN
      code_backend_theme/static/src/img/icons/Employees.png
  63. BIN
      code_backend_theme/static/src/img/icons/Events.png
  64. BIN
      code_backend_theme/static/src/img/icons/Expenses.png
  65. BIN
      code_backend_theme/static/src/img/icons/Fleet.png
  66. BIN
      code_backend_theme/static/src/img/icons/Inventory.png
  67. BIN
      code_backend_theme/static/src/img/icons/Invoicing.png
  68. BIN
      code_backend_theme/static/src/img/icons/Link Tracker.png
  69. BIN
      code_backend_theme/static/src/img/icons/Live Chat.png
  70. BIN
      code_backend_theme/static/src/img/icons/Lunch.png
  71. BIN
      code_backend_theme/static/src/img/icons/Maintenance.png
  72. BIN
      code_backend_theme/static/src/img/icons/Manufacturing.png
  73. BIN
      code_backend_theme/static/src/img/icons/Members.png
  74. BIN
      code_backend_theme/static/src/img/icons/Note.png
  75. BIN
      code_backend_theme/static/src/img/icons/Point of Sale.png
  76. BIN
      code_backend_theme/static/src/img/icons/Project.png
  77. BIN
      code_backend_theme/static/src/img/icons/Purchase.png
  78. BIN
      code_backend_theme/static/src/img/icons/Recruitment.png
  79. BIN
      code_backend_theme/static/src/img/icons/Repairs.png
  80. BIN
      code_backend_theme/static/src/img/icons/SMS Marketing.png
  81. BIN
      code_backend_theme/static/src/img/icons/Sales.png
  82. BIN
      code_backend_theme/static/src/img/icons/Surveys.png
  83. BIN
      code_backend_theme/static/src/img/icons/Time Off.png
  84. BIN
      code_backend_theme/static/src/img/icons/Timesheets.png
  85. BIN
      code_backend_theme/static/src/img/icons/Website.png
  86. BIN
      code_backend_theme/static/src/img/icons/apps.png
  87. BIN
      code_backend_theme/static/src/img/icons/close.png
  88. BIN
      code_backend_theme/static/src/img/icons/discuss.png
  89. BIN
      code_backend_theme/static/src/img/icons/eLearning.png
  90. BIN
      code_backend_theme/static/src/img/icons/settings.png
  91. 93
      code_backend_theme/static/src/js/chrome/sidebar_menu.js
  92. 32
      code_backend_theme/static/src/js/fields/colors.js
  93. 82
      code_backend_theme/static/src/js/fields/graph_arch_parser.js
  94. 537
      code_backend_theme/static/src/js/fields/graph_model.js
  95. 627
      code_backend_theme/static/src/js/fields/graph_renderer.js
  96. 160
      code_backend_theme/static/src/js/fields/graph_view.js
  97. 57
      code_backend_theme/static/src/js/user_menu/user_menu.js
  98. 68
      code_backend_theme/static/src/scss/datetimepicker.scss
  99. 146
      code_backend_theme/static/src/scss/login.scss
  100. 347
      code_backend_theme/static/src/scss/navigation_bar.scss

40
code_backend_theme/README.rst

@ -0,0 +1,40 @@
Code Backend Theme
==================
* Code Backend Theme module for Odoo 15 community editions
Installation
============
- www.odoo.com/documentation/15.0/setup/install.html
- Install our custom addon
License
-------
General Public License, Version 3 (LGPL v3).
(https://www.odoo.com/documentation/user/14.0/legal/licenses/licenses.html)
Company
-------
* 'Cybrosys Techno Solutions <https://cybrosys.com/>'__
Credits
-------
* 'Cybrosys Techno Solutions <https://cybrosys.com/>'__
Contacts
--------
* Mail Contact : odoo@cybrosys.com
Bug Tracker
-----------
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported.
Maintainer
==========
This module is maintained by Cybrosys Technologies.
For support and more information, please visit https://www.cybrosys.com
Further information
===================
HTML Description: `<static/description/index.html>`__

22
code_backend_theme/__init__.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2021-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 LESSER
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
from .hooks import test_pre_init_hook, test_post_init_hook

71
code_backend_theme/__manifest__.py

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2021-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 LESSER
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
{
"name": "Code Backend Theme V15",
"description": """Minimalist and elegant backend theme for Odoo 14, Backend Theme, Theme""",
"summary": "Code Backend Theme V15 is an attractive theme for backend",
"category": "Theme/Backend",
"version": "15.0.1.0.0",
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions',
'website': "https://www.cybrosys.com",
"depends": ['base', 'web', 'mail'],
"data": [
'views/icons.xml',
'views/layout.xml',
],
'assets': {
'web.assets_frontend': [
'code_backend_theme/static/src/scss/login.scss',
],
'web.assets_backend': [
'code_backend_theme/static/src/scss/theme_accent.scss',
'code_backend_theme/static/src/scss/navigation_bar.scss',
'code_backend_theme/static/src/scss/datetimepicker.scss',
'code_backend_theme/static/src/scss/theme.scss',
'code_backend_theme/static/src/scss/sidebar.scss',
('replace', '/web/static/src/views/graph/colors.js', '/code_backend_theme/static/src/js/fields/colors.js'),
('replace', '/web/static/src/views/graph/graph_renderer.js', '/code_backend_theme/static/src/js/fields/graph_renderer.js'),
('replace', '/web/static/src/views/graph/graph_model.js', '/code_backend_theme/static/src/js/fields/graph_model.js'),
('replace', '/web/static/src/views/graph/graph_arch_parser.js', '/code_backend_theme/static/src/js/fields/graph_arch_parser.js'),
('replace', '/web/static/src/views/graph/graph_view.js', '/code_backend_theme/static/src/js/fields/graph_view.js'),
'code_backend_theme/static/src/js/chrome/sidebar_menu.js',
('replace', '/web/static/src/webclient/user_menu/user_menu.js', '/code_backend_theme/static/src/js/user_menu/user_menu.js'),
],
'web.assets_qweb': [
'code_backend_theme/static/src/xml/styles.xml',
'code_backend_theme/static/src/xml/top_bar.xml',
],
},
'images': [
'static/description/banner.png',
'static/description/theme_screenshot.png',
],
'license': 'LGPL-3',
'pre_init_hook': 'test_pre_init_hook',
'post_init_hook': 'test_post_init_hook',
'installable': True,
'application': False,
'auto_install': False,
}

7
code_backend_theme/doc/RELEASE_NOTES.md

@ -0,0 +1,7 @@
## Module <code_backend_theme>
#### 22.10.2021
#### Version 15.0.1.0.0
#### ADD
Initial Commit

292
code_backend_theme/hooks.py

@ -0,0 +1,292 @@
"""Hooks for Changing Menu Web_icon"""
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2021-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 LESSER
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import base64
from odoo import api, SUPERUSER_ID
from odoo.modules import get_module_resource
def test_pre_init_hook(cr):
"""pre init hook"""
env = api.Environment(cr, SUPERUSER_ID, {})
menu_item = env['ir.ui.menu'].search([('parent_id', '=', False)])
for menu in menu_item:
if menu.name == 'Contacts':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Contacts.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Link Tracker':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Link Tracker.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Dashboards':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Dashboards.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Sales':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Sales.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Invoicing':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Invoicing.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Inventory':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Inventory.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Purchase':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Purchase.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Calendar':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Calendar.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'CRM':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'CRM.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Note':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Note.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Website':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Website.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Point of Sale':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Point of Sale.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Manufacturing':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Manufacturing.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Repairs':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Repairs.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Email Marketing':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Email Marketing.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'SMS Marketing':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'SMS Marketing.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Project':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Project.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Surveys':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Surveys.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Employees':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Employees.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Recruitment':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Recruitment.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Attendances':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Attendances.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Time Off':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Time Off.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Expenses':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Expenses.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Maintenance':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Maintenance.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Live Chat':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Live Chat.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Lunch':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Lunch.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Fleet':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Fleet.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Timesheets':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Timesheets.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Events':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Events.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'eLearning':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'eLearning.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Members':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Members.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
def test_post_init_hook(cr, registry):
"""post init hook"""
env = api.Environment(cr, SUPERUSER_ID, {})
menu_item = env['ir.ui.menu'].search([('parent_id', '=', False)])
for menu in menu_item:
if menu.name == 'Contacts':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Contacts.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Link Tracker':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Link Tracker.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Dashboards':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Dashboards.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Sales':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Sales.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Invoicing':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Invoicing.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Inventory':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Inventory.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Purchase':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Purchase.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Calendar':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Calendar.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'CRM':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'CRM.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Note':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Note.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Website':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Website.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Point of Sale':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Point of Sale.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Manufacturing':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Manufacturing.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Repairs':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Repairs.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Email Marketing':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Email Marketing.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'SMS Marketing':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'SMS Marketing.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Project':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Project.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Surveys':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Surveys.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Employees':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Employees.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Recruitment':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Recruitment.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Attendances':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Attendances.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Time Off':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Time Off.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Expenses':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Expenses.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Maintenance':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Maintenance.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Live Chat':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Live Chat.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Lunch':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Lunch.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Fleet':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Fleet.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Timesheets':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Timesheets.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Events':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Events.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'eLearning':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'eLearning.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})
if menu.name == 'Members':
img_path = get_module_resource(
'code_backend_theme', 'static', 'src', 'img', 'icons', 'Members.png')
menu.write({'web_icon_data': base64.b64encode(open(img_path, "rb").read())})

BIN
code_backend_theme/static/description/assets/all_screens.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
code_backend_theme/static/description/assets/easily-access-menu.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

BIN
code_backend_theme/static/description/assets/hero.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
code_backend_theme/static/description/assets/icons/check.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
code_backend_theme/static/description/assets/icons/chevron.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

BIN
code_backend_theme/static/description/assets/icons/cogs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
code_backend_theme/static/description/assets/icons/consultation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
code_backend_theme/static/description/assets/icons/ecom-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

BIN
code_backend_theme/static/description/assets/icons/education-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

BIN
code_backend_theme/static/description/assets/icons/hotel-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

BIN
code_backend_theme/static/description/assets/icons/license.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
code_backend_theme/static/description/assets/icons/lifebuoy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
code_backend_theme/static/description/assets/icons/manufacturing-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

BIN
code_backend_theme/static/description/assets/icons/pos-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

BIN
code_backend_theme/static/description/assets/icons/puzzle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

BIN
code_backend_theme/static/description/assets/icons/restaurant-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
code_backend_theme/static/description/assets/icons/service-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

BIN
code_backend_theme/static/description/assets/icons/trading-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

BIN
code_backend_theme/static/description/assets/icons/training.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

BIN
code_backend_theme/static/description/assets/icons/update.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
code_backend_theme/static/description/assets/icons/user.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

BIN
code_backend_theme/static/description/assets/icons/wrench.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
code_backend_theme/static/description/assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
code_backend_theme/static/description/assets/menu_focus.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
code_backend_theme/static/description/assets/resp-gif.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

BIN
code_backend_theme/static/description/assets/responsive.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
code_backend_theme/static/description/assets/screenshots/10.newlookoftabs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
code_backend_theme/static/description/assets/screenshots/11.recruitment.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
code_backend_theme/static/description/assets/screenshots/12.saleskanban.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
code_backend_theme/static/description/assets/screenshots/13.modified kanban employee.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

BIN
code_backend_theme/static/description/assets/screenshots/15.sidebarwithlistview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

BIN
code_backend_theme/static/description/assets/screenshots/16grapghview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
code_backend_theme/static/description/assets/screenshots/17.attendanceview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
code_backend_theme/static/description/assets/screenshots/2.groupbyview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

BIN
code_backend_theme/static/description/assets/screenshots/3.settings page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
code_backend_theme/static/description/assets/screenshots/4.discusspage.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
code_backend_theme/static/description/assets/screenshots/5.productskanaban.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

BIN
code_backend_theme/static/description/assets/screenshots/6.purchase view.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
code_backend_theme/static/description/assets/screenshots/7.productviewsmartbuttons.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
code_backend_theme/static/description/assets/screenshots/8error.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
code_backend_theme/static/description/assets/screenshots/Form view.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
code_backend_theme/static/description/assets/screenshots/kanabangroupview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
code_backend_theme/static/description/assets/screenshots/listview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

BIN
code_backend_theme/static/description/assets/screenshots/login.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
code_backend_theme/static/description/assets/screenshots/modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

BIN
code_backend_theme/static/description/banner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

BIN
code_backend_theme/static/description/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

956
code_backend_theme/static/description/index.html

@ -0,0 +1,956 @@
<!-- HERO -->
<div class="container">
<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-weight: bolder !important; font-size: 2.5rem !important; color: #212121;">
Code Backend Theme <sup>
</h1>
<p class="my-1 text-center text-uppercase"
style="letter-spacing: 4px !important; color: #74788D !important;">Minimalist and Elegant Backend
Theme for Odoo 15</p>
</div>
<div class="col-lg-12 d-flex justify-content-center align-items-center" style="margin: 2rem 0;">
<img src="./assets/hero.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
</div>
<!-- END OF HERO -->
<!-- OVERVIEW -->
<div class="container">
<div class="row" style="padding: 0rem 2.5rem !important; background-color: #fff !important;">
<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;">
The Code Backend Theme V15 Gives You a Fully Modified View with a Full Screen Display.
This is a Minimalist and Elegant Backend Theme for Odoo 15.
This Theme Will Change Your Old Experience to a New Experience With Odoo.
It is a Perfect Choice for Your Odoo Backend and an Attractive Theme for Your Odoo 15.
It will Give You a Clean Layout with a New Color Combination and a Modified Font. It has a
Sidebar with
New App Icons and Company Logo. This Will Change Your Old Kanban, List and Form Views to A Fully
Modified View.
</p>
</div>
<div class="col-lg-12 mt-4">
<div class="alert alert-warning text-center" role="alert">
<i class="fa fa-exclamation-triangle mr-2" aria-hidden="true"></i>Please make sure that you install
all
your apps prior to the installation of this theme.
</div>
</div>
</div>
</div>
<!-- END OF OVERVIEW-->
<!-- FEATURE -->
<div class="container" style="margin-top: 3rem;">
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center">
<h2
style="font-weight: 300 !important; background-color: #fff !important; z-index: 1 !important; padding: 0 1rem !important;">
Features</h2>
</div>
</div>
<!-- RESPONSIVE -->
<div class="container" style="margin-top: 3rem;">
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #556EE6 !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/responsive.jpg" width="100%" height="auto" class="img-responsive">
</div>
<div class="row">
<div class="col-lg-4 d-flex justify-content-center align-items-center"
style="padding: 1.5rem !important; margin: 0rem 0rem 3rem !important;">
<img src="assets/resp-gif.gif" width="80%" height="auto" class="img-responsive rounded">
</div>
<div class="col-lg-8" style="padding: 2.5rem 1.5rem!important;">
<div class="text-center"
style="font-size: 0.9rem !important; background-color: #556EE6 !important; padding: 0.5 1.5rem !important; width: 60px; color: #ffffff !important; font-weight: 700; border-radius: 0.2rem !important; margin: 10px 0 !important;">
New
</div>
<h3 style="font-weight: 700 !important;">Fully Responsive Layout</h3>
<h6
style="font-style: Montserrat, 'sans-serif' !important; color: #2A3042 !important; font-weight: 300 !important;">
Now take advantage of everything your dashboard has to offer even on the go. Our design are
now
fully responsive enabling you to view and manage everything from the comfort of your mobile
device. Everything
has been designed in a meticulous fashion so that every view snaps itself to fit the size of
the
device you are using, be it smartphones, tablet or any other portables, our theme adjusts
itself
to fit the screen size.
</h6>
<span class="d-flex" style="margin-top: 2rem !important;">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Fully responsive</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Fly-out hamburger menu on the left</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Fits perfectly to all screen sizes</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Quick access menu at the bottom in discuss</p>
</span>
</div>
</div>
</div>
</div>
<!-- END OF RESPONSIVE -->
<!-- FEATURE -->
<div class="container" style="margin-top: 3rem;">
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #556EE6 !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2.5rem 1.5rem!important;">
<h3 style="font-weight: 700 !important;">Kanban Group View</h3>
<h6
style="font-style: Montserrat, 'sans-serif' !important; color: #2A3042 !important; font-weight: 300 !important;">
The Code Backend Theme V15 Gives You a Fully Modified Kanban View and Kanban Group View.
The Section Wise Separated Stages give a Pleasant Experience And an Extraordinary Design
To Your Content Tiles Making The Tiles Look Great.
It will Give You a Clean Layout with the New Color Combination and a Modified Font.
</h6>
<div class="row mt-4">
<div class="col-lg-6">
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Modified Font</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Color Combination</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Full Screen View</p>
</span>
</div>
<div class="col-lg-6">
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Stages are Separated in View</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Clean Layout</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Buttons with New Colors</p>
</span>
</div>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/kanabangroupview.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
</div>
<!-- END OF FEATURE -->
<!-- FEATURE -->
<div class="container" style="margin-top: 3rem;">
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #556EE6 !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2.5rem 1.5rem!important;">
<h3 style="font-weight: 700 !important;">List View</h3>
<h6
style="font-style: Montserrat, 'sans-serif' !important; color: #2A3042 !important; font-weight: 300 !important;">
The All new Code Backend Theme V15 Gives You The Fully Modified List View and This Table Design
is Also Have Awesome Design and it Gives You More Beauty for Your Odoo Backend.
It will Give You a Clean Layout with the New Color Combination and a Modified Font.
</h6>
<div class="row mt-4">
<div class="col-lg-6">
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Modified Table Style</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Color Combination</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Scroll Bar</p>
</span>
</div>
<div class="col-lg-6">
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Status Tag</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Scrollbar</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Buttons with New Colors</p>
</span>
</div>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/listview.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
</div>
<!-- END OF FEATURE -->
<!-- FEATURE -->
<div class="container" style="margin-top: 3rem;">
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #556EE6 !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2.5rem 1.5rem!important;">
<h3 style="font-weight: 700 !important;">Form View</h3>
<h6
style="font-style: Montserrat, 'sans-serif' !important; color: #2A3042 !important; font-weight: 300 !important;">
Code Backend Theme Gives You The Fully Modified Form View with a Full Screen Experience. It will
Give You a Clean Layout with the New Color Combination
and a Modified Font.
</h6>
<div class="row mt-4">
<div class="col-lg-6">
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Modified Form Style</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Full Screen Form View</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Looks for Tabs</p>
</span>
</div>
<div class="col-lg-6">
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Style for Required Field</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Chatter Style Under Form View</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Looks for Status Button</p>
</span>
</div>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/Form view.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
</div>
<!-- END OF FEATURE -->
<!-- TWO COLUMN BLOCK -->
<div class="container" style="margin-top: 3rem;">
<div class="row" style="margin: 2rem; ; min-width: 100% !important;">
<div class="col-lg-8" style="padding: 1rem 1rem 1rem 0rem !important;">
<div class=" shadow"
style="background-color: #fff !important; border-top: 3px solid #556EE6 !important; padding: 2.5rem 0rem 0rem 0rem !important;">
<h3 class="mx-4 mt-3" style="font-weight: 700 !important;">Overview</h3>
<h6 class="mx-4"
style="font-style: Montserrat, 'sans-serif' !important; color: #2A3042 !important; font-weight: 300 !important;">
Code Backend Theme V15 is an Attractive Theme for Your Odoo 15.
This Theme Will Change Improve Your Experience With Odoo.
This is a Minimalist and Elegant Backend Theme for Odoo 15 And Can Offer a Perfect Choice
for
Your Odoo Backend.
</h6>
<div class="mx-4 my-4">
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Modified Structure for All Type Views</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Style for Active Menus, Radio Buttons and Checkboxes</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Color Combination</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>New Look for All Applications</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>A Clean layout and New Font Style</p>
</span>
<span class="d-flex">
<i class="fa fa-check-square mr-2"
style="color:#556EE6 !important; margin-top: 5px !important;"></i>
<p>Sidebar with New Menu Icons</p>
</span>
</div>
<img src="./assets/all_screens.png" class="img-responsive" width="100% !important"
height="auto !important">
</div>
</div>
<div class="col-lg-4" style="padding: 1rem 0rem 1rem 1rem!important;">
<div class="shadow"
style="background-color: #fff !important; border-top: 3px solid #556EE6 !important; padding: 2.5rem 0rem 0rem 1.5rem !important; position: relative; overflow: hidden !important;">
<div class="text-center"
style="font-size: 0.9rem !important; background-color: #556EE6 !important; padding: 0.5 1.5rem !important; width: 60px; color: #ffffff !important; font-weight: 700; border-radius: 0.2rem !important; margin: 10px 0 !important;">
New
</div>
<h3 class="mt-3" style="font-weight: 700 !important;">All-New Menu Design</h3>
<h6
style="font-style: Montserrat, 'sans-serif' !important; color: #2A3042 !important; font-weight: 300 !important; padding-bottom: 3.8rem !important;">
The All-New Menu Design is Main Attractive Section for the Code Backend Theme. The Sidebar
have New Minimalist
Icons for Applications in Odoo. Also the Sidebar Have Closing and Opening Option.
Customisable Logo Attached in Sidebar
That is Automatically Fetch Your Company Logo.
</h6>
<img src="./assets/menu_focus.png" class="img-responsive" width="100% !important"
height="auto !important">
</div>
</div>
</div>
</div>
<!-- END OF TWO COLUMN BLOCK -->
<!-- FEATURE -->
<div class="container" style="margin-top: 3rem;">
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #556EE6 !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2.5rem 1.5rem!important;">
<h3 style="font-weight: 700 !important;">Easily Access Sidebar Menu</h3>
<h6
style="font-style: Montserrat, 'sans-serif' !important; color: #2A3042 !important; font-weight: 300 !important;">
Reveal the sidebar menu with just a click. Sidebar menu features all the relevant links to
navigate
through the application.
Hiding the sidebar leaves more space on the main area offering a distraction-free view that lets
you
focus on what matters the most.
</h6>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/easily-access-menu.gif" width="100%" height="auto" class="img-responsive">
</div>
</div>
</div>
<!-- END OF FEATURE -->
<!-- SCREENSHOTS -->
<div class="container" style="margin-top: 3rem;">
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center">
<h2
style="font-weight: 300 !important; background-color: #fff !important; z-index: 1 !important; padding: 0 1rem !important;">
Screenshots</h2>
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">1</h6>
</div>
<h6 class="mt-2 ml-2">Login Page</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/login.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">2</h6>
</div>
<h6 class="mt-2 ml-2">Group By View</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/2.groupbyview.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">3</h6>
</div>
<h6 class="mt-2 ml-2">Settings Page</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/3.settings page.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">4</h6>
</div>
<h6 class="mt-2 ml-2">Discuss Page</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/4.discusspage.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">5</h6>
</div>
<h6 class="mt-2 ml-2">Product Kanban View</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/5.productskanaban.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">6</h6>
</div>
<h6 class="mt-2 ml-2">Purchase List View</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/6.purchase view.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">7</h6>
</div>
<h6 class="mt-2 ml-2">Product View with Smart Buttons</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/7.productviewsmartbuttons.png" width="100%" height="auto"
class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">8</h6>
</div>
<h6 class="mt-2 ml-2">Modified Alert Notifications are Placed on the Right Bottom of Display
</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/8error.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">9</h6>
</div>
<h6 class="mt-2 ml-2">Wizards and User Error Popups</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/modal.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">10</h6>
</div>
<h6 class="mt-2 ml-2">New Looks for The Tabs</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/10.newlookoftabs.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">11</h6>
</div>
<h6 class="mt-2 ml-2">Recruitment Kanban View With Ribbons</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/11.recruitment.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">12</h6>
</div>
<h6 class="mt-2 ml-2">Sales Kanban View</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/12.saleskanban.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">13</h6>
</div>
<h6 class="mt-2 ml-2">Modified Kanban View for Employees With New Designed Category Section</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/13.modified kanban employee.png" width="100%" height="auto"
class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">14</h6>
</div>
<h6 class="mt-2 ml-2">Sidebar with List View</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/15.sidebarwithlistview.png" width="100%" height="auto"
class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">15</h6>
</div>
<h6 class="mt-2 ml-2">Attendance Pages</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/17.attendanceview.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
<div class="row shadow"
style="margin: 2rem; padding: 0rem !important; background-color: #fff !important; border-top: 3px solid #74788D !important; min-width: 100% !important;">
<div class="col-lg-12" style="padding: 2rem 1.5rem!important;">
<div class="d-flex my-3">
<div class="d-flex justify-content-center align-items-center"
style="background-color: #556EE6 !important; border: 4px solid #D4DAF9 !important; color: #fff !important; height: 35px; width: 35px; border-radius: 50% !important; font-size: 1.1rem !important;">
<h6 style="margin-top: 0.3rem; color: #fff !important;">16</h6>
</div>
<h6 class="mt-2 ml-2">Graphs with Sidebar</h6>
</div>
</div>
<div class="col-lg-12" style="padding-left: 0 !important; padding-right: 0!important;">
<img src="assets/screenshots/16grapghview.png" width="100%" height="auto" class="img-responsive">
</div>
</div>
</div>
<!-- END OF SCREENSHOTS -->
<!-- OUR SERVICES -->
<section class="container" style="margin-top: 6rem !important;">
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center mb-4">
<h2
style="font-weight: 300 !important; background-color: #fff !important; z-index: 1 !important; padding: 0 1rem !important;">
Our Services</h2>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/cogs.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Customization</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/wrench.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/lifebuoy.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Support</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/user.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Hire
Odoo
Developer</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/puzzle.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Integration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/update.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Migration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/consultation.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Consultancy</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/training.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/license.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Licensing Consultancy</h6>
</div>
</div>
</section>
<!-- END OF END OF OUR SERVICES -->
<!-- OUR INDUSTRIES -->
<section class="container" style="margin-top: 6rem !important; background-color: #fff !important;">
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center mb-4">
<h2
style="font-weight: 300 !important; background-color: #fff !important; z-index: 1 !important; padding: 0 1rem !important;">
Our Industries</h2>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/trading-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Trading
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">Easily
procure
and
sell your products</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/pos-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
POS
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">Easy
configuration
and convivial experience</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/education-black.png" class="img-responsive mb-3" height="48px"
width="48px">
<h5
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Education
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">A
platform for
educational management</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/manufacturing-black.png" class="img-responsive mb-3" height="48px"
width="48px">
<h5
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Manufacturing
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">Plan,
track and
schedule your operations</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/ecom-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
E-commerce &amp; Website
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">Mobile
friendly,
awe-inspiring product pages</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/service-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Service Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">Keep
track of
services and invoice</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/restaurant-black.png" class="img-responsive mb-3" height="48px"
width="48px">
<h5
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Restaurant
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">Run
your bar or
restaurant methodically</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 10px; padding: 2rem !important; height: 250px !important;">
<img src="./assets/icons/hotel-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5
style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Hotel Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">An
all-inclusive
hotel management application</p>
</div>
</div>
</div>
</section>
<!-- END OF END OF OUR INDUSTRIES -->
<!-- FOOTER -->
<!-- Footer Section -->
<section class="container" style="margin: 5rem auto 2rem; background-color: #fff !important;">
<div class="row" style="max-width:1540px;">
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center mb-4">
<h2
style="font-weight: 300 !important; background-color: #fff !important; z-index: 1 !important; padding: 0 1rem !important;">
Need Help?</h2>
</div>
</div>
<!-- Contact Cards -->
<div class="row d-flex justify-content-center align-items-center"
style="max-width:1540px; margin: 0 auto 2rem auto;">
<div class="col-lg-12" style="padding: 0rem 3rem 2rem; border-radius: 10px; margin-right: 3rem; ">
<div class="row mt-4">
<div class="col-lg-6">
<a href="mailto:odoo@cybrosys.com" target="_blank" class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #4d4d4d; color: #FFF; border-radius: 4px;"><i
class="fa fa-envelope mr-2"></i>odoo@cybrosys.com</a>
</div>
<div class="col-lg-6">
<a href="https://api.whatsapp.com/send?phone=918606827707" target="_blank"
class="btn btn-block mb-2 deep_hover"
style="text-decoration: none; background-color: #25D366; color: #FFF; border-radius: 4px;">
<i class="fa fa-whatsapp mr-2"></i>+91 86068 27707</a>
</div>
</div>
</div>
</div>
<!-- End of Contact Cards -->
</section>
<!-- 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; ">
<!-- 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>
<!-- End of Logo -->
</div>
</section>
<!-- END OF FOOTER -->
</div>

BIN
code_backend_theme/static/description/theme_screenshot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

BIN
code_backend_theme/static/src/img/code_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
code_backend_theme/static/src/img/icons/Attendances.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

BIN
code_backend_theme/static/src/img/icons/CRM.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

BIN
code_backend_theme/static/src/img/icons/Calendar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

BIN
code_backend_theme/static/src/img/icons/Contacts.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

BIN
code_backend_theme/static/src/img/icons/Dashboards.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 B

BIN
code_backend_theme/static/src/img/icons/Email Marketing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

BIN
code_backend_theme/static/src/img/icons/Employees.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

BIN
code_backend_theme/static/src/img/icons/Events.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

BIN
code_backend_theme/static/src/img/icons/Expenses.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

BIN
code_backend_theme/static/src/img/icons/Fleet.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

BIN
code_backend_theme/static/src/img/icons/Inventory.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

BIN
code_backend_theme/static/src/img/icons/Invoicing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

BIN
code_backend_theme/static/src/img/icons/Link Tracker.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

BIN
code_backend_theme/static/src/img/icons/Live Chat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

BIN
code_backend_theme/static/src/img/icons/Lunch.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

BIN
code_backend_theme/static/src/img/icons/Maintenance.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

BIN
code_backend_theme/static/src/img/icons/Manufacturing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

BIN
code_backend_theme/static/src/img/icons/Members.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

BIN
code_backend_theme/static/src/img/icons/Note.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

BIN
code_backend_theme/static/src/img/icons/Point of Sale.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

BIN
code_backend_theme/static/src/img/icons/Project.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

BIN
code_backend_theme/static/src/img/icons/Purchase.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

BIN
code_backend_theme/static/src/img/icons/Recruitment.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

BIN
code_backend_theme/static/src/img/icons/Repairs.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

BIN
code_backend_theme/static/src/img/icons/SMS Marketing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

BIN
code_backend_theme/static/src/img/icons/Sales.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

BIN
code_backend_theme/static/src/img/icons/Surveys.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

BIN
code_backend_theme/static/src/img/icons/Time Off.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

BIN
code_backend_theme/static/src/img/icons/Timesheets.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

BIN
code_backend_theme/static/src/img/icons/Website.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

BIN
code_backend_theme/static/src/img/icons/apps.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

BIN
code_backend_theme/static/src/img/icons/close.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 B

BIN
code_backend_theme/static/src/img/icons/discuss.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

BIN
code_backend_theme/static/src/img/icons/eLearning.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

BIN
code_backend_theme/static/src/img/icons/settings.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

93
code_backend_theme/static/src/js/chrome/sidebar_menu.js

@ -0,0 +1,93 @@
odoo.define('code_backend_theme.SidebarMenu', function (require) {
"use strict";
//sidebar toggle effect
$(document).on("click", "#closeSidebar", function(event){
$("#closeSidebar").hide();
$("#openSidebar").show();
});
$(document).on("click", "#openSidebar", function(event){
$("#openSidebar").hide();
$("#closeSidebar").show();
});
$(document).on("click", "#openSidebar", function(event){
$("#sidebar_panel").css({'display':'block'});
$(".o_action_manager").css({'margin-left': '200px','transition':'all .1s linear'});
$(".top_heading").css({'margin-left': '200px','transition':'all .1s linear'});
//add class in navbar
var navbar = $(".o_main_navbar");
var navbar_id = navbar.data("id");
$("nav").addClass(navbar_id);
navbar.addClass("small_nav");
//add class in action-manager
var action_manager = $(".o_action_manager");
var action_manager_id = action_manager.data("id");
$("div").addClass(action_manager_id);
action_manager.addClass("sidebar_margin");
//add class in top_heading
var top_head = $(".top_heading");
var top_head_id = top_head.data("id");
$("div").addClass(top_head_id);
top_head.addClass("sidebar_margin");
});
$(document).on("click", "#closeSidebar", function(event){
$("#sidebar_panel").css({'display':'none'});
$(".o_action_manager").css({'margin-left': '0px'});
$(".top_heading").css({'margin-left': '0px'});
//remove class in navbar
var navbar = $(".o_main_navbar");
var navbar_id = navbar.data("id");
$("nav").removeClass(navbar_id);
navbar.removeClass("small_nav");
//remove class in action-manager
var action_manager = $(".o_action_manager");
var action_manager_id = action_manager.data("id");
$("div").removeClass(action_manager_id);
action_manager.removeClass("sidebar_margin");
//remove class in top_heading
var top_head = $(".top_heading");
var top_head_id = top_head.data("id");
$("div").removeClass(top_head_id);
top_head.removeClass("sidebar_margin");
});
$(document).on("click", ".sidebar a", function(event){
var menu = $(".sidebar a");
var $this = $(this);
var id = $this.data("id");
$("header").removeClass().addClass(id);
menu.removeClass("active");
$this.addClass("active");
//sidebar close on menu-item click
$("#sidebar_panel").css({'display':'none'});
$(".o_action_manager").css({'margin-left': '0px'});
$(".top_heading").css({'margin-left': '0px'});
$("#closeSidebar").hide();
$("#openSidebar").show();
//remove class in navbar
var navbar = $(".o_main_navbar");
var navbar_id = navbar.data("id");
$("nav").removeClass(navbar_id);
navbar.removeClass("small_nav");
//remove class in action-manager
var action_manager = $(".o_action_manager");
var action_manager_id = action_manager.data("id");
$("div").removeClass(action_manager_id);
action_manager.removeClass("sidebar_margin");
//remove class in top_heading
var top_head = $(".top_heading");
var top_head_id = top_head.data("id");
$("div").removeClass(top_head_id);
top_head.removeClass("sidebar_margin");
});
});

32
code_backend_theme/static/src/js/fields/colors.js

@ -0,0 +1,32 @@
/** @odoo-module **/
export const COLORS = ["#556ee6", "#f1b44c", "#50a5f1", "#ffbb78", "#34c38f", "#98df8a", "#d62728",
"#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2",
"#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"];
/**
* @param {number} index
* @returns {string}
*/
export function getColor(index) {
return COLORS[index % COLORS.length];
}
export const DEFAULT_BG = "#d3d3d3";
export const BORDER_WHITE = "rgba(255,255,255,0.6)";
const RGB_REGEX = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
/**
* @param {string} hex
* @param {number} opacity
* @returns {string}
*/
export function hexToRGBA(hex, opacity) {
const rgb = RGB_REGEX.exec(hex)
.slice(1, 4)
.map((n) => parseInt(n, 16))
.join(",");
return `rgba(${rgb},${opacity})`;
}

82
code_backend_theme/static/src/js/fields/graph_arch_parser.js

@ -0,0 +1,82 @@
/** @odoo-module **/
import { evaluateExpr } from "@web/core/py_js/py";
import { GROUPABLE_TYPES } from "@web/search/utils/misc";
import { XMLParser } from "@web/core/utils/xml";
import { archParseBoolean } from "@web/views/helpers/utils";
const MODES = ["bar", "line", "pie"];
const ORDERS = ["ASC", "DESC", null];
export class GraphArchParser extends XMLParser {
parse(arch, fields = {}) {
const archInfo = { fields, fieldAttrs: {}, groupBy: [] };
this.visitXML(arch, (node) => {
switch (node.tagName) {
case "graph": {
if (node.hasAttribute("disable_linking")) {
archInfo.disableLinking = archParseBoolean(
node.getAttribute("disable_linking")
);
}
if (node.hasAttribute("stacked")) {
archInfo.stacked = archParseBoolean(node.getAttribute("stacked"));
}
const mode = node.getAttribute("type");
if (mode && MODES.includes(mode)) {
archInfo.mode = mode;
}
const order = node.getAttribute("order");
if (order && ORDERS.includes(order)) {
archInfo.order = order;
}
const title = node.getAttribute("string");
if (title) {
archInfo.title = title;
}
break;
}
case "field": {
let fieldName = node.getAttribute("name"); // exists (rng validation)
if (fieldName === "id") {
break;
}
const string = node.getAttribute("string");
if (string) {
if (!archInfo.fieldAttrs[fieldName]) {
archInfo.fieldAttrs[fieldName] = {};
}
archInfo.fieldAttrs[fieldName].string = string;
}
const isInvisible = Boolean(
evaluateExpr(node.getAttribute("invisible") || "0")
);
if (isInvisible) {
if (!archInfo.fieldAttrs[fieldName]) {
archInfo.fieldAttrs[fieldName] = {};
}
archInfo.fieldAttrs[fieldName].isInvisible = true;
break;
}
const isMeasure = node.getAttribute("type") === "measure";
if (isMeasure) {
// the last field with type="measure" (if any) will be used as measure else __count
archInfo.measure = fieldName;
} else {
const { type } = archInfo.fields[fieldName]; // exists (rng validation)
if (GROUPABLE_TYPES.includes(type)) {
let groupBy = fieldName;
const interval = node.getAttribute("interval");
if (interval) {
groupBy += `:${interval}`;
}
archInfo.groupBy.push(groupBy);
}
}
break;
}
}
});
return archInfo;
}
}

537
code_backend_theme/static/src/js/fields/graph_model.js

@ -0,0 +1,537 @@
/** @odoo-module **/
import { sortBy } from "@web/core/utils/arrays";
import { KeepLast, Race } from "@web/core/utils/concurrency";
import { rankInterval } from "@web/search/utils/dates";
import { getGroupBy } from "@web/search/utils/group_by";
import { GROUPABLE_TYPES } from "@web/search/utils/misc";
import { Model } from "@web/views/helpers/model";
import { computeReportMeasures, processMeasure } from "@web/views/helpers/utils";
export const SEP = " / ";
/**
* @typedef {import("@web/search/search_model").SearchParams} SearchParams
*/
class DateClasses {
// We view the param "array" as a matrix of values and undefined.
// An equivalence class is formed of defined values of a column.
// So nothing has to do with dates but we only use Dateclasses to manage
// identification of dates.
/**
* @param {(any[])[]} array
*/
constructor(array) {
this.__referenceIndex = null;
this.__array = array;
for (let i = 0; i < this.__array.length; i++) {
const arr = this.__array[i];
if (arr.length && this.__referenceIndex === null) {
this.__referenceIndex = i;
}
}
}
/**
* @param {number} index
* @param {any} o
* @returns {string}
*/
classLabel(index, o) {
return `${this.__array[index].indexOf(o)}`;
}
/**
* @param {string} classLabel
* @returns {any[]}
*/
classMembers(classLabel) {
const classNumber = Number(classLabel);
const classMembers = new Set();
for (const arr of this.__array) {
if (arr[classNumber] !== undefined) {
classMembers.add(arr[classNumber]);
}
}
return [...classMembers];
}
/**
* @param {string} classLabel
* @param {number} [index]
* @returns {any}
*/
representative(classLabel, index) {
const classNumber = Number(classLabel);
const i = index === undefined ? this.__referenceIndex : index;
if (i === null) {
return null;
}
return this.__array[i][classNumber];
}
/**
* @param {number} index
* @returns {number}
*/
arrayLength(index) {
return this.__array[index].length;
}
}
export class GraphModel extends Model {
/**
* @override
*/
setup(params) {
// concurrency management
this.keepLast = new KeepLast();
this.race = new Race();
const _fetchDataPoints = this._fetchDataPoints.bind(this);
this._fetchDataPoints = (...args) => {
return this.race.add(_fetchDataPoints(...args));
};
this.initialGroupBy = null;
this.metaData = params;
this.data = null;
this.searchParams = null;
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @param {SearchParams} searchParams
*/
async load(searchParams) {
this.searchParams = searchParams;
if (!this.initialGroupBy) {
this.initialGroupBy = searchParams.context.graph_groupbys || this.metaData.groupBy; // = arch groupBy --> change that
}
const metaData = this._buildMetaData();
return this._fetchDataPoints(metaData);
}
/**
* @override
*/
hasData() {
return this.dataPoints.length > 0;
}
/**
* Only supposed to be called to change one or several parameters among
* "measure", "mode", "order", and "stacked".
* @param {Object} params
*/
async updateMetaData(params) {
if ("measure" in params) {
const metaData = this._buildMetaData(params);
await this._fetchDataPoints(metaData);
} else {
await this.race.getCurrentProm();
this.metaData = Object.assign({}, this.metaData, params);
this._prepareData();
}
this.notify();
}
//--------------------------------------------------------------------------
// Protected
//--------------------------------------------------------------------------
/**
* @protected
* @param {Object} [params={}]
* @returns {Object}
*/
_buildMetaData(params) {
const { comparison, domain, context, groupBy } = this.searchParams;
const metaData = Object.assign({}, this.metaData, { context });
if (comparison) {
metaData.domains = comparison.domains;
metaData.comparisonField = comparison.fieldName;
} else {
metaData.domains = [{ arrayRepr: domain, description: null }];
}
metaData.measure = context.graph_measure || metaData.measure;
metaData.mode = context.graph_mode || metaData.mode;
metaData.groupBy = groupBy.length ? groupBy : this.initialGroupBy;
this._normalize(metaData);
metaData.measures = computeReportMeasures(
metaData.fields,
metaData.fieldAttrs,
[metaData.measure],
metaData.additionalMeasures
);
return Object.assign(metaData, params);
}
/**
* Fetch the data points determined by the metaData. This function has
* several side effects. It can alter this.metaData and set this.dataPoints.
* @protected
* @param {Object} metaData
*/
async _fetchDataPoints(metaData) {
this.dataPoints = await this.keepLast.add(this._loadDataPoints(metaData));
this.metaData = metaData;
this._prepareData();
}
/**
* Separates dataPoints coming from the read_group(s) into different
* datasets. This function returns the parameters data and labels used
* to produce the charts.
* @protected
* @param {Object[]}
* @returns {Object}
*/
_getData(dataPoints) {
const { comparisonField, groupBy, mode } = this.metaData;
let identify = false;
if (comparisonField && groupBy.length && groupBy[0].fieldName === comparisonField) {
identify = true;
}
const dateClasses = identify ? this._getDateClasses(dataPoints) : null;
// dataPoints --> labels
let labels = [];
const labelMap = {};
for (const dataPt of dataPoints) {
const x = dataPt.labels.slice(0, mode === "pie" ? undefined : 1);
const trueLabel = x.length ? x.join(SEP) : this.env._t("Total");
if (dateClasses) {
x[0] = dateClasses.classLabel(dataPt.originIndex, x[0]);
}
const key = JSON.stringify(x);
if (labelMap[key] === undefined) {
labelMap[key] = labels.length;
if (dateClasses) {
if (mode === "pie") {
x[0] = dateClasses.classMembers(x[0]).join(", ");
} else {
x[0] = dateClasses.representative(x[0]);
}
}
const label = x.length ? x.join(SEP) : this.env._t("Total");
labels.push(label);
}
dataPt.labelIndex = labelMap[key];
dataPt.trueLabel = trueLabel;
}
// dataPoints + labels --> datasetsTmp --> datasets
const datasetsTmp = {};
for (const dataPt of dataPoints) {
const { domain, labelIndex, originIndex, trueLabel, value } = dataPt;
const datasetLabel = this._getDatasetLabel(dataPt);
if (!(datasetLabel in datasetsTmp)) {
let dataLength = labels.length;
if (mode !== "pie" && dateClasses) {
dataLength = dateClasses.arrayLength(originIndex);
}
datasetsTmp[datasetLabel] = {
data: new Array(dataLength).fill(0),
trueLabels: labels.slice(0, dataLength), // should be good // check this in case identify = true
domains: new Array(dataLength).fill([]),
label: datasetLabel,
originIndex: originIndex,
};
}
datasetsTmp[datasetLabel].data[labelIndex] = value;
datasetsTmp[datasetLabel].domains[labelIndex] = domain;
datasetsTmp[datasetLabel].trueLabels[labelIndex] = trueLabel;
}
// sort by origin
let datasets = sortBy(Object.values(datasetsTmp), "originIndex");
if (mode === "pie") {
// We kinda have a matrix. We remove the zero columns and rows. This is a global operation.
// That's why it cannot be done before.
datasets = datasets.filter((dataset) => dataset.data.some((v) => Boolean(v)));
const labelsToKeepIndexes = {};
labels.forEach((_, index) => {
if (datasets.some((dataset) => Boolean(dataset.data[index]))) {
labelsToKeepIndexes[index] = true;
}
});
labels = labels.filter((_, index) => labelsToKeepIndexes[index]);
for (const dataset of datasets) {
dataset.data = dataset.data.filter((_, index) => labelsToKeepIndexes[index]);
dataset.domains = dataset.domains.filter((_, index) => labelsToKeepIndexes[index]);
dataset.trueLabels = dataset.trueLabels.filter(
(_, index) => labelsToKeepIndexes[index]
);
}
}
return { datasets, labels };
}
/**
* Determines the dataset to which the data point belongs.
* @protected
* @param {Object} dataPoint
* @returns {string}
*/
_getDatasetLabel(dataPoint) {
const { measure, measures, domains, mode } = this.metaData;
const { labels, originIndex } = dataPoint;
if (mode === "pie") {
return domains[originIndex].description || "";
}
// ([origin] + second to last groupBys) or measure
let datasetLabel = labels.slice(1).join(SEP);
if (domains.length > 1) {
datasetLabel =
domains[originIndex].description + (datasetLabel ? SEP + datasetLabel : "");
}
datasetLabel = datasetLabel || measures[measure].string;
return datasetLabel;
}
/**
* @protected
* @param {Object[]} dataPoints
* @returns {DateClasses}
*/
_getDateClasses(dataPoints) {
const { domains } = this.metaData;
const dateSets = domains.map(() => new Set());
for (const { labels, originIndex } of dataPoints) {
const date = labels[0];
dateSets[originIndex].add(date);
}
const arrays = dateSets.map((dateSet) => [...dateSet]);
return new DateClasses(arrays);
}
/**
* Eventually filters and sort data points.
* @protected
* @returns {Object[]}
*/
_getProcessedDataPoints() {
const { domains, groupBy, mode, order } = this.metaData;
let processedDataPoints = [];
if (mode === "line") {
processedDataPoints = this.dataPoints.filter(
(dataPoint) => dataPoint.labels[0] !== this.env._t("Undefined")
);
} else {
processedDataPoints = this.dataPoints.filter((dataPoint) => dataPoint.count !== 0);
}
if (order !== null && mode !== "pie" && domains.length === 1 && groupBy.length > 0) {
// group data by their x-axis value, and then sort datapoints
// based on the sum of values by group in ascending/descending order
const groupedDataPoints = {};
for (const dataPt of processedDataPoints) {
const key = dataPt.labels[0]; // = x-axis value under the current assumptions
if (!groupedDataPoints[key]) {
groupedDataPoints[key] = [];
}
groupedDataPoints[key].push(dataPt);
}
const groups = Object.values(groupedDataPoints);
const groupTotal = (group) => group.reduce((sum, dataPt) => sum + dataPt.value, 0);
processedDataPoints = sortBy(groups, groupTotal, order.toLowerCase()).flat();
}
return processedDataPoints;
}
/**
* Determines whether the set of data points is good. If not, this.data will be (re)set to null
* @protected
* @param {Object[]}
* @returns {boolean}
*/
_isValidData(dataPoints) {
const { mode } = this.metaData;
let somePositive = false;
let someNegative = false;
if (mode === "pie") {
for (const dataPt of dataPoints) {
if (dataPt.value > 0) {
somePositive = true;
} else if (dataPt.value < 0) {
someNegative = true;
}
}
if (someNegative && somePositive) {
return false;
}
}
return true;
}
/**
* Fetch and process graph data. It is basically a(some) read_group(s)
* with correct fields for each domain. We have to do some light processing
* to separate date groups in the field list, because they can be defined
* with an aggregation function, such as my_date:week.
* @protected
* @param {Object} metaData
* @returns {Object[]}
*/
async _loadDataPoints(metaData) {
const { measure, domains, fields, groupBy, resModel } = metaData;
const measures = ["__count"];
if (measure !== "__count") {
let { group_operator, type } = fields[measure];
if (type === "many2one") {
group_operator = "count_distinct";
}
if (group_operator === undefined) {
throw new Error(
`No aggregate function has been provided for the measure '${measure}'`
);
}
measures.push(`${measure}:${group_operator}`);
}
const proms = [];
const numbering = {}; // used to avoid ambiguity with many2one with values with same labels:
// for instance [1, "ABC"] [3, "ABC"] should be distinguished.
domains.forEach((domain, originIndex) => {
proms.push(
this.orm
.webReadGroup(
resModel,
domain.arrayRepr,
measures,
groupBy.map((gb) => gb.spec),
{ lazy: false }, // what is this thing???
{ fill_temporal: true, ...this.searchParams.context }
)
.then((data) => {
const dataPoints = [];
for (const group of data.groups) {
const { __domain, __count } = group;
const labels = [];
for (const gb of groupBy) {
let label;
const val = group[gb.spec];
const fieldName = gb.fieldName;
const { type } = fields[fieldName];
if (type === "boolean") {
label = `${val}`; // toUpperCase?
} else if (val === false) {
label = this.env._t("Undefined");
} else if (type === "many2one") {
const [id, name] = val;
const key = JSON.stringify([fieldName, name]);
if (!numbering[key]) {
numbering[key] = {};
}
const numbers = numbering[key];
if (!numbers[id]) {
numbers[id] = Object.keys(numbers).length + 1;
}
const num = numbers[id];
label = num === 1 ? name : `${name} (${num})`;
} else if (type === "selection") {
const selected = fields[fieldName].selection.find(
(s) => s[0] === val
);
label = selected[1];
} else {
label = val;
}
labels.push(label);
}
let value = group[measure];
if (value instanceof Array) {
// case where measure is a many2one and is used as groupBy
value = 1;
}
if (!Number.isInteger(value)) {
metaData.allIntegers = false;
}
dataPoints.push({
count: __count,
domain: __domain,
value,
labels,
originIndex,
});
}
return dataPoints;
})
);
});
const promResults = await Promise.all(proms);
return promResults.flat();
}
/**
* Process metaData.groupBy in order to keep only the finest interval option for
* elements based on date/datetime field (e.g. 'date:year'). This means that
* 'week' is prefered to 'month'. The field stays at the place of its first occurence.
* For instance,
* ['foo', 'date:month', 'bar', 'date:week'] becomes ['foo', 'date:week', 'bar'].
* @protected
* @param {Object} metaData
*/
_normalize(metaData) {
const { fields } = metaData;
const groupBy = [];
for (const gb of metaData.groupBy) {
let ngb = gb;
if (typeof gb === "string") {
ngb = getGroupBy(gb, fields);
}
groupBy.push(ngb);
}
const processedGroupBy = [];
for (const gb of groupBy) {
const { fieldName, interval } = gb;
const { store, type } = fields[fieldName];
if (
!store ||
["id", "__count"].includes(fieldName) ||
!GROUPABLE_TYPES.includes(type)
) {
continue;
}
const index = processedGroupBy.findIndex((gb) => gb.fieldName === fieldName);
if (index === -1) {
processedGroupBy.push(gb);
} else if (interval) {
const registeredInterval = processedGroupBy[index].interval;
if (rankInterval(registeredInterval) < rankInterval(interval)) {
processedGroupBy.splice(index, 1, gb);
}
}
}
metaData.groupBy = processedGroupBy;
metaData.measure = processMeasure(metaData.measure);
}
/**
* @protected
*/
async _prepareData() {
const processedDataPoints = this._getProcessedDataPoints();
this.data = null;
if (this._isValidData(processedDataPoints)) {
this.data = this._getData(processedDataPoints);
}
}
}

627
code_backend_theme/static/src/js/fields/graph_renderer.js

@ -0,0 +1,627 @@
/** @odoo-module **/
import { _lt } from "@web/core/l10n/translation";
import { BORDER_WHITE, DEFAULT_BG, getColor, hexToRGBA } from "./colors";
import { formatFloat } from "@web/fields/formatters";
import { SEP } from "./graph_model";
import { sortBy } from "@web/core/utils/arrays";
import { useAssets } from "@web/core/assets";
import { useEffect } from "@web/core/utils/hooks";
const { Component, hooks } = owl;
const { useRef } = hooks;
const NO_DATA = _lt("No data");
/**
* @param {Object} chartArea
* @returns {string}
*/
function getMaxWidth(chartArea) {
const { left, right } = chartArea;
return Math.floor((right - left) / 1.618) + "px";
}
/**
* Used to avoid too long legend items.
* @param {string|Strin} label
* @returns {string} shortened version of the input label
*/
function shortenLabel(label) {
// string returned could be wrong if a groupby value contain a " / "!
const groups = label.toString().split(SEP);
let shortLabel = groups.slice(0, 3).join(SEP);
if (shortLabel.length > 30) {
shortLabel = `${shortLabel.slice(0, 30)}...`;
} else if (groups.length > 3) {
shortLabel = `${shortLabel}${SEP}...`;
}
return shortLabel;
}
export class GraphRenderer extends Component {
setup() {
this.model = this.props.model;
this.canvasRef = useRef("canvas");
this.containerRef = useRef("container");
this.chart = null;
this.tooltip = null;
this.legendTooltip = null;
useAssets({ jsLibs: ["/web/static/lib/Chart/Chart.js"] });
useEffect(() => this.renderChart());
}
willUnmount() {
if (this.chart) {
this.chart.destroy();
}
}
/**
* This function aims to remove a suitable number of lines from the
* tooltip in order to make it reasonably visible. A message indicating
* the number of lines is added if necessary.
* @param {HTMLElement} tooltip
* @param {number} maxTooltipHeight this the max height in pixels of the tooltip
*/
adjustTooltipHeight(tooltip, maxTooltipHeight) {
const sizeOneLine = tooltip.querySelector("tbody tr").clientHeight;
const tbodySize = tooltip.querySelector("tbody").clientHeight;
const toKeep = Math.max(
0,
Math.floor((maxTooltipHeight - (tooltip.clientHeight - tbodySize)) / sizeOneLine) - 1
);
const lines = tooltip.querySelectorAll("tbody tr");
const toRemove = lines.length - toKeep;
if (toRemove > 0) {
for (let index = toKeep; index < lines.length; ++index) {
lines[index].remove();
}
const tr = document.createElement("tr");
const td = document.createElement("td");
tr.classList.add("o_show_more");
td.innerText = this.env._t("...");
tr.appendChild(td);
tooltip.querySelector("tbody").appendChild(tr);
}
}
/**
* Creates a custom HTML tooltip.
* @param {Object} data
* @param {Object} metaData
* @param {Object} tooltipModel see chartjs documentation
*/
customTooltip(data, metaData, tooltipModel) {
const { measure, measures, disableLinking, mode } = metaData;
this.el.style.cursor = "";
this.removeTooltips();
if (tooltipModel.opacity === 0 || tooltipModel.dataPoints.length === 0) {
return;
}
if (!disableLinking && mode !== "line") {
this.el.style.cursor = "pointer";
}
const chartAreaTop = this.chart.chartArea.top;
const viewContentTop = this.el.getBoundingClientRect().top;
const innerHTML = this.env.qweb.renderToString("web.GraphRenderer.CustomTooltip", {
maxWidth: getMaxWidth(this.chart.chartArea),
measure: measures[measure].string,
tooltipItems: this.getTooltipItems(data, metaData, tooltipModel),
});
const template = Object.assign(document.createElement("template"), { innerHTML });
const tooltip = template.content.firstChild;
this.containerRef.el.prepend(tooltip);
let top;
const tooltipHeight = tooltip.clientHeight;
const minTopAllowed = Math.floor(chartAreaTop);
const maxTopAllowed = Math.floor(window.innerHeight - (viewContentTop + tooltipHeight)) - 2;
const y = Math.floor(tooltipModel.y);
if (minTopAllowed <= maxTopAllowed) {
// Here we know that the full tooltip can fit in the screen.
// We put it in the position where Chart.js would put it
// if two conditions are respected:
// 1: the tooltip is not cut (because we know it is possible to not cut it)
// 2: the tooltip does not hide the legend.
// If it is not possible to use the Chart.js proposition (y)
// we use the best approximated value.
if (y <= maxTopAllowed) {
if (y >= minTopAllowed) {
top = y;
} else {
top = minTopAllowed;
}
} else {
top = maxTopAllowed;
}
} else {
// Here we know that we cannot satisfy condition 1 above,
// so we position the tooltip at the minimal position and
// cut it the minimum possible.
top = minTopAllowed;
const maxTooltipHeight = window.innerHeight - (viewContentTop + chartAreaTop) - 2;
this.adjustTooltipHeight(tooltip, maxTooltipHeight);
}
this.fixTooltipLeftPosition(tooltip, tooltipModel.x);
tooltip.style.top = Math.floor(top) + "px";
this.tooltip = tooltip;
}
/**
* Sets best left position of a tooltip approaching the proposal x.
* @param {HTMLElement} tooltip
* @param {number} x
*/
fixTooltipLeftPosition(tooltip, x) {
let left;
const tooltipWidth = tooltip.clientWidth;
const minLeftAllowed = Math.floor(this.chart.chartArea.left + 2);
const maxLeftAllowed = Math.floor(this.chart.chartArea.right - tooltipWidth - 2);
x = Math.floor(x);
if (x < minLeftAllowed) {
left = minLeftAllowed;
} else if (x > maxLeftAllowed) {
left = maxLeftAllowed;
} else {
left = x;
}
tooltip.style.left = `${left}px`;
}
/**
* Used to format correctly the values in tooltips and yAxes.
* @param {number} value
* @param {boolean} [allIntegers=true]
* @returns {string}
*/
formatValue(value, allIntegers = true) {
const largeNumber = Math.abs(value) >= 1000;
if (allIntegers && !largeNumber) {
return String(value);
}
if (largeNumber) {
return formatFloat(value, { humanReadable: true, decimals: 2, minDigits: 1 });
}
return formatFloat(value);
}
/**
* Returns the bar chart data
* @returns {Object}
*/
getBarChartData() {
// style data
const { domains, stacked } = this.model.metaData;
const data = this.model.data;
for (let index = 0; index < data.datasets.length; ++index) {
const dataset = data.datasets[index];
// used when stacked
if (stacked) {
dataset.stack = domains[dataset.originIndex].description || "";
}
// set dataset color
dataset.backgroundColor = getColor(index);
}
return data;
}
/**
* Returns the chart config.
* @returns {Object}
*/
getChartConfig() {
const { mode } = this.model.metaData;
let data;
switch (mode) {
case "bar":
data = this.getBarChartData();
break;
case "line":
data = this.getLineChartData();
break;
case "pie":
data = this.getPieChartData();
}
const options = this.prepareOptions();
return { data, options, type: mode };
}
/**
* Returns an object used to style chart elements independently from
* the datasets.
* @returns {Object}
*/
getElementOptions() {
const { mode } = this.model.metaData;
const elementOptions = {};
if (mode === "bar") {
elementOptions.rectangle = { borderWidth: 1 };
} else if (mode === "line") {
elementOptions.line = { fill: false, tension: 0 };
}
return elementOptions;
}
/**
* @returns {Object}
*/
getLegendOptions() {
const { mode } = this.model.metaData;
const data = this.model.data;
const refLength = mode === "pie" ? data.labels.length : data.datasets.length;
const legendOptions = {
display: refLength <= 20,
position: "top",
onHover: this.onlegendHover.bind(this),
onLeave: this.onLegendLeave.bind(this),
};
if (mode === "line") {
legendOptions.onClick = this.onLegendClick.bind(this);
}
if (mode === "pie") {
legendOptions.labels = {
generateLabels: (chart) => {
const { data } = chart;
const metaData = data.datasets.map(
(_, index) => chart.getDatasetMeta(index).data
);
const labels = data.labels.map((label, index) => {
const hidden = metaData.some((data) => data[index] && data[index].hidden);
const fullText = label;
const text = shortenLabel(fullText);
const fillStyle = label === NO_DATA ? DEFAULT_BG : getColor(index);
return { text, fullText, fillStyle, hidden, index };
});
return labels;
},
};
} else {
const referenceColor = mode === "bar" ? "backgroundColor" : "borderColor";
legendOptions.labels = {
generateLabels: (chart) => {
const { data } = chart;
const labels = data.datasets.map((dataset, index) => {
return {
text: shortenLabel(dataset.label),
fullText: dataset.label,
fillStyle: dataset[referenceColor],
hidden: !chart.isDatasetVisible(index),
lineCap: dataset.borderCapStyle,
lineDash: dataset.borderDash,
lineDashOffset: dataset.borderDashOffset,
lineJoin: dataset.borderJoinStyle,
lineWidth: dataset.borderWidth,
strokeStyle: dataset[referenceColor],
pointStyle: dataset.pointStyle,
datasetIndex: index,
};
});
return labels;
},
};
}
return legendOptions;
}
/**
* Returns line chart data.
* @returns {Object}
*/
getLineChartData() {
const { groupBy, domains } = this.model.metaData;
const data = this.model.data;
for (let index = 0; index < data.datasets.length; ++index) {
const dataset = data.datasets[index];
if (groupBy.length <= 1 && domains.length > 1) {
if (dataset.originIndex === 0) {
dataset.fill = "origin";
dataset.backgroundColor = hexToRGBA(getColor(0), 0.4);
dataset.borderColor = getColor(0);
} else if (dataset.originIndex === 1) {
dataset.borderColor = getColor(1);
} else {
dataset.borderColor = getColor(index);
}
} else {
dataset.borderColor = getColor(index);
}
if (data.labels.length === 1) {
// shift of the real value to right. This is done to
// center the points in the chart. See data.labels below in
// Chart parameters
dataset.data.unshift(undefined);
dataset.trueLabels.unshift(undefined);
dataset.domains.unshift(undefined);
}
dataset.pointBackgroundColor = dataset.borderColor;
dataset.pointBorderColor = "rgba(0,0,0,0.2)";
}
if (data.datasets.length === 1) {
const dataset = data.datasets[0];
dataset.fill = "origin";
dataset.backgroundColor = hexToRGBA(getColor(0), 0.4);
}
// center the points in the chart (without that code they are put
// on the left and the graph seems empty)
data.labels = data.labels.length > 1 ? data.labels : ["", ...data.labels, ""];
return data;
}
/**
* Returns pie chart data.
* @returns {Object}
*/
getPieChartData() {
const { domains } = this.model.metaData;
const data = this.model.data;
// style/complete data
// give same color to same groups from different origins
const colors = data.labels.map((_, index) => getColor(index));
for (const dataset of data.datasets) {
dataset.backgroundColor = colors;
dataset.borderColor = BORDER_WHITE;
}
// make sure there is a zone associated with every origin
const representedOriginIndexes = new Set(
data.datasets.map((dataset) => dataset.originIndex)
);
let addNoDataToLegend = false;
const fakeData = new Array(data.labels.length + 1);
fakeData[data.labels.length] = 1;
const fakeTrueLabels = new Array(data.labels.length + 1);
fakeTrueLabels[data.labels.length] = NO_DATA;
for (let index = 0; index < domains.length; ++index) {
if (!representedOriginIndexes.has(index)) {
data.datasets.push({
label: domains[index].description,
data: fakeData,
trueLabels: fakeTrueLabels,
backgroundColor: [...colors, DEFAULT_BG],
borderColor: BORDER_WHITE,
});
addNoDataToLegend = true;
}
}
if (addNoDataToLegend) {
data.labels.push(NO_DATA);
}
return data;
}
/**
* Returns the options used to generate the chart axes.
* @returns {Object}
*/
getScaleOptions() {
const {
allIntegers,
displayScaleLabels,
fields,
groupBy,
measure,
measures,
mode,
} = this.model.metaData;
if (mode === "pie") {
return {};
}
const xAxe = {
type: "category",
scaleLabel: {
display: Boolean(groupBy.length && displayScaleLabels),
labelString: groupBy.length ? fields[groupBy[0].fieldName].string : "",
},
};
const yAxe = {
type: "linear",
scaleLabel: {
display: displayScaleLabels,
labelString: measures[measure].string,
},
ticks: {
callback: (value) => this.formatValue(value, allIntegers),
suggestedMax: 0,
suggestedMin: 0,
},
};
return { xAxes: [xAxe], yAxes: [yAxe] };
}
/**
* This function extracts the information from the data points in
* tooltipModel.dataPoints (corresponding to datapoints over a given
* label determined by the mouse position) that will be displayed in a
* custom tooltip.
* @param {Object} data
* @param {Object} metaData
* @param {Object} tooltipModel see chartjs documentation
* @returns {Object[]}
*/
getTooltipItems(data, metaData, tooltipModel) {
const { allIntegers, domains, mode, groupBy } = metaData;
const sortedDataPoints = sortBy(tooltipModel.dataPoints, "yLabel", "desc");
const items = [];
for (const item of sortedDataPoints) {
const id = item.index;
const dataset = data.datasets[item.datasetIndex];
let label = dataset.trueLabels[id];
let value = this.formatValue(dataset.data[id], allIntegers);
let boxColor;
if (mode === "pie") {
if (label === NO_DATA) {
value = this.formatValue(0, allIntegers);
}
if (domains.length > 1) {
label = `${dataset.label} / ${label}`;
}
boxColor = dataset.backgroundColor[id];
} else {
if (groupBy.length > 1 || domains.length > 1) {
label = `${label} / ${dataset.label}`;
}
boxColor = mode === "bar" ? dataset.backgroundColor : dataset.borderColor;
}
items.push({ id, label, value, boxColor });
}
return items;
}
/**
* Returns the options used to generate chart tooltips.
* @returns {Object}
*/
getTooltipOptions() {
const { data, metaData } = this.model;
const { mode } = metaData;
const tooltipOptions = {
enabled: false,
custom: this.customTooltip.bind(this, data, metaData),
};
if (mode === "line") {
tooltipOptions.mode = "index";
tooltipOptions.intersect = false;
}
return tooltipOptions;
}
/**
* If a group has been clicked on, display a view of its records.
* @param {MouseEvent} ev
*/
onGraphClicked(ev) {
const [activeElement] = this.chart.getElementAtEvent(ev);
if (!activeElement) {
return;
}
const { _datasetIndex, _index } = activeElement;
const { domains } = this.chart.data.datasets[_datasetIndex];
if (domains) {
this.props.onGraphClicked(domains[_index]);
}
}
/**
* Overrides the default legend 'onClick' behaviour. This is done to
* remove all existing tooltips right before updating the chart.
* @param {Event} ev
* @param {Object} legendItem
*/
onLegendClick(ev, legendItem) {
this.removeTooltips();
// Default 'onClick' fallback. See web/static/lib/Chart/Chart.js#15138
const index = legendItem.datasetIndex;
const meta = this.chart.getDatasetMeta(index);
meta.hidden = meta.hidden === null ? !this.chart.data.datasets[index].hidden : null;
this.chart.update();
}
/**
* If the text of a legend item has been shortened and the user mouse
* hovers that item (actually the event type is mousemove), a tooltip
* with the item full text is displayed.
* @param {Event} ev
* @param {Object} legendItem
*/
onlegendHover(ev, legendItem) {
this.canvasRef.el.style.cursor = "pointer";
/**
* The string legendItem.text is an initial segment of legendItem.fullText.
* If the two coincide, no need to generate a tooltip. If a tooltip
* for the legend already exists, it is already good and does not
* need to be recreated.
*/
const { fullText, text } = legendItem;
if (this.legendTooltip || text === fullText) {
return;
}
const viewContentTop = this.el.getBoundingClientRect().top;
const legendTooltip = Object.assign(document.createElement("div"), {
className: "o_tooltip_legend",
innerText: fullText,
});
legendTooltip.style.top = `${ev.clientY - viewContentTop}px`;
legendTooltip.style.maxWidth = getMaxWidth(this.chart.chartArea);
this.containerRef.el.appendChild(legendTooltip);
this.fixTooltipLeftPosition(legendTooltip, ev.clientX);
this.legendTooltip = legendTooltip;
}
/**
* If there's a legend tooltip and the user mouse out of the
* corresponding legend item, the tooltip is removed.
*/
onLegendLeave() {
this.canvasRef.el.style.cursor = "";
this.removeLegendTooltip();
}
/**
* Prepares options for the chart according to the current mode
* (= chart type). This function returns the parameter options used to
* instantiate the chart.
*/
prepareOptions() {
const { disableLinking, mode } = this.model.metaData;
const options = {
maintainAspectRatio: false,
scales: this.getScaleOptions(),
legend: this.getLegendOptions(),
tooltips: this.getTooltipOptions(),
elements: this.getElementOptions(),
};
if (!disableLinking && mode !== "line") {
options.onClick = this.onGraphClicked.bind(this);
}
return options;
}
/**
* Removes the legend tooltip (if any).
*/
removeLegendTooltip() {
if (this.legendTooltip) {
this.legendTooltip.remove();
this.legendTooltip = null;
}
}
/**
* Removes all existing tooltips (if any).
*/
removeTooltips() {
if (this.tooltip) {
this.tooltip.remove();
this.tooltip = null;
}
this.removeLegendTooltip();
}
/**
* Instantiates a Chart (Chart.js lib) to render the graph according to
* the current config.
*/
renderChart() {
if (this.chart) {
this.chart.destroy();
}
const config = this.getChartConfig();
this.chart = new Chart(this.canvasRef.el, config);
// To perform its animations, ChartJS will perform each animation
// step in the next animation frame. The initial rendering itself
// is delayed for consistency. We can avoid this by manually
// advancing the animation service.
Chart.animationService.advance();
}
}
GraphRenderer.template = "web.GraphRenderer";
GraphRenderer.props = ["model", "onGraphClicked"];

160
code_backend_theme/static/src/js/fields/graph_view.js

@ -0,0 +1,160 @@
/** @odoo-module **/
import { _lt } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { GroupByMenu } from "@web/search/group_by_menu/group_by_menu";
import { standardViewProps } from "@web/views/helpers/standard_view_props";
import { useSetupView } from "@web/views/helpers/view_hook";
import { Layout } from "@web/views/layout";
import { useModel } from "@web/views/helpers/model";
import { GraphArchParser } from "./graph_arch_parser";
import { GraphModel } from "./graph_model";
import { GraphRenderer } from "./graph_renderer";
const viewRegistry = registry.category("views");
const { Component } = owl;
export class GraphView extends Component {
setup() {
this.actionService = useService("action");
let modelParams;
if (this.props.state) {
modelParams = this.props.state.metaData;
} else {
const { arch, fields } = this.props;
const parser = new this.constructor.ArchParser();
const archInfo = parser.parse(arch, fields);
modelParams = {
additionalMeasures: this.props.additionalMeasures,
disableLinking: Boolean(archInfo.disableLinking),
displayScaleLabels: this.props.displayScaleLabels,
fieldAttrs: archInfo.fieldAttrs,
fields: this.props.fields,
groupBy: archInfo.groupBy,
measure: archInfo.measure || "__count",
mode: archInfo.mode || "bar",
order: archInfo.order || null,
resModel: this.props.resModel,
stacked: "stacked" in archInfo ? archInfo.stacked : true,
title: archInfo.title || this.env._t("Untitled"),
};
}
this.model = useModel(this.constructor.Model, modelParams);
useSetupView({
getLocalState: () => {
return { metaData: this.model.metaData };
},
getContext: () => this.getContext(),
});
}
/**
* @returns {Object}
*/
getContext() {
// expand context object? change keys?
const { measure, groupBy, mode } = this.model.metaData;
return {
graph_measure: measure,
graph_mode: mode,
graph_groupbys: groupBy.map((gb) => gb.spec),
};
}
/**
* @param {string} domain the domain of the clicked area
*/
onGraphClicked(domain) {
const { context, resModel, title } = this.model.metaData;
const views = {};
for (const [viewId, viewType] of this.env.config.views || []) {
views[viewType] = viewId;
}
function getView(viewType) {
return [views[viewType] || false, viewType];
}
const actionViews = [getView("list"), getView("form")];
this.actionService.doAction(
{
context,
domain,
name: title,
res_model: resModel,
target: "current",
type: "ir.actions.act_window",
views: actionViews,
},
{
viewType: "list",
}
);
}
/**
* @param {CustomEvent} ev
*/
onMeasureSelected(ev) {
const { measure } = ev.detail.payload;
this.model.updateMetaData({ measure });
}
/**
* @param {"bar"|"line"|"pie"} mode
*/
onModeSelected(mode) {
this.model.updateMetaData({ mode });
}
/**
* @param {"ASC"|"DESC"} order
*/
toggleOrder(order) {
const { order: currentOrder } = this.model.metaData;
const nextOrder = currentOrder === order ? null : order;
this.model.updateMetaData({ order: nextOrder });
}
toggleStacked() {
const { stacked } = this.model.metaData;
this.model.updateMetaData({ stacked: !stacked });
}
}
GraphView.template = "web.GraphView";
GraphView.buttonTemplate = "web.GraphView.Buttons";
GraphView.components = { GroupByMenu, Renderer: GraphRenderer, Layout };
GraphView.defaultProps = {
additionalMeasures: [],
displayGroupByMenu: false,
displayScaleLabels: true,
};
GraphView.props = {
...standardViewProps,
additionalMeasures: { type: Array, elements: String, optional: true },
displayGroupByMenu: { type: Boolean, optional: true },
displayScaleLabels: { type: Boolean, optional: true },
};
GraphView.type = "graph";
GraphView.display_name = _lt("Graph");
GraphView.icon = "fa-bar-chart";
GraphView.multiRecord = true;
GraphView.Model = GraphModel;
GraphView.ArchParser = GraphArchParser;
GraphView.searchMenuTypes = ["filter", "groupBy", "comparison", "favorite"];
viewRegistry.add("graph", GraphView);

57
code_backend_theme/static/src/js/user_menu/user_menu.js

@ -0,0 +1,57 @@
/** @odoo-module **/
import { browser } from "@web/core/browser/browser";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { registry } from "@web/core/registry";
import { useEffect, useService } from "@web/core/utils/hooks";
const { Component } = owl;
const userMenuRegistry = registry.category("user_menuitems");
class UserMenuItem extends DropdownItem {
setup() {
super.setup();
useEffect(
() => {
if (this.props.payload.id) {
this.el.dataset.menu = this.props.payload.id;
}
},
() => []
);
}
}
export class UserMenu extends Component {
setup() {
this.user = useService("user");
const { origin } = browser.location;
const { userId } = this.user;
this.source = `${origin}/web/image?model=res.users&field=avatar_128&id=${userId}`;
}
getElements() {
const sortedItems = userMenuRegistry
.getAll()
.map((element) => element(this.env))
.sort((x, y) => {
const xSeq = x.sequence ? x.sequence : 100;
const ySeq = y.sequence ? y.sequence : 100;
return xSeq - ySeq;
});
return sortedItems;
}
onDropdownItemSelected(ev) {
ev.detail.payload.callback();
}
}
UserMenu.template = "web.UserMenu";
UserMenu.components = { UserMenuItem };
const systrayItem = {
Component: UserMenu,
isDisplayed: (env) => true,
};
registry.category("systray").add("web.user_menu", systrayItem, { sequence: 0 });

68
code_backend_theme/static/src/scss/datetimepicker.scss

@ -0,0 +1,68 @@
/* date time picker colour changes for the theme */
.datepicker {
.table-sm {
> thead {
> tr > .prev {
color: #fff !important;
background-color: $primary_accent !important;
&:hover{
background-color: darken($primary_accent, 10%) !important;
}
> .fa{
color: #fff !important;
}
}
> tr > .next {
color: #fff !important;
background-color: $primary_accent !important;
&:hover{
background-color: darken($primary_accent, 10%) !important;
}
> .fa{
color: #fff !important;
}
}
> tr > .picker-switch {
color: #fff !important;
background-color: $primary_accent !important;
&:hover{
background-color: darken($primary_accent, 10%) !important;
}
}
}
> tbody > tr > td {
&.today:before {
border-bottom-color: $primary_accent !important;
}
&.active {
background-color: $primary_accent !important;
}
}
}
}
.picker-switch {
span.fa {
margin: 0;
@include transition($btn-transition);
&.primary {
background-color: $primary_accent;
color: white;
&:hover {
background-color: darken($primary_accent, 20%);
}
}
}
}
.daterangepicker .drp-calendar .calendar-table thead tr:first-child {
color: #FFFFFF;
background-color: $primary_accent;
}
.daterangepicker .drp-calendar .calendar-table tbody tr td:not(.off).active, .daterangepicker .drp-calendar .calendar-table tbody tr td:not(.off).active:hover {
background-color: $primary_accent;
}
.daterangepicker .drp-calendar .calendar-table thead tr:first-child th.prev:hover, .daterangepicker .drp-calendar .calendar-table thead tr:first-child th.next:hover {
background-color: darken($primary_accent, 20%);
}

146
code_backend_theme/static/src/scss/login.scss

@ -0,0 +1,146 @@
#wrapwrap > main {
background: #f8f8fb;
}
.navbar {
background: #fff !important;
}
body {
font-family: 'Poppins', sans-serif !important;
}
body.bg-100 {
background-color: #000000 !important;
}
.card.o_database_list {
align-items: center;
max-width: 450px !important
}
.card.o_database_list .card-body {
background-color: #fff !important;
border-radius: 5px !important;
-webkit-box-shadow: 0 0.75rem 1.5rem rgba(18,38,63,.03) !important;
box-shadow: 0 0.75rem 1.5rem rgba(18,38,63, .03) !important;
width: 450px;
}
a {
color: #556ee6;
text-decoration: none;
}
a:hover {
color: #4458b8;
text-decoration: underline;
}
.alert-info {
color: #306391;
background-color: #dcedfc;
border-color: #cbe4fb;
}
.oe_login_form button.btn-link {
color: #495057;
font-weight: 500;
font-size: 14px !important;
}
.oe_login_form button.btn-link:hover {
color: #171a1c;
}
//login button starts
.btn-primary {
color: #fff;
background-color: #556ee6;
border-color: #556ee6;
}
.btn-primary:hover {
color: #fff;
background-color: #485ec4;
border-color: #4458b8;
}
.btn-check:active+.btn-primary,
.btn-check:checked+.btn-primary,
.btn-primary.active,.btn-primary:active,
.show>.btn-primary.dropdown-toggle {
color: #fff;
background-color: #4458b8 !important;
border-color: #4053ad !important;
}
.btn-check:focus+.btn-primary, .btn-primary:focus {
color: #fff;
background-color: #485ec4 !important;
border-color: #4458b8 !important;
-webkit-box-shadow: 0 0 0 .15rem rgba(111,132,234,.5) !important;
box-shadow: 0 0 0 .15rem rgba(111,132,234,.5) !important;
}
.oe_login_form .btn {
display: inline-block;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
padding: .47rem .75rem;
border-radius: .25rem;
-webkit-transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
}
.btn-secondary {
color: #fff !important;
background-color: #74788d !important;
border-color: #74788d !important;
}
.btn-secondary:hover {
color: #fff !important;
background-color: #636678 !important;
border-color: #5d6071 !important;
}
.btn-secondary:active {
color: #fff;
background-color: #5d6071 !important;
border-color: #575a6a !important;
}
.btn-secondary i,.btn-secondary span {
color: #fff !important;
}
.btn-fill-secondary:focus, .btn-secondary:focus, .btn-fill-secondary.focus, .focus.btn-secondary {
box-shadow: none !important;
}
//login button ends
//input starts
.oe_login_form input {
display: block;
width: 100%;
height: 40px !important;
padding: 10px 20px;
font-size: 13px;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da !important;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: .25rem;
-webkit-transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
transition: border-color .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;
box-shadow: none !important;
margin-bottom:10px !important;
}
form label {
font-weight: 400 !important;
}
.oe_login_form a.btn.btn-secondary {
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.35rem 0.75rem;
}
.oe_login_form a.btn.btn-secondary i.fa.fa-database {
margin-left: 5px;
}

347
code_backend_theme/static/src/scss/navigation_bar.scss

@ -0,0 +1,347 @@
.o_form_statusbar{
.o_statusbar_buttons{
.btn{
margin-right: 30px !important;
}
}
}
.o_cp_left{
.btn{
margin-right: 30px !important;
}
}
.o_calendar_buttons > button > .fa{
color: #ffffff !important;
}
.o_main_navbar, .btn-primary, .btn-primary:active, .o_searchview_facet_label {
background-color: $primary_accent !important;
color: $inverse_accent !important;
}
.o_search_panel_section_icon {
color: $primary_accent !important;
}
.btn-secondary {
border-radius: 0;
border: solid 1px $primary_accent !important;
color: $primary_accent !important;
}
.o_list_view .o_list_table thead {
position: sticky;
top: 0;
}
.breadcrumb-item > a, .o_menu_item > a {
color: $primary_accent !important;
}
.fa-trash {
color: #f46a6a !important;
}
.o_main_navbar > a:hover {
background-color: lighten($primary_accent, 10%) !important;
}
.o_main_navbar > .o_menu_sections > li > a:hover, .o_main_navbar > .o_menu_systray > li > a:hover, .o_main_navbar > .o_menu_sections > li.show > a, .o_main_navbar > .o_menu_systray > li.show > a {
background-color: lighten($primary_accent, 10%) !important;
}
.o_main_navbar > .o_menu_apps > li > a:hover, .o_main_navbar > .o_menu_apps > li > a:active {
background-color: lighten($primary_accent, 10%) !important;
}
.o_main_navbar > .o_menu_apps > .dropdown.show > .dropdown-menu.show {
max-height: 100vh !important;
height: 93vh !important;
}
.o_main_navbar > .o_menu_apps > .dropdown.show > .dropdown-menu.show > a {
//border-bottom: 1px solid lighten($primary_accent, 30%);
}
.o_mail_discuss_sidebar {
background-color: #1c2833;
}
.dropdown-toggle:after {
background-color: lighten($primary_accent, 10%) !important;
}
.o_external_button {
border: none !important;
}
.o_field_x2many_list_row_add > a {
color: $primary_accent !important;
}
.nav-item > a {
color: $primary_accent !important;
}
.o_main_navbar > .o_menu_apps > li > a > i {
color: $inverse_accent !important;
font-size: 16px !important;
}
.o_form_uri > span {
color: $primary_accent !important;
}
.o_required_modifier.o_input {
background-color: $inverse_accent !important;
color: $primary_accent !important;
border-left: solid 3px #f46a6a !important;
}
.o_input {
border: solid 1px $primary_accent !important;
color: $primary_accent !important;
}
.o-no-caret > i, button[aria-pressed=true] {
color: $inverse_accent !important;
}
.o_loading {
background-color: $primary_accent;
}
.fas {
color: $inverse_accent !important;
}
.dashboard_mainbar {
width: 100%;
}
.a_app_menu_title {
display: none;
}
.o_menu_apps > .dropdown.show > .dropdown-menu.show:hover .a_app_menu_title {
display: inline-block;
width: 200px;
}
.o_required_modifier.o_input, .o_required_modifier.o_input {
background-color: $inverse_accent !important;
color: $primary_accent !important;
border-left: solid 3px #f46a6a !important;
}
.o_required_modifier .o_input, .o_required_modifier .o_input {
background-color: $inverse_accent !important;
}
.dropdown-toggle:after {
background-color: #ffffff00 !important;
}
.o_required_modifier > .o_input_dropdown > .ui-autocomplete-input {
background-color: $inverse_accent !important;
color: $primary_accent !important;
border-left: solid 3px #f46a6a !important;
}
.o_datepicker.o_field_date.o_field_widget.o_required_modifier > input {
background-color: $inverse_accent !important;
color: $primary_accent !important;
border-left: solid 3px #f46a6a !important;
}
.ui-state-active {
background-color: $primary_accent !important;
color: $inverse_accent !important;
}
.oe_search_bgnd {
background-color: lighten($primary_accent, 20%) !important;
color: $inverse_accent !important;
}
.oe_search_tab {
background-color: $primary_accent !important;
color: $inverse_accent !important;
}
.o_horizontal_separator {
color: $primary_accent !important
}
.o_field_widget.o_field_image .o_form_image_controls {
background-color: $primary_accent !important;
}
.o_field_widget.o_field_image .o_form_image_controls > button {
color: $inverse_accent !important;
}
.dropdown-item.o_app.mt0:hover , .dropdown-item.o_app.mt0:hover > .a_app_menu_title{
background-color: $primary_accent !important;
color: $inverse_accent !important;
}
// .o_address_country{
// display: none !important;
// }
div.o_boolean_toggle.custom-control.custom-checkbox > input.custom-control-input:checked + label.custom-control-label::before {
background-color: $primary_accent !important;
}
div.o_boolean_toggle.custom-control.custom-checkbox > input.custom-control-input:checked + label.custom-control-label::before {
background-color: $primary_accent !important;
}
.o_mail_systray_item .o_mail_systray_dropdown .o_mail_systray_dropdown_top .o_filter_button.active {
color: $primary_accent;
text-decoration: none;
}
.o_mail_user_status.o_user_online {
color: #fff !important;
}
.o_form_view .o_form_statusbar > .o_statusbar_status > .o_arrow_button.btn-primary.disabled::after {
border-left-color: $primary_accent;
}
.btn-link {
font-weight: 400;
color: $primary_accent !important;
text-decoration: none;
}
.o_thread_window_header {
background-color: $primary_accent !important;
}
.o_thread_window_close,.o_thread_window_expand{
color: $inverse_accent !important;
}
.o_menu_sections, .o_menu_systray, .o_web_client > header{
background: $primary_accent !important;
}
.fa-building-o{
color: white !important;
}
.o_button_import, .oe_import_file{
background: #5aa29f !important;
color: white !important;
border: solid 2px #5aa29f !important;
}
.o_button_import:hover, .oe_import_file:hover,.o_button_import:active, .oe_import_file:active{
background: white !important;
color: #5aa29f !important;
border: solid 2px #5aa29f !important;
}
.o_form_button_save,.o_form_button_edit{
background: #7BA94F !important;
color: white !important;
border: solid 2px #7BA94F !important;
}
.o_form_button_save:hover,.o_form_button_edit:hover,.o_form_button_save:active,.o_form_button_edit:active{
background: white !important;
color: #7BA94F !important;
border: solid 2px #7BA94F !important;
}
.o-kanban-button-new, .o_list_button_add,.o_form_button_create{
background: #b9408d !important;
color: white !important;
border: solid 2px #b9408d !important;
}
.o-kanban-button-new:hover, .o_list_button_add:hover,.o_form_button_create:hover,.o-kanban-button-new:active, .o_list_button_add:active,.o_form_button_create:active{
background: white !important;
color: #b9408d !important;
border: solid 2px #b9408d !important;
}
.o_form_button_cancel,.o_import_cancel{
background: #cf4137 !important;
color: white !important;
border: solid 2px #cf4137 !important;
}
.o_form_button_cancel:hover,.o_import_cancel:hover,.o_form_button_cancel:active,.o_import_cancel:active{
background: white !important;
color: #cf4137 !important;
border: solid 2px #cf4137 !important;
}
.report_button{
border-radius: 0 !important;
border: solid 2px $primary_accent;
background: $primary_accent !important;
}
.report_button:hover,.report_button:active{
border-radius: 0 !important;
border: solid 2px $primary_accent !important;
color: $primary_accent !important;
background: $inverse_accent !important;
}
.btn-primary{
border-radius: 0 !important;
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link{
border: none;
border-bottom: solid;
font-weight: bold;
}
.nav-link{
@include hover-focus {
border: none;
}
}
.o_data_row:has(.custom-control-input:checked){
background: blue !important;
}
.o_field_one2many{
.o_list_view{
.table-responsive{
max-height:50vh;
}
}
}
thead{
position: sticky;
position: -webkit-sticky;
top: 0;
}
.o_list_view .o_list_table tbody{
position: sticky;
top: 30px;
}
.o_list_view{
.o_list_table{
thead{
z-index:999;
}
}
}
.o_list_view .table-responsive .table{
width: max-content !important;
min-width: 100%;
thead
{
z-index:999;
tr:nth-child(1) th{
position: sticky;
top: 0;
z-index: 999;
background-color: #eeeeee !important;
}
}
}
.o_list_view .o_list_table tbody{
position:initial !important;
}
.o_list_view .table-responsive .table thead{
z-index: 1;
}
.o_optional_columns_dropdown_toggle{
z-index: 999;
}
.o_progressbar .o_progress .o_progressbar_complete {
background-color: #3d9bbb;
}
.o_cp_left .btn {
margin-right: 10px !important;
}
.o_main_navbar .o_menu_sections {
flex-wrap: wrap !important;
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save