import { useEffect, useState } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { useAppPath } from '@core/hooks/useAppPath' import { apiFetchJson, getItem, createItem, updateItem } from '../../lib/workbench-data' import { Button } from '@core/components/ui/button' import { Input } from '@core/components/ui/input' import { Card, CardContent, CardHeader, CardTitle } from '@core/components/ui/card' import { Textarea } from '@core/components/ui/textarea' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@core/components/ui/select' import { WorkItemThread } from '../../components/work/WorkItemThread' import { ArrowLeft } from 'lucide-react' import { PRIORITY_OPTIONS } from '../../components/work/options' const STATUS_OPTIONS = [ { value: 'roadmap', label: 'Roadmap' }, { value: 'decomposing', label: 'Decomposing' }, { value: 'ready', label: 'Ready' }, { value: 'active', label: 'Active' }, { value: 'completed', label: 'Completed' }, { value: 'parked', label: 'Parked' }, { value: 'cancelled', label: 'Cancelled' }, ] type FormData = { priority: string owner_id: string outcome: string success_criteria: string target_date: string start_at: string end_at: string } const emptyForm: FormData = { priority: '', owner_id: '', outcome: '', success_criteria: '', target_date: '', start_at: '', end_at: '', } function getInitialForm(item?: any): FormData { if (!item) return emptyForm const d = item.data || {} return { priority: d.priority || '', owner_id: d.owner_id || '', outcome: d.outcome || '', success_criteria: d.success_criteria || '', target_date: d.target_date || '', start_at: d.start_at || '', end_at: d.end_at || '', } } export default function InitiativeDetailPage() { const { id } = useParams<{ id: string }>() const navigate = useNavigate() const appPath = useAppPath() const isCreating = id === 'new' const [item, setItem] = useState(null) const [title, setTitle] = useState('') const [status, setStatus] = useState('roadmap') const [form, setForm] = useState(emptyForm) const [comments, setComments] = useState([]) const [loading, setLoading] = useState(!isCreating) const [loadingComments, setLoadingComments] = useState(false) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const loadItem = () => { if (!id || isCreating) return setLoading(true) getItem(id) .then((rec: any) => { setItem(rec) setTitle(rec.title || '') setStatus(rec.status || 'roadmap') setForm(getInitialForm(rec)) setError(null) }) .catch((err: any) => setError(err.message || 'Failed to load initiative')) .finally(() => setLoading(false)) } const loadComments = () => { const itemId = id if (!itemId || isCreating) return setLoadingComments(true) apiFetchJson(`/api/admin-data?entity=threads&target_id=${itemId}&target_type=items&type_slug=initiatives_thread&limit=1`) .then(async (json: any) => { const threads = Array.isArray(json?.data) ? json.data : Array.isArray(json) ? json : [] if (threads.length === 0) { setComments([]) return } const threadId = threads[0].id const mjson = await apiFetchJson(`/api/admin-data?entity=messages&thread_id=${threadId}&type_slug=initiatives_messages&limit=200&sort_field=created_at&sort_direction=asc`) setComments(Array.isArray(mjson?.data) ? mjson.data : Array.isArray(mjson) ? mjson : []) }) .catch(() => setComments([])) .finally(() => setLoadingComments(false)) } useEffect(() => { if (isCreating) { setLoading(false) setTitle('') setStatus('roadmap') setForm(emptyForm) setComments([]) setError(null) return } loadItem() loadComments() }, [id, isCreating]) const handleTitleBlur = async (value: string) => { if (isCreating || !id || !item) return const trimmed = value.trim() if (trimmed === (item.title || '')) return try { await updateItem(id, { title: trimmed }) loadItem() } catch (err: any) { setError(err.message || 'Failed to update title') } } const handleUpdateDescription = async (description: string) => { if (isCreating || !id || !item) return try { await updateItem(id, { description }) loadItem() } catch (err: any) { setError(err.message || 'Failed to update description') } } const buildPayload = () => ({ title: title.trim(), status, data: { ...(item?.data || {}), ...form, priority: form.priority || null, }, }) const handleSave = async (e: React.FormEvent) => { e.preventDefault() setSaving(true) try { if (isCreating) { const created = await createItem('workbench_initiative', { title: title.trim() || 'New Initiative', status: status || 'roadmap', data: { ...form, priority: form.priority || null }, }) const newId = created.id if (!newId) throw new Error('Initiative was created but no id was returned') navigate(appPath(`/planning/initiatives/${newId}`), { replace: true }) } else { await updateItem(id, buildPayload()) loadItem() } } catch (err: any) { setError(err.message || 'Failed to save initiative') } finally { setSaving(false) } } const handlePost = async (content: string) => { if (isCreating || !id) return const tjson = await apiFetchJson(`/api/admin-data?entity=threads&target_id=${id}&target_type=items&type_slug=initiatives_thread&limit=1`) const threads = Array.isArray(tjson?.data) ? tjson.data : Array.isArray(tjson) ? tjson : [] let threadId = threads[0]?.id if (!threadId) { const created = await apiFetchJson('/api/admin-data?entity=threads', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ entity: 'threads', type_slug: 'initiatives_thread', target_id: id, target_type: 'items' }), }) threadId = created.id || created.data?.id if (!threadId) throw new Error('Failed to create thread') } const msgs = await apiFetchJson(`/api/admin-data?entity=messages&thread_id=${threadId}&type_slug=initiatives_messages&limit=200`) const existing = Array.isArray(msgs?.data) ? msgs.data : Array.isArray(msgs) ? msgs : [] const nextSequence = existing.length await apiFetchJson('/api/admin-data?entity=messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ entity: 'messages', type_slug: 'initiatives_messages', thread_id: threadId, content: content.trim(), direction: 'outbound', sequence: nextSequence, }), }) loadComments() } const handleAddEpic = () => { if (!item) return navigate(appPath(`/planning/epics/new?initiative_id=${item.id}`)) } const handleChange = (field: keyof FormData, value: string) => { setForm(prev => ({ ...prev, [field]: value })) } const renderField = (field: keyof FormData, label: string, type: 'input' | 'textarea' = 'input') => (
{type === 'textarea' ? (