feat: can now delete task
Build and Deploy / build-and-push (push) Successful in 1m22s Details

This commit is contained in:
Ludo 2026-05-04 16:09:15 +02:00
parent fc9bc3fe62
commit 844eb00270
6 changed files with 61 additions and 53 deletions

View File

@ -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);

View File

@ -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() {
<TodaysTasks
tasks={todaysTasks}
onComplete={handleCompleteTask}
onRenew={handleRenewTask}
/>
<CreateTaskForm onCreateTask={handleCreateTask} />
<Timeline tasks={tasks} />
<Timeline tasks={tasks} onDeleteTask={handleDeleteTask} />
</>
)}
</main>

View File

@ -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(),

View File

@ -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;
}

View File

@ -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) {
) : (
<div className={`upcoming-list${hasMore ? ' upcoming-list--scrollable' : ''}`}>
{upcomingItems.map(({ task, dueDate }) => (
<UpcomingRow key={task.id} task={task} dueLabel={formatDueDate(dueDate)} />
<UpcomingRow key={task.id} task={task} dueLabel={formatDueDate(dueDate)} onDelete={() => onDeleteTask(task.id)} />
))}
</div>
)}
@ -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 (
<div className="upcoming-row">
<span
className="upcoming-name"
onClick={() => setTooltipVisible(v => !v)}
onMouseEnter={() => setTooltipVisible(true)}
onMouseLeave={() => setTooltipVisible(false)}
>
<span className="upcoming-name">
<span className="upcoming-name-text">{task.name}</span>
{tooltipVisible && <span className="name-tooltip">{task.name}</span>}
</span>
<span className="upcoming-badge">
{task.priority === 'ESSENTIAL' ? 'Essential' : 'When I have time'}
</span>
<span className="upcoming-due">{dueLabel}</span>
<button className="upcoming-delete" onClick={onDelete} title="Remove task"></button>
</div>
);
}

View File

@ -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}
/>
))}
</div>
@ -53,7 +51,6 @@ export default function TodaysTasks({ tasks, onComplete, onRenew }: TodaysTasksP
key={task.id}
task={task}
onComplete={onComplete}
onRenew={onRenew}
/>
))}
</div>
@ -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) {
>
</button>
<button
className="action-btn"
onClick={() => onRenew(task.id)}
title="Renew task"
>
</button>
</div>
</div>
);