338 lines
12 KiB
TypeScript
338 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useCallback, useEffect } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { ArrowLeft, Users, Menu as MenuIcon, Save } from "lucide-react";
|
|
import { roleAPI, RoleGroup } from "@/lib/api/role";
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
import { useRouter } from "next/navigation";
|
|
import { AlertCircle } from "lucide-react";
|
|
import { DualListBox } from "@/components/common/DualListBox";
|
|
import { MenuPermissionsTable } from "./MenuPermissionsTable";
|
|
|
|
interface RoleDetailManagementProps {
|
|
roleId: string;
|
|
}
|
|
|
|
/**
|
|
* 권한 그룹 상세 관리 컴포넌트
|
|
*
|
|
* 기능:
|
|
* - 권한 그룹 정보 표시
|
|
* - 멤버 관리 (Dual List Box)
|
|
* - 메뉴 권한 설정 (CRUD 체크박스)
|
|
*/
|
|
export function RoleDetailManagement({ roleId }: RoleDetailManagementProps) {
|
|
const { user: currentUser } = useAuth();
|
|
const router = useRouter();
|
|
|
|
const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN";
|
|
|
|
// 상태 관리
|
|
const [roleGroup, setRoleGroup] = useState<RoleGroup | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// 탭 상태
|
|
const [activeTab, setActiveTab] = useState<"members" | "permissions">("members");
|
|
|
|
// 멤버 관리 상태
|
|
const [availableUsers, setAvailableUsers] = useState<Array<{ id: string; name: string; dept?: string }>>([]);
|
|
const [selectedUsers, setSelectedUsers] = useState<Array<{ id: string; name: string; dept?: string }>>([]);
|
|
const [isSavingMembers, setIsSavingMembers] = useState(false);
|
|
|
|
// 메뉴 권한 상태
|
|
const [menuPermissions, setMenuPermissions] = useState<any[]>([]);
|
|
const [isSavingPermissions, setIsSavingPermissions] = useState(false);
|
|
|
|
// 데이터 로드
|
|
const loadRoleGroup = useCallback(async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await roleAPI.getById(parseInt(roleId, 10));
|
|
|
|
if (response.success && response.data) {
|
|
setRoleGroup(response.data);
|
|
} else {
|
|
setError(response.message || "권한 그룹 정보를 불러오는데 실패했습니다.");
|
|
}
|
|
} catch (err) {
|
|
console.error("권한 그룹 정보 로드 오류:", err);
|
|
setError("권한 그룹 정보를 불러오는 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [roleId]);
|
|
|
|
// 멤버 목록 로드
|
|
const loadMembers = useCallback(async () => {
|
|
if (!roleGroup) return;
|
|
|
|
try {
|
|
// 1. 권한 그룹 멤버 조회
|
|
const membersResponse = await roleAPI.getMembers(roleGroup.objid);
|
|
if (membersResponse.success && membersResponse.data) {
|
|
setSelectedUsers(
|
|
membersResponse.data.map((member: any) => ({
|
|
id: member.userId,
|
|
label: member.userName || member.userId,
|
|
description: member.deptName,
|
|
})),
|
|
);
|
|
}
|
|
|
|
// 2. 전체 사용자 목록 조회 (같은 회사)
|
|
const userAPI = await import("@/lib/api/user");
|
|
|
|
console.log("🔍 사용자 목록 조회 요청:", {
|
|
companyCode: roleGroup.companyCode,
|
|
size: 1000,
|
|
});
|
|
|
|
const usersResponse = await userAPI.userAPI.getList({
|
|
companyCode: roleGroup.companyCode,
|
|
size: 1000, // 대량 조회
|
|
});
|
|
|
|
console.log("✅ 사용자 목록 응답:", {
|
|
success: usersResponse.success,
|
|
count: usersResponse.data?.length,
|
|
total: usersResponse.total,
|
|
});
|
|
|
|
if (usersResponse.success && usersResponse.data) {
|
|
setAvailableUsers(
|
|
usersResponse.data.map((user: any) => ({
|
|
id: user.userId,
|
|
label: user.userName || user.userId,
|
|
description: user.deptName,
|
|
})),
|
|
);
|
|
console.log("📋 설정된 전체 사용자 수:", usersResponse.data.length);
|
|
}
|
|
} catch (err) {
|
|
console.error("멤버 목록 로드 오류:", err);
|
|
}
|
|
}, [roleGroup]);
|
|
|
|
// 메뉴 권한 로드
|
|
const loadMenuPermissions = useCallback(async () => {
|
|
if (!roleGroup) return;
|
|
|
|
console.log("🔍 [loadMenuPermissions] 메뉴 권한 로드 시작", {
|
|
roleGroupId: roleGroup.objid,
|
|
roleGroupName: roleGroup.authName,
|
|
companyCode: roleGroup.companyCode,
|
|
});
|
|
|
|
try {
|
|
const response = await roleAPI.getMenuPermissions(roleGroup.objid);
|
|
|
|
console.log("✅ [loadMenuPermissions] API 응답", {
|
|
success: response.success,
|
|
dataCount: response.data?.length,
|
|
data: response.data,
|
|
});
|
|
|
|
if (response.success && response.data) {
|
|
setMenuPermissions(response.data);
|
|
console.log("✅ [loadMenuPermissions] 상태 업데이트 완료", {
|
|
count: response.data.length,
|
|
});
|
|
} else {
|
|
console.warn("⚠️ [loadMenuPermissions] 응답 실패", {
|
|
message: response.message,
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error("❌ [loadMenuPermissions] 메뉴 권한 로드 오류:", err);
|
|
}
|
|
}, [roleGroup]);
|
|
|
|
useEffect(() => {
|
|
loadRoleGroup();
|
|
}, [loadRoleGroup]);
|
|
|
|
useEffect(() => {
|
|
if (roleGroup && activeTab === "members") {
|
|
loadMembers();
|
|
} else if (roleGroup && activeTab === "permissions") {
|
|
loadMenuPermissions();
|
|
}
|
|
}, [roleGroup, activeTab, loadMembers, loadMenuPermissions]);
|
|
|
|
// 멤버 저장 핸들러
|
|
const handleSaveMembers = useCallback(async () => {
|
|
if (!roleGroup) return;
|
|
|
|
setIsSavingMembers(true);
|
|
try {
|
|
// 현재 선택된 사용자 ID 목록
|
|
const selectedUserIds = selectedUsers.map((user) => user.id);
|
|
|
|
// 멤버 업데이트 API 호출
|
|
const response = await roleAPI.updateMembers(roleGroup.objid, selectedUserIds);
|
|
|
|
if (response.success) {
|
|
alert("멤버가 성공적으로 저장되었습니다.");
|
|
loadMembers(); // 새로고침
|
|
} else {
|
|
alert(response.message || "멤버 저장에 실패했습니다.");
|
|
}
|
|
} catch (err) {
|
|
console.error("멤버 저장 오류:", err);
|
|
alert("멤버 저장 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setIsSavingMembers(false);
|
|
}
|
|
}, [roleGroup, selectedUsers, loadMembers]);
|
|
|
|
// 메뉴 권한 저장 핸들러
|
|
const handleSavePermissions = useCallback(async () => {
|
|
if (!roleGroup) return;
|
|
|
|
setIsSavingPermissions(true);
|
|
try {
|
|
const response = await roleAPI.setMenuPermissions(roleGroup.objid, menuPermissions);
|
|
|
|
if (response.success) {
|
|
alert("메뉴 권한이 성공적으로 저장되었습니다.");
|
|
loadMenuPermissions(); // 새로고침
|
|
} else {
|
|
alert(response.message || "메뉴 권한 저장에 실패했습니다.");
|
|
}
|
|
} catch (err) {
|
|
console.error("메뉴 권한 저장 오류:", err);
|
|
alert("메뉴 권한 저장 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setIsSavingPermissions(false);
|
|
}
|
|
}, [roleGroup, menuPermissions, loadMenuPermissions]);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="bg-card flex h-64 flex-col items-center justify-center rounded-lg border p-12 shadow-sm">
|
|
<div className="flex flex-col items-center gap-4">
|
|
<div className="border-primary h-8 w-8 animate-spin rounded-full border-4 border-t-transparent"></div>
|
|
<p className="text-muted-foreground text-sm">권한 그룹 정보를 불러오는 중...</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error || !roleGroup) {
|
|
return (
|
|
<div className="bg-card flex h-64 flex-col items-center justify-center rounded-lg border p-6 shadow-sm">
|
|
<AlertCircle className="text-destructive mb-4 h-12 w-12" />
|
|
<h3 className="mb-2 text-lg font-semibold">오류 발생</h3>
|
|
<p className="text-muted-foreground mb-4 text-center text-sm">{error || "권한 그룹을 찾을 수 없습니다."}</p>
|
|
<Button variant="outline" onClick={() => router.push("/admin/roles")}>
|
|
목록으로 돌아가기
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* 페이지 헤더 */}
|
|
<div className="space-y-2 border-b pb-4">
|
|
<div className="flex items-center gap-4">
|
|
<Button variant="ghost" size="icon" onClick={() => router.push("/admin/roles")} className="h-10 w-10">
|
|
<ArrowLeft className="h-5 w-5" />
|
|
</Button>
|
|
<div className="flex-1">
|
|
<h1 className="text-3xl font-bold tracking-tight">{roleGroup.authName}</h1>
|
|
<p className="text-muted-foreground text-sm">
|
|
{roleGroup.authCode} • {roleGroup.companyCode}
|
|
</p>
|
|
</div>
|
|
<span
|
|
className={`rounded-full px-3 py-1 text-sm font-medium ${
|
|
roleGroup.status === "active" ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"
|
|
}`}
|
|
>
|
|
{roleGroup.status === "active" ? "활성" : "비활성"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 탭 네비게이션 */}
|
|
<div className="flex gap-4 border-b">
|
|
<button
|
|
onClick={() => setActiveTab("members")}
|
|
className={`flex items-center gap-2 border-b-2 px-4 py-3 text-sm font-medium transition-colors ${
|
|
activeTab === "members"
|
|
? "border-primary text-primary"
|
|
: "text-muted-foreground hover:text-foreground border-transparent"
|
|
}`}
|
|
>
|
|
<Users className="h-4 w-4" />
|
|
멤버 관리 ({selectedUsers.length})
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab("permissions")}
|
|
className={`flex items-center gap-2 border-b-2 px-4 py-3 text-sm font-medium transition-colors ${
|
|
activeTab === "permissions"
|
|
? "border-primary text-primary"
|
|
: "text-muted-foreground hover:text-foreground border-transparent"
|
|
}`}
|
|
>
|
|
<MenuIcon className="h-4 w-4" />
|
|
메뉴 권한 ({menuPermissions.length})
|
|
</button>
|
|
</div>
|
|
|
|
{/* 탭 컨텐츠 */}
|
|
<div className="space-y-6">
|
|
{activeTab === "members" && (
|
|
<>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-xl font-semibold">멤버 관리</h2>
|
|
<p className="text-muted-foreground text-sm">이 권한 그룹에 속한 사용자를 관리합니다</p>
|
|
</div>
|
|
<Button onClick={handleSaveMembers} disabled={isSavingMembers} className="gap-2">
|
|
<Save className="h-4 w-4" />
|
|
{isSavingMembers ? "저장 중..." : "멤버 저장"}
|
|
</Button>
|
|
</div>
|
|
|
|
<DualListBox
|
|
availableItems={availableUsers}
|
|
selectedItems={selectedUsers}
|
|
onSelectionChange={setSelectedUsers}
|
|
availableLabel="전체 사용자"
|
|
selectedLabel="그룹 멤버"
|
|
enableSearch
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{activeTab === "permissions" && (
|
|
<>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-xl font-semibold">메뉴 권한 설정</h2>
|
|
<p className="text-muted-foreground text-sm">이 권한 그룹에서 접근 가능한 메뉴와 권한을 설정합니다</p>
|
|
</div>
|
|
<Button onClick={handleSavePermissions} disabled={isSavingPermissions} className="gap-2">
|
|
<Save className="h-4 w-4" />
|
|
{isSavingPermissions ? "저장 중..." : "권한 저장"}
|
|
</Button>
|
|
</div>
|
|
|
|
<MenuPermissionsTable
|
|
permissions={menuPermissions}
|
|
onPermissionsChange={setMenuPermissions}
|
|
roleGroup={roleGroup}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|