+ {" "}
{task.task_name}
@@ -271,9 +257,11 @@ export default function ProjectTasksDashboard() {
{task.priority}
{showStatusBadge && (
-
- {task.status.replace("_", " ")}
-
+
)}
@@ -317,18 +305,13 @@ export default function ProjectTasksDashboard() {
Due in {task.statusInfo.days} days
- )}
+ )}{" "}
{(task.status === "pending" || task.status === "in_progress") && (
-
+
)}
diff --git a/src/components/ProjectTasksSection.js b/src/components/ProjectTasksSection.js
index 788097d..8b01026 100644
--- a/src/components/ProjectTasksSection.js
+++ b/src/components/ProjectTasksSection.js
@@ -2,6 +2,7 @@
import React, { useState, useEffect } from "react";
import ProjectTaskForm from "./ProjectTaskForm";
+import TaskStatusDropdown from "./TaskStatusDropdown";
import { Card, CardHeader, CardContent } from "./ui/Card";
import Button from "./ui/Button";
import Badge from "./ui/Badge";
@@ -142,7 +143,6 @@ export default function ProjectTasksSection({ projectId }) {
refetchTasks(); // Refresh the list
setShowAddTaskModal(false); // Close the modal
};
-
const handleStatusChange = async (taskId, newStatus) => {
try {
const res = await fetch(`/api/project-tasks/${taskId}`, {
@@ -243,20 +243,6 @@ export default function ProjectTasksSection({ projectId }) {
return "default";
}
};
- const getStatusVariant = (status) => {
- switch (status) {
- case "completed":
- return "success";
- case "in_progress":
- return "primary";
- case "pending":
- return "warning";
- case "cancelled":
- return "danger";
- default:
- return "default";
- }
- };
const toggleDescription = (taskId) => {
setExpandedDescriptions((prev) => ({
@@ -460,28 +446,13 @@ export default function ProjectTasksSection({ projectId }) {
{task.date_started
? new Date(task.date_started).toLocaleDateString()
: "Not started"}
-
+ {" "}
-
-
-
- {task.status.replace("_", " ")}
-
-
+
|
diff --git a/src/components/TaskStatusDropdown.js b/src/components/TaskStatusDropdown.js
new file mode 100644
index 0000000..5d2def2
--- /dev/null
+++ b/src/components/TaskStatusDropdown.js
@@ -0,0 +1,188 @@
+"use client";
+
+import { useState, useEffect, useRef } from "react";
+import { createPortal } from "react-dom";
+import Badge from "@/components/ui/Badge";
+
+export default function TaskStatusDropdown({
+ task,
+ size = "sm",
+ showDropdown = true,
+ onStatusChange,
+}) {
+ const [status, setStatus] = useState(task.status);
+ const [loading, setLoading] = useState(false);
+ const [isOpen, setIsOpen] = useState(false);
+ const [dropdownPosition, setDropdownPosition] = useState({
+ top: 0,
+ left: 0,
+ width: 0,
+ });
+ const buttonRef = useRef(null);
+
+ const statusConfig = {
+ pending: {
+ label: "Pending",
+ variant: "warning",
+ },
+ in_progress: {
+ label: "In Progress",
+ variant: "primary",
+ },
+ completed: {
+ label: "Completed",
+ variant: "success",
+ },
+ cancelled: {
+ label: "Cancelled",
+ variant: "danger",
+ },
+ };
+ const handleChange = async (newStatus) => {
+ if (newStatus === status) {
+ setIsOpen(false);
+ return;
+ }
+
+ setStatus(newStatus);
+ setLoading(true);
+ setIsOpen(false);
+
+ try {
+ const res = await fetch(`/api/project-tasks/${task.id}`, {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ status: newStatus }),
+ });
+
+ if (res.ok) {
+ // Call the callback if provided (for parent component to refresh)
+ if (onStatusChange) {
+ onStatusChange(task.id, newStatus);
+ }
+ } else {
+ // Revert on error
+ setStatus(task.status);
+ alert("Failed to update task status");
+ }
+ } catch (error) {
+ console.error("Failed to update status:", error);
+ setStatus(task.status); // Revert on error
+ alert("Error updating task status");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const updateDropdownPosition = () => {
+ if (buttonRef.current) {
+ const rect = buttonRef.current.getBoundingClientRect();
+ setDropdownPosition({
+ top: rect.bottom + window.scrollY + 4,
+ left: rect.left + window.scrollX,
+ width: rect.width,
+ });
+ }
+ };
+
+ const handleOpen = () => {
+ setIsOpen(true);
+ updateDropdownPosition();
+ };
+
+ useEffect(() => {
+ if (isOpen) {
+ const handleResize = () => updateDropdownPosition();
+ const handleScroll = () => updateDropdownPosition();
+
+ window.addEventListener("resize", handleResize);
+ window.addEventListener("scroll", handleScroll, true);
+
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ window.removeEventListener("scroll", handleScroll, true);
+ };
+ }
+ }, [isOpen]);
+
+ const currentConfig = statusConfig[status] || {
+ label: "Unknown",
+ variant: "default",
+ };
+
+ if (!showDropdown) {
+ return (
+
+ {currentConfig.label}
+
+ );
+ }
+
+ return (
+
+ {" "}
+ {" "}
+ {isOpen &&
+ typeof window !== "undefined" &&
+ createPortal(
+ <>
+
+ {Object.entries(statusConfig).map(([statusKey, config]) => (
+
+ ))}
+
+ setIsOpen(false)}
+ />
+ >,
+ document.body
+ )}
+
+ );
+}
|