import { useState, useCallback, useRef } from "preact/hooks"; import { useT } from "@shared/i18n/context"; import type { TranslationKey } from "@shared/i18n/translations"; import type { AssignmentAccount } from "@shared/hooks/use-proxy-assignments"; import type { ProxyEntry } from "@shared/types"; const PAGE_SIZE = 50; const statusStyles: Record = { active: [ "bg-green-100 text-green-700 border-green-200 dark:bg-[#11281d] dark:text-primary dark:border-[#1a442e]", "active", ], expired: [ "bg-red-100 text-red-600 border-red-200 dark:bg-red-900/20 dark:text-red-400 dark:border-red-800/30", "expired", ], rate_limited: [ "bg-amber-100 text-amber-700 border-amber-200 dark:bg-amber-900/20 dark:text-amber-400 dark:border-amber-800/30", "rateLimited", ], refreshing: [ "bg-blue-100 text-blue-600 border-blue-200 dark:bg-blue-900/20 dark:text-blue-400 dark:border-blue-800/30", "refreshing", ], disabled: [ "bg-slate-100 text-slate-500 border-slate-200 dark:bg-slate-800/30 dark:text-slate-400 dark:border-slate-700/30", "disabled", ], }; interface AccountTableProps { accounts: AssignmentAccount[]; proxies: ProxyEntry[]; selectedIds: Set; onSelectionChange: (ids: Set) => void; onSingleProxyChange: (accountId: string, proxyId: string) => void; filterGroup: string | null; statusFilter: string; onStatusFilterChange: (status: string) => void; } export function AccountTable({ accounts, proxies, selectedIds, onSelectionChange, onSingleProxyChange, filterGroup, statusFilter, onStatusFilterChange, }: AccountTableProps) { const t = useT(); const [search, setSearch] = useState(""); const [page, setPage] = useState(0); const lastClickedIndex = useRef(null); // Filter accounts let filtered = accounts; // By group if (filterGroup) { filtered = filtered.filter((a) => a.proxyId === filterGroup); } // By status if (statusFilter && statusFilter !== "all") { filtered = filtered.filter((a) => a.status === statusFilter); } // By search if (search) { const lower = search.toLowerCase(); filtered = filtered.filter((a) => a.email.toLowerCase().includes(lower)); } const totalCount = filtered.length; const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); const safePage = Math.min(page, totalPages - 1); const pageAccounts = filtered.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE); const pageAccountsRef = useRef(pageAccounts); pageAccountsRef.current = pageAccounts; // Reset page when filters change const handleSearch = useCallback((value: string) => { setSearch(value); setPage(0); }, []); const handleStatusFilter = useCallback( (value: string) => { onStatusFilterChange(value); setPage(0); }, [onStatusFilterChange], ); // Select/deselect const toggleSelect = useCallback( (id: string, index: number, shiftKey: boolean) => { const newSet = new Set(selectedIds); const currentPage = pageAccountsRef.current; if (shiftKey && lastClickedIndex.current !== null) { // Range select const start = Math.min(lastClickedIndex.current, index); const end = Math.max(lastClickedIndex.current, index); for (let i = start; i <= end; i++) { if (currentPage[i]) { newSet.add(currentPage[i].id); } } } else { if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } } lastClickedIndex.current = index; onSelectionChange(newSet); }, [selectedIds, onSelectionChange], ); const toggleSelectAll = useCallback(() => { const currentPage = pageAccountsRef.current; const pageIds = currentPage.map((a) => a.id); const allSelected = pageIds.every((id) => selectedIds.has(id)); const newSet = new Set(selectedIds); if (allSelected) { for (const id of pageIds) newSet.delete(id); } else { for (const id of pageIds) newSet.add(id); } onSelectionChange(newSet); }, [selectedIds, onSelectionChange]); const allPageSelected = pageAccounts.length > 0 && pageAccounts.every((a) => selectedIds.has(a.id)); return (
{/* Filters */}
handleSearch((e.target as HTMLInputElement).value)} class="flex-1 px-3 py-2 text-sm border border-gray-200 dark:border-border-dark rounded-lg bg-white dark:bg-bg-dark text-slate-700 dark:text-text-main focus:outline-none focus:ring-1 focus:ring-primary" />
{/* Table */}
{/* Table Header */}
Email
{/* Rows */} {pageAccounts.length === 0 ? (
{t("noAccounts")}
) : ( pageAccounts.map((acct, idx) => { const isSelected = selectedIds.has(acct.id); const [statusCls, statusKey] = statusStyles[acct.status] || statusStyles.disabled; return (
{ if ((e.target as HTMLElement).tagName === "SELECT") return; toggleSelect(acct.id, idx, e.shiftKey); }} > {acct.email}
); }) )}
{/* Pagination */} {totalPages > 1 && (
{t("totalItems")} {totalCount}
{safePage + 1} / {totalPages}
)} {/* Hint */}

{t("shiftSelectHint")}

); }