main
This commit is contained in:
123
colorful-todo.tsx
Normal file
123
colorful-todo.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user