Implement Tree Editor with visual preview and documentation updates
Tree Editor Features: - Zustand store with immer middleware and zundo for undo/redo - Form-based node editing (Decision, Action, Solution types) - Visual tree preview with solution connection indicators - NodePicker with type-grouped dropdown (Decisions/Actions/Solutions) - SharedLinksMap for detecting nodes with multiple sources - Modal component with scrollable body, fixed header/footer New Components: - TreeEditorLayout, TreeMetadataForm, NodeList, NodeEditorModal - NodeFormDecision, NodeFormAction, NodeFormResolution - DynamicArrayField, NodePicker - TreePreviewPanel, TreePreviewNode Documentation: - Updated README.md status to Phase 2 - Added Tree Editor details to CURRENT-STATE.md - Added modal/Zustand lessons to LESSONS-LEARNED.md - Updated file structure in CLAUDE-SETUP.md - Added Tree Editor progress to PROGRESS.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
85
frontend/src/components/tree-editor/NodeEditorModal.tsx
Normal file
85
frontend/src/components/tree-editor/NodeEditorModal.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Modal } from '@/components/common/Modal'
|
||||
import { useTreeEditorStore } from '@/store/treeEditorStore'
|
||||
import { NodeFormDecision } from './NodeFormDecision'
|
||||
import { NodeFormAction } from './NodeFormAction'
|
||||
import { NodeFormResolution } from './NodeFormResolution'
|
||||
import type { TreeStructure } from '@/types'
|
||||
|
||||
interface NodeEditorModalProps {
|
||||
node: TreeStructure
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function NodeEditorModal({ node, onClose }: NodeEditorModalProps) {
|
||||
const { updateNode, validationErrors } = useTreeEditorStore()
|
||||
const nodeErrors = validationErrors.filter(e => e.nodeId === node.id)
|
||||
|
||||
const handleUpdate = (updates: Partial<TreeStructure>) => {
|
||||
updateNode(node.id, updates)
|
||||
}
|
||||
|
||||
const getTitle = () => {
|
||||
switch (node.type) {
|
||||
case 'decision':
|
||||
return 'Edit Decision Node'
|
||||
case 'action':
|
||||
return 'Edit Action Node'
|
||||
case 'solution':
|
||||
return 'Edit Solution Node'
|
||||
default:
|
||||
return 'Edit Node'
|
||||
}
|
||||
}
|
||||
|
||||
const footerContent = (
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal isOpen={true} onClose={onClose} title={getTitle()} size="lg" footer={footerContent}>
|
||||
{/* Node ID display */}
|
||||
<div className="mb-4 text-xs text-muted-foreground">
|
||||
Node ID: <code className="rounded bg-muted px-1 py-0.5">{node.id}</code>
|
||||
</div>
|
||||
|
||||
{/* Validation errors */}
|
||||
{nodeErrors.length > 0 && (
|
||||
<div className="mb-4 space-y-1">
|
||||
{nodeErrors.map((error, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`rounded-md px-3 py-2 text-sm ${
|
||||
error.severity === 'error'
|
||||
? 'bg-destructive/10 text-destructive'
|
||||
: 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400'
|
||||
}`}
|
||||
>
|
||||
{error.message}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Type-specific form */}
|
||||
{node.type === 'decision' && (
|
||||
<NodeFormDecision node={node} onUpdate={handleUpdate} />
|
||||
)}
|
||||
{node.type === 'action' && (
|
||||
<NodeFormAction node={node} onUpdate={handleUpdate} />
|
||||
)}
|
||||
{node.type === 'solution' && (
|
||||
<NodeFormResolution node={node} onUpdate={handleUpdate} />
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default NodeEditorModal
|
||||
Reference in New Issue
Block a user