From d2228543b1fdcdf009660ad79469210f13968713 Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Tue, 31 Mar 2026 09:07:53 +0530 Subject: [PATCH] zonal manager changed to regions manged --- src/api/API.ts | 3 + src/components/applications/MasterPage.tsx | 103 ++++++-- .../applications/MasterPage/DDLeadDialog.tsx | 164 ++++++++++++ .../MasterPage/DDLeadManagement.tsx | 117 +++++++++ .../applications/MasterPage/ZMDialog.tsx | 239 ++++++------------ .../applications/MasterPage/ZMManagement.tsx | 10 +- src/hooks/useMasterData.ts | 45 ++-- src/services/master.service.ts | 8 + src/store/slices/masterSlice.ts | 7 +- 9 files changed, 498 insertions(+), 198 deletions(-) create mode 100644 src/components/applications/MasterPage/DDLeadDialog.tsx create mode 100644 src/components/applications/MasterPage/DDLeadManagement.tsx diff --git a/src/api/API.ts b/src/api/API.ts index 2c38b41..aae4a6f 100644 --- a/src/api/API.ts +++ b/src/api/API.ts @@ -29,6 +29,9 @@ export const API = { getASMs: () => client.get('/master/asms'), getZonalManagers: () => client.get('/master/zonal-managers'), saveZonalManager: (data: any) => client.post('/master/zonal-managers', data), + getDDLeads: () => client.get('/master/dd-leads'), + saveDDLead: (data: any) => client.post('/master/dd-leads', data), + // Onboarding submitApplication: (data: any) => client.post('/onboarding/apply', data), diff --git a/src/components/applications/MasterPage.tsx b/src/components/applications/MasterPage.tsx index 279be54..86dfe61 100644 --- a/src/components/applications/MasterPage.tsx +++ b/src/components/applications/MasterPage.tsx @@ -25,6 +25,8 @@ import { EmailTemplates } from './MasterPage/EmailTemplates'; import { LocationManagement } from './MasterPage/LocationManagement'; import { ASMDialog } from './MasterPage/ASMDialog'; import { ZMDialog } from './MasterPage/ZMDialog'; +import { DDLeadManagement } from './MasterPage/DDLeadManagement'; +import { DDLeadDialog } from './MasterPage/DDLeadDialog'; import { ZoneDialog } from './MasterPage/ZoneDialog'; import { RegionDialog } from './MasterPage/RegionDialog'; import { TemplateDialog } from './MasterPage/TemplateDialog'; @@ -35,7 +37,7 @@ import { RootState } from '../../store'; export const MasterPage: React.FC = () => { const { fetchInitialData, fetchAreas } = useMasterData(); const { - asms, zonalManagerMappings, + asms, zonalManagerMappings, ddLeads, allDistricts, users, roles, @@ -75,8 +77,17 @@ export const MasterPage: React.FC = () => { const [zmEmployeeId, setZmEmployeeId] = useState(''); const [zmStatus, setZmStatus] = useState<'active' | 'inactive'>('active'); const [selectedZMZone, setSelectedZMZone] = useState(''); - const [selectedZMStates, setSelectedZMStates] = useState([]); - const [selectedZMDistricts, setSelectedZMDistricts] = useState([]); + const [selectedZMRegions, setSelectedZMRegions] = useState([]); + + // DD-Lead Management State + const [showDDLeadDialog, setShowDDLeadDialog] = useState(false); + const [editingDDLeadId, setEditingDDLeadId] = useState(null); + const [ddLeadManagerId, setDdLeadManagerId] = useState(''); + const [ddLeadName, setDdLeadName] = useState(''); + const [ddLeadCode, setDdLeadCode] = useState(''); + const [ddLeadEmployeeId, setDdLeadEmployeeId] = useState(''); + const [ddLeadStatus, setDdLeadStatus] = useState<'active' | 'inactive'>('active'); + const [selectedDDLeadZones, setSelectedDDLeadZones] = useState([]); // Role Management State const [showRoleDialog, setShowRoleDialog] = useState(false); @@ -204,11 +215,21 @@ export const MasterPage: React.FC = () => { setZmEmployeeId(zm.employeeId || ''); setZmStatus(zm.status?.toLowerCase() === 'active' ? 'active' : 'inactive'); setSelectedZMZone(zm.zoneId || ''); - setSelectedZMStates(zm.stateNames || []); - setSelectedZMDistricts(zm.districts?.map((d: any) => d.id) || []); + setSelectedZMRegions(zm.assignedRegionIds || []); setShowZMDialog(true); }; + const handleEditDDLead = (lead: any) => { + setEditingDDLeadId(lead.id); + setDdLeadManagerId(lead.id); + setDdLeadName(lead.name); + setDdLeadCode(lead.leadCode || ''); + setDdLeadEmployeeId(lead.employeeId || ''); + setDdLeadStatus(lead.status?.toLowerCase() === 'active' ? 'active' : 'inactive'); + setSelectedDDLeadZones(lead.assignedZoneIds || []); + setShowDDLeadDialog(true); + }; + const handleSaveZM = async () => { if (!zmManagerId || !selectedZMZone) { toast.error('Manager and Zone are required'); @@ -219,7 +240,7 @@ export const MasterPage: React.FC = () => { userId: zmManagerId, zmCode, zoneId: selectedZMZone, - districts: selectedZMDistricts, + regionIds: selectedZMRegions, status: zmStatus }; @@ -237,6 +258,34 @@ export const MasterPage: React.FC = () => { } }; + const handleSaveDDLead = async () => { + if (!ddLeadManagerId) { + toast.error('Manager user is required'); + return; + } + try { + const payload = { + userId: ddLeadManagerId, + leadCode: ddLeadCode, + zoneIds: selectedDDLeadZones, + status: ddLeadStatus + }; + + const res = await (masterService as any).saveDDLead(payload) as any; + if (res.success) { + toast.success(`DD Lead ${editingDDLeadId ? 'updated' : 'assigned'} successfully`); + setShowDDLeadDialog(false); + fetchInitialData(); + } else { + toast.error(res.message || 'Failed to save DD Lead'); + } + } catch (error: any) { + const msg = error?.response?.data?.message || error?.message || 'Failed to save DD Lead'; + toast.error(msg); + } + }; + + const handleSaveZone = async () => { try { const payload = { id: editingZoneId, name: zoneName, code: zoneCode, description: zoneDescription, managerId: zonalBusinessHeadId }; @@ -426,7 +475,7 @@ export const MasterPage: React.FC = () => { onAddZM={() => { setEditingZMId(null); setZmManagerId(''); setZmName(''); setZmCode(''); setZmEmployeeId(''); setZmStatus('active'); setSelectedZMZone(selectedZone === 'all' ? '' : selectedZone); - setSelectedZMStates([]); setSelectedZMDistricts([]); + setSelectedZMRegions([]); setShowZMDialog(true); }} onEditZM={handleEditZM} @@ -435,6 +484,15 @@ export const MasterPage: React.FC = () => { { setEditingASMId(null); setAsmManagerId(''); setAsmName(''); setAsmCode(''); setAsmEmployeeId(''); setSelectedASMZone(selectedZone === 'all' ? '' : selectedZone); setSelectedASMRegion(''); setSelectedASMStates([]); setSelectedASMDistricts([]); setShowASMDialog(true); }} onEditASM={handleEditASM} onDeleteASM={() => toast.error('ASM deletion restricted')} /> + { + setEditingDDLeadId(null); setDdLeadManagerId(''); setDdLeadName(''); setDdLeadCode(''); setDdLeadEmployeeId(''); + setDdLeadStatus('active'); setSelectedDDLeadZones([]); + setShowDDLeadDialog(true); + }} + onEditLead={handleEditDDLead} + onDeleteLead={() => toast.error('DD-Lead deletion restricted')} /> + 0 ? users : asms} /> @@ -508,16 +566,31 @@ export const MasterPage: React.FC = () => { setZmEmployeeId={setZmEmployeeId} zmStatus={zmStatus} setZmStatus={setZmStatus} - selectedZMZone={selectedZMZone} - setSelectedZMZone={setSelectedZMZone} - selectedZMStates={selectedZMStates} - setSelectedZMStates={setSelectedZMStates} - selectedZMDistricts={selectedZMDistricts} - setSelectedZMDistricts={setSelectedZMDistricts} + selectedZone={selectedZMZone} + setSelectedZone={setSelectedZMZone} + selectedRegions={selectedZMRegions} + setSelectedRegions={setSelectedZMRegions} onSave={handleSaveZM} userAssignedData={users.length > 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={getDistrictsForSelectedState} + /> + 0 ? users.map(u => ({...u, name: u.fullName || u.name, role: u.role?.roleName, roleCode: u.role?.roleCode, allRoles: u.allRoles, assignedZoneIds: (ddLeads.find(l => l.id === u.id)?.assignedZoneIds || [])})) : asms} /> diff --git a/src/components/applications/MasterPage/DDLeadDialog.tsx b/src/components/applications/MasterPage/DDLeadDialog.tsx new file mode 100644 index 0000000..70c73bd --- /dev/null +++ b/src/components/applications/MasterPage/DDLeadDialog.tsx @@ -0,0 +1,164 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../../ui/dialog'; +import { Button } from '../../ui/button'; +import { Label } from '../../ui/label'; +import { Input } from '../../ui/input'; +import { Checkbox } from '../../ui/checkbox'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select'; +import { RootState } from '../../../store'; + +interface DDLeadDialogProps { + isOpen: boolean; + onOpenChange: (open: boolean) => void; + editingLeadId: string | null; + leadManagerId: string; + setLeadManagerId: (id: string) => void; + leadName: string; + setLeadName: (name: string) => void; + leadCode: string; + setLeadCode: (code: string) => void; + leadEmployeeId: string; + setLeadEmployeeId: (id: string) => void; + leadStatus: 'active' | 'inactive'; + setLeadStatus: (status: 'active' | 'inactive') => void; + selectedZones: string[]; + setSelectedZones: (zones: string[]) => void; + onSave: () => void; + userAssignedData: any[]; // All users to pick from +} + +export const DDLeadDialog: React.FC = ({ + isOpen, onOpenChange, editingLeadId, leadManagerId, setLeadManagerId, + leadName, setLeadName, leadCode, setLeadCode, leadEmployeeId, setLeadEmployeeId, + leadStatus, setLeadStatus, selectedZones, setSelectedZones, onSave, + userAssignedData +}) => { + const { zones } = useSelector((state: RootState) => state.master); + + // Filter users that have DD-related roles or are already DD-Leads + const filteredLeadUsers = userAssignedData.filter(u => { + const roles = u.allRoles || []; + return roles.some((r: string) => { + const roleStr = (r || '').toUpperCase(); + return ['DD-ZM', 'ZM', 'ZBH', 'ZONE BUSINESS HEAD', 'DD LEAD', 'DD_LEAD'].includes(roleStr) || + roleStr.includes('ZONAL') || roleStr.includes('LEAD'); + }); + }); + + const toggleZone = (zoneId: string) => { + if (selectedZones.includes(zoneId)) { + setSelectedZones(selectedZones.filter(id => id !== zoneId)); + } else { + setSelectedZones([...selectedZones, zoneId]); + } + }; + + return ( + + + + {editingLeadId ? 'Edit' : 'Add'} DD Lead + Assign a user to the DD Lead role and map them to Zones. + +
+
+ + +
+ +
+ +
+
+ {zones.map((zone) => ( +
+ toggleZone(zone.id)} + /> + +
+ ))} +
+
+
+ +
+
+ + +
+
+ + setLeadCode(e.target.value)} /> +
+
+ +
+ + setLeadName(e.target.value)} /> +
+ +
+ + +
+ +
+ + +
+
+
+
+ ); +}; diff --git a/src/components/applications/MasterPage/DDLeadManagement.tsx b/src/components/applications/MasterPage/DDLeadManagement.tsx new file mode 100644 index 0000000..91bea78 --- /dev/null +++ b/src/components/applications/MasterPage/DDLeadManagement.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card'; +import { Button } from '../../ui/button'; +import { Badge } from '../../ui/badge'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../ui/table'; +import { Users, Plus, Edit2, Trash2, MapPin } from 'lucide-react'; +import { RootState } from '../../../store'; + +interface DDLeadManagementProps { + selectedZone: string; + onAddLead: () => void; + onEditLead: (lead: any) => void; + onDeleteLead: (id: string, name: string) => void; +} + +export const DDLeadManagement: React.FC = ({ + selectedZone, onAddLead, onEditLead, onDeleteLead +}) => { + const { ddLeads, zones } = useSelector((state: RootState) => state.master); + + const filteredLeads = (ddLeads || []).filter((lead: any) => + selectedZone === 'all' || + (lead.assignedZoneIds && lead.assignedZoneIds.includes(selectedZone)) + ); + + const getZoneName = (zoneId: string) => { + return zones.find(z => z.id === zoneId)?.name || 'Unknown Zone'; + }; + + return ( + + +
+
+ DD-Leads (Dealer Development Lead) + Manage DD-Leads and their zonal assignments +
+ +
+
+ + + + + Lead Code + Name + Assigned Zones + Contact + Status + Actions + + + + {filteredLeads.length > 0 ? ( + filteredLeads.map((lead: any) => ( + + +
+ + {lead.leadCode || 'N/A'} +
+
+ {lead.name} + +
+ {lead.assignedZoneIds && lead.assignedZoneIds.length > 0 ? ( + lead.assignedZoneIds.map((zoneId: string) => ( + + + {getZoneName(zoneId)} + + )) + ) : ( + National / No Zone + )} +
+
+ +
+

{lead.email}

+

{lead.phone || 'N/A'}

+
+
+ + + {lead.status.charAt(0).toUpperCase() + lead.status.slice(1)} + + + +
+ + +
+
+
+ )) + ) : ( + + + No DD-Leads found. Click the button above to add one. + + + )} +
+
+
+
+ ); +}; diff --git a/src/components/applications/MasterPage/ZMDialog.tsx b/src/components/applications/MasterPage/ZMDialog.tsx index 3648068..4ea63bb 100644 --- a/src/components/applications/MasterPage/ZMDialog.tsx +++ b/src/components/applications/MasterPage/ZMDialog.tsx @@ -6,7 +6,6 @@ import { Label } from '../../ui/label'; import { Input } from '../../ui/input'; import { Checkbox } from '../../ui/checkbox'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip'; import { RootState } from '../../../store'; interface ZMDialogProps { @@ -23,25 +22,20 @@ interface ZMDialogProps { setZmEmployeeId: (id: string) => void; zmStatus: 'active' | 'inactive'; setZmStatus: (status: 'active' | 'inactive') => void; - selectedZMZone: string; - setSelectedZMZone: (id: string) => void; - selectedZMStates: string[]; - setSelectedZMStates: (states: string[]) => void; - selectedZMDistricts: string[]; - setSelectedZMDistricts: (districts: string[]) => void; + selectedZone: string; + setSelectedZone: (id: string) => void; + selectedRegions: string[]; + setSelectedRegions: (regions: string[]) => void; onSave: () => void; userAssignedData: any[]; - districtsAssignedToOthers: Record; - getDistrictsForSelectedState: (state: string) => { id: string; name: string }[]; } export const ZMDialog: React.FC = ({ isOpen, onOpenChange, editingZMId, zmManagerId, setZmManagerId, zmName, setZmName, zmCode, setZmCode, zmEmployeeId, setZmEmployeeId, - zmStatus, setZmStatus, selectedZMZone, setSelectedZMZone, - selectedZMStates, setSelectedZMStates, - selectedZMDistricts, setSelectedZMDistricts, onSave, - userAssignedData, districtsAssignedToOthers, getDistrictsForSelectedState + zmStatus, setZmStatus, selectedZone, setSelectedZone, + selectedRegions, setSelectedRegions, onSave, + userAssignedData }) => { const { zones, regionalOffices } = useSelector((state: RootState) => state.master); @@ -49,131 +43,22 @@ export const ZMDialog: React.FC = ({ const roles = u.allRoles || []; return roles.some((r: string) => { const roleStr = (r || '').toUpperCase(); - return ['ZM', 'ZONAL MANAGER', 'ZBH', 'ZONE BUSINESS HEAD', 'RM', 'RBM', 'REGIONAL MANAGER', 'ASM', 'AREA SALES MANAGER'].includes(roleStr) || - roleStr.includes('ZONAL') || roleStr.includes('REGIONAL') || roleStr.includes('AREA SALES'); + return ['ZM', 'ZONAL MANAGER', 'ZBH', 'ZONE BUSINESS HEAD', 'RM', 'RBM', 'REGIONAL MANAGER'].includes(roleStr) || + roleStr.includes('ZONAL') || roleStr.includes('REGIONAL'); }); }); + const availableRegions = regionalOffices.filter(r => r.zoneId === selectedZone); + return ( - + {editingZMId ? 'Edit' : 'Add'} Zonal Manager - Configure ZM details and district-level assignments + Assign Zonal Manager to Regions within a Zone -
-
- - -
- - {selectedZMZone && ( -
- -
- {(() => { - const relevantRegions = regionalOffices.filter(r => r.zoneId === selectedZMZone); - const availableStates = Array.from(new Set(relevantRegions.flatMap(r => r.states || []).map((s: any) => typeof s === 'string' ? s : s.name))).sort(); - - return availableStates.length > 0 ? ( -
- {availableStates.map((state) => ( -
- { - if (checked) { - setSelectedZMStates([...selectedZMStates, state]); - } else { - setSelectedZMStates(selectedZMStates.filter(s => s !== state)); - const stateDistricts = getDistrictsForSelectedState(state); - setSelectedZMDistricts(selectedZMDistricts.filter(dId => !stateDistricts.some(sd => sd.id === dId))); - } - }} - /> - -
- ))} -
- ) : ( -

No states found for this zone

- ); - })()} -
-
- )} - - {selectedZMStates.length > 0 && ( -
- -
- - {selectedZMStates.map((state) => { - const districts = getDistrictsForSelectedState(state); - if (districts.length === 0) return null; - - return ( -
-

{state}

-
- {districts.map((district: any) => ( -
- - -
- { - if (checked) { - setSelectedZMDistricts([...selectedZMDistricts, district.id]); - } else { - setSelectedZMDistricts(selectedZMDistricts.filter(id => id !== district.id)); - } - }} - /> - -
-
- {districtsAssignedToOthers[district.id] && ( - -

Also assigned to: {districtsAssignedToOthers[district.id].join(', ')}

-
- )} -
-
- ))} -
-
- ); - })} -
-
-
- )} - -
+
+
@@ -222,23 +95,69 @@ export const ZMDialog: React.FC = ({
- +
- setZmCode(e.target.value)} /> + setZmCode(e.target.value)} />
- - setZmName(e.target.value)} /> + +
+ {selectedZone && ( +
+ +
+
+ {availableRegions.map((region) => ( +
+ { + if (checked) { + setSelectedRegions([...selectedRegions, region.id]); + } else { + setSelectedRegions(selectedRegions.filter(id => id !== region.id)); + } + }} + /> + +
+ ))} + {availableRegions.length === 0 && ( +

No regions found for this zone

+ )} +
+
+
+ )} +