From fb0594957c0a57546818b105aa55836eea6fbeaa Mon Sep 17 00:00:00 2001 From: abuzaid4exp Date: Mon, 17 Nov 2025 09:24:50 +0200 Subject: [PATCH 1/4] Add custom sidebar theme with menu and navbar integration --- .../odex_sidebar_backend_theme/ __init__.py | 1 + .../__manifest__.py | 32 ++++ .../static/src/js/menu_item.js | 56 ++++++ .../static/src/js/navbar_patch.js | 25 +++ .../static/src/js/sidebar_menu.js | 103 +++++++++++ .../static/src/scss/sidebar_menu.scss | 173 ++++++++++++++++++ .../static/src/xml/menu_item_template.xml | 23 +++ .../static/src/xml/navbar_patch.xml | 12 ++ .../static/src/xml/sidebar_menu_template.xml | 13 ++ 9 files changed, 438 insertions(+) create mode 100644 odex30_base/odex_sidebar_backend_theme/ __init__.py create mode 100644 odex30_base/odex_sidebar_backend_theme/__manifest__.py create mode 100644 odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js create mode 100644 odex30_base/odex_sidebar_backend_theme/static/src/js/navbar_patch.js create mode 100644 odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js create mode 100644 odex30_base/odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss create mode 100644 odex30_base/odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml create mode 100644 odex30_base/odex_sidebar_backend_theme/static/src/xml/navbar_patch.xml create mode 100644 odex30_base/odex_sidebar_backend_theme/static/src/xml/sidebar_menu_template.xml diff --git a/odex30_base/odex_sidebar_backend_theme/ __init__.py b/odex30_base/odex_sidebar_backend_theme/ __init__.py new file mode 100644 index 0000000..7c68785 --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme/ __init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/odex30_base/odex_sidebar_backend_theme/__manifest__.py b/odex30_base/odex_sidebar_backend_theme/__manifest__.py new file mode 100644 index 0000000..f117350 --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme/__manifest__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Custom sidebar', + 'version': '18.0.1.0.0', + 'category': 'Web', + 'summary': 'Custom menu for navigating all Odoo apps and menus smoothly', + 'description': """ + Replace the default Odoo app menu bar with a collapsible sidebar menu. + """, + 'author': 'Your Company', + 'website': 'https://www.yourcompany.com', + 'depends': ['web'], + 'data': [], + 'assets': { + 'web.assets_backend': [ + 'odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss', + + # 'odex_sidebar_backend_theme/static/src/xml/sidebar_menu_template.xml', + # 'odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml', + + 'odex_sidebar_backend_theme/static/src/js/sidebar_menu.js', + 'odex_sidebar_backend_theme/static/src/js/menu_item.js', + + 'odex_sidebar_backend_theme/static/src/xml/navbar_patch.xml', + 'odex_sidebar_backend_theme/static/src/js/navbar_patch.js', + ], + }, + 'installable': True, + 'application': False, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js b/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js new file mode 100644 index 0000000..b84f5eb --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js @@ -0,0 +1,56 @@ +/** @odoo-module **/ + +import { Component, xml } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; + +export class MenuItem extends Component { + static template = xml` +
  • + + + +
  • ` + + static components = { MenuItem }; + + // 2. نحدد الـ props التي يستقبلها المكون + static props = { + menu: { type: Object }, + siblings: { type: Array }, + }; + + setup() { + this.menuService = useService("menu"); + } + + // 3. تعديل دالة toggleMenu لتتعامل مع الأشقاء + toggleMenu(menu, siblings) { + const wasOpen = menu.isOpen; // نحفظ الحالة القديمة + + // الخطوة أ: إغلاق جميع القوائم الشقيقة + for (const sibling of siblings) { + sibling.isOpen = false; + } + + // الخطوة ب: فتح القائمة الحالية فقط إذا كانت مغلقة في الأصل + // هذا يمنعها من إعادة الفتح فورًا ويسمح بسلوك الإغلاق عند النقر عليها مرة أخرى + if (!wasOpen) { + menu.isOpen = true; + } + } + + selectMenu(menu) { + this.menuService.selectMenu(menu); + } +} diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/js/navbar_patch.js b/odex30_base/odex_sidebar_backend_theme/static/src/js/navbar_patch.js new file mode 100644 index 0000000..32436bb --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme/static/src/js/navbar_patch.js @@ -0,0 +1,25 @@ +/** @odoo-module **/ + +// **العودة إلى استيراد NavBar** +import { NavBar } from "@web/webclient/navbar/navbar"; +import { patch } from "@web/core/utils/patch"; +import { useService } from "@web/core/utils/hooks"; + + +// **العودة إلى عمل patch لمكون NavBar** +patch(NavBar.prototype, { + setup() { + super.setup(...arguments); // مهم جدًا + // استدعاء خدمة dialog + this.busService = useService("bus_service"); + }, + + // 3. إضافة دالة لإرسال الحدث + toggleSidebar() { + this.busService.trigger("toggle-sidebar"); + }, +}); + + + + diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js b/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js new file mode 100644 index 0000000..d90d079 --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js @@ -0,0 +1,103 @@ +/** @odoo-module **/ + +import { Component, onMounted, useEffect, useState, xml } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { MenuItem } from "./menu_item"; + +export class SidebarMenu extends Component { + static template = xml` +
    +
    + +
    + +
    ` + ; + + static components = { MenuItem }; + + setup() { + this.menuService = useService("menu"); + this.busService = useService("bus_service"); + + this.state = useState({ + menus: [], + isOpen: false + }); + + // =================== التحكم عبر JavaScript يبدأ هنا =================== + + // const applyLayoutChanges = (isOpen) => { + // const actionManager = document.querySelector('.o_action_manager'); + // const mainNavbar = document.querySelector('.o_navbar'); + // const sidebarWidth = "250px"; + + // if (isOpen) { + // if (actionManager) actionManager.style.marginLeft = sidebarWidth; + // if (mainNavbar) mainNavbar.style.marginLeft = sidebarWidth; + // } else { + // if (actionManager) actionManager.style.marginLeft = '0'; + // if (mainNavbar) mainNavbar.style.marginLeft = '0'; + // } + // }; + + // // 2. استخدام useEffect لمراقبة التغييرات في state.isOpen + // useEffect( + // (isOpen) => { + // // هذا الكود سيعمل في كل مرة تتغير فيها قيمة isOpen + // applyLayoutChanges(isOpen); + // }, + // () => [this.state.isOpen] // <-- هذا هو الجزء الحاسم: نطلب من useEffect مراقبة هذه القيمة + // ); + + // ==================================================================== + + // 1. عرّف الدالة كمتغير داخل setup + const toggleSidebar = () => { + // الآن `this` مضمون أنه يشير إلى نسخة المكون التي تم إنشاؤها بواسطة setup + this.state.isOpen = !this.state.isOpen; + // this.busService.trigger("sidebar-state-changed", this.state.isOpen); + }; + + // 2. اربطها بـ this حتى يمكن الوصول إليها من أماكن أخرى إذا لزم الأمر + this.toggleSidebar = toggleSidebar; + // =============================================== + + onMounted(() => { + this.loadMenus(); + this.busService.addEventListener("toggle-sidebar", this.toggleSidebar); + }); + } + + loadMenus() { + const allMenus = this.menuService.getAll(); + const clonedMenus = structuredClone(allMenus); + const menuMap = new Map(clonedMenus.map(menu => [menu.id, menu])); + let finalMenuTree = []; + + for (const menu of clonedMenus) { + if (menu.children) { + menu.children = menu.children.map(childId => menuMap.get(childId)).filter(Boolean); + } else { + menu.children = []; + } + menu.isOpen = false; + } + + const rootMenu = clonedMenus.find(menu => menu.id === "root"); + if (rootMenu && rootMenu.children) { + finalMenuTree = rootMenu.children; + } + + this.state.menus.splice(0, this.state.menus.length, ...finalMenuTree); + } + } + +registry.category("main_components").add("sidebar_menu_component", { + Component: SidebarMenu, +}); diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss b/odex30_base/odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss new file mode 100644 index 0000000..1917c4e --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss @@ -0,0 +1,173 @@ +/* =================================== + 1. تصميم الـ Sidebar الرئيسي + =================================== */ +.custom_sidebar { + position: fixed; + top: 0; + left: 0; + width: 250px; + height: 100%; + background-color: #2f3542 !important; + overflow-y: auto; + z-index: 999; + padding-top: 60px; + transform: translateX(-100%); + transition: transform 0.3s ease-in-out; +} + +.custom_sidebar .custom_icon_close { +color: #fff; + position: absolute; + top: 8px; + right: 30px; + font-size: 1.2rem; + cursor: pointer; +} + +/* عند إضافة كلاس is-open، تظهر القائمة */ +.custom_sidebar.is-open { + transform: translateX(0); +} + +// اخقاء الايقون الافتراضية للابلكيشن من الشريط العلوي +.o_navbar .o_main_navbar .o_navbar_apps_menu .o-dropdown { + display: none !important; +} + +/* =================================== + 2. إخفاء الشريط العلوي وتعديل المحتوى الرئيسي + =================================== */ +.o_web_client { + margin-left: 0 !important; + transition: all 0.3s ease-in-out; +} + +/* تعديل عرض الشريط العلوي والمحتوى الرئيسي */ +.o_navbar, +.o_action_manager { + transition: all 0.3s ease-in-out; +} + +/* إخفاء القوائم داخل الشريط العلوي */ +.o_main_navbar .o_menu_sections { + display: none !important; +} + +/* =================================== + 3. تصميم عناصر القائمة (Accordion) + =================================== */ + +/* تصميم حاوية عنصر القائمة الرئيسي */ +.custom_sidebar .menu-item-container { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + border-bottom: 1px solid rgba(255, 255, 255, 0.18); + transition: background-color 0.3s; +} + +.custom_sidebar .sidebar_menu_list { + list-style: none; + padding: 0; + margin: 0; +} + +/* تصميم رابط القائمة الرئيسي */ +.custom_sidebar .menu-item-container .menu-item-link { + flex-grow: 1; + padding: 13px 9px; + color: #b9d0ec; + font-size: 15px; + text-decoration: none; +} + +/* تغيير اللون عند الفتح (عندما يكون للعنصر li كلاس 'open') */ +.custom_sidebar li.open > .menu-item-container { + background: #45ae54; +} + +.custom_sidebar li.open > .menu-item-container .menu-item-link { + color: #fff; +} + +/* تصميم أيقونة الفتح/الإغلاق (السهم) */ +.custom_sidebar .toggle-icon { + width: 20px; + height: 20px; + margin-right: 10px; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23b9d0ec' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e" ); + background-repeat: no-repeat; + background-position: center; + transition: transform 0.2s ease-in-out; +} + +/* تغيير لون السهم عند الفتح */ +.custom_sidebar li.open > .menu-item-container .toggle-icon { + transform: rotate(90deg); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e" ); +} + +/* =================================== + 4. تصميم القوائم الفرعية + =================================== */ +.custom_sidebar .submenu_list { + list-style: none; + padding: 5px 0; + margin: 0; + background-color: rgba(0, 0, 0, 0.2); /* خلفية داكنة قليلاً للقوائم الفرعية */ +} + +.custom_sidebar .submenu_list li .menu-item-link { + font-size: 14px; + padding-left: 25px; /* مسافة بادئة أكبر لتمييز المستوى */ +} + +/* تصميم خاص بعناصر المستوى الأخير (التي لا تفتح قوائم أخرى) */ +.custom_sidebar .submenu_list li:not(.has-children) .menu-item-link { + color: #fff; + transition: all 0.3s; +} + +.custom_sidebar .submenu_list li:not(.has-children) .menu-item-link:hover { + background-color: #193658; + color: #fff; +} + +/* =================================== + 5. تصميم شريط التمرير (Scrollbar) + =================================== */ +.custom_sidebar::-webkit-scrollbar { + width: 8px; +} + +.custom_sidebar::-webkit-scrollbar-track { + background: #2f3542; /* خلفية شريط التمرير بنفس لون الـ Sidebar */ +} + +.custom_sidebar::-webkit-scrollbar-thumb { + background-color: #45ae54; + border-radius: 4px; +} + +.custom_sidebar::-webkit-scrollbar-thumb:hover { + background-color: #5bc569; /* لون أفتح قليلاً عند المرور */ +} + +/* =================================== + 6. تصميم أيقونة الفتح (جديد) + =================================== */ +.sidebar_toggle_icon { + position: relative; + display: flex; + align-items: center; + width: auto; + height: calc(var(--o-navbar-height) - 0px); + border-radius: 0; + user-select: none; + background: transparent; + color: var(--NavBar-entry-color, rgba(255, 255, 255, 0.9)); + font-size: 1.2em; + padding: 0 15px; + border: none; +} \ No newline at end of file diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml b/odex30_base/odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml new file mode 100644 index 0000000..b408df6 --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml @@ -0,0 +1,23 @@ + + + + +
  • + + + +
  • +
    + +
    diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/xml/navbar_patch.xml b/odex30_base/odex_sidebar_backend_theme/static/src/xml/navbar_patch.xml new file mode 100644 index 0000000..9f4bf58 --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme/static/src/xml/navbar_patch.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/xml/sidebar_menu_template.xml b/odex30_base/odex_sidebar_backend_theme/static/src/xml/sidebar_menu_template.xml new file mode 100644 index 0000000..c3c5016 --- /dev/null +++ b/odex30_base/odex_sidebar_backend_theme/static/src/xml/sidebar_menu_template.xml @@ -0,0 +1,13 @@ + + + + +
    + +
    +
    +
    From ed605e4de928fadb6babefdfad618ac1115c99fe Mon Sep 17 00:00:00 2001 From: abuzaid4exp Date: Mon, 17 Nov 2025 10:35:22 +0200 Subject: [PATCH 2/4] Enhance sidebar menu functionality with localStorage integration for active menu state --- .../static/src/js/menu_item.js | 8 +++++ .../static/src/js/sidebar_menu.js | 35 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js b/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js index b84f5eb..7d05b1f 100644 --- a/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js +++ b/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js @@ -52,5 +52,13 @@ export class MenuItem extends Component { selectMenu(menu) { this.menuService.selectMenu(menu); + try { + // استخدم xmlid إذا كان موجودًا، وإلا استخدم name كمعرّف ثابت + const uniqueId = menu.xmlid || menu.name || menu.id; + localStorage.setItem('odex_sidebar_active_menu', uniqueId); + console.log('Saved menu:', uniqueId, menu.name); + } catch (e) { + console.error('Storage error:', e); + } } } diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js b/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js index d90d079..9ab2595 100644 --- a/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js +++ b/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js @@ -77,12 +77,19 @@ export class SidebarMenu extends Component { loadMenus() { const allMenus = this.menuService.getAll(); const clonedMenus = structuredClone(allMenus); + + // أنشئ خرائط بناءً على xmlid و name أيضًا const menuMap = new Map(clonedMenus.map(menu => [menu.id, menu])); + const menuMapByXmlId = new Map(clonedMenus.map(menu => [menu.xmlid || menu.name, menu])); + const parentMap = new Map(); let finalMenuTree = []; for (const menu of clonedMenus) { if (menu.children) { - menu.children = menu.children.map(childId => menuMap.get(childId)).filter(Boolean); + menu.children = menu.children.map(childId => { + parentMap.set(childId, menu.id); + return menuMap.get(childId); + }).filter(Boolean); } else { menu.children = []; } @@ -94,6 +101,32 @@ export class SidebarMenu extends Component { finalMenuTree = rootMenu.children; } + try { + const activeId = localStorage.getItem('odex_sidebar_active_menu'); + console.log('Restored menu ID:', activeId); + + // ابحث أولاً بـ xmlid/name، ثم بـ id + let targetMenu = menuMapByXmlId.get(activeId) || menuMap.get(activeId); + + if (targetMenu) { + let currentId = targetMenu.id; + while (parentMap.has(currentId)) { + const parentId = parentMap.get(currentId); + const parentMenu = menuMap.get(parentId); + if (parentMenu) { + parentMenu.isOpen = true; + currentId = parentId; + } else { + break; + } + } + } else { + console.warn('Active menu not found in map:', activeId); + } + } catch (e) { + console.error('Storage error:', e); + } + this.state.menus.splice(0, this.state.menus.length, ...finalMenuTree); } } From 0a1cdc9d93925c8d9ea49a56e001feb5fe72edaf Mon Sep 17 00:00:00 2001 From: abuzaid4exp Date: Mon, 17 Nov 2025 12:08:40 +0200 Subject: [PATCH 3/4] Refactor sidebar menu templates and JavaScript components for improved structure and functionality --- .../odex_sidebar_backend_theme/__manifest__.py | 4 ++-- .../static/src/js/menu_item.js | 18 +----------------- .../static/src/js/sidebar_menu.js | 13 +------------ .../static/src/scss/sidebar_menu.scss | 2 +- .../static/src/xml/menu_item_template.xml | 2 +- .../static/src/xml/sidebar_menu_template.xml | 4 +++- 6 files changed, 9 insertions(+), 34 deletions(-) diff --git a/odex30_base/odex_sidebar_backend_theme/__manifest__.py b/odex30_base/odex_sidebar_backend_theme/__manifest__.py index f117350..b5024dd 100644 --- a/odex30_base/odex_sidebar_backend_theme/__manifest__.py +++ b/odex30_base/odex_sidebar_backend_theme/__manifest__.py @@ -15,8 +15,8 @@ 'web.assets_backend': [ 'odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss', - # 'odex_sidebar_backend_theme/static/src/xml/sidebar_menu_template.xml', - # 'odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml', + 'odex_sidebar_backend_theme/static/src/xml/sidebar_menu_template.xml', + 'odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml', 'odex_sidebar_backend_theme/static/src/js/sidebar_menu.js', 'odex_sidebar_backend_theme/static/src/js/menu_item.js', diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js b/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js index 7d05b1f..3f84bf8 100644 --- a/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js +++ b/odex30_base/odex_sidebar_backend_theme/static/src/js/menu_item.js @@ -4,23 +4,7 @@ import { Component, xml } from "@odoo/owl"; import { useService } from "@web/core/utils/hooks"; export class MenuItem extends Component { - static template = xml` -
  • - - - -
  • ` + static template = "odex_sidebar_backend_theme.MenuItem" static components = { MenuItem }; diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js b/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js index 9ab2595..358c0ab 100644 --- a/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js +++ b/odex30_base/odex_sidebar_backend_theme/static/src/js/sidebar_menu.js @@ -6,18 +6,7 @@ import { useService } from "@web/core/utils/hooks"; import { MenuItem } from "./menu_item"; export class SidebarMenu extends Component { - static template = xml` -
    -
    - -
    - -
    ` - ; + static template = "odex_sidebar_backend_theme.SidebarMenu" static components = { MenuItem }; diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss b/odex30_base/odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss index 1917c4e..0a6c2d3 100644 --- a/odex30_base/odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss +++ b/odex30_base/odex_sidebar_backend_theme/static/src/scss/sidebar_menu.scss @@ -42,7 +42,7 @@ color: #fff; transition: all 0.3s ease-in-out; } -/* تعديل عرض الشريط العلوي والمحتوى الرئيسي */ +/* تعديل الشريط العلوي والمحتوى الرئيسي */ .o_navbar, .o_action_manager { transition: all 0.3s ease-in-out; diff --git a/odex30_base/odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml b/odex30_base/odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml index b408df6..1945f6d 100644 --- a/odex30_base/odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml +++ b/odex30_base/odex_sidebar_backend_theme/static/src/xml/menu_item_template.xml @@ -2,7 +2,7 @@ -
  • +