diff --git a/src/Components/TreeChart/FlowChart.jsx b/src/Components/TreeChart/FlowChart.jsx index 082cdd6..d4d2370 100644 --- a/src/Components/TreeChart/FlowChart.jsx +++ b/src/Components/TreeChart/FlowChart.jsx @@ -1,15 +1,14 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import ReactFlow, { Controls, Background } from 'reactflow'; import 'reactflow/dist/style.css'; import { debounce } from 'lodash'; import { useFlowChart } from './FlowChartComponents/useFlowChart'; import { useNodeHandlers } from './FlowChartComponents/useNodeHandlers'; import { useDataParser } from './FlowChartComponents/DataParser'; -import NodeWrapper from './FlowChartComponents/NodeWrapper'; // Исправленный импорт +import NodeWrapper from './FlowChartComponents/NodeWrapper'; -// Определяем кастомные типы узлов const nodeTypes = { - customNode: NodeWrapper // Тип должен соответствовать тому, что вы указываете в parseData + customNode: NodeWrapper // Должно совпадать с type в useDataParser }; const FlowChart = ({ data }) => { @@ -27,6 +26,7 @@ const FlowChart = ({ data }) => { } = useFlowChart(data); const { parseData } = useDataParser(nodePositions, collapsedNodes); + const initialized = useRef(false); const debouncedSetNodePositions = useMemo( () => debounce(setNodePositions, 100), @@ -39,7 +39,24 @@ const FlowChart = ({ data }) => { const { nodes: initialNodes, edges: initialEdges } = parseData(data); setNodes(initialNodes); setEdges(initialEdges); - }, [data, parseData, setNodes, setEdges]); + + if (!initialized.current && data) { + const findAndCollapseLastLevelParents = (items) => { + items.forEach(item => { + if (item.items?.length > 0) { + const hasGrandchildren = item.items.some(child => child.items?.length > 0); + if (!hasGrandchildren) { + toggleNodeCollapse(item.id); + } else { + findAndCollapseLastLevelParents(item.items); + } + } + }); + }; + findAndCollapseLastLevelParents(data.items || []); + initialized.current = true; + } + }, [data, parseData, setNodes, setEdges, toggleNodeCollapse]); const onNodeClick = (event, node) => { if (node.data.hasChildren) { @@ -48,9 +65,7 @@ const FlowChart = ({ data }) => { }; useEffect(() => { - return () => { - debouncedSetNodePositions.cancel(); - }; + return () => debouncedSetNodePositions.cancel(); }, [debouncedSetNodePositions]); return ( diff --git a/src/Components/TreeChart/FlowChartComponents/DataParser.jsx b/src/Components/TreeChart/FlowChartComponents/DataParser.jsx index 73855cd..658f1ea 100644 --- a/src/Components/TreeChart/FlowChartComponents/DataParser.jsx +++ b/src/Components/TreeChart/FlowChartComponents/DataParser.jsx @@ -1,34 +1,7 @@ import { useCallback } from 'react'; import { isLeafNode } from './nodeUtils'; -import { getStatusColor } from '../dataUtils'; export const useDataParser = (nodePositions, collapsedNodes) => { - const getNodeStyle = useCallback((item, isLeaf) => ({ - width: isLeaf ? 40 : 50, - height: isLeaf ? 40 : 50, - borderRadius: '50%', - backgroundColor: getStatusColor(item.status), - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - color: 'black', - border: '2px solid #fff', - fontSize: isLeaf ? '0.8rem' : '1rem' - }), []); - - const getCenterNodeStyle = useCallback((item) => ({ - width: 60, - height: 60, - borderRadius: '50%', - backgroundColor: getStatusColor(item.status), - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - color: 'black', - border: '2px solid #fff', - fontSize: '1.2rem' - }), []); - const parseData = useCallback((data) => { if (!data) return { nodes: [], edges: [] }; @@ -39,7 +12,7 @@ export const useDataParser = (nodePositions, collapsedNodes) => { const baseLevelRadius = 150; const traverse = (item, parentId = null, level = 0, angleStart = 0, angleEnd = 2 * Math.PI, parentRadius = 0) => { - if (!item || collapsedNodes[parentId]) return; // Пропускаем свёрнутые узлы + if (!item || collapsedNodes[parentId]) return; const nodeId = item.id; const items = item.items || []; @@ -54,8 +27,15 @@ export const useDataParser = (nodePositions, collapsedNodes) => { const node = { id: nodeId, position, - style: getNodeStyle(item, isLeaf), - data: { label: item.title, hasChildren: items.length > 0, collapsed: collapsedNodes[nodeId] } + type: 'customNode', // Важно для кастомного рендеринга + data: { + label: item.title, + status: item.status, + hasChildren: items.length > 0, + collapsed: collapsedNodes[nodeId], + isLeaf, + isCenterNode: parentId === null // Центральный узел + } }; nodes.push(node); @@ -65,14 +45,16 @@ export const useDataParser = (nodePositions, collapsedNodes) => { id: `${parentId}-${nodeId}`, source: parentId, target: nodeId, - style: { stroke: isLeaf ? '#aaa' : '#666', strokeWidth: isLeaf ? 1 : 2 } + style: { + stroke: isLeaf ? '#aaa' : '#666', + strokeWidth: isLeaf ? 1 : 2 + } }); } if (!collapsedNodes[nodeId] && items.length > 0) { const spreadAngle = angleEnd - angleStart; items.forEach((child, index) => { - if (!child) return; const itemAngleStart = angleStart + (index / items.length) * spreadAngle; const itemAngleEnd = angleStart + ((index + 1) / items.length) * spreadAngle; traverse(child, nodeId, level + 1, itemAngleStart, itemAngleEnd, parentRadius + baseLevelRadius); @@ -80,25 +62,33 @@ export const useDataParser = (nodePositions, collapsedNodes) => { } }; + // Центральный узел const centerNode = { id: data.id, position: nodePositions[data.id] || { x: centerX, y: centerY }, - style: getCenterNodeStyle(data), - data: { label: data.title, hasChildren: data.items.length > 0, collapsed: collapsedNodes[data.id] } + type: 'customNode', + data: { + label: data.title, + status: data.status, + hasChildren: data.items.length > 0, + collapsed: collapsedNodes[data.id], + isLeaf: false, + isCenterNode: true + } }; nodes.push(centerNode); + // Обработка дочерних узлов if (!collapsedNodes[data.id] && data.items.length > 0) { const angleStep = (2 * Math.PI) / data.items.length; data.items.forEach((child, index) => { - if (!child) return; traverse(child, data.id, 1, index * angleStep, (index + 1) * angleStep, 0); }); } return { nodes, edges }; - }, [nodePositions, collapsedNodes, getNodeStyle, getCenterNodeStyle]); + }, [nodePositions, collapsedNodes]); return { parseData }; }; \ No newline at end of file diff --git a/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx b/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx index af1283d..7ee61a0 100644 --- a/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx +++ b/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx @@ -1,26 +1,63 @@ -import React, { memo } from 'react'; +import React from 'react'; +import { getStatusColor } from '../dataUtils'; + +const NodeWrapper = ({ id, data, selected }) => { + // Параметры стиля + const size = data.isCenterNode ? 80 : (data.isLeaf ? 60 : 70); + const fontSize = data.isCenterNode ? '1.2rem' : (data.isLeaf ? '0.8rem' : '1rem'); + const backgroundColor = getStatusColor(data.status); + + // Базовый стиль узла + const nodeStyle = { + width: size, + height: size, + borderRadius: '50%', + backgroundColor, + border: `2px solid ${selected ? '#1890ff' : '#fff'}`, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize, + color: '#000', + position: 'relative', + boxShadow: selected ? '0 0 8px rgba(24, 144, 255, 0.5)' : 'none', + transition: 'all 0.2s ease' + }; -const NodeWrapper = memo(({ id, data, selected, style }) => { return ( -
- {data.label} +
+
+ {data.label} +
+ {data.hasChildren && ( - {data.collapsed ? '+' : '-'} - +
)}
); -}); +}; -export default NodeWrapper; +export default React.memo(NodeWrapper); \ No newline at end of file