refactor(task-master): migrate tasks to a typed feature module

- introduce a new feature-oriented TaskMaster domain under src/components/task-master

- add typed TaskMaster context/provider with explicit project, task, MCP, and loading state handling

- split task UI into focused components (panel, board, toolbar, content, card, detail modal, setup/help modals, banner)

- move task board filtering/sorting/kanban derivation into dedicated hooks and utilities

- relocate CreateTaskModal into the feature module and keep task views modular/readable

- remove legacy monolithic TaskList/TaskDetail/TaskCard files and route main task panel to the new feature panel

- replace contexts/TaskMasterContext.jsx with a typed contexts/TaskMasterContext.ts re-export to the feature context

- update MainContent project sync logic to compare by project name to avoid state churn

- validation: npm run typecheck, npm run build
This commit is contained in:
Haileyesus
2026-03-02 17:55:57 +03:00
parent b18e0de2f3
commit cd122913ce
28 changed files with 2796 additions and 2912 deletions

View File

@@ -0,0 +1,97 @@
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { TaskBoardSortField, TaskBoardSortOrder, TaskBoardView, TaskKanbanColumn, TaskMasterTask } from '../types';
import { buildKanbanColumns } from '../utils/taskKanban';
import { sortTasks, toggleSortOrder } from '../utils/taskSorting';
type UseTaskBoardStateOptions = {
tasks: TaskMasterTask[];
defaultView?: TaskBoardView;
};
function matchesSearch(task: TaskMasterTask, searchTerm: string): boolean {
if (!searchTerm) {
return true;
}
const normalizedSearch = searchTerm.toLowerCase();
const description = typeof task.description === 'string' ? task.description : '';
return (
task.title.toLowerCase().includes(normalizedSearch)
|| description.toLowerCase().includes(normalizedSearch)
|| String(task.id).toLowerCase().includes(normalizedSearch)
);
}
export function useTaskBoardState({ tasks, defaultView = 'kanban' }: UseTaskBoardStateOptions) {
const { t } = useTranslation('tasks');
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [priorityFilter, setPriorityFilter] = useState('all');
const [sortField, setSortField] = useState<TaskBoardSortField>('id');
const [sortOrder, setSortOrder] = useState<TaskBoardSortOrder>('asc');
const [viewMode, setViewMode] = useState<TaskBoardView>(defaultView);
const [showFilters, setShowFilters] = useState(false);
const statuses = useMemo(() => {
return [...new Set(tasks.map((task) => task.status).filter(Boolean))] as string[];
}, [tasks]);
const priorities = useMemo(() => {
return [...new Set(tasks.map((task) => task.priority).filter(Boolean))] as string[];
}, [tasks]);
const filteredTasks = useMemo(() => {
const filtered = tasks.filter((task) => {
const status = task.status ?? 'pending';
const priority = task.priority ?? 'medium';
const matchesStatus = statusFilter === 'all' || status === statusFilter;
const matchesPriority = priorityFilter === 'all' || priority === priorityFilter;
return matchesSearch(task, searchTerm) && matchesStatus && matchesPriority;
});
return sortTasks(filtered, sortField, sortOrder);
}, [tasks, searchTerm, statusFilter, priorityFilter, sortField, sortOrder]);
const kanbanColumns = useMemo<TaskKanbanColumn[]>(() => {
return buildKanbanColumns(filteredTasks, t);
}, [filteredTasks, t]);
const handleSortChange = (nextSortField: TaskBoardSortField) => {
setSortOrder((currentOrder) => toggleSortOrder(sortField, currentOrder, nextSortField));
setSortField(nextSortField);
};
const clearFilters = () => {
setSearchTerm('');
setStatusFilter('all');
setPriorityFilter('all');
};
return {
searchTerm,
setSearchTerm,
statusFilter,
setStatusFilter,
priorityFilter,
setPriorityFilter,
sortField,
setSortField,
sortOrder,
setSortOrder,
viewMode,
setViewMode,
showFilters,
setShowFilters,
statuses,
priorities,
filteredTasks,
kanbanColumns,
handleSortChange,
clearFilters,
};
}