Dealer_Onboard_Frontend/src/features/master/components/RegionDialog.tsx

313 lines
14 KiB
TypeScript

import React from 'react';
import { useSelector } from 'react-redux';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Checkbox } from '@/components/ui/checkbox';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { RootState } from '@/store';
interface RegionDialogProps {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
editingRegionId: string | null;
regionName: string;
setRegionName: (name: string) => void;
regionDescription: string;
setRegionDescription: (desc: string) => void;
selectedRegionZone: string;
setSelectedRegionZone: (id: string) => void;
regionalManagerId: string;
setRegionalManagerId: (id: string) => void;
selectedRegionStates: string[]; // District IDs selected
setSelectedRegionStates: (districts: string[]) => void;
onSave: () => void;
userAssignedData: any[];
}
export const RegionDialog: React.FC<RegionDialogProps> = ({
isOpen, onOpenChange, editingRegionId, regionName, setRegionName,
regionDescription, setRegionDescription,
selectedRegionZone, setSelectedRegionZone, regionalManagerId, setRegionalManagerId,
selectedRegionStates, setSelectedRegionStates, onSave, userAssignedData
}) => {
const { zones, allStates, allDistricts, regionalOffices } = useSelector((state: RootState) => state.master);
// Internal: which states are checked (for filtering districts)
const [selectedStateIds, setSelectedStateIds] = React.useState<string[]>([]);
// --- Build conflict map: districtId -> region name (for districts in OTHER regions) ---
const assignedToOtherRegion = React.useMemo(() => {
const map: Record<string, string> = {};
(regionalOffices || []).forEach((r: any) => {
if (r.id !== editingRegionId) {
(r.districts || []).forEach((d: any) => {
map[d.id] = r.name;
});
}
});
return map;
}, [regionalOffices, editingRegionId]);
// --- States visible for the selected zone (only states that have at least 1 district in this zone OR unassigned) ---
const statesForZone = React.useMemo(() => {
if (!selectedRegionZone) return allStates;
const stateIdsWithZoneDistricts = new Set(
allDistricts
.filter(d =>
d.zoneId === selectedRegionZone ||
!d.zoneId ||
selectedRegionStates.includes(d.id)
)
.map(d => d.stateId)
.filter(Boolean)
);
return allStates.filter((s: any) =>
s.zoneId === selectedRegionZone ||
stateIdsWithZoneDistricts.has(s.id) ||
!s.zoneId
);
}, [allStates, allDistricts, selectedRegionZone, selectedRegionStates]);
// --- Districts shown = only those in selected states + (in this zone or unassigned) ---
const availableDistricts = React.useMemo(() => {
if (selectedStateIds.length === 0) return [];
return allDistricts.filter(d =>
selectedStateIds.includes(d.stateId as string) &&
(!d.zoneId || d.zoneId === selectedRegionZone || d.regionId === editingRegionId)
);
}, [allDistricts, selectedStateIds, selectedRegionZone, editingRegionId]);
// --- Group districts by state for rendering ---
const districtsByState = React.useMemo(() => {
const map: Record<string, { stateName: string; districts: typeof allDistricts }> = {};
availableDistricts.forEach(d => {
const state = allStates.find((s: any) => s.id === d.stateId);
const stateName = state?.name || (d.stateId as string);
if (!map[d.stateId as string]) map[d.stateId as string] = { stateName, districts: [] };
map[d.stateId as string].districts.push(d);
});
return Object.values(map);
}, [availableDistricts, allStates]);
// --- Prefill selected states and manager when dialog opens in EDIT mode ---
React.useEffect(() => {
if (!isOpen) {
setSelectedStateIds([]);
return;
}
// Derive states from already-selected districts
if (selectedRegionStates.length > 0) {
const derived = Array.from(new Set(
allDistricts
.filter(d => selectedRegionStates.includes(d.id))
.map(d => d.stateId)
.filter(Boolean)
)) as string[];
setSelectedStateIds(derived);
}
}, [isOpen]); // Reduced dependencies to avoid re-triggering during active selection
// --- When zone changes, reset state & district selections ---
const handleZoneChange = (zoneId: string) => {
setSelectedRegionZone(zoneId);
setSelectedStateIds([]);
setSelectedRegionStates([]);
};
// --- Toggle state checkbox ---
const handleStateToggle = (stateId: string, checked: boolean) => {
if (checked) {
setSelectedStateIds(prev => [...prev, stateId]);
} else {
setSelectedStateIds(prev => prev.filter(id => id !== stateId));
// Remove districts of this state from selection
const districtIdsInState = allDistricts.filter(d => d.stateId === stateId).map(d => d.id);
setSelectedRegionStates(selectedRegionStates.filter(id => !districtIdsInState.includes(id)));
}
};
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingRegionId ? 'Edit' : 'Add'} Regional Office</DialogTitle>
<DialogDescription>Configure regional office details and coverage area</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* Region Name */}
<div>
<Label>Region Name</Label>
<Input placeholder="e.g., Delhi NCR Region" className="mt-2 text-slate-900" value={regionName} onChange={(e) => setRegionName(e.target.value)} />
</div>
{/* Zone */}
<div>
<Label>Zone</Label>
<Select value={selectedRegionZone} onValueChange={handleZoneChange}>
<SelectTrigger className="mt-2 text-slate-900">
<SelectValue placeholder="Select zone" />
</SelectTrigger>
<SelectContent>
{zones.map((zone) => (
<SelectItem key={zone.id} value={zone.id}>{zone.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Regional Manager — all users dropdown */}
<div>
<Label>Regional Manager</Label>
<Select value={regionalManagerId} onValueChange={setRegionalManagerId}>
<SelectTrigger className="mt-2 w-full text-slate-900">
<SelectValue placeholder="Select from available users" />
</SelectTrigger>
<SelectContent className="max-h-60">
{userAssignedData.map((user) => (
<SelectItem key={user.id} value={user.id}>
{user.name || user.fullName}
{user.email ? `${user.email}` : ''}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Description */}
<div>
<Label>Description</Label>
<Textarea placeholder="Describe the region..." className="mt-2 text-slate-900" rows={2} value={regionDescription} onChange={(e) => setRegionDescription(e.target.value)} />
</div>
{/* States Covered — scoped to selected zone */}
<div>
<Label>States Covered</Label>
{!selectedRegionZone && (
<p className="text-xs text-re-red mt-1">Select a zone first to see available states</p>
)}
<div className="mt-2 border rounded-lg p-3 max-h-40 overflow-y-auto bg-slate-50">
{statesForZone.length === 0 ? (
<p className="text-xs text-slate-400 italic">
{selectedRegionZone ? 'No states with available districts in this zone' : 'Select a zone to load states'}
</p>
) : (
<div className="space-y-2">
{statesForZone.map((state: any) => (
<div key={state.id} className="flex items-center space-x-2">
<Checkbox
id={`region-state-${state.id}`}
checked={selectedStateIds.includes(state.id)}
disabled={!selectedRegionZone}
onCheckedChange={(checked) => handleStateToggle(state.id, !!checked)}
/>
<label
htmlFor={`region-state-${state.id}`}
className={`text-sm cursor-pointer ${!selectedRegionZone ? 'text-slate-400' : 'text-slate-900'}`}
>
{state.name}
</label>
</div>
))}
</div>
)}
</div>
<p className="text-xs text-slate-500 mt-1">
{selectedStateIds.length} {selectedStateIds.length === 1 ? 'state' : 'states'} selected
</p>
</div>
{/* Districts Covered — filtered by zone + selected states */}
<div>
<Label>Districts Covered</Label>
<div className="mt-2 border rounded-lg p-3 max-h-56 overflow-y-auto bg-slate-50">
{selectedStateIds.length === 0 ? (
<p className="text-xs text-slate-400 italic">Select one or more states above to see districts</p>
) : districtsByState.length === 0 ? (
<p className="text-xs text-slate-400 italic">No available districts in the selected states for this zone</p>
) : (
<TooltipProvider>
{districtsByState.map(({ stateName, districts }) => (
<div key={stateName} className="mb-4 last:mb-0">
<h4 className="text-xs font-semibold text-re-red-hover uppercase tracking-wide mb-2 pb-1 border-b border-slate-200">
{stateName}
</h4>
<div className="space-y-2 ml-1">
{districts.map((district) => {
const conflictRegion = assignedToOtherRegion[district.id];
const inDifferentZone = district.zoneId && district.zoneId !== selectedRegionZone && district.regionId !== editingRegionId;
const isDisabled = !!(conflictRegion || inDifferentZone);
const tooltipText = conflictRegion
? `Already assigned to region: ${conflictRegion}`
: inDifferentZone
? `Belongs to a different zone`
: '';
return (
<div key={district.id} className="flex items-center space-x-2">
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center space-x-2 w-full">
<Checkbox
id={`region-district-${district.id}`}
disabled={isDisabled}
checked={selectedRegionStates.includes(district.id)}
onCheckedChange={(checked) => {
if (checked) {
setSelectedRegionStates([...selectedRegionStates, district.id]);
} else {
setSelectedRegionStates(selectedRegionStates.filter(id => id !== district.id));
}
}}
/>
<label
htmlFor={`region-district-${district.id}`}
className={`text-sm flex-1 ${isDisabled ? 'text-slate-400 cursor-not-allowed line-through' : 'text-slate-900 cursor-pointer'}`}
>
{district.name}
{conflictRegion && (
<span className="ml-2 text-xs text-red-400 font-normal no-underline" style={{ textDecoration: 'none' }}>
(in {conflictRegion})
</span>
)}
</label>
</div>
</TooltipTrigger>
{isDisabled && tooltipText && (
<TooltipContent>
<p className="text-xs">{tooltipText}</p>
</TooltipContent>
)}
</Tooltip>
</div>
);
})}
</div>
</div>
))}
</TooltipProvider>
)}
</div>
<p className="text-xs text-slate-500 mt-1">
{selectedRegionStates.length} {selectedRegionStates.length === 1 ? 'district' : 'districts'} selected
</p>
</div>
{/* Actions */}
<div className="flex gap-3 pt-4">
<Button variant="outline" className="flex-1" onClick={() => onOpenChange(false)}>Cancel</Button>
<Button className="flex-1 bg-re-red hover:bg-re-red-hover" onClick={onSave}>Save Regional Office</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
};