Files
tasks/colorful-todo.tsx
noahpombas-dev 93d814c3f4 main
2025-04-03 20:32:22 +02:00

124 lines
4.6 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Progress } from "@/components/ui/progress";
import { Toggle } from "@/components/ui/toggle";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Plus, Eye, EyeOff, Trash2 } from "lucide-react";
interface Todo {
id: number;
text: string;
completed: boolean;
}
export default function ColorfulTodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
const [newTodo, setNewTodo] = useState("");
const [showCompleted, setShowCompleted] = useState(true);
// Fetch tasks from API
useEffect(() => {
fetch("/api/tasks")
.then((res) => res.json())
.then(setTodos)
.catch(() => console.error("Failed to load tasks"));
}, []);
// Save tasks to API
const saveTasks = (updatedTodos: Todo[]) => {
setTodos(updatedTodos);
fetch("/api/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updatedTodos),
}).catch(() => console.error("Failed to save tasks"));
};
const addTodo = (e: React.FormEvent) => {
e.preventDefault();
if (newTodo.trim()) {
saveTasks([{ id: Date.now(), text: newTodo, completed: false }, ...todos]);
setNewTodo("");
}
};
const toggleTodo = (id: number) => {
saveTasks(
todos.map((todo) => (todo.id === id ? { ...todo, completed: !todo.completed } : todo))
);
};
const deleteTodo = (id: number) => {
saveTasks(todos.filter((todo) => todo.id !== id));
};
const completedCount = todos.filter((todo) => todo.completed).length;
const progress = todos.length > 0 ? (completedCount / todos.length) * 100 : 0;
return (
<div className="min-h-screen p-8 bg-gradient-to-b from-black to-[#222] flex items-center justify-center">
<Card className="w-full max-w-md bg-slate-800 shadow-lg border-0">
<CardContent className="p-6">
<div className="space-y-6">
{/* Header with Progress */}
<div className="space-y-2">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold text-slate-100">Tasks</h1>
<Toggle
pressed={showCompleted}
onPressedChange={setShowCompleted}
className="bg-slate-700 data-[state=on]:bg-slate-600 hover:bg-slate-600"
>
{showCompleted ? <Eye className="h-4 w-4 text-slate-200" /> : <EyeOff className="h-4 w-4 text-slate-200" />}
</Toggle>
</div>
<Progress value={progress} className="h-2 [&>div]:bg-green-700 bg-slate-200/20" />
<p className="text-sm text-slate-300">{completedCount} of {todos.length} tasks completed</p>
</div>
{/* Add Todo Form */}
<form onSubmit={addTodo} className="flex gap-2">
<Input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new task..."
className="flex-1 bg-slate-700 text-slate-100 placeholder-slate-400 border-slate-600 focus:border-sky-400 focus:ring-sky-400"
/>
<Button type="submit" size="icon" className="bg-sky-500 hover:bg-sky-600 text-white">
<Plus className="h-4 w-4" />
</Button>
</form>
{/* Todo List */}
<motion.div layout className="space-y-2">
<AnimatePresence initial={false}>
{todos
.filter((todo) => showCompleted || !todo.completed)
.map((todo) => (
<motion.div key={todo.id} layout>
<div className="group flex items-center gap-3 p-3 rounded-lg bg-slate-700/30 hover:bg-slate-700/50">
<Checkbox checked={todo.completed} onCheckedChange={() => toggleTodo(todo.id)} />
<span className="flex-1 text-slate-100">{todo.text}</span>
{todo.completed && (
<Button size="icon" variant="ghost" onClick={() => deleteTodo(todo.id)}>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</motion.div>
))}
</AnimatePresence>
</motion.div>
</div>
</CardContent>
</Card>
</div>
);
}