diff --git a/frontend/components/admin/dashboard/DashboardDesigner.tsx b/frontend/components/admin/dashboard/DashboardDesigner.tsx index 048e5172..0c41ff7c 100644 --- a/frontend/components/admin/dashboard/DashboardDesigner.tsx +++ b/frontend/components/admin/dashboard/DashboardDesigner.tsx @@ -6,6 +6,7 @@ import { DashboardCanvas } from "./DashboardCanvas"; import { DashboardTopMenu } from "./DashboardTopMenu"; import { ElementConfigModal } from "./ElementConfigModal"; import { ListWidgetConfigModal } from "./widgets/ListWidgetConfigModal"; +import { MenuAssignmentModal } from "./MenuAssignmentModal"; import { DashboardElement, ElementType, ElementSubtype } from "./types"; import { GRID_CONFIG, snapToGrid, snapSizeToGrid, calculateCellSize } from "./gridUtils"; import { Resolution, RESOLUTIONS, detectScreenResolution } from "./ResolutionSelector"; @@ -33,6 +34,10 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D const [canvasBackgroundColor, setCanvasBackgroundColor] = useState("#f9fafb"); const canvasRef = useRef(null); + // 메뉴 할당 모달 상태 + const [menuAssignmentModalOpen, setMenuAssignmentModalOpen] = useState(false); + const [savedDashboardInfo, setSavedDashboardInfo] = useState<{ id: string; title: string } | null>(null); + // 화면 해상도 자동 감지 const [screenResolution] = useState(() => detectScreenResolution()); const [resolution, setResolution] = useState(screenResolution); @@ -348,10 +353,12 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D console.log("✅ 저장된 대시보드:", savedDashboard); console.log("✅ 저장된 settings:", (savedDashboard as any).settings); - alert(`대시보드 "${savedDashboard.title}"이 업데이트되었습니다!`); - - // Next.js 라우터로 뷰어 페이지 이동 - router.push(`/dashboard/${savedDashboard.id}`); + // 메뉴 할당 모달 띄우기 (기존 대시보드 업데이트 시에도) + setSavedDashboardInfo({ + id: savedDashboard.id, + title: savedDashboard.title, + }); + setMenuAssignmentModalOpen(true); } else { // 새 대시보드 생성 const title = prompt("대시보드 제목을 입력하세요:", "새 대시보드"); @@ -372,11 +379,16 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D savedDashboard = await dashboardApi.createDashboard(dashboardData); - const viewDashboard = confirm(`대시보드 "${title}"이 저장되었습니다!\n\n지금 확인해보시겠습니까?`); - if (viewDashboard) { - // Next.js 라우터로 뷰어 페이지 이동 - router.push(`/dashboard/${savedDashboard.id}`); - } + // 대시보드 ID 설정 (다음 저장 시 업데이트 모드로 전환) + setDashboardId(savedDashboard.id); + setDashboardTitle(savedDashboard.title); + + // 메뉴 할당 모달 띄우기 + setSavedDashboardInfo({ + id: savedDashboard.id, + title: savedDashboard.title, + }); + setMenuAssignmentModalOpen(true); } } catch (error) { const errorMessage = error instanceof Error ? error.message : "알 수 없는 오류"; @@ -384,6 +396,58 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D } }, [elements, dashboardId, router, resolution, canvasBackgroundColor]); + // 메뉴 할당 처리 + const handleMenuAssignment = useCallback( + async (assignToMenu: boolean, menuId?: string, menuType?: "0" | "1") => { + if (!savedDashboardInfo) return; + + try { + if (assignToMenu && menuId) { + // 메뉴 API를 통해 대시보드 URL 할당 + const { menuApi } = await import("@/lib/api/menu"); + + // 대시보드 URL 생성 (관리자 메뉴면 mode=admin 추가) + let dashboardUrl = `/dashboard/${savedDashboardInfo.id}`; + if (menuType === "0") { + dashboardUrl += "?mode=admin"; + } + + // 메뉴 정보 가져오기 + const menuResponse = await menuApi.getMenuInfo(menuId); + if (menuResponse.success && menuResponse.data) { + const menu = menuResponse.data; + + // 메뉴 URL 업데이트 + await menuApi.updateMenu(menuId, { + menuUrl: dashboardUrl, + parentObjId: menu.parent_obj_id || menu.PARENT_OBJ_ID || "0", + menuNameKor: menu.menu_name_kor || menu.MENU_NAME_KOR || "", + menuDesc: menu.menu_desc || menu.MENU_DESC || "", + seq: menu.seq || menu.SEQ || 1, + menuType: menu.menu_type || menu.MENU_TYPE || "1", + status: menu.status || menu.STATUS || "active", + companyCode: menu.company_code || menu.COMPANY_CODE || "", + langKey: menu.lang_key || menu.LANG_KEY || "", + }); + + alert(`메뉴 "${menu.menu_name_kor || menu.MENU_NAME_KOR}"에 대시보드가 할당되었습니다!`); + } + } + + // 모달 닫기 + setMenuAssignmentModalOpen(false); + setSavedDashboardInfo(null); + + // 대시보드 뷰어로 이동 + router.push(`/dashboard/${savedDashboardInfo.id}`); + } catch (error) { + console.error("메뉴 할당 오류:", error); + alert("메뉴 할당 중 오류가 발생했습니다."); + } + }, + [savedDashboardInfo, router], + ); + // 로딩 중이면 로딩 화면 표시 if (isLoading) { return ( @@ -458,6 +522,22 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D )} )} + + {/* 메뉴 할당 모달 */} + {savedDashboardInfo && ( + { + setMenuAssignmentModalOpen(false); + setSavedDashboardInfo(null); + // 모달을 그냥 닫으면 대시보드 뷰어로 이동 + router.push(`/dashboard/${savedDashboardInfo.id}`); + }} + onConfirm={handleMenuAssignment} + dashboardId={savedDashboardInfo.id} + dashboardTitle={savedDashboardInfo.title} + /> + )} ); } diff --git a/frontend/components/admin/dashboard/MenuAssignmentModal.tsx b/frontend/components/admin/dashboard/MenuAssignmentModal.tsx new file mode 100644 index 00000000..9220a0c8 --- /dev/null +++ b/frontend/components/admin/dashboard/MenuAssignmentModal.tsx @@ -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 = ({ + isOpen, + onClose, + onConfirm, + dashboardId, + dashboardTitle, +}) => { + const [assignToMenu, setAssignToMenu] = useState(false); + const [selectedMenuId, setSelectedMenuId] = useState(""); + const [selectedMenuType, setSelectedMenuType] = useState<"0" | "1">("0"); + const [adminMenus, setAdminMenus] = useState([]); + const [userMenus, setUserMenus] = useState([]); + 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 ( + + + + 대시보드 저장 완료 + '{dashboardTitle}' 대시보드가 저장되었습니다. + + +
+
+ + setAssignToMenu(value === "yes")} + className="flex space-x-4" + > +
+ + +
+
+ + +
+
+
+ + {assignToMenu && ( + <> +
+ + { + setSelectedMenuType(value as "0" | "1"); + setSelectedMenuId(""); // 메뉴 타입 변경 시 선택 초기화 + }} + className="flex space-x-4" + > +
+ + +
+
+ + +
+
+
+ +
+ + {loading ? ( +
+ +
+ ) : ( + + )} +
+ + )} +
+ + + + + +
+
+ ); +};