"use client"; import { RoundPageContainer } from "@/components"; import { cn } from "@/lib/utils"; import { Loader2Icon } from "lucide-react"; import { useTranslations } from "next-intl"; import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useRbacContext } from "../contexts/RbacContext"; import { ACTION_TYPES, ActionType, PermissionValue, type PermToken } from "../data/RbacTypes"; import RbacPermissionCell from "./RbacPermissionCell"; import { RbacPermissionPicker } from "./RbacPermissionPicker"; function findToken(tokens: PermToken[] | undefined, action: ActionType): PermToken | undefined { if (!tokens) return undefined; return tokens.find((t) => t.action === action); } function cellValue(tokens: PermToken[] | undefined, action: ActionType): PermissionValue | undefined { const tok = findToken(tokens, action); if (!tok) return undefined; return tok.scope; } interface ActivePicker { moduleId: string; rowKey: string; action: ActionType; isRoleColumn: boolean; anchor: HTMLElement; } interface CellButtonProps { moduleId: string; rowKey: string; action: ActionType; tokens: PermToken[] | undefined; isRoleColumn: boolean; onOpen: (picker: ActivePicker) => void; } const CellButton = memo(function CellButton({ moduleId, rowKey, action, tokens, isRoleColumn, onOpen, }: CellButtonProps) { const ref = useRef(null); const value = cellValue(tokens, action); const handleClick = useCallback(() => { if (!ref.current) return; onOpen({ moduleId, rowKey, action, isRoleColumn, anchor: ref.current }); }, [onOpen, moduleId, rowKey, action, isRoleColumn]); return (
); }); const ACTION_LABELS: Record = { read: "Read", create: "Create", update: "Update", delete: "Delete", }; export default function RbacByRoleContainer() { const t = useTranslations(); const { matrix, modulePaths, loading, error, roleNames, moduleNames, updateCell, clearCell } = useRbacContext(); const [selectedRoleId, setSelectedRoleId] = useState(null); const [activePicker, setActivePicker] = useState(null); const openPicker = useCallback((picker: ActivePicker) => { setActivePicker(picker); }, []); const closePicker = useCallback(() => { setActivePicker(null); }, []); const handleSelectRole = useCallback((id: string) => { setSelectedRoleId(id); setActivePicker(null); }, []); const sortedRoleIds = useMemo(() => { if (!roleNames) return []; return Object.keys(roleNames).sort((a, b) => (roleNames[a] ?? a).localeCompare(roleNames[b] ?? b)); }, [roleNames]); const sortedModuleIds = useMemo(() => { if (!matrix) return []; return Object.keys(matrix).sort((a, b) => (moduleNames?.[a] ?? a).localeCompare(moduleNames?.[b] ?? b)); }, [matrix, moduleNames]); useEffect(() => { if (!selectedRoleId && sortedRoleIds.length > 0) { setSelectedRoleId(sortedRoleIds[0]); } }, [selectedRoleId, sortedRoleIds]); const activeValue = useMemo(() => { if (!activePicker || !matrix) return undefined; const block = matrix[activePicker.moduleId]; if (!block) return undefined; const tokens = activePicker.rowKey === "default" ? block.default : (block as Record)[activePicker.rowKey]; return cellValue(tokens, activePicker.action); }, [activePicker, matrix]); const activeSegments = useMemo(() => { if (!activePicker) return []; return (modulePaths[activePicker.moduleId] as string[] | undefined) ?? []; }, [activePicker, modulePaths]); const handleSetValue = useCallback( (value: PermissionValue) => { if (!activePicker) return; updateCell(activePicker.moduleId, activePicker.rowKey, activePicker.action, value); }, [activePicker, updateCell], ); const handleClear = useCallback(() => { if (!activePicker || !activePicker.isRoleColumn) return; clearCell(activePicker.moduleId, activePicker.rowKey, activePicker.action); }, [activePicker, clearCell]); if (loading) { return (
); } if (error) { return (

{error}

); } if (!matrix || !selectedRoleId) return null; return (
{sortedModuleIds.length > 0 ? (
{ACTION_TYPES.map((action) => ( ))} {sortedModuleIds.map((moduleId) => { const block = matrix[moduleId]; if (!block) return null; const defaultTokens = block.default ?? []; const roleTokens = (block as Record)[selectedRoleId]; const moduleLabel = moduleNames?.[moduleId] ?? moduleId; return ( {ACTION_TYPES.map((action) => ( ))} {ACTION_TYPES.map((action) => ( ))} ); })}
{t("rbac.module")} {ACTION_LABELS[action]}
{moduleLabel}
{t("rbac.defaults")}
{roleNames?.[selectedRoleId] ?? selectedRoleId}
) : (

{t("rbac.select_role_prompt")}

)}
); } export { RbacByRoleContainer };