Refactored the admin dashboard to use tabbed navigation for analytics and management. Enhanced AdminAnalytics with Recharts visualizations, added top vendors by revenue, and improved chart tooltips. Removed unused columns from vendor table. Updated layout and notification context to exclude admin pages from dashboard-specific UI and notifications. Minor debug logging added to SystemStatusCard.
182 lines
6.8 KiB
TypeScript
182 lines
6.8 KiB
TypeScript
import React from "react";
|
|
import { Card, CardContent, CardDescription, 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 { Search, MoreHorizontal, UserCheck, UserX, Mail } from "lucide-react";
|
|
import { fetchServer } from "@/lib/api";
|
|
|
|
interface Vendor {
|
|
_id: string;
|
|
username: string;
|
|
email?: string;
|
|
storeId?: string;
|
|
pgpKey?: string;
|
|
createdAt?: string;
|
|
currentToken?: string;
|
|
isAdmin?: boolean;
|
|
}
|
|
|
|
export default async function AdminVendorsPage() {
|
|
let vendors: Vendor[] = [];
|
|
let error: string | null = null;
|
|
|
|
try {
|
|
vendors = await fetchServer<Vendor[]>("/admin/vendors");
|
|
} catch (err) {
|
|
console.error("Failed to fetch vendors:", err);
|
|
error = "Failed to load vendors";
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold tracking-tight">All Vendors</h1>
|
|
<p className="text-sm text-muted-foreground mt-1">Manage vendor accounts and permissions</p>
|
|
</div>
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="text-center text-red-500">
|
|
<p>{error}</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const activeVendors = vendors.filter(v => v.currentToken);
|
|
const adminVendors = vendors.filter(v => v.isAdmin);
|
|
const suspendedVendors = vendors.filter(v => !v.currentToken);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold tracking-tight">All Vendors</h1>
|
|
<p className="text-sm text-muted-foreground mt-1">Manage vendor accounts and permissions</p>
|
|
</div>
|
|
|
|
{/* Stats Cards */}
|
|
<div className="grid gap-4 md: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 Vendors</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{vendors.length}</div>
|
|
<p className="text-xs text-muted-foreground">Registered vendors</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 Vendors</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{activeVendors.length}</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
{vendors.length > 0 ? Math.round((activeVendors.length / vendors.length) * 100) : 0}% 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">Suspended</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{suspendedVendors.length}</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
{vendors.length > 0 ? Math.round((suspendedVendors.length / vendors.length) * 100) : 0}% suspension 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">Admin Users</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{adminVendors.length}</div>
|
|
<p className="text-xs text-muted-foreground">Administrative access</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Search and Filters */}
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<CardTitle>Vendor Management</CardTitle>
|
|
<CardDescription>View and manage all vendor accounts</CardDescription>
|
|
</div>
|
|
<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 vendors..." className="pl-8 w-64" />
|
|
</div>
|
|
<Button variant="outline" size="sm">
|
|
<Mail className="h-4 w-4 mr-2" />
|
|
Send Message
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Vendor</TableHead>
|
|
<TableHead>Store</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead>Join Date</TableHead>
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{vendors.map((vendor) => (
|
|
<TableRow key={vendor._id}>
|
|
<TableCell>
|
|
<div className="font-medium">{vendor.username}</div>
|
|
</TableCell>
|
|
<TableCell>{vendor.storeId || 'No store'}</TableCell>
|
|
<TableCell>
|
|
<div className="flex flex-col space-y-1">
|
|
<Badge
|
|
variant={vendor.currentToken ? "default" : "destructive"}
|
|
>
|
|
{vendor.currentToken ? "active" : "suspended"}
|
|
</Badge>
|
|
{vendor.isAdmin && (
|
|
<Badge variant="secondary" className="text-xs">
|
|
Admin
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
{vendor.createdAt ? new Date(vendor.createdAt).toLocaleDateString() : 'N/A'}
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
<div className="flex items-center justify-end space-x-2">
|
|
<Button variant="outline" size="sm">
|
|
<UserCheck className="h-4 w-4" />
|
|
</Button>
|
|
<Button variant="outline" size="sm">
|
|
<UserX className="h-4 w-4" />
|
|
</Button>
|
|
<Button variant="outline" size="sm">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|