diff --git a/frontend/src/pages/MyTreesPage.tsx b/frontend/src/pages/MyTreesPage.tsx index 55ef180e..04422db4 100644 --- a/frontend/src/pages/MyTreesPage.tsx +++ b/frontend/src/pages/MyTreesPage.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' import { useNavigate, Link } from 'react-router-dom' -import { Play, Pencil, Share2, Trash2, GitBranch, Clock, TrendingUp, FolderTree, Plus, ListOrdered, ChevronDown } from 'lucide-react' +import { Play, Pencil, Share2, Trash2, GitBranch, Clock, TrendingUp, FolderTree, Plus, ListOrdered, ChevronDown, Wrench } from 'lucide-react' import { treesApi } from '@/api/trees' import { sessionsApi } from '@/api/sessions' import type { TreeListItem } from '@/types' @@ -71,7 +71,9 @@ export function MyTreesPage() { } const handleStartSession = (tree: TreeWithStats) => { - if (tree.tree_type === 'procedural') { + if (tree.tree_type === 'maintenance') { + navigate(`/flows/${tree.id}/maintenance`) + } else if (tree.tree_type === 'procedural') { navigate(`/flows/${tree.id}/navigate`) } else { navigate(`/trees/${tree.id}/navigate`) @@ -79,7 +81,8 @@ export function MyTreesPage() { } const getEditPath = (tree: TreeWithStats) => { - return tree.tree_type === 'procedural' ? `/flows/${tree.id}/edit` : `/trees/${tree.id}/edit` + return tree.tree_type === 'procedural' || tree.tree_type === 'maintenance' + ? `/flows/${tree.id}/edit` : `/trees/${tree.id}/edit` } const handleDeleteTree = async () => { @@ -153,6 +156,17 @@ export function MyTreesPage() {
Step-by-step procedure
+ setShowCreateMenu(false)} + className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent" + > + +
+
Maintenance Flow
+
Scheduled multi-target tasks
+
+ )} @@ -209,6 +223,9 @@ export function MyTreesPage() { {tree.tree_type === 'procedural' && ( )} + {tree.tree_type === 'maintenance' && ( + + )}

{tree.name}

@@ -217,6 +234,11 @@ export function MyTreesPage() { Procedure )} + {tree.tree_type === 'maintenance' && ( + + Maintenance + + )} {tree.category_info && ( {tree.category_info.name} diff --git a/frontend/src/pages/ProceduralEditorPage.tsx b/frontend/src/pages/ProceduralEditorPage.tsx index ab1644db..de8fa565 100644 --- a/frontend/src/pages/ProceduralEditorPage.tsx +++ b/frontend/src/pages/ProceduralEditorPage.tsx @@ -1,20 +1,23 @@ import { useEffect } from 'react' -import { useParams, useNavigate } from 'react-router-dom' -import { Save, ArrowLeft, ListOrdered } from 'lucide-react' +import { useParams, useNavigate, useSearchParams } from 'react-router-dom' +import { Save, ArrowLeft, ListOrdered, Wrench } from 'lucide-react' import { treesApi } from '@/api/trees' import { useProceduralEditorStore } from '@/store/proceduralEditorStore' import { IntakeFormBuilder } from '@/components/procedural-editor/IntakeFormBuilder' import { StepList } from '@/components/procedural-editor/StepList' import { TagInput } from '@/components/common/TagInput' import { toast } from '@/lib/toast' +import type { TreeType } from '@/types' export function ProceduralEditorPage() { const { id } = useParams<{ id: string }>() + const [searchParams] = useSearchParams() const navigate = useNavigate() const isEditMode = !!id const { treeId, + treeType, name, description, tags, @@ -34,12 +37,16 @@ export function ProceduralEditorPage() { getTreeForSave, } = useProceduralEditorStore() + const isMaintenance = treeType === 'maintenance' + const flowLabel = isMaintenance ? 'Maintenance Flow' : 'Procedure' + // Load tree or init new useEffect(() => { if (isEditMode && id) { loadExistingTree(id) } else { - initNew() + const urlType = searchParams.get('type') + initNew((urlType === 'maintenance' ? 'maintenance' : 'procedural') as TreeType) } return () => { reset() } @@ -48,21 +55,21 @@ export function ProceduralEditorPage() { const loadExistingTree = async (treeId: string) => { try { const tree = await treesApi.get(treeId) - if (tree.tree_type !== 'procedural') { - toast.error('This tree is not a procedural flow') + if (tree.tree_type !== 'procedural' && tree.tree_type !== 'maintenance') { + toast.error('This flow is not a procedural or maintenance flow') navigate('/my-trees') return } loadTree(tree) } catch { - toast.error('Failed to load procedure') + toast.error('Failed to load flow') navigate('/my-trees') } } const handleSave = async (saveStatus?: 'draft' | 'published') => { if (!name.trim()) { - toast.error('Please enter a name for the procedure') + toast.error(`Please enter a name for the ${flowLabel.toLowerCase()}`) return } @@ -76,18 +83,18 @@ export function ProceduralEditorPage() { if (isEditMode && treeId) { await treesApi.update(treeId, payload) markSaved() - toast.success('Procedure saved') + toast.success(`${flowLabel} saved`) } else { const created = await treesApi.create(payload) markSaved() - toast.success('Procedure created') + 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 procedure' + const errorText = typeof message === 'string' ? message : typeof message === 'object' && message?.message ? message.message : `Failed to save ${flowLabel.toLowerCase()}` toast.error(errorText) } finally { setIsSaving(false) @@ -114,9 +121,11 @@ export function ProceduralEditorPage() {
- + {isMaintenance + ? + : }

- {isEditMode ? 'Edit Procedure' : 'New Procedure'} + {isEditMode ? `Edit ${flowLabel}` : `New ${flowLabel}`}

diff --git a/frontend/src/pages/TreeLibraryPage.tsx b/frontend/src/pages/TreeLibraryPage.tsx index 6988b5a4..955f7a75 100644 --- a/frontend/src/pages/TreeLibraryPage.tsx +++ b/frontend/src/pages/TreeLibraryPage.tsx @@ -196,7 +196,9 @@ export function TreeLibraryPage() { } const handleStartSession = (treeId: string, treeType?: string) => { - if (treeType === 'procedural') { + if (treeType === 'maintenance') { + navigate(`/flows/${treeId}/maintenance`) + } else if (treeType === 'procedural') { navigate(`/flows/${treeId}/navigate`) } else { navigate(`/trees/${treeId}/navigate`) @@ -267,14 +269,14 @@ export function TreeLibraryPage() { {canCreateTrees && ( - {typeFilter === 'procedural' ? 'New Project' : 'Create Flow'} + {typeFilter === 'procedural' ? 'New Project' : typeFilter === 'maintenance' ? 'New Maintenance Flow' : 'Create Flow'} )} diff --git a/frontend/src/store/proceduralEditorStore.ts b/frontend/src/store/proceduralEditorStore.ts index 73156b1d..b9beff1d 100644 --- a/frontend/src/store/proceduralEditorStore.ts +++ b/frontend/src/store/proceduralEditorStore.ts @@ -64,6 +64,7 @@ function createDefaultField(index: number, existingFields: IntakeFormField[]): I interface ProceduralEditorState { // Tree metadata treeId: string | null + treeType: TreeType name: string description: string categoryId: string | null @@ -83,11 +84,12 @@ interface ProceduralEditorState { isSaving: boolean // Actions - Init - initNew: () => void + initNew: (type?: TreeType) => void loadTree: (tree: Tree) => void reset: () => void // Actions - Metadata + setTreeType: (treeType: TreeType) => void setName: (name: string) => void setDescription: (description: string) => void setCategoryId: (categoryId: string | null) => void @@ -131,6 +133,7 @@ export const useProceduralEditorStore = create()( immer((set, get) => ({ // Initial state treeId: null, + treeType: 'procedural' as TreeType, name: '', description: '', categoryId: null, @@ -146,9 +149,10 @@ export const useProceduralEditorStore = create()( isSaving: false, // Init - initNew: () => { + initNew: (type?: TreeType) => { set((state) => { state.treeId = null + state.treeType = type || 'procedural' state.name = '' state.description = '' state.categoryId = null @@ -169,6 +173,7 @@ export const useProceduralEditorStore = create()( const structure = tree.tree_structure as unknown as ProceduralTreeStructure set((state) => { state.treeId = tree.id + state.treeType = tree.tree_type state.name = tree.name state.description = tree.description || '' state.categoryId = tree.category_id @@ -188,6 +193,7 @@ export const useProceduralEditorStore = create()( reset: () => { set((state) => { state.treeId = null + state.treeType = 'procedural' state.name = '' state.description = '' state.categoryId = null @@ -205,6 +211,7 @@ export const useProceduralEditorStore = create()( }, // Metadata + setTreeType: (treeType) => set((state) => { state.treeType = treeType }), setName: (name) => set((state) => { state.name = name; state.isDirty = true }), setDescription: (description) => set((state) => { state.description = description; state.isDirty = true }), setCategoryId: (categoryId) => set((state) => { state.categoryId = categoryId; state.isDirty = true }), @@ -343,7 +350,7 @@ export const useProceduralEditorStore = create()( return { name: state.name, description: state.description, - tree_type: 'procedural' as TreeType, + tree_type: state.treeType, tree_structure: { steps: state.steps }, intake_form: state.intakeForm.length > 0 ? state.intakeForm : undefined, category_id: state.categoryId,