feat: flexible intake — deferred variables + prepared sessions (#103)

* feat: flexible intake — deferred variables + prepared sessions

Remove blocking intake form modal. Variables are now filled inline during
flow execution or pre-filled via prepared sessions. Adds PATCH /sessions/{id}/variables
endpoint, POST /sessions/prepare for session pre-staging, inline variable prompts
in StepDetail, editable Session Variables panel, and "Prepared for You" dashboard section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pass treeData directly to startSession to avoid stale state

setTree(treeData) hasn't committed when startSession runs immediately
after, so tree is still null and getStepsFromTree returns []. This
caused the step detail area to render empty on new session start.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: wire PrepareSessionModal entry point in Flow Library

Add "Prepare session" button (clipboard icon) to grid, list, and table
views for procedural/maintenance flows. Clicking fetches tree intake
fields and account members, then opens PrepareSessionModal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #103.
This commit is contained in:
chihlasm
2026-03-10 09:49:51 -04:00
committed by GitHub
parent 4727106141
commit ccd06c9ed4
25 changed files with 1214 additions and 102 deletions

View File

@@ -0,0 +1,83 @@
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { ClipboardList, ArrowRight, Clock } from 'lucide-react'
import { sessionsApi } from '@/api/sessions'
import type { Session } from '@/types/session'
import { getTreeNavigatePath } from '@/lib/routing'
import { cn } from '@/lib/utils'
export function PreparedSessions() {
const navigate = useNavigate()
const [sessions, setSessions] = useState<Session[]>([])
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
sessionsApi.list({ status: 'prepared', size: 10 })
.then(setSessions)
.catch(() => setSessions([]))
.finally(() => setIsLoading(false))
}, [])
if (isLoading || sessions.length === 0) return null
const handleStart = (session: Session) => {
const treeType = (session.tree_snapshot as unknown as Record<string, unknown>)?.tree_type as string | undefined
navigate(getTreeNavigatePath(session.tree_id, treeType), {
state: { sessionId: session.id },
})
}
return (
<div className="glass-card-static p-5 fade-in" style={{ animationDelay: '200ms' }}>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<ClipboardList className="h-4 w-4 text-cyan-400" />
<h3 className="font-heading text-sm font-semibold text-foreground">Prepared for You</h3>
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-cyan-400/20 text-[0.625rem] font-bold text-cyan-400">
{sessions.length}
</span>
</div>
</div>
<div className="space-y-2">
{sessions.map(session => {
const snapshot = session.tree_snapshot as unknown as Record<string, unknown>
const flowName = (snapshot?.name as string) || 'Unknown Flow'
const filledVars = Object.values(session.session_variables || {}).filter(v => v?.trim()).length
const treeType = snapshot?.tree_type as string | undefined
return (
<button
key={session.id}
onClick={() => handleStart(session)}
className={cn(
'group flex w-full items-center justify-between gap-3 rounded-lg border border-border px-4 py-3',
'text-left transition-all hover:border-cyan-500/30 hover:bg-cyan-500/5'
)}
>
<div className="min-w-0 flex-1">
<p className="text-sm font-medium text-foreground truncate">{flowName}</p>
<div className="mt-1 flex items-center gap-3 text-xs text-muted-foreground">
{session.ticket_number && (
<span>{session.ticket_number}</span>
)}
{session.client_name && (
<span>{session.client_name}</span>
)}
{filledVars > 0 && (
<span>{filledVars} variable{filledVars > 1 ? 's' : ''} pre-filled</span>
)}
<span className="flex items-center gap-1">
<Clock className="h-3 w-3" />
{treeType === 'procedural' ? 'Project' : treeType === 'maintenance' ? 'Maintenance' : 'Flow'}
</span>
</div>
</div>
<ArrowRight className="h-4 w-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100" />
</button>
)
})}
</div>
</div>
)
}