// src/components/MilestoneDrawer.js import React, { useMemo, useState, useEffect } from 'react'; import { Button } from './ui/button.js'; import { Card, CardContent } from './ui/card.js'; import { ChevronLeft, Check, Trash2, PencilLine, X } from 'lucide-react'; import { flattenTasks } from '../utils/taskHelpers.js'; import authFetch from '../utils/authFetch.js'; import format from 'date-fns/format'; const pillStyle = { completed : 'bg-green-100 text-green-800', in_progress : 'bg-blue-100 text-blue-800', not_started : 'bg-gray-100 text-gray-700' }; const statusLabel = { not_started : 'Not started', in_progress : 'In progress', completed : 'Completed' }; const nextStatus = { not_started:'in_progress', in_progress:'completed', completed:'not_started' }; export default function MilestoneDrawer({ milestone, // single milestone object milestones = [], // still available if you compute progress elsewhere open, onClose, onTaskToggle = () => {} }) { // Local task list (flatten if your milestone.tasks has nested shape) const [tasks, setTasks] = useState(milestone ? flattenTasks([milestone]) : []); const [adding, setAdding] = useState(false); const [editingId, setEditingId] = useState(null); const [draftNew, setDraftNew] = useState({ title:'', due_date:'', description:'' }); const [draftEdit, setDraftEdit] = useState({ title:'', due_date:'', description:'' }); useEffect(() => { setTasks(milestone ? flattenTasks([milestone]) : []); setAdding(false); setEditingId(null); setDraftNew({ title:'', due_date:'', description:'' }); }, [milestone]); if (!open || !milestone) return null; const done = tasks.filter(t => t.status === 'completed').length; const prog = tasks.length ? Math.round(100 * done / tasks.length) : 0; async function toggle(t) { const newStatus = nextStatus[t.status] || 'not_started'; // optimistic local update setTasks(prev => prev.map(x => x.id === t.id ? { ...x, status:newStatus } : x)); onTaskToggle(t.id, newStatus); await authFetch(`/api/premium/tasks/${t.id}`, { method : 'PUT', headers: { 'Content-Type': 'application/json' }, body : JSON.stringify({ status: newStatus }) }); } async function createTask() { const { title, due_date, description } = draftNew; if (!title.trim()) return; const body = { milestone_id: milestone.id, title: title.trim(), description: description || '', due_date: due_date || null, status: 'not_started' }; // optimistic add (temporary id) const tempId = `tmp-${Date.now()}`; setTasks(prev => [...prev, { ...body, id: tempId }]); setDraftNew({ title:'', due_date:'', description:'' }); setAdding(false); const res = await authFetch('/api/premium/tasks', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) }); if (res.ok) { const saved = await res.json(); const real = Array.isArray(saved) ? saved[0] : saved; // replace temp id with real id setTasks(prev => prev.map(t => t.id === tempId ? { ...t, id: real.id } : t)); } else { // rollback on failure setTasks(prev => prev.filter(t => t.id !== tempId)); alert(await res.text()); } } function beginEdit(t) { setEditingId(t.id); setDraftEdit({ title: t.title || '', due_date: t.due_date ? String(t.due_date).slice(0,10) : '', description: t.description || '' }); } function cancelEdit() { setEditingId(null); setDraftEdit({ title:'', due_date:'', description:'' }); } async function saveEdit(id) { const body = { title: (draftEdit.title || '').trim(), description: draftEdit.description || '', due_date: draftEdit.due_date || null }; if (!body.title) return; // optimistic local update setTasks(prev => prev.map(t => t.id === id ? { ...t, ...body } : t)); setEditingId(null); const res = await authFetch(`/api/premium/tasks/${id}`, { method:'PUT', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) }); if (!res.ok) alert(await res.text()); } async function remove(id) { // optimistic local delete const prev = tasks; setTasks(prev.filter(t => t.id !== id)); const res = await authFetch(`/api/premium/tasks/${id}`, { method:'DELETE' }); if (!res.ok) { alert(await res.text()); setTasks(prev); // rollback } } return ( <> {/* Click-away area: tap to close (sits above page, below the panel) */}