diff --git a/src/components/EditTaskModal.tsx b/src/components/EditTaskModal.tsx new file mode 100644 index 0000000..a8a914d --- /dev/null +++ b/src/components/EditTaskModal.tsx @@ -0,0 +1,340 @@ +'use client'; + +import { useApp, type Task } from './AppProvider'; +import { useState, useEffect, useRef } from 'react'; +import { format } from 'date-fns'; + +export default function EditTaskModal() { + const { + showEditTaskModal, setShowEditTaskModal, + selectedTask, editingTask, setEditingTask, + projects, refreshTasks, + } = useApp(); + + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [projectId, setProjectId] = useState(''); + const [priority, setPriority] = useState<'low' | 'medium' | 'high' | 'urgent'>('medium'); + const [dueDate, setDueDate] = useState(''); + const [status, setStatus] = useState<'todo' | 'in_progress' | 'done'>('todo'); + const [recurrenceRule, setRecurrenceRule] = useState<'none' | 'daily' | 'weekly' | 'biweekly' | 'monthly' | 'yearly'>('none'); + const [recurrenceInterval, setRecurrenceInterval] = useState(1); + const [subtasks, setSubtasks] = useState([]); + const [newSubtaskTitle, setNewSubtaskTitle] = useState(''); + + useEffect(() => { + if (editingTask) { + setTitle(editingTask.title); + setDescription(editingTask.description); + setProjectId(editingTask.projectId || ''); + setPriority(editingTask.priority); + setDueDate(editingTask.dueDate ? format(new Date(editingTask.dueDate), 'yyyy-MM-dd') : ''); + setStatus(editingTask.status); + setRecurrenceRule(editingTask.recurrenceRule); + setRecurrenceInterval(editingTask.recurrenceInterval); + } + }, [editingTask]); + + // Fetch subtasks when modal opens + useEffect(() => { + if (showEditTaskModal && editingTask) { + fetch(`/api/tasks/${editingTask.id}`) + .then(res => res.json()) + .then(data => { + setSubtasks(data.subtasks || []); + }) + .catch(console.error); + } + }, [showEditTaskModal, editingTask]); + + const handleSave = async () => { + if (!editingTask || !title.trim()) return; + + try { + await fetch(`/api/tasks/${editingTask.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: title.trim(), + description: description.trim(), + projectId: projectId || null, + priority, + dueDate: dueDate || null, + status, + recurrenceRule, + recurrenceInterval, + }), + }); + + setShowEditTaskModal(false); + refreshTasks(); + } catch (error) { + console.error('Failed to update task:', error); + } + }; + + const handleDelete = async () => { + if (!editingTask) return; + if (!confirm('Delete this task and all its subtasks?')) return; + + try { + await fetch(`/api/tasks/${editingTask.id}`, { method: 'DELETE' }); + setShowEditTaskModal(false); + refreshTasks(); + } catch (error) { + console.error('Failed to delete task:', error); + } + }; + + const handleToggleSubtask = async (subtask: Task) => { + try { + await fetch(`/api/tasks/${subtask.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'toggle-done' }), + }); + // Refresh subtasks + const res = await fetch(`/api/tasks/${editingTask?.id}`); + const data = await res.json(); + setSubtasks(data.subtasks || []); + refreshTasks(); + } catch (error) { + console.error('Failed to toggle subtask:', error); + } + }; + + const handleAddSubtask = async () => { + if (!newSubtaskTitle.trim() || !editingTask) return; + + try { + await fetch('/api/tasks', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: newSubtaskTitle.trim(), + projectId: editingTask.projectId, + priority: editingTask.priority, + parentTaskId: editingTask.id, + }), + }); + + setNewSubtaskTitle(''); + // Refresh subtasks + const res = await fetch(`/api/tasks/${editingTask.id}`); + const data = await res.json(); + setSubtasks(data.subtasks || []); + refreshTasks(); + } catch (error) { + console.error('Failed to add subtask:', error); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + setShowEditTaskModal(false); + } + if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { + handleSave(); + } + }; + + if (!showEditTaskModal || !editingTask) return null; + + const priorityConfig = { + urgent: { label: 'Urgent', icon: '🔴', bg: 'bg-[var(--priority-urgent)]/10', text: 'text-[var(--priority-urgent)]' }, + high: { label: 'High', icon: '🟠', bg: 'bg-[var(--priority-high)]/10', text: 'text-[var(--priority-high)]' }, + medium: { label: 'Medium', icon: '🟡', bg: 'bg-[var(--priority-medium)]/10', text: 'text-[var(--priority-medium)]' }, + low: { label: 'Low', icon: '🟢', bg: 'bg-[var(--priority-low)]/10', text: 'text-[var(--priority-low)]' }, + }; + + const prio = priorityConfig[priority]; + + return ( +
{ if (e.target === e.currentTarget) setShowEditTaskModal(false); }} + > +
+ {/* Header */} +
+

Edit Task

+ +
+ + {/* Body */} +
+ {/* Title */} + setTitle(e.target.value)} + className="w-full bg-content border border-border rounded-lg px-3 py-2.5 text-sm font-medium focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent/20 transition-colors" + /> + + {/* Description */} +