Add URL intake field type, fix variable name editing collapsing fields (index-based keys/updates), auto-generate variable names by field type, add section header as first-class step type, and simplify step editor with "More Options" collapsible for advanced fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
201 lines
7.5 KiB
TypeScript
201 lines
7.5 KiB
TypeScript
import { Plus, GripVertical, Trash2, ChevronDown, CheckCircle2, AlertTriangle, Info, Zap, Shield, SeparatorHorizontal } from 'lucide-react'
|
|
import type { StepContentType } from '@/types'
|
|
import { StepEditor } from './StepEditor'
|
|
import { useProceduralEditorStore } from '@/store/proceduralEditorStore'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
const contentTypeConfig: Record<StepContentType, { icon: typeof Zap; color: string; label: string }> = {
|
|
action: { icon: Zap, color: 'text-blue-400', label: 'Action' },
|
|
informational: { icon: Info, color: 'text-white/50', label: 'Info' },
|
|
verification: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Verify' },
|
|
warning: { icon: AlertTriangle, color: 'text-yellow-400', label: 'Warning' },
|
|
}
|
|
|
|
export function StepList() {
|
|
const {
|
|
steps,
|
|
intakeForm,
|
|
expandedStepId,
|
|
setExpandedStepId,
|
|
addStep,
|
|
addSectionHeader,
|
|
removeStep,
|
|
updateStep,
|
|
} = useProceduralEditorStore()
|
|
|
|
const procedureSteps = steps.filter((s) => s.type === 'procedure_step')
|
|
let stepCounter = 0
|
|
|
|
return (
|
|
<div className="glass-card rounded-2xl p-4 sm:p-6">
|
|
<div className="mb-4 flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<Shield className="h-5 w-5 text-white/50" />
|
|
<h2 className="text-lg font-semibold text-white">Steps</h2>
|
|
<span className="text-sm text-white/40">
|
|
({procedureSteps.length} step{procedureSteps.length !== 1 ? 's' : ''})
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={() => addSectionHeader()}
|
|
className="flex items-center gap-1.5 rounded-md border border-white/10 px-3 py-1.5 text-sm text-white/60 hover:bg-white/10 hover:text-white"
|
|
>
|
|
<SeparatorHorizontal className="h-3.5 w-3.5" />
|
|
Add Section
|
|
</button>
|
|
<button
|
|
onClick={() => addStep()}
|
|
className="flex items-center gap-1.5 rounded-md border border-white/10 px-3 py-1.5 text-sm text-white/60 hover:bg-white/10 hover:text-white"
|
|
>
|
|
<Plus className="h-3.5 w-3.5" />
|
|
Add Step
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
{steps.map((step) => {
|
|
if (step.type === 'procedure_end') {
|
|
return (
|
|
<div
|
|
key={step.id}
|
|
className="flex items-center gap-2 rounded-lg border border-dashed border-white/10 bg-white/[0.02] px-3 py-2"
|
|
>
|
|
<CheckCircle2 className="h-4 w-4 text-emerald-400/50" />
|
|
<input
|
|
type="text"
|
|
value={step.title}
|
|
onChange={(e) => updateStep(step.id, { title: e.target.value })}
|
|
className="flex-1 bg-transparent text-sm text-white/50 focus:outline-none"
|
|
placeholder="Procedure Complete"
|
|
/>
|
|
<span className="text-[10px] text-white/30">END</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Section header rendering
|
|
if (step.type === 'section_header') {
|
|
const isExpanded = expandedStepId === step.id
|
|
|
|
if (isExpanded) {
|
|
return (
|
|
<div key={step.id}>
|
|
<StepEditor
|
|
step={step}
|
|
stepNumber={0}
|
|
onUpdate={(updates) => updateStep(step.id, updates)}
|
|
onCollapse={() => setExpandedStepId(null)}
|
|
availableVariables={intakeForm}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
key={step.id}
|
|
className="group flex items-center gap-2 border-b border-white/[0.06] pb-1 pt-3"
|
|
>
|
|
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-white/20 group-hover:text-white/40" />
|
|
<span
|
|
className="min-w-0 flex-1 cursor-pointer text-xs font-semibold uppercase tracking-wider text-white/40 hover:text-white/60"
|
|
onClick={() => setExpandedStepId(step.id)}
|
|
>
|
|
{step.title || 'Untitled Section'}
|
|
</span>
|
|
<button
|
|
onClick={() => removeStep(step.id)}
|
|
className="shrink-0 rounded p-1 text-white/30 opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
|
|
>
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 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
|
|
|
|
if (isExpanded) {
|
|
return (
|
|
<div key={step.id}>
|
|
<StepEditor
|
|
step={step}
|
|
stepNumber={stepNumber}
|
|
onUpdate={(updates) => updateStep(step.id, updates)}
|
|
onCollapse={() => setExpandedStepId(null)}
|
|
availableVariables={intakeForm}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div key={step.id}>
|
|
<div
|
|
className={cn(
|
|
'group flex items-center gap-2 rounded-xl border border-white/[0.06] px-3 py-2.5 transition-colors',
|
|
'hover:border-white/10 hover:bg-white/[0.03]'
|
|
)}
|
|
>
|
|
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-white/20 group-hover:text-white/40" />
|
|
|
|
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/10 text-xs font-medium text-white/70">
|
|
{stepNumber}
|
|
</span>
|
|
|
|
<span className={cn('shrink-0', config.color)}>
|
|
<Icon className="h-3.5 w-3.5" />
|
|
</span>
|
|
|
|
<span
|
|
className="min-w-0 flex-1 cursor-pointer truncate text-sm text-white"
|
|
onClick={() => setExpandedStepId(step.id)}
|
|
>
|
|
{step.title || 'Untitled step'}
|
|
</span>
|
|
|
|
{step.estimated_minutes && (
|
|
<span className="shrink-0 text-[10px] text-white/30">
|
|
~{step.estimated_minutes}m
|
|
</span>
|
|
)}
|
|
|
|
<button
|
|
onClick={() => setExpandedStepId(step.id)}
|
|
className="shrink-0 rounded p-1 text-white/30 hover:bg-white/10 hover:text-white"
|
|
>
|
|
<ChevronDown className="h-3.5 w-3.5" />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => removeStep(step.id)}
|
|
className="shrink-0 rounded p-1 text-white/30 opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
|
|
>
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Add step button at bottom */}
|
|
<button
|
|
onClick={() => addStep()}
|
|
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-white/10 py-2 text-sm text-white/40 transition-colors hover:border-white/20 hover:text-white/60"
|
|
>
|
|
<Plus className="h-3.5 w-3.5" />
|
|
Add Step
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|