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