diff --git a/src/app/page.js b/src/app/page.js index d29b047..96e6771 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -5,6 +5,7 @@ import Link from "next/link"; import { Card, CardHeader, CardContent } from "@/components/ui/Card"; import Button from "@/components/ui/Button"; import Badge from "@/components/ui/Badge"; +import TaskStatusDropdown from "@/components/TaskStatusDropdown"; import PageContainer from "@/components/ui/PageContainer"; import PageHeader from "@/components/ui/PageHeader"; import { LoadingState } from "@/components/ui/States"; @@ -906,20 +907,11 @@ export default function Home() {

{task.task_name}

- - {task.status?.replace("_", " ")} - + showDropdown={false} + /> {task.priority && ( + {daysRemaining !== null && ( {daysRemaining === 0 @@ -103,6 +104,7 @@ export default async function ProjectViewPage({ params }) {
+ {" "}

Project Information @@ -283,37 +285,13 @@ export default async function ProjectViewPage({ params }) {

+ {" "}
Current Status -
- - {project.project_status === "registered" - ? "Registered" - : project.project_status === "in_progress_design" - ? "In Progress (Design)" - : project.project_status === "in_progress_construction" - ? "In Progress (Construction)" - : project.project_status === "fulfilled" - ? "Completed" - : "Unknown"} - -
-
- -
+
- {daysRemaining !== null && (
diff --git a/src/app/tasks/page.js b/src/app/tasks/page.js index 00ef406..6d543a3 100644 --- a/src/app/tasks/page.js +++ b/src/app/tasks/page.js @@ -5,6 +5,7 @@ import Link from "next/link"; import { Card, CardHeader, CardContent } from "@/components/ui/Card"; import Button from "@/components/ui/Button"; import Badge from "@/components/ui/Badge"; +import TaskStatusDropdown from "@/components/TaskStatusDropdown"; import { Input } from "@/components/ui/Input"; import { formatDistanceToNow, parseISO } from "date-fns"; import PageContainer from "@/components/ui/PageContainer"; @@ -125,32 +126,6 @@ export default function ProjectTasksPage() { } }; - const getStatusVariant = (status) => { - switch (status) { - case "completed": - return "success"; - case "in_progress": - return "warning"; - case "pending": - return "secondary"; - default: - return "secondary"; - } - }; - - const getStatusDisplayName = (status) => { - switch (status) { - case "in_progress": - return "In Progress"; - case "completed": - return "Completed"; - case "pending": - return "Pending"; - default: - return status; - } - }; - const statusCounts = { all: allTasks.length, pending: allTasks.filter((task) => task.status === "pending").length, @@ -354,13 +329,16 @@ export default function ProjectTasksPage() {
+ {" "}

{task.task_name}

- - {getStatusDisplayName(task.status)} - + )}
-

Project

@@ -396,7 +373,6 @@ export default function ProjectTasksPage() {
)}
-
Added{" "} @@ -408,23 +384,8 @@ export default function ProjectTasksPage() { Max wait: {task.max_wait_days} days )}
-
- +
{" "}
- {task.status !== "completed" && ( - - )} - {" "} + {isOpen && + typeof window !== "undefined" && + createPortal( + <> +
+ {Object.entries(statusConfig).map(([statusKey, config]) => ( + + ))} +
+
setIsOpen(false)} + /> + , + document.body + )} +
); } diff --git a/src/components/ProjectTasksDashboard.js b/src/components/ProjectTasksDashboard.js index 5106284..70ff0f7 100644 --- a/src/components/ProjectTasksDashboard.js +++ b/src/components/ProjectTasksDashboard.js @@ -4,6 +4,7 @@ import { useState, useEffect } from "react"; import { Card, CardHeader, CardContent } from "./ui/Card"; import Button from "./ui/Button"; import Badge from "./ui/Badge"; +import TaskStatusDropdown from "./TaskStatusDropdown"; import SearchBar from "./ui/SearchBar"; import { Select } from "./ui/Input"; import Link from "next/link"; @@ -222,7 +223,6 @@ export default function ProjectTasksDashboard() { alert("Error updating task status"); } }; - const getPriorityVariant = (priority) => { switch (priority) { case "urgent": @@ -238,21 +238,6 @@ export default function ProjectTasksDashboard() { } }; - const getStatusBadgeVariant = (status) => { - switch (status) { - case "completed": - return "success"; - case "in_progress": - return "primary"; - case "pending": - return "warning"; - case "cancelled": - return "danger"; - default: - return "default"; - } - }; - const getOverdueBadgeVariant = (days) => { if (days > 7) return "danger"; if (days > 3) return "warning"; @@ -263,6 +248,7 @@ export default function ProjectTasksDashboard() {
+ {" "}

{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 + )} +
+ ); +}