'use client' import { useState, useEffect, useCallback, useRef } from 'react' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { useMissionControl } from '@/store' import { useSmartPoll } from '@/lib/use-smart-poll' import { createClientLogger } from '@/lib/client-logger' import { useFocusTrap } from '@/lib/use-focus-trap' import { AgentAvatar } from '@/components/ui/agent-avatar' import { MarkdownRenderer } from '@/components/markdown-renderer' const log = createClientLogger('TaskBoard') interface Task { id: number title: string description?: string status: 'inbox' | 'assigned' | 'in_progress' | 'review' | 'quality_review' | 'done' priority: 'low' | 'medium' | 'high' | 'critical' | 'urgent' assigned_to?: string created_by: string created_at: number updated_at: number due_date?: number estimated_hours?: number actual_hours?: number tags?: string[] metadata?: any aegisApproved?: boolean project_id?: number project_ticket_no?: number project_name?: string project_prefix?: string ticket_ref?: string } interface Agent { id: number name: string role: string status: 'offline' | 'idle' | 'busy' | 'error' taskStats?: { total: number assigned: number in_progress: number completed: number } } interface Comment { id: number task_id: number author: string content: string created_at: number parent_id?: number mentions?: string[] replies?: Comment[] } interface Project { id: number name: string slug: string ticket_prefix: string status: 'active' | 'archived' } interface MentionOption { handle: string recipient: string type: 'user' | 'agent' display: string role?: string } const statusColumns = [ { key: 'inbox', title: 'Inbox', color: 'bg-secondary text-foreground' }, { key: 'assigned', title: 'Assigned', color: 'bg-blue-500/20 text-blue-400' }, { key: 'in_progress', title: 'In Progress', color: 'bg-yellow-500/20 text-yellow-400' }, { key: 'review', title: 'Review', color: 'bg-purple-500/20 text-purple-400' }, { key: 'quality_review', title: 'Quality Review', color: 'bg-indigo-500/20 text-indigo-400' }, { key: 'done', title: 'Done', color: 'bg-green-500/20 text-green-400' }, ] const priorityColors: Record = { low: 'border-green-500', medium: 'border-yellow-500', high: 'border-orange-500', critical: 'border-red-500', } function useMentionTargets() { const [mentionTargets, setMentionTargets] = useState([]) useEffect(() => { let cancelled = false const run = async () => { try { const response = await fetch('/api/mentions?limit=200') if (!response.ok) return const data = await response.json() if (!cancelled) setMentionTargets(data.mentions || []) } catch { // mention autocomplete is non-critical } } run() return () => { cancelled = true } }, []) return mentionTargets } function MentionTextarea({ id, value, onChange, rows = 3, placeholder, className, mentionTargets, }: { id?: string value: string onChange: (next: string) => void rows?: number placeholder?: string className?: string mentionTargets: MentionOption[] }) { const textareaRef = useRef(null) const [open, setOpen] = useState(false) const [activeIndex, setActiveIndex] = useState(0) const [query, setQuery] = useState('') const [range, setRange] = useState<{ start: number; end: number } | null>(null) const filtered = mentionTargets .filter((target) => { if (!query) return true const q = query.toLowerCase() return target.handle.includes(q) || target.display.toLowerCase().includes(q) }) .slice(0, 8) const detectMentionQuery = (nextValue: string, caret: number) => { const left = nextValue.slice(0, caret) const match = left.match(/(?:^|[^\w.-])@([A-Za-z0-9._-]{0,63})$/) if (!match) { setOpen(false) setQuery('') setRange(null) return } const matched = match[1] || '' const start = caret - matched.length - 1 setQuery(matched) setRange({ start, end: caret }) setActiveIndex(0) setOpen(true) } const insertMention = (option: MentionOption) => { if (!range) return const next = `${value.slice(0, range.start)}@${option.handle} ${value.slice(range.end)}` onChange(next) setOpen(false) setQuery('') const cursor = range.start + option.handle.length + 2 requestAnimationFrame(() => { const node = textareaRef.current if (!node) return node.focus() node.setSelectionRange(cursor, cursor) }) } return (