diff --git a/code_backend_theme/README.rst b/code_backend_theme/README.rst new file mode 100755 index 000000000..9b88fee91 --- /dev/null +++ b/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 '__ + +Credits +------- +* 'Cybrosys Techno Solutions '__ + +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: ``__ + diff --git a/code_backend_theme/__init__.py b/code_backend_theme/__init__.py new file mode 100644 index 000000000..5042f465e --- /dev/null +++ b/code_backend_theme/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2021-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# 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 . +# +############################################################################# +from .hooks import test_pre_init_hook, test_post_init_hook diff --git a/code_backend_theme/__manifest__.py b/code_backend_theme/__manifest__.py new file mode 100644 index 000000000..06b0dac21 --- /dev/null +++ b/code_backend_theme/__manifest__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2021-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# 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 . +# +############################################################################# + +{ + "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, +} diff --git a/code_backend_theme/doc/RELEASE_NOTES.md b/code_backend_theme/doc/RELEASE_NOTES.md new file mode 100644 index 000000000..c52b1d904 --- /dev/null +++ b/code_backend_theme/doc/RELEASE_NOTES.md @@ -0,0 +1,7 @@ +## Module + +#### 22.10.2021 +#### Version 15.0.1.0.0 +#### ADD +Initial Commit + diff --git a/code_backend_theme/hooks.py b/code_backend_theme/hooks.py new file mode 100644 index 000000000..503cb8add --- /dev/null +++ b/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() +# Author: Cybrosys Techno Solutions() +# +# 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 . +# +############################################################################# +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())}) diff --git a/code_backend_theme/static/description/assets/all_screens.png b/code_backend_theme/static/description/assets/all_screens.png new file mode 100644 index 000000000..dda177cbe Binary files /dev/null and b/code_backend_theme/static/description/assets/all_screens.png differ diff --git a/code_backend_theme/static/description/assets/easily-access-menu.gif b/code_backend_theme/static/description/assets/easily-access-menu.gif new file mode 100644 index 000000000..08340f307 Binary files /dev/null and b/code_backend_theme/static/description/assets/easily-access-menu.gif differ diff --git a/code_backend_theme/static/description/assets/hero.png b/code_backend_theme/static/description/assets/hero.png new file mode 100644 index 000000000..c5d27476a Binary files /dev/null and b/code_backend_theme/static/description/assets/hero.png differ diff --git a/code_backend_theme/static/description/assets/icons/check.png b/code_backend_theme/static/description/assets/icons/check.png new file mode 100644 index 000000000..c8e85f51d Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/check.png differ diff --git a/code_backend_theme/static/description/assets/icons/chevron.png b/code_backend_theme/static/description/assets/icons/chevron.png new file mode 100644 index 000000000..2089293d6 Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/chevron.png differ diff --git a/code_backend_theme/static/description/assets/icons/cogs.png b/code_backend_theme/static/description/assets/icons/cogs.png new file mode 100644 index 000000000..95d0bad62 Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/cogs.png differ diff --git a/code_backend_theme/static/description/assets/icons/consultation.png b/code_backend_theme/static/description/assets/icons/consultation.png new file mode 100644 index 000000000..8319d4baa Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/consultation.png differ diff --git a/code_backend_theme/static/description/assets/icons/ecom-black.png b/code_backend_theme/static/description/assets/icons/ecom-black.png new file mode 100644 index 000000000..a9385ff13 Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/ecom-black.png differ diff --git a/code_backend_theme/static/description/assets/icons/education-black.png b/code_backend_theme/static/description/assets/icons/education-black.png new file mode 100644 index 000000000..3eb09b27b Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/education-black.png differ diff --git a/code_backend_theme/static/description/assets/icons/hotel-black.png b/code_backend_theme/static/description/assets/icons/hotel-black.png new file mode 100644 index 000000000..130f613be Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/hotel-black.png differ diff --git a/code_backend_theme/static/description/assets/icons/license.png b/code_backend_theme/static/description/assets/icons/license.png new file mode 100644 index 000000000..a5869797e Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/license.png differ diff --git a/code_backend_theme/static/description/assets/icons/lifebuoy.png b/code_backend_theme/static/description/assets/icons/lifebuoy.png new file mode 100644 index 000000000..658d56ccc Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/lifebuoy.png differ diff --git a/code_backend_theme/static/description/assets/icons/manufacturing-black.png b/code_backend_theme/static/description/assets/icons/manufacturing-black.png new file mode 100644 index 000000000..697eb0e9f Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/manufacturing-black.png differ diff --git a/code_backend_theme/static/description/assets/icons/pos-black.png b/code_backend_theme/static/description/assets/icons/pos-black.png new file mode 100644 index 000000000..97c0f90c1 Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/pos-black.png differ diff --git a/code_backend_theme/static/description/assets/icons/puzzle.png b/code_backend_theme/static/description/assets/icons/puzzle.png new file mode 100644 index 000000000..65cf854e7 Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/puzzle.png differ diff --git a/code_backend_theme/static/description/assets/icons/restaurant-black.png b/code_backend_theme/static/description/assets/icons/restaurant-black.png new file mode 100644 index 000000000..4a35eb939 Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/restaurant-black.png differ diff --git a/code_backend_theme/static/description/assets/icons/service-black.png b/code_backend_theme/static/description/assets/icons/service-black.png new file mode 100644 index 000000000..301ab51cb Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/service-black.png differ diff --git a/code_backend_theme/static/description/assets/icons/trading-black.png b/code_backend_theme/static/description/assets/icons/trading-black.png new file mode 100644 index 000000000..9398ba2f1 Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/trading-black.png differ diff --git a/code_backend_theme/static/description/assets/icons/training.png b/code_backend_theme/static/description/assets/icons/training.png new file mode 100644 index 000000000..884ca024d Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/training.png differ diff --git a/code_backend_theme/static/description/assets/icons/update.png b/code_backend_theme/static/description/assets/icons/update.png new file mode 100644 index 000000000..ecbc5a01a Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/update.png differ diff --git a/code_backend_theme/static/description/assets/icons/user.png b/code_backend_theme/static/description/assets/icons/user.png new file mode 100644 index 000000000..6ffb23d9f Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/user.png differ diff --git a/code_backend_theme/static/description/assets/icons/wrench.png b/code_backend_theme/static/description/assets/icons/wrench.png new file mode 100644 index 000000000..6c04dea0f Binary files /dev/null and b/code_backend_theme/static/description/assets/icons/wrench.png differ diff --git a/code_backend_theme/static/description/assets/logo.png b/code_backend_theme/static/description/assets/logo.png new file mode 100644 index 000000000..90757391a Binary files /dev/null and b/code_backend_theme/static/description/assets/logo.png differ diff --git a/code_backend_theme/static/description/assets/menu_focus.png b/code_backend_theme/static/description/assets/menu_focus.png new file mode 100644 index 000000000..f9bd80144 Binary files /dev/null and b/code_backend_theme/static/description/assets/menu_focus.png differ diff --git a/code_backend_theme/static/description/assets/resp-gif.gif b/code_backend_theme/static/description/assets/resp-gif.gif new file mode 100644 index 000000000..f6939e09a Binary files /dev/null and b/code_backend_theme/static/description/assets/resp-gif.gif differ diff --git a/code_backend_theme/static/description/assets/responsive.jpg b/code_backend_theme/static/description/assets/responsive.jpg new file mode 100644 index 000000000..06cb4e9a5 Binary files /dev/null and b/code_backend_theme/static/description/assets/responsive.jpg differ diff --git a/code_backend_theme/static/description/assets/screenshots/10.newlookoftabs.png b/code_backend_theme/static/description/assets/screenshots/10.newlookoftabs.png new file mode 100644 index 000000000..a365ac470 Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/10.newlookoftabs.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/11.recruitment.png b/code_backend_theme/static/description/assets/screenshots/11.recruitment.png new file mode 100644 index 000000000..f86b6db24 Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/11.recruitment.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/12.saleskanban.png b/code_backend_theme/static/description/assets/screenshots/12.saleskanban.png new file mode 100644 index 000000000..7bb4e6d6e Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/12.saleskanban.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/13.modified kanban employee.png b/code_backend_theme/static/description/assets/screenshots/13.modified kanban employee.png new file mode 100644 index 000000000..a83b3a195 Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/13.modified kanban employee.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/15.sidebarwithlistview.png b/code_backend_theme/static/description/assets/screenshots/15.sidebarwithlistview.png new file mode 100644 index 000000000..c0e10d33e Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/15.sidebarwithlistview.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/16grapghview.png b/code_backend_theme/static/description/assets/screenshots/16grapghview.png new file mode 100644 index 000000000..345fd7cd0 Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/16grapghview.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/17.attendanceview.png b/code_backend_theme/static/description/assets/screenshots/17.attendanceview.png new file mode 100644 index 000000000..5340d340f Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/17.attendanceview.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/2.groupbyview.png b/code_backend_theme/static/description/assets/screenshots/2.groupbyview.png new file mode 100644 index 000000000..2403a5cdc Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/2.groupbyview.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/3.settings page.png b/code_backend_theme/static/description/assets/screenshots/3.settings page.png new file mode 100644 index 000000000..9acf3e154 Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/3.settings page.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/4.discusspage.png b/code_backend_theme/static/description/assets/screenshots/4.discusspage.png new file mode 100644 index 000000000..0718aa1de Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/4.discusspage.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/5.productskanaban.png b/code_backend_theme/static/description/assets/screenshots/5.productskanaban.png new file mode 100644 index 000000000..54e54ae24 Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/5.productskanaban.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/6.purchase view.png b/code_backend_theme/static/description/assets/screenshots/6.purchase view.png new file mode 100644 index 000000000..7a9b4861d Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/6.purchase view.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/7.productviewsmartbuttons.png b/code_backend_theme/static/description/assets/screenshots/7.productviewsmartbuttons.png new file mode 100644 index 000000000..4813dd41e Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/7.productviewsmartbuttons.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/8error.png b/code_backend_theme/static/description/assets/screenshots/8error.png new file mode 100644 index 000000000..5cec9219a Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/8error.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/Form view.png b/code_backend_theme/static/description/assets/screenshots/Form view.png new file mode 100644 index 000000000..3bf3a68b4 Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/Form view.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/kanabangroupview.png b/code_backend_theme/static/description/assets/screenshots/kanabangroupview.png new file mode 100644 index 000000000..f4c7f3a7e Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/kanabangroupview.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/listview.png b/code_backend_theme/static/description/assets/screenshots/listview.png new file mode 100644 index 000000000..39fd526e8 Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/listview.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/login.png b/code_backend_theme/static/description/assets/screenshots/login.png new file mode 100644 index 000000000..cb6e824f5 Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/login.png differ diff --git a/code_backend_theme/static/description/assets/screenshots/modal.png b/code_backend_theme/static/description/assets/screenshots/modal.png new file mode 100644 index 000000000..b89e182ca Binary files /dev/null and b/code_backend_theme/static/description/assets/screenshots/modal.png differ diff --git a/code_backend_theme/static/description/banner.png b/code_backend_theme/static/description/banner.png new file mode 100644 index 000000000..7aa0f62dd Binary files /dev/null and b/code_backend_theme/static/description/banner.png differ diff --git a/code_backend_theme/static/description/icon.png b/code_backend_theme/static/description/icon.png new file mode 100644 index 000000000..623a78372 Binary files /dev/null and b/code_backend_theme/static/description/icon.png differ diff --git a/code_backend_theme/static/description/index.html b/code_backend_theme/static/description/index.html new file mode 100644 index 000000000..cb36c12bf --- /dev/null +++ b/code_backend_theme/static/description/index.html @@ -0,0 +1,956 @@ + +
+
+
+

+ Code Backend Theme +

+

Minimalist and Elegant Backend + Theme for Odoo 15

+
+
+ +
+
+
+ + + +
+
+
+

+ 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. +

+
+
+ +
+
+
+ + + + +
+
+
+ +

+ Features

+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ New +
+

Fully Responsive Layout

+
+ 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. +
+ + +

Fully responsive

+
+ + + +

Fly-out hamburger menu on the left

+
+ + + +

Fits perfectly to all screen sizes

+
+ + + +

Quick access menu at the bottom in discuss

+
+ +
+
+ +
+
+ + +
+
+
+

Kanban Group View

+
+ 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. +
+
+
+ + +

Modified Font

+
+ + + +

New Color Combination

+
+ + + +

Full Screen View

+
+
+ +
+ + +

Stages are Separated in View

+
+ + + +

Clean Layout

+
+ + + +

Buttons with New Colors

+
+
+
+
+
+ +
+
+
+ + + +
+
+
+

List View

+
+ 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. +
+
+
+ + +

Modified Table Style

+
+ + + +

New Color Combination

+
+ + + +

New Scroll Bar

+
+
+ +
+ + +

New Status Tag

+
+ + + +

New Scrollbar

+
+ + + +

Buttons with New Colors

+
+
+
+
+
+ +
+
+
+ + + +
+
+
+

Form View

+
+ 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. +
+
+
+ + +

Modified Form Style

+
+ + + +

Full Screen Form View

+
+ + + +

New Looks for Tabs

+
+
+ +
+ + +

New Style for Required Field

+
+ + + +

New Chatter Style Under Form View

+
+ + + +

New Looks for Status Button

+
+
+
+
+
+ +
+
+
+ + + +
+
+ +
+
+

Overview

+
+ 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. +
+
+ + +

Modified Structure for All Type Views

+
+ + + +

New Style for Active Menus, Radio Buttons and Checkboxes

+
+ + +

New Color Combination

+
+ + + +

New Look for All Applications

+
+ + +

A Clean layout and New Font Style

+
+ + +

Sidebar with New Menu Icons

+
+ +
+ +
+
+ +
+
+
+ New +
+

All-New Menu Design

+ +
+ 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. +
+ +
+
+ +
+
+ + + + +
+
+
+

Easily Access Sidebar Menu

+
+ 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. +
+
+
+ +
+
+
+ + + +
+
+
+ +

+ Screenshots

+
+
+ +
+
+
+
+
1
+
+
Login Page
+
+
+
+ +
+
+ +
+
+
+
+
2
+
+
Group By View
+
+
+
+ +
+
+ +
+
+
+
+
3
+
+
Settings Page
+
+
+
+ +
+
+ +
+
+
+
+
4
+
+
Discuss Page
+
+
+
+ +
+
+ +
+
+
+
+
5
+
+
Product Kanban View
+
+
+
+ +
+
+ +
+
+
+
+
6
+
+
Purchase List View
+
+
+
+ +
+
+ +
+
+
+
+
7
+
+
Product View with Smart Buttons
+
+
+
+ +
+
+
+
+
+
+
8
+
+
Modified Alert Notifications are Placed on the Right Bottom of Display +
+
+
+
+ +
+
+ +
+
+
+
+
9
+
+
Wizards and User Error Popups
+
+
+
+ +
+
+ +
+
+
+
+
10
+
+
New Looks for The Tabs
+
+
+
+ +
+
+ +
+
+
+
+
11
+
+
Recruitment Kanban View With Ribbons
+
+
+
+ +
+
+
+
+
+
+
12
+
+
Sales Kanban View
+
+
+
+ +
+
+ +
+
+
+
+
13
+
+
Modified Kanban View for Employees With New Designed Category Section
+
+
+
+ +
+
+ + +
+
+
+
+
14
+
+
Sidebar with List View
+
+
+
+ +
+
+ + + +
+
+
+
+
15
+
+
Attendance Pages
+
+
+
+ +
+
+ +
+
+
+
+
16
+
+
Graphs with Sidebar
+
+
+
+ +
+
+
+ + + + +
+
+
+ +

+ Our Services

+
+ +
+
+ +
+
+ Odoo + Customization
+
+ +
+
+ +
+
+ Odoo + Implementation
+
+ +
+
+ +
+
+ Odoo + Support
+
+ +
+
+ +
+
+ Hire + Odoo + Developer
+
+ +
+
+ +
+
+ Odoo + Integration
+
+ +
+
+ +
+
+ Odoo + Migration
+
+ +
+
+ +
+
+ Odoo + Consultancy
+
+ +
+
+ +
+
+ Odoo + Implementation
+
+ +
+
+ +
+
+ Odoo + Licensing Consultancy
+
+
+
+ + + +
+
+
+ +

+ Our Industries

+
+ +
+
+ +
+ Trading +
+

Easily + procure + and + sell your products

+
+
+ +
+
+ +
+ POS +
+

Easy + configuration + and convivial experience

+
+
+ +
+
+ +
+ Education +
+

A + platform for + educational management

+
+
+ +
+
+ +
+ Manufacturing +
+

Plan, + track and + schedule your operations

+
+
+
+
+ +
+ E-commerce & Website +
+

Mobile + friendly, + awe-inspiring product pages

+
+
+
+
+ +
+ Service Management +
+

Keep + track of + services and invoice

+
+
+
+
+ +
+ Restaurant +
+

Run + your bar or + restaurant methodically

+
+
+
+
+ +
+ Hotel Management +
+

An + all-inclusive + hotel management application

+
+
+
+
+ + + + + +
+
+
+ +

+ Need Help?

+
+
+ +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
\ No newline at end of file diff --git a/code_backend_theme/static/description/theme_screenshot.png b/code_backend_theme/static/description/theme_screenshot.png new file mode 100644 index 000000000..173d711c7 Binary files /dev/null and b/code_backend_theme/static/description/theme_screenshot.png differ diff --git a/code_backend_theme/static/src/img/code_logo.png b/code_backend_theme/static/src/img/code_logo.png new file mode 100644 index 000000000..124152a05 Binary files /dev/null and b/code_backend_theme/static/src/img/code_logo.png differ diff --git a/code_backend_theme/static/src/img/icons/Attendances.png b/code_backend_theme/static/src/img/icons/Attendances.png new file mode 100644 index 000000000..6f2e83d36 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Attendances.png differ diff --git a/code_backend_theme/static/src/img/icons/CRM.png b/code_backend_theme/static/src/img/icons/CRM.png new file mode 100644 index 000000000..01e326198 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/CRM.png differ diff --git a/code_backend_theme/static/src/img/icons/Calendar.png b/code_backend_theme/static/src/img/icons/Calendar.png new file mode 100644 index 000000000..bfa7e4761 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Calendar.png differ diff --git a/code_backend_theme/static/src/img/icons/Contacts.png b/code_backend_theme/static/src/img/icons/Contacts.png new file mode 100644 index 000000000..5865350a0 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Contacts.png differ diff --git a/code_backend_theme/static/src/img/icons/Dashboards.png b/code_backend_theme/static/src/img/icons/Dashboards.png new file mode 100644 index 000000000..0ab419664 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Dashboards.png differ diff --git a/code_backend_theme/static/src/img/icons/Email Marketing.png b/code_backend_theme/static/src/img/icons/Email Marketing.png new file mode 100644 index 000000000..66c873ab5 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Email Marketing.png differ diff --git a/code_backend_theme/static/src/img/icons/Employees.png b/code_backend_theme/static/src/img/icons/Employees.png new file mode 100644 index 000000000..85aff52c1 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Employees.png differ diff --git a/code_backend_theme/static/src/img/icons/Events.png b/code_backend_theme/static/src/img/icons/Events.png new file mode 100644 index 000000000..b0c5e7188 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Events.png differ diff --git a/code_backend_theme/static/src/img/icons/Expenses.png b/code_backend_theme/static/src/img/icons/Expenses.png new file mode 100644 index 000000000..67406ddc3 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Expenses.png differ diff --git a/code_backend_theme/static/src/img/icons/Fleet.png b/code_backend_theme/static/src/img/icons/Fleet.png new file mode 100644 index 000000000..422959bf3 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Fleet.png differ diff --git a/code_backend_theme/static/src/img/icons/Inventory.png b/code_backend_theme/static/src/img/icons/Inventory.png new file mode 100644 index 000000000..5dd73dd19 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Inventory.png differ diff --git a/code_backend_theme/static/src/img/icons/Invoicing.png b/code_backend_theme/static/src/img/icons/Invoicing.png new file mode 100644 index 000000000..9be39f578 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Invoicing.png differ diff --git a/code_backend_theme/static/src/img/icons/Link Tracker.png b/code_backend_theme/static/src/img/icons/Link Tracker.png new file mode 100644 index 000000000..b468d30c4 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Link Tracker.png differ diff --git a/code_backend_theme/static/src/img/icons/Live Chat.png b/code_backend_theme/static/src/img/icons/Live Chat.png new file mode 100644 index 000000000..60a44e72e Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Live Chat.png differ diff --git a/code_backend_theme/static/src/img/icons/Lunch.png b/code_backend_theme/static/src/img/icons/Lunch.png new file mode 100644 index 000000000..d8a2e95b9 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Lunch.png differ diff --git a/code_backend_theme/static/src/img/icons/Maintenance.png b/code_backend_theme/static/src/img/icons/Maintenance.png new file mode 100644 index 000000000..a0bb8a72b Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Maintenance.png differ diff --git a/code_backend_theme/static/src/img/icons/Manufacturing.png b/code_backend_theme/static/src/img/icons/Manufacturing.png new file mode 100644 index 000000000..4b922f995 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Manufacturing.png differ diff --git a/code_backend_theme/static/src/img/icons/Members.png b/code_backend_theme/static/src/img/icons/Members.png new file mode 100644 index 000000000..c538782c8 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Members.png differ diff --git a/code_backend_theme/static/src/img/icons/Note.png b/code_backend_theme/static/src/img/icons/Note.png new file mode 100644 index 000000000..329f473e2 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Note.png differ diff --git a/code_backend_theme/static/src/img/icons/Point of Sale.png b/code_backend_theme/static/src/img/icons/Point of Sale.png new file mode 100644 index 000000000..bbdb5a1d4 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Point of Sale.png differ diff --git a/code_backend_theme/static/src/img/icons/Project.png b/code_backend_theme/static/src/img/icons/Project.png new file mode 100644 index 000000000..c22fe19fb Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Project.png differ diff --git a/code_backend_theme/static/src/img/icons/Purchase.png b/code_backend_theme/static/src/img/icons/Purchase.png new file mode 100644 index 000000000..457740d18 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Purchase.png differ diff --git a/code_backend_theme/static/src/img/icons/Recruitment.png b/code_backend_theme/static/src/img/icons/Recruitment.png new file mode 100644 index 000000000..b3a2dc763 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Recruitment.png differ diff --git a/code_backend_theme/static/src/img/icons/Repairs.png b/code_backend_theme/static/src/img/icons/Repairs.png new file mode 100644 index 000000000..0547711f1 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Repairs.png differ diff --git a/code_backend_theme/static/src/img/icons/SMS Marketing.png b/code_backend_theme/static/src/img/icons/SMS Marketing.png new file mode 100644 index 000000000..6b90e20d8 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/SMS Marketing.png differ diff --git a/code_backend_theme/static/src/img/icons/Sales.png b/code_backend_theme/static/src/img/icons/Sales.png new file mode 100644 index 000000000..6259ec78e Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Sales.png differ diff --git a/code_backend_theme/static/src/img/icons/Surveys.png b/code_backend_theme/static/src/img/icons/Surveys.png new file mode 100644 index 000000000..f422f46e2 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Surveys.png differ diff --git a/code_backend_theme/static/src/img/icons/Time Off.png b/code_backend_theme/static/src/img/icons/Time Off.png new file mode 100644 index 000000000..c34cde490 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Time Off.png differ diff --git a/code_backend_theme/static/src/img/icons/Timesheets.png b/code_backend_theme/static/src/img/icons/Timesheets.png new file mode 100644 index 000000000..3c552b3e6 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Timesheets.png differ diff --git a/code_backend_theme/static/src/img/icons/Website.png b/code_backend_theme/static/src/img/icons/Website.png new file mode 100644 index 000000000..cd10570fa Binary files /dev/null and b/code_backend_theme/static/src/img/icons/Website.png differ diff --git a/code_backend_theme/static/src/img/icons/apps.png b/code_backend_theme/static/src/img/icons/apps.png new file mode 100644 index 000000000..ce5cf33fc Binary files /dev/null and b/code_backend_theme/static/src/img/icons/apps.png differ diff --git a/code_backend_theme/static/src/img/icons/close.png b/code_backend_theme/static/src/img/icons/close.png new file mode 100644 index 000000000..c6ea25feb Binary files /dev/null and b/code_backend_theme/static/src/img/icons/close.png differ diff --git a/code_backend_theme/static/src/img/icons/discuss.png b/code_backend_theme/static/src/img/icons/discuss.png new file mode 100644 index 000000000..382b72f87 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/discuss.png differ diff --git a/code_backend_theme/static/src/img/icons/eLearning.png b/code_backend_theme/static/src/img/icons/eLearning.png new file mode 100644 index 000000000..e0cd5c2a5 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/eLearning.png differ diff --git a/code_backend_theme/static/src/img/icons/settings.png b/code_backend_theme/static/src/img/icons/settings.png new file mode 100644 index 000000000..7ff68e472 Binary files /dev/null and b/code_backend_theme/static/src/img/icons/settings.png differ diff --git a/code_backend_theme/static/src/js/chrome/sidebar_menu.js b/code_backend_theme/static/src/js/chrome/sidebar_menu.js new file mode 100644 index 000000000..dc1b79f55 --- /dev/null +++ b/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"); + }); +}); \ No newline at end of file diff --git a/code_backend_theme/static/src/js/fields/colors.js b/code_backend_theme/static/src/js/fields/colors.js new file mode 100644 index 000000000..9c7a4f015 --- /dev/null +++ b/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})`; +} diff --git a/code_backend_theme/static/src/js/fields/graph_arch_parser.js b/code_backend_theme/static/src/js/fields/graph_arch_parser.js new file mode 100644 index 000000000..5e4e28d27 --- /dev/null +++ b/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; + } +} diff --git a/code_backend_theme/static/src/js/fields/graph_model.js b/code_backend_theme/static/src/js/fields/graph_model.js new file mode 100644 index 000000000..d72f17898 --- /dev/null +++ b/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); + } + } +} diff --git a/code_backend_theme/static/src/js/fields/graph_renderer.js b/code_backend_theme/static/src/js/fields/graph_renderer.js new file mode 100644 index 000000000..1c06b3eee --- /dev/null +++ b/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"]; diff --git a/code_backend_theme/static/src/js/fields/graph_view.js b/code_backend_theme/static/src/js/fields/graph_view.js new file mode 100644 index 000000000..da9e306b2 --- /dev/null +++ b/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); diff --git a/code_backend_theme/static/src/js/user_menu/user_menu.js b/code_backend_theme/static/src/js/user_menu/user_menu.js new file mode 100644 index 000000000..6a394dda6 --- /dev/null +++ b/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 }); diff --git a/code_backend_theme/static/src/scss/datetimepicker.scss b/code_backend_theme/static/src/scss/datetimepicker.scss new file mode 100644 index 000000000..9c8f31088 --- /dev/null +++ b/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%); +} \ No newline at end of file diff --git a/code_backend_theme/static/src/scss/login.scss b/code_backend_theme/static/src/scss/login.scss new file mode 100644 index 000000000..96c6ba704 --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/code_backend_theme/static/src/scss/navigation_bar.scss b/code_backend_theme/static/src/scss/navigation_bar.scss new file mode 100644 index 000000000..88746f2d1 --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/code_backend_theme/static/src/scss/sidebar.scss b/code_backend_theme/static/src/scss/sidebar.scss new file mode 100644 index 000000000..37342af2a --- /dev/null +++ b/code_backend_theme/static/src/scss/sidebar.scss @@ -0,0 +1,97 @@ +#sidebar_panel { + height: calc(100% - 0%); + position: fixed; + top: 0px; + background-color: #2a3042; + display: none; + width: 200px; + overflow-y: scroll; + -ms-overflow-style: none; /* Hide scrollbar for IE and Edge */ + scrollbar-width: none; /* Hide scrollbar for Firefox */ + z-index: 999; +} +#sidebar_panel::-webkit-scrollbar { + display: none; /* Hide scrollbar for Chrome, Safari and Opera */ +} +.sidebar_panel .sidebar { + padding: 0; + white-space: nowrap; + padding-bottom: 20px; + padding-top: 5px; +} +.sidebar_panel .sidebar_close { + text-align: end; + display: none; + position: sticky; + height: 35px; + padding-top: 5px; + top: 0; + background: #2a3042; + z-index: 1; +} +.sidebar_panel .sidebar_close a#closeSidebar { + font-size: 18px; + margin-right: 10px; + color: #ffffff; + opacity: .3; +} +.sidebar_panel .sidebar_close a#closeSidebar img { + width: 15px; +} +.sidebar_panel .sidebar .sidebar_logo { + padding-top: 20px; + text-align: center; + padding-bottom: 20px; +} +.sidebar_panel .sidebar .sidebar_logo img { + max-width: 150px; +} + +.sidebar_panel .sidebar .sidebar_head { + padding-top: 20px; + padding-left: 15px; + color: #6a7187; + font-size: 14px; +} + +.sidebar_panel .sidebar .sidebar_menu { + list-style: none; + margin: 0; + padding: 0; +} + +.sidebar_panel .sidebar .sidebar_menu li { + margin: 0; + padding: 0; + border: 0px; + display: block; +} + +.sidebar_panel .sidebar .sidebar_menu li a { + margin: 0; + border: 0px; + display: block; + cursor: pointer; + overflow: hidden; + padding: 8px 10px 8px 25px; + color: #ffffff; + font-size: 13px; + transition:.3s all; +} +.sidebar_panel .sidebar .sidebar_menu li:hover a { + background: #1e2230; + color: #fff; +} +.sidebar_panel .nav-link { + opacity: .5; + transition:.3s all; +} +.sidebar_panel .sidebar a.nav-link.active { + color: #fff !important; + opacity: 1; +} + +.sidebar_panel .sidebar .sidebar_menu li a .sidebar_img { + width: 20px; + margin-right: 8px; +} \ No newline at end of file diff --git a/code_backend_theme/static/src/scss/theme.scss b/code_backend_theme/static/src/scss/theme.scss new file mode 100644 index 000000000..dd1d17b07 --- /dev/null +++ b/code_backend_theme/static/src/scss/theme.scss @@ -0,0 +1,1638 @@ +//Top Bar +// @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap'); +@font-face { + font-family: 'Poppins' !important; + src: url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap'); +} +body { + font-family: 'Poppins', sans-serif !important; + background-color: #f8f8fb; +} +a { + color: $primary_accent; +} +a:hover { + color: $primary_hover; +} +a.btn { + height: auto !important; +} + +.o_main_navbar, .o_searchview_facet_label { + background-color: transparent !important; + color: $f_color !important; + border:1px !important; +} +.o_menu_sections, .o_menu_systray, .o_web_client > header{ +background: $bg_white !important; +} +.oe_topbar_name { + color: $f_color !important; +} +.o_main_navbar > .o_menu_apps > li > a > i { + color: $f_color !important; + font-size: 16px !important; +} +.o_main_navbar { + height: 65px; +// display: flex; +// justify-content: space-between; + align-items: center; + border-bottom: 0px solid #5f5e97 !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; + background-color: #fff !important; +} + +.top_heading { + display: flex; + justify-content: center; + align-items: center; +} +.top_heading ul { + margin-bottom: 0 !important; +} +.top_heading > a { + margin-left: 10px; + font-size: 20px; +} +.top_heading li { + list-style: none; +} + +.o-no-caret > i, button[aria-pressed="true"] { + color: $f_color !important; +} +.o_main_navbar > ul > li > a, .o_main_navbar > ul > li > label { + height: 46px; + padding: 0 5px; + color: $f_color !important; + line-height: 46px; +} +.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: $bg_white !important; +} + +.topbar_icon .fa { + color: #555b6d !important; + font-size: 20px !important +} + +.o_main_navbar .o_user_menu .oe_topbar_avatar { + height: 35px; + width: 35px; +} + +.o_MessagingMenu_counter { + background-color: #556ee6; + color: #fff !important; +} +.o_mail_systray_item .o_notification_counter { + background: #556ee6 !important; +} + +.o_control_panel { + background: #f8f8fb !important; + border:none !important; +} + +.o_searchview { + border: 1px solid #ddd; + height: 38px; + padding-left: 12px; + padding-right: 20px; + -webkit-box-shadow: none; + box-shadow: none; + border-radius: 5px; + padding-top: 6px; +} +.o_searchview .o_searchview_icon { + position: absolute; + top: 11px; + left: auto; + bottom: auto; + right: 8px; +} +.o_searchview .o_searchview_facet { + border: 1px solid #ddd; + background: #f8f8fb; + border-radius: 10px; + padding: 0 4px; +} +.o_cp_bottom_right span.o_dropdown_title { + color: #555b6d; +} +.o_cp_bottom_left .o_form_button_cancel { + margin-right: 5px !important; + padding: 6px 8px !important; + color: #fff !important; + background-color: #f46a6a !important; + border-color: #f46a6a !important; +} +.o_cp_bottom_left .o_form_button_cancel:hover { + background-color: #cf5a5a !important; + border-color: #c35555 !important; +} +.o_control_panel { + border-bottom: none important; + margin: 15px 15px 0 15px; + background-color: #ffffff !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; + border-radius: 5px; +} + +.o_form_view, +.o_widget_Discuss { + background: #f8f8fb; +} +.o_form_statusbar .o_statusbar_buttons .btn { + margin-right: 15px !important; +} +.o_form_view .o_form_statusbar > .o_statusbar_status > .o_arrow_button:not(:first-child):before, .o_form_view .o_form_statusbar > .o_statusbar_status > .o_arrow_button:not(:first-child):after { + border-top: 21px solid transparent !important; + border-bottom: 17px solid transparent !important; +} +.o_form_view .o_form_statusbar > .o_statusbar_status > .o_arrow_button:not(:first-child):before { + right: -11px; + border-left-color: $primary_accent; +} +//Top Bar End + +// Button +.btn { + display: inline-block; + font-weight: 400 !important; + line-height: 1.5 !important; + color: #495057; + text-align: center; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 6px 15px !important; + border-radius: 4px !important; + -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; + height: 40px !important; + margin-bottom: 2px; +} +.btn-primary { + color: #fff !important; + background-color: $primary_accent !important; + border-color: $primary_accent !important; +} +.btn-primary:hover { + color: #fff !important; + background-color: $primary_hover !important; + border-color: #4458b8 !important; +} +.btn-primary:active { + color: #fff !important; + 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; +} +.btn-primary i,.btn-primary span { + color: #fff !important; +} +.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-check:focus+.btn-secondary, .btn-secondary:focus { + color: #fff; + background-color: #636678 !important; + border-color: #5d6071 !important; + -webkit-box-shadow: 0 0 0 .15rem rgba(137,140,158,.5) !important; + box-shadow: 0 0 0 .15rem rgba(137,140,158,.5) !important; +} +.btn-secondary i,.btn-secondary span { + color: #fff !important; +} +.btn-info { + color: #fff !important; + background-color: #50a5f1 !important; + border-color: #50a5f1 !important; +} +.btn-info:hover { + color: #fff !important; + background-color: #448ccd !important; + border-color: #4084c1 !important; +} +button[name="action_cancel"], +button[name="button_cancel"], +button[special="cancel"] { + color: #fff !important; + background-color: #f46a6a !important; + border-color: #f46a6a !important; +} +button[name="action_cancel"]:hover, +button[name="button_cancel"]:hover, +button[special="cancel"]:hover { + background-color: #cf5a5a !important; + border-color: #c35555 !important; +} +button[name="action_cancel"]:active, +button[name="button_cancel"]:active, +button[special="cancel"]:active { + color: #fff !important; + background-color: #c35555 !important; + border-color: #b75050 !important; +} +button[name="action_cancel"]:focus, +button[name="button_cancel"]:focus, +button[special="cancel"]:focus { + color: #fff !important; + background-color: #cf5a5a !important; + border-color: #c35555 !important; + -webkit-box-shadow: 0 0 0 0.15rem rgba(246,128,128,.50) !important; + box-shadow: 0 0 0 0.15rem rgba(246,128,128,.50) !important; +} +button[name="update_module"] { + margin-right: 2px; +} + +.btn-warning, +button[name="action_uninstall"] { + color: #fff !important; + background-color: #f1b44c !important; + border-color: #f1b44c !important; +} +.btn-warning:hover, +button[name="action_uninstall"]:hover { + color: #fff !important; + background-color: #cd9941 !important; + border-color: #c1903d !important; +} +.btn-warning:active, +button[name="action_uninstall"]:active { + color: #fff !important; + background-color: #c1903d !important; + border-color: #b58739 !important; +} +.btn-warning:focus, +button[name="action_uninstall"]:focus { + color: #fff !important; + background-color: #cd9941 !important; + border-color: #c1903d !important; + -webkit-box-shadow: 0 0 0 0.15rem rgba(243,191,103,.50) !important; + box-shadow: 0 0 0 0.15rem rgba(243,191,1,.50) !important; +} + +.o_statusbar_status .o_arrow_button.btn-secondary { + border: solid 1px #556ee6 !important; + color: #556ee6 !important; + background-color: #fff !important; +} +.o_statusbar_status .o_arrow_button.btn-secondary:hover:after { + border-color: #fff !important; +} +.o_statusbar_status .o_arrow_button.btn-primary { + border: solid 1px #556ee6 !important; + color: #fff !important; +} +.o_cp_bottom_right .btn-secondary { + background: #fff; + border-color: #dddddd !important; + margin-right: 5px !important; + padding : 6px 8px !important; +} +.o_cp_bottom_left .o_form_button_create { + color: #fff !important; + background-color: #74788d !important; + border-color: #74788d !important; + padding: 5px 15px !important; +} + +.o_web_settings_invite { + margin-left: 7px; + position: relative; + top: -1px; +} +.o_form_view .oe_button_box .btn.oe_stat_button { + height: 44px !important; + opacity: 1 !important; + border-radius: 0px !important; +} +.oe_module_action .btn { + height: auto !important; +} +.o_cp_buttons a.btn { + display: flex !important; + align-items: center; +} +button.fa.fa-external-link.btn.btn-secondary.o_external_button { + padding: 2px !important; + color: #74788d !important; + background: none !important; +} +.o_statusbar_status.o_field_widget.o_readonly_modifier .btn { + margin-right: 0 !important; +} +.btn-group .btn { + margin-right: 3px; +} +.o_Composer_actionButton.o-last.o-has-current-partner-avatar.o-composer-is-compact { + height: 48px !important; + border-bottom-left-radius: 0px !important; + border-top-left-radius: 0px !important; +} +.oe_right .btn { + margin-left: 2px; +} +//Button End + +//table +.table-sm th, .table-sm td { + padding: 0.5rem; +} +tr.o_data_row { + font-size: 15px; +} +.o_list_view .table-responsive .table thead tr:nth-child(1) th { + position: sticky; + top: 0; + z-index: 999; + background-color: #ffffff !important; +} +th.o_list_record_selector { + color: #495057 !important; + border-color: #eff2f7 !important; + background-color: #f8f9fa !important; +} +.table>:not(caption)>*>* { + padding: 1rem 1rem; + background-color: var(--bs-table-bg); + border-bottom-width: 1px; + -webkit-box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg); + box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg); +} +.o_list_view .o_list_table thead > tr > th:not(.o_list_record_selector).o_list_number_th { + text-align: left !important; +} +.o_list_view .o_list_table thead > tr > th:not(.o_list_record_selector) { + border-left: none !important; +} +.table thead th { + vertical-align: bottom; + border-bottom: none; +} +.table th { + border-top: 1px solid #fff !important; +} +.o_list_view .o_list_table tfoot { + background-color: #fff !important; +} +.o_list_view .table-responsive .o_list_table tfoot tr:nth-child(1) td { + background-color: #fff !important; +} +.o_list_view .o_list_table tr:focus-within, +.o_list_view .o_list_table.table-striped tr:focus-within { + background-color: #fff !important; +} +// Table End + +// Checkbox +.custom-checkbox label { + position: relative; + cursor: pointer; +} + +.custom-checkbox label:before { + content:''; + -webkit-appearance: none; + background-color: transparent; + border: 1px solid #0079bf; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05); + padding: 10px; + display: inline-block; + position: relative; + vertical-align: middle; + cursor: pointer; + margin-right: 5px; + margin-top: 0px; +} + +.custom-checkbox input:checked + label:after { + content: ''; + display: block; + position: absolute; + top: 2px; + left: 9px; + width: 6px; + height: 14px; + border: solid #0079bf; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.custom-checkbox label::before { + content: ''; + -webkit--webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: transparent; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05); + padding: 10px; + display: inline-block; + position: relative; + vertical-align: middle; + cursor: pointer; + margin-right: 5px; +} +.custom-control-label::before { + position: absolute; + top: 0px !important; + left: 1px !important; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + content: ""; + background-color: #FFFFFF; + border: 1px solid rgba(0,0,0,.25) !important; + box-shadow: none !important; + border-radius: 5 !important; + margin-top: -5px; +} +.custom-checkbox { + padding-left: 0rem !important; +} + +.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: $primary_accent; +} + +.custom-checkbox input:checked + label::after { + content: ''; + display: block; + position: absolute; + top: 1px; + left: 9px; + width: 6px; + height: 14px; + border: solid #fff; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} +.dropdown-item .custom-checkbox input:checked + label::after { + content: ''; + display: block; + position: absolute; + top: 1px; + left: 19px; + width: 6px; + height: 14px; + border: solid #fff; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.custom-control-input:checked ~ .custom-control-label::before { + color: #ffffff; + background-color: #556ee6; + border-color: #556ee6; +} +.custom-control.custom-checkbox .custom-control-input:not(:checked):not(:indeterminate) ~ .custom-control-label:before { + background: none; + outline: none !important; +} + +.o_list_selection_box { + display: inline-block; + padding: 0.375rem 0.75rem; + vertical-align: middle; + border: 1px solid rgba(85,110,230,.25)!important; + background-color: rgba(85,110,230,.25)!important; + color: $primary_accent !important; + border-radius: 5px; +} +.o_radio_input + .custom-control-label::before { + position: absolute !important; + top: 8.5px !important; + left: -1.5rem !important; + display: block !important; + width: 1rem !important; + height: 1rem !important; + pointer-events: none !important; + content: "" !important; + background-color: #FFFFFF !important; + border: #adb5bd solid 1px !important; +} +.custom-radio .custom-control-input:checked ~ .custom-control-label::after { + background: $primary_accent !important; + border-radius: 30px; +} +// Checkbox End + +//sheet +.o_form_view .o_form_sheet_bg { + border-bottom: none; + background: #ffffff !important; + box-shadow: none !important; + border-radius: 5px; + padding: 10px; +} +.o_form_view .o_form_sheet_bg > .o_form_sheet { + min-width: 650px; + max-width: initial; + min-height: 330px; + border: 1px solid #c8c8d3; + box-shadow: none !important; + background: white; + margin: 4.8px auto; + padding: 24px; + padding-right: 16px; + padding-left: 16px; + border-radius: 5px; +} +.o_form_view .o_form_statusbar { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: flex; + justify-content: space-between; + padding-left: 5px; + border-bottom: none !important; + background-color: white; + border-radius: 5px; +} +.o_required_modifier > .o_input_dropdown > .ui-autocomplete-input { + border-left: solid 3px #f46a6a !important; +} +//sheet end + +//Discuss +.o_Discuss { + padding: 15px !important; + background: #f5f5f9 !important; +} + +.o_Discuss .o_DiscussSidebar { + background-color: #ffffff !important; + color: #dee2e6 !important; + border-radius: 5px; + margin-right: 10px !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; +} + +.o_DiscussSidebar_item.o-active { + color: #f46a6a !important; + font-weight: 500 !important; +} +.o_DiscussSidebarItem:hover { + background: none !important; +} +.o_DiscussSidebarItem { + cursor: pointer !important; + color: #74788d !important; + line-height: 24px !important; + padding: 10px 5px !important; +} +.o_DiscussSidebarItem_activeIndicator.o-item-active { + background: none !important; +} +.o_DiscussSidebar_item.o-active .fa { + color: #f46a6a !important; +} +.o_widget_Discuss .o_Discuss_content { + border-top: none !important; + background-color: #ffffff !important; + border-radius: 5px; + -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; +} +.o_DiscussSidebarItem_counter { + background-color: #f46a6a !important; + color: #fff !important; +} +.o_ThreadIcon_online { + color: #34c38f; +} +.o_Message_prettyBody table th { + color: $primary_accent !important; +} +.o_Message_prettyBody table thead tr td { + background-color: $primary_hover !important; +} +.o_DiscussSidebarMailbox.o-active { + background-color: #e9ecef; + color: #f46a6a !important; + font-weight: 500 !important; +} +.o_Discuss .o_DiscussSidebar { + color: #74788d !important; + line-height: 24px !important; + padding: 10px 5px !important; +} +.o_DiscussSidebarMailbox.o-starred-box .o_DiscussSidebarMailbox_counter { + border-color: #f46a6a !important; + background-color: #f46a6a !important; + color: #fff !important; +} +.o_list_buttons.d-flex button { + margin-right: 5px; +} +// Discuss End + +//Chatter @form +.o_FormRenderer_chatterContainer { + max-width: initial; + margin-top: 15px !important; + padding: 0 !important; + border-radius: 5px; + -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; +} +.o_ChatterContainer { + padding: 10px; + background: #fff; + border-radius: 5px; +} +.o_Discuss .o_Message .o_Message_prettyBody p a { + background: $primary_accent !important; +} +.o_Message.o-not-discussion { + background-color: #eff2f7; + margin: 5px; + border-radius: 5px; + border-bottom: none; +} +.o_Composer { + background-color: #fff; + border-radius: 0 0 5px 5px; + border: none !important; +} +//Chatter @form Ends + +//Chatter mini starts +.o_ChatWindow { + background-color: #fff !important; + border-radius: 5px 5px 0 0 !important; + -webkit-box-shadow: 0 0.75rem 1.5rem rgba(18,38,63,.25) !important; + box-shadow: 0 0.75rem 1.5rem rgba(18,38,63, .25) !important; +} +.o_PartnerImStatusIcon_icon.o-offline { + color: #495057 !important; +} +.o_PartnerImStatusIcon_icon.o-online { + color: #34c38f !important; +} +.o_PartnerImStatusIcon_innerBackground { + color: white !important; +} +i.o_PartnerImStatusIcon_outerBackground.fa.fa-circle.fa-stack-1x { + color: #fff !important; +} +.o_ChatWindowHeader_item.o_ChatWindowHeader_rightArea .fa { + color: #fff !important; +} +.o_ChatWindowHeader { + background-color: #556ee6; + color: white !important; +} +//Chatter mini ends + +// Tabs Start +.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { + border: none; + border-bottom: solid; + font-weight: bold; + background: $primary_accent; + color: #ffffff !important; + border-radius: 5px; +} +.nav-link { + display: block; + padding: 1rem 2rem; +} +.o_form_view .o_notebook { + clear: both; + margin-top: 25px; +} +.nav-tabs { + border-bottom: none; +} +.nav-tabs .nav-link { + border: 1px solid #e3e3e3; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom: none !important; +} +.nav-item > a { + color: #555b6d !important; +} +.nav-link:hover, .nav-link:focus { + border: 1px solid #e9e9e9 !important; + border-bottom: none !important; +} +ul.sidebar_menu .nav-link:hover,ul.sidebar_menu .nav-link:focus { + border: none !important; + border-bottom: none !important; +} +a#closeSidebar { + margin-left: 10px; +} + +a#openSidebar { + margin-left: 10px; +} + +// TAbs End + +//font color +.o_horizontal_separator { + color: #495057 !important; +} +.btn-link { + font-weight: 400; + color: $primary_accent !important; + text-decoration: none; +} +.o_activity_view .o_record_selector { + color: #34c38f; +} +//font color ends + +//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; +} +select { + 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; + 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; +} +.o_field_widget .o_input_dropdown .o_dropdown_button { + position: absolute; + top: 6px; + left: auto; + bottom: auto; + right: 9px; +} +.o_datepicker .o_datepicker_button { + position: absolute; + top: 6px; + left: auto; + bottom: auto; + right: 9px; + pointer-events: none; +} +.o_input { + border: solid 1px #ced4da !important; + color: #495057 !important; +} +.o_form_view .oe_button_box .btn.oe_stat_button > .o_stat_info .o_stat_value, +.o_form_view .oe_button_box .btn.oe_stat_button > span .o_stat_value { + color: #556ee6; +} +.o_form_view .oe_button_box .oe_stat_button .o_button_icon { + color: #556ee6; +} +//form end + +//search view +.o_searchview input.o_searchview_input { + border: none !important; + height: auto !important; + margin: 0 !important; +} +.o_base_settings .o_control_panel .o_panel .o_setting_search .searchIcon { + top: 9px; + right: 10px; +} +.o_base_settings .o_control_panel .o_panel .o_setting_search .searchInput { + padding: 10px 15px; +} +//search view end + +//settings page +.o_web_settings_invite { + margin-left: 7px; +} +.o_settings_container .o_setting_box .o_setting_right_pane { + margin-left: 32px !important; + border-left: 1px solid #dee2e6; + padding-left: 12px; +} +.o_setting_container { + background: #f8f8fb; + padding: 15px 0 0 0; +} +.o_base_settings .o_control_panel { + margin: 0; +} +.settings { + margin-left: 15px; + background-color: #ffffff !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; + border-radius: 5px; +} +.settings_tab { + flex : auto; + background-color: #ffffff !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; + border-radius: 5px; +} +.o_base_settings .o_setting_container .settings_tab .selected, + .o_base_settings .o_setting_container .settings_tab .selected span { + box-shadow: none !important; + color: #f46a6a !important; + font-weight: 500 !important; + background: none !important; +} +.o_base_settings .o_setting_container .settings_tab .tab { + color: #74788d !important; +} +.o_setting_container .o_field_widget.o_field_many2one.o_with_button.oe_inline { + margin-left: 15px !important; +} +//settings page end + +//scrollbar starts +::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 3px rgba(0,0,0,.19); + border-radius: 10px; + background-color: #F5F5F5; +} +::-webkit-scrollbar { + width: 12px; + background-color: #F5F5F5; +} +::-webkit-scrollbar-thumb { + border-radius: 10px; + -webkit-box-shadow: inset 0 0 3px rgba(0,0,0,.2); + background-color: #c9ccd6; +} +//scrollbar ends + +//common starts +.o_content { + margin: 15px; + border-radius: 5px; + -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; + background-color: #f8f8fb; +} +.o_action{ + background: #f8f8fb; +} +//common ends + +//kanban_view starts +.o_kanban_view { + border-radius: 5px; + background: #fff; + -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; +} +.o_kanban_view.o_kanban_ungrouped .o_kanban_record { + border-radius: 5px; + -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; +} +.o_kanban_view .o_kanban_group { + background: #fff; + border-radius: 5px; + margin-right: 10px; +} +.o_kanban_view.o_kanban_grouped { + background-color: #f8f8fb; +} +.o_kanban_view.o_kanban_grouped .o_kanban_record, .o_kanban_view.o_kanban_grouped .o_kanban_quick_create { + border-radius: 5px; + margin-bottom: 10px; + -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; +} +.o_kanban_view.o_kanban_dashboard .o_kanban_record .o_kanban_card_header + .container.o_kanban_card_content .o_kanban_primary_bottom.bottom_block { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + background: rgba(85,110,230,.25) !important; +} +.o_kanban_view .o_kanban_record.o_kanban_record_has_image_fill .o_kanban_image_fill_left { + border-bottom-left-radius: 5px; + border-top-left-radius: 5px; +} +.o_kanban_record::after { + border-bottom-left-radius: 5px; + border-top-left-radius: 5px; +} +.o_kanban_quick_create .o_form_view, +.o_kanban_quick_create .o_action, +.o_kanban_quick_create .o_content { + background: #fff; + margin: 0px; + -webkit-box-shadow: none !important; + box-shadow: none !important; +} +.o_kanban_view .o_kanban_content .bg-primary { + border-radius: 5px 0 0 5px; +} +//kanban_view ends +//Canvas +canvas.chartjs-render-monitor { + width: 100% !important; + height: 100% !important; +} +//Canvas End +//Apps menu starts +.o_search_panel { + border-right: none; + background-color: #ffffff; + border-radius: 5px; + -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; +} +.list-group-item-action { + width: 100%; + color: #74788d; + text-align: inherit; +} +.list-group-item-action:hover, .list-group-item-action:focus { + color: none !important; + background: none !important; +} +.o_search_panel .list-group-item header.active { + background: none !important; + color: #f46a6a !important; + font-weight: 500 !important; +} +.o_controller_with_searchpanel .o_renderer_with_searchpanel { + margin-left: 15px; +} +//Apps menu ends + +//Website app starts +.o_dashboards .o_website_dashboard { + background-color: #ffffff !important; +} +.o_dashboards .o_website_dashboard div.o_box { + box-shadow: none !important; +} +.o_inner_box { + background-color: $primary_accent !important; + border-radius: 5px; +} +.o_inner_box:hover { + background-color: $primary_hover !important; +} +//Website app ends + +//Purchase app starts +td.o_main { + background-color: $primary_accent !important; + border-radius: 5px; +} +td.o_main:hover { + background-color: $primary_hover !important; +} +.o_purchase_dashboard .table > thead > tr > td, +.o_purchase_dashboard .table tbody > tr > td { + border-radius: 5px; +} +//Purchase app ends + +//input inside colour starts +select, input { + color: #4c4c4c !important; +} +.o_required_modifier > .o_input_dropdown > .ui-autocomplete-input { + color: #4c4c4c !important; +} +.o_required_modifier.o_input, .o_required_modifier.o_input { + color: #4c4c4c !important; +} +.o_datepicker.o_field_date.o_field_widget.o_required_modifier > input { + color: #4c4c4c !important; +} +//input inside colour ends + +//welcome demo starts +.o_onboarding_container.collapse.show { + border-radius: 5px !important; + margin: 15px 15px 0px 15px; + -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; +} +.o_onboarding.o_onboarding_blue, +.o_onboarding.o_onboarding_blue .o_onboarding_step_title, +.o_onboarding.o_onboarding_blue .o_onboarding_step_action, +.o_onboarding.o_onboarding_blue a.o_onboarding_all_done, +.o_onboarding.o_onboarding_blue .o_onboarding_all_done > .fa, +.o_onboarding.o_onboarding_blue .o_onboarding_btn_close { + border-radius: 5px; +} +.o_onboarding { + border-radius: 5px; +} +//welcome demo ends + +//modal starts +.modal.o_technical_modal .modal-content { + border-radius: 5px; + -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; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #f6f6f6; +} +.modal.o_technical_modal .modal-content .o_form_view, +.modal.o_technical_modal .modal-content .o_content, +.modal.o_technical_modal .modal-content .o_action { + background-color: #fff !important;; + -webkit-box-shadow: none !important; + box-shadow: none !important; +} +.modal.show .modal-dialog { + display: -webkit-box; + display: -webkit-flex; + display: flex; + align-items: center; + min-height: calc(100% - 1rem); +} +.modal .o_form_view .o_group .o_field_widget { + width: 98% !important; +} +//modal ends + +//Calendar starts +.o_calendar_view { + background: #fff; +} +//Calendar ends + +//expense starts +.o_content .o_expense_container { + background: #fff; +} +//expense ends + +//lunch starts +.o_lunch_content .o_lunch_banner { + border-bottom: none !important; + background-color: #fff !important; + margin: 0 15px 10px 15px; + border-radius: 5px; + -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; +} +//lunch ends + +//kiosk starts +.o_hr_attendance_kiosk_backdrop { + background-color: #f8f8fb !important; +} +.o_hr_attendance_kiosk_mode { + background-color: #fff !important; + border-radius: 0 0 5px 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; +} +.o_hr_attendance_kiosk_mode .o_hr_attendance_user_badge { + border-radius: 5px 5px 0 0 !important; + border-top: none !important; + background: rgba(85,110,230,.25) !important; +} +.o_hr_attendance_sign_in_out_icon.fa-sign-in { + color: #fff !important; + background-color: $primary_accent !important; + border-color: $primary_accent !important; +} +.o_hr_attendance_sign_in_out_icon.fa-sign-in:hover { + color: #fff !important; + background-color: $primary_hover !important; + border-color: #4458b8 !important; +} +.o_hr_attendance_sign_in_out_icon.fa-sign-in:active { + color: #fff !important; + background-color: #4458b8 !important; + border-color: #4053ad !important; +} +.o_hr_attendance_sign_in_out_icon.fa-sign-in: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; +} +.o_hr_attendance_kiosk_mode h3.mt0.mb0.text-muted { + color: $primary_accent !important; +} +.o_hr_attendance_button_employees div.mb16.mt16 { + margin-top: 4px !important; +} +//kiosk ends + +//timeoff starts +.o_timeoff_container { + background: #fff; +} +//timeoff ends + +//project .fa colours starts +.o_mail_activity .o_activity_color_planned { + color: #34c38f !important; +} +.o_mail_activity .o_activity_color_overdue { + color: #f46a6a !important; +} +.o_field_widget.o_priority > .o_priority_star.fa-star { + color: #f1b44c !important; +} +//project .fa colours ends + +//sale status icon starts +.bg-primary { + background-color: $primary_accent !important; +} +.bg-secondary { + background-color: $secondary_accent !important; +} +.bg-success-light { + color: #1f7556 !important; + background-color: #d6f3e9 !important; +} +.bg-info-light { + color: #306391 !important; + background-color: #dcedfc !important; +} +.bg-danger-light { + background-color: #fde1e1 !important; + color: #924040 !important; +} +.bg-warning-light { + background-color: #fcf0db !important; + color: #916c2e !important; +} +.o_field_widget.o_field_badge { + color: #464855; + background-color: #e3e4e8; +} +//sale status icon end + +//stock icon colors starts +.o_list_view .o_list_table .text-danger, +.o_list_view .o_list_table .oe_import .alert.text-error, +.oe_import .o_list_view .o_list_table .alert.text-error { + color: #f46a6a !important; +} +.text-danger { + color: #f1b44c !important; +} +.text-warning { + color: #f1b44c !important; +} +.text-error { + color: #f46a6a !important; +} +.text-success { + color: #34c38f !important; +} +//stock icon colors ends + +//badges starts +.badge-primary { + background-color: #556ee6 !important; +} +.badge-secondary { + background-color: #74788d; +} +.badge-success { + background-color: #34c38f; +} +.badge-info { + background-color: #50a5f1; +} +.badge-danger { + background-color: #f46a6a; +} +.badge-warning { + background-color: #f1b44c; +} +.badge-dark { + background-color: #343a40; +} +.bg-success { + background-color: #34c38f !important; +} +.bg-success-full { + background-color: #34c38f !important; +} +.bg-warning-full { + background-color: #f1b44c !important; +} +.bg-danger-full { + background-color: #f46a6a; +} +.o-planned { + color: #34c38f !important; +} +.o-overdue { + color: #f46a6a !important; +} +.o_progressbar_complete { + background-color: #34c38f !important; +} +//badges ends + +//alert starts +.alert-warning { + color: #916c2e; + background-color: #fcf0db; + border-color: #fbe9c9; +} +.alert-danger { + color: #924040; + background-color: #fde1e1; + border-color: #fcd2d2; +} +.alert-success { + color: #1f7556; + background-color: #d6f3e9; + border-color: #c2eddd; +} +.alert-info { + color: #306391; + background-color: #dcedfc; + border-color: #cbe4fb; +} +.toast { + border-radius: 5px !important; + color: #924040 !important; + background-color: #fde1e1 !important; + border-color: #fcd2d2 !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; +} +.toast-header { + border-bottom: 1px solid #fcd2d2 !important; + color: #924040 !important; + background-color: #fde1e1 !important; + border-color: #fcd2d2 !important; +} +.toast-body { + color: #924040 !important; + background-color: #fde1e1 !important; + border-color: #fcd2d2 !important; +} +.o_notification_manager { + top: auto !important; + bottom: 30px !important; +} +//alert ends + +//recruitment starts +div.o_boolean_toggle.custom-control.custom-checkbox > label.custom-control-label::before, +div.o_boolean_toggle.custom-control.custom-checkbox > label.custom-control-label::after { + left: 0.1rem !important; +} +div.o_boolean_toggle.custom-control.custom-checkbox > input.custom-control-input:checked + label.custom-control-label::after { + top: 1.5px !important; +} +div.o_boolean_toggle.custom-control.custom-checkbox > label.custom-control-label::after { + top: 1px; +} +.o_kanban_view.o_kanban_dashboard.o_hr_recruitment_kanban .ribbon span { + background-color: #556ee6; +} +.o_kanban_view.o_kanban_dashboard.o_hr_recruitment_kanban.o_kanban_ungrouped .o_kanban_record:not(.o_kanban_ghost) { + height: 197px; +} +//recruitment ends + +//note editor starts +.note-popover .popover .popover-body, .panel-heading.note-toolbar { + padding: 5px !important; + border-bottom: none !important; + background: rgba(85, 110, 230, 0.25) !important; + border-radius: 5px 5px 0 0 ; +} +.note-editor .note-statusbar { + border-radius: 0 0 5px 5px; +} +.wysiwyg_iframe, .note-editor { + border: 1px solid #556ee640 !important; + border-radius: 5px; +} +//note editor ends + +//progress starts +.oe_kanban_content .progress .progress-bar { + background-color: #556ee6; +} +.progress-bar.bg-muted-full { + background-color: #dee2e6; +} +//progress ends + +//email marketing starts +.o_domain_node.o_domain_tree.o_domain_selector > .o_domain_debug_container { + background: rgba(85, 110, 230, 0.25); + color: #000; +} +.o_domain_node.o_domain_tree.o_domain_selector > .o_domain_debug_container > input { + background: rgba(85, 110, 230, 0.25); +} +.o_field_widget.o_field_domain.o_inline_mode.o_edit_mode > .o_field_domain_panel { + top: -18px; +} +.o_domain_node .o_domain_selector_row > .o_domain_node_control_panel { + top: 5px; +} +.o_mail_emojis_dropdown { + bottom: 50px; +} +//emal marketing ends + +// menu+ color starts +.o_main_navbar > ul > li.o_extra_menu_items.show > ul > li > a { + background-color: #74788d; +} +.o_main_navbar > ul > li.o_extra_menu_items.show > ul > li > a.dropdown-toggle { + background-color: #abadba; +} +//menu+ color ends + +//top menu bar starts +nav.o_main_navbar.small_nav { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + height: auto; +} + +ul.o_menu_systray.topbar_icon { + margin-left: auto; +} +.o_main_navbar .o_user_menu > a { + display: flex; + align-items: center; +} +.oe_topbar_name { + max-width: 300px; + margin-left: 5px; + white-space: nowrap; + overflow: hidden; +} +ul.o_menu_sections { + padding-left: 20px; +} +@media (max-width: 1371px){ + nav.o_main_navbar { + display: flex; + flex-wrap: wrap; + justify-content: left; + height: auto; + } + ul.o_menu_systray.topbar_icon { + margin-right: 0; + + } + +} +//top menu bar ends + +//datetimepicker starts +.datepicker td, +.datepicker td a, +.datepicker th, +.datepicker span { + padding: 0.5rem !important; +} +div.dropdown-menu.bootstrap-datetimepicker-widget{ + width: auto !important; +} +//datetimepicker ends + +//groupby color starts +.o_list_view tbody > tr.o_group_header { + background-image: none !important; +} +.table-striped tbody tr.o_group_header:nth-of-type(odd) { + background-color: rgba(85,110,230,0.17); +} +.table-striped tbody tr.o_group_header:hover { + background-color: rgba(85,110,230,0.23); +} +//groupby color ends + +//Responsive starts +@media (max-width:992px) { + .o-dropdown.dropup > .o-dropdown--menu, .o-dropdown.dropdown > .o-dropdown--menu, .o-dropdown.dropleft > .o-dropdown--menu, .o-dropdown.dropright > .o-dropdown--menu { + position:absolute !important; + } + .o_action_manager.sidebar_margin { + margin: 0 !important; + background-color:red; + } + .top_heading.sidebar_margin { + margin: 0 !important; + } + .sidebar_panel .sidebar { + position: relative; + padding-top: 0px !important; + } + .sidebar_panel .sidebar_close { + display: block !important; + } + +} +@media (max-width:480px) { + ul.o_menu_apps { + padding-left: 25px; + } + .o_control_panel { + margin: 15px 10px 0 10px; + } + .o_control_panel > div { + flex-wrap: wrap; + } + .o_cp_top_left, .o_cp_top_right, + .o_cp_bottom_left, .o_cp_bottom_right { + width: 100%; + } + .o_widget_Discuss .o_Discuss.o-mobile:not(.o-adding-item) { + border: none !important; + } + html .o_web_client > .o_action_manager { + overflow: auto !important; + } + ul.o_menu_systray.topbar_icon li:nth-child(1) { + margin-left: 20px; + } + .o_main_navbar .o_user_menu { + margin-left: auto; + } + .sidebar_panel .sidebar .sidebar_logo img { + max-width: 112px; + } + .sidebar_panel .sidebar .sidebar_logo { + padding-top: 20px; + text-align: center; + padding-bottom: 5px; + } + li.o_switch_company_menu.show .dropdown-menu.dropdown-menu-right.show { + right: auto !important; + left: 0px; + } + ul.o_menu_systray.topbar_icon .dropdown-menu.show{ + left: 0px !important; + right: auto !important; + } + ul.o_menu_systray.topbar_icon { + margin-left: 0; + } + .o_MessagingMenu_dropdownMenu.o-mobile { + top: 70px; + } + .o_control_panel .o_cp_bottom_right { + flex-wrap: wrap; + } + .o_form_view .o_form_statusbar { + flex-wrap: wrap; + } + .btn { + padding: 2px 10px !important; + height: 35px !important; + } + .o_form_view .o_form_statusbar > .o_statusbar_status > .o_arrow_button:not(:first-child):before, + .o_form_view .o_form_statusbar > .o_statusbar_status > .o_arrow_button:not(:first-child):after { + border-top: 17px solid transparent !important; + border-bottom: 16px solid transparent !important; + } + .o_statusbar_status.o_field_widget.o_readonly_modifier { + width: 100%; + justify-content: flex-end; + margin-top: 5px; + } + .o_form_view .o_form_statusbar > .o_statusbar_buttons > .btn { + margin: 1px 0px 4px 0; + } + .o_form_statusbar .o_statusbar_buttons .btn { + margin-right: 5px !important; + } + .o_control_panel .o_cp_bottom_left > .o_cp_action_menus .o_dropdown_toggler_btn { + margin-right: 3px; + } + .o_form_view .o_form_sheet_bg > .o_form_sheet { + min-width: auto; + } + .nav-tabs .nav-link { + padding: 8px; + } + .modal.o_technical_modal.o_modal_full .modal-dialog .modal-content .modal-header { + background: $primary_accent; + } + .o_form_view .o_group .o_group_col_6 { + width: 100%; + } + .o_kanban_view.o_kanban_ungrouped .o_kanban_record { + flex-wrap: wrap; + } + .o_kanban_view .o_kanban_record.o_kanban_record_has_image_fill .o_kanban_image_fill_left { + border-radius: 5px; + } + .o_field_widget.o_field_image .o_form_image_controls > .fa.o_select_file_button { + background: #74788d; + } +} +@media (max-width:400px) { + .o_main_navbar .o_user_menu .oe_topbar_avatar { + height: 35px; + width: 35px; + } + + ul.o_menu_systray.topbar_icon .dropdown-menu.show{ + left: 0px !important; + margin-right: 50px !important; + } + ul.o_menu_systray.topbar_icon { + margin-left: 0; + } +} +//Responsive ends +//New changes + +.o_base_settings .o_setting_container .settings_tab { + flex:auto !important; +} + + +.o_main_navbar .dropdown .dropdown-toggle, .o_main_navbar .o_menu_sections .dropdown .dropdown-toggle, +.o_main_navbar .o_menu_systray .dropdown .dropdown-toggle, .o_main_navbar .o_nav_entry, +.o_main_navbar .o_menu_sections .o_nav_entry, .o_main_navbar .o_menu_systray .o_nav_entry, +.o_main_navbar > .o_menu_sections > div, .o_main_navbar > .o_menu_sections > div > a, +.o_main_navbar .o_menu_systray > div, .o_main_navbar .o_menu_systray > div > a, +.o_main_navbar .o_menu_toggle, .o_main_navbar .o_navbar_apps_menu, .o_main_navbar .o_menu_brand { + color: $f_color !important; +} + + +.o_content .o_expense_purple { + color: #556ee6; +} +.row.o_recruitment_kanban_boxes .custom-checkbox input:checked + label::after { + display:none +} \ No newline at end of file diff --git a/code_backend_theme/static/src/scss/theme_accent.scss b/code_backend_theme/static/src/scss/theme_accent.scss new file mode 100644 index 000000000..fd27b8ff7 --- /dev/null +++ b/code_backend_theme/static/src/scss/theme_accent.scss @@ -0,0 +1,9 @@ +$primary_accent: #556ee6 !default; +$secondary_accent: #334332 !default; +$inverse_accent: #ffffff !default; +$o-kanban-color-border-width: 8px; +$selected_row: #ffffff !default; +$bg_white: #ffffff !default; +$f_color: #555b6d !default; +$primary_hover: #485ec4 !default; + diff --git a/code_backend_theme/static/src/xml/styles.xml b/code_backend_theme/static/src/xml/styles.xml new file mode 100644 index 000000000..bbc167347 --- /dev/null +++ b/code_backend_theme/static/src/xml/styles.xml @@ -0,0 +1,27 @@ + + + + + + d-flex align-items-center + + + + + + + + + + + + + + o_search_panel_counter ml-2 small + + + + + \ No newline at end of file diff --git a/code_backend_theme/static/src/xml/top_bar.xml b/code_backend_theme/static/src/xml/top_bar.xml new file mode 100644 index 000000000..7b593abe0 --- /dev/null +++ b/code_backend_theme/static/src/xml/top_bar.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/code_backend_theme/views/icons.xml b/code_backend_theme/views/icons.xml new file mode 100644 index 000000000..62d026525 --- /dev/null +++ b/code_backend_theme/views/icons.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/code_backend_theme/views/layout.xml b/code_backend_theme/views/layout.xml new file mode 100644 index 000000000..20f83c30a --- /dev/null +++ b/code_backend_theme/views/layout.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file