import { useEffect, useMemo, useRef, useState } from 'react' import type { OrgRole, OrgEmployee, SavedOrgSummary } from '../types/visual' import { StructureEditor } from './StructureEditor' import { resolveRoleIcon } from './roleIcons' /* ── Inline SVG icon data-URIs (no external CDN — see P4.5 Phase 5) ─── */ const ICON = { rocket: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23888' d='M13.13 22.19L11.5 18.36c3.07-1.39 5.51-3.94 6.69-7.07L22 13l-8.87 9.19zM5.64 12.5L2 10.87l9.19-8.87 1.63 3.81c-3.13 1.18-5.68 3.62-7.07 6.69zM14.54 9.46c-.78-.78-.78-2.05 0-2.83s2.05-.78 2.83 0 .78 2.05 0 2.83c-.79.78-2.05.78-2.83 0zM8 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2z'/%3E%3C/svg%3E", people: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23888' d='M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z'/%3E%3C/svg%3E", check: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23888' d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z'/%3E%3C/svg%3E", addPerson: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23888' d='M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E", trash: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23888' d='M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z'/%3E%3C/svg%3E", team: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23888' d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E", deploy: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2322c55e' d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z'/%3E%3C/svg%3E", person: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23888' d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E", } /* ── Quick Start Wizard ────────────────────────────────────────── */ interface QuickStartProps { onComplete: (roles: Array<{ name: string; responsibility: string; reportsTo: string }>) => void onSwitchToTab: (target: 'employees' | 'architecture') => void } function QuickStartWizard({ onComplete, onSwitchToTab }: QuickStartProps) { const [step, setStep] = useState(1) const [members, setMembers] = useState>([ { name: '', resp: '', parent: 'owner' }, ]) const addMember = () => setMembers([...members, { name: '', resp: '', parent: 'owner' }]) const updateMember = (i: number, field: string, val: string) => { const next = [...members]; (next[i] as any)[field] = val; setMembers(next) } const removeMember = (i: number) => { if (members.length <= 1) return setMembers(members.filter((_, idx) => idx !== i)) } const validMembers = members.filter(m => m.name.trim()) const memberNames = validMembers.map(m => m.name.trim()).filter(Boolean) const handleFinish = () => { onComplete( validMembers.map(m => ({ name: m.name.trim(), responsibility: m.resp.trim(), reportsTo: m.parent })), ) } return (

Build Your Team

Create an org team in a few steps, or

{[ { n: 1, icon: ICON.people, label: 'Team Members' }, { n: 2, icon: ICON.check, label: 'Preview' }, ].map(s => (
s.n ? ' qs-step-done' : ''}`}> {s.label}
))}
{step === 1 && (

Who's on your team?

Add each team member with their name and what they do.

{members.map((m, i) => (
updateMember(i, 'name', e.target.value)} /> updateMember(i, 'resp', e.target.value)} /> {members.length > 1 && ( )}
))}
)} {step === 2 && (

Your team at a glance

Team ({validMembers.length} members)
{validMembers.map((m, i) => (
{m.name} {m.resp && — {m.resp}} {m.parent === 'owner' ? ' (reports to you)' : ` (managed by ${m.parent})`}
))}
Actor Runtime

Seat routing and delegation will be derived from your reporting structure and any team seats you configure later.

)}
) } /* ── TeamView ──────────────────────────────────────────────────── */ interface TeamViewProps { roles: OrgRole[] employees: OrgEmployee[] /** role_id -> recruited names for the selected session (canvas display only). */ sessionRecruitmentByRole?: Record | null isCustomMode?: boolean onAddRole: (roleId: string, name: string, responsibility: string, reportsTo: string, icon?: string | null) => void onBulkAddRoles?: (roles: Array<{ role_id: string; name: string; responsibility: string; reports_to: string }>) => void onUpdateRole: (roleId: string, updates: { name?: string; responsibility?: string; reports_to?: string; can_spawn?: string[]; icon?: string | null; execution_strategy?: string; preferred_external_agent?: string | null; prompt_refs?: string[] }) => void onDeleteRole: (roleId: string) => void onExport: (data: { package_id: string; name: string; description: string; version: string }) => void onImportEmployee?: (employeeId: string) => void onResetArchitecture?: () => void onSwitchToTab: (target: 'employees' | 'architecture') => void // Saved org architectures — passed to StructureEditor for toolbar pill savedOrgsList?: SavedOrgSummary[] | null activeSavedOrg?: string | null currentOrgVersion?: number versionAtLoad?: number | null onSavedOrgsList?: () => void onSavedOrgSaveAs?: (name: string, overwrite: boolean) => void onSavedOrgLoad?: (name: string) => void onSavedOrgDelete?: (name: string) => void } export function TeamView({ roles, employees, sessionRecruitmentByRole, isCustomMode, onAddRole, onBulkAddRoles, onUpdateRole, onDeleteRole, onExport, onImportEmployee, onResetArchitecture, onSwitchToTab, savedOrgsList, activeSavedOrg, currentOrgVersion, versionAtLoad, onSavedOrgsList, onSavedOrgSaveAs, onSavedOrgLoad, onSavedOrgDelete, }: TeamViewProps) { const [quickStartPending, setQuickStartPending] = useState(false) const [showExportForm, setShowExportForm] = useState(false) const [exportId, setExportId] = useState('') const [exportName, setExportName] = useState('') const [exportDesc, setExportDesc] = useState('') const [exportVersion, setExportVersion] = useState('1.0.0') const empByRole = useMemo(() => { const m = new Map() for (const e of employees) { const roleIds = e.role_ids?.length ? e.role_ids : [e.role_id] for (const roleId of roleIds) { if (!roleId) continue const list = m.get(roleId) || []; list.push(e); m.set(roleId, list) } } return m }, [employees]) const handleExport = () => { if (!exportId.trim() || !exportName.trim()) return onExport({ package_id: exportId.trim(), name: exportName.trim(), description: exportDesc, version: exportVersion }) setShowExportForm(false); setExportId(''); setExportName(''); setExportDesc(''); setExportVersion('1.0.0') } // When roles arrive after bulk add, clear the quick-start loading state. const quickStartTimer = useRef>(null) useEffect(() => { if (quickStartPending && roles.length > 0) { setQuickStartPending(false) if (quickStartTimer.current) { clearTimeout(quickStartTimer.current); quickStartTimer.current = null } } }, [roles.length, quickStartPending]) // Timeout: if roles never arrive within 10s, reset to wizard useEffect(() => { if (quickStartPending) { quickStartTimer.current = setTimeout(() => setQuickStartPending(false), 10000) return () => { if (quickStartTimer.current) clearTimeout(quickStartTimer.current) } } }, [quickStartPending]) const handleQuickStart = ( newRoles: Array<{ name: string; responsibility: string; reportsTo: string }>, ) => { const nameToId = new Map() const usedIds = new Set() const bulkRoles: Array<{ role_id: string; name: string; responsibility: string; reports_to: string }> = [] for (const r of newRoles) { let id = r.name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '').replace(/^-+|-+$/g, '') if (!id) continue // skip roles with empty ID (e.g. name was "!!!") // Deduplicate: append suffix if ID already used let finalId = id let suffix = 2 while (usedIds.has(finalId)) { finalId = `${id}-${suffix++}` } usedIds.add(finalId) nameToId.set(r.name, finalId) const parentId = r.reportsTo === 'owner' ? 'owner' : (nameToId.get(r.reportsTo) || 'owner') bulkRoles.push({ role_id: finalId, name: r.name, responsibility: r.responsibility, reports_to: parentId }) } if (bulkRoles.length === 0) return // all roles had invalid names if (onBulkAddRoles) { setQuickStartPending(true) onBulkAddRoles(bulkRoles) } else { for (const r of bulkRoles) onAddRole(r.role_id, r.name, r.responsibility, r.reports_to) setQuickStartPending(true) } } const handleReset = () => { if (!confirm('This will remove all org roles, employees, and work-item templates. Continue?')) return onResetArchitecture?.() } const assignedRoleCount = useMemo(() => { let count = 0 for (const role of roles) { if ((empByRole.get(role.role_id) ?? []).length > 0) count += 1 } return count }, [roles, empByRole]) const linkedEmployeeCount = useMemo( () => employees.filter(e => e.linked_agent_id).length, [employees], ) const vacantRoleCount = Math.max(0, roles.length - assignedRoleCount) // Show wizard only in org mode when no roles exist if (isCustomMode && roles.length === 0) { return (
{quickStartPending ? (
Setting up your team...
) : ( )}
) } return (
{isCustomMode ? 'Saved org workspace' : 'Corporate baseline'} {isCustomMode ? 'Editable company architecture' : 'Built-in company architecture'}
{assignedRoleCount} staffed roles {vacantRoleCount} vacant {linkedEmployeeCount} in office
{isCustomMode && (
{onResetArchitecture && ( )}
)}
{!isCustomMode && (
Corporate is fixed and read-only; saved company architectures are edited separately.
)} {/* Structure Editor (Canvas + Table + Inspector) */} {/* Export form */} {showExportForm && (

Export as .opcpkg

setExportId(e.target.value)} placeholder="my-architecture" />
setExportName(e.target.value)} placeholder="My Architecture" />
setExportDesc(e.target.value)} placeholder="An org team structure" />
setExportVersion(e.target.value)} />
)} {/* Team Roster — enhanced with actions */}

Team Roster

{employees.length} members {assignedRoleCount}/{roles.length} staffed
{roles.map(r => { const emps = empByRole.get(r.role_id) || [] return (
{r.name} {emps.length || 'vacant'}
{emps.length > 0 ? emps.map(e => (
{e.name} {e.seniority}
{e.linked_agent_id ? ( In Office ) : onImportEmployee && e.role_id ? ( ) : null}
)) : (
No members yet {isCustomMode && }
)}
) })}
) }