diff --git a/backend-node/src/controllers/adminController.ts b/backend-node/src/controllers/adminController.ts index bea2a69b..1501a2c0 100644 --- a/backend-node/src/controllers/adminController.ts +++ b/backend-node/src/controllers/adminController.ts @@ -237,7 +237,7 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { // 회사 코드 필터 (권한 그룹 멤버 관리 시 사용) if (companyCode && typeof companyCode === "string" && companyCode.trim()) { - whereConditions.push(`company_code = $${paramIndex}`); + whereConditions.push(`u.company_code = $${paramIndex}`); queryParams.push(companyCode.trim()); paramIndex++; logger.info("회사 코드 필터 적용", { companyCode }); @@ -246,7 +246,7 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { // 최고 관리자 필터링 (회사 관리자와 일반 사용자는 최고 관리자를 볼 수 없음) if (req.user && req.user.companyCode !== "*") { // 최고 관리자가 아닌 경우, company_code가 "*"인 사용자는 제외 - whereConditions.push(`company_code != '*'`); + whereConditions.push(`u.company_code != '*'`); logger.info("최고 관리자 필터링 적용", { userCompanyCode: req.user.companyCode, }); @@ -259,15 +259,15 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { const searchTerm = search.trim(); whereConditions.push(`( - sabun ILIKE $${paramIndex} OR - user_type_name ILIKE $${paramIndex} OR - dept_name ILIKE $${paramIndex} OR - position_name ILIKE $${paramIndex} OR - user_id ILIKE $${paramIndex} OR - user_name ILIKE $${paramIndex} OR - tel ILIKE $${paramIndex} OR - cell_phone ILIKE $${paramIndex} OR - email ILIKE $${paramIndex} + u.sabun ILIKE $${paramIndex} OR + u.user_type_name ILIKE $${paramIndex} OR + u.dept_name ILIKE $${paramIndex} OR + u.position_name ILIKE $${paramIndex} OR + u.user_id ILIKE $${paramIndex} OR + u.user_name ILIKE $${paramIndex} OR + u.tel ILIKE $${paramIndex} OR + u.cell_phone ILIKE $${paramIndex} OR + u.email ILIKE $${paramIndex} )`); queryParams.push(`%${searchTerm}%`); paramIndex++; @@ -277,21 +277,21 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { // 단일 필드 검색 searchType = "single"; const fieldMap: { [key: string]: string } = { - sabun: "sabun", - companyName: "user_type_name", - deptName: "dept_name", - positionName: "position_name", - userId: "user_id", - userName: "user_name", - tel: "tel", - cellPhone: "cell_phone", - email: "email", + sabun: "u.sabun", + companyName: "u.user_type_name", + deptName: "u.dept_name", + positionName: "u.position_name", + userId: "u.user_id", + userName: "u.user_name", + tel: "u.tel", + cellPhone: "u.cell_phone", + email: "u.email", }; if (fieldMap[searchField as string]) { if (searchField === "tel") { whereConditions.push( - `(tel ILIKE $${paramIndex} OR cell_phone ILIKE $${paramIndex})` + `(u.tel ILIKE $${paramIndex} OR u.cell_phone ILIKE $${paramIndex})` ); queryParams.push(`%${searchValue}%`); paramIndex++; @@ -307,13 +307,13 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { } else { // 고급 검색 (개별 필드별 AND 조건) const advancedSearchFields = [ - { param: search_sabun, field: "sabun" }, - { param: search_companyName, field: "user_type_name" }, - { param: search_deptName, field: "dept_name" }, - { param: search_positionName, field: "position_name" }, - { param: search_userId, field: "user_id" }, - { param: search_userName, field: "user_name" }, - { param: search_email, field: "email" }, + { param: search_sabun, field: "u.sabun" }, + { param: search_companyName, field: "u.user_type_name" }, + { param: search_deptName, field: "u.dept_name" }, + { param: search_positionName, field: "u.position_name" }, + { param: search_userId, field: "u.user_id" }, + { param: search_userName, field: "u.user_name" }, + { param: search_email, field: "u.email" }, ]; let hasAdvancedSearch = false; @@ -330,7 +330,7 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { // 전화번호 검색 if (search_tel && typeof search_tel === "string" && search_tel.trim()) { whereConditions.push( - `(tel ILIKE $${paramIndex} OR cell_phone ILIKE $${paramIndex})` + `(u.tel ILIKE $${paramIndex} OR u.cell_phone ILIKE $${paramIndex})` ); queryParams.push(`%${search_tel.trim()}%`); paramIndex++; @@ -354,7 +354,7 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { // 현재 로그인한 사용자의 회사 코드 필터 (슈퍼관리자가 아닌 경우) if (req.user && req.user.companyCode !== "*" && !companyCode) { - whereConditions.push(`company_code = $${paramIndex}`); + whereConditions.push(`u.company_code = $${paramIndex}`); queryParams.push(req.user.companyCode); paramIndex++; logger.info("사용자 회사 코드 필터 적용", { @@ -364,13 +364,13 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { // 기존 필터들 if (deptCode) { - whereConditions.push(`dept_code = $${paramIndex}`); + whereConditions.push(`u.dept_code = $${paramIndex}`); queryParams.push(deptCode); paramIndex++; } if (status) { - whereConditions.push(`status = $${paramIndex}`); + whereConditions.push(`u.status = $${paramIndex}`); queryParams.push(status); paramIndex++; } @@ -383,7 +383,7 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { // 총 개수 조회 const countQuery = ` SELECT COUNT(*) as total - FROM user_info + FROM user_info u ${whereClause} `; const countResult = await query<{ total: string }>(countQuery, queryParams); @@ -394,26 +394,28 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { const offset = (Number(page) - 1) * limit; const usersQuery = ` SELECT - sabun, - user_id, - user_name, - user_name_eng, - dept_code, - dept_name, - position_code, - position_name, - email, - tel, - cell_phone, - user_type, - user_type_name, - regdate, - status, - company_code, - locale - FROM user_info + u.sabun, + u.user_id, + u.user_name, + u.user_name_eng, + u.dept_code, + u.dept_name, + u.position_code, + u.position_name, + u.email, + u.tel, + u.cell_phone, + u.user_type, + u.user_type_name, + u.regdate, + u.status, + u.company_code, + u.locale, + c.company_name + FROM user_info u + LEFT JOIN company_mng c ON u.company_code = c.company_code ${whereClause} - ORDER BY regdate DESC, user_name ASC + ORDER BY u.regdate DESC, u.user_name ASC LIMIT $${paramIndex} OFFSET $${paramIndex + 1} `; @@ -436,6 +438,7 @@ export const getUserList = async (req: AuthenticatedRequest, res: Response) => { userTypeName: user.user_type_name || null, status: user.status || "active", companyCode: user.company_code || null, + companyName: user.company_name || null, locale: user.locale || null, regDate: user.regdate ? new Date(user.regdate).toISOString().split("T")[0] diff --git a/backend-node/src/routes/adminRoutes.ts b/backend-node/src/routes/adminRoutes.ts index d0ddbd6c..3a173cbe 100644 --- a/backend-node/src/routes/adminRoutes.ts +++ b/backend-node/src/routes/adminRoutes.ts @@ -33,6 +33,7 @@ import { getTableSchema, // 테이블 스키마 조회 } from "../controllers/adminController"; import { authenticateToken } from "../middleware/authMiddleware"; +import { requireSuperAdmin } from "../middleware/permissionMiddleware"; const router = Router(); @@ -68,13 +69,13 @@ router.delete("/users/:userId", deleteUser); // 사용자 삭제 (soft delete) // 부서 관리 API router.get("/departments", getDepartmentList); // 부서 목록 조회 -// 회사 관리 API -router.get("/companies", getCompanyList); -router.get("/companies/db", getCompanyListFromDB); // 실제 DB에서 회사 목록 조회 -router.get("/companies/:companyCode", getCompanyByCode); // 회사 단건 조회 -router.post("/companies", createCompany); // 회사 등록 -router.put("/companies/:companyCode", updateCompany); // 회사 수정 -router.delete("/companies/:companyCode", deleteCompany); // 회사 삭제 +// 회사 관리 API (최고관리자 전용) +router.get("/companies", requireSuperAdmin, getCompanyList); +router.get("/companies/db", requireSuperAdmin, getCompanyListFromDB); +router.get("/companies/:companyCode", requireSuperAdmin, getCompanyByCode); +router.post("/companies", requireSuperAdmin, createCompany); +router.put("/companies/:companyCode", requireSuperAdmin, updateCompany); +router.delete("/companies/:companyCode", requireSuperAdmin, deleteCompany); // 사용자 로케일 API router.get("/user-locale", getUserLocale); diff --git a/backend-node/src/routes/companyManagementRoutes.ts b/backend-node/src/routes/companyManagementRoutes.ts index 630a3234..34b044fc 100644 --- a/backend-node/src/routes/companyManagementRoutes.ts +++ b/backend-node/src/routes/companyManagementRoutes.ts @@ -1,5 +1,6 @@ import express from "express"; import { authenticateToken } from "../middleware/authMiddleware"; +import { requireSuperAdmin } from "../middleware/permissionMiddleware"; import { AuthenticatedRequest } from "../types/auth"; import { logger } from "../utils/logger"; import { FileSystemManager } from "../utils/fileSystemManager"; @@ -7,8 +8,9 @@ import { query, queryOne } from "../database/db"; const router = express.Router(); -// 모든 라우트에 인증 미들웨어 적용 +// 모든 라우트에 인증 + 최고관리자 권한 필수 router.use(authenticateToken); +router.use(requireSuperAdmin); /** * DELETE /api/company-management/:companyCode diff --git a/frontend/app/(main)/admin/userMng/companyList/page.tsx b/frontend/app/(main)/admin/userMng/companyList/page.tsx index 8c8bd617..1f08f097 100644 --- a/frontend/app/(main)/admin/userMng/companyList/page.tsx +++ b/frontend/app/(main)/admin/userMng/companyList/page.tsx @@ -7,12 +7,18 @@ import { CompanyFormModal } from "@/components/admin/CompanyFormModal"; import { CompanyDeleteDialog } from "@/components/admin/CompanyDeleteDialog"; import { DiskUsageSummary } from "@/components/admin/DiskUsageSummary"; import { ScrollToTop } from "@/components/common/ScrollToTop"; +import { useAuth } from "@/hooks/useAuth"; +import { AlertCircle } from "lucide-react"; +import { Button } from "@/components/ui/button"; /** * 회사 관리 페이지 - * 모든 회사 관리 기능을 통합하여 제공 + * 최고 관리자만 접근 가능 */ export default function CompanyPage() { + const { user: currentUser } = useAuth(); + const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN"; + const { // 데이터 companies, @@ -51,6 +57,29 @@ export default function CompanyPage() { clearError, } = useCompanyManagement(); + if (!isSuperAdmin) { + return ( +
+
+
+

회사 관리

+

시스템에서 사용하는 회사 정보를 관리합니다

+
+
+ +

접근 권한 없음

+

+ 회사 관리는 최고 관리자만 접근할 수 있습니다. +

+ +
+
+
+ ); + } + return (
diff --git a/frontend/app/(main)/admin/userMng/rolesList/[id]/page.tsx b/frontend/app/(main)/admin/userMng/rolesList/[id]/page.tsx index 4609312c..f78b4a37 100644 --- a/frontend/app/(main)/admin/userMng/rolesList/[id]/page.tsx +++ b/frontend/app/(main)/admin/userMng/rolesList/[id]/page.tsx @@ -3,7 +3,7 @@ import React, { useState, useCallback, useEffect } from "react"; import { use } from "react"; import { Button } from "@/components/ui/button"; -import { ArrowLeft, Users, Menu as MenuIcon, Save, AlertCircle } from "lucide-react"; +import { ArrowLeft, Users, Menu as MenuIcon, Save, AlertCircle, Building2, User as UserIcon } from "lucide-react"; import { roleAPI, RoleGroup } from "@/lib/api/role"; import { useAuth } from "@/hooks/useAuth"; import { useRouter } from "next/navigation"; @@ -38,8 +38,12 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin const [activeTab, setActiveTab] = useState<"members" | "permissions">("members"); // 멤버 관리 상태 + const [memberMode, setMemberMode] = useState<"user" | "dept">("user"); const [availableUsers, setAvailableUsers] = useState>([]); const [selectedUsers, setSelectedUsers] = useState>([]); + const [availableDepts, setAvailableDepts] = useState>([]); + const [selectedDepts, setSelectedDepts] = useState>([]); + const [allUsersMap, setAllUsersMap] = useState>(new Map()); const [isSavingMembers, setIsSavingMembers] = useState(false); // 메뉴 권한 상태 @@ -86,32 +90,52 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin // 2. 전체 사용자 목록 조회 (같은 회사) const userAPI = await import("@/lib/api/user"); - - console.log("🔍 사용자 목록 조회 요청:", { + const usersResponse = await userAPI.userAPI.getList({ 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); + const userItems = usersResponse.data.map((user: any) => ({ + id: user.userId, + label: user.userName || user.userId, + description: user.deptName, + })); + setAvailableUsers(userItems); + + // 전체 사용자 맵 저장 (부서별 추가 시 사용) + const uMap = new Map(); + userItems.forEach((u: any) => uMap.set(u.id, u)); + setAllUsersMap(uMap); + + // 3. 부서 목록 로드 + 부서별 사용자 수 계산 + const { getDepartments } = await import("@/lib/api/department"); + const deptsResponse = await getDepartments(roleGroup.companyCode); + const depts = deptsResponse?.success ? deptsResponse.data : deptsResponse; + if (Array.isArray(depts)) { + // 부서별 사용자 그룹핑 + const deptUserMap = new Map(); + usersResponse.data.forEach((user: any) => { + const deptCode = user.deptCode || user.dept_code; + if (deptCode) { + if (!deptUserMap.has(deptCode)) deptUserMap.set(deptCode, []); + deptUserMap.get(deptCode)!.push(user.userId); + } + }); + + setAvailableDepts( + depts.map((dept: any) => { + const deptCode = dept.deptCode || dept.dept_code; + const userIds = deptUserMap.get(deptCode) || []; + return { + id: `dept_${deptCode}`, + label: dept.deptName || dept.dept_name || deptCode, + description: `${userIds.length}명`, + userIds, + }; + }).filter((d: any) => d.userIds.length > 0), + ); + } } } catch (err) { console.error("멤버 목록 로드 오류:", err); @@ -164,6 +188,41 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin } }, [roleGroup, activeTab, loadMembers, loadMenuPermissions]); + // 부서 선택 변경 시 우측에 부서 표시 + 해당 사용자를 멤버에 추가 + const handleDeptSelectionChange = useCallback( + (newSelectedDepts: Array<{ id: string; label: string; description?: string; [key: string]: any }>) => { + // 새로 추가된 부서 찾기 + const prevIds = new Set(selectedDepts.map((d) => d.id)); + const addedDepts = newSelectedDepts.filter((d) => !prevIds.has(d.id)); + + // 우측 부서 목록 업데이트 + setSelectedDepts(newSelectedDepts as typeof selectedDepts); + + // 새로 추가된 부서의 사용자만 멤버에 추가 + if (addedDepts.length > 0) { + const userIdsToAdd = new Set(); + addedDepts.forEach((deptItem) => { + const fullDept = availableDepts.find((d) => d.id === deptItem.id); + fullDept?.userIds.forEach((uid) => userIdsToAdd.add(uid)); + }); + + const existingIds = new Set(selectedUsers.map((u) => u.id)); + const newUsers: Array<{ id: string; label: string; description?: string }> = []; + userIdsToAdd.forEach((uid) => { + if (!existingIds.has(uid)) { + const userInfo = allUsersMap.get(uid); + if (userInfo) newUsers.push(userInfo); + } + }); + + if (newUsers.length > 0) { + setSelectedUsers((prev) => [...prev, ...newUsers]); + } + } + }, + [availableDepts, selectedDepts, selectedUsers, allUsersMap], + ); + // 멤버 저장 핸들러 const handleSaveMembers = useCallback(async () => { if (!roleGroup) return; @@ -302,20 +361,84 @@ export default function RoleDetailPage({ params }: { params: Promise<{ id: strin

멤버 관리

이 권한 그룹에 속한 사용자를 관리합니다

- +
+ {/* 사용자별/부서별 모드 전환 */} +
+ + +
+ +
- + {memberMode === "user" ? ( + + ) : ( + <> + ( +
+ {item.label} + {item.description} +
+ )} + /> + + {/* 현재 멤버 요약 */} +
+

현재 그룹 멤버 ({selectedUsers.length}명)

+ {selectedUsers.length === 0 ? ( +

멤버가 없습니다

+ ) : ( +
+ {selectedUsers.map((user) => ( + + {user.label} + + + ))} +
+ )} +
+ + )} )} diff --git a/frontend/app/(main)/admin/userMng/rolesList/page.tsx b/frontend/app/(main)/admin/userMng/rolesList/page.tsx index 48a4ff32..58ba5359 100644 --- a/frontend/app/(main)/admin/userMng/rolesList/page.tsx +++ b/frontend/app/(main)/admin/userMng/rolesList/page.tsx @@ -9,6 +9,7 @@ import { AlertCircle } from "lucide-react"; import { RoleFormModal } from "@/components/admin/RoleFormModal"; import { RoleDeleteModal } from "@/components/admin/RoleDeleteModal"; import { useRouter } from "next/navigation"; +import { useTabStore } from "@/stores/tabStore"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { companyAPI } from "@/lib/api/company"; import { ScrollToTop } from "@/components/common/ScrollToTop"; @@ -29,6 +30,7 @@ import { ScrollToTop } from "@/components/common/ScrollToTop"; export default function RolesPage() { const { user: currentUser } = useAuth(); const router = useRouter(); + const openTab = useTabStore((s) => s.openTab); // 회사 관리자 또는 최고 관리자 여부 const isAdmin = @@ -147,9 +149,13 @@ export default function RolesPage() { // 상세 페이지로 이동 const handleViewDetail = useCallback( (role: RoleGroup) => { - router.push(`/admin/userMng/rolesList/${role.objid}`); + openTab({ + type: "admin", + title: role.authName, + adminUrl: `/admin/userMng/rolesList/${role.objid}`, + }); }, - [router], + [openTab], ); // 관리자가 아니면 접근 제한 diff --git a/frontend/app/(main)/admin/userMng/userAuthList/page.tsx b/frontend/app/(main)/admin/userMng/userAuthList/page.tsx index c49adbd6..158693f7 100644 --- a/frontend/app/(main)/admin/userMng/userAuthList/page.tsx +++ b/frontend/app/(main)/admin/userMng/userAuthList/page.tsx @@ -165,6 +165,7 @@ export default function UserAuthPage() { -
- {/* 페이지 헤더 */} +
+ {/* 상단 고정: 헤더 + 툴바 */} +

사용자 관리

시스템 사용자 계정 및 권한을 관리합니다

- {/* 툴바 - 검색, 필터, 등록 버튼 */} - {/* 에러 메시지 */} {error && (
@@ -142,8 +140,10 @@ export default function UserMngPage() {

{error}

)} +
- {/* 사용자 목록 테이블 */} + {/* 중간: 테이블 (스크롤 영역) */} +
+
- {/* 페이지네이션 */} - {!isLoading && users.length > 0 && ( + {/* 하단 고정: 페이지네이션 */} + {!isLoading && users.length > 0 && ( +
- )} +
+ )} - {/* 사용자 등록/수정 모달 */} - - - {/* 비밀번호 초기화 모달 */} - -
- - {/* Scroll to Top 버튼 (모바일/태블릿 전용) */} + {/* 모달 */} + +
); diff --git a/frontend/components/admin/UserAuthTable.tsx b/frontend/components/admin/UserAuthTable.tsx index 50e7d889..597afcc7 100644 --- a/frontend/components/admin/UserAuthTable.tsx +++ b/frontend/components/admin/UserAuthTable.tsx @@ -9,6 +9,7 @@ import { ResponsiveDataView, RDVColumn, RDVCardField } from "@/components/common interface UserAuthTableProps { users: any[]; isLoading: boolean; + isSuperAdmin?: boolean; paginationInfo: { currentPage: number; pageSize: number; @@ -24,7 +25,7 @@ interface UserAuthTableProps { * * 사용자 목록과 권한 정보를 표시하고 권한 변경 기능 제공 */ -export function UserAuthTable({ users, isLoading, paginationInfo, onEditAuth, onPageChange }: UserAuthTableProps) { +export function UserAuthTable({ users, isLoading, isSuperAdmin, paginationInfo, onEditAuth, onPageChange }: UserAuthTableProps) { // 권한 레벨 표시 const getUserTypeInfo = (userType: string) => { switch (userType) { @@ -90,12 +91,16 @@ export function UserAuthTable({ users, isLoading, paginationInfo, onEditAuth, on key: "userName", label: "사용자명", }, - { - key: "companyName", - label: "회사", - hideOnMobile: true, - render: (_value, row) => {row.companyName || row.companyCode}, - }, + ...(isSuperAdmin + ? [ + { + key: "companyName", + label: "회사", + hideOnMobile: true, + render: (_value: any, row: any) => {row.companyName || row.companyCode}, + } as RDVColumn, + ] + : []), { key: "deptName", label: "부서", @@ -120,10 +125,14 @@ export function UserAuthTable({ users, isLoading, paginationInfo, onEditAuth, on // 모바일 카드 필드 정의 const cardFields: RDVCardField[] = [ - { - label: "회사", - render: (user) => {user.companyName || user.companyCode}, - }, + ...(isSuperAdmin + ? [ + { + label: "회사", + render: (user: any) => {user.companyName || user.companyCode}, + } as RDVCardField, + ] + : []), { label: "부서", render: (user) => {user.deptName || "-"}, diff --git a/frontend/components/admin/UserTable.tsx b/frontend/components/admin/UserTable.tsx index a0073f4f..84946f85 100644 --- a/frontend/components/admin/UserTable.tsx +++ b/frontend/components/admin/UserTable.tsx @@ -7,6 +7,7 @@ import { PaginationInfo } from "@/components/common/Pagination"; import { ResponsiveDataView, RDVColumn, RDVCardField } from "@/components/common/ResponsiveDataView"; import { UserStatusConfirmDialog } from "./UserStatusConfirmDialog"; import { UserHistoryModal } from "./UserHistoryModal"; +import { useAuth } from "@/hooks/useAuth"; interface UserTableProps { users: User[]; @@ -28,6 +29,9 @@ export function UserTable({ onPasswordReset, onEdit, }: UserTableProps) { + const { user: currentUser } = useAuth(); + const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN"; + // 확인 모달 상태 관리 const [confirmDialog, setConfirmDialog] = useState<{ isOpen: boolean; @@ -119,13 +123,19 @@ export function UserTable({ hideOnMobile: true, render: (value) => {value || "-"}, }, - { - key: "companyCode", - label: "회사", - width: "120px", - hideOnMobile: true, - render: (value) => {value || "-"}, - }, + ...(isSuperAdmin + ? [ + { + key: "companyCode" as keyof User, + label: "회사", + width: "120px", + hideOnMobile: true, + render: (value: any, user: User) => ( + {(user as any).companyName || value || "-"} + ), + }, + ] + : []), { key: "deptName", label: "부서명", @@ -202,11 +212,17 @@ export function UserTable({ render: (user) => {user.sabun || "-"}, hideEmpty: true, }, - { - label: "회사", - render: (user) => {user.companyCode || ""}, - hideEmpty: true, - }, + ...(isSuperAdmin + ? [ + { + label: "회사", + render: (user: User) => ( + {(user as any).companyName || user.companyCode || ""} + ), + hideEmpty: true, + }, + ] + : []), { label: "부서", render: (user) => {user.deptName || ""}, diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx index d82d44f0..faa38879 100644 --- a/frontend/components/layout/AppLayout.tsx +++ b/frontend/components/layout/AppLayout.tsx @@ -100,9 +100,19 @@ const getMenuIcon = (menuName: string, dbIconName?: string | null) => { }; const convertMenuToUI = (menus: MenuItem[], userInfo: ExtendedUserInfo | null, parentId: string = "0", parentPath: string = ""): any[] => { + const isSuperAdmin = userInfo?.companyCode === "*" && userInfo?.userType === "SUPER_ADMIN"; + const filteredMenus = menus .filter((menu) => (menu.parent_obj_id || menu.PARENT_OBJ_ID) === parentId) .filter((menu) => (menu.status || menu.STATUS) === "active") + .filter((menu) => { + // 회사관리 메뉴는 최고관리자만 표시 + const url = (menu.menu_url || menu.MENU_URL || "").toLowerCase(); + if (url.includes("companylist") || url.includes("company-list")) { + return isSuperAdmin; + } + return true; + }) .sort((a, b) => (a.seq || a.SEQ || 0) - (b.seq || b.SEQ || 0)); if (parentId === "0") {