Files
lotteryAdmin/src/modules/agents/agents-subnav.tsx

141 lines
5.6 KiB
TypeScript

"use client";
import { Check, ChevronDown, Search } from "lucide-react";
import { useDeferredValue, useEffect, useMemo, useState } from "react";
import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next";
import {
AdminSubnavBar,
} from "@/components/admin/admin-subnav";
import { buttonVariants } from "@/components/ui/button";
import { useAdminSiteCodeOptions } from "@/hooks/use-admin-site-code-options";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { PRD_INTEGRATION_ACCESS_ANY } from "@/lib/admin-prd";
import { Input } from "@/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useAdminProfile } from "@/stores/admin-session";
import { useAgentManagementSiteStore } from "@/stores/agent-management-site";
import { cn } from "@/lib/utils";
export function AgentsSubnav(): React.ReactElement {
const { t } = useTranslation("agents");
const pathname = usePathname();
const profile = useAdminProfile();
const { sites: siteOptions } = useAdminSiteCodeOptions();
const adminSiteId = useAgentManagementSiteStore((s) => s.adminSiteId);
const setAdminSiteId = useAgentManagementSiteStore((s) => s.setAdminSiteId);
const [sitePickerOpen, setSitePickerOpen] = useState(false);
const [siteKeyword, setSiteKeyword] = useState("");
const deferredKeyword = useDeferredValue(siteKeyword);
const canSwitchSite =
profile?.is_super_admin === true ||
adminHasAnyPermission(profile?.permissions, [...PRD_INTEGRATION_ACCESS_ANY]);
useEffect(() => {
if (adminSiteId !== null || siteOptions.length === 0) {
return;
}
const boundSiteId = profile?.agent?.admin_site_id;
if (boundSiteId != null) {
setAdminSiteId(boundSiteId);
return;
}
setAdminSiteId(siteOptions[0]?.id ?? null);
}, [adminSiteId, profile?.agent?.admin_site_id, setAdminSiteId, siteOptions]);
const selectSiteId = adminSiteId ?? siteOptions[0]?.id ?? null;
const selectedSite = useMemo(() => {
const site = siteOptions.find((item) => item.id === selectSiteId);
return site ?? null;
}, [selectSiteId, siteOptions]);
const filteredSites = useMemo(() => {
const normalized = deferredKeyword.trim().toLowerCase();
if (normalized === "") {
return siteOptions;
}
return siteOptions.filter((site) => site.name.toLowerCase().includes(normalized));
}, [deferredKeyword, siteOptions]);
const siteSelector =
pathname !== "/admin/agents/list" && canSwitchSite && siteOptions.length > 0 && selectSiteId !== null ? (
<Popover open={sitePickerOpen} onOpenChange={setSitePickerOpen}>
<PopoverTrigger
className={cn(
buttonVariants({ variant: "outline", size: "lg" }),
"h-10 w-[240px] justify-between gap-2 bg-background px-3 text-left font-normal",
)}
>
<span className="min-w-0 flex-1 truncate">
{selectedSite?.name ?? t("lineFilter", { defaultValue: "一级代理" })}
</span>
<span className="shrink-0 text-xs text-muted-foreground">
{selectedSite?.code ?? ""}
</span>
<ChevronDown className="size-4 shrink-0 text-muted-foreground" />
</PopoverTrigger>
<PopoverContent align="end" className="w-[320px] p-0">
<div className="border-b border-border/60 p-3">
<div className="relative">
<Search className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
<Input
value={siteKeyword}
onChange={(event) => setSiteKeyword(event.target.value)}
placeholder={t("siteSearch", { defaultValue: "搜索站点名称" })}
className="pl-9"
/>
</div>
</div>
<ScrollArea className="max-h-72">
<div className="p-2">
{filteredSites.map((site) => {
const active = site.id === selectSiteId;
return (
<button
key={site.id}
type="button"
className={cn(
"flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition-colors",
active ? "bg-primary/10 text-primary" : "hover:bg-muted/70",
)}
onClick={() => {
setAdminSiteId(site.id);
setSitePickerOpen(false);
setSiteKeyword("");
}}
>
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-medium text-foreground">{site.name}</div>
<div className="truncate text-xs text-muted-foreground">{site.code}</div>
</div>
{active ? <Check className="size-4 shrink-0" /> : null}
</button>
);
})}
{filteredSites.length === 0 ? (
<div className="px-3 py-6 text-center text-sm text-muted-foreground">
{t("common:states.empty", { defaultValue: "暂无数据" })}
</div>
) : null}
</div>
</ScrollArea>
</PopoverContent>
</Popover>
) : null;
return (
<AdminSubnavBar trailing={siteSelector}>
<div className="pb-1">
<p className="text-sm font-medium text-foreground">
{t("title", { defaultValue: "代理管理" })}
</p>
</div>
</AdminSubnavBar>
);
}