'use client'; import { useEffect, useRef } from 'react'; import type { ReactNode } from 'react'; import { cn } from '@/lib/cn'; import type { Task } from '@/lib/types'; interface TaskDetailModalProps { task: Task; onClose: () => void; } const priorityColors: Record = { critical: 'text-error', high: 'text-warning', medium: 'text-blue-400', low: 'text-text-muted', }; const statusBadgeColors: Record = { 'not-started': 'bg-gray-600/20 text-gray-300', 'in-progress': 'bg-blue-600/20 text-blue-400', blocked: 'bg-error/20 text-error', done: 'bg-success/20 text-success', cancelled: 'bg-gray-600/20 text-gray-500', }; function DetailRow({ label, children, }: { label: string; children: ReactNode; }): React.ReactElement { return (
{label} {children}
); } export function TaskDetailModal({ task, onClose }: TaskDetailModalProps): React.ReactElement { const dialogRef = useRef(null); useEffect(() => { function handleKeyDown(e: KeyboardEvent): void { if (e.key === 'Escape') onClose(); } document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [onClose]); // Trap focus on mount useEffect(() => { dialogRef.current?.focus(); }, []); const prLinks = extractPrLinks(task.metadata); return ( /* backdrop */
{ if (e.target === e.currentTarget) onClose(); }} role="presentation" >
{/* Header */}

{task.title}

{/* Body */}
{/* Badges */}
{task.status} {task.priority} priority
{/* Description */} {task.description && (

{task.description}

)} {/* Details */}
{task.assignee ? {task.assignee} : null} {task.dueDate ? ( {new Date(task.dueDate).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric', })} ) : null} {new Date(task.createdAt).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric', })} {new Date(task.updatedAt).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric', })}
{/* Tags */} {task.tags != null && task.tags.length > 0 ? (

Tags

{task.tags.map((tag) => ( {tag} ))}
) : null} {/* PR Links */} {prLinks.length > 0 ? (

Pull Requests

) : null} {/* Notes from metadata */}
); } interface PrLink { url: string; label?: string; } function extractPrLinks(metadata: Record | null): PrLink[] { if (!metadata) return []; const raw = metadata['pr_links'] ?? metadata['prLinks']; if (!Array.isArray(raw)) return []; return raw .filter((item): item is PrLink => { if (typeof item === 'string') return true; if (typeof item === 'object' && item !== null && 'url' in item) return true; return false; }) .map((item): PrLink => { if (typeof item === 'string') return { url: item }; return item as PrLink; }); } function getNotesString(metadata: Record | null): string | null { if (!metadata) return null; const notes = metadata['notes']; if (typeof notes === 'string' && notes.trim().length > 0) return notes; return null; } function NotesSection({ notes }: { notes: string | null }): React.ReactElement | null { if (!notes) return null; return (

Notes

{notes}

); }