zonal manager changed to

regions manged
This commit is contained in:
laxmanhalaki 2026-03-31 09:07:53 +05:30
parent ec51a6cf9b
commit d2228543b1
9 changed files with 498 additions and 198 deletions

View File

@ -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),

View File

@ -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<string[]>([]);
const [selectedZMDistricts, setSelectedZMDistricts] = useState<string[]>([]);
const [selectedZMRegions, setSelectedZMRegions] = useState<string[]>([]);
// DD-Lead Management State
const [showDDLeadDialog, setShowDDLeadDialog] = useState(false);
const [editingDDLeadId, setEditingDDLeadId] = useState<string | null>(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<string[]>([]);
// 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 = () => {
<ASMManagement selectedZone={selectedZone} onAddASM={() => { setEditingASMId(null); setAsmManagerId(''); setAsmName(''); setAsmCode(''); setAsmEmployeeId(''); setSelectedASMZone(selectedZone === 'all' ? '' : selectedZone); setSelectedASMRegion(''); setSelectedASMStates([]); setSelectedASMDistricts([]); setShowASMDialog(true); }}
onEditASM={handleEditASM} onDeleteASM={() => toast.error('ASM deletion restricted')} />
<DDLeadManagement selectedZone={selectedZone}
onAddLead={() => {
setEditingDDLeadId(null); setDdLeadManagerId(''); setDdLeadName(''); setDdLeadCode(''); setDdLeadEmployeeId('');
setDdLeadStatus('active'); setSelectedDDLeadZones([]);
setShowDDLeadDialog(true);
}}
onEditLead={handleEditDDLead}
onDeleteLead={() => toast.error('DD-Lead deletion restricted')} />
<UserManagementTable userAssignedData={users.length > 0 ? users : asms} />
</TabsContent>
@ -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}
/>
<DDLeadDialog
isOpen={showDDLeadDialog}
onOpenChange={setShowDDLeadDialog}
editingLeadId={editingDDLeadId}
leadManagerId={ddLeadManagerId}
setLeadManagerId={setDdLeadManagerId}
leadName={ddLeadName}
setLeadName={setDdLeadName}
leadCode={ddLeadCode}
setLeadCode={setDdLeadCode}
leadEmployeeId={ddLeadEmployeeId}
setLeadEmployeeId={setDdLeadEmployeeId}
leadStatus={ddLeadStatus}
setLeadStatus={setDdLeadStatus}
selectedZones={selectedDDLeadZones}
setSelectedZones={setSelectedDDLeadZones}
onSave={handleSaveDDLead}
userAssignedData={users.length > 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}
/>
<TemplateDialog isOpen={showTemplateDialog} onOpenChange={setShowTemplateDialog} editingTemplate={editingTemplate} setEditingTemplate={setEditingTemplate} testDataInput={testDataInput} setTestDataInput={setTestDataInput} previewLoading={previewLoading} handlePreviewTemplate={handlePreviewTemplate} previewContent={previewContent} handleSaveTemplate={handleSaveTemplate} />
<LocationDialog isOpen={showLocationDialog} onOpenChange={setShowLocationDialog} editingLocationId={editingLocationId} locationState={locationState} setLocationState={setLocationState} locationDistrict={locationDistrict} setLocationDistrict={setLocationDistrict} locationCity={locationCity} setLocationCity={setLocationCity} locationActiveFrom={locationActiveFrom} setLocationActiveFrom={setLocationActiveFrom} locationActiveTo={locationActiveTo} setLocationActiveTo={setLocationActiveTo} locationStatus={locationStatus} setLocationStatus={setLocationStatus} onSave={handleSaveLocation} />

View File

@ -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<DDLeadDialogProps> = ({
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 (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingLeadId ? 'Edit' : 'Add'} DD Lead</DialogTitle>
<DialogDescription>Assign a user to the DD Lead role and map them to Zones.</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="border-t border-slate-100 pt-4">
<Label>Select DD Lead User <span className="text-red-500">*</span></Label>
<Select
value={leadManagerId}
onValueChange={(value) => {
setLeadManagerId(value);
const selectedUser = userAssignedData.find(u => u.id === value);
if (selectedUser) {
setLeadName(selectedUser.name);
setLeadEmployeeId(selectedUser.employeeId || '');
setLeadCode(selectedUser.leadCode || selectedUser.employeeId || '');
// If they have existing assigned zones
if (selectedUser.assignedZoneIds) {
setSelectedZones(selectedUser.assignedZoneIds);
} else {
setSelectedZones([]);
}
}
}}
disabled={!!editingLeadId}
>
<SelectTrigger className="mt-2 w-full text-slate-900 border-slate-200">
<SelectValue placeholder="Select User" />
</SelectTrigger>
<SelectContent className="max-h-60">
{filteredLeadUsers.length > 0 ? (
filteredLeadUsers.map((user) => (
<SelectItem key={user.id} value={user.id}>
<div className="flex flex-col text-left">
<span className="font-medium text-slate-900">{user.name}</span>
<span className="text-xs text-slate-500">{user.email}</span>
</div>
</SelectItem>
))
) : (
<div className="p-2 text-sm text-slate-500 text-center">No suitable users found</div>
)}
</SelectContent>
</Select>
</div>
<div>
<Label>Assigned Zones (Review Mapping)</Label>
<div className="mt-2 border border-slate-200 rounded-lg p-3 max-h-48 overflow-y-auto bg-slate-50/50">
<div className="grid grid-cols-2 gap-2">
{zones.map((zone) => (
<div key={zone.id} className="flex items-center space-x-2 py-1">
<Checkbox
id={`lead-zone-${zone.id}`}
checked={selectedZones.includes(zone.id)}
onCheckedChange={() => toggleZone(zone.id)}
/>
<label htmlFor={`lead-zone-${zone.id}`} className="text-sm cursor-pointer text-slate-700 font-medium">
{zone.name}
</label>
</div>
))}
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label>Employee ID</Label>
<Input readOnly placeholder="Auto-populated" className="mt-2 bg-slate-50 border-slate-200" value={leadEmployeeId} />
</div>
<div>
<Label>Lead Code</Label>
<Input placeholder="Enter Lead Code" className="mt-2 text-slate-900 border-slate-200" value={leadCode} onChange={(e) => setLeadCode(e.target.value)} />
</div>
</div>
<div>
<Label>Full Name</Label>
<Input placeholder="Enter full name" className="mt-2 text-slate-900 border-slate-200" value={leadName} onChange={(e) => setLeadName(e.target.value)} />
</div>
<div>
<Label>Status</Label>
<Select value={leadStatus} onValueChange={(val: 'active' | 'inactive') => setLeadStatus(val)}>
<SelectTrigger className="mt-2 text-slate-900 border-slate-200">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex gap-3 pt-6">
<Button variant="outline" className="flex-1 border-slate-200" onClick={() => onOpenChange(false)}>Cancel</Button>
<Button className="flex-1 bg-amber-600 hover:bg-amber-700 shadow-sm" onClick={onSave}>Save DD Lead</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
};

View File

@ -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<DDLeadManagementProps> = ({
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 (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>DD-Leads (Dealer Development Lead)</CardTitle>
<CardDescription>Manage DD-Leads and their zonal assignments</CardDescription>
</div>
<Button onClick={onAddLead} className="bg-amber-600 hover:bg-amber-700">
<Plus className="w-4 h-4 mr-2" />
Add DD-Lead
</Button>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Lead Code</TableHead>
<TableHead>Name</TableHead>
<TableHead>Assigned Zones</TableHead>
<TableHead>Contact</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredLeads.length > 0 ? (
filteredLeads.map((lead: any) => (
<TableRow key={lead.id}>
<TableCell>
<div className="flex items-center gap-2">
<Users className="w-4 h-4 text-amber-600" />
<span className="font-medium">{lead.leadCode || 'N/A'}</span>
</div>
</TableCell>
<TableCell>{lead.name}</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{lead.assignedZoneIds && lead.assignedZoneIds.length > 0 ? (
lead.assignedZoneIds.map((zoneId: string) => (
<Badge key={zoneId} variant="outline" className="flex items-center gap-1">
<MapPin className="w-3 h-3" />
{getZoneName(zoneId)}
</Badge>
))
) : (
<Badge variant="secondary">National / No Zone</Badge>
)}
</div>
</TableCell>
<TableCell>
<div className="text-sm">
<p className="text-slate-900">{lead.email}</p>
<p className="text-slate-500">{lead.phone || 'N/A'}</p>
</div>
</TableCell>
<TableCell>
<Badge variant={lead.status === 'active' ? 'default' : 'secondary'} className={lead.status === 'active' ? 'bg-emerald-100 text-emerald-700' : ''}>
{lead.status.charAt(0).toUpperCase() + lead.status.slice(1)}
</Badge>
</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end gap-2">
<Button variant="ghost" size="sm" onClick={() => onEditLead(lead)}>
<Edit2 className="w-4 h-4" />
</Button>
<Button variant="ghost" size="sm" onClick={() => onDeleteLead(lead.id, lead.name)} className="text-red-600 hover:text-red-700 hover:bg-red-50">
<Trash2 className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-slate-500">
No DD-Leads found. Click the button above to add one.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</CardContent>
</Card>
);
};

View File

@ -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<string, string[]>;
getDistrictsForSelectedState: (state: string) => { id: string; name: string }[];
}
export const ZMDialog: React.FC<ZMDialogProps> = ({
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<ZMDialogProps> = ({
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 (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogContent className="max-w-xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingZMId ? 'Edit' : 'Add'} Zonal Manager</DialogTitle>
<DialogDescription>Configure ZM details and district-level assignments</DialogDescription>
<DialogDescription>Assign Zonal Manager to Regions within a Zone</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Managed Zone <span className="text-red-500">*</span></Label>
<Select value={selectedZMZone} onValueChange={(value) => {
setSelectedZMZone(value);
setSelectedZMStates([]);
setSelectedZMDistricts([]);
}}>
<SelectTrigger className="mt-2 text-slate-900 border-slate-200">
<SelectValue placeholder="Select zone" />
</SelectTrigger>
<SelectContent>
{zones.map((zone) => (
<SelectItem key={zone.id} value={zone.id}>{zone.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{selectedZMZone && (
<div>
<Label>States Covered (within Zone)</Label>
<div className="mt-2 border border-slate-200 rounded-lg p-3 max-h-48 overflow-y-auto bg-slate-50/50">
{(() => {
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 ? (
<div className="grid grid-cols-2 gap-2">
{availableStates.map((state) => (
<div key={state} className="flex items-center space-x-2 py-1">
<Checkbox
id={`zm-state-${state}`}
checked={selectedZMStates.includes(state)}
onCheckedChange={(checked) => {
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)));
}
}}
/>
<label htmlFor={`zm-state-${state}`} className="text-sm cursor-pointer text-slate-700 font-medium">
{state}
</label>
</div>
))}
</div>
) : (
<p className="text-sm text-slate-500 italic">No states found for this zone</p>
);
})()}
</div>
</div>
)}
{selectedZMStates.length > 0 && (
<div>
<Label>Specific Districts Managed</Label>
<div className="mt-2 border border-slate-200 rounded-lg p-3 max-h-64 overflow-y-auto bg-slate-50/50">
<TooltipProvider>
{selectedZMStates.map((state) => {
const districts = getDistrictsForSelectedState(state);
if (districts.length === 0) return null;
return (
<div key={state} className="mb-4 last:mb-0">
<h4 className="text-xs font-bold text-amber-700 mb-2 pb-1 border-b border-slate-100 uppercase tracking-wider">{state}</h4>
<div className="grid grid-cols-2 gap-x-4 gap-y-1 ml-1">
{districts.map((district: any) => (
<div key={district.id}>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center space-x-2 py-0.5">
<Checkbox
id={`zm-district-${district.id}`}
checked={selectedZMDistricts.includes(district.id)}
onCheckedChange={(checked) => {
if (checked) {
setSelectedZMDistricts([...selectedZMDistricts, district.id]);
} else {
setSelectedZMDistricts(selectedZMDistricts.filter(id => id !== district.id));
}
}}
/>
<label
htmlFor={`zm-district-${district.id}`}
className="text-sm cursor-pointer text-slate-600 truncate max-w-[180px]"
>
{district.name}
</label>
</div>
</TooltipTrigger>
{districtsAssignedToOthers[district.id] && (
<TooltipContent side="right">
<p className="text-xs">Also assigned to: {districtsAssignedToOthers[district.id].join(', ')}</p>
</TooltipContent>
)}
</Tooltip>
</div>
))}
</div>
</div>
);
})}
</TooltipProvider>
</div>
</div>
)}
<div className="border-t border-slate-100 pt-4">
<div className="space-y-6">
<div className="border-b border-slate-100 pb-4">
<Label>Select Zonal Manager User <span className="text-red-500">*</span></Label>
<Select
value={zmManagerId}
@ -183,18 +68,10 @@ export const ZMDialog: React.FC<ZMDialogProps> = ({
if (selectedUser) {
setZmName(selectedUser.name);
setZmEmployeeId(selectedUser.employeeId || '');
setZmCode(selectedUser.zmCode || selectedUser.employeeId || '');
// Search territoryProfile for ZM/DD-ZM role to find the existing managerCode
const zmProfile = (selectedUser.territoryProfile || []).find((tp: any) => tp.roleCode === 'ZM' || tp.roleCode === 'DD-ZM');
const existingCode = zmProfile?.managerCode || selectedUser.zmCode || selectedUser.employeeId || '';
setZmCode(existingCode);
if (zmProfile?.zoneId) {
setSelectedZMZone(zmProfile.zoneId);
}
setSelectedZMDistricts(selectedUser.districts?.map((d: any) => d.id) || []);
if (selectedUser.stateNames) setSelectedZMStates(selectedUser.stateNames);
if (selectedUser.zoneId) setSelectedZone(selectedUser.zoneId);
if (selectedUser.assignedRegionIds) setSelectedRegions(selectedUser.assignedRegionIds);
}
}}
disabled={!!editingZMId}
@ -203,18 +80,14 @@ export const ZMDialog: React.FC<ZMDialogProps> = ({
<SelectValue placeholder="Select ZM User" />
</SelectTrigger>
<SelectContent className="max-h-60">
{filteredZMUsers.length > 0 ? (
filteredZMUsers.map((user) => (
{filteredZMUsers.map((user) => (
<SelectItem key={user.id} value={user.id}>
<div className="flex flex-col text-left">
<span className="font-medium text-slate-900">{user.name}</span>
<span className="text-xs text-slate-500">{user.email}</span>
</div>
</SelectItem>
))
) : (
<div className="p-2 text-sm text-slate-500 text-center">No Zonal Managers found</div>
)}
))}
</SelectContent>
</Select>
</div>
@ -222,23 +95,69 @@ export const ZMDialog: React.FC<ZMDialogProps> = ({
<div className="grid grid-cols-2 gap-4">
<div>
<Label>Employee ID</Label>
<Input readOnly placeholder="Auto-populated" className="mt-2 bg-slate-50 border-slate-200" value={zmEmployeeId} />
<Input readOnly className="mt-2 bg-slate-50 border-slate-200" value={zmEmployeeId} />
</div>
<div>
<Label>ZM Code</Label>
<Input placeholder="Enter ZM Code" className="mt-2 text-slate-900 border-slate-200" value={zmCode} onChange={(e) => setZmCode(e.target.value)} />
<Input placeholder="Enter ZM Code" className="mt-2" value={zmCode} onChange={(e) => setZmCode(e.target.value)} />
</div>
</div>
<div>
<Label>Full Name</Label>
<Input placeholder="Enter full name" className="mt-2 text-slate-900 border-slate-200" value={zmName} onChange={(e) => setZmName(e.target.value)} />
<Label>Managed Zone <span className="text-red-500">*</span></Label>
<Select value={selectedZone} onValueChange={(value) => {
setSelectedZone(value);
setSelectedRegions([]);
}}>
<SelectTrigger className="mt-2">
<SelectValue placeholder="Select zone" />
</SelectTrigger>
<SelectContent>
{zones.map((z) => (
<SelectItem key={z.id} value={z.id}>{z.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{selectedZone && (
<div>
<Label className="mb-2 block">Assigned Regions <span className="text-red-500">*</span></Label>
<div className="border border-slate-200 rounded-lg p-4 bg-slate-50/50 max-h-60 overflow-y-auto">
<div className="grid grid-cols-1 gap-3">
{availableRegions.map((region) => (
<div key={region.id} className="flex items-center space-x-3 p-2 rounded hover:bg-white transition-colors">
<Checkbox
id={`region-${region.id}`}
checked={selectedRegions.includes(region.id)}
onCheckedChange={(checked) => {
if (checked) {
setSelectedRegions([...selectedRegions, region.id]);
} else {
setSelectedRegions(selectedRegions.filter(id => id !== region.id));
}
}}
/>
<label htmlFor={`region-${region.id}`} className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer flex-1">
{region.name}
<span className="text-xs text-slate-500 block mt-1">
{region.code || 'No Code'}
</span>
</label>
</div>
))}
{availableRegions.length === 0 && (
<p className="text-sm text-slate-500 italic text-center py-4">No regions found for this zone</p>
)}
</div>
</div>
</div>
)}
<div>
<Label>Status</Label>
<Select value={zmStatus} onValueChange={(val: 'active' | 'inactive') => setZmStatus(val)}>
<SelectTrigger className="mt-2 text-slate-900 border-slate-200">
<SelectTrigger className="mt-2">
<SelectValue />
</SelectTrigger>
<SelectContent>
@ -249,8 +168,8 @@ export const ZMDialog: React.FC<ZMDialogProps> = ({
</div>
<div className="flex gap-3 pt-6">
<Button variant="outline" className="flex-1 border-slate-200" onClick={() => onOpenChange(false)}>Cancel</Button>
<Button className="flex-1 bg-amber-600 hover:bg-amber-700 shadow-sm" onClick={onSave}>Save Zonal Manager</Button>
<Button variant="outline" className="flex-1" onClick={() => onOpenChange(false)}>Cancel</Button>
<Button className="flex-1 bg-amber-600 hover:bg-amber-700" onClick={onSave}>Save Zonal Manager</Button>
</div>
</div>
</DialogContent>

View File

@ -46,7 +46,7 @@ export const ZMManagement: React.FC<ZMManagementProps> = ({
<TableHead>ZM Code</TableHead>
<TableHead>Name</TableHead>
<TableHead>Zone</TableHead>
<TableHead>Districts Managed</TableHead>
<TableHead>Regions Managed</TableHead>
<TableHead>Contact</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Actions</TableHead>
@ -71,14 +71,14 @@ export const ZMManagement: React.FC<ZMManagementProps> = ({
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{zm.districts.slice(0, 3).map((district: any, idx: number) => (
{(zm.regionNames || []).slice(0, 3).map((regionName: string, idx: number) => (
<Badge key={idx} variant="secondary" className="text-xs">
{district.name}
{regionName}
</Badge>
))}
{zm.districts.length > 3 && (
{(zm.regionNames || []).length > 3 && (
<Badge variant="secondary" className="text-xs">
+{zm.districts.length - 3}
+{(zm.regionNames || []).length - 3}
</Badge>
)}
</div>

View File

@ -17,7 +17,7 @@ export const useMasterData = () => {
const [
rolesRes, zonesRes, permsRes, regionsRes, usersRes,
statesRes, emailTemplatesRes, districtsRes, areasRes, slaRes,
asmsRes, zmsRes
asmsRes, zmsRes, ddLeadsRes
] = await Promise.all([
masterService.getRoles().catch(() => ({ success: false })),
masterService.getZones().catch(() => ({ success: false })),
@ -30,7 +30,8 @@ export const useMasterData = () => {
masterService.getAreas({ limit: 'all' }).catch(() => ({ success: false })),
masterService.getSlaConfigs().catch(() => ({ success: false })),
(masterService as any).getASMs().catch(() => ({ success: false })),
(masterService as any).getZonalManagers().catch(() => ({ success: false }))
(masterService as any).getZonalManagers().catch(() => ({ success: false })),
(masterService as any).getDDLeads().catch(() => ({ success: false }))
]);
const getBody = (res: any) => res.success ? res : (res.data ? res.data : res);
@ -47,6 +48,7 @@ export const useMasterData = () => {
const bodySla = getBody(slaRes);
const bodyAsms = getBody(asmsRes);
const bodyZms = getBody(zmsRes);
const bodyDdLeads = getBody(ddLeadsRes);
const users = (bodyUsers?.users || bodyUsers?.data || []).map((u: any) => ({
...u,
@ -64,7 +66,6 @@ export const useMasterData = () => {
const zones = (bodyZones?.zones || bodyZones?.data || []).map((z: any) => {
const zoneName = (z.name || z.zoneName || '').toUpperCase();
const zoneUsers = users.filter((u: any) => u.allZones?.includes(zoneName));
const zoneZmUsers = zoneUsers.filter((u: any) => u.allRoles?.some((r: string) => (r === 'ZM' || r.includes('ZONAL MANAGER')) && !r.includes('HEAD')));
return {
id: z.id, name: zoneName, description: z.description || '',
@ -112,18 +113,27 @@ export const useMasterData = () => {
const asms = (bodyAsms?.data || bodyAsms || []);
const zonalManagers = (bodyZms?.data || bodyZms || []);
const zonalManagerMappings = zonalManagers.length > 0 ? zonalManagers : users.filter((u: any) => u.allRoles?.some((r: string) => (r === 'ZM' || r === 'DD-ZM' || r.includes('ZONAL MANAGER')) && !r.includes('HEAD')))
.map((u: any) => {
const zmT = u.territoryProfile?.find((t: any) => (t.roleCode === 'ZM' || t.roleCode === 'DD-ZM' || t.role === 'ZONAL MANAGER')) || {};
const districts = u.territoryProfile?.filter((t: any) => (t.roleCode === 'ZM' || t.roleCode === 'DD-ZM' || t.role === 'ZONAL MANAGER') && t.locationType === 'district').map((t: any) => ({ id: t.locationId, name: t.locationName })) || [];
const zonalManagerMappings = zonalManagers.length > 0 ? zonalManagers : users.filter((u: any) =>
u.allRoles?.some((r: string) => (r === 'ZM' || r === 'DD-ZM' || r.includes('ZONAL MANAGER')) && !r.includes('HEAD'))
).map((u: any) => {
const zmT = (u.territoryProfile || []).find((t: any) => (t.roleCode === 'ZM' || t.roleCode === 'DD-ZM')) || {};
const regions = (u.territoryProfile || [])
.filter((t: any) => (t.roleCode === 'ZM' || t.roleCode === 'DD-ZM') && t.locationType === 'region')
.map((t: any) => t.locationName);
const regionIds = (u.territoryProfile || [])
.filter((t: any) => (t.roleCode === 'ZM' || t.roleCode === 'DD-ZM') && t.locationType === 'region')
.map((t: any) => t.locationId);
return {
id: u.id, name: u.fullName, code: u.employeeId || 'N/A', email: u.email, phone: u.mobileNumber,
id: u.id,
name: u.fullName,
zmCode: u.employeeId || 'N/A',
email: u.email,
phone: u.mobileNumber,
zoneId: zmT.zoneId,
regionId: zmT.regionId,
zoneName: zmT.zone || u.zone || 'Not Assigned',
regionName: zmT.region || u.region || 'Not Assigned',
districts: districts,
regionNames: regions,
assignedRegionIds: regionIds,
status: u.status
};
});
@ -159,6 +169,7 @@ export const useMasterData = () => {
emailTemplates: bodyEmail?.data || [],
slaConfigs,
users,
ddLeads: bodyDdLeads?.data || bodyDdLeads || [],
loading: false
}));

View File

@ -79,6 +79,14 @@ export const masterService = {
saveZonalManager: async (data: any) => {
return (API as any).saveZonalManager(data).then((res: any) => res.data);
},
getDDLeads: async () => {
const response = await (API as any).getDDLeads();
return response.data;
},
saveDDLead: async (data: any) => {
return (API as any).saveDDLead(data).then((res: any) => res.data);
},
// User Management
getUsers: async () => {

View File

@ -91,6 +91,7 @@ export interface MasterState {
emailTemplates: any[];
slaConfigs: any[];
users: any[];
ddLeads: any[];
areasPagination: {
total: number;
page: number;
@ -116,6 +117,7 @@ const initialState: MasterState = {
emailTemplates: [],
slaConfigs: [],
users: [],
ddLeads: [],
areasPagination: {
total: 0,
page: 1,
@ -162,13 +164,16 @@ const masterSlice = createSlice({
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
setDdLeads: (state, action: PayloadAction<any[]>) => {
state.ddLeads = action.payload;
},
},
});
export const {
setMasterData, setZones, setRegionalOffices, setAsms,
setZonalManagerMappings, setUsers, setLoading, setError,
setAreasData, setAreasLoading
setAreasData, setAreasLoading, setDdLeads
} = masterSlice.actions;
export default masterSlice.reducer;