import React, { createContext, useContext, useState, useEffect } from 'react'; import { allPageData, SidebarItem } from './data'; import { useLanguage } from '../../providers/language/LanguageContext'; import { resolveLanguageText } from './pageInterface'; import { usePermissions } from '../../hooks/usePermissions'; import { getUserDataCache } from '../../utils/userCache'; import { FaHome, FaHatWizard } from 'react-icons/fa'; import { RiFolderSettingsFill } from 'react-icons/ri'; // Configuration for parent groups that don't have a page definition // Maps parentPath to icon and default order const parentGroupConfig: Record>; defaultOrder?: number; }> = { 'start': { icon: FaHome, defaultOrder: 1 }, 'administration': { icon: RiFolderSettingsFill, defaultOrder: 2 }, 'admin': { icon: FaHatWizard, defaultOrder: 3 } }; interface SidebarContextType { sidebarItems: SidebarItem[]; loading: boolean; error: string | null; refreshSidebar: () => Promise; } const SidebarContext = createContext(undefined); export const useSidebar = () => { const context = useContext(SidebarContext); if (!context) { throw new Error('useSidebar must be used within a SidebarProvider'); } return context; }; interface SidebarProviderProps { children: React.ReactNode; } export const SidebarProvider: React.FC = ({ children }) => { const [sidebarItems, setSidebarItems] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Get translation function from language context const { t } = useLanguage(); const { canView } = usePermissions(); // Get sidebar items from page data const getSidebarItems = async (): Promise => { const items: SidebarItem[] = []; // Get all unique parent paths from pages that have subpages const parentPaths = new Set(); allPageData.forEach(page => { if (page.parentPath && !page.hide && page.showInSidebar !== false) { parentPaths.add(page.parentPath); } }); // Create parent groups for each parentPath (even if no page exists for that path) const parentGroups = new Map>; order: number; subpages: typeof allPageData; }>(); for (const parentPath of parentPaths) { // Check if a page exists for this parent path const parentPage = allPageData.find(p => p.path === parentPath && !p.hide); // Get all subpages for this parent const subpages = allPageData.filter(p => p.parentPath === parentPath && !p.hide && p.showInSidebar !== false ); if (subpages.length > 0) { // Use parent page data if it exists, otherwise create a virtual parent // Try to resolve name from translation key (e.g., "start.title") or use capitalized path let parentName: string; if (parentPage) { parentName = resolveLanguageText(parentPage.name, t); } else { // Try to resolve as translation key first (e.g., "start.title") const translationKey = `${parentPath}.title`; const translated = t(translationKey); parentName = translated !== translationKey ? translated : parentPath.charAt(0).toUpperCase() + parentPath.slice(1); } // Get icon: use parent page icon if exists, otherwise use config, or undefined const parentIcon = parentPage?.icon || parentGroupConfig[parentPath]?.icon; // Determine order: use parent page order if exists, otherwise use config default, // then minimum order of subpages, or default to 0 let parentOrder = parentPage?.order; if (parentOrder === undefined) { parentOrder = parentGroupConfig[parentPath]?.defaultOrder; if (parentOrder === undefined) { const subpageOrders = subpages.map(s => s.order ?? 0); parentOrder = subpageOrders.length > 0 ? Math.min(...subpageOrders) : 0; } } parentGroups.set(parentPath, { id: parentPage?.id || parentPath, name: parentName, icon: parentIcon, order: parentOrder, subpages: subpages }); } } // Process parent groups for (const [_parentPath, parentGroup] of parentGroups.entries()) { // Filter subpages by RBAC access and privilegeChecker const accessibleSubpages = []; for (const subpage of parentGroup.subpages) { try { // Check RBAC access const hasSubpageRBACAccess = await canView('UI', subpage.path); if (!hasSubpageRBACAccess) { continue; } // Check client-side privilegeChecker if provided if (subpage.privilegeChecker) { try { const hasPrivilege = await subpage.privilegeChecker(); if (!hasPrivilege) { continue; } } catch (error) { console.error(`Error checking privilegeChecker for subpage ${subpage.path}:`, error); continue; } } accessibleSubpages.push(subpage); } catch (error) { console.error(`Error checking RBAC access for subpage ${subpage.path}:`, error); } } if (accessibleSubpages.length > 0) { // Create parent item with submenu (no link since it's not a real page) items.push({ id: parentGroup.id, name: parentGroup.name, link: undefined, // No link - parent is not a clickable page icon: parentGroup.icon, moduleEnabled: true, order: parentGroup.order, submenu: accessibleSubpages.map(subpage => ({ id: subpage.id, name: resolveLanguageText(subpage.name, t), link: `/${subpage.path}`, icon: subpage.icon })) }); } } // Get main pages (no parent path) const mainPages = allPageData .filter(page => !page.parentPath && !page.hide && page.showInSidebar !== false) .sort((a, b) => (a.order || 0) - (b.order || 0)); // Log user info for debugging const cachedUser = getUserDataCache(); console.log('👤 SidebarProvider: Current user info:', { username: cachedUser?.username, roleLabels: cachedUser?.roleLabels, roleLabelsLength: Array.isArray(cachedUser?.roleLabels) ? cachedUser.roleLabels.length : 0 }); // Process each main page console.log('📋 SidebarProvider: Processing pages, total:', mainPages.length, 'pages to check'); const pageAccessResults: Array<{ path: string; name: string; hasAccess: boolean }> = []; for (const pageData of mainPages) { console.log('🔍 SidebarProvider: Checking access for page:', { path: pageData.path, name: pageData.name, hasSubpages: pageData.hasSubpages }); // Check RBAC permissions try { const hasRBACAccess = await canView('UI', pageData.path); console.log('🔍 SidebarProvider: RBAC check result:', { path: pageData.path, hasAccess: hasRBACAccess }); if (!hasRBACAccess) { console.log('⛔ SidebarProvider: Page hidden due to RBAC:', pageData.path); continue; } // Check client-side privilegeChecker if provided if (pageData.privilegeChecker) { try { const hasPrivilege = await pageData.privilegeChecker(); if (!hasPrivilege) { console.log('⛔ SidebarProvider: Page hidden due to privilegeChecker:', pageData.path); continue; } } catch (error) { console.error(`❌ SidebarProvider: Error checking privilegeChecker for ${pageData.path}:`, error); continue; } } } catch (error) { console.error(`❌ SidebarProvider: Error checking RBAC access for ${pageData.path}:`, error); continue; } // Check if this page has subpages if (pageData.hasSubpages) { // Find all subpages for this parent const allSubpages = allPageData.filter(p => p.parentPath === pageData.path && !p.hide && p.showInSidebar !== false ); // Filter subpages by RBAC access const accessibleSubpages = []; console.log('📋 SidebarProvider: Checking subpages for:', { parentPath: pageData.path, totalSubpages: allSubpages.length }); for (const subpage of allSubpages) { try { console.log('🔍 SidebarProvider: Checking subpage access:', { parentPath: pageData.path, subpagePath: subpage.path, subpageName: subpage.name }); const hasSubpageRBACAccess = await canView('UI', subpage.path); console.log('🔍 SidebarProvider: Subpage RBAC result:', { subpagePath: subpage.path, hasAccess: hasSubpageRBACAccess }); if (!hasSubpageRBACAccess) { console.log('⛔ SidebarProvider: Subpage hidden due to RBAC:', subpage.path); continue; } // Check client-side privilegeChecker if provided if (subpage.privilegeChecker) { try { const hasPrivilege = await subpage.privilegeChecker(); if (!hasPrivilege) { console.log('⛔ SidebarProvider: Subpage hidden due to privilegeChecker:', subpage.path); continue; } } catch (error) { console.error(`❌ SidebarProvider: Error checking privilegeChecker for subpage ${subpage.path}:`, error); continue; } } accessibleSubpages.push(subpage); console.log('✅ SidebarProvider: Subpage added:', subpage.path); } catch (error) { console.error(`❌ SidebarProvider: Error checking RBAC access for subpage ${subpage.path}:`, error); } } console.log('📋 SidebarProvider: Subpage filtering complete:', { parentPath: pageData.path, totalSubpages: allSubpages.length, accessibleSubpages: accessibleSubpages.length, accessiblePaths: accessibleSubpages.map(s => s.path) }); if (accessibleSubpages.length > 0) { console.log('✅ SidebarProvider: Adding parent page with subpages:', { path: pageData.path, name: pageData.name, subpagesCount: accessibleSubpages.length }); // Create expandable item with submenu items.push({ id: pageData.id, name: resolveLanguageText(pageData.name, t), link: `/${pageData.path}`, icon: pageData.icon, moduleEnabled: pageData.moduleEnabled ?? true, order: pageData.order || 0, submenu: accessibleSubpages.map(subpage => ({ id: subpage.id, name: resolveLanguageText(subpage.name, t), link: `/${subpage.path}`, icon: subpage.icon })) }); } else { // No accessible subpages, show as regular item console.log('✅ SidebarProvider: Adding parent page without accessible subpages:', { path: pageData.path, name: pageData.name }); items.push({ id: pageData.id, name: resolveLanguageText(pageData.name, t), link: `/${pageData.path}`, icon: pageData.icon, moduleEnabled: pageData.moduleEnabled ?? true, order: pageData.order || 0 }); } } else { // Regular items without subpages console.log('✅ SidebarProvider: Adding regular page:', { path: pageData.path, name: pageData.name }); items.push({ id: pageData.id, name: resolveLanguageText(pageData.name, t), link: `/${pageData.path}`, icon: pageData.icon, moduleEnabled: pageData.moduleEnabled ?? true, order: pageData.order || 0 }); } } // Sort all items by order const sortedItems = items.sort((a, b) => (a.order || 0) - (b.order || 0)); // Summary of page access checks const accessiblePages = pageAccessResults.filter(r => r.hasAccess); const deniedPages = pageAccessResults.filter(r => !r.hasAccess); console.log('📊 SidebarProvider: Page access summary:', { totalPagesChecked: pageAccessResults.length, accessiblePages: accessiblePages.length, deniedPages: deniedPages.length, accessiblePagePaths: accessiblePages.map(p => p.path), deniedPagePaths: deniedPages.map(p => p.path), deniedPageDetails: deniedPages.map(p => ({ path: p.path, name: p.name })) }); console.log('📊 SidebarProvider: Final sidebar items built and sorted:', { totalItems: sortedItems.length, sortedPaths: sortedItems.map(item => item.link), items: sortedItems.map(item => ({ id: item.id, link: item.link, name: item.name, hasSubmenu: !!item.submenu, submenuCount: item.submenu?.length || 0 })) }); return sortedItems; }; // Refresh sidebar items const refreshSidebar = async () => { console.log('🔄 SidebarProvider: Refreshing sidebar items...'); setLoading(true); setError(null); try { const items = await getSidebarItems(); console.log('✅ SidebarProvider: Setting sidebar items:', { count: items.length, items: items.map(item => ({ id: item.id, link: item.link, name: item.name })) }); setSidebarItems(items); } catch (err) { console.error('❌ SidebarProvider: Error refreshing sidebar:', err); setError(err instanceof Error ? err.message : 'Failed to load sidebar items'); } finally { setLoading(false); } }; // Load sidebar items on mount and when language changes useEffect(() => { refreshSidebar(); }, [t]); const contextValue: SidebarContextType = { sidebarItems, loading, error, refreshSidebar }; return ( {children} ); }; export default SidebarProvider;