diff --git a/frontend/src/components/procedural-editor/StepEditor.tsx b/frontend/src/components/procedural-editor/StepEditor.tsx index 46453d87..39d4506a 100644 --- a/frontend/src/components/procedural-editor/StepEditor.tsx +++ b/frontend/src/components/procedural-editor/StepEditor.tsx @@ -2,6 +2,7 @@ import { useState } from 'react' import { ChevronUp, ChevronDown, AlertTriangle, Clock, ExternalLink, CheckSquare, Terminal, Settings2 } from 'lucide-react' import type { ProceduralStep, StepContentType, IntakeFormField } from '@/types' import { cn } from '@/lib/utils' +import { FallbackSteps } from '@/components/procedural/FallbackSteps' const CONTENT_TYPE_OPTIONS: { value: StepContentType; label: string; color: string }[] = [ { value: 'action', label: 'Action', color: 'text-blue-400' }, @@ -278,6 +279,32 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa } )} + + {/* Fallback Steps — procedure_step only */} + {step.type === 'procedure_step' && ( + { + const newFallback: ProceduralStep = { + id: crypto.randomUUID(), + type: 'procedure_step', + title: '', + } + onUpdate({ fallback_steps: [...(step.fallback_steps ?? []), newFallback] }) + }} + onRemove={(index) => { + const updated = (step.fallback_steps ?? []).filter((_, i) => i !== index) + onUpdate({ fallback_steps: updated.length > 0 ? updated : undefined }) + }} + onUpdate={(index, updates) => { + const updated = (step.fallback_steps ?? []).map((fb, i) => + i === index ? { ...fb, ...updates } : fb + ) + onUpdate({ fallback_steps: updated }) + }} + /> + )} ) diff --git a/frontend/src/pages/ProceduralNavigationPage.tsx b/frontend/src/pages/ProceduralNavigationPage.tsx index 25de94d8..5d5cacac 100644 --- a/frontend/src/pages/ProceduralNavigationPage.tsx +++ b/frontend/src/pages/ProceduralNavigationPage.tsx @@ -5,7 +5,8 @@ import { treesApi } from '@/api/trees' import { sessionsApi } from '@/api/sessions' import { stepsApi } from '@/api/steps' import type { Tree, Session, ProceduralStep, DecisionRecord, RuntimeStep, CustomProceduralStep, IntakeFormField } from '@/types' -import type { CustomStep } from '@/types/session' +import type { CustomStep, FallbackStepRecord } from '@/types/session' +import { FallbackSteps } from '@/components/procedural/FallbackSteps' import type { Step } from '@/types/step' import { StepChecklist } from '@/components/procedural/StepChecklist' import { StepDetail } from '@/components/procedural/StepDetail' @@ -84,6 +85,9 @@ export function ProceduralNavigationPage() { const [batchProgress, setBatchProgress] = useState<{ completed: number; total: number } | null>(null) const timerRef = useRef | null>(null) + // Fallback step decisions + const [fallbackDecisions, setFallbackDecisions] = useState([]) + // Custom step state const [runtimeSteps, setRuntimeSteps] = useState([]) const [sessionCustomSteps, setSessionCustomSteps] = useState([]) @@ -434,6 +438,22 @@ export function ProceduralNavigationPage() { setShowCsatModal(false) } + const handleFallbackComplete = ( + parentStepId: string, + fallbackStepId: string, + notes: string | null, + outcome: 'resolved' | 'not_resolved' | 'skipped' + ) => { + const record: FallbackStepRecord = { + parent_step_id: parentStepId, + fallback_step_id: fallbackStepId, + completed_at: new Date().toISOString(), + notes, + outcome, + } + setFallbackDecisions((prev) => [...prev, record]) + } + const handleStepCreated = (step: Step | CustomStepDraft, isFromLibrary: boolean) => { setPendingCustomStep(step) setPendingIsFromLibrary(isFromLibrary) @@ -734,6 +754,22 @@ export function ProceduralNavigationPage() { /> )} + {/* Fallback steps — shown when step has fallback alternatives */} + {currentStep && !('isCustom' in currentStep && currentStep.isCustom) && 'fallback_steps' in currentStep && ( + d.parent_step_id === currentStep.id && d.outcome === 'resolved') + .map((d) => d.fallback_step_id) + )} + onComplete={(fallbackStepId, notes, outcome) => + handleFallbackComplete(currentStep.id, fallbackStepId, notes, outcome) + } + /> + )} + {/* Add custom step — only on current active incomplete non-custom step */} {currentStep && !completedStepIds.has(currentStep.id) && !('isCustom' in currentStep && currentStep.isCustom) && (