Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions app/frontend/src/features/request/services/requestService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -386,5 +428,6 @@ export default {
checkUserVolunteerStatus,
withdrawFromTask,
assignVolunteers,
markTaskAsCompleted,
getMockTaskVolunteers,
};
152 changes: 140 additions & 12 deletions app/frontend/src/pages/RequestDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(() => {
Expand All @@ -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
Expand Down Expand Up @@ -257,6 +281,12 @@ const RequestDetail = () => {
acceptedVolunteersCount < request.volunteer_number &&
!volunteerRecord;
const canWithdraw = isAuthenticated && !isTaskCreator && volunteerRecord;
const canMarkAsComplete =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic appears to have a flaw. The canMarkAsComplete variable will evaluate to false when acceptedVolunteersCount > 0 but less than the required number of volunteers. This is because the request.status remains "POSTED" until the required number of volunteers is met.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are definitely right. However, I think the problem is not related to this implementation but to the backend. In app/backend/core/models/task.py, we should update the logic of making a request "ASSIGNED" from the "POSTED" state as follows:

# Current logic:
  if current_assignee_count < self.volunteer_number:
      if self.status == TaskStatus.ASSIGNED:
          self.status = TaskStatus.POSTED
          self.save()
          return True
  elif current_assignee_count >= self.volunteer_number:
      if self.status == TaskStatus.POSTED:
          self.status = TaskStatus.ASSIGNED
          self.save()
          return True

  # New logic:
  if current_assignee_count < 1:
      if self.status == TaskStatus.ASSIGNED:
          self.status = TaskStatus.POSTED
          self.save()
          return True
  elif current_assignee_count >= 1:
      if self.status == TaskStatus.POSTED:
          self.status = TaskStatus.ASSIGNED
          self.save()
          return True

Let me know what you think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to here from you friends.

@aahmeterenasl @erayyuklu @akcayyusufkaan @erdkocak

isAuthenticated &&
isTaskCreator &&
(request?.status === "ASSIGNED" || request?.status === "IN_PROGRESS") &&
acceptedVolunteersCount > 0 &&
request?.status !== "COMPLETED";

// Debug logging
console.log("Permission debug:", {
Expand All @@ -268,6 +298,9 @@ const RequestDetail = () => {
volunteerRecord: volunteerRecord?.id,
canVolunteer,
canWithdraw,
canMarkAsComplete,
isDeadlinePassed: isDeadlinePassed(request.deadline),
deadline: request?.deadline,
});

// Button handlers
Expand Down Expand Up @@ -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 (
<div className="flex min-h-screen bg-gray-50">
{/* Sidebar */}
Expand Down Expand Up @@ -542,35 +622,83 @@ const RequestDetail = () => {
? "Waiting for Volunteers"
: request.status === "ASSIGNED"
? isTaskCreator
? null
? acceptedVolunteersCount > 0
? "Ready to mark as complete"
: null
: volunteerRecord &&
volunteerRecord.status === "ACCEPTED"
? "Task Assigned to You"
: acceptedVolunteersCount < request.volunteer_number
? "Waiting for More Volunteers"
: "Task Assigned"
: request.status === "IN_PROGRESS"
? "In Progress"
? isTaskCreator && acceptedVolunteersCount > 0
? "Ready to mark as complete"
: "In Progress"
: request.status === "COMPLETED"
? "Completed"
? "Task Completed"
: "Unknown Status"}
</p>
</div>
)}

{/* Action Buttons */}
<div className="space-y-3">
{/* Primary Action Button for Task Creator */}
{/* Primary Action Buttons for Task Creator - Mark as Complete and Select Volunteer */}
{canEdit &&
(request.status === "POSTED" ||
request.status === "ASSIGNED") && (
<div className={canMarkAsComplete ? "grid grid-cols-2 gap-3" : ""}>
{/* Select Volunteer Button */}
<button
onClick={handleSelectVolunteer}
className={`py-3 px-6 bg-purple-600 text-white text-base font-medium rounded-lg hover:bg-purple-700 transition-colors ${
canMarkAsComplete ? "" : "w-full"
}`}
>
{request.status === "ASSIGNED"
? "Change Volunteers"
: "Select Volunteer"}
</button>

{/* Mark as Complete Button */}
{canMarkAsComplete && (
<button
onClick={handleMarkAsComplete}
disabled={isMarkingComplete}
className="py-3 px-6 bg-green-600 text-white text-base font-medium rounded-lg hover:bg-green-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors flex items-center justify-center"
>
{isMarkingComplete ? (
<>
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2"></div>
Marking...
</>
) : (
"Mark as Complete"
)}
</button>
)}
</div>
)}

{/* Mark as Complete Button (when not in Edit mode) */}
{canMarkAsComplete &&
(!canEdit ||
(request.status !== "POSTED" &&
request.status !== "ASSIGNED")) && (
<button
onClick={handleSelectVolunteer}
className="w-full py-3 px-6 bg-purple-600 text-white text-base font-medium rounded-lg hover:bg-purple-700 transition-colors"
onClick={handleMarkAsComplete}
disabled={isMarkingComplete}
className="w-full py-3 px-6 bg-green-600 text-white text-base font-medium rounded-lg hover:bg-green-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors flex items-center justify-center"
>
{request.status === "ASSIGNED"
? "Change Volunteers"
: "Select Volunteer"}
{isMarkingComplete ? (
<>
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2"></div>
Marking Complete...
</>
) : (
"Mark as Complete"
)}
</button>
)}

Expand Down Expand Up @@ -607,8 +735,8 @@ const RequestDetail = () => {
</button>
)}

{/* Secondary Action Buttons - Only for Task Creator */}
{canEdit && (
{/* Secondary Action Buttons - Only for Task Creator and not completed */}
{canEdit && request.status !== "COMPLETED" && (
<div className="grid grid-cols-2 gap-3">
<button
onClick={handleEditTask}
Expand Down