// 云账号详情侧栏 — 概览 / 区域 / 账单 // Logan.Li import { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from '@/components/ui/sheet'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { CheckCircle2, CloudDownload, Loader2, MapPin, RefreshCw, Wallet, XCircle, } from 'lucide-react'; import { toast } from 'sonner'; import { cloudApi, billingApi } from '@/api'; import { CloudBrandIcon, getProviderColor } from '@/components/icons/CloudBrandIcon'; import type { JsonData } from '@/types/common'; interface CloudAccountDetailSheetProps { account: JsonData | null; open: boolean; onOpenChange: (open: boolean) => void; providerLabels: Record; onSyncRegions?: () => void; onSyncBilling?: () => void; onImportHosts?: () => void; } export function CloudAccountDetailSheet({ account, open, onOpenChange, providerLabels, onSyncRegions, onSyncBilling, onImportHosts, }: CloudAccountDetailSheetProps) { const { t } = useTranslation(); const [tab, setTab] = useState('overview'); const [regions, setRegions] = useState([]); const [regionsLoading, setRegionsLoading] = useState(false); const [syncingRegions, setSyncingRegions] = useState(false); const [billings, setBillings] = useState([]); const [billingsLoading, setBillingsLoading] = useState(false); const [syncingBilling, setSyncingBilling] = useState(false); const accountId = account?.id as number | string | undefined; const loadRegions = useCallback(async () => { if (!accountId) return; setRegionsLoading(true); try { const res = await cloudApi.getAccountRegions(accountId); setRegions(res?.list || []); } catch { setRegions([]); } finally { setRegionsLoading(false); } }, [accountId]); const loadBillings = useCallback(async () => { if (!accountId) return; setBillingsLoading(true); try { const monthStart = new Date().toISOString().slice(0, 7) + '-01'; const res = await billingApi.list({ cloud_account_id: Number(accountId), start_date: monthStart, page_size: 20, }); setBillings(res?.list || []); } catch { setBillings([]); } finally { setBillingsLoading(false); } }, [accountId]); useEffect(() => { if (!open || !accountId) return; setTab('overview'); loadRegions(); loadBillings(); }, [open, accountId, loadRegions, loadBillings]); const handleSyncRegions = async () => { if (!accountId) return; setSyncingRegions(true); try { await cloudApi.syncRegions(accountId); await loadRegions(); onSyncRegions?.(); toast.success(t('cloud.syncRegionsSuccess')); } catch (e) { toast.error(t('cloud.syncRegionsFailed'), { description: (e as JsonData).message }); } finally { setSyncingRegions(false); } }; const handleSyncBilling = async () => { if (!accountId) return; setSyncingBilling(true); try { const res = await cloudApi.syncBilling(accountId); await loadBillings(); onSyncBilling?.(); const errs = res?.errors || []; if (errs.length > 0) { toast.warning(t('cloud.syncBillingPartial'), { description: errs.join('\n') }); } else { toast.success(t('cloud.syncBillingSuccess'), { description: t('cloud.syncBillingResult', { date: res.billing_date, created: res.created, updated: res.updated, }), }); } } catch (e) { toast.error(t('cloud.syncBillingFailed'), { description: (e as JsonData).message }); } finally { setSyncingBilling(false); } }; if (!account) return null; const provider = String(account.provider || ''); const serverCount = account.server_count ?? account.serverCount ?? 0; const regionCount = account.region_count ?? account.regionCount ?? regions.length; const monthlyCost = account.monthly_cost ?? account.monthlyCost ?? 0; const lastSync = account.last_sync_at || account.lastSyncAt || account.last_sync; return (
{account.name} {providerLabels[provider] || provider} {account.status === 'active' ? ( {t('cloud.normal')} ) : account.status === 'inactive' ? ( {t('cloud.stopped')} ) : null}
{t('cloud.tabOverview')} {t('cloud.tabRegions')} {t('cloud.tabBilling')}

{t('cloud.monthlyFee')}

¥{Number(monthlyCost).toLocaleString()}

{t('cloud.serverCount')}

{serverCount}

{t('cloud.regionCount')}

{regionCount}

{lastSync && (

{t('cloud.syncTime')}: {new Date(lastSync).toLocaleString()}

)}
{onImportHosts && ( )}
{t('cloud.regionListHint')}
{regionsLoading ? (
) : regions.length === 0 ? (

{t('cloud.noRegions')}

) : (
{regions.map((r) => (
{r.name || r.code} {r.code}
))}
)}
{t('cloud.billingMonthHint')}
{billingsLoading ? (
) : billings.length === 0 ? (

{t('cloud.noBillingRecords')}

) : (
{billings.map((b) => (

{b.service || '-'}

{b.billing_date} · {b.region || '-'}

¥{Number(b.cost || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
))}
)}
); }