ERP-node/frontend/app/(main)/admin/aiAssistant/dashboard/page.tsx

191 lines
7.8 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect } from "react";
import { getAiAssistantAuth, aiAssistantApi } from "@/lib/api/aiAssistant";
import type { UsageSummary, ApiKeyItem, AdminStats } from "@/lib/api/aiAssistant";
import { BarChart3, Key, Zap, TrendingUp, Loader2, AlertCircle, Users, Cpu } from "lucide-react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Progress } from "@/components/ui/progress";
import { toast } from "sonner";
export default function AiAssistantDashboardPage() {
const auth = getAiAssistantAuth();
const user = auth?.user;
const isAdmin = user?.role === "admin";
const [loading, setLoading] = useState(true);
const [usage, setUsage] = useState<UsageSummary | null>(null);
const [apiKeys, setApiKeys] = useState<ApiKeyItem[]>([]);
const [stats, setStats] = useState<AdminStats | null>(null);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
setLoading(true);
try {
const usageRes = await aiAssistantApi.get("/usage");
setUsage(usageRes.data?.data ?? null);
const keysRes = await aiAssistantApi.get("/api-keys");
setApiKeys(keysRes.data?.data ?? []);
if (isAdmin) {
const statsRes = await aiAssistantApi.get("/admin/stats");
setStats(statsRes.data?.data ?? null);
}
} catch {
toast.error("데이터를 불러오는데 실패했습니다.");
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="flex h-64 items-center justify-center">
<Loader2 className="text-primary h-8 w-8 animate-spin" />
</div>
);
}
const monthlyTokens = usage?.usage?.monthly?.totalTokens ?? 0;
const monthlyLimit = usage?.limit?.monthly ?? 0;
const usagePercent = monthlyLimit > 0 ? Math.round((monthlyTokens / monthlyLimit) * 100) : 0;
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight"></h1>
<p className="text-muted-foreground mt-1">, {user?.name || user?.email}!</p>
</div>
<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"> </CardTitle>
<Zap className="text-muted-foreground h-4 w-4" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{(usage?.usage?.today?.tokens ?? 0).toLocaleString()}
</div>
<p className="text-muted-foreground text-xs"></p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"> </CardTitle>
<BarChart3 className="text-muted-foreground h-4 w-4" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{monthlyTokens.toLocaleString()}</div>
<p className="text-muted-foreground mb-2 text-xs">
/ {monthlyLimit.toLocaleString()}
</p>
<Progress value={usagePercent} className="h-2" />
<p className="text-muted-foreground mt-1 text-right text-xs">{usagePercent}% </p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"> </CardTitle>
<TrendingUp className="text-muted-foreground h-4 w-4" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{(usage?.usage?.today?.requests ?? 0).toLocaleString()}
</div>
<p className="text-muted-foreground text-xs"></p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"> API </CardTitle>
<Key className="text-muted-foreground h-4 w-4" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{apiKeys.filter((k) => k.status === "active").length}
</div>
<p className="text-muted-foreground text-xs"></p>
</CardContent>
</Card>
</div>
{isAdmin && stats && (
<Card className="bg-gradient-to-r from-primary to-primary/80 text-primary-foreground">
<CardHeader>
<CardTitle> </CardTitle>
<CardDescription className="text-primary-foreground/70"> </CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
<div className="space-y-1">
<div className="flex items-center gap-2">
<Users className="h-4 w-4" />
<span className="text-sm opacity-80"> </span>
</div>
<p className="text-2xl font-bold">{stats.users?.total ?? 0}</p>
</div>
<div className="space-y-1">
<div className="flex items-center gap-2">
<Users className="h-4 w-4" />
<span className="text-sm opacity-80"> </span>
</div>
<p className="text-2xl font-bold">{stats.users?.active ?? 0}</p>
</div>
<div className="space-y-1">
<div className="flex items-center gap-2">
<Key className="h-4 w-4" />
<span className="text-sm opacity-80"> API </span>
</div>
<p className="text-2xl font-bold">{stats.apiKeys?.total ?? 0}</p>
</div>
<div className="space-y-1">
<div className="flex items-center gap-2">
<Cpu className="h-4 w-4" />
<span className="text-sm opacity-80"> </span>
</div>
<p className="text-2xl font-bold">{stats.providers?.active ?? 0}</p>
</div>
</div>
</CardContent>
</Card>
)}
<Card>
<CardHeader>
<CardTitle> API </CardTitle>
<CardDescription> API </CardDescription>
</CardHeader>
<CardContent>
{apiKeys.length === 0 ? (
<div className="flex flex-col items-center justify-center py-8 text-center">
<AlertCircle className="text-muted-foreground mb-3 h-10 w-10" />
<p className="text-muted-foreground">API .</p>
<p className="text-muted-foreground text-sm"> .</p>
</div>
) : (
<div className="space-y-3">
{apiKeys.slice(0, 5).map((key) => (
<div
key={key.id}
className="bg-card flex items-center justify-between rounded-lg border p-3"
>
<div>
<p className="font-medium">{key.name}</p>
<p className="text-muted-foreground font-mono text-sm">{key.keyPrefix}...</p>
</div>
<Badge variant={key.status === "active" ? "success" : "secondary"}>
{key.status === "active" ? "활성" : "비활성"}
</Badge>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
);
}