From a54b1d0c39aad762aa553f5cca86ce389aafed10 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 19 Mar 2025 07:50:37 -0400 Subject: [PATCH] =?UTF-8?q?=D0=A7=D0=B0=D1=81=D1=82=D0=B8=D1=87=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=BE=D1=82=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BB=20=D0=BA=D0=BE=D0=B4,=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20sidebar=20menu=20=D0=B8?= =?UTF-8?q?=20tabs=20=D1=81=20=D1=81=D0=B8=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20MUI,=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D0=BB=20=D0=B0=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 9 +- package.json | 3 +- src/App.jsx | 39 ++-- src/Charts/PrometheusChart.jsx | 5 + src/Components/Layout/Dashboard.jsx | 130 +++---------- src/Components/Layout/SidebarMenu.jsx | 119 ++++-------- .../Layout/SidebarMenuComponents/MenuItem.jsx | 55 ++++++ .../SidebarMenuComponents/SidebarFooter.jsx | 17 ++ src/Components/UI/LoginModal.jsx | 131 +++++-------- src/Components/UI/MUItabs.jsx | 64 +++++++ src/Components/UI/TreeTable.jsx | 176 ++++++++++++++---- src/Components/UI/TreeTable222.jsx | 151 --------------- src/Components/hooks/TabContent.jsx | 32 ++++ src/Components/hooks/useSidebarResize.jsx | 43 +++++ src/Components/hooks/useTabs.jsx | 26 +++ src/Style/Dashboard.css | 20 +- src/Style/SystemStatusTable.css | 2 +- src/Style/ThemeSwitch.jsx | 31 --- src/Style/TreeTable.css | 22 ++- src/Style/dark-theme.css | 0 src/Style/light-theme.css | 6 +- src/Style/theme.jsx | 73 ++++++++ src/main.jsx | 4 +- 23 files changed, 639 insertions(+), 519 deletions(-) mode change 100755 => 100644 src/Components/Layout/SidebarMenu.jsx create mode 100644 src/Components/Layout/SidebarMenuComponents/MenuItem.jsx create mode 100644 src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx create mode 100644 src/Components/UI/MUItabs.jsx delete mode 100755 src/Components/UI/TreeTable222.jsx create mode 100644 src/Components/hooks/TabContent.jsx create mode 100644 src/Components/hooks/useSidebarResize.jsx create mode 100644 src/Components/hooks/useTabs.jsx delete mode 100755 src/Style/ThemeSwitch.jsx mode change 100755 => 100644 src/Style/dark-theme.css mode change 100755 => 100644 src/Style/light-theme.css create mode 100644 src/Style/theme.jsx diff --git a/.gitignore b/.gitignore index e20089e..e7fea06 100755 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,11 @@ dist-ssr *.sw? *.les* -node_modules \ No newline at end of file +node_modules + +# Игнорировать .env файлы +.env +.env.local +.env.development +.env.production +.env.test \ No newline at end of file diff --git a/package.json b/package.json index 4d8ad3d..508687a 100755 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "react-datepicker": "^8.1.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@mui/material": "^6.4.7" + "@mui/material": "^6.4.7", + "@mui/icons-material": "^6.4.8" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/src/App.jsx b/src/App.jsx index f127d25..2b0954c 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,26 +1,39 @@ -import React, { useState } from "react"; +import React, { useState, useMemo } from "react"; +import { ThemeProvider, CssBaseline, Switch, Box } from "@mui/material"; import Dashboard from "./Components/Layout/Dashboard"; -import LoginModal from "./Components/UI/LoginModal"; // Импортируем компонент авторизации -import "./Style/LoginModal.css"; // Импортируем стили +import LoginModal from "./Components/UI/LoginModal"; +import { lightTheme, darkTheme } from "./Style/theme"; +import "./Style/LoginModal.css"; function App() { - const [isAuthenticated, setIsAuthenticated] = useState(false); // Состояние авторизации - const [showLoginModal, setShowLoginModal] = useState(true); // Показывать ли модальное окно + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [showLoginModal, setShowLoginModal] = useState(true); + const [isDarkMode, setIsDarkMode] = useState( + window.matchMedia("(prefers-color-scheme: dark)").matches + ); + + const theme = useMemo(() => (isDarkMode ? darkTheme : lightTheme), [isDarkMode]); const handleLogin = () => { - setIsAuthenticated(true); // Устанавливаем авторизацию - setShowLoginModal(false); // Скрываем модальное окно + setIsAuthenticated(true); + setShowLoginModal(false); }; return ( -
- {!isAuthenticated && showLoginModal && ( + + + {!isAuthenticated && showLoginModal ? ( setShowLoginModal(false)} /> + ) : ( + + + + setIsDarkMode((prev) => !prev)} /> + + )} - - {isAuthenticated && } -
+ ); } -export default App; \ No newline at end of file +export default App; diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index 81d42ab..d3a151b 100755 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -96,6 +96,11 @@ const PrometheusChart = ({ metricName }) => { params: { metric: metricName, start, end, step }, }); + /* + const response = await axios.get(`${process.env.REACT_APP_BACK_URL}/metrics`, { + params: { metric: metricName, start, end, step }, + }); */ + const result = response.data; let metrics = Array.isArray(result) ? result : result.data || []; diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index c27e8ac..1db005c 100755 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -1,33 +1,32 @@ -import React, { useState, useEffect, useRef, useCallback } from "react"; +import React, { useState, useEffect } from "react"; import SidebarMenu from "./SidebarMenu"; -import TreeChart from "../TreeChart/TreeChart"; import "../../Style/Dashboard.css"; -import SystemStatusChart from "../../Charts/SystemStatusChart"; -import Tabs from "../UI/Tabs"; -import menuData from "../TreeChart/menuData.json"; -import TreeTable from "../UI/TreeTable"; import { statusManager1, statusManager2 } from "../TreeChart/dataUtils"; import generateTabContent from "../TreeChart/tabContent"; +import CustomTabs from "../UI/MUItabs"; +import useTabs from "../hooks/useTabs"; +import useSidebarResize from "../hooks/useSidebarResize"; +import TabContent from "../hooks/TabContent"; +import menuData from "../TreeChart/menuData.json"; const Dashboard = () => { - const [tabs, setTabs] = useState([]); - const [activeTab, setActiveTab] = useState("Главная"); + const { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab } = useTabs("Главная"); + const { sidebarWidth, startResizing } = useSidebarResize(250); const [tabContent, setTabContent] = useState({}); const [treeData1, setTreeData1] = useState(menuData); const [treeData2, setTreeData2] = useState(menuData); - const [sidebarWidth, setSidebarWidth] = useState(250); - const [isResizing, setIsResizing] = useState(false); const [statusHistories, setStatusHistories] = useState({ history1: [], history2: [], }); - const sidebarRef = useRef(null); + // Генерация контента для вкладок useEffect(() => { const generatedTabContent = generateTabContent(menuData); setTabContent(generatedTabContent); }, []); + // Обновление статусов каждые 30 секунд useEffect(() => { const interval = setInterval(() => { const updatedData1 = JSON.parse(JSON.stringify(treeData1)); @@ -56,106 +55,33 @@ const Dashboard = () => { return () => clearInterval(interval); }, [treeData1, treeData2]); - const startResizing = useCallback((e) => { - e.preventDefault(); - setIsResizing(true); - }, []); - - const resize = useCallback((e) => { - if (isResizing) { - const newWidth = e.clientX; - if (newWidth > 100 && newWidth < 400) { - setSidebarWidth(newWidth); - } - } - }, [isResizing]); - - const stopResizing = useCallback(() => { - setIsResizing(false); - }, []); - - useEffect(() => { - const handleMouseMove = (e) => resize(e); - const handleMouseUp = () => stopResizing(); - - if (isResizing) { - window.addEventListener("mousemove", handleMouseMove); - window.addEventListener("mouseup", handleMouseUp); - } - - return () => { - window.removeEventListener("mousemove", handleMouseMove); - window.removeEventListener("mouseup", handleMouseUp); - }; - }, [isResizing, resize, stopResizing]); - - const handleOpenTab = (id, title) => { - if (!tabs.some((tab) => tab.id === id)) { - setTabs([...tabs, { id, title }]); - } - setActiveTab(id); - }; - - const handleCloseTab = (id) => { - const newTabs = tabs.filter((tab) => tab.id !== id); - setTabs(newTabs); - if (activeTab === id) { - setActiveTab(newTabs.length > 0 ? newTabs[newTabs.length - 1].id : "Главная"); - } - }; - - const renderTabContent = () => { - if (activeTab === "Главная") { - return ( -
-

Общий мониторинг состояния системы

- -
-
- - -
-
- - -
-
- - - -
- ); - } else if (activeTab === "Визуализация") { - return handleOpenTab(id, title)} />; - } else { - const tabData = tabContent[activeTab]; - return tabData ? tabData.content :

Нет данных

; - } - }; - return (
-
- -
+ {/* Сайдбар */} +
+ +
-
- + {/* Вкладки */} + setActiveTab(id)} + onTabClick={setActiveTab} onCloseTab={handleCloseTab} /> + + {/* Контент вкладки */}
- {renderTabContent()} +
diff --git a/src/Components/Layout/SidebarMenu.jsx b/src/Components/Layout/SidebarMenu.jsx old mode 100755 new mode 100644 index 651c2e7..60acb84 --- a/src/Components/Layout/SidebarMenu.jsx +++ b/src/Components/Layout/SidebarMenu.jsx @@ -1,86 +1,49 @@ -import React, { useState } from "react"; -import "../../Style/SidebarMenu.css"; -import { ThemeProvider, createTheme, CssBaseline, Button } from "@mui/material"; -import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем только нужную функцию +import React from "react"; +import { Drawer, List } from "@mui/material"; +import MenuItem from "./SidebarMenuComponents/MenuItem"; +import SidebarFooter from "./SidebarMenuComponents/SidebarFooter"; -const MenuItem = ({ item, onSelectItem, sidebarWidth }) => { - const [isOpen, setIsOpen] = useState(false); - const hasChildren = Array.isArray(item.items) && item.items.length > 0; - const statusColor = getStatusColor(item.status); - - const handleSingleClick = () => { - if (hasChildren) { - setIsOpen(!isOpen); - } else { - onSelectItem(item); - } - }; - - const handleOpenParent = (e) => { - e.stopPropagation(); - onSelectItem(item); +const SidebarMenu = ({ data, onOpenTab, sidebarWidth, startResizing }) => { + const handleSelectItem = (id, title, children) => { + onOpenTab(id, title, children); }; return ( -
-
- {/* Круглый индикатор статуса */} -
- {/* Текст элемента меню */} - {item.title} + + +

Меню

+ +
- {/* Иконки */} - {hasChildren && ( -
- {/* Иконка для открытия родителя */} - - 📂 - - {/* Иконка для разворачивания/сворачивания */} - - {isOpen ? "▲" : "▼"} - -
- )} -
- {isOpen && hasChildren && ( -
- {item.items.map((child, index) => ( - - ))} -
- )} -
+ {/* Ресайзер */} +
+ + + ); }; -function SidebarMenu({ data, onOpenTab, sidebarWidth }) { - const handleSelectItem = (item) => { - onOpenTab(item.id, item.title); - }; - - return ( -
-
{/* Динамическая ширина */} -

Меню

- -
-
{/* Динамическая ширина */} -

Помощь

-

Настройка

-
-
- ); -} - -export default SidebarMenu; \ No newline at end of file +export default SidebarMenu; diff --git a/src/Components/Layout/SidebarMenuComponents/MenuItem.jsx b/src/Components/Layout/SidebarMenuComponents/MenuItem.jsx new file mode 100644 index 0000000..82f5f5d --- /dev/null +++ b/src/Components/Layout/SidebarMenuComponents/MenuItem.jsx @@ -0,0 +1,55 @@ +import React from "react"; +import { Drawer, List, ListItem, ListItemIcon, ListItemText, Collapse } from "@mui/material"; +import { ExpandLess, ExpandMore, Folder, FolderOpen } from "@mui/icons-material"; + +// Функция для сбора всех потомков +const getAllChildren = (node) => { + let children = []; + if (node.items && node.items.length > 0) { + node.items.forEach((child) => { + children.push(child); // Добавляем текущий элемент + children = children.concat(getAllChildren(child)); // Рекурсивно добавляем потомков + }); + } + return children; +}; + +const MenuItem = ({ item, onSelectItem }) => { + const [isOpen, setIsOpen] = React.useState(false); + const hasChildren = Array.isArray(item.items) && item.items.length > 0; + + const handleToggle = () => { + setIsOpen(!isOpen); + }; + + const handleOpenTab = (e) => { + e.stopPropagation(); // Останавливаем всплытие события + const allChildren = getAllChildren(item); // Собираем всех потомков + onSelectItem(item.id, item.title, allChildren); // Передаем данные в родительский компонент + }; + + return ( + <> + + +
+ {hasChildren ? (isOpen ? : ) : } +
+
+ + {hasChildren && (isOpen ? : )} +
+ {hasChildren && ( + + + {item.items.map((child, index) => ( + + ))} + + + )} + + ); +}; + +export default MenuItem; \ No newline at end of file diff --git a/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx b/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx new file mode 100644 index 0000000..ec62bda --- /dev/null +++ b/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx @@ -0,0 +1,17 @@ +import React from "react"; +import { List, ListItem, ListItemText } from "@mui/material"; + +const SidebarFooter = ({ sidebarWidth }) => { + return ( + + + + + + + + + ); +}; + +export default SidebarFooter; \ No newline at end of file diff --git a/src/Components/UI/LoginModal.jsx b/src/Components/UI/LoginModal.jsx index 7d77404..98405a8 100755 --- a/src/Components/UI/LoginModal.jsx +++ b/src/Components/UI/LoginModal.jsx @@ -1,18 +1,8 @@ import React, { useState } from "react"; import Modal from "./Modal"; import "../../Style/LoginModal.css"; -import Box from '@mui/material/Box'; -import IconButton from '@mui/material/IconButton'; -import Input from '@mui/material/Input'; -import FilledInput from '@mui/material/FilledInput'; -import OutlinedInput from '@mui/material/OutlinedInput'; -import InputLabel from '@mui/material/InputLabel'; -import InputAdornment from '@mui/material/InputAdornment'; -import FormHelperText from '@mui/material/FormHelperText'; -import FormControl from '@mui/material/FormControl'; import TextField from '@mui/material/TextField'; - const LoginModal = ({ onLogin, onClose }) => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); @@ -21,13 +11,31 @@ const LoginModal = ({ onLogin, onClose }) => { const handleClickShowPassword = () => setShowPassword((show) => !show); - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); - if (username === "admin" && password === "admin") { - onLogin(); // Успешная авторизация - onClose(); // Закрыть модальное окно - } else { - setError("Неверный логин или пароль"); + + try { + // Отправляем данные на бэкенд + console.log("Отправляем данные:", { username, password }); + const response = await fetch('http://192.168.2.39:3000/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ login: username, password }), + }); + + const data = await response.json(); + + if (data.success) { + onLogin(); // Успешная авторизация + onClose(); // Закрыть модальное окно + } else { + setError(data.message || "Неверный логин или пароль"); + } + } catch (err) { + console.error('Ошибка при отправке запроса:', err); + setError("Ошибка при подключении к серверу"); } }; @@ -35,77 +43,28 @@ const LoginModal = ({ onLogin, onClose }) => {

Авторизация

- {/*
- - setUsername(e.target.value)} - required - /> -
-
- - setPassword(e.target.value)} - required - /> -
*/} - - {/* :not(style)': { m: 1, width: '25ch' } }} - noValidate - autoComplete="off" - > */} - setUsername(e.target.value)} - size="normal" - /> + setUsername(e.target.value)} + size="normal" + /> - setPassword(e.target.value)} - size="normal" - /> - {/* - Password - - - {showPassword ? : } - - - } - label="Password" - /> - */} - {/* */} + setPassword(e.target.value)} + size="normal" + /> {error &&

{error}

} diff --git a/src/Components/UI/MUItabs.jsx b/src/Components/UI/MUItabs.jsx new file mode 100644 index 0000000..06ef06f --- /dev/null +++ b/src/Components/UI/MUItabs.jsx @@ -0,0 +1,64 @@ +import React from "react"; +import { Tabs, Tab, Box } from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; + +const CustomTabs = ({ tabs, activeTab, onTabClick, onCloseTab }) => { + const handleMouseDown = (e, id) => { + if (e.button === 1) { + e.preventDefault(); + onCloseTab(id); + } + }; + + const handleChange = (event, newValue) => { + onTabClick(newValue); + }; + + return ( + + + {/* Всегда отображаемые вкладки */} + handleMouseDown(e, "Главная")} + /> + handleMouseDown(e, "Визуализация")} + /> + + {/* Динамически добавляемые вкладки */} + {tabs.map((tab) => ( + + {tab.title} + { + e.stopPropagation(); + onCloseTab(tab.id); + }} + /> + + } + value={tab.id} + onMouseDown={(e) => handleMouseDown(e, tab.id)} + /> + ))} + + + ); +}; + +export default CustomTabs; \ No newline at end of file diff --git a/src/Components/UI/TreeTable.jsx b/src/Components/UI/TreeTable.jsx index 51287aa..ddcb5a0 100755 --- a/src/Components/UI/TreeTable.jsx +++ b/src/Components/UI/TreeTable.jsx @@ -27,12 +27,6 @@ const TreeTable = ({ data }) => { } }; - useEffect(() => { - adjustFontSize(); - window.addEventListener("resize", adjustFontSize); - return () => window.removeEventListener("resize", adjustFontSize); - }, [data]); - useEffect(() => { const newLog = []; const traverse = (items) => { @@ -41,7 +35,7 @@ const TreeTable = ({ data }) => { newLog.push({ title: item.title, status: item.status, - time: new Date().toLocaleTimeString() // Добавляем время + time: new Date().toLocaleTimeString(), // Добавляем время }); } if (item.items) { @@ -50,19 +44,31 @@ const TreeTable = ({ data }) => { }); }; traverse(data.items); - setLog(newLog); + + // Ограничиваем количество сообщений до 50 + setLog((prevLog) => [...newLog, ...prevLog].slice(0, 50)); }, [data]); const filteredData = data.items.filter((item) => item.title !== "Функциональные задачи"); + // Функция для отображения заголовков const renderHeaders = (items) => { return items.map((item) => { const colSpan = item.items ? item.items.length : 1; return (
-
-
+
+
{item.title}
@@ -70,41 +76,117 @@ const TreeTable = ({ data }) => { }); }; - const renderRows = (items) => { - if (!items || items.length === 0) return null; - const hasChildren = items.some((item) => item.items && item.items.length > 0); - if (!hasChildren) return null; - return ( - - {items.map((item) => { - if (item.items && item.items.length > 0) { - return ( - - {item.items.map((child) => ( - -
-
-
- {child.title} -
- - ))} - - ); + // Функция для отображения подзаголовков + const renderSubHeaders = (items) => { + return items.map((item) => { + if (item.items) { + return item.items.map((child) => ( + +
+
+
+ {child.title} +
+ + )); + } else { + return ( + +
+
+
+ {item.title} +
+ + ); + } + }); + }; + + // Функция для отображения данных + const renderData = (items) => { + return items.map((item) => { + if (item.items) { + return item.items.map((child) => { + if (child.items) { + return child.items.map((subChild) => ( + +
+
+
+ {subChild.title} +
+ + )); } else { return ( - +
-
-
- {item.title} +
+
+ {child.title}
); } - })} - - ); + }); + } else { + return ( + +
+
+
+ {item.title} +
+ + ); + } + }); }; return ( @@ -118,15 +200,27 @@ const TreeTable = ({ data }) => { title={data.title} >
-
-
+
+
{data.title}
{renderHeaders(filteredData)} + {renderSubHeaders(filteredData)} - {renderRows(filteredData)} + + {renderData(filteredData)} + - {isLogVisible && ( -
-

Лог статусов

-
    - {log.map((entry, index) => ( -
  • - [{entry.time}] {entry.status}: {entry.title} -
  • - ))} -
-
- )} -
- ); -}; - -export default TreeTable; \ No newline at end of file diff --git a/src/Components/hooks/TabContent.jsx b/src/Components/hooks/TabContent.jsx new file mode 100644 index 0000000..bd33c2c --- /dev/null +++ b/src/Components/hooks/TabContent.jsx @@ -0,0 +1,32 @@ +import SystemStatusChart from "../../Charts/SystemStatusChart"; +import TreeTable from "../UI/TreeTable"; +import TreeChart from "../TreeChart/TreeChart"; + +const TabContent = ({ activeTab, statusHistories, treeData1, tabContent, handleOpenTab }) => { + if (activeTab === "Главная") { + return ( +
+

Общий мониторинг состояния системы

+
+
+ + +
+
+ + +
+
+ + +
+ ); + } else if (activeTab === "Визуализация") { + return handleOpenTab(id, title)} />; + } else { + const tabData = tabContent[activeTab]; + return tabData ? tabData.content :

Нет данных

; + } +}; + +export default TabContent; \ No newline at end of file diff --git a/src/Components/hooks/useSidebarResize.jsx b/src/Components/hooks/useSidebarResize.jsx new file mode 100644 index 0000000..f41e0a7 --- /dev/null +++ b/src/Components/hooks/useSidebarResize.jsx @@ -0,0 +1,43 @@ +import { useState, useCallback, useEffect } from "react"; + +const useSidebarResize = (initialWidth = 250) => { + const [sidebarWidth, setSidebarWidth] = useState(initialWidth); + const [isResizing, setIsResizing] = useState(false); + + const startResizing = useCallback((e) => { + e.preventDefault(); + setIsResizing(true); + }, []); + + const resize = useCallback((e) => { + if (isResizing) { + const newWidth = e.clientX; + if (newWidth > 100 && newWidth < 400) { + setSidebarWidth(newWidth); + } + } + }, [isResizing]); + + const stopResizing = useCallback(() => { + setIsResizing(false); + }, []); + + useEffect(() => { + const handleMouseMove = (e) => resize(e); + const handleMouseUp = () => stopResizing(); + + if (isResizing) { + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); + } + + return () => { + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); + }; + }, [isResizing, resize, stopResizing]); + + return { sidebarWidth, startResizing }; +}; + +export default useSidebarResize; \ No newline at end of file diff --git a/src/Components/hooks/useTabs.jsx b/src/Components/hooks/useTabs.jsx new file mode 100644 index 0000000..188cdf5 --- /dev/null +++ b/src/Components/hooks/useTabs.jsx @@ -0,0 +1,26 @@ +import { useState, useCallback } from "react"; + +const useTabs = (initialTab) => { + const [tabs, setTabs] = useState([]); + const [activeTab, setActiveTab] = useState(initialTab); + + const handleOpenTab = useCallback((id, title) => { + setTabs((prevTabs) => + prevTabs.some((tab) => tab.id === id) + ? prevTabs + : [...prevTabs, { id, title }] + ); + setActiveTab(id); + }, []); + + const handleCloseTab = useCallback((id) => { + setTabs((prevTabs) => prevTabs.filter((tab) => tab.id !== id)); + if (activeTab === id) { + setActiveTab(tabs.length > 1 ? tabs[tabs.length - 2].id : initialTab); + } + }, [activeTab, tabs, initialTab]); + + return { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab }; +}; + +export default useTabs; \ No newline at end of file diff --git a/src/Style/Dashboard.css b/src/Style/Dashboard.css index a7f393c..4f9eb18 100755 --- a/src/Style/Dashboard.css +++ b/src/Style/Dashboard.css @@ -2,24 +2,34 @@ .dashboard-container { display: flex; height: 100vh; - width: calc(100vw - 20px); + width: 100vw; overflow: hidden; - margin-left: 20px; background-color: var(--background-color); color: var(--text-color); } +/* Сайдбар */ +.sidebar { + flex-shrink: 0; + height: 100vh; + overflow-y: auto; + background-color: var(--sidebar-color); + color: var(--sidebar-text-color); + transition: width 0.2s ease; +} + /* Основной контент */ .main-content { - flex: 1; + flex-grow: 1; + display: flex; + flex-direction: column; padding: 20px; - margin-left: 50px; - transition: margin-left 0.2s ease; overflow: auto; background-color: var(--background-color); color: var(--text-color); } + /* Контент */ .content { background-color: var(--modal-background); diff --git a/src/Style/SystemStatusTable.css b/src/Style/SystemStatusTable.css index 5711e1e..be9c38f 100755 --- a/src/Style/SystemStatusTable.css +++ b/src/Style/SystemStatusTable.css @@ -48,7 +48,7 @@ button { } button:hover { - background-color: #0056b3; + background-color: #000000; } caption { diff --git a/src/Style/ThemeSwitch.jsx b/src/Style/ThemeSwitch.jsx deleted file mode 100755 index 3f00059..0000000 --- a/src/Style/ThemeSwitch.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import { createTheme, ThemeProvider } from "@mui/material/styles"; - -// Светлая тема -const lightTheme = createTheme({ - palette: { - mode: "light", - background: { - default: "#FFFFFF", - paper: "#f4f4f4", - }, - text: { - primary: "#000000", - secondary: "#333333", - }, - }, -}); - -// Темная тема -const darkTheme = createTheme({ - palette: { - mode: "dark", - background: { - default: "#1E1E1E", - paper: "#2d2d2d", - }, - text: { - primary: "#E0E0E0", - secondary: "#CCCCCC", - }, - }, -}); \ No newline at end of file diff --git a/src/Style/TreeTable.css b/src/Style/TreeTable.css index 10eb027..d31dad5 100755 --- a/src/Style/TreeTable.css +++ b/src/Style/TreeTable.css @@ -1,6 +1,7 @@ .tree-table-container { width: 100%; - overflow-x: auto; + overflow-x: hidden; + /* Убираем горизонтальный скролл */ } .tree-table { @@ -8,26 +9,33 @@ border-collapse: collapse; text-align: center; table-layout: fixed; + /* Фиксированная ширина колонок */ background-color: var(--table-cell-background); color: var(--table-text-color); - /* Используем переменную для цвета текста */ } .tree-table-header { padding: 10px; - border: 1px solid var(--table-border); + border: 1px solid black; font-weight: bold; white-space: nowrap; + /* Текст не переносится */ overflow: hidden; + /* Скрываем текст, который не помещается */ text-overflow: ellipsis; + /* Добавляем многоточие */ background-color: var(--table-header-background); } .tree-table-cell { padding: 8px; - border: 1px solid var(--table-border); + border: 1px solid black; white-space: nowrap; + /* Текст не переносится */ overflow: hidden; + /* Скрываем текст, который не помещается */ + text-overflow: ellipsis; + /* Добавляем многоточие */ } .cell-content, @@ -40,6 +48,12 @@ text-overflow: ellipsis; } +.cell-text { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; +} + .status-indicator-bar { width: 6px; height: 20px; diff --git a/src/Style/dark-theme.css b/src/Style/dark-theme.css old mode 100755 new mode 100644 diff --git a/src/Style/light-theme.css b/src/Style/light-theme.css old mode 100755 new mode 100644 index 6e98f53..b4f6d03 --- a/src/Style/light-theme.css +++ b/src/Style/light-theme.css @@ -1,7 +1,7 @@ /* Светлая тема по умолчанию */ :root { --background-color: #FFFFFF; - --text-color: #FFFFFF; + --text-color: #000000; --header-color: #333333; /* Основной цвет текста (черный) */ --sidebar-color: #3d74c7; @@ -16,8 +16,8 @@ --table-cell-background: #FFFFFF; --table-text-color: #000000; /* Черный текст в таблице */ - + /* hover for buttons */ --hover-button: #2d62b1; - --hover-text-color : #FFFFFF + --hover-text-color: #FFFFFF } \ No newline at end of file diff --git a/src/Style/theme.jsx b/src/Style/theme.jsx new file mode 100644 index 0000000..02b38ed --- /dev/null +++ b/src/Style/theme.jsx @@ -0,0 +1,73 @@ +import { createTheme } from "@mui/material/styles"; + +export const lightTheme = createTheme({ + palette: { + mode: "light", + background: { + default: "#FFFFFF", + paper: "#FFFFFF", + }, + text: { + primary: "#000000", + }, + primary: { + main: "#3d74c7", + }, + secondary: { + main: "#0f55bec2", + }, + custom: { + background: "#FFFFFF", + text: "#000000", + sidebar: "#3d74c7", + sidebarText: "#FFFFFF", + modalBackground: "#FFFFFF", + modalBtnBackground: "#0f55bec2", + modalText: "#333333", + tableBorder: "#ddd", + tableHeaderBackground: "#f9f9f9", + tableCellBackground: "#FFFFFF", + tableText: "#000000", + treeChartText: "#000000", + scrollbarTrack: "#f1f1f1", + hoverButton: "#2d62b1", + hoverText: "#FFFFFF", + }, + }, +}); + +export const darkTheme = createTheme({ + palette: { + mode: "dark", + background: { + default: "#1E1E1E", + paper: "#2d2d2d", + }, + text: { + primary: "#E0E0E0", + }, + primary: { + main: "#2d2d2d", + }, + secondary: { + main: "#333333", + }, + custom: { + background: "#1E1E1E", + text: "#E0E0E0", + sidebar: "#2d2d2d", + sidebarText: "#E0E0E0", + modalBackground: "#2d2d2d", + modalBtnBackground: "#333333", + modalText: "#FFFFFF", + tableBorder: "#444444", + tableHeaderBackground: "#2d2d2d", + tableCellBackground: "#333333", + tableText: "#E0E0E0", + treeChartText: "#FFFFFF", + scrollbarTrack: "#333", + hoverButton: "#333d4d", + hoverText: "#E0E0E0", + }, + }, +}); diff --git a/src/main.jsx b/src/main.jsx index f802dc0..e373476 100755 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,8 +2,8 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.jsx' -import './Style/light-theme.css'; // Подключаем светлую тему по умолчанию -import './Style/dark-theme.css'; // Подключаем темную тему +//import './Style/light-theme.css'; // Подключаем светлую тему по умолчанию +//import './Style/dark-theme.css'; // Подключаем темную тему createRoot(document.getElementById('root')).render(