Изменил меню, улучшил наполнение вкладок, улучшил графики
parent
90d6565a5a
commit
a01d0972f5
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 && (
|
||||||
|
|
|
||||||
|
|
@ -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; // Экспортируем только функцию
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue