Seed: project-management template (23 files)
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import { useState } from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
FolderKanban, ListChecks, CheckCircle2, AlertTriangle,
|
||||
Clock, ArrowUpRight, MoreHorizontal, Plus
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import Header from "@/components/Header";
|
||||
|
||||
const kpiCards = [
|
||||
{
|
||||
title: "Total Projects",
|
||||
value: "24",
|
||||
change: "+3 this month",
|
||||
icon: FolderKanban,
|
||||
color: "text-violet-600 bg-violet-100",
|
||||
},
|
||||
{
|
||||
title: "Active Tasks",
|
||||
value: "142",
|
||||
change: "+18 this week",
|
||||
icon: ListChecks,
|
||||
color: "text-indigo-600 bg-indigo-100",
|
||||
},
|
||||
{
|
||||
title: "Completed This Week",
|
||||
value: "38",
|
||||
change: "+12% vs last week",
|
||||
icon: CheckCircle2,
|
||||
color: "text-emerald-600 bg-emerald-100",
|
||||
},
|
||||
{
|
||||
title: "Overdue",
|
||||
value: "7",
|
||||
change: "-2 from yesterday",
|
||||
icon: AlertTriangle,
|
||||
color: "text-red-600 bg-red-100",
|
||||
},
|
||||
];
|
||||
|
||||
const activityFeed = [
|
||||
{ initials: "AK", name: "Alex Kim", action: "completed the homepage redesign task", time: "5 minutes ago", color: "bg-violet-500" },
|
||||
{ initials: "MR", name: "Maria Rodriguez", action: "moved API integration to In Progress", time: "22 minutes ago", color: "bg-indigo-500" },
|
||||
{ initials: "JL", name: "Jordan Lee", action: "added a comment on the onboarding flow", time: "1 hour ago", color: "bg-emerald-500" },
|
||||
{ initials: "SC", name: "Sam Chen", action: "created a new project: Mobile App v2", time: "3 hours ago", color: "bg-amber-500" },
|
||||
{ initials: "TP", name: "Taylor Park", action: "uploaded design assets to the brand kit", time: "5 hours ago", color: "bg-sky-500" },
|
||||
];
|
||||
|
||||
const myTasks = [
|
||||
{ id: 1, title: "Design system color tokens", priority: "High", priorityColor: "bg-red-100 text-red-700", due: "Apr 13", assignee: "AK", done: false },
|
||||
{ id: 2, title: "API integration for user auth", priority: "High", priorityColor: "bg-red-100 text-red-700", due: "Apr 14", assignee: "MR", done: false },
|
||||
{ id: 3, title: "Update onboarding flow copy", priority: "Medium", priorityColor: "bg-amber-100 text-amber-700", due: "Apr 15", assignee: "JL", done: false },
|
||||
{ id: 4, title: "Fix sidebar responsive layout", priority: "Medium", priorityColor: "bg-amber-100 text-amber-700", due: "Apr 16", assignee: "SC", done: true },
|
||||
{ id: 5, title: "Write unit tests for dashboard", priority: "Low", priorityColor: "bg-slate-100 text-slate-700", due: "Apr 18", assignee: "TP", done: false },
|
||||
];
|
||||
|
||||
export default function IndexPage() {
|
||||
const [tasks, setTasks] = useState(myTasks);
|
||||
|
||||
const toggleTask = (id: number) => {
|
||||
setTasks(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));
|
||||
toast("Task completed!");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-slate-50">
|
||||
<Sidebar activePath="/" />
|
||||
<div className="flex-1 lg:ml-64">
|
||||
<Header />
|
||||
<main className="p-6 space-y-8">
|
||||
{/* ── Welcome ── */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-900 animate-fade-up">Welcome back, Alex</h1>
|
||||
<p className="text-slate-500 mt-1">Here is what is happening across your projects today.</p>
|
||||
</div>
|
||||
|
||||
{/* ── KPI Cards ── */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-6 stagger">
|
||||
{kpiCards.map((kpi) => (
|
||||
<Card key={kpi.title} 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 animate-fade-up">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className={`flex items-center justify-center h-12 w-12 rounded-xl \${kpi.color}`}>
|
||||
<kpi.icon className="h-6 w-6" />
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-slate-400">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-2xl font-bold text-slate-900">{kpi.value}</p>
|
||||
<p className="text-sm text-slate-500 mt-1">{kpi.title}</p>
|
||||
<p className="text-xs text-emerald-600 mt-2 font-medium">{kpi.change}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* ── Recent Activity ── */}
|
||||
<Card className="border-0 shadow-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<div>
|
||||
<CardTitle className="text-lg font-semibold text-slate-900">Recent Activity</CardTitle>
|
||||
<CardDescription>Latest actions across your workspace</CardDescription>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="text-violet-600 hover:text-violet-700">View All</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{activityFeed.map((item, i) => (
|
||||
<div key={i} className="flex items-start gap-3 p-2 rounded-lg backdrop-blur-sm bg-card/80 border-border/50">
|
||||
<div className={`h-8 w-8 rounded-full \${item.color} flex items-center justify-center shrink-0`}>
|
||||
<span className="text-xs font-semibold text-white">{item.initials}</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm text-slate-700">
|
||||
<span className="font-medium">{item.name}</span>{" "}{item.action}
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 mt-0.5 flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />{item.time}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* ── My Tasks ── */}
|
||||
<Card className="border-0 shadow-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<div>
|
||||
<CardTitle className="text-lg font-semibold text-slate-900">My Tasks</CardTitle>
|
||||
<CardDescription>Your assigned tasks for this sprint</CardDescription>
|
||||
</div>
|
||||
<Button size="sm" className="bg-violet-600 hover:bg-violet-700 text-white">
|
||||
<Plus className="mr-1 h-4 w-4" /> Add Task
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className={`flex items-center gap-3 p-3 rounded-lg hover:bg-slate-50 transition-colors \${task.done ? "opacity-60" : ""}`}>
|
||||
<Checkbox checked={task.done} onCheckedChange={() => toggleTask(task.id)} className="border-slate-300 data-[state=checked]:bg-violet-600 data-[state=checked]:border-violet-600" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={`text-sm font-medium \${task.done ? "line-through text-slate-400" : "text-slate-900"}`}>{task.title}</p>
|
||||
</div>
|
||||
<Badge className={`\${task.priorityColor} text-xs font-medium border-0`}>{task.priority}</Badge>
|
||||
<span className="text-xs text-slate-400 hidden sm:block">{task.due}</span>
|
||||
<Avatar className="h-6 w-6">
|
||||
<AvatarFallback className="text-[10px] bg-violet-100 text-violet-700">{task.assignee}</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user