refactor(task-list): restructure TaskRow and introduce new column components

- Refactored TaskRow to simplify state management and enhance readability by extracting logic into custom hooks.
- Introduced new components for rendering task columns, including DatePickerColumn, TitleColumn, and various column types for better modularity.
- Improved task name editing functionality with better state handling and click outside detection.
- Streamlined date handling and formatting within the task row for improved user experience.
This commit is contained in:
chamiakJ
2025-07-10 12:17:22 +05:30
parent 6448d24e20
commit 70cca5d4c0
8 changed files with 1595 additions and 833 deletions

View File

@@ -0,0 +1,63 @@
import { useCallback } from 'react';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { useSocket } from '@/socket/socketContext';
import { SocketEvents } from '@/shared/socket-events';
import { toggleTaskSelection } from '@/features/task-management/selection.slice';
import { Task } from '@/types/task-management.types';
interface UseTaskRowActionsProps {
task: Task;
taskId: string;
taskName: string;
editTaskName: boolean;
setEditTaskName: (editing: boolean) => void;
}
export const useTaskRowActions = ({
task,
taskId,
taskName,
editTaskName,
setEditTaskName,
}: UseTaskRowActionsProps) => {
const dispatch = useAppDispatch();
const { socket, connected } = useSocket();
// Handle checkbox change
const handleCheckboxChange = useCallback((e: any) => {
e.stopPropagation(); // Prevent row click when clicking checkbox
dispatch(toggleTaskSelection(taskId));
}, [dispatch, taskId]);
// Handle task name save
const handleTaskNameSave = useCallback(() => {
if (taskName?.trim() !== '' && connected && taskName.trim() !== (task.title || task.name || '').trim()) {
socket?.emit(
SocketEvents.TASK_NAME_CHANGE.toString(),
JSON.stringify({
task_id: task.id,
name: taskName.trim(),
parent_task: task.parent_task_id,
})
);
}
setEditTaskName(false);
}, [taskName, connected, socket, task.id, task.parent_task_id, task.title, task.name, setEditTaskName]);
// Handle task name edit start
const handleTaskNameEdit = useCallback(() => {
setEditTaskName(true);
}, [setEditTaskName]);
// Handle task name change
const handleTaskNameChange = useCallback((name: string) => {
// This will be handled by the parent component's state setter
}, []);
return {
handleCheckboxChange,
handleTaskNameSave,
handleTaskNameEdit,
handleTaskNameChange,
};
};

View File

@@ -0,0 +1,320 @@
import React, { useCallback } from 'react';
import { Task } from '@/types/task-management.types';
import {
DragHandleColumn,
CheckboxColumn,
TaskKeyColumn,
DescriptionColumn,
StatusColumn,
AssigneesColumn,
PriorityColumn,
ProgressColumn,
LabelsColumn,
PhaseColumn,
TimeTrackingColumn,
EstimationColumn,
DateColumn,
ReporterColumn,
CustomColumn,
} from '../components/TaskRowColumns';
import { TitleColumn } from '../components/TitleColumn';
import { DatePickerColumn } from '../components/DatePickerColumn';
interface UseTaskRowColumnsProps {
task: Task;
projectId: string;
isSubtask: boolean;
isSelected: boolean;
isDarkMode: boolean;
visibleColumns: Array<{
id: string;
width: string;
isSticky?: boolean;
key?: string;
custom_column?: boolean;
custom_column_obj?: any;
isCustom?: boolean;
}>;
updateTaskCustomColumnValue?: (taskId: string, columnKey: string, value: string) => void;
// From useTaskRowState
taskDisplayName: string;
convertedTask: any;
formattedDates: any;
dateValues: any;
labelsAdapter: any;
activeDatePicker: string | null;
setActiveDatePicker: (field: string | null) => void;
editTaskName: boolean;
taskName: string;
setEditTaskName: (editing: boolean) => void;
setTaskName: (name: string) => void;
// From useTaskRowActions
handleCheckboxChange: (e: any) => void;
handleTaskNameSave: () => void;
handleTaskNameEdit: () => void;
// Drag and drop
attributes: any;
listeners: any;
}
export const useTaskRowColumns = ({
task,
projectId,
isSubtask,
isSelected,
isDarkMode,
visibleColumns,
updateTaskCustomColumnValue,
taskDisplayName,
convertedTask,
formattedDates,
dateValues,
labelsAdapter,
activeDatePicker,
setActiveDatePicker,
editTaskName,
taskName,
setEditTaskName,
setTaskName,
handleCheckboxChange,
handleTaskNameSave,
handleTaskNameEdit,
attributes,
listeners,
}: UseTaskRowColumnsProps) => {
const renderColumn = useCallback((columnId: string, width: string, isSticky?: boolean, index?: number) => {
switch (columnId) {
case 'dragHandle':
return (
<DragHandleColumn
width={width}
isSubtask={isSubtask}
attributes={attributes}
listeners={listeners}
/>
);
case 'checkbox':
return (
<CheckboxColumn
width={width}
isSelected={isSelected}
onCheckboxChange={handleCheckboxChange}
/>
);
case 'taskKey':
return (
<TaskKeyColumn
width={width}
taskKey={task.task_key || ''}
/>
);
case 'title':
return (
<TitleColumn
width={width}
task={task}
projectId={projectId}
isSubtask={isSubtask}
taskDisplayName={taskDisplayName}
editTaskName={editTaskName}
taskName={taskName}
onEditTaskName={setEditTaskName}
onTaskNameChange={setTaskName}
onTaskNameSave={handleTaskNameSave}
/>
);
case 'description':
return (
<DescriptionColumn
width={width}
description={task.description || ''}
/>
);
case 'status':
return (
<StatusColumn
width={width}
task={task}
projectId={projectId}
isDarkMode={isDarkMode}
/>
);
case 'assignees':
return (
<AssigneesColumn
width={width}
task={task}
convertedTask={convertedTask}
isDarkMode={isDarkMode}
/>
);
case 'priority':
return (
<PriorityColumn
width={width}
task={task}
projectId={projectId}
isDarkMode={isDarkMode}
/>
);
case 'dueDate':
return (
<DatePickerColumn
width={width}
task={task}
field="dueDate"
formattedDate={formattedDates.due}
dateValue={dateValues.due}
isDarkMode={isDarkMode}
activeDatePicker={activeDatePicker}
onActiveDatePickerChange={setActiveDatePicker}
/>
);
case 'startDate':
return (
<DatePickerColumn
width={width}
task={task}
field="startDate"
formattedDate={formattedDates.start}
dateValue={dateValues.start}
isDarkMode={isDarkMode}
activeDatePicker={activeDatePicker}
onActiveDatePickerChange={setActiveDatePicker}
/>
);
case 'progress':
return (
<ProgressColumn
width={width}
task={task}
/>
);
case 'labels':
return (
<LabelsColumn
width={width}
task={task}
labelsAdapter={labelsAdapter}
isDarkMode={isDarkMode}
visibleColumns={visibleColumns}
/>
);
case 'phase':
return (
<PhaseColumn
width={width}
task={task}
projectId={projectId}
isDarkMode={isDarkMode}
/>
);
case 'timeTracking':
return (
<TimeTrackingColumn
width={width}
taskId={task.id || ''}
isDarkMode={isDarkMode}
/>
);
case 'estimation':
return (
<EstimationColumn
width={width}
task={task}
/>
);
case 'completedDate':
return (
<DateColumn
width={width}
formattedDate={formattedDates.completed}
/>
);
case 'createdDate':
return (
<DateColumn
width={width}
formattedDate={formattedDates.created}
/>
);
case 'lastUpdated':
return (
<DateColumn
width={width}
formattedDate={formattedDates.updated}
/>
);
case 'reporter':
return (
<ReporterColumn
width={width}
reporter={task.reporter || ''}
/>
);
default:
// Handle custom columns
const column = visibleColumns.find(col => col.id === columnId);
if (column && (column.custom_column || column.isCustom) && updateTaskCustomColumnValue) {
return (
<CustomColumn
width={width}
column={column}
task={task}
updateTaskCustomColumnValue={updateTaskCustomColumnValue}
/>
);
}
return null;
}
}, [
task,
projectId,
isSubtask,
isSelected,
isDarkMode,
visibleColumns,
updateTaskCustomColumnValue,
taskDisplayName,
convertedTask,
formattedDates,
dateValues,
labelsAdapter,
activeDatePicker,
setActiveDatePicker,
editTaskName,
taskName,
setEditTaskName,
setTaskName,
handleCheckboxChange,
handleTaskNameSave,
handleTaskNameEdit,
attributes,
listeners,
]);
return { renderColumn };
};

View File

@@ -0,0 +1,96 @@
import { useState, useEffect, useMemo } from 'react';
import { Task } from '@/types/task-management.types';
import { InlineMember } from '@/types/teamMembers/inlineMember.types';
import { dayjs } from '@/shared/antd-imports';
import { getTaskDisplayName, formatDate } from '../components/TaskRowColumns';
export const useTaskRowState = (task: Task) => {
// State for tracking which date picker is open
const [activeDatePicker, setActiveDatePicker] = useState<string | null>(null);
// State for editing task name
const [editTaskName, setEditTaskName] = useState(false);
const [taskName, setTaskName] = useState(task.title || task.name || '');
// Update local taskName state when task name changes
useEffect(() => {
setTaskName(task.title || task.name || '');
}, [task.title, task.name]);
// Memoize task display name
const taskDisplayName = useMemo(() => getTaskDisplayName(task), [task.title, task.name, task.task_key]);
// Memoize converted task for AssigneeSelector to prevent recreation
const convertedTask = useMemo(() => ({
id: task.id,
name: taskDisplayName,
task_key: task.task_key || taskDisplayName,
assignees:
task.assignee_names?.map((assignee: InlineMember, index: number) => ({
team_member_id: assignee.team_member_id || `assignee-${index}`,
id: assignee.team_member_id || `assignee-${index}`,
project_member_id: assignee.team_member_id || `assignee-${index}`,
name: assignee.name || '',
})) || [],
parent_task_id: task.parent_task_id,
status_id: undefined,
project_id: undefined,
manual_progress: undefined,
}), [task.id, taskDisplayName, task.task_key, task.assignee_names, task.parent_task_id]);
// Memoize formatted dates
const formattedDates = useMemo(() => ({
due: (() => {
const dateValue = task.dueDate || task.due_date;
return dateValue ? formatDate(dateValue) : null;
})(),
start: task.startDate ? formatDate(task.startDate) : null,
completed: task.completedAt ? formatDate(task.completedAt) : null,
created: (task.createdAt || task.created_at) ? formatDate(task.createdAt || task.created_at) : null,
updated: task.updatedAt ? formatDate(task.updatedAt) : null,
}), [task.dueDate, task.due_date, task.startDate, task.completedAt, task.createdAt, task.created_at, task.updatedAt]);
// Memoize date values for DatePicker
const dateValues = useMemo(
() => ({
start: task.startDate ? dayjs(task.startDate) : undefined,
due: (task.dueDate || task.due_date) ? dayjs(task.dueDate || task.due_date) : undefined,
}),
[task.startDate, task.dueDate, task.due_date]
);
// Create labels adapter for LabelsSelector
const labelsAdapter = useMemo(() => ({
id: task.id,
name: task.title || task.name,
parent_task_id: task.parent_task_id,
manual_progress: false,
all_labels: task.all_labels?.map(label => ({
id: label.id,
name: label.name,
color_code: label.color_code,
})) || [],
labels: task.labels?.map(label => ({
id: label.id,
name: label.name,
color_code: label.color,
})) || [],
}), [task.id, task.title, task.name, task.parent_task_id, task.all_labels, task.labels, task.all_labels?.length, task.labels?.length]);
return {
// State
activeDatePicker,
setActiveDatePicker,
editTaskName,
setEditTaskName,
taskName,
setTaskName,
// Computed values
taskDisplayName,
convertedTask,
formattedDates,
dateValues,
labelsAdapter,
};
};