import React, { useRef, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { CheckIcon, XMarkIcon, FolderIcon, TagIcon, ChevronDownIcon, PauseCircleIcon, PlayCircleIcon, CheckCircleIcon, ExclamationTriangleIcon, CalendarDaysIcon, CalendarIcon, PlayIcon, FireIcon, ArrowUpIcon, ArrowDownIcon, } from '@heroicons/react/24/outline'; import { Link } from 'react-router-dom'; import { Task, PriorityType } from '../../../entities/Task'; import { formatDateTime } from '../../../utils/dateUtils'; interface TaskDetailsHeaderProps { task: Task; onTitleUpdate: (newTitle: string) => Promise; onStatusUpdate: (newStatus: number) => Promise; onPriorityUpdate: (newPriority: PriorityType) => Promise; onEdit: () => void; onDelete: () => void; getProjectLink?: (project: any) => string; getTagLink?: (tag: any) => string; activePill: string; onPillChange: (pill: string) => void; showOverdueIcon?: boolean; showPastDueBadge?: boolean; onOverdueIconClick?: () => void; isOverdueAlertVisible?: boolean; onDismissOverdueAlert?: () => void; onToggleTodayPlan?: () => void; onQuickStatusToggle?: () => void; attachmentCount?: number; subtasksCount?: number; } const TaskDetailsHeader: React.FC = ({ task, onTitleUpdate, onStatusUpdate, onPriorityUpdate, onEdit, onDelete, getProjectLink, getTagLink, activePill, onPillChange, showOverdueIcon = false, showPastDueBadge = false, onOverdueIconClick, isOverdueAlertVisible = false, onDismissOverdueAlert, onToggleTodayPlan, onQuickStatusToggle, attachmentCount = 0, subtasksCount = 0, }) => { const { t } = useTranslation(); const [isEditingTitle, setIsEditingTitle] = useState(false); const [editedTitle, setEditedTitle] = useState(task.name); const [actionsMenuOpen, setActionsMenuOpen] = useState(false); const [statusDropdownOpen, setStatusDropdownOpen] = useState(false); const [priorityDropdownOpen, setPriorityDropdownOpen] = useState(false); const titleInputRef = useRef(null); const actionsMenuRef = useRef(null); const statusDropdownRef = useRef(null); const priorityDropdownRef = useRef(null); useEffect(() => { setEditedTitle(task.name); }, [task.name]); useEffect(() => { if (isEditingTitle && titleInputRef.current) { titleInputRef.current.focus(); titleInputRef.current.select(); } }, [isEditingTitle]); useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if ( actionsMenuOpen && actionsMenuRef.current && !actionsMenuRef.current.contains(e.target as Node) ) { setActionsMenuOpen(false); } if ( statusDropdownOpen && statusDropdownRef.current && !statusDropdownRef.current.contains(e.target as Node) ) { setStatusDropdownOpen(false); } if ( priorityDropdownOpen && priorityDropdownRef.current && !priorityDropdownRef.current.contains(e.target as Node) ) { setPriorityDropdownOpen(false); } }; if (actionsMenuOpen || statusDropdownOpen || priorityDropdownOpen) { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); } }, [actionsMenuOpen, statusDropdownOpen, priorityDropdownOpen]); const handleStartTitleEdit = () => { setIsEditingTitle(true); }; const handleSaveTitle = async () => { if (editedTitle.trim() && editedTitle !== task.name) { await onTitleUpdate(editedTitle.trim()); } setIsEditingTitle(false); }; const handleCancelTitleEdit = () => { setEditedTitle(task.name); setIsEditingTitle(false); }; const handleTitleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleSaveTitle(); } else if (e.key === 'Escape') { handleCancelTitleEdit(); } }; const getStatusLabel = () => { const status = task.status; if (status === 'not_started' || status === 0) { return t('task.status.notStarted', 'Not started'); } else if (status === 'in_progress' || status === 1) { return t('task.status.inProgress', 'In progress'); } else if (status === 'done' || status === 2) { return t('task.status.done', 'Done'); } else if (status === 'archived' || status === 3) { return t('task.status.archived', 'Archived'); } else if (status === 'waiting' || status === 4) { return t('task.status.waiting', 'Waiting'); } return t('task.status.notStarted', 'Not started'); }; const getStatusButtonClass = () => { const status = task.status; if (status === 'not_started' || status === 0) { return 'px-2 sm:px-2.5 py-1 rounded-md text-xs font-medium transition-colors flex items-center gap-1 sm:gap-2 sm:ml-2 border border-gray-300 text-gray-600 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800/60'; } const baseClass = 'px-2 sm:px-2.5 py-1 rounded-md text-xs font-medium transition-colors flex items-center gap-1 sm:gap-2 sm:ml-2 border'; if (status === 'in_progress' || status === 1) { return `${baseClass} border-blue-500 text-blue-600 dark:border-blue-400 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/30`; } else if (status === 'done' || status === 2) { return `${baseClass} border-green-500 text-green-600 dark:border-green-400 dark:text-green-300 hover:bg-green-50 dark:hover:bg-green-900/30`; } else if (status === 'archived' || status === 3) { return `${baseClass} border-purple-500 text-purple-600 dark:border-purple-400 dark:text-purple-300 hover:bg-purple-50 dark:hover:bg-purple-900/30`; } else if (status === 'waiting' || status === 4) { return `${baseClass} border-yellow-500 text-yellow-600 dark:border-yellow-400 dark:text-yellow-300 hover:bg-yellow-50 dark:hover:bg-yellow-900/30`; } return `${baseClass} border-gray-300 text-gray-700 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800/60`; }; const handleStatusChange = async (newStatus: number | string) => { setStatusDropdownOpen(false); const statusNum = typeof newStatus === 'string' ? parseInt(newStatus) : newStatus; await onStatusUpdate(statusNum); }; const getStatusIcon = ( statusOverride?: number | string ): React.ElementType => { const status = typeof statusOverride !== 'undefined' ? statusOverride : task.status; if (status === 'in_progress' || status === 1) { return PlayCircleIcon; } else if (status === 'done' || status === 2) { return CheckCircleIcon; } return PauseCircleIcon; }; const getStatusIconClass = (statusOverride?: number | string) => { const status = typeof statusOverride !== 'undefined' ? statusOverride : task.status; if (status === 'in_progress' || status === 1) { return 'text-blue-500 dark:text-blue-400'; } else if (status === 'done' || status === 2) { return 'text-green-500 dark:text-green-400'; } return 'text-gray-500 dark:text-gray-400'; }; const getPriorityLabel = (priorityOverride?: PriorityType) => { const priority = typeof priorityOverride !== 'undefined' ? priorityOverride : task.priority; if (priority === 'low' || priority === 0) { return t('priority.low', 'Low'); } else if (priority === 'medium' || priority === 1) { return t('priority.medium', 'Medium'); } else if (priority === 'high' || priority === 2) { return t('priority.high', 'High'); } return t('priority.none', 'None'); }; const getPriorityButtonClass = () => { const priority = task.priority; if (priority === null || priority === undefined) { return 'px-2 sm:px-2.5 py-1 rounded-md text-xs font-medium transition-colors flex items-center gap-1 sm:gap-2 sm:ml-1 border border-gray-300 text-gray-600 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800/60'; } const baseClass = 'px-2 sm:px-2.5 py-1 rounded-md text-xs font-medium transition-colors flex items-center gap-1 sm:gap-2 sm:ml-1 border'; if (priority === 'low' || priority === 0) { return `${baseClass} border-blue-500 text-blue-600 dark:border-blue-400 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/30`; } else if (priority === 'medium' || priority === 1) { return `${baseClass} border-yellow-500 text-yellow-600 dark:border-yellow-400 dark:text-yellow-300 hover:bg-yellow-50 dark:hover:bg-yellow-900/30`; } else if (priority === 'high' || priority === 2) { return `${baseClass} border-red-500 text-red-600 dark:border-red-400 dark:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/30`; } return `${baseClass} border-gray-300 text-gray-700 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800/60`; }; const handlePriorityChange = async (newPriority: PriorityType) => { setPriorityDropdownOpen(false); await onPriorityUpdate(newPriority); }; const getPriorityIcon = ( priorityOverride?: PriorityType ): React.ElementType => { const priority = typeof priorityOverride !== 'undefined' ? priorityOverride : task.priority; if (priority === 'low' || priority === 0) { return ArrowDownIcon; } else if (priority === 'medium' || priority === 1) { return ArrowUpIcon; } else if (priority === 'high' || priority === 2) { return FireIcon; } return XMarkIcon; }; const getPriorityIconClass = (priorityOverride?: PriorityType) => { const priority = typeof priorityOverride !== 'undefined' ? priorityOverride : task.priority; if (priority === 'low' || priority === 0) { return 'text-blue-500 dark:text-blue-400'; } else if (priority === 'medium' || priority === 1) { return 'text-yellow-500 dark:text-yellow-400'; } else if (priority === 'high' || priority === 2) { return 'text-red-500 dark:text-red-400'; } return 'text-gray-500 dark:text-gray-400'; }; const formattedUpdatedAt = task.updated_at ? formatDateTime(new Date(task.updated_at)) : null; return (
{isEditingTitle ? (
setEditedTitle(e.target.value) } onKeyDown={handleTitleKeyDown} onBlur={handleSaveTitle} className="text-2xl font-normal text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-800 border-2 border-blue-500 dark:border-blue-400 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 w-full" placeholder={t( 'task.titlePlaceholder', 'Enter task title' )} />
) : ( <>

{task.name}

{/* Status Dropdown Button - Next to title */}
{statusDropdownOpen && (
)}
{/* Priority Dropdown Button - Next to status */}
{priorityDropdownOpen && (
)}
{/* Past Due Badge - Right of priority button */} {showPastDueBadge && (
{t( 'task.pastDue', 'Past Due' )}
)} {formattedUpdatedAt && ( {t( 'task.lastUpdatedAt', 'Updated at' )} :{' '} {formattedUpdatedAt} )}
{/* Project and tags display below title */} {(task.Project || (task.tags && task.tags.length > 0)) && (
{task.Project && ( e.stopPropagation() } > {task.Project.name} )} {task.tags && task.tags.length > 0 && (
{task.tags.map( ( tag: any, index: number ) => ( e.stopPropagation() } > {tag.name} {index < task.tags! .length - 1 && ( {', '} )} ) )}
)}
)} )}
{/* Divider - Edge to edge */}
{/* Pills Navigation */}
{(showOverdueIcon || onToggleTodayPlan || onQuickStatusToggle) && (
{showOverdueIcon && (
{isOverdueAlertVisible && (

{t( 'task.overdueAlert', "This task was in your plan yesterday and wasn't completed." )}

{t( 'task.overdueYesterday', 'Consider prioritizing this task or breaking it into smaller steps.' )}

)}
)} {onToggleTodayPlan && ( )} {onQuickStatusToggle && (task.status === 'not_started' || task.status === 'in_progress' || task.status === 0 || task.status === 1) && ( )}
{actionsMenuOpen && (
)}
)}
); }; export default TaskDetailsHeader;