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) {
>
✓
-
);