import { useState, useEffect, useContext } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { ArrowLeft, Plus, Check, Trash2, Edit3, LayoutGrid, List, GanttChart, Settings, Calendar, Clock, MessageCircle, X } from 'lucide-react' import { format, differenceInDays, startOfDay, addDays, isAfter, isBefore } from 'date-fns' import { AppContext } from '../App' import { api, PRIORITY_CONFIG } from '../utils/api' import { useAuth } from '../contexts/AuthContext' import StatusBadge from '../components/StatusBadge' import BrandBadge from '../components/BrandBadge' import Modal from '../components/Modal' import CommentsSection from '../components/CommentsSection' const TASK_COLUMNS = [ { id: 'todo', label: 'To Do', color: 'bg-gray-400' }, { id: 'in_progress', label: 'In Progress', color: 'bg-blue-400' }, { id: 'done', label: 'Done', color: 'bg-emerald-400' }, ] export default function ProjectDetail() { const { id } = useParams() const navigate = useNavigate() const { teamMembers, brands } = useContext(AppContext) const { permissions, canEditResource, canDeleteResource } = useAuth() const canManageProject = permissions?.canEditProjects const [project, setProject] = useState(null) const [tasks, setTasks] = useState([]) const [loading, setLoading] = useState(true) const [assignableUsers, setAssignableUsers] = useState([]) const [view, setView] = useState('kanban') const [showTaskModal, setShowTaskModal] = useState(false) const [showProjectModal, setShowProjectModal] = useState(false) const [editingTask, setEditingTask] = useState(null) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) const [taskToDelete, setTaskToDelete] = useState(null) const [taskForm, setTaskForm] = useState({ title: '', description: '', priority: 'medium', assigned_to: '', start_date: '', due_date: '', status: 'todo' }) const [projectForm, setProjectForm] = useState({ name: '', description: '', brand_id: '', owner_id: '', status: 'active', start_date: '', due_date: '' }) const [showDiscussion, setShowDiscussion] = useState(false) // Drag state for kanban const [draggedTask, setDraggedTask] = useState(null) const [dragOverCol, setDragOverCol] = useState(null) useEffect(() => { loadProject() }, [id]) useEffect(() => { api.get('/users/assignable').then(res => setAssignableUsers(res.data || res || [])).catch(() => {}) }, []) const loadProject = async () => { try { const proj = await api.get(`/projects/${id}`) setProject(proj.data || proj) const tasksRes = await api.get(`/tasks?project_id=${id}`) setTasks(Array.isArray(tasksRes) ? tasksRes : (tasksRes.data || [])) } catch (err) { console.error('Failed to load project:', err) } finally { setLoading(false) } } const handleTaskSave = async () => { try { const data = { title: taskForm.title, description: taskForm.description, priority: taskForm.priority, assigned_to: taskForm.assigned_to ? Number(taskForm.assigned_to) : null, start_date: taskForm.start_date || null, due_date: taskForm.due_date || null, status: taskForm.status, project_id: Number(id), } if (editingTask) { await api.patch(`/tasks/${editingTask._id}`, data) } else { await api.post('/tasks', data) } setShowTaskModal(false) setEditingTask(null) setTaskForm({ title: '', description: '', priority: 'medium', assigned_to: '', start_date: '', due_date: '', status: 'todo' }) loadProject() } catch (err) { console.error('Task save failed:', err) } } const handleTaskStatusChange = async (taskId, newStatus) => { try { await api.patch(`/tasks/${taskId}`, { status: newStatus }) loadProject() } catch (err) { console.error('Status change failed:', err) } } const handleDeleteTask = async (taskId) => { setTaskToDelete(taskId) setShowDeleteConfirm(true) } const confirmDeleteTask = async () => { if (!taskToDelete) return try { await api.delete(`/tasks/${taskToDelete}`) loadProject() setTaskToDelete(null) } catch (err) { console.error('Delete failed:', err) } } const openEditTask = (task) => { setEditingTask(task) setTaskForm({ title: task.title || '', description: task.description || '', priority: task.priority || 'medium', assigned_to: task.assignedTo || task.assigned_to || '', start_date: task.startDate || task.start_date ? new Date(task.startDate || task.start_date).toISOString().slice(0, 10) : '', due_date: task.dueDate ? new Date(task.dueDate).toISOString().slice(0, 10) : '', status: task.status || 'todo', }) setShowTaskModal(true) } const openNewTask = () => { setEditingTask(null) setTaskForm({ title: '', description: '', priority: 'medium', assigned_to: '', start_date: '', due_date: '', status: 'todo' }) setShowTaskModal(true) } const openEditProject = () => { if (!project) return setProjectForm({ name: project.name || '', description: project.description || '', brand_id: project.brandId || project.brand_id || '', owner_id: project.ownerId || project.owner_id || '', status: project.status || 'active', start_date: project.startDate || project.start_date ? new Date(project.startDate || project.start_date).toISOString().slice(0, 10) : '', due_date: project.dueDate ? new Date(project.dueDate).toISOString().slice(0, 10) : '', }) setShowProjectModal(true) } const handleProjectSave = async () => { try { await api.patch(`/projects/${id}`, { name: projectForm.name, description: projectForm.description, brand_id: projectForm.brand_id ? Number(projectForm.brand_id) : null, owner_id: projectForm.owner_id ? Number(projectForm.owner_id) : null, status: projectForm.status, start_date: projectForm.start_date || null, due_date: projectForm.due_date || null, }) setShowProjectModal(false) loadProject() } catch (err) { console.error('Project save failed:', err) } } // Drag handlers const handleDragStart = (e, task) => { setDraggedTask(task) e.dataTransfer.effectAllowed = 'move' setTimeout(() => { e.target.style.opacity = '0.4' }, 0) } const handleDragEnd = (e) => { e.target.style.opacity = '1' setDraggedTask(null) setDragOverCol(null) } const handleDragOver = (e, colId) => { e.preventDefault() e.dataTransfer.dropEffect = 'move' setDragOverCol(colId) } const handleDragLeave = (e) => { if (!e.currentTarget.contains(e.relatedTarget)) setDragOverCol(null) } const handleDrop = (e, colId) => { e.preventDefault() setDragOverCol(null) if (draggedTask && draggedTask.status !== colId) { handleTaskStatusChange(draggedTask._id, colId) } setDraggedTask(null) } if (loading) { return (
) } if (!project) { return (

Project not found

) } const canEditProject = canEditResource('project', project) const completedTasks = tasks.filter(t => t.status === 'done').length const progress = tasks.length > 0 ? Math.round((completedTasks / tasks.length) * 100) : 0 const ownerName = project.ownerName || project.owner_name const brandName = project.brandName || project.brand_name return (
{/* Main content */}
{/* Back button */} {/* Project header */}

{project.name}

{brandName && } {ownerName && ( Owned by {ownerName} )} {project.dueDate && ( Due {format(new Date(project.dueDate), 'MMMM d, yyyy')} )}
{canEditProject && ( )}
{project.description && (

{project.description}

)} {/* Progress */}
Progress {progress}%

{completedTasks} of {tasks.length} tasks completed

{/* View switcher + Add Task */}
{[ { id: 'kanban', icon: LayoutGrid, label: 'Board' }, { id: 'list', icon: List, label: 'List' }, { id: 'gantt', icon: GanttChart, label: 'Timeline' }, ].map(v => ( ))}
{/* ─── KANBAN VIEW ─── */} {view === 'kanban' && (
{TASK_COLUMNS.map(col => { const colTasks = tasks.filter(t => t.status === col.id) const isOver = dragOverCol === col.id && draggedTask?.status !== col.id return (

{col.label}

{colTasks.length}
handleDragOver(e, col.id)} onDragLeave={handleDragLeave} onDrop={(e) => handleDrop(e, col.id)} > {colTasks.length === 0 ? (
{isOver ? 'Drop here' : 'No tasks'}
) : ( colTasks.map(task => ( openEditTask(task)} onDelete={() => handleDeleteTask(task._id)} onStatusChange={handleTaskStatusChange} onDragStart={handleDragStart} onDragEnd={handleDragEnd} /> )) )}
) })}
)} {/* ─── LIST VIEW ─── */} {view === 'list' && (
{tasks.length === 0 ? ( ) : ( tasks.map(task => { const prio = PRIORITY_CONFIG[task.priority] || PRIORITY_CONFIG.medium const assigneeName = task.assignedName || task.assigned_name const isOverdue = task.dueDate && new Date(task.dueDate) < new Date() && task.status !== 'done' return ( ) }) )}
Task Status Priority Assignee Due
No tasks yet
{task.description &&

{task.description}

}
{prio.label} {assigneeName || '—'} {task.dueDate ? format(new Date(task.dueDate), 'MMM d, yyyy') : '—'}
{canEditResource('task', task) && ( )} {canDeleteResource('task', task) && ( )}
)} {/* ─── GANTT / TIMELINE VIEW ─── */} {view === 'gantt' && }
{/* end main content */} {/* ─── DISCUSSION SIDEBAR ─── */} {showDiscussion && (

Discussion

)} {/* ─── TASK MODAL ─── */} { setShowTaskModal(false); setEditingTask(null) }} title={editingTask ? 'Edit Task' : 'Add Task'} size="md" >
setTaskForm(f => ({ ...f, title: e.target.value }))} className="w-full px-3 py-2 text-sm border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-primary/20 focus:border-brand-primary" placeholder="Task title" />