Converting Task Steps to Child Tasks - Implementation Guide
✅ Completed
1. Admin Task Creation Component
- ✅ Updated
create-partner-task-with-tabs.tsxto create child tasks instead of metadata steps - ✅ Changed payload to include
child_tasksarray - ✅ Added
dependency_type: "subtask"to establish parent-child relationship - ✅ Updated UI note to reflect that steps are now actual tasks
🔄 Next Steps Required
2. Partner API Endpoints
Create GET endpoint for subtasks
File: /src/api/partners/assigned-tasks/[taskId]/subtasks/route.ts
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework";
import { getPartnerFromActorId } from "../../../helpers";
import { TASKS_MODULE } from "../../../../../modules/tasks";
import TaskService from "../../../../../modules/tasks/service";
export async function GET(
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) {
const { taskId } = req.params;
const actorId = req.auth_context?.actor_id;
if (!actorId) {
return res.status(401).json({ error: "Authentication required" });
}
const partner = await getPartnerFromActorId(actorId, req.scope);
if (!partner) {
return res.status(401).json({ error: "Partner not found" });
}
const taskService: TaskService = req.scope.resolve(TASKS_MODULE);
// Verify parent task is assigned to this partner
const parentTask = await taskService.retrieveTask(taskId, {
relations: ["subtasks"],
});
// Check if task is assigned to partner (via partner-task link)
// ... add verification logic
// Return subtasks sorted by order
const subtasks = parentTask.subtasks || [];
const sortedSubtasks = subtasks.sort((a, b) => {
const orderA = a.metadata?.order || 0;
const orderB = b.metadata?.order || 0;
return orderA - orderB;
});
res.status(200).json({
subtasks: sortedSubtasks,
count: sortedSubtasks.length,
});
}
Create POST endpoint for completing subtasks
File: /src/api/partners/assigned-tasks/[taskId]/subtasks/[subtaskId]/complete/route.ts
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework";
import { getPartnerFromActorId } from "../../../../helpers";
import { TASKS_MODULE } from "../../../../../../modules/tasks";
import TaskService from "../../../../../../modules/tasks/service";
export async function POST(
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) {
const { taskId, subtaskId } = req.params;
const actorId = req.auth_context?.actor_id;
if (!actorId) {
return res.status(401).json({ error: "Authentication required" });
}
const partner = await getPartnerFromActorId(actorId, req.scope);
if (!partner) {
return res.status(401).json({ error: "Partner not found" });
}
const taskService: TaskService = req.scope.resolve(TASKS_MODULE);
// Verify subtask belongs to parent task
const subtask = await taskService.retrieveTask(subtaskId, {
relations: ["parent_task"],
});
if (subtask.parent_task?.id !== taskId) {
return res.status(400).json({ error: "Subtask does not belong to this task" });
}
// Update subtask status
const updatedSubtask = await taskService.updateTasks({
id: subtaskId,
status: "completed",
completed_at: new Date(),
});
// Check if all subtasks are completed, then complete parent
const parentTask = await taskService.retrieveTask(taskId, {
relations: ["subtasks"],
});
const allSubtasksCompleted = parentTask.subtasks.every(
(st) => st.status === "completed"
);
if (allSubtasksCompleted) {
await taskService.updateTasks({
id: taskId,
status: "completed",
completed_at: new Date(),
});
}
res.status(200).json({
subtask: updatedSubtask,
parent_completed: allSubtasksCompleted,
});
}
3. Update Middlewares
File: /src/api/middlewares.ts
Add routes for subtasks:
{
matcher: "/partners/assigned-tasks/:taskId/subtasks",
method: "GET",
middlewares: [
authenticate("partner", ["session", "bearer"]),
],
},
{
matcher: "/partners/assigned-tasks/:taskId/subtasks/:subtaskId/complete",
method: "POST",
middlewares: [
authenticate("partner", ["session", "bearer"]),
],
},
4. Partner Dashboard Actions
File: /src/partner/app/dashboard/actions.ts
Add server actions:
export async function getTaskSubtasks(taskId: string) {
const token = await getAuthCookie();
if (!token) redirect("/login");
const MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000";
try {
const response = await fetch(
`${MEDUSA_BACKEND_URL}/partners/assigned-tasks/${taskId}/subtasks`,
{
headers: {
Authorization: `Bearer ${token}`,
},
cache: "no-store",
}
);
await enforceAuthOrRedirect(response);
if (!response.ok) {
console.error("Failed to fetch subtasks");
return { subtasks: [], count: 0 };
}
return await response.json();
} catch (e) {
throw e;
}
}
export async function completeSubtask(taskId: string, subtaskId: string) {
const token = await getAuthCookie();
if (!token) redirect("/login");
const MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL || "http://localhost:9000";
const response = await fetch(
`${MEDUSA_BACKEND_URL}/partners/assigned-tasks/${taskId}/subtasks/${subtaskId}/complete`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
cache: "no-store",
}
);
await enforceAuthOrRedirect(response);
if (!response.ok) {
throw new Error("Failed to complete subtask");
}
return await response.json();
}
5. Update Partner Task Detail Page
File: /src/partner/app/dashboard/tasks/[taskId]/task-detail-content.tsx
Update to fetch and display subtasks:
// Add to component state
const [subtasks, setSubtasks] = useState<Subtask[]>([]);
const [isLoadingSubtasks, setIsLoadingSubtasks] = useState(true);
// Fetch subtasks on mount
useEffect(() => {
async function fetchSubtasks() {
try {
const result = await getTaskSubtasks(task.id);
setSubtasks(result.subtasks || []);
} catch (error) {
console.error("Failed to fetch subtasks:", error);
} finally {
setIsLoadingSubtasks(false);
}
}
fetchSubtasks();
}, [task.id]);
// Add handler for completing subtask
const handleCompleteSubtask = async (subtaskId: string) => {
try {
await completeSubtask(task.id, subtaskId);
router.refresh();
// Refetch subtasks
const result = await getTaskSubtasks(task.id);
setSubtasks(result.subtasks || []);
} catch (error) {
console.error("Failed to complete subtask:", error);
}
};
// Update the steps section to show actual subtasks instead of metadata
📊 Benefits of This Approach
- Individual Tracking: Each step is a real task with its own status
- Better Analytics: Can track completion time for each step
- Flexible Updates: Partners can update steps independently
- Automatic Parent Completion: Parent task completes when all subtasks are done
- Scalable: Can add more features to subtasks (comments, attachments, etc.)
🔍 Testing Checklist
- Create a task with multiple steps from admin
- Verify child tasks are created in database
- Partner can view all subtasks in task detail page
- Partner can complete individual subtasks
- Parent task auto-completes when all subtasks are done
- Workflow preview shows correct step order
- Comments work on both parent and child tasks