Improve tree editor modal UX: cancel/save and inline node naming
Change 1: Add Cancel button and defer saving until Done is clicked
- NodeEditorModal now uses local draft state instead of updating store directly
- Cancel button discards changes; Done button commits to store
- If editing a brand new node, Cancel deletes it entirely
- NodeList tracks isEditingNewNode to pass to modal
Change 2: Inline node naming when creating from NodePicker dropdown
- Selecting "+ New Decision/Action/Solution" shows inline title input
- User enters title before node is created (Enter to create, Escape to cancel)
- Node appears in dropdown with human-readable title immediately
Change 3: Improved dropdown labels
- Format changed from "UUID (UUID...)" to "Title (UUID...)"
- Untitled nodes show "Untitled Question" or "Untitled {type}"
- Root node shows "Root Question (root)" when empty
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Modal } from '@/components/common/Modal'
|
||||
import { useTreeEditorStore } from '@/store/treeEditorStore'
|
||||
import { NodeFormDecision } from './NodeFormDecision'
|
||||
@@ -8,14 +9,39 @@ import type { TreeStructure } from '@/types'
|
||||
interface NodeEditorModalProps {
|
||||
node: TreeStructure
|
||||
onClose: () => void
|
||||
/** If true, this is a brand new node - cancel will delete it entirely */
|
||||
isNewNode?: boolean
|
||||
}
|
||||
|
||||
export function NodeEditorModal({ node, onClose }: NodeEditorModalProps) {
|
||||
const { updateNode, validationErrors } = useTreeEditorStore()
|
||||
export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditorModalProps) {
|
||||
const { updateNode, deleteNode, validationErrors } = useTreeEditorStore()
|
||||
const nodeErrors = validationErrors.filter(e => e.nodeId === node.id)
|
||||
|
||||
const handleUpdate = (updates: Partial<TreeStructure>) => {
|
||||
updateNode(node.id, updates)
|
||||
// Local draft state - changes are NOT persisted until "Done" is clicked
|
||||
const [draft, setDraft] = useState<TreeStructure>(() => structuredClone(node))
|
||||
|
||||
// Reset draft when node changes (e.g., external update)
|
||||
useEffect(() => {
|
||||
setDraft(structuredClone(node))
|
||||
}, [node.id]) // Only reset when switching to a different node
|
||||
|
||||
const handleUpdate = useCallback((updates: Partial<TreeStructure>) => {
|
||||
setDraft(prev => ({ ...prev, ...updates }))
|
||||
}, [])
|
||||
|
||||
const handleSave = () => {
|
||||
// Commit all draft changes to the store
|
||||
updateNode(node.id, draft)
|
||||
onClose()
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
if (isNewNode) {
|
||||
// Delete the unsaved new node entirely
|
||||
deleteNode(node.id)
|
||||
}
|
||||
// Discard changes and close (draft is just thrown away)
|
||||
onClose()
|
||||
}
|
||||
|
||||
const getTitle = () => {
|
||||
@@ -32,10 +58,17 @@ export function NodeEditorModal({ node, onClose }: NodeEditorModalProps) {
|
||||
}
|
||||
|
||||
const footerContent = (
|
||||
<div className="flex justify-end">
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
onClick={handleCancel}
|
||||
className="rounded-md border border-input bg-background px-4 py-2 text-sm font-medium text-foreground hover:bg-accent"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
Done
|
||||
@@ -68,15 +101,15 @@ export function NodeEditorModal({ node, onClose }: NodeEditorModalProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Type-specific form */}
|
||||
{node.type === 'decision' && (
|
||||
<NodeFormDecision node={node} onUpdate={handleUpdate} />
|
||||
{/* Type-specific form - uses draft state, not the original node */}
|
||||
{draft.type === 'decision' && (
|
||||
<NodeFormDecision node={draft} onUpdate={handleUpdate} />
|
||||
)}
|
||||
{node.type === 'action' && (
|
||||
<NodeFormAction node={node} onUpdate={handleUpdate} />
|
||||
{draft.type === 'action' && (
|
||||
<NodeFormAction node={draft} onUpdate={handleUpdate} />
|
||||
)}
|
||||
{node.type === 'solution' && (
|
||||
<NodeFormResolution node={node} onUpdate={handleUpdate} />
|
||||
{draft.type === 'solution' && (
|
||||
<NodeFormResolution node={draft} onUpdate={handleUpdate} />
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user