Spaces:
Paused
Paused
File size: 3,377 Bytes
f878eff | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | import { useState } from "preact/hooks";
import { useT } from "../../../shared/i18n/context";
import type { AssignmentAccount } from "../../../shared/hooks/use-proxy-assignments";
import type { ProxyEntry } from "../../../shared/types";
interface ProxyGroup {
id: string;
label: string;
count: number;
}
interface ProxyGroupListProps {
accounts: AssignmentAccount[];
proxies: ProxyEntry[];
selectedGroup: string | null;
onSelectGroup: (groupId: string | null) => void;
}
export function ProxyGroupList({ accounts, proxies, selectedGroup, onSelectGroup }: ProxyGroupListProps) {
const t = useT();
const [search, setSearch] = useState("");
// Build groups with counts
const groups: ProxyGroup[] = [];
// "All"
groups.push({ id: "__all__", label: t("allAccounts"), count: accounts.length });
// Count by assignment
const countMap = new Map<string, number>();
for (const acct of accounts) {
const key = acct.proxyId || "global";
countMap.set(key, (countMap.get(key) || 0) + 1);
}
// Special groups
groups.push({ id: "global", label: t("globalDefault"), count: countMap.get("global") || 0 });
// Proxy entries
for (const p of proxies) {
groups.push({ id: p.id, label: p.name, count: countMap.get(p.id) || 0 });
}
groups.push({ id: "direct", label: t("directNoProxy"), count: countMap.get("direct") || 0 });
groups.push({ id: "auto", label: t("autoRoundRobin"), count: countMap.get("auto") || 0 });
// "Unassigned" — accounts not in any explicit assignment (they default to "global")
// Since getAssignment defaults to "global", all accounts have a proxyId.
// "Unassigned" is not really a separate bucket in this model — skip or show 0.
const lowerSearch = search.toLowerCase();
const filtered = search
? groups.filter((g) => g.label.toLowerCase().includes(lowerSearch))
: groups;
const active = selectedGroup ?? "__all__";
return (
<div class="flex flex-col gap-1">
{/* Search */}
<input
type="text"
placeholder={t("searchProxy")}
value={search}
onInput={(e) => setSearch((e.target as HTMLInputElement).value)}
class="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 mb-2"
/>
{filtered.map((g) => {
const isActive = active === g.id;
return (
<button
key={g.id}
onClick={() => onSelectGroup(g.id === "__all__" ? null : g.id)}
class={`flex items-center justify-between px-3 py-2 rounded-lg text-sm transition-colors text-left ${
isActive
? "bg-primary/10 text-primary font-medium border border-primary/20"
: "hover:bg-slate-50 dark:hover:bg-border-dark text-slate-700 dark:text-text-main"
}`}
>
<span class="truncate">{g.label}</span>
<span
class={`ml-2 px-2 py-0.5 rounded-full text-xs font-medium shrink-0 ${
isActive
? "bg-primary/20 text-primary"
: "bg-slate-100 dark:bg-border-dark text-slate-500 dark:text-text-dim"
}`}
>
{g.count}
</span>
</button>
);
})}
</div>
);
}
|