'use client'; import Image from 'next/image'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { usePathname } from 'next/navigation'; import { useTranslate } from '@/components/hooks/useTranslate'; import { useLanguage } from '@/components/hooks/useLanguage'; import { BiHomeSmile, BiBookOpen, BiBookContent, BiChat, BiListOl, BiNotepad, BiClipboard, BiDetail, BiTask, BiShield, BiCalendar, BiImages, // BiBell, BiSpreadsheet, BiX, BiGlobe, BiChevronDown, BiBus, BiGroup, BiBell, BiInfoCircle, BiSupport, } from 'react-icons/bi'; import { LuNotebook } from 'react-icons/lu'; import { useGetSchoolSettings } from '@/lib/api/student/queryHooks'; interface StudentSidebarProps { collapsed: boolean; isMobile?: boolean; isOpen?: boolean; onClose?: () => void; } export default function StudentSidebar({ collapsed, isMobile = false, isOpen = false, onClose, }: StudentSidebarProps) { // Get current pathname for route-based active item detection const pathname = usePathname(); const translate = useTranslate(); // Get language state and functions from Redux const { changeLanguage, currentLanguageName, languages } = useLanguage(); // Fetch the dynamic school logo once; reuse everywhere so desktop and mobile match backend branding const { data: schoolSettings, isLoading: isLoadingLogo } = useGetSchoolSettings(); const fallbackLogo = '/assets/images/common/logo.png'; const dynamicLogo = schoolSettings?.data?.settings?.horizontal_logo; const resolvedLogo = typeof dynamicLogo === 'string' && dynamicLogo.trim().length > 0 ? dynamicLogo.trim() : fallbackLogo; // Language selector state for mobile const [selectedLanguage, setSelectedLanguage] = useState(currentLanguageName); const [isLanguageDropdownOpen, setIsLanguageDropdownOpen] = useState(false); // Initialize selected language from Redux store useEffect(() => { setSelectedLanguage(currentLanguageName); }, [currentLanguageName]); // Get enabled features from school settings // Features object from API: { "1": "Student Management", "2": "Academics Management", ... } const enabledFeatures = schoolSettings?.data?.features || {}; // Helper function to check if a feature is enabled by its name const isFeatureEnabled = (featureName: string) => { // If features list is empty or undefined, assume no features are enabled if (!enabledFeatures || Object.keys(enabledFeatures).length === 0) { return false; } // Check if the feature name exists in the values of enabledFeatures object return Object.values(enabledFeatures).some( (feature) => typeof feature === 'string' && feature.toLowerCase() === featureName.toLowerCase(), ); }; // All navigation items with their associated feature requirements // featureRequired: name of the feature from API that must be enabled to show this item // If featureRequired is null, the item is always shown const allNavigationItems = [ { id: 'home', label: translate('home'), icon: BiHomeSmile, route: '/student/dashboard', featureRequired: null, // Always show home/dashboard }, { id: 'subjects', label: translate('mySubjects'), icon: BiBookOpen, route: '/student/subjects', featureRequired: 'Academics Management', // Feature ID: 2 }, { id: 'assignments', label: translate('assignments'), icon: BiBookContent, route: '/student/assignments', featureRequired: 'Assignment Management', // Feature ID: 11 }, { id: 'chats', label: translate('chats'), icon: BiChat, route: '/student/chats', featureRequired: 'Chat Module', // Feature ID: 20 }, { id: 'timetable', label: translate('timetable'), icon: BiListOl, route: '/student/timetable', featureRequired: 'Timetable Management', // Feature ID: 7 }, { id: 'noticeboard', label: translate('noticeboard'), icon: BiNotepad, route: '/student/noticeboard', featureRequired: 'Announcement Management', // Feature ID: 12 }, { id: 'exams', label: translate('exams'), icon: BiClipboard, route: '/student/exams', featureRequired: 'Exam Management', // Feature ID: 9 }, { id: 'result', label: translate('result'), icon: BiDetail, route: '/student/result', featureRequired: 'Exam Management', // Feature ID: 9 (results are part of exam management) }, { id: 'report', label: translate('report'), icon: BiTask, route: '/student/report', featureRequired: ['Exam Management', 'Assignment Management'], // Show if either is enabled }, { id: 'diary', label: translate('myDiary'), icon: LuNotebook, route: '/student/diary', featureRequired: 'Student Management', // Feature ID: 1 (diary is related to student management) }, { id: 'transportation', label: translate('transportation'), icon: BiBus, route: '/student/transportation', featureRequired: 'Transportation Module', // Feature ID: 21 }, { id: 'teachers', label: translate('teachers'), icon: BiGroup, route: '/student/teachers', featureRequired: 'Teacher Management', // Feature ID: 4 }, { id: 'holiday', label: translate('holiday'), icon: BiCalendar, route: '/student/holiday', featureRequired: 'Holiday Management', // Feature ID: 6 }, { id: 'gallery', label: translate('gallery'), icon: BiImages, route: '/student/gallery', featureRequired: 'School Gallery Management', // Feature ID: 17 }, { id: 'guardian', label: translate('guardianDetails'), icon: BiBookContent, route: '/student/guardian', featureRequired: null, // Always show guardian details }, { id: 'notifications', label: translate('notifications'), icon: BiBell, route: '/student/notifications', featureRequired: 'Announcement Management', // Feature ID: 12 }, { id: 'privacy', label: translate('privacyPolicy'), icon: BiShield, route: '/student/privacy', featureRequired: null, // Always show privacy policy }, { id: 'terms', label: translate('termsAndCondition'), icon: BiSpreadsheet, route: '/student/terms', featureRequired: null, // Always show terms and conditions }, { id: 'about-us', label: translate('aboutUs'), icon: BiInfoCircle, route: '/student/about-us', featureRequired: null, // Always show about us }, { id: 'contact-us', label: translate('contactUs'), icon: BiSupport, route: '/student/contact-us', featureRequired: null, // Always show contact us }, ]; // Filter navigation items based on enabled features // Only show items where the required feature is enabled or no feature is required const navigationItems = allNavigationItems.filter((item) => { // If no feature is required, always show the item if (!item.featureRequired) { return true; } // Check if the required feature is enabled // Support validation for multiple features (OR condition) if (Array.isArray(item.featureRequired)) { return item.featureRequired.some((feature) => isFeatureEnabled(feature)); } // Single feature check return isFeatureEnabled(item.featureRequired); }); // Function to determine active item based on current route const getActiveItem = () => { // Check for exact route matches first const exactMatch = navigationItems.find((item) => item.route === pathname); if (exactMatch) { return exactMatch.id; } // Check for partial matches (e.g., /student/dashboard/profile should match dashboard) const partialMatch = navigationItems.find( (item) => pathname.startsWith(item.route) && item.route !== '/student', ); if (partialMatch) { return partialMatch.id; } // Default to home if no match found return 'home'; }; // Get the current active item based on route const activeItem = getActiveItem(); // Handle mobile drawer close on item click const handleItemClick = (itemId: string) => { // Find the route for the clicked item and navigate const item = navigationItems.find((navItem) => navItem.id === itemId); if (item) { // Navigation will be handled by Next.js Link components // Just close the mobile drawer if needed if (isMobile && onClose) { onClose(); } } }; // Handle language selection - update Redux store and close dropdown const handleLanguageSelect = (languageName: string) => { // Find the language code from the language name const selectedLang = languages.find((lang) => lang.name === languageName); if (selectedLang) { // Update language in Redux store changeLanguage(selectedLang.code); setSelectedLanguage(selectedLang.name); } setIsLanguageDropdownOpen(false); // Close mobile sidebar after language selection (like other navigation items) if (isMobile && onClose) { onClose(); } }; // Handle escape key to close mobile drawer useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape' && isMobile && isOpen && onClose) { onClose(); } }; if (isMobile && isOpen) { document.addEventListener('keydown', handleEscape); // Prevent body scroll when drawer is open document.body.style.overflow = 'hidden'; } return () => { document.removeEventListener('keydown', handleEscape); document.body.style.overflow = 'unset'; }; }, [isMobile, isOpen, onClose]); // Mobile drawer overlay if (isMobile) { return ( <> {/* Overlay */} {isOpen && ( <div className="fixed inset-0 bg-(--light-primary-color) bg-opacity-50 z-40 lg:hidden" onClick={onClose} /> )} {/* Mobile Drawer - Responsive width for different mobile sizes */} {/* Using explicit LTR/RTL variants for proper positioning */} <div className={`mobile-drawer fixed top-0 ltr:left-0 rtl:right-0 h-screen w-72 sm:w-80 bg-white z-50 transform transition-transform duration-300 ease-in-out lg:hidden ${ isOpen ? 'translate-x-0' : 'ltr:-translate-x-full rtl:translate-x-full' }`} onClick={(e) => e.stopPropagation()} // Prevent clicks inside drawer from bubbling to overlay > <div className="flex flex-col h-full p-4 sm:p-6 ltr:border-r rtl:border-l border-gray-200"> {/* Mobile Header with Close Button - Responsive sizing */} <div className="shrink-0 flex items-center justify-between mb-6 sm:mb-8"> <div className="flex items-center gap-2 w-[120px] h-[42px] sm:w-[140px] sm:h-[50px]"> <Image src={resolvedLogo} alt="Logo" width={0} height={0} className="w-full h-full object-contain" priority /> </div> <button onClick={onClose} className="p-1.5 sm:p-2 hover:bg-gray-100 rounded-lg transition-colors" > <BiX className="w-5 h-5 sm:w-6 sm:h-6 text-gray-600" /> </button> </div> {/* Navigation Menu - Responsive sizing */} <nav className="flex-1 min-h-0 overflow-y-auto space-y-1 scrollbar-thin scrollbar-track-gray-100 scrollbar-thumb-gray-400 hover:scrollbar-thumb-gray-500 mb-20"> {navigationItems.map((item) => { const Icon = item.icon; return ( <Link key={item.id} href={item.route} onClick={() => handleItemClick(item.id)} className={`w-full flex items-center text-base font-normal cursor-pointer gap-2 sm:gap-3 px-3 sm:px-4 py-2.5 sm:py-3 rounded-lg transition-colors text-left ${ activeItem === item.id ? 'bg-(--primary-color) text-white' : 'text-black hover:bg-(--primary-color) hover:text-white' }`} > <Icon className="w-4 h-4 sm:w-5 sm:h-5 shrink-0" /> <span className="text-xs sm:text-sm font-medium"> {item.label} </span> </Link> ); })} {/* Language Selector - Mobile Only - Responsive sizing */} <div className="relative"> {/* Language Dropdown Container */} <div className="relative"> <button onClick={(e) => { e.stopPropagation(); // Prevent event bubbling to avoid closing sidebar setIsLanguageDropdownOpen(!isLanguageDropdownOpen); }} className="w-full flex items-center justify-between px-3 sm:px-4 py-2.5 sm:py-3 text-left" > <div className="flex items-center gap-2 sm:gap-3"> <BiGlobe className="w-4 h-4 sm:w-5 sm:h-5 text-gray-600" /> <span className="text-xs sm:text-sm font-medium text-gray-900"> {selectedLanguage} </span> </div> <BiChevronDown className={`w-3.5 h-3.5 sm:w-4 sm:h-4 text-gray-500 transition-transform ${ isLanguageDropdownOpen ? 'rotate-180' : '' }`} /> </button> {/* Language Dropdown Options - Responsive sizing */} {isLanguageDropdownOpen && ( <div className="absolute bottom-full left-0 right-0 mb-1 bg-white border border-gray-300 rounded-lg shadow-lg z-10 max-h-48 overflow-y-auto"> {languages.map((language) => ( <button key={language.code} onClick={(e) => { e.stopPropagation(); // Prevent event bubbling to avoid closing sidebar handleLanguageSelect(language.name); }} className={`w-full flex items-center gap-2 sm:gap-3 px-3 sm:px-4 py-2.5 sm:py-3 text-left hover:bg-gray-50 transition-colors first:rounded-t-lg last:rounded-b-lg ${ selectedLanguage === language.name ? 'bg-gray-100' : '' }`} > <span className="text-base sm:text-lg"> {language.flag} </span> <span className="text-xs sm:text-sm font-medium text-gray-900"> {language.name} </span> </button> ))} </div> )} </div> </div> </nav> </div> </div> </> ); } // Desktop sidebar - Responsive width for tablets and desktop // Using explicit LTR/RTL variants for proper positioning return ( <div className={`${ collapsed ? 'w-14 md:w-16' : 'w-64 md:w-76' } bg-white text-black h-screen fixed ltr:left-0 rtl:right-0 top-0 z-50 transition-all duration-300 ltr:border-r rtl:border-l border-[#EAEAEA] hidden md:block`} > <div className=""> {/* Logo Section - Responsive sizing for tablet and desktop */} {/* Using gap instead of space-x for RTL support */} <div className={`flex items-center ${ collapsed ? 'justify-center' : 'gap-2 px-4 md:px-6 pt-4 md:pt-6' } mb-[10px]`} > {!collapsed && ( <span className="h-[50px] w-[150px] md:h-[60px] md:w-[180px] flex items-center justify-center overflow-hidden"> {isLoadingLogo ? ( // Loading skeleton for logo - matches logo dimensions with pulse animation <div className="w-full h-full bg-gray-200 rounded animate-pulse" /> ) : ( <Link href="/student/dashboard"> <Image src={resolvedLogo} alt="Logo" width={0} height={0} className="w-full h-full object-contain aspect-auto" priority /> </Link> )} </span> )} </div> {!collapsed && <div className="h-px w-full bg-gray-200 mb-3"></div>} {/* Navigation Menu - Responsive sizing for tablet and desktop */} <nav className={`space-y-2 overflow-y-auto scrollbar-thin scrollbar-track-gray-100 scrollbar-thumb-gray-400 ${ collapsed ? 'pb-0 h-[calc(100vh-100px)] px-1.5 md:px-2' : 'pb-4 md:pb-6 h-[calc(100vh-150px)] px-4 md:px-6' }`} > {navigationItems.map((item) => { const Icon = item.icon; return ( <Link key={item.id} href={item.route} className={`w-full flex items-center text-base font-normal cursor-pointer ${ collapsed ? 'justify-center px-1.5 md:px-2' : 'gap-2 md:gap-3 px-3 md:px-4' } py-2.5 md:py-3 rounded-lg transition-colors text-left ${ activeItem === item.id ? 'bg-(--primary-color) text-white' : 'text-black hover:bg-(--primary-color) hover:text-white' }`} title={collapsed ? item.label : undefined} > <Icon className="w-4 h-4 md:w-5 md:h-5 shrink-0" /> {!collapsed && ( <span className="text-xs md:text-sm font-medium"> {item.label} </span> )} </Link> ); })} </nav> </div> </div> ); }