import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { useSelector } from 'react-redux'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Globe, Shield, Mail, MapPin, SlidersHorizontal, Settings, FileText, Settings2 } from 'lucide-react'; import { toast } from 'sonner'; // Services & Hooks import { masterService } from '@/services/master.service'; import { useMasterData } from '@/hooks/useMasterData'; // Sub-components import { ZonesOverview } from '@/features/master/components/ZonesOverview'; import { ZoneDetails } from '@/features/master/components/ZoneDetails'; import { RegionalManagement } from '@/features/master/components/RegionalManagement'; import { ASMManagement } from '@/features/master/components/ASMManagement'; import { ZMManagement } from '@/features/master/components/ZMManagement'; import { UserManagementTable } from '@/features/master/components/UserManagementTable'; import { RolePermissions } from '@/features/master/components/RolePermissions'; import { RoleDialog } from '@/features/master/components/RoleDialog'; import { AddRoleDialog } from '@/features/master/components/AddRoleDialog'; import { EmailTemplates } from '@/features/master/components/EmailTemplates'; import { LocationManagement } from '@/features/master/components/LocationManagement'; import { ASMDialog } from '@/features/master/components/ASMDialog'; import { DealerAsmAssignment } from '@/features/master/components/DealerAsmAssignment'; import { ZMDialog } from '@/features/master/components/ZMDialog'; import { ZoneDialog } from '@/features/master/components/ZoneDialog'; import { RegionDialog } from '@/features/master/components/RegionDialog'; import { TemplateDialog } from '@/features/master/components/TemplateDialog'; import { LocationDialog } from '@/features/master/components/LocationDialog'; import { SecurityDepositMaster } from '@/features/master/components/SecurityDepositMaster'; import { DocumentConfigManagement } from '@/features/master/components/DocumentConfigManagement'; import { AutoAssignmentSettings } from '@/features/master/components/AutoAssignmentSettings'; import { ApprovalPoliciesPage } from '@/components/admin/ApprovalPoliciesPage'; import { RootState } from '@/store'; export const MasterPage: React.FC = () => { const { fetchInitialData, fetchAreas } = useMasterData(); const { asms, zonalManagerMappings, allStates, allDistricts, users, roles, loading } = useSelector((state: RootState) => state.master); // Tab & Selection State const [activeTab, setActiveTab] = useState('hierarchy'); const [selectedZone, setSelectedZone] = useState('all'); // Dialog Visibility const [showASMDialog, setShowASMDialog] = useState(false); const [showZoneDialog, setShowZoneDialog] = useState(false); const [showRegionDialog, setShowRegionDialog] = useState(false); const [showTemplateDialog, setShowTemplateDialog] = useState(false); const [showLocationDialog, setShowLocationDialog] = useState(false); // Form State (ASM) const [editingASMId, setEditingASMId] = useState(null); const [asmManagerId, setAsmManagerId] = useState(''); const [asmStatus, setAsmStatus] = useState<'active' | 'inactive'>('active'); const [selectedASMZone, setSelectedASMZone] = useState(''); const [selectedASMRegion, setSelectedASMRegion] = useState(''); const [selectedASMStates, setSelectedASMStates] = useState([]); const [selectedASMDistricts, setSelectedASMDistricts] = useState([]); const [asmRoleCode, setAsmRoleCode] = useState<'ASM' | 'DD-AM'>('DD-AM'); // ZM Management State const [showZMDialog, setShowZMDialog] = useState(false); const [editingZMId, setEditingZMId] = useState(null); const [zmManagerId, setZmManagerId] = useState(''); const [zmStatus, setZmStatus] = useState<'active' | 'inactive'>('active'); const [selectedZMZone, setSelectedZMZone] = useState(''); const [selectedZMRegions, setSelectedZMRegions] = useState([]); // Role Management State const [showRoleDialog, setShowRoleDialog] = useState(false); const [editingRole, setEditingRole] = useState(null); const [showAddRoleDialog, setShowAddRoleDialog] = useState(false); // Form State (Zone) const [editingZoneId, setEditingZoneId] = useState(null); const [zoneName, setZoneName] = useState(''); const [zoneCode, setZoneCode] = useState(''); const [zoneDescription, setZoneDescription] = useState(''); const [zonalBusinessHeadId, setZonalBusinessHeadId] = useState('none'); // Form State (Region) const [editingRegionId, setEditingRegionId] = useState(null); const [regionName, setRegionName] = useState(''); const [regionDescription, setRegionDescription] = useState(''); const [selectedRegionZone, setSelectedRegionZone] = useState(''); const [regionalManagerId, setRegionalManagerId] = useState(''); const [selectedRegionDistricts, setSelectedRegionDistricts] = useState([]); // Form State (Template) const [editingTemplate, setEditingTemplate] = useState(null); const [testDataInput, setTestDataInput] = useState('{"applicant_name": "John Doe"}'); const [previewLoading, setPreviewLoading] = useState(false); const [previewContent, setPreviewContent] = useState(null); // Form State (Location) const [editingLocationId, setEditingLocationId] = useState(null); const [locationState, setLocationState] = useState(''); const [locationDistrict, setLocationDistrict] = useState(''); const [locationCity, setLocationCity] = useState(''); const [locationActiveFrom, setLocationActiveFrom] = useState(''); const [locationActiveTo, setLocationActiveTo] = useState(''); const [locationStatus, setLocationStatus] = useState('active'); // Search & Pagination State (Locations) const [districtsSearch, setDistrictsSearch] = useState(''); const [districtsPage, setDistrictsPage] = useState(1); const [locationStateFilter, setLocationStateFilter] = useState('all'); const [locationStatusFilter, setLocationStatusFilter] = useState('all'); // Initial Load useEffect(() => { fetchInitialData(); }, [fetchInitialData]); // Sync editingRole with latest data from Redux useEffect(() => { if (editingRole && roles.length > 0) { const latest = roles.find((r: any) => r.id === editingRole.id); if (latest) setEditingRole(latest); } }, [roles, editingRole?.id]); // Shared Data Helpers const districtsAssignedToOthers = useMemo(() => { const map: Record = {}; [...asms, ...zonalManagerMappings].forEach(m => { const dists = (m as any).areasManaged || (m as any).districts || []; dists.forEach((d: any) => { const id = typeof d === 'string' ? d : d.id; if (!map[id]) map[id] = []; if (!map[id].includes(m.name)) map[id].push(m.name); }); }); return map; }, [asms, zonalManagerMappings]); const getDistrictsForSelectedState = useCallback((stateName: string, regionId?: string) => { return allDistricts .filter(d => d.stateName?.toUpperCase() === stateName?.toUpperCase() && (!regionId || d.regionId === regionId) ) .map(d => ({ id: d.id, name: d.name })); }, [allDistricts]); // Handlers const handleSaveASM = async () => { if (!asmManagerId) { toast.error('Please select a DD-AM user'); return; } try { const payload = { userId: asmManagerId, roleCode: asmRoleCode, districts: selectedASMDistricts, status: asmStatus }; const res = await masterService.saveASM(payload) as any; if (res.success) { toast.success(`DD Area Manager ${editingASMId ? 'updated' : 'assigned'} successfully`); setShowASMDialog(false); fetchInitialData(); } else { toast.error(res.message || 'Failed to save ASM'); } } catch (error: any) { const msg = error?.response?.data?.message || error?.message || 'Failed to save ASM'; toast.error(msg); } }; const handleEditASM = (asm: any) => { setEditingASMId(asm.id); setAsmManagerId(asm.id); setAsmStatus(asm.status.toLowerCase() as 'active' | 'inactive'); setSelectedASMZone(asm.zoneId); setSelectedASMRegion(asm.regionId); setSelectedASMStates(asm.stateNames || []); setSelectedASMDistricts(asm.areasManaged?.map((a: any) => a.id) || []); setAsmRoleCode('DD-AM'); setShowASMDialog(true); }; const handleEditZM = (zm: any) => { setEditingZMId(zm.id); setZmManagerId(zm.id); setZmStatus(zm.status?.toLowerCase() === 'active' ? 'active' : 'inactive'); setSelectedZMZone(zm.zoneId || ''); setSelectedZMRegions(zm.assignedRegionIds || []); setShowZMDialog(true); }; const handleSaveZM = async () => { if (!zmManagerId || !selectedZMZone) { toast.error('Manager and Zone are required'); return; } try { const payload = { userId: zmManagerId, zoneId: selectedZMZone, regionIds: selectedZMRegions, status: zmStatus }; const res = await (masterService as any).saveZonalManager(payload) as any; if (res.success) { toast.success(`Zonal Manager ${editingZMId ? 'updated' : 'assigned'} successfully`); setShowZMDialog(false); fetchInitialData(); } else { toast.error(res.message || 'Failed to save Zonal Manager'); } } catch (error: any) { const msg = error?.response?.data?.message || error?.message || 'Failed to save Zonal Manager'; toast.error(msg); } }; const handleSaveZone = async () => { try { const payload = { id: editingZoneId, name: zoneName, code: zoneCode, description: zoneDescription, managerId: zonalBusinessHeadId === 'none' ? null : zonalBusinessHeadId }; const res = await masterService.saveZone(payload) as any; if (res.success) { toast.success('Zone saved successfully'); setShowZoneDialog(false); fetchInitialData(); } else { toast.error(res.message || 'Error saving zone'); } } catch (error: any) { const msg = error?.response?.data?.message || error?.message || 'Error saving zone'; toast.error(msg); } }; const handleSaveRegion = async () => { try { const payload = { ...(editingRegionId ? { id: editingRegionId } : {}), name: regionName, description: regionDescription, parentId: selectedRegionZone, managerId: regionalManagerId, districts: selectedRegionDistricts, status: 'Active' }; const res = await masterService.saveRegion(payload) as any; if (res.success) { toast.success('Region saved successfully'); setShowRegionDialog(false); fetchInitialData(); } else { toast.error(res.message || 'Error saving region'); } } catch (error: any) { const msg = error?.response?.data?.message || error?.message || 'Error saving region'; toast.error(msg); } }; const handleSaveTemplate = async (body: string) => { try { if (!editingTemplate?.id) { toast.error('Open a template from the list to edit.'); return; } const res = await masterService.updateEmailTemplate(editingTemplate.id, { ...editingTemplate, body }) as any; if (res.success) { toast.success('Template saved'); setShowTemplateDialog(false); fetchInitialData(); } else { toast.error(res.message || 'Error saving template'); } } catch (error: any) { const msg = error?.response?.data?.message || error?.message || 'Error saving template'; toast.error(msg); } }; const handlePreviewTemplate = async (body: string) => { setPreviewLoading(true); try { let data: Record; try { data = JSON.parse(testDataInput) as Record; } catch { toast.error('Mock test data must be valid JSON'); return; } const res = await masterService.previewEmailTemplate({ subject: editingTemplate?.subject, body, data }) as any; if (res.success) { setPreviewContent(res.data); } else { toast.error(res.message || 'Preview failed'); } } catch (error: any) { const d = error?.response?.data; const detail = d?.error || d?.message; toast.error(detail || error?.message || 'Preview failed'); } finally { setPreviewLoading(false); } }; const handleSaveRole = async (roleId: string, permissions: string[]) => { try { const res = await masterService.updateRole(roleId, { permissions }) as any; if (res.success) { toast.success('Role permissions updated successfully'); setShowRoleDialog(false); fetchInitialData(); } else { toast.error(res.message || 'Error saving role permissions'); } } catch (error: any) { const msg = error?.response?.data?.message || error?.message || 'Error saving role permissions'; toast.error(msg); } }; const handleEditRole = (role: any) => { setEditingRole(role); setShowRoleDialog(true); }; const handleCreateRole = async (data: { roleCode: string; roleName: string; description?: string }) => { try { const payload = { ...data, permissionIds: [] }; const res = await (masterService as any).createRole(payload); if (res?.success) { toast.success('Role created successfully'); fetchInitialData(); return; } throw new Error(res?.message || 'Failed to create role'); } catch (error: any) { const msg = error?.response?.data?.message || error?.message || 'Failed to create role'; toast.error(msg); throw error; } }; const handleEditLocation = (loc: any) => { const matchedDistrict = allDistricts.find((d: any) => d.id === loc.districtId); setEditingLocationId(loc.id); setLocationState(matchedDistrict?.stateId || loc.stateId || ''); setLocationCity(loc.city || ''); setLocationDistrict(loc.districtId || ''); setLocationActiveFrom(loc.openFrom ? new Date(loc.openFrom).toISOString().split('T')[0] : ''); setLocationActiveTo(loc.openTo ? new Date(loc.openTo).toISOString().split('T')[0] : ''); setLocationStatus(loc.isOpportunity ? 'active' : 'inactive'); setShowLocationDialog(true); }; const handleSaveLocation = async () => { try { if (!locationState) { toast.error('Please select a state'); return; } if (!locationDistrict) { toast.error('Please select a district'); return; } const selectedState = allStates.find((s: any) => s.id === locationState); const selectedDistrict = allDistricts.find((d: any) => d.id === locationDistrict); const payload = { id: editingLocationId, stateId: locationState, stateName: (selectedState as any)?.name || (selectedState as any)?.stateName || '', districtId: locationDistrict, name: locationCity || selectedDistrict?.name || 'New Location', city: locationCity, status: locationStatus, openFrom: locationActiveFrom, openTo: locationActiveTo, isOpportunity: locationStatus === 'active' }; const res = await (editingLocationId ? masterService.updateArea(editingLocationId, payload) : masterService.createArea(payload)) as any; if (res.success) { toast.success('Location saved'); setShowLocationDialog(false); fetchAreas({ search: districtsSearch, page: districtsPage }); } } catch (error) { toast.error('Error saving location'); } }; useEffect(() => { const handler = setTimeout(() => { fetchAreas({ search: districtsSearch, page: districtsPage, stateId: locationStateFilter === 'all' ? undefined : locationStateFilter, isOpportunity: locationStatusFilter === 'all' ? undefined : (locationStatusFilter === 'active' ? 'true' : 'false') }); }, 500); return () => clearTimeout(handler); }, [districtsSearch, districtsPage, locationStateFilter, locationStatusFilter, fetchAreas]); return (

Master Configuration

Centralized governance for locations, roles, and operational policies

{loading ? (

Synchronizing Global Settings...

) : ( Organisation Roles Emails Locations Approvals Docs Config Governance App Settings setSelectedZone(selectedZone === id ? 'all' : id)} /> { setEditingZoneId(null); setZoneName(''); setZoneCode(''); setZoneDescription(''); setZonalBusinessHeadId('none'); setShowZoneDialog(true); }} onEditZone={(z) => { setEditingZoneId(z.id); setZoneName(z.name); setZoneCode(z.code); setZoneDescription(z.description); setZonalBusinessHeadId(z.zonalBusinessHead?.id || 'none'); setShowZoneDialog(true); }} /> { setEditingRegionId(null); setRegionName(''); setSelectedRegionZone(selectedZone === 'all' ? '' : selectedZone); setRegionalManagerId(''); setSelectedRegionDistricts([]); setShowRegionDialog(true); }} onEditRegion={(r) => { setEditingRegionId(r.id); setRegionName(r.name); setSelectedRegionZone(r.zoneId); setRegionalManagerId(r.regionalManager?.id || ''); setSelectedRegionDistricts(r.districts?.map((d: any) => d.id) || []); setShowRegionDialog(true); }} onDeleteRegion={() => toast.error('Regional office deletion is restricted via portal')} /> { setEditingZMId(null); setZmManagerId(''); setZmStatus('active'); setSelectedZMZone(selectedZone === 'all' ? '' : selectedZone); setSelectedZMRegions([]); setShowZMDialog(true); }} onEditZM={handleEditZM} onDeleteZM={() => toast.error('ZM deletion restricted')} /> { setEditingASMId(null); setAsmManagerId(''); setSelectedASMZone(selectedZone === 'all' ? '' : selectedZone); setSelectedASMRegion(''); setSelectedASMStates([]); setSelectedASMDistricts([]); setAsmRoleCode('DD-AM'); setShowASMDialog(true); }} onEditASM={handleEditASM} onDeleteASM={() => toast.error('ASM deletion restricted')} /> 0 ? users : asms} /> setShowAddRoleDialog(true)} onEditRole={handleEditRole} /> { setEditingTemplate(template); if (template.placeholders && Array.isArray(template.placeholders)) { const defaults = template.placeholders.reduce((acc: any, p: string) => { acc[p] = `[${p}]`; return acc; }, {}); setTestDataInput(JSON.stringify(defaults, null, 2)); } else { setTestDataInput('{}'); } setShowTemplateDialog(true); }} onDeleteTemplate={() => toast.error('Delete Template restricted')} /> { setLocationStateFilter(val); setDistrictsPage(1); }} statusFilter={locationStatusFilter} onStatusFilterChange={(val: string) => { setLocationStatusFilter(val); setDistrictsPage(1); }} onAddLocation={() => { setEditingLocationId(null); setLocationState(''); setLocationCity(''); setLocationDistrict(''); setLocationActiveFrom(''); setLocationActiveTo(''); setLocationStatus('active'); setShowLocationDialog(true); }} onEditLocation={handleEditLocation} onDeleteLocation={(id) => { if (window.confirm('Are you sure you want to delete this location?')) { (masterService as any).deleteArea(id).then((res: any) => { if (res.success) { toast.success('Location deleted'); fetchAreas({ search: districtsSearch, page: districtsPage, stateId: locationStateFilter === 'all' ? undefined : locationStateFilter }); } }); } }} onSearch={(term) => { setDistrictsSearch(term); setDistrictsPage(1); // Reset to first page on search }} onPageChange={setDistrictsPage} searchTerm={districtsSearch} /> )} {/* Main Dialogs */} 0 ? users.map(u => ({ ...u, name: u.fullName || u.name, role: u.role?.roleName, roleCode: u.role?.roleCode, allRoles: u.allRoles })) : asms} /> 0 ? users.map(u => ({ ...u, name: u.fullName || u.name, role: u.role?.roleName, roleCode: u.role?.roleCode, allRoles: u.allRoles })) : asms} /> 0 ? users.map(u => ({ ...u, name: u.fullName || u.name, role: u.role?.roleName, roleCode: u.role?.roleCode, allRoles: u.allRoles })) : asms} districtsAssignedToOthers={districtsAssignedToOthers} getDistrictsForSelectedState={(state) => getDistrictsForSelectedState(state, selectedASMRegion || undefined)} /> 0 ? users.map(u => ({ ...u, name: u.fullName || u.name, role: u.role?.roleName, roleCode: u.role?.roleCode, allRoles: u.allRoles })) : asms} />
); }; // No default export as App.tsx expects named export MasterPage