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 {
|
||||
Tabs, TabsContent, TabsList, TabsTrigger
|
||||
} from '../ui/tabs';
|
||||
import {
|
||||
Globe, Shield, Clock, Mail, MapPin, SlidersHorizontal
|
||||
} from 'lucide-react';
|
||||
import { Globe, Shield, Clock, Mail, MapPin, SlidersHorizontal } from 'lucide-react';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
@ -25,6 +23,7 @@ import { RolePermissions } from './MasterPage/RolePermissions';
|
||||
import { EmailTemplates } from './MasterPage/EmailTemplates';
|
||||
import { LocationManagement } from './MasterPage/LocationManagement';
|
||||
import { ASMDialog } from './MasterPage/ASMDialog';
|
||||
import { ZMDialog } from './MasterPage/ZMDialog';
|
||||
import { ZoneDialog } from './MasterPage/ZoneDialog';
|
||||
import { RegionDialog } from './MasterPage/RegionDialog';
|
||||
import { TemplateDialog } from './MasterPage/TemplateDialog';
|
||||
@ -36,10 +35,12 @@ export const MasterPage: React.FC = () => {
|
||||
const { fetchInitialData } = useMasterData();
|
||||
const {
|
||||
asms, zonalManagerMappings,
|
||||
allStates, allDistricts,
|
||||
allDistricts,
|
||||
users,
|
||||
loading
|
||||
} = useSelector((state: RootState) => state.master);
|
||||
|
||||
|
||||
// Tab & Selection State
|
||||
const [activeTab, setActiveTab] = useState('hierarchy');
|
||||
const [selectedZone, setSelectedZone] = useState('all');
|
||||
@ -63,11 +64,24 @@ export const MasterPage: React.FC = () => {
|
||||
const [selectedASMStates, setSelectedASMStates] = 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)
|
||||
const [editingZoneId, setEditingZoneId] = useState<string | null>(null);
|
||||
const [zoneName, setZoneName] = useState('');
|
||||
const [zoneCode, setZoneCode] = useState('');
|
||||
const [zoneDescription, setZoneDescription] = useState('');
|
||||
const [zonalBusinessHeadId, setZonalBusinessHeadId] = useState('');
|
||||
|
||||
// Form State (Region)
|
||||
const [editingRegionId, setEditingRegionId] = useState<string | null>(null);
|
||||
@ -76,7 +90,7 @@ export const MasterPage: React.FC = () => {
|
||||
const [regionDescription, setRegionDescription] = useState('');
|
||||
const [selectedRegionZone, setSelectedRegionZone] = useState('');
|
||||
const [regionalManagerId, setRegionalManagerId] = useState('');
|
||||
const [selectedRegionStates, setSelectedRegionStates] = useState<string[]>([]);
|
||||
const [selectedRegionDistricts, setSelectedRegionDistricts] = useState<string[]>([]);
|
||||
|
||||
// Form State (Template)
|
||||
const [editingTemplate, setEditingTemplate] = useState<any>(null);
|
||||
@ -104,9 +118,10 @@ export const MasterPage: React.FC = () => {
|
||||
const map: Record<string, string[]> = {};
|
||||
[...asms, ...zonalManagerMappings].forEach(m => {
|
||||
const dists = (m as any).areasManaged || (m as any).districts || [];
|
||||
dists.forEach((d: string) => {
|
||||
if (!map[d]) map[d] = [];
|
||||
if (!map[d].includes(m.name)) map[d].push(m.name);
|
||||
dists.forEach((d: any) => {
|
||||
const id = typeof d === 'string' ? d : d.id;
|
||||
if (!map[id]) map[id] = [];
|
||||
if (!map[id].includes(m.name)) map[id].push(m.name);
|
||||
});
|
||||
});
|
||||
return map;
|
||||
@ -114,12 +129,9 @@ export const MasterPage: React.FC = () => {
|
||||
|
||||
const getDistrictsForSelectedState = useCallback((stateName: string) => {
|
||||
return allDistricts
|
||||
.filter(d => {
|
||||
const sObj = allStates.find(s => s.id === d.stateId);
|
||||
return sObj?.stateName === stateName;
|
||||
})
|
||||
.map(d => d.districtName);
|
||||
}, [allDistricts, allStates]);
|
||||
.filter(d => d.stateName?.toUpperCase() === stateName?.toUpperCase())
|
||||
.map(d => ({ id: d.id, name: d.name }));
|
||||
}, [allDistricts]);
|
||||
|
||||
// Handlers
|
||||
const handleSaveASM = async () => {
|
||||
@ -148,13 +160,51 @@ export const MasterPage: React.FC = () => {
|
||||
setSelectedASMZone(asm.zoneId);
|
||||
setSelectedASMRegion(asm.regionId);
|
||||
setSelectedASMStates(asm.stateNames || []);
|
||||
setSelectedASMDistricts(asm.areasManaged || []);
|
||||
setSelectedASMDistricts(asm.areasManaged?.map((a: any) => a.id) || []);
|
||||
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 () => {
|
||||
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;
|
||||
if (res.success) {
|
||||
toast.success('Zone saved successfully');
|
||||
@ -167,13 +217,13 @@ export const MasterPage: React.FC = () => {
|
||||
const handleSaveRegion = async () => {
|
||||
try {
|
||||
const payload = {
|
||||
id: editingRegionId,
|
||||
zoneId: selectedRegionZone,
|
||||
name: regionName,
|
||||
code: regionCode,
|
||||
description: regionDescription,
|
||||
parentId: selectedRegionZone,
|
||||
managerId: regionalManagerId,
|
||||
states: selectedRegionStates
|
||||
districts: selectedRegionDistricts,
|
||||
status: 'Active'
|
||||
};
|
||||
const res = await masterService.saveRegion(payload) as any;
|
||||
if (res.success) {
|
||||
@ -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">
|
||||
<ZonesOverview selectedZone={selectedZone} onZoneClick={(id) => setSelectedZone(selectedZone === id ? 'all' : id)} />
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-1">
|
||||
<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>
|
||||
<ZoneDetails selectedZone={selectedZone} onAddZone={() => { setEditingZoneId(null); setZoneName(''); setZoneCode(''); setZoneDescription(''); setZonalBusinessHeadId(''); setShowZoneDialog(true); }}
|
||||
onEditZone={(z) => { setEditingZoneId(z.id); setZoneName(z.name); setZoneCode(z.code); setZoneDescription(z.description); setZonalBusinessHeadId(z.zonalBusinessHead?.id || ''); setShowZoneDialog(true); }} />
|
||||
|
||||
<ZMManagement selectedZone={selectedZone} onAddZM={() => toast.info('ZM assignment functionality being updated')}
|
||||
onEditZM={() => toast.info('Edit ZM restricted')} onDeleteZM={() => toast.error('Delete ZM restricted')} />
|
||||
<RegionalManagement selectedZone={selectedZone} onAddRegion={() => { setEditingRegionId(null); setRegionName(''); setRegionCode(''); setSelectedRegionZone(selectedZone === 'all' ? '' : selectedZone); setRegionalManagerId(''); setSelectedRegionDistricts([]); setShowRegionDialog(true); }}
|
||||
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); }}
|
||||
onEditASM={handleEditASM} onDeleteASM={() => toast.error('ASM deletion restricted')} />
|
||||
|
||||
<UserManagementTable userAssignedData={asms} />
|
||||
<UserManagementTable userAssignedData={users.length > 0 ? users : asms} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="roles" className="animate-in fade-in duration-300">
|
||||
@ -319,9 +379,34 @@ export const MasterPage: React.FC = () => {
|
||||
)}
|
||||
|
||||
{/* Main Dialogs */}
|
||||
<ZoneDialog isOpen={showZoneDialog} onOpenChange={setShowZoneDialog} editingZoneId={editingZoneId} zoneName={zoneName} setZoneName={setZoneName} zoneCode={zoneCode} setZoneCode={setZoneCode} zoneDescription={zoneDescription} setZoneDescription={setZoneDescription} onSave={handleSaveZone} />
|
||||
<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} />
|
||||
<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} />
|
||||
<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={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={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} />
|
||||
<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>
|
||||
|
||||
@ -7,7 +7,6 @@ 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 { UserCog, Users } from 'lucide-react';
|
||||
import { RootState } from '../../../store';
|
||||
|
||||
interface ASMDialogProps {
|
||||
@ -35,7 +34,7 @@ interface ASMDialogProps {
|
||||
onSave: () => void;
|
||||
userAssignedData: any[];
|
||||
districtsAssignedToOthers: Record<string, string[]>;
|
||||
getDistrictsForSelectedState: (state: string) => string[];
|
||||
getDistrictsForSelectedState: (state: string) => { id: string; name: string }[];
|
||||
}
|
||||
|
||||
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 filteredASMUsers = userAssignedData.filter(u => {
|
||||
const code = (u.roleCode || '').toLowerCase();
|
||||
const name = (u.role || '').toLowerCase();
|
||||
return code === 'asm' || name.includes('area sales manager');
|
||||
const roles = u.allRoles || [];
|
||||
return roles.some((r: string) => {
|
||||
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 (
|
||||
@ -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">
|
||||
{(() => {
|
||||
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 ? (
|
||||
<div className="space-y-2">
|
||||
@ -124,7 +126,7 @@ export const ASMDialog: React.FC<ASMDialogProps> = ({
|
||||
} else {
|
||||
setSelectedASMStates(selectedASMStates.filter(s => s !== 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">
|
||||
<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">
|
||||
{districts.map((district: string) => (
|
||||
<div key={district}>
|
||||
{districts.map((district: any) => (
|
||||
<div key={district.id}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center space-x-2 py-0.5">
|
||||
<Checkbox
|
||||
id={`asm-district-${district}`}
|
||||
checked={selectedASMDistricts.includes(district)}
|
||||
disabled={!!districtsAssignedToOthers[district]}
|
||||
id={`asm-district-${district.id}`}
|
||||
checked={selectedASMDistricts.includes(district.id)}
|
||||
disabled={!!districtsAssignedToOthers[district.id]}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setSelectedASMDistricts([...selectedASMDistricts, district]);
|
||||
setSelectedASMDistricts([...selectedASMDistricts, district.id]);
|
||||
} else {
|
||||
setSelectedASMDistricts(selectedASMDistricts.filter(d => d !== district));
|
||||
setSelectedASMDistricts(selectedASMDistricts.filter(id => id !== district.id));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`asm-district-${district}`}
|
||||
className={`text-sm flex items-center gap-1.5 ${districtsAssignedToOthers[district] ? "text-slate-400 cursor-not-allowed" : "cursor-pointer text-slate-900"}`}
|
||||
htmlFor={`asm-district-${district.id}`}
|
||||
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>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
{districtsAssignedToOthers[district] && (
|
||||
{districtsAssignedToOthers[district.id] && (
|
||||
<TooltipContent>
|
||||
<p>Already managed by: {districtsAssignedToOthers[district].join(', ')}</p>
|
||||
<p>Already managed by: {districtsAssignedToOthers[district.id].join(', ')}</p>
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
@ -208,7 +210,7 @@ export const ASMDialog: React.FC<ASMDialogProps> = ({
|
||||
setAsmName(selectedUser.name);
|
||||
setAsmCode(selectedUser.asmCode || '');
|
||||
setAsmEmployeeId(selectedUser.employeeId || '');
|
||||
setSelectedASMDistricts(selectedUser.areasManaged || []);
|
||||
setSelectedASMDistricts(selectedUser.areasManaged?.map((a: any) => a.id) || []);
|
||||
setSelectedASMStates(selectedUser.stateNames || []);
|
||||
}
|
||||
}}
|
||||
|
||||
@ -77,8 +77,10 @@ export const ASMManagement: React.FC<ASMManagementProps> = ({
|
||||
<TableCell className="text-sm text-slate-600">{asm.regionName}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{asm.areasManaged.map((area: string, idx: number) => {
|
||||
const otherManagers = (districtsAssignedToOthers[area] || []).filter((name: string) => name !== asm.name);
|
||||
{asm.areasManaged.map((area: any, idx: number) => {
|
||||
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;
|
||||
return (
|
||||
<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" : ""}`}
|
||||
title={isShared ? `Also managed by: ${otherManagers.join(', ')}` : undefined}
|
||||
>
|
||||
{area}
|
||||
{areaName}
|
||||
{isShared && <Users className="w-2.5 h-2.5 ml-1 inline" />}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@ -16,7 +16,7 @@ interface LocationManagementProps {
|
||||
export const LocationManagement: React.FC<LocationManagementProps> = ({
|
||||
onAddLocation, onEditLocation, onDeleteLocation
|
||||
}) => {
|
||||
const { allAreas } = useSelector((state: RootState) => state.master);
|
||||
const { allDistricts } = useSelector((state: RootState) => state.master);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@ -47,35 +47,35 @@ export const LocationManagement: React.FC<LocationManagementProps> = ({
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{allAreas.map((area) => (
|
||||
<TableRow key={area.id}>
|
||||
{allDistricts.map((district) => (
|
||||
<TableRow key={district.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<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>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium text-slate-900">{area.areaName} ({area.city})</TableCell>
|
||||
<TableCell className="text-slate-600 text-sm">{area.district?.districtName || 'N/A'}</TableCell>
|
||||
<TableCell className="text-slate-600 text-sm">{area.pincode}</TableCell>
|
||||
<TableCell className="font-medium text-slate-900">{district.name}</TableCell>
|
||||
<TableCell className="text-slate-600 text-sm">{district.regionName || 'N/A'}</TableCell>
|
||||
<TableCell className="text-slate-600 text-sm">{district.code || 'N/A'}</TableCell>
|
||||
<TableCell>
|
||||
{area.manager ? (
|
||||
<span className="text-slate-700 font-medium">{area.manager.fullName}</span>
|
||||
{district.asmName ? (
|
||||
<span className="text-slate-700 font-medium">{district.asmName}</span>
|
||||
) : (
|
||||
<span className="text-slate-400 italic text-sm">Unassigned</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={area.isActive ? 'default' : 'secondary'} className={area.isActive ? 'bg-emerald-100 text-emerald-700' : ''}>
|
||||
{area.isActive ? 'Active' : 'Inactive'}
|
||||
<Badge variant={district.isActive ? 'default' : 'secondary'} className={district.isActive ? 'bg-emerald-100 text-emerald-700' : ''}>
|
||||
{district.isActive ? 'Active' : 'Inactive'}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<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" />
|
||||
</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" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -7,6 +7,7 @@ import { Input } from '../../ui/input';
|
||||
import { Textarea } from '../../ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
|
||||
import { Checkbox } from '../../ui/checkbox';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip';
|
||||
import { RootState } from '../../../store';
|
||||
|
||||
interface RegionDialogProps {
|
||||
@ -23,8 +24,8 @@ interface RegionDialogProps {
|
||||
setSelectedRegionZone: (id: string) => void;
|
||||
regionalManagerId: string;
|
||||
setRegionalManagerId: (id: string) => void;
|
||||
selectedRegionStates: string[];
|
||||
setSelectedRegionStates: (states: string[]) => void;
|
||||
selectedRegionStates: string[]; // This now contains District IDs
|
||||
setSelectedRegionStates: (districts: string[]) => void;
|
||||
onSave: () => void;
|
||||
userAssignedData: any[]; // Used for RM selection
|
||||
}
|
||||
@ -35,12 +36,28 @@ export const RegionDialog: React.FC<RegionDialogProps> = ({
|
||||
selectedRegionZone, setSelectedRegionZone, regionalManagerId, setRegionalManagerId,
|
||||
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 code = (u.roleCode || '').toLowerCase();
|
||||
const name = (u.role || '').toLowerCase();
|
||||
return code === 'rm' || code === 'rbm' || name.includes('regional manager');
|
||||
const roles = u.allRoles || [];
|
||||
return roles.some((r: string) => {
|
||||
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 (
|
||||
@ -99,29 +116,50 @@ export const RegionDialog: React.FC<RegionDialogProps> = ({
|
||||
</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="grid grid-cols-2 gap-2">
|
||||
{allStates
|
||||
.filter(s => !selectedRegionZone || s.zoneId === selectedRegionZone)
|
||||
.map((state) => (
|
||||
<div key={state.id} className="flex items-center space-x-2">
|
||||
{allDistricts
|
||||
.map((district) => {
|
||||
const assignedRegionName = districtsAssignedToOthers[district.id];
|
||||
const isDisabled = !!assignedRegionName;
|
||||
|
||||
return (
|
||||
<div key={district.id} className="flex items-center space-x-2">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`region-state-${state.id}`}
|
||||
checked={selectedRegionStates.includes(state.stateName)}
|
||||
id={`region-district-${district.id}`}
|
||||
disabled={isDisabled}
|
||||
checked={selectedRegionStates.includes(district.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setSelectedRegionStates([...selectedRegionStates, state.stateName]);
|
||||
setSelectedRegionStates([...selectedRegionStates, district.id]);
|
||||
} else {
|
||||
setSelectedRegionStates(selectedRegionStates.filter(s => s !== state.stateName));
|
||||
setSelectedRegionStates(selectedRegionStates.filter(id => id !== district.id));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label htmlFor={`region-state-${state.id}`} className="text-sm cursor-pointer text-slate-900">
|
||||
{state.stateName}
|
||||
<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>
|
||||
|
||||
@ -43,7 +43,7 @@ export const RegionalManagement: React.FC<RegionalManagementProps> = ({
|
||||
<TableHead>Region Name</TableHead>
|
||||
<TableHead>Zone</TableHead>
|
||||
<TableHead>Regional Manager</TableHead>
|
||||
<TableHead>States</TableHead>
|
||||
<TableHead>Districts</TableHead>
|
||||
<TableHead>Cities</TableHead>
|
||||
<TableHead>Regional Officers</TableHead>
|
||||
<TableHead>ASMs</TableHead>
|
||||
@ -74,14 +74,14 @@ export const RegionalManagement: React.FC<RegionalManagementProps> = ({
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<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">
|
||||
{state}
|
||||
{district.name || district}
|
||||
</Badge>
|
||||
))}
|
||||
{region.states.length > 2 && (
|
||||
{(region.districts || []).length > 2 && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
+{region.states.length - 2}
|
||||
+{(region.districts || []).length - 2}
|
||||
</Badge>
|
||||
)}
|
||||
</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>
|
||||
<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">
|
||||
{district}
|
||||
{district.name}
|
||||
</Badge>
|
||||
))}
|
||||
{zm.districts.length > 3 && (
|
||||
|
||||
@ -72,23 +72,23 @@ export const ZoneDetails: React.FC<ZoneDetailsProps> = ({ selectedZone, onAddZon
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{zone.zbh && zone.zbh.name && (
|
||||
{zone.zonalBusinessHead && zone.zonalBusinessHead.name && (
|
||||
<div className="border-t pt-3">
|
||||
<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="flex items-center gap-2">
|
||||
<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>
|
||||
{zone.zbh.email && (
|
||||
{zone.zonalBusinessHead.email && (
|
||||
<div className="flex items-center gap-2 ml-6">
|
||||
<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>
|
||||
)}
|
||||
{zone.zbh.phone && (
|
||||
{zone.zonalBusinessHead.phone && (
|
||||
<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>
|
||||
|
||||
@ -4,6 +4,7 @@ import { Button } from '../../ui/button';
|
||||
import { Label } from '../../ui/label';
|
||||
import { Input } from '../../ui/input';
|
||||
import { Textarea } from '../../ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
|
||||
|
||||
interface ZoneDialogProps {
|
||||
isOpen: boolean;
|
||||
@ -15,13 +16,26 @@ interface ZoneDialogProps {
|
||||
setZoneCode: (code: string) => void;
|
||||
zoneDescription: string;
|
||||
setZoneDescription: (desc: string) => void;
|
||||
zonalBusinessHeadId: string;
|
||||
setZonalBusinessHeadId: (id: string) => void;
|
||||
userAssignedData: any[];
|
||||
onSave: () => void;
|
||||
}
|
||||
|
||||
export const ZoneDialog: React.FC<ZoneDialogProps> = ({
|
||||
isOpen, onOpenChange, editingZoneId, zoneName, setZoneName,
|
||||
zoneCode, setZoneCode, zoneDescription, setZoneDescription, onSave
|
||||
zoneCode, setZoneCode, zoneDescription, setZoneDescription,
|
||||
zonalBusinessHeadId, setZonalBusinessHeadId, userAssignedData, onSave
|
||||
}) => {
|
||||
const filteredZBHUsers = (userAssignedData || []).filter((u: any) => {
|
||||
const roles = u.allRoles || [];
|
||||
return roles.some((r: string) => {
|
||||
const roleStr = (r || '').toUpperCase();
|
||||
return ['ZBH', 'ZONE BUSINESS HEAD', 'ZONAL 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-md">
|
||||
@ -30,13 +44,14 @@ export const ZoneDialog: React.FC<ZoneDialogProps> = ({
|
||||
<DialogDescription>Configure zonal details and geographical boundaries</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label>Zone Name</Label>
|
||||
<Input
|
||||
placeholder="e.g., North Zone"
|
||||
className="mt-2 text-slate-900"
|
||||
value={zoneName}
|
||||
onChange={(e) => setZoneName(e.target.value)}
|
||||
onChange={(e: any) => setZoneName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -45,9 +60,28 @@ export const ZoneDialog: React.FC<ZoneDialogProps> = ({
|
||||
placeholder="e.g., NZ"
|
||||
className="mt-2 text-slate-900"
|
||||
value={zoneCode}
|
||||
onChange={(e) => setZoneCode(e.target.value)}
|
||||
onChange={(e: any) => setZoneCode(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label>Zonal Business Head</Label>
|
||||
<Select value={zonalBusinessHeadId} onValueChange={setZonalBusinessHeadId}>
|
||||
<SelectTrigger className="mt-2 w-full text-slate-900">
|
||||
<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
|
||||
@ -55,7 +89,7 @@ export const ZoneDialog: React.FC<ZoneDialogProps> = ({
|
||||
className="mt-2 text-slate-900"
|
||||
rows={3}
|
||||
value={zoneDescription}
|
||||
onChange={(e) => setZoneDescription(e.target.value)}
|
||||
onChange={(e: any) => setZoneDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-3 pt-4">
|
||||
|
||||
@ -11,15 +11,11 @@ interface ZonesOverviewProps {
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||
{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 (
|
||||
<Card
|
||||
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 gap-2">
|
||||
<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>
|
||||
<Badge variant="outline" className="text-xs">{zone.code}</Badge>
|
||||
</div>
|
||||
@ -45,15 +41,15 @@ export const ZonesOverview: React.FC<ZonesOverviewProps> = ({ selectedZone, onZo
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<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 className="flex items-center justify-between">
|
||||
<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 className="flex items-center justify-between">
|
||||
<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 className="flex items-center justify-between">
|
||||
<span className="text-slate-500">ZMs</span>
|
||||
|
||||
@ -11,19 +11,27 @@ export const getAncestorByType = (node: any, type: string): any => {
|
||||
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 (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');
|
||||
if (regAncestor?.id) return regAncestor.id;
|
||||
if (loc.type === 'region') return loc.id;
|
||||
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) {
|
||||
const dName = loc.districtName || loc.name;
|
||||
const dObj = parsedDistricts.find((d: any) => d.districtName === dName || d.id === loc.id);
|
||||
if (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;
|
||||
|
||||
@ -4,7 +4,6 @@ import { masterService } from '../services/master.service';
|
||||
import {
|
||||
setMasterData, setLoading, setError
|
||||
} from '../store/slices/masterSlice';
|
||||
import { getAncestorByType, getRegionId, getZoneId } from './useLocationHelpers';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export const useMasterData = () => {
|
||||
@ -43,239 +42,117 @@ export const useMasterData = () => {
|
||||
const bodyAreas = getBody(areasRes);
|
||||
const bodySla = getBody(slaRes);
|
||||
|
||||
const users = bodyUsers?.users || bodyUsers?.data || [];
|
||||
const usersByLocation: Record<string, any[]> = {};
|
||||
users.forEach((u: any) => {
|
||||
const locId = u.location?.id || u.locationId;
|
||||
if (locId) {
|
||||
if (!usersByLocation[locId]) usersByLocation[locId] = [];
|
||||
usersByLocation[locId].push(u);
|
||||
}
|
||||
});
|
||||
|
||||
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 users = (bodyUsers?.users || bodyUsers?.data || []).map((u: any) => ({
|
||||
...u,
|
||||
name: u.fullName || u.name,
|
||||
role: u.role?.roleName || 'System User',
|
||||
zone: u.allZones?.join(', ') || 'Global',
|
||||
region: u.allRegions?.join(', ') || 'Unassigned',
|
||||
status: u.isActive !== false ? 'Active' : 'Inactive'
|
||||
}));
|
||||
|
||||
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) => ({
|
||||
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 hierarchyZmIds = zoneZmIds[z.id] || [];
|
||||
const hAsmIds = zoneAsmIds[z.id] || [];
|
||||
const hRmIds = zoneRmIds[z.id] || [];
|
||||
|
||||
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);
|
||||
const zones = (bodyZones?.zones || bodyZones?.data || []).map((z: any) => {
|
||||
const zoneName = (z.name || z.zoneName || '').toUpperCase();
|
||||
const zoneUsers = users.filter((u: any) => u.allZones?.includes(zoneName));
|
||||
const zoneZmUsers = zoneUsers.filter((u: any) => u.allRoles?.some((r: string) => (r === 'ZM' || r.includes('ZONAL MANAGER')) && !r.includes('HEAD')));
|
||||
|
||||
return {
|
||||
id: z.id, name: 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',
|
||||
states: Array.isArray(z.children) ? z.children.filter((child: any) => child.type === 'state').map((c: any) => c.name) : [],
|
||||
zmCount: hierarchyZmIds.length,
|
||||
asmCount: hAsmIds.length,
|
||||
regionalOfficerCount: hRmIds.length,
|
||||
zbh: { name: zbhUser.fullName || 'Not Assigned', email: zbhUser.email || '', phone: zbhUser.mobileNumber || '' },
|
||||
zonalManagers: zoneZmUsers.map((m: any) => ({ name: m.fullName || 'Unknown', email: m.email || '', phone: m.mobileNumber || '', districts: [] }))
|
||||
regionCount: z.regionCount || 0,
|
||||
asmCount: z.asmCount || 0,
|
||||
regionalOfficerCount: z.regionalOfficerCount || 0,
|
||||
zmCount: z.zmCount || 0,
|
||||
states: z.states || [],
|
||||
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 hRmIds = regionRmIds[r.id] || [];
|
||||
const hAsmIds = regionAsmIds[r.id] || [];
|
||||
const rmUser = users.find((u: any) => hRmIds.includes(u.id)) || ({} as any);
|
||||
return {
|
||||
...r, regionalOfficerCount: hRmIds.length, asmCount: hAsmIds.length,
|
||||
regionalManager: rmUser.id ? { id: rmUser.id, name: rmUser.fullName, email: rmUser.email, phone: rmUser.mobileNumber } : undefined
|
||||
};
|
||||
});
|
||||
const regionalOffices = (bodyRegions?.regions || bodyRegions?.data || []).map((r: any) => ({
|
||||
id: r.id,
|
||||
code: r.code || (r.name ? r.name.substring(0, 3).toUpperCase() : 'REG'),
|
||||
name: r.name || r.regionName,
|
||||
zoneId: r.zoneId,
|
||||
zoneName: r.zoneName || 'Unknown',
|
||||
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 c = (u.roleCode || u.role?.roleCode || '').toUpperCase();
|
||||
const n = (u.role?.roleName || '').toUpperCase();
|
||||
return isAsmRole(c) || isAsmRole(n);
|
||||
}).map((u: any) => {
|
||||
const zid = getZoneId(u.location, regionIdToZoneId, stateToRegionId, allDistricts, allStates);
|
||||
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 || '') : '';
|
||||
const asms = users.filter((u: any) => u.allRoles?.some((r: string) => r === 'ASM' || r === 'AREA SALES MANAGER'))
|
||||
.map((u: any) => {
|
||||
const asmT = u.territoryProfile?.find((t: any) => t.roleCode === 'ASM') || {};
|
||||
const asmCode = asmT.managerCode || u.asmCode || '';
|
||||
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 stateNames = Array.from(new Set(u.territoryProfile?.filter((t: any) => t.roleCode === 'ASM' && t.state).map((t: any) => t.state).filter(Boolean))) as string[];
|
||||
|
||||
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'
|
||||
id: u.id, name: u.fullName, code: u.employeeId || 'N/A',
|
||||
asmCode: asmCode, employeeId: u.employeeId || '',
|
||||
email: u.email, phone: u.mobileNumber,
|
||||
zoneId: asmT.zoneId,
|
||||
regionId: asmT.regionId,
|
||||
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) => {
|
||||
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);
|
||||
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 || 'N/A',
|
||||
zoneId: zid || '',
|
||||
zoneName: (zid ? rawZones.find((z: any) => z.id === zid)?.name : 'Not Assigned') || 'Not Assigned',
|
||||
regionId: rid || '',
|
||||
regionName: (rid ? parsedRegions.find((r: any) => r.id === rid)?.name : 'Not Assigned') || 'Not Assigned',
|
||||
districts: u.districts || [], status: (u.isActive !== false) ? 'Active' : 'Inactive'
|
||||
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 allStates = (bodyStates?.states || bodyStates?.data || []).map((s: any) => ({
|
||||
...s, stateName: s.name
|
||||
}));
|
||||
|
||||
const allDistricts = (bodyDistricts?.districts || bodyDistricts?.data || []).map((d: any) => ({
|
||||
...d,
|
||||
districtName: d.name,
|
||||
stateId: d.stateId // Now populated by backend's deep resolution
|
||||
}));
|
||||
|
||||
const allAreas = (bodyAreas.areas || bodyAreas.data || []).map((a: any) => {
|
||||
const dId = getAncestorByType(a, 'district')?.id || a.districtId;
|
||||
const dObj = allDistricts.find((d: any) => d.id === dId);
|
||||
const sObj = allStates.find((s: any) => s.id === dObj?.stateId);
|
||||
return {
|
||||
...a, areaName: a.name, districtId: dId,
|
||||
district: { districtName: dObj?.districtName || 'Unknown', stateId: dObj?.stateId, state: { stateName: sObj?.stateName || 'Unknown' } }
|
||||
};
|
||||
});
|
||||
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) => ({
|
||||
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 || [],
|
||||
emailTemplates: bodyEmail?.data || [],
|
||||
slaConfigs,
|
||||
users,
|
||||
loading: false
|
||||
}));
|
||||
|
||||
|
||||
@ -3,14 +3,14 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
export interface Zone {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
code: string;
|
||||
regionCount: number;
|
||||
districtCount: number;
|
||||
states: string[];
|
||||
zmCount: number;
|
||||
asmCount?: number;
|
||||
regionalOfficerCount?: number;
|
||||
zbh: { name: string; email: string; phone: string };
|
||||
zonalManagers: any[];
|
||||
zonalBusinessHead: { id: string; name: string; email: string; phone: string } | null;
|
||||
zonalManagers: { id: string; name: string; email: string; phone: string; districts: string[] }[];
|
||||
}
|
||||
|
||||
export interface Region {
|
||||
@ -19,12 +19,11 @@ export interface Region {
|
||||
name: string;
|
||||
zoneId: string;
|
||||
zoneName: string;
|
||||
states: string[];
|
||||
cities: string[];
|
||||
districts: { id: string; name: string }[];
|
||||
status: string;
|
||||
regionalOfficerCount: number;
|
||||
asmCount: number;
|
||||
regionalManager?: { id: string; name: string; email: string; phone: string };
|
||||
regionalManager?: { id: string; fullName: string; email: string } | null;
|
||||
}
|
||||
|
||||
export interface ASM {
|
||||
@ -65,12 +64,25 @@ export interface MasterState {
|
||||
asms: ASM[];
|
||||
zonalManagerMappings: ZonalManagerMapping[];
|
||||
roles: any[];
|
||||
allStates: any[];
|
||||
allDistricts: any[];
|
||||
allStates: { id: string, name: string, zone?: { name: string } }[];
|
||||
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[];
|
||||
availablePermissions: any[];
|
||||
emailTemplates: any[];
|
||||
slaConfigs: any[];
|
||||
users: any[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
@ -87,6 +99,7 @@ const initialState: MasterState = {
|
||||
availablePermissions: [],
|
||||
emailTemplates: [],
|
||||
slaConfigs: [],
|
||||
users: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
@ -110,6 +123,9 @@ const masterSlice = createSlice({
|
||||
setZonalManagerMappings: (state, action: PayloadAction<ZonalManagerMapping[]>) => {
|
||||
state.zonalManagerMappings = action.payload;
|
||||
},
|
||||
setUsers: (state, action: PayloadAction<any[]>) => {
|
||||
state.users = action.payload;
|
||||
},
|
||||
setLoading: (state, action: PayloadAction<boolean>) => {
|
||||
state.loading = action.payload;
|
||||
},
|
||||
@ -121,7 +137,7 @@ const masterSlice = createSlice({
|
||||
|
||||
export const {
|
||||
setMasterData, setZones, setRegionalOffices, setAsms,
|
||||
setZonalManagerMappings, setLoading, setError
|
||||
setZonalManagerMappings, setUsers, setLoading, setError
|
||||
} = masterSlice.actions;
|
||||
|
||||
export default masterSlice.reducer;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user