kontenhumas-fe/app/[locale]/(admin)/admin/settings/menu-management/page.tsx

975 lines
34 KiB
TypeScript

"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<MasterMenu[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [editingMenu, setEditingMenu] = useState<MasterMenu | null>(null);
const [selectedMenuForActions, setSelectedMenuForActions] = useState<MasterMenu | null>(null);
const [menuActions, setMenuActions] = useState<MenuAction[]>([]);
const [isActionsDialogOpen, setIsActionsDialogOpen] = useState(false);
const [isActionFormOpen, setIsActionFormOpen] = useState(false);
const [editingAction, setEditingAction] = useState<MenuAction | null>(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 (
<>
<SiteBreadcrumb />
<div className="container mx-auto p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">Menu Management</h1>
<p className="text-gray-600 mt-2">
Manage system menus and their configurations
</p>
</div>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button className="flex items-center gap-2" onClick={() => handleOpenDialog()}>
<PlusIcon className="h-4 w-4" />
Create Menu
</Button>
</DialogTrigger>
{/* @ts-ignore */}
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{editingMenu ? `Edit Menu: ${editingMenu.name}` : "Create New Menu"}
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<FormField
label="Menu Name"
name="name"
type="text"
placeholder="e.g., Dashboard, Content Management"
value={formData.name}
onChange={(value) => setFormData({ ...formData, name: value })}
required
/>
<FormField
label="Description"
name="description"
type="text"
placeholder="Brief description of the menu"
value={formData.description}
onChange={(value) => setFormData({ ...formData, description: value })}
required
/>
<FormField
label="Group"
name="group"
type="text"
placeholder="e.g., Main, Settings, Content"
value={formData.group}
onChange={(value) => setFormData({ ...formData, group: value })}
required
/>
<FormField
label="Parent Menu"
name="parentMenuId"
type="select"
placeholder="Select parent menu (optional)"
value={formData.parentMenuId || 0}
onChange={(value) => 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}`,
})),
]}
/>
<FormField
label="Icon"
name="icon"
type="text"
placeholder="e.g., material-symbols:dashboard, heroicons:bars-3"
value={formData.icon}
onChange={(value) => setFormData({ ...formData, icon: value })}
helpText="Icon identifier (e.g., from Iconify)"
/>
<FormField
label="Status ID"
name="statusId"
type="number"
placeholder="1"
value={formData.statusId}
onChange={(value) => setFormData({ ...formData, statusId: Number(value) || 1 })}
required
/>
<div className="flex items-center justify-end gap-2 pt-4 border-t">
<Button
variant="outline"
onClick={() => setIsDialogOpen(false)}
>
Cancel
</Button>
<Button onClick={handleSave}>
{editingMenu ? "Update" : "Create"} Menu
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
{isLoading ? (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-4 text-gray-600">Loading menus...</p>
</div>
) : menus.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{menus.map((menu) => {
const parentMenu = menus.find((m) => m.id === menu.parentMenuId);
return (
<Card key={menu.id} className="hover:shadow-lg transition-shadow">
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span className="truncate">{menu.name}</span>
{menu.isActive ? (
<span className="px-2 py-1 text-xs bg-green-100 text-green-800 rounded-full">
Active
</span>
) : (
<span className="px-2 py-1 text-xs bg-gray-100 text-gray-800 rounded-full">
Inactive
</span>
)}
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2 mb-4">
<div className="text-sm text-gray-600">{menu.description}</div>
<div className="text-xs text-gray-500">
<span className="font-medium">Group:</span> {menu.group}
</div>
{parentMenu && (
<div className="text-xs text-gray-500">
<span className="font-medium">Parent:</span> {parentMenu.name}
</div>
)}
{menu.icon && (
<div className="text-xs text-gray-500">
<span className="font-medium">Icon:</span> {menu.icon}
</div>
)}
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => handleOpenDialog(menu)}
>
<EditIcon className="h-4 w-4 mr-2" />
Edit
</Button>
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => handleManageActions(menu)}
>
<MenuIcon className="h-4 w-4 mr-2" />
Actions
</Button>
<Button
variant="outline"
size="sm"
className="flex-1 text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={() => handleDelete(menu)}
>
<DeleteIcon className="h-4 w-4 mr-2" />
Delete
</Button>
</div>
</CardContent>
</Card>
);
})}
</div>
) : (
<Card>
<CardContent className="flex items-center justify-center py-12">
<div className="text-center">
<MenuIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No Menus Found</h3>
<p className="text-gray-500 mb-4">
Create your first menu to define system navigation
</p>
<Button onClick={() => handleOpenDialog()}>
<PlusIcon className="h-4 w-4 mr-2" />
Create Menu
</Button>
</div>
</CardContent>
</Card>
)}
{/* Actions Management Dialog */}
<Dialog open={isActionsDialogOpen} onOpenChange={setIsActionsDialogOpen}>
{/* @ts-ignore */}
<DialogContent size="lg" className="!max-w-[60vw] !w-[60vw] !min-w-[60vw] max-h-[95vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
Manage Actions: {selectedMenuForActions?.name}
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="flex items-center justify-between">
<p className="text-sm text-gray-600">
Manage actions available for this menu
</p>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={handleQuickAddActions}
>
Quick Add Standard Actions
</Button>
<Button
size="sm"
onClick={() => handleOpenActionForm()}
>
<PlusIcon className="h-4 w-4 mr-2" />
Add Action
</Button>
</div>
</div>
{menuActions.length > 0 ? (
<div className="border rounded-lg">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Code</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Path URL</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Method</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Position</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{menuActions.map((action) => (
<tr key={action.id}>
<td className="px-4 py-3 text-sm font-medium text-gray-900">{action.actionCode}</td>
<td className="px-4 py-3 text-sm text-gray-600">{action.actionName}</td>
<td className="px-4 py-3 text-sm text-gray-600">{action.pathUrl || "-"}</td>
<td className="px-4 py-3 text-sm text-gray-600">{action.httpMethod || "-"}</td>
<td className="px-4 py-3 text-sm text-gray-600">{action.position || "-"}</td>
<td className="px-4 py-3 text-sm">
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleOpenActionForm(action)}
>
<EditIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
className="text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={() => handleDeleteAction(action)}
>
<DeleteIcon className="h-4 w-4" />
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<Card>
<CardContent className="flex items-center justify-center py-12">
<div className="text-center">
<p className="text-gray-500 mb-4">No actions found for this menu</p>
<Button onClick={() => handleOpenActionForm()}>
<PlusIcon className="h-4 w-4 mr-2" />
Add First Action
</Button>
</div>
</CardContent>
</Card>
)}
</div>
</DialogContent>
</Dialog>
{/* Action Form Dialog */}
<Dialog open={isActionFormOpen} onOpenChange={setIsActionFormOpen}>
{/* @ts-ignore */}
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{editingAction ? `Edit Action: ${editingAction.actionName}` : "Create New Action"}
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<FormField
label="Action Code"
name="actionCode"
type="text"
placeholder="e.g., view, create, edit, delete"
value={actionFormData.actionCode}
onChange={(value) => setActionFormData({ ...actionFormData, actionCode: value })}
required
helpText="Unique identifier for the action"
/>
<FormField
label="Action Name"
name="actionName"
type="text"
placeholder="e.g., View Content, Create Content"
value={actionFormData.actionName}
onChange={(value) => setActionFormData({ ...actionFormData, actionName: value })}
required
/>
<FormField
label="Description"
name="description"
type="textarea"
placeholder="Brief description of the action"
value={actionFormData.description}
onChange={(value) => setActionFormData({ ...actionFormData, description: value })}
/>
<FormField
label="Path URL"
name="pathUrl"
type="text"
placeholder="e.g., /admin/articles, /api/articles"
value={actionFormData.pathUrl}
onChange={(value) => setActionFormData({ ...actionFormData, pathUrl: value })}
helpText="Optional: URL path for routing"
/>
<FormField
label="HTTP Method"
name="httpMethod"
type="select"
placeholder="Select HTTP method"
value={actionFormData.httpMethod || "none"}
onChange={(value) => 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" },
]}
/>
<FormField
label="Position"
name="position"
type="number"
placeholder="1"
value={actionFormData.position}
onChange={(value) => setActionFormData({ ...actionFormData, position: Number(value) || 0 })}
helpText="Order of action in the menu"
/>
<div className="flex items-center justify-end gap-2 pt-4 border-t">
<Button
variant="outline"
onClick={() => setIsActionFormOpen(false)}
>
Cancel
</Button>
<Button onClick={handleSaveAction}>
{editingAction ? "Update" : "Create"} Action
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</>
);
}