This commit is contained in:
2024-09-25 17:20:34 +07:00
parent 3762dbe776
commit f89d9e3658
3 changed files with 249 additions and 25 deletions

View File

@@ -759,3 +759,63 @@ foreignObject {
font-weight: 600; font-weight: 600;
line-height: 1.6; line-height: 1.6;
} }
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5); /* Màu đen mờ */
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
/* Nội dung modal */
.modal-content {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
width: 400px;
}
/* Các nút trong modal */
.modal-content button {
margin: 10px;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
/* Style cho nút Yes, No, Both */
.modal-content button:nth-child(1) {
background-color: #28a745; /* Màu xanh cho Yes */
color: white;
}
.modal-content button:nth-child(2) {
background-color: #dc3545; /* Màu đỏ cho No */
color: white;
}
.modal-content button:nth-child(3) {
background-color: #ffc107; /* Màu vàng cho Both */
color: black;
}
/* Nút Cancel */
.modal-content button:nth-child(4) {
background-color: #6c757d; /* Màu xám cho Cancel */
color: white;
}
/* Hover effect cho các nút */
.modal-content button:hover {
opacity: 0.8;
}

View File

@@ -10,6 +10,7 @@ import {
ReactFlowProvider, ReactFlowProvider,
useNodesState, useNodesState,
useEdgesState, useEdgesState,
useStoreApi,
} from '@xyflow/react'; } from '@xyflow/react';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import '../assets/css/style.css'; import '../assets/css/style.css';
@@ -18,6 +19,7 @@ import ShowPopup from './ShowPopup.tsx';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import CustomEdge from './CustomEdge'; import CustomEdge from './CustomEdge';
import MenuFlow from './MenuFlow'; import MenuFlow from './MenuFlow';
import DeleteNodeModal from './popup/DeleteNodeModal.tsx';
// Nội dung cho mỗi node // Nội dung cho mỗi node
const nodeContents = { const nodeContents = {
@@ -53,8 +55,8 @@ const nodeContents = {
}; };
const NodeFlow = ({ initialNodes, initialEdges }) => { const NodeFlow = ({ initialNodes, initialEdges }) => {
const [nodes, setNodes] = useState(initialNodes); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges] = useState(initialEdges); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const [showTabBar, setShowTabBar] = useState(false); const [showTabBar, setShowTabBar] = useState(false);
const [lastNodeId, setLastNodeId] = useState(nodes[0].id); const [lastNodeId, setLastNodeId] = useState(nodes[0].id);
const [showPopup, setShowPopup] = useState(false); const [showPopup, setShowPopup] = useState(false);
@@ -64,6 +66,9 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
const { screenToFlowPosition } = useReactFlow(); const { screenToFlowPosition } = useReactFlow();
const [showActions, setShowActions] = useState(null); const [showActions, setShowActions] = useState(null);
const [showPopupSuccess, setShowPopupSuccess] = useState(false); const [showPopupSuccess, setShowPopupSuccess] = useState(false);
const store = useStoreApi();
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [deleteNodeId, setDeleteNodeId] = useState(null);
const handleAddNoteClick = () => { const handleAddNoteClick = () => {
setShowTabBar(true); setShowTabBar(true);
@@ -337,7 +342,7 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
Sửa Sửa
</button> </button>
<button <button
onClick={() => handleDeleteNode(newNodeId)} onClick={() => handleDeleteNode(newNodeId, type.key)}
className="btn-remove" className="btn-remove"
> >
Xóa Xóa
@@ -505,8 +510,6 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
} }
} }
} }
console.log('Updated nodes:', nodes);
}; };
const onDragOver = useCallback((event) => { const onDragOver = useCallback((event) => {
@@ -546,15 +549,33 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
// Hàm xóa node // Hàm xóa node
const handleDeleteNode = useCallback( const handleDeleteNode = useCallback(
(nodeId) => { (nodeId, nodeKey) => {
// Tìm tất cả các edges có nodeId là source hoặc target // Tìm tất cả các edges có nodeId là source hoặc target
const connectedEdges = edges.filter( const allNode = store.getState();
const edgeNode = allNode.edges;
const Node = allNode.nodes;
if (nodeKey === 'ifelse') {
setDeleteNodeId(nodeId); // Lưu lại ID của node muốn xóa
// Hiển thị modal để người dùng chọn nhánh xóa
setIsDeleteModalOpen(true);
document
.querySelector('.actions-' + nodeId)
?.classList.remove('staticNode');
} else {
const isConfirmed = window.confirm(
'Bạn có chắc chắn muốn xóa node này không?'
);
if (!isConfirmed) return;
// Tìm tất cả các edges có nodeId là source hoặc target
const connectedEdges = edgeNode.filter(
(edge) => edge.source === nodeId || edge.target === nodeId (edge) => edge.source === nodeId || edge.target === nodeId
); );
console.log(connectedEdges); console.log('connectedEdges', connectedEdges);
// Lấy các nodes đang nối với node bị xóa
const sourceNode = connectedEdges.find( const sourceNode = connectedEdges.find(
(edge) => edge.target === nodeId (edge) => edge.target === nodeId
)?.source; )?.source;
@@ -562,16 +583,28 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
(edge) => edge.source === nodeId (edge) => edge.source === nodeId
)?.target; )?.target;
// Cập nhật nodes: xóa node bị xóa // Xóa node và các node con
setNodes((nds) => nds.filter((node) => node.id !== nodeId)); setNodes((nds) => {
const updatedNodes = nds.filter((node) => node.id !== nodeId);
// Cập nhật edges: xóa các cạnh kết nối với node bị xóa // Cập nhật vị trí cho các node còn lại
const sortedNodes = updatedNodes.sort(
(a, b) => a.position.y - b.position.y
);
return sortedNodes.map((node, index) => ({
...node,
position: {
...node.position,
y: index * 110, // Khoảng cách giữa các node
},
}));
});
// Xóa các cạnh kết nối với node bị xóa và các node con
setEdges((eds) => setEdges((eds) =>
eds.filter((edge) => edge.source !== nodeId && edge.target !== nodeId) eds.filter((edge) => edge.source !== nodeId && edge.target !== nodeId)
); );
// Nếu có cả sourceNode và targetNode, tạo một edge mới nối 2 node này
if (sourceNode && targetNode) {
setEdges((eds) => [ setEdges((eds) => [
...eds, ...eds,
{ {
@@ -585,6 +618,111 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
[edges, setNodes, setEdges] [edges, setNodes, setEdges]
); );
// Hàm xác định và xóa nhánh dựa trên node đã chọn
const handleConfirmDeleteBranchNode = (branch) => {
const allNode = store.getState();
const Node = allNode.nodes;
const edgeNode = allNode.edges;
// Tìm node ifelse
const ifelseNode = allNode.nodes.find((node) => node.id === deleteNodeId);
// Tìm edges của nhánh được chọn (Yes hoặc No)
const branchEdges = edgeNode.filter((edge) => edge.data?.label === branch);
// Lấy ID của tất cả các node trong nhánh dựa trên các edges
const branchNodeIds = branchEdges.map((edge) => edge.target);
// Lấy danh sách các node trong nhánh dựa trên cùng tọa độ x với node được chọn
const selectedNode = allNode.nodes.find(
(node) => node.id === branchNodeIds[0]
);
const nodesToDelete = allNode.nodes
.filter((node) => node.position.x === selectedNode.position.x) // Lọc các node có cùng tọa độ x với node được chọn
.map((node) => node.id); // Lấy ID của các node trong nhánh
// Bao gồm cả node ifelse trong danh sách node cần xóa
nodesToDelete.push(ifelseNode.id);
// Tìm edges nối đến nhánh còn lại
const remainingEdges = edgeNode.filter(
(edge) => edge.source === ifelseNode.id && edge.data?.label !== branch
);
const TargetIfelse = edgeNode.filter(
(edge) => edge.target === ifelseNode.id
);
const NodeRemaining = allNode.nodes.find(
(node) => node.id === remainingEdges[0].target
);
// Xóa tất cả các node trong nhánh
setNodes((nds) => {
const updatedNodes = nds.filter(
(node) => !nodesToDelete.includes(node.id)
);
// Nếu còn node sau khi xóa
if (updatedNodes.length > 0) {
// Lấy tất cả các node của nhánh còn lại
const nodesToRemaining = allNode.nodes
.filter((node) => node.position.x === NodeRemaining.position.x) // Lọc các node có cùng tọa độ x với node được chọn
.map((node) => node); // Lấy các node trong nhánh
// Đặt lại vị trí của tất cả các node còn lại theo thứ tự và khoảng cách đều
const updatedRemainingNodes = nodesToRemaining.map((node, index) => ({
...node,
position: {
x: ifelseNode.position.x,
y: ifelseNode.position.y + index * 110,
},
}));
// Kết hợp lại với các node khác (nếu có)
return updatedNodes.map((node) => {
const updatedNode = updatedRemainingNodes.find(
(n) => n.id === node.id
);
return updatedNode ? updatedNode : node;
});
}
return updatedNodes; // Trả về danh sách nodes đã được cập nhật
});
// Xóa các edges liên quan đến nhánh được chọn
setEdges((eds) => {
const updatedEdges = [
...eds.filter(
(edge) =>
!nodesToDelete.includes(edge.source) &&
!nodesToDelete.includes(edge.target)
),
{
id: `edge-${NodeRemaining.id}-${TargetIfelse[0].source}`,
source: NodeRemaining.id,
target: TargetIfelse[0].source,
},
];
return updatedEdges;
});
setIsDeleteModalOpen(false); // Đóng modal
};
// Hàm xử lý xác nhận xóa
const handleConfirmDelete = (choice) => {
if (choice === 'yes') {
handleConfirmDeleteBranchNode('Yes');
} else if (choice === 'no') {
handleConfirmDeleteBranchNode('No');
} else if (choice === 'both') {
handleConfirmDeleteBranchNode('Yes');
handleConfirmDeleteBranchNode('No');
}
};
// Hàm sửa node // Hàm sửa node
const handleEditNode = (nodeId: string) => { const handleEditNode = (nodeId: string) => {
// Logic chỉnh sửa node // Logic chỉnh sửa node
@@ -614,6 +752,8 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
panOnScroll panOnScroll
defaultEdgeOptions={defaultEdgeOptions} defaultEdgeOptions={defaultEdgeOptions}
onConnect={onConnect} onConnect={onConnect}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
> >
<Controls position="right-bottom" /> <Controls position="right-bottom" />
<Background variant={BackgroundVariant.Lines} color="#ccccc" /> <Background variant={BackgroundVariant.Lines} color="#ccccc" />
@@ -652,6 +792,13 @@ const NodeFlow = ({ initialNodes, initialEdges }) => {
</div> </div>
</div> </div>
)} )}
{isDeleteModalOpen && (
<DeleteNodeModal
onConfirm={handleConfirmDelete}
onClose={() => setIsDeleteModalOpen(false)}
/>
)}
</div> </div>
); );
}; };

View File

@@ -0,0 +1,17 @@
import React, { useState, useCallback } from 'react';
const DeleteNodeModal = ({ onConfirm, onClose }) => {
return (
<div className="modal-overlay">
<div className="modal-content">
<h3>Bạn muốn xóa nhánh nào?</h3>
<button onClick={() => onConfirm('yes')}>Yes</button>
<button onClick={() => onConfirm('no')}>No</button>
<button onClick={() => onConfirm('both')}>Both</button>
<button onClick={() => onClose()}>Cancel</button>
</div>
</div>
);
};
export default DeleteNodeModal;