Spaces:
Running
Running
File size: 5,405 Bytes
8e723d7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | "use client";
import React, { useState, useEffect } from "react";
import { db } from "@/lib/firebase";
import { collection, onSnapshot, addDoc, updateDoc, deleteDoc, doc, query, orderBy } from "firebase/firestore";
import { motion, AnimatePresence } from "framer-motion";
import { Plus, Trash2, CheckCircle2, Circle, Clock } from "lucide-react";
interface Task {
id: string;
title: string;
status: "todo" | "doing" | "done";
priority: "low" | "medium" | "high";
}
export default function TasksPage() {
const [tasks, setTasks] = useState<Task[]>([]);
const [newTask, setNewTask] = useState("");
useEffect(() => {
const unsub = onSnapshot(collection(db, "tasks"), (snap) => {
setTasks(snap.docs.map(doc => ({ id: doc.id, ...doc.data() } as Task)));
});
return () => unsub();
}, []);
const addTask = async (e: React.FormEvent) => {
e.preventDefault();
if (!newTask.trim()) return;
await addDoc(collection(db, "tasks"), {
title: newTask,
status: "todo",
priority: "medium",
createdAt: new Date()
});
setNewTask("");
};
const toggleStatus = async (task: Task) => {
const nextStatus = task.status === "todo" ? "doing" : task.status === "doing" ? "done" : "todo";
await updateDoc(doc(db, "tasks", task.id), { status: nextStatus });
};
return (
<div className="p-10 max-w-6xl mx-auto">
<header className="mb-12">
<h1 className="text-5xl font-black italic tracking-tighter mb-2">TASKS / SYSTEM</h1>
<p className="text-gray-500 font-medium">Gestión de flujo de trabajo corporativo</p>
</header>
<form onSubmit={addTask} className="mb-12 relative group">
<input
type="text"
placeholder="Escribe una nueva tarea y pulsa Enter..."
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded-3xl px-8 py-6 outline-none focus:border-blue-500/40 transition-all text-xl font-medium placeholder:text-gray-700 backdrop-blur-md"
/>
<button type="submit" className="absolute right-4 top-1/2 -translate-y-1/2 p-3 bg-blue-600 rounded-2xl hover:bg-blue-500 transition-all">
<Plus size={24} />
</button>
</form>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<TaskColumn title="Pendientes" status="todo" tasks={tasks.filter(t => t.status === "todo")} onToggle={toggleStatus} />
<TaskColumn title="En Proceso" status="doing" tasks={tasks.filter(t => t.status === "doing")} onToggle={toggleStatus} />
<TaskColumn title="Completadas" status="done" tasks={tasks.filter(t => t.status === "done")} onToggle={toggleStatus} />
</div>
</div>
);
}
function TaskColumn({ title, status, tasks, onToggle }: { title: string, status: string, tasks: Task[], onToggle: (t: Task) => void }) {
return (
<div className="space-y-6">
<div className="flex items-center justify-between px-4">
<h2 className="text-xs font-black uppercase tracking-[0.2em] text-gray-500">{title}</h2>
<span className="text-xs font-mono bg-white/10 px-2 py-1 rounded-md">{tasks.length}</span>
</div>
<div className="space-y-4">
<AnimatePresence mode="popLayout">
{tasks.map((task) => (
<motion.div
key={task.id}
layout
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
className="bg-white/5 border border-white/5 rounded-2xl p-5 hover:bg-white/10 hover:border-white/20 transition-all cursor-pointer group"
onClick={() => onToggle(task)}
>
<div className="flex items-start gap-4">
<div className="mt-1">
{status === "done" ? <CheckCircle2 className="text-green-400" size={18} /> :
status === "doing" ? <Clock className="text-orange-400" size={18} /> :
<Circle className="text-gray-600" size={18} />}
</div>
<div className="flex-1">
<p className={`font-semibold text-sm ${status === "done" ? 'line-through text-gray-600' : 'text-gray-200'}`}>
{task.title}
</p>
<div className="flex gap-2 mt-3">
<span className={`text-[8px] font-black uppercase px-2 py-0.5 rounded-full border ${
task.priority === 'high' ? 'border-red-500/40 text-red-400 bg-red-500/10' :
task.priority === 'medium' ? 'border-orange-500/40 text-orange-400 bg-orange-500/10' :
'border-gray-500/40 text-gray-400 bg-gray-500/10'
}`}>
{task.priority || 'medium'}
</span>
</div>
</div>
<button
onClick={(e) => { e.stopPropagation(); deleteDoc(doc(db, "tasks", task.id)); }}
className="opacity-0 group-hover:opacity-100 p-2 hover:bg-red-500/20 text-red-400 rounded-lg transition-all"
>
<Trash2 size={14} />
</button>
</div>
</motion.div>
))}
</AnimatePresence>
</div>
</div>
);
}
|