Enhance task status change handling and progress updates
- Updated SQL queries to retrieve color codes for task statuses from the correct table, ensuring accurate data representation. - Added logic to automatically set task progress to 100% when a task is marked as done, improving task completion handling. - Enhanced frontend components to manage task status changes and reflect updates in real-time, including handling parent task progress. - Integrated logging for task status changes and progress updates to improve traceability and debugging.
This commit is contained in:
@@ -27,7 +27,7 @@ export async function on_get_done_statuses(
|
|||||||
|
|
||||||
// Query to get all statuses in the "done" category for the project
|
// Query to get all statuses in the "done" category for the project
|
||||||
const result = await db.query(`
|
const result = await db.query(`
|
||||||
SELECT ts.id, ts.name, ts.sort_order, ts.color_code
|
SELECT ts.id, ts.name, ts.sort_order, stsc.color_code
|
||||||
FROM task_statuses ts
|
FROM task_statuses ts
|
||||||
INNER JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id
|
INNER JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id
|
||||||
WHERE ts.project_id = $1
|
WHERE ts.project_id = $1
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import db from "../../config/db";
|
|||||||
import {NotificationsService} from "../../services/notifications/notifications.service";
|
import {NotificationsService} from "../../services/notifications/notifications.service";
|
||||||
import {TASK_STATUS_COLOR_ALPHA} from "../../shared/constants";
|
import {TASK_STATUS_COLOR_ALPHA} from "../../shared/constants";
|
||||||
import {SocketEvents} from "../events";
|
import {SocketEvents} from "../events";
|
||||||
import {getLoggedInUserIdFromSocket, log_error, notifyProjectUpdates} from "../util";
|
import {getLoggedInUserIdFromSocket, log, log_error, notifyProjectUpdates} from "../util";
|
||||||
import TasksControllerV2 from "../../controllers/tasks-controller-v2";
|
import TasksControllerV2 from "../../controllers/tasks-controller-v2";
|
||||||
import {getTaskDetails, logStatusChange} from "../../services/activity-logs/activity-logs.service";
|
import {getTaskDetails, logProgressChange, logStatusChange} from "../../services/activity-logs/activity-logs.service";
|
||||||
import { assignMemberIfNot } from "./on-quick-assign-or-remove";
|
import { assignMemberIfNot } from "./on-quick-assign-or-remove";
|
||||||
|
import logger from "../../utils/logger";
|
||||||
|
|
||||||
export async function on_task_status_change(_io: Server, socket: Socket, data?: string) {
|
export async function on_task_status_change(_io: Server, socket: Socket, data?: string) {
|
||||||
try {
|
try {
|
||||||
@@ -49,6 +50,46 @@ export async function on_task_status_change(_io: Server, socket: Socket, data?:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the new status is in a "done" category
|
||||||
|
if (changeResponse.status_category?.is_done) {
|
||||||
|
// Get current progress value
|
||||||
|
const progressResult = await db.query(`
|
||||||
|
SELECT progress_value, manual_progress
|
||||||
|
FROM tasks
|
||||||
|
WHERE id = $1
|
||||||
|
`, [body.task_id]);
|
||||||
|
|
||||||
|
const currentProgress = progressResult.rows[0]?.progress_value;
|
||||||
|
const isManualProgress = progressResult.rows[0]?.manual_progress;
|
||||||
|
|
||||||
|
// Only update if not already 100%
|
||||||
|
if (currentProgress !== 100) {
|
||||||
|
// Update progress to 100%
|
||||||
|
await db.query(`
|
||||||
|
UPDATE tasks
|
||||||
|
SET progress_value = 100, manual_progress = TRUE
|
||||||
|
WHERE id = $1
|
||||||
|
`, [body.task_id]);
|
||||||
|
|
||||||
|
log(`Task ${body.task_id} moved to done status - progress automatically set to 100%`, null);
|
||||||
|
|
||||||
|
// Log the progress change to activity logs
|
||||||
|
await logProgressChange({
|
||||||
|
task_id: body.task_id,
|
||||||
|
old_value: currentProgress !== null ? currentProgress.toString() : "0",
|
||||||
|
new_value: "100",
|
||||||
|
socket
|
||||||
|
});
|
||||||
|
|
||||||
|
// If this is a subtask, update parent task progress
|
||||||
|
if (body.parent_task) {
|
||||||
|
setTimeout(() => {
|
||||||
|
socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), body.parent_task);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const info = await TasksControllerV2.getTaskCompleteRatio(body.parent_task || body.task_id);
|
const info = await TasksControllerV2.getTaskCompleteRatio(body.parent_task || body.task_id);
|
||||||
|
|
||||||
socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), {
|
socket.emit(SocketEvents.TASK_STATUS_CHANGE.toString(), {
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ import Flex from 'antd/lib/flex';
|
|||||||
import { SocketEvents } from '@/shared/socket-events';
|
import { SocketEvents } from '@/shared/socket-events';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useSocket } from '@/socket/socketContext';
|
import { useSocket } from '@/socket/socketContext';
|
||||||
|
import { useAuthService } from '@/hooks/useAuth';
|
||||||
|
import logger from '@/utils/errorLogger';
|
||||||
|
import { ITaskListStatusChangeResponse } from '@/types/tasks/task-list-status.types';
|
||||||
|
import { setTaskStatus } from '@/features/task-drawer/task-drawer.slice';
|
||||||
|
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||||
|
import { updateBoardTaskStatus } from '@/features/board/board-slice';
|
||||||
|
import { updateTaskStatus } from '@/features/tasks/tasks.slice';
|
||||||
|
import useTabSearchParam from '@/hooks/useTabSearchParam';
|
||||||
|
|
||||||
interface TaskDrawerProgressProps {
|
interface TaskDrawerProgressProps {
|
||||||
task: ITaskViewModel;
|
task: ITaskViewModel;
|
||||||
@@ -15,17 +23,18 @@ interface TaskDrawerProgressProps {
|
|||||||
|
|
||||||
const TaskDrawerProgress = ({ task, form }: TaskDrawerProgressProps) => {
|
const TaskDrawerProgress = ({ task, form }: TaskDrawerProgressProps) => {
|
||||||
const { t } = useTranslation('task-drawer/task-drawer');
|
const { t } = useTranslation('task-drawer/task-drawer');
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { tab } = useTabSearchParam();
|
||||||
|
|
||||||
const { project } = useAppSelector(state => state.projectReducer);
|
const { project } = useAppSelector(state => state.projectReducer);
|
||||||
const { socket, connected } = useSocket();
|
const { socket, connected } = useSocket();
|
||||||
const [isCompletionModalVisible, setIsCompletionModalVisible] = useState(false);
|
const [isCompletionModalVisible, setIsCompletionModalVisible] = useState(false);
|
||||||
|
const currentSession = useAuthService().getCurrentSession();
|
||||||
|
|
||||||
const isSubTask = !!task?.parent_task_id;
|
const isSubTask = !!task?.parent_task_id;
|
||||||
// Safe handling of sub_tasks_count which might be undefined in some cases
|
// Safe handling of sub_tasks_count which might be undefined in some cases
|
||||||
const hasSubTasks = (task?.sub_tasks_count || 0) > 0;
|
const hasSubTasks = (task?.sub_tasks_count || 0) > 0;
|
||||||
|
|
||||||
// Log task details for debugging
|
|
||||||
console.log(`TaskDrawerProgress: task=${task?.id}, sub_tasks_count=${task?.sub_tasks_count}, hasSubTasks=${hasSubTasks}`);
|
|
||||||
|
|
||||||
// HIGHEST PRIORITY CHECK: Never show progress inputs for parent tasks with subtasks
|
// HIGHEST PRIORITY CHECK: Never show progress inputs for parent tasks with subtasks
|
||||||
if (hasSubTasks) {
|
if (hasSubTasks) {
|
||||||
return null;
|
return null;
|
||||||
@@ -48,7 +57,7 @@ const TaskDrawerProgress = ({ task, form }: TaskDrawerProgressProps) => {
|
|||||||
if (data.weight !== undefined) {
|
if (data.weight !== undefined) {
|
||||||
form.setFieldsValue({ weight: data.weight });
|
form.setFieldsValue({ weight: data.weight });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we should prompt the user to mark the task as done
|
// Check if we should prompt the user to mark the task as done
|
||||||
if (data.should_prompt_for_done) {
|
if (data.should_prompt_for_done) {
|
||||||
setIsCompletionModalVisible(true);
|
setIsCompletionModalVisible(true);
|
||||||
@@ -79,7 +88,7 @@ const TaskDrawerProgress = ({ task, form }: TaskDrawerProgressProps) => {
|
|||||||
if (value === 100) {
|
if (value === 100) {
|
||||||
setIsCompletionModalVisible(true);
|
setIsCompletionModalVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure parent_task_id is not undefined
|
// Ensure parent_task_id is not undefined
|
||||||
const parent_task_id = task.parent_task_id || null;
|
const parent_task_id = task.parent_task_id || null;
|
||||||
|
|
||||||
@@ -114,7 +123,7 @@ const TaskDrawerProgress = ({ task, form }: TaskDrawerProgressProps) => {
|
|||||||
parent_task_id: parent_task_id,
|
parent_task_id: parent_task_id,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// If this is a subtask, request the parent's progress to be updated in UI
|
// If this is a subtask, request the parent's progress to be updated in UI
|
||||||
if (parent_task_id) {
|
if (parent_task_id) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -127,30 +136,49 @@ const TaskDrawerProgress = ({ task, form }: TaskDrawerProgressProps) => {
|
|||||||
const handleMarkTaskAsComplete = () => {
|
const handleMarkTaskAsComplete = () => {
|
||||||
// Close the modal
|
// Close the modal
|
||||||
setIsCompletionModalVisible(false);
|
setIsCompletionModalVisible(false);
|
||||||
|
|
||||||
// Find a "Done" status for this project
|
// Find a "Done" status for this project
|
||||||
if (connected && task.id) {
|
if (connected && task.id) {
|
||||||
// Emit socket event to get "done" category statuses
|
// Emit socket event to get "done" category statuses
|
||||||
socket?.emit(SocketEvents.GET_DONE_STATUSES.toString(), task.project_id, (doneStatuses: any[]) => {
|
socket?.emit(
|
||||||
if (doneStatuses && doneStatuses.length > 0) {
|
SocketEvents.GET_DONE_STATUSES.toString(),
|
||||||
// Use the first "done" status
|
task.project_id,
|
||||||
const doneStatusId = doneStatuses[0].id;
|
(doneStatuses: any[]) => {
|
||||||
|
if (doneStatuses && doneStatuses.length > 0) {
|
||||||
// Emit socket event to update the task status
|
// Use the first "done" status
|
||||||
socket?.emit(
|
const doneStatusId = doneStatuses[0].id;
|
||||||
SocketEvents.TASK_STATUS_CHANGE.toString(),
|
|
||||||
JSON.stringify({
|
// Emit socket event to update the task status
|
||||||
task_id: task.id,
|
socket?.emit(
|
||||||
status_id: doneStatusId,
|
SocketEvents.TASK_STATUS_CHANGE.toString(),
|
||||||
project_id: task.project_id
|
JSON.stringify({
|
||||||
})
|
task_id: task.id,
|
||||||
);
|
status_id: doneStatusId,
|
||||||
|
project_id: task.project_id,
|
||||||
console.log(`Task ${task.id} marked as done with status ${doneStatusId}`);
|
team_id: currentSession?.team_id,
|
||||||
} else {
|
parent_task: task.parent_task_id || null,
|
||||||
console.error(`No "done" statuses found for project ${task.project_id}`);
|
})
|
||||||
|
);
|
||||||
|
socket?.once(
|
||||||
|
SocketEvents.TASK_STATUS_CHANGE.toString(),
|
||||||
|
(data: ITaskListStatusChangeResponse) => {
|
||||||
|
dispatch(setTaskStatus(data));
|
||||||
|
|
||||||
|
if (tab === 'tasks-list') {
|
||||||
|
dispatch(updateTaskStatus(data));
|
||||||
|
}
|
||||||
|
if (tab === 'board') {
|
||||||
|
dispatch(updateBoardTaskStatus(data));
|
||||||
|
}
|
||||||
|
if (data.parent_task)
|
||||||
|
socket?.emit(SocketEvents.GET_TASK_PROGRESS.toString(), data.parent_task);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.error(`No "done" statuses found for project ${task.project_id}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -235,7 +263,7 @@ const TaskDrawerProgress = ({ task, form }: TaskDrawerProgressProps) => {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="Mark Task as Done?"
|
title="Mark Task as Done?"
|
||||||
open={isCompletionModalVisible}
|
open={isCompletionModalVisible}
|
||||||
|
|||||||
Reference in New Issue
Block a user