From 144fffbb1a660ea3ca3ba8e76066cdb550d60d74 Mon Sep 17 00:00:00 2001 From: Altahir Hassan Date: Mon, 5 Jan 2026 15:59:28 +0400 Subject: [PATCH] Add Odex Sidebar Backend Theme 2 with collapsible menu and configuration options --- .../odex_sidebar_backend_theme2/ __init__.py | 1 - .../odex_sidebar_backend_theme2/README.rst | 142 ++++++++++++++++++ .../odex_sidebar_backend_theme2/__init__.py | 3 + .../__manifest__.py | 45 ++++-- .../models/__init__.py | 3 + .../models/res_config_settings.py | 90 +++++++++++ .../static/src/js/sidebar_css_loader.js | 54 +++++++ .../static/src/js/sidebar_menu.js | 35 ++++- .../static/src/scss/sidebar_menu.scss | 5 - .../static/src/xml/sidebar_menu_template.xml | 4 +- .../views/res_config_settings.xml | 43 ++++++ 11 files changed, 403 insertions(+), 22 deletions(-) delete mode 100644 odex30_base/odex_sidebar_backend_theme2/ __init__.py create mode 100644 odex30_base/odex_sidebar_backend_theme2/README.rst create mode 100644 odex30_base/odex_sidebar_backend_theme2/__init__.py create mode 100644 odex30_base/odex_sidebar_backend_theme2/models/__init__.py create mode 100644 odex30_base/odex_sidebar_backend_theme2/models/res_config_settings.py create mode 100644 odex30_base/odex_sidebar_backend_theme2/static/src/js/sidebar_css_loader.js create mode 100644 odex30_base/odex_sidebar_backend_theme2/views/res_config_settings.xml diff --git a/odex30_base/odex_sidebar_backend_theme2/ __init__.py b/odex30_base/odex_sidebar_backend_theme2/ __init__.py deleted file mode 100644 index 7c68785..0000000 --- a/odex30_base/odex_sidebar_backend_theme2/ __init__.py +++ /dev/null @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/odex30_base/odex_sidebar_backend_theme2/README.rst b/odex30_base/odex_sidebar_backend_theme2/README.rst new file mode 100644 index 0000000..f3c345c --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme2/README.rst @@ -0,0 +1,142 @@ +======================== +Odex Sidebar Backend Theme v2 +======================== + +Overview +======== + +This module provides a modern, collapsible sidebar menu system that replaces the default Odex app menu bar with an enhanced navigation experience. It delivers an improved user interface with better space management and responsive design. + +Features +======== + +* **Collapsible/Expandable Sidebar Menu**: Toggle the sidebar to maximize screen space for your work +* **Smooth Navigation**: Seamlessly navigate across all Odex applications and menus +* **Responsive Design**: Adapts beautifully to different screen sizes (desktop, tablet, mobile) +* **Persistent State**: Sidebar collapse/expand state is saved and persists across user sessions +* **Enable/Disable Toggle**: Control the sidebar feature through the Settings panel +* **Optimized Performance**: Minimal performance impact with efficient menu rendering + +Installation +============ + +1. Download or clone this module into your Odex addons directory +2. Restart the Odex server +3. Navigate to **Apps** and search for "Odex Sidebar Backend Theme 2" +4. Click **Install** + +Dependencies +============ + +This module requires: + +* Odex 18.0 (or compatible version) +* web module +* base module + +Configuration +============= + +After installation, you can configure the sidebar menu feature: + +1. Navigate to **Settings > General Settings** (or **Settings > Sidebar Menu** if available) +2. Find the **Sidebar Menu** section +3. Enable or disable the sidebar menu feature as needed +4. Click **Save** + +The setting is automatically saved and persists across all user sessions. + +Usage +===== + +Once enabled: + +* Click the **menu icon** (≡) at the top of the sidebar to collapse/expand it +* The sidebar state is automatically saved for your next session +* All applications and menu items are accessible through the sidebar +* The responsive design automatically adjusts on smaller screens + +Module Structure +================ + +:: + + odex_sidebar_backend_theme2/ + ├── __init__.py # Python initialization + ├── __manifest__.py # Module metadata + ├── views/ + │ └── res_config_settings.xml # Settings configuration + ├── static/ + │ └── src/ + │ ├── scss/ + │ │ └── sidebar_menu.scss # Sidebar styling + │ ├── js/ + │ │ ├── sidebar_menu.js # Main sidebar logic + │ │ ├── sidebar_css_loader.js # CSS loading functionality + │ │ ├── menu_item.js # Menu item handling + │ │ └── navbar_patch.js # Navigation bar customizations + │ └── xml/ + │ ├── sidebar_menu_template.xml # Sidebar template + │ ├── menu_item_template.xml # Menu item template + │ └── navbar_patch.xml # Navbar patches + └── README.rst # This file + +Technologies +============= + +* JavaScript (ES6+) +* SCSS/CSS +* XML (Odex QWeb templates) +* Python + +Browser Support +=============== + +* Chrome/Edge 90+ +* Firefox 88+ +* Safari 14+ +* Mobile browsers (iOS Safari, Chrome Mobile) + +Troubleshooting +=============== + +**Sidebar not appearing:** +- Clear your browser cache +- Log out and log back in +- Check that the module is enabled in Settings + +**Sidebar not saving state:** +- Ensure cookies are enabled in your browser +- Check browser console for JavaScript errors (F12) + +**Performance issues:** +- Clear browser cache and local storage +- Try disabling and re-enabling the module + +Support +======= + +For issues, questions, or feature requests, please contact Epxert Ltd. + +* Website: https://www.exp-sa.com +* Email: support@exp-sa.com + +License +======= + +This module is licensed under the LGPL-3 License. See the LICENSE file for details. + +Authors +======= + +* Epxert Ltd. (https://www.exp-sa.com) + +Changelog +========= + +**Version 1.0.0** (Initial Release) +- Initial release of Odex Sidebar Backend Theme 2 +- Collapsible sidebar menu functionality +- Persistent state management +- Responsive design support +- Enable/disable toggle in settings diff --git a/odex30_base/odex_sidebar_backend_theme2/__init__.py b/odex30_base/odex_sidebar_backend_theme2/__init__.py new file mode 100644 index 0000000..cde864b --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme2/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/odex30_base/odex_sidebar_backend_theme2/__manifest__.py b/odex30_base/odex_sidebar_backend_theme2/__manifest__.py index ba61d13..9fff9d6 100644 --- a/odex30_base/odex_sidebar_backend_theme2/__manifest__.py +++ b/odex30_base/odex_sidebar_backend_theme2/__manifest__.py @@ -1,26 +1,46 @@ # -*- coding: utf-8 -*- { - 'name': 'Odex Sidebar Backend Theme2', - 'version': '18.0.1.0.0', - 'category': 'Web', - 'summary': 'Custom menu for navigating all Odoo apps and menus smoothly', + 'name': 'Odex Sidebar Backend Theme 2', + 'version': '1.0.0', + 'category': 'Web/Themes', + 'summary': 'Enhanced collapsible sidebar menu for Odex backend navigation', 'description': """ - Replace the default Odoo app menu bar with a collapsible sidebar menu. + Odex Sidebar Backend Theme 2 + ============================= + + This module provides a modern, collapsible sidebar menu system that replaces + the default Odex app menu bar with an enhanced navigation experience. + + Key Features: + - Collapsible/expandable sidebar menu for better space management + - Smooth navigation across all Odex applications and menus + - Responsive design that adapts to different screen sizes + - Persistent sidebar state (collapse/expand) across sessions + - Enable/disable sidebar via Settings panel + - Minimal performance impact with optimized menu rendering + + Configuration: + Navigate to Settings > Sidebar Menu to enable or disable the sidebar menu feature. + The setting is automatically saved and persists across all user sessions. """, - 'author': 'Your Company', - 'website': 'https://www.yourcompany.com', - 'depends': ['web'], - 'data': [], + 'author': 'Epxert Ltd.', + 'website': 'https://www.exp-sa.com', + 'license': 'LGPL-3', + 'depends': [ + 'web', + 'base', + ], + 'data': [ + 'views/res_config_settings.xml', + ], 'assets': { 'web.assets_backend': [ 'odex_sidebar_backend_theme2/static/src/scss/sidebar_menu.scss', - 'odex_sidebar_backend_theme2/static/src/xml/sidebar_menu_template.xml', 'odex_sidebar_backend_theme2/static/src/xml/menu_item_template.xml', - 'odex_sidebar_backend_theme2/static/src/js/sidebar_menu.js', + 'odex_sidebar_backend_theme2/static/src/js/sidebar_css_loader.js', 'odex_sidebar_backend_theme2/static/src/js/menu_item.js', - 'odex_sidebar_backend_theme2/static/src/xml/navbar_patch.xml', 'odex_sidebar_backend_theme2/static/src/js/navbar_patch.js', ], @@ -28,5 +48,4 @@ 'installable': True, 'application': False, 'auto_install': False, - 'license': 'LGPL-3', } diff --git a/odex30_base/odex_sidebar_backend_theme2/models/__init__.py b/odex30_base/odex_sidebar_backend_theme2/models/__init__.py new file mode 100644 index 0000000..034e86d --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme2/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import res_config_settings diff --git a/odex30_base/odex_sidebar_backend_theme2/models/res_config_settings.py b/odex30_base/odex_sidebar_backend_theme2/models/res_config_settings.py new file mode 100644 index 0000000..4fd19c2 --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme2/models/res_config_settings.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +from odoo import fields,api, models + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + sidebar_menu_enable = fields.Boolean( + config_parameter='odex_sidebar_backend_theme2.sidebar_menu_enable', + string='Enable Sidebar Menu', + help='Enable or disable the sidebar menu in the backend' + ) + + disable_nav_menu_section = fields.Boolean( + config_parameter='odex_sidebar_backend_theme2.disable_nav_menu_section', + string='Disable Navigation Menu Section', + help='Enable or disable the top navigation bar menu section in the backend interface' + ) + + sidebar_menu_icon = fields.Binary( + string="Sidebar Icon", + help="Upload an icon for the sidebar menu.", + ) + + # set default value for the setting + def get_values(self): + res = super(ResConfigSettings, self).get_values() + IrConfigParam = self.env['ir.config_parameter'].sudo() + sidebar_menu_enable = IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_enable') + disable_nav_menu_section = IrConfigParam.get_param('odex_sidebar_backend_theme2.disable_nav_menu_section') + res.update( + sidebar_menu_enable=sidebar_menu_enable == 'True', + disable_nav_menu_section=disable_nav_menu_section == 'True', + sidebar_menu_icon=IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_icon') + ) + return res + + def _generate_sidebar_css(self): + """Generate CSS rules for sidebar menu state""" + if self.disable_nav_menu_section: + return """ +/* Sidebar Menu Disabled - Hide Top Menu Sections */ +.o_main_navbar .o_menu_sections { +{ + display: none !important; + visibility: hidden !important; +} +""" + return "" + + # save the setting value + def set_values(self): + super(ResConfigSettings, self).set_values() + IrConfigParam = self.env['ir.config_parameter'].sudo() + IrConfigParam.set_param( + 'odex_sidebar_backend_theme2.sidebar_menu_enable', + str(self.sidebar_menu_enable) + ) + IrConfigParam.set_param( + 'odex_sidebar_backend_theme2.disable_nav_menu_section', + str(self.disable_nav_menu_section) + ) + IrConfigParam.set_param( + 'odex_sidebar_backend_theme2.sidebar_menu_icon', + self.sidebar_menu_icon or '' + ) + + if self.sidebar_menu_icon: + # Store the image URL in config parameter + image_url = f"/web/image/res.config.settings/{self.id}/sidebar_menu_icon" + self.env['ir.config_parameter'].sudo().set_param('odex_sidebar_backend_theme2.sidebar_menu_icon_url', image_url) + else: + self.env['ir.config_parameter'].sudo().set_param('odex_sidebar_backend_theme2.sidebar_menu_icon_url', '') + + # Generate and store CSS for sidebar state + css_code = self._generate_sidebar_css() + IrConfigParam.set_param( + 'odex_sidebar_backend_theme2.sidebar_css', + css_code + ) + + return True + + @api.model + def get_sidebar_setting(self): + IrConfigParam = self.env['ir.config_parameter'].sudo() + sidebar_enabled = IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_enable') == 'True' + sidebar_icon_url = IrConfigParam.get_param('odex_sidebar_backend_theme2.sidebar_menu_icon_url') + return {'sidebar_enabled': sidebar_enabled, 'sidebar_icon_url': sidebar_icon_url} \ No newline at end of file diff --git a/odex30_base/odex_sidebar_backend_theme2/static/src/js/sidebar_css_loader.js b/odex30_base/odex_sidebar_backend_theme2/static/src/js/sidebar_css_loader.js new file mode 100644 index 0000000..63e940d --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme2/static/src/js/sidebar_css_loader.js @@ -0,0 +1,54 @@ +/** @odoo-module **/ + +import { useService } from "@web/core/utils/hooks"; +import { registry } from "@web/core/registry"; + +/** + * Load and inject sidebar CSS rules based on configuration + */ +export function loadSidebarCSS() { + // Get the RPC service + const rpc = useService("rpc"); + + const loadCSS = async () => { + try { + // Fetch the stored CSS from config parameter + const css = await rpc( + '/web/dataset/call_kw/ir.config_parameter/get_param', + { + model: 'ir.config_parameter', + method: 'get_param', + args: ['odex_sidebar_backend_theme2.sidebar_css'], + kwargs: {}, + } + ); + + if (css && css.trim()) { + // Create a style element and inject the CSS + const style = document.createElement('style'); + style.type = 'text/css'; + style.id = 'sidebar-dynamic-css'; + style.innerHTML = css; + document.head.appendChild(style); + console.log('Sidebar CSS injected successfully'); + } + } catch (error) { + console.error('Error loading sidebar CSS:', error); + } + }; + + // Load CSS when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', loadCSS); + } else { + loadCSS(); + } +} + +// Initialize on module load +registry.category("web_tour.tours").add("sidebar_css_loader", { + steps: [], +}); + +// Auto-load CSS on page load +// loadSidebarCSS(); diff --git a/odex30_base/odex_sidebar_backend_theme2/static/src/js/sidebar_menu.js b/odex30_base/odex_sidebar_backend_theme2/static/src/js/sidebar_menu.js index 4e785b1..512cb6e 100644 --- a/odex30_base/odex_sidebar_backend_theme2/static/src/js/sidebar_menu.js +++ b/odex30_base/odex_sidebar_backend_theme2/static/src/js/sidebar_menu.js @@ -4,6 +4,7 @@ import { Component, onMounted, useEffect, useState } from "@odoo/owl"; import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { MenuItem } from "./menu_item"; +import { rpc } from "@web/core/network/rpc" export class SidebarMenu extends Component { static template = "odex_sidebar_backend_theme2.SidebarMenu" @@ -14,13 +15,18 @@ export class SidebarMenu extends Component { this.menuService = useService("menu"); this.busService = useService("bus_service"); this.actionService = useService("action"); + this.rpc = rpc; this.state = useState({ menus: [], isOpen: true, - isCollapsed: false + isCollapsed: false, + sidebarEnabled: false, + sidebarMenuIconUrl: null, }); + this.loadSidebarSetting() + // =================== JavaScript Control Starts Here =================== const applyLayoutChanges = (isOpen) => { @@ -92,6 +98,33 @@ export class SidebarMenu extends Component { }); } + async loadSidebarSetting() { + try { + const result = await this.rpc('/web/dataset/call_kw', { + model: 'res.config.settings', + method: 'get_sidebar_setting', + args: [], + kwargs: {}, + }); + + // Convert string result to boolean + this.state.sidebarEnabled = result.sidebar_enabled !== 'False' && result.sidebar_enabled !== false && result.sidebar_enabled !== ''; + + // If sidebar is disabled, close it + if (!this.state.sidebarEnabled) { + this.state.isOpen = false; + } + + // Load sidebar menu icon URL + this.state.sidebarMenuIconUrl = result.sidebar_icon_url || '/odex_sidebar_backend_theme2/static/src/img/logo1.png'; + + } catch (error) { + console.error('Error loading sidebar setting:', error); + // Default to enabled if setting cannot be loaded + this.state.sidebarEnabled = true; + } + } + loadMenus() { const allMenus = this.menuService.getAll(); const clonedMenus = structuredClone(allMenus); diff --git a/odex30_base/odex_sidebar_backend_theme2/static/src/scss/sidebar_menu.scss b/odex30_base/odex_sidebar_backend_theme2/static/src/scss/sidebar_menu.scss index 365569d..e5fd256 100644 --- a/odex30_base/odex_sidebar_backend_theme2/static/src/scss/sidebar_menu.scss +++ b/odex30_base/odex_sidebar_backend_theme2/static/src/scss/sidebar_menu.scss @@ -461,11 +461,6 @@ display: none !important; } -/* Hide submenus inside the top bar */ -.o_main_navbar .o_menu_sections { - display: none !important; -} - .o_web_client { margin-left: 0 !important; transition: all 0.3s ease-in-out; diff --git a/odex30_base/odex_sidebar_backend_theme2/static/src/xml/sidebar_menu_template.xml b/odex30_base/odex_sidebar_backend_theme2/static/src/xml/sidebar_menu_template.xml index 18ec6b1..199a731 100644 --- a/odex30_base/odex_sidebar_backend_theme2/static/src/xml/sidebar_menu_template.xml +++ b/odex30_base/odex_sidebar_backend_theme2/static/src/xml/sidebar_menu_template.xml @@ -1,11 +1,11 @@ -
+