Seed: dashboard template (24 files)

This commit is contained in:
2026-07-23 22:10:35 +00:00
parent 10fd33b6fc
commit 2022f8ab35
+194
View File
@@ -0,0 +1,194 @@
import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import {
Search, Plus, Pencil, Trash2, ChevronLeft, ChevronRight,
MoreHorizontal, Filter, Download
} from "lucide-react";
import Sidebar from "@/components/Sidebar";
import Header from "@/components/Header";
const users = [
{ id: 1, name: "Sarah Mitchell", initials: "SM", email: "sarah@example.com", role: "Admin", roleColor: "bg-red-100 text-red-700", status: "Active", statusColor: "bg-emerald-100 text-emerald-700", joined: "Jan 15, 2025" },
{ id: 2, name: "James Cooper", initials: "JC", email: "james@example.com", role: "Editor", roleColor: "bg-sky-100 text-sky-700", status: "Active", statusColor: "bg-emerald-100 text-emerald-700", joined: "Feb 22, 2025" },
{ id: 3, name: "Emily Zhang", initials: "EZ", email: "emily@example.com", role: "Viewer", roleColor: "bg-slate-100 text-slate-700", status: "Active", statusColor: "bg-emerald-100 text-emerald-700", joined: "Mar 08, 2025" },
{ id: 4, name: "Michael Brown", initials: "MB", email: "michael@example.com", role: "Editor", roleColor: "bg-sky-100 text-sky-700", status: "Inactive", statusColor: "bg-slate-100 text-slate-500", joined: "Apr 14, 2025" },
{ id: 5, name: "Lisa Anderson", initials: "LA", email: "lisa@example.com", role: "Admin", roleColor: "bg-red-100 text-red-700", status: "Active", statusColor: "bg-emerald-100 text-emerald-700", joined: "May 03, 2025" },
{ id: 6, name: "David Kim", initials: "DK", email: "david@example.com", role: "Viewer", roleColor: "bg-slate-100 text-slate-700", status: "Active", statusColor: "bg-emerald-100 text-emerald-700", joined: "Jun 19, 2025" },
{ id: 7, name: "Rachel Torres", initials: "RT", email: "rachel@example.com", role: "Editor", roleColor: "bg-sky-100 text-sky-700", status: "Inactive", statusColor: "bg-slate-100 text-slate-500", joined: "Jul 27, 2025" },
{ id: 8, name: "Chris Walker", initials: "CW", email: "chris@example.com", role: "Viewer", roleColor: "bg-slate-100 text-slate-700", status: "Active", statusColor: "bg-emerald-100 text-emerald-700", joined: "Aug 11, 2025" },
];
export default function UsersPage() {
const [search, setSearch] = useState("");
const [bulkAction, setBulkAction] = useState("");
const [selectedUsers, setSelectedUsers] = useState<number[]>([]);
const filteredUsers = users.filter(
(u) =>
u.name.toLowerCase().includes(search.toLowerCase()) ||
u.email.toLowerCase().includes(search.toLowerCase())
);
const toggleSelect = (id: number) => {
setSelectedUsers((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
);
};
const toggleAll = () => {
setSelectedUsers((prev) =>
prev.length === filteredUsers.length ? [] : filteredUsers.map((u) => u.id)
);
};
return (
<div className="flex min-h-screen bg-slate-50">
<Sidebar activePath="/users" />
<div className="flex-1 lg:ml-64">
<Header />
<main className="p-6 space-y-8">
<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">User Management</h1>
<p className="text-sm text-slate-500 mt-1">Manage your team members and their permissions</p>
</div>
<Button className="bg-sky-600 hover:bg-sky-700 text-white active:scale-[0.98] transition-all relative">
<Plus className="mr-2 h-4 w-4" /> Add User
<span className="absolute -top-1.5 -right-1.5 h-5 w-5 rounded-full bg-red-500 text-[10px] font-bold text-white flex items-center justify-center">2</span>
</Button>
</div>
<Card className="border-0 shadow-sm">
<CardContent className="p-6">
{/* ── Toolbar ── */}
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-3 mb-6">
<div className="relative flex-1 w-full sm:max-w-sm">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
<Input
placeholder="Search users..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-10 bg-slate-50 border-slate-200"
/>
</div>
<div className="flex items-center gap-2">
{selectedUsers.length > 0 && (
<Select value={bulkAction} onValueChange={setBulkAction}>
<SelectTrigger className="w-[160px] bg-white">
<SelectValue placeholder="Bulk Actions" />
</SelectTrigger>
<SelectContent>
<SelectItem value="activate">Activate</SelectItem>
<SelectItem value="deactivate">Deactivate</SelectItem>
<SelectItem value="delete">Delete</SelectItem>
</SelectContent>
</Select>
)}
<Button variant="outline" size="sm" className="active:scale-[0.98]">
<Filter className="mr-2 h-4 w-4" /> Filter
</Button>
<Button variant="outline" size="sm" className="active:scale-[0.98]">
<Download className="mr-2 h-4 w-4" /> Export
</Button>
</div>
</div>
{/* ── Users Table ── */}
<Table>
<TableHeader>
<TableRow className="border-slate-100">
<TableHead className="w-12">
<input
type="checkbox"
className="rounded border-slate-300"
checked={selectedUsers.length === filteredUsers.length && filteredUsers.length > 0}
onChange={toggleAll}
/>
</TableHead>
<TableHead className="text-slate-500 font-medium">User <span className="ml-1 text-muted-foreground"></span></TableHead>
<TableHead className="text-slate-500 font-medium">Email <span className="ml-1 text-muted-foreground"></span></TableHead>
<TableHead className="text-slate-500 font-medium">Role <span className="ml-1 text-muted-foreground"></span></TableHead>
<TableHead className="text-slate-500 font-medium">Status</TableHead>
<TableHead className="text-slate-500 font-medium">Joined</TableHead>
<TableHead className="text-slate-500 font-medium text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredUsers.map((user, idx) => (
<TableRow key={user.id} className={`border-slate-100 hover:bg-muted/50 transition-colors \${idx % 2 === 1 ? "bg-muted/30" : ""}`}>
<TableCell>
<input
type="checkbox"
className="rounded border-slate-300"
checked={selectedUsers.includes(user.id)}
onChange={() => toggleSelect(user.id)}
/>
</TableCell>
<TableCell>
<div className="flex items-center gap-3">
<Avatar className="h-9 w-9">
<AvatarFallback className="bg-sky-100 text-sky-700 text-xs font-semibold">{user.initials}</AvatarFallback>
</Avatar>
<span className="font-medium text-slate-900">{user.name}</span>
</div>
</TableCell>
<TableCell className="text-slate-600">{user.email}</TableCell>
<TableCell>
<Badge variant="secondary" className={`\${user.roleColor} border-0 font-medium`}>
{user.role}
</Badge>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<span className={`h-2 w-2 rounded-full \${user.status === "Active" ? "bg-emerald-500" : "bg-slate-400"}`} />
<span className={`text-sm font-medium \${user.status === "Active" ? "text-emerald-700" : "text-slate-500"}`}>{user.status}</span>
</div>
</TableCell>
<TableCell className="text-slate-500">{user.joined}</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end gap-1">
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-slate-400 hover:text-sky-600">
<Pencil className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-slate-400 hover:text-red-600">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{/* ── Pagination ── */}
<div className="flex items-center justify-between mt-6 pt-4 border-t border-slate-100">
<p className="text-sm text-slate-500">
Showing <span className="font-medium text-slate-900">1-8</span> of <span className="font-medium text-slate-900">248</span> users
</p>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" disabled>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" className="bg-sky-50 text-sky-700 border-sky-200">1</Button>
<Button variant="outline" size="sm">2</Button>
<Button variant="outline" size="sm">3</Button>
<span className="text-slate-400">...</span>
<Button variant="outline" size="sm">31</Button>
<Button variant="outline" size="sm">
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
</main>
</div>
</div>
);
}