'use client'; import { useMemo, useState, useSyncExternalStore } from 'react'; import { DndContext, closestCenter, type DragEndEvent, } from '@dnd-kit/core'; import { SortableContext, arrayMove, useSortable, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { IconGripVertical, IconPlus, IconTrash, IconChevronDown, IconChevronUp, IconRobot, IconCheck, IconClipboard, } from '@tabler/icons-react'; import { Card, CardContent } from '@kandev/ui/card'; import { Button } from '@kandev/ui/button'; import { Input } from '@kandev/ui/input'; import { Textarea } from '@kandev/ui/textarea'; import { Checkbox } from '@kandev/ui/checkbox'; import { Label } from '@kandev/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@kandev/ui/select'; import type { WorkflowStep, WorkflowStepType, StepBehaviors } from '@/lib/types/http'; import { useDebouncedCallback } from '@/hooks/use-debounce'; const STEP_COLORS = [ { value: 'bg-slate-500', label: 'Gray' }, { value: 'bg-red-500', label: 'Red' }, { value: 'bg-orange-500', label: 'Orange' }, { value: 'bg-yellow-500', label: 'Yellow' }, { value: 'bg-green-500', label: 'Green' }, { value: 'bg-cyan-500', label: 'Cyan' }, { value: 'bg-blue-500', label: 'Blue' }, { value: 'bg-indigo-500', label: 'Indigo' }, { value: 'bg-purple-500', label: 'Purple' }, ]; const STEP_TYPES: { value: WorkflowStepType; label: string }[] = [ { value: 'backlog', label: 'Backlog' }, { value: 'planning', label: 'Planning' }, { value: 'implementation', label: 'Implementation' }, { value: 'review', label: 'Review' }, { value: 'verification', label: 'Verification' }, { value: 'done', label: 'Done' }, { value: 'blocked', label: 'Blocked' }, ]; type WorkflowStepEditorProps = { steps: WorkflowStep[]; onUpdateStep: (stepId: string, updates: Partial) => void; onAddStep: () => void; onRemoveStep: (stepId: string) => void; onReorderSteps: (steps: WorkflowStep[]) => void; readOnly?: boolean; }; type SortableStepCardProps = { step: WorkflowStep; onUpdate: (updates: Partial) => void; onRemove: () => void; readOnly?: boolean; }; // Inner component that uses step values directly - reset via key prop from parent function SortableStepCardInner({ step, onUpdate, onRemove, readOnly = false }: SortableStepCardProps) { const [expanded, setExpanded] = useState(false); // Local state for text inputs to avoid API calls on every keystroke const [localName, setLocalName] = useState(step.name); const [localPromptPrefix, setLocalPromptPrefix] = useState(step.behaviors?.promptPrefix ?? ''); const [localPromptSuffix, setLocalPromptSuffix] = useState(step.behaviors?.promptSuffix ?? ''); const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: step.id, }); const style = { transform: CSS.Transform.toString(transform), transition, }; const behaviors = step.behaviors ?? {}; // Debounced update functions const debouncedUpdateName = useDebouncedCallback((name: string) => { onUpdate({ name }); }, 500); const debouncedUpdatePromptPrefix = useDebouncedCallback((promptPrefix: string) => { onUpdate({ behaviors: { ...behaviors, promptPrefix } }); }, 500); const debouncedUpdatePromptSuffix = useDebouncedCallback((promptSuffix: string) => { onUpdate({ behaviors: { ...behaviors, promptSuffix } }); }, 500); const updateBehaviors = (updates: Partial) => { onUpdate({ behaviors: { ...behaviors, ...updates } }); }; return (
{ if (readOnly) return; setLocalName(e.target.value); debouncedUpdateName(e.target.value); }} className="w-full min-w-0" placeholder="Step name" disabled={readOnly} />
{behaviors.autoStartAgent && } {behaviors.requireApproval && } {behaviors.planMode && }
{expanded && (
{ if (readOnly) return; updateBehaviors({ autoStartAgent: checked === true }); }} disabled={readOnly} />
{ if (readOnly) return; updateBehaviors({ planMode: checked === true }); }} disabled={readOnly} />
{ if (readOnly) return; updateBehaviors({ requireApproval: checked === true }); }} disabled={readOnly} />