Todo List
A complete todo list Construct with all the essentials.
Complete Code
"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import { Plus, Trash2 } from "lucide-react";
interface Todo {
id: string;
text: string;
completed: boolean;
}
const STORAGE_KEY = "work-construct-todo-list";
export default function App() {
const [todos, setTodos] = useState<Todo[]>(() => {
if (typeof window === "undefined") return [];
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : [];
});
const [newTodo, setNewTodo] = useState("");
useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
}, [todos]);
const addTodo = () => {
if (!newTodo.trim()) return;
setTodos([
...todos,
{ id: crypto.randomUUID(), text: newTodo.trim(), completed: false },
]);
setNewTodo("");
};
const toggleTodo = (id: string) => {
setTodos(todos.map((t) =>
t.id === id ? { ...t, completed: !t.completed } : t
));
};
const deleteTodo = (id: string) => {
setTodos(todos.filter((t) => t.id !== id));
};
const completedCount = todos.filter((t) => t.completed).length;
return (
<div className="min-h-screen bg-background p-4 sm:p-6">
<Card className="mx-auto max-w-md">
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>Todo List</CardTitle>
{todos.length > 0 && (
<Badge variant="secondary">
{completedCount}/{todos.length}
</Badge>
)}
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex gap-2">
<Input
placeholder="Add a task..."
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTodo()}
aria-label="New task"
/>
<Button onClick={addTodo} aria-label="Add task">
<Plus className="h-4 w-4" />
</Button>
</div>
<div className="space-y-2">
{todos.map((todo) => (
<div
key={todo.id}
className="flex items-center gap-3 rounded-lg border p-3"
>
<Checkbox
id={todo.id}
checked={todo.completed}
onCheckedChange={() => toggleTodo(todo.id)}
/>
<Label
htmlFor={todo.id}
className={`flex-1 ${
todo.completed ? "line-through text-muted-foreground" : ""
}`}
>
{todo.text}
</Label>
<Button
variant="ghost"
size="icon"
onClick={() => deleteTodo(todo.id)}
aria-label="Delete task"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
</div>
{todos.length === 0 && (
<p className="text-center text-sm text-muted-foreground py-8">
No tasks yet. Add one above!
</p>
)}
{completedCount > 0 && (
<Button
variant="outline"
size="sm"
className="w-full"
onClick={() => setTodos(todos.filter((t) => !t.completed))}
>
Clear {completedCount} completed
</Button>
)}
</CardContent>
</Card>
</div>
);
}