"use client"; import React, { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { ChevronDownIcon, ChevronUpIcon, SaveIcon, EyeIcon, RotateCcwIcon, UsersIcon, HierarchyIcon, PlusIcon, TrashIcon } from "@/components/icons"; import { FormField } from "./common/FormField"; import { DynamicArray } from "./common/DynamicArray"; import { UserLevelsCreateRequest, UserLevel, Province, createUserLevel, updateUserLevel, getUserLevels, getProvinces, } from "@/service/approval-workflows"; import { MasterMenu, getMasterMenus, } from "@/service/menu-modules"; import { getUserLevelMenuAccessesByUserLevelId, createUserLevelMenuAccessesBatch, deleteUserLevelMenuAccess, UserLevelMenuAccess, } from "@/service/user-level-menu-accesses"; import { getUserLevelMenuActionAccessesByUserLevelIdAndMenuId, createUserLevelMenuActionAccessesBatch, deleteUserLevelMenuActionAccess, UserLevelMenuActionAccess, } from "@/service/user-level-menu-action-accesses"; import { getMenuActionsByMenuId, MenuAction, } from "@/service/menu-actions"; import Swal from "sweetalert2"; interface UserLevelsFormProps { initialData?: UserLevelsCreateRequest; onSave?: (data: UserLevelsCreateRequest) => void; onCancel?: () => void; isLoading?: boolean; mode?: "single" | "bulk"; } export const UserLevelsForm: React.FC = ({ initialData, onSave, onCancel, isLoading = false, mode = "single", }) => { // Form state const [formData, setFormData] = useState({ name: "", aliasName: "", levelNumber: 1, parentLevelId: undefined, provinceId: undefined, group: "", isApprovalActive: true, isActive: true, }); const [bulkFormData, setBulkFormData] = useState([]); const [userLevels, setUserLevels] = useState([]); const [provinces, setProvinces] = useState([]); const [menus, setMenus] = useState([]); const [selectedMenuIds, setSelectedMenuIds] = useState([]); const [userLevelMenuAccesses, setUserLevelMenuAccesses] = useState([]); const [menuActionsMap, setMenuActionsMap] = useState>({}); const [selectedActionAccesses, setSelectedActionAccesses] = useState>({}); const [errors, setErrors] = useState>({}); const [isSubmitting, setIsSubmitting] = useState(false); const [expandedHierarchy, setExpandedHierarchy] = useState(false); const [isLoadingData, setIsLoadingData] = useState(true); const [activeTab, setActiveTab] = useState(mode === "single" ? "basic" : "bulk"); useEffect(() => { if (initialData) { setFormData(initialData); } }, [initialData]); useEffect(() => { const loadData = async () => { try { const [userLevelsRes, provincesRes, menusRes] = await Promise.all([ getUserLevels(), getProvinces(), getMasterMenus({ limit: 100 }), ]); if (!userLevelsRes?.error) setUserLevels(userLevelsRes?.data?.data || []); if (!provincesRes?.error) setProvinces(provincesRes?.data?.data || []); if (!menusRes?.error) { 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); // Load actions for each menu const actionsMap: Record = {}; for (const menu of menusData) { try { const actionsRes = await getMenuActionsByMenuId(menu.id); if (!actionsRes?.error) { actionsMap[menu.id] = actionsRes?.data?.data || []; } } catch (error) { console.error(`Error loading actions for menu ${menu.id}:`, error); } } setMenuActionsMap(actionsMap); } } catch (error) { console.error("Error loading form data:", error); } finally { setIsLoadingData(false); } }; loadData(); }, []); useEffect(() => { const loadAccesses = async () => { if (initialData && (initialData as any).id) { const userLevelId = (initialData as any).id; try { // Load menu accesses const menuRes = await getUserLevelMenuAccessesByUserLevelId(userLevelId); if (!menuRes?.error) { const menuAccesses = menuRes?.data?.data || []; setUserLevelMenuAccesses(menuAccesses); setSelectedMenuIds(menuAccesses.filter((a: UserLevelMenuAccess) => a.canAccess).map((a: UserLevelMenuAccess) => a.menuId)); // Load action accesses for each menu const actionAccesses: Record = {}; for (const menuAccess of menuAccesses.filter((a: UserLevelMenuAccess) => a.canAccess)) { try { const actionRes = await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(userLevelId, menuAccess.menuId); if (!actionRes?.error) { const actions = actionRes?.data?.data || []; actionAccesses[menuAccess.menuId] = actions .filter((a: UserLevelMenuActionAccess) => a.canAccess) .map((a: UserLevelMenuActionAccess) => a.actionCode); } } catch (error) { console.error(`Error loading action accesses for menu ${menuAccess.menuId}:`, error); } } setSelectedActionAccesses(actionAccesses); } } catch (error) { console.error("Error loading accesses:", error); } } }; loadAccesses(); }, [initialData]); const validateForm = (data: UserLevelsCreateRequest): Record => { const newErrors: Record = {}; if (!data.name.trim()) { newErrors.name = "Level name is required"; } else if (data.name.trim().length < 3) { newErrors.name = "Level name must be at least 3 characters"; } if (!data.aliasName.trim()) { newErrors.aliasName = "Alias name is required"; } else if (data.aliasName.trim().length < 3) { newErrors.aliasName = "Alias name must be at least 3 characters"; } if (!data.levelNumber || data.levelNumber <= 0) { newErrors.levelNumber = "Level number must be a positive integer"; } // Check for duplicate level numbers // const existingLevel = userLevels.length > 0 ? userLevels.find(level => level.levelNumber === data.levelNumber) : null; // if (existingLevel && (!initialData || existingLevel.id !== (initialData as any).id)) { // newErrors.levelNumber = "Level number already exists"; // } return newErrors; }; const validateBulkForm = (): boolean => { let isValid = true; const newErrors: Record = {}; bulkFormData.forEach((data, index) => { const itemErrors = validateForm(data); Object.keys(itemErrors).forEach(key => { newErrors[`${index}.${key}`] = itemErrors[key]; }); if (Object.keys(itemErrors).length > 0) { isValid = false; } }); setErrors(newErrors); return isValid; }; const handleFieldChange = (field: keyof UserLevelsCreateRequest, value: any) => { setFormData(prev => ({ ...prev, [field]: value })); if (errors[field]) { setErrors(prev => ({ ...prev, [field]: "" })); } }; const handleBulkFieldChange = (index: number, field: keyof UserLevelsCreateRequest, value: any) => { setBulkFormData((prev: UserLevelsCreateRequest[]) => { const newData = [...prev]; newData[index] = { ...newData[index], [field]: value }; return newData; }); }; const handleTabChange = (value: string) => { setActiveTab(value); if (value === "bulk") { } else { } }; const addBulkItem = () => { const newItem: UserLevelsCreateRequest = { name: "", aliasName: "", levelNumber: 1, parentLevelId: undefined, provinceId: undefined, group: "", isApprovalActive: true, isActive: true, }; setBulkFormData(prev => [...prev, newItem]); }; const renderBulkItemForm = (item: UserLevelsCreateRequest, index: number, onUpdate: (item: UserLevelsCreateRequest) => void, onDelete: () => void) => { return (
onUpdate({ ...item, name: value })} error={errors[`${index}.name`]} required /> onUpdate({ ...item, aliasName: value.toUpperCase() })} error={errors[`${index}.aliasName`]} required helpText="Short identifier for system use" /> onUpdate({ ...item, levelNumber: value ? Number(value) : 1 })} error={errors[`${index}.levelNumber`]} required min={1} helpText="Higher number = higher authority" />
0 ? "Select parent level" : "No parent levels available"} value={item.parentLevelId} onChange={(value) => onUpdate({ ...item, parentLevelId: value !== undefined ? Number(value) : undefined })} options={[ { value: 0, label: "No Parent (Root Level)" }, ...(userLevels.length > 0 ? userLevels.map(level => ({ value: level.id, label: `${level.name} (Level ${level.levelNumber})`, })) : []) ]} helpText={userLevels.length === 0 ? "No parent levels found. This will be a root level." : "Select parent level for hierarchy"} disabled={userLevels.length === 0} /> 0 ? "Select province" : "No provinces available"} value={item.provinceId} onChange={(value) => onUpdate({ ...item, provinceId: value ? Number(value) : undefined })} options={provinces.length > 0 ? provinces.map(province => ({ value: province.id, label: province.prov_name, })) : []} helpText={provinces.length === 0 ? "No provinces found. Please ensure provinces are available in the system." : "Geographic scope for this level"} disabled={provinces.length === 0} />
onUpdate({ ...item, group: value })} helpText="Group classification for organization" /> onUpdate({ ...item, isApprovalActive: value })} helpText="Users with this level can participate in approval process" /> onUpdate({ ...item, isActive: value })} helpText="Level is available for assignment" />
); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Determine current mode based on active tab const currentMode = activeTab === "bulk" ? "bulk" : "single"; if (currentMode === "single") { const validationErrors = validateForm(formData); console.log("Form mode: ", currentMode); console.log("Error single ", validationErrors); if (Object.keys(validationErrors).length > 0) { setErrors(validationErrors); Swal.fire({ title: "Validation Error", text: "Please fix the errors before submitting", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); return; } } else { if (!validateBulkForm()) { Swal.fire({ title: "Validation Error", text: "Please fix the errors before submitting", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); return; } } setIsSubmitting(true); try { if (onSave) { if (currentMode === "single") { // Check if editing or creating const isEditing = !!(initialData as any)?.id; const userLevelId = (initialData as any)?.id; // Save user level first let userLevelResponse; if (isEditing) { userLevelResponse = await updateUserLevel(userLevelId, formData); } else { userLevelResponse = await createUserLevel(formData); } if (userLevelResponse?.error) { Swal.fire({ title: "Error", text: userLevelResponse?.message || `Failed to ${isEditing ? 'update' : 'create'} user level`, icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); setIsSubmitting(false); return; } // Get the user level ID const createdUserLevelId = userLevelResponse?.data?.data?.id || userLevelId; // Save menu accesses if (createdUserLevelId && selectedMenuIds.length > 0) { // Delete existing menu accesses if editing if ((initialData as any)?.id) { for (const access of userLevelMenuAccesses) { await deleteUserLevelMenuAccess(access.id); } } // Create new menu accesses in batch const menuAccessResponse = await createUserLevelMenuAccessesBatch({ userLevelId: createdUserLevelId, menuIds: selectedMenuIds, }); if (menuAccessResponse?.error) { console.error("Error saving menu accesses:", menuAccessResponse?.message); } else { // Save action accesses for each menu for (const menuId of selectedMenuIds) { const actionCodes = selectedActionAccesses[menuId] || []; // Delete existing action accesses for this menu if editing if ((initialData as any)?.id) { try { const existingActionsRes = await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(createdUserLevelId, menuId); if (!existingActionsRes?.error) { const existingActions = existingActionsRes?.data?.data || []; for (const action of existingActions) { await deleteUserLevelMenuActionAccess(action.id); } } } catch (error) { console.error(`Error deleting existing action accesses for menu ${menuId}:`, error); } } // Create new action accesses in batch if (actionCodes.length > 0) { const actionAccessResponse = await createUserLevelMenuActionAccessesBatch({ userLevelId: createdUserLevelId, menuId: menuId, actionCodes: actionCodes, }); if (actionAccessResponse?.error) { console.error(`Error saving action accesses for menu ${menuId}:`, actionAccessResponse?.message); } } } } } onSave(formData); } else { // For bulk mode, save each item individually let hasErrors = false; let successCount = 0; for (const item of bulkFormData) { const response = await createUserLevel(item); if (response?.error) { hasErrors = true; Swal.fire({ title: "Error", text: `Failed to create user level "${item.name}": ${response?.message || "Unknown error"}`, icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { successCount++; Swal.fire({ title: "Success", text: `User level "${item.name}" created successfully`, icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } } // Refresh page if at least one item was created successfully if (successCount > 0) { setTimeout(() => { window.location.reload(); }, 1000); // Small delay to let user see the success message } } } else { if (currentMode === "single") { // Check if editing or creating const isEditing = !!(initialData as any)?.id; const userLevelId = (initialData as any)?.id; let response; if (isEditing) { response = await updateUserLevel(userLevelId, formData); } else { response = await createUserLevel(formData); } console.log(`${isEditing ? 'Update' : 'Create'} Response: `, response); if (response?.error) { Swal.fire({ title: "Error", text: response?.message || `Failed to ${isEditing ? 'update' : 'create'} user level`, icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { // Get the user level ID const createdUserLevelId = response?.data?.data?.id || userLevelId; // Save menu accesses if (createdUserLevelId && selectedMenuIds.length > 0) { // Delete existing menu accesses if editing if ((initialData as any)?.id) { for (const access of userLevelMenuAccesses) { await deleteUserLevelMenuAccess(access.id); } } // Create new menu accesses in batch const menuAccessResponse = await createUserLevelMenuAccessesBatch({ userLevelId: createdUserLevelId, menuIds: selectedMenuIds, }); if (menuAccessResponse?.error) { console.error("Error saving menu accesses:", menuAccessResponse?.message); } else { // Save action accesses for each menu for (const menuId of selectedMenuIds) { const actionCodes = selectedActionAccesses[menuId] || []; // Delete existing action accesses for this menu if editing if ((initialData as any)?.id) { try { const existingActionsRes = await getUserLevelMenuActionAccessesByUserLevelIdAndMenuId(createdUserLevelId, menuId); if (!existingActionsRes?.error) { const existingActions = existingActionsRes?.data?.data || []; for (const action of existingActions) { await deleteUserLevelMenuActionAccess(action.id); } } } catch (error) { console.error(`Error deleting existing action accesses for menu ${menuId}:`, error); } } // Create new action accesses in batch if (actionCodes.length > 0) { const actionAccessResponse = await createUserLevelMenuActionAccessesBatch({ userLevelId: createdUserLevelId, menuId: menuId, actionCodes: actionCodes, }); if (actionAccessResponse?.error) { console.error(`Error saving action accesses for menu ${menuId}:`, actionAccessResponse?.message); } } } } } Swal.fire({ title: "Success", text: isEditing ? "User level updated successfully" : "User level created successfully", icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }).then(() => { // Refresh page after successful save window.location.reload(); }); } } else { // Bulk creation const promises = bulkFormData.map(item => createUserLevel(item)); const responses = await Promise.all(promises); console.log("Create Responses: ", responses); const failedCount = responses.filter((r: any) => r.error).length; const successCount = responses.length - failedCount; if (failedCount === 0) { Swal.fire({ title: "Success", text: `All ${successCount} user levels created successfully`, icon: "success", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } else { Swal.fire({ title: "Partial Success", text: `${successCount} user levels created successfully, ${failedCount} failed`, icon: "warning", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } } } } catch (error) { console.error("Error submitting form:", error); Swal.fire({ title: "Error", text: "An unexpected error occurred", icon: "error", confirmButtonText: "OK", customClass: { popup: 'swal-z-index-9999' } }); } finally { setIsSubmitting(false); } }; const handleReset = () => { Swal.fire({ title: "Reset Form", text: "Are you sure you want to reset all form data?", icon: "warning", showCancelButton: true, confirmButtonText: "Yes, reset", cancelButtonText: "Cancel", customClass: { popup: 'swal-z-index-9999' } }).then((result) => { if (result.isConfirmed) { if (mode === "single") { setFormData({ name: "", aliasName: "", levelNumber: 1, parentLevelId: undefined, provinceId: undefined, group: "", isApprovalActive: true, isActive: true, }); } else { setBulkFormData([]); } setErrors({}); } }); }; const renderHierarchyTree = () => { const buildTree = (levels: UserLevel[], parentId?: number): UserLevel[] => { return levels .filter(level => level.parentLevelId === parentId) .map(level => ({ ...level, children: buildTree(levels, level.id) })); }; const tree = buildTree(userLevels); const renderNode = (node: UserLevel & { children?: UserLevel[] }, depth = 0) => (
{node.name} (Level {node.levelNumber}) {node.group && - {node.group}}
{node.children?.map((child: any) => renderNode(child, depth + 1))}
); return (
{tree.map(node => renderNode(node))}
); }; return (
{isLoadingData && (
Loading form data...
)} Basic Information Menu Access Action Access Bulk Operations {/* Basic Information Tab */} User Level Basic Information
handleFieldChange("name", value)} error={errors.name} required /> handleFieldChange("aliasName", value.toUpperCase())} error={errors.aliasName} required helpText="Short identifier for system use" /> handleFieldChange("levelNumber", value ? Number(value) : 1)} error={errors.levelNumber} required min={1} helpText="Higher number = higher authority" />
0 ? "Select parent level" : "No parent levels available"} value={formData.parentLevelId} onChange={(value) => handleFieldChange("parentLevelId", value !== undefined ? Number(value) : undefined)} options={[ { value: 0, label: "No Parent (Root Level)" }, ...(userLevels.length > 0 ? userLevels.map(level => ({ value: level.id, label: `${level.name} (Level ${level.levelNumber})`, })) : []) ]} helpText={userLevels.length === 0 ? "No parent levels found. This will be a root level." : "Select parent level for hierarchy"} disabled={userLevels.length === 0} /> 0 ? "Select province" : "No provinces available"} value={formData.provinceId} onChange={(value) => handleFieldChange("provinceId", value ? Number(value) : undefined)} options={provinces.length > 0 ? provinces.map(province => ({ value: province.id, label: province.prov_name, })) : []} helpText={provinces.length === 0 ? "No provinces found. Please ensure provinces are available in the system." : "Geographic scope for this level"} disabled={provinces.length === 0} />
handleFieldChange("group", value)} helpText="Group classification for organization" />
handleFieldChange("isApprovalActive", value)} helpText="Users with this level can participate in approval process" /> handleFieldChange("isActive", value)} helpText="Level is available for assignment" />
{/* Hierarchy Tab */} {/* Level Hierarchy Visualization {userLevels.length > 0 ? (
{renderHierarchyTree()}
) : (

No user levels found

)}
*/} {/* Menu Access Tab */} Menu Access Configuration

Select which menus this user level can access. Users with this level will only see selected menus in the navigation.

{menus.length > 0 ? (
{menus.map((menu) => ( ))}
) : (
No menus available
)}
{/* Action Access Tab */} Action Access Configuration

Configure which actions users with this level can perform in each menu. First select menus in the "Menu Access" tab.

{selectedMenuIds.length > 0 ? (
{selectedMenuIds.map((menuId) => { const menu = menus.find((m) => m.id === menuId); const actions = menuActionsMap[menuId] || []; const selectedActions = selectedActionAccesses[menuId] || []; if (!menu) return null; return ( {menu.name}

{menu.description}

{actions.length > 0 ? (
{actions.map((action) => ( ))}
) : (
No actions available for this menu
)}
); })}
) : (

No menus selected

Please select menus in the "Menu Access" tab first

)}
{/* Bulk Operations Tab */} Bulk User Level Creation

User Levels

{bulkFormData.length === 0 ? (

No user levels added yet. Add multiple levels to create them in bulk.

) : (
{bulkFormData.map((item, index) => (
User Level #{index + 1}
{renderBulkItemForm( item, index, (updatedItem) => { const newItems = [...bulkFormData]; newItems[index] = updatedItem; setBulkFormData(newItems); }, () => { const newItems = bulkFormData.filter((_, i) => i !== index); setBulkFormData(newItems); } )}
))}
)}
{/* Form Actions */}
{onCancel && ( )}
); };