feat: integrate FallbackSteps into editor and session runner (Task 18)
Wire FallbackSteps edit mode into StepEditor for procedure_step type with add/remove/update handlers using crypto.randomUUID(). Add execute mode rendering in ProceduralNavigationPage with fallbackDecisions state tracking per parent step. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState } from 'react'
|
|||||||
import { ChevronUp, ChevronDown, AlertTriangle, Clock, ExternalLink, CheckSquare, Terminal, Settings2 } from 'lucide-react'
|
import { ChevronUp, ChevronDown, AlertTriangle, Clock, ExternalLink, CheckSquare, Terminal, Settings2 } from 'lucide-react'
|
||||||
import type { ProceduralStep, StepContentType, IntakeFormField } from '@/types'
|
import type { ProceduralStep, StepContentType, IntakeFormField } from '@/types'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { FallbackSteps } from '@/components/procedural/FallbackSteps'
|
||||||
|
|
||||||
const CONTENT_TYPE_OPTIONS: { value: StepContentType; label: string; color: string }[] = [
|
const CONTENT_TYPE_OPTIONS: { value: StepContentType; label: string; color: string }[] = [
|
||||||
{ value: 'action', label: 'Action', color: 'text-blue-400' },
|
{ value: 'action', label: 'Action', color: 'text-blue-400' },
|
||||||
@@ -278,6 +279,32 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Fallback Steps — procedure_step only */}
|
||||||
|
{step.type === 'procedure_step' && (
|
||||||
|
<FallbackSteps
|
||||||
|
fallbackSteps={step.fallback_steps ?? []}
|
||||||
|
mode="edit"
|
||||||
|
onAdd={() => {
|
||||||
|
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 })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { treesApi } from '@/api/trees'
|
|||||||
import { sessionsApi } from '@/api/sessions'
|
import { sessionsApi } from '@/api/sessions'
|
||||||
import { stepsApi } from '@/api/steps'
|
import { stepsApi } from '@/api/steps'
|
||||||
import type { Tree, Session, ProceduralStep, DecisionRecord, RuntimeStep, CustomProceduralStep, IntakeFormField } from '@/types'
|
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 type { Step } from '@/types/step'
|
||||||
import { StepChecklist } from '@/components/procedural/StepChecklist'
|
import { StepChecklist } from '@/components/procedural/StepChecklist'
|
||||||
import { StepDetail } from '@/components/procedural/StepDetail'
|
import { StepDetail } from '@/components/procedural/StepDetail'
|
||||||
@@ -84,6 +85,9 @@ export function ProceduralNavigationPage() {
|
|||||||
const [batchProgress, setBatchProgress] = useState<{ completed: number; total: number } | null>(null)
|
const [batchProgress, setBatchProgress] = useState<{ completed: number; total: number } | null>(null)
|
||||||
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||||
|
|
||||||
|
// Fallback step decisions
|
||||||
|
const [fallbackDecisions, setFallbackDecisions] = useState<FallbackStepRecord[]>([])
|
||||||
|
|
||||||
// Custom step state
|
// Custom step state
|
||||||
const [runtimeSteps, setRuntimeSteps] = useState<RuntimeStep[]>([])
|
const [runtimeSteps, setRuntimeSteps] = useState<RuntimeStep[]>([])
|
||||||
const [sessionCustomSteps, setSessionCustomSteps] = useState<CustomStep[]>([])
|
const [sessionCustomSteps, setSessionCustomSteps] = useState<CustomStep[]>([])
|
||||||
@@ -434,6 +438,22 @@ export function ProceduralNavigationPage() {
|
|||||||
setShowCsatModal(false)
|
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) => {
|
const handleStepCreated = (step: Step | CustomStepDraft, isFromLibrary: boolean) => {
|
||||||
setPendingCustomStep(step)
|
setPendingCustomStep(step)
|
||||||
setPendingIsFromLibrary(isFromLibrary)
|
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 && (
|
||||||
|
<FallbackSteps
|
||||||
|
fallbackSteps={(currentStep as ProceduralStep).fallback_steps ?? []}
|
||||||
|
mode="execute"
|
||||||
|
completedIds={new Set(
|
||||||
|
fallbackDecisions
|
||||||
|
.filter((d) => 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 */}
|
{/* Add custom step — only on current active incomplete non-custom step */}
|
||||||
{currentStep && !completedStepIds.has(currentStep.id) && !('isCustom' in currentStep && currentStep.isCustom) && (
|
{currentStep && !completedStepIds.has(currentStep.id) && !('isCustom' in currentStep && currentStep.isCustom) && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user