import { useEffect, useMemo, useRef, useState } from 'react'; import { useStore } from '@renderer/store'; import { normalizePath } from '@renderer/utils/pathNormalize'; import { getNonEmptyTaskCategories, groupTasksByDate } from '@renderer/utils/taskGrouping'; import { ListTodo, Search, X } from 'lucide-react'; import { useShallow } from 'zustand/react/shallow'; import { SidebarTaskItem } from './SidebarTaskItem'; import type { GlobalTask } from '@shared/types'; type StatusFilter = 'all' | 'active' | 'done'; const filterButtons: { value: StatusFilter; label: string }[] = [ { value: 'all', label: 'All' }, { value: 'active', label: 'Active' }, { value: 'done', label: 'Done' }, ]; const dateCategoryLabels: Record = { 'Previous 7 Days': 'Last 7 Days', Older: 'Earlier', }; function applyFilter(tasks: GlobalTask[], filter: StatusFilter): GlobalTask[] { if (filter === 'all') return tasks; if (filter === 'active') return tasks.filter((t) => t.status === 'pending' || t.status === 'in_progress'); return tasks.filter((t) => t.status === 'completed'); } function applySearch(tasks: GlobalTask[], query: string): GlobalTask[] { if (!query.trim()) return tasks; const q = query.toLowerCase(); return tasks.filter( (t) => t.subject.toLowerCase().includes(q) || t.owner?.toLowerCase().includes(q) || t.teamDisplayName.toLowerCase().includes(q) ); } function applyProjectFilter(tasks: GlobalTask[], projectPath: string | null): GlobalTask[] { if (!projectPath) return tasks; const normalized = normalizePath(projectPath); return tasks.filter((t) => t.projectPath && normalizePath(t.projectPath) === normalized); } export const GlobalTaskList = (): React.JSX.Element => { const { globalTasks, globalTasksLoading, fetchAllTasks, projects, activeProjectId, viewMode, repositoryGroups, selectedRepositoryId, selectedWorktreeId, } = useStore( useShallow((s) => ({ globalTasks: s.globalTasks, globalTasksLoading: s.globalTasksLoading, fetchAllTasks: s.fetchAllTasks, projects: s.projects, activeProjectId: s.activeProjectId, viewMode: s.viewMode, repositoryGroups: s.repositoryGroups, selectedRepositoryId: s.selectedRepositoryId, selectedWorktreeId: s.selectedWorktreeId, })) ); const [filter, setFilter] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); const searchInputRef = useRef(null); const hasFetchedRef = useRef(false); useEffect(() => { if (!hasFetchedRef.current) { hasFetchedRef.current = true; void fetchAllTasks(); } }, [fetchAllTasks]); const selectedProjectPath = useMemo(() => { if (viewMode === 'grouped') { const repo = repositoryGroups.find((r) => r.id === selectedRepositoryId); const worktree = repo?.worktrees.find((w) => w.id === selectedWorktreeId); return worktree?.path ?? null; } const project = projects.find((p) => p.id === activeProjectId); return project?.path ?? null; }, [ viewMode, repositoryGroups, selectedRepositoryId, selectedWorktreeId, projects, activeProjectId, ]); const filtered = useMemo(() => { let result = globalTasks; result = applyProjectFilter(result, selectedProjectPath); result = applyFilter(result, filter); result = applySearch(result, searchQuery); return result; }, [globalTasks, selectedProjectPath, filter, searchQuery]); const grouped = useMemo(() => groupTasksByDate(filtered), [filtered]); const categories = useMemo(() => getNonEmptyTaskCategories(grouped), [grouped]); return (
{/* Header + Filter bar */}
Tasks
{filterButtons.map((btn) => ( ))}
{/* Search bar */}
setSearchQuery(e.target.value)} className="min-w-0 flex-1 bg-transparent text-[12px] text-text placeholder:text-text-muted focus:outline-none" /> {searchQuery && ( )}
{/* Content */}
{globalTasksLoading && globalTasks.length === 0 && (
{[1, 2, 3].map((i) => (
))}
)} {!globalTasksLoading && categories.length === 0 && (
{searchQuery || selectedProjectPath ? 'No matching tasks' : 'No tasks found'}
)} {categories.map((category) => { const tasks = grouped[category]; let lastTeam: string | null = null; return (
{/* Date header */}
{dateCategoryLabels[category] ?? category}
{tasks.map((task) => { const showTeamHeader = task.teamName !== lastTeam; lastTeam = task.teamName; return (
{showTeamHeader && (
Team: {task.teamDisplayName}
)}
); })}
); })}
); };