Change1:
Developing new API which is completely driven by address hierarchy and uses AddressNode
psql table to get required nodes data
...
Point to consider for better efficiency of this api(lesser load on db):
...
on selecting any of value from dropdown of any field this api will be called by passing
queryString=selectedValue of node
andsearchType=type of selected field
Frontend changes to be done in add consignment(crm) alongwith customer-portal and counter-booking(crm). (@saarthak to confirm all screens(frontend) where this change has to be done).What to do if any of middle node in hierarchy data is not found commented in above code snippet in catch block because it will hinder fetching data of other higher level (is it possible to occur in prod DB)
Should disable all highe higher level nodes irrespective of the case that their value is fetched or not OR disable only those fields for which value is fetched. @saarthak
Change2:
common/models/customer-portal-parts/address-node-api-helper.js
→ getAddressNodeData
...
Code Block |
---|
const conditions = { and: [ {hierarchy_id: hierarchyId}, {organisation_id: organisationId}, ], }; if (isDataSearchApplied) { searchQuery = helper.sanitizeStringCode(searchQuery); let queryString = `${searchQuery}%`; if (sortResult) { queryString = `%${queryString}`; } const orConditions = shouldIncludeNameInSearchFilter ? [ { name: { ilike: queryString } }, { code: { ilike: queryString } } ] : [{ code: { ilike: queryString } }]; conditions.and.push({ or: orConditions }); } const heirarchyData = await extendedModels.AddressNode.find({ where: conditions, fields: { code: true, name: true, id: true } }); |
Frontend changes:
In case allowOverrideHierarchy
is false , we will let user only enter values from dropdown only
else user can either select from dropdown or enter other values too
...
At frontend side changes are to be done at 3 places
Add Consignment modal (
src/components/pages/details/AddDetails.tsx
)Counter Booking → Add Address (
src/components/pages/OpsDashboard/Manage/counterBooking/AddAddress2.tsx
)Customer-Portal →
src/components/address/create-address.tsx
List to be populated in dropdown will be fetched after entering atleast 3 characters in the input field for corresponding node( pincode, city, state, country).
Debouncing will be implemented at all places whereever we are fetching list to be populated in the dropdown.
In case
allowOverrideHierarchy
is false , we will let user only enter values from dropdown only.In case
allowOverrideHierarchy
is true: user can either select from dropdown or enter other values too.Only those fields will be disabled for which data is fetched, only when allow override hierarchy is false
Code Block |
---|
{!this.state.allowOverrideHierarchy && getFieldDecorator('sender_country', {
rules: [{ required: false, message: t('cannot_be_empty') }],
})(
<Select
showSearch
optionFilterProp="children"
onChange={(value) => this.saveAddressUsingAddressHierarchy(value, 'country', 'sender')}
placeholder={t("Country")}
disabled={this.state.senderDisableCountry}
onSearch={this.loadCountries}
allowClear
>
{this.state?.countriesList?.map(opt => <Option key={opt.id} value={opt.code}>{t(opt.name)} ({opt.code})</Option>)}
</Select>,
)}
{this.state.allowOverrideHierarchy && getFieldDecorator('sender_country', {
rules: [{ required: false, message: t('cannot_be_empty') }],
})(
<AutoComplete
options={this.state?.countriesList?.map(item => ({ label: `${item.name} (${item.code})`, value: item.code }))}
onSearch={this.loadCountries}
onSelect={(value) => this.saveAddressUsingAddressHierarchy(value, 'country', 'sender')}
onChange={(value) => this.handleAddressFields('country', value, !this.state.allowOverrideHierarchy, 'sender')}
disabled={this.state.senderDisableCountry}
allowClear
>
<Input
allowClear
placeholder={t("Country")}
/>
</AutoComplete>, |
Instead of showing only Name of options in dropdown it will now be shown as Name (Code)
. This will require another update in find query in common/models/customer-portal-parts/address-node-api-helper.js
→ getAddressNodeData
(mentioned above in backend changes).
Chnages in src/components/address/create-address.tsx
General Function to be called whenever any option from rendered dropdown is selected, it make call to api api/CustomerPortal/fetchGeneralAddressCode
It fetches data from AddressNode table on basis of address hierarchy setup, and returns data for current and higher nodes to populate.
It also handle the logic to either disbale the higher level nodes based on whether their value is fetched and allow_override_hierarchy is false.
It also update (auto populate) the value of higher nodes.
Code Block |
---|
const saveAddressUsingAddressHierarchy = async (nodeValue: string, nodeType: string) => {
if (!isKeyOfNewAddress(nodeType) || !nodeValue) return;
setLoading(true);
const fetchLocality = await fetchAddressUsingAddressHierarchy({ queryString: nodeValue, searchType: nodeType });
if (!fetchLocality.isSuccess) {
setNewAddress({
...newAddress,
city: '',
state: '',
country: '',
});
setDisableLocality(false);
} else {
let nodeFound = false;
const parentLevelNodes = [];
for (let i = 0; i < hierarchyData?.length; i += 1) {
const curNode = hierarchyData[i].location_key.toLowerCase();
if ((nodeFound || curNode === nodeType.toLocaleLowerCase()) && curNode in fetchLocality?.data) {
nodeFound = true;
parentLevelNodes.push({ key: hierarchyData[i].location_key, value: fetchLocality?.data[curNode] });
}
}
const fetchedAddressFieldsObj = parentLevelNodes?.reduce((acc, curr) => {
if (isKeyOfNewAddress(curr.key)) {
acc[curr.key] = curr.value || null;
if (curr.key !== nodeType && (!isAddressMappingAllowed && curr.value)) {
setDisableLocality(true, curr.key);
} else {
setDisableLocality(false, curr.key);
}
}
return acc;
}, {} as { [key: string]: string | null });
setNewAddress({
...newAddress,
...fetchedAddressFieldsObj,
});
}
setLoading(false);
}; |
Function to be called on typing text to be searched in address fields( pincode, city, state country)
Code Block |
---|
const loadPincodes = async (value: any) => {
if (value?.length < 3) return;
if (!hierarchyData?.some((node: any) => node.location_key === 'pincode')) return;
setLoading(true);
const response = await fetchPincodeList(value);
if (response && response.isSuccess && Array.isArray(response?.data)) {
setLocalityPincodesList(response.data || []);
}
setLoading(false);
};
const loadCitiesList = async (value: any) => {
if (value?.length < 3) return;
if (!hierarchyData?.some((node: any) => node.location_key === 'city')) return;
setLoading(true);
const response = await fetchCitiesList(value);
if (response && response.isSuccess && Array.isArray(response?.data)) {
setLocalityCitiesList(response.data || []);
}
setLoading(false);
};
const loadStatesList = async (value: any) => {
if (value?.length < 3) return;
if (!hierarchyData?.some((node: any) => node.location_key === 'state')) return;
setLoading(true);
const response = await fetchStatesList(value);
if (response && response.isSuccess && Array.isArray(response?.data)) {
setLocalityStatesList(response.data || []);
}
setLoading(false);
};
const loadCountriesList = async (value: any) => {
if (value?.length < 3) return;
if (!hierarchyData?.some((node: any) => node.location_key === 'country')) return;
setLoading(true);
const response = await fetchCountriesList(value);
if (response && response.isSuccess && Array.isArray(response?.data)) {
setCountriesList(response.data || []);
}
setLoading(false);
}; |
Debouncing logic is also applied :
Code Block |
---|
useEffect(() => {
loadPincodes(newAddress.pincode);
}, [debouncePincode]);
useEffect(() => {
loadCitiesList(newAddress.city);
}, [debounceCity]);
useEffect(() => {
loadStatesList(newAddress.state);
}, [debounceState]);
useEffect(() => {
loadCountriesList(newAddress.country);
}, [debounceCountry]);
//import { useDebounce } from 'hooks/use-debounce'; using already imported (was being used for w3wCode) useDebounce HoC
//SEARCH_TIMEOUT = 500 -> debounce time is used by default
const debouncePincode = useDebounce(newAddress.pincode);
const debounceCity = useDebounce(newAddress.city);
const debounceState = useDebounce(newAddress.state);
const debounceCountry = useDebounce(newAddress.country);
|
Following rendering logic is used for all nodes (pincode, state, city, country)
It renders only dropdown for case when
allow_override_hierarchy
is false (user can only select options from dropdown only).When
allow_override_hierarchy
is true: user can type and search will be done in db, if data found user can select any option from dropdown and on basis of it higher level nodes is auto-populated, and can also enter anything out of option in dropdown for current node and higher nodes too.On clearing a node’s value, all higher node's value will also be cleared.
Code Block |
---|
const renderStateBox = () => { if (isAddressMappingAllowed) { return ( <AutoComplete value={newAddress.state} className={classes.stateInput} dropdownClassName={classes.pincodeInputDropdown} options={ localityStatesList.map((state: any) => { return { label: state.name, value: state.code, }; }) } onSearch={loadStatesList} onSelect={(e) => saveAddressUsingAddressHierarchy(e, 'state')} onChange={(value) => updateAddressFieldWithValidations('state', value)} style={{ textAlign: 'left' }} disabled={disableState} allowClear > <Input className={classes.autoCompleteInputBox} allowClear placeholder={t('address_state')} /> rules: [{ required: false, message: t('cannot_be_empty') }], </AutoComplete> ); })( return ( <Select value={newAddress.state} showSearch className={classes.stateInput} optionFilterProp="children" dropdownClassName={classes.pincodeInputDropdown} onChange={(valuee: any) => this.saveAddressUsingAddressHierarchy(value, 'countryupdateAddressFieldWithValidations('state', 'sender'e)} placeholder={t("Country"'address_state')} disabledoptions={this.state.senderDisableCountry} onSearch={this.loadCountries} localityStatesList.map((state: any) => { allowClear return { > label: {this.state?.countriesList?.map(opt => <Option key={opt.id} value={opt.code}>{t(opt.name)} ({opt.code})</Option>)}.name, </Select>, value: state.name, )} }; {this.state.allowOverrideHierarchy && getFieldDecorator('sender_country', { }) rules: [{ required: false, message: t('cannot_be_empty') }], } })( onSelect={(e) => saveAddressUsingAddressHierarchy(e, 'state')} <AutoComplete onSearch={loadStatesList} options={this.state?.countriesList?.map(item => ({ label: `${item.name} (${item.code})`, value: item.code }))disabled={disableState} showSearch onSearch={this.loadCountries} allowClear onSelect={(value) => this.saveAddressUsingAddressHierarchy(value, 'country', 'sender')} /> ); }; |
Changes in src/network/pickup.api.ts
Code Block |
---|
//api to fetch states list export onChange={(value)const FETCH_STATES_LIST => this.handleAddressFields('country', value, !this.state.allowOverrideHierarchy, 'sender')} disabled={this.state.senderDisableCountry} allowClear > '/api/CustomerPortal/addressManagement/getAddressNodeData?type=State'; //api to fetch nodes value based on address hierarchy (new dev api) export const FETCH_ADDRESS_FROM_ADDRESS_HIERARCHY = '/api/CustomerPortal/fetchGeneralAddressCode'; export const fetchAddressUsingAddressHierarchy = (params: any) => { return POST(`${API_BASE_URL}${FETCH_ADDRESS_FROM_ADDRESS_HIERARCHY}`, params); }; //exporting functions to fetch nodes' list to be called from create-address.tsx file on entering sompething in inpit fileds (> 3chars) export const fetchCountriesList = (params: any) => { return GET(`${API_BASE_URL}${FETCH_COUNTRIES_NODE_DATA}`, { isDataSearchApplied: true, searchQuery: params, sortResult: true }); }; export const <InputfetchCitiesList = (params: any) => { return GET(`${API_BASE_URL}${FETCH_CITIES_LIST}`, { isDataSearchApplied: allowCleartrue, searchQuery: params, sortResult: true }); }; export const fetchStatesList = (params: any) => { placeholder={t("Country")}return GET(`${API_BASE_URL}${FETCH_STATES_LIST}`, { isDataSearchApplied: /> </AutoComplete>, |
...
true, searchQuery: params, sortResult: true });
}; |
changes in src/network/api.constants.ts
Code Block |
---|
export const FETCH_STATES_LIST = '/api/CustomerPortal/addressManagement/getAddressNodeData?type=State';
export const FETCH_COUNTRIES_NODE_DATA = '/api/CustomerPortal/addressManagement/getAddressNodeData?type=Country';
export const FETCH_ADDRESS_FROM_ADDRESS_HIERARCHY = '/api/CustomerPortal/fetchGeneralAddressCode'; |