"use client"; import { FormEvent, useMemo, useState } from "react"; import { Check, Pencil, Plus, Power, X } from "lucide-react"; import { useRouter } from "next/navigation"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 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 { JoinRequest, Organization, PublicAccount, RoleDefinition } from "@/modules/core/types"; type UserManagementClientProps = { canManageAccounts?: boolean; organizations: Organization[]; roles: RoleDefinition[]; joinRequests: JoinRequest[]; }; function getOrganizationRoles(roles: RoleDefinition[], organizationId: string): RoleDefinition[] { return roles.filter((role) => role.scope === "organization" && role.organizationId === organizationId && role.isEnabled); } export function UserManagementClient({ canManageAccounts = false, organizations, roles, joinRequests, }: UserManagementClientProps): React.ReactElement { const router = useRouter(); const [isCreateOpen, setIsCreateOpen] = useState(false); const [selectedOrganizationId, setSelectedOrganizationId] = useState(""); const [reviewRoleByRequestId, setReviewRoleByRequestId] = useState>({}); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const organizationRoles = useMemo( () => getOrganizationRoles(roles, selectedOrganizationId), [roles, selectedOrganizationId], ); const pendingJoinRequests = joinRequests.filter((request) => request.status === "pending"); function openCreateDialog(): void { setMessage(""); setIsCreateOpen(true); } function closeCreateDialog(): void { if (isPending) { return; } setIsCreateOpen(false); } async function handleCreateAccount(event: FormEvent): Promise { event.preventDefault(); if (!canManageAccounts) { setMessage("当前角色无权创建用户"); return; } setMessage(""); const formData = new FormData(event.currentTarget); const organizationId = String(formData.get("organizationId") ?? ""); const roleId = String(formData.get("roleId") ?? ""); if (!organizationId || !roleId) { setMessage("请选择机构和角色"); return; } setIsPending(true); const response = await fetch("/api/settings/accounts", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ organizationId, roleId, name: String(formData.get("name") ?? ""), email: String(formData.get("email") ?? ""), avatarUrl: String(formData.get("avatarUrl") ?? ""), password: String(formData.get("password") ?? ""), }), }); const result = (await response.json()) as ApiResult<{ account: unknown }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setMessage(result.reason); setIsCreateOpen(false); event.currentTarget.reset(); router.refresh(); } async function reviewJoinRequest(requestId: string, decision: "approved" | "rejected", roleId: string): Promise { if (!canManageAccounts) { setMessage("当前角色无权审批加入申请"); return; } setMessage(""); setIsPending(true); const response = await fetch(`/api/settings/join-requests/${requestId}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify({ decision, roleId }), }); const result = (await response.json()) as ApiResult<{ joinRequest: unknown }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setMessage(result.reason); router.refresh(); } return (
用户操作 创建账号和审批入口已收进弹窗,列表区域保持可扫描。

可选机构

{organizations.length}

当前机构角色

{organizationRoles.length}

待审批

{pendingJoinRequests.length}

{message ? (

{message}

) : null}
加入申请审批 公开注册选择机构后进入这里审批。 {pendingJoinRequests.map((request) => { const requestRoles = getOrganizationRoles(roles, request.organizationId); const selectedRoleId = reviewRoleByRequestId[request.id] ?? ""; return (

{request.accountName}

{request.accountEmail}

{request.organizationName}

待审批
({ value: organization.id, label: organization.name })), ]} required value={selectedOrganizationId} />
{message ? (

{message}

) : null}
); } type UserAccountActionsProps = { account: PublicAccount; currentAccountId: string; organizations: Organization[]; roles: RoleDefinition[]; }; export function UserAccountActions({ account, currentAccountId, organizations, roles, }: UserAccountActionsProps): React.ReactElement { const router = useRouter(); const [isEditOpen, setIsEditOpen] = useState(false); const [selectedOrganizationId, setSelectedOrganizationId] = useState(() => account.organizationId ?? ""); const [message, setMessage] = useState(""); const [isPending, setIsPending] = useState(false); const organizationRoles = useMemo( () => getOrganizationRoles(roles, selectedOrganizationId), [roles, selectedOrganizationId], ); const defaultRoleId = organizationRoles.find((role) => role.key === account.role || role.id === account.role)?.id ?? ""; const isCurrentAccount = account.id === currentAccountId; async function updateAccount(payload: Record): Promise { setMessage(""); setIsPending(true); const response = await fetch(`/api/settings/accounts/${account.id}`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify(payload), }); const result = (await response.json()) as ApiResult<{ account: PublicAccount }>; setIsPending(false); if (!result.success) { setMessage(result.reason); return; } setMessage(result.reason); setIsEditOpen(false); router.refresh(); } async function disableAccount(): Promise { setMessage(""); setIsPending(true); const response = await fetch(`/api/settings/accounts/${account.id}`, { method: "DELETE", }); const result = (await response.json()) as ApiResult<{ account: PublicAccount }>; 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 updateAccount({ name: String(formData.get("name") ?? ""), avatarUrl: String(formData.get("avatarUrl") ?? ""), status: String(formData.get("status") ?? ""), organizationId: String(formData.get("organizationId") ?? ""), roleId: String(formData.get("roleId") ?? ""), }); } return (
{account.status === "disabled" ? ( ) : ( )} setIsEditOpen(false)} open={isEditOpen} title="编辑用户" >