"use client"; import React, { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { PlusIcon, MenuIcon, EditIcon, DeleteIcon } from "@/components/icons"; import { MasterMenu, getMasterMenus, getMasterMenuById, createMasterMenu, updateMasterMenu, deleteMasterMenu, } from "@/service/menu-modules"; import { MenuAction, getMenuActionsByMenuId, createMenuAction, updateMenuAction, deleteMenuAction, createMenuActionsBatch, } from "@/service/menu-actions"; import SiteBreadcrumb from "@/components/site-breadcrumb"; import Swal from "sweetalert2"; import { FormField } from "@/components/form/common/FormField"; import { getCookiesDecrypt } from "@/lib/utils"; export default function MenuManagementPage() { const router = useRouter(); const [menus, setMenus] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingMenu, setEditingMenu] = useState(null); const [selectedMenuForActions, setSelectedMenuForActions] = useState(null); const [menuActions, setMenuActions] = useState([]); const [isActionsDialogOpen, setIsActionsDialogOpen] = useState(false); const [isActionFormOpen, setIsActionFormOpen] = useState(false); const [editingAction, setEditingAction] = useState(null); const [actionFormData, setActionFormData] = useState({ actionCode: "", actionName: "", description: "", pathUrl: "", httpMethod: "none", position: 0, }); const [formData, setFormData] = useState({ name: "", description: "", group: "", statusId: 1, parentMenuId: undefined as number | undefined, icon: "", }); useEffect(() => { // Check if user has roleId = 1 const roleId = getCookiesDecrypt("urie"); if (Number(roleId) !== 1) { Swal.fire({ title: "Access Denied", text: "You don't have permission to access this page", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }).then(() => { router.push("/admin/dashboard"); }); return; } loadData(); }, [router]); const loadData = async () => { setIsLoading(true); try { const menusRes = await getMasterMenus({ limit: 100 }); if (menusRes?.error) { Swal.fire({ title: "Error", text: menusRes?.message || "Failed to load menus", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); setMenus([]); } else { // Transform snake_case to camelCase for consistency const menusData = (menusRes?.data?.data || []).map((menu: any) => ({ ...menu, moduleId: menu.module_id || menu.moduleId, parentMenuId: menu.parent_menu_id !== undefined ? menu.parent_menu_id : menu.parentMenuId, statusId: menu.status_id || menu.statusId, isActive: menu.is_active !== undefined ? menu.is_active : menu.isActive, })); setMenus(menusData); } } catch (error) { console.error("Error loading data:", error); Swal.fire({ title: "Error", text: "An unexpected error occurred while loading menus", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); setMenus([]); } finally { setIsLoading(false); } }; const handleOpenDialog = async (menu?: MasterMenu) => { if (menu) { // Fetch fresh data from API to ensure all fields are loaded correctly try { const res = await getMasterMenuById(menu.id); if (!res?.error && res?.data?.data) { const menuData = res.data.data as any; setEditingMenu(menu); setFormData({ name: menuData.name || menu.name || "", description: menuData.description || menu.description || "", group: menuData.group || menu.group || "", statusId: menuData.status_id || menuData.statusId || menu.statusId || 1, parentMenuId: menuData.parent_menu_id !== undefined && menuData.parent_menu_id !== null ? menuData.parent_menu_id : (menuData.parentMenuId !== undefined && menuData.parentMenuId !== null ? menuData.parentMenuId : (menu.parentMenuId || undefined)), icon: menuData.icon || menu.icon || "", }); } else { // Fallback to menu object if API call fails setEditingMenu(menu); setFormData({ name: menu.name || "", description: menu.description || "", group: menu.group || "", statusId: (menu as any).status_id || menu.statusId || 1, parentMenuId: (menu as any).parent_menu_id !== undefined && (menu as any).parent_menu_id !== null ? (menu as any).parent_menu_id : (menu.parentMenuId || undefined), icon: menu.icon || "", }); } } catch (error) { console.error("Error loading menu details:", error); // Fallback to menu object if API call fails setEditingMenu(menu); setFormData({ name: menu.name || "", description: menu.description || "", group: menu.group || "", statusId: (menu as any).status_id || menu.statusId || 1, parentMenuId: (menu as any).parent_menu_id !== undefined && (menu as any).parent_menu_id !== null ? (menu as any).parent_menu_id : (menu.parentMenuId || undefined), icon: menu.icon || "", }); } } else { setEditingMenu(null); setFormData({ name: "", description: "", group: "", statusId: 1, parentMenuId: undefined, icon: "", }); } setIsDialogOpen(true); }; const handleSave = async () => { try { // Prepare payload without moduleId const payload = { name: formData.name, description: formData.description, group: formData.group, statusId: formData.statusId, parentMenuId: formData.parentMenuId, icon: formData.icon, }; if (editingMenu) { const res = await updateMasterMenu(editingMenu.id, payload); if (res?.error) { Swal.fire({ title: "Error", text: res?.message || "Failed to update menu", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { Swal.fire({ title: "Success", text: "Menu updated successfully", icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); await loadData(); setIsDialogOpen(false); } } else { const res = await createMasterMenu(payload); if (res?.error) { Swal.fire({ title: "Error", text: res?.message || "Failed to create menu", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { Swal.fire({ title: "Success", text: "Menu created successfully", icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); await loadData(); setIsDialogOpen(false); } } } catch (error) { console.error("Error saving menu:", error); Swal.fire({ title: "Error", text: "An unexpected error occurred", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } }; const handleManageActions = async (menu: MasterMenu) => { try { setSelectedMenuForActions(menu); setIsActionsDialogOpen(true); await loadMenuActions(menu.id); } catch (error) { console.error("Error opening actions dialog:", error); Swal.fire({ title: "Error", text: "Failed to open actions management", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } }; const loadMenuActions = async (menuId: number) => { try { const res = await getMenuActionsByMenuId(menuId); if (res?.error) { Swal.fire({ title: "Error", text: res?.message || "Failed to load menu actions", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); setMenuActions([]); } else { setMenuActions(res?.data?.data || []); } } catch (error) { console.error("Error loading menu actions:", error); Swal.fire({ title: "Error", text: "An unexpected error occurred while loading menu actions", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); setMenuActions([]); } }; const handleOpenActionForm = (action?: MenuAction) => { if (action) { setEditingAction(action); setActionFormData({ actionCode: action.actionCode, actionName: action.actionName, description: action.description || "", pathUrl: action.pathUrl || "", httpMethod: action.httpMethod || "none", position: action.position || 0, }); } else { setEditingAction(null); setActionFormData({ actionCode: "", actionName: "", description: "", pathUrl: "", httpMethod: "none", position: menuActions.length + 1, }); } setIsActionFormOpen(true); }; const handleSaveAction = async () => { if (!selectedMenuForActions) return; try { // Prepare payload, converting "none" back to undefined for httpMethod const payload = { menuId: selectedMenuForActions.id, actionCode: actionFormData.actionCode, actionName: actionFormData.actionName, description: actionFormData.description || undefined, pathUrl: actionFormData.pathUrl || undefined, httpMethod: actionFormData.httpMethod === "none" ? undefined : actionFormData.httpMethod, position: actionFormData.position, }; if (editingAction) { const res = await updateMenuAction(editingAction.id, payload); if (res?.error) { Swal.fire({ title: "Error", text: res?.message || "Failed to update action", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { Swal.fire({ title: "Success", text: "Action updated successfully", icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); await loadMenuActions(selectedMenuForActions.id); setIsActionFormOpen(false); } } else { const res = await createMenuAction(payload); if (res?.error) { Swal.fire({ title: "Error", text: res?.message || "Failed to create action", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { Swal.fire({ title: "Success", text: "Action created successfully", icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); await loadMenuActions(selectedMenuForActions.id); setIsActionFormOpen(false); } } } catch (error) { console.error("Error saving action:", error); Swal.fire({ title: "Error", text: "An unexpected error occurred", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } }; const handleDeleteAction = async (action: MenuAction) => { const result = await Swal.fire({ title: "Delete Action?", text: `Are you sure you want to delete "${action.actionName}"?`, icon: "warning", showCancelButton: true, confirmButtonText: "Yes, delete it", cancelButtonText: "Cancel", customClass: { popup: 'swal-z-index-9999' } }); if (result.isConfirmed) { try { const res = await deleteMenuAction(action.id); if (res?.error) { Swal.fire({ title: "Error", text: res?.message || "Failed to delete action", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { Swal.fire({ title: "Deleted!", text: "Action has been deleted.", icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); if (selectedMenuForActions) { await loadMenuActions(selectedMenuForActions.id); } } } catch (error) { console.error("Error deleting action:", error); Swal.fire({ title: "Error", text: "An unexpected error occurred", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } } }; const handleQuickAddActions = async () => { if (!selectedMenuForActions) return; const standardActions = ["view", "create", "edit", "delete", "approve", "export"]; const existingActionCodes = menuActions.map(a => a.actionCode); const newActions = standardActions.filter(code => !existingActionCodes.includes(code)); if (newActions.length === 0) { Swal.fire({ title: "Info", text: "All standard actions already exist", icon: "info", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); return; } try { const res = await createMenuActionsBatch({ menuId: selectedMenuForActions.id, actionCodes: newActions, }); if (res?.error) { Swal.fire({ title: "Error", text: res?.message || "Failed to create actions", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { Swal.fire({ title: "Success", text: `${newActions.length} actions created successfully`, icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); await loadMenuActions(selectedMenuForActions.id); } } catch (error) { console.error("Error creating actions:", error); Swal.fire({ title: "Error", text: "An unexpected error occurred", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } }; const handleDelete = async (menu: MasterMenu) => { const result = await Swal.fire({ title: "Delete Menu?", text: `Are you sure you want to delete "${menu.name}"? This action cannot be undone.`, icon: "warning", showCancelButton: true, confirmButtonText: "Yes, delete it", cancelButtonText: "Cancel", customClass: { popup: 'swal-z-index-9999' } }); if (result.isConfirmed) { try { const res = await deleteMasterMenu(menu.id); if (res?.error) { Swal.fire({ title: "Error", text: res?.message || "Failed to delete menu", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { Swal.fire({ title: "Deleted!", text: "Menu has been deleted.", icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); await loadData(); } } catch (error) { console.error("Error deleting menu:", error); Swal.fire({ title: "Error", text: "An unexpected error occurred", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } } }; const roleId = getCookiesDecrypt("urie"); if (Number(roleId) !== 1) { return null; // Will redirect in useEffect } return ( <>

Menu Management

Manage system menus and their configurations

{/* @ts-ignore */} {editingMenu ? `Edit Menu: ${editingMenu.name}` : "Create New Menu"}
setFormData({ ...formData, name: value })} required /> setFormData({ ...formData, description: value })} required /> setFormData({ ...formData, group: value })} required /> setFormData({ ...formData, parentMenuId: Number(value) === 0 ? undefined : Number(value) })} options={[ { value: 0, label: "No Parent (Root Menu)" }, ...menus .filter((m) => !m.parentMenuId || m.id !== editingMenu?.id) .map((menu) => ({ value: menu.id, label: `${menu.name} - ${menu.group}`, })), ]} /> setFormData({ ...formData, icon: value })} helpText="Icon identifier (e.g., from Iconify)" /> setFormData({ ...formData, statusId: Number(value) || 1 })} required />
{isLoading ? (

Loading menus...

) : menus.length > 0 ? (
{menus.map((menu) => { const parentMenu = menus.find((m) => m.id === menu.parentMenuId); return ( {menu.name} {menu.isActive ? ( Active ) : ( Inactive )}
{menu.description}
Group: {menu.group}
{parentMenu && (
Parent: {parentMenu.name}
)} {menu.icon && (
Icon: {menu.icon}
)}
); })}
) : (

No Menus Found

Create your first menu to define system navigation

)} {/* Actions Management Dialog */} {/* @ts-ignore */} Manage Actions: {selectedMenuForActions?.name}

Manage actions available for this menu

{menuActions.length > 0 ? (
{menuActions.map((action) => ( ))}
Code Name Path URL Method Position Actions
{action.actionCode} {action.actionName} {action.pathUrl || "-"} {action.httpMethod || "-"} {action.position || "-"}
) : (

No actions found for this menu

)}
{/* Action Form Dialog */} {/* @ts-ignore */} {editingAction ? `Edit Action: ${editingAction.actionName}` : "Create New Action"}
setActionFormData({ ...actionFormData, actionCode: value })} required helpText="Unique identifier for the action" /> setActionFormData({ ...actionFormData, actionName: value })} required /> setActionFormData({ ...actionFormData, description: value })} /> setActionFormData({ ...actionFormData, pathUrl: value })} helpText="Optional: URL path for routing" /> setActionFormData({ ...actionFormData, httpMethod: value })} options={[ { value: "none", label: "Not specified" }, { value: "GET", label: "GET" }, { value: "POST", label: "POST" }, { value: "PUT", label: "PUT" }, { value: "PATCH", label: "PATCH" }, { value: "DELETE", label: "DELETE" }, ]} /> setActionFormData({ ...actionFormData, position: Number(value) || 0 })} helpText="Order of action in the menu" />
); }