nbased on new hierach ui updated still flow not stable
This commit is contained in:
parent
def289e12b
commit
c210f640d8
@ -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>
|
||||||
|
|||||||
@ -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 || []);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
257
src/components/applications/MasterPage/ZMDialog.tsx
Normal file
257
src/components/applications/MasterPage/ZMDialog.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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 && (
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user