fix: adaptive #1
ci-front / build (push) Successful in 2m39s

This commit is contained in:
nikita
2026-04-05 07:41:34 +03:00
parent 17d4770de6
commit 5f6c4303db
@@ -1,5 +1,6 @@
import { useState, useRef, useEffect } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { FaCode } from "react-icons/fa";
import { FaCode, FaChevronDown } from "react-icons/fa";
import {
FaHome,
FaServer,
@@ -8,32 +9,63 @@ import {
FaRocket,
FaKey,
FaFileAlt,
FaSun,
FaMoon,
FaPalette,
FaSignOutAlt,
FaShieldAlt,
} from "react-icons/fa";
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
import { useThemeStore } from "@/modules/theme-bw/stores/theme.store";
import { themes } from "@/modules/theme-changer/config/theme.config";
import {
applyTheme,
getCurrentTheme,
} from "@/modules/theme-changer/utils/apply.theme";
export const Navigation = () => {
const navigate = useNavigate();
const location = useLocation();
const { user, logout } = useAuthStore();
const { toggleTheme, theme } = useThemeStore();
const isDark = theme === "dark";
const { setTheme } = useThemeStore();
const [dropdownOpen, setDropdownOpen] = useState(false);
const [themePickerOpen, setThemePickerOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const currentTheme = getCurrentTheme();
const navItems = [
{ path: "/", label: "Главная", icon: FaHome },
{ path: "/add-agents", label: "Агенты", icon: FaServer },
{ path: "/templates", label: "Шаблоны", icon: FaCode },
{ path: "/add-agents", label: "Деплой", icon: FaRocket },
{ path: "/registration", label: "Регистрация", icon: FaKey },
{ path: "/logs", label: "Логи", icon: FaFileAlt },
{ path: "/admin", label: "Админка", icon: FaUsers, adminOnly: true },
];
const isActive = (path: string) => location.pathname === path;
// Закрытие дропдауна при клике вне
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(e.target as Node)
) {
setDropdownOpen(false);
setThemePickerOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const handleLogout = () => {
logout();
navigate("/auth");
};
const handleThemeChange = (themeId: string) => {
applyTheme(themeId);
setTheme(themeId as any);
setThemePickerOpen(false);
};
return (
<div
className="flex-shrink-0 border-b"
@@ -43,12 +75,13 @@ export const Navigation = () => {
}}
>
<div className="flex items-center justify-between px-4 py-2.5">
{/* Навигация с горизонтальным скроллом */}
{/* Навигация */}
<div className="flex items-center flex-1 mx-4 overflow-x-auto scrollbar-hide">
<div className="flex items-center gap-1 whitespace-nowrap">
{navItems
.filter((item) => {
if (item.adminOnly && !user?.permission_admin) return false;
if ((item as any).adminOnly && !user?.permission_admin)
return false;
return true;
})
.map((item) => {
@@ -87,81 +120,210 @@ export const Navigation = () => {
</div>
</div>
{/* Профиль пользователя */}
<div className="flex items-center gap-2">
{user && (
<div className="flex items-center gap-2">
{/* Профиль пользователя — дропдаун */}
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setDropdownOpen(!dropdownOpen)}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg transition-all"
style={{
backgroundColor: dropdownOpen
? "var(--bg-secondary)"
: "transparent",
border: "1px solid var(--border)",
}}
>
<div
className="w-7 h-7 rounded-full flex items-center justify-center"
style={{ backgroundColor: "var(--accent)" }}
>
<FaUser size={11} style={{ color: "var(--accent-text)" }} />
</div>
<span
className="text-xs font-medium"
style={{ color: "var(--text-primary)" }}
>
{user?.name || user?.login || "Пользователь"}
</span>
<FaChevronDown
size={10}
style={{
color: "var(--text-secondary)",
transform: dropdownOpen ? "rotate(180deg)" : "rotate(0)",
transition: "transform 0.2s",
}}
/>
</button>
{/* Dropdown menu */}
{dropdownOpen && (
<div
className="absolute right-0 top-full mt-2 rounded-lg shadow-xl border z-50"
style={{
backgroundColor: "var(--card-bg)",
borderColor: "var(--border)",
minWidth: "220px",
}}
>
{/* User info */}
<div
className="w-8 h-8 rounded-full flex items-center justify-center"
style={{ backgroundColor: "var(--bg-secondary)" }}
className="px-4 py-3 border-b"
style={{ borderColor: "var(--border)" }}
>
<FaUser size={12} style={{ color: "var(--accent)" }} />
<div className="flex items-center gap-2 mb-1">
<div
className="w-8 h-8 rounded-full flex items-center justify-center"
style={{ backgroundColor: "var(--accent)" }}
>
<FaUser size={12} style={{ color: "var(--accent-text)" }} />
</div>
<div>
<p
className="text-sm font-semibold"
style={{ color: "var(--text-primary)" }}
>
{user?.name || user?.login}
</p>
<p
className="text-[10px]"
style={{ color: "var(--text-muted)" }}
>
{user?.login}
</p>
</div>
</div>
</div>
<span
className="text-xs"
style={{ color: "var(--text-secondary)" }}
{/* Theme selector */}
<div className="relative">
<button
onClick={() => setThemePickerOpen(!themePickerOpen)}
className="w-full flex items-center gap-3 px-4 py-2.5 text-xs transition-colors"
style={{ color: "var(--text-primary)" }}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor =
"var(--bg-secondary)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = "transparent";
}}
>
<FaPalette
size={12}
style={{ color: "var(--text-secondary)" }}
/>
<span className="flex-1 text-left">
Тема: {themes.find((t) => t.id === currentTheme)?.name}
</span>
<FaChevronDown
size={9}
style={{
color: "var(--text-muted)",
transform: themePickerOpen
? "rotate(180deg)"
: "rotate(0)",
transition: "transform 0.2s",
}}
/>
</button>
{/* Theme sub-dropdown */}
{themePickerOpen && (
<div
className="absolute right-full top-0 mr-1 rounded-lg shadow-xl border z-50"
style={{
backgroundColor: "var(--card-bg)",
borderColor: "var(--border)",
minWidth: "180px",
}}
>
{themes.map((t) => (
<button
key={t.id}
onClick={() => handleThemeChange(t.id)}
className="w-full flex items-center gap-3 px-4 py-2 text-xs transition-colors first:rounded-t-lg last:rounded-b-lg"
style={{
color:
currentTheme === t.id
? "var(--accent)"
: "var(--text-primary)",
backgroundColor:
currentTheme === t.id
? "var(--bg-secondary)"
: "transparent",
}}
onMouseEnter={(e) => {
if (currentTheme !== t.id) {
e.currentTarget.style.backgroundColor =
"var(--bg-secondary)";
}
}}
onMouseLeave={(e) => {
if (currentTheme !== t.id) {
e.currentTarget.style.backgroundColor =
"transparent";
}
}}
>
<div
className="w-4 h-4 rounded-full border"
style={{
backgroundColor: t.colors.primary,
borderColor: "var(--border)",
}}
/>
<span>{t.name}</span>
</button>
))}
</div>
)}
</div>
{/* Admin (if admin) */}
{user?.permission_admin && (
<button
onClick={() => {
setDropdownOpen(false);
navigate("/admin");
}}
className="w-full flex items-center gap-3 px-4 py-2.5 text-xs transition-colors"
style={{ color: "var(--text-primary)" }}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor =
"var(--bg-secondary)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = "transparent";
}}
>
<FaShieldAlt size={12} style={{ color: "#f59e0b" }} />
<span>Админка</span>
</button>
)}
{/* Divider */}
<div
className="my-1 border-b"
style={{ borderColor: "var(--border)" }}
/>
{/* Logout */}
<button
onClick={handleLogout}
className="w-full flex items-center gap-3 px-4 py-2.5 text-xs transition-colors rounded-b-lg"
style={{ color: "var(--error-text)" }}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor =
"rgba(239, 68, 68, 0.1)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = "transparent";
}}
>
{user.name}
</span>
<FaSignOutAlt size={12} />
<span>Выйти</span>
</button>
</div>
)}
{/* Переключатель темы */}
<button
onClick={toggleTheme}
className="p-1.5 rounded-lg transition-colors"
style={{
backgroundColor: "var(--bg-secondary)",
color: isDark ? "#fbbf24" : "#3b82f6",
border: "1px solid var(--border)",
}}
title={isDark ? "Светлая тема" : "Тёмная тема"}
>
{isDark ? <FaSun size={12} /> : <FaMoon size={12} />}
</button>
{/* Админка */}
<button
onClick={() => navigate("/admin")}
className="p-1.5 rounded-lg transition-colors"
style={{
backgroundColor:
location.pathname === "/admin"
? "var(--accent)"
: "var(--bg-secondary)",
color:
location.pathname === "/admin"
? "var(--accent-text)"
: "var(--text-secondary)",
border: "1px solid var(--border)",
}}
title="Админка"
>
<FaUsers size={12} />
</button>
<button
onClick={() => {
logout();
navigate("/auth");
}}
className="px-3 py-1.5 rounded-lg text-xs font-medium transition-colors"
style={{
backgroundColor: "var(--error-bg)",
color: "var(--error-text)",
border: "1px solid var(--error-border)",
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = "var(--error-text)";
e.currentTarget.style.color = "#fff";
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = "var(--error-bg)";
e.currentTarget.style.color = "var(--error-text)";
}}
>
Выйти
</button>
</div>
</div>
</div>