import { useState, useEffect, useRef, useCallback } from 'react' import { motion } from 'framer-motion' import { X, Loader2 } from 'lucide-react' import { WorkTaskStatus } from '../types/index.ts' import { useTask, useUpdateTask, useStartTask, usePauseTask, useResumeTask, useCompleteTask, useAbandonTask, } from '../api/tasks.ts' import { COLUMN_CONFIG } from '../lib/constants.ts' import SubtaskList from './SubtaskList.tsx' import NotesList from './NotesList.tsx' interface TaskDetailPanelProps { taskId: number onClose: () => void } function formatElapsed(startedAt: string, completedAt: string | null): string { const start = new Date(startedAt).getTime() const end = completedAt ? new Date(completedAt).getTime() : Date.now() const mins = Math.floor((end - start) / 60_000) if (mins < 60) return `${mins}m` const hours = Math.floor(mins / 60) const remainder = mins % 60 if (hours < 24) return `${hours}h ${remainder}m` const days = Math.floor(hours / 24) return `${days}d ${hours % 24}h` } export default function TaskDetailPanel({ taskId, onClose }: TaskDetailPanelProps) { const { data: task, isLoading } = useTask(taskId) const updateTask = useUpdateTask() const startTask = useStartTask() const pauseTask = usePauseTask() const resumeTask = useResumeTask() const completeTask = useCompleteTask() const abandonTask = useAbandonTask() // Inline editing states const [editingTitle, setEditingTitle] = useState(false) const [titleValue, setTitleValue] = useState('') const [editingDesc, setEditingDesc] = useState(false) const [descValue, setDescValue] = useState('') const [editingCategory, setEditingCategory] = useState(false) const [categoryValue, setCategoryValue] = useState('') const [editingEstimate, setEditingEstimate] = useState(false) const [estimateValue, setEstimateValue] = useState('') const titleInputRef = useRef(null) const descInputRef = useRef(null) const categoryInputRef = useRef(null) const estimateInputRef = useRef(null) // Escape key handler const handleEscape = useCallback( (e: KeyboardEvent) => { if (e.key === 'Escape') { // If editing, cancel editing first if (editingTitle || editingDesc || editingCategory || editingEstimate) { setEditingTitle(false) setEditingDesc(false) setEditingCategory(false) setEditingEstimate(false) return } onClose() } }, [editingTitle, editingDesc, editingCategory, editingEstimate, onClose] ) useEffect(() => { document.addEventListener('keydown', handleEscape) return () => document.removeEventListener('keydown', handleEscape) }, [handleEscape]) // Focus inputs when entering edit mode useEffect(() => { if (editingTitle) titleInputRef.current?.focus() }, [editingTitle]) useEffect(() => { if (editingDesc) descInputRef.current?.focus() }, [editingDesc]) useEffect(() => { if (editingCategory) categoryInputRef.current?.focus() }, [editingCategory]) useEffect(() => { if (editingEstimate) estimateInputRef.current?.focus() }, [editingEstimate]) // --- Save handlers --- function saveTitle() { if (task && titleValue.trim() && titleValue.trim() !== task.title) { updateTask.mutate({ id: taskId, title: titleValue.trim() }) } setEditingTitle(false) } function saveDescription() { if (task && descValue !== (task.description ?? '')) { updateTask.mutate({ id: taskId, description: descValue }) } setEditingDesc(false) } function saveCategory() { if (task && categoryValue.trim() !== (task.category ?? '')) { updateTask.mutate({ id: taskId, category: categoryValue.trim() || undefined }) } setEditingCategory(false) } function saveEstimate() { const val = estimateValue.trim() === '' ? undefined : parseInt(estimateValue, 10) if (task) { const newVal = val && !isNaN(val) ? val : undefined if (newVal !== (task.estimatedMinutes ?? undefined)) { updateTask.mutate({ id: taskId, estimatedMinutes: newVal }) } } setEditingEstimate(false) } // --- Status helpers --- const statusConfig = COLUMN_CONFIG.find((c) => c.status === task?.status) // Progress percent let progressPercent: number | null = null if (task?.estimatedMinutes && task.startedAt) { const start = new Date(task.startedAt).getTime() const end = task.completedAt ? new Date(task.completedAt).getTime() : Date.now() const elapsedMins = (end - start) / 60_000 progressPercent = Math.min(100, (elapsedMins / task.estimatedMinutes) * 100) } return ( <> {/* Overlay */} {/* Panel */} {isLoading || !task ? (
) : ( <> {/* Scrollable content */}
{/* Header */}
{/* Title row with close button inline */}
{editingTitle ? ( setTitleValue(e.target.value)} onBlur={saveTitle} onKeyDown={(e) => { if (e.key === 'Enter') saveTitle() if (e.key === 'Escape') setEditingTitle(false) }} className="w-full bg-[var(--color-page)] text-xl font-semibold text-[var(--color-text-primary)] px-3 py-2 rounded border border-[var(--color-accent)] outline-none" /> ) : (

{ setTitleValue(task.title) setEditingTitle(true) }} > {task.title}

)}
{/* Status badge + Category */}
{statusConfig && ( {statusConfig.label} )} {editingCategory ? ( setCategoryValue(e.target.value)} onBlur={saveCategory} onKeyDown={(e) => { if (e.key === 'Enter') saveCategory() if (e.key === 'Escape') setEditingCategory(false) }} placeholder="Category..." className="bg-[var(--color-page)] text-xs text-[var(--color-text-primary)] px-2 py-1 rounded border border-[var(--color-accent)] outline-none w-28" /> ) : ( { setCategoryValue(task.category ?? '') setEditingCategory(true) }} > {task.category || 'Add category'} )}
{/* Description */}

Description

{editingDesc ? (