import { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; import { Plus, RefreshCw, Wallet, CloudDownload, Loader2 } from 'lucide-react'; import { toast } from 'sonner'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { billingApi } from '@/api'; import { formatCurrency } from './CloudBillingHelpers'; import { StatStrip } from '@/components/ui/stat-strip'; import { BillingRecordDialog } from './BillingRecordDialog'; import { BillingRuleDialog } from './BillingRuleDialog'; import { PageShell } from '@/components/layout/PageShell'; import { OverviewTab } from './cloud-billing/OverviewTab'; import { RecordsTab, RecordsFilterToolbar } from './cloud-billing/RecordsTab'; import { AlertRulesTab } from './cloud-billing/AlertRulesTab'; import type { JsonData } from '@/types/common'; export function CloudBilling() { const { t } = useTranslation(); const confirm = useConfirm(); const [activeTab, setActiveTab] = useState<'overview' | 'records' | 'alerts'>('overview'); const [stats, setStats] = useState(null); const [statsLoading, setStatsLoading] = useState(true); const [records, setRecords] = useState([]); const [recordsLoading, setRecordsLoading] = useState(true); const [filterProvider, setFilterProvider] = useState(''); const [filterStart, setFilterStart] = useState(''); const [filterEnd, setFilterEnd] = useState(''); const [rules, setRules] = useState([]); const [rulesLoading, setRulesLoading] = useState(true); const [recordDialogOpen, setRecordDialogOpen] = useState(false); const [editingRecord, setEditingRecord] = useState(null); const [ruleDialogOpen, setRuleDialogOpen] = useState(false); const [editingRule, setEditingRule] = useState(null); const [syncing, setSyncing] = useState(false); const loadStats = useCallback(async () => { setStatsLoading(true); try { const res = await billingApi.getStats(); setStats(res); } catch (e) { toast.error(t('cloud.loadStatsFailed'), { description: (e as JsonData).message }); } finally { setStatsLoading(false); } }, [t]); const loadRecords = useCallback(async () => { setRecordsLoading(true); try { const params: JsonData = {}; if (filterProvider) params.provider = filterProvider; if (filterStart) params.start_date = filterStart; if (filterEnd) params.end_date = filterEnd; const res = await billingApi.list(params); setRecords(res.list || []); } catch (e) { toast.error(t('cloud.loadBillingFailed'), { description: (e as JsonData).message }); } finally { setRecordsLoading(false); } }, [filterProvider, filterStart, filterEnd, t]); const loadRules = useCallback(async () => { setRulesLoading(true); try { const res = await billingApi.listAlertRules(); setRules(res.list || []); } catch (e) { toast.error(t('cloud.loadAlertRulesFailed'), { description: (e as JsonData).message }); } finally { setRulesLoading(false); } }, [t]); useEffect(() => { loadStats(); }, [loadStats]); useEffect(() => { if (activeTab === 'records') loadRecords(); }, [activeTab, loadRecords]); useEffect(() => { if (activeTab === 'alerts') loadRules(); }, [activeTab, loadRules]); const refreshAll = () => { loadStats(); if (activeTab === 'records') loadRecords(); if (activeTab === 'alerts') loadRules(); }; // 从云账号 AK 同步昨日账单(当前支持阿里云) // Logan.Li const handleSyncYesterday = async () => { setSyncing(true); try { const res = await billingApi.sync(); const errs = res?.errors || []; if (errs.length > 0) { toast.warning(t('cloud.syncBillingPartial'), { description: `${t('cloud.syncBillingResult', { date: res.billing_date, created: res.created, updated: res.updated, })}\n${errs.join('\n')}`, }); } else { toast.success(t('cloud.syncBillingSuccess'), { description: t('cloud.syncBillingResult', { date: res.billing_date, created: res.created, updated: res.updated, }), }); } refreshAll(); } catch (e) { toast.error(t('cloud.syncBillingFailed'), { description: (e as JsonData).message }); } finally { setSyncing(false); } }; const openAddRecord = () => { setEditingRecord(null); setRecordDialogOpen(true); }; const openEditRecord = (r: JsonData) => { setEditingRecord(r); setRecordDialogOpen(true); }; const openAddRule = () => { setEditingRule(null); setRuleDialogOpen(true); }; const openEditRule = (r: JsonData) => { setEditingRule(r); setRuleDialogOpen(true); }; const handleDeleteRecord = async (id: number) => { const ok = await confirm({ title: t('cloud.deleteBillingRecord'), description: t('cloud.deleteBillingConfirm'), variant: 'destructive' }); if (!ok) return; try { await billingApi.remove(id); loadRecords(); loadStats(); toast(t('common.deleteSuccess')); } catch (e) { toast.error(t('common.deleteFailed'), { description: (e as JsonData).message }); } }; const handleDeleteRule = async (id: number) => { const ok = await confirm({ title: t('cloud.deleteAlertRule'), description: t('cloud.deleteAlertRuleConfirm'), variant: 'destructive' }); if (!ok) return; try { await billingApi.deleteAlertRule(id); loadRules(); toast(t('common.deleteSuccess')); } catch (e) { toast.error(t('common.deleteFailed'), { description: (e as JsonData).message }); } }; const handleToggleRule = async (id: number) => { try { await billingApi.toggleAlertRule(id); loadRules(); loadStats(); } catch (e) { toast.error(t('cloud.operationFailed'), { description: (e as JsonData).message }); } }; const pageTabs = [ { id: 'overview', label: t('cloud.overviewStats') }, { id: 'records', label: t('cloud.costRecords') }, { id: 'alerts', label: t('cloud.billingAlert'), badge: rules.length > 0 ? rules.length : undefined }, ]; return ( } tabs={pageTabs} activeTab={activeTab} onTabChange={(id) => setActiveTab(id as typeof activeTab)} toolbar={activeTab === 'records' ? ( ) : undefined} metrics={ stats && !statsLoading ? ( 0 ? 'red' : 'muted' }, ]} /> ) : undefined } actions={( <> {activeTab === 'records' && ( )} {activeTab === 'alerts' && ( )} )} > {/* Tab 1:概览统计 */} {activeTab === 'overview' && ( )} {/* Tab 2:成本记录 */} {activeTab === 'records' && ( )} {/* Tab 3:预警规则 */} {activeTab === 'alerts' && ( )} { loadRecords(); loadStats(); }} /> { loadRules(); loadStats(); }} /> ); }