"use client"; import { FormEvent, useMemo, useState } from "react"; import { Pencil, Plus, Power } from "lucide-react"; import { useRouter } from "next/navigation"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Dialog } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import type { ApiResult } from "@/modules/core/server/api"; import type { Permission, RoleDefinition } from "@/modules/core/types"; type PermissionDefinition = { id: Permission; label: string; category: string; description: string; }; type RoleManagementClientProps = { permissions: PermissionDefinition[]; }; type PermissionGroup = { category: string; permissions: PermissionDefinition[]; }; type PermissionTreeProps = { permissions: PermissionDefinition[]; selectedPermissions: Permission[]; onChange: (permissions: Permission[]) => void; }; function groupPermissions(permissions: PermissionDefinition[]): PermissionGroup[] { const groups = new Map(); for (const permission of permissions) { const currentGroup = groups.get(permission.category) ?? []; currentGroup.push(permission); groups.set(permission.category, currentGroup); } return Array.from(groups.entries()).map(([category, groupPermissions]) => ({ category, permissions: groupPermissions, })); } function orderSelectedPermissions(permissions: PermissionDefinition[], selectedSet: ReadonlySet): Permission[] { return permissions.filter((permission) => selectedSet.has(permission.id)).map((permission) => permission.id); } function PermissionTree({ permissions, selectedPermissions, onChange, }: PermissionTreeProps): React.ReactElement { const groups = useMemo(() => groupPermissions(permissions), [permissions]); const selectedSet = useMemo(() => new Set(selectedPermissions), [selectedPermissions]); function togglePermission(permissionId: Permission, isChecked: boolean): void { const nextSelected = new Set(selectedSet); if (isChecked) { nextSelected.add(permissionId); } else { nextSelected.delete(permissionId); } onChange(orderSelectedPermissions(permissions, nextSelected)); } function toggleCategory(groupPermissions: PermissionDefinition[], isChecked: boolean): void { const nextSelected = new Set(selectedSet); for (const permission of groupPermissions) { if (isChecked) { nextSelected.add(permission.id); } else { nextSelected.delete(permission.id); } } onChange(orderSelectedPermissions(permissions, nextSelected)); } return (
权限树 {selectedPermissions.length}/{permissions.length}
{groups.map((group) => { const selectedCount = group.permissions.filter((permission) => selectedSet.has(permission.id)).length; const isAllSelected = selectedCount === group.permissions.length && group.permissions.length > 0; return (
{group.permissions.map((permission) => ( ))}
); })}
); } export function RoleManagementClient({ permissions }: RoleManagementClientProps): React.ReactElement { const router = useRouter(); const [isCreateOpen, setIsCreateOpen] = useState(false); const [selectedPermissions, setSelectedPermissions] = useState([]); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); function openCreateDialog(): void { setMessage(""); setSelectedPermissions([]); setIsCreateOpen(true); } function closeCreateDialog(): void { if (isPending) { return; } setIsCreateOpen(false); } async function handleSubmit(event: FormEvent): Promise { event.preventDefault(); setMessage(""); setIsPending(true); const formData = new FormData(event.currentTarget); const response = await fetch("/api/settings/roles", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ label: String(formData.get("label") ?? ""), key: String(formData.get("key") ?? ""), description: String(formData.get("description") ?? ""), permissions: selectedPermissions, }), }); const result = (await response.json()) as ApiResult<{ role: RoleDefinition }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setMessage(result.reason); setSelectedPermissions([]); setIsCreateOpen(false); event.currentTarget.reset(); router.refresh(); } return ( <>
{message ? (

{message}

) : null}
); } type RoleRowActionsProps = { permissions: PermissionDefinition[]; role: RoleDefinition; }; export function RoleRowActions({ permissions, role }: RoleRowActionsProps): React.ReactElement { const router = useRouter(); const [isEditOpen, setIsEditOpen] = useState(false); const [selectedPermissions, setSelectedPermissions] = useState(role.permissions); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); function openEditDialog(): void { setMessage(""); setSelectedPermissions(role.permissions); setIsEditOpen(true); } function closeEditDialog(): void { if (isPending) { return; } setIsEditOpen(false); } async function updateRole(payload: Record): Promise { setMessage(""); setIsPending(true); const response = await fetch(`/api/settings/roles/${role.id}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(payload), }); const result = (await response.json()) as ApiResult<{ role: RoleDefinition }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setMessage(result.reason); setIsEditOpen(false); router.refresh(); } async function disableRole(): Promise { setMessage(""); setIsPending(true); const response = await fetch(`/api/settings/roles/${role.id}`, { method: "DELETE", }); const result = (await response.json()) as ApiResult<{ role: RoleDefinition }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } router.refresh(); } async function handleSubmit(event: FormEvent): Promise { event.preventDefault(); const formData = new FormData(event.currentTarget); await updateRole({ label: String(formData.get("label") ?? ""), key: String(formData.get("key") ?? ""), description: String(formData.get("description") ?? ""), isEnabled: String(formData.get("isEnabled") ?? "") === "true", permissions: selectedPermissions, }); } if (role.isSystem) { return 内置只读; } return (
{role.isEnabled ? ( ) : ( )}