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:
@@ -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: {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<div style={{ pointerEvents: 'none' }}>
|
||||||
checked={checkLabelSelected(label.id || '')}
|
<Checkbox
|
||||||
onChange={() => handleLabelToggle(label)}
|
checked={checkLabelSelected(label.id || '')}
|
||||||
isDarkMode={isDarkMode}
|
onChange={() => {}} // Empty handler since we handle click on the div
|
||||||
/>
|
isDarkMode={isDarkMode}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="w-3 h-3 rounded-full shrink-0"
|
className="w-3 h-3 rounded-full shrink-0"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user