2025-10-16 14:53:06 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
2025-11-05 16:36:32 +09:00
|
|
|
|
|
|
|
|
|
2025-10-16 14:53:06 +09:00
|
|
|
DialogHeader,
|
2025-11-05 16:36:32 +09:00
|
|
|
|
|
|
|
|
} from "@/components/ui/resizable-dialog";
|
2025-10-16 14:53:06 +09:00
|
|
|
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) {
|
2025-10-17 10:38:22 +09:00
|
|
|
// console.error("메뉴 목록 로드 실패:", error);
|
2025-10-16 14:53:06 +09:00
|
|
|
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 (
|
2025-11-05 16:36:32 +09:00
|
|
|
<ResizableDialog open={isOpen} onOpenChange={handleClose}>
|
|
|
|
|
<ResizableDialogContent className="sm:max-w-[500px]">
|
|
|
|
|
<ResizableDialogHeader>
|
|
|
|
|
<ResizableDialogTitle>대시보드 저장 완료</ResizableDialogTitle>
|
|
|
|
|
<ResizableDialogDescription>'{dashboardTitle}' 대시보드가 저장되었습니다.</ResizableDialogDescription>
|
|
|
|
|
</ResizableDialogHeader>
|
2025-10-16 14:53:06 +09:00
|
|
|
|
|
|
|
|
<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">
|
2025-10-29 17:53:03 +09:00
|
|
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
2025-10-16 14:53:06 +09:00
|
|
|
</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>
|
|
|
|
|
|
2025-11-05 16:36:32 +09:00
|
|
|
<ResizableDialogFooter>
|
2025-10-16 14:53:06 +09:00
|
|
|
<Button variant="outline" onClick={handleClose}>
|
|
|
|
|
취소
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={handleConfirm}>{assignToMenu ? "메뉴에 할당하고 완료" : "완료"}</Button>
|
2025-11-05 16:36:32 +09:00
|
|
|
</ResizableDialogFooter>
|
|
|
|
|
</ResizableDialogContent>
|
|
|
|
|
</ResizableDialog>
|
2025-10-16 14:53:06 +09:00
|
|
|
);
|
|
|
|
|
};
|