diff --git a/frontend/src/app/providers/layout/layout.tsx b/frontend/src/app/providers/layout/layout.tsx index 8a54aea..e24408e 100644 --- a/frontend/src/app/providers/layout/layout.tsx +++ b/frontend/src/app/providers/layout/layout.tsx @@ -1,6 +1,9 @@ import { useState, useEffect, type ReactNode } from "react"; import { Sidebar } from "@/app/providers/layout/sidebar/sidebar"; -import { Navigation } from "@/app/providers/layout/navigation/navigation"; +import { + Navigation, + BottomNav, +} from "@/app/providers/layout/navigation/navigation"; import { useAgentStore } from "@/app/providers/layout/store/agent.store"; export const Layout = ({ children }: { children: ReactNode }) => { @@ -8,6 +11,9 @@ export const Layout = ({ children }: { children: ReactNode }) => { const [isMobile, setIsMobile] = useState(() => typeof window !== "undefined" ? window.innerWidth < 856 : false, ); + const [isVerySmall, setIsVerySmall] = useState(() => + typeof window !== "undefined" ? window.innerWidth < 600 : false, + ); const { fetchAgents } = useAgentStore(); const sidebarOpen = isMobile ? mobileOpen : true; @@ -19,6 +25,7 @@ export const Layout = ({ children }: { children: ReactNode }) => { if (!mobile) { setMobileOpen(false); } + setIsVerySmall(window.innerWidth < 600); }; window.addEventListener("resize", handleResize); @@ -55,8 +62,13 @@ export const Layout = ({ children }: { children: ReactNode }) => { isMobile={isMobile} />
- +
{children}
+ {isVerySmall && }
); diff --git a/frontend/src/app/providers/layout/navigation/navigation.tsx b/frontend/src/app/providers/layout/navigation/navigation.tsx index 289c62c..a5deef1 100644 --- a/frontend/src/app/providers/layout/navigation/navigation.tsx +++ b/frontend/src/app/providers/layout/navigation/navigation.tsx @@ -24,11 +24,13 @@ import { interface NavigationProps { onToggleSidebar?: () => void; isMobile?: boolean; + isVerySmall?: boolean; } export const Navigation: React.FC = ({ onToggleSidebar, isMobile, + isVerySmall = false, }) => { const navigate = useNavigate(); const location = useLocation(); @@ -48,7 +50,6 @@ export const Navigation: React.FC = ({ const isActive = (path: string) => location.pathname === path; - // Закрытие дропдауна при клике вне useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if ( @@ -74,281 +75,349 @@ export const Navigation: React.FC = ({ setThemePickerOpen(false); }; + const renderNavItems = (showLabels: boolean, iconSize: number) => ( +
+ {navItems + .filter((item) => { + if ((item as any).adminOnly && !user?.permission_admin) return false; + return true; + }) + .map((item) => { + const Icon = item.icon; + const active = isActive(item.path); + return ( + + ); + })} +
+ ); + + return ( + <> + {/* Верхний бар */} +
+
+ {/* Бургер — только на мобильных */} + {isMobile && ( + + )} + + {/* Название по центру — только на очень маленьких экранах */} + {isVerySmall && ( +
+ + HellreigN + +
+ )} + + {/* Навигация — только если НЕ очень маленький экран */} + {!isVerySmall && ( +
+ {renderNavItems(true, 12)} +
+ )} + + {/* Профиль пользователя — дропдаун */} +
+ + + {dropdownOpen && ( +
+
+
+
+ +
+
+

+ {user?.name || user?.login} +

+

+ {user?.login} +

+
+
+
+ +
+ + + {themePickerOpen && ( +
+ {themes.map((t) => ( + + ))} +
+ )} +
+ + {user?.permission_admin && ( + + )} + +
+ + +
+ )} +
+
+
+ + ); +}; + +export const BottomNav: React.FC = () => { + const navigate = useNavigate(); + const location = useLocation(); + const { user } = useAuthStore(); + + const navItems = [ + { path: "/templates", label: "Шаблоны", icon: FaCode }, + { path: "/add-agents", label: "Деплой", icon: FaRocket }, + { path: "/registration", label: "Регистрация", icon: FaKey }, + { path: "/logs", label: "Логи", icon: FaFileAlt }, + ]; + + const isActive = (path: string) => location.pathname === path; + return (
-
- {/* Бургер — только на мобильных */} - {isMobile && ( - - )} - - {/* Навигация */} -
-
- {navItems - .filter((item) => { - if ((item as any).adminOnly && !user?.permission_admin) - return false; - return true; - }) - .map((item) => { - const Icon = item.icon; - const active = isActive(item.path); - return ( - - ); - })} -
-
- - {/* Профиль пользователя — дропдаун */} -
- - - {/* Dropdown menu */} - {dropdownOpen && ( -
- {/* User info */} -
-
-
- -
-
-

- {user?.name || user?.login} -

-

- {user?.login} -

-
-
-
- - {/* Theme selector */} -
- - - {/* Theme sub-dropdown */} - {themePickerOpen && ( -
- {themes.map((t) => ( - - ))} -
- )} -
- - {/* Admin (if admin) */} - {user?.permission_admin && ( - - )} - - {/* Divider */} -
- - {/* Logout */} +
+ {navItems + .filter((item) => { + if ((item as any).adminOnly && !user?.permission_admin) + return false; + return true; + }) + .map((item) => { + const Icon = item.icon; + const active = isActive(item.path); + return ( -
- )} -
+ ); + })}
);