import { useRef, useEffect, useCallback, type ReactNode } from 'react' import { Plus, GripVertical, Trash2, ChevronDown, CheckCircle2, AlertTriangle, Info, Zap, ListOrdered, SeparatorHorizontal } from 'lucide-react' import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core' import type { DragEndEvent } from '@dnd-kit/core' import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import type { StepContentType } from '@/types' import { StepEditor } from './StepEditor' import { useProceduralEditorStore } from '@/store/proceduralEditorStore' import { cn } from '@/lib/utils' const contentTypeConfig: Record = { action: { icon: Zap, color: 'text-blue-400', label: 'Action' }, informational: { icon: Info, color: 'text-muted-foreground', label: 'Info' }, verification: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Verify' }, warning: { icon: AlertTriangle, color: 'text-yellow-400', label: 'Warning' }, } function SortableStepWrapper({ id, disabled, children, }: { id: string disabled?: boolean children: (props: { dragHandleProps: Record }) => ReactNode }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id, disabled }) const style = { transform: CSS.Transform.toString(transform), transition, } return (
{children({ dragHandleProps: { ...attributes, ...listeners } })}
) } interface StepListProps { onStepContextMenu?: (e: React.MouseEvent, stepId: string) => void } export function StepList({ onStepContextMenu }: StepListProps) { const { steps, intakeForm, expandedStepId, setExpandedStepId, addStep, addSectionHeader, removeStep, updateStep, moveStep, } = useProceduralEditorStore() const procedureSteps = steps.filter((s) => s.type === 'procedure_step') const totalMinutes = steps .filter(s => s.type === 'procedure_step' && s.estimated_minutes) .reduce((sum, s) => sum + (s.estimated_minutes || 0), 0) // Auto-scroll to new steps const scrollTargetRef = useRef(null) const prevStepCount = useRef(steps.length) useEffect(() => { if (steps.length > prevStepCount.current) { setTimeout(() => { scrollTargetRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) }, 50) } prevStepCount.current = steps.length }, [steps.length]) // DnD setup const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) ) const handleDragEnd = useCallback((event: DragEndEvent) => { const { active, over } = event if (!over || active.id === over.id) return const oldIndex = steps.findIndex(s => s.id === active.id) const newIndex = steps.findIndex(s => s.id === over.id) if (oldIndex === -1 || newIndex === -1) return // Don't allow moving past the procedure_end step const endIndex = steps.findIndex(s => s.type === 'procedure_end') if (newIndex >= endIndex) return moveStep(oldIndex, newIndex) }, [steps, moveStep]) // Sortable items: everything except procedure_end const sortableItems = steps.filter(s => s.type !== 'procedure_end').map(s => s.id) let stepCounter = 0 return (

Steps

({procedureSteps.length} step{procedureSteps.length !== 1 ? 's' : ''} {totalMinutes > 0 ? ` \u00b7 ~${totalMinutes} min` : ''})
{steps.map((step) => { if (step.type === 'procedure_end') { // procedure_end: non-draggable, always last return (
updateStep(step.id, { title: e.target.value })} className="flex-1 bg-transparent text-sm text-muted-foreground focus:outline-hidden" placeholder="Procedure Complete" /> END
) } // Section header rendering if (step.type === 'section_header') { const isExpanded = expandedStepId === step.id if (isExpanded) { return ( {() => (
updateStep(step.id, updates)} onCollapse={() => setExpandedStepId(null)} availableVariables={intakeForm} />
)}
) } return ( {({ dragHandleProps }) => (
setExpandedStepId(step.id)} > {step.title || 'Untitled Section'}
)}
) } // Regular procedure step stepCounter++ const stepNumber = stepCounter const isExpanded = expandedStepId === step.id const contentType = step.content_type || 'action' const config = contentTypeConfig[contentType] const Icon = config.icon const isGhost = !!(step as unknown as Record)._suggestion if (isExpanded) { return ( {() => (
updateStep(step.id, updates)} onCollapse={() => setExpandedStepId(null)} availableVariables={intakeForm} />
)}
) } return ( {({ dragHandleProps }) => (
onStepContextMenu?.(e, step.id)} >
{stepNumber} setExpandedStepId(step.id)} > {step.title || 'Untitled step'} {step.estimated_minutes && ( ~{step.estimated_minutes}m )}
{isGhost && (
)}
)}
) })}
{/* Add step button at bottom */}
) }