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"}