2025-10-27 16:40:59 +09:00
|
|
|
"use client";
|
|
|
|
|
|
2025-12-30 15:28:05 +09:00
|
|
|
import React, { useState, useCallback, useEffect } from "react";
|
|
|
|
|
import { UserAuthTable } from "@/components/admin/UserAuthTable";
|
|
|
|
|
import { UserAuthEditModal } from "@/components/admin/UserAuthEditModal";
|
|
|
|
|
import { userAPI } from "@/lib/api/user";
|
|
|
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
|
|
|
import { AlertCircle } from "lucide-react";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
2025-10-27 16:40:59 +09:00
|
|
|
import { ScrollToTop } from "@/components/common/ScrollToTop";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 사용자 권한 관리 페이지
|
|
|
|
|
* URL: /admin/userAuth
|
|
|
|
|
*
|
|
|
|
|
* 최고 관리자만 접근 가능
|
|
|
|
|
* 사용자별 권한 레벨(SUPER_ADMIN, COMPANY_ADMIN, USER 등) 관리
|
|
|
|
|
*/
|
|
|
|
|
export default function UserAuthPage() {
|
2025-12-30 15:28:05 +09:00
|
|
|
const { user: currentUser } = useAuth();
|
|
|
|
|
|
|
|
|
|
// 최고 관리자 여부
|
|
|
|
|
const isSuperAdmin = currentUser?.companyCode === "*" && currentUser?.userType === "SUPER_ADMIN";
|
|
|
|
|
|
|
|
|
|
// 상태 관리
|
|
|
|
|
const [users, setUsers] = useState<any[]>([]);
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
const [paginationInfo, setPaginationInfo] = useState({
|
|
|
|
|
currentPage: 1,
|
|
|
|
|
pageSize: 20,
|
|
|
|
|
totalItems: 0,
|
|
|
|
|
totalPages: 0,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 권한 변경 모달
|
|
|
|
|
const [authEditModal, setAuthEditModal] = useState({
|
|
|
|
|
isOpen: false,
|
|
|
|
|
user: null as any | null,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 데이터 로드
|
|
|
|
|
const loadUsers = useCallback(
|
|
|
|
|
async (page: number = 1) => {
|
|
|
|
|
setIsLoading(true);
|
|
|
|
|
setError(null);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await userAPI.getList({
|
|
|
|
|
page,
|
|
|
|
|
size: paginationInfo.pageSize,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (response.success && response.data) {
|
|
|
|
|
setUsers(response.data);
|
|
|
|
|
setPaginationInfo({
|
|
|
|
|
currentPage: response.currentPage || page,
|
|
|
|
|
pageSize: response.pageSize || paginationInfo.pageSize,
|
|
|
|
|
totalItems: response.total || 0,
|
|
|
|
|
totalPages: Math.ceil((response.total || 0) / (response.pageSize || paginationInfo.pageSize)),
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
setError(response.message || "사용자 목록을 불러오는데 실패했습니다.");
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("사용자 목록 로드 오류:", err);
|
|
|
|
|
setError("사용자 목록을 불러오는 중 오류가 발생했습니다.");
|
|
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[paginationInfo.pageSize],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
loadUsers(1);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// 권한 변경 핸들러
|
|
|
|
|
const handleEditAuth = (user: any) => {
|
|
|
|
|
setAuthEditModal({
|
|
|
|
|
isOpen: true,
|
|
|
|
|
user,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 권한 변경 모달 닫기
|
|
|
|
|
const handleAuthEditClose = () => {
|
|
|
|
|
setAuthEditModal({
|
|
|
|
|
isOpen: false,
|
|
|
|
|
user: null,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 권한 변경 성공
|
|
|
|
|
const handleAuthEditSuccess = () => {
|
|
|
|
|
loadUsers(paginationInfo.currentPage);
|
|
|
|
|
handleAuthEditClose();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 페이지 변경
|
|
|
|
|
const handlePageChange = (page: number) => {
|
|
|
|
|
loadUsers(page);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 최고 관리자가 아닌 경우
|
|
|
|
|
if (!isSuperAdmin) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="bg-background flex min-h-screen flex-col">
|
|
|
|
|
<div className="space-y-6 p-6">
|
|
|
|
|
<div className="space-y-2 border-b pb-4">
|
|
|
|
|
<h1 className="text-3xl font-bold tracking-tight">사용자 권한 관리</h1>
|
|
|
|
|
<p className="text-muted-foreground text-sm">사용자별 권한 레벨을 관리합니다. (최고 관리자 전용)</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<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">
|
|
|
|
|
권한 관리는 최고 관리자만 접근할 수 있습니다.
|
|
|
|
|
</p>
|
|
|
|
|
<Button variant="outline" onClick={() => window.history.back()}>
|
|
|
|
|
뒤로 가기
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ScrollToTop />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 16:40:59 +09:00
|
|
|
return (
|
|
|
|
|
<div className="bg-background flex min-h-screen flex-col">
|
|
|
|
|
<div className="space-y-6 p-6">
|
|
|
|
|
{/* 페이지 헤더 */}
|
|
|
|
|
<div className="space-y-2 border-b pb-4">
|
|
|
|
|
<h1 className="text-3xl font-bold tracking-tight">사용자 권한 관리</h1>
|
|
|
|
|
<p className="text-muted-foreground text-sm">사용자별 권한 레벨을 관리합니다. (최고 관리자 전용)</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-12-30 15:28:05 +09:00
|
|
|
{/* 에러 메시지 */}
|
|
|
|
|
{error && (
|
|
|
|
|
<div className="border-destructive/50 bg-destructive/10 rounded-lg border p-4">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<p className="text-destructive text-sm font-semibold">오류가 발생했습니다</p>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setError(null)}
|
|
|
|
|
className="text-destructive hover:text-destructive/80 transition-colors"
|
|
|
|
|
aria-label="에러 메시지 닫기"
|
|
|
|
|
>
|
|
|
|
|
✕
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-destructive/80 mt-1.5 text-sm">{error}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 사용자 권한 테이블 */}
|
|
|
|
|
<UserAuthTable
|
|
|
|
|
users={users}
|
|
|
|
|
isLoading={isLoading}
|
|
|
|
|
paginationInfo={paginationInfo}
|
|
|
|
|
onEditAuth={handleEditAuth}
|
|
|
|
|
onPageChange={handlePageChange}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* 권한 변경 모달 */}
|
|
|
|
|
<UserAuthEditModal
|
|
|
|
|
isOpen={authEditModal.isOpen}
|
|
|
|
|
onClose={handleAuthEditClose}
|
|
|
|
|
onSuccess={handleAuthEditSuccess}
|
|
|
|
|
user={authEditModal.user}
|
|
|
|
|
/>
|
2025-10-27 16:40:59 +09:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Scroll to Top 버튼 (모바일/태블릿 전용) */}
|
|
|
|
|
<ScrollToTop />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|