"use client"; import { ChevronDown } from "lucide-react"; import { useMemo, useState } from "react"; import { Checkbox } from "@/components/ui/checkbox"; import { AdminNoResourceState, AdminTableNoResourceRow } from "@/components/admin/admin-no-resource-state"; import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; import type { AdminPermissionCatalogData } from "@/types/api/admin-user"; type PermissionSelectorProps = { catalog: AdminPermissionCatalogData | null; selectedSlugs: string[]; onChange: (next: string[]) => void; resolveGroupLabel: (key: string, fallback: string) => string; resolvePermissionLabel: (slug: string, fallback: string) => string; selectableSlugs?: string[] | null; helperText?: string; summaryText?: string; emptyText: string; heightClassName?: string; }; export function AdminPermissionSelector({ catalog, selectedSlugs, onChange, resolveGroupLabel, resolvePermissionLabel, selectableSlugs = null, helperText, summaryText, emptyText, heightClassName = "h-[52vh]", }: PermissionSelectorProps): React.ReactElement { const [expandedGroups, setExpandedGroups] = useState>({}); const selectedSet = useMemo(() => new Set(selectedSlugs), [selectedSlugs]); const allowedSet = useMemo( () => (selectableSlugs ? new Set(selectableSlugs) : null), [selectableSlugs], ); const groups = useMemo(() => { const rawGroups = catalog?.permission_menu_groups ?? []; const mapped = rawGroups .map((group) => ({ key: group.key, label: resolveGroupLabel(group.key, group.label), permissions: group.permissions.filter((permission) => allowedSet ? allowedSet.has(permission.slug) : true, ), })) .filter((group) => group.permissions.length > 0); if (mapped.length > 0) { return mapped; } const fallbackPermissions = (catalog?.permissions ?? []).filter((permission) => allowedSet ? allowedSet.has(permission.slug) : true, ); if (fallbackPermissions.length === 0) { return []; } return [ { key: "all", label: resolveGroupLabel("all", "全部权限"), permissions: fallbackPermissions, }, ]; }, [allowedSet, catalog, resolveGroupLabel]); const totalCount = useMemo( () => groups.reduce((sum, group) => sum + group.permissions.length, 0), [groups], ); const toggleOne = (slug: string, checked: boolean) => { const next = new Set(selectedSet); if (checked) { next.add(slug); } else { next.delete(slug); } onChange(Array.from(next).sort()); }; const toggleGroup = (slugs: string[], checked: boolean) => { const next = new Set(selectedSet); for (const slug of slugs) { if (checked) { next.add(slug); } else { next.delete(slug); } } onChange(Array.from(next).sort()); }; const toggleExpanded = (key: string) => { setExpandedGroups((prev) => ({ ...prev, [key]: !(prev[key] ?? true) })); }; const isExpanded = (key: string): boolean => expandedGroups[key] ?? true; if (groups.length === 0 || totalCount === 0) { return (
); } return (
{helperText || summaryText ? (
{helperText} {summaryText}
) : null}
{groups.map((group) => { const expanded = isExpanded(group.key); const groupSlugs = group.permissions.map((permission) => permission.slug); const selectedCount = groupSlugs.filter((slug) => selectedSet.has(slug)).length; const checkedState = selectedCount === 0 ? false : selectedCount === groupSlugs.length ? true : "indeterminate"; return (
toggleGroup(groupSlugs, value === true)} /> {selectedCount}/{group.permissions.length}
{expanded ? (
{group.permissions.map((permission, index) => ( ))}
) : null}
); })}
); }