84 lines
3.4 KiB
TypeScript
84 lines
3.4 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useBillingStore } from '../../store/billingStore';
|
|
import ComponentCard from '../common/ComponentCard';
|
|
|
|
export default function UsageChartWidget() {
|
|
const { usageSummary, loading, loadUsageSummary } = useBillingStore();
|
|
const [dateRange, setDateRange] = useState<'week' | 'month' | 'year'>('month');
|
|
|
|
useEffect(() => {
|
|
const now = new Date();
|
|
let startDate: string;
|
|
|
|
if (dateRange === 'week') {
|
|
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
startDate = weekAgo.toISOString().split('T')[0];
|
|
} else if (dateRange === 'month') {
|
|
startDate = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().split('T')[0];
|
|
} else {
|
|
startDate = new Date(now.getFullYear(), 0, 1).toISOString().split('T')[0];
|
|
}
|
|
|
|
const endDate = now.toISOString().split('T')[0];
|
|
loadUsageSummary(startDate, endDate);
|
|
}, [dateRange, loadUsageSummary]);
|
|
|
|
if (loading && !usageSummary) {
|
|
return (
|
|
<ComponentCard title="Usage Summary" desc="Loading...">
|
|
<div className="animate-pulse">Loading usage data...</div>
|
|
</ComponentCard>
|
|
);
|
|
}
|
|
|
|
if (!usageSummary) return null;
|
|
|
|
return (
|
|
<ComponentCard
|
|
title="Usage Summary"
|
|
desc="Credit usage breakdown by operation and model"
|
|
>
|
|
<div className="space-y-4">
|
|
<div className="flex justify-end items-center mb-4">
|
|
<select
|
|
className="h-9 rounded-lg border border-gray-300 bg-transparent px-3 py-2 text-sm shadow-theme-xs text-gray-800 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
|
value={dateRange}
|
|
onChange={(e) => setDateRange(e.target.value as 'week' | 'month' | 'year')}
|
|
>
|
|
<option value="week">Last 7 Days</option>
|
|
<option value="month">This Month</option>
|
|
<option value="year">This Year</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">Total Credits Used</div>
|
|
<div className="text-2xl font-bold">{usageSummary.total_credits_used}</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">Total Cost</div>
|
|
<div className="text-2xl font-bold">${(Number(usageSummary.total_cost_usd) || 0).toFixed(2)}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h4 className="text-sm font-semibold mb-2">By Operation</h4>
|
|
<div className="space-y-2">
|
|
{usageSummary.by_operation && Object.entries(usageSummary.by_operation).map(([op, stats]) => (
|
|
<div key={op} className="flex justify-between items-center text-sm">
|
|
<span className="capitalize">{op.replace('_', ' ')}</span>
|
|
<span className="font-medium">{stats.credits} credits (${(Number(stats.cost) || 0).toFixed(2)})</span>
|
|
</div>
|
|
))}
|
|
{(!usageSummary.by_operation || Object.keys(usageSummary.by_operation || {}).length === 0) && (
|
|
<div className="text-sm text-gray-500 dark:text-gray-400">No usage data available</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ComponentCard>
|
|
);
|
|
}
|
|
|