import { useCallback, useEffect, useState } from 'react' import { ArrowRight, Check, GitBranch, Plus, Workflow } from 'lucide-react' import { toast } from 'sonner' import { AppShell } from '@/components/AppShell' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card, CardContent } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from '@/components/ui/sheet' import { Textarea } from '@/components/ui/textarea' import { api } from '@/lib/api' import { useAuth } from '@/store/auth' interface Division { id: string name: string } interface ChangeSummary { id: string customerName: string title: string status: string estimateHours: number | null amount: number | null currency: string stepCount: number doneStepCount: number } interface ChangeStep { id: string divisionId: string | null divisionName: string | null title: string estimateHours: number status: string dependsOnStepId: string | null order: number } interface ChangeDetail { summary: ChangeSummary description: string | null totalStepHours: number steps: ChangeStep[] } const STAGES = ['Requested', 'Estimated', 'Approved', 'Paid', 'Live'] as const const STEP_STATUSES = ['Pending', 'InProgress', 'Done', 'Blocked'] as const // The single commercial action available at each pipeline stage. const ACTION: Record = { Requested: { label: 'Send estimate', path: 'estimate' }, Estimated: { label: 'Mark approved', path: 'approve' }, Approved: { label: 'Record payment', path: 'pay' }, Paid: { label: 'Go live', path: 'go-live' }, Live: null, Rejected: null, } const STATUS_TONE: Record = { Requested: 'bg-muted text-muted-foreground', Estimated: 'bg-sky-500/15 text-sky-700 dark:text-sky-400', Approved: 'bg-violet-500/15 text-violet-700 dark:text-violet-400', Paid: 'bg-amber-500/15 text-amber-700 dark:text-amber-400', Live: 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400', Rejected: 'bg-destructive/15 text-destructive', } export function PipelinePage() { const organizationId = useAuth((s) => s.organizationId) const [requests, setRequests] = useState([]) const [divisions, setDivisions] = useState([]) const [openId, setOpenId] = useState(null) const [creating, setCreating] = useState(false) const load = useCallback(async () => { if (!organizationId) return try { const [list, divs] = await Promise.all([ api.get(`/api/orgboard/change-requests?organizationId=${organizationId}`), api.get(`/api/orgboard/divisions?organizationId=${organizationId}`).catch(() => []), ]) setRequests(list) setDivisions(divs) } catch (err) { toast.error((err as Error).message) } }, [organizationId]) useEffect(() => { void load() }, [load]) return (

Delivery pipeline

Customer change requests across divisions: estimate → approve → pay → go-live.

{creating && organizationId && ( { setCreating(false) void load() }} /> )} {/* Pipeline legend */}
{STAGES.map((s, i) => ( {s} {i < STAGES.length - 1 && } ))}
{requests.map((cr) => { const pct = cr.stepCount > 0 ? Math.round((cr.doneStepCount / cr.stepCount) * 100) : 0 return ( ) })} {requests.length === 0 && ( No change requests yet. Log a customer ask to start the pipeline. )}
{openId && ( setOpenId(null)} onChanged={load} /> )}
) } function NewRequestForm({ organizationId, onCreated }: { organizationId: string; onCreated: () => void }) { const [customer, setCustomer] = useState('') const [title, setTitle] = useState('') const [description, setDescription] = useState('') const [busy, setBusy] = useState(false) async function submit() { if (!customer.trim() || !title.trim()) { toast.error('Customer and title are required.') return } setBusy(true) try { await api.post('/api/orgboard/change-requests', { organizationId, customerName: customer, title, description }) toast.success('Change request logged.') onCreated() } catch (err) { toast.error((err as Error).message) } finally { setBusy(false) } } return (
setCustomer(e.target.value)} placeholder="Acme Corp" />
setTitle(e.target.value)} placeholder="Add SSO to the portal" />