124 lines
4.6 KiB
TypeScript
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>
|
|
);
|
|
}
|