import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { format, formatDistanceToNow, parseISO } from 'date-fns'; import { Pencil, Trash2, Plus, MessageSquare, ClipboardList, Calendar, User, Flag, Activity, Send, X, } from 'lucide-react'; import api, { getErrorMessage } from '@/lib/api'; import type { ProjectTask, TaskComment, Person } from '@/types'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Checkbox } from '@/components/ui/checkbox'; import { Textarea } from '@/components/ui/textarea'; const taskStatusColors: Record = { pending: 'bg-gray-500/10 text-gray-400 border-gray-500/20', in_progress: 'bg-blue-500/10 text-blue-400 border-blue-500/20', completed: 'bg-green-500/10 text-green-400 border-green-500/20', }; const taskStatusLabels: Record = { pending: 'Pending', in_progress: 'In Progress', completed: 'Completed', }; const priorityColors: Record = { none: 'bg-gray-500/20 text-gray-400', low: 'bg-green-500/20 text-green-400', medium: 'bg-yellow-500/20 text-yellow-400', high: 'bg-red-500/20 text-red-400', }; interface TaskDetailPanelProps { task: ProjectTask | null; projectId: number; onEdit: (task: ProjectTask) => void; onDelete: (taskId: number) => void; onAddSubtask: (parentId: number) => void; onClose?: () => void; } export default function TaskDetailPanel({ task, projectId, onEdit, onDelete, onAddSubtask, onClose, }: TaskDetailPanelProps) { const queryClient = useQueryClient(); const [commentText, setCommentText] = useState(''); const { data: people = [] } = useQuery({ queryKey: ['people'], queryFn: async () => { const { data } = await api.get('/people'); return data; }, }); const toggleSubtaskMutation = useMutation({ mutationFn: async ({ taskId, status }: { taskId: number; status: string }) => { const newStatus = status === 'completed' ? 'pending' : 'completed'; const { data } = await api.put(`/projects/${projectId}/tasks/${taskId}`, { status: newStatus, }); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['projects', projectId.toString()] }); }, }); const addCommentMutation = useMutation({ mutationFn: async (content: string) => { const { data } = await api.post( `/projects/${projectId}/tasks/${task!.id}/comments`, { content } ); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['projects', projectId.toString()] }); setCommentText(''); }, onError: (error) => { toast.error(getErrorMessage(error, 'Failed to add comment')); }, }); const deleteCommentMutation = useMutation({ mutationFn: async (commentId: number) => { await api.delete(`/projects/${projectId}/tasks/${task!.id}/comments/${commentId}`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['projects', projectId.toString()] }); toast.success('Comment deleted'); }, }); const handleAddComment = () => { const trimmed = commentText.trim(); if (!trimmed) return; addCommentMutation.mutate(trimmed); }; if (!task) { return (

Select a task to view details

); } const assignedPerson = task.person_id ? people.find((p) => p.id === task.person_id) : null; const comments = task.comments || []; return (
{/* Header */}

{task.title}

{onClose && ( )}
{/* Scrollable content */}
{/* Fields grid */}
Status
{taskStatusLabels[task.status]}
Priority
{task.priority}
Due Date

{task.due_date ? format(parseISO(task.due_date), 'MMM d, yyyy') : '—'}

Assigned

{assignedPerson ? assignedPerson.name : '—'}

{/* Description */} {task.description && (

Description

{task.description}

)} {/* Subtasks */}

Subtasks {task.subtasks.length > 0 && ( ({task.subtasks.filter((s) => s.status === 'completed').length}/ {task.subtasks.length}) )}

{!task.parent_task_id && ( )}
{task.subtasks.length > 0 ? (
{task.subtasks.map((subtask) => (
toggleSubtaskMutation.mutate({ taskId: subtask.id, status: subtask.status, }) } disabled={toggleSubtaskMutation.isPending} /> {subtask.title} {subtask.priority}
))}
) : (

No subtasks yet

)}
{/* Comments */}

Comments {comments.length > 0 && ( ({comments.length}) )}

{/* Comment list */} {comments.length > 0 && (
{comments.map((comment) => (

{comment.content}

{formatDistanceToNow(parseISO(comment.created_at), { addSuffix: true, })}
))}
)} {/* Add comment */}