fina autoamtiona adn billing and credits

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-04 15:54:15 +00:00
parent f8a9293196
commit 40dfe20ead
40 changed files with 5680 additions and 18 deletions

View File

@@ -0,0 +1,470 @@
/**
* Admin Billing Management Page
* Admin-only interface for managing credits, billing, and user accounts
*/
import React, { useState, useEffect } from 'react';
import PageMeta from '../../components/common/PageMeta';
import ComponentCard from '../../components/common/ComponentCard';
import EnhancedMetricCard from '../../components/dashboard/EnhancedMetricCard';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import Button from '../../components/ui/button/Button';
import Badge from '../../components/ui/badge/Badge';
import {
BoltIcon,
UserIcon,
DollarLineIcon,
PlugInIcon,
CheckCircleIcon,
TimeIcon
} from '../../icons';
interface UserAccount {
id: number;
username: string;
email: string;
credits: number;
subscription_plan: string;
is_active: boolean;
date_joined: string;
}
interface CreditCostConfig {
id: number;
model_name: string;
operation_type: string;
cost: number;
is_active: boolean;
created_at: string;
}
interface SystemStats {
total_users: number;
active_users: number;
total_credits_issued: number;
total_credits_used: number;
}
const AdminBilling: React.FC = () => {
const toast = useToast();
const [stats, setStats] = useState<SystemStats | null>(null);
const [users, setUsers] = useState<UserAccount[]>([]);
const [creditConfigs, setCreditConfigs] = useState<CreditCostConfig[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'overview' | 'users' | 'pricing'>('overview');
const [searchTerm, setSearchTerm] = useState('');
const [selectedUser, setSelectedUser] = useState<UserAccount | null>(null);
const [creditAmount, setCreditAmount] = useState('');
const [adjustmentReason, setAdjustmentReason] = useState('');
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
setLoading(true);
const [statsData, usersData, configsData] = await Promise.all([
fetchAPI('/v1/admin/billing/stats/'),
fetchAPI('/v1/admin/users/?limit=100'),
fetchAPI('/v1/admin/credit-costs/'),
]);
setStats(statsData);
setUsers(usersData.results || []);
setCreditConfigs(configsData.results || []);
} catch (error: any) {
toast?.error(error?.message || 'Failed to load admin data');
} finally {
setLoading(false);
}
};
const handleAdjustCredits = async () => {
if (!selectedUser || !creditAmount) {
toast?.error('Please select a user and enter amount');
return;
}
try {
await fetchAPI(`/v1/admin/users/${selectedUser.id}/adjust-credits/`, {
method: 'POST',
body: JSON.stringify({
amount: parseInt(creditAmount),
reason: adjustmentReason || 'Admin adjustment',
}),
});
toast?.success(`Credits adjusted for ${selectedUser.username}`);
setCreditAmount('');
setAdjustmentReason('');
setSelectedUser(null);
loadData();
} catch (error: any) {
toast?.error(error?.message || 'Failed to adjust credits');
}
};
const handleUpdateCreditCost = async (configId: number, newCost: number) => {
try {
await fetchAPI(`/v1/admin/credit-costs/${configId}/`, {
method: 'PATCH',
body: JSON.stringify({ cost: newCost }),
});
toast?.success('Credit cost updated successfully');
loadData();
} catch (error: any) {
toast?.error(error?.message || 'Failed to update credit cost');
}
};
const filteredUsers = users.filter(user =>
user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.email.toLowerCase().includes(searchTerm.toLowerCase())
);
if (loading) {
return (
<div className="p-6">
<PageMeta title="Admin - Billing Management" description="Manage billing and credits" />
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
<p className="mt-4 text-gray-600 dark:text-gray-400">Loading admin data...</p>
</div>
</div>
);
}
return (
<div className="p-6">
<PageMeta title="Admin - Billing Management" description="Manage billing and credits" />
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Billing Management</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Admin controls for credits, pricing, and user billing
</p>
</div>
<a
href="/admin/igny8_core/"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700"
>
<PlugInIcon className="w-4 h-4 mr-2" />
Django Admin
</a>
</div>
{/* System Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<EnhancedMetricCard
title="Total Users"
value={stats?.total_users || 0}
icon={UserIcon}
color="blue"
iconColor="text-blue-500"
/>
<EnhancedMetricCard
title="Active Users"
value={stats?.active_users || 0}
icon={CheckCircleIcon}
color="green"
iconColor="text-green-500"
/>
<EnhancedMetricCard
title="Credits Issued"
value={stats?.total_credits_issued || 0}
icon={DollarLineIcon}
color="amber"
iconColor="text-amber-500"
/>
<EnhancedMetricCard
title="Credits Used"
value={stats?.total_credits_used || 0}
icon={BoltIcon}
color="purple"
iconColor="text-purple-500"
/>
</div>
{/* Tabs */}
<div className="mb-6 border-b border-gray-200 dark:border-gray-700">
<nav className="-mb-px flex space-x-8">
<button
onClick={() => setActiveTab('overview')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'overview'
? 'border-primary-500 text-primary-600 dark:text-primary-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
Overview
</button>
<button
onClick={() => setActiveTab('users')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'users'
? 'border-primary-500 text-primary-600 dark:text-primary-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
User Management ({users.length})
</button>
<button
onClick={() => setActiveTab('pricing')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'pricing'
? 'border-primary-500 text-primary-600 dark:text-primary-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
Credit Pricing ({creditConfigs.length})
</button>
</nav>
</div>
{/* Tab Content */}
{activeTab === 'overview' && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<ComponentCard title="Quick Actions">
<div className="space-y-3">
<Button
variant="primary"
fullWidth
onClick={() => setActiveTab('users')}
>
<UserIcon className="w-4 h-4 mr-2" />
Manage User Credits
</Button>
<Button
variant="secondary"
fullWidth
onClick={() => setActiveTab('pricing')}
>
<DollarLineIcon className="w-4 h-4 mr-2" />
Update Credit Costs
</Button>
<Button
variant="outline"
fullWidth
onClick={() => window.open('/admin/igny8_core/creditcostconfig/', '_blank')}
>
<PlugInIcon className="w-4 h-4 mr-2" />
Full Admin Panel
</Button>
</div>
</ComponentCard>
<ComponentCard title="Recent Activity">
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
Activity log coming soon
</div>
</ComponentCard>
</div>
)}
{activeTab === 'users' && (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<ComponentCard title="User Accounts">
<div className="mb-4">
<input
type="text"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="Search by username or email..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
User
</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Plan
</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Credits
</th>
<th className="px-4 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
{filteredUsers.map((user) => (
<tr key={user.id}>
<td className="px-4 py-4">
<div className="text-sm font-medium text-gray-900 dark:text-white">
{user.username}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{user.email}
</div>
</td>
<td className="px-4 py-4 whitespace-nowrap">
<Badge variant="info">{user.subscription_plan || 'Free'}</Badge>
</td>
<td className="px-4 py-4 whitespace-nowrap text-right font-bold text-amber-600 dark:text-amber-400">
{user.credits}
</td>
<td className="px-4 py-4 whitespace-nowrap text-center">
<Button
variant="outline"
size="sm"
onClick={() => setSelectedUser(user)}
>
Adjust
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</ComponentCard>
</div>
<div>
<ComponentCard title="Adjust Credits">
{selectedUser ? (
<div className="space-y-4">
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<div className="text-sm font-medium text-gray-900 dark:text-white">
{selectedUser.username}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400">
Current: {selectedUser.credits} credits
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Amount</label>
<input
type="number"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="Enter credits (use - for deduction)"
value={creditAmount}
onChange={(e) => setCreditAmount(e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Reason</label>
<input
type="text"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="e.g., Bonus credits, Refund, etc."
value={adjustmentReason}
onChange={(e) => setAdjustmentReason(e.target.value)}
/>
</div>
<div className="flex gap-2">
<Button
variant="primary"
fullWidth
onClick={handleAdjustCredits}
>
Apply Adjustment
</Button>
<Button
variant="outline"
onClick={() => {
setSelectedUser(null);
setCreditAmount('');
setAdjustmentReason('');
}}
>
Cancel
</Button>
</div>
</div>
) : (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
Select a user to adjust credits
</div>
)}
</ComponentCard>
</div>
</div>
)}
{activeTab === 'pricing' && (
<ComponentCard title="Credit Cost Configuration">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Model
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Operation
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Cost (Credits)
</th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Status
</th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
{creditConfigs.map((config) => (
<tr key={config.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
{config.model_name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
{config.operation_type}
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-bold text-amber-600 dark:text-amber-400">
{config.cost}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
<Badge variant={config.is_active ? 'success' : 'warning'}>
{config.is_active ? 'Active' : 'Inactive'}
</Badge>
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
<Button
variant="outline"
size="sm"
onClick={() => window.open(`/admin/igny8_core/creditcostconfig/${config.id}/change/`, '_blank')}
>
Edit
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mt-4 text-sm text-gray-600 dark:text-gray-400">
To add new credit costs or modify these settings, use the{' '}
<a
href="/admin/igny8_core/creditcostconfig/"
target="_blank"
rel="noopener noreferrer"
className="text-primary-600 dark:text-primary-400 hover:underline"
>
Django Admin Panel
</a>
</div>
</ComponentCard>
)}
</div>
);
};
export default AdminBilling;

View File

@@ -17,6 +17,7 @@ import {
import ActivityLog from '../../components/Automation/ActivityLog';
import ConfigModal from '../../components/Automation/ConfigModal';
import RunHistory from '../../components/Automation/RunHistory';
import CurrentProcessingCard from '../../components/Automation/CurrentProcessingCard';
import PageMeta from '../../components/common/PageMeta';
import ComponentCard from '../../components/common/ComponentCard';
import EnhancedMetricCard from '../../components/dashboard/EnhancedMetricCard';
@@ -600,6 +601,22 @@ const AutomationPage: React.FC = () => {
</div>
</div>
{/* Current Processing Card - Shows real-time automation progress */}
{currentRun && (currentRun.status === 'running' || currentRun.status === 'paused') && activeSite && (
<CurrentProcessingCard
runId={currentRun.run_id}
siteId={activeSite.id}
currentRun={currentRun}
onUpdate={() => {
// Refresh current run status
loadCurrentRun();
}}
onClose={() => {
// Card will remain in DOM but user acknowledged it
// Can add state here to minimize it if needed
}}
/>
)}
{/* Pipeline Stages */}
<ComponentCard>

View File

@@ -0,0 +1,362 @@
/**
* Credits & Billing Page
* User-facing credits usage, transactions, and billing information
*/
import React, { useState, useEffect } from 'react';
import PageMeta from '../../components/common/PageMeta';
import ComponentCard from '../../components/common/ComponentCard';
import EnhancedMetricCard from '../../components/dashboard/EnhancedMetricCard';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import Button from '../../components/ui/button/Button';
import Badge from '../../components/ui/badge/Badge';
import {
BoltIcon,
DollarLineIcon,
ClockIcon,
CheckCircleIcon
} from '../../icons';
interface CreditTransaction {
id: number;
transaction_type: string;
amount: number;
balance_after: number;
description: string;
created_at: string;
}
interface CreditUsageLog {
id: number;
operation_type: string;
credits_used: number;
model_used: string;
created_at: string;
metadata: any;
}
interface AccountBalance {
credits: number;
subscription_plan: string;
monthly_credits_included: number;
bonus_credits: number;
}
const CreditsAndBilling: React.FC = () => {
const toast = useToast();
const [balance, setBalance] = useState<AccountBalance | null>(null);
const [transactions, setTransactions] = useState<CreditTransaction[]>([]);
const [usageLogs, setUsageLogs] = useState<CreditUsageLog[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'overview' | 'transactions' | 'usage'>('overview');
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
setLoading(true);
const [balanceData, transactionsData, usageData] = await Promise.all([
fetchAPI('/v1/billing/account_balance/'),
fetchAPI('/v1/billing/transactions/?limit=50'),
fetchAPI('/v1/billing/usage/?limit=50'),
]);
setBalance(balanceData);
setTransactions(transactionsData.results || []);
setUsageLogs(usageData.results || []);
} catch (error: any) {
toast?.error(error?.message || 'Failed to load billing data');
} finally {
setLoading(false);
}
};
const getTransactionTypeColor = (type: string) => {
switch (type) {
case 'purchase': return 'success';
case 'grant': return 'info';
case 'deduction': return 'warning';
case 'refund': return 'primary';
case 'adjustment': return 'secondary';
default: return 'default';
}
};
const formatOperationType = (type: string) => {
return type.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
};
if (loading) {
return (
<div className="p-6">
<PageMeta title="Credits & Billing" description="Manage your credits and billing" />
<div className="text-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
<p className="mt-4 text-gray-600 dark:text-gray-400">Loading billing data...</p>
</div>
</div>
);
}
return (
<div className="p-6">
<PageMeta title="Credits & Billing" description="Manage your credits and billing" />
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Credits & Billing</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Manage your credits, view transactions, and monitor usage
</p>
</div>
<Button variant="primary" onClick={() => {
// TODO: Link to purchase credits page
toast?.info('Purchase credits feature coming soon');
}}>
<DollarLineIcon className="w-4 h-4 mr-2" />
Purchase Credits
</Button>
</div>
{/* Credit Balance Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<EnhancedMetricCard
title="Current Balance"
value={balance?.credits || 0}
icon={BoltIcon}
color="amber"
iconColor="text-amber-500"
/>
<EnhancedMetricCard
title="Monthly Included"
value={balance?.monthly_credits_included || 0}
subtitle={balance?.subscription_plan || 'Free'}
icon={CheckCircleIcon}
color="green"
iconColor="text-green-500"
/>
<EnhancedMetricCard
title="Bonus Credits"
value={balance?.bonus_credits || 0}
icon={DollarLineIcon}
color="blue"
iconColor="text-blue-500"
/>
<EnhancedMetricCard
title="Total This Month"
value={usageLogs.reduce((sum, log) => sum + log.credits_used, 0)}
icon={ClockIcon}
color="purple"
iconColor="text-purple-500"
/>
</div>
{/* Tabs */}
<div className="mb-6 border-b border-gray-200 dark:border-gray-700">
<nav className="-mb-px flex space-x-8">
<button
onClick={() => setActiveTab('overview')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'overview'
? 'border-primary-500 text-primary-600 dark:text-primary-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
Overview
</button>
<button
onClick={() => setActiveTab('transactions')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'transactions'
? 'border-primary-500 text-primary-600 dark:text-primary-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
Transactions ({transactions.length})
</button>
<button
onClick={() => setActiveTab('usage')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'usage'
? 'border-primary-500 text-primary-600 dark:text-primary-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
Usage History ({usageLogs.length})
</button>
</nav>
</div>
{/* Tab Content */}
{activeTab === 'overview' && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Recent Transactions */}
<ComponentCard title="Recent Transactions">
<div className="space-y-3">
{transactions.slice(0, 5).map((transaction) => (
<div key={transaction.id} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex-1">
<div className="flex items-center gap-2">
<Badge variant={getTransactionTypeColor(transaction.transaction_type)}>
{transaction.transaction_type}
</Badge>
<span className="text-sm text-gray-900 dark:text-white">
{transaction.description}
</span>
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{new Date(transaction.created_at).toLocaleString()}
</div>
</div>
<div className="text-right">
<div className={`font-bold ${transaction.amount > 0 ? 'text-green-600' : 'text-red-600'}`}>
{transaction.amount > 0 ? '+' : ''}{transaction.amount}
</div>
<div className="text-xs text-gray-500">
Balance: {transaction.balance_after}
</div>
</div>
</div>
))}
{transactions.length === 0 && (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
No transactions yet
</div>
)}
</div>
</ComponentCard>
{/* Recent Usage */}
<ComponentCard title="Recent Usage">
<div className="space-y-3">
{usageLogs.slice(0, 5).map((log) => (
<div key={log.id} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex-1">
<div className="text-sm font-medium text-gray-900 dark:text-white">
{formatOperationType(log.operation_type)}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{log.model_used} {new Date(log.created_at).toLocaleString()}
</div>
</div>
<div className="text-right">
<div className="font-bold text-amber-600 dark:text-amber-400">
{log.credits_used} credits
</div>
</div>
</div>
))}
{usageLogs.length === 0 && (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
No usage history yet
</div>
)}
</div>
</ComponentCard>
</div>
)}
{activeTab === 'transactions' && (
<ComponentCard title="All Transactions">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Date
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Type
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Description
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Amount
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Balance
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
{transactions.map((transaction) => (
<tr key={transaction.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
{new Date(transaction.created_at).toLocaleDateString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<Badge variant={getTransactionTypeColor(transaction.transaction_type)}>
{transaction.transaction_type}
</Badge>
</td>
<td className="px-6 py-4 text-sm text-gray-900 dark:text-white">
{transaction.description}
</td>
<td className={`px-6 py-4 whitespace-nowrap text-sm text-right font-bold ${
transaction.amount > 0 ? 'text-green-600' : 'text-red-600'
}`}>
{transaction.amount > 0 ? '+' : ''}{transaction.amount}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white text-right">
{transaction.balance_after}
</td>
</tr>
))}
</tbody>
</table>
</div>
</ComponentCard>
)}
{activeTab === 'usage' && (
<ComponentCard title="Usage History">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Date
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Operation
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Model
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Credits
</th>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
{usageLogs.map((log) => (
<tr key={log.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
{new Date(log.created_at).toLocaleString()}
</td>
<td className="px-6 py-4 text-sm text-gray-900 dark:text-white">
{formatOperationType(log.operation_type)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
{log.model_used || 'N/A'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-right font-bold text-amber-600 dark:text-amber-400">
{log.credits_used}
</td>
</tr>
))}
</tbody>
</table>
</div>
</ComponentCard>
)}
</div>
);
};
export default CreditsAndBilling;