diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index da79352..dc3e685 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -81,6 +81,19 @@ fastify.patch<{ Params: { id: string } }>('/api/tasks/:id/complete', async (requ reply.send(updatedTask); }); +fastify.delete<{ Params: { id: string } }>('/api/tasks/:id', async (request, reply) => { + const id = parseInt(request.params.id); + + const deleted = await db.delete(tasks).where(eq(tasks.id, id)).returning(); + + if (!deleted.length) { + reply.code(404).send({ error: 'Task not found' }); + return; + } + + reply.code(204).send(); +}); + fastify.patch<{ Params: { id: string } }>('/api/tasks/:id/renew', async (request, reply) => { const id = parseInt(request.params.id); diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 1b0d401..8da9ff3 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -63,6 +63,21 @@ function App() { } } + const handleDeleteTask = async (taskId: number) => { + setError(null) + try { + const response = await fetch(`/api/tasks/${taskId}`, { method: 'DELETE' }) + if (!response.ok) { + setError('Failed to delete task') + return + } + await fetchTasks() + } catch (error) { + console.error('Error deleting task:', error) + setError('Failed to delete task') + } + } + const handleCompleteTask = async (taskId: number) => { setError(null) try { @@ -81,24 +96,6 @@ function App() { } } - const handleRenewTask = async (taskId: number) => { - setError(null) - try { - const response = await fetch(`/api/tasks/${taskId}/renew`, { - method: 'PATCH', - }) - - if (!response.ok) { - setError('Failed to renew task') - return - } - await fetchTasks() - } catch (error) { - console.error('Error renewing task:', error) - setError('Failed to renew task') - } - } - const todaysTasks = tasks.filter(isTaskDueToday) return ( @@ -125,10 +122,9 @@ function App() { - + )} diff --git a/apps/web/src/components/CreateTaskForm.tsx b/apps/web/src/components/CreateTaskForm.tsx index 744a1fd..f081f19 100644 --- a/apps/web/src/components/CreateTaskForm.tsx +++ b/apps/web/src/components/CreateTaskForm.tsx @@ -18,10 +18,7 @@ export default function CreateTaskForm({ onCreateTask }: CreateTaskFormProps) { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if (!name.trim()) { - alert('Please enter a task name'); - return; - } + if (!name.trim()) return; const taskInput: NewTaskInput = { name: name.trim(), diff --git a/apps/web/src/components/Timeline.css b/apps/web/src/components/Timeline.css index b804acd..1a4cb42 100644 --- a/apps/web/src/components/Timeline.css +++ b/apps/web/src/components/Timeline.css @@ -2,7 +2,9 @@ margin-top: 48px; padding-top: 32px; border-top: 1px solid #e8e8e8; -}.upcoming-list { +} + +.upcoming-list { display: flex; flex-direction: column; border: 1px solid #e8e8e8; @@ -17,7 +19,7 @@ .upcoming-row { display: grid; - grid-template-columns: 1fr auto auto; + grid-template-columns: 1fr auto auto auto; align-items: center; gap: 16px; padding: 12px 16px; @@ -101,9 +103,26 @@ border-top-color: #111; } +.upcoming-delete { + background: none; + border: none; + cursor: pointer; + color: #bbb; + font-size: 13px; + padding: 2px 4px; + line-height: 1; + border-radius: 4px; + transition: color 0.15s, background 0.15s; +} + +.upcoming-delete:hover { + color: #111; + background: #f0f0f0; +} + @media (max-width: 768px) { .upcoming-row { - grid-template-columns: 1fr auto; + grid-template-columns: 1fr auto auto; gap: 4px 12px; padding: 10px 12px; } diff --git a/apps/web/src/components/Timeline.tsx b/apps/web/src/components/Timeline.tsx index b0ed066..f820a8d 100644 --- a/apps/web/src/components/Timeline.tsx +++ b/apps/web/src/components/Timeline.tsx @@ -1,15 +1,15 @@ -import { useState } from 'react'; import type { Task } from '../types'; import { calculateNextDueDate } from '../utils/taskUtils'; import './Timeline.css'; interface TimelineProps { tasks: Task[]; + onDeleteTask: (id: number) => void; } const MAX_VISIBLE = 5; -export default function Timeline({ tasks }: TimelineProps) { +export default function Timeline({ tasks, onDeleteTask }: TimelineProps) { const today = new Date(); today.setHours(0, 0, 0, 0); @@ -42,7 +42,7 @@ export default function Timeline({ tasks }: TimelineProps) { ) : (
{upcomingItems.map(({ task, dueDate }) => ( - + onDeleteTask(task.id)} /> ))}
)} @@ -53,26 +53,20 @@ export default function Timeline({ tasks }: TimelineProps) { interface UpcomingRowProps { task: Task; dueLabel: string; + onDelete: () => void; } -function UpcomingRow({ task, dueLabel }: UpcomingRowProps) { - const [tooltipVisible, setTooltipVisible] = useState(false); - +function UpcomingRow({ task, dueLabel, onDelete }: UpcomingRowProps) { return (
- setTooltipVisible(v => !v)} - onMouseEnter={() => setTooltipVisible(true)} - onMouseLeave={() => setTooltipVisible(false)} - > + {task.name} - {tooltipVisible && {task.name}} {task.priority === 'ESSENTIAL' ? 'Essential' : 'When I have time'} {dueLabel} +
); } diff --git a/apps/web/src/components/TodaysTasks.tsx b/apps/web/src/components/TodaysTasks.tsx index bdbbf0b..5ff5766 100644 --- a/apps/web/src/components/TodaysTasks.tsx +++ b/apps/web/src/components/TodaysTasks.tsx @@ -5,10 +5,9 @@ import './TodaysTasks.css'; interface TodaysTasksProps { tasks: Task[]; onComplete: (taskId: number) => void; - onRenew: (taskId: number) => void; } -export default function TodaysTasks({ tasks, onComplete, onRenew }: TodaysTasksProps) { +export default function TodaysTasks({ tasks, onComplete }: TodaysTasksProps) { const essentialTasks = tasks.filter(t => t.priority === 'ESSENTIAL'); const whenIHaveTimeTasks = tasks.filter(t => t.priority === 'WHEN_I_HAVE_TIME'); const [showWhenIHaveTime, setShowWhenIHaveTime] = useState(false); @@ -27,7 +26,6 @@ export default function TodaysTasks({ tasks, onComplete, onRenew }: TodaysTasksP key={task.id} task={task} onComplete={onComplete} - onRenew={onRenew} /> ))} @@ -53,7 +51,6 @@ export default function TodaysTasks({ tasks, onComplete, onRenew }: TodaysTasksP key={task.id} task={task} onComplete={onComplete} - onRenew={onRenew} /> ))} @@ -67,10 +64,9 @@ export default function TodaysTasks({ tasks, onComplete, onRenew }: TodaysTasksP interface TaskCardProps { task: Task; onComplete: (taskId: number) => void; - onRenew: (taskId: number) => void; } -function TaskCard({ task, onComplete, onRenew }: TaskCardProps) { +function TaskCard({ task, onComplete }: TaskCardProps) { const [tooltipVisible, setTooltipVisible] = useState(false); return ( @@ -92,13 +88,6 @@ function TaskCard({ task, onComplete, onRenew }: TaskCardProps) { > ✓ - );