diff --git a/src/app/project-tasks/page.js b/src/app/project-tasks/page.js
new file mode 100644
index 0000000..77766a3
--- /dev/null
+++ b/src/app/project-tasks/page.js
@@ -0,0 +1,15 @@
+import ProjectTasksDashboard from "@/components/ProjectTasksDashboard";
+import PageContainer from "@/components/ui/PageContainer";
+import PageHeader from "@/components/ui/PageHeader";
+
+export default function ProjectTasksPage() {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/components/ProjectTasksDashboard.js b/src/components/ProjectTasksDashboard.js
new file mode 100644
index 0000000..5106284
--- /dev/null
+++ b/src/components/ProjectTasksDashboard.js
@@ -0,0 +1,519 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import { Card, CardHeader, CardContent } from "./ui/Card";
+import Button from "./ui/Button";
+import Badge from "./ui/Badge";
+import SearchBar from "./ui/SearchBar";
+import { Select } from "./ui/Input";
+import Link from "next/link";
+import {
+ differenceInCalendarDays,
+ parseISO,
+ formatDistanceToNow,
+} from "date-fns";
+
+export default function ProjectTasksDashboard() {
+ const [allTasks, setAllTasks] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [filter, setFilter] = useState("all");
+ const [searchTerm, setSearchTerm] = useState("");
+
+ useEffect(() => {
+ const fetchAllTasks = async () => {
+ try {
+ const res = await fetch("/api/all-project-tasks");
+ const tasks = await res.json();
+ setAllTasks(tasks);
+ } catch (error) {
+ console.error("Failed to fetch project tasks:", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchAllTasks();
+ }, []);
+ // Calculate task status based on date_added and max_wait_days
+ const getTaskStatus = (task) => {
+ if (task.status === "completed" || task.status === "cancelled") {
+ return { type: "completed", days: 0 };
+ }
+
+ try {
+ // Handle different date formats
+ const addedDate = task.date_added.includes("T")
+ ? parseISO(task.date_added)
+ : new Date(task.date_added + "T00:00:00");
+
+ const daysElapsed = differenceInCalendarDays(new Date(), addedDate);
+ const maxWaitDays = task.max_wait_days || 0;
+ const daysOverdue = daysElapsed - maxWaitDays;
+
+ if (daysOverdue > 0) {
+ return { type: "overdue", days: daysOverdue };
+ } else if (maxWaitDays - daysElapsed <= 2) {
+ return { type: "due_soon", days: maxWaitDays - daysElapsed };
+ } else {
+ return { type: "pending", days: maxWaitDays - daysElapsed };
+ }
+ } catch (error) {
+ console.error("Error parsing date:", task.date_added, error);
+ return { type: "pending", days: 0 };
+ }
+ };
+
+ // Categorize tasks
+ const categorizeTasks = () => {
+ const now = new Date();
+ const categories = {
+ overdue: [],
+ due_soon: [],
+ pending: [],
+ in_progress: [],
+ recent_completed: [],
+ };
+ allTasks.forEach((task) => {
+ const taskStatus = getTaskStatus(task);
+ try {
+ const addedDate = task.date_added.includes("T")
+ ? parseISO(task.date_added)
+ : new Date(task.date_added + "T00:00:00");
+ const daysAgo = differenceInCalendarDays(now, addedDate); // First check if task is overdue (regardless of status)
+ if (
+ taskStatus.type === "overdue" &&
+ task.status !== "completed" &&
+ task.status !== "cancelled"
+ ) {
+ categories.overdue.push({ ...task, statusInfo: taskStatus });
+ }
+ // Then check if it's due soon (regardless of status)
+ else if (
+ taskStatus.type === "due_soon" &&
+ task.status !== "completed" &&
+ task.status !== "cancelled"
+ ) {
+ categories.due_soon.push({ ...task, statusInfo: taskStatus });
+ }
+ // Then categorize by actual status
+ else if (task.status === "pending") {
+ categories.pending.push({ ...task, statusInfo: taskStatus });
+ } else if (task.status === "in_progress") {
+ categories.in_progress.push({ ...task, statusInfo: taskStatus });
+ } else if (task.status === "completed" || task.status === "cancelled") {
+ // Show all completed/cancelled tasks (most recent activity)
+ categories.recent_completed.push({ ...task, statusInfo: taskStatus });
+ }
+ } catch (error) {
+ console.error("Error processing task:", task, error);
+ // Still add to appropriate category if there's an error
+ if (task.status === "pending") {
+ categories.pending.push({
+ ...task,
+ statusInfo: { type: "pending", days: 0 },
+ });
+ } else if (task.status === "in_progress") {
+ categories.in_progress.push({
+ ...task,
+ statusInfo: { type: "pending", days: 0 },
+ });
+ }
+ }
+ }); // Sort each category
+ categories.overdue.sort((a, b) => b.statusInfo.days - a.statusInfo.days);
+ categories.due_soon.sort((a, b) => a.statusInfo.days - b.statusInfo.days);
+ categories.pending.sort((a, b) => {
+ try {
+ const dateA = a.date_added.includes("T")
+ ? parseISO(a.date_added)
+ : new Date(a.date_added + "T00:00:00");
+ const dateB = b.date_added.includes("T")
+ ? parseISO(b.date_added)
+ : new Date(b.date_added + "T00:00:00");
+ return dateB - dateA;
+ } catch (error) {
+ return 0;
+ }
+ });
+ categories.in_progress.sort((a, b) => {
+ try {
+ const dateA = a.date_added.includes("T")
+ ? parseISO(a.date_added)
+ : new Date(a.date_added + "T00:00:00");
+ const dateB = b.date_added.includes("T")
+ ? parseISO(b.date_added)
+ : new Date(b.date_added + "T00:00:00");
+ return dateB - dateA;
+ } catch (error) {
+ return 0;
+ }
+ });
+ categories.recent_completed.sort((a, b) => {
+ try {
+ const dateA = a.date_added.includes("T")
+ ? parseISO(a.date_added)
+ : new Date(a.date_added + "T00:00:00");
+ const dateB = b.date_added.includes("T")
+ ? parseISO(b.date_added)
+ : new Date(b.date_added + "T00:00:00");
+ return dateB - dateA;
+ } catch (error) {
+ return 0;
+ }
+ });
+
+ return categories;
+ };
+
+ const categorizedTasks = categorizeTasks();
+
+ // Filter tasks based on search and filter
+ const filterTasks = (tasks) => {
+ if (!searchTerm) return tasks;
+ return tasks.filter(
+ (task) =>
+ task.task_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ task.project_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ task.wp.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ };
+ const getVisibleTasks = () => {
+ switch (filter) {
+ case "overdue":
+ return filterTasks(categorizedTasks.overdue);
+ case "due_soon":
+ return filterTasks(categorizedTasks.due_soon);
+ case "pending":
+ return filterTasks(categorizedTasks.pending);
+ case "in_progress":
+ return filterTasks(categorizedTasks.in_progress);
+ case "completed":
+ return filterTasks(categorizedTasks.recent_completed);
+ default:
+ return {
+ overdue: filterTasks(categorizedTasks.overdue),
+ due_soon: filterTasks(categorizedTasks.due_soon),
+ pending: filterTasks(categorizedTasks.pending),
+ in_progress: filterTasks(categorizedTasks.in_progress),
+ recent_completed: filterTasks(categorizedTasks.recent_completed),
+ };
+ }
+ };
+
+ const visibleTasks = getVisibleTasks();
+
+ const handleStatusChange = async (taskId, newStatus) => {
+ try {
+ const res = await fetch(`/api/project-tasks/${taskId}`, {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ status: newStatus }),
+ });
+
+ if (res.ok) {
+ // Refresh tasks
+ const res2 = await fetch("/api/all-project-tasks");
+ const tasks = await res2.json();
+ setAllTasks(tasks);
+ } else {
+ alert("Failed to update task status");
+ }
+ } catch (error) {
+ alert("Error updating task status");
+ }
+ };
+
+ const getPriorityVariant = (priority) => {
+ switch (priority) {
+ case "urgent":
+ return "urgent";
+ case "high":
+ return "high";
+ case "normal":
+ return "normal";
+ case "low":
+ return "low";
+ default:
+ return "default";
+ }
+ };
+
+ 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";
+ return "high";
+ };
+
+ const TaskCard = ({ task, showStatusBadge = false }) => (
+
+
+
+
+
+ {task.task_name}
+
+
+ {task.priority}
+
+ {showStatusBadge && (
+
+ {task.status.replace("_", " ")}
+
+ )}
+
+
+
+ {task.project_name}
+
+ WP: {task.wp}
+ Plot: {task.plot}
+
{" "}
+
+
+ Added:{" "}
+ {(() => {
+ try {
+ const addedDate = task.date_added.includes("T")
+ ? parseISO(task.date_added)
+ : new Date(task.date_added + "T00:00:00");
+ return formatDistanceToNow(addedDate, { addSuffix: true });
+ } catch (error) {
+ return task.date_added;
+ }
+ })()}
+
+ Max wait: {task.max_wait_days} days
+ Type: {task.task_type}
+
+
+
+ {task.statusInfo && task.statusInfo.type === "overdue" && (
+
+ {task.statusInfo.days} days overdue
+
+ )}
+ {task.statusInfo && task.statusInfo.type === "due_soon" && (
+
+ Due in {task.statusInfo.days} days
+
+ )}
+ {(task.status === "pending" || task.status === "in_progress") && (
+
+ )}
+
+
+
+ );
+
+ const SectionCard = ({ title, tasks, variant = "default", count }) => (
+
+
+
+
{title}
+
+ {count || tasks.length} {tasks.length === 1 ? "task" : "tasks"}
+
+
+
+
+ {tasks.length === 0 ? (
+
+
No tasks in this category
+
+ ) : (
+ tasks.map((task) => (
+
+ ))
+ )}
+
+
+ );
+ const filterOptions = [
+ { value: "all", label: "All Categories" },
+ { value: "overdue", label: "Overdue" },
+ { value: "due_soon", label: "Due Soon" },
+ { value: "pending", label: "Pending" },
+ { value: "in_progress", label: "In Progress" },
+ { value: "completed", label: "Recent Activity" },
+ ];
+
+ if (loading) {
+ return (
+
+
+
+
+ {[1, 2, 3, 4].map((i) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ return (
+
+ {" "}
+ {/* Summary Stats */}
+
+
+
+
+ {categorizedTasks.overdue.length}
+
+ Overdue
+
+
+
+
+
+ {categorizedTasks.due_soon.length}
+
+ Due Soon
+
+
+
+
+
+ {categorizedTasks.pending.length}
+
+ Pending
+
+
+
+
+
+ {categorizedTasks.in_progress.length}
+
+ In Progress
+
+
+
+
+
+ {categorizedTasks.recent_completed.length}
+
+ Recent Activity
+
+
+
+ {/* Search and Filters */}
+
setSearchTerm(e.target.value)}
+ placeholder="Search tasks, projects, or WP..."
+ resultsCount={
+ filter === "all"
+ ? allTasks.length
+ : Array.isArray(visibleTasks)
+ ? visibleTasks.length
+ : 0
+ }
+ resultsText="tasks"
+ filters={
+
+
+
+
+ }
+ />{" "}
+ {/* Task Sections */}
+ {filter === "all" ? (
+
+
+
+
+
+
+
+ ) : (
+
+ f.value === filter)?.label || "Tasks"
+ }
+ tasks={Array.isArray(visibleTasks) ? visibleTasks : []}
+ variant={
+ filter === "overdue"
+ ? "danger"
+ : filter === "due_soon"
+ ? "warning"
+ : filter === "pending"
+ ? "primary"
+ : filter === "in_progress"
+ ? "secondary"
+ : "success"
+ }
+ />
+
+ )}
+
+ );
+}
diff --git a/src/components/ui/Navigation.js b/src/components/ui/Navigation.js
index a075997..0020eea 100644
--- a/src/components/ui/Navigation.js
+++ b/src/components/ui/Navigation.js
@@ -17,7 +17,7 @@ const Navigation = () => {
{ href: "/", label: "Dashboard" },
{ href: "/projects", label: "Projects" },
{ href: "/tasks/templates", label: "Task Templates" },
- { href: "/tasks", label: "Project Tasks" },
+ { href: "/project-tasks", label: "Project Tasks" },
{ href: "/contracts", label: "Contracts" },
];