대시보드에 화면 할당 (중간)
This commit is contained in:
210
frontend/components/admin/dashboard/MenuAssignmentModal.tsx
Normal file
210
frontend/components/admin/dashboard/MenuAssignmentModal.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { toast } from "sonner";
|
||||
import { menuApi, MenuItem } from "@/lib/api/menu";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
interface MenuAssignmentModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: (assignToMenu: boolean, menuId?: string, menuType?: "0" | "1") => void;
|
||||
dashboardId: string;
|
||||
dashboardTitle: string;
|
||||
}
|
||||
|
||||
export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
dashboardId,
|
||||
dashboardTitle,
|
||||
}) => {
|
||||
const [assignToMenu, setAssignToMenu] = useState<boolean>(false);
|
||||
const [selectedMenuId, setSelectedMenuId] = useState<string>("");
|
||||
const [selectedMenuType, setSelectedMenuType] = useState<"0" | "1">("0");
|
||||
const [adminMenus, setAdminMenus] = useState<MenuItem[]>([]);
|
||||
const [userMenus, setUserMenus] = useState<MenuItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 메뉴 목록 로드
|
||||
useEffect(() => {
|
||||
if (isOpen && assignToMenu) {
|
||||
loadMenus();
|
||||
}
|
||||
}, [isOpen, assignToMenu]);
|
||||
|
||||
const loadMenus = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [adminResponse, userResponse] = await Promise.all([
|
||||
menuApi.getAdminMenus(), // 관리자 메뉴
|
||||
menuApi.getUserMenus(), // 사용자 메뉴
|
||||
]);
|
||||
|
||||
if (adminResponse.success) {
|
||||
setAdminMenus(adminResponse.data || []);
|
||||
}
|
||||
if (userResponse.success) {
|
||||
setUserMenus(userResponse.data || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("메뉴 목록 로드 실패:", error);
|
||||
toast.error("메뉴 목록을 불러오는데 실패했습니다.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 메뉴 트리를 평탄화하여 Select 옵션으로 변환
|
||||
const flattenMenus = (menus: MenuItem[], level: number = 0): Array<{ id: string; name: string; level: number }> => {
|
||||
const result: Array<{ id: string; name: string; level: number }> = [];
|
||||
|
||||
menus.forEach((menu) => {
|
||||
const menuId = menu.objid || menu.OBJID || "";
|
||||
const menuName = menu.menu_name_kor || menu.MENU_NAME_KOR || "";
|
||||
const parentId = menu.parent_obj_id || menu.PARENT_OBJ_ID || "0";
|
||||
|
||||
// 메뉴 이름이 있고, 최상위가 아닌 경우에만 추가
|
||||
if (menuName && parentId !== "0") {
|
||||
result.push({
|
||||
id: menuId,
|
||||
name: " ".repeat(level) + menuName,
|
||||
level,
|
||||
});
|
||||
|
||||
// 하위 메뉴가 있으면 재귀 호출
|
||||
const children = menus.filter((m) => (m.parent_obj_id || m.PARENT_OBJ_ID) === menuId);
|
||||
if (children.length > 0) {
|
||||
result.push(...flattenMenus(children, level + 1));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const currentMenus = selectedMenuType === "0" ? adminMenus : userMenus;
|
||||
const flatMenus = flattenMenus(currentMenus);
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (assignToMenu && !selectedMenuId) {
|
||||
toast.error("메뉴를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
onConfirm(assignToMenu, selectedMenuId, selectedMenuType);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAssignToMenu(false);
|
||||
setSelectedMenuId("");
|
||||
setSelectedMenuType("0");
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>대시보드 저장 완료</DialogTitle>
|
||||
<DialogDescription>'{dashboardTitle}' 대시보드가 저장되었습니다.</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-3">
|
||||
<Label>이 대시보드를 메뉴에 할당하시겠습니까?</Label>
|
||||
<RadioGroup
|
||||
value={assignToMenu ? "yes" : "no"}
|
||||
onValueChange={(value) => setAssignToMenu(value === "yes")}
|
||||
className="flex space-x-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="yes" id="yes" />
|
||||
<Label htmlFor="yes" className="cursor-pointer font-normal">
|
||||
예, 메뉴에 할당
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="no" id="no" />
|
||||
<Label htmlFor="no" className="cursor-pointer font-normal">
|
||||
아니오, 나중에
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{assignToMenu && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label>메뉴 타입</Label>
|
||||
<RadioGroup
|
||||
value={selectedMenuType}
|
||||
onValueChange={(value) => {
|
||||
setSelectedMenuType(value as "0" | "1");
|
||||
setSelectedMenuId(""); // 메뉴 타입 변경 시 선택 초기화
|
||||
}}
|
||||
className="flex space-x-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="0" id="admin" />
|
||||
<Label htmlFor="admin" className="cursor-pointer font-normal">
|
||||
관리자 메뉴
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="1" id="user" />
|
||||
<Label htmlFor="user" className="cursor-pointer font-normal">
|
||||
사용자 메뉴
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>할당할 메뉴 선택</Label>
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
|
||||
</div>
|
||||
) : (
|
||||
<Select value={selectedMenuId} onValueChange={setSelectedMenuId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="메뉴를 선택하세요" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="max-h-[300px]">
|
||||
{flatMenus.map((menu) => (
|
||||
<SelectItem key={menu.id} value={menu.id}>
|
||||
{menu.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleConfirm}>{assignToMenu ? "메뉴에 할당하고 완료" : "완료"}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user