"use client"; import React, { useState, useEffect } from "react"; import ProjectTaskForm from "./ProjectTaskForm"; import { Card, CardHeader, CardContent } from "./ui/Card"; import Button from "./ui/Button"; import Badge from "./ui/Badge"; export default function ProjectTasksSection({ projectId }) { const [projectTasks, setProjectTasks] = useState([]); const [loading, setLoading] = useState(true); const [taskNotes, setTaskNotes] = useState({}); const [newNote, setNewNote] = useState({}); const [loadingNotes, setLoadingNotes] = useState({}); const [showAddTaskModal, setShowAddTaskModal] = useState(false); const [expandedDescriptions, setExpandedDescriptions] = useState({}); const [expandedNotes, setExpandedNotes] = useState({}); useEffect(() => { const fetchProjectTasks = async () => { try { const res = await fetch(`/api/project-tasks?project_id=${projectId}`); const tasks = await res.json(); setProjectTasks(tasks); // Fetch notes for each task const notesPromises = tasks.map(async (task) => { try { const notesRes = await fetch(`/api/task-notes?task_id=${task.id}`); const notes = await notesRes.json(); return { taskId: task.id, notes }; } catch (error) { console.error(`Failed to fetch notes for task ${task.id}:`, error); return { taskId: task.id, notes: [] }; } }); const notesResults = await Promise.all(notesPromises); const notesMap = {}; notesResults.forEach(({ taskId, notes }) => { notesMap[taskId] = notes; }); setTaskNotes(notesMap); } catch (error) { console.error("Failed to fetch project tasks:", error); } finally { setLoading(false); } }; fetchProjectTasks(); }, [projectId]); // Handle escape key to close modal useEffect(() => { const handleEscape = (e) => { if (e.key === "Escape" && showAddTaskModal) { setShowAddTaskModal(false); } }; document.addEventListener("keydown", handleEscape); return () => document.removeEventListener("keydown", handleEscape); }, [showAddTaskModal]); // Prevent body scroll when modal is open and handle map z-index useEffect(() => { if (showAddTaskModal) { // Prevent body scroll document.body.style.overflow = "hidden"; // Find and temporarily lower z-index of leaflet containers const leafletContainers = document.querySelectorAll(".leaflet-container"); leafletContainers.forEach((container) => { container.style.zIndex = "1"; }); // Also handle navigation and other potential high z-index elements const navElements = document.querySelectorAll("nav"); navElements.forEach((nav) => { nav.style.position = "relative"; nav.style.zIndex = "1"; }); } else { // Restore body scroll document.body.style.overflow = "unset"; // Restore leaflet container z-index const leafletContainers = document.querySelectorAll(".leaflet-container"); leafletContainers.forEach((container) => { container.style.zIndex = ""; }); // Restore navigation z-index const navElements = document.querySelectorAll("nav"); navElements.forEach((nav) => { nav.style.position = ""; nav.style.zIndex = ""; }); } // Cleanup function return () => { document.body.style.overflow = "unset"; const leafletContainers = document.querySelectorAll(".leaflet-container"); leafletContainers.forEach((container) => { container.style.zIndex = ""; }); const navElements = document.querySelectorAll("nav"); navElements.forEach((nav) => { nav.style.position = ""; nav.style.zIndex = ""; }); }; }, [showAddTaskModal]); const refetchTasks = async () => { try { const res = await fetch(`/api/project-tasks?project_id=${projectId}`); const tasks = await res.json(); setProjectTasks(tasks); // Refresh notes for all tasks const notesPromises = tasks.map(async (task) => { try { const notesRes = await fetch(`/api/task-notes?task_id=${task.id}`); const notes = await notesRes.json(); return { taskId: task.id, notes }; } catch (error) { console.error(`Failed to fetch notes for task ${task.id}:`, error); return { taskId: task.id, notes: [] }; } }); const notesResults = await Promise.all(notesPromises); const notesMap = {}; notesResults.forEach(({ taskId, notes }) => { notesMap[taskId] = notes; }); setTaskNotes(notesMap); } catch (error) { console.error("Failed to fetch project tasks:", error); } }; const handleTaskAdded = () => { refetchTasks(); // Refresh the list setShowAddTaskModal(false); // Close the modal }; 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) { refetchTasks(); // Refresh the list } else { alert("Failed to update task status"); } } catch (error) { alert("Error updating task status"); } }; const handleDeleteTask = async (taskId) => { if (!confirm("Are you sure you want to delete this task?")) return; try { const res = await fetch(`/api/project-tasks/${taskId}`, { method: "DELETE", }); if (res.ok) { refetchTasks(); // Refresh the list } else { alert("Failed to delete task"); } } catch (error) { alert("Error deleting task"); } }; const handleAddNote = async (taskId) => { const noteContent = newNote[taskId]?.trim(); if (!noteContent) return; setLoadingNotes((prev) => ({ ...prev, [taskId]: true })); try { const res = await fetch("/api/task-notes", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ task_id: taskId, note: noteContent, }), }); if (res.ok) { // Refresh notes for this task const notesRes = await fetch(`/api/task-notes?task_id=${taskId}`); const notes = await notesRes.json(); setTaskNotes((prev) => ({ ...prev, [taskId]: notes })); setNewNote((prev) => ({ ...prev, [taskId]: "" })); } else { alert("Failed to add note"); } } catch (error) { alert("Error adding note"); } finally { setLoadingNotes((prev) => ({ ...prev, [taskId]: false })); } }; const handleDeleteNote = async (noteId, taskId) => { if (!confirm("Are you sure you want to delete this note?")) return; try { const res = await fetch(`/api/task-notes?note_id=${noteId}`, { method: "DELETE", }); if (res.ok) { // Refresh notes for this task const notesRes = await fetch(`/api/task-notes?task_id=${taskId}`); const notes = await notesRes.json(); setTaskNotes((prev) => ({ ...prev, [taskId]: notes })); } else { alert("Failed to delete note"); } } catch (error) { alert("Error deleting note"); } }; 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 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) => ({ ...prev, [taskId]: !prev[taskId], })); }; const toggleNotes = (taskId) => { setExpandedNotes((prev) => ({ ...prev, [taskId]: !prev[taskId], })); }; return (

Project Tasks

{projectTasks.length} {projectTasks.length === 1 ? "task" : "tasks"}
{" "} {/* Add Task Modal */} {showAddTaskModal && (
{ if (e.target === e.currentTarget) { setShowAddTaskModal(false); } }} >

Add New Task

)} {/* Current Tasks */}

Current Tasks

{loading ? (
) : projectTasks.length === 0 ? (

No tasks assigned to this project yet.

Add a task above to get started.

) : (
{" "} {projectTasks.map((task) => ( {/* Main task row */} {" "} {/* Description row (expandable) */} {task.description && expandedDescriptions[task.id] && ( )} {/* Notes row (expandable) */} {expandedNotes[task.id] && ( )} ))}
Task Priority Max Wait Date Started Status Actions

{task.task_name}

{task.description && ( )}
{task.priority} {task.max_wait_days} days {task.date_started ? new Date(task.date_started).toLocaleDateString() : "Not started"}
{task.status.replace("_", " ")}
Description:

{task.description}

Notes ({taskNotes[task.id]?.length || 0})
{/* Existing Notes */} {taskNotes[task.id] && taskNotes[task.id].length > 0 && (
{taskNotes[task.id].map((note) => (
{note.is_system && ( System )}

{note.note}

{new Date( note.note_date ).toLocaleDateString()}{" "} at{" "} {new Date( note.note_date ).toLocaleTimeString()}

{!note.is_system && ( )}
))}
)} {/* Add New Note */}
setNewNote((prev) => ({ ...prev, [task.id]: e.target.value, })) } className="flex-1 px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500" onKeyPress={(e) => { if (e.key === "Enter") { handleAddNote(task.id); } }} />
)}
); }