refactor(task-list): enhance task rendering and editing functionality in TaskRow and TaskListV2Table
- Updated TaskListV2Table to pass isFirstInGroup prop to renderTask for improved task grouping logic. - Enhanced TaskRow to support inline editing of task names with a new input field and associated state management. - Implemented click outside detection to save task name changes when editing is complete. - Improved layout and styling for better user experience during task editing and display.
This commit is contained in:
@@ -457,7 +457,7 @@ const TaskListV2Section: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderTask = useCallback(
|
const renderTask = useCallback(
|
||||||
(taskIndex: number) => {
|
(taskIndex: number, isFirstInGroup: boolean = false) => {
|
||||||
const item = virtuosoItems[taskIndex];
|
const item = virtuosoItems[taskIndex];
|
||||||
|
|
||||||
if (!item || !urlProjectId) return null;
|
if (!item || !urlProjectId) return null;
|
||||||
@@ -480,6 +480,7 @@ const TaskListV2Section: React.FC = () => {
|
|||||||
taskId={item.id}
|
taskId={item.id}
|
||||||
projectId={urlProjectId}
|
projectId={urlProjectId}
|
||||||
visibleColumns={visibleColumns}
|
visibleColumns={visibleColumns}
|
||||||
|
isFirstInGroup={isFirstInGroup}
|
||||||
updateTaskCustomColumnValue={updateTaskCustomColumnValue}
|
updateTaskCustomColumnValue={updateTaskCustomColumnValue}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -647,9 +648,12 @@ const TaskListV2Section: React.FC = () => {
|
|||||||
virtuosoGroups.slice(0, groupIndex).reduce((sum, g) => sum + g.count, 0) +
|
virtuosoGroups.slice(0, groupIndex).reduce((sum, g) => sum + g.count, 0) +
|
||||||
taskIndex;
|
taskIndex;
|
||||||
|
|
||||||
|
// Check if this is the first actual task in the group (not AddTaskRow)
|
||||||
|
const isFirstTaskInGroup = taskIndex === 0 && !('isAddTaskRow' in task);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={task.id || `add-task-${group.id}-${taskIndex}`}>
|
<div key={task.id || `add-task-${group.id}-${taskIndex}`}>
|
||||||
{renderTask(globalTaskIndex)}
|
{renderTask(globalTaskIndex, isFirstTaskInGroup)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { memo, useMemo, useCallback, useState } from 'react';
|
import React, { memo, useMemo, useCallback, useState, useRef, useEffect } from 'react';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { CheckCircleOutlined, HolderOutlined, CloseOutlined, DownOutlined, RightOutlined, DoubleRightOutlined, ArrowsAltOutlined, CommentOutlined, EyeOutlined, PaperClipOutlined, MinusCircleOutlined, RetweetOutlined } from '@ant-design/icons';
|
import { CheckCircleOutlined, HolderOutlined, CloseOutlined, DownOutlined, RightOutlined, DoubleRightOutlined, ArrowsAltOutlined, CommentOutlined, EyeOutlined, PaperClipOutlined, MinusCircleOutlined, RetweetOutlined } from '@ant-design/icons';
|
||||||
import { Checkbox, DatePicker, Tooltip } from 'antd';
|
import { Checkbox, DatePicker, Tooltip, Input } from 'antd';
|
||||||
|
import type { InputRef } from 'antd';
|
||||||
import { dayjs, taskManagementAntdConfig } from '@/shared/antd-imports';
|
import { dayjs, taskManagementAntdConfig } from '@/shared/antd-imports';
|
||||||
import { Task } from '@/types/task-management.types';
|
import { Task } from '@/types/task-management.types';
|
||||||
import { InlineMember } from '@/types/teamMembers/inlineMember.types';
|
import { InlineMember } from '@/types/teamMembers/inlineMember.types';
|
||||||
@@ -40,6 +41,7 @@ interface TaskRowProps {
|
|||||||
isCustom?: boolean;
|
isCustom?: boolean;
|
||||||
}>;
|
}>;
|
||||||
isSubtask?: boolean;
|
isSubtask?: boolean;
|
||||||
|
isFirstInGroup?: boolean;
|
||||||
updateTaskCustomColumnValue?: (taskId: string, columnKey: string, value: string) => void;
|
updateTaskCustomColumnValue?: (taskId: string, columnKey: string, value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ const formatDate = (dateString: string): string => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumns, isSubtask = false, updateTaskCustomColumnValue }) => {
|
const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumns, isSubtask = false, isFirstInGroup = false, updateTaskCustomColumnValue }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const task = useAppSelector(state => selectTaskById(state, taskId));
|
const task = useAppSelector(state => selectTaskById(state, taskId));
|
||||||
const isSelected = useAppSelector(state => selectIsTaskSelected(state, taskId));
|
const isSelected = useAppSelector(state => selectIsTaskSelected(state, taskId));
|
||||||
@@ -106,6 +108,12 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
|
|||||||
|
|
||||||
// State for tracking which date picker is open
|
// State for tracking which date picker is open
|
||||||
const [activeDatePicker, setActiveDatePicker] = useState<string | null>(null);
|
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 || '');
|
||||||
|
const inputRef = useRef<InputRef>(null);
|
||||||
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
return null; // Don't render if task is not found in store
|
return null; // Don't render if task is not found in store
|
||||||
@@ -153,6 +161,45 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
|
|||||||
manual_progress: undefined,
|
manual_progress: undefined,
|
||||||
}), [task.id, taskDisplayName, task.task_key, task.assignee_names, task.parent_task_id]);
|
}), [task.id, taskDisplayName, task.task_key, task.assignee_names, task.parent_task_id]);
|
||||||
|
|
||||||
|
// Handle task name save
|
||||||
|
const handleTaskNameSave = useCallback(() => {
|
||||||
|
const newTaskName = inputRef.current?.input?.value || taskName;
|
||||||
|
if (newTaskName?.trim() !== '' && connected && newTaskName.trim() !== (task.title || task.name || '').trim()) {
|
||||||
|
socket?.emit(
|
||||||
|
SocketEvents.TASK_NAME_CHANGE.toString(),
|
||||||
|
JSON.stringify({
|
||||||
|
task_id: task.id,
|
||||||
|
name: newTaskName.trim(),
|
||||||
|
parent_task: task.parent_task_id,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setEditTaskName(false);
|
||||||
|
}, [taskName, connected, socket, task.id, task.parent_task_id, task.title, task.name]);
|
||||||
|
|
||||||
|
// Handle click outside for task name editing
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
|
||||||
|
handleTaskNameSave();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (editTaskName) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [editTaskName, handleTaskNameSave]);
|
||||||
|
|
||||||
|
// Update local taskName state when task name changes
|
||||||
|
useEffect(() => {
|
||||||
|
setTaskName(task.title || task.name || '');
|
||||||
|
}, [task.title, task.name]);
|
||||||
|
|
||||||
// Memoize formatted dates
|
// Memoize formatted dates
|
||||||
const formattedDates = useMemo(() => ({
|
const formattedDates = useMemo(() => ({
|
||||||
due: (() => {
|
due: (() => {
|
||||||
@@ -289,129 +336,169 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'title':
|
case 'title':
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between group pl-1 border-r border-gray-200 dark:border-gray-700" style={baseStyle}>
|
<div
|
||||||
<div className="flex items-center flex-1 min-w-0">
|
className="flex items-center justify-between group pl-1 border-r border-gray-200 dark:border-gray-700"
|
||||||
{/* Indentation for subtasks - tighter spacing */}
|
style={baseStyle}
|
||||||
{isSubtask && <div className="w-4 flex-shrink-0" />}
|
>
|
||||||
|
{editTaskName ? (
|
||||||
{/* Expand/Collapse button - only show for parent tasks */}
|
/* Full cell input when editing */
|
||||||
{!isSubtask && (
|
<div className="flex-1" style={{ height: '38px' }} ref={wrapperRef}>
|
||||||
<button
|
<Input
|
||||||
onClick={handleToggleExpansion}
|
ref={inputRef}
|
||||||
className={`flex h-4 w-4 items-center justify-center rounded-sm text-xs mr-1 hover:border hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 hover:scale-110 transition-all duration-300 ease-out flex-shrink-0 ${
|
variant="borderless"
|
||||||
task.sub_tasks_count != null && task.sub_tasks_count > 0
|
value={taskName}
|
||||||
? 'opacity-100'
|
onChange={(e) => setTaskName(e.target.value)}
|
||||||
: 'opacity-0 group-hover:opacity-100'
|
autoFocus
|
||||||
}`}
|
onPressEnter={handleTaskNameSave}
|
||||||
>
|
onBlur={handleTaskNameSave}
|
||||||
<div
|
className="text-sm"
|
||||||
className="transition-transform duration-300 ease-out"
|
style={{
|
||||||
style={{
|
width: '100%',
|
||||||
transform: task.show_sub_tasks ? 'rotate(90deg)' : 'rotate(0deg)',
|
height: '38px',
|
||||||
transformOrigin: 'center'
|
margin: '0',
|
||||||
}}
|
padding: '8px 12px',
|
||||||
>
|
border: '1px solid #1677ff',
|
||||||
<RightOutlined className="text-gray-600 dark:text-gray-400" />
|
backgroundColor: 'rgba(22, 119, 255, 0.02)',
|
||||||
</div>
|
borderRadius: '3px',
|
||||||
</button>
|
fontSize: '14px',
|
||||||
)}
|
lineHeight: '22px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
{/* Additional indentation for subtasks after the expand button space */}
|
outline: 'none',
|
||||||
{isSubtask && <div className="w-2 flex-shrink-0" />}
|
boxShadow: '0 0 0 2px rgba(22, 119, 255, 0.1)',
|
||||||
|
}}
|
||||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
/>
|
||||||
{/* Task name with dynamic width */}
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
) : (
|
||||||
<Tooltip title={taskDisplayName}>
|
/* Normal layout when not editing */
|
||||||
<span
|
<>
|
||||||
className="text-sm text-gray-700 dark:text-gray-300 truncate cursor-pointer block"
|
<div className="flex items-center flex-1 min-w-0">
|
||||||
style={{
|
{/* Indentation for subtasks - tighter spacing */}
|
||||||
whiteSpace: 'nowrap',
|
{isSubtask && <div className="w-4 flex-shrink-0" />}
|
||||||
overflow: 'hidden',
|
|
||||||
textOverflow: 'ellipsis',
|
{/* Expand/Collapse button - only show for parent tasks */}
|
||||||
}}
|
{!isSubtask && (
|
||||||
|
<button
|
||||||
|
onClick={handleToggleExpansion}
|
||||||
|
className={`flex h-4 w-4 items-center justify-center rounded-sm text-xs mr-1 hover:border hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 hover:scale-110 transition-all duration-300 ease-out flex-shrink-0 ${
|
||||||
|
task.sub_tasks_count != null && task.sub_tasks_count > 0
|
||||||
|
? 'opacity-100'
|
||||||
|
: 'opacity-0 group-hover:opacity-100'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{taskDisplayName}
|
<div
|
||||||
</span>
|
className="transition-transform duration-300 ease-out"
|
||||||
</Tooltip>
|
style={{
|
||||||
|
transform: task.show_sub_tasks ? 'rotate(90deg)' : 'rotate(0deg)',
|
||||||
|
transformOrigin: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RightOutlined className="text-gray-600 dark:text-gray-400" />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Additional indentation for subtasks after the expand button space */}
|
||||||
|
{isSubtask && <div className="w-2 flex-shrink-0" />}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||||
|
{/* Task name with dynamic width */}
|
||||||
|
<div className="flex-1 min-w-0" ref={wrapperRef}>
|
||||||
|
<span
|
||||||
|
className="text-sm text-gray-700 dark:text-gray-300 truncate cursor-text block"
|
||||||
|
style={{
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
setEditTaskName(true);
|
||||||
|
}}
|
||||||
|
title={taskDisplayName}
|
||||||
|
>
|
||||||
|
{taskDisplayName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Indicators container - flex-shrink-0 to prevent compression */}
|
||||||
|
<div className="flex items-center gap-1 flex-shrink-0">
|
||||||
|
{/* Subtask count indicator - only show if count > 0 */}
|
||||||
|
{!isSubtask && task.sub_tasks_count != null && task.sub_tasks_count > 0 && (
|
||||||
|
<Tooltip title={t(`indicators.tooltips.subtasks${task.sub_tasks_count === 1 ? '' : '_plural'}`, { count: task.sub_tasks_count })}>
|
||||||
|
<div className="flex items-center gap-1 px-1.5 py-0.5 bg-blue-50 dark:bg-blue-900/20 rounded text-xs">
|
||||||
|
<span className="text-blue-600 dark:text-blue-400 font-medium">
|
||||||
|
{task.sub_tasks_count}
|
||||||
|
</span>
|
||||||
|
<DoubleRightOutlined className="text-blue-600 dark:text-blue-400" style={{ fontSize: 10 }} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Task indicators - compact layout */}
|
||||||
|
{task.comments_count != null && task.comments_count !== 0 && (
|
||||||
|
<Tooltip title={t(`indicators.tooltips.comments${task.comments_count === 1 ? '' : '_plural'}`, { count: task.comments_count })}>
|
||||||
|
<CommentOutlined
|
||||||
|
className="text-gray-500 dark:text-gray-400"
|
||||||
|
style={{ fontSize: 12 }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{task.has_subscribers && (
|
||||||
|
<Tooltip title={t('indicators.tooltips.subscribers')}>
|
||||||
|
<EyeOutlined
|
||||||
|
className="text-gray-500 dark:text-gray-400"
|
||||||
|
style={{ fontSize: 12 }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{task.attachments_count != null && task.attachments_count !== 0 && (
|
||||||
|
<Tooltip title={t(`indicators.tooltips.attachments${task.attachments_count === 1 ? '' : '_plural'}`, { count: task.attachments_count })}>
|
||||||
|
<PaperClipOutlined
|
||||||
|
className="text-gray-500 dark:text-gray-400"
|
||||||
|
style={{ fontSize: 12 }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{task.has_dependencies && (
|
||||||
|
<Tooltip title={t('indicators.tooltips.dependencies')}>
|
||||||
|
<MinusCircleOutlined
|
||||||
|
className="text-gray-500 dark:text-gray-400"
|
||||||
|
style={{ fontSize: 12 }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{task.schedule_id && (
|
||||||
|
<Tooltip title={t('indicators.tooltips.recurring')}>
|
||||||
|
<RetweetOutlined
|
||||||
|
className="text-gray-500 dark:text-gray-400"
|
||||||
|
style={{ fontSize: 12 }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Indicators container - flex-shrink-0 to prevent compression */}
|
<button
|
||||||
<div className="flex items-center gap-1 flex-shrink-0">
|
className="opacity-0 group-hover:opacity-100 transition-all duration-200 ml-2 mr-2 px-3 py-1.5 text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 cursor-pointer rounded-md shadow-sm hover:shadow-md flex items-center gap-1 flex-shrink-0"
|
||||||
{/* Subtask count indicator - only show if count > 0 */}
|
onClick={(e) => {
|
||||||
{!isSubtask && task.sub_tasks_count != null && task.sub_tasks_count > 0 && (
|
e.stopPropagation();
|
||||||
<Tooltip title={t(`indicators.tooltips.subtasks${task.sub_tasks_count === 1 ? '' : '_plural'}`, { count: task.sub_tasks_count })}>
|
dispatch(setSelectedTaskId(task.id));
|
||||||
<div className="flex items-center gap-1 px-1.5 py-0.5 bg-blue-50 dark:bg-blue-900/20 rounded text-xs">
|
dispatch(setShowTaskDrawer(true));
|
||||||
<span className="text-blue-600 dark:text-blue-400 font-medium">
|
}}
|
||||||
{task.sub_tasks_count}
|
>
|
||||||
</span>
|
<ArrowsAltOutlined />
|
||||||
<DoubleRightOutlined className="text-blue-600 dark:text-blue-400" style={{ fontSize: 10 }} />
|
{t('openButton')}
|
||||||
</div>
|
</button>
|
||||||
</Tooltip>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Task indicators - compact layout */}
|
|
||||||
{task.comments_count != null && task.comments_count !== 0 && (
|
|
||||||
<Tooltip title={t(`indicators.tooltips.comments${task.comments_count === 1 ? '' : '_plural'}`, { count: task.comments_count })}>
|
|
||||||
<CommentOutlined
|
|
||||||
className="text-gray-500 dark:text-gray-400"
|
|
||||||
style={{ fontSize: 12 }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{task.has_subscribers && (
|
|
||||||
<Tooltip title={t('indicators.tooltips.subscribers')}>
|
|
||||||
<EyeOutlined
|
|
||||||
className="text-gray-500 dark:text-gray-400"
|
|
||||||
style={{ fontSize: 12 }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{task.attachments_count != null && task.attachments_count !== 0 && (
|
|
||||||
<Tooltip title={t(`indicators.tooltips.attachments${task.attachments_count === 1 ? '' : '_plural'}`, { count: task.attachments_count })}>
|
|
||||||
<PaperClipOutlined
|
|
||||||
className="text-gray-500 dark:text-gray-400"
|
|
||||||
style={{ fontSize: 12 }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{task.has_dependencies && (
|
|
||||||
<Tooltip title={t('indicators.tooltips.dependencies')}>
|
|
||||||
<MinusCircleOutlined
|
|
||||||
className="text-gray-500 dark:text-gray-400"
|
|
||||||
style={{ fontSize: 12 }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{task.schedule_id && (
|
|
||||||
<Tooltip title={t('indicators.tooltips.recurring')}>
|
|
||||||
<RetweetOutlined
|
|
||||||
className="text-gray-500 dark:text-gray-400"
|
|
||||||
style={{ fontSize: 12 }}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="opacity-0 group-hover:opacity-100 transition-all duration-200 ml-2 mr-2 px-3 py-1.5 text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 cursor-pointer rounded-md shadow-sm hover:shadow-md flex items-center gap-1 flex-shrink-0"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
dispatch(setSelectedTaskId(task.id));
|
|
||||||
dispatch(setShowTaskDrawer(true));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowsAltOutlined />
|
|
||||||
{t('openButton')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -755,6 +842,10 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
|
|||||||
isDarkMode,
|
isDarkMode,
|
||||||
projectId,
|
projectId,
|
||||||
|
|
||||||
|
// Edit task name state - CRITICAL for re-rendering
|
||||||
|
editTaskName,
|
||||||
|
taskName,
|
||||||
|
|
||||||
// Task data - include specific fields that might update via socket
|
// Task data - include specific fields that might update via socket
|
||||||
task,
|
task,
|
||||||
task.labels, // Explicit dependency for labels updates
|
task.labels, // Explicit dependency for labels updates
|
||||||
@@ -775,6 +866,7 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
|
|||||||
// Handlers
|
// Handlers
|
||||||
handleDateChange,
|
handleDateChange,
|
||||||
datePickerHandlers,
|
datePickerHandlers,
|
||||||
|
handleTaskNameSave,
|
||||||
|
|
||||||
// Translation
|
// Translation
|
||||||
t,
|
t,
|
||||||
@@ -787,8 +879,10 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={{ ...style, height: '40px' }}
|
||||||
className={`flex items-center min-w-max px-1 py-2 border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 ${
|
className={`flex items-center min-w-max px-1 border-b border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 ${
|
||||||
|
isFirstInGroup ? 'border-t border-gray-200 dark:border-gray-700' : ''
|
||||||
|
} ${
|
||||||
isDragging ? 'shadow-lg border border-blue-300' : ''
|
isDragging ? 'shadow-lg border border-blue-300' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ interface TaskRowWithSubtasksProps {
|
|||||||
width: string;
|
width: string;
|
||||||
isSticky?: boolean;
|
isSticky?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
isFirstInGroup?: boolean;
|
||||||
updateTaskCustomColumnValue?: (taskId: string, columnKey: string, value: string) => void;
|
updateTaskCustomColumnValue?: (taskId: string, columnKey: string, value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +154,7 @@ const TaskRowWithSubtasks: React.FC<TaskRowWithSubtasksProps> = memo(({
|
|||||||
taskId,
|
taskId,
|
||||||
projectId,
|
projectId,
|
||||||
visibleColumns,
|
visibleColumns,
|
||||||
|
isFirstInGroup = false,
|
||||||
updateTaskCustomColumnValue
|
updateTaskCustomColumnValue
|
||||||
}) => {
|
}) => {
|
||||||
const task = useAppSelector(state => selectTaskById(state, taskId));
|
const task = useAppSelector(state => selectTaskById(state, taskId));
|
||||||
@@ -175,6 +177,7 @@ const TaskRowWithSubtasks: React.FC<TaskRowWithSubtasksProps> = memo(({
|
|||||||
taskId={taskId}
|
taskId={taskId}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
visibleColumns={visibleColumns}
|
visibleColumns={visibleColumns}
|
||||||
|
isFirstInGroup={isFirstInGroup}
|
||||||
updateTaskCustomColumnValue={updateTaskCustomColumnValue}
|
updateTaskCustomColumnValue={updateTaskCustomColumnValue}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user