TP-31303|master back merge|Aman Singh

This commit is contained in:
aman.singh
2023-09-13 17:35:37 +05:30
25 changed files with 824 additions and 302 deletions

55
App.tsx
View File

@@ -1,34 +1,34 @@
import React from 'react';
import React, { useState } from 'react';
import {
AppState,
LogBox,
Permission,
type Permission,
PermissionsAndroid,
Platform,
StatusBar,
} from 'react-native';
import { Provider } from 'react-redux';
import { init as initApm } from '@cobo/apm-rum-react-native';
import store, { persistor } from './src/store/store';
import { PersistGate } from 'redux-persist/integration/react';
import { NavigationContainer } from '@react-navigation/native';
import * as Sentry from '@sentry/react-native';
import codePush from 'react-native-code-push';
import AsyncStorage from '@react-native-async-storage/async-storage';
import CodePush from 'react-native-code-push';
import store, { persistor } from './src/store/store';
import { navigationRef } from './src/components/utlis/navigationUtlis';
import FullScreenLoader from './RN-UI-LIB/src/components/FullScreenLoader';
import { toastConfigs, ToastContainer } from './RN-UI-LIB/src/components/toast';
import * as Sentry from '@sentry/react-native';
import { APM_APP_NAME, APM_BASE_URL, ENV } from './src/constants/config';
import { COLORS } from './RN-UI-LIB/src/styles/colors';
import codePush from 'react-native-code-push';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { LocalStorageKeys } from './src/common/Constants';
import Permissions from './src/screens/permissions/Permissions';
import { setJsErrorHandler } from './src/services/exception-handler.service';
import SuspenseLoader from './RN-UI-LIB/src/components/suspense_loader/SuspenseLoader';
import ErrorBoundary from './src/common/ErrorBoundary';
import CodePush from 'react-native-code-push';
import { TDocumentObj } from './src/screens/caseDetails/interface';
import { type TDocumentObj } from './src/screens/caseDetails/interface';
import AuthRouter from './src/screens/auth/AuthRouter';
import { initSentry } from './src/components/utlis/sentry';
import {
@@ -38,6 +38,7 @@ import {
import usePolling from './src/hooks/usePolling';
import { MILLISECONDS_IN_A_SECOND } from './RN-UI-LIB/src/utlis/common';
import { GlobalImageMap, hydrateGlobalImageMap } from './src/common/CachedImage';
import analytics from '@react-native-firebase/analytics';
initSentry();
@@ -66,9 +67,10 @@ function handleAppStateChange(nextAppState: any) {
const PERMISSION_CHECK_POLL_INTERVAL = 5 * MILLISECONDS_IN_A_SECOND;
const App = () => {
function App() {
const [permissions, setPermissions] = React.useState(true);
const [isGlobalDocumentMapLoaded, setIsGlobalDocumentMapLoaded] = React.useState(false);
const [routeName, setRouteName] = useState('Unknown');
const askForPermissions = async () => {
const permissionsToRequest = await getPermissionsToRequest();
@@ -90,6 +92,17 @@ const App = () => {
}
};
const getActiveRouteName = (state) => {
if (!state || typeof state.index !== 'number') {
return 'Unknown';
}
const route = state.routes[state.index];
if (route.state) {
return getActiveRouteName(route.state);
}
return route?.name || '';
};
usePolling(askForPermissions, PERMISSION_CHECK_POLL_INTERVAL);
initApm({
@@ -123,7 +136,25 @@ const App = () => {
return (
<Provider store={store}>
<PersistGate loading={<FullScreenLoader loading />} persistor={persistor}>
<NavigationContainer ref={navigationRef}>
<NavigationContainer
ref={navigationRef}
onStateChange={async (state) => {
const currentRouteName = getActiveRouteName(state);
if (routeName !== currentRouteName) {
await analytics().logScreenView({
screen_name: currentRouteName,
screen_class: currentRouteName,
});
await analytics().logEvent('screen_view', {
screen_name: currentRouteName,
screen_class: currentRouteName,
});
setRouteName(currentRouteName);
}
}}
>
<StatusBar backgroundColor={COLORS.BACKGROUND.INDIGO_DARK} />
<SuspenseLoader
fallBack={<FullScreenLoader loading />}
@@ -137,7 +168,7 @@ const App = () => {
</PersistGate>
</Provider>
);
};
}
const AppWithSentry = Sentry.wrap(App);

View File

@@ -131,8 +131,8 @@ def reactNativeArchitectures() {
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
def VERSION_CODE = 81
def VERSION_NAME = "2.3.8"
def VERSION_CODE = 83
def VERSION_NAME = "2.3.10"
android {
ndkVersion rootProject.ext.ndkVersion
@@ -326,3 +326,4 @@ def isNewArchitectureEnabled() {
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}
apply plugin: 'com.google.gms.google-services'

View File

@@ -1,39 +1,39 @@
{
"project_info": {
"project_number": "60755663443",
"project_id": "address-verification-app",
"storage_bucket": "address-verification-app.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:60755663443:android:988149d3da3c00d38584a6",
"android_client_info": {
"package_name": "com.avapp"
}
},
"oauth_client": [
{
"client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyA70_d2M2ke-Mu0OHGZ6iZilBbD6A-_z0c"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com",
"client_type": 3
}
]
"project_info": {
"project_number": "60755663443",
"project_id": "address-verification-app",
"storage_bucket": "address-verification-app.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:60755663443:android:988149d3da3c00d38584a6",
"android_client_info": {
"package_name": "com.avapp"
}
},
"oauth_client": [
{
"client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyA70_d2M2ke-Mu0OHGZ6iZilBbD6A-_z0c"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
}
],
"configuration_version": "1"
}
],
"configuration_version": "1"
}

View File

@@ -1,5 +1,7 @@
package com.avapp;
import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
@@ -16,8 +18,12 @@ import com.microsoft.codepush.react.CodePush;
import android.database.CursorWindow;
import java.lang.reflect.Field;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
@@ -30,7 +36,6 @@ public class MainApplication extends Application implements ReactApplication {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new DeviceUtilsModulePackage());
return packages;
}
@@ -65,6 +70,9 @@ public class MainApplication extends Application implements ReactApplication {
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
// https://github.com/rt2zz/redux-persist/issues/284#issuecomment-1011214066
try {
Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");

View File

@@ -2,12 +2,13 @@
"project_info": {
"project_number": "60755663443",
"project_id": "address-verification-app",
"storage_bucket": "address-verification-app.appspot.com"
"storage_bucket": "address-verification-app.appspot.com",
"firebase_url": "https://address-verification-app-default-rtdb.firebaseio.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:60755663443:android:988149d3da3c00d38584a6",
"mobilesdk_app_id": "1:60755663443:android:4a948ee9d0b4e3098584a6",
"android_client_info": {
"package_name": "com.avapp"
}

View File

@@ -29,6 +29,7 @@
"@react-native-async-storage/async-storage": "1.17.11",
"@react-native-clipboard/clipboard": "^1.11.2",
"@react-native-community/netinfo": "9.3.7",
"@react-native-firebase/analytics": "16.4.6",
"@react-native-firebase/app": "16.4.6",
"@react-native-firebase/auth": "16.5.0",
"@react-native-firebase/crashlytics": "16.5.0",

View File

@@ -1,27 +1,27 @@
import { type AxiosResponse } from 'axios';
import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper';
import { AppDispatch } from '../store/store';
import { IGroupedGeolocationAddressItem } from '../types/addressGeolocation.types';
import { type AppDispatch } from '../store/store';
import { type IGroupedGeolocationAddressItem } from '../types/addressGeolocation.types';
import { setEmiSchedule, setEmiScheduleLoading } from '../reducer/emiScheduleSlice';
import { setFeedbackHistory, setFeedbackHistoryLoading } from '../reducer/feedbackHistorySlice';
import { setRepayments, setRepaymentsLoading } from '../reducer/repaymentsSlice';
import { AxiosResponse } from 'axios';
import { setAddresses, setAddressLoading } from '../reducer/addressSlice';
import { IFeedback } from '../types/feedback.types';
import { type IFeedback } from '../types/feedback.types';
import { allSettled } from '../components/utlis/commonFunctions';
import { GenericType } from '../common/GenericTypes';
import { type GenericType } from '../common/GenericTypes';
import { addClickstreamEvent } from '../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../common/Constants';
import { PageRouteEnum } from '../screens/auth/ProtectedRouter';
import { MILLISECONDS_IN_A_MINUTE, _map } from '../../RN-UI-LIB/src/utlis/common';
import { logError } from '../components/utlis/errorUtils';
import { IDocument, removeDocumentByQuestionKey } from '../reducer/feedbackImagesSlice';
import { type IDocument, removeDocumentByQuestionKey } from '../reducer/feedbackImagesSlice';
// TODO: Need to add respective interfaces instead of any
interface IUnifiedData {
addressesAndGeoLocations: IGroupedGeolocationAddressItem;
feedbacks: Array<any>;
emiSchedule: Array<any>;
repayments: Array<any>;
feedbacks: any[];
emiSchedule: any[];
repayments: any[];
}
export interface IUploadImagePayload {
@@ -35,6 +35,7 @@ export enum UnifiedCaseDetailsTypes {
FEEDBACKS = 'includeFeedbacks',
EMI_SCHEDULE = 'includeEmiSchedule',
REPAYMENTS = 'includeRepayments',
INCLUDE_ADDRESS_FEEDBACK = 'includeAddressFeedback',
}
const initialUrlParams = {
@@ -42,6 +43,7 @@ const initialUrlParams = {
[UnifiedCaseDetailsTypes.FEEDBACKS]: false,
[UnifiedCaseDetailsTypes.EMI_SCHEDULE]: false,
[UnifiedCaseDetailsTypes.REPAYMENTS]: false,
[UnifiedCaseDetailsTypes.INCLUDE_ADDRESS_FEEDBACK]: false,
};
const setUnifiedDataLoading =
@@ -65,13 +67,13 @@ const setUnifiedData =
(
queryParams: Record<UnifiedCaseDetailsTypes, boolean>,
loanAccountNumbers: string[],
unifiedPromiseData: PromiseSettledResult<AxiosResponse<IUnifiedData>>[]
unifiedPromiseData: Array<PromiseSettledResult<AxiosResponse<IUnifiedData>>>
) =>
(dispatch: AppDispatch) => {
unifiedPromiseData.forEach((promiseResult, index) => {
const { status } = promiseResult;
if (status === 'fulfilled' && promiseResult.value) {
const { addressesAndGeoLocations, feedbacks, emiSchedule, repayments } =
const { addressesAndGeoLocations, feedbacks, emiSchedule, repayments, addressFeedbacks } =
promiseResult.value.data;
const loanAccountNumber = loanAccountNumbers[index];
if (queryParams[UnifiedCaseDetailsTypes.EMI_SCHEDULE]) {
@@ -93,12 +95,15 @@ const setUnifiedData =
if (queryParams[UnifiedCaseDetailsTypes.ADDRESS_AND_GEOLOCATIONS]) {
dispatch(setAddresses({ loanAccountNumber, addressesAndGeoLocations }));
}
if (queryParams[UnifiedCaseDetailsTypes.INCLUDE_ADDRESS_FEEDBACK]) {
dispatch(setAddresses({ loanAccountNumber, addressFeedbacks, addressesAndGeoLocations }));
}
}
});
};
export const getCaseUnifiedData =
(loanAccountNumbers: string[], infoToGet: Array<UnifiedCaseDetailsTypes>) =>
(loanAccountNumbers: string[], infoToGet: UnifiedCaseDetailsTypes[]) =>
(dispatch: AppDispatch) => {
const queryParams = { ...initialUrlParams };
for (const key of infoToGet) {
@@ -109,7 +114,7 @@ export const getCaseUnifiedData =
requestedEntities: JSON.stringify(infoToGet || []),
});
dispatch(setUnifiedDataLoading(queryParams, loanAccountNumbers));
const promisesList: Promise<AxiosResponse<any, any>>[] = [];
const promisesList: Array<Promise<AxiosResponse<any, any>>> = [];
loanAccountNumbers.forEach((loanAccountNumber) => {
const url = getApiUrl(ApiKeys.CASE_UNIFIED_DETAILS, { loanAccountNumber }, queryParams);
const promise = axiosInstance.get(url, {

View File

@@ -1,13 +1,15 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { IGroupedGeolocationAddressItem } from '../types/addressGeolocation.types';
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import { type IGroupedGeolocationAddressItem } from '../types/addressGeolocation.types';
interface IAddressState {
[loanAccountNumber: string]: {
type IAddressState = Record<
string,
{
addressFeedbacks: never[];
addressesAndGeoLocations: IGroupedGeolocationAddressItem;
isLoading: boolean;
timestamp: string;
};
}
}
>;
const initialState: IAddressState = {};
@@ -16,9 +18,10 @@ const AddressSlice = createSlice({
initialState,
reducers: {
setAddresses: (state, action) => {
const { loanAccountNumber, addressesAndGeoLocations } = action.payload;
const { loanAccountNumber, addressesAndGeoLocations, addressFeedbacks } = action.payload;
state[loanAccountNumber] = {
addressesAndGeoLocations,
addressFeedbacks: addressFeedbacks || [],
timestamp: new Date().toISOString(),
isLoading: false,
};

View File

@@ -4,29 +4,39 @@ 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 { IAddress, IGroupedAddressesItem } from '../../types/addressGeolocation.types';
import { type IAddress, type IGroupedAddressesItem } from '../../types/addressGeolocation.types';
import AddressItem from './AddressItem';
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
import GeolocationItem from './GeolocationItem';
import { PageRouteEnum } from '../auth/ProtectedRouter';
import { GenericFunctionArgs } from '../../common/GenericTypes';
import { type GenericFunctionArgs } from '../../common/GenericTypes';
import SimilarAddressItem from './SimilarAddressItem';
import filterFarAwayMetaAddresses from './utils/FilterFarAwayMetaAddresses';
import filterNearMetaAddresses from './utils/FilterNearMetaAddresses';
import { useAppSelector } from '../../hooks';
import { MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES } from './constants';
interface IAddressContainer {
groupedAddressList?: IGroupedAddressesItem[];
caseId?: string;
handlePageRouting?: GenericFunctionArgs;
addressFeedbacks?: any;
}
const SeparatorBorderComponent = () => {
return <View style={[styles.borderLine, GenericStyles.mv16]}></View>;
};
function SeparatorBorderComponent() {
return <View style={[styles.borderLine, GenericStyles.mv16]} />;
}
const AddressContainer: React.FC<IAddressContainer> = ({
groupedAddressList,
caseId,
handlePageRouting,
addressFeedbacks,
}) => {
const { currentGeolocationCoordinates } = useAppSelector((state) => ({
currentGeolocationCoordinates: state.foregroundService?.deviceGeolocationCoordinate,
}));
const handleOpenOldFeedbacks = (groupedAddress: IGroupedAddressesItem) => {
const similarAddressIds = groupedAddress?.similarAddresses?.map((item) => item.id) || [];
const commonParams = {
@@ -46,51 +56,67 @@ const AddressContainer: React.FC<IAddressContainer> = ({
return (
<View>
{groupedAddressList?.map((groupedAddress: IGroupedAddressesItem, index: number) => (
<View>
<Accordion
accordionStyle={[GenericStyles.pv24, GenericStyles.ph16]}
accordionHeader={
<AddressItem
caseId={caseId}
key={groupedAddress.metaAddress?.id}
addressItem={groupedAddress.metaAddress}
isGroupedAddress={true}
showRelativeDistance={true}
showActionButtons={true}
contactabilityStatus={groupedAddress.contactabilityStatus}
groupedAddressIdx={index + 1}
handleOldFeedbackRouting={() => handleOpenOldFeedbacks(groupedAddress)}
handleCloseRouting={() => handlePageRouting?.(PageRouteEnum.ADDRESS_GEO)}
/>
}
onExpanded={(isExpanded) =>
handleAccordionExpand(isExpanded, groupedAddress?.metaAddress?.id)
}
>
{groupedAddress?.similarAddresses.length ? (
<View>
<SeparatorBorderComponent />
<Text
style={[styles.textContainer, styles.accordionDetailHeading, GenericStyles.pb8]}
>
Similar addresses
</Text>
{groupedAddress.similarAddresses.map((similarAddress: IAddress) => (
<View style={{ flex: 1 }}>
<AddressItem
key={similarAddress?.id}
addressItem={similarAddress}
containerStyle={styles.card}
showSource={true}
/>
</View>
))}
</View>
) : null}
</Accordion>
</View>
))}
{filterNearMetaAddresses({
metaAddresses: groupedAddressList,
maximumDistance: MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES,
currentLocationCoordinates: currentGeolocationCoordinates,
})?.map((groupedAddress: IGroupedAddressesItem, index: number) => {
const lastFeedbackForAddress = addressFeedbacks.find(
(addressFeedback) =>
addressFeedback?.addressReferenceId === groupedAddress?.metaAddress?.id
);
return (
<View>
<Accordion
accordionStyle={[GenericStyles.pv24, GenericStyles.ph16]}
isExpansionDisabled={!groupedAddress?.similarAddresses.length}
touchableOpacity={0.8}
touchableDelay={50}
accordionHeader={
<AddressItem
caseId={caseId}
key={groupedAddress.metaAddress?.id}
addressItem={groupedAddress.metaAddress}
isGroupedAddress
showRelativeDistance
showActionButtons
contactabilityStatus={groupedAddress.contactabilityStatus}
groupedAddressIdx={index + 1}
handleOldFeedbackRouting={() => {
handleOpenOldFeedbacks(groupedAddress);
}}
handleCloseRouting={() => handlePageRouting?.(PageRouteEnum.ADDRESS_GEO)}
lastFeedbackForAddress={lastFeedbackForAddress}
/>
}
onExpanded={(isExpanded) => {
handleAccordionExpand(isExpanded, groupedAddress?.metaAddress?.id);
}}
>
{groupedAddress?.similarAddresses.length ? (
<View>
<SeparatorBorderComponent />
<Text
style={[styles.textContainer, styles.accordionDetailHeading, GenericStyles.pb8]}
>
Similar addresses
</Text>
{groupedAddress.similarAddresses.map((similarAddress: IAddress) => (
<View style={{ flex: 1 }}>
<SimilarAddressItem
key={similarAddress?.id}
addressItem={similarAddress}
containerStyle={styles.card}
showSource
/>
</View>
))}
</View>
) : null}
</Accordion>
</View>
);
})}
</View>
);
};

View File

@@ -1,7 +1,15 @@
import React from 'react';
import { View, StyleSheet, ViewStyle, TouchableOpacity, ScrollView, Linking } from 'react-native';
import {
View,
StyleSheet,
type ViewStyle,
TouchableOpacity,
ScrollView,
Linking,
} from 'react-native';
import dayjs from 'dayjs';
import Text from '../../../RN-UI-LIB/src/components/Text';
import { IAddress, IGeolocationCoordinate } from '../../types/addressGeolocation.types';
import { type IAddress, type IGeolocationCoordinate } from '../../types/addressGeolocation.types';
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import {
@@ -18,10 +26,11 @@ import { updatePreDefinedCaseFormJourney } from '../../reducer/caseReducer';
import { getCollectionFeedbackOnAddressPreDefinedJourney } from '../../components/utlis/addressGeolocationUtils';
import { _map } from '../../../RN-UI-LIB/src/utlis/common';
import { getTemplateRoute, navigateToScreen } from '../../components/utlis/navigationUtlis';
import { GenericFunctionArgs } from '../../common/GenericTypes';
import { type GenericFunctionArgs } from '../../common/GenericTypes';
import { toast } from '../../../RN-UI-LIB/src/components/toast';
import { ToastMessages } from '../allCases/constants';
import AddressSource from './AddressSource';
import relativeDistanceFormatter from './utils/relativeDistanceFormatter';
interface IAddressItem {
addressItem: IAddress;
@@ -35,9 +44,10 @@ interface IAddressItem {
handleCloseRouting?: GenericFunctionArgs;
handleOldFeedbackRouting?: GenericFunctionArgs;
showSource?: boolean;
lastFeedbackForAddress?: any;
}
const AddressItem = ({
function AddressItem({
addressItem,
containerStyle = {},
isGroupedAddress = false,
@@ -49,7 +59,8 @@ const AddressItem = ({
handleCloseRouting,
handleOldFeedbackRouting,
showSource = false,
}: IAddressItem) => {
lastFeedbackForAddress,
}: IAddressItem) {
const { currentGeolocationCoordinates, prefilledAddressScreenTemplate } = useAppSelector(
(state) => ({
currentGeolocationCoordinates: state.foregroundService?.deviceGeolocationCoordinate,
@@ -72,22 +83,8 @@ const AddressItem = ({
addressGeolocationCoordinated
);
}
const openGeolocation = () => {
const geolocationUrl = getGoogleMapUrl(addressItem?.latitude, addressItem?.longitude);
if (!geolocationUrl) {
toast({
type: 'error',
text1: ToastMessages.GEOLOCATION_COORDINATES_INCORRECT,
});
return;
}
return Linking.openURL(geolocationUrl);
};
const handleAddFeedback = () => {
if (prefilledAddressScreenTemplate) {
if (prefilledAddressScreenTemplate != null) {
const addressKey = '{{addressReferenceId}}';
const { visitedWidgets, widgetContext } = getCollectionFeedbackOnAddressPreDefinedJourney(
prefilledAddressScreenTemplate,
@@ -104,14 +101,13 @@ const AddressItem = ({
})
);
if (visitedWidgets.length) {
_map(visitedWidgets, (visited) =>
_map(visitedWidgets, (visited) => {
navigateToScreen(getTemplateRoute(visited, CaseAllocationType.COLLECTION_CASE), {
caseId: caseId,
caseId,
journey: TaskTitleUIMapping.COLLECTION_FEEDBACK,
handleCloseRouting: handleCloseRouting,
})
);
return;
handleCloseRouting,
});
});
}
}
};
@@ -132,6 +128,7 @@ const AddressItem = ({
<Text
style={{
color: groupedAddressIdx === 1 ? COLORS.BASE.BLUE : COLORS.TEXT.DARK,
fontWeight: 'bold',
}}
>
{groupedAddressIdx}
@@ -141,62 +138,111 @@ const AddressItem = ({
<View
style={[styles.container, GenericStyles.columnDirection, containerStyle, , { flex: 1 }]}
>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={[styles.textContainer, styles.cardBoldTitle]}
>
{sanitizeString([addressItem?.pinCode, addressItem?.city].filter(Boolean).join(', '))}
</Text>
<View style={[styles.container, GenericStyles.row, { alignItems: 'center' }]}>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={[styles.textContainer, styles.cardBoldTitle, { fontWeight: 'bold' }]}
>
{sanitizeString([addressItem?.pinCode, addressItem?.city].filter(Boolean).join(', '))}
</Text>
<Text numberOfLines={1} ellipsizeMode="tail" style={[GenericStyles.ml4]}>
{showRelativeDistance && relativeDistanceBwLatLong ? (
<>({relativeDistanceFormatter(relativeDistanceBwLatLong)} km away)</>
) : null}
</Text>
</View>
{lastFeedbackForAddress?.feedbackPresent ? (
<View style={[styles.container, { marginVertical: 8 }]}>
<View>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={{
fontSize: 12,
}}
>
<Text style={{ fontSize: 12 }}>Last visit: </Text>
<Text
style={{
fontSize: 12,
fontWeight: 'bold',
color: lastFeedbackForAddress?.feedbackColourCode
? lastFeedbackForAddress?.feedbackColourCode
: COLORS.TEXT.BLACK,
}}
>
{' '}
{lastFeedbackForAddress?.latestFeedbackStatus || ''}{' '}
</Text>
</Text>
</View>
<View style={[GenericStyles.row, styles.container]}>
<Text style={{ fontSize: 12 }}>Visited on: </Text>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={{ fontSize: 12, fontWeight: 'bold' }}
>
{dayjs(lastFeedbackForAddress?.latestFeedbackTimestamp).format('DD MMM, YYYY')}
</Text>
</View>
</View>
) : (
<View>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={{
fontSize: 12,
}}
>
<Text style={{ fontSize: 12 }}>Last visit: </Text>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={{ color: '#E92C2C', fontSize: 12, fontWeight: 'bold' }}
>
Unvisited address
</Text>
</Text>
</View>
)}
{isGroupedAddress && contactabilityStatus ? (
<View style={GenericStyles.mv8}>
<Tag variant={TagVariant.yellow} text={contactabilityStatus} />
</View>
) : null}
<Text
numberOfLines={3}
ellipsizeMode="tail"
style={[styles.textContainer, styles.cardLightTitle]}
>
<Text style={[styles.textContainer, styles.cardLightTitle, { color: COLORS.TEXT.BLACK }]}>
{sanitizeString(addressItem?.addressText)}
</Text>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={[styles.textContainer, styles.cardFooterText, GenericStyles.fontSize12]}
>
{sanitizeString(`${dateFormat(new Date(addressItem?.updatedAt), BUSINESS_DATE_FORMAT)}`)}
{showRelativeDistance && relativeDistanceBwLatLong ? (
<>
<Text style={GenericStyles.tiny}>&nbsp;&nbsp;&#9679;&nbsp;&nbsp;</Text>
{!isNaN(relativeDistanceBwLatLong) ? relativeDistanceBwLatLong.toFixed(2) : '--'} km
away
</>
) : null}
{showSource ? <AddressSource addressItem={addressItem} /> : null}
</Text>
{showActionButtons ? (
<View style={[styles.container, GenericStyles.row, GenericStyles.pt12]}>
<TouchableOpacity
activeOpacity={0.7}
onPress={handleAddFeedback}
style={[{ flexBasis: '35%' }]}
style={[{ flexBasis: '38%' }]}
hitSlop={{ top: 25, bottom: 25, left: 15, right: 15 }}
>
<Text style={styles.actionBtn}>Add Feedback</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
onPress={handleOldFeedbackRouting}
style={[{ flexBasis: '30%' }]}
>
<Text style={styles.actionBtn}>Old feedbacks</Text>
</TouchableOpacity>
{lastFeedbackForAddress?.feedbackPresent ? (
<TouchableOpacity
activeOpacity={0.7}
onPress={handleOldFeedbackRouting}
style={[{ flexBasis: '38%' }]}
hitSlop={{ top: 25, bottom: 25, left: 15, right: 15 }}
>
<Text style={styles.actionBtn}>Old feedbacks</Text>
</TouchableOpacity>
) : null}
</View>
) : null}
</View>
</View>
);
};
}
const styles = StyleSheet.create({
container: {

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Linking, StyleSheet, Text, TextStyle, TouchableOpacity, View } from 'react-native';
import { Linking, StyleSheet, Text, type TextStyle, TouchableOpacity, View } from 'react-native';
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import {
@@ -12,12 +12,16 @@ import {
getGoogleMapUrl,
sanitizeString,
} from '../../components/utlis/commonFunctions';
import { IGeoLocation, IGeolocationCoordinate } from '../../types/addressGeolocation.types';
import {
type IGeoLocation,
type IGeolocationCoordinate,
} from '../../types/addressGeolocation.types';
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
import { RootState } from '../../store/store';
import { type RootState } from '../../store/store';
import { useAppSelector } from '../../hooks';
import CustomLocationSmallIcon from '../../assets/icons/CustomLocationSmallIcon';
import relativeDistanceFormatter from './utils/relativeDistanceFormatter';
interface IGeolocationItem {
geolocationItem: IGeoLocation;
@@ -26,16 +30,16 @@ interface IGeolocationItem {
containerStyle?: TextStyle;
}
const SeparatorBorderComponent = () => {
return <View style={[styles.borderLine, { marginHorizontal: 16 }]}></View>;
};
function SeparatorBorderComponent() {
return <View style={[styles.borderLine, { marginHorizontal: 16 }]} />;
}
const GeolocationItem = ({
function GeolocationItem({
geolocationItem,
showSeparator = true,
highlightIcon = false,
containerStyle,
}: IGeolocationItem) => {
}: IGeolocationItem) {
const currentGeolocationCoordinates: IGeolocationCoordinate = useAppSelector(
(state: RootState) => state.foregroundService?.deviceGeolocationCoordinate
);
@@ -53,7 +57,7 @@ const GeolocationItem = ({
const locationDate = dateFormat(new Date(geolocationItem?.timestamp), BUSINESS_DATE_FORMAT);
const locationTime = dateFormat(new Date(geolocationItem?.timestamp), BUSINESS_TIME_FORMAT);
const openGeolocation = () => {
const openGeolocation = async () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VIEW_MAP_GEO_CLICKED, {
latitude: geolocationItem.latitude,
longitude: geolocationItem.longitude,
@@ -61,31 +65,28 @@ const GeolocationItem = ({
const geolocationUrl = getGoogleMapUrl(geolocationItem?.latitude, geolocationItem?.longitude);
if (!geolocationUrl) return;
return Linking.openURL(geolocationUrl);
return await Linking.openURL(geolocationUrl);
};
return (
<View style={[GenericStyles.columnDirection, styles.container, containerStyle]}>
<View style={[GenericStyles.row]}>
{
<View
style={[
styles.iconContainer,
{
backgroundColor: highlightIcon ? COLORS.BACKGROUND.BLUE : COLORS.BACKGROUND.SILVER,
},
]}
>
<CustomLocationSmallIcon
fillColor={highlightIcon ? COLORS.BASE.BLUE : COLORS.TEXT.LIGHT}
/>
</View>
}
<View
style={[
styles.iconContainer,
{
backgroundColor: highlightIcon ? COLORS.BACKGROUND.BLUE : COLORS.BACKGROUND.SILVER,
},
]}
>
<CustomLocationSmallIcon
fillColor={highlightIcon ? COLORS.BASE.BLUE : COLORS.TEXT.LIGHT}
/>
</View>
<View style={[GenericStyles.columnDirection]}>
<View style={[styles.contentContainer]}>
<Text style={[styles.titleText, GenericStyles.mb4]}>
{!isNaN(relativeDistanceBwLatLong) ? relativeDistanceBwLatLong.toFixed(2) : '--'} km
away
{relativeDistanceFormatter(relativeDistanceBwLatLong)} km away
</Text>
<Text style={[styles.textContainer, GenericStyles.mb12]}>
{sanitizeString(locationDate)}
@@ -105,7 +106,7 @@ const GeolocationItem = ({
) : null}
</View>
);
};
}
const styles = StyleSheet.create({
container: {

View File

@@ -137,7 +137,7 @@ const NewAddressContainer: React.FC<INewAddressContainer> = ({ route: routeParam
title="House, flat, building"
onChangeText={(s) => onChange(s)}
onBlur={onBlur}
placeholder="Enter here"
placeholder="Eg. A-101"
value={value}
required
/>
@@ -155,7 +155,7 @@ const NewAddressContainer: React.FC<INewAddressContainer> = ({ route: routeParam
title="Street, locality, colony"
onChangeText={(s) => onChange(s)}
onBlur={onBlur}
placeholder="Enter here"
placeholder="Eg. Chandni Chowk"
value={value}
required
/>
@@ -175,7 +175,7 @@ const NewAddressContainer: React.FC<INewAddressContainer> = ({ route: routeParam
maxLength={6}
onChangeText={(s) => onChange(s)}
onBlur={onBlur}
placeholder="Enter here"
placeholder="Eg. 110006"
value={value}
required
/>
@@ -193,7 +193,7 @@ const NewAddressContainer: React.FC<INewAddressContainer> = ({ route: routeParam
title="City"
onChangeText={(s) => onChange(s)}
onBlur={onBlur}
placeholder="Enter here"
placeholder="Eg. Delhi"
value={value}
required
/>

View File

@@ -0,0 +1,244 @@
import React from 'react';
import {
View,
StyleSheet,
type ViewStyle,
TouchableOpacity,
ScrollView,
Linking,
} from 'react-native';
import Text from '../../../RN-UI-LIB/src/components/Text';
import { type IAddress, type IGeolocationCoordinate } from '../../types/addressGeolocation.types';
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import {
getDistanceFromLatLonInKm,
getGoogleMapUrl,
sanitizeString,
} from '../../components/utlis/commonFunctions';
import { BUSINESS_DATE_FORMAT, dateFormat } from '../../../RN-UI-LIB/src/utlis/dates';
import { useAppDispatch, useAppSelector } from '../../hooks';
import { RootState } from '../../store/store';
import Tag, { TagVariant } from '../../../RN-UI-LIB/src/components/Tag';
import { CaseAllocationType, TaskTitleUIMapping } from '../allCases/interface';
import { updatePreDefinedCaseFormJourney } from '../../reducer/caseReducer';
import { getCollectionFeedbackOnAddressPreDefinedJourney } from '../../components/utlis/addressGeolocationUtils';
import { _map } from '../../../RN-UI-LIB/src/utlis/common';
import { getTemplateRoute, navigateToScreen } from '../../components/utlis/navigationUtlis';
import { type GenericFunctionArgs } from '../../common/GenericTypes';
import { toast } from '../../../RN-UI-LIB/src/components/toast';
import { ToastMessages } from '../allCases/constants';
import AddressSource from './AddressSource';
interface IAddressItem {
addressItem: IAddress;
containerStyle?: ViewStyle;
isGroupedAddress?: boolean;
showRelativeDistance?: boolean;
showActionButtons?: boolean;
contactabilityStatus?: string;
groupedAddressIdx?: number;
caseId?: string;
handleCloseRouting?: GenericFunctionArgs;
handleOldFeedbackRouting?: GenericFunctionArgs;
showSource?: boolean;
}
function SimilarAddressItem({
addressItem,
containerStyle = {},
isGroupedAddress = false,
showActionButtons = false,
showRelativeDistance = false,
contactabilityStatus = '',
groupedAddressIdx = 1,
caseId,
handleCloseRouting,
handleOldFeedbackRouting,
showSource = false,
}: IAddressItem) {
const { currentGeolocationCoordinates, prefilledAddressScreenTemplate } = useAppSelector(
(state) => ({
currentGeolocationCoordinates: state.foregroundService?.deviceGeolocationCoordinate,
prefilledAddressScreenTemplate:
state.case.templateData[CaseAllocationType.COLLECTION_CASE]?.prefilledAddressScreenTemplate,
})
);
const dispatch = useAppDispatch();
let relativeDistanceBwLatLong = 0;
if (isGroupedAddress) {
const addressGeolocationCoordinated: IGeolocationCoordinate = {
latitude: addressItem.latitude,
longitude: addressItem.longitude,
};
relativeDistanceBwLatLong = getDistanceFromLatLonInKm(
currentGeolocationCoordinates,
addressGeolocationCoordinated
);
}
const openGeolocation = async () => {
const geolocationUrl = getGoogleMapUrl(addressItem?.latitude, addressItem?.longitude);
if (!geolocationUrl) {
toast({
type: 'error',
text1: ToastMessages.GEOLOCATION_COORDINATES_INCORRECT,
});
return;
}
return await Linking.openURL(geolocationUrl);
};
const handleAddFeedback = () => {
if (prefilledAddressScreenTemplate != null) {
const addressKey = '{{addressReferenceId}}';
const { visitedWidgets, widgetContext } = getCollectionFeedbackOnAddressPreDefinedJourney(
prefilledAddressScreenTemplate,
addressKey,
addressItem.id
);
dispatch(
updatePreDefinedCaseFormJourney({
caseId,
journeyId: TaskTitleUIMapping.COLLECTION_FEEDBACK,
visitedWidgets,
widgetContext,
})
);
if (visitedWidgets.length) {
_map(visitedWidgets, (visited) => {
navigateToScreen(getTemplateRoute(visited, CaseAllocationType.COLLECTION_CASE), {
caseId,
journey: TaskTitleUIMapping.COLLECTION_FEEDBACK,
handleCloseRouting,
});
});
}
}
};
return (
<View style={[GenericStyles.row]}>
{isGroupedAddress ? (
<View
style={[
styles.iconContainer,
{
borderColor: groupedAddressIdx === 1 ? COLORS.BORDER.BLUE_CC : COLORS.BORDER.PRIMARY,
backgroundColor:
groupedAddressIdx === 1 ? COLORS.BACKGROUND.BLUE : COLORS.BACKGROUND.SILVER,
},
]}
>
<Text
style={{
color: groupedAddressIdx === 1 ? COLORS.BASE.BLUE : COLORS.TEXT.DARK,
}}
>
{groupedAddressIdx}
</Text>
</View>
) : null}
<View
style={[styles.container, GenericStyles.columnDirection, containerStyle, , { flex: 1 }]}
>
<Text style={[styles.textContainer, styles.cardBoldTitle, { fontWeight: 'bold' }]}>
{sanitizeString([addressItem?.pinCode, addressItem?.city].filter(Boolean).join(', '))}
</Text>
{isGroupedAddress && contactabilityStatus ? (
<View style={GenericStyles.mv8}>
<Tag variant={TagVariant.yellow} text={contactabilityStatus} />
</View>
) : null}
<Text
numberOfLines={3}
ellipsizeMode="tail"
style={[styles.textContainer, styles.cardLightTitle, { color: COLORS.TEXT.BLACK }]}
>
{sanitizeString(addressItem?.addressText)}
</Text>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={[styles.textContainer, styles.cardFooterText, GenericStyles.fontSize12]}
>
{sanitizeString(`${dateFormat(new Date(addressItem?.updatedAt), BUSINESS_DATE_FORMAT)}`)}
{showRelativeDistance && relativeDistanceBwLatLong ? (
<>
<Text style={GenericStyles.tiny}>&nbsp;&nbsp;&#9679;&nbsp;&nbsp;</Text>
{!isNaN(relativeDistanceBwLatLong) ? relativeDistanceBwLatLong.toFixed(2) : '--'} km
away
</>
) : null}
{showSource ? <AddressSource addressItem={addressItem} /> : null}
</Text>
{showActionButtons ? (
<View style={[styles.container, GenericStyles.row, GenericStyles.pt12]}>
<TouchableOpacity
activeOpacity={0.7}
onPress={handleAddFeedback}
style={[{ flexBasis: '35%' }]}
>
<Text style={styles.actionBtn}>Add Feedback</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
onPress={handleOldFeedbackRouting}
style={[{ flexBasis: '30%' }]}
>
<Text style={styles.actionBtn}>Old feedbacks</Text>
</TouchableOpacity>
</View>
) : null}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: COLORS.BACKGROUND.PRIMARY,
},
textContainer: {
fontSize: 14,
lineHeight: 20,
},
iconContainer: {
marginRight: 8,
borderRadius: 12.5,
width: 25,
height: 25,
backgroundColor: COLORS.BACKGROUND.GREY_D9,
alignItems: 'center',
borderWidth: 0.5,
},
cardBoldTitle: {
fontWeight: '500',
color: COLORS.TEXT.DARK,
},
cardLightTitle: {
fontWeight: '400',
color: '#BCBCBC',
},
cardFooterText: {
fontWeight: '400',
color: COLORS.TEXT.LIGHT,
},
actionBtn: {
fontSize: 13,
lineHeight: 20,
fontWeight: '500',
color: COLORS.TEXT.BLUE,
},
dotStyle: {
fontSize: 11,
color: COLORS.TEXT.LIGHT,
},
});
export default SimilarAddressItem;

View File

@@ -5,7 +5,7 @@ import { goBack, navigateToScreen } from '../../components/utlis/navigationUtlis
import useIsOnline from '../../hooks/useIsOnline';
import OfflineScreen from '../../common/OfflineScreen';
import { getUngroupedAddress } from '../../action/addressGeolocationAction';
import { IAddress } from '../../types/addressGeolocation.types';
import { type IAddress } from '../../types/addressGeolocation.types';
import AddressItem from './AddressItem';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import { PageRouteEnum } from '../auth/ProtectedRouter';
@@ -16,6 +16,10 @@ import SuspenseLoader from '../../../RN-UI-LIB/src/components/suspense_loader/Su
import LineLoader from '../../../RN-UI-LIB/src/components/suspense_loader/LineLoader';
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import filterFarAwayMetaAddresses from './utils/FilterFarAwayMetaAddresses';
import { useAppSelector } from '../../hooks';
import { CaseAllocationType } from '../allCases/interface';
import SimilarAddressItem from './SimilarAddressItem';
const PAGE_TITLE = 'Additional addresses';
@@ -25,13 +29,14 @@ interface IUngroupedAddress {
loanAccountNumber: string;
caseId: string;
customerReferenceId: string;
fetchUngroupedAddress: (lan: string) => Promise<IAddress[]>;
};
};
}
const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routeParams }) => {
const {
params: { loanAccountNumber, caseId, customerReferenceId },
params: { loanAccountNumber, caseId, customerReferenceId, fetchUngroupedAddress },
} = routeParams;
const [loading, setLoading] = useState(false);
@@ -47,9 +52,10 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
useEffect(() => {
if (isOnline) {
setLoading(true);
getUngroupedAddress(loanAccountNumber)
fetchUngroupedAddress(loanAccountNumber)
.then((res: IAddress[]) => {
setUngroupedAddressList(res);
setLoading(true);
})
.finally(() => {
setLoading(false);
@@ -90,7 +96,7 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
<>
{[...Array(8).keys()].map(() => (
<LineLoader
width={'100%'}
width="100%"
height={75}
style={[GenericStyles.br6, { marginBottom: 20 }]}
/>
@@ -104,16 +110,18 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
<View>
<AddressItem
caseId={caseId}
showRelativeDistance={true}
showRelativeDistance
containerStyle={styles.addressItemContainer}
key={ungroupedAddressItem?.id}
addressItem={ungroupedAddressItem}
showActionButtons={true}
handleOldFeedbackRouting={() => handleOpenOldFeedbacks(ungroupedAddressItem)}
handleCloseRouting={() =>
navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams)
}
showSource={true}
showActionButtons
handleOldFeedbackRouting={() => {
handleOpenOldFeedbacks(ungroupedAddressItem);
}}
handleCloseRouting={() => {
navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams);
}}
showSource
/>
</View>
))}

View File

@@ -0,0 +1 @@
export const MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES = 100;

View File

@@ -1,10 +1,10 @@
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { SafeAreaView, ScrollView, StyleSheet, Text, 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 { useSelector } from 'react-redux';
import { RootState } from '../../store/store';
import { type RootState } from '../../store/store';
import GeolocationContainer from './GeolocationContainer';
import AddressContainer from './AddressContainer';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
@@ -23,6 +23,11 @@ import SuspenseLoader from '../../../RN-UI-LIB/src/components/suspense_loader/Su
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';
const PAGE_TITLE = 'All addresses';
@@ -41,18 +46,20 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams
params: { loanAccountNumber, customerReferenceId, caseId },
} = routeParams;
const [ungroupedAddress, setUngroupedAddress] = useState<IAddress[]>([]);
const [retryBtnToggle, setRetryBtnToggle] = useState(false);
const dispatch = useAppDispatch();
const isOnline = useIsOnline();
const { addressGeolocation, isLoading, currentGeolocationCoordinates } = useSelector(
(state: RootState) => ({
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 commonParams = {
loanAccountNumber,
@@ -73,11 +80,40 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ADD_NEW_ADDRESS_CLICKED, commonParams);
};
const fetchUngroupedAddress = async (loanAccountNumber) =>
await getUngroupedAddress(loanAccountNumber).then((res: IAddress[]) => {
const farAwayAddresses = filterFarAwayMetaAddresses({
metaAddresses: addressGeolocation?.groupedAddresses,
maximumDistance: MAXIMUM_ALLOWED_DISTANCE_FOR_GROUPED_ADDRESSES,
currentLocationCoordinates: currentGeolocationCoordinates,
});
const metaAddresses = farAwayAddresses.map((farAwayAddress) => farAwayAddress?.metaAddress);
setUngroupedAddress([...res, ...metaAddresses]);
return [...res, ...metaAddresses];
});
useEffect(() => {
async function getUngroupedAddress() {
await fetchUngroupedAddress(loanAccountNumber);
return null;
}
if (addressGeolocation?.groupedAddresses?.length > 0) {
getUngroupedAddress();
}
}, [addressGeolocation?.groupedAddresses, isLoading]);
useEffect(() => {
if (isOnline) {
dispatch(setAddressLoading({ loanAccountNumbers: [loanAccountNumber], isLoading: true }));
dispatch(
getCaseUnifiedData([loanAccountNumber], [UnifiedCaseDetailsTypes.ADDRESS_AND_GEOLOCATIONS])
getCaseUnifiedData(
[loanAccountNumber],
[
UnifiedCaseDetailsTypes.ADDRESS_AND_GEOLOCATIONS,
UnifiedCaseDetailsTypes.INCLUDE_ADDRESS_FEEDBACK,
]
)
);
}
}, [retryBtnToggle, isOnline]);
@@ -87,9 +123,9 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams
if (!currentGeolocationCoordinates.latitude || !currentGeolocationCoordinates.longitude) {
(async () => {
const location = await CaptureGeolocation.fetchLocation(Date.now() + '', 0);
const location = await CaptureGeolocation.fetchLocation(`${Date.now()}`, 0);
if (location) {
if (location != null) {
dispatch(
setDeviceGeolocation({
latitude: location.latitude,
@@ -109,68 +145,81 @@ const AddressGeolocation: React.FC<IAddressGeolocation> = ({ route: routeParams
}
return (
<ScrollView>
<NavigationHeader title={PAGE_TITLE} onBack={goBack} />
<SuspenseLoader
loading={isLoading}
fallBack={
<>
{[...Array(8).keys()].map(() => (
<LineLoader
width={'100%'}
height={75}
style={[GenericStyles.br6, { marginBottom: 20 }]}
/>
))}
</>
}
>
<View>
<AddressContainer
groupedAddressList={addressGeolocation.groupedAddresses}
caseId={caseId}
handlePageRouting={handleRouting}
/>
<View
style={[
GenericStyles.row,
GenericStyles.pv24,
GenericStyles.ph16,
{ backgroundColor: COLORS.BACKGROUND.PRIMARY },
]}
<Layout>
<SafeAreaView style={[GenericStyles.fill]}>
<NavigationHeader title={PAGE_TITLE} onBack={goBack} />
<ScrollView>
<SuspenseLoader
loading={isLoading}
fallBack={
<>
{[...Array(8).keys()].map(() => (
<LineLoader
width="100%"
height={75}
style={[GenericStyles.br6, { marginBottom: 20 }]}
/>
))}
</>
}
>
<View style={styles.iconContainer}>
<HomeIconSmall />
</View>
<View style={[GenericStyles.columnDirection]}>
<View style={[styles.contentContainer]}>
<Text style={[styles.titleText, GenericStyles.mb4]}>Additional addresses</Text>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => handleRouting(PageRouteEnum.ADDITIONAL_ADDRESSES)}
<View>
<AddressContainer
groupedAddressList={addressGeolocation.groupedAddresses}
addressFeedbacks={addressFeedbacks}
caseId={caseId}
handlePageRouting={handleRouting}
/>
{ungroupedAddress?.length > 0 ? (
<View
style={[
GenericStyles.row,
GenericStyles.pv24,
GenericStyles.ph16,
{ backgroundColor: COLORS.BACKGROUND.PRIMARY },
]}
>
<Text style={styles.actionBtn}>View all addresses</Text>
</TouchableOpacity>
</View>
</View>
</View>
<Text style={[styles.textContainer, GenericStyles.p16]}>User geolocations</Text>
<GeolocationContainer geolocationList={addressGeolocation.geoLocations} />
<View style={[GenericStyles.p16, styles.btnContainer]}>
<Button
title="New address"
style={GenericStyles.w100}
onPress={handleNewAddressCta}
leftIcon={
<View style={GenericStyles.mr10}>
<PlusIcon />
<View style={styles.iconContainer}>
<HomeIconSmall />
</View>
<View style={[GenericStyles.columnDirection]}>
<View style={[styles.contentContainer]}>
<Text style={[styles.titleText, GenericStyles.mb4]}>
Additional addresses
</Text>
<TouchableOpacity
activeOpacity={0.2}
onPress={() => {
handleRouting(PageRouteEnum.ADDITIONAL_ADDRESSES, {
fetchUngroupedAddress,
});
}}
>
<Text style={styles.actionBtn}>View all addresses</Text>
</TouchableOpacity>
</View>
</View>
</View>
}
/>
</View>
) : null}
<Text style={[styles.textContainer, GenericStyles.p16]}>User geolocations</Text>
<GeolocationContainer geolocationList={addressGeolocation.geoLocations} />
</View>
</SuspenseLoader>
</ScrollView>
<View style={[GenericStyles.p16, styles.btnContainer]}>
<Button
title="New address"
style={GenericStyles.w100}
onPress={handleNewAddressCta}
leftIcon={
<View style={GenericStyles.mr10}>
<PlusIcon />
</View>
}
/>
</View>
</SuspenseLoader>
</ScrollView>
</SafeAreaView>
</Layout>
);
};

View File

@@ -0,0 +1,24 @@
import { getDistanceFromLatLonInKm } from '../../../components/utlis/commonFunctions';
interface FilterFarAwayMetaAddressesParams {
metaAddresses: any[];
maximumDistance: number;
currentLocationCoordinates: { latitude: number; longitude: number };
}
const filterFarAwayMetaAddresses = ({
metaAddresses = [],
maximumDistance,
currentLocationCoordinates,
}: FilterFarAwayMetaAddressesParams) =>
metaAddresses?.filter((metaAddress) => {
const distance = getDistanceFromLatLonInKm(currentLocationCoordinates, {
latitude: metaAddress?.metaAddress?.latitude,
longitude: metaAddress?.metaAddress?.longitude,
});
if (distance >= maximumDistance) {
return true;
}
return false;
});
export default filterFarAwayMetaAddresses;

View File

@@ -0,0 +1,24 @@
import { getDistanceFromLatLonInKm } from '../../../components/utlis/commonFunctions';
interface FilterNearMetaAddressesParams {
metaAddresses: any[];
maximumDistance: number;
currentLocationCoordinates: { latitude: number; longitude: number };
}
const filterNearMetaAddresses = ({
metaAddresses = [],
maximumDistance,
currentLocationCoordinates,
}: FilterNearMetaAddressesParams) =>
metaAddresses?.filter((metaAddress) => {
const distance = getDistanceFromLatLonInKm(currentLocationCoordinates, {
latitude: metaAddress?.metaAddress?.latitude,
longitude: metaAddress?.metaAddress?.longitude,
});
if (distance <= maximumDistance) {
return true;
}
return false;
});
export default filterNearMetaAddresses;

View File

@@ -0,0 +1,13 @@
const MAXIMUM_DISTANCE_WITH_DECIMAL = 10;
const relativeDistanceFormatter = (relativeDistance: number) => {
if (isNaN(relativeDistance)) {
return '--';
}
if (relativeDistance >= MAXIMUM_DISTANCE_WITH_DECIMAL) {
return Math.round(relativeDistance, 0);
}
return relativeDistance.toFixed(2);
};
export default relativeDistanceFormatter;

View File

@@ -166,10 +166,6 @@ const ListItem: React.FC<IListItem> = (props) => {
]
);
if (!isCompleted && caseStatus === CaseStatuses.CLOSED) {
return null;
}
const isCaseItemPinnedMainView = getCaseItemAvatarCaseDetailObj.isPinned && allCasesView;
const caseCompleted = COMPLETED_STATUSES.includes(caseStatus);

View File

@@ -13,7 +13,17 @@ interface ICollectionCaseData {
}
const CollectionCaseData: React.FC<ICollectionCaseData> = ({ caseData }) => {
const { fatherName, currentDpd, loanAccountNumber, dpdBucket, pos, collectionTag } = caseData;
const {
fatherName,
currentDpd,
loanAccountNumber,
dpdBucket,
pos,
collectionTag,
employmentDetail,
} = caseData;
const showEmploymentDetails = false;
return (
<View>
@@ -37,6 +47,23 @@ const CollectionCaseData: React.FC<ICollectionCaseData> = ({ caseData }) => {
POS {formatAmount(pos)}
</Text>
</View>
{showEmploymentDetails ? (
<View style={[GenericStyles.row, GenericStyles.alignCenter, GenericStyles.mt4]}>
{employmentDetail?.employmentType && (
<Text style={[styles.greyText]} small>
{employmentDetail.employmentType}
</Text>
)}
{employmentDetail?.employmentType && employmentDetail?.employerName && (
<View style={styles.lineStyle} />
)}
{employmentDetail?.employerName && (
<Text style={[styles.greyText]} small numberOfLines={1}>
{employmentDetail.employerName}
</Text>
)}
</View>
) : null}
{collectionTag ? (
<Text style={[styles.greyText]} small>
{collectionTag}

View File

@@ -214,6 +214,8 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
<Accordion
accordionStyle={[GenericStyles.pv12, GenericStyles.br8, getShadowStyle(4)]}
isActive={feedback.referenceId === activeFeedbackReferenceId}
touchableDelay={50}
touchableOpacity={0.8}
accordionHeader={
<FeedbackDetailItem
key={feedback.referenceId}

View File

@@ -163,6 +163,11 @@ export enum PaymentStatus {
'Closed' = 'Closed',
}
export interface EmploymentDetails {
employmentType: string;
employerName: string;
}
export interface CaseDetail {
id: string;
allocationReferenceId?: string;
@@ -212,6 +217,7 @@ export interface CaseDetail {
feedbackStatus?: FeedbackStatus;
attemptedAt?: number;
forceSubmit?: boolean;
employmentDetail?: EmploymentDetails;
}
export interface AddressesGeolocationPayload {

View File

@@ -1603,6 +1603,11 @@
resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-9.3.7.tgz#92407f679f00bae005c785a9284e61d63e292b34"
integrity sha512-+taWmE5WpBp0uS6kf+bouCx/sn89G9EpR4s2M/ReLvctVIFL2Qh8WnWfBxqK9qwgmFha/uqjSr2Gq03OOtiDcw==
"@react-native-firebase/analytics@16.4.6":
version "16.4.6"
resolved "https://registry.yarnpkg.com/@react-native-firebase/analytics/-/analytics-16.4.6.tgz#833b871014de49091cc3f50f98dbc1920b346097"
integrity sha512-Gp6kD3RCIy3f9u0pQgGfp5jGwNwLLxo2blsXbTsj/yHOE4g+leH/wojPPENs1yc2g6OMhzGYbO19/beCP6bIng==
"@react-native-firebase/app@16.4.6":
version "16.4.6"
resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-16.4.6.tgz#929a86894b401352259e21d4cb4dab1d37de2bc7"
@@ -3419,7 +3424,7 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
dayjs@1.11.9:
dayjs@^1.11.9:
version "1.11.9"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a"
integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==