nbased on new hierach ui updated still flow not stable

This commit is contained in:
laxman h 2026-03-27 23:06:08 +05:30
parent def289e12b
commit c210f640d8
14 changed files with 723 additions and 407 deletions

View File

@ -3,9 +3,7 @@ import { useSelector } from 'react-redux';
import { import {
Tabs, TabsContent, TabsList, TabsTrigger Tabs, TabsContent, TabsList, TabsTrigger
} from '../ui/tabs'; } from '../ui/tabs';
import { import { Globe, Shield, Clock, Mail, MapPin, SlidersHorizontal } from 'lucide-react';
Globe, Shield, Clock, Mail, MapPin, SlidersHorizontal
} from 'lucide-react';
import { Badge } from '../ui/badge'; import { Badge } from '../ui/badge';
import { toast } from 'sonner'; import { toast } from 'sonner';
@ -25,6 +23,7 @@ import { RolePermissions } from './MasterPage/RolePermissions';
import { EmailTemplates } from './MasterPage/EmailTemplates'; import { EmailTemplates } from './MasterPage/EmailTemplates';
import { LocationManagement } from './MasterPage/LocationManagement'; import { LocationManagement } from './MasterPage/LocationManagement';
import { ASMDialog } from './MasterPage/ASMDialog'; import { ASMDialog } from './MasterPage/ASMDialog';
import { ZMDialog } from './MasterPage/ZMDialog';
import { ZoneDialog } from './MasterPage/ZoneDialog'; import { ZoneDialog } from './MasterPage/ZoneDialog';
import { RegionDialog } from './MasterPage/RegionDialog'; import { RegionDialog } from './MasterPage/RegionDialog';
import { TemplateDialog } from './MasterPage/TemplateDialog'; import { TemplateDialog } from './MasterPage/TemplateDialog';
@ -36,10 +35,12 @@ export const MasterPage: React.FC = () => {
const { fetchInitialData } = useMasterData(); const { fetchInitialData } = useMasterData();
const { const {
asms, zonalManagerMappings, asms, zonalManagerMappings,
allStates, allDistricts, allDistricts,
users,
loading loading
} = useSelector((state: RootState) => state.master); } = useSelector((state: RootState) => state.master);
// Tab & Selection State // Tab & Selection State
const [activeTab, setActiveTab] = useState('hierarchy'); const [activeTab, setActiveTab] = useState('hierarchy');
const [selectedZone, setSelectedZone] = useState('all'); const [selectedZone, setSelectedZone] = useState('all');
@ -63,11 +64,24 @@ export const MasterPage: React.FC = () => {
const [selectedASMStates, setSelectedASMStates] = useState<string[]>([]); const [selectedASMStates, setSelectedASMStates] = useState<string[]>([]);
const [selectedASMDistricts, setSelectedASMDistricts] = useState<string[]>([]); const [selectedASMDistricts, setSelectedASMDistricts] = useState<string[]>([]);
// ZM Management State
const [showZMDialog, setShowZMDialog] = useState(false);
const [editingZMId, setEditingZMId] = useState<string | null>(null);
const [zmManagerId, setZmManagerId] = useState('');
const [zmName, setZmName] = useState('');
const [zmCode, setZmCode] = useState('');
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[]>([]);
// Form State (Zone) // Form State (Zone)
const [editingZoneId, setEditingZoneId] = useState<string | null>(null); const [editingZoneId, setEditingZoneId] = useState<string | null>(null);
const [zoneName, setZoneName] = useState(''); const [zoneName, setZoneName] = useState('');
const [zoneCode, setZoneCode] = useState(''); const [zoneCode, setZoneCode] = useState('');
const [zoneDescription, setZoneDescription] = useState(''); const [zoneDescription, setZoneDescription] = useState('');
const [zonalBusinessHeadId, setZonalBusinessHeadId] = useState('');
// Form State (Region) // Form State (Region)
const [editingRegionId, setEditingRegionId] = useState<string | null>(null); const [editingRegionId, setEditingRegionId] = useState<string | null>(null);
@ -76,7 +90,7 @@ export const MasterPage: React.FC = () => {
const [regionDescription, setRegionDescription] = useState(''); const [regionDescription, setRegionDescription] = useState('');
const [selectedRegionZone, setSelectedRegionZone] = useState(''); const [selectedRegionZone, setSelectedRegionZone] = useState('');
const [regionalManagerId, setRegionalManagerId] = useState(''); const [regionalManagerId, setRegionalManagerId] = useState('');
const [selectedRegionStates, setSelectedRegionStates] = useState<string[]>([]); const [selectedRegionDistricts, setSelectedRegionDistricts] = useState<string[]>([]);
// Form State (Template) // Form State (Template)
const [editingTemplate, setEditingTemplate] = useState<any>(null); const [editingTemplate, setEditingTemplate] = useState<any>(null);
@ -104,9 +118,10 @@ export const MasterPage: React.FC = () => {
const map: Record<string, string[]> = {}; const map: Record<string, string[]> = {};
[...asms, ...zonalManagerMappings].forEach(m => { [...asms, ...zonalManagerMappings].forEach(m => {
const dists = (m as any).areasManaged || (m as any).districts || []; const dists = (m as any).areasManaged || (m as any).districts || [];
dists.forEach((d: string) => { dists.forEach((d: any) => {
if (!map[d]) map[d] = []; const id = typeof d === 'string' ? d : d.id;
if (!map[d].includes(m.name)) map[d].push(m.name); if (!map[id]) map[id] = [];
if (!map[id].includes(m.name)) map[id].push(m.name);
}); });
}); });
return map; return map;
@ -114,12 +129,9 @@ export const MasterPage: React.FC = () => {
const getDistrictsForSelectedState = useCallback((stateName: string) => { const getDistrictsForSelectedState = useCallback((stateName: string) => {
return allDistricts return allDistricts
.filter(d => { .filter(d => d.stateName?.toUpperCase() === stateName?.toUpperCase())
const sObj = allStates.find(s => s.id === d.stateId); .map(d => ({ id: d.id, name: d.name }));
return sObj?.stateName === stateName; }, [allDistricts]);
})
.map(d => d.districtName);
}, [allDistricts, allStates]);
// Handlers // Handlers
const handleSaveASM = async () => { const handleSaveASM = async () => {
@ -148,13 +160,51 @@ export const MasterPage: React.FC = () => {
setSelectedASMZone(asm.zoneId); setSelectedASMZone(asm.zoneId);
setSelectedASMRegion(asm.regionId); setSelectedASMRegion(asm.regionId);
setSelectedASMStates(asm.stateNames || []); setSelectedASMStates(asm.stateNames || []);
setSelectedASMDistricts(asm.areasManaged || []); setSelectedASMDistricts(asm.areasManaged?.map((a: any) => a.id) || []);
setShowASMDialog(true); setShowASMDialog(true);
}; };
const handleEditZM = (zm: any) => {
setEditingZMId(zm.id);
setZmManagerId(zm.id);
setZmName(zm.name);
setZmCode(zm.code === 'N/A' ? '' : zm.code);
setZmEmployeeId(zm.code === 'N/A' ? '' : zm.code);
setZmStatus(zm.status.toLowerCase() as 'active' | 'inactive');
setSelectedZMZone(zm.zoneId || '');
setSelectedZMStates(zm.stateNames || []);
setSelectedZMDistricts(zm.districts?.map((d: any) => d.id) || []);
setShowZMDialog(true);
};
const handleSaveZM = async () => {
if (!zmManagerId || !selectedZMZone) {
toast.error('Manager and Zone are required');
return;
}
try {
const payload = {
userId: zmManagerId,
roleCode: 'DD-ZM',
zmCode,
districts: selectedZMDistricts,
status: zmStatus,
locationId: selectedZMZone // ZM primary location is the zone
};
// Use the generic saveASM method which I generalized on the backend
const res = await masterService.saveASM(payload) as any;
if (res.success) {
toast.success(`Zonal Manager ${editingZMId ? 'updated' : 'assigned'} successfully`);
setShowZMDialog(false);
fetchInitialData();
}
} catch (error) { toast.error('Failed to save Zonal Manager'); }
};
const handleSaveZone = async () => { const handleSaveZone = async () => {
try { try {
const payload = { id: editingZoneId, name: zoneName, code: zoneCode, description: zoneDescription }; const payload = { id: editingZoneId, name: zoneName, code: zoneCode, description: zoneDescription, managerId: zonalBusinessHeadId };
const res = await masterService.saveZone(payload) as any; const res = await masterService.saveZone(payload) as any;
if (res.success) { if (res.success) {
toast.success('Zone saved successfully'); toast.success('Zone saved successfully');
@ -167,14 +217,14 @@ export const MasterPage: React.FC = () => {
const handleSaveRegion = async () => { const handleSaveRegion = async () => {
try { try {
const payload = { const payload = {
id: editingRegionId, name: regionName,
zoneId: selectedRegionZone, code: regionCode,
name: regionName, description: regionDescription,
code: regionCode, parentId: selectedRegionZone,
description: regionDescription, managerId: regionalManagerId,
managerId: regionalManagerId, districts: selectedRegionDistricts,
states: selectedRegionStates status: 'Active'
}; };
const res = await masterService.saveRegion(payload) as any; const res = await masterService.saveRegion(payload) as any;
if (res.success) { if (res.success) {
toast.success('Region saved successfully'); toast.success('Region saved successfully');
@ -272,25 +322,35 @@ export const MasterPage: React.FC = () => {
<TabsContent value="hierarchy" className="space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-300"> <TabsContent value="hierarchy" className="space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-300">
<ZonesOverview selectedZone={selectedZone} onZoneClick={(id) => setSelectedZone(selectedZone === id ? 'all' : id)} /> <ZonesOverview selectedZone={selectedZone} onZoneClick={(id) => setSelectedZone(selectedZone === id ? 'all' : id)} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <ZoneDetails selectedZone={selectedZone} onAddZone={() => { setEditingZoneId(null); setZoneName(''); setZoneCode(''); setZoneDescription(''); setZonalBusinessHeadId(''); setShowZoneDialog(true); }}
<div className="lg:col-span-1"> onEditZone={(z) => { setEditingZoneId(z.id); setZoneName(z.name); setZoneCode(z.code); setZoneDescription(z.description); setZonalBusinessHeadId(z.zonalBusinessHead?.id || ''); setShowZoneDialog(true); }} />
<ZoneDetails selectedZone={selectedZone} onAddZone={() => { setEditingZoneId(null); setZoneName(''); setZoneCode(''); setZoneDescription(''); setShowZoneDialog(true); }}
onEditZone={(z) => { setEditingZoneId(z.id); setZoneName(z.name); setZoneCode(z.code); setZoneDescription(z.description); setShowZoneDialog(true); }} />
</div>
<div className="lg:col-span-2">
<RegionalManagement selectedZone={selectedZone} onAddRegion={() => { setEditingRegionId(null); setRegionName(''); setRegionCode(''); setSelectedRegionZone(selectedZone === 'all' ? '' : selectedZone); setRegionalManagerId(''); setSelectedRegionStates([]); setShowRegionDialog(true); }}
onEditRegion={(r) => { setEditingRegionId(r.id); setRegionName(r.name); setRegionCode(r.code); setSelectedRegionZone(r.zoneId); setRegionalManagerId(r.managerId || ''); setSelectedRegionStates(r.states || []); setShowRegionDialog(true); }}
onDeleteRegion={() => toast.error('Regional office deletion is restricted via portal')} />
</div>
</div>
<ZMManagement selectedZone={selectedZone} onAddZM={() => toast.info('ZM assignment functionality being updated')} <RegionalManagement selectedZone={selectedZone} onAddRegion={() => { setEditingRegionId(null); setRegionName(''); setRegionCode(''); setSelectedRegionZone(selectedZone === 'all' ? '' : selectedZone); setRegionalManagerId(''); setSelectedRegionDistricts([]); setShowRegionDialog(true); }}
onEditZM={() => toast.info('Edit ZM restricted')} onDeleteZM={() => toast.error('Delete ZM restricted')} /> onEditRegion={(r) => {
setEditingRegionId(r.id);
setRegionName(r.name);
setRegionCode(r.code);
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')} />
<ZMManagement selectedZone={selectedZone}
onAddZM={() => {
setEditingZMId(null); setZmManagerId(''); setZmName(''); setZmCode(''); setZmEmployeeId('');
setZmStatus('active'); setSelectedZMZone(selectedZone === 'all' ? '' : selectedZone);
setSelectedZMStates([]); setSelectedZMDistricts([]);
setShowZMDialog(true);
}}
onEditZM={handleEditZM}
onDeleteZM={() => toast.error('ZM deletion restricted')} />
<ASMManagement selectedZone={selectedZone} onAddASM={() => { setEditingASMId(null); setAsmManagerId(''); setAsmName(''); setAsmCode(''); setAsmEmployeeId(''); setSelectedASMZone(selectedZone === 'all' ? '' : selectedZone); setSelectedASMRegion(''); setSelectedASMStates([]); setSelectedASMDistricts([]); setShowASMDialog(true); }} <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')} /> onEditASM={handleEditASM} onDeleteASM={() => toast.error('ASM deletion restricted')} />
<UserManagementTable userAssignedData={asms} /> <UserManagementTable userAssignedData={users.length > 0 ? users : asms} />
</TabsContent> </TabsContent>
<TabsContent value="roles" className="animate-in fade-in duration-300"> <TabsContent value="roles" className="animate-in fade-in duration-300">
@ -319,9 +379,34 @@ export const MasterPage: React.FC = () => {
)} )}
{/* Main Dialogs */} {/* Main Dialogs */}
<ZoneDialog isOpen={showZoneDialog} onOpenChange={setShowZoneDialog} editingZoneId={editingZoneId} zoneName={zoneName} setZoneName={setZoneName} zoneCode={zoneCode} setZoneCode={setZoneCode} zoneDescription={zoneDescription} setZoneDescription={setZoneDescription} onSave={handleSaveZone} /> <ZoneDialog isOpen={showZoneDialog} onOpenChange={setShowZoneDialog} editingZoneId={editingZoneId} zoneName={zoneName} setZoneName={setZoneName} zoneCode={zoneCode} setZoneCode={setZoneCode} zoneDescription={zoneDescription} setZoneDescription={setZoneDescription} zonalBusinessHeadId={zonalBusinessHeadId} setZonalBusinessHeadId={setZonalBusinessHeadId} onSave={handleSaveZone} 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} />
<RegionDialog isOpen={showRegionDialog} onOpenChange={setShowRegionDialog} editingRegionId={editingRegionId} regionName={regionName} setRegionName={setRegionName} regionCode={regionCode} setRegionCode={setRegionCode} regionDescription={regionDescription} setRegionDescription={setRegionDescription} selectedRegionZone={selectedRegionZone} setSelectedRegionZone={setSelectedRegionZone} regionalManagerId={regionalManagerId} setRegionalManagerId={setRegionalManagerId} selectedRegionStates={selectedRegionStates} setSelectedRegionStates={setSelectedRegionStates} onSave={handleSaveRegion} userAssignedData={asms} /> <RegionDialog isOpen={showRegionDialog} onOpenChange={setShowRegionDialog} editingRegionId={editingRegionId} regionName={regionName} setRegionName={setRegionName} regionCode={regionCode} setRegionCode={setRegionCode} regionDescription={regionDescription} setRegionDescription={setRegionDescription} selectedRegionZone={selectedRegionZone} setSelectedRegionZone={setSelectedRegionZone} regionalManagerId={regionalManagerId} setRegionalManagerId={setRegionalManagerId} selectedRegionStates={selectedRegionDistricts} setSelectedRegionStates={setSelectedRegionDistricts} onSave={handleSaveRegion} 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} />
<ASMDialog isOpen={showASMDialog} onOpenChange={setShowASMDialog} editingASMId={editingASMId} asmManagerId={asmManagerId} setAsmManagerId={setAsmManagerId} asmName={asmName} setAsmName={setAsmName} asmCode={asmCode} setAsmCode={setAsmCode} asmEmployeeId={asmEmployeeId} setAsmEmployeeId={setAsmEmployeeId} asmStatus={asmStatus} setAsmStatus={setAsmStatus} selectedASMZone={selectedASMZone} setSelectedASMZone={setSelectedASMZone} selectedASMRegion={selectedASMRegion} setSelectedASMRegion={setSelectedASMRegion} selectedASMStates={selectedASMStates} setSelectedASMStates={setSelectedASMStates} selectedASMDistricts={selectedASMDistricts} setSelectedASMDistricts={setSelectedASMDistricts} onSave={handleSaveASM} userAssignedData={asms} districtsAssignedToOthers={districtsAssignedToOthers} getDistrictsForSelectedState={getDistrictsForSelectedState} /> <ASMDialog isOpen={showASMDialog} onOpenChange={setShowASMDialog} editingASMId={editingASMId} asmManagerId={asmManagerId} setAsmManagerId={setAsmManagerId} asmName={asmName} setAsmName={setAsmName} asmCode={asmCode} setAsmCode={setAsmCode} asmEmployeeId={asmEmployeeId} setAsmEmployeeId={setAsmEmployeeId} asmStatus={asmStatus} setAsmStatus={setAsmStatus} selectedASMZone={selectedASMZone} setSelectedASMZone={setSelectedASMZone} selectedASMRegion={selectedASMRegion} setSelectedASMRegion={setSelectedASMRegion} selectedASMStates={selectedASMStates} setSelectedASMStates={setSelectedASMStates} selectedASMDistricts={selectedASMDistricts} setSelectedASMDistricts={setSelectedASMDistricts} onSave={handleSaveASM} 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} />
<ZMDialog
isOpen={showZMDialog}
onOpenChange={setShowZMDialog}
editingZMId={editingZMId}
zmManagerId={zmManagerId}
setZmManagerId={setZmManagerId}
zmName={zmName}
setZmName={setZmName}
zmCode={zmCode}
setZmCode={setZmCode}
zmEmployeeId={zmEmployeeId}
setZmEmployeeId={setZmEmployeeId}
zmStatus={zmStatus}
setZmStatus={setZmStatus}
selectedZMZone={selectedZMZone}
setSelectedZMZone={setSelectedZMZone}
selectedZMStates={selectedZMStates}
setSelectedZMStates={setSelectedZMStates}
selectedZMDistricts={selectedZMDistricts}
setSelectedZMDistricts={setSelectedZMDistricts}
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}
/>
<TemplateDialog isOpen={showTemplateDialog} onOpenChange={setShowTemplateDialog} editingTemplate={editingTemplate} setEditingTemplate={setEditingTemplate} testDataInput={testDataInput} setTestDataInput={setTestDataInput} previewLoading={previewLoading} handlePreviewTemplate={handlePreviewTemplate} previewContent={previewContent} handleSaveTemplate={handleSaveTemplate} /> <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} locationPincode={locationPincode} setLocationPincode={setLocationPincode} locationActiveFrom={locationActiveFrom} setLocationActiveFrom={setLocationActiveFrom} locationActiveTo={locationActiveTo} setLocationActiveTo={setLocationActiveTo} locationStatus={locationStatus} setLocationStatus={setLocationStatus} onSave={handleSaveLocation} /> <LocationDialog isOpen={showLocationDialog} onOpenChange={setShowLocationDialog} editingLocationId={editingLocationId} locationState={locationState} setLocationState={setLocationState} locationDistrict={locationDistrict} setLocationDistrict={setLocationDistrict} locationCity={locationCity} setLocationCity={setLocationCity} locationPincode={locationPincode} setLocationPincode={setLocationPincode} locationActiveFrom={locationActiveFrom} setLocationActiveFrom={setLocationActiveFrom} locationActiveTo={locationActiveTo} setLocationActiveTo={setLocationActiveTo} locationStatus={locationStatus} setLocationStatus={setLocationStatus} onSave={handleSaveLocation} />
</div> </div>

View File

@ -7,7 +7,6 @@ import { Input } from '../../ui/input';
import { Checkbox } from '../../ui/checkbox'; import { Checkbox } from '../../ui/checkbox';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip';
import { UserCog, Users } from 'lucide-react';
import { RootState } from '../../../store'; import { RootState } from '../../../store';
interface ASMDialogProps { interface ASMDialogProps {
@ -35,7 +34,7 @@ interface ASMDialogProps {
onSave: () => void; onSave: () => void;
userAssignedData: any[]; userAssignedData: any[];
districtsAssignedToOthers: Record<string, string[]>; districtsAssignedToOthers: Record<string, string[]>;
getDistrictsForSelectedState: (state: string) => string[]; getDistrictsForSelectedState: (state: string) => { id: string; name: string }[];
} }
export const ASMDialog: React.FC<ASMDialogProps> = ({ export const ASMDialog: React.FC<ASMDialogProps> = ({
@ -49,9 +48,12 @@ export const ASMDialog: React.FC<ASMDialogProps> = ({
const { zones, regionalOffices } = useSelector((state: RootState) => state.master); const { zones, regionalOffices } = useSelector((state: RootState) => state.master);
const filteredASMUsers = userAssignedData.filter(u => { const filteredASMUsers = userAssignedData.filter(u => {
const code = (u.roleCode || '').toLowerCase(); const roles = u.allRoles || [];
const name = (u.role || '').toLowerCase(); return roles.some((r: string) => {
return code === 'asm' || name.includes('area sales manager'); const roleStr = (r || '').toUpperCase();
return ['ASM', 'AREA SALES MANAGER', 'RM', 'RBM', 'REGIONAL MANAGER', 'ZBH', 'ZONE BUSINESS HEAD', 'ZONAL BUSINESS HEAD'].includes(roleStr) ||
roleStr.includes('AREA SALES') || roleStr.includes('REGIONAL') || roleStr.includes('ZONAL');
});
}); });
return ( return (
@ -109,7 +111,7 @@ export const ASMDialog: React.FC<ASMDialogProps> = ({
<div className="mt-2 border rounded-lg p-3 max-h-48 overflow-y-auto bg-slate-50"> <div className="mt-2 border rounded-lg p-3 max-h-48 overflow-y-auto bg-slate-50">
{(() => { {(() => {
const selectedRegion = regionalOffices.find(r => r.id === selectedASMRegion); const selectedRegion = regionalOffices.find(r => r.id === selectedASMRegion);
const availableStates = selectedRegion?.states || []; const availableStates = (selectedRegion?.states || []).map((s: any) => typeof s === 'string' ? s : s.name);
return availableStates.length > 0 ? ( return availableStates.length > 0 ? (
<div className="space-y-2"> <div className="space-y-2">
@ -123,8 +125,8 @@ export const ASMDialog: React.FC<ASMDialogProps> = ({
setSelectedASMStates([...selectedASMStates, state]); setSelectedASMStates([...selectedASMStates, state]);
} else { } else {
setSelectedASMStates(selectedASMStates.filter(s => s !== state)); setSelectedASMStates(selectedASMStates.filter(s => s !== state));
const stateDistricts = getDistrictsForSelectedState(state); const stateDistricts = getDistrictsForSelectedState(state);
setSelectedASMDistricts(selectedASMDistricts.filter(d => !stateDistricts.includes(d))); setSelectedASMDistricts(selectedASMDistricts.filter(dId => !stateDistricts.some(sd => sd.id === dId)));
} }
}} }}
/> />
@ -155,34 +157,34 @@ export const ASMDialog: React.FC<ASMDialogProps> = ({
<div key={state} className="mb-4 last:mb-0"> <div key={state} className="mb-4 last:mb-0">
<h4 className="text-sm text-amber-700 mb-2 pb-1 border-b border-slate-200">{state}</h4> <h4 className="text-sm text-amber-700 mb-2 pb-1 border-b border-slate-200">{state}</h4>
<div className="space-y-2 ml-2"> <div className="space-y-2 ml-2">
{districts.map((district: string) => ( {districts.map((district: any) => (
<div key={district}> <div key={district.id}>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="flex items-center space-x-2 py-0.5"> <div className="flex items-center space-x-2 py-0.5">
<Checkbox <Checkbox
id={`asm-district-${district}`} id={`asm-district-${district.id}`}
checked={selectedASMDistricts.includes(district)} checked={selectedASMDistricts.includes(district.id)}
disabled={!!districtsAssignedToOthers[district]} disabled={!!districtsAssignedToOthers[district.id]}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
if (checked) { if (checked) {
setSelectedASMDistricts([...selectedASMDistricts, district]); setSelectedASMDistricts([...selectedASMDistricts, district.id]);
} else { } else {
setSelectedASMDistricts(selectedASMDistricts.filter(d => d !== district)); setSelectedASMDistricts(selectedASMDistricts.filter(id => id !== district.id));
} }
}} }}
/> />
<label <label
htmlFor={`asm-district-${district}`} htmlFor={`asm-district-${district.id}`}
className={`text-sm flex items-center gap-1.5 ${districtsAssignedToOthers[district] ? "text-slate-400 cursor-not-allowed" : "cursor-pointer text-slate-900"}`} className={`text-sm flex items-center gap-1.5 ${districtsAssignedToOthers[district.id] ? "text-slate-400 cursor-not-allowed" : "cursor-pointer text-slate-900"}`}
> >
{district} {district.name}
</label> </label>
</div> </div>
</TooltipTrigger> </TooltipTrigger>
{districtsAssignedToOthers[district] && ( {districtsAssignedToOthers[district.id] && (
<TooltipContent> <TooltipContent>
<p>Already managed by: {districtsAssignedToOthers[district].join(', ')}</p> <p>Already managed by: {districtsAssignedToOthers[district.id].join(', ')}</p>
</TooltipContent> </TooltipContent>
)} )}
</Tooltip> </Tooltip>
@ -208,7 +210,7 @@ export const ASMDialog: React.FC<ASMDialogProps> = ({
setAsmName(selectedUser.name); setAsmName(selectedUser.name);
setAsmCode(selectedUser.asmCode || ''); setAsmCode(selectedUser.asmCode || '');
setAsmEmployeeId(selectedUser.employeeId || ''); setAsmEmployeeId(selectedUser.employeeId || '');
setSelectedASMDistricts(selectedUser.areasManaged || []); setSelectedASMDistricts(selectedUser.areasManaged?.map((a: any) => a.id) || []);
setSelectedASMStates(selectedUser.stateNames || []); setSelectedASMStates(selectedUser.stateNames || []);
} }
}} }}

View File

@ -77,8 +77,10 @@ export const ASMManagement: React.FC<ASMManagementProps> = ({
<TableCell className="text-sm text-slate-600">{asm.regionName}</TableCell> <TableCell className="text-sm text-slate-600">{asm.regionName}</TableCell>
<TableCell> <TableCell>
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{asm.areasManaged.map((area: string, idx: number) => { {asm.areasManaged.map((area: any, idx: number) => {
const otherManagers = (districtsAssignedToOthers[area] || []).filter((name: string) => name !== asm.name); const areaId = typeof area === 'string' ? area : area.id;
const areaName = typeof area === 'string' ? area : area.name;
const otherManagers = (districtsAssignedToOthers[areaId] || []).filter((name: string) => name !== asm.name);
const isShared = otherManagers.length > 0; const isShared = otherManagers.length > 0;
return ( return (
<Badge <Badge
@ -87,7 +89,7 @@ export const ASMManagement: React.FC<ASMManagementProps> = ({
className={`text-xs ${isShared ? "border-amber-300 bg-amber-50 text-amber-700 font-medium" : ""}`} className={`text-xs ${isShared ? "border-amber-300 bg-amber-50 text-amber-700 font-medium" : ""}`}
title={isShared ? `Also managed by: ${otherManagers.join(', ')}` : undefined} title={isShared ? `Also managed by: ${otherManagers.join(', ')}` : undefined}
> >
{area} {areaName}
{isShared && <Users className="w-2.5 h-2.5 ml-1 inline" />} {isShared && <Users className="w-2.5 h-2.5 ml-1 inline" />}
</Badge> </Badge>
); );

View File

@ -16,7 +16,7 @@ interface LocationManagementProps {
export const LocationManagement: React.FC<LocationManagementProps> = ({ export const LocationManagement: React.FC<LocationManagementProps> = ({
onAddLocation, onEditLocation, onDeleteLocation onAddLocation, onEditLocation, onDeleteLocation
}) => { }) => {
const { allAreas } = useSelector((state: RootState) => state.master); const { allDistricts } = useSelector((state: RootState) => state.master);
return ( return (
<div className="space-y-4"> <div className="space-y-4">
@ -47,35 +47,35 @@ export const LocationManagement: React.FC<LocationManagementProps> = ({
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{allAreas.map((area) => ( {allDistricts.map((district) => (
<TableRow key={area.id}> <TableRow key={district.id}>
<TableCell> <TableCell>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<MapPin className="w-4 h-4 text-amber-600" /> <MapPin className="w-4 h-4 text-amber-600" />
<span className="font-medium">{area.district?.state?.stateName || area.state?.stateName || 'N/A'}</span> <span className="font-medium">{district.stateName || 'N/A'}</span>
</div> </div>
</TableCell> </TableCell>
<TableCell className="font-medium text-slate-900">{area.areaName} ({area.city})</TableCell> <TableCell className="font-medium text-slate-900">{district.name}</TableCell>
<TableCell className="text-slate-600 text-sm">{area.district?.districtName || 'N/A'}</TableCell> <TableCell className="text-slate-600 text-sm">{district.regionName || 'N/A'}</TableCell>
<TableCell className="text-slate-600 text-sm">{area.pincode}</TableCell> <TableCell className="text-slate-600 text-sm">{district.code || 'N/A'}</TableCell>
<TableCell> <TableCell>
{area.manager ? ( {district.asmName ? (
<span className="text-slate-700 font-medium">{area.manager.fullName}</span> <span className="text-slate-700 font-medium">{district.asmName}</span>
) : ( ) : (
<span className="text-slate-400 italic text-sm">Unassigned</span> <span className="text-slate-400 italic text-sm">Unassigned</span>
)} )}
</TableCell> </TableCell>
<TableCell> <TableCell>
<Badge variant={area.isActive ? 'default' : 'secondary'} className={area.isActive ? 'bg-emerald-100 text-emerald-700' : ''}> <Badge variant={district.isActive ? 'default' : 'secondary'} className={district.isActive ? 'bg-emerald-100 text-emerald-700' : ''}>
{area.isActive ? 'Active' : 'Inactive'} {district.isActive ? 'Active' : 'Inactive'}
</Badge> </Badge>
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<div className="flex gap-2 justify-end"> <div className="flex gap-2 justify-end">
<Button variant="ghost" size="sm" onClick={() => onEditLocation(area)}> <Button variant="ghost" size="sm" onClick={() => onEditLocation(district)}>
<Edit2 className="w-4 h-4" /> <Edit2 className="w-4 h-4" />
</Button> </Button>
<Button variant="ghost" size="sm" onClick={() => onDeleteLocation(area.id)} className="text-red-500 hover:text-red-600"> <Button variant="ghost" size="sm" onClick={() => onDeleteLocation(district.id)} className="text-red-500 hover:text-red-600">
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
</Button> </Button>
</div> </div>

View File

@ -7,6 +7,7 @@ import { Input } from '../../ui/input';
import { Textarea } from '../../ui/textarea'; import { Textarea } from '../../ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
import { Checkbox } from '../../ui/checkbox'; import { Checkbox } from '../../ui/checkbox';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip';
import { RootState } from '../../../store'; import { RootState } from '../../../store';
interface RegionDialogProps { interface RegionDialogProps {
@ -23,8 +24,8 @@ interface RegionDialogProps {
setSelectedRegionZone: (id: string) => void; setSelectedRegionZone: (id: string) => void;
regionalManagerId: string; regionalManagerId: string;
setRegionalManagerId: (id: string) => void; setRegionalManagerId: (id: string) => void;
selectedRegionStates: string[]; selectedRegionStates: string[]; // This now contains District IDs
setSelectedRegionStates: (states: string[]) => void; setSelectedRegionStates: (districts: string[]) => void;
onSave: () => void; onSave: () => void;
userAssignedData: any[]; // Used for RM selection userAssignedData: any[]; // Used for RM selection
} }
@ -35,12 +36,28 @@ export const RegionDialog: React.FC<RegionDialogProps> = ({
selectedRegionZone, setSelectedRegionZone, regionalManagerId, setRegionalManagerId, selectedRegionZone, setSelectedRegionZone, regionalManagerId, setRegionalManagerId,
selectedRegionStates, setSelectedRegionStates, onSave, userAssignedData selectedRegionStates, setSelectedRegionStates, onSave, userAssignedData
}) => { }) => {
const { zones, allStates } = useSelector((state: RootState) => state.master); const { zones, allDistricts, regionalOffices } = useSelector((state: RootState) => state.master);
// Map of District ID -> Region Name for districts assigned to other regions
const districtsAssignedToOthers = React.useMemo(() => {
const mapping: Record<string, string> = {};
(regionalOffices || []).forEach((r: any) => {
if (r.id !== editingRegionId) {
(r.districts || []).forEach((d: any) => {
mapping[d.id] = r.name;
});
}
});
return mapping;
}, [regionalOffices, editingRegionId]);
const filteredRMUsers = userAssignedData.filter(u => { const filteredRMUsers = userAssignedData.filter(u => {
const code = (u.roleCode || '').toLowerCase(); const roles = u.allRoles || [];
const name = (u.role || '').toLowerCase(); return roles.some((r: string) => {
return code === 'rm' || code === 'rbm' || name.includes('regional manager'); const roleStr = (r || '').toUpperCase();
return ['RM', 'RBM', 'REGIONAL MANAGER', 'ZBH', 'ZONE BUSINESS HEAD', 'ZONAL BUSINESS HEAD', 'ASM', 'AREA SALES MANAGER'].includes(roleStr) ||
roleStr.includes('REGIONAL') || roleStr.includes('ZONAL') || roleStr.includes('AREA SALES');
});
}); });
return ( return (
@ -99,29 +116,50 @@ export const RegionDialog: React.FC<RegionDialogProps> = ({
</div> </div>
<div> <div>
<Label>States Covered</Label> <Label>Districts Covered</Label>
<div className="mt-2 border rounded-lg p-3 max-h-48 overflow-y-auto bg-slate-50"> <div className="mt-2 border rounded-lg p-3 max-h-48 overflow-y-auto bg-slate-50">
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
{allStates {allDistricts
.filter(s => !selectedRegionZone || s.zoneId === selectedRegionZone) .map((district) => {
.map((state) => ( const assignedRegionName = districtsAssignedToOthers[district.id];
<div key={state.id} className="flex items-center space-x-2"> const isDisabled = !!assignedRegionName;
<Checkbox
id={`region-state-${state.id}`} return (
checked={selectedRegionStates.includes(state.stateName)} <div key={district.id} className="flex items-center space-x-2">
onCheckedChange={(checked) => { <TooltipProvider>
if (checked) { <Tooltip>
setSelectedRegionStates([...selectedRegionStates, state.stateName]); <TooltipTrigger asChild>
} else { <div className="flex items-center space-x-2">
setSelectedRegionStates(selectedRegionStates.filter(s => s !== state.stateName)); <Checkbox
} id={`region-district-${district.id}`}
}} disabled={isDisabled}
/> checked={selectedRegionStates.includes(district.id)}
<label htmlFor={`region-state-${state.id}`} className="text-sm cursor-pointer text-slate-900"> onCheckedChange={(checked) => {
{state.stateName} if (checked) {
</label> setSelectedRegionStates([...selectedRegionStates, district.id]);
</div> } else {
))} setSelectedRegionStates(selectedRegionStates.filter(id => id !== district.id));
}
}}
/>
<label
htmlFor={`region-district-${district.id}`}
className={`text-sm cursor-pointer ${isDisabled ? 'text-slate-400' : 'text-slate-900'}`}
>
{district.name}
</label>
</div>
</TooltipTrigger>
{isDisabled && (
<TooltipContent>
<p className="text-xs">Already assigned to {assignedRegionName}</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
</div>
);
})}
</div> </div>
</div> </div>
</div> </div>

View File

@ -43,7 +43,7 @@ export const RegionalManagement: React.FC<RegionalManagementProps> = ({
<TableHead>Region Name</TableHead> <TableHead>Region Name</TableHead>
<TableHead>Zone</TableHead> <TableHead>Zone</TableHead>
<TableHead>Regional Manager</TableHead> <TableHead>Regional Manager</TableHead>
<TableHead>States</TableHead> <TableHead>Districts</TableHead>
<TableHead>Cities</TableHead> <TableHead>Cities</TableHead>
<TableHead>Regional Officers</TableHead> <TableHead>Regional Officers</TableHead>
<TableHead>ASMs</TableHead> <TableHead>ASMs</TableHead>
@ -73,15 +73,15 @@ export const RegionalManagement: React.FC<RegionalManagementProps> = ({
)} )}
</TableCell> </TableCell>
<TableCell> <TableCell>
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{region.states.slice(0, 2).map((state: string, idx: number) => ( {(region.districts || []).slice(0, 2).map((district: any, idx: number) => (
<Badge key={idx} variant="secondary" className="text-xs"> <Badge key={idx} variant="secondary" className="text-xs">
{state} {district.name || district}
</Badge> </Badge>
))} ))}
{region.states.length > 2 && ( {(region.districts || []).length > 2 && (
<Badge variant="secondary" className="text-xs"> <Badge variant="secondary" className="text-xs">
+{region.states.length - 2} +{(region.districts || []).length - 2}
</Badge> </Badge>
)} )}
</div> </div>

View File

@ -0,0 +1,257 @@
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 { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip';
import { RootState } from '../../../store';
interface ZMDialogProps {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
editingZMId: string | null;
zmManagerId: string;
setZmManagerId: (id: string) => void;
zmName: string;
setZmName: (name: string) => void;
zmCode: string;
setZmCode: (code: string) => void;
zmEmployeeId: string;
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;
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
}) => {
const { zones, regionalOffices } = useSelector((state: RootState) => state.master);
const filteredZMUsers = userAssignedData.filter(u => {
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 (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingZMId ? 'Edit' : 'Add'} Zonal Manager</DialogTitle>
<DialogDescription>Configure ZM details and district-level assignments</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">
<Label>Select Zonal Manager User <span className="text-red-500">*</span></Label>
<Select
value={zmManagerId}
onValueChange={(value) => {
setZmManagerId(value);
const selectedUser = userAssignedData.find(u => u.id === value);
if (selectedUser) {
setZmName(selectedUser.name);
setZmCode(selectedUser.zmCode || '');
setZmEmployeeId(selectedUser.employeeId || '');
// Note: zmCode logic might need to check how it's stored in territoryProfile
const zmProfile = selectedUser.territoryProfile?.find((tp: any) => tp.roleCode === 'ZM' || tp.roleCode === 'DD-ZM');
if (zmProfile) {
setZmCode(zmProfile.managerCode || '');
setSelectedZMZone(zmProfile.zoneId || '');
}
setSelectedZMDistricts(selectedUser.districts?.map((d: any) => d.id) || []);
// Extract state names from assigned districts if not explicit
if (selectedUser.stateNames) setSelectedZMStates(selectedUser.stateNames);
}
}}
disabled={!!editingZMId}
>
<SelectTrigger className="mt-2 w-full text-slate-900 border-slate-200">
<SelectValue placeholder="Select ZM User" />
</SelectTrigger>
<SelectContent className="max-h-60">
{filteredZMUsers.length > 0 ? (
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>
<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} />
</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)} />
</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)} />
</div>
<div>
<Label>Status</Label>
<Select value={zmStatus} onValueChange={(val: 'active' | 'inactive') => setZmStatus(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 Zonal Manager</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
};

View File

@ -65,9 +65,9 @@ export const ZMManagement: React.FC<ZMManagementProps> = ({
<TableCell className="text-sm text-slate-600">{zm.regionName}</TableCell> <TableCell className="text-sm text-slate-600">{zm.regionName}</TableCell>
<TableCell> <TableCell>
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{zm.districts.slice(0, 3).map((district: string, idx: number) => ( {zm.districts.slice(0, 3).map((district: any, idx: number) => (
<Badge key={idx} variant="secondary" className="text-xs"> <Badge key={idx} variant="secondary" className="text-xs">
{district} {district.name}
</Badge> </Badge>
))} ))}
{zm.districts.length > 3 && ( {zm.districts.length > 3 && (

View File

@ -72,23 +72,23 @@ export const ZoneDetails: React.FC<ZoneDetailsProps> = ({ selectedZone, onAddZon
</div> </div>
</div> </div>
{zone.zbh && zone.zbh.name && ( {zone.zonalBusinessHead && zone.zonalBusinessHead.name && (
<div className="border-t pt-3"> <div className="border-t pt-3">
<Label className="text-xs text-slate-600 mb-2 block">Zone Business Head (ZBH)</Label> <Label className="text-xs text-slate-600 mb-2 block">Zone Business Head (ZBH)</Label>
<div className="bg-amber-50 rounded-lg p-3 space-y-1"> <div className="bg-amber-50 rounded-lg p-3 space-y-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<UserCog className="w-4 h-4 text-amber-600" /> <UserCog className="w-4 h-4 text-amber-600" />
<span className="text-sm text-slate-900">{zone.zbh.name}</span> <span className="text-sm text-slate-900">{zone.zonalBusinessHead.name}</span>
</div> </div>
{zone.zbh.email && ( {zone.zonalBusinessHead.email && (
<div className="flex items-center gap-2 ml-6"> <div className="flex items-center gap-2 ml-6">
<Mail className="w-3 h-3 text-slate-400" /> <Mail className="w-3 h-3 text-slate-400" />
<span className="text-xs text-slate-600">{zone.zbh.email}</span> <span className="text-xs text-slate-600">{zone.zonalBusinessHead.email}</span>
</div> </div>
)} )}
{zone.zbh.phone && ( {zone.zonalBusinessHead.phone && (
<div className="flex items-center gap-2 ml-6"> <div className="flex items-center gap-2 ml-6">
<span className="text-xs text-slate-600">{zone.zbh.phone}</span> <span className="text-xs text-slate-600">{zone.zonalBusinessHead.phone}</span>
</div> </div>
)} )}
</div> </div>

View File

@ -4,6 +4,7 @@ import { Button } from '../../ui/button';
import { Label } from '../../ui/label'; import { Label } from '../../ui/label';
import { Input } from '../../ui/input'; import { Input } from '../../ui/input';
import { Textarea } from '../../ui/textarea'; import { Textarea } from '../../ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
interface ZoneDialogProps { interface ZoneDialogProps {
isOpen: boolean; isOpen: boolean;
@ -15,55 +16,88 @@ interface ZoneDialogProps {
setZoneCode: (code: string) => void; setZoneCode: (code: string) => void;
zoneDescription: string; zoneDescription: string;
setZoneDescription: (desc: string) => void; setZoneDescription: (desc: string) => void;
zonalBusinessHeadId: string;
setZonalBusinessHeadId: (id: string) => void;
userAssignedData: any[];
onSave: () => void; onSave: () => void;
} }
export const ZoneDialog: React.FC<ZoneDialogProps> = ({ export const ZoneDialog: React.FC<ZoneDialogProps> = ({
isOpen, onOpenChange, editingZoneId, zoneName, setZoneName, isOpen, onOpenChange, editingZoneId, zoneName, setZoneName,
zoneCode, setZoneCode, zoneDescription, setZoneDescription, onSave zoneCode, setZoneCode, zoneDescription, setZoneDescription,
zonalBusinessHeadId, setZonalBusinessHeadId, userAssignedData, onSave
}) => { }) => {
return ( const filteredZBHUsers = (userAssignedData || []).filter((u: any) => {
<Dialog open={isOpen} onOpenChange={onOpenChange}> const roles = u.allRoles || [];
<DialogContent className="max-w-md"> return roles.some((r: string) => {
<DialogHeader> const roleStr = (r || '').toUpperCase();
<DialogTitle>{editingZoneId ? 'Edit' : 'Add'} Zone</DialogTitle> return ['ZBH', 'ZONE BUSINESS HEAD', 'ZONAL BUSINESS HEAD', 'RM', 'RBM', 'REGIONAL MANAGER', 'ASM', 'AREA SALES MANAGER'].includes(roleStr) ||
<DialogDescription>Configure zonal details and geographical boundaries</DialogDescription> roleStr.includes('ZONAL') || roleStr.includes('REGIONAL') || roleStr.includes('AREA SALES');
</DialogHeader> });
<div className="space-y-4"> });
<div>
<Label>Zone Name</Label> return (
<Input <Dialog open={isOpen} onOpenChange={onOpenChange}>
placeholder="e.g., North Zone" <DialogContent className="max-w-md">
className="mt-2 text-slate-900" <DialogHeader>
value={zoneName} <DialogTitle>{editingZoneId ? 'Edit' : 'Add'} Zone</DialogTitle>
onChange={(e) => setZoneName(e.target.value)} <DialogDescription>Configure zonal details and geographical boundaries</DialogDescription>
/> </DialogHeader>
</div> <div className="space-y-4">
<div> <div className="grid grid-cols-2 gap-4">
<Label>Zone Code</Label> <div>
<Input <Label>Zone Name</Label>
placeholder="e.g., NZ" <Input
className="mt-2 text-slate-900" placeholder="e.g., North Zone"
value={zoneCode} className="mt-2 text-slate-900"
onChange={(e) => setZoneCode(e.target.value)} value={zoneName}
/> onChange={(e: any) => setZoneName(e.target.value)}
</div> />
<div> </div>
<Label>Description</Label> <div>
<Textarea <Label>Zone Code</Label>
placeholder="Describe the zone's coverage..." <Input
className="mt-2 text-slate-900" placeholder="e.g., NZ"
rows={3} className="mt-2 text-slate-900"
value={zoneDescription} value={zoneCode}
onChange={(e) => setZoneDescription(e.target.value)} onChange={(e: any) => setZoneCode(e.target.value)}
/> />
</div> </div>
<div className="flex gap-3 pt-4"> </div>
<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 Zone</Button> <div>
</div> <Label>Zonal Business Head</Label>
</div> <Select value={zonalBusinessHeadId} onValueChange={setZonalBusinessHeadId}>
</DialogContent> <SelectTrigger className="mt-2 w-full text-slate-900">
</Dialog> <SelectValue placeholder="Select Head" />
); </SelectTrigger>
<SelectContent className="max-h-60">
<SelectItem value="none">None / Unassigned</SelectItem>
{filteredZBHUsers.map((user) => (
<SelectItem key={user.id} value={user.id}>
{user.name} ({user.email})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label>Description</Label>
<Textarea
placeholder="Describe the zone's coverage..."
className="mt-2 text-slate-900"
rows={3}
value={zoneDescription}
onChange={(e: any) => setZoneDescription(e.target.value)}
/>
</div>
<div className="flex gap-3 pt-4">
<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 Zone</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}; };

View File

@ -11,15 +11,11 @@ interface ZonesOverviewProps {
} }
export const ZonesOverview: React.FC<ZonesOverviewProps> = ({ selectedZone, onZoneClick }) => { export const ZonesOverview: React.FC<ZonesOverviewProps> = ({ selectedZone, onZoneClick }) => {
const { zones, regionalOffices, asms } = useSelector((state: RootState) => state.master); const { zones } = useSelector((state: RootState) => state.master);
return ( return (
<div className="grid grid-cols-1 md:grid-cols-5 gap-4"> <div className="grid grid-cols-1 md:grid-cols-5 gap-4">
{zones.map((zone: any) => { {zones.map((zone: any) => {
const zoneRegions = regionalOffices.filter((r: any) => r.zoneId === zone.id);
const zoneASMs = asms.filter((a: any) => a.zoneId === zone.id);
const totalRegionalOfficers = zoneRegions.reduce((sum: number, region: any) => sum + region.regionalOfficerCount, 0);
return ( return (
<Card <Card
key={zone.id} key={zone.id}
@ -31,7 +27,7 @@ export const ZonesOverview: React.FC<ZonesOverviewProps> = ({ selectedZone, onZo
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Globe className="w-5 h-5 text-amber-600" /> <Globe className="w-5 h-5 text-amber-600" />
<CardTitle className="text-lg">{zone.name} Zone</CardTitle> <CardTitle className="text-lg">{zone.name.toUpperCase().endsWith('ZONE') ? zone.name : `${zone.name} Zone`}</CardTitle>
</div> </div>
<Badge variant="outline" className="text-xs">{zone.code}</Badge> <Badge variant="outline" className="text-xs">{zone.code}</Badge>
</div> </div>
@ -45,15 +41,15 @@ export const ZonesOverview: React.FC<ZonesOverviewProps> = ({ selectedZone, onZo
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-slate-500">Regions</span> <span className="text-slate-500">Regions</span>
<Badge className="bg-indigo-600">{zoneRegions.length}</Badge> <Badge className="bg-indigo-600">{zone.regionCount}</Badge>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-slate-500">Regional Officers</span> <span className="text-slate-500">Regional Officers</span>
<Badge className="bg-purple-600">{totalRegionalOfficers}</Badge> <Badge className="bg-purple-600">{zone.regionalOfficerCount}</Badge>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-slate-500">ASMs</span> <span className="text-slate-500">ASMs</span>
<Badge className="bg-green-600">{zoneASMs.length}</Badge> <Badge className="bg-green-600">{zone.asmCount}</Badge>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-slate-500">ZMs</span> <span className="text-slate-500">ZMs</span>

View File

@ -11,19 +11,27 @@ export const getAncestorByType = (node: any, type: string): any => {
return null; return null;
}; };
export const getRegionId = (loc: any, stateToRegionId: Record<string, string>, parsedDistricts: any[], parsedStates: any[]) => { export const getRegionId = (loc: any, stateToRegionId: Record<string, string>, parsedDistricts: any[], parsedStates: any[]): string | null => {
if (!loc) return null; if (!loc) return null;
if (typeof loc === 'string') {
const dObj = parsedDistricts.find((d: any) => d.id === loc || d.districtName === loc);
if (dObj) return getRegionId(dObj, stateToRegionId, parsedDistricts, parsedStates);
const sObj = parsedStates.find((s: any) => s.id === loc || s.stateName === loc);
if (sObj) return getRegionId(sObj, stateToRegionId, parsedDistricts, parsedStates);
if (stateToRegionId[loc.toUpperCase()]) return stateToRegionId[loc.toUpperCase()];
return null;
}
const regAncestor = getAncestorByType(loc, 'region'); const regAncestor = getAncestorByType(loc, 'region');
if (regAncestor?.id) return regAncestor.id; if (regAncestor?.id) return regAncestor.id;
if (loc.type === 'region') return loc.id; if (loc.type === 'region') return loc.id;
const sName = loc.stateName || loc.state?.name || loc.state?.stateName; const sName = loc.stateName || loc.state?.name || loc.state?.stateName;
if (sName && stateToRegionId[sName]) return stateToRegionId[sName]; if (sName && stateToRegionId[sName.toUpperCase()]) return stateToRegionId[sName.toUpperCase()];
if (loc.type === 'district' || loc.districtName) { if (loc.type === 'district' || loc.districtName) {
const dName = loc.districtName || loc.name; const dName = loc.districtName || loc.name;
const dObj = parsedDistricts.find((d: any) => d.districtName === dName || d.id === loc.id); const dObj = parsedDistricts.find((d: any) => d.districtName === dName || d.id === loc.id);
if (dObj?.stateId) { if (dObj?.stateId) {
const sObj = parsedStates.find((s: any) => s.id === dObj.stateId); const sObj = parsedStates.find((s: any) => s.id === dObj.stateId);
if (sObj?.stateName && stateToRegionId[sObj.stateName]) return stateToRegionId[sObj.stateName]; if (sObj?.stateName && stateToRegionId[sObj.stateName.toUpperCase()]) return stateToRegionId[sObj.stateName.toUpperCase()];
} }
} }
return null; return null;

View File

@ -4,7 +4,6 @@ import { masterService } from '../services/master.service';
import { import {
setMasterData, setLoading, setError setMasterData, setLoading, setError
} from '../store/slices/masterSlice'; } from '../store/slices/masterSlice';
import { getAncestorByType, getRegionId, getZoneId } from './useLocationHelpers';
import { toast } from 'sonner'; import { toast } from 'sonner';
export const useMasterData = () => { export const useMasterData = () => {
@ -43,239 +42,117 @@ export const useMasterData = () => {
const bodyAreas = getBody(areasRes); const bodyAreas = getBody(areasRes);
const bodySla = getBody(slaRes); const bodySla = getBody(slaRes);
const users = bodyUsers?.users || bodyUsers?.data || []; const users = (bodyUsers?.users || bodyUsers?.data || []).map((u: any) => ({
const usersByLocation: Record<string, any[]> = {}; ...u,
users.forEach((u: any) => { name: u.fullName || u.name,
const locId = u.location?.id || u.locationId; role: u.role?.roleName || 'System User',
if (locId) { zone: u.allZones?.join(', ') || 'Global',
if (!usersByLocation[locId]) usersByLocation[locId] = []; region: u.allRegions?.join(', ') || 'Unassigned',
usersByLocation[locId].push(u); status: u.isActive !== false ? 'Active' : 'Inactive'
}
});
const rawZones = bodyZones?.zones || bodyZones?.data || [];
const rawRegions = bodyRegions?.regions || bodyRegions?.data || [];
const rawStates = bodyStates?.states || bodyStates?.data || [];
const rawDistricts = bodyDistricts?.districts || bodyDistricts?.data || [];
const allStates = rawStates.map((s: any) => ({
...s,
stateName: s.name,
zoneId: getAncestorByType(s, 'zone')?.id || s.zoneId
})); }));
const allDistricts = rawDistricts.map((d: any) => ({
...d,
districtName: d.name,
stateId: getAncestorByType(d, 'state')?.id || d.stateId
}));
const stateToRegionId: Record<string, string> = {};
const regionIdToZoneId: Record<string, string> = {};
rawRegions.forEach((r: any) => {
const zAncestor = getAncestorByType(r, 'zone');
if (zAncestor?.id) regionIdToZoneId[r.id] = zAncestor.id;
else if (r.zoneId) regionIdToZoneId[r.id] = r.zoneId;
});
const isAsmRole = (role: string) => role === 'ASM' || role === 'AREA SALES MANAGER';
const isRmRole = (role: string) => role === 'RM' || role === 'REGIONAL MANAGER';
const isZmRole = (role: string) => (role === 'ZM' || role.includes('ZONAL MANAGER')) && !role.includes('HEAD');
const parsedRegions = rawRegions.map((r: any) => {
const zoneId = getZoneId(r, regionIdToZoneId, stateToRegionId, allDistricts, allStates);
const states = Array.isArray(r.children) ? r.children.filter((c: any) => c.type === 'state').map((c: any) => c.name) : [];
states.forEach((sName: string) => { if (sName) stateToRegionId[sName] = r.id; });
return {
id: r.id,
code: r.name ? r.name.substring(0, 3).toUpperCase() : 'REG',
name: r.name || r.regionName,
zoneId: zoneId,
zoneName: r.zone?.name || r.zone?.zoneName || 'Unknown',
states: states,
cities: [],
status: (r.isActive !== false) ? 'Active' : 'Inactive',
regionalOfficerCount: 0,
asmCount: 0
};
});
const regionAsmIds: Record<string, string[]> = {};
const regionRmIds: Record<string, string[]> = {};
const zoneZmIds: Record<string, string[]> = {};
const zoneAsmIds: Record<string, string[]> = {};
const zoneRmIds: Record<string, string[]> = {};
users.forEach((u: any) => {
const asmRegionIds = new Set<string>();
const rmRegionIds = new Set<string>();
const zmZoneIds = new Set<string>();
const pRole = (u.roleCode || u.role?.roleCode || '').toUpperCase();
const pRoleName = (u.role?.roleName || '').toUpperCase();
const loc = u.location;
if (isAsmRole(pRole) || isAsmRole(pRoleName)) {
const rid = getRegionId(loc, stateToRegionId, allDistricts, allStates);
if (rid) asmRegionIds.add(rid);
}
if (isRmRole(pRole) || isRmRole(pRoleName)) {
const rid = getRegionId(loc, stateToRegionId, allDistricts, allStates);
if (rid) rmRegionIds.add(rid);
}
if (isZmRole(pRole) || isZmRole(pRoleName)) {
const zid = getZoneId(loc, regionIdToZoneId, stateToRegionId, allDistricts, allStates);
if (zid) zmZoneIds.add(zid);
}
if (Array.isArray(u.userRoles)) {
u.userRoles.forEach((ur: any) => {
const urRole = (ur.role?.roleCode || '').toUpperCase();
const urRoleName = (ur.role?.roleName || '').toUpperCase();
const rid = getRegionId(ur.location, stateToRegionId, allDistricts, allStates);
const zid = getZoneId(ur.location, regionIdToZoneId, stateToRegionId, allDistricts, allStates);
if (isAsmRole(urRole) || isAsmRole(urRoleName)) { if (rid) asmRegionIds.add(rid); }
if (isRmRole(urRole) || isRmRole(urRoleName)) { if (rid) rmRegionIds.add(rid); }
if (isZmRole(urRole) || isZmRole(urRoleName)) { if (zid) zmZoneIds.add(zid); }
});
}
if (Array.isArray(u.areaManagers)) {
u.areaManagers.forEach((am: any) => {
const rid = getRegionId(am.area || am.area?.district, stateToRegionId, allDistricts, allStates);
if (rid) asmRegionIds.add(rid);
});
}
asmRegionIds.forEach((rid: string) => {
if (!regionAsmIds[rid]) regionAsmIds[rid] = [];
if (!regionAsmIds[rid].includes(u.id)) regionAsmIds[rid].push(u.id);
const zid = regionIdToZoneId[rid];
if (zid) {
if (!zoneAsmIds[zid]) zoneAsmIds[zid] = [];
if (!zoneAsmIds[zid].includes(u.id)) zoneAsmIds[zid].push(u.id);
}
});
rmRegionIds.forEach((rid: string) => {
if (!regionRmIds[rid]) regionRmIds[rid] = [];
if (!regionRmIds[rid].includes(u.id)) regionRmIds[rid].push(u.id);
const zid = regionIdToZoneId[rid];
if (zid) {
if (!zoneRmIds[zid]) zoneRmIds[zid] = [];
if (!zoneRmIds[zid].includes(u.id)) zoneRmIds[zid].push(u.id);
}
});
zmZoneIds.forEach((zid: string) => {
if (!zoneZmIds[zid]) zoneZmIds[zid] = [];
if (!zoneZmIds[zid].includes(u.id)) zoneZmIds[zid].push(u.id);
});
});
const roles = (bodyRoles?.roles || bodyRoles?.data || []).map((r: any) => ({ const roles = (bodyRoles?.roles || bodyRoles?.data || []).map((r: any) => ({
id: r.id, name: r.roleName, permissions: r.permissions?.map((p: any) => p.permissionCode) || [], userCount: r.userCount || 0 id: r.id, name: r.roleName, permissions: r.permissions?.map((p: any) => p.permissionCode) || [], userCount: r.userCount || 0
})); }));
const zones = rawZones.map((z: any) => { const zones = (bodyZones?.zones || bodyZones?.data || []).map((z: any) => {
const hierarchyZmIds = zoneZmIds[z.id] || []; const zoneName = (z.name || z.zoneName || '').toUpperCase();
const hAsmIds = zoneAsmIds[z.id] || []; const zoneUsers = users.filter((u: any) => u.allZones?.includes(zoneName));
const hRmIds = zoneRmIds[z.id] || []; const zoneZmUsers = zoneUsers.filter((u: any) => u.allRoles?.some((r: string) => (r === 'ZM' || r.includes('ZONAL MANAGER')) && !r.includes('HEAD')));
const zoneUsersMatched = users.filter((u: any) => getZoneId(u.location, regionIdToZoneId, stateToRegionId, allDistricts, allStates) === z.id || (Array.isArray(u.userRoles) && u.userRoles.some((ur: any) => getZoneId(ur.location, regionIdToZoneId, stateToRegionId, allDistricts, allStates) === z.id)));
const zoneZmUsers = zoneUsersMatched.filter((u: any) => {
const c = (u.roleCode || u.role?.roleCode || '').toUpperCase();
const n = (u.role?.roleName || '').toUpperCase();
return isZmRole(c) || isZmRole(n);
});
const zbhUser = zoneUsersMatched.find((u: any) => {
const c = (u.roleCode || u.role?.roleCode || '').toLowerCase();
const n = (u.role?.roleName || '').toLowerCase();
return c === 'zbh' || n.includes('zonal business head') || c.includes('business head');
}) || ({} as any);
return { return {
id: z.id, name: z.name || z.zoneName, description: z.description || '', id: z.id, name: zoneName, description: z.description || '',
code: z.name ? z.name.substring(0, 3).toUpperCase() : 'ZON', code: z.name ? z.name.substring(0, 3).toUpperCase() : 'ZON',
states: Array.isArray(z.children) ? z.children.filter((child: any) => child.type === 'state').map((c: any) => c.name) : [], regionCount: z.regionCount || 0,
zmCount: hierarchyZmIds.length, asmCount: z.asmCount || 0,
asmCount: hAsmIds.length, regionalOfficerCount: z.regionalOfficerCount || 0,
regionalOfficerCount: hRmIds.length, zmCount: z.zmCount || 0,
zbh: { name: zbhUser.fullName || 'Not Assigned', email: zbhUser.email || '', phone: zbhUser.mobileNumber || '' }, states: z.states || [],
zonalManagers: zoneZmUsers.map((m: any) => ({ name: m.fullName || 'Unknown', email: m.email || '', phone: m.mobileNumber || '', districts: [] })) zbh: {
name: z.zonalBusinessHead?.fullName || 'Not Assigned',
email: z.zonalBusinessHead?.email || '',
phone: z.zonalBusinessHead?.mobileNumber || ''
},
zonalManagers: zoneZmUsers.map((m: any) => ({
name: m.fullName || 'Unknown',
email: m.email || '',
phone: m.mobileNumber || '',
districts: m.territoryProfile?.filter((t: any) => (t.roleCode === 'ZM' || t.role === 'ZONAL MANAGER') && (t.zone === zoneName || t.zoneId === z.id) && t.locationType === 'district').map((t: any) => t.locationName) || []
}))
}; };
}); });
const regionalOffices = parsedRegions.map((r: any) => { const regionalOffices = (bodyRegions?.regions || bodyRegions?.data || []).map((r: any) => ({
const hRmIds = regionRmIds[r.id] || []; id: r.id,
const hAsmIds = regionAsmIds[r.id] || []; code: r.code || (r.name ? r.name.substring(0, 3).toUpperCase() : 'REG'),
const rmUser = users.find((u: any) => hRmIds.includes(u.id)) || ({} as any); name: r.name || r.regionName,
return { zoneId: r.zoneId,
...r, regionalOfficerCount: hRmIds.length, asmCount: hAsmIds.length, zoneName: r.zoneName || 'Unknown',
regionalManager: rmUser.id ? { id: rmUser.id, name: rmUser.fullName, email: rmUser.email, phone: rmUser.mobileNumber } : undefined states: r.states || [],
}; cities: r.cities || [],
}); asmCount: r.asmCount || 0,
regionalOfficerCount: r.regionalOfficerCount || 0,
regionalManager: r.regionalManager ? {
id: r.regionalManager.id,
name: r.regionalManager.fullName,
email: r.regionalManager.email,
phone: r.regionalManager.mobileNumber
} : undefined,
status: r.isActive !== false ? 'Active' : 'Inactive'
}));
const asms = users.filter((u: any) => { const asms = users.filter((u: any) => u.allRoles?.some((r: string) => r === 'ASM' || r === 'AREA SALES MANAGER'))
const c = (u.roleCode || u.role?.roleCode || '').toUpperCase(); .map((u: any) => {
const n = (u.role?.roleName || '').toUpperCase(); const asmT = u.territoryProfile?.find((t: any) => t.roleCode === 'ASM') || {};
return isAsmRole(c) || isAsmRole(n); const asmCode = asmT.managerCode || u.asmCode || '';
}).map((u: any) => { const areasManaged = u.territoryProfile?.filter((t: any) => t.roleCode === 'ASM' && (t.locationType === 'area' || t.locationType === 'district')).map((t: any) => ({ id: t.locationId, name: t.locationName })) || [];
const zid = getZoneId(u.location, regionIdToZoneId, stateToRegionId, allDistricts, allStates); const stateNames = Array.from(new Set(u.territoryProfile?.filter((t: any) => t.roleCode === 'ASM' && t.state).map((t: any) => t.state).filter(Boolean))) as string[];
const rid = getRegionId(u.location, stateToRegionId, allDistricts, allStates);
const regObj = rid ? parsedRegions.find((reg: any) => reg.id === rid) : null;
const zonObj = zid ? rawZones.find((z: any) => z.id === zid) : null;
const district = getAncestorByType(u.location, 'district');
const state = getAncestorByType(u.location, 'state');
const dFromAM = Array.isArray(u.areaManagers) ? Array.from(new Set(u.areaManagers.map((am: any) => am.area?.district?.districtName || am.area?.district?.name).filter(Boolean))) : [];
const dFromUR = Array.isArray(u.userRoles) ? Array.from(new Set(u.userRoles.filter((ur: any) => ur?.role?.roleCode === 'ASM' && ur?.location?.type === 'district').map((ur: any) => ur?.location?.name).filter(Boolean))) : [];
const mDists = Array.from(new Set([...dFromAM, ...dFromUR]));
const sFromAM = Array.isArray(u.areaManagers) ? Array.from(new Set(u.areaManagers.map((am: any) => am.area?.state?.stateName || am.area?.district?.state?.stateName).filter(Boolean))) : [];
const sFromUR = Array.isArray(u.userRoles) ? Array.from(new Set(u.userRoles.filter((ur: any) => ur?.role?.roleCode === 'ASM' && (ur?.location?.type === 'district' || ur?.location?.type === 'state')).map((ur: any) => ur?.location?.stateName || ur?.location?.name).filter(Boolean))) : [];
const mStates = Array.from(new Set([...sFromAM, ...sFromUR]));
const aAsmCode = Array.isArray(u.userRoles) ? (u.userRoles.find((ur: any) => ur?.role?.roleCode === 'ASM' && ur?.managerCode)?.managerCode || '') : '';
return {
id: u.id, code: u.employeeId || 'N/A', asmCode: u.asmCode || aAsmCode || '', employeeId: u.employeeId || '',
name: u.fullName, zoneId: zid || '', regionId: rid || '',
zoneName: zonObj?.name || zonObj?.zoneName || 'Unassigned', regionName: regObj?.name || regObj?.regionName || 'Unassigned',
areasManaged: mDists.length > 0 ? mDists : (district?.name ? [district?.name] : []),
districtNames: mDists.length > 0 ? mDists : (district?.name ? [district?.name] : []),
stateNames: mStates.length > 0 ? mStates : (state?.name ? [state?.name] : []),
email: u.email, phone: u.mobileNumber, status: (u.isActive !== false) ? 'Active' : 'Inactive'
};
});
const zonalManagerMappings = users.filter((u: any) => {
const c = (u.roleCode || u.role?.roleCode || '').toUpperCase();
const n = (u.role?.roleName || '').toUpperCase();
return (isZmRole(c) || isZmRole(n)) && !n.includes('HEAD');
}).map((u: any) => {
const zid = getZoneId(u.location, regionIdToZoneId, stateToRegionId, allDistricts, allStates);
const rid = getRegionId(u.location, stateToRegionId, allDistricts, allStates);
return { return {
id: u.id, name: u.fullName, code: u.employeeId || 'N/A', email: u.email, phone: u.mobileNumber || 'N/A', id: u.id, name: u.fullName, code: u.employeeId || 'N/A',
zoneId: zid || '', asmCode: asmCode, employeeId: u.employeeId || '',
zoneName: (zid ? rawZones.find((z: any) => z.id === zid)?.name : 'Not Assigned') || 'Not Assigned', email: u.email, phone: u.mobileNumber,
regionId: rid || '', zoneId: asmT.zoneId,
regionName: (rid ? parsedRegions.find((r: any) => r.id === rid)?.name : 'Not Assigned') || 'Not Assigned', regionId: asmT.regionId,
districts: u.districts || [], status: (u.isActive !== false) ? 'Active' : 'Inactive' zoneName: asmT.zone || u.zone || 'Unassigned',
regionName: asmT.region || u.region || 'Unassigned',
areasManaged: areasManaged, // Now contains {id, name} objects
stateNames: stateNames,
status: u.status
}; };
}); });
const zonalManagerMappings = 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 })) || [];
return {
id: u.id, name: u.fullName, code: 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,
status: u.status
};
});
const allAreas = (bodyAreas.areas || bodyAreas.data || []).map((a: any) => { const allStates = (bodyStates?.states || bodyStates?.data || []).map((s: any) => ({
const dId = getAncestorByType(a, 'district')?.id || a.districtId; ...s, stateName: s.name
const dObj = allDistricts.find((d: any) => d.id === dId); }));
const sObj = allStates.find((s: any) => s.id === dObj?.stateId);
return { const allDistricts = (bodyDistricts?.districts || bodyDistricts?.data || []).map((d: any) => ({
...a, areaName: a.name, districtId: dId, ...d,
district: { districtName: dObj?.districtName || 'Unknown', stateId: dObj?.stateId, state: { stateName: sObj?.stateName || 'Unknown' } } districtName: d.name,
}; stateId: d.stateId // Now populated by backend's deep resolution
}); }));
const allAreas = (bodyAreas.areas || bodyAreas.data || []).map((a: any) => ({
...a,
areaName: a.name,
districtId: a.districtId // Now populated by backend's deep resolution
}));
const slaConfigs = (bodySla.data || []).map((s: any) => ({ const slaConfigs = (bodySla.data || []).map((s: any) => ({
id: s.id, stage: s.stageCode || 'Unknown', days: s.tatValue || 0, enabled: s.isActive !== false, id: s.id, stage: s.stageCode || 'Unknown', days: s.tatValue || 0, enabled: s.isActive !== false,
@ -288,6 +165,7 @@ export const useMasterData = () => {
availablePermissions: bodyPerms?.permissions || bodyPerms?.data || [], availablePermissions: bodyPerms?.permissions || bodyPerms?.data || [],
emailTemplates: bodyEmail?.data || [], emailTemplates: bodyEmail?.data || [],
slaConfigs, slaConfigs,
users,
loading: false loading: false
})); }));

View File

@ -3,14 +3,14 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
export interface Zone { export interface Zone {
id: string; id: string;
name: string; name: string;
description: string; description?: string;
code: string; code: string;
regionCount: number;
districtCount: number;
states: string[]; states: string[];
zmCount: number; zmCount: number;
asmCount?: number; zonalBusinessHead: { id: string; name: string; email: string; phone: string } | null;
regionalOfficerCount?: number; zonalManagers: { id: string; name: string; email: string; phone: string; districts: string[] }[];
zbh: { name: string; email: string; phone: string };
zonalManagers: any[];
} }
export interface Region { export interface Region {
@ -19,12 +19,11 @@ export interface Region {
name: string; name: string;
zoneId: string; zoneId: string;
zoneName: string; zoneName: string;
states: string[]; districts: { id: string; name: string }[];
cities: string[];
status: string; status: string;
regionalOfficerCount: number; regionalOfficerCount: number;
asmCount: number; asmCount: number;
regionalManager?: { id: string; name: string; email: string; phone: string }; regionalManager?: { id: string; fullName: string; email: string } | null;
} }
export interface ASM { export interface ASM {
@ -65,12 +64,25 @@ export interface MasterState {
asms: ASM[]; asms: ASM[];
zonalManagerMappings: ZonalManagerMapping[]; zonalManagerMappings: ZonalManagerMapping[];
roles: any[]; roles: any[];
allStates: any[]; allStates: { id: string, name: string, zone?: { name: string } }[];
allDistricts: any[]; allDistricts: {
id: string,
name: string,
code?: string,
stateId: string,
regionId: string,
zoneId: string,
stateName: string,
regionName: string,
zoneName: string,
asmName?: string,
isActive: boolean
}[];
allAreas: any[]; allAreas: any[];
availablePermissions: any[]; availablePermissions: any[];
emailTemplates: any[]; emailTemplates: any[];
slaConfigs: any[]; slaConfigs: any[];
users: any[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
} }
@ -87,6 +99,7 @@ const initialState: MasterState = {
availablePermissions: [], availablePermissions: [],
emailTemplates: [], emailTemplates: [],
slaConfigs: [], slaConfigs: [],
users: [],
loading: false, loading: false,
error: null, error: null,
}; };
@ -110,6 +123,9 @@ const masterSlice = createSlice({
setZonalManagerMappings: (state, action: PayloadAction<ZonalManagerMapping[]>) => { setZonalManagerMappings: (state, action: PayloadAction<ZonalManagerMapping[]>) => {
state.zonalManagerMappings = action.payload; state.zonalManagerMappings = action.payload;
}, },
setUsers: (state, action: PayloadAction<any[]>) => {
state.users = action.payload;
},
setLoading: (state, action: PayloadAction<boolean>) => { setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload; state.loading = action.payload;
}, },
@ -121,7 +137,7 @@ const masterSlice = createSlice({
export const { export const {
setMasterData, setZones, setRegionalOffices, setAsms, setMasterData, setZones, setRegionalOffices, setAsms,
setZonalManagerMappings, setLoading, setError setZonalManagerMappings, setUsers, setLoading, setError
} = masterSlice.actions; } = masterSlice.actions;
export default masterSlice.reducer; export default masterSlice.reducer;