183 lines
5.9 KiB
TypeScript
183 lines
5.9 KiB
TypeScript
import { useState, useMemo } from 'react'
|
|
import { CheckSquare, Square, MinusSquare } from 'lucide-react'
|
|
import type { CheckedAccount } from '../../types'
|
|
import {
|
|
Table,
|
|
TableHeader,
|
|
TableBody,
|
|
TableRow,
|
|
TableHead,
|
|
TableCell,
|
|
StatusBadge,
|
|
Button,
|
|
} from '../common'
|
|
import { maskEmail, maskToken } from '../../utils/format'
|
|
|
|
interface AccountTableProps {
|
|
accounts: CheckedAccount[]
|
|
selectedIds: number[]
|
|
onSelectionChange: (ids: number[]) => void
|
|
disabled?: boolean
|
|
}
|
|
|
|
export default function AccountTable({
|
|
accounts,
|
|
selectedIds,
|
|
onSelectionChange,
|
|
disabled = false,
|
|
}: AccountTableProps) {
|
|
const [showTokens, setShowTokens] = useState(false)
|
|
|
|
const activeAccounts = useMemo(
|
|
() => accounts.filter((acc) => acc.status === 'active'),
|
|
[accounts]
|
|
)
|
|
|
|
const allSelected = selectedIds.length === accounts.length && accounts.length > 0
|
|
const someSelected = selectedIds.length > 0 && selectedIds.length < accounts.length
|
|
const activeSelected = activeAccounts.every((acc) => selectedIds.includes(acc.id))
|
|
|
|
const handleSelectAll = () => {
|
|
if (allSelected) {
|
|
onSelectionChange([])
|
|
} else {
|
|
onSelectionChange(accounts.map((acc) => acc.id))
|
|
}
|
|
}
|
|
|
|
const handleSelectActive = () => {
|
|
onSelectionChange(activeAccounts.map((acc) => acc.id))
|
|
}
|
|
|
|
const handleSelectNone = () => {
|
|
onSelectionChange([])
|
|
}
|
|
|
|
const handleToggle = (id: number) => {
|
|
if (selectedIds.includes(id)) {
|
|
onSelectionChange(selectedIds.filter((i) => i !== id))
|
|
} else {
|
|
onSelectionChange([...selectedIds, id])
|
|
}
|
|
}
|
|
|
|
if (accounts.length === 0) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
{/* Selection controls */}
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleSelectActive}
|
|
disabled={disabled || activeAccounts.length === 0}
|
|
>
|
|
全选正常 ({activeAccounts.length})
|
|
</Button>
|
|
<Button variant="outline" size="sm" onClick={handleSelectAll} disabled={disabled}>
|
|
全选 ({accounts.length})
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={handleSelectNone}
|
|
disabled={disabled || selectedIds.length === 0}
|
|
>
|
|
取消全选
|
|
</Button>
|
|
<div className="flex-1" />
|
|
<Button variant="ghost" size="sm" onClick={() => setShowTokens(!showTokens)}>
|
|
{showTokens ? '隐藏 Token' : '显示 Token'}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Selection summary */}
|
|
<div className="text-sm text-slate-600 dark:text-slate-400">
|
|
已选择{' '}
|
|
<span className="font-medium text-slate-900 dark:text-slate-100">{selectedIds.length}</span>{' '}
|
|
个账号
|
|
{activeSelected && activeAccounts.length > 0 && (
|
|
<span className="ml-2 text-green-600 dark:text-green-400">
|
|
(包含全部 {activeAccounts.length} 个正常账号)
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* Table */}
|
|
<div className="border border-slate-200 dark:border-slate-700 rounded-lg overflow-hidden">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow hoverable={false}>
|
|
<TableHead className="w-12">
|
|
<button
|
|
onClick={handleSelectAll}
|
|
disabled={disabled}
|
|
className="p-1 hover:bg-slate-200 dark:hover:bg-slate-600 rounded disabled:opacity-50"
|
|
>
|
|
{allSelected ? (
|
|
<CheckSquare className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
|
) : someSelected ? (
|
|
<MinusSquare className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
|
) : (
|
|
<Square className="h-4 w-4 text-slate-400" />
|
|
)}
|
|
</button>
|
|
</TableHead>
|
|
<TableHead>邮箱</TableHead>
|
|
<TableHead>状态</TableHead>
|
|
<TableHead>Account ID</TableHead>
|
|
<TableHead>Plan Type</TableHead>
|
|
{showTokens && <TableHead>Token</TableHead>}
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{accounts.map((account) => (
|
|
<TableRow key={account.id}>
|
|
<TableCell>
|
|
<button
|
|
onClick={() => handleToggle(account.id)}
|
|
disabled={disabled}
|
|
className="p-1 hover:bg-slate-100 dark:hover:bg-slate-700 rounded disabled:opacity-50"
|
|
>
|
|
{selectedIds.includes(account.id) ? (
|
|
<CheckSquare className="h-4 w-4 text-blue-600 dark:text-blue-400" />
|
|
) : (
|
|
<Square className="h-4 w-4 text-slate-400" />
|
|
)}
|
|
</button>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="font-mono text-sm">{maskEmail(account.account)}</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<StatusBadge status={account.status} />
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="font-mono text-xs text-slate-500 dark:text-slate-400">
|
|
{account.accountId || '-'}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="text-sm text-slate-600 dark:text-slate-400">
|
|
{account.planType || '-'}
|
|
</span>
|
|
</TableCell>
|
|
{showTokens && (
|
|
<TableCell>
|
|
<span className="font-mono text-xs text-slate-500 dark:text-slate-400">
|
|
{maskToken(account.token, 12)}
|
|
</span>
|
|
</TableCell>
|
|
)}
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|