diff --git a/app/backend/core/models/task.py b/app/backend/core/models/task.py index ad49446a..4551308f 100644 --- a/app/backend/core/models/task.py +++ b/app/backend/core/models/task.py @@ -281,20 +281,20 @@ def check_expiry(self): return False def update_status_based_on_assignees(self): - """Update task status based on assignee count vs volunteer_number requirement""" + """Update task status based on assignee count""" current_assignee_count = self.assignees.count() - - # If we have fewer assignees than required, status should be POSTED - if current_assignee_count < self.volunteer_number: + + # If we have no assignees, status should be POSTED + if current_assignee_count < 1: if self.status == TaskStatus.ASSIGNED: self.status = TaskStatus.POSTED self.save() return True - # If we have enough assignees, status should be ASSIGNED - elif current_assignee_count >= self.volunteer_number: + # If we have at least one assignee, status should be ASSIGNED + elif current_assignee_count >= 1: if self.status == TaskStatus.POSTED: self.status = TaskStatus.ASSIGNED self.save() return True - + return False \ No newline at end of file diff --git a/app/frontend/src/features/request/services/requestService.js b/app/frontend/src/features/request/services/requestService.js index b20e0622..093e98b2 100644 --- a/app/frontend/src/features/request/services/requestService.js +++ b/app/frontend/src/features/request/services/requestService.js @@ -88,6 +88,8 @@ export const getTaskById = async (taskId) => { taskData = response.data; } + console.log('Task data status from API:', taskData?.status); + // Fetch photos for the task try { console.log(`Fetching photos for task ${taskId}`); @@ -310,6 +312,46 @@ export const assignVolunteers = async (taskId, volunteerIds) => { } }; +/** + * Mark a task as completed + * + * @param {string|number} taskId - ID of the task to mark as completed + * @returns {Promise} Promise that resolves to the updated task + */ +export const markTaskAsCompleted = async (taskId) => { + try { + console.log(`Marking task ${taskId} as completed`); + + // Use the dedicated status update endpoint + const response = await api.post(`/tasks/${taskId}/update-status/`, { + status: 'COMPLETED' + }); + console.log('Mark as completed API response:', response.data); + console.log('Response status:', response.status); + + // Handle different response formats + let taskData; + if (response.data.data) { + taskData = response.data.data; + } else { + taskData = response.data; + } + + // Ensure the status is explicitly set to COMPLETED in the returned data + if (taskData) { + taskData.status = 'COMPLETED'; + console.log('Final task data after marking complete:', taskData); + } + + return taskData; + } catch (error) { + console.error(`Error marking task ${taskId} as completed:`, error); + console.error('Error response:', error.response?.data); + console.error('Error status:', error.response?.status); + throw error; + } +}; + /** * Mock volunteers data for development */ @@ -386,5 +428,6 @@ export default { checkUserVolunteerStatus, withdrawFromTask, assignVolunteers, + markTaskAsCompleted, getMockTaskVolunteers, }; diff --git a/app/frontend/src/pages/RequestDetail.jsx b/app/frontend/src/pages/RequestDetail.jsx index 677a7f0e..0797e286 100644 --- a/app/frontend/src/pages/RequestDetail.jsx +++ b/app/frontend/src/pages/RequestDetail.jsx @@ -10,10 +10,14 @@ import MoreVertIcon from "@mui/icons-material/MoreVert"; import EditIcon from "@mui/icons-material/Edit"; import DeleteIcon from "@mui/icons-material/Delete"; import VolunteerActivismIcon from "@mui/icons-material/VolunteerActivism"; -import { updateTask } from "../features/request/services/createRequestService"; +import { + updateTask, + updateTaskStatus, +} from "../features/request/services/createRequestService"; import { cancelTask } from "../features/request/services/createRequestService"; // Add this import import { getTaskById as getRequestById, + getTaskVolunteers, volunteerForTask, checkUserVolunteerStatus, withdrawFromTask, @@ -50,6 +54,15 @@ const RequestDetail = () => { const [deleteSuccess, setDeleteSuccess] = useState(false); const [isVolunteering, setIsVolunteering] = useState(false); const [volunteerRecord, setVolunteerRecord] = useState(null); + const [isMarkingComplete, setIsMarkingComplete] = useState(false); + + // Helper function to check if deadline has passed + const isDeadlinePassed = (deadline) => { + if (!deadline) return false; + const now = new Date(); + const deadlineDate = new Date(deadline); + return now > deadlineDate; + }; // Fetch request details and volunteer status useEffect(() => { @@ -62,6 +75,17 @@ const RequestDetail = () => { // Fetch request data const requestData = await getRequestById(requestId); console.log("Received request data:", requestData); + + // Fetch volunteers for the task + try { + const volunteers = await getTaskVolunteers(requestId); + console.log("Received volunteers data:", volunteers); + requestData.volunteers = volunteers; + } catch (volunteersError) { + console.warn(`Could not fetch volunteers for task ${requestId}:`, volunteersError); + requestData.volunteers = []; + } + setRequest(requestData); // Check if current user has volunteered for this task @@ -257,6 +281,12 @@ const RequestDetail = () => { acceptedVolunteersCount < request.volunteer_number && !volunteerRecord; const canWithdraw = isAuthenticated && !isTaskCreator && volunteerRecord; + const canMarkAsComplete = + isAuthenticated && + isTaskCreator && + (request?.status === "ASSIGNED" || request?.status === "IN_PROGRESS") && + acceptedVolunteersCount > 0 && + request?.status !== "COMPLETED"; // Debug logging console.log("Permission debug:", { @@ -268,6 +298,9 @@ const RequestDetail = () => { volunteerRecord: volunteerRecord?.id, canVolunteer, canWithdraw, + canMarkAsComplete, + isDeadlinePassed: isDeadlinePassed(request.deadline), + deadline: request?.deadline, }); // Button handlers @@ -388,6 +421,53 @@ const RequestDetail = () => { } }; + const handleMarkAsComplete = async () => { + if (!canMarkAsComplete) return; + + try { + console.log("Marking task as completed:", request.id); + console.log("Current task status:", request.status); + setIsMarkingComplete(true); + + // According to backend validation, tasks can only be marked as COMPLETED from IN_PROGRESS status + // So if the task is in ASSIGNED status, we need to first transition to IN_PROGRESS + if (request.status === "ASSIGNED") { + console.log("Task is ASSIGNED, first transitioning to IN_PROGRESS..."); + await updateTaskStatus(request.id, "IN_PROGRESS"); + console.log("Successfully transitioned to IN_PROGRESS"); + } + + // Now transition to COMPLETED + console.log("Transitioning to COMPLETED..."); + const statusUpdateResult = await updateTaskStatus( + request.id, + "COMPLETED" + ); + console.log("Task status updated to COMPLETED:", statusUpdateResult); + + // Refresh the task data from the backend to get the latest state + console.log("Refreshing task data after marking complete..."); + const refreshedTask = await getRequestById(request.id); + console.log("Refreshed task data:", refreshedTask); + + // Update the request state with the refreshed data + setRequest(refreshedTask); + + alert("Task marked as completed successfully!"); + } catch (error) { + console.error("Error marking task as completed:", error); + console.error("Error details:", error.response?.data); + const errorMessage = + error.response?.data?.message || + error.response?.data?.error || + error.message || + "Failed to mark task as completed"; + alert(`Error: ${errorMessage}`); + } finally { + setIsMarkingComplete(false); + } + }; + return (