Изменил меню, улучшил наполнение вкладок, улучшил графики

pull/8/head
DmitriyA 2025-03-06 03:29:46 -05:00
parent 90d6565a5a
commit a01d0972f5
5 changed files with 196 additions and 68 deletions

View File

@ -33,7 +33,7 @@ const DateRangeSelector = ({ onDateChange }) => {
}; };
return ( return (
<div style={{ marginBottom: '20px' }}> <div style={{ marginBottom: '20px', position: 'relative' }}>
<DatePicker <DatePicker
selectsRange selectsRange
startDate={startDate} startDate={startDate}
@ -41,6 +41,15 @@ const DateRangeSelector = ({ onDateChange }) => {
onChange={handleDateChange} onChange={handleDateChange}
isClearable isClearable
dateFormat="yyyy-MM-dd" dateFormat="yyyy-MM-dd"
popperPlacement="right-start" // Открывать справа от поля ввода
popperModifiers={[
{
name: 'offset',
options: {
offset: [0, 10], // Смещение календаря относительно поля ввода
},
},
]}
/> />
</div> </div>
); );

View File

@ -7,23 +7,46 @@ const MenuItem = ({ item, onSelectItem, sidebarWidth }) => {
const hasChildren = Array.isArray(item.items) && item.items.length > 0; const hasChildren = Array.isArray(item.items) && item.items.length > 0;
const statusColor = getStatusColor(item.status); const statusColor = getStatusColor(item.status);
const handleClick = () => { // Обработчик одинарного клика (разворачивание/сворачивание или открытие элемента)
const handleSingleClick = () => {
if (hasChildren) { if (hasChildren) {
setIsOpen(!isOpen); setIsOpen(!isOpen); // Разворачиваем/сворачиваем дочерние элементы
} else { } else {
onSelectItem(item); onSelectItem(item); // Если нет потомков, открываем элемент как вкладку
} }
}; };
// Обработчик клика для открытия родителя
const handleOpenParent = (e) => {
e.stopPropagation(); // Останавливаем всплытие события, чтобы не сработал handleSingleClick
onSelectItem(item); // Открываем родителя
};
return ( return (
<div className="menu-item" style={{ width: sidebarWidth - 20 }}> {/* Динамическая ширина */} <div className="menu-item" style={{ width: sidebarWidth - 20 }}> {/* Динамическая ширина */}
<div onClick={handleClick} className="menu-item-header"> <div
onClick={handleSingleClick} // Одинарный клик для разворачивания/сворачивания или открытия
className="menu-item-header"
>
{/* Круглый индикатор статуса */} {/* Круглый индикатор статуса */}
<div <div
className={`status-indicator ${statusColor === "red" ? "blinking" : ""}`} className={`status-indicator ${statusColor === "red" ? "blinking" : ""}`}
style={{ backgroundColor: statusColor }} style={{ backgroundColor: statusColor }}
/> />
<span>{item.title}</span> <span>{item.title}</span>
{/* Иконка для открытия родителя */}
{hasChildren && (
<span
onClick={handleOpenParent}
className="open-parent-icon"
title="Открыть родителя"
>
📂
</span>
)}
{/* Иконка для разворачивания/сворачивания */}
{hasChildren && <span>{isOpen ? "▲" : "▼"}</span>} {hasChildren && <span>{isOpen ? "▲" : "▼"}</span>}
</div> </div>
{isOpen && hasChildren && ( {isOpen && hasChildren && (

View File

@ -2,34 +2,42 @@ import React, { lazy, Suspense } from "react";
const PrometheusChart = lazy(() => import('../../Charts/PrometheusChart')); const PrometheusChart = lazy(() => import('../../Charts/PrometheusChart'));
// Вкладки, для которых нужно отобразить график // Функция для генерации названия метрики на основе id
const tabsWithCharts = ["188", "189"]; const getMetricName = (id) => {
return `zvks_apiforsnmp_measure_${id}`;
// Маппинг id на метрики
const metricMapping = {
188: "zvks_apiforsnmp_measure_277",
189: "zvks_apiforsnmp_measure_36",
}; };
const generateTabContent = (data) => { // Функция для рекурсивного сбора всех id потомков
const getAllChildIds = (node) => {
let ids = [];
if (node.id) {
ids.push(node.id); // Добавляем id текущего узла
}
if (node.items && node.items.length > 0) {
node.items.forEach((child) => {
ids = ids.concat(getAllChildIds(child)); // Рекурсивно собираем id потомков
});
}
return ids;
};
const tabContent = (data) => {
const tabContent = {}; const tabContent = {};
console.log("jsonData:", data);
console.log("jsonData.items:", data.items);
const generateContent = (nodes) => { const generateContent = (nodes) => {
nodes.forEach((node) => { nodes.forEach((node) => {
console.log("Обрабатываем узел:", node);
// Если у узла есть вложенные элементы, рекурсивно обрабатываем их // Если у узла есть вложенные элементы, рекурсивно обрабатываем их
if (node.items && node.items.length > 0) { if (node.items && node.items.length > 0) {
console.log("Идём вглубь:", node.items);
generateContent(node.items); generateContent(node.items);
} }
// Если у узла есть id, добавляем его в tabContent // Если у узла есть id, добавляем его в tabContent
if (node.id) { if (node.id) {
console.log("Добавляем в tabContent:", node.id);
let content = ( let content = (
<div> <div>
@ -38,13 +46,32 @@ const generateTabContent = (data) => {
</div> </div>
); );
// Если id узла есть в списке tabsWithCharts, добавляем график // Если у узла есть потомки, добавляем графики для всех потомков
if (tabsWithCharts.includes(node.id)) { if (node.items && node.items.length > 0) {
console.log("Добавляем график для:", node.id); const childIds = getAllChildIds(node); // Получаем все id потомков
const charts = childIds.map((id) => {
const metricName = getMetricName(id);
return (
<div key={id}>
<h3>{node.title} - {id}</h3>
<Suspense fallback={<div>Загрузка графика...</div>}>
<PrometheusChart metricName={metricName} />
</Suspense>
</div>
);
});
// Получаем метрику для текущего id content = (
const metricName = metricMapping[node.id]; <div>
<h2>{node.title}</h2>
<p>Контент для {node.title}.</p>
{charts}
</div>
);
} else {
// Если у узла нет потомков, добавляем график для него
const metricName = getMetricName(node.id);
content = ( content = (
<div> <div>
<h2>{node.title}</h2> <h2>{node.title}</h2>
@ -75,4 +102,4 @@ const generateTabContent = (data) => {
return tabContent; return tabContent;
}; };
export default generateTabContent; // Экспортируем только функцию export default tabContent; // Экспортируем только функцию

View File

@ -43,16 +43,17 @@ const TreeTable = ({ data }) => {
// Функция для отображения строк с вложенными элементами // Функция для отображения строк с вложенными элементами
const renderRows = (data) => { const renderRows = (data) => {
const rows = [];
// Находим максимальное количество элементов среди всех "АО" и "ПО"
const maxItems = Math.max( const maxItems = Math.max(
...data.map((item) => ...data.flatMap((item) => [
Math.max( item.items[0]?.items?.length || 0, // АО
item.items[0]?.items?.length || 0, // АО item.items[1]?.items?.length || 0 // ПО
item.items[1]?.items?.length || 0 // ПО ])
)
)
); );
const rows = []; // Генерируем строки
for (let i = 0; i < maxItems; i++) { for (let i = 0; i < maxItems; i++) {
rows.push( rows.push(
<tr key={i} className="tree-table-row"> <tr key={i} className="tree-table-row">
@ -72,5 +73,4 @@ const renderRows = (data) => {
return rows; return rows;
}; };
export default TreeTable; export default TreeTable;

View File

@ -1,60 +1,129 @@
/* DatePicker.css */
.react-datepicker-wrapper { .react-datepicker-wrapper {
width: 100%; 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 { .react-datepicker {
position: absolute !important; font-family: 'Segoe UI', sans-serif;
z-index: 1000 !important; border: 1px solid #e0e0e0;
width: auto; border-radius: 8px;
max-width: 300px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
background-color: white; background-color: #fff;
border: 1px solid #ccc; /* Непрозрачный фон */
border-radius: 4px; z-index: 1000;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* Календарь поверх других элементов */
}
.react-datepicker-popper {
z-index: 1000;
/* Календарь поверх других элементов */
pointer-events: auto;
/* Разрешить взаимодействие только с календарем */
} }
.react-datepicker__header { .react-datepicker__header {
background-color: #f0f0f0; background-color: #f8f8f8;
border-bottom: 1px solid #ccc; /* Непрозрачный фон заголовка */
border-radius: 4px 4px 0 0; border-bottom: 1px solid #e0e0e0;
padding: 8px; border-radius: 8px 8px 0 0;
padding: 12px;
} }
.react-datepicker__current-month { .react-datepicker__current-month {
font-size: 1.1em; font-size: 14px;
font-weight: bold; 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 { .react-datepicker__day {
padding: 5px; width: 28px;
margin: 2px; line-height: 28px;
border-radius: 4px; text-align: center;
} font-size: 12px;
color: #333;
.react-datepicker__day--selected, cursor: pointer;
.react-datepicker__day--keyboard-selected { border-radius: 50%;
background-color: #3e95cd; transition: background-color 0.2s ease, color 0.2s ease;
color: white;
} }
.react-datepicker__day:hover { .react-datepicker__day:hover {
background-color: #f0f0f0; background-color: #f0f0f0;
} }
.react-datepicker__navigation { .react-datepicker__day--selected {
top: 10px; background-color: #0078d4;
color: #fff;
} }
.react-datepicker__navigation--previous { .react-datepicker__day--selected:hover {
border-right-color: #333; background-color: #005bb5;
} }
.react-datepicker__navigation--next { .react-datepicker__day--outside-month {
border-left-color: #333; color: #ccc;
} }
/* Исправление странного появления в центре экрана */ .react-datepicker__day--disabled {
.react-datepicker-popper { color: #ccc;
z-index: 9999 !important; cursor: not-allowed;
transform: translate3d(0px, 0px, 0px) !important;
} }