Seed: project-management template (23 files)

This commit is contained in:
2026-07-23 22:14:36 +00:00
parent 9d89b18544
commit f8c401561c
+194
View File
@@ -0,0 +1,194 @@
import { useState } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
Plus, Calendar, Filter, GripVertical, MoreHorizontal
} from "lucide-react";
import { toast } from "sonner";
import Sidebar from "@/components/Sidebar";
import Header from "@/components/Header";
interface Task {
id: number;
title: string;
priority: string;
priorityColor: string;
assignee: string;
assigneeColor: string;
due: string;
tags: { label: string; color: string }[];
}
interface Column {
id: string;
title: string;
color: string;
dotColor: string;
tasks: Task[];
}
const initialColumns: Column[] = [
{
id: "todo",
title: "To Do",
color: "bg-slate-100 text-slate-700",
dotColor: "bg-slate-400",
tasks: [
{ id: 1, title: "Create user onboarding wizard", priority: "High", priorityColor: "bg-red-100 text-red-700", assignee: "AK", assigneeColor: "bg-violet-500", due: "Apr 14", tags: [{ label: "Frontend", color: "bg-violet-100 text-violet-700" }] },
{ id: 2, title: "Write API documentation", priority: "Medium", priorityColor: "bg-amber-100 text-amber-700", assignee: "MR", assigneeColor: "bg-indigo-500", due: "Apr 16", tags: [{ label: "Docs", color: "bg-sky-100 text-sky-700" }] },
{ id: 3, title: "Set up error monitoring", priority: "Low", priorityColor: "bg-slate-100 text-slate-600", assignee: "JL", assigneeColor: "bg-emerald-500", due: "Apr 18", tags: [{ label: "DevOps", color: "bg-orange-100 text-orange-700" }] },
{ id: 4, title: "Design email templates", priority: "Medium", priorityColor: "bg-amber-100 text-amber-700", assignee: "SC", assigneeColor: "bg-amber-500", due: "Apr 20", tags: [{ label: "Design", color: "bg-pink-100 text-pink-700" }] },
],
},
{
id: "in-progress",
title: "In Progress",
color: "bg-violet-100 text-violet-700",
dotColor: "bg-violet-500",
tasks: [
{ id: 5, title: "Implement auth flow with OAuth", priority: "High", priorityColor: "bg-red-100 text-red-700", assignee: "MR", assigneeColor: "bg-indigo-500", due: "Apr 13", tags: [{ label: "Backend", color: "bg-emerald-100 text-emerald-700" }] },
{ id: 6, title: "Build notification system", priority: "High", priorityColor: "bg-red-100 text-red-700", assignee: "AK", assigneeColor: "bg-violet-500", due: "Apr 15", tags: [{ label: "Frontend", color: "bg-violet-100 text-violet-700" }, { label: "Backend", color: "bg-emerald-100 text-emerald-700" }] },
{ id: 7, title: "Optimize database queries", priority: "Medium", priorityColor: "bg-amber-100 text-amber-700", assignee: "TP", assigneeColor: "bg-sky-500", due: "Apr 17", tags: [{ label: "Backend", color: "bg-emerald-100 text-emerald-700" }] },
],
},
{
id: "done",
title: "Done",
color: "bg-emerald-100 text-emerald-700",
dotColor: "bg-emerald-500",
tasks: [
{ id: 8, title: "Set up CI/CD pipeline", priority: "High", priorityColor: "bg-red-100 text-red-700", assignee: "JL", assigneeColor: "bg-emerald-500", due: "Apr 10", tags: [{ label: "DevOps", color: "bg-orange-100 text-orange-700" }] },
{ id: 9, title: "Design landing page mockups", priority: "Medium", priorityColor: "bg-amber-100 text-amber-700", assignee: "SC", assigneeColor: "bg-amber-500", due: "Apr 9", tags: [{ label: "Design", color: "bg-pink-100 text-pink-700" }] },
{ id: 10, title: "Configure analytics tracking", priority: "Low", priorityColor: "bg-slate-100 text-slate-600", assignee: "TP", assigneeColor: "bg-sky-500", due: "Apr 8", tags: [{ label: "Frontend", color: "bg-violet-100 text-violet-700" }] },
],
},
];
export default function TasksPage() {
const [columns, setColumns] = useState(initialColumns);
const [priorityFilter, setPriorityFilter] = useState("all");
const [assigneeFilter, setAssigneeFilter] = useState("all");
const handleAddTask = (columnId: string) => {
toast("Task creation dialog would open here");
};
const filteredColumns = columns.map(col => ({
...col,
tasks: col.tasks.filter(t => {
if (priorityFilter !== "all" && t.priority !== priorityFilter) return false;
if (assigneeFilter !== "all" && t.assignee !== assigneeFilter) return false;
return true;
}),
}));
return (
<div className="flex min-h-screen bg-slate-50">
<Sidebar activePath="/tasks" />
<div className="flex-1 lg:ml-64">
<Header />
<main className="p-6 space-y-6">
{/* ── Header ── */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-slate-900 animate-fade-up">Tasks</h1>
<p className="text-slate-500 mt-1">Kanban board for your current sprint</p>
</div>
<Button className="bg-violet-600 hover:bg-violet-700 text-white" onClick={() => toast("Task added!")}>
<Plus className="mr-2 h-4 w-4" /> New Task
</Button>
</div>
{/* ── Filters ── */}
<div className="flex flex-wrap items-center gap-3">
<div className="flex items-center gap-2 text-sm text-slate-500">
<Filter className="h-4 w-4" />
<span>Filter by:</span>
</div>
<Select value={priorityFilter} onValueChange={setPriorityFilter}>
<SelectTrigger className="w-[140px] h-9">
<SelectValue placeholder="Priority" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Priorities</SelectItem>
<SelectItem value="High">High</SelectItem>
<SelectItem value="Medium">Medium</SelectItem>
<SelectItem value="Low">Low</SelectItem>
</SelectContent>
</Select>
<Select value={assigneeFilter} onValueChange={setAssigneeFilter}>
<SelectTrigger className="w-[140px] h-9">
<SelectValue placeholder="Assignee" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Members</SelectItem>
<SelectItem value="AK">Alex Kim</SelectItem>
<SelectItem value="MR">Maria Rodriguez</SelectItem>
<SelectItem value="JL">Jordan Lee</SelectItem>
<SelectItem value="SC">Sam Chen</SelectItem>
<SelectItem value="TP">Taylor Park</SelectItem>
</SelectContent>
</Select>
</div>
{/* ── Kanban Board ── */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 stagger">
{filteredColumns.map((column) => (
<div key={column.id} className="space-y-3 animate-fade-up">
{/* Column Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className={`h-2.5 w-2.5 rounded-full \${column.dotColor}`} />
<h2 className="font-semibold text-slate-900">{column.title}</h2>
<Badge variant="secondary" className="text-xs">{column.tasks.length}</Badge>
</div>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-slate-400 hover:text-violet-600" onClick={() => handleAddTask(column.id)}>
<Plus className="h-4 w-4" />
</Button>
</div>
{/* Task Cards */}
<div className="space-y-3">
{column.tasks.map((task) => (
<Card key={task.id} className="border-0 shadow-sm backdrop-blur-sm bg-card/80 border-border/50 hover:-translate-y-1 hover:shadow-lg transition-all duration-300 cursor-pointer group">
<CardContent className="p-4">
<div className="flex items-start justify-between mb-2">
<h3 className="text-sm font-medium text-slate-900 group-hover:text-violet-600 transition-colors leading-snug">{task.title}</h3>
<Button variant="ghost" size="sm" className="h-6 w-6 p-0 text-slate-300 opacity-0 group-hover:opacity-100 transition-opacity shrink-0 ml-2">
<MoreHorizontal className="h-3.5 w-3.5" />
</Button>
</div>
<div className="flex flex-wrap gap-1.5 mb-3">
{task.tags.map((tag, i) => (
<Badge key={i} className={`\${tag.color} text-[10px] font-medium border-0 px-1.5 py-0`}>{tag.label}</Badge>
))}
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Badge className={`\${task.priorityColor} text-[10px] font-medium border-0`}>{task.priority}</Badge>
<span className="text-[11px] text-slate-400 flex items-center gap-1">
<Calendar className="h-3 w-3" />{task.due}
</span>
</div>
<Avatar className="h-6 w-6">
<AvatarFallback className={`text-[10px] text-white \${task.assigneeColor}`}>{task.assignee}</AvatarFallback>
</Avatar>
</div>
</CardContent>
</Card>
))}
<Button variant="ghost" className="w-full border-2 border-dashed border-slate-200 text-slate-400 hover:text-violet-600 hover:border-violet-300 transition-colors" onClick={() => handleAddTask(column.id)}>
<Plus className="mr-1 h-4 w-4" /> Add Task
</Button>
</div>
</div>
))}
</div>
</main>
</div>
</div>
);
}