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..7c68785
--- /dev/null
+++ b/odex30_base/odex_sidebar_backend_theme2/ __init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
\ No newline at end of file
diff --git a/odex30_base/odex_sidebar_backend_theme2/__manifest__.py b/odex30_base/odex_sidebar_backend_theme2/__manifest__.py
new file mode 100644
index 0000000..ba61d13
--- /dev/null
+++ b/odex30_base/odex_sidebar_backend_theme2/__manifest__.py
@@ -0,0 +1,32 @@
+# -*- 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',
+ '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_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/menu_item.js',
+
+ 'odex_sidebar_backend_theme2/static/src/xml/navbar_patch.xml',
+ 'odex_sidebar_backend_theme2/static/src/js/navbar_patch.js',
+ ],
+ },
+ 'installable': True,
+ 'application': False,
+ 'auto_install': False,
+ 'license': 'LGPL-3',
+}
diff --git a/odex30_base/odex_sidebar_backend_theme2/static/src/img/logo1.png b/odex30_base/odex_sidebar_backend_theme2/static/src/img/logo1.png
new file mode 100644
index 0000000..cd74056
Binary files /dev/null and b/odex30_base/odex_sidebar_backend_theme2/static/src/img/logo1.png differ
diff --git a/odex30_base/odex_sidebar_backend_theme2/static/src/js/menu_item.js b/odex30_base/odex_sidebar_backend_theme2/static/src/js/menu_item.js
new file mode 100644
index 0000000..017214d
--- /dev/null
+++ b/odex30_base/odex_sidebar_backend_theme2/static/src/js/menu_item.js
@@ -0,0 +1,49 @@
+/** @odoo-module **/
+
+import { Component } from "@odoo/owl";
+import { useService } from "@web/core/utils/hooks";
+
+export class MenuItem extends Component {
+ static template = "odex_sidebar_backend_theme2.MenuItem"
+
+ static components = { MenuItem };
+
+ // 2. Define props received by the component
+ static props = {
+ menu: { type: Object },
+ siblings: { type: Array },
+ isCollapsed: { type: Boolean, optional: true },
+ };
+
+ setup() {
+ this.menuService = useService("menu");
+ }
+
+ // 3. Modify toggleMenu function to handle siblings
+ toggleMenu(menu, siblings) {
+ const wasOpen = menu.isOpen; // Save old state
+
+ // Step A: Close all sibling menus
+ for (const sibling of siblings) {
+ sibling.isOpen = false;
+ }
+
+ // Step B: Open current menu only if it was originally closed
+ // This prevents immediate reopening and allows closing behavior when clicking again
+ if (!wasOpen) {
+ menu.isOpen = true;
+ }
+ }
+
+ selectMenu(menu) {
+ this.menuService.selectMenu(menu);
+ try {
+ // Use xmlid if available, otherwise use name as a stable identifier
+ 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_theme2/static/src/js/navbar_patch.js b/odex30_base/odex_sidebar_backend_theme2/static/src/js/navbar_patch.js
new file mode 100644
index 0000000..b9fef6a
--- /dev/null
+++ b/odex30_base/odex_sidebar_backend_theme2/static/src/js/navbar_patch.js
@@ -0,0 +1,25 @@
+/** @odoo-module **/
+
+// **Re-import NavBar**
+import { NavBar } from "@web/webclient/navbar/navbar";
+import { patch } from "@web/core/utils/patch";
+import { useService } from "@web/core/utils/hooks";
+
+
+// **Patch NavBar component**
+patch(NavBar.prototype, {
+ setup() {
+ super.setup(...arguments); // Very important
+ // Initialize bus service
+ this.busService = useService("bus_service");
+ },
+
+ // 3. Add function to trigger event
+ toggleSidebar() {
+ this.busService.trigger("toggle-sidebar");
+ },
+});
+
+
+
+
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
new file mode 100644
index 0000000..4e785b1
--- /dev/null
+++ b/odex30_base/odex_sidebar_backend_theme2/static/src/js/sidebar_menu.js
@@ -0,0 +1,152 @@
+/** @odoo-module **/
+
+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";
+
+export class SidebarMenu extends Component {
+ static template = "odex_sidebar_backend_theme2.SidebarMenu"
+
+ static components = { MenuItem };
+
+ setup() {
+ this.menuService = useService("menu");
+ this.busService = useService("bus_service");
+ this.actionService = useService("action");
+
+ this.state = useState({
+ menus: [],
+ isOpen: true,
+ isCollapsed: false
+ });
+
+ // =================== JavaScript Control Starts Here ===================
+
+ const applyLayoutChanges = (isOpen) => {
+ const actionManager = document.querySelector('.o_action_manager');
+ const mainNavbar = document.querySelector('.o_navbar');
+
+ // Determine sidebar width based on collapse state
+ const collapsedWidth = "90px";
+
+ if (isOpen) {
+ if (actionManager) actionManager.style.marginInlineStart = collapsedWidth;
+ if (mainNavbar) mainNavbar.style.marginInlineStart = collapsedWidth;
+ } else {
+ // If sidebar is hidden, remove margin
+ if (actionManager) actionManager.style.marginInlineStart = '0';
+ if (mainNavbar) mainNavbar.style.marginInlineStart = '0';
+ }
+ };
+
+ // Use useEffect to monitor changes in open and collapse states
+ useEffect(
+ () => {
+ // This code runs every time isOpen or isCollapsed changes
+ applyLayoutChanges(this.state.isOpen, this.state.isCollapsed);
+ },
+ () => [this.state.isOpen, this.state.isCollapsed] // <-- Monitor both values
+ );
+
+ // ====================================================================
+
+ const toggleSidebar = () => {
+ this.state.isOpen = !this.state.isOpen;
+ };
+
+ const toggleCollapse = () => {
+ this.state.isCollapsed = !this.state.isCollapsed;
+ try {
+ localStorage.setItem('odex_sidebar_collapsed', this.state.isCollapsed);
+ } catch (e) {
+ console.error('Storage error:', e);
+ }
+ };
+
+ const handleLogout = async () => {
+ await this.actionService.doAction({
+ type: 'ir.actions.act_url',
+ url: '/web/session/logout',
+ target: 'self',
+ });
+ };
+
+ this.toggleSidebar = toggleSidebar;
+ this.toggleCollapse = toggleCollapse;
+ this.handleLogout = handleLogout;
+
+ onMounted(() => {
+ this.loadMenus();
+ this.busService.addEventListener("toggle-sidebar", this.toggleSidebar);
+
+ try {
+ const isCollapsed = localStorage.getItem('odex_sidebar_collapsed') === 'true';
+ this.state.isCollapsed = isCollapsed;
+ } catch (e) {
+ console.error('Storage error:', e);
+ }
+
+ // Call function on initial load to ensure correct state application
+ applyLayoutChanges(this.state.isOpen, this.state.isCollapsed);
+ });
+ }
+
+ loadMenus() {
+ const allMenus = this.menuService.getAll();
+ const clonedMenus = structuredClone(allMenus);
+
+ 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 => {
+ parentMap.set(childId, menu.id);
+ return 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;
+ }
+
+ try {
+ const activeId = localStorage.getItem('odex_sidebar_active_menu');
+ console.log('Restored menu ID:', activeId);
+
+ 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);
+ }
+}
+
+registry.category("main_components").add("sidebar_menu_component", {
+ Component: SidebarMenu,
+});
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
new file mode 100644
index 0000000..365569d
--- /dev/null
+++ b/odex30_base/odex_sidebar_backend_theme2/static/src/scss/sidebar_menu.scss
@@ -0,0 +1,494 @@
+/* ===================================
+ 1. Main Sidebar Design
+ =================================== */
+.custom_sidebar {
+ display: flex;
+ flex-direction: column;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 270px;
+ height: 100vh;
+ overflow: auto;
+ background-color: #151a2d;
+ z-index: 999;
+ transform: translateX(0%);
+ transition: all 0.4s ease-in-out;
+
+ &:not(.is-open) {
+ transform: translateX(-100%) !important;
+ /* Use !important to force hiding */
+ }
+
+ /* =============== Add New Flyout =============== */
+
+ &.is-collapsed {
+ overflow: visible;
+
+ li {
+ position: relative;
+ z-index: 1000;
+ }
+
+ .flyout-panel {
+ display: none;
+ position: absolute;
+ left: 100%;
+ top: 0;
+ min-width: 220px;
+ background: #151a2d;
+ color: #fff;
+ border-radius: 10px;
+ padding: 10px;
+ box-shadow: 0 12px 30px rgba(8, 10, 20, 0.6);
+ transform-origin: left top;
+ transition: opacity 180ms ease, transform 180ms ease;
+ opacity: 0;
+ transform: translateX(-6px) scale(0.98);
+ pointer-events: none;
+ }
+
+ li:hover > .flyout-panel {
+ display: block;
+ opacity: 1;
+ transform: translateX(0) scale(1);
+ pointer-events: auto;
+ }
+
+ .flyout-list {
+ list-style: none;
+ padding: 6px 4px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+
+ .flyout-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 10px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: background 150ms ease;
+ white-space: nowrap;
+ }
+
+ .flyout-item:hover {
+ background: rgba(255, 255, 255, 0.05);
+ }
+
+ .flyout-item .icon {
+ width: 28px;
+ height: 28px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .flyout-item .label {
+ color: #fff;
+ font-size: 0.95rem;
+ }
+
+ /* Submenu flyout */
+ .flyout-item.has-children {
+ position: relative;
+ }
+
+ .flyout-subpanel {
+ display: none;
+ position: absolute;
+ left: 100%;
+ top: 0;
+ min-width: 200px;
+ background: #151a2d;
+ border-radius: 10px;
+ padding: 10px;
+ box-shadow: 0 12px 30px rgba(8, 10, 20, 0.6);
+ opacity: 0;
+ transform: translateX(-6px) scale(0.98);
+ transition: opacity 180ms ease, transform 180ms ease;
+ pointer-events: none;
+ z-index: 1200;
+ }
+
+ .flyout-item.has-children:hover > .flyout-subpanel {
+ display: block;
+ opacity: 1;
+ transform: translateX(0) scale(1);
+ pointer-events: auto;
+ }
+ }
+
+ &.is-collapsed {
+ width: 90px;
+ transform: translateX(0);
+
+ .sidebar-header {
+ padding: 25px 10px;
+ justify-content: center;
+ flex-direction: column;
+
+ .header-logo {
+ img {
+ width: 46px;
+ height: 46px;
+ }
+ }
+
+ .sidebar-toggler {
+ position: static;
+ width: 50%;
+ margin-top: 25px;
+ }
+ }
+
+ .sidebar-nav {
+ .sidebar_menu_list {
+ padding: 0 8px;
+
+ &.primary-nav {
+
+ .has-children,
+ li:not(.has-children) {
+ position: relative;
+
+ .menu-item-container {
+ padding: 11px 8px;
+ justify-content: center;
+
+ .menu-item-icon {
+ margin: 0;
+ width: 30px;
+ height: 30px;
+ }
+
+ .menu-item-link {
+ display: none;
+ }
+
+ .toggle-icon {
+ display: none;
+ }
+
+ &:hover {
+ background-color: #eef2ff;
+ color: #151a2d;
+ }
+
+ &:active {
+ background-color: #d9e1fd;
+ transform: scale(0.95);
+ }
+ }
+ }
+
+ .submenu_list {
+ display: none;
+ }
+ }
+
+ &.secondary-nav {
+ width: auto;
+ bottom: 20px;
+ left: 0;
+ right: 0;
+ padding: 0 8px;
+
+ .nav-item {
+ position: relative;
+ z-index: 1000;
+
+ .nav-link {
+ padding: 11px 8px;
+ justify-content: center;
+
+ .nav-label {
+ display: none;
+ }
+
+ .nav_icon {
+ margin: 0;
+ }
+ }
+
+ .dropdown_menu {
+ display: none;
+ }
+
+ // Flyout support for secondary-nav
+ &:hover > .flyout-panel {
+ display: block;
+ opacity: 1;
+ transform: translateX(0) scale(1);
+ pointer-events: auto;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ li.open {
+ > .menu-item-container {
+ background-color: #fff;
+
+ .menu-item-link {
+ color: #151a2d !important;
+ }
+
+ .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");
+ }
+ }
+ }
+
+ &::-webkit-scrollbar {
+ width: 8px;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: #2f3542;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: #151a2d;
+ border-radius: 4px;
+ }
+
+ &::-webkit-scrollbar-thumb:hover {
+ background-color: #151a2d;
+ }
+
+ .sidebar-header {
+ display: flex;
+ position: relative;
+ padding: 25px 20px;
+ align-items: center;
+ justify-content: space-between;
+
+ .header-logo {
+ img {
+ width: 46px;
+ height: 46px;
+ display: block;
+ object-fit: contain;
+ border-radius: 50%;
+ }
+ }
+
+ .sidebar-toggler {
+ position: absolute;
+ right: 20px;
+ height: 35px;
+ width: 35px;
+ color: #151a2d;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ background: #eef2ff;
+ align-items: center;
+ justify-content: center;
+ border-radius: 8px;
+ transition: 0.4s ease;
+
+ &:hover {
+ background: #d9e1fd;
+ }
+
+ span {
+ transition: 0.4s ease;
+ }
+
+ i {
+ font-size: 18px;
+ transition: transform 0.4s ease;
+ }
+ }
+ }
+
+ .sidebar-nav {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .sidebar_menu_list {
+ list-style: none;
+ display: flex;
+ gap: 4px;
+ padding: 0 15px;
+ flex-direction: column;
+ transform: translateY(15px);
+ transition: 0.4s ease;
+
+ &.primary-nav {
+ .has-children,
+ li:not(.has-children) {
+ &:hover {
+ > .menu-item-container {
+ background-color: #eef2ff;
+
+ .menu-item-link {
+ color: #151a2d;
+ }
+ }
+ }
+
+ .menu-item-container {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+ transition: all 0.3s;
+ border: 1px solid #151a2d;
+ text-decoration: none;
+ padding: 11px 15px;
+ border-radius: 8px;
+ white-space: nowrap;
+
+ // image icon style
+ .menu-item-icon {
+ width: 25px;
+ height: 25px;
+ margin: 0 10px;
+ object-fit: contain;
+ }
+
+ // name style
+ .menu-item-link {
+ color: #fff;
+ flex-grow: 1;
+ font-size: 1rem;
+ text-decoration: none;
+ transition: opacity 0.3s ease;
+ }
+
+ // toggle icon style
+ .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;
+ }
+ }
+ }
+
+ .submenu_list {
+ list-style: none;
+ padding: 5px 0;
+
+ li {
+ .menu-item-container {
+ margin-left: 8px;
+ border: 1px solid transparent;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background-color: #eef2ff;
+ border-color: #151a2d;
+
+ .menu-item-link {
+ color: #151a2d;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ &.secondary-nav {
+ margin-top: auto;
+ // width: calc(100% - 30px);
+ background: #151a2d;
+ padding: 20px 15px;
+
+ .nav-item {
+ position: relative;
+ // margin: 0 15px;
+
+ &:hover > .nav-link {
+ color: #151a2d !important;
+ background-color: #eef2ff;
+ }
+
+ .nav-link {
+ color: #fff !important;
+ display: flex;
+ gap: 12px;
+ white-space: nowrap;
+ border-radius: 8px;
+ padding: 11px 15px;
+ align-items: center;
+ text-decoration: none;
+ border: 1px solid #151a2d;
+ transition: 0.4s ease;
+
+ .nav_icon {
+ font-size: 24px;
+ margin: 0 10px;
+ }
+
+ .nav-label {
+ font-size: 1rem;
+ transition: opacity 0.3s ease;
+ }
+ }
+
+ .dropdown_menu {
+ height: 0;
+ overflow-y: hidden;
+ list-style: none;
+ padding-left: 15px;
+ transition: height 0.4s ease;
+ }
+ }
+ }
+ }
+ }
+}
+
+/* ===================================
+ 2. Hide Top Bar and Adjust Main Content
+ =================================== */
+// Hide default application icon from top bar
+.o_navbar .o_main_navbar .o_navbar_apps_menu .o-dropdown {
+ 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;
+}
+
+/* Adjust Top Bar and Main Content */
+.o_navbar,
+.o_action_manager {
+ transition: all 0.3s ease-in-out;
+}
+
+/* Open Icon Design (New) */
+.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_theme2/static/src/xml/menu_item_template.xml b/odex30_base/odex_sidebar_backend_theme2/static/src/xml/menu_item_template.xml
new file mode 100644
index 0000000..f993b67
--- /dev/null
+++ b/odex30_base/odex_sidebar_backend_theme2/static/src/xml/menu_item_template.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/odex30_base/odex_sidebar_backend_theme2/static/src/xml/navbar_patch.xml b/odex30_base/odex_sidebar_backend_theme2/static/src/xml/navbar_patch.xml
new file mode 100644
index 0000000..055aeb6
--- /dev/null
+++ b/odex30_base/odex_sidebar_backend_theme2/static/src/xml/navbar_patch.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 0000000..18ec6b1
--- /dev/null
+++ b/odex30_base/odex_sidebar_backend_theme2/static/src/xml/sidebar_menu_template.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+