import React, { useState, useMemo, useEffect } from 'react'; import { Search, Filter, ArrowUpDown, ArrowUp, ArrowDown, List, Grid, ChevronDown, Columns, Plus, Settings, Terminal, FileText, HelpCircle, X } from 'lucide-react'; import { cn } from '../lib/utils'; import TaskCard from './TaskCard'; import CreateTaskModal from './CreateTaskModal'; import { useTaskMaster } from '../contexts/TaskMasterContext'; import Shell from './Shell'; import { api } from '../utils/api'; import { useTranslation } from 'react-i18next'; const TaskList = ({ tasks = [], onTaskClick, className = '', showParentTasks = false, defaultView = 'kanban', // 'list', 'grid', or 'kanban' currentProject, onTaskCreated, onShowPRDEditor, existingPRDs = [], onRefreshPRDs }) => { const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [priorityFilter, setPriorityFilter] = useState('all'); const [sortBy, setSortBy] = useState('id'); // 'id', 'title', 'status', 'priority', 'updated' const [sortOrder, setSortOrder] = useState('asc'); // 'asc' or 'desc' const [viewMode, setViewMode] = useState(defaultView); const [showFilters, setShowFilters] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false); const [showCLI, setShowCLI] = useState(false); const [showHelpGuide, setShowHelpGuide] = useState(false); const [isTaskMasterComplete, setIsTaskMasterComplete] = useState(false); const [showPRDDropdown, setShowPRDDropdown] = useState(false); const { projectTaskMaster, refreshProjects, refreshTasks, setCurrentProject } = useTaskMaster(); const { t } = useTranslation('tasks'); // Close PRD dropdown when clicking outside useEffect(() => { const handleClickOutside = (event) => { if (showPRDDropdown && !event.target.closest('.relative')) { setShowPRDDropdown(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [showPRDDropdown]); // Get unique status values from tasks const statuses = useMemo(() => { const statusSet = new Set(tasks.map(task => task.status).filter(Boolean)); return Array.from(statusSet).sort(); }, [tasks]); // Get unique priority values from tasks const priorities = useMemo(() => { const prioritySet = new Set(tasks.map(task => task.priority).filter(Boolean)); return Array.from(prioritySet).sort(); }, [tasks]); // Filter and sort tasks const filteredAndSortedTasks = useMemo(() => { let filtered = tasks.filter(task => { // Text search const searchLower = searchTerm.toLowerCase(); const matchesSearch = !searchTerm || task.title.toLowerCase().includes(searchLower) || task.description?.toLowerCase().includes(searchLower) || task.id.toString().includes(searchLower); // Status filter const matchesStatus = statusFilter === 'all' || task.status === statusFilter; // Priority filter const matchesPriority = priorityFilter === 'all' || task.priority === priorityFilter; return matchesSearch && matchesStatus && matchesPriority; }); // Sort tasks filtered.sort((a, b) => { let aVal, bVal; switch (sortBy) { case 'title': aVal = a.title.toLowerCase(); bVal = b.title.toLowerCase(); break; case 'status': // Custom status ordering: pending, in-progress, done, blocked, deferred, cancelled const statusOrder = { pending: 1, 'in-progress': 2, done: 3, blocked: 4, deferred: 5, cancelled: 6 }; aVal = statusOrder[a.status] || 99; bVal = statusOrder[b.status] || 99; break; case 'priority': // Custom priority ordering: high should be sorted first in descending const priorityOrder = { high: 3, medium: 2, low: 1 }; aVal = priorityOrder[a.priority] || 0; bVal = priorityOrder[b.priority] || 0; break; case 'updated': aVal = new Date(a.updatedAt || a.createdAt || 0); bVal = new Date(b.updatedAt || b.createdAt || 0); break; case 'id': default: // Handle numeric and dotted IDs (1, 1.1, 1.2, 2, 2.1, etc.) const parseId = (id) => { const parts = id.toString().split('.'); return parts.map(part => parseInt(part, 10)); }; const aIds = parseId(a.id); const bIds = parseId(b.id); // Compare each part for (let i = 0; i < Math.max(aIds.length, bIds.length); i++) { const aId = aIds[i] || 0; const bId = bIds[i] || 0; if (aId !== bId) { aVal = aId; bVal = bId; break; } } break; } if (sortBy === 'updated') { return sortOrder === 'asc' ? aVal - bVal : bVal - aVal; } if (typeof aVal === 'string') { return sortOrder === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); } return sortOrder === 'asc' ? aVal - bVal : bVal - aVal; }); return filtered; }, [tasks, searchTerm, statusFilter, priorityFilter, sortBy, sortOrder]); // Organize tasks by status for Kanban view const kanbanColumns = useMemo(() => { const allColumns = [ { id: 'pending', title: t('kanban.pending'), status: 'pending', color: 'bg-slate-50 dark:bg-slate-900/50 border-slate-200 dark:border-slate-700', headerColor: 'bg-slate-100 dark:bg-slate-800 text-slate-800 dark:text-slate-200' }, { id: 'in-progress', title: t('kanban.inProgress'), status: 'in-progress', color: 'bg-blue-50 dark:bg-blue-900/50 border-blue-200 dark:border-blue-700', headerColor: 'bg-blue-100 dark:bg-blue-800 text-blue-800 dark:text-blue-200' }, { id: 'done', title: t('kanban.done'), status: 'done', color: 'bg-emerald-50 dark:bg-emerald-900/50 border-emerald-200 dark:border-emerald-700', headerColor: 'bg-emerald-100 dark:bg-emerald-800 text-emerald-800 dark:text-emerald-200' }, { id: 'blocked', title: t('kanban.blocked'), status: 'blocked', color: 'bg-red-50 dark:bg-red-900/50 border-red-200 dark:border-red-700', headerColor: 'bg-red-100 dark:bg-red-800 text-red-800 dark:text-red-200' }, { id: 'deferred', title: t('kanban.deferred'), status: 'deferred', color: 'bg-amber-50 dark:bg-amber-900/50 border-amber-200 dark:border-amber-700', headerColor: 'bg-amber-100 dark:bg-amber-800 text-amber-800 dark:text-amber-200' }, { id: 'cancelled', title: t('kanban.cancelled'), status: 'cancelled', color: 'bg-gray-50 dark:bg-gray-900/50 border-gray-200 dark:border-gray-700', headerColor: 'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200' } ]; // Only show columns that have tasks or are part of the main workflow const mainWorkflowStatuses = ['pending', 'in-progress', 'done']; const columnsWithTasks = allColumns.filter(column => { const hasTask = filteredAndSortedTasks.some(task => task.status === column.status); const isMainWorkflow = mainWorkflowStatuses.includes(column.status); return hasTask || isMainWorkflow; }); return columnsWithTasks.map(column => ({ ...column, tasks: filteredAndSortedTasks.filter(task => task.status === column.status) })); }, [filteredAndSortedTasks, t]); const handleSortChange = (newSortBy) => { if (sortBy === newSortBy) { setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); } else { setSortBy(newSortBy); setSortOrder('asc'); } }; const clearFilters = () => { setSearchTerm(''); setStatusFilter('all'); setPriorityFilter('all'); }; const getSortIcon = (field) => { if (sortBy !== field) return ; return sortOrder === 'asc' ? : ; }; if (tasks.length === 0) { // Check if TaskMaster is configured by looking for .taskmaster directory const hasTaskMasterDirectory = currentProject?.taskMasterConfigured || currentProject?.taskmaster?.hasTaskmaster || projectTaskMaster?.hasTaskmaster; return (
{!hasTaskMasterDirectory ? ( // TaskMaster not configured

{t('notConfigured.title')}

{t('notConfigured.description')}

{/* What is TaskMaster section */}

{t('notConfigured.whatIsTitle')}

• {t('notConfigured.features.aiPowered')}

• {t('notConfigured.features.prdTemplates')}

• {t('notConfigured.features.dependencyTracking')}

• {t('notConfigured.features.progressVisualization')}

• {t('notConfigured.features.cliIntegration')}

) : ( // TaskMaster configured but no tasks - show Getting Started guide

{t('gettingStarted.title')}

{t('gettingStarted.subtitle')}

{/* Step 1 */}
1

{t('gettingStarted.steps.createPRD.title')}

{t('gettingStarted.steps.createPRD.description')}

{/* Show existing PRDs if any */} {existingPRDs.length > 0 && (

{t('gettingStarted.steps.createPRD.existingPRDs')}

{existingPRDs.map((prd) => ( ))}
)}
{/* Step 2 */}
2

{t('gettingStarted.steps.generateTasks.title')}

{t('gettingStarted.steps.generateTasks.description')}

{/* Step 3 */}
3

{t('gettingStarted.steps.analyzeTasks.title')}

{t('gettingStarted.steps.analyzeTasks.description')}

{/* Step 4 */}
4

{t('gettingStarted.steps.startBuilding.title')}

{t('gettingStarted.steps.startBuilding.description')}

{t('gettingStarted.tip')}
)} {/* TaskMaster CLI Setup Modal */} {showCLI && (
{/* Modal Header */}

{t('setupModal.title')}

{t('setupModal.subtitle', { projectName: currentProject?.displayName })}

{/* Terminal Container */}
{ // Focus the terminal when clicked const terminalElement = e.currentTarget.querySelector('.xterm-screen'); if (terminalElement) { terminalElement.focus(); } }} > { setIsTaskMasterComplete(true); if (exitCode === 0) { // Auto-refresh after successful completion setTimeout(() => { refreshProjects(); if (currentProject) { setCurrentProject(currentProject); } }, 1000); } }} />
{/* Modal Footer */}
{isTaskMasterComplete ? (
{t('setupModal.completed')}
) : ( t('setupModal.willStart') )}
)}
); } return (
{/* Header Controls */}
{/* Search Bar */}
setSearchTerm(e.target.value)} className="pl-10 pr-4 py-2 w-full border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
{/* Controls */}
{/* View Toggle */}
{/* Filters Toggle */} {/* Action Buttons */} {currentProject && ( <> {/* Help Button */} {/* PRD Management */}
{existingPRDs.length > 0 ? ( // Dropdown when PRDs exist
{showPRDDropdown && (
{t('gettingStarted.steps.createPRD.existingPRDs')}
{existingPRDs.map((prd) => ( ))}
)}
) : ( // Simple button when no PRDs exist )}
{/* Add Task Button */} {((currentProject?.taskMasterConfigured || currentProject?.taskmaster?.hasTaskmaster || projectTaskMaster?.hasTaskmaster) || tasks.length > 0) && ( )} )}
{/* Expanded Filters */} {showFilters && (
{/* Status Filter */}
{/* Priority Filter */}
{/* Sort By */}
{/* Filter Actions */}
{t('filters.showing', { filtered: filteredAndSortedTasks.length, total: tasks.length })}
)} {/* Quick Sort Buttons */}
{/* Task Cards */} {filteredAndSortedTasks.length === 0 ? (

{t('noMatchingTasks.title')}

{t('noMatchingTasks.description')}

) : viewMode === 'kanban' ? ( /* Kanban Board Layout - Dynamic grid based on column count */
= 6 && "grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6" )}> {kanbanColumns.map((column) => (
{/* Column Header */}

{column.title}

{column.tasks.length}
{/* Column Tasks */}
{column.tasks.length === 0 ? (
{t('kanban.noTasksYet')}
{column.status === 'pending' ? t('kanban.tasksWillAppear') : column.status === 'in-progress' ? t('kanban.moveTasksHere') : column.status === 'done' ? t('kanban.completedTasksHere') : t('kanban.statusTasksHere')}
) : ( column.tasks.map((task) => (
onTaskClick?.(task)} showParent={showParentTasks} className="w-full shadow-sm hover:shadow-md transition-shadow cursor-pointer" />
)) )}
))}
) : (
{filteredAndSortedTasks.map((task) => ( onTaskClick?.(task)} showParent={showParentTasks} className={viewMode === 'grid' ? 'h-full' : ''} /> ))}
)} {/* Create Task Modal */} {showCreateModal && ( setShowCreateModal(false)} onTaskCreated={() => { setShowCreateModal(false); if (onTaskCreated) onTaskCreated(); }} /> )} {/* Help Guide Modal */} {showHelpGuide && (
{/* Modal Header */}

{t('helpGuide.title')}

{t('helpGuide.subtitle')}

{/* Modal Content */}
{/* Step 1 */}
1

{t('gettingStarted.steps.createPRD.title')}

{t('gettingStarted.steps.createPRD.description')}

{/* Step 2 */}
2

{t('gettingStarted.steps.generateTasks.title')}

{t('gettingStarted.steps.generateTasks.description')}

{t('helpGuide.examples.parsePRD')}

{/* Step 3 */}
3

{t('gettingStarted.steps.analyzeTasks.title')}

{t('gettingStarted.steps.analyzeTasks.description')}

{t('helpGuide.examples.expandTask')}

{/* Step 4 */}
4

{t('gettingStarted.steps.startBuilding.title')}

{t('gettingStarted.steps.startBuilding.description')}

{t('helpGuide.examples.addTask')}

{t('helpGuide.moreExamples')}
{/* Pro Tips */}

{t('helpGuide.proTips.title')}

  • {t('helpGuide.proTips.search')}
  • {t('helpGuide.proTips.views')}
  • {t('helpGuide.proTips.filters')}
  • {t('helpGuide.proTips.details')}
{/* Learn More Section */}

{t('helpGuide.learnMore.title')}

{t('helpGuide.learnMore.description')}

{t('helpGuide.learnMore.githubButton')}
)}
); }; export default TaskList;