diff --git a/adminfront/src/components/layout/RoleSwitcher.tsx b/adminfront/src/components/layout/RoleSwitcher.tsx index a173b7d5..68e299c4 100644 --- a/adminfront/src/components/layout/RoleSwitcher.tsx +++ b/adminfront/src/components/layout/RoleSwitcher.tsx @@ -63,7 +63,8 @@ const RoleSwitcher: FC = () => { border: "1px solid #333", }} > -
{ fontWeight: "bold", paddingBottom: isCollapsed ? "0" : "4px", borderBottom: isCollapsed ? "none" : "1px solid #444", + background: "transparent", + border: "none", + width: "100%", + color: "inherit", + textAlign: "inherit", }} onClick={toggleCollapse} > @@ -88,7 +94,7 @@ const RoleSwitcher: FC = () => { )}
{isCollapsed ? : } - + {!isCollapsed && (
(); // First pass: Initialize all groups as nodes and populate childrenOf map - groups.forEach((group) => { + for (const group of groups) { childrenOf.set(group.id, []); - }); + } // Second pass: Populate children - groups.forEach((group) => { + for (const group of groups) { const node: UserGroupNode = { ...group, - children: childrenOf.get(group.id)!, + children: childrenOf.get(group.id) ?? [], }; if (group.parentId === parentId) { nodes.push(node); @@ -79,13 +83,13 @@ function buildGroupTree( nodes.push(node); } } - }); + } // Sort children for consistent rendering (optional, but good for UI) nodes.sort((a, b) => a.name.localeCompare(b.name)); - nodes.forEach((node) => { + for (const node of nodes) { node.children.sort((a, b) => a.name.localeCompare(b.name)); - }); + } return nodes; } @@ -97,8 +101,16 @@ interface UserGroupTreeNodeProps { selectedGroupId: string | null; onDelete: (groupId: string) => void; onAddSubGroup: (parentId: string) => void; - addMemberMutation: any; // Simplified type for now - removeMemberMutation: any; // Simplified type for now + addMemberMutation: UseMutationResult< + void, + AxiosError<{ error?: string }>, + { groupId: string; userId: string } + >; + removeMemberMutation: UseMutationResult< + void, + AxiosError<{ error?: string }>, + { groupId: string; userId: string } + >; } const UserGroupTreeNode: React.FC = ({ diff --git a/adminfront/src/features/tenants/routes/TenantListPage.tsx b/adminfront/src/features/tenants/routes/TenantListPage.tsx index db652d75..78b850a2 100644 --- a/adminfront/src/features/tenants/routes/TenantListPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantListPage.tsx @@ -38,7 +38,9 @@ function buildTenantTree(tenants: TenantSummary[]): TenantNode[] { } for (const tenant of tenants) { - const node = tenantMap.get(tenant.id)!; + const node = tenantMap.get(tenant.id); + if (!node) continue; + if (tenant.parentId) { const parent = tenantMap.get(tenant.parentId); if (parent) { diff --git a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx index 77a68415..942e0e00 100644 --- a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx +++ b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx @@ -50,23 +50,29 @@ function buildGroupTree(groups: GroupSummary[]): UserGroupNode[] { const nodeMap = new Map(); const rootNodes: UserGroupNode[] = []; - groups.forEach((group) => { + for (const group of groups) { nodeMap.set(group.id, { ...group, children: [] }); - }); + } + + for (const group of groups) { + const node = nodeMap.get(group.id); + if (!node) continue; - groups.forEach((group) => { - const node = nodeMap.get(group.id)!; if (group.parentId && nodeMap.has(group.parentId)) { - const parent = nodeMap.get(group.parentId)!; - parent.children.push(node); + const parent = nodeMap.get(group.parentId); + if (parent) { + parent.children.push(node); + } } else { rootNodes.push(node); } - }); + } const sortNodes = (nodes: UserGroupNode[]) => { nodes.sort((a, b) => a.name.localeCompare(b.name)); - nodes.forEach((node) => sortNodes(node.children)); + for (const node of nodes) { + sortNodes(node.children); + } }; sortNodes(rootNodes); @@ -227,7 +233,7 @@ export function TenantUserGroupsTab() { groupsQuery.refetch(); if ( selectedGroupId && - selectedGroupId === (deleteMutation.variables as any) + selectedGroupId === deleteMutation.variables ) { setSelectedGroupId(null); } diff --git a/adminfront/src/features/user-groups/routes/UserGroupDetailPage.tsx b/adminfront/src/features/user-groups/routes/UserGroupDetailPage.tsx index 89270480..59091292 100644 --- a/adminfront/src/features/user-groups/routes/UserGroupDetailPage.tsx +++ b/adminfront/src/features/user-groups/routes/UserGroupDetailPage.tsx @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import type { AxiosError } from "axios"; import { ArrowLeft, Shield, Trash2, UserPlus, Users } from "lucide-react"; import { useState } from "react"; import { Link, useParams } from "react-router-dom"; @@ -62,14 +63,13 @@ export function UserGroupDetailPage() { const [selectedTargetTenantId, setSelectedTargetTenantId] = useState(""); const [selectedRelation, setSelectedRelation] = useState("view"); - // Fetch specific group details const { data: currentGroup, isLoading: isGroupLoading, error, } = useQuery({ queryKey: ["user-group-detail", id], - queryFn: () => fetchGroup(tenantId!, id!), + queryFn: () => fetchGroup(tenantId ?? "", id ?? ""), enabled: !!id && !!tenantId, retry: false, }); @@ -77,7 +77,7 @@ export function UserGroupDetailPage() { // Fetch assigned roles const { data: groupRoles, isLoading: isRolesLoading } = useQuery({ queryKey: ["user-group-roles", id], - queryFn: () => fetchGroupRoles(tenantId!, id!), + queryFn: () => fetchGroupRoles(tenantId ?? "", id ?? ""), enabled: !!id && !!tenantId, }); @@ -96,7 +96,7 @@ export function UserGroupDetailPage() { }); const addMemberMutation = useMutation({ - mutationFn: (userId: string) => addGroupMember(tenantId!, id!, userId), + mutationFn: (userId: string) => addGroupMember(tenantId ?? "", id ?? "", userId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["user-group-detail", id] }); setIsAddMemberOpen(false); @@ -105,15 +105,17 @@ export function UserGroupDetailPage() { t("msg.admin.groups.members.add_success", "구성원이 추가되었습니다."), ); }, - onError: (error: any) => { + onError: (error: AxiosError<{ error?: string }>) => { toast.error( - error.message || t("err.common.unknown", "오류가 발생했습니다."), + error.response?.data?.error || + error.message || + t("err.common.unknown", "오류가 발생했습니다."), ); }, }); const removeMemberMutation = useMutation({ - mutationFn: (userId: string) => removeGroupMember(tenantId!, id!, userId), + mutationFn: (userId: string) => removeGroupMember(tenantId ?? "", id ?? "", userId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["user-group-detail", id] }); toast.success( @@ -127,7 +129,7 @@ export function UserGroupDetailPage() { const assignRoleMutation = useMutation({ mutationFn: () => - assignGroupRole(tenantId!, id!, selectedTargetTenantId, selectedRelation), + assignGroupRole(tenantId ?? "", id ?? "", selectedTargetTenantId, selectedRelation), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["user-group-roles", id] }); setIsAddRoleOpen(false); @@ -135,16 +137,18 @@ export function UserGroupDetailPage() { t("msg.admin.groups.roles.assign_success", "역할이 할당되었습니다."), ); }, - onError: (error: any) => { + onError: (error: AxiosError<{ error?: string }>) => { toast.error( - error.message || t("err.common.unknown", "오류가 발생했습니다."), + error.response?.data?.error || + error.message || + t("err.common.unknown", "오류가 발생했습니다."), ); }, }); const removeRoleMutation = useMutation({ mutationFn: (role: { targetTenantId: string; relation: string }) => - removeGroupRole(tenantId!, id!, role.targetTenantId, role.relation), + removeGroupRole(tenantId ?? "", id ?? "", role.targetTenantId, role.relation), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["user-group-roles", id] }); toast.success( @@ -172,8 +176,8 @@ export function UserGroupDetailPage() {

Error:{" "} - {(error as any)?.response?.data?.error || - (error as any)?.message || + {(error as AxiosError<{ error?: string }>)?.response?.data?.error || + error.message || "Not found"}