fix(task-list): update styling and improve task handling

- Adjusted color styling for the CreateStatusButton based on theme mode.
- Enhanced TaskGroupHeader with improved border styling and spacing for better visual consistency.
- Increased width and padding for the AddCustomColumnButton to improve usability.
- Updated TaskRow to include additional dependencies for task labels and phase updates in socket handling.
- Refactored task management slice to ensure accurate label and phase updates during real-time interactions.
- Removed unnecessary debug logging from CustomColumnModal and SelectionTypeColumn components for cleaner code.
This commit is contained in:
chamiakJ
2025-07-07 05:07:05 +05:30
parent 48c3d58f7e
commit 411147efce
11 changed files with 141 additions and 131 deletions

View File

@@ -21,7 +21,7 @@ const CreateStatusButton = () => {
onClick={() => dispatch(toggleDrawer())} onClick={() => dispatch(toggleDrawer())}
icon={ icon={
<SettingOutlined <SettingOutlined
style={{ color: themeMode === 'dark' ? colors.white : colors.midBlue }} style={{ color: themeMode === 'dark' ? colors.white : 'black' }}
/> />
} }
/> />

View File

@@ -235,7 +235,7 @@ const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, o
return ( return (
<div <div
ref={setNodeRef} ref={setNodeRef}
className={`inline-flex w-max items-center px-1 cursor-pointer hover:opacity-80 transition-opacity duration-200 ease-in-out border-b border-gray-200 dark:border-gray-700 rounded-t-md ${ className={`inline-flex w-max items-center px-1 cursor-pointer hover:opacity-80 transition-opacity duration-200 ease-in-out border-t border-b border-gray-200 dark:border-gray-700 rounded-t-md pr-2 ${
isOver ? 'ring-2 ring-blue-400 ring-opacity-50' : '' isOver ? 'ring-2 ring-blue-400 ring-opacity-50' : ''
}`} }`}
style={{ style={{
@@ -291,7 +291,7 @@ const TaskGroupHeader: React.FC<TaskGroupHeaderProps> = ({ group, isCollapsed, o
{/* Group name and count */} {/* Group name and count */}
<div className="flex items-center"> <div className="flex items-center">
<span <span
className="text-sm font-semibold" className="text-sm font-semibold pr-2"
style={{ color: headerTextColor }} style={{ color: headerTextColor }}
> >
{group.name} {group.name}

View File

@@ -491,7 +491,7 @@ const TaskListV2: React.FC = () => {
</div> </div>
); );
})} })}
<div className="flex items-center justify-center" style={{ width: '60px', flexShrink: 0 }}> <div className="flex items-center justify-center" style={{ width: '70px', flexShrink: 0, paddingRight: '8px' }}>
<AddCustomColumnButton /> <AddCustomColumnButton />
</div> </div>
</div> </div>

View File

@@ -49,6 +49,7 @@ interface TaskLabelsCellProps {
} }
const TaskLabelsCell: React.FC<TaskLabelsCellProps> = memo(({ labels, isDarkMode }) => { const TaskLabelsCell: React.FC<TaskLabelsCellProps> = memo(({ labels, isDarkMode }) => {
console.log('labels', labels);
if (!labels) { if (!labels) {
return null; return null;
} }
@@ -190,7 +191,7 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
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.id, task.title, task.name, task.parent_task_id, task.labels, task.labels?.length]);
// Handle checkbox change // Handle checkbox change
const handleCheckboxChange = useCallback((e: any) => { const handleCheckboxChange = useCallback((e: any) => {
@@ -654,8 +655,10 @@ const TaskRow: React.FC<TaskRowProps> = memo(({ taskId, projectId, visibleColumn
isDarkMode, isDarkMode,
projectId, projectId,
// Task data // Task data - include specific fields that might update via socket
task, task,
task.labels, // Explicit dependency for labels updates
task.phase, // Explicit dependency for phase updates
taskDisplayName, taskDisplayName,
convertedTask, convertedTask,

View File

@@ -29,7 +29,7 @@ export const AddCustomColumnButton: React.FC = memo(() => {
<button <button
onClick={handleModalOpen} onClick={handleModalOpen}
className={` className={`
group relative w-8 h-8 rounded-lg border-2 border-dashed transition-all duration-200 group relative w-9 h-9 rounded-lg border-2 border-dashed transition-all duration-200
flex items-center justify-center flex items-center justify-center
${isDarkMode ${isDarkMode
? 'border-gray-600 hover:border-blue-500 hover:bg-blue-500/10 text-gray-500 hover:text-blue-400' ? 'border-gray-600 hover:border-blue-500 hover:bg-blue-500/10 text-gray-500 hover:text-blue-400'
@@ -37,7 +37,7 @@ export const AddCustomColumnButton: React.FC = memo(() => {
} }
`} `}
> >
<PlusOutlined className="text-xs transition-transform duration-200 group-hover:scale-110" /> <PlusOutlined className="text-sm transition-transform duration-200 group-hover:scale-110" />
{/* Subtle glow effect on hover */} {/* Subtle glow effect on hover */}
<div className={` <div className={`

View File

@@ -1275,7 +1275,6 @@ const ImprovedTaskFilters: React.FC<ImprovedTaskFiltersProps> = ({ position, cla
}`} }`}
/> />
<span className={`text-xs ${themeClasses.optionText}`}>{t('showArchivedText')}</span> <span className={`text-xs ${themeClasses.optionText}`}>{t('showArchivedText')}</span>
<InboxOutlined className={`w-3.5 h-3.5 ${themeClasses.secondaryText}`} />
</label> </label>
)} )}

View File

@@ -37,6 +37,7 @@ import { fetchTaskGroups } from '@/features/tasks/tasks.slice';
import { updatePhaseLabel } from '@/features/project/project.slice'; import { updatePhaseLabel } from '@/features/project/project.slice';
import useTabSearchParam from '@/hooks/useTabSearchParam'; import useTabSearchParam from '@/hooks/useTabSearchParam';
import { fetchBoardTaskGroups } from '@/features/board/board-slice'; import { fetchBoardTaskGroups } from '@/features/board/board-slice';
import { fetchTasksV3 } from '@/features/task-management/task-management.slice';
interface UpdateSortOrderBody { interface UpdateSortOrderBody {
from_index: number; from_index: number;
@@ -67,6 +68,7 @@ const PhaseDrawer = () => {
const refreshTasks = async () => { const refreshTasks = async () => {
if (tab === 'tasks-list') { if (tab === 'tasks-list') {
await dispatch(fetchTasksV3(projectId || ''));
await dispatch(fetchTaskGroups(projectId || '')); await dispatch(fetchTaskGroups(projectId || ''));
} else if (tab === 'board') { } else if (tab === 'board') {
await dispatch(fetchBoardTaskGroups(projectId || '')); await dispatch(fetchBoardTaskGroups(projectId || ''));
@@ -131,6 +133,8 @@ const PhaseDrawer = () => {
if (res.done) { if (res.done) {
dispatch(updatePhaseLabel(phaseName)); dispatch(updatePhaseLabel(phaseName));
setInitialPhaseName(phaseName); setInitialPhaseName(phaseName);
// Refresh tasks to update phase label in task list
await refreshTasks();
} }
} catch (error) { } catch (error) {
logger.error('Error updating phase name', error); logger.error('Error updating phase name', error);

View File

@@ -264,10 +264,10 @@ export const fetchTasksV3 = createAsyncThunk(
progress: typeof task.complete_ratio === 'number' ? task.complete_ratio : 0, progress: typeof task.complete_ratio === 'number' ? task.complete_ratio : 0,
assignees: task.assignees?.map((a: { team_member_id: string }) => a.team_member_id) || [], assignees: task.assignees?.map((a: { team_member_id: string }) => a.team_member_id) || [],
assignee_names: task.assignee_names || task.names || [], assignee_names: task.assignee_names || task.names || [],
labels: task.labels?.map((l: { id: string; label_id: string; name: string; color_code: string; end: boolean; names: string[] }) => ({ labels: task.labels?.map((l: { id: string; label_id: string; name: string; color: string; end: boolean; names: string[] }) => ({
id: l.id || l.label_id, id: l.id || l.label_id,
name: l.name, name: l.name,
color: l.color_code || '#1890ff', color: l.color || '#1890ff',
end: l.end, end: l.end,
names: l.names, names: l.names,
})) || [], })) || [],

View File

@@ -91,16 +91,17 @@ export const useTaskSocketHandlers = () => {
// REAL-TIME UPDATES: Update the task-management slice for immediate UI updates // REAL-TIME UPDATES: Update the task-management slice for immediate UI updates
if (data.id) { if (data.id) {
dispatch( const currentTask = store.getState().taskManagement.entities[data.id];
updateTask({ if (currentTask) {
id: data.id, const updatedTask: Task = {
changes: { ...currentTask,
assignees: data.assignees?.map(a => a.team_member_id) || [], assignees: data.assignees?.map(a => a.team_member_id) || [],
assignee_names: data.names || [], assignee_names: data.names || [],
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}, updated_at: new Date().toISOString(),
}) };
); dispatch(updateTask(updatedTask));
}
} }
// Update the old task slice (for backward compatibility) // Update the old task slice (for backward compatibility)
@@ -147,22 +148,23 @@ export const useTaskSocketHandlers = () => {
// REAL-TIME UPDATES: Update the task-management slice for immediate UI updates // REAL-TIME UPDATES: Update the task-management slice for immediate UI updates
if (labels.id) { if (labels.id) {
dispatch( const currentTask = store.getState().taskManagement.entities[labels.id];
updateTask({ if (currentTask) {
id: labels.id, const updatedTask: Task = {
changes: { ...currentTask,
labels: labels:
labels.labels?.map(l => ({ labels.all_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,
})) || [], })) || [],
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}, updated_at: new Date().toISOString(),
}) };
); dispatch(updateTask(updatedTask));
}
} }
// Update the old task slice and other related slices (for backward compatibility) // Update the old task slice and other related slices (for backward compatibility)
@@ -281,15 +283,16 @@ export const useTaskSocketHandlers = () => {
// For the task management slice, update task progress // For the task management slice, update task progress
const taskId = data.parent_task || data.id; const taskId = data.parent_task || data.id;
if (taskId) { if (taskId) {
dispatch( const currentTask = store.getState().taskManagement.entities[taskId];
updateTask({ if (currentTask) {
id: taskId, const updatedTask: Task = {
changes: { ...currentTask,
progress: data.complete_ratio, progress: data.complete_ratio,
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}, updated_at: new Date().toISOString(),
}) };
); dispatch(updateTask(updatedTask));
}
} }
// Update enhanced kanban slice // Update enhanced kanban slice
@@ -326,14 +329,19 @@ export const useTaskSocketHandlers = () => {
if (currentTask) { if (currentTask) {
// Get priority list to map priority_id to priority name // Get priority list to map priority_id to priority name
const priorityList = state.priorityReducer?.priorities || []; const priorityList = state.priorityReducer?.priorities || [];
const priority = priorityList.find(p => p.id === response.priority_id);
let newPriorityValue: 'critical' | 'high' | 'medium' | 'low' = 'medium'; let newPriorityValue: 'critical' | 'high' | 'medium' | 'low' = 'medium';
if (priority?.name) {
const priorityName = priority.name.toLowerCase(); if (response.priority_id) {
if (['critical', 'high', 'medium', 'low'].includes(priorityName)) { const priority = priorityList.find(p => p.id === response.priority_id);
newPriorityValue = priorityName as 'critical' | 'high' | 'medium' | 'low'; if (priority?.name) {
const priorityName = priority.name.toLowerCase();
if (['critical', 'high', 'medium', 'low'].includes(priorityName)) {
newPriorityValue = priorityName as 'critical' | 'high' | 'medium' | 'low';
}
} }
} else {
// No priority selected (cleared) - default to medium or find unmapped
newPriorityValue = 'medium';
} }
// Update the task entity first // Update the task entity first
@@ -353,11 +361,33 @@ export const useTaskSocketHandlers = () => {
const currentGroup = groups.find(group => group.taskIds.includes(response.id)); const currentGroup = groups.find(group => group.taskIds.includes(response.id));
// Find target group based on new priority value // Find target group based on new priority value
const targetGroup = groups.find( let targetGroup: any = null;
group => group.groupValue?.toLowerCase() === newPriorityValue.toLowerCase()
); if (response.priority_id) {
// Find group by priority name (groupValue should match the priority name)
targetGroup = groups.find(
group => group.groupValue?.toLowerCase() === newPriorityValue.toLowerCase() ||
group.title?.toLowerCase() === newPriorityValue.toLowerCase()
);
} else {
// Find "Unmapped" group for tasks without a priority
targetGroup = groups.find(
group =>
group.groupValue === 'Unmapped' ||
group.title === 'Unmapped' ||
group.groupValue === '' ||
group.title?.toLowerCase().includes('unmapped') ||
group.groupValue?.toLowerCase().includes('unmapped')
);
}
if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) { if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) {
console.log('🔄 Moving task between priority groups:', {
taskId: response.id,
from: currentGroup.title,
to: targetGroup.title,
newPriorityValue
});
dispatch( dispatch(
moveTaskBetweenGroups({ moveTaskBetweenGroups({
taskId: response.id, taskId: response.id,
@@ -365,6 +395,8 @@ export const useTaskSocketHandlers = () => {
targetGroupId: targetGroup.id, targetGroupId: targetGroup.id,
}) })
); );
} else if (!targetGroup && response.priority_id) {
console.log('🔧 Target priority group not found for priority:', newPriorityValue);
} else { } else {
console.log('🔧 No group movement needed for priority change'); console.log('🔧 No group movement needed for priority change');
} }
@@ -413,19 +445,24 @@ export const useTaskSocketHandlers = () => {
// For the task management slice, update task name // For the task management slice, update task name
if (data.id) { if (data.id) {
dispatch( const currentTask = store.getState().taskManagement.entities[data.id];
updateTask({ if (currentTask) {
id: data.id, const updatedTask: Task = {
changes: { ...currentTask,
title: data.name, title: data.name,
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}, updated_at: new Date().toISOString(),
}) };
); dispatch(updateTask(updatedTask));
}
} }
// Update enhanced kanban slice // Update enhanced kanban slice (add manual_progress property for compatibility)
dispatch(updateEnhancedKanbanTaskName({ task: data })); const taskWithProgress = {
...data,
manual_progress: false,
} as IProjectTask;
dispatch(updateEnhancedKanbanTaskName({ task: taskWithProgress }));
}, },
[dispatch] [dispatch]
); );
@@ -460,15 +497,13 @@ export const useTaskSocketHandlers = () => {
} }
// Update the task entity // Update the task entity
dispatch( const updatedTask: Task = {
updateTask({ ...currentTask,
id: taskId, phase: newPhaseValue,
changes: { updatedAt: new Date().toISOString(),
phase: newPhaseValue, updated_at: new Date().toISOString(),
updatedAt: new Date().toISOString(), };
}, dispatch(updateTask(updatedTask));
})
);
// Handle group movement ONLY if grouping by phase // Handle group movement ONLY if grouping by phase
const groups = state.taskManagement.groups; const groups = state.taskManagement.groups;
@@ -482,31 +517,41 @@ export const useTaskSocketHandlers = () => {
let targetGroup: any = null; let targetGroup: any = null;
if (newPhaseValue && newPhaseValue.trim() !== '') { if (newPhaseValue && newPhaseValue.trim() !== '') {
// Find group by phase name // Find group by phase name (groupValue should match the phase name)
targetGroup = groups.find( targetGroup = groups.find(
group => group.groupValue === newPhaseValue || group.title === newPhaseValue group => group.groupValue === newPhaseValue ||
group.title === newPhaseValue ||
group.groupValue?.toLowerCase() === newPhaseValue.toLowerCase() ||
group.title?.toLowerCase() === newPhaseValue.toLowerCase()
); );
} else { } else {
// Find "Unmapped" group for tasks without a phase or with default phase // Find "Unmapped" group for tasks without a phase
targetGroup = groups.find( targetGroup = groups.find(
group => group =>
group.groupValue === 'Unmapped' || group.groupValue === 'Unmapped' ||
group.title === 'Unmapped' || group.title === 'Unmapped' ||
group.title.toLowerCase().includes('unmapped') group.groupValue === '' ||
group.title?.toLowerCase().includes('unmapped') ||
group.groupValue?.toLowerCase().includes('unmapped')
); );
} }
if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) { if (currentGroup && targetGroup && currentGroup.id !== targetGroup.id) {
console.log('🔄 Moving task between phase groups:', {
taskId,
from: currentGroup.title,
to: targetGroup.title,
newPhaseValue
});
dispatch( dispatch(
moveTaskBetweenGroups({ moveTaskBetweenGroups({
taskId: taskId, taskId: taskId,
fromGroupId: currentGroup.id, sourceGroupId: currentGroup.id,
toGroupId: targetGroup.id, targetGroupId: targetGroup.id,
taskUpdate: {
phase: newPhaseValue,
},
}) })
); );
} else if (!targetGroup && newPhaseValue) {
console.log('🔧 Target phase group not found for phase:', newPhaseValue);
} else { } else {
console.log('🔧 No group movement needed for phase change'); console.log('🔧 No group movement needed for phase change');
} }
@@ -534,11 +579,13 @@ export const useTaskSocketHandlers = () => {
// Update task-management slice for task-list-v2 components // Update task-management slice for task-list-v2 components
const currentTask = store.getState().taskManagement.entities[task.id]; const currentTask = store.getState().taskManagement.entities[task.id];
if (currentTask) { if (currentTask) {
dispatch(updateTask({ const updatedTask: Task = {
...currentTask, ...currentTask,
startDate: task.start_date, startDate: task.start_date,
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
})); updated_at: new Date().toISOString(),
};
dispatch(updateTask(updatedTask));
} }
}, },
[dispatch] [dispatch]

View File

@@ -91,15 +91,7 @@ const CustomColumnModal = () => {
// Use the column data passed from TaskListV2 // Use the column data passed from TaskListV2
const openedColumn = currentColumnData; const openedColumn = currentColumnData;
// Debug logging
console.log('Modal Debug Info:', {
customColumnId,
customColumnModalType,
currentColumnData,
openedColumn,
openedColumnFound: !!openedColumn,
openedColumnId: openedColumn?.id
});
// Function to reset all form and Redux state // Function to reset all form and Redux state
const resetModalData = () => { const resetModalData = () => {
@@ -110,14 +102,6 @@ const CustomColumnModal = () => {
// Function to handle deleting a custom column // Function to handle deleting a custom column
const handleDeleteColumn = async () => { const handleDeleteColumn = async () => {
console.log('Delete function called with:', {
customColumnId,
openedColumn,
openedColumnId: openedColumn?.id,
openedColumnKey: openedColumn?.key,
fullColumnData: openedColumn
});
// The customColumnId should now be the UUID passed from TaskListV2 // The customColumnId should now be the UUID passed from TaskListV2
// But also check the column data as a fallback, prioritizing uuid over id // But also check the column data as a fallback, prioritizing uuid over id
const columnUUID = customColumnId || const columnUUID = customColumnId ||
@@ -126,26 +110,12 @@ const CustomColumnModal = () => {
openedColumn?.custom_column_obj?.uuid || openedColumn?.custom_column_obj?.uuid ||
openedColumn?.custom_column_obj?.id; openedColumn?.custom_column_obj?.id;
console.log('Extracted UUID candidates:', {
'openedColumn?.id': openedColumn?.id,
'openedColumn?.uuid': openedColumn?.uuid,
'openedColumn?.custom_column_obj?.id': openedColumn?.custom_column_obj?.id,
'openedColumn?.custom_column_obj?.uuid': openedColumn?.custom_column_obj?.uuid,
'finalColumnUUID': columnUUID
});
if (!customColumnId || !columnUUID) { if (!customColumnId || !columnUUID) {
console.error('Missing required data for deletion:', {
customColumnId,
columnUUID,
openedColumn
});
message.error('Cannot delete column: Missing UUID'); message.error('Cannot delete column: Missing UUID');
return; return;
} }
try { try {
console.log('Attempting to delete column with UUID:', columnUUID);
// Make API request to delete the custom column using the service // Make API request to delete the custom column using the service
await tasksCustomColumnsService.deleteCustomColumn(columnUUID); await tasksCustomColumnsService.deleteCustomColumn(columnUUID);
@@ -442,16 +412,11 @@ const CustomColumnModal = () => {
} else if (openedColumn.custom_column_obj?.fieldType === 'selection') { } else if (openedColumn.custom_column_obj?.fieldType === 'selection') {
// Directly set the selections list in the Redux store // Directly set the selections list in the Redux store
if (Array.isArray(openedColumn.custom_column_obj?.selectionsList)) { if (Array.isArray(openedColumn.custom_column_obj?.selectionsList)) {
console.log(
'Setting selections list:',
openedColumn.custom_column_obj.selectionsList
);
dispatch(setSelectionsList(openedColumn.custom_column_obj.selectionsList)); dispatch(setSelectionsList(openedColumn.custom_column_obj.selectionsList));
} }
} else if (openedColumn.custom_column_obj?.fieldType === 'labels') { } else if (openedColumn.custom_column_obj?.fieldType === 'labels') {
// Directly set the labels list in the Redux store // Directly set the labels list in the Redux store
if (Array.isArray(openedColumn.custom_column_obj?.labelsList)) { if (Array.isArray(openedColumn.custom_column_obj?.labelsList)) {
console.log('Setting labels list:', openedColumn.custom_column_obj.labelsList);
dispatch(setLabelsList(openedColumn.custom_column_obj.labelsList)); dispatch(setLabelsList(openedColumn.custom_column_obj.labelsList));
} }
} }

View File

@@ -35,20 +35,12 @@ const SelectionTypeColumn = () => {
// Use the current column data passed from TaskListV2 // Use the current column data passed from TaskListV2
const openedColumn = currentColumnData; const openedColumn = currentColumnData;
console.log('SelectionTypeColumn render:', {
customColumnModalType,
customColumnId,
openedColumn,
storeSelectionsList,
'openedColumn?.custom_column_obj?.selectionsList':
openedColumn?.custom_column_obj?.selectionsList,
});
// Load existing selections when in edit mode // Load existing selections when in edit mode
useEffect(() => { useEffect(() => {
if (customColumnModalType === 'edit' && openedColumn?.custom_column_obj?.selectionsList) { if (customColumnModalType === 'edit' && openedColumn?.custom_column_obj?.selectionsList) {
const existingSelections = openedColumn.custom_column_obj.selectionsList; const existingSelections = openedColumn.custom_column_obj.selectionsList;
console.log('Loading existing selections:', existingSelections);
if (Array.isArray(existingSelections) && existingSelections.length > 0) { if (Array.isArray(existingSelections) && existingSelections.length > 0) {
setSelections(existingSelections); setSelections(existingSelections);