Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:36:12 +08:00
commit 7df054b028
15 changed files with 4043 additions and 0 deletions

View File

@@ -0,0 +1,683 @@
# Admin Dashboard Example
This example demonstrates how to build a comprehensive admin dashboard using the Backstage Design System.
## Project Structure
```
admin-dashboard/
├── src/
│ ├── components/
│ │ ├── ui/ # Base UI components
│ │ ├── layout/ # Layout components
│ │ ├── dashboard/ # Dashboard-specific components
│ │ └── forms/ # Form components
│ ├── pages/
│ │ ├── Dashboard.tsx # Main dashboard page
│ │ ├── Users.tsx # User management
│ │ ├── Analytics.tsx # Analytics page
│ │ └── Settings.tsx # Settings page
│ ├── hooks/ # Custom hooks
│ ├── lib/ # Utilities
│ └── types/ # TypeScript types
```
## Main Dashboard Page
```typescript
"use client";
import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Progress } from "@/components/ui/progress";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
BarChart3,
Users,
TrendingUp,
DollarSign,
Search,
Filter,
Download,
Plus,
Eye,
Edit,
Trash2
} from "lucide-react";
interface DashboardStats {
totalUsers: number;
activeUsers: number;
revenue: number;
growth: number;
}
interface RecentActivity {
id: string;
user: string;
action: string;
timestamp: string;
status: "success" | "warning" | "error";
}
interface TopPerformer {
id: string;
name: string;
email: string;
sales: number;
target: number;
performance: number;
}
export default function AdminDashboard() {
const [stats, setStats] = useState<DashboardStats>({
totalUsers: 0,
activeUsers: 0,
revenue: 0,
growth: 0,
});
const [recentActivity, setRecentActivity] = useState<RecentActivity[]>([]);
const [topPerformers, setTopPerformers] = useState<TopPerformer[]>([]);
const [searchTerm, setSearchTerm] = useState("");
const [selectedTab, setSelectedTab] = useState("overview");
// Simulate data loading
useEffect(() => {
const loadData = () => {
setStats({
totalUsers: 12543,
activeUsers: 8432,
revenue: 245780,
growth: 12.5,
});
setRecentActivity([
{
id: "1",
user: "John Doe",
action: "Created new project",
timestamp: "2 minutes ago",
status: "success",
},
{
id: "2",
user: "Jane Smith",
action: "Updated user profile",
timestamp: "5 minutes ago",
status: "success",
},
{
id: "3",
user: "Bob Johnson",
action: "Failed login attempt",
timestamp: "10 minutes ago",
status: "error",
},
{
id: "4",
user: "Alice Brown",
action: "Exported data",
timestamp: "15 minutes ago",
status: "warning",
},
]);
setTopPerformers([
{
id: "1",
name: "Sarah Wilson",
email: "sarah@example.com",
sales: 45000,
target: 50000,
performance: 90,
},
{
id: "2",
name: "Mike Chen",
email: "mike@example.com",
sales: 42000,
target: 45000,
performance: 93,
},
{
id: "3",
name: "Emma Davis",
email: "emma@example.com",
sales: 38000,
target: 40000,
performance: 95,
},
]);
};
loadData();
}, []);
const getStatusBadge = (status: RecentActivity["status"]) => {
const variants = {
success: "default",
warning: "secondary",
error: "destructive",
} as const;
return variants[status] || "secondary";
};
return (
<div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-3xl font-bold tracking-tight">Admin Dashboard</h2>
<p className="text-muted-foreground">
Welcome back! Here's what's happening with your platform.
</p>
</div>
<div className="flex items-center space-x-2">
<Button variant="outline" size="sm">
<Download className="mr-2 h-4 w-4" />
Export
</Button>
<Button size="sm">
<Plus className="mr-2 h-4 w-4" />
Add User
</Button>
</div>
</div>
{/* Stats Cards */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Users</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.totalUsers.toLocaleString()}</div>
<p className="text-xs text-muted-foreground">
+180 from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Users</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.activeUsers.toLocaleString()}</div>
<p className="text-xs text-muted-foreground">
+{((stats.activeUsers / stats.totalUsers) * 100).toFixed(1)}% active rate
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Revenue</CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">${stats.revenue.toLocaleString()}</div>
<p className="text-xs text-muted-foreground">
+{stats.growth}% from last month
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Growth Rate</CardTitle>
<BarChart3 className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.growth}%</div>
<p className="text-xs text-muted-foreground">
Above target by 2.5%
</p>
</CardContent>
</Card>
</div>
{/* Main Content Tabs */}
<Tabs value={selectedTab} onValueChange={setSelectedTab} className="space-y-4">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="users">Users</TabsTrigger>
<TabsTrigger value="analytics">Analytics</TabsTrigger>
<TabsTrigger value="reports">Reports</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-4">
<div className="grid gap-4 md:grid-cols-2">
{/* Recent Activity */}
<Card>
<CardHeader>
<CardTitle>Recent Activity</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{recentActivity.map((activity) => (
<div key={activity.id} className="flex items-center space-x-4">
<div className="flex-1 space-y-1">
<p className="text-sm font-medium leading-none">
{activity.user}
</p>
<p className="text-sm text-muted-foreground">
{activity.action}
</p>
</div>
<div className="flex flex-col items-end space-y-1">
<Badge variant={getStatusBadge(activity.status)}>
{activity.status}
</Badge>
<p className="text-xs text-muted-foreground">
{activity.timestamp}
</p>
</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Top Performers */}
<Card>
<CardHeader>
<CardTitle>Top Performers</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{topPerformers.map((performer) => (
<div key={performer.id} className="space-y-2">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium">{performer.name}</p>
<p className="text-xs text-muted-foreground">
{performer.email}
</p>
</div>
<div className="text-right">
<p className="text-sm font-medium">
${performer.sales.toLocaleString()}
</p>
<p className="text-xs text-muted-foreground">
Target: ${performer.target.toLocaleString()}
</p>
</div>
</div>
<Progress value={performer.performance} className="h-2" />
<p className="text-xs text-muted-foreground">
{performer.performance}% of target
</p>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="users" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>User Management</CardTitle>
<div className="flex items-center space-x-2">
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search users..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-8 w-[300px]"
/>
</div>
<Button variant="outline" size="sm">
<Filter className="mr-2 h-4 w-4" />
Filter
</Button>
</div>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead>Status</TableHead>
<TableHead>Last Active</TableHead>
<TableHead className="w-[100px]">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">John Doe</TableCell>
<TableCell>john@example.com</TableCell>
<TableCell>Admin</TableCell>
<TableCell>
<Badge>Active</Badge>
</TableCell>
<TableCell>2 hours ago</TableCell>
<TableCell>
<div className="flex items-center space-x-2">
<Button variant="ghost" size="sm">
<Eye className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm">
<Edit className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" className="text-red-600">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Jane Smith</TableCell>
<TableCell>jane@example.com</TableCell>
<TableCell>Editor</TableCell>
<TableCell>
<Badge variant="secondary">Inactive</Badge>
</TableCell>
<TableCell>1 day ago</TableCell>
<TableCell>
<div className="flex items-center space-x-2">
<Button variant="ghost" size="sm">
<Eye className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm">
<Edit className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" className="text-red-600">
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="analytics" className="space-y-4">
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>User Growth</CardTitle>
</CardHeader>
<CardContent>
<div className="h-[200px] flex items-center justify-center text-muted-foreground">
Chart Component Here
<br />
(Integrate with Chart.js, Recharts, or similar)
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Revenue Trends</CardTitle>
</CardHeader>
<CardContent>
<div className="h-[200px] flex items-center justify-center text-muted-foreground">
Chart Component Here
<br />
(Integrate with Chart.js, Recharts, or similar)
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="reports" className="space-y-4">
<Card>
<CardHeader>
<CardTitle>Generate Reports</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="grid gap-4 md:grid-cols-3">
<Button variant="outline" className="h-24 flex flex-col items-center justify-center">
<Users className="h-8 w-8 mb-2" />
User Report
</Button>
<Button variant="outline" className="h-24 flex flex-col items-center justify-center">
<DollarSign className="h-8 w-8 mb-2" />
Revenue Report
</Button>
<Button variant="outline" className="h-24 flex flex-col items-center justify-center">
<BarChart3 className="h-8 w-8 mb-2" />
Analytics Report
</Button>
</div>
<div className="text-center">
<p className="text-sm text-muted-foreground">
Select a report type to generate detailed insights
</p>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
```
## Layout Component with Sidebar
```typescript
"use client";
import { useState } from "react";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Home,
Users,
BarChart3,
Settings,
FileText,
Shield,
HelpCircle,
LogOut,
ChevronUp,
} from "lucide-react";
const navigationItems = [
{
title: "Overview",
items: [
{ title: "Dashboard", icon: Home, href: "/" },
{ title: "Analytics", icon: BarChart3, href: "/analytics" },
],
},
{
title: "Management",
items: [
{ title: "Users", icon: Users, href: "/users" },
{ title: "Reports", icon: FileText, href: "/reports" },
{ title: "Security", icon: Shield, href: "/security" },
],
},
{
title: "System",
items: [
{ title: "Settings", icon: Settings, href: "/settings" },
{ title: "Help", icon: HelpCircle, href: "/help" },
],
},
];
interface AdminLayoutProps {
children: React.ReactNode;
}
export function AdminLayout({ children }: AdminLayoutProps) {
return (
<SidebarProvider defaultOpen={true}>
<div className="flex min-h-screen">
<Sidebar className="border-r">
<SidebarHeader>
<div className="flex items-center gap-2 px-2 py-1">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
<Home className="h-4 w-4" />
</div>
<span className="font-semibold">Admin Panel</span>
</div>
</SidebarHeader>
<SidebarContent>
{navigationItems.map((section) => (
<SidebarGroup key={section.title}>
<SidebarGroupLabel>{section.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{section.items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.href} className="flex items-center gap-2">
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton className="h-12">
<Avatar className="h-8 w-8">
<AvatarImage src="/avatars/admin.png" />
<AvatarFallback>AD</AvatarFallback>
</Avatar>
<div className="flex flex-col items-start text-sm">
<span className="font-medium">Admin User</span>
<span className="text-xs text-muted-foreground">
admin@example.com
</span>
</div>
<ChevronUp className="ml-auto h-4 w-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuItem>
<Settings className="mr-2 h-4 w-4" />
Account Settings
</DropdownMenuItem>
<DropdownMenuItem>
<HelpCircle className="mr-2 h-4 w-4" />
Support
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-red-600">
<LogOut className="mr-2 h-4 w-4" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
<SidebarRail />
</Sidebar>
<div className="flex-1 flex flex-col">
<header className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="flex h-14 items-center px-4">
<SidebarTrigger className="mr-4" />
<div className="flex-1" />
<div className="flex items-center space-x-2">
<Button variant="outline" size="sm">
Notifications
</Button>
</div>
</div>
</header>
<main className="flex-1">
{children}
</main>
</div>
</div>
</SidebarProvider>
);
}
```
## Key Features
### 1. **Responsive Design**
- Mobile-first approach with collapsible sidebar
- Adaptive grid layouts for different screen sizes
- Touch-friendly interactions on mobile devices
### 2. **Real-time Data**
- Live activity feed with status indicators
- Performance metrics with progress bars
- Automatic data refresh capabilities
### 3. **Advanced Filtering & Search**
- Global search functionality
- Advanced filtering options
- URL-based state management for bookmarkable views
### 4. **Role-based Access Control**
- Different permission levels for different user types
- Conditional UI rendering based on user roles
- Secure routing and API access patterns
### 5. **Data Visualization**
- Integration points for chart libraries
- Progress indicators and status displays
- Trend analysis and reporting features
### 6. **Accessibility Features**
- Full keyboard navigation support
- Screen reader compatibility
- High contrast mode support
- Focus management for modal interactions
This admin dashboard provides a solid foundation for building comprehensive management interfaces with the Backstage Design System, featuring modern React patterns, TypeScript safety, and enterprise-grade functionality.