TP-62632 | Address screen refactor

This commit is contained in:
Aman Chaturvedi
2024-04-02 20:48:40 +05:30
parent 4161d611cb
commit 3dd5fdb2be
11 changed files with 241 additions and 323 deletions

View File

@@ -7,10 +7,13 @@ import axiosInstance, { ApiKeys, API_STATUS_CODE, getApiUrl } from '../component
import { AppDispatch } from '../store/store';
import { logError } from '../components/utlis/errorUtils';
import { IAddAddressPayload, IAddressGeolocationPayload } from '../types/addressGeolocation.types';
import { UnifiedCaseDetailsTypes, initialUrlParams } from './caseApiActions';
import { UnifiedCaseDetailsTypes } from './caseApiActions';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { IUngroupedAddressWithFeedbacks } from '@screens/addressGeolocation';
import {
setUngroupedAddresses,
setUngroupedAddressesLoading,
} from '@reducers/ungroupedAddressesSlice';
export const getAddressesGeolocation =
(payload: IAddressGeolocationPayload) => (dispatch: AppDispatch) => {
@@ -60,34 +63,35 @@ export const addAddress =
});
};
export const getUngroupedAddress = (
loanAccountNumber: string,
infoToGet: UnifiedCaseDetailsTypes[]
) => {
const queryParams = { ...initialUrlParams };
for (const key of infoToGet) {
queryParams[key] = true;
}
export const getUngroupedAddress = (loanAccountNumber: string) => (dispatch: AppDispatch) => {
const queryParams = {
[UnifiedCaseDetailsTypes.UNGROUPED_ADDRESSES]: true,
[UnifiedCaseDetailsTypes.INCLUDE_ADDRESS_FEEDBACK]: true,
};
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_UNIFIED_ENTITY_REQUESTED, {
lan: loanAccountNumber || '',
requestedEntities: JSON.stringify(infoToGet || []) || '',
requestedEntities: JSON.stringify([
UnifiedCaseDetailsTypes.UNGROUPED_ADDRESSES,
UnifiedCaseDetailsTypes.INCLUDE_ADDRESS_FEEDBACK,
]),
});
dispatch(setUngroupedAddressesLoading({ loanAccountNumber, isLoading: true }));
const url = getApiUrl(ApiKeys.CASE_UNIFIED_DETAILS_V4, { loanAccountNumber }, queryParams);
return axiosInstance
axiosInstance
.get(url)
.then((response) => {
if (response.status === API_STATUS_CODE.OK) {
const ungroupedAddressesWithFeedbacks: IUngroupedAddressWithFeedbacks = {
const ungroupedAddressesWithFeedbacks = {
ungroupedAddresses: response?.data?.ungroupedAddresses || [],
ungroupedAddressFeedbacks: response?.data?.addressFeedbacks || [],
loanAccountNumber,
};
return ungroupedAddressesWithFeedbacks;
dispatch(setUngroupedAddresses(ungroupedAddressesWithFeedbacks));
}
throw response;
})
.catch((err) => {
logError(err);
throw new Error(err);
dispatch(setUngroupedAddressesLoading({ loanAccountNumber, isLoading: false }));
});
};

View File

@@ -10,12 +10,7 @@ import CosmosForegroundService, {
type IForegroundTask,
} from '../services/foregroundServices/foreground.service';
import useIsOnline from '../hooks/useIsOnline';
import {
IGeolocationPayload,
getSyncTime,
sendCurrentGeolocationAndBuffer,
sendLocationAndActivenessToServer,
} from '../hooks/capturingApi';
import { getSyncTime, sendCurrentGeolocationAndBuffer } from '../hooks/capturingApi';
import { isTimeDifferenceWithinRange } from '../components/utlis/commonFunctions';
import { setIsTimeSynced } from '../reducer/foregroundServiceSlice';
import { logError } from '../components/utlis/errorUtils';
@@ -49,12 +44,6 @@ import { GlobalImageMap } from './CachedImage';
import { addClickstreamEvent } from '../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from './Constants';
import useResyncFirebase from '@hooks/useResyncFirebase';
import { CaptureGeolocation } from '@components/form/services/geoLocation.service';
import getLitmusExperimentResult, {
LitmusExperimentName,
LitmusExperimentNameMap,
} from '@services/litmusExperiments.service';
import { setLitmusExperimentResult } from '@reducers/litmusExperimentSlice';
export enum FOREGROUND_TASKS {
GEOLOCATION = 'GEOLOCATION',
@@ -82,59 +71,17 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
const {
isTeamLead,
caseSyncLock,
isTrackingComponentV2Enabled,
referenceId,
pendingList = [],
pinnedList = [],
geolocations = [],
deviceId,
userId,
} = useAppSelector((state) => ({
isTeamLead: state.user.isTeamLead,
caseSyncLock: state?.user?.caseSyncLock,
isTrackingComponentV2Enabled: state.litmusExperiment?.isTrackingComponentV2Enabled,
referenceId: state.user.user?.referenceId!,
pendingList: state.allCases.pendingList,
pinnedList: state.allCases.pinnedList,
geolocations: state.foregroundService.deviceGeolocationsBuffer,
deviceId: state?.user?.deviceId,
userId: state?.user?.user?.referenceId,
}));
const handleSendGeolocation = async () => {
try {
const location = await CaptureGeolocation.fetchLocation(`${Date.now()}`, 0, appState.current);
if (!location) {
return;
}
const isActiveOnApp: string | boolean = (await getItem(StorageKeys.IS_USER_ACTIVE)) || false;
const userActivityonApp: string =
(await getItem(StorageKeys.USER_ACTIVITY_ON_APP)) || AgentActivity.LOW;
const geolocation: IGeolocationPayload = {
latitude: location.latitude,
longitude: location.longitude,
accuracy: location.accuracy,
timestamp: Date.now(),
isActiveOnApp: Boolean(isActiveOnApp),
userActivityOnApp: String(userActivityonApp),
deviceId: deviceId,
};
dispatch(sendLocationAndActivenessToServer([geolocation]));
} catch (e: any) {
logError(e, 'Error during background location sending.');
}
};
useEffect(() => {
if (!isOnline || isTrackingComponentV2Enabled) {
return;
}
if (geolocations.length > 0) {
dispatch(sendLocationAndActivenessToServer(geolocations, true));
}
}, [geolocations, isOnline, isTrackingComponentV2Enabled]);
const handleTimeSync = async () => {
try {
if (!isOnline) {
@@ -296,9 +243,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
},
{
taskId: FOREGROUND_TASKS.GEOLOCATION,
task: isTrackingComponentV2Enabled
? () => dispatch(sendCurrentGeolocationAndBuffer(appState.current))
: handleSendGeolocation,
task: () => dispatch(sendCurrentGeolocationAndBuffer(appState.current)),
delay: 3 * MILLISECONDS_IN_A_MINUTE, // 3 minutes
onLoop: true,
},
@@ -364,32 +309,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
}
};
const handleGeolocationTaskUpdate = async () => {
const trackingComponentExperimentName = LitmusExperimentName.COSMOS_TRACKING_COMPONENT_V2;
const trackingComponentExperiment = await getLitmusExperimentResult(
trackingComponentExperimentName,
{ 'x-customer-id': userId }
);
// If the tracking component experiment is changed, update to new geolocation task
if (trackingComponentExperiment !== isTrackingComponentV2Enabled) {
const updatedGeolocationTask = {
taskId: FOREGROUND_TASKS.GEOLOCATION,
task: trackingComponentExperiment
? () => dispatch(sendCurrentGeolocationAndBuffer(appState.current))
: handleSendGeolocation,
delay: 3 * MILLISECONDS_IN_A_MINUTE,
onLoop: true,
};
CosmosForegroundService.updateTask(updatedGeolocationTask);
dispatch(
setLitmusExperimentResult({
experimentName: trackingComponentExperimentName,
result: trackingComponentExperiment,
})
);
}
};
const handleAppStateChange = async (nextAppState: AppStateStatus) => {
// App comes to foreground from background
const now = dayJs().toString();
@@ -401,7 +320,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
dispatch(getConfigData());
CosmosForegroundService.start(tasks);
resyncFirebase();
handleGeolocationTaskUpdate();
}
if (nextAppState === AppStates.BACKGROUND) {
await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now);
@@ -433,7 +351,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
let appStateSubscription: NativeEventSubscription;
appStateSubscription = AppState.addEventListener('change', handleAppStateChange);
CosmosForegroundService.start(tasks);
handleGeolocationTaskUpdate();
return () => {
appStateSubscription?.remove();
};

View File

@@ -31,7 +31,13 @@ export const sendLocationAndActivenessToServerV2 =
},
})
.then(() => {
dispatch(clearDeviceGeolocationsBuffer());
// clear the buffer after sending the location to the server
dispatch(
setDeviceGeolocationsBuffer({
deviceGeolocationBuffer: [],
deviceGeolocationCoordinate: geolocationBuffer[0],
})
);
})
.catch((error) => {
dispatch(
@@ -44,33 +50,6 @@ export const sendLocationAndActivenessToServerV2 =
});
};
export const sendLocationAndActivenessToServer =
(geolocationBuffer: IGeolocationPayload[], clearBuffer?: boolean) =>
(dispatch: AppDispatch, getState: AppGetState) => {
axiosInstance
.post(getApiUrl(ApiKeys.SEND_LOCATION), geolocationBuffer, {
headers: {
donotHandleError: 'true',
},
})
.then(() => {
clearBuffer && dispatch(clearDeviceGeolocationsBuffer());
})
.catch((error) => {
if (!clearBuffer) {
const { foregroundService } = getState();
const latestGeolocationsBuffer = foregroundService.deviceGeolocationsBuffer || [];
dispatch(
setDeviceGeolocationsBuffer({
deviceGeolocationBuffer: [...geolocationBuffer, ...latestGeolocationsBuffer],
deviceGeolocationCoordinate: geolocationBuffer[0],
})
);
}
logError(error, 'Error while sending location to server');
});
};
export const sendCurrentGeolocationAndBuffer =
(appState: AppStateStatus) => async (dispatch: AppDispatch, getState: AppGetState) => {
try {

View File

@@ -0,0 +1,43 @@
import { useAppSelector } from '@hooks';
import { MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES } from '@screens/addressGeolocation/constants';
import filterMetaAddressesByDistance from '@screens/addressGeolocation/utils/FilterFarAwayMetaAddresses';
import { RootState } from '@store';
import { useMemo } from 'react';
const useAddresses = (loanAccountNumber: string) => {
const {
addressGeolocation,
currentGeolocationCoordinates,
ungroupedAddresses = [],
} = useAppSelector((state: RootState) => ({
addressGeolocation: state.address?.[loanAccountNumber]?.addressesAndGeoLocations || {},
isLoading: state.address?.[loanAccountNumber]?.isLoading || false,
currentGeolocationCoordinates: state.foregroundService?.deviceGeolocationCoordinate || {},
addressFeedbacks: state.address?.[loanAccountNumber]?.addressFeedbacks || [],
ungroupedAddresses: state.ungroupedAddresses[loanAccountNumber]?.ungroupedAddresses,
}));
const { farAwayAddresses = [], nearByAddresses = [] } = useMemo(() => {
const { farAwayAddresses = [], nearByAddresses } = filterMetaAddressesByDistance({
metaAddresses: addressGeolocation?.groupedAddresses,
maximumDistance: MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES,
currentLocationCoordinates: currentGeolocationCoordinates,
});
return { farAwayAddresses, nearByAddresses };
}, [addressGeolocation, currentGeolocationCoordinates]);
const { additionalAddresses = [] } = useMemo(() => {
const metaAddresses = farAwayAddresses.map((farAwayAddress) => {
return {
...farAwayAddress?.metaAddress,
similarAddresses: farAwayAddress?.similarAddresses,
};
});
const additionalAddresses = [...metaAddresses, ...ungroupedAddresses];
return { additionalAddresses, farAwayAddresses, nearByAddresses };
}, [farAwayAddresses, ungroupedAddresses]);
return { farAwayAddresses, nearByAddresses, additionalAddresses };
};
export default useAddresses;

View File

@@ -0,0 +1,45 @@
import { createSlice } from '@reduxjs/toolkit';
import { IAddress } from '../types/addressGeolocation.types';
import { IAddressFeedback } from './addressSlice';
type IUngroupedAddressesSlice = Record<
string,
{
ungroupedAddresses: IAddress[];
ungroupedAddressFeedbacks: IAddressFeedback[];
isLoading: boolean;
}
>;
const initialState: IUngroupedAddressesSlice = {};
const UngroupedAddressesSlice = createSlice({
name: 'ungroupedAddressesSlice',
initialState,
reducers: {
setUngroupedAddresses: (state, action) => {
const {
loanAccountNumber,
ungroupedAddresses = [],
ungroupedAddressFeedbacks = [],
} = action.payload;
state[loanAccountNumber] = {
ungroupedAddresses,
ungroupedAddressFeedbacks,
isLoading: false,
};
},
setUngroupedAddressesLoading: (state, action) => {
const { loanAccountNumber, isLoading } = action.payload;
state[loanAccountNumber] = {
...(state?.[loanAccountNumber] || {}),
isLoading,
};
},
},
});
export const { setUngroupedAddresses, setUngroupedAddressesLoading } =
UngroupedAddressesSlice.actions;
export default UngroupedAddressesSlice.reducer;

View File

@@ -1,34 +1,29 @@
import React, { useEffect } from 'react';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import Accordion from '../../../RN-UI-LIB/src/components/accordian/Accordian';
import Text from '../../../RN-UI-LIB/src/components/Text';
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import { type IAddress, type IGroupedAddressesItem } from '../../types/addressGeolocation.types';
import { type IAddress } from '../../types/addressGeolocation.types';
import AddressItem from './AddressItem';
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
import { type GenericFunctionArgs } from '../../common/GenericTypes';
import SimilarAddressItem from './SimilarAddressItem';
import filterNearMetaAddresses from './utils/FilterNearMetaAddresses';
import { useAppSelector } from '../../hooks';
import { MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES } from './constants';
import { CaptureGeolocation, DeviceLocation } from '@components/form/services/geoLocation.service';
import { setDeviceGeolocation } from '@reducers/foregroundServiceSlice';
import { useAppDispatch } from '../../hooks';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import useAddresses from '@hooks/useAddresses';
interface IAddressContainer {
groupedAddressList?: IGroupedAddressesItem[];
caseId?: string;
caseId: string;
handlePageRouting?: GenericFunctionArgs;
addressFeedbacks?: any;
}
function SeparatorBorderComponent() {
return <View style={[styles.borderLine, GenericStyles.mv16]} />;
}
const getAllAddressIds = (groupedAddress: IGroupedAddressesItem) => {
const getAllAddressIds = (groupedAddress: IAddress) => {
// Set for unique address IDs
const addressIds = new Set();
@@ -50,18 +45,18 @@ const getAllAddressIds = (groupedAddress: IGroupedAddressesItem) => {
return Array.from(addressIds);
};
const AddressContainer: React.FC<IAddressContainer> = ({
groupedAddressList,
caseId,
handlePageRouting,
addressFeedbacks,
}) => {
const { currentGeolocationCoordinates } = useAppSelector((state) => ({
currentGeolocationCoordinates: state.foregroundService?.deviceGeolocationCoordinate,
}));
const AddressContainer: React.FC<IAddressContainer> = ({ caseId, handlePageRouting }) => {
const { loanAccountNumber, addressFeedbacks } = useAppSelector((state) => {
const loanAccountNumber = state.allCases?.caseDetails?.[caseId]?.loanAccountNumber;
return {
loanAccountNumber,
addressFeedbacks: state.address?.[loanAccountNumber]?.addressFeedbacks || [],
};
});
const dispatch = useAppDispatch();
const handleOpenOldFeedbacks = (groupedAddress: IGroupedAddressesItem) => {
const { nearByAddresses } = useAddresses(loanAccountNumber);
const handleOpenOldFeedbacks = (groupedAddress: IAddress) => {
const addressIds = getAllAddressIds(groupedAddress);
const commonParams = {
addressText: groupedAddress?.metaAddress?.addressText,
@@ -78,19 +73,9 @@ const AddressContainer: React.FC<IAddressContainer> = ({
}
};
useEffect(() => {
CaptureGeolocation.watchLocation((location: DeviceLocation) => {
return dispatch(setDeviceGeolocation(location));
});
}, []);
return (
<View>
{filterNearMetaAddresses({
metaAddresses: groupedAddressList,
maximumDistance: MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES,
currentLocationCoordinates: currentGeolocationCoordinates,
})?.map((groupedAddress: IGroupedAddressesItem, index: number) => {
{nearByAddresses?.map((groupedAddress: IAddress, index: number) => {
const lastFeedbackForAddress = addressFeedbacks.find(
(addressFeedback) =>
addressFeedback?.addressReferenceId === groupedAddress?.metaAddress?.id
@@ -99,7 +84,7 @@ const AddressContainer: React.FC<IAddressContainer> = ({
<View key={groupedAddress?.metaAddress?.id}>
<Accordion
accordionStyle={[GenericStyles.pv24, GenericStyles.ph16]}
isExpansionDisabled={!groupedAddress?.similarAddresses.length}
isExpansionDisabled={!groupedAddress?.similarAddresses?.length}
touchableOpacity={0.8}
touchableDelay={50}
accordionHeader={
@@ -127,7 +112,7 @@ const AddressContainer: React.FC<IAddressContainer> = ({
handleAccordionExpand(isExpanded, groupedAddress?.metaAddress?.id);
}}
>
{groupedAddress?.similarAddresses.length ? (
{groupedAddress?.similarAddresses?.length ? (
<View>
<SeparatorBorderComponent />
<Text
@@ -136,9 +121,8 @@ const AddressContainer: React.FC<IAddressContainer> = ({
Similar addresses
</Text>
{groupedAddress.similarAddresses.map((similarAddress: IAddress) => (
<View style={{ flex: 1 }}>
<View style={GenericStyles.fill} key={similarAddress?.id}>
<SimilarAddressItem
key={similarAddress?.id}
addressItem={similarAddress}
containerStyle={styles.card}
showSource

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Dimensions, ScrollView, StyleSheet, View } from 'react-native';
import React, { useEffect } from 'react';
import { Dimensions, RefreshControl, ScrollView, StyleSheet, View } from 'react-native';
import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader';
import { goBack, navigateToScreen } from '../../components/utlis/navigationUtlis';
import useIsOnline from '../../hooks/useIsOnline';
@@ -16,10 +16,11 @@ import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import SimilarAddressItem from './SimilarAddressItem';
import Accordion from '../../../RN-UI-LIB/src/components/accordian/Accordian';
import { IAddressFeedback } from '@reducers/addressSlice';
import getAddressIdsArrayForFarAddresses from './utils/getAddressIdsArrayForFarAddresses';
import { IUngroupedAddressWithFeedbacks } from '.';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { useAppDispatch, useAppSelector } from '@hooks';
import { getUngroupedAddress } from '@actions/addressGeolocationAction';
import useAddresses from '@hooks/useAddresses';
const PAGE_TITLE = 'Additional addresses';
@@ -29,7 +30,6 @@ interface IUngroupedAddress {
loanAccountNumber: string;
caseId: string;
customerReferenceId: string;
fetchUngroupedAddress: (loanAccountNumber: string) => Promise<IUngroupedAddressWithFeedbacks>;
};
};
}
@@ -48,38 +48,28 @@ const handleAccordionExpand = (isExpanded: boolean, addressId: string) => {
const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routeParams }) => {
const {
params: { loanAccountNumber, caseId, customerReferenceId, fetchUngroupedAddress },
params: { loanAccountNumber, caseId, customerReferenceId },
} = routeParams;
const [loading, setLoading] = useState(false);
const [retryBtnToggle, setRetryBtnToggle] = useState(false);
const [ungroupedAddressList, setUngroupedAddressList] = useState<IAddress[]>([]);
const [addressFeedbacksList, setAddressFeedbacksList] = useState<IAddressFeedback[]>([]);
const { ungroupedAddressFeedbacks = [], isLoading = false } =
useAppSelector((state) => state.ungroupedAddresses?.[loanAccountNumber]) || {};
const { additionalAddresses = [] } = useAddresses(loanAccountNumber);
const dispatch = useAppDispatch();
const isOnline = useIsOnline();
const handleRetryEvent = () => {
setRetryBtnToggle((retryBtnToggle) => !retryBtnToggle);
const fetchUngroupedAddress = () => {
// fetch ungrouped address
dispatch(getUngroupedAddress(loanAccountNumber));
};
useEffect(() => {
if (isOnline) {
setLoading(true);
fetchUngroupedAddress(loanAccountNumber)
.then((res: IUngroupedAddressWithFeedbacks) => {
setUngroupedAddressList(res?.ungroupedAddresses || []);
setAddressFeedbacksList(res?.ungroupedAddressFeedbacks || []);
setLoading(true);
})
.finally(() => {
setLoading(false);
});
}
}, [retryBtnToggle, isOnline]);
if (!isOnline) {
return (
<OfflineScreen handleRetryEvent={handleRetryEvent} goBack={goBack} pageTitle={PAGE_TITLE} />
<OfflineScreen
handleRetryEvent={fetchUngroupedAddress}
goBack={goBack}
pageTitle={PAGE_TITLE}
/>
);
}
@@ -105,13 +95,21 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
return (
<View style={GenericStyles.fill}>
<NavigationHeader title={PAGE_TITLE} onBack={goBack} />
<ScrollView>
<ScrollView
refreshControl={
<RefreshControl
refreshing={false}
onRefresh={fetchUngroupedAddress}
/>
}
>
<SuspenseLoader
loading={loading}
loading={isLoading}
fallBack={
<>
{[...Array(8).keys()].map(() => (
{[...Array(8).keys()].map((_, index) => (
<LineLoader
key={index}
width="100%"
height={75}
style={[GenericStyles.br6, { marginBottom: 20 }]}
@@ -120,13 +118,16 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
</>
}
>
{ungroupedAddressList?.length ? (
{additionalAddresses?.length ? (
<View>
{ungroupedAddressList.map((ungroupedAddressItem: IAddress, index: number) => {
const lastFeedbackForAddress = addressFeedbacksList?.find((addressFeedback)=>{
return (addressFeedback?.addressReferenceId === ungroupedAddressItem?.id)});
{additionalAddresses.map((ungroupedAddressItem: IAddress, index: number) => {
const lastFeedbackForAddress = ungroupedAddressFeedbacks?.find(
(addressFeedback) => {
return addressFeedback?.addressReferenceId === ungroupedAddressItem?.id;
}
);
return (
<View>
<View key={ungroupedAddressItem?.id}>
{!ungroupedAddressItem?.similarAddresses ? (
<AddressItem
caseId={caseId}
@@ -162,7 +163,10 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
handleOpenOldFeedbacks(ungroupedAddressItem);
}}
handleCloseRouting={() => {
navigateToScreen(CaseDetailStackEnum.ADDITIONAL_ADDRESSES, commonParams);
navigateToScreen(
CaseDetailStackEnum.ADDITIONAL_ADDRESSES,
commonParams
);
}}
showSource
lastFeedbackForAddress={lastFeedbackForAddress}
@@ -184,7 +188,8 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
>
Similar addresses
</Text>
{ungroupedAddressItem.similarAddresses.map((similarAddress: IAddress) => (
{ungroupedAddressItem.similarAddresses.map(
(similarAddress: IAddress) => (
<View style={GenericStyles.fill} key={similarAddress?.id}>
<SimilarAddressItem
addressItem={similarAddress}
@@ -192,13 +197,14 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
showSource
/>
</View>
))}
)
)}
</View>
) : null}
</Accordion>
)}
</View> );
</View>
);
})}
</View>
) : (

View File

@@ -1,9 +1,15 @@
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import {
AppState,
RefreshControl,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native';
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader';
import { goBack, navigateToScreen } from '../../components/utlis/navigationUtlis';
import { useAppDispatch } from '../../hooks';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { type RootState } from '../../store/store';
import GeolocationContainer from './GeolocationContainer';
import AddressContainer from './AddressContainer';
@@ -17,50 +23,34 @@ import { getCaseUnifiedData, UnifiedCaseDetailsTypes } from '../../action/caseAp
import { CLICKSTREAM_EVENT_NAMES, HIT_SLOP } from '../../common/Constants';
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import HomeIconSmall from '../../assets/icons/HomeIconSmall';
import { IAddressFeedback, setAddressLoading } from '../../reducer/addressSlice';
import { setAddressLoading } from '../../reducer/addressSlice';
import SuspenseLoader from '../../../RN-UI-LIB/src/components/suspense_loader/SuspenseLoader';
import LineLoader from '../../../RN-UI-LIB/src/components/suspense_loader/LineLoader';
import { CaptureGeolocation } from '../../components/form/services/geoLocation.service';
import { setDeviceGeolocation } from '../../reducer/foregroundServiceSlice';
import Layout from '../layout/Layout';
import { getUngroupedAddress } from '../../action/addressGeolocationAction';
import { type IAddress } from '../../types/addressGeolocation.types';
import filterFarAwayMetaAddresses from './utils/FilterFarAwayMetaAddresses';
import { MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES } from './constants';
import CustomTabs from '@rn-ui-lib/components/customTabs/CustomTabs';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { ADDRESSES_TABS, AddressGeolocationTabEnum, IAddressGeolocation } from './constant';
import useAddresses from '@hooks/useAddresses';
import { sendCurrentGeolocationAndBuffer } from '@hooks/capturingApi';
import Text from '@rn-ui-lib/components/Text';
const PAGE_TITLE = 'All addresses';
export interface IUngroupedAddressWithFeedbacks {
ungroupedAddresses: IAddress[];
ungroupedAddressFeedbacks: IAddressFeedback[];
}
const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams }) => {
const {
params: { loanAccountNumber, customerReferenceId, caseId },
} = routeParams;
const [ungroupedAddress, setUngroupedAddress] = useState<IAddress[]>([]);
const { addressGeolocation, isLoading } = useAppSelector((state: RootState) => ({
addressGeolocation: state.address?.[loanAccountNumber]?.addressesAndGeoLocations || {},
isLoading: state.address?.[loanAccountNumber]?.isLoading || false,
}));
const [selectedTab, setSelectedTab] = useState<string>(AddressGeolocationTabEnum.ADDRESS);
const [ungroupedAddressFeedbacks, setUngroupedAddressFeedbacks] = useState<IAddressFeedback[]>(
[]
);
const [retryBtnToggle, setRetryBtnToggle] = useState(false);
const dispatch = useAppDispatch();
const isOnline = useIsOnline();
const { addressGeolocation, isLoading, currentGeolocationCoordinates, addressFeedbacks } =
useSelector((state: RootState) => ({
addressGeolocation: state.address?.[loanAccountNumber]?.addressesAndGeoLocations || {},
isLoading: state.address?.[loanAccountNumber]?.isLoading || false,
currentGeolocationCoordinates: state.foregroundService?.deviceGeolocationCoordinate || {},
addressFeedbacks: state.address?.[loanAccountNumber]?.addressFeedbacks || [],
}));
const { additionalAddresses } = useAddresses(loanAccountNumber);
const commonParams = {
loanAccountNumber,
@@ -81,75 +71,29 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ADD_NEW_ADDRESS_CLICKED, commonParams);
};
const fetchUngroupedAddress = async (loanAccountNumber: string) =>
await getUngroupedAddress(loanAccountNumber, [
UnifiedCaseDetailsTypes.UNGROUPED_ADDRESSES,
UnifiedCaseDetailsTypes.INCLUDE_ADDRESS_FEEDBACK,
]).then((res: IUngroupedAddressWithFeedbacks) => {
const farAwayAddresses = filterFarAwayMetaAddresses({
metaAddresses: addressGeolocation?.groupedAddresses,
maximumDistance: MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES,
currentLocationCoordinates: currentGeolocationCoordinates,
});
const metaAddresses = farAwayAddresses.map((farAwayAddress) => {
return {
...farAwayAddress?.metaAddress,
similarAddresses: farAwayAddress?.similarAddresses,
};
});
const ungroupedAddresses = [...metaAddresses, ...(res?.ungroupedAddresses || [])];
const ungroupedAddressFeedbacks = [
...addressFeedbacks,
...(res?.ungroupedAddressFeedbacks || []),
];
setUngroupedAddress(ungroupedAddresses);
setUngroupedAddressFeedbacks(ungroupedAddressFeedbacks);
const ungroupedAddressesWithFeedbacks: IUngroupedAddressWithFeedbacks = {
ungroupedAddresses,
ungroupedAddressFeedbacks,
};
return ungroupedAddressesWithFeedbacks;
});
useEffect(() => {
async function getUngroupedAddress() {
await fetchUngroupedAddress(loanAccountNumber);
return null;
}
getUngroupedAddress();
}, [addressGeolocation?.groupedAddresses, isLoading]);
const getGroupedAddresses = () => {
dispatch(setAddressLoading({ loanAccountNumbers: [loanAccountNumber], isLoading: true }));
dispatch(
getCaseUnifiedData(
[loanAccountNumber],
[
UnifiedCaseDetailsTypes.ADDRESS_AND_GEOLOCATIONS,
UnifiedCaseDetailsTypes.INCLUDE_ADDRESS_FEEDBACK,
]
)
);
dispatch(sendCurrentGeolocationAndBuffer(AppState.currentState));
};
useEffect(() => {
if (isOnline) {
dispatch(setAddressLoading({ loanAccountNumbers: [loanAccountNumber], isLoading: true }));
dispatch(
getCaseUnifiedData(
[loanAccountNumber],
[
UnifiedCaseDetailsTypes.ADDRESS_AND_GEOLOCATIONS,
UnifiedCaseDetailsTypes.INCLUDE_ADDRESS_FEEDBACK,
]
)
);
getGroupedAddresses();
}
}, [retryBtnToggle, isOnline]);
useEffect(() => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ALL_ADDRESSES_LANDED, commonParams);
(async () => {
const location = await CaptureGeolocation.fetchLocation(`${Date.now()}`, 0);
if (location != null) {
dispatch(
setDeviceGeolocation({
latitude: location.latitude,
longitude: location.longitude,
timestamp: Date.now(),
})
);
}
})();
dispatch(getUngroupedAddress(loanAccountNumber));
}, []);
const handleTabChange = (tab: string) => {
@@ -180,7 +124,9 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams
onTabChange={handleTabChange}
containerStyle={[GenericStyles.ml16, GenericStyles.pt16, GenericStyles.silverBackground]}
/>
<ScrollView>
<ScrollView
refreshControl={<RefreshControl refreshing={false} onRefresh={getGroupedAddresses} />}
>
<SuspenseLoader
loading={isLoading}
fallBack={
@@ -198,13 +144,8 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams
>
<View style={GenericStyles.fill}>
<View style={selectedTab === AddressGeolocationTabEnum.ADDRESS ? {} : styles.hidden}>
<AddressContainer
groupedAddressList={addressGeolocation.groupedAddresses}
addressFeedbacks={addressFeedbacks}
caseId={caseId}
handlePageRouting={handleRouting}
/>
{ungroupedAddress?.length > 0 ? (
<AddressContainer caseId={caseId} handlePageRouting={handleRouting} />
{additionalAddresses?.length > 0 ? (
<View
style={[
GenericStyles.row,
@@ -224,12 +165,7 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams
<TouchableOpacity
activeOpacity={0.7}
hitSlop={HIT_SLOP}
onPress={() =>
handleRouting(CaseDetailStackEnum.ADDITIONAL_ADDRESSES, {
fetchUngroupedAddress,
ungroupedAddressFeedbacks,
})
}
onPress={() => handleRouting(CaseDetailStackEnum.ADDITIONAL_ADDRESSES)}
>
<Text style={styles.actionBtn}>View all addresses</Text>
</TouchableOpacity>
@@ -294,11 +230,6 @@ const styles = StyleSheet.create({
lineHeight: 20,
color: COLORS.TEXT.BLUE,
},
textContainer: {
fontSize: 14,
fontWeight: '500',
color: COLORS.TEXT.BLACK_24,
},
hidden: {
display: 'none',
},

View File

@@ -1,24 +1,29 @@
import { IAddress } from '@interfaces/addressGeolocation.types';
import { getDistanceFromLatLonInKm } from '../../../components/utlis/commonFunctions';
interface FilterFarAwayMetaAddressesParams {
interface filterMetaAddressesByDistance {
metaAddresses: any[];
maximumDistance: number;
currentLocationCoordinates: { latitude: number; longitude: number };
}
const filterFarAwayMetaAddresses = ({
const filterMetaAddressesByDistance = ({
metaAddresses = [],
maximumDistance,
currentLocationCoordinates,
}: FilterFarAwayMetaAddressesParams) =>
metaAddresses?.filter((metaAddress) => {
}: filterMetaAddressesByDistance) => {
const farAwayAddresses: IAddress[] = [];
const nearByAddresses: IAddress[] = [];
metaAddresses?.forEach((metaAddress) => {
const distance = getDistanceFromLatLonInKm(currentLocationCoordinates, {
latitude: metaAddress?.metaAddress?.latitude,
longitude: metaAddress?.metaAddress?.longitude,
});
if (distance >= maximumDistance || isNaN(distance)) {
return true;
return farAwayAddresses.push(metaAddress);
}
return false;
return nearByAddresses.push(metaAddress);
});
return { nearByAddresses, farAwayAddresses };
};
export default filterFarAwayMetaAddresses;
export default filterMetaAddressesByDistance;

View File

@@ -38,6 +38,7 @@ import agentPerformanceSlice from '../reducer/agentPerformanceSlice';
import telephoneNumbersSlice from '../reducer/telephoneNumbersSlice';
import { getStorageEngine } from '../PersistStorageEngine';
import litmusExperimentSlice from '@reducers/litmusExperimentSlice';
import ungroupedAddressesSlice from '@reducers/ungroupedAddressesSlice';
const rootReducer = combineReducers({
case: caseReducer,
@@ -64,6 +65,7 @@ const rootReducer = combineReducers({
agentPerformance: agentPerformanceSlice,
telephoneNumbers: telephoneNumbersSlice,
litmusExperiment: litmusExperimentSlice,
ungroupedAddresses: ungroupedAddressesSlice,
});
const persistConfig = {
@@ -88,7 +90,7 @@ const persistConfig = {
'feedbackFilters',
'litmusExperiment',
],
blackList: ['case', 'filters', 'reportees', 'agentPerformance'],
blackList: ['case', 'filters', 'reportees', 'agentPerformance', 'ungroupedAddresses'],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);

View File

@@ -51,6 +51,8 @@ export interface IAddress {
metaAddressReferences: IMetaAddress[];
similarAddresses?: IAddress[];
createdAt?: string;
metaAddress: IAddress;
contactabilityStatus: string;
}
export interface IGroupedAddressesItem {