This commit is contained in:
2024-08-23 14:19:10 +07:00
parent 36ac386324
commit b6f0356c09
2 changed files with 137 additions and 244 deletions

View File

@@ -4,6 +4,7 @@ import ReactDOM from 'react-dom/client';
import App from './App'; import App from './App';
import NoteFlow from './nodes/NoteFlow'; import NoteFlow from './nodes/NoteFlow';
import './index.css'; import './index.css';
import NoteFlowNew from './nodes/NodeFlowNew';
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>

View File

@@ -1,31 +1,37 @@
import { useState,useCallback } from 'react'; import { useState } from 'react';
import {ReactFlow, addEdge, Controls, Background, MarkerType } from '@xyflow/react'; import { ReactFlow, addEdge, Controls, Background } from '@xyflow/react';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import { FiUser, FiMail, FiCheckCircle,FiList } from 'react-icons/fi'; import { FiUser, FiMail, FiCheckCircle, FiList } from 'react-icons/fi';
import {FaShuffle } from "react-icons/fa6"; import { FaShuffle } from 'react-icons/fa6';
const initialNodes = [
const initialNode = { {
id: '1', id: '1',
type: 'input', type: 'input',
data: { data: {
label: ( label: (
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<FiUser style={{ marginRight: 5 }} /> <FiUser style={{ marginRight: 5 }} />
<span>Customer signs up for product updates</span> <span>Customer signs up for product updates</span>
</div> </div>
) ),
}, },
position: { x: 250, y: 100 }, position: { x: 250, y: 100 },
}; },
{
id: '2',
data: { label: 'Add Note' },
position: { x: 250, y: 250 },
},
];
const tabBarOptions = [ const tabBarOptions = [
{ {
label: 'Send email', label: 'Send email',
id: '2', id: '3',
type: '', type: '',
html: ( html: (
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<FiMail style={{ marginRight: 5 }} /> <FiMail style={{ marginRight: 5 }} />
Send email Send email
</div> </div>
@@ -34,11 +40,11 @@ const tabBarOptions = [
}, },
{ {
label: 'Replied to conversation?', label: 'Replied to conversation?',
id: '3', id: '4',
type: '', type: '',
html: ( html: (
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<FaShuffle style={{ marginRight: 5, transform:`rotate(90deg)` }} /> <FaShuffle style={{ marginRight: 5, transform: `rotate(90deg)` }} />
<span>Replied to conversation?</span> <span>Replied to conversation?</span>
</div> </div>
), ),
@@ -46,10 +52,10 @@ const tabBarOptions = [
}, },
{ {
label: 'Send survey', label: 'Send survey',
id: '4', id: '5',
type: 'output', type: 'output',
html: ( html: (
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<FiCheckCircle style={{ marginRight: 5 }} /> <FiCheckCircle style={{ marginRight: 5 }} />
Send survey Send survey
</div> </div>
@@ -58,61 +64,25 @@ const tabBarOptions = [
}, },
{ {
label: 'Contact Exists', label: 'Contact Exists',
id: '5', id: '6',
type: 'output', type: 'output',
html: ( html: (
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<FiList style={{ marginRight: 5 }} /> <FiList style={{ marginRight: 5 }} />
Contact Exits Contact Exists
</div> </div>
), ),
position: { x: 400, y: 400 }, position: { x: 400, y: 400 },
}, },
]; ];
// Nội dung popup cho mỗi node
const nodeContents = {
'2': `
<label for="email-subject">Subject:</label>
<input type="text" id="email-subject" name="email-subject" placeholder="Enter subject">
<label for="email-body">Body:</label>
<textarea id="email-body" name="email-body" rows="4" placeholder="Enter email body"></textarea>
`,
'1': `
<label for="customer-name">Customer Name:</label>
<input type="text" id="customer-name" name="customer-name" placeholder="Enter customer name">
<label for="customer-email">Customer Email:</label>
<input type="email" id="customer-email" name="customer-email" placeholder="Enter customer email">
`,
'3': `
<label for="reply-status">Reply Status:</label>
<select id="reply-status" name="reply-status">
<option value="replied">Replied</option>
<option value="not-replied">Not Replied</option>
</select>
`,
'5': `
<label for="contact-id">Contact ID:</label>
<input type="text" id="contact-id" name="contact-id" placeholder="Enter contact ID">
`,
'4': `
<label for="survey-question">Survey Question:</label>
<input type="text" id="survey-question" name="survey-question" placeholder="Enter survey question">
<label for="survey-options">Options (comma-separated):</label>
<input type="text" id="survey-options" name="survey-options" placeholder="Option 1, Option 2, ...">
`
};
const NoteFlow = () => { const NoteFlow = () => {
const [nodes, setNodes] = useState([initialNode]); const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState([]); const [edges, setEdges] = useState([
{ id: 'e1-2', source: '1', target: '2' }, // Kết nối node "Add Note" với node mặc định ban đầu
]);
const [showTabBar, setShowTabBar] = useState(false); const [showTabBar, setShowTabBar] = useState(false);
const [lastNodeId, setLastNodeId] = useState('1'); const [lastNodeId, setLastNodeId] = useState('1');
const [showAdditionalOptions, setShowAdditionalOptions] = useState(false);
const [selectedNodeContent, setSelectedNodeContent] = useState('');
const [showPopup, setShowPopup] = useState(false);
const [nodeToDelete, setNodeToDelete] = useState(null);
const handleAddNoteClick = () => { const handleAddNoteClick = () => {
setShowTabBar(true); setShowTabBar(true);
@@ -120,121 +90,101 @@ const NoteFlow = () => {
const handleTabClick = (option) => { const handleTabClick = (option) => {
setShowTabBar(false); setShowTabBar(false);
const newNode = { const newNode = {
id: option.id, id: option.id,
data: { label: option.html }, data: { label: option.html },
position: { x: option.position.x, y: option.position.y }, position: { x: option.position.x, y: option.position.y },
}; };
setNodes((nds) => [...nds, newNode]); const addNoteNode = nodes.find((node) => node.id === '2');
setEdges((eds) => addEdge({ id: `e${lastNodeId}-${option.id}`, source: lastNodeId, target: option.id, animated: false }, eds));
setLastNodeId(option.id);
if (option.id === '4') {
// Xóa node "Add Note"
setNodes((nds) => nds.filter((node) => node.id !== '2'));
// Thêm node "Replied to conversation?"
if (option.id === '3') { const repliedNode = {
const yesNode = {
id: '4', id: '4',
type: 'output', data: { label: option.html },
data: { label: ( position: { x: option.position.x, y: option.position.y },
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}> };
// Thêm các nhánh "Send survey" và "Contact Exists"
const yesNode = {
id: '5',
data: {
label: (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<FiCheckCircle style={{ marginRight: 5 }} /> <FiCheckCircle style={{ marginRight: 5 }} />
Send survey Send survey
</div> </div>
) }, ),
position: { x: 100, y: 400 }, },
position: { x: option.position.x - 150, y: option.position.y + 150 },
}; };
const noNode = { const noNode = {
id: '5', id: '6',
type: 'output', data: {
data: { label: ( label: (
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'center'}}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<FiList style={{ marginRight: 5 }} /> <FiList style={{ marginRight: 5 }} />
Contact Exits Contact Exists
</div> </div>
) }, ),
position: { x: 400, y: 400 }, },
position: { x: option.position.x + 150, y: option.position.y + 150 },
}; };
setNodes((nds) => [...nds, yesNode, noNode]); setNodes((nds) => [...nds, repliedNode, yesNode, noNode]);
setEdges((eds) => [ setEdges((eds) => [
...eds, ...eds,
{ id: 'e3-4', source: '3', target: '4', label: 'yes', animated: false,}, { id: 'e4-5', source: '4', target: '5', label: 'yes', animated: false },
{ id: 'e3-5', source: '3', target: '5', label: 'no', animated: false}, { id: 'e4-6', source: '4', target: '6', label: 'no', animated: false },
{ id: `e${lastNodeId}-4`, source: lastNodeId, target: '4' } // Kết nối node hiện tại với "Replied to conversation?"
]); ]);
} else {
if (addNoteNode) {
// Di chuyển node "Add Note" xuống dưới node mới
setNodes((nds) =>
nds.map((node) =>
node.id === '2'
? { ...node, position: { x: option.position.x, y: option.position.y + 150 } }
: node
)
);
} }
};
const handleAdditionalOptionClick = (option) => { // Thêm node mới vào danh sách node
setShowAdditionalOptions(false); setNodes((nds) => [...nds, newNode]);
const surveyNode = { // Kết nối node mới với node hiện tại
id: option.id, setEdges((eds) => addEdge({ id: `e${lastNodeId}-${option.id}`, source: lastNodeId, target: option.id }, eds));
data: { label: 'Send survey' },
position: { x: 100, y: 150 * (nodes.length + 1) },
};
const contactExistsNode = { // Kết nối node "Add Note" cũ với node mới
id: '5', if (addNoteNode) {
data: { label: 'Contact Exists' }, setEdges((eds) => addEdge({ id: `e${option.id}-2`, source: option.id, target: '2' }, eds));
position: { x: 400, y: 150 * (nodes.length + 1) }, }
};
setNodes((nds) => [...nds, surveyNode, contactExistsNode]); // Cập nhật lastNodeId
setLastNodeId(option.id);
setEdges((eds) => [ }
...eds,
{ id: `e${lastNodeId}-${option.id}`, source: lastNodeId, target: option.id, label: 'yes', animated: true },
{ id: `e${option.id}-5`, source: option.id, target: '5', label: 'no', animated: true },
]);
}; };
const onNodeClick = (event, node) => { const onNodeClick = (event, node) => {
const content = nodeContents[node.id]; if (node.id === '2') { // Check if the clicked node is "Add Note"
setSelectedNodeContent(content); setShowTabBar(true);
setShowPopup(true);
};
const hideenPopup = () => {
setShowPopup(false);
}
const onConnect = (params) => setEdges((eds) => addEdge(params, eds));
const onNodeContextMenu = useCallback((event, node) => {
event.preventDefault(); // Ngăn không cho menu chuột phải mặc định hiện ra
setNodeToDelete(node);
}, []);
const handleNodeDelete = () => {
if (nodeToDelete) {
setNodes((nds) => nds.filter((node) => node.id !== nodeToDelete.id));
setEdges((eds) => eds.filter((edge) => edge.source !== nodeToDelete.id && edge.target !== nodeToDelete.id));
setNodeToDelete(null);
} }
}; };
return ( return (
<div style={{ height: '100vh', width: '100%' }}> <div style={{ height: '100vh', width: '100%', position: 'relative' }}>
<ReactFlow nodes={nodes} edges={edges} onConnect={onConnect} onNodeClick={onNodeClick} onNodeContextMenu={onNodeContextMenu}> <ReactFlow nodes={nodes} edges={edges} onNodeClick={onNodeClick}>
<Controls /> <Controls />
<Background /> <Background />
</ReactFlow> </ReactFlow>
<button
onClick={handleAddNoteClick}
style={{
position: 'absolute',
top: 10,
left: 10,
zIndex: 10,
padding: '10px',
}}
>
Add Note
</button>
{showTabBar && ( {showTabBar && (
<div <div
style={{ style={{
@@ -262,64 +212,6 @@ const NoteFlow = () => {
))} ))}
</div> </div>
)} )}
{showAdditionalOptions && (
<div className='popup'
>
{additionalOptions.map((option) => (
<div
key={option.id}
onClick={() => handleAdditionalOptionClick(option)}
style={{
padding: '10px',
cursor: 'pointer',
borderBottom: '1px solid #ddd',
}}
>
{option.label}
</div>
))}
</div>
)}
{showPopup && (
<>
<div className="popup-overlay" onClick={hideenPopup}></div>
<div
className={`popup global-popup ${showPopup ? '' : 'hidden'}`}
>
<div className="popup-content">
<div className="conent-form">
<form>
<div dangerouslySetInnerHTML={{ __html: selectedNodeContent }} />
<button onClick={hideenPopup}>Close</button>
</form>
</div>
</div>
</div>
</>
)}
{nodeToDelete && (
<div
style={{
position: 'absolute',
top: nodeToDelete.position.y - 12,
left: nodeToDelete.position.x + 260,
zIndex: 10,
}}
>
<button
onClick={handleNodeDelete}
style={{
background: 'red',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
X
</button>
</div>
)}
</div> </div>
); );
}; };