import { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { DataTable, type DataTableColumn } from '@/components/ui/data-table'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Plus, Trash2, Pencil, MapPin, CheckCircle2, XCircle, Loader2, RefreshCw, } from 'lucide-react'; import { regionApi } from '@/api'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { toast } from 'sonner'; import { PageShell } from '@/components/layout/PageShell'; import { ContentPanel } from '@/components/layout/ContentPanel'; import { PageLoading } from '@/components/layout/PageLoading'; import { PageEmpty } from '@/components/layout/PageEmpty'; import { StatStrip } from '@/components/ui/stat-strip'; import type { JsonData } from '@/types/common'; function getProviderNames(t: (key: string) => string): Record { return { aws: 'AWS', aliyun: t('cloud.aliyun'), tencent: t('cloud.tencent'), huawei: t('cloud.huawei'), azure: 'Azure', }; } const providerColors: Record = { aws: 'dg-text-orange', aliyun: 'dg-text-orange', tencent: 'dg-text-blue', huawei: 'dg-text-red', azure: 'dg-text-blue', }; export function RegionManager() { const { t } = useTranslation(); const confirm = useConfirm(); const [regions, setRegions] = useState([]); const [loading, setLoading] = useState(true); const [selectedProvider, setSelectedProvider] = useState('all'); const [selectedIds, setSelectedIds] = useState<(string | number)[]>([]); const [dialogOpen, setDialogOpen] = useState(false); const [editingItem, setEditingItem] = useState(null); const [submitting, setSubmitting] = useState(false); const [formData, setFormData] = useState({ name: '', code: '', provider: 'aliyun', location: '', status: 'active', endpoint: '', }); const providerNames = getProviderNames(t); const loadData = useCallback(async () => { setLoading(true); try { const res = await regionApi.list(); const items = (res as JsonData)?.list || []; setRegions(items); } catch (_e) { setRegions([]); } finally { setLoading(false); } }, []); useEffect(() => { loadData(); }, [loadData]); const filtered = regions.filter(r => { const matchProvider = selectedProvider === 'all' || r.provider === selectedProvider; return matchProvider; }); const openCreate = () => { setEditingItem(null); setFormData({ name: '', code: '', provider: 'aliyun', location: '', status: 'active', endpoint: '' }); setDialogOpen(true); }; const openEdit = (item: JsonData) => { setEditingItem(item); setFormData({ name: item.name || '', code: item.code || '', provider: item.provider || 'aliyun', location: item.location || '', status: item.status || 'active', endpoint: item.endpoint || '', }); setDialogOpen(true); }; const handleSubmit = async () => { if (!formData.name.trim() || !formData.code.trim()) return; setSubmitting(true); try { if (editingItem) { await regionApi.update(editingItem.id, formData); } else { await regionApi.create(formData); } setDialogOpen(false); loadData(); } catch (_e) { // handleSubmit error — silently ignored } finally { setSubmitting(false); } }; // 单条删除 // Logan.Li const handleDelete = async (id: number) => { const region = regions.find(r => r.id === id); const ok = await confirm({ title: t('cloud.deleteRegion'), description: t('cloud.deleteRegionConfirm', { name: region?.name || id }), confirmText: t('common.delete'), variant: 'destructive', }); if (!ok) return; try { await regionApi.delete(id); toast.success(t('cloud.regionDeleted')); setSelectedIds(prev => prev.filter(i => i !== id)); loadData(); } catch (e) { console.error(t('cloud.deleteRegion'), e); toast.error(t('common.deleteFailed')); } }; // 批量删除 // Logan.Li const handleBatchDelete = async () => { if (selectedIds.length === 0) return; const ok = await confirm({ title: t('cloud.batchDeleteRegion'), description: t('cloud.batchDeleteRegionConfirm', { count: selectedIds.length }), confirmText: t('cloud.batchDelete'), variant: 'destructive', }); if (!ok) return; try { await regionApi.batchDelete(selectedIds.map(Number)); toast.success(t('cloud.batchDeletedRegions', { count: selectedIds.length })); setSelectedIds([]); loadData(); } catch (e) { console.error(t('cloud.batchDeleteFailed'), e); toast.error(t('cloud.batchDeleteFailed')); } }; // DataTable 列定义 const columns: DataTableColumn[] = [ { key: 'name', header: t('cloud.regionName'), sortable: true, render: (r) => {r.name} }, { key: 'code', header: t('cloud.code'), sortable: true, render: (r) => {r.code} }, { key: 'provider', header: t('cloud.vendor'), sortable: true, hideable: false, render: (r) => {providerNames[r.provider] || r.provider}, }, { key: 'location', header: t('cloud.location'), render: (r) => r.location || '—' }, { key: 'endpoint', header: t('cloud.regionEndpoint'), render: (r) => {r.endpoint || '—'} }, { key: 'status', header: t('cloud.regionStatus'), sortable: true, render: (r) => r.status === 'active' ? ( {t('cloud.enabled')} ) : ( {t('cloud.disabled')} ), }, { key: 'actions', header: t('common.actions'), align: 'right', hideable: false, render: (r) => (
), }, ]; const stats = { total: regions.length, active: regions.filter(r => r.status === 'active').length, inactive: regions.filter(r => r.status !== 'active').length, }; const toolbar = ( ); const batchToolbar = selectedIds.length > 0 ? (
{t('cloud.selectedRegionCount', { count: selectedIds.length })}
) : undefined; return ( } metrics={ !loading ? ( ) : undefined } toolbar={toolbar} actions={( <> )} > {loading ? ( ) : filtered.length === 0 ? ( } title={t('cloud.noRegionData')} action={( )} /> ) : ( r.id} searchable searchPlaceholder={t('cloud.searchRegion')} searchKeys={['name', 'code']} sortable selectable selectedIds={selectedIds} onSelectionChange={setSelectedIds} columnToggle exportable exportFilename="regions.csv" emptyText={t('cloud.noMatchRegion')} /> )} {/* Dialog */} {editingItem ? t('cloud.editRegion') : t('cloud.addRegion')} description
setFormData({ ...formData, name: e.target.value })} placeholder={t('cloud.regionNamePlaceholder')} className="dg-input" />
setFormData({ ...formData, code: e.target.value })} placeholder={t('cloud.regionCodePlaceholder')} className="dg-input font-mono" disabled={!!editingItem} />
setFormData({ ...formData, location: e.target.value })} placeholder={t('cloud.locationPlaceholder')} className="dg-input" />
setFormData({ ...formData, endpoint: e.target.value })} placeholder={t('cloud.endpointPlaceholder')} className="dg-input font-mono text-sm" />
); }