redesign and fix graphics
test-org/trust-module-frontend/pipeline/pr-rc This commit looks good
Details
test-org/trust-module-frontend/pipeline/pr-rc This commit looks good
Details
parent
ed2e03e202
commit
bd96278895
|
|
@ -1,9 +1,11 @@
|
|||
import React, { useState } from 'react';
|
||||
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer } from 'recharts';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer, ReferenceArea } from 'recharts';
|
||||
|
||||
const LineChartComponent = ({ chartData, metricName, colors, description, onRangeSelect, filteredData }) => {
|
||||
const [selectionStart, setSelectionStart] = useState(null);
|
||||
const [selectionEnd, setSelectionEnd] = useState(null);
|
||||
const [selectionArea, setSelectionArea] = useState(null);
|
||||
const [isSelecting, setIsSelecting] = useState(false);
|
||||
const chartRef = useRef(null);
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const allTimes = Object.values(chartData)
|
||||
.flat()
|
||||
|
|
@ -21,27 +23,48 @@ const LineChartComponent = ({ chartData, metricName, colors, description, onRang
|
|||
|
||||
const displayData = filteredData || data;
|
||||
|
||||
const handleClick = (e) => {
|
||||
// Блокировка выделения текста при перетаскивании
|
||||
useEffect(() => {
|
||||
const handleSelectStart = (e) => {
|
||||
if (isSelecting) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('selectstart', handleSelectStart);
|
||||
return () => document.removeEventListener('selectstart', handleSelectStart);
|
||||
}, [isSelecting]);
|
||||
|
||||
const handleMouseDown = (e) => {
|
||||
if (!e || !e.activeLabel) return;
|
||||
|
||||
const clickedTime = e.activeLabel;
|
||||
|
||||
if (!selectionStart) {
|
||||
setSelectionStart(clickedTime);
|
||||
} else if (!selectionEnd) {
|
||||
setSelectionEnd(clickedTime);
|
||||
|
||||
const startIndex = data.findIndex(point => point.time === selectionStart);
|
||||
const endIndex = data.findIndex(point => point.time === clickedTime);
|
||||
|
||||
onRangeSelect({ startIndex, endIndex });
|
||||
|
||||
setSelectionStart(null);
|
||||
setSelectionEnd(null);
|
||||
}
|
||||
setIsSelecting(true);
|
||||
setSelectionArea({ start: e.activeLabel, end: null });
|
||||
};
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
if (!selectionArea?.start || !e?.activeLabel) return;
|
||||
setSelectionArea(prev => ({ ...prev, end: e.activeLabel }));
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsSelecting(false);
|
||||
|
||||
if (!selectionArea?.start || !selectionArea?.end) {
|
||||
setSelectionArea(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const startIndex = data.findIndex(point => point.time === selectionArea.start);
|
||||
const endIndex = data.findIndex(point => point.time === selectionArea.end);
|
||||
|
||||
onRangeSelect({
|
||||
startIndex: Math.min(startIndex, endIndex),
|
||||
endIndex: Math.max(startIndex, endIndex)
|
||||
});
|
||||
|
||||
setSelectionArea(null);
|
||||
};
|
||||
|
||||
// Упрощенный Tooltip без указания instance
|
||||
const CustomTooltip = ({ active, payload, label }) => {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
|
|
@ -49,10 +72,15 @@ const LineChartComponent = ({ chartData, metricName, colors, description, onRang
|
|||
backgroundColor: '#fff',
|
||||
padding: '10px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px'
|
||||
borderRadius: '4px',
|
||||
boxShadow: '0 2px 5px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<p style={{ fontWeight: 'bold', marginBottom: '5px' }}>{`Время: ${label}`}</p>
|
||||
<p>{`Значение: ${payload[0].value}`}</p>
|
||||
{payload.map((item, index) => (
|
||||
<p key={index} style={{ color: item.color }}>
|
||||
{`${item.name}: ${item.value}`}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -60,12 +88,41 @@ const LineChartComponent = ({ chartData, metricName, colors, description, onRang
|
|||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{ position: 'relative' }}
|
||||
ref={containerRef}
|
||||
className={isSelecting ? 'no-selection' : ''}
|
||||
>
|
||||
<style>
|
||||
{`
|
||||
.no-selection {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
left: 10,
|
||||
backgroundColor: 'rgba(255,255,255,0.8)',
|
||||
padding: '5px 10px',
|
||||
borderRadius: 4,
|
||||
fontSize: 12,
|
||||
zIndex: 10,
|
||||
pointerEvents: 'none'
|
||||
}}>
|
||||
</div>
|
||||
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<LineChart
|
||||
data={displayData}
|
||||
onClick={handleClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
ref={chartRef}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||
<XAxis
|
||||
|
|
@ -81,7 +138,6 @@ const LineChartComponent = ({ chartData, metricName, colors, description, onRang
|
|||
content={<CustomTooltip />}
|
||||
cursor={{ stroke: '#ccc', strokeWidth: 1 }}
|
||||
/>
|
||||
{/* Убрали <Legend /> чтобы скрыть имена instance */}
|
||||
{Object.keys(chartData).map((key, index) => (
|
||||
<Line
|
||||
key={key}
|
||||
|
|
@ -91,9 +147,18 @@ const LineChartComponent = ({ chartData, metricName, colors, description, onRang
|
|||
strokeWidth={2}
|
||||
dot={false}
|
||||
activeDot={{ r: 6 }}
|
||||
// Убрали name чтобы не отображалось в tooltip
|
||||
name={key}
|
||||
/>
|
||||
))}
|
||||
|
||||
{selectionArea?.start && selectionArea?.end && (
|
||||
<ReferenceArea
|
||||
x1={selectionArea.start}
|
||||
x2={selectionArea.end}
|
||||
strokeOpacity={0.3}
|
||||
fill="#4a6baf"
|
||||
/>
|
||||
)}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ const PrometheusChart = ({ metricName }) => {
|
|||
else if (range <= 86400) step = 120;
|
||||
else step = 300;
|
||||
|
||||
const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/metrics`, {
|
||||
const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/api/metrics`, {
|
||||
params: {
|
||||
metric: metricName,
|
||||
start,
|
||||
|
|
@ -100,7 +100,7 @@ const PrometheusChart = ({ metricName }) => {
|
|||
step
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const result = response.data;
|
||||
let metrics = Array.isArray(result) ? result : result.data || [];
|
||||
|
||||
|
|
@ -161,10 +161,17 @@ const PrometheusChart = ({ metricName }) => {
|
|||
const handleRangeChange = (event) => {
|
||||
const selectedValue = event.target.value;
|
||||
const range = TIME_RANGES.find(range => range.value === parseInt(selectedValue, 10));
|
||||
setSelectedRange(range);
|
||||
|
||||
// Принудительно сбрасываем состояние
|
||||
setSelectedGraphRange(null);
|
||||
setFilteredData(null);
|
||||
|
||||
// Обновляем диапазон
|
||||
setSelectedRange({ ...range }); // Создаем новый объект, чтобы React увидел изменение
|
||||
setUseCustomRange(false);
|
||||
setSelectedGraphRange(null); // Сбрасываем выбранный диапазон
|
||||
setFilteredData(null); // Сбрасываем отфильтрованные данные
|
||||
|
||||
// Принудительно обновляем данные
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const handleCustomRangeChange = () => {
|
||||
|
|
@ -173,6 +180,12 @@ const PrometheusChart = ({ metricName }) => {
|
|||
setFilteredData(null); // Сбрасываем отфильтрованные данные
|
||||
};
|
||||
|
||||
const handleResetZoom = () => {
|
||||
setSelectedGraphRange(null);
|
||||
setFilteredData(null);
|
||||
fetchData(); // Принудительно обновляем данные
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedGraphRange) {
|
||||
const { startIndex, endIndex } = selectedGraphRange;
|
||||
|
|
@ -236,32 +249,56 @@ const PrometheusChart = ({ metricName }) => {
|
|||
marginBottom: '15px'
|
||||
}}>
|
||||
{/* Стандартные диапазоны */}
|
||||
<div style={{ flex: '1 1 200px' }}>
|
||||
<label htmlFor="time-range" style={{
|
||||
display: 'block',
|
||||
marginBottom: '5px',
|
||||
fontWeight: '500',
|
||||
color: '#555'
|
||||
}}>Стандартные диапазоны:</label>
|
||||
<select
|
||||
id="time-range"
|
||||
value={selectedRange.value}
|
||||
onChange={handleRangeChange}
|
||||
<div style={{ flex: '1 1 200px', display: 'flex', gap: '10px', alignItems: 'flex-end' }}>
|
||||
<div style={{ flex: '1' }}>
|
||||
<label htmlFor="time-range" style={{
|
||||
display: 'block',
|
||||
marginBottom: '5px',
|
||||
fontWeight: '500',
|
||||
color: '#555'
|
||||
}}>Стандартные диапазоны:</label>
|
||||
<select
|
||||
id="time-range"
|
||||
value={selectedRange.value}
|
||||
onChange={handleRangeChange}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '8px 12px',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #ddd',
|
||||
color: "#333",
|
||||
backgroundColor: '#f9f9f9'
|
||||
}}
|
||||
>
|
||||
{TIME_RANGES.map(range => (
|
||||
<option key={range.value} value={range.value}>{range.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Кнопка сброса */}
|
||||
<button
|
||||
onClick={handleResetZoom}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '8px 12px',
|
||||
borderRadius: '4px',
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#f0f0f0',
|
||||
color: '#333',
|
||||
border: '1px solid #ddd',
|
||||
color: "#333",
|
||||
backgroundColor: '#f9f9f9'
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
height: '36px',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
onMouseOver={(e) => e.target.style.backgroundColor = '#e0e0e0'}
|
||||
onMouseOut={(e) => e.target.style.backgroundColor = '#f0f0f0'}
|
||||
>
|
||||
{TIME_RANGES.map(range => (
|
||||
<option key={range.value} value={range.value}>{range.label}</option>
|
||||
))}
|
||||
</select>
|
||||
Сбросить
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Кастомный диапазон */}
|
||||
<div style={{ flex: '1 1 300px' }}>
|
||||
<div style={{
|
||||
|
|
@ -351,7 +388,7 @@ const PrometheusChart = ({ metricName }) => {
|
|||
Текущий диапазон: {useCustomRange
|
||||
? `${startDate.toLocaleString()} - ${endDate.toLocaleString()}`
|
||||
: selectedRange.label}
|
||||
</div>
|
||||
</div >
|
||||
|
||||
{/* График */}
|
||||
<LineChartComponent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
|
||||
const StyledButton = styled(Button)(({ theme }) => ({
|
||||
margin: theme.spacing(1),
|
||||
// Дополнительные стили
|
||||
}));
|
||||
|
||||
const CustomButton = ({
|
||||
children,
|
||||
variant = 'contained',
|
||||
color = 'primary',
|
||||
loading = false,
|
||||
startIcon,
|
||||
endIcon,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<StyledButton
|
||||
variant={variant}
|
||||
color={color}
|
||||
startIcon={startIcon && !loading ? startIcon : undefined}
|
||||
endIcon={endIcon && !loading ? endIcon : undefined}
|
||||
disabled={loading}
|
||||
{...props}
|
||||
>
|
||||
{loading ? <CircularProgress size={24} /> : children}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomButton;
|
||||
|
|
@ -17,7 +17,7 @@ const LoginModal = ({ onLogin, onClose }) => {
|
|||
|
||||
try {
|
||||
// Отправляем данные на бэкенд
|
||||
const response = await fetch(`${import.meta.env.VITE_BACK_URL}/auth/login`, {
|
||||
const response = await fetch(`${import.meta.env.VITE_BACK_URL}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
|
|
@ -1,12 +1,27 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import "../../Style/TreeTable.css";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Button,
|
||||
Collapse,
|
||||
Box,
|
||||
Typography,
|
||||
useTheme,
|
||||
Tooltip
|
||||
} from '@mui/material';
|
||||
import { statusManager1, statusManager2 } from "../TreeChart/dataUtils";
|
||||
|
||||
const TreeTable = ({ data }) => {
|
||||
const theme = useTheme();
|
||||
const tableRef = useRef(null);
|
||||
const [fontSize, setFontSize] = useState(16);
|
||||
const [log, setLog] = useState([]);
|
||||
const [isLogVisible, setIsLogVisible] = useState(true);
|
||||
const [isLogVisible, setIsLogVisible] = useState(false);
|
||||
|
||||
const adjustFontSize = () => {
|
||||
if (tableRef.current) {
|
||||
|
|
@ -27,6 +42,13 @@ const TreeTable = ({ data }) => {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
adjustFontSize();
|
||||
window.addEventListener('resize', adjustFontSize);
|
||||
return () => window.removeEventListener('resize', adjustFontSize);
|
||||
}, [data]);
|
||||
|
||||
// Логирование статусов
|
||||
useEffect(() => {
|
||||
const newLog = [];
|
||||
const traverse = (items) => {
|
||||
|
|
@ -35,7 +57,7 @@ const TreeTable = ({ data }) => {
|
|||
newLog.push({
|
||||
title: item.title,
|
||||
status: item.status,
|
||||
time: new Date().toLocaleTimeString(), // Добавляем время
|
||||
time: new Date().toLocaleTimeString(),
|
||||
});
|
||||
}
|
||||
if (item.items) {
|
||||
|
|
@ -44,204 +66,285 @@ const TreeTable = ({ data }) => {
|
|||
});
|
||||
};
|
||||
traverse(data.items);
|
||||
|
||||
// Ограничиваем количество сообщений до 50
|
||||
setLog((prevLog) => [...newLog, ...prevLog].slice(0, 50));
|
||||
setLog(prevLog => [...newLog, ...prevLog].slice(0, 50));
|
||||
}, [data]);
|
||||
|
||||
const filteredData = data.items.filter((item) => item.title !== "Функциональные задачи");
|
||||
const filteredData = data.items.filter(item => item.title !== "Функциональные задачи");
|
||||
|
||||
// Функция для отображения заголовков
|
||||
const renderHeaders = (items) => {
|
||||
// Компонент индикаторов статуса
|
||||
const StatusIndicators = ({ status }) => (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: '4px',
|
||||
height: '20px',
|
||||
display: 'inline-block',
|
||||
backgroundColor: statusManager1.getStatusColor(status),
|
||||
marginRight: '4px',
|
||||
verticalAlign: 'middle'
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: '4px',
|
||||
height: '20px',
|
||||
display: 'inline-block',
|
||||
backgroundColor: statusManager2.getStatusColor(status),
|
||||
marginRight: '8px',
|
||||
verticalAlign: 'middle'
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
// Ячейка с тултипом
|
||||
const TableCellWithTooltip = ({ children, title, ...props }) => (
|
||||
<Tooltip title={title} arrow>
|
||||
<TableCell {...props}>
|
||||
{children}
|
||||
</TableCell>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
// Рендер заголовков (первый уровень)
|
||||
const renderMainHeaders = (items) => {
|
||||
return items.map((item) => {
|
||||
const colSpan = item.items ? item.items.length : 1;
|
||||
return (
|
||||
<th key={item.id} colSpan={colSpan} className="tree-table-header" title={item.title}>
|
||||
<div className="header-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(item.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(item.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
<TableCellWithTooltip
|
||||
key={item.id}
|
||||
colSpan={colSpan}
|
||||
align="center"
|
||||
title={item.title}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: '8px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
>
|
||||
<StatusIndicators status={item.status} />
|
||||
<Typography component="span" variant="subtitle2" noWrap>
|
||||
{item.title}
|
||||
</div>
|
||||
</th>
|
||||
</Typography>
|
||||
</TableCellWithTooltip>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Функция для отображения подзаголовков
|
||||
// Рендер подзаголовков (второй уровень)
|
||||
const renderSubHeaders = (items) => {
|
||||
return items.map((item) => {
|
||||
return items.flatMap((item) => {
|
||||
if (item.items) {
|
||||
return item.items.map((child) => (
|
||||
<th key={child.id} className="tree-table-header" title={child.title}>
|
||||
<div className="header-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(child.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(child.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
<TableCellWithTooltip
|
||||
key={child.id}
|
||||
align="center"
|
||||
title={child.title}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: '8px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
>
|
||||
<StatusIndicators status={child.status} />
|
||||
<Typography component="span" variant="subtitle2" noWrap>
|
||||
{child.title}
|
||||
</div>
|
||||
</th>
|
||||
</Typography>
|
||||
</TableCellWithTooltip>
|
||||
));
|
||||
} else {
|
||||
return (
|
||||
<th key={item.id} className="tree-table-header" title={item.title}>
|
||||
<div className="header-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(item.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(item.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
{item.title}
|
||||
</div>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TableCellWithTooltip
|
||||
key={item.id}
|
||||
align="center"
|
||||
title={item.title}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: '8px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
>
|
||||
<StatusIndicators status={item.status} />
|
||||
<Typography component="span" variant="subtitle2" noWrap>
|
||||
{item.title}
|
||||
</Typography>
|
||||
</TableCellWithTooltip>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Функция для отображения данных
|
||||
const renderData = (items) => {
|
||||
return items.map((item) => {
|
||||
// Рендер данных (третий уровень)
|
||||
const renderDataCells = (items) => {
|
||||
return items.flatMap((item) => {
|
||||
if (item.items) {
|
||||
return item.items.map((child) => {
|
||||
return item.items.flatMap((child) => {
|
||||
if (child.items) {
|
||||
return child.items.map((subChild) => (
|
||||
<td key={subChild.id} className="tree-table-cell" title={subChild.title}>
|
||||
<div className="cell-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(subChild.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(subChild.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
<span className="cell-text">{subChild.title}</span>
|
||||
</div>
|
||||
</td>
|
||||
));
|
||||
} else {
|
||||
return (
|
||||
<td key={child.id} className="tree-table-cell" title={child.title}>
|
||||
<div className="cell-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(child.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(child.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
<span className="cell-text">{child.title}</span>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return (
|
||||
<td key={item.id} className="tree-table-cell" title={item.title}>
|
||||
<div className="cell-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(item.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(item.status),
|
||||
marginLeft: "5px",
|
||||
<TableCellWithTooltip
|
||||
key={subChild.id}
|
||||
title={subChild.title}
|
||||
sx={{
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: '8px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
/>
|
||||
<span className="cell-text">{item.title}</span>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
>
|
||||
<StatusIndicators status={subChild.status} />
|
||||
<Typography component="span" variant="body2" noWrap>
|
||||
{subChild.title}
|
||||
</Typography>
|
||||
</TableCellWithTooltip>
|
||||
));
|
||||
}
|
||||
return (
|
||||
<TableCellWithTooltip
|
||||
key={child.id}
|
||||
title={child.title}
|
||||
sx={{
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: '8px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
>
|
||||
<StatusIndicators status={child.status} />
|
||||
<Typography component="span" variant="body2" noWrap>
|
||||
{child.title}
|
||||
</Typography>
|
||||
</TableCellWithTooltip>
|
||||
);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<TableCellWithTooltip
|
||||
key={item.id}
|
||||
title={item.title}
|
||||
sx={{
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: '8px',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}}
|
||||
>
|
||||
<StatusIndicators status={item.status} />
|
||||
<Typography component="span" variant="body2" noWrap>
|
||||
{item.title}
|
||||
</Typography>
|
||||
</TableCellWithTooltip>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tree-table-container">
|
||||
<table ref={tableRef} className="tree-table" style={{ fontSize: `${fontSize}px` }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
colSpan={filteredData.reduce((acc, item) => acc + (item.items ? item.items.length : 1), 0)}
|
||||
className="tree-table-header"
|
||||
title={data.title}
|
||||
>
|
||||
<div className="header-content">
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{ backgroundColor: statusManager1.getStatusColor(data.status) }}
|
||||
/>
|
||||
<div
|
||||
className="status-indicator-bar"
|
||||
style={{
|
||||
backgroundColor: statusManager2.getStatusColor(data.status),
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
/>
|
||||
{data.title}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>{renderHeaders(filteredData)}</tr>
|
||||
<tr>{renderSubHeaders(filteredData)}</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="tree-table-row">{renderData(filteredData)}</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button
|
||||
onClick={() => setIsLogVisible(!isLogVisible)}
|
||||
className="toggle-log-button"
|
||||
style={{ marginTop: "10px" }}
|
||||
>
|
||||
{isLogVisible ? "Скрыть лог" : "Показать лог"}
|
||||
</button>
|
||||
{isLogVisible && (
|
||||
<div className="status-log">
|
||||
<h3>Лог статусов</h3>
|
||||
<ul>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
ref={tableRef}
|
||||
sx={{
|
||||
fontSize: `${fontSize}px`,
|
||||
width: '100%',
|
||||
'& .MuiTableCell-root': {
|
||||
py: 1,
|
||||
px: 2
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Table sx={{ width: '100%', tableLayout: 'fixed' }}>
|
||||
<TableHead>
|
||||
{/* Основной заголовок таблицы */}
|
||||
<TableRow>
|
||||
<TableCellWithTooltip
|
||||
colSpan={filteredData.reduce((acc, item) => acc + (item.items ? item.items.length : 1), 0)}
|
||||
align="center"
|
||||
title={data.title}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: '8px'
|
||||
}}
|
||||
>
|
||||
<StatusIndicators status={data.status} />
|
||||
<Typography component="span" variant="subtitle1" fontWeight="bold" noWrap>
|
||||
{data.title}
|
||||
</Typography>
|
||||
</TableCellWithTooltip>
|
||||
</TableRow>
|
||||
|
||||
{/* Строка с основными заголовками */}
|
||||
<TableRow>
|
||||
{renderMainHeaders(filteredData)}
|
||||
</TableRow>
|
||||
|
||||
{/* Строка с подзаголовками (которая пропала в предыдущей версии) */}
|
||||
<TableRow>
|
||||
{renderSubHeaders(filteredData)}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
{renderDataCells(filteredData)}
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => setIsLogVisible(!isLogVisible)}
|
||||
size="small"
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
{isLogVisible ? 'Скрыть историю изменения статусов' : 'Показать историю изменения статусов'}
|
||||
</Button>
|
||||
|
||||
<Collapse in={isLogVisible}>
|
||||
<Box sx={{
|
||||
mt: 2,
|
||||
p: 2,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: 1,
|
||||
backgroundColor: theme.palette.background.paper
|
||||
}}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
История изменения статусов
|
||||
</Typography>
|
||||
<Box component="ul" sx={{
|
||||
pl: 2,
|
||||
maxHeight: 200,
|
||||
overflow: 'auto',
|
||||
listStyle: 'none'
|
||||
}}>
|
||||
{log.map((entry, index) => (
|
||||
<li key={index} style={{ color: statusManager1.getStatusColor(entry.status) }}>
|
||||
<Box
|
||||
component="li"
|
||||
key={index}
|
||||
sx={{
|
||||
py: 1,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
color: statusManager1.getStatusColor(entry.status)
|
||||
}}
|
||||
>
|
||||
[{entry.time}] {entry.status}: {entry.title}
|
||||
</li>
|
||||
</Box>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ export const lightTheme = createTheme({
|
|||
main: "#0f55bec2",
|
||||
},
|
||||
custom: {
|
||||
background: "#FFFFFF",
|
||||
background: "#025EA1",
|
||||
text: "#000000",
|
||||
sidebar: "#3d74c7",
|
||||
sidebar: "#025EA1",
|
||||
sidebarText: "#FFFFFF",
|
||||
modalBackground: "#FFFFFF",
|
||||
modalBtnBackground: "#0f55bec2",
|
||||
|
|
|
|||
Loading…
Reference in New Issue