diff --git a/src/Charts/NegativeStatusChart.jsx b/src/Charts/NegativeStatusChart.jsx new file mode 100644 index 0000000..d142ded --- /dev/null +++ b/src/Charts/NegativeStatusChart.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; + +const NegativeStatusChart = ({ data }) => { + // Подсчет количества негативных статусов + const processedData = data.map(entry => ({ + time: entry.time, + negativeCount: entry.statuses.filter(status => ['yellow', 'orange', 'red'].includes(status)).length + })); + + return ( + + + + + + + + + + + ); +}; + +export default NegativeStatusChart; diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index 1a0d741..81d42ab 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -34,6 +34,45 @@ const PrometheusChart = ({ metricName }) => { const [filteredData, setFilteredData] = useState(null); // Отфильтрованные данные const intervalRef = useRef(null); + // Функция для интерполяции данных + const interpolateData = (data, minPoints = 15) => { + if (data.length >= minPoints) return data; + + const interpolatedData = []; + for (let i = 0; i < data.length - 1; i++) { + interpolatedData.push(data[i]); + + const currentPoint = data[i]; + const nextPoint = data[i + 1]; + + // Вычисляем разницу во времени между точками + const currentTime = new Date(currentPoint.time).getTime(); + const nextTime = new Date(nextPoint.time).getTime(); + const timeDiff = nextTime - currentTime; + + // Добавляем промежуточные точки + const steps = Math.ceil((minPoints - data.length) / (data.length - 1)); + for (let j = 1; j <= steps; j++) { + const interpolatedTime = new Date(currentTime + (timeDiff * j) / (steps + 1)).toLocaleString(); + const interpolatedPoint = { time: interpolatedTime }; + + // Интерполируем значения для каждой метрики + Object.keys(currentPoint).forEach(key => { + if (key !== 'time') { + const currentValue = currentPoint[key]; + const nextValue = nextPoint[key]; + interpolatedPoint[key] = currentValue + ((nextValue - currentValue) * j) / (steps + 1); + } + }); + + interpolatedData.push(interpolatedPoint); + } + } + + interpolatedData.push(data[data.length - 1]); // Добавляем последнюю точку + return interpolatedData.slice(0, minPoints); // Обрезаем до minPoints + }; + const fetchData = async () => { try { let start, end; @@ -147,7 +186,10 @@ const PrometheusChart = ({ metricName }) => { }); const filtered = data.slice(startIndex, endIndex + 1); - setFilteredData(filtered); // Сохраняем отфильтрованные данные + + // Интерполируем данные, если точек меньше 15 + const interpolated = interpolateData(filtered, 15); + setFilteredData(interpolated); // Сохраняем интерполированные данные } else { setFilteredData(null); // Сбрасываем фильтрацию, если диапазон не выбран } diff --git a/src/Charts/SystemStatusChart.jsx b/src/Charts/SystemStatusChart.jsx new file mode 100644 index 0000000..0ee7531 --- /dev/null +++ b/src/Charts/SystemStatusChart.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; + +const SystemStatusChart = ({ data }) => { + // Обрезаем массив, оставляя только последние 20 точек + const trimmedData = data.slice(-20); + + return ( + + + + + + + + + + + ); +}; + +export default SystemStatusChart; \ No newline at end of file diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index 040dff0..415d68a 100644 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef } from "react"; import SidebarMenu from "./SidebarMenu"; import TreeChart from "../TreeChart/TreeChart"; import "../../Style/Dashboard.css"; -import ErrorIndicator from "../UI/ErrorIndicator"; +import SystemStatusChart from "../../Charts/SystemStatusChart"; import Tabs from "../UI/Tabs"; import menuData from "../TreeChart/menuData.json"; // Импортируем JSON-данные import TreeTable from "../UI/TreeTable"; @@ -16,6 +16,7 @@ const Dashboard = () => { const [treeData, setTreeData] = useState(menuData); // Загружаем меню в state const [sidebarWidth, setSidebarWidth] = useState(250); // Начальная ширина сайдбара const [isResizing, setIsResizing] = useState(false); // Состояние перетаскивания + const [statusHistory, setStatusHistory] = useState([]); // История статусов для графика const sidebarRef = useRef(null); // Референс на сайдбар // Генерация контента для вкладок на основе menuData @@ -29,7 +30,17 @@ const Dashboard = () => { const interval = setInterval(() => { setTreeData((prevData) => { const updatedData = JSON.parse(JSON.stringify(prevData)); // Клонируем данные - updateStatuses(updatedData); // Обновляем статусы + const averageStatusValue = updateStatuses(updatedData); // Обновляем статусы и получаем среднее значение + + // Преобразуем среднее значение в проценты (0% - 100%) + const statusPercentage = (1 - (averageStatusValue / 3)) * 100; + + // Добавляем новое состояние в историю + setStatusHistory((prevHistory) => [ + ...prevHistory, + { time: new Date().toLocaleTimeString(), status: statusPercentage } + ]); + return updatedData; }); }, 30000); @@ -93,8 +104,11 @@ const Dashboard = () => { if (activeTab === "Главная") { return (
-

Общий мониторинг

- {/* Используем актуальные данные */} +

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

+ + {/* График состояния системы */} + + {/* Используем актуальные данные */}
); } else if (activeTab === "Визуализация") { diff --git a/src/Components/TreeChart/dataUtils.jsx b/src/Components/TreeChart/dataUtils.jsx index fa67130..7ee90e0 100644 --- a/src/Components/TreeChart/dataUtils.jsx +++ b/src/Components/TreeChart/dataUtils.jsx @@ -1,37 +1,56 @@ // Функция для генерации случайных статусов const getRandomStatus = () => { const statuses = [ - ...Array(90).fill("green"), // 63/70 chance (примерно 90%) - ...Array(6).fill("yellow"), // 1/70 chance (примерно 1.43%) - ...Array(3).fill("orange"), // 1/70 chance (примерно 1.43%) - ...Array(1).fill("red"), // 1/70 chance (примерно 1.43%) + ...Array(90).fill("green"), // 90% chance + ...Array(6).fill("yellow"), // 6% chance + ...Array(3).fill("orange"), // 3% chance + ...Array(1).fill("red"), // 1% chance ]; return statuses[Math.floor(Math.random() * statuses.length)]; }; +// Функция для получения числового значения статуса +const getStatusValue = (status) => { + switch (status) { + case "green": + return 0; + case "yellow": + return 1; + case "orange": + return 2; + case "red": + return 3; + default: + return 0; // По умолчанию green + } +}; + +// Функция для получения статуса по числовому значению +const getStatusFromValue = (value) => { + if (value >= 3) return "red"; + if (value >= 2) return "orange"; + if (value >= 1) return "yellow"; + return "green"; +}; + // Функция для обновления статусов в дереве const updateStatuses = (data) => { if (!data.items || data.items.length === 0) { // Если это элемент нижнего уровня, генерируем случайный статус data.status = getRandomStatus(); - return data.status; + return getStatusValue(data.status); } // Рекурсивно обновляем статусы для всех дочерних элементов - let childStatuses = data.items.map((child) => updateStatuses(child)); + let childStatusValues = data.items.map((child) => updateStatuses(child)); - // Определяем статус текущего элемента на основе статусов дочерних элементов - if (childStatuses.includes("red")) { - data.status = "red"; - } else if (childStatuses.includes("orange")) { - data.status = "orange"; - } else if (childStatuses.includes("yellow")) { - data.status = "yellow"; - } else { - data.status = "green"; - } + // Вычисляем среднее арифметическое значение статусов + const averageStatusValue = childStatusValues.reduce((sum, value) => sum + value, 0) / childStatusValues.length; - return data.status; + // Определяем статус текущего элемента на основе среднего значения + data.status = getStatusFromValue(averageStatusValue); + + return getStatusValue(data.status); }; // Функция для получения цвета по статусу @@ -40,13 +59,13 @@ const getStatusColor = (status) => { case "green": return "#4CAF50"; // Зеленый case "yellow": - return "#FFEB3B"; // Желтый + return "#cebd21"; // Желтый case "orange": return "#FF9800"; // Оранжевый case "red": return "#F44336"; // Красный default: - return "#4CAF50"; // Синий (или любой другой стандартный цвет) + return "#4CAF50"; // По умолчанию зеленый } }; diff --git a/src/Components/TreeChart/menuData.json b/src/Components/TreeChart/menuData.json index 4cdcaff..35530f8 100644 --- a/src/Components/TreeChart/menuData.json +++ b/src/Components/TreeChart/menuData.json @@ -1,9 +1,10 @@ { - "title": "Сервер ЗВКС", + "title": "Сервис ЗВКС", "id": "1", "items": [ { "title": "Функциональные задачи", + "id": "functional_tasks", "items": [ { "id": "system_control", @@ -451,9 +452,11 @@ }, { "title": "Медиа сервер", + "id": "media_server_1", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_1", "items": [ { "id": "media_system_software_1_2", @@ -475,6 +478,7 @@ }, { "title": "Программное обеспечение", + "id": "software_1", "items": [ { "id": "media_software_1_2", @@ -498,9 +502,11 @@ }, { "title": "Медиа сервер", + "id": "media_server_2", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_2", "items": [ { "id": "media_system_software_1_3", @@ -522,6 +528,7 @@ }, { "title": "Программное обеспечение", + "id": "software_2", "items": [ { "id": "media_software_1_3", @@ -545,9 +552,11 @@ }, { "title": "Медиа сервер", + "id": "media_server_3", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_3", "items": [ { "id": "media_system_software_1_4", @@ -569,6 +578,7 @@ }, { "title": "Программное обеспечение", + "id": "software_3", "items": [ { "id": "media_software_1_4", @@ -592,9 +602,11 @@ }, { "title": "Медиа сервер", + "id": "media_server_4", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_4", "items": [ { "id": "media_system_software_1_5", @@ -616,6 +628,7 @@ }, { "title": "Программное обеспечение", + "id": "software_4", "items": [ { "id": "media_software_1_5", @@ -639,9 +652,11 @@ }, { "title": "Сервер систем", + "id": "system_server_1", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_5", "items": [ { "id": "copy_system_software_1", @@ -663,6 +678,7 @@ }, { "title": "Программное обеспечение", + "id": "software_5", "items": [ { "id": "copy_software_1", @@ -686,9 +702,11 @@ }, { "title": "Сервер систем", + "id": "system_server_2", "items": [ { "title": "Аппаратное обеспечение", + "id": "system_software_6", "items": [ { "id": "control_system_software_1", @@ -710,6 +728,7 @@ }, { "title": "Программное обеспечение", + "id": "software_6", "items": [ { "id": "control_software_1", diff --git a/src/Components/UI/Modal.jsx b/src/Components/UI/Modal.jsx index 3718035..9ad4753 100644 --- a/src/Components/UI/Modal.jsx +++ b/src/Components/UI/Modal.jsx @@ -5,7 +5,6 @@ const Modal = ({ children, onClose }) => {
{children} -
); diff --git a/src/Components/UI/TreeTable.jsx b/src/Components/UI/TreeTable.jsx index 28b2105..235ac1d 100644 --- a/src/Components/UI/TreeTable.jsx +++ b/src/Components/UI/TreeTable.jsx @@ -1,76 +1,146 @@ -import React from "react"; +import React, { useEffect, useRef, useState } from "react"; import "../../Style/TreeTable.css"; -import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем функцию +import { getStatusColor } from "../TreeChart/dataUtils"; const TreeTable = ({ data }) => { - // Фильтруем данные, чтобы убрать "Функциональные задачи" - const filteredData = data.filter((item) => item.title !== "Функциональные задачи"); + const tableRef = useRef(null); + const [fontSize, setFontSize] = useState(16); + const [log, setLog] = useState([]); + const [isLogVisible, setIsLogVisible] = useState(true); + + const adjustFontSize = () => { + if (tableRef.current) { + let newSize = 16; + const maxWidth = window.innerWidth; + + while (tableRef.current.scrollWidth > maxWidth && newSize > 10) { + newSize -= 1; + tableRef.current.style.fontSize = `${newSize}px`; + } + + while (tableRef.current.scrollWidth < maxWidth && newSize < 16) { + newSize += 1; + tableRef.current.style.fontSize = `${newSize}px`; + } + + setFontSize(newSize); + } + }; + + useEffect(() => { + adjustFontSize(); + window.addEventListener("resize", adjustFontSize); + return () => window.removeEventListener("resize", adjustFontSize); + }, [data]); + + useEffect(() => { + const newLog = []; + const traverse = (items) => { + items.forEach((item) => { + if (["yellow", "orange", "red"].includes(item.status)) { + newLog.push({ + title: item.title, + status: item.status, + time: new Date().toLocaleTimeString() // Добавляем время + }); + } + if (item.items) { + traverse(item.items); + } + }); + }; + traverse(data.items); + setLog(newLog); + }, [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} +
+ + ); + }); + }; + + 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} +
+ + ))} + + ); + } else { + return ( + +
+
+ {item.title} +
+ + ); + } + })} + + ); + }; return ( -
- +
+
- {/* Первый уровень: Заголовки "Медиа сервер" */} - {filteredData.map((item, index) => ( - - ))} - - {/* Второй уровень: "АО" и "ПО" */} - - {filteredData.map((item, index) => ( - - - - - ))} + + {renderHeaders(filteredData)} - - {/* Третий уровень: Вложенные элементы "АО" и "ПО" */} - {renderRows(filteredData)} - + {renderRows(filteredData)}
- {item.title} -
- АО - - ПО - acc + (item.items ? item.items.length : 1), 0)} + className="tree-table-header" + title={data.title} + > +
+
+ {data.title} +
+
+ + {isLogVisible && ( +
+

Лог статусов

+
    + {log.map((entry, index) => ( +
  • + [{entry.time}] {entry.status}: {entry.title} +
  • + ))} +
+
+ )}
); }; -// Функция для отображения строк с вложенными элементами -const renderRows = (data) => { - const rows = []; - - // Находим максимальное количество элементов среди всех "АО" и "ПО" - const maxItems = Math.max( - ...data.flatMap((item) => [ - item.items[0]?.items?.length || 0, // АО - item.items[1]?.items?.length || 0 // ПО - ]) - ); - - // Генерируем строки - for (let i = 0; i < maxItems; i++) { - rows.push( - - {data.map((item, index) => ( - - - {item.items[0]?.items[i]?.title || ""} - - - {item.items[1]?.items[i]?.title || ""} - - - ))} - - ); - } - - return rows; -}; -export default TreeTable; \ No newline at end of file +export default TreeTable; diff --git a/src/Components/UI/TreeTable3.jsx b/src/Components/UI/TreeTable3.jsx new file mode 100644 index 0000000..ced08c1 --- /dev/null +++ b/src/Components/UI/TreeTable3.jsx @@ -0,0 +1,88 @@ +import React from "react"; +import "../../Style/TreeTable.css"; +import { getStatusColor } from "../TreeChart/dataUtils"; // Импортируем функцию + +const TreeTable = ({ data }) => { + // Проверяем, что data существует и имеет нужную структуру + if (!data || !data.items) { + return
Данные не загружены или имеют неверный формат
; + } + + // Фильтруем данные, чтобы убрать "Функциональные задачи" + const filteredData = data.items.filter((item) => item.title !== "Функциональные задачи"); + + return ( +
+ + + {/* Первый уровень: Название сервера */} + + + + {/* Второй уровень: Заголовки устройств */} + + {filteredData.map((item, index) => ( + + ))} + + {/* Третий уровень: Подзаголовки "АО" и "ПО" */} + + {filteredData.map((item, index) => ( + + + + + ))} + + + + {/* Четвертый уровень: Данные "АО" и "ПО" */} + {renderRows(filteredData)} + +
+ {data.title} +
+ {item.title} +
+ {item.items[0]?.title || "Нет данных"} + + {item.items[1]?.title || "Нет данных"} +
+
+ ); +}; + +// Функция для отображения строк с вложенными элементами +const renderRows = (data) => { + const rows = []; + + // Находим максимальное количество элементов среди всех "АО" и "ПО" + const maxItems = Math.max( + ...data.flatMap((item) => [ + item.items[0]?.items?.length || 0, // АО + item.items[1]?.items?.length || 0 // ПО + ]) + ); + + // Генерируем строки + for (let i = 0; i < maxItems; i++) { + rows.push( + + {data.map((item, index) => ( + + + {item.items[0]?.items[i]?.title || ""} + + + {item.items[1]?.items[i]?.title || ""} + + + ))} + + ); + } + + return rows; +}; + +export default TreeTable; \ No newline at end of file diff --git a/src/Style/Dashboard.css b/src/Style/Dashboard.css index 537a8ca..3e1be18 100644 --- a/src/Style/Dashboard.css +++ b/src/Style/Dashboard.css @@ -2,46 +2,11 @@ .dashboard-container { display: flex; height: 100vh; - width: calc(100vw - 20px); /* Учитываем отступ */ + width: calc(100vw - 20px); overflow: hidden; margin-left: 20px; -} - -/* Сайдбар */ -.sidebar { - height: 100vh; - background-color: #3d74c7; - color: white; - position: fixed; - left: 0; - top: 0; - z-index: 999; - overflow: hidden; - transition: width 0.2s ease; - /* Плавное изменение ширины */ - display: flex; - flex-direction: column; -} - -/* Элемент для перетаскивания */ -.sidebar-resizer { - width: 5px; - /* Ширина элемента перетаскивания */ - height: 100%; - background-color: rgba(255, 255, 255, 0.1); - position: absolute; - right: 0; - top: 0; - cursor: ew-resize; - /* Курсор "изменить размер" */ - transition: background-color 0.2s ease; - z-index: 1000; - /* Убедимся, что элемент поверх других */ -} - -.sidebar-resizer:hover { - background-color: rgba(255, 255, 255, 0.3); - /* Эффект при наведении */ + background-color: var(--background-color); + color: var(--text-color); } /* Основной контент */ @@ -50,24 +15,27 @@ padding: 20px; margin-left: 50px; transition: margin-left 0.2s ease; - overflow: auto; /* Позволяет прокручивать контент, если он не влезает */ + overflow: auto; + background-color: var(--background-color); + color: var(--text-color); } /* Контент */ .content { - background-color: #f9f9f9; + background-color: var(--modal-background); padding: 20px; border-radius: 10px; - box-shadow: 0 2px 5px rgba(29, 1, 1, 0.521); - max-width: 100%; /* Гарантируем, что контент не выйдет за границы */ - overflow: auto; /* Включаем скролл, если нужно */ + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.521); + max-width: 100%; + overflow: auto; + color: var(--text-color); } /* Заголовки */ h2 { - color: #444; + color: var(--text-color); } p { - color: #333; + color: var(--text-color); } \ No newline at end of file diff --git a/src/Style/DatePicker.css b/src/Style/DatePicker.css index 3b564d1..e69de29 100644 --- a/src/Style/DatePicker.css +++ b/src/Style/DatePicker.css @@ -1,129 +0,0 @@ -/* DatePicker.css */ - -.react-datepicker-wrapper { - width: auto; - display: inline-block; -} - -.react-datepicker__input-container input { - width: 200px; - padding: 8px 12px; - border: 1px solid #ddd; - border-radius: 6px; - font-size: 14px; - color: #333; - background-color: #fff; - transition: border-color 0.2s ease; -} - -.react-datepicker__input-container input:focus { - border-color: #0078d4; - outline: none; -} - -.react-datepicker { - font-family: 'Segoe UI', sans-serif; - border: 1px solid #e0e0e0; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - background-color: #fff; - /* Непрозрачный фон */ - z-index: 1000; - /* Календарь поверх других элементов */ -} - -.react-datepicker-popper { - z-index: 1000; - /* Календарь поверх других элементов */ - pointer-events: auto; - /* Разрешить взаимодействие только с календарем */ -} - -.react-datepicker__header { - background-color: #f8f8f8; - /* Непрозрачный фон заголовка */ - border-bottom: 1px solid #e0e0e0; - border-radius: 8px 8px 0 0; - padding: 12px; -} - -.react-datepicker__current-month { - font-size: 14px; - font-weight: 600; - color: #333; -} - -.react-datepicker__navigation { - top: 12px; - border: 0.45rem solid transparent; -} - -.react-datepicker__navigation--previous { - left: 12px; - border-right-color: #666; -} - -.react-datepicker__navigation--next { - right: 12px; - border-left-color: #666; -} - -.react-datepicker__day-names { - display: flex; - justify-content: space-between; - padding: 0 8px; - margin-top: 8px; -} - -.react-datepicker__day-name { - width: 28px; - line-height: 28px; - text-align: center; - font-size: 12px; - color: #666; -} - -.react-datepicker__month { - background-color: #fff; - /* Непрозрачный фон месяца */ - margin: 0; - padding: 8px; -} - -.react-datepicker__week { - display: flex; - justify-content: space-between; -} - -.react-datepicker__day { - width: 28px; - line-height: 28px; - text-align: center; - font-size: 12px; - color: #333; - cursor: pointer; - border-radius: 50%; - transition: background-color 0.2s ease, color 0.2s ease; -} - -.react-datepicker__day:hover { - background-color: #f0f0f0; -} - -.react-datepicker__day--selected { - background-color: #0078d4; - color: #fff; -} - -.react-datepicker__day--selected:hover { - background-color: #005bb5; -} - -.react-datepicker__day--outside-month { - color: #ccc; -} - -.react-datepicker__day--disabled { - color: #ccc; - cursor: not-allowed; -} \ No newline at end of file diff --git a/src/Style/LoginModal.css b/src/Style/LoginModal.css index ae99fe0..efd544a 100644 --- a/src/Style/LoginModal.css +++ b/src/Style/LoginModal.css @@ -11,12 +11,13 @@ } .modal { - background: rgb(255, 255, 255); + background: var(--modal-background); padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); max-width: 400px; width: 100%; + color: var(--modal-text); } .modal h2 { @@ -26,7 +27,7 @@ .modal label { display: block; margin-bottom: 5px; - color: black; + color: var(--modal-text); } .modal input { @@ -35,20 +36,22 @@ margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; + background-color: var(--modal-background); + color: var(--modal-text); } .modal button { padding: 10px 20px; margin-bottom: 5px; - background: #08294b; - color: white; + background: var(--accent-color); + color: var(--text-color); border: none; border-radius: 4px; cursor: pointer; } .modal button:hover { - background: #0056b3; + background: var(--accent-hover-color); } .error { diff --git a/src/Style/SidebarMenu.css b/src/Style/SidebarMenu.css index 9fcb8d6..bbfc484 100644 --- a/src/Style/SidebarMenu.css +++ b/src/Style/SidebarMenu.css @@ -1,15 +1,15 @@ -/* Боковое меню */ +/* Сайдбар */ .sidebar { height: 100vh; - background-color: #3d74c7; - color: white; + background-color: var(--sidebar-color); + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ position: fixed; left: 0; top: 0; z-index: 999; overflow: hidden; transition: width 0.2s ease; - /* Плавное изменение ширины */ display: flex; flex-direction: column; } @@ -18,11 +18,8 @@ .sidebar-content { flex: 1; overflow-y: auto; - /* Вертикальная прокрутка */ overflow-x: hidden; - /* Убираем горизонтальную прокрутку */ padding-bottom: 20px; - /* Отступ для "Помощи" и "Настроек" */ } /* Заголовок меню */ @@ -30,23 +27,39 @@ margin-bottom: 20px; font-size: 18px; font-weight: bold; - color: white; + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ padding: 10px; - /* Добавляем отступы */ } /* Элементы меню */ .menu-item { margin-bottom: 10px; - color: white; + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ width: 100%; - /* Ширина на всю ширину сайдбара */ +} + +/* Элемент для перетаскивания */ +.sidebar-resizer { + width: 5px; + height: 100%; + background-color: rgba(255, 255, 255, 0.1); + position: absolute; + right: 0; + top: 0; + cursor: ew-resize; + transition: background-color 0.2s ease; + z-index: 1000; +} + +.sidebar-resizer:hover { + background-color: rgba(255, 255, 255, 0.3); } .menu-item-header { display: flex; align-items: center; - /* Выравниваем элементы по центру */ padding: 10px; border-radius: 5px; cursor: pointer; @@ -55,7 +68,6 @@ .menu-item-header:hover { background-color: rgba(255, 255, 255, 0.1); - /* Легкий эффект при наведении */ } /* Круглый индикатор статуса */ @@ -63,35 +75,8 @@ width: 10px; height: 10px; border-radius: 50%; - /* Делаем круглым */ margin-right: 10px; - /* Отступ от текста */ flex-shrink: 0; - /* Запрещаем сжатие */ -} - -/* Анимация мигания для красного индикатора */ -@keyframes blink { - 0% { - opacity: 1; - } - - /* Полная видимость */ - 50% { - opacity: 0.3; - } - - /* Полупрозрачность */ - 100% { - opacity: 1; - } - - /* Полная видимость */ -} - -.status-indicator.blinking { - animation: blink 1s infinite; - /* Бесконечная анимация с интервалом 1 секунда */ } /* Подменю */ @@ -103,22 +88,18 @@ /* Футер сайдбара */ .sidebar-footer { padding: 10px; - background-color: #3d74c7; + background-color: var(--sidebar-color); text-align: center; border-top: 1px solid rgba(255, 255, 255, 0.1); - /* Разделительная линия */ flex-shrink: 0; - /* Запрещаем сжатие */ width: 100%; - /* Ширина на всю ширину сайдбара */ } .help, .settings { - color: white; + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ margin: 5px 0; - /* Отступы между элементами */ overflow-x: hidden; - /* Убираем горизонтальную прокрутку */ text-align: left; } \ No newline at end of file diff --git a/src/Style/TreeTable.css b/src/Style/TreeTable.css index d2093b1..10eb027 100644 --- a/src/Style/TreeTable.css +++ b/src/Style/TreeTable.css @@ -1,58 +1,48 @@ -/* Контейнер для таблицы с прокруткой */ -.table-container { +.tree-table-container { width: 100%; - /* Занимает всю доступную ширину */ overflow-x: auto; - /* Горизонтальная прокрутка при необходимости */ - margin: 0 auto; - /* Центрирование контейнера */ } -/* Стили для таблицы */ .tree-table { - width: auto; - /* Автоматическая ширина, чтобы таблица могла расширяться */ - min-width: 95%; - /* Минимальная ширина таблицы */ + width: 100%; border-collapse: collapse; - margin: 0 auto; - /* Центрирование таблицы */ + text-align: center; + table-layout: fixed; + background-color: var(--table-cell-background); + color: var(--table-text-color); + /* Используем переменную для цвета текста */ } -/* Заголовки таблицы (первый уровень) */ -.tree-table th { - border: 1px solid #ddd; - padding: 8px; - text-align: left; - white-space: nowrap; - /* Запрет на перенос текста */ - font-weight: bold; - /* Жирный шрифт для заголовков */ -} - -/* Подзаголовки (второй уровень: "АО" и "ПО") */ -.tree-table-subheader { - font-weight: 500; - /* Жирный шрифт для подзаголовков */ -} - -/* Ячейки таблицы */ -.tree-table td { - border: 1px solid #ddd; - padding: 8px; - text-align: left; - white-space: nowrap; - /* Запрет на перенос текста */ - font-weight: normal; - /* Обычный шрифт для ячеек */ -} - -/* Цвет фона для заголовков */ .tree-table-header { - background-color: #f4f4f4; + padding: 10px; + border: 1px solid var(--table-border); + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + background-color: var(--table-header-background); } -/* Чередование цвета строк */ -.tree-table-row:nth-child(even) { - background-color: #f9f9f9; +.tree-table-cell { + padding: 8px; + border: 1px solid var(--table-border); + white-space: nowrap; + overflow: hidden; +} + +.cell-content, +.header-content { + display: flex; + align-items: center; + gap: 2px; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +.status-indicator-bar { + width: 6px; + height: 20px; + border-radius: 3px; + flex-shrink: 0; } \ No newline at end of file diff --git a/src/Style/common.css b/src/Style/common.css index d170447..d0184b1 100644 --- a/src/Style/common.css +++ b/src/Style/common.css @@ -1,72 +1,53 @@ -/* src/Style/common.css */ - /* Контейнер для вкладок */ .tabs { display: flex; gap: 5px; - /* Расстояние между вкладками */ padding: 5px; - background-color: #3d74c7; - /* Цвет фона */ - border-bottom: 2px solid #195fc9; - /* Линия под вкладками */ + background-color: var(--sidebar-color); + border-bottom: 2px solid var(--accent-color); overflow-x: auto; - /* Прокрутка, если вкладок много */ border-radius: 5px; - /* Скругление углов */ white-space: nowrap; - /* Запрет переноса текста */ } /* Стили для отдельной вкладки */ .tab { display: flex; align-items: center; - background-color: #3d74c7; - /* Цвет фона вкладки */ - color: white; - /* Цвет текста */ + background-color: var(--sidebar-color); + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ padding: 5px 15px; - /* Отступы внутри вкладки */ border-radius: 5px 5px 0 0; - /* Скругление углов */ cursor: pointer; - /* Курсор при наведении */ flex-shrink: 0; - /* Запрет сжатия */ transition: background-color 0.3s ease; - /* Плавное изменение цвета */ } /* Активная вкладка */ .tab.active { - background-color: #195fc9; - /* Цвет фона активной вкладки */ + background-color: var(--accent-color); } /* Кнопка закрытия вкладки */ .close-tab { background: none; border: none; - color: white; - /* Цвет крестика */ + color: var(--sidebar-text-color); + /* Используем переменную для цвета текста */ cursor: pointer; font-size: 16px; margin-left: 10px; - /* Отступ от текста */ padding: 0; transition: color 0.3s ease; - /* Плавное изменение цвета */ } /* Эффект при наведении на кнопку закрытия */ .close-tab:hover { color: #ff6b6b; - /* Цвет крестика при наведении */ } /* Эффект при наведении на вкладку */ .tab:hover { - background-color: #195fc9; - /* Цвет фона при наведении */ + background-color: var(--accent-hover-color); } \ No newline at end of file diff --git a/src/Style/dark-theme.css b/src/Style/dark-theme.css new file mode 100644 index 0000000..16709fb --- /dev/null +++ b/src/Style/dark-theme.css @@ -0,0 +1,19 @@ +/* Темная тема, если пользователь предпочитает ее */ +@media (prefers-color-scheme: dark) { + :root { + --background-color: #1E1E1E; + --text-color: #E0E0E0; + /* Основной цвет текста (светлый) */ + --sidebar-color: #2d2d2d; + /* Темный цвет сайдбара */ + --sidebar-text-color: #E0E0E0; + /* Светлый текст в сайдбаре */ + --modal-background: #333333; + --modal-text: #FFFFFF; + --table-border: #444444; + --table-header-background: #2d2d2d; + --table-cell-background: #333333; + --table-text-color: #E0E0E0; + /* Светлый текст в таблице */ + } +} \ No newline at end of file diff --git a/src/Style/light-theme.css b/src/Style/light-theme.css new file mode 100644 index 0000000..6a43ada --- /dev/null +++ b/src/Style/light-theme.css @@ -0,0 +1,17 @@ +/* Светлая тема по умолчанию */ +:root { + --background-color: #FFFFFF; + --text-color: #333333; + /* Основной цвет текста (черный) */ + --sidebar-color: #3d74c7; + /* Синий цвет сайдбара */ + --sidebar-text-color: #FFFFFF; + /* Белый текст в сайдбаре и вкладках */ + --modal-background: #FFFFFF; + --modal-text: #333333; + --table-border: #ddd; + --table-header-background: #f9f9f9; + --table-cell-background: #FFFFFF; + --table-text-color: #000000; + /* Черный текст в таблице */ +} \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx index b9a1a6d..f802dc0 100755 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,6 +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'; // Подключаем темную тему createRoot(document.getElementById('root')).render(