Skip to content

Commit 271f76e

Browse files
authored
Merge pull request #2641 from Rushikesh-Sonawane99/release-1.15.0
Issue #PS-000 chore:Fixed center creation issue with incomplete catchment area
2 parents 64db7c9 + 12c4789 commit 271f76e

File tree

5 files changed

+118
-32
lines changed

5 files changed

+118
-32
lines changed

apps/admin-app-repo/src/pages/centers.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ const Centers = () => {
134134
}
135135
});
136136
alterSchema.required = requiredArray;
137+
137138
//add max selection custom
138139
if (alterSchema?.properties?.state) {
139140
alterSchema.properties.state.maxSelection = 1;
@@ -354,10 +355,16 @@ const Centers = () => {
354355
key: 'image',
355356
label: 'Images',
356357
render: (row: any) => {
357-
console.log('Row in image column:', row);
358+
const rawImage = row?.image;
359+
const images: string[] = Array.isArray(rawImage)
360+
? rawImage
361+
: typeof rawImage === 'string' && rawImage
362+
? [rawImage]
363+
: [];
364+
358365
return (
359366
<div style={{ display: 'flex', gap: '8px' }}>
360-
{row?.image?.map((imgUrl: string, index: number) => (
367+
{images.map((imgUrl: string, index: number) => (
361368
<img
362369
key={index}
363370
src={imgUrl}

libs/shared-lib-v2/src/DynamicForm/components/RJSFWidget/CatchmentAreaWidget.tsx

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,12 @@ const CatchmentAreaWidget = ({
134134
options,
135135
}: WidgetProps) => {
136136
const { t } = useTranslation();
137+
const sanitizeSelectedStates = (input: any): SelectedState[] =>
138+
Array.isArray(input)
139+
? (input.filter((s: any) => s && typeof s === 'object') as SelectedState[])
140+
: [];
137141
const [selectedStates, setSelectedStates] = useState<SelectedState[]>(
138-
value || []
142+
sanitizeSelectedStates(value)
139143
);
140144
const [stateSelectValue, setStateSelectValue] = useState('');
141145
const [districtSelectValues, setDistrictSelectValues] = useState<{
@@ -156,8 +160,10 @@ const CatchmentAreaWidget = ({
156160
}>({});
157161

158162
// Get available states (not yet selected)
163+
const normalizedSelectedStates = sanitizeSelectedStates(selectedStates);
159164
const availableStates = states.filter(
160-
(state) => !selectedStates.some((selected) => selected.stateId === state.id)
165+
(state) =>
166+
!normalizedSelectedStates.some((selected) => selected.stateId === state.id)
161167
);
162168

163169
// Fetch states from API
@@ -224,7 +230,7 @@ const CatchmentAreaWidget = ({
224230
// Handle value prop changes (e.g., form reset, external updates)
225231
useEffect(() => {
226232
if (value && JSON.stringify(value) !== JSON.stringify(selectedStates)) {
227-
setSelectedStates(value);
233+
setSelectedStates(sanitizeSelectedStates(value));
228234
}
229235
}, [value]);
230236

@@ -482,25 +488,46 @@ const CatchmentAreaWidget = ({
482488

483489
// Check if any states are selected
484490
const hasSelectedStates = () => {
485-
return selectedStates.length > 0;
491+
return (
492+
normalizedSelectedStates.length > 0 &&
493+
normalizedSelectedStates.some((s: any) => s?.stateId || s?.stateName)
494+
);
486495
};
487496

497+
const isCatchmentComplete = () => {
498+
if (!hasSelectedStates()) return false;
499+
for (const state of normalizedSelectedStates as any[]) {
500+
const districtsArr = Array.isArray(state?.districts) ? state.districts : [];
501+
if (districtsArr.length === 0) return false;
502+
for (const district of districtsArr) {
503+
const blocksArr = Array.isArray(district?.blocks) ? district.blocks : [];
504+
if (blocksArr.length === 0) return false;
505+
}
506+
}
507+
return true;
508+
};
509+
510+
const showIncompleteCatchmentMessage = hasSelectedStates() && !isCatchmentComplete();
511+
488512
// Get a string representation of selected states for the hidden input
489513
const getSelectedStatesString = () => {
490-
return selectedStates.map((state) => state.stateName).join(', ');
514+
return normalizedSelectedStates
515+
.map((state: any) => state?.stateName)
516+
.filter(Boolean)
517+
.join(', ');
491518
};
492519

493520
return (
494-
<Box sx={{ mb: 3 }}>
495-
{rawErrors.length > 0 && (
496-
<Typography
497-
color="error"
498-
variant="caption"
499-
sx={{ display: 'block', mb: 1 }}
500-
>
501-
{rawErrors[0]}
502-
</Typography>
503-
)}
521+
<Box
522+
id={id}
523+
tabIndex={-1}
524+
className={
525+
rawErrors.length > 0 || showIncompleteCatchmentMessage
526+
? 'field-error'
527+
: undefined
528+
}
529+
sx={{ mb: 3, outline: 'none' }}
530+
>
504531
{/* State Selection */}
505532
{loadingStates ? (
506533
<Box
@@ -549,17 +576,26 @@ const CatchmentAreaWidget = ({
549576
</FormControl>
550577
)
551578
)}
579+
{(rawErrors.length > 0 || showIncompleteCatchmentMessage) && (
580+
<Typography
581+
variant="caption"
582+
sx={{ display: 'block', mt: -1, mb: 1, color: '#000' }}
583+
>
584+
Select at least 1 district and 1 block under each selected state. Also
585+
select at least 1 block under each selected district.
586+
</Typography>
587+
)}
552588
{/* Hidden text input to force native validation */}
553589
<input
554-
value={hasSelectedStates() ? getSelectedStatesString() : ''}
590+
value={isCatchmentComplete() ? getSelectedStatesString() : ''}
555591
required={required}
556592
onChange={() => {}}
557593
tabIndex={-1}
558594
style={{
559595
height: 1,
560596
padding: 0,
561597
border: 0,
562-
...(hasSelectedStates() && { visibility: 'hidden' }),
598+
...(isCatchmentComplete() && { visibility: 'hidden' }),
563599
}}
564600
aria-hidden="true"
565601
/>

libs/shared-lib-v2/src/MapUser/EmailSearchUser.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ const EmailSearchUser: React.FC<EmailSearchUserProps> = ({
358358
{loading ? (
359359
<CircularProgress size={24} color="inherit" />
360360
) : isUserLoaded ? (
361-
t('COMMON.CHANGE_USER')
361+
t('COMMON.RESET_USER')
362362
) : (
363363
t('COMMON.FETCH_USER')
364364
)}

libs/shared-lib-v2/src/MapUser/WorkingVillageAssignmentWidget.tsx

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ const WorkingVillageAssignmentWidget: React.FC<WorkingVillageAssignmentWidgetPro
8585
const themeColor = '#FDBE16';
8686
const themeColorLight = 'rgba(253, 190, 22, 0.1)'; // 10% opacity
8787
const themeColorLighter = 'rgba(253, 190, 22, 0.05)'; // 5% opacity
88+
const __villagesLoadSeqRef = React.useRef(0);
8889

8990
// Local state management for geography filters and center selection
9091
const [selectedState, setSelectedState] = useState<string>(''); // Selected state ID
@@ -151,11 +152,19 @@ const WorkingVillageAssignmentWidget: React.FC<WorkingVillageAssignmentWidgetPro
151152
stateName: string;
152153
}>>([]);
153154

154-
// Store center's CATCHMENT_AREA fetched via API in reassign mode
155-
const [reassignCenterCatchmentArea, setReassignCenterCatchmentArea] = useState<any>(null);
155+
// Store center's CATCHMENT_AREA fetched via API in reassign mode (keyed by centerId to avoid stale usage during center switches)
156+
const [reassignCenterCatchmentArea, setReassignCenterCatchmentArea] = useState<{
157+
centerId: string;
158+
selectedValues: any[] | null;
159+
} | null>(null);
156160

157161
// Load villages for all catchment blocks
158162
const loadVillagesForBlocks = useCallback(async () => {
163+
const __seq = ++__villagesLoadSeqRef.current;
164+
const __centerAtStart = selectedCenter;
165+
const __blockIdsAtStart = Array.isArray(catchmentBlocks)
166+
? catchmentBlocks.map((b) => String(b.id)).join(',')
167+
: '';
159168
if (!selectedCenter || catchmentBlocks.length === 0) {
160169
setVillagesByBlock({});
161170
setVillagesDisplayLimit({});
@@ -278,6 +287,18 @@ const WorkingVillageAssignmentWidget: React.FC<WorkingVillageAssignmentWidgetPro
278287
}
279288
}
280289

290+
// Latest-wins guard: prevent older in-flight loads from overwriting the newest center/blocks state
291+
// (this can happen during rapid center changes in reassign flow)
292+
const __isLatest = __seq === __villagesLoadSeqRef.current;
293+
const __sameCenter = selectedCenter === __centerAtStart;
294+
const __sameBlocks =
295+
(Array.isArray(catchmentBlocks)
296+
? catchmentBlocks.map((b) => String(b.id)).join(',')
297+
: '') === __blockIdsAtStart;
298+
if (!__isLatest || !__sameCenter || !__sameBlocks) {
299+
return;
300+
}
301+
281302
setVillagesByBlock(villagesData);
282303
setVillagesDisplayLimit(displayLimits);
283304

@@ -873,7 +894,8 @@ const WorkingVillageAssignmentWidget: React.FC<WorkingVillageAssignmentWidgetPro
873894
return;
874895
}
875896

876-
setReassignCenterCatchmentArea(null); // Clear previous center's catchment while loading
897+
// Clear previous center's catchment while loading, but keep centerId so extract logic can't accidentally use stale data
898+
setReassignCenterCatchmentArea({ centerId: selectedCenter, selectedValues: null });
877899
setSelectedVillages(new Set()); // Clear village selection when center changes so new center's list is fresh
878900
let isMounted = true;
879901
const centerIdToFetch = selectedCenter;
@@ -916,17 +938,29 @@ const WorkingVillageAssignmentWidget: React.FC<WorkingVillageAssignmentWidgetPro
916938
);
917939

918940
if (catchmentAreaField && catchmentAreaField.selectedValues) {
919-
setReassignCenterCatchmentArea(catchmentAreaField.selectedValues);
941+
setReassignCenterCatchmentArea({
942+
centerId: centerIdToFetch,
943+
selectedValues: catchmentAreaField.selectedValues,
944+
});
920945
} else {
921-
setReassignCenterCatchmentArea(null);
946+
setReassignCenterCatchmentArea({
947+
centerId: centerIdToFetch,
948+
selectedValues: null,
949+
});
922950
}
923951
} else {
924-
setReassignCenterCatchmentArea(null);
952+
setReassignCenterCatchmentArea({
953+
centerId: centerIdToFetch,
954+
selectedValues: null,
955+
});
925956
}
926957
} catch (error) {
927958
console.error('Error fetching center CATCHMENT_AREA:', error);
928959
if (isMounted) {
929-
setReassignCenterCatchmentArea(null);
960+
setReassignCenterCatchmentArea({
961+
centerId: centerIdToFetch,
962+
selectedValues: null,
963+
});
930964
}
931965
}
932966
};
@@ -941,7 +975,14 @@ const WorkingVillageAssignmentWidget: React.FC<WorkingVillageAssignmentWidgetPro
941975
// Extract catchment blocks from selected center's CATCHMENT_AREA or fetched CATCHMENT_AREA (in reassign mode)
942976
useEffect(() => {
943977
// Priority 1: In reassign mode, use fetched CATCHMENT_AREA for the currently selected center (fetched when selectedCenter changes)
944-
if (isForReassign && selectedCenter && reassignCenterCatchmentArea) {
978+
if (
979+
isForReassign &&
980+
selectedCenter &&
981+
reassignCenterCatchmentArea &&
982+
reassignCenterCatchmentArea.centerId === selectedCenter &&
983+
Array.isArray(reassignCenterCatchmentArea.selectedValues) &&
984+
reassignCenterCatchmentArea.selectedValues.length > 0
985+
) {
945986
// Extract blocks from fetched center's CATCHMENT_AREA
946987
const extractedBlocks: Array<{
947988
id: string | number;
@@ -952,8 +993,9 @@ const WorkingVillageAssignmentWidget: React.FC<WorkingVillageAssignmentWidgetPro
952993
stateName: string;
953994
}> = [];
954995

955-
if (Array.isArray(reassignCenterCatchmentArea) && reassignCenterCatchmentArea.length > 0) {
956-
reassignCenterCatchmentArea.forEach((stateData: any) => {
996+
const __selectedValues = reassignCenterCatchmentArea.selectedValues;
997+
if (Array.isArray(__selectedValues) && __selectedValues.length > 0) {
998+
__selectedValues.forEach((stateData: any) => {
957999
const stateId = stateData.stateId;
9581000
const stateName = stateData.stateName || '';
9591001

@@ -1847,7 +1889,7 @@ const WorkingVillageAssignmentWidget: React.FC<WorkingVillageAssignmentWidgetPro
18471889
return (
18481890
<Accordion
18491891
key={block.id}
1850-
defaultExpanded
1892+
defaultExpanded={false}
18511893
sx={{
18521894
border: '1px solid',
18531895
borderColor: 'divider',

libs/shared-lib-v2/src/lib/context/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@
161161
},
162162
"CHANGE_USER": "Change User",
163163
"FETCH_USER": "Fetch User",
164-
"SEARCH_USER_BY_EMAIL_DESCRIPTION": "Search for an existing user by email and allocate working village to them"
164+
"SEARCH_USER_BY_EMAIL_DESCRIPTION": "Search for an existing user by email and allocate working village to them",
165+
"RESET_USER": "Reset User"
165166
},
166167
"SWITCH_ACCOUNT": {
167168
"TITLE": "Select Account",

0 commit comments

Comments
 (0)