import { useEffect, useState, useCallback } from 'react' import { useParams, useNavigate, useSearchParams } from 'react-router-dom' import { Save, ArrowLeft, ListOrdered, Wrench, Settings, FileText, Calendar } from 'lucide-react' import { treesApi } from '@/api/trees' import { useProceduralEditorStore } from '@/store/proceduralEditorStore' import { CollapsibleEditorSection } from '@/components/procedural-editor/CollapsibleEditorSection' import { IntakeFormBuilder } from '@/components/procedural-editor/IntakeFormBuilder' import { MaintenanceScheduleSection } from '@/components/procedural-editor/MaintenanceScheduleSection' import { getScheduleSummary } from '@/components/procedural-editor/scheduleUtils' import { StepList } from '@/components/procedural-editor/StepList' import { TagInput } from '@/components/common/TagInput' import { Spinner } from '@/components/common/Spinner' import { toast } from '@/lib/toast' import type { TreeType, MaintenanceSchedule, TargetList } from '@/types' type SectionKey = 'details' | 'intake' | 'schedule' export function ProceduralEditorPage() { const { id } = useParams<{ id: string }>() const [searchParams] = useSearchParams() const navigate = useNavigate() const isEditMode = !!id const { treeId, treeType, name, description, tags, isPublic, intakeForm, isDirty, isSaving, isLoading, initNew, loadTree, reset, setName, setDescription, setTags, setIsPublic, setIsSaving, markSaved, getTreeForSave, } = useProceduralEditorStore() const isMaintenance = treeType === 'maintenance' const flowLabel = isMaintenance ? 'Maintenance Flow' : 'Procedure' // Accordion state: only one section open at a time const [expandedSection, setExpandedSection] = useState( isEditMode ? null : 'details' ) // Schedule state for collapsed summary const [schedule, setSchedule] = useState(null) const [scheduleTargetList, setScheduleTargetList] = useState(null) const toggleSection = useCallback((key: SectionKey) => { setExpandedSection(prev => prev === key ? null : key) }, []) const handleScheduleLoaded = useCallback((s: MaintenanceSchedule | null, tl: TargetList | null) => { setSchedule(s) setScheduleTargetList(tl) }, []) // Load tree or init new useEffect(() => { if (isEditMode && id) { loadExistingTree(id) } else { const urlType = searchParams.get('type') initNew((urlType === 'maintenance' ? 'maintenance' : 'procedural') as TreeType) // New flows: details expanded, or schedule for new maintenance setExpandedSection(urlType === 'maintenance' ? 'schedule' : 'details') } return () => { reset() } }, [id]) const loadExistingTree = async (treeId: string) => { try { const tree = await treesApi.get(treeId) if (tree.tree_type !== 'procedural' && tree.tree_type !== 'maintenance') { toast.error('This flow is not a procedural or maintenance flow') navigate('/trees') return } loadTree(tree) } catch { toast.error('Failed to load flow') navigate('/trees') } } const handleSave = async (saveStatus?: 'draft' | 'published') => { if (!name.trim()) { toast.error(`Please enter a name for the ${flowLabel.toLowerCase()}`) return } setIsSaving(true) try { const payload = getTreeForSave() if (saveStatus) { payload.status = saveStatus } if (isEditMode && treeId) { await treesApi.update(treeId, payload) markSaved() toast.success(`${flowLabel} saved`) } else { const created = await treesApi.create(payload) markSaved() toast.success(`${flowLabel} created`) navigate(`/flows/${created.id}/edit`, { replace: true }) } } catch (err: unknown) { const message = err && typeof err === 'object' && 'response' in err ? (err as { response?: { data?: { detail?: string | { message?: string } } } }).response?.data?.detail : null const errorText = typeof message === 'string' ? message : typeof message === 'object' && message?.message ? message.message : `Failed to save ${flowLabel.toLowerCase()}` toast.error(errorText) } finally { setIsSaving(false) } } // Summary strings for collapsed sections const detailsSummary = [ name ? `"${name}"` : '"Untitled"', tags.length > 0 ? `${tags.length} tag${tags.length !== 1 ? 's' : ''}` : 'No tags', isPublic ? 'Public' : 'Private', ].join(' \u00b7 ') const scheduleSummary = getScheduleSummary(schedule, scheduleTargetList) const intakeSummary = intakeForm.length === 0 ? 'No fields defined' : `${intakeForm.length} field${intakeForm.length !== 1 ? 's' : ''}: ${intakeForm.map(f => f.label || f.variable_name).slice(0, 4).join(', ')}${intakeForm.length > 4 ? ', \u2026' : ''}` if (isLoading) { return (
) } return (
{/* Toolbar — sticky */}
{isMaintenance ? : }

{isEditMode ? `Edit ${flowLabel}` : `New ${flowLabel}`} {name && — {name}}

{isDirty && ( Unsaved changes )}
{/* Collapsible sections */}
} summary={detailsSummary} expanded={expandedSection === 'details'} onToggle={() => toggleSection('details')} >
setName(e.target.value)} placeholder="e.g. Domain Controller Build" className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20" />