각 회사별 데이터 분리
This commit is contained in:
211
frontend/components/admin/UserAuthEditModal.tsx
Normal file
211
frontend/components/admin/UserAuthEditModal.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { userAPI } from "@/lib/api/user";
|
||||
import { Shield, ShieldCheck, User, Users, Building2, AlertTriangle } from "lucide-react";
|
||||
|
||||
interface UserAuthEditModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
user: any | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 권한 변경 모달
|
||||
*
|
||||
* 권한 레벨만 변경 가능 (최고 관리자 전용)
|
||||
*/
|
||||
export function UserAuthEditModal({ isOpen, onClose, onSuccess, user }: UserAuthEditModalProps) {
|
||||
const [selectedUserType, setSelectedUserType] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
// 모달 열릴 때 현재 권한 설정
|
||||
useEffect(() => {
|
||||
if (isOpen && user) {
|
||||
setSelectedUserType(user.userType || "USER");
|
||||
setShowConfirmation(false);
|
||||
}
|
||||
}, [isOpen, user]);
|
||||
|
||||
// 권한 정보
|
||||
const userTypeOptions = [
|
||||
{
|
||||
value: "SUPER_ADMIN",
|
||||
label: "최고 관리자",
|
||||
description: "모든 회사 관리, DDL 실행, 회사 생성/삭제 가능",
|
||||
icon: <ShieldCheck className="h-4 w-4" />,
|
||||
color: "text-purple-600",
|
||||
},
|
||||
{
|
||||
value: "COMPANY_ADMIN",
|
||||
label: "회사 관리자",
|
||||
description: "자기 회사 데이터 및 사용자 관리 가능",
|
||||
icon: <Building2 className="h-4 w-4" />,
|
||||
color: "text-blue-600",
|
||||
},
|
||||
{
|
||||
value: "USER",
|
||||
label: "일반 사용자",
|
||||
description: "자기 회사 데이터 조회/수정만 가능",
|
||||
icon: <User className="h-4 w-4" />,
|
||||
color: "text-gray-600",
|
||||
},
|
||||
{
|
||||
value: "GUEST",
|
||||
label: "게스트",
|
||||
description: "제한된 조회 권한",
|
||||
icon: <Users className="h-4 w-4" />,
|
||||
color: "text-green-600",
|
||||
},
|
||||
{
|
||||
value: "PARTNER",
|
||||
label: "협력업체",
|
||||
description: "협력업체 전용 권한",
|
||||
icon: <Shield className="h-4 w-4" />,
|
||||
color: "text-orange-600",
|
||||
},
|
||||
];
|
||||
|
||||
const selectedOption = userTypeOptions.find((opt) => opt.value === selectedUserType);
|
||||
|
||||
// 권한 변경 여부 확인
|
||||
const isUserTypeChanged = user && selectedUserType !== user.userType;
|
||||
|
||||
// 권한 변경 처리
|
||||
const handleSave = async () => {
|
||||
if (!user || !isUserTypeChanged) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
// SUPER_ADMIN 변경 시 확인
|
||||
if (selectedUserType === "SUPER_ADMIN" && !showConfirmation) {
|
||||
setShowConfirmation(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await userAPI.update({
|
||||
userId: user.userId,
|
||||
userName: user.userName,
|
||||
companyCode: user.companyCode,
|
||||
deptCode: user.deptCode,
|
||||
userType: selectedUserType,
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
onSuccess?.();
|
||||
} else {
|
||||
alert(response.message || "권한 변경에 실패했습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("권한 변경 오류:", error);
|
||||
alert("권한 변경 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">사용자 권한 변경</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 사용자 정보 */}
|
||||
<div className="bg-muted/50 rounded-lg border p-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">사용자 ID</span>
|
||||
<span className="font-mono font-medium">{user.userId}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">사용자명</span>
|
||||
<span className="font-medium">{user.userName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">회사</span>
|
||||
<span className="font-medium">{user.companyName || user.companyCode}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">현재 권한</span>
|
||||
<span className="font-medium">
|
||||
{userTypeOptions.find((opt) => opt.value === user.userType)?.label || user.userType}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 권한 선택 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="userType" className="text-sm font-medium">
|
||||
새로운 권한 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Select value={selectedUserType} onValueChange={setSelectedUserType}>
|
||||
<SelectTrigger className="h-10">
|
||||
<SelectValue placeholder="권한 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{userTypeOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={option.color}>{option.icon}</span>
|
||||
<span>{option.label}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedOption && <p className="text-muted-foreground text-xs">{selectedOption.description}</p>}
|
||||
</div>
|
||||
|
||||
{/* SUPER_ADMIN 경고 */}
|
||||
{showConfirmation && selectedUserType === "SUPER_ADMIN" && (
|
||||
<div className="rounded-lg border border-orange-300 bg-orange-50 p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="mt-0.5 h-5 w-5 flex-shrink-0 text-orange-600" />
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-semibold text-orange-900">최고 관리자 권한 부여 경고</p>
|
||||
<p className="text-xs text-orange-800">
|
||||
최고 관리자 권한은 모든 회사 데이터에 접근하고, DDL을 실행하며, 회사를 생성/삭제할 수 있는 최상위
|
||||
권한입니다. 신중하게 부여해주세요.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
disabled={isLoading}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isLoading || !isUserTypeChanged}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
{isLoading ? "처리중..." : showConfirmation ? "확인 및 저장" : "저장"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user