Implement task completion prompt and enhance progress handling
- Added logic to prompt users to mark tasks as done when progress reaches 100%, integrating with the socket events for real-time updates. - Updated backend functions to check task statuses and determine if a prompt is necessary based on the task's current state. - Enhanced frontend components to display a modal for confirming task completion, improving user experience and clarity in task management. - Refactored socket event handling to include new events for retrieving "done" statuses, ensuring accurate task status updates across the application.
This commit is contained in:
@@ -221,6 +221,8 @@ CREATE OR REPLACE FUNCTION update_parent_task_progress() RETURNS TRIGGER AS
|
||||
$$
|
||||
DECLARE
|
||||
_parent_task_id UUID;
|
||||
_project_id UUID;
|
||||
_ratio FLOAT;
|
||||
BEGIN
|
||||
-- Check if this is a subtask
|
||||
IF NEW.parent_task_id IS NOT NULL THEN
|
||||
@@ -232,6 +234,21 @@ BEGIN
|
||||
WHERE id = _parent_task_id;
|
||||
END IF;
|
||||
|
||||
-- If this task has progress value of 100 and doesn't have subtasks, we might want to prompt the user
|
||||
-- to mark it as done. We'll annotate this in a way that the socket handler can detect.
|
||||
IF NEW.progress_value = 100 OR NEW.weight = 100 OR NEW.total_minutes > 0 THEN
|
||||
-- Check if task has status in "done" category
|
||||
SELECT project_id FROM tasks WHERE id = NEW.id INTO _project_id;
|
||||
|
||||
-- Get the progress ratio for this task
|
||||
SELECT get_task_complete_ratio(NEW.id)->>'ratio' INTO _ratio;
|
||||
|
||||
IF _ratio::FLOAT >= 100 THEN
|
||||
-- Log that this task is at 100% progress
|
||||
RAISE NOTICE 'Task % progress is at 100%%, may need status update', NEW.id;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Socket } from "socket.io";
|
||||
import db from "../../config/db";
|
||||
import { log_error } from "../util";
|
||||
|
||||
// Define a type for the callback function
|
||||
type DoneStatusesCallback = (statuses: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
sort_order: number;
|
||||
color_code: string;
|
||||
}>) => void;
|
||||
|
||||
/**
|
||||
* Socket handler to get task statuses in the "done" category for a project
|
||||
* Used when prompting users to mark a task as done when progress reaches 100%
|
||||
*/
|
||||
export async function on_get_done_statuses(
|
||||
io: any,
|
||||
socket: Socket,
|
||||
projectId: string,
|
||||
callback: DoneStatusesCallback
|
||||
) {
|
||||
try {
|
||||
if (!projectId) {
|
||||
return callback([]);
|
||||
}
|
||||
|
||||
// Query to get all statuses in the "done" category for the project
|
||||
const result = await db.query(`
|
||||
SELECT ts.id, ts.name, ts.sort_order, ts.color_code
|
||||
FROM task_statuses ts
|
||||
INNER JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id
|
||||
WHERE ts.project_id = $1
|
||||
AND stsc.is_done = TRUE
|
||||
ORDER BY ts.sort_order ASC
|
||||
`, [projectId]);
|
||||
|
||||
const doneStatuses = result.rows;
|
||||
|
||||
console.log(`Found ${doneStatuses.length} "done" statuses for project ${projectId}`);
|
||||
|
||||
// Use callback to return the result
|
||||
callback(doneStatuses);
|
||||
|
||||
} catch (error) {
|
||||
log_error(`Error getting "done" statuses for project ${projectId}: ${error}`);
|
||||
callback([]);
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,32 @@ async function updateTaskAncestors(io: any, socket: Socket, projectId: string, t
|
||||
const ratio = progressRatio?.rows[0]?.ratio?.ratio || 0;
|
||||
console.log(`Updated task ${taskId} progress after time estimation change: ${ratio}`);
|
||||
|
||||
// Check if this task needs a "done" status prompt
|
||||
let shouldPromptForDone = false;
|
||||
|
||||
if (ratio >= 100) {
|
||||
// Get the task's current status
|
||||
const taskStatusResult = await db.query(`
|
||||
SELECT ts.id, stsc.is_done
|
||||
FROM tasks t
|
||||
JOIN task_statuses ts ON t.status_id = ts.id
|
||||
JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id
|
||||
WHERE t.id = $1
|
||||
`, [taskId]);
|
||||
|
||||
// If the task isn't already in a "done" category, we should prompt the user
|
||||
if (taskStatusResult.rows.length > 0 && !taskStatusResult.rows[0].is_done) {
|
||||
shouldPromptForDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Emit the updated progress
|
||||
socket.emit(
|
||||
SocketEvents.TASK_PROGRESS_UPDATED.toString(),
|
||||
{
|
||||
task_id: taskId,
|
||||
progress_value: ratio
|
||||
progress_value: ratio,
|
||||
should_prompt_for_done: shouldPromptForDone
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -30,12 +30,32 @@ async function updateTaskAncestors(io: any, socket: Socket, projectId: string, t
|
||||
const ratio = progressRatio?.rows[0]?.ratio?.ratio || 0;
|
||||
console.log(`Updated task ${taskId} progress: ${ratio}`);
|
||||
|
||||
// Check if this task needs a "done" status prompt
|
||||
let shouldPromptForDone = false;
|
||||
|
||||
if (ratio >= 100) {
|
||||
// Get the task's current status
|
||||
const taskStatusResult = await db.query(`
|
||||
SELECT ts.id, stsc.is_done
|
||||
FROM tasks t
|
||||
JOIN task_statuses ts ON t.status_id = ts.id
|
||||
JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id
|
||||
WHERE t.id = $1
|
||||
`, [taskId]);
|
||||
|
||||
// If the task isn't already in a "done" category, we should prompt the user
|
||||
if (taskStatusResult.rows.length > 0 && !taskStatusResult.rows[0].is_done) {
|
||||
shouldPromptForDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Emit the updated progress
|
||||
socket.emit(
|
||||
SocketEvents.TASK_PROGRESS_UPDATED.toString(),
|
||||
{
|
||||
task_id: taskId,
|
||||
progress_value: ratio
|
||||
progress_value: ratio,
|
||||
should_prompt_for_done: shouldPromptForDone
|
||||
}
|
||||
);
|
||||
|
||||
@@ -81,12 +101,13 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
||||
|
||||
// Get the current progress value to log the change
|
||||
const currentProgressResult = await db.query(
|
||||
"SELECT progress_value, project_id FROM tasks WHERE id = $1",
|
||||
"SELECT progress_value, project_id, status_id FROM tasks WHERE id = $1",
|
||||
[task_id]
|
||||
);
|
||||
|
||||
const currentProgress = currentProgressResult.rows[0]?.progress_value;
|
||||
const projectId = currentProgressResult.rows[0]?.project_id;
|
||||
const statusId = currentProgressResult.rows[0]?.status_id;
|
||||
|
||||
// Update the task progress in the database
|
||||
await db.query(
|
||||
@@ -103,13 +124,33 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
||||
new_value: progress_value.toString(),
|
||||
socket
|
||||
});
|
||||
|
||||
if (projectId) {
|
||||
// Check if progress is 100% and the task isn't already in a "done" status category
|
||||
let shouldPromptForDone = false;
|
||||
|
||||
if (progress_value >= 100) {
|
||||
// Check if the task's current status is in a "done" category
|
||||
const statusCategoryResult = await db.query(`
|
||||
SELECT stsc.is_done
|
||||
FROM task_statuses ts
|
||||
JOIN sys_task_status_categories stsc ON ts.category_id = stsc.id
|
||||
WHERE ts.id = $1
|
||||
`, [statusId]);
|
||||
|
||||
// If the task isn't already in a "done" category, we should prompt the user
|
||||
if (statusCategoryResult.rows.length > 0 && !statusCategoryResult.rows[0].is_done) {
|
||||
shouldPromptForDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Emit the update to all clients in the project room
|
||||
socket.emit(
|
||||
SocketEvents.TASK_PROGRESS_UPDATED.toString(),
|
||||
{
|
||||
task_id,
|
||||
progress_value
|
||||
progress_value,
|
||||
should_prompt_for_done: shouldPromptForDone
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -67,4 +67,7 @@ export enum SocketEvents {
|
||||
// Task subtasks count events
|
||||
GET_TASK_SUBTASKS_COUNT,
|
||||
TASK_SUBTASKS_COUNT,
|
||||
|
||||
// Task completion events
|
||||
GET_DONE_STATUSES,
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ import { on_custom_column_pinned_change } from "./commands/on_custom_column_pinn
|
||||
import { on_update_task_progress } from "./commands/on-update-task-progress";
|
||||
import { on_update_task_weight } from "./commands/on-update-task-weight";
|
||||
import { on_get_task_subtasks_count } from "./commands/on-get-task-subtasks-count";
|
||||
import { on_get_done_statuses } from "./commands/on-get-done-statuses";
|
||||
|
||||
export function register(io: any, socket: Socket) {
|
||||
log(socket.id, "client registered");
|
||||
@@ -72,7 +73,6 @@ export function register(io: any, socket: Socket) {
|
||||
socket.on(SocketEvents.TASK_TIME_ESTIMATION_CHANGE.toString(), data => on_time_estimation_change(io, socket, data));
|
||||
socket.on(SocketEvents.TASK_DESCRIPTION_CHANGE.toString(), data => on_task_description_change(io, socket, data));
|
||||
socket.on(SocketEvents.GET_TASK_PROGRESS.toString(), data => on_get_task_progress(io, socket, data));
|
||||
socket.on(SocketEvents.GET_TASK_PROGRESS.toString(), data => on_get_task_progress(io, socket, data));
|
||||
socket.on(SocketEvents.TASK_TIMER_START.toString(), data => on_task_timer_start(io, socket, data));
|
||||
socket.on(SocketEvents.TASK_TIMER_STOP.toString(), data => on_task_timer_stop(io, socket, data));
|
||||
socket.on(SocketEvents.TASK_SORT_ORDER_CHANGE.toString(), data => on_task_sort_order_change(io, socket, data));
|
||||
@@ -112,6 +112,7 @@ export function register(io: any, socket: Socket) {
|
||||
socket.on(SocketEvents.UPDATE_TASK_PROGRESS.toString(), data => on_update_task_progress(io, socket, data));
|
||||
socket.on(SocketEvents.UPDATE_TASK_WEIGHT.toString(), data => on_update_task_weight(io, socket, data));
|
||||
socket.on(SocketEvents.GET_TASK_SUBTASKS_COUNT.toString(), (taskId) => on_get_task_subtasks_count(io, socket, taskId));
|
||||
socket.on(SocketEvents.GET_DONE_STATUSES.toString(), (projectId, callback) => on_get_done_statuses(io, socket, projectId, callback));
|
||||
|
||||
// socket.io built-in event
|
||||
socket.on("disconnect", (reason) => on_disconnect(io, socket, reason));
|
||||
|
||||
Reference in New Issue
Block a user