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

196 lines
7.6 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { aiAssistantApi } from "@/lib/api/aiAssistant";
import type { UsageSummary } from "@/lib/api/aiAssistant";
import { BarChart3, Calendar, Loader2, TrendingUp, Zap, DollarSign } from "lucide-react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { toast } from "sonner";
interface DailyUsageItem {
date?: string;
totalTokens?: number;
requestCount?: number;
}
export default function AiAssistantUsagePage() {
const [loading, setLoading] = useState(true);
const [usage, setUsage] = useState<UsageSummary | null>(null);
const [dailyUsage, setDailyUsage] = useState<DailyUsageItem[]>([]);
const [period, setPeriod] = useState("7");
useEffect(() => {
loadUsage();
}, [period]);
const loadUsage = async () => {
setLoading(true);
try {
const [usageRes, dailyRes] = await Promise.all([
aiAssistantApi.get("/usage"),
aiAssistantApi.get(`/usage/daily?days=${period}`),
]);
setUsage(usageRes.data?.data ?? null);
setDailyUsage((dailyRes.data?.data as { usage?: DailyUsageItem[] })?.usage ?? []);
} 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 todayTokens = usage?.usage?.today?.tokens ?? 0;
const todayRequests = usage?.usage?.today?.requests ?? 0;
const monthlyTokens = usage?.usage?.monthly?.totalTokens ?? 0;
const monthlyCost = usage?.usage?.monthly?.totalCost ?? 0;
const monthlyLimit = usage?.limit?.monthly ?? 0;
const usagePercent = monthlyLimit > 0 ? Math.round((monthlyTokens / monthlyLimit) * 100) : 0;
const maxTokens = Math.max(...dailyUsage.map((d) => d.totalTokens ?? 0), 1);
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight"></h1>
<p className="text-muted-foreground mt-1">API .</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">{todayTokens.toLocaleString()}</div>
</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">{todayRequests.toLocaleString()}</div>
</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>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"> </CardTitle>
<DollarSign className="text-muted-foreground h-4 w-4" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">${monthlyCost.toFixed(4)}</div>
</CardContent>
</Card>
</div>
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle> </CardTitle>
<CardDescription> </CardDescription>
</div>
<Select value={period} onValueChange={setPeriod}>
<SelectTrigger className="w-[140px]">
<Calendar className="mr-2 h-4 w-4" />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="7"> 7</SelectItem>
<SelectItem value="14"> 14</SelectItem>
<SelectItem value="30"> 30</SelectItem>
</SelectContent>
</Select>
</CardHeader>
<CardContent>
{dailyUsage.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<BarChart3 className="text-muted-foreground mb-4 h-12 w-12" />
<p className="text-muted-foreground"> .</p>
</div>
) : (
<div className="space-y-3">
{dailyUsage.map((day, idx) => (
<div key={day.date ?? idx} className="flex items-center gap-4">
<div className="text-muted-foreground w-20 text-sm">
{day.date
? new Date(day.date).toLocaleDateString("ko-KR", {
month: "short",
day: "numeric",
})
: "-"}
</div>
<div className="flex-1">
<div className="bg-muted h-8 overflow-hidden rounded-lg">
<div
className="bg-primary h-full rounded-lg transition-all duration-500"
style={{
width: `${((day.totalTokens ?? 0) / maxTokens) * 100}%`,
}}
/>
</div>
</div>
<div className="w-28 text-right">
<span className="text-sm font-medium">
{(day.totalTokens ?? 0).toLocaleString()}
</span>
<span className="text-muted-foreground ml-1 text-xs"></span>
</div>
<div className="text-muted-foreground w-16 text-right text-sm">
{day.requestCount ?? 0}
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-primary to-primary/80 text-primary-foreground">
<CardContent className="pt-6">
<div className="mb-4 flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold">
: {(usage?.plan ?? "FREE").toUpperCase()}
</h3>
<p className="text-primary-foreground/70">
: {monthlyLimit > 0 ? monthlyLimit.toLocaleString() : "무제한"}
</p>
</div>
<div className="text-right">
<p className="text-3xl font-bold">{usagePercent}%</p>
<p className="text-primary-foreground/70 text-sm"></p>
</div>
</div>
<Progress value={usagePercent} className="bg-primary-foreground/20 h-3" />
</CardContent>
</Card>
</div>
);
}