'use client'; import { useEffect, useMemo, useState, useRef, useCallback } from 'react'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import type { NavBadges } from '@/api/navBadges'; import { ChildSelectorCompact } from '@/components/navigation/ChildSelector'; import { useIsMobile } from '@/hooks/useIsMobile'; interface NavigationItem { label: string; path: string; icon: string; isProfile?: boolean; } interface User { id?: number; first_name?: string; last_name?: string; email?: string; avatar_url?: string | null; avatar?: string | null; } interface BottomNavigationBarProps { userRole?: string; user?: User | null; navBadges?: NavBadges | null; /** Слот для кнопки уведомлений (на мобильном — 4-й элемент в первом ряду). */ notificationsSlot?: React.ReactNode; /** Выдвижная панель справа (3 колонки). При клике по пункту вызывается onClose. */ slideout?: boolean; onClose?: () => void; } function getAvatarUrl(user: User | null | undefined): string | null { if (!user) return null; const url = user.avatar_url || user.avatar; if (!url) return null; if (url.startsWith('http')) return url; const base = typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.hostname}:8123` : ''; return url.startsWith('/') ? `${base}${url}` : `${base}/${url}`; } function getBadgeCount(item: NavigationItem, navBadges: NavBadges | null | undefined): number { if (!navBadges) return 0; switch (item.path) { case '/schedule': return navBadges.lessons_today; case '/chat': return navBadges.chat_unread; case '/homework': return navBadges.homework_pending; case '/feedback': return navBadges.feedback_pending; case '/students': return navBadges.mentorship_requests_pending ?? 0; default: return 0; } } export function BottomNavigationBar({ userRole, user, navBadges, notificationsSlot, slideout, onClose }: BottomNavigationBarProps) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const tabParam = searchParams?.get('tab'); const [activeIndex, setActiveIndex] = useState(0); const [expanded, setExpanded] = useState(false); const avatarUrl = getAvatarUrl(user); const isMobile = useIsMobile(); // Swipe gesture handling (secondary to "More" button) const navContainerRef = useRef(null); const touchStartY = useRef(null); const touchStartX = useRef(null); const SWIPE_THRESHOLD = 30; const handleTouchStart = useCallback((e: React.TouchEvent) => { touchStartY.current = e.touches[0].clientY; touchStartX.current = e.touches[0].clientX; }, []); const handleTouchEnd = useCallback((e: React.TouchEvent) => { if (touchStartY.current === null || touchStartX.current === null) return; const deltaY = touchStartY.current - e.changedTouches[0].clientY; const deltaX = Math.abs(touchStartX.current - e.changedTouches[0].clientX); touchStartY.current = null; touchStartX.current = null; if (Math.abs(deltaY) < SWIPE_THRESHOLD || deltaX > Math.abs(deltaY)) return; if (deltaY > 0) { setExpanded(true); } else { setExpanded(false); } }, []); // Определяем навигационные элементы в зависимости от роли const navigationItems = useMemo(() => { const baseItems: NavigationItem[] = [ { label: 'Главная', path: '/dashboard', icon: 'home' }, { label: 'Расписание', path: '/schedule', icon: 'calendar_month' }, { label: 'Чат', path: '/chat', icon: 'chat' }, ]; let roleItems: NavigationItem[] = []; if (userRole === 'mentor') { roleItems = [ { label: 'Студенты', path: '/students', icon: 'group' }, { label: 'Материалы', path: '/materials', icon: 'folder' }, { label: 'Домашние задания', path: '/homework', icon: 'assignment' }, { label: 'Обратная связь', path: '/feedback', icon: 'rate_review' }, ]; } else if (userRole === 'client') { roleItems = [ { label: 'Материалы', path: '/materials', icon: 'folder' }, { label: 'Домашние задания', path: '/homework', icon: 'assignment' }, { label: 'Прогресс', path: '/my-progress', icon: 'trending_up' }, { label: 'Мои менторы', path: '/request-mentor', icon: 'person_add' }, ]; } else if (userRole === 'parent') { // Родитель: те же страницы, что и студент, кроме материалов roleItems = [ { label: 'Домашние задания', path: '/homework', icon: 'assignment' }, { label: 'Прогресс', path: '/my-progress', icon: 'trending_up' }, ]; } const common: NavigationItem[] = [ ...baseItems, ...roleItems, { label: 'Профиль', path: '/profile', icon: 'person', isProfile: true }, ]; // Аналитика, Тарифы и Рефералы только для ментора if (userRole === 'mentor') { common.push( { label: 'Аналитика', path: '/analytics', icon: 'analytics' }, { label: 'Тарифы', path: '/payment', icon: 'credit_card' }, { label: 'Рефералы', path: '/referrals', icon: 'group_add' } ); } return common; }, [userRole]); // Mobile: first 3 items + "More" button; Desktop: first 3/5 items + notifications const MOBILE_FIRST_ROW_COUNT = 3; const desktopFirstCount = notificationsSlot ? 3 : 5; const firstRowItems = isMobile ? navigationItems.slice(0, MOBILE_FIRST_ROW_COUNT) : navigationItems.slice(0, desktopFirstCount); const restItems = isMobile ? navigationItems.slice(MOBILE_FIRST_ROW_COUNT) : navigationItems.slice(desktopFirstCount); const hasMore = restItems.length > 0; // Подсветка активного таба по текущему URL useEffect(() => { const idx = navigationItems.findIndex((item) => { if (item.path === '/payment') return pathname === '/payment'; if (item.path === '/analytics') return pathname === '/analytics'; if (item.path === '/referrals') return pathname === '/referrals'; if (item.path === '/feedback') return pathname === '/feedback'; if (item.path === '/homework') return pathname === '/homework'; if (item.path === '/profile') return pathname === '/profile' && !tabParam; if (item.path === '/request-mentor') return pathname === '/request-mentor'; return pathname?.startsWith(item.path); }); if (idx !== -1) setActiveIndex(idx); }, [pathname, navigationItems, tabParam]); const handleTabClick = (index: number) => { const item = navigationItems[index]; if (!item) return; setActiveIndex(index); setExpanded(false); router.push(item.path); onClose?.(); }; if (!navigationItems.length) return null; const renderButton = (item: NavigationItem, index: number) => { const isActive = index === activeIndex; const showAvatar = item.isProfile && (avatarUrl || user); const badgeCount = getBadgeCount(item, navBadges); return ( ); }; if (slideout) { return (
{userRole === 'parent' && (
)} {navigationItems.map((item, i) => renderButton(item, i))}
); } // "More" button for mobile const renderMoreButton = () => ( ); // Index offset for rest items const restIndexOffset = isMobile ? MOBILE_FIRST_ROW_COUNT : desktopFirstCount; return (
{/* Desktop: swipe handle + arrow trigger */} {hasMore && !isMobile && ( <>
setExpanded((e) => !e)} >
)}
{userRole === 'parent' && } {userRole === 'parent' ? (
{firstRowItems.map((item, i) => renderButton(item, i))} {isMobile && hasMore ? renderMoreButton() : notificationsSlot}
) : ( <> {firstRowItems.map((item, i) => renderButton(item, i))} {isMobile && hasMore ? renderMoreButton() : notificationsSlot} )}
{restItems.map((item, i) => renderButton(item, restIndexOffset + i))} {/* On mobile: notifications at the end of expanded section */} {isMobile && notificationsSlot && (
{notificationsSlot}
)}
); }