Merge pull request #241 from Worklenz/release/v2.0.4-bug-fix

feat(task-management): add all_labels support and improve label handling
This commit is contained in:
Chamika J
2025-07-07 09:41:24 +05:30
committed by GitHub
6 changed files with 53 additions and 12 deletions

View File

@@ -1078,6 +1078,11 @@ export default class TasksControllerV2 extends TasksControllerBase {
end: l.end, end: l.end,
names: l.names names: l.names
})) || [], })) || [],
all_labels: task.all_labels?.map((l: any) => ({
id: l.id || l.label_id,
name: l.name,
color_code: l.color_code || "#1890ff"
})) || [],
dueDate: task.end_date || task.END_DATE, dueDate: task.end_date || task.END_DATE,
startDate: task.start_date, startDate: task.start_date,
timeTracking: { timeTracking: {

View File

@@ -223,16 +223,21 @@ const LabelsSelector: React.FC<LabelsSelectorProps> = ({ task, isDarkMode = fals
<div <div
key={label.id} key={label.id}
className={` className={`
flex items-center gap-2 p-2 cursor-pointer transition-colors flex items-center gap-2 px-2 py-1 cursor-pointer transition-colors
${isDarkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-50'} ${isDarkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-50'}
`} `}
onClick={() => handleLabelToggle(label)} onClick={(e) => {
e.stopPropagation();
handleLabelToggle(label);
}}
> >
<div style={{ pointerEvents: 'none' }}>
<Checkbox <Checkbox
checked={checkLabelSelected(label.id || '')} checked={checkLabelSelected(label.id || '')}
onChange={() => handleLabelToggle(label)} onChange={() => {}} // Empty handler since we handle click on the div
isDarkMode={isDarkMode} isDarkMode={isDarkMode}
/> />
</div>
<div <div
className="w-3 h-3 rounded-full shrink-0" className="w-3 h-3 rounded-full shrink-0"

View File

@@ -180,17 +180,17 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
name: task.title || task.name, name: task.title || task.name,
parent_task_id: task.parent_task_id, parent_task_id: task.parent_task_id,
manual_progress: false, manual_progress: false,
all_labels: task.labels?.map(label => ({ all_labels: task.all_labels?.map(label => ({
id: label.id, id: label.id,
name: label.name, name: label.name,
color_code: label.color, color_code: label.color_code,
})) || [], })) || [],
labels: task.labels?.map(label => ({ labels: task.labels?.map(label => ({
id: label.id, id: label.id,
name: label.name, name: label.name,
color_code: label.color, color_code: label.color,
})) || [], })) || [],
}), [task.id, task.title, task.name, task.parent_task_id, task.labels, task.labels?.length]); }), [task.id, task.title, task.name, task.parent_task_id, task.all_labels, task.labels, task.all_labels?.length, task.labels?.length]);
// Handle checkbox change // Handle checkbox change
const handleCheckboxChange = useCallback((e: any) => { const handleCheckboxChange = useCallback((e: any) => {
@@ -556,7 +556,7 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
case 'labels': case 'labels':
return ( return (
<div className="flex items-center gap-0.5 flex-wrap min-w-0" style={{ ...baseStyle, minWidth: '150px' }}> <div className="flex items-center gap-0.5 flex-wrap min-w-0" style={{ ...baseStyle, minWidth: '150px', width: 'auto', flexGrow: 1 }}>
<TaskLabelsCell labels={task.labels} isDarkMode={isDarkMode} /> <TaskLabelsCell labels={task.labels} isDarkMode={isDarkMode} />
<LabelsSelector task={labelsAdapter} isDarkMode={isDarkMode} /> <LabelsSelector task={labelsAdapter} isDarkMode={isDarkMode} />
</div> </div>

View File

@@ -271,6 +271,11 @@ export const fetchTasksV3 = createAsyncThunk(
end: l.end, end: l.end,
names: l.names, names: l.names,
})) || [], })) || [],
all_labels: task.all_labels?.map((l: { id: string; label_id: string; name: string; color_code: string }) => ({
id: l.id || l.label_id,
name: l.name,
color_code: l.color_code || '#1890ff',
})) || [],
dueDate: task.dueDate, dueDate: task.dueDate,
startDate: task.startDate, startDate: task.startDate,
timeTracking: { timeTracking: {

View File

@@ -43,6 +43,7 @@ import {
selectCurrentGroupingV3, selectCurrentGroupingV3,
fetchTasksV3, fetchTasksV3,
addSubtaskToParent, addSubtaskToParent,
removeTemporarySubtask,
} from '@/features/task-management/task-management.slice'; } from '@/features/task-management/task-management.slice';
import { import {
updateEnhancedKanbanSubtask, updateEnhancedKanbanSubtask,
@@ -153,13 +154,19 @@ export const useTaskSocketHandlers = () => {
const updatedTask: Task = { const updatedTask: Task = {
...currentTask, ...currentTask,
labels: labels:
labels.all_labels?.map(l => ({ labels.labels?.map(l => ({
id: l.id || '', id: l.id || '',
name: l.name || '', name: l.name || '',
color: l.color_code || '#1890ff', color: l.color_code || '#1890ff',
end: l.end, end: l.end,
names: l.names, names: l.names,
})) || [], })) || [],
all_labels:
labels.all_labels?.map(l => ({
id: l.id || '',
name: l.name || '',
color_code: l.color_code || '#1890ff',
})) || [],
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
}; };
@@ -675,6 +682,24 @@ export const useTaskSocketHandlers = () => {
parent_task_id: data.parent_task_id, parent_task_id: data.parent_task_id,
is_sub_task: true, is_sub_task: true,
}; };
// Before adding the real subtask, remove any temporary subtasks with the same name
// This prevents duplication from optimistic updates
const parentTask = store.getState().taskManagement.entities[data.parent_task_id];
if (parentTask && parentTask.sub_tasks) {
const temporarySubtasks = parentTask.sub_tasks.filter(
(st: Task) => st.isTemporary && st.name === subtask.title
);
// Remove each temporary subtask
temporarySubtasks.forEach((tempSubtask: Task) => {
dispatch(removeTemporarySubtask({
parentTaskId: data.parent_task_id,
tempId: tempSubtask.id
}));
});
}
dispatch(addSubtaskToParent({ parentId: data.parent_task_id, subtask })); dispatch(addSubtaskToParent({ parentId: data.parent_task_id, subtask }));
// Also update enhanced kanban slice for subtask creation // Also update enhanced kanban slice for subtask creation

View File

@@ -33,6 +33,7 @@ export interface Task {
statusColor?: string; statusColor?: string;
priorityColor?: string; priorityColor?: string;
labels?: { id: string; name: string; color: string; end?: boolean; names?: string[] }[]; labels?: { id: string; name: string; color: string; end?: boolean; names?: string[] }[];
all_labels?: { id: string; name: string; color_code: string }[]; // Complete list of labels for selection logic
comments_count?: number; comments_count?: number;
attachments_count?: number; attachments_count?: number;
has_dependencies?: boolean; has_dependencies?: boolean;