Collapsible component supporting edit and execute modes. Edit mode provides title/description inputs with add/remove controls. Execute mode shows "This worked" / "Didn't help" action buttons with emerald/ rose styling. Amber accent styling throughout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
154 lines
6.3 KiB
TypeScript
154 lines
6.3 KiB
TypeScript
import { useState } from 'react'
|
|
import { AlertCircle, ChevronDown, ChevronRight, Plus, Trash2, Check, X } from 'lucide-react'
|
|
import type { ProceduralStep } from '@/types'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface FallbackStepsProps {
|
|
fallbackSteps: ProceduralStep[]
|
|
mode: 'edit' | 'execute'
|
|
// Edit mode
|
|
onAdd?: () => void
|
|
onRemove?: (index: number) => void
|
|
onUpdate?: (index: number, updates: Partial<ProceduralStep>) => void
|
|
// Execute mode
|
|
onComplete?: (stepId: string, notes: string | null, outcome: 'resolved' | 'not_resolved' | 'skipped') => void
|
|
completedIds?: Set<string>
|
|
}
|
|
|
|
export function FallbackSteps({
|
|
fallbackSteps,
|
|
mode,
|
|
onAdd,
|
|
onRemove,
|
|
onUpdate,
|
|
onComplete,
|
|
completedIds,
|
|
}: FallbackStepsProps) {
|
|
const [expanded, setExpanded] = useState(false)
|
|
|
|
// In execute mode, hide if no fallback steps
|
|
if (mode === 'execute' && fallbackSteps.length === 0) {
|
|
return null
|
|
}
|
|
|
|
const toggleLabel =
|
|
mode === 'execute'
|
|
? "Didn't work?"
|
|
: `Fallback branches (${fallbackSteps.length})`
|
|
|
|
return (
|
|
<div className="mt-4">
|
|
{/* Toggle button */}
|
|
<button
|
|
type="button"
|
|
onClick={() => setExpanded((v) => !v)}
|
|
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-amber-400/80 transition-colors"
|
|
>
|
|
<AlertCircle className="h-4 w-4 text-amber-400/80 shrink-0" />
|
|
<span>{toggleLabel}</span>
|
|
{expanded ? (
|
|
<ChevronDown className="h-4 w-4" />
|
|
) : (
|
|
<ChevronRight className="h-4 w-4" />
|
|
)}
|
|
</button>
|
|
|
|
{expanded && (
|
|
<div className="mt-3 border-l-2 border-amber-400/20 pl-4">
|
|
<div className="space-y-2">
|
|
{fallbackSteps.map((fbStep, index) => {
|
|
const isCompleted = completedIds?.has(fbStep.id)
|
|
|
|
return (
|
|
<div
|
|
key={fbStep.id}
|
|
className={cn(
|
|
'rounded-lg border p-3 transition-colors',
|
|
'bg-white/[0.02] border-border/50',
|
|
isCompleted && 'border-emerald-500/30 bg-emerald-500/5'
|
|
)}
|
|
>
|
|
{mode === 'edit' ? (
|
|
<div className="space-y-2">
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="text"
|
|
value={fbStep.title}
|
|
onChange={(e) => onUpdate?.(index, { title: e.target.value })}
|
|
placeholder="Fallback step title"
|
|
className="flex-1 rounded border border-border bg-card px-2.5 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => onRemove?.(index)}
|
|
className="shrink-0 rounded p-1.5 text-muted-foreground hover:bg-rose-500/10 hover:text-rose-400 transition-colors"
|
|
title="Remove fallback step"
|
|
>
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
</button>
|
|
</div>
|
|
<textarea
|
|
value={fbStep.description || ''}
|
|
onChange={(e) =>
|
|
onUpdate?.(index, { description: e.target.value || undefined })
|
|
}
|
|
placeholder="Describe this alternative approach..."
|
|
rows={2}
|
|
className="w-full rounded border border-border bg-card px-2.5 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
|
/>
|
|
</div>
|
|
) : (
|
|
// Execute mode
|
|
<div>
|
|
<p className={cn('text-sm font-medium', isCompleted ? 'text-emerald-400' : 'text-foreground')}>
|
|
{fbStep.title}
|
|
</p>
|
|
{fbStep.description && (
|
|
<p className="mt-1 text-xs text-muted-foreground">{fbStep.description}</p>
|
|
)}
|
|
{!isCompleted && (
|
|
<div className="mt-3 flex gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => onComplete?.(fbStep.id, null, 'resolved')}
|
|
className="flex items-center gap-1.5 rounded-lg border border-emerald-500/30 bg-emerald-500/10 px-3 py-1.5 text-xs font-medium text-emerald-400 hover:bg-emerald-500/20 transition-colors"
|
|
>
|
|
<Check className="h-3.5 w-3.5" />
|
|
This worked
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => onComplete?.(fbStep.id, null, 'not_resolved')}
|
|
className="flex items-center gap-1.5 rounded-lg border border-rose-500/30 bg-rose-500/10 px-3 py-1.5 text-xs font-medium text-rose-400 hover:bg-rose-500/20 transition-colors"
|
|
>
|
|
<X className="h-3.5 w-3.5" />
|
|
Didn't help
|
|
</button>
|
|
</div>
|
|
)}
|
|
{isCompleted && (
|
|
<p className="mt-2 text-xs text-emerald-400/70">Resolved via this fallback</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
|
|
{mode === 'edit' && (
|
|
<button
|
|
type="button"
|
|
onClick={onAdd}
|
|
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-amber-400/20 px-3 py-2 text-xs text-amber-400/60 hover:border-amber-400/40 hover:text-amber-400/80 transition-colors"
|
|
>
|
|
<Plus className="h-3.5 w-3.5" />
|
|
Add fallback step
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|