105 lines
2.8 KiB
TypeScript
105 lines
2.8 KiB
TypeScript
import React, { useRef, useEffect, useState } from "react";
|
||
import type {
|
||
GraphData,
|
||
GraphNode,
|
||
GraphLink,
|
||
ContextMenuState,
|
||
} from "./types";
|
||
import { useGraphStore } from "./store/useGraphStore";
|
||
import {
|
||
ForceGraph,
|
||
GraphControls,
|
||
GraphContextMenu,
|
||
GraphStatusBar,
|
||
GraphStats,
|
||
} from "./components";
|
||
|
||
interface GraphProps {
|
||
initialData?: GraphData;
|
||
onExport?: () => void;
|
||
onDataChange?: (data: GraphData) => void;
|
||
}
|
||
|
||
export const Graph: React.FC<GraphProps> = ({
|
||
initialData,
|
||
onExport,
|
||
onDataChange,
|
||
}) => {
|
||
const fgRef = useRef<any>(null);
|
||
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
|
||
|
||
const data = useGraphStore((s) => s.data);
|
||
const isLinkMode = useGraphStore((s) => s.isLinkMode);
|
||
const selectedNode = useGraphStore((s) => s.selectedNode);
|
||
const setData = useGraphStore((s) => s.setData);
|
||
|
||
// Инициализация данных
|
||
useEffect(() => {
|
||
if (initialData) setData(initialData);
|
||
}, [initialData, setData]);
|
||
|
||
// Закрыть контекстное меню по клику вне
|
||
useEffect(() => {
|
||
const handleClickOutside = () => setContextMenu(null);
|
||
document.addEventListener("click", handleClickOutside);
|
||
return () => document.removeEventListener("click", handleClickOutside);
|
||
}, []);
|
||
|
||
const handleNodeRightClick = (node: GraphNode, event: MouseEvent) => {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
setContextMenu({ x: event.clientX, y: event.clientY, node, link: null });
|
||
};
|
||
|
||
if (!data || data.nodes.length === 0) {
|
||
return (
|
||
<div className="bg-gray-900 rounded-xl shadow-lg p-6">
|
||
<div className="flex items-center justify-center h-96">
|
||
<div className="text-center">
|
||
<p className="text-gray-400 mb-4">Нет данных для отображения</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
className="p-4 h-full flex flex-col"
|
||
style={{ backgroundColor: "var(--card-bg)" }}
|
||
>
|
||
{/* Статистика сверху */}
|
||
<GraphStats data={data} />
|
||
|
||
{/* Граф */}
|
||
<div
|
||
className="flex-1 rounded-lg overflow-hidden relative mt-2"
|
||
style={{ border: "1px solid var(--border)" }}
|
||
>
|
||
<ForceGraph
|
||
ref={fgRef}
|
||
data={data}
|
||
onNodeRightClick={handleNodeRightClick}
|
||
/>
|
||
|
||
<GraphContextMenu
|
||
menu={contextMenu}
|
||
data={data}
|
||
onClose={() => setContextMenu(null)}
|
||
/>
|
||
|
||
<GraphStatusBar isLinkMode={isLinkMode} selectedNode={selectedNode} />
|
||
</div>
|
||
|
||
{/* Кнопки снизу */}
|
||
<GraphControls
|
||
fgRef={fgRef}
|
||
onExport={onExport}
|
||
onDataChange={onDataChange}
|
||
/>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default Graph;
|