import { useState, useEffect, useCallback, useRef } from 'react' import api from '../services/api' import { Comments } from './Comments' import { TaskAttachments } from './TaskAttachments' import { SubtaskList } from './SubtaskList' import { UserSelect } from './UserSelect' import { UserSearchResult } from '../services/collaboration' import { customFieldsApi, CustomField, CustomValueResponse } from '../services/customFields' import { CustomFieldInput } from './CustomFieldInput' import { SkeletonList } from './Skeleton' interface Task { id: string project_id: string title: string description: string | null priority: string status_id: string | null status_name: string | null status_color: string | null assignee_id: string | null assignee_name: string | null due_date: string | null time_estimate: number | null subtask_count: number custom_values?: CustomValueResponse[] } interface TaskStatus { id: string name: string color: string is_done: boolean } interface TaskDetailModalProps { task: Task statuses: TaskStatus[] isOpen: boolean onClose: () => void onUpdate: () => void onSubtaskClick?: (subtaskId: string) => void } export function TaskDetailModal({ task, statuses, isOpen, onClose, onUpdate, onSubtaskClick, }: TaskDetailModalProps) { const [isEditing, setIsEditing] = useState(false) const [saving, setSaving] = useState(false) const [editForm, setEditForm] = useState({ title: task.title, description: task.description || '', priority: task.priority, status_id: task.status_id || '', assignee_id: task.assignee_id || '', due_date: task.due_date ? task.due_date.split('T')[0] : '', time_estimate: task.time_estimate || '', }) const [, setSelectedAssignee] = useState( task.assignee_id && task.assignee_name ? { id: task.assignee_id, name: task.assignee_name, email: '' } : null ) // Custom fields state const [customFields, setCustomFields] = useState([]) const [customValues, setCustomValues] = useState([]) const [editCustomValues, setEditCustomValues] = useState>({}) const [loadingCustomFields, setLoadingCustomFields] = useState(false) const loadCustomFields = useCallback(async () => { setLoadingCustomFields(true) try { const response = await customFieldsApi.getCustomFields(task.project_id) setCustomFields(response.fields) } catch (err) { console.error('Failed to load custom fields:', err) } finally { setLoadingCustomFields(false) } }, [task.project_id]) // Load custom fields for the project useEffect(() => { if (task.project_id) { loadCustomFields() } }, [task.project_id, loadCustomFields]) // Initialize custom values from task useEffect(() => { setCustomValues(task.custom_values || []) // Build edit values map const valuesMap: Record = {} if (task.custom_values) { task.custom_values.forEach((cv) => { valuesMap[cv.field_id] = cv.value }) } setEditCustomValues(valuesMap) }, [task.custom_values]) // Reset form when task changes useEffect(() => { setEditForm({ title: task.title, description: task.description || '', priority: task.priority, status_id: task.status_id || '', assignee_id: task.assignee_id || '', due_date: task.due_date ? task.due_date.split('T')[0] : '', time_estimate: task.time_estimate || '', }) setSelectedAssignee( task.assignee_id && task.assignee_name ? { id: task.assignee_id, name: task.assignee_name, email: '' } : null ) setIsEditing(false) }, [task]) // Reference to the modal overlay for focus management const overlayRef = useRef(null) // Handle Escape key to close modal useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && isOpen) { onClose() } } if (isOpen) { document.addEventListener('keydown', handleKeyDown) // Focus the overlay for keyboard accessibility overlayRef.current?.focus() } return () => { document.removeEventListener('keydown', handleKeyDown) } }, [isOpen, onClose]) if (!isOpen) return null const handleSave = async () => { setSaving(true) try { const payload: Record = { title: editForm.title, description: editForm.description || null, priority: editForm.priority, } if (editForm.status_id) { payload.status_id = editForm.status_id } if (editForm.assignee_id) { payload.assignee_id = editForm.assignee_id } else { payload.assignee_id = null } if (editForm.due_date) { payload.due_date = editForm.due_date } else { payload.due_date = null } if (editForm.time_estimate) { payload.time_estimate = Number(editForm.time_estimate) } else { payload.time_estimate = null } // Include custom field values (only non-formula fields) const customValuesPayload = Object.entries(editCustomValues) .filter(([fieldId]) => { const field = customFields.find((f) => f.id === fieldId) return field && field.field_type !== 'formula' }) .map(([fieldId, value]) => ({ field_id: fieldId, value: value, })) if (customValuesPayload.length > 0) { payload.custom_values = customValuesPayload } await api.patch(`/tasks/${task.id}`, payload) setIsEditing(false) onUpdate() } catch (err) { console.error('Failed to update task:', err) } finally { setSaving(false) } } const handleCustomFieldChange = (fieldId: string, value: string | number | null) => { setEditCustomValues((prev) => ({ ...prev, [fieldId]: value, })) } const handleAssigneeChange = (userId: string | null, user: UserSearchResult | null) => { setEditForm({ ...editForm, assignee_id: userId || '' }) setSelectedAssignee(user) } const handleOverlayClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) { onClose() } } const getPriorityColor = (priority: string): string => { const colors: Record = { low: '#808080', medium: '#0066cc', high: '#ff9800', urgent: '#f44336', } return colors[priority] || colors.medium } return (
{isEditing ? ( setEditForm({ ...editForm, title: e.target.value })} style={styles.titleInput} autoFocus /> ) : (

{task.title}

)}
{!isEditing ? ( ) : ( <> )}
{/* Description */}
{isEditing ? (