"use client"; import { useState, useEffect, Fragment } from "react"; import { Card, CardHeader, CardContent } from "./ui/Card"; import Button from "./ui/Button"; import Badge from "./ui/Badge"; import TaskStatusDropdownSimple from "./TaskStatusDropdownSimple"; import SearchBar from "./ui/SearchBar"; import { Select } from "./ui/Input"; import Link from "next/link"; import { differenceInCalendarDays, parseISO, formatDistanceToNow, } from "date-fns"; import { formatDate } from "@/lib/utils"; export default function ProjectTasksList() { const [allTasks, setAllTasks] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [groupBy, setGroupBy] = useState("none"); 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 { // For in-progress tasks, use date_started if available and valid, otherwise fall back to date_added let referenceDate; console.log(task.date_started); if ( task.status === "in_progress" && task.date_started && task.date_started.trim() !== "" ) { // Handle the format "2025-06-20 08:40:38" referenceDate = new Date(task.date_started); } else { // Handle date_added format referenceDate = task.date_added.includes("T") ? parseISO(task.date_added) : new Date(task.date_added); } // Check if date is valid if (isNaN(referenceDate.getTime())) { throw new Error("Invalid date"); } const daysElapsed = differenceInCalendarDays(new Date(), referenceDate); const maxWaitDays = task.max_wait_days || 0; const daysRemaining = maxWaitDays - daysElapsed; if (task.status === "in_progress") { if (daysRemaining < 0) { return { type: "overdue", days: Math.abs(daysRemaining), daysRemaining: daysRemaining, }; } else { return { type: "in_progress", days: daysRemaining, daysRemaining: daysRemaining, }; } } // For pending tasks, use original logic 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, task.date_started, error ); return { type: "pending", days: 0 }; } }; // Group tasks by status const groupTasksByStatus = () => { const groups = { pending: [], in_progress: [], completed: [], }; allTasks.forEach((task) => { const statusInfo = getTaskStatus(task); const taskWithStatus = { ...task, statusInfo }; if (task.status === "completed" || task.status === "cancelled") { groups.completed.push(taskWithStatus); } else if (task.status === "in_progress") { groups.in_progress.push(taskWithStatus); } else { groups.pending.push(taskWithStatus); } }); // Sort pending tasks by date_added (newest first) groups.pending.sort((a, b) => { try { const dateA = new Date(a.date_added); const dateB = new Date(b.date_added); return dateB - dateA; // Newest first } catch (error) { return 0; } }); // Sort in_progress tasks by time left (urgent first - less time left comes first) groups.in_progress.sort((a, b) => { // If both have valid time remaining, sort by days remaining (ascending - urgent first) if ( !isNaN(a.statusInfo.daysRemaining) && !isNaN(b.statusInfo.daysRemaining) ) { return a.statusInfo.daysRemaining - b.statusInfo.daysRemaining; } // If one has invalid time, sort by date_started as fallback try { const dateA = a.date_started ? new Date(a.date_started) : new Date(a.date_added); const dateB = b.date_started ? new Date(b.date_started) : new Date(b.date_added); return dateA - dateB; // Oldest started first } catch (error) { return 0; } }); // Sort completed tasks by date_completed if available, otherwise by date_added (most recently completed first) groups.completed.sort((a, b) => { try { // Try to use date_completed first if (a.date_completed && b.date_completed) { const dateA = new Date(a.date_completed); const dateB = new Date(b.date_completed); return dateB - dateA; // Most recently completed first } // If only one has date_completed, prioritize it if (a.date_completed && !b.date_completed) return -1; if (!a.date_completed && b.date_completed) return 1; // Fall back to date_added for both const dateA = new Date(a.date_added); const dateB = new Date(b.date_added); return dateB - dateA; // Newest first } catch (error) { return 0; } }); return groups; }; const taskGroups = groupTasksByStatus(); // Filter tasks based on search term 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.city?.toLowerCase().includes(searchTerm.toLowerCase()) || task.address?.toLowerCase().includes(searchTerm.toLowerCase()) ); }; // Group tasks by task name when groupBy is set to "task_name" const groupTasksByName = (tasks) => { if (groupBy !== "task_name") return { "All Tasks": tasks }; const groups = {}; tasks.forEach((task) => { const taskName = task.task_name; if (!groups[taskName]) { groups[taskName] = []; } groups[taskName].push(task); }); return groups; }; 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 getOverdueBadgeVariant = (days) => { if (days > 7) return "danger"; if (days > 3) return "warning"; return "high"; }; const TaskRow = ({ task, showTimeLeft = false }) => (
{task.task_name} {task.priority}
{" "} {task.project_name} {task.city || "N/A"} {task.address || "N/A"} {showTimeLeft && (
{task.statusInfo && task.statusInfo.type === "in_progress" && ( {!isNaN(task.statusInfo.daysRemaining) ? task.statusInfo.daysRemaining > 0 ? `${task.statusInfo.daysRemaining}d left` : `${Math.abs(task.statusInfo.daysRemaining)}d overdue` : "Calculating..."} )} {task.statusInfo && task.statusInfo.type === "overdue" && task.status === "in_progress" && ( {!isNaN(task.statusInfo.daysRemaining) ? `${Math.abs(task.statusInfo.daysRemaining)}d overdue` : "Overdue"} )}
)} {task.status === "completed" && task.date_completed ? (
Completed:{" "} {(() => { try { const completedDate = new Date(task.date_completed); return formatDistanceToNow(completedDate, { addSuffix: true, }); } catch (error) { return task.date_completed; } })()}
) : task.status === "in_progress" && task.date_started ? (
Started:{" "} {(() => { try { const startedDate = new Date(task.date_started); return formatDistanceToNow(startedDate, { addSuffix: true }); } catch (error) { return task.date_started; } })()}
) : ( (() => { try { const addedDate = task.date_added.includes("T") ? parseISO(task.date_added) : new Date(task.date_added); return formatDistanceToNow(addedDate, { addSuffix: true }); } catch (error) { return task.date_added; } })() )} {task.max_wait_days} days ); const TaskTable = ({ tasks, showGrouped = false, showTimeLeft = false }) => { const filteredTasks = filterTasks(tasks); const groupedTasks = groupTasksByName(filteredTasks); const colSpan = showTimeLeft ? "8" : "7"; return (
{" "} {" "} {showTimeLeft && ( )} {" "} {Object.entries(groupedTasks).map(([groupName, groupTasks]) => ( {showGrouped && groupName !== "All Tasks" && ( )} {groupTasks.map((task) => ( ))} ))}
Task Name Project City Address Time Left Date Info Max Wait Actions
{groupName} ({groupTasks.length} tasks)
{filteredTasks.length === 0 && (

No tasks found

)}
); }; if (loading) { return (
); } return (
{/* Summary Stats */}
{taskGroups.pending.length}
Pending
{taskGroups.in_progress.length}
In Progress
{taskGroups.completed.length}
Completed
{" "} {/* Search and Controls */}{" "} setSearchTerm(e.target.value)} placeholder="Search tasks, projects, city, or address..." resultsCount={ filterTasks(taskGroups.pending).length + filterTasks(taskGroups.in_progress).length + filterTasks(taskGroups.completed).length } resultsText="tasks" filters={
} />{" "} {/* Task Tables */}
{/* Pending Tasks */}

Pending Tasks {taskGroups.pending.length}

Tasks waiting to be started

{/* In Progress Tasks */}

In Progress Tasks {taskGroups.in_progress.length}

Tasks currently being worked on - showing time left for completion

{/* Completed Tasks */}

Completed Tasks {taskGroups.completed.length}

Recently completed and cancelled tasks

); }