TP-66615_main | Merge naster

This commit is contained in:
yashmantri
2024-06-17 17:22:16 +05:30
22 changed files with 566 additions and 80 deletions

View File

@@ -134,8 +134,8 @@ def reactNativeArchitectures() {
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
def VERSION_CODE = 165
def VERSION_NAME = "2.10.11"
def VERSION_CODE = 166
def VERSION_NAME = "2.11.0"
android {
ndkVersion rootProject.ext.ndkVersion

View File

@@ -1,7 +1,7 @@
{
"name": "AV_APP",
"version": "2.10.11",
"buildNumber": "165",
"version": "2.11.0",
"buildNumber": "166",
"private": true,
"scripts": {
"android:dev": "yarn move:dev && react-native run-android",

View File

@@ -37,6 +37,7 @@ import CosmosForegroundService from '../services/foregroundServices/foreground.s
import { resetReportees } from '../reducer/reporteesSlice';
import { resetPerformanceData } from '@reducers/agentPerformanceSlice';
import { clearStorageEngine, getStorageEngine } from '../PersistStorageEngine';
import { resetNearbyCasesData } from '@reducers/nearbyCasesSlice';
export interface GenerateOTPPayload {
phoneNumber: string;
@@ -228,6 +229,7 @@ export const handleLogout = () => async (dispatch: AppDispatch) => {
dispatch(resetProfileData());
dispatch(resetReportees());
dispatch(resetPerformanceData());
dispatch(resetNearbyCasesData());
dispatch(setSelectedAgent(MY_CASE_ITEM));
} catch (err) {
logError(err as Error, 'Logout clear session details error');

View File

@@ -0,0 +1,32 @@
import * as React from 'react';
import Svg, { Mask, Rect, G, Path, Text, TSpan } from 'react-native-svg';
import { COLORS } from '@rn-ui-lib/colors';
interface ILocationDistanceIcon {
iconColor?: string;
backgroundColor?: string;
}
const LocationDistanceIcon = (props: ILocationDistanceIcon) => {
const iconColor = props?.iconColor || COLORS.TEXT.BLUE_DARK_2;
const backgroundColor = props?.backgroundColor || COLORS.BACKGROUND.BLUE;
return (
<Svg width="16" height="20" viewBox="0 0 16 20" fill="none">
<Rect width="16" height="40" fill={backgroundColor} />
<Mask id="mask0_15800_100880" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="20">
<Rect width="16" height="20" fill="#D9D9D9" />
</Mask>
<G transform={`translate(0, -2)`}>
<G mask="url(#mask0_15800_100880)" transform="translate(0,4)">
<Path
d="M8 14.6673C6.82222 14.6673 5.86111 14.4812 5.11667 14.109C4.37222 13.7368 4 13.2562 4 12.6673C4 12.2784 4.16111 11.9395 4.48333 11.6507C4.80556 11.3618 5.25 11.134 5.81667 10.9673L6.2 12.234C6.01111 12.2895 5.83889 12.359 5.68333 12.4423C5.52778 12.5257 5.42222 12.6007 5.36667 12.6673C5.51111 12.8451 5.84444 13.0007 6.36667 13.134C6.88889 13.2673 7.43333 13.334 8 13.334C8.56667 13.334 9.11389 13.2673 9.64167 13.134C10.1694 13.0007 10.5056 12.8451 10.65 12.6673C10.5944 12.6007 10.4889 12.5257 10.3333 12.4423C10.1778 12.359 10.0056 12.2895 9.81667 12.234L10.2 10.9673C10.7667 11.134 11.2083 11.3618 11.525 11.6507C11.8417 11.9395 12 12.2784 12 12.6673C12 13.2562 11.6278 13.7368 10.8833 14.109C10.1389 14.4812 9.17778 14.6673 8 14.6673ZM8 12.6673C7.87778 12.6673 7.76667 12.6312 7.66667 12.559C7.56667 12.4868 7.49444 12.3895 7.45 12.2673C7.19444 11.4784 6.87222 10.8173 6.48333 10.284C6.09444 9.75065 5.71667 9.23954 5.35 8.75065C4.99444 8.26176 4.68611 7.75621 4.425 7.23398C4.16389 6.71176 4.03333 6.06732 4.03333 5.30065C4.03333 4.18954 4.41667 3.25065 5.18333 2.48398C5.95 1.71732 6.88889 1.33398 8 1.33398C9.11111 1.33398 10.05 1.71732 10.8167 2.48398C11.5833 3.25065 11.9667 4.18954 11.9667 5.30065C11.9667 6.06732 11.8389 6.71176 11.5833 7.23398C11.3278 7.75621 11.0167 8.26176 10.65 8.75065C10.2944 9.23954 9.91945 9.75065 9.525 10.284C9.13056 10.8173 8.80556 11.4784 8.55 12.2673C8.50556 12.3895 8.43333 12.4868 8.33333 12.559C8.23333 12.6312 8.12222 12.6673 8 12.6673ZM8 6.71732C8.38889 6.71732 8.72222 6.57843 9 6.30065C9.27778 6.02287 9.41667 5.68954 9.41667 5.30065C9.41667 4.91176 9.27778 4.57843 9 4.30065C8.72222 4.02287 8.38889 3.88398 8 3.88398C7.61111 3.88398 7.27778 4.02287 7 4.30065C6.72222 4.57843 6.58333 4.91176 6.58333 5.30065C6.58333 5.68954 6.72222 6.02287 7 6.30065C7.27778 6.57843 7.61111 6.71732 8 6.71732Z"
fill={iconColor}
/>
</G>
</G>
</Svg>
);
};
export default LocationDistanceIcon;

View File

@@ -0,0 +1,18 @@
import * as React from 'react';
import Svg, { Mask, Rect, G, Path } from 'react-native-svg';
const NewLocationIcon = () => (
<Svg width="20" height="22" viewBox="0 0 22 22" fill="none">
<Mask id="mask0_15902_102703" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="22">
<Rect width="20" height="20" fill="#D9D9D9" />
</Mask>
<G mask="url(#mask0_15902_102703)" transform="translate(0,2)">
<Path
d="M9.16667 18.2917V17.4583C7.43056 17.2639 5.94097 16.5451 4.69792 15.3021C3.45486 14.059 2.73611 12.5694 2.54167 10.8333H1.70833C1.47222 10.8333 1.27431 10.7535 1.11458 10.5938C0.954861 10.434 0.875 10.2361 0.875 10C0.875 9.76389 0.954861 9.56597 1.11458 9.40625C1.27431 9.24653 1.47222 9.16667 1.70833 9.16667H2.54167C2.73611 7.43056 3.45486 5.94097 4.69792 4.69792C5.94097 3.45486 7.43056 2.73611 9.16667 2.54167V1.70833C9.16667 1.47222 9.24653 1.27431 9.40625 1.11458C9.56597 0.954861 9.76389 0.875 10 0.875C10.2361 0.875 10.434 0.954861 10.5938 1.11458C10.7535 1.27431 10.8333 1.47222 10.8333 1.70833V2.54167C12.5694 2.73611 14.059 3.45486 15.3021 4.69792C16.5451 5.94097 17.2639 7.43056 17.4583 9.16667H18.2917C18.5278 9.16667 18.7257 9.24653 18.8854 9.40625C19.0451 9.56597 19.125 9.76389 19.125 10C19.125 10.2361 19.0451 10.434 18.8854 10.5938C18.7257 10.7535 18.5278 10.8333 18.2917 10.8333H17.4583C17.2639 12.5694 16.5451 14.059 15.3021 15.3021C14.059 16.5451 12.5694 17.2639 10.8333 17.4583V18.2917C10.8333 18.5278 10.7535 18.7257 10.5938 18.8854C10.434 19.0451 10.2361 19.125 10 19.125C9.76389 19.125 9.56597 19.0451 9.40625 18.8854C9.24653 18.7257 9.16667 18.5278 9.16667 18.2917ZM10 15.8333C11.6111 15.8333 12.9861 15.2639 14.125 14.125C15.2639 12.9861 15.8333 11.6111 15.8333 10C15.8333 8.38889 15.2639 7.01389 14.125 5.875C12.9861 4.73611 11.6111 4.16667 10 4.16667C8.38889 4.16667 7.01389 4.73611 5.875 5.875C4.73611 7.01389 4.16667 8.38889 4.16667 10C4.16667 11.6111 4.73611 12.9861 5.875 14.125C7.01389 15.2639 8.38889 15.8333 10 15.8333ZM10 13.3333C9.08333 13.3333 8.29861 13.0069 7.64583 12.3542C6.99306 11.7014 6.66667 10.9167 6.66667 10C6.66667 9.08333 6.99306 8.29861 7.64583 7.64583C8.29861 6.99306 9.08333 6.66667 10 6.66667C10.9167 6.66667 11.7014 6.99306 12.3542 7.64583C13.0069 8.29861 13.3333 9.08333 13.3333 10C13.3333 10.9167 13.0069 11.7014 12.3542 12.3542C11.7014 13.0069 10.9167 13.3333 10 13.3333Z"
fill="#F98600"
/>
</G>
</Svg>
);
export default NewLocationIcon;

View File

@@ -167,6 +167,10 @@ export const CLICKSTREAM_EVENT_NAMES = {
name: 'FA_CASE_LIST_BUTTON_CLICKED',
description: 'Clicks on case list button on bottom navigation bar',
},
FA_CASE_LIST_SORT_TAB_CHANGED: {
name: 'FA_CASE_LIST_SORT_TAB_CHANGED',
description: 'Case list sort tab changed',
},
// TODO LIST
AV_TODO_LIST_LOAD: {
@@ -704,6 +708,22 @@ export const CLICKSTREAM_EVENT_NAMES = {
name: 'FA_NEARBY_CASE_CLICKED',
description: 'FA_NEARBY_CASE_CLICKED',
},
FA_NEARBY_CASES_BANNER_DISPLAY: {
name: 'FA_NEARBY_CASES_BANNER_DISPLAY',
description: 'New Location detected banner is set to be displayed',
},
FA_NEARBY_CASES_BANNER_PULLED_TO_REFRESH: {
name: 'FA_NEARBY_CASES_BANNER_PULLED_TO_REFRESH',
description: 'User pulled the banner to refresh the list',
},
FA_NEARBY_CASES_UPDATE_SUCCESS: {
name: 'FA_NEARBY_CASES_UPDATE_SUCCESS',
description: 'Successfully updated Nearby Cases list',
},
FA_NEARBY_CASES_UPDATE_FAILED: {
name: 'FA_NEARBY_CASES_UPDATE_FAILED',
description: 'Failed to update Nearby Cases list',
},
FA_DAILY_COMMITMENT_CLICKED: {
name: 'FA_DAILY_COMMITMENT_CLICKED',
@@ -1065,8 +1085,10 @@ export const getPrefixBase64Image = (contentType: MimeType) => {
export const PrefixJpegBase64Image = getPrefixBase64Image(MimeType['image/jpeg']);
export const HEADER_HEIGHT_MAX = 132;
export const HEADER_HEIGHT_MIN = 80;
export const HEADER_HEIGHT_MAX = 200;
export const HEADER_HEIGHT_MIN = 148;
export const NEARBY_CASES_HEADER_HEIGHT_MAX = 248;
export const NEARBY_CASES_HEADER_HEIGHT_MIN = 196;
export const VISIT_PLAN_HEADER_HEIGHT_MAX = 183;
export const VISIT_PLAN_HEADER_HEIGHT_MIN = 130;
export const HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS = HEADER_HEIGHT_MAX + 50;
@@ -1074,6 +1096,7 @@ export const HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS = HEADER_HEIGHT_MIN + 50;
export const VISIT_PLAN_HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS = VISIT_PLAN_HEADER_HEIGHT_MAX + 50;
export const VISIT_PLAN_HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS = VISIT_PLAN_HEADER_HEIGHT_MIN + 50;
export const HEADER_SCROLL_DISTANCE = (HEADER_HEIGHT_MAX - HEADER_HEIGHT_MIN) * 2;
export const NEARBY_CASES_HEADER_SCROLL_DISTANCE = (NEARBY_CASES_HEADER_HEIGHT_MAX - NEARBY_CASES_HEADER_HEIGHT_MIN) * 2;
export const HEADER_SCROLL_DISTANCE_WITH_QUICK_FILTERS =
(HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS - HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS) * 2;

View File

@@ -11,7 +11,10 @@ import CosmosForegroundService, {
} from '../services/foregroundServices/foreground.service';
import useIsOnline from '../hooks/useIsOnline';
import { getSyncTime, sendCurrentGeolocationAndBuffer } from '../hooks/capturingApi';
import { isTimeDifferenceWithinRange, setAsyncStorageItem } from '../components/utlis/commonFunctions';
import {
isTimeDifferenceWithinRange,
setAsyncStorageItem,
} from '../components/utlis/commonFunctions';
import { setIsTimeSynced } from '../reducer/foregroundServiceSlice';
import { logError } from '../components/utlis/errorUtils';
import { useAppDispatch, useAppSelector } from '../hooks';
@@ -49,13 +52,21 @@ import { GlobalImageMap } from './CachedImage';
import { addClickstreamEvent } from '../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from './Constants';
import useResyncFirebase from '@hooks/useResyncFirebase';
import { imageSyncService, prepareImagesForUpload, sendImagesToServer } from '@services/imageSyncService';
import {
imageSyncService,
prepareImagesForUpload,
sendImagesToServer,
} from '@services/imageSyncService';
import { getImages } from '@components/utlis/ImageUtlis';
import getLitmusExperimentResult, { LitmusExperimentName, LitmusExperimentNameMap } from '@services/litmusExperiments.service';
import getLitmusExperimentResult, {
LitmusExperimentName,
LitmusExperimentNameMap,
} from '@services/litmusExperiments.service';
import { GLOBAL } from '@constants/Global';
import { sendAudiosToServer } from '@services/audioSyncService';
import { sendVideosToServer } from '@services/videoSyncService';
import { getSyncUrl } from '@services/syncJsonDataToBe';
import { handleCheckAndUpdatePullToRefreshStateForNearbyCases } from '@screens/allCases/utils';
export enum FOREGROUND_TASKS {
GEOLOCATION = 'GEOLOCATION',
@@ -72,6 +83,7 @@ export enum FOREGROUND_TASKS {
VIDEO_UPLOAD_JOB = 'VIDEO_UPLOAD_JOB',
AUDIO_UPLOAD_JOB = 'AUDIO_UPLOAD_JOB',
DATA_SYNC_JOB = 'DATA_SYNC_JOB',
NEARBY_CASES_GEOLOCATION_CHECK = 'NEARBY_CASES_GEOLOCATION_CHECK',
}
interface ITrackingComponent {
@@ -305,7 +317,13 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
task: getSyncUrl,
delay: getCalendarAndAccountsUploadJobIntervalInMinutes() * MILLISECONDS_IN_A_MINUTE, // 12 hours
onLoop: true,
}
},
{
taskId: FOREGROUND_TASKS.NEARBY_CASES_GEOLOCATION_CHECK,
task: handleCheckAndUpdatePullToRefreshStateForNearbyCases,
delay: 3 * MILLISECONDS_IN_A_MINUTE, // 3 minutes
onLoop: true,
},
];
if (!isTeamLead) {

View File

@@ -151,7 +151,7 @@ const RadioButton: React.FC<IRadioButton> = (props) => {
key={option}
id={option as string}
value={options[option]?.text}
containerStyle={GenericStyles.whiteBackground}
containerStyle={[GenericStyles.whiteBackground]}
/>
))}
</RadioGroup>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import { useForm } from 'react-hook-form';
import { ScrollView, StyleSheet, View } from 'react-native';
import Geolocation from 'react-native-geolocation-service';
@@ -12,7 +12,7 @@ import { syncCaseDetail } from '../../action/dataActions';
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
import { useAppDispatch, useAppSelector } from '../../hooks';
import useIsOnline from '../../hooks/useIsOnline';
import {getUpdatedCollectionCaseDetail, setIsFeedbackSubmitting, updateCaseDetail} from '../../reducer/allCasesSlice';
import { getUpdatedCollectionCaseDetail, updateCaseDetail } from '../../reducer/allCasesSlice';
import { deleteInteraction, deleteJourney, updateInteraction } from '../../reducer/caseReducer';
import { CaseAllocationType } from '../../screens/allCases/interface';
import { getUnSyncedCase } from '../../screens/caseDetails/interactionsHandler';
@@ -41,6 +41,7 @@ import NavigationHeader, { Icon } from '../../../RN-UI-LIB/src/components/Naviga
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { useNavigation, useRoute } from '@react-navigation/native';
import { NUDGE_BOTTOM_SHEET_DEFAULT_STATE } from './constants';
import {useBackHandler} from "@hooks/useBackHandler";
interface IWidget {
route: {
@@ -63,6 +64,7 @@ const Widget: React.FC<IWidget> = (props) => {
NUDGE_BOTTOM_SHEET_DEFAULT_STATE
);
const isOnline = useIsOnline();
const [isSubmitting, setIsSubmitting] = useState(false);
const { params } = props.route;
const { caseId, journey, handleCloseRouting } = params;
const caseKey = useRef<string>('');
@@ -73,7 +75,6 @@ const Widget: React.FC<IWidget> = (props) => {
const caseData = useAppSelector((state) => state.allCases?.caseDetails?.[caseId]);
const dataToBeValidated = useAppSelector((state) => state.case?.caseForm?.[caseId]?.[journey]);
const intermediateDocsToBeUploaded = useAppSelector((state) => state.feedbackImages?.intermediateDocsToBeUploaded);
const isFeedbackSubmitting = useAppSelector((state) => state?.allCases?.isFeedbackSubmitting);
const name = getWidgetNameFromRoute(props.route.name, caseType);
const { sections, conditionActions: widgetConditionActions, isLeaf } = templateData.widget[name];
@@ -93,6 +94,20 @@ const Widget: React.FC<IWidget> = (props) => {
setIsJourneyFirstScreen(isFirst);
}, [templateData, name]);
const handleBackPress = useCallback(() => {
if (isSubmitting) {
toast({
type: 'info',
text1: ToastMessages.FEEDBACK_SUBMISSION_UNDER_PROCESS,
});
return true;
}
return false;
}, [isSubmitting]);
useBackHandler(handleBackPress);
const {
control,
setValue,
@@ -194,7 +209,7 @@ const Widget: React.FC<IWidget> = (props) => {
};
const onSuccessfulSubmit = (data: any, interactionId: string, nextActions?: any) => {
dispatch(setIsFeedbackSubmitting(false));
setIsSubmitting(false);
setNudgeBottomSheetDetails(NUDGE_BOTTOM_SHEET_DEFAULT_STATE);
navigateToScreen(CaseDetailStackEnum.COLLECTION_CASE_DETAIL, {
journey: journey,
@@ -220,7 +235,7 @@ const Widget: React.FC<IWidget> = (props) => {
}
const submitJourneyWithGeoLocation = (data: any, _: any, submitViaNudge?: boolean) => {
dispatch(setIsFeedbackSubmitting(true));
setIsSubmitting(true);
addClickstreamEvent(
submitViaNudge
? CLICKSTREAM_EVENT_NAMES.FA_SUBMIT_ANYWAYS_CLICKED
@@ -235,11 +250,13 @@ const Widget: React.FC<IWidget> = (props) => {
if (location) {
return handleSubmitJourney(data, location);
}
}).catch((err) => {
setIsSubmitting(false);
});
};
const onErrorSubmit = (errObj: GenericType, data?: GenericType, interactionId?: string) => {
dispatch(setIsFeedbackSubmitting(false));
setIsSubmitting(false);
if (nudgeBottomSheetDetails?.showNudgeBottomSheet) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SUBMIT_ANYWAYS_FAILED, {
caseId,
@@ -310,6 +327,7 @@ const Widget: React.FC<IWidget> = (props) => {
type: 'info',
text1: ToastMessages.FEEDBACK_SUBMITTED_OFFLINE,
});
setIsSubmitting(false);
}
};
@@ -329,6 +347,14 @@ const Widget: React.FC<IWidget> = (props) => {
}, []);
const handleCloseIconPress = () => {
if (isSubmitting) {
toast({
type: 'info',
text1: ToastMessages.FEEDBACK_SUBMISSION_UNDER_PROCESS,
});
return;
}
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FORM_CLOSE_CLICKED, {
caseId,
});
@@ -414,15 +440,15 @@ const Widget: React.FC<IWidget> = (props) => {
style={[styles.autoFlex, styles.mH16]}
title={'Back'}
testID={'test_back'}
disabled={isLeaf && isFeedbackSubmitting}
disabled={isLeaf && isSubmitting}
onPress={handleBackButton}
leftIcon={<ArrowSolidIcon size={10} />}
/>
<Button
style={[styles.autoFlex, styles.mH16]}
title={isLeaf ? 'Submit' : 'Next'}
showLoader={isLeaf && isFeedbackSubmitting}
disabled={isLeaf && isFeedbackSubmitting}
showLoader={isLeaf && isSubmitting}
disabled={isLeaf && isSubmitting}
onPress={
isLeaf
? handleSubmit(submitJourneyWithGeoLocation, onError)
@@ -436,7 +462,7 @@ const Widget: React.FC<IWidget> = (props) => {
</View>
<NudgeSuspiciousFeedbackBottomSheet
caseId={caseId}
successBtnLoader={isFeedbackSubmitting}
successBtnLoader={isSubmitting}
nudgeBottomSheetDetails={nudgeBottomSheetDetails}
setNudgeBottomSheetDetails={setNudgeBottomSheetDetails}
successCallbackFn={handleSubmit(

View File

@@ -0,0 +1,10 @@
import { useEffect } from 'react'
import { BackHandler } from 'react-native'
export const useBackHandler = (handler: () => boolean) => {
useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', handler)
return () => BackHandler.removeEventListener('hardwareBackPress', handler)
}, [handler])
}

View File

@@ -475,9 +475,9 @@ export const handleRevivalNotificationNavigation = (action: string, notification
});
break;
case NotificationAction.VIEW_NEARBY_CASES:
navigateToScreen(PageRouteEnum.NEARBY_CASES, {
screen: PageRouteEnum.NEARBY_CASES,
params: { notificationId },
navigateToScreen(PageRouteEnum.HOME, {
screen: BOTTOM_TAB_ROUTES.Cases,
params: { allCasesView: true },
});
break;
case NotificationAction.VIEW_AGENT_PERFORMANCE_DASHBOARD:

View File

@@ -1,10 +1,9 @@
import { useEffect, useRef } from 'react';
import { BackHandler, AppState, AppStateStatus } from 'react-native';
import { useAppSelector } from '.';
import { CLICKSTREAM_EVENT_NAMES } from '../common/Constants';
import { addClickstreamEvent } from '../services/clickstreamEventService';
import store, { RootState } from '../store/store';
import {toast} from "@rn-ui-lib/components/toast";
import {ToastMessages} from "@screens/allCases/constants";
import { RootState } from '../store/store';
const THREE_MINUTES = 3 * 60 * 1000;
const useNativeButtons = () => {
@@ -12,13 +11,6 @@ const useNativeButtons = () => {
const intervalRef = useRef(0);
const handleBackButton = () => {
if (store?.getState()?.allCases?.isFeedbackSubmitting) {
toast({
type: 'info',
text1: ToastMessages.FEEDBACK_SUBMISSION_UNDER_PROCESS,
});
return true;
}
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_NATIVE_BACK_PRESSED);
return false;
};

View File

@@ -47,7 +47,6 @@ interface IAllCasesSlice {
selectedCaseId: string;
allCasesViewSearchQuery: string;
visitPlanSearchQuery: string;
isFeedbackSubmitting: boolean;
}
const initialState: IAllCasesSlice = {
@@ -72,7 +71,6 @@ const initialState: IAllCasesSlice = {
selectedCaseId: '',
allCasesViewSearchQuery: '',
visitPlanSearchQuery: '',
isFeedbackSubmitting: false,
};
const getCaseListComponents = (casesList: ICaseItem[], caseDetails: Record<string, CaseDetail>) => {
@@ -313,7 +311,6 @@ const allCasesSlice = createSlice({
updatedCaseDetail.offlineCaseKey = caseKey;
}
state.caseDetails[caseKey] = updatedCaseDetail;
state.isFeedbackSubmitting = false;
},
setPinnedRank: (state, action) => {
const caseId = action.payload.caseReferenceId;
@@ -476,9 +473,6 @@ const allCasesSlice = createSlice({
setVisitPlanSearchQuery: (state, action) => {
state.visitPlanSearchQuery = action.payload;
},
setIsFeedbackSubmitting: (state, action) => {
state.isFeedbackSubmitting = action.payload;
}
},
});
@@ -503,7 +497,6 @@ export const {
setSelectedCaseId,
setAllCasesViewSearchQuery,
setVisitPlanSearchQuery,
setIsFeedbackSubmitting
} = allCasesSlice.actions;
export default allCasesSlice.reducer;

View File

@@ -0,0 +1,46 @@
import { IGeolocationCoordinate } from '@interfaces/addressGeolocation.types';
import { createSlice } from '@reduxjs/toolkit';
import { TABS_KEYS } from '@screens/allCases/constants';
import { ICaseItem } from '@screens/allCases/interface';
const initialState = {
nearbyCasesList: [] as ICaseItem[],
locationNearbyCasesListUpdated: {} as IGeolocationCoordinate,
isPullToRefreshNearbyCasesVisible: false as boolean,
caseReferenceIdToDistanceMap: new Map() as Map<string, number>,
sortTabSelected: TABS_KEYS.HIGHEST_OD as string,
};
export const nearbyCasesListSlice = createSlice({
name: 'nearbyCasesSlice',
initialState,
reducers: {
setNearbyCasesList: (state, action) => {
state.nearbyCasesList = action.payload;
},
setLocationNearbyCasesListUpdated: (state, action) => {
state.locationNearbyCasesListUpdated = action.payload;
},
setIsPullToRefreshNearbyCasesVisible: (state, action) => {
state.isPullToRefreshNearbyCasesVisible = action.payload;
},
setCaseReferenceIdToDistanceMap: (state, action) => {
state.caseReferenceIdToDistanceMap = action.payload;
},
setSortTabSelected: (state, action) => {
state.sortTabSelected = action.payload;
},
resetNearbyCasesData: () => initialState,
},
});
export const {
setNearbyCasesList,
setLocationNearbyCasesListUpdated,
setIsPullToRefreshNearbyCasesVisible,
setCaseReferenceIdToDistanceMap,
setSortTabSelected,
resetNearbyCasesData,
} = nearbyCasesListSlice.actions;
export default nearbyCasesListSlice.reducer;

View File

@@ -7,6 +7,7 @@ import NotificationMenu from '../../components/notificationMenu';
import HeaderLabel from './HeaderLabel';
import { goBack } from '../../components/utlis/navigationUtlis';
import BackArrowIcon from '../../../RN-UI-LIB/src/Icons/BackArrowIcon';
import FilterHeaderComponent from './FilterHeaderComponent';
interface ICaseListHeader {
searchQuery: string;
@@ -18,6 +19,7 @@ interface ICaseListHeader {
filteredListCount: number;
isVisitPlan?: boolean;
isAgentDashboard?: boolean;
onTabChange: (tabKey: string) => void;
}
const CaseListHeader: React.FC<ICaseListHeader> = ({
@@ -30,6 +32,7 @@ const CaseListHeader: React.FC<ICaseListHeader> = ({
filteredListCount,
isVisitPlan,
isAgentDashboard,
onTabChange,
}) => {
return (
<Animated.View
@@ -73,6 +76,9 @@ const CaseListHeader: React.FC<ICaseListHeader> = ({
isAgentDashboard={isAgentDashboard}
/>
)}
{!isVisitPlan ? (
<FilterHeaderComponent onTabChange={onTabChange} />
) : null}
</Animated.View>
);
};

View File

@@ -7,6 +7,7 @@ import {
Pressable,
StyleSheet,
View,
RefreshControl,
} from 'react-native';
import { useFocusEffect } from '@react-navigation/native';
import { FlashList } from '@shopify/flash-list';
@@ -15,7 +16,7 @@ import { GenericStyles, SCREEN_HEIGHT, SCREEN_WIDTH } from '../../../RN-UI-LIB/s
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import { type RootState } from '../../store/store';
import { CaseTypes, type ICaseItem, type ICaseItemCaseDetailObj } from './interface';
import { LIST_HEADER_ITEMS, ListHeaderItems } from './constants';
import { BOTTOM_TAB_ROUTES, LIST_HEADER_ITEMS, ListHeaderItems, TABS_KEYS } from './constants';
import CaseItem from './CaseItem';
import { Search } from '../../../RN-UI-LIB/src/utlis/search';
import FiltersContainer from '../../components/screens/allCases/allCasesFilters/FiltersContainer';
@@ -30,6 +31,9 @@ import {
HEADER_HEIGHT_MIN_WITH_QUICK_FILTERS,
HEADER_SCROLL_DISTANCE,
HEADER_SCROLL_DISTANCE_WITH_QUICK_FILTERS,
NEARBY_CASES_HEADER_HEIGHT_MAX,
NEARBY_CASES_HEADER_HEIGHT_MIN,
NEARBY_CASES_HEADER_SCROLL_DISTANCE,
VISIT_PLAN_HEADER_HEIGHT_MAX,
VISIT_PLAN_HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS,
VISIT_PLAN_HEADER_HEIGHT_MIN,
@@ -41,7 +45,13 @@ import { evaluateFilterForCases } from '../../components/screens/allCases/allCas
import { debounce } from '../../components/utlis/commonFunctions';
import { getCurrentScreen } from '../../components/utlis/navigationUtlis';
import { VisitPlanStatus } from '../../reducer/userSlice';
import { getAttemptedList, getNonAttemptedList } from './utils';
import {
getAttemptedList,
getNearByCases,
getNonAttemptedList,
handleCheckAndUpdatePullToRefreshStateForNearbyCases,
updateNearbyCasesListAndLocation,
} from './utils';
import { type GenericType } from '../../common/GenericTypes';
import Heading from '../../../RN-UI-LIB/src/components/Heading';
import { row } from '../emiSchedule/constants';
@@ -53,11 +63,9 @@ import { getFilterCount, getSearchQuery, getSelectedFilters } from '../Dashboard
import AttendanceIcon from '@assets/icons/AttendanceIcon';
import FloatingBannerCta from '@common/FloatingBannerCta';
import GoogleFormModal from './GoogleFormModal';
import {
setAllCasesViewSearchQuery,
setVisitPlanSearchQuery,
} from '@reducers/allCasesSlice';
import { setAllCasesViewSearchQuery, setVisitPlanSearchQuery } from '@reducers/allCasesSlice';
import { setDashboardSearchQuery } from '@reducers/agentPerformanceSlice';
import { setSortTabSelected } from '@reducers/nearbyCasesSlice';
export const getItem = (item: ICaseItem[], index: number) => item[index];
export const ESTIMATED_ITEM_SIZE = 250; // Average height of List item
@@ -116,7 +124,15 @@ const CasesList: React.FC<ICasesList> = ({
const [showFilterModal, setShowFilterModal] = React.useState<boolean>(false);
const flashListRef = useRef<GenericType>(null);
const scrollAnimation = useRef(new Animated.Value(0)).current;
const allCasesList = useAppSelector((state) => state?.allCases?.casesList) || [];
const nearbyCases = useAppSelector((state) => state?.nearbyCasesSlice?.nearbyCasesList) || [];
const isPullToRefreshNearbyCasesVisible = useAppSelector(
(state) => state?.nearbyCasesSlice?.isPullToRefreshNearbyCasesVisible
);
const selectedTab = useAppSelector((state) => state?.nearbyCasesSlice?.sortTabSelected);
const isNearestCaseTabSelected = selectedTab === TABS_KEYS.NEAREST_CASE;
const isNearestCaseView = isNearestCaseTabSelected && (getCurrentScreen()?.name === BOTTOM_TAB_ROUTES.Cases);
const isPullToRefreshBannerVisible = isNearestCaseView && isPullToRefreshNearbyCasesVisible;
const dispatch = useAppDispatch();
const firePageLoadEvent = () => {
@@ -124,6 +140,7 @@ const CasesList: React.FC<ICasesList> = ({
if (getCurrentScreen()?.name === 'Cases') {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_CASE_LIST_LOAD, {
caseCount: casesList.length,
tabKey: selectedTab,
});
} else {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VISIT_PLAN_LOAD);
@@ -136,14 +153,38 @@ const CasesList: React.FC<ICasesList> = ({
}, [])
);
const onTabChange = useCallback(
(tabKey: string) => {
dispatch(setSortTabSelected(tabKey));
if (tabKey === TABS_KEYS.NEAREST_CASE) {
handleCheckAndUpdatePullToRefreshStateForNearbyCases();
}
// @ts-expect-error
const currentScrollOffset = scrollAnimation?._value;
if (currentScrollOffset > 0) {
scrollAnimation.setValue(0);
flashListRef?.current?.scrollToOffset({ animated: true, offset: 0 });
}
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CASE_LIST_SORT_TAB_CHANGED, {
selectedTabKey: tabKey,
});
},
[setSortTabSelected]
);
const toggleAgentSelectionBottomSheet = () => {
dispatch(setShowAgentSelectionBottomSheet(!showAgentSelectionBottomSheet));
};
const headerHeight = scrollAnimation.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE],
inputRange: isPullToRefreshBannerVisible
? [0, NEARBY_CASES_HEADER_SCROLL_DISTANCE]
: [0, HEADER_SCROLL_DISTANCE],
outputRange: isVisitPlan
? [VISIT_PLAN_HEADER_HEIGHT_MAX, VISIT_PLAN_HEADER_HEIGHT_MIN]
: isPullToRefreshBannerVisible
? [NEARBY_CASES_HEADER_HEIGHT_MAX, NEARBY_CASES_HEADER_HEIGHT_MIN]
: [HEADER_HEIGHT_MAX, HEADER_HEIGHT_MIN],
extrapolate: 'clamp',
});
@@ -161,7 +202,8 @@ const CasesList: React.FC<ICasesList> = ({
});
const { filteredCasesList, listEmptyComponent } = useMemo(() => {
const filteredList = casesList.filter(
const cases = isNearestCaseView ? nearbyCases : casesList;
const filteredList = cases?.filter(
(caseItem) =>
LIST_HEADER_ITEMS.includes(caseItem?.type as CaseTypes) ||
evaluateFilterForCases(caseDetails[caseItem.caseReferenceId], filters, selectedFilters)
@@ -202,7 +244,16 @@ const CasesList: React.FC<ICasesList> = ({
);
return { filteredCasesList: filteredListAfterQuery, listEmptyComponent };
}, [casesList, caseDetails, filters, selectedFilters, searchQuery, isLockedVisitPlanStatus]);
}, [
casesList,
caseDetails,
filters,
selectedFilters,
searchQuery,
isLockedVisitPlanStatus,
selectedTab,
nearbyCases
]);
useEffect(() => {
if (flashListRef?.current) {
@@ -217,10 +268,14 @@ const CasesList: React.FC<ICasesList> = ({
}
}, [searchQuery]);
useEffect(() => {
if (!isVisitPlan && nearbyCases?.length != casesList?.length) {
updateNearbyCasesListAndLocation(allCasesList, caseDetails);
}
}, [allCasesList]);
const filteredCasesListWithCTA = useMemo(() => {
if (!isVisitPlan) {
if (allCasesView && filteredCasesList?.length)
return [ListHeaderItems.NEARBY_CASES as ICaseItem, ...filteredCasesList];
return [...filteredCasesList];
}
if (isLockedVisitPlanStatus) {
@@ -327,6 +382,14 @@ const CasesList: React.FC<ICasesList> = ({
[showAttendanceBanner, isTeamLead]
);
const [refreshing, setRefreshing] = useState(false);
const onRefresh = useCallback(() => {
if (!isPullToRefreshNearbyCasesVisible) return;
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_NEARBY_CASES_BANNER_PULLED_TO_REFRESH);
updateNearbyCasesListAndLocation(allCasesList, caseDetails);
}, [isPullToRefreshNearbyCasesVisible, allCasesList, caseDetails]);
return (
<View style={[GenericStyles.fill, styles.container]}>
<CaseListHeader
@@ -339,13 +402,15 @@ const CasesList: React.FC<ICasesList> = ({
showFilters={showFilters}
isVisitPlan={isVisitPlan}
isAgentDashboard={isAgentDashboard}
onTabChange={onTabChange}
/>
{visitPlansUpdating ? (
<Animated.View style={[styles.fillOverlay, { top: headerHeightValue }]} />
) : null}
<View style={GenericStyles.fill}>
<View style={[GenericStyles.fill, isPullToRefreshBannerVisible ? styles.mt64 : styles.mt16]}>
{filteredCasesListWithCTA.length > 0 ? (
<FlashList
key={selectedTab}
ref={flashListRef}
data={filteredCasesListWithCTA}
keyboardShouldPersistTaps="handled"
@@ -356,6 +421,17 @@ const CasesList: React.FC<ICasesList> = ({
ListEmptyComponent={listEmptyComponent}
estimatedItemSize={ESTIMATED_ITEM_SIZE}
estimatedListSize={ESTIMATED_LIST_SIZE}
refreshControl={
isPullToRefreshBannerVisible ? (
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
progressViewOffset={200}
/>
) : (
<></>
)
}
/>
) : (
<View style={GenericStyles.ph12}>{listEmptyComponent}</View>
@@ -462,6 +538,12 @@ const styles = StyleSheet.create({
backgroundColor: COLORS.BACKGROUND.VIOLET,
padding: 8,
},
mt16: {
marginTop: 16,
},
mt64: {
marginTop: 64,
},
});
export default CasesList;

View File

@@ -0,0 +1,65 @@
import Text from '@rn-ui-lib/components/Text';
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { TABS, TABS_KEYS } from './constants';
import { COLORS } from '@rn-ui-lib/colors';
import { useAppSelector } from '@hooks';
import PullToRefreshNearbyCasesHeader from './PullToRefreshNearbyCasesHeader';
import TabsWithRadioChips from '@rn-ui-lib/components/customTabs/TabsWithRadioChips';
interface IFilterHeaderComponent {
onTabChange: (tabKey: string) => void;
}
const FilterHeaderComponent = (props: IFilterHeaderComponent) => {
const { onTabChange } = props;
const selectedTab = useAppSelector((state) => state?.nearbyCasesSlice?.sortTabSelected);
const isPullToRefreshNearbyCasesVisible = useAppSelector(
(state) => state?.nearbyCasesSlice?.isPullToRefreshNearbyCasesVisible
);
const isNearestCaseView = (selectedTab === TABS_KEYS.NEAREST_CASE);
return (
<>
<View style={styles.filterContainer}>
<Text bold dark style={styles.headerText}>
Sort by
</Text>
<TabsWithRadioChips
tabs={TABS}
currentTab={selectedTab}
onTabChange={onTabChange}
containerStyle={styles.tabContainer}
chipContainerStyle={styles.chipContainer}
/>
</View>
{isNearestCaseView && isPullToRefreshNearbyCasesVisible && (
<PullToRefreshNearbyCasesHeader />
)}
</>
);
};
const styles = StyleSheet.create({
filterContainer: {
height: 64,
backgroundColor: COLORS.BACKGROUND.PRIMARY,
padding: 16,
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignContent: 'center',
elevation: 4,
},
headerText: {
paddingRight: 12,
paddingTop: 2,
},
tabContainer: {
backgroundColor: 'transparent',
},
chipContainer: {
marginTop: 0,
},
});
export default FilterHeaderComponent;

View File

@@ -27,12 +27,13 @@ import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
import RoundCheckIcon from '../../../RN-UI-LIB/src/Icons/RoundCheckIcon';
import { getDocumentList } from '../../components/utlis/commonFunctions';
import { toast } from '../../../RN-UI-LIB/src/components/toast';
import { COMPLETED_STATUSES, ToastMessages } from './constants';
import { COMPLETED_STATUSES, TABS_KEYS, TAG_CONTAINER_WIDTH, ToastMessages } from './constants';
import { VisitPlanStatus } from '../../reducer/userSlice';
import { PaymentStatus } from '../caseDetails/interface';
import relativeDistanceFormatter from '@screens/addressGeolocation/utils/relativeDistanceFormatter';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { PageRouteEnum } from '@screens/auth/ProtectedRouter';
import LocationDistanceIcon from '@assets/icons/LocationDistanceIcon';
interface IListItem {
caseListItemDetailObj: ICaseItemCaseDetailObj;
@@ -188,6 +189,19 @@ const ListItem: React.FC<IListItem> = (props) => {
!isTeamLead &&
!nearbyCaseView;
const distanceMapOfNearbyCases =
useAppSelector((state) => state.nearbyCasesSlice.caseReferenceIdToDistanceMap) || {};
const selectedTab = useAppSelector((state) => state?.nearbyCasesSlice?.sortTabSelected);
const distanceOfCaseItem = distanceMapOfNearbyCases.get(caseListItemDetailObj?.id);
const isNearestCaseView = selectedTab === TABS_KEYS.NEAREST_CASE;
const showInVisitPlanTag = isCaseItemPinnedMainView && !caseCompleted;
const widthStyle = {
width: showInVisitPlanTag
? TAG_CONTAINER_WIDTH.VISIT_PLAN_TAG
: showVisitPlanBtn
? TAG_CONTAINER_WIDTH.VISIT_PLAN_BUTTON
: TAG_CONTAINER_WIDTH.DEFAULT,
};
return (
<Pressable onPress={handleCaseClick}>
<View
@@ -213,7 +227,7 @@ const ListItem: React.FC<IListItem> = (props) => {
<RoundCheckIcon focused={isCaseSelected} />
</Pressable>
) : null}
{isCaseItemPinnedMainView && !caseCompleted && (
{showInVisitPlanTag && (
<View style={[GenericStyles.absolute, styles.visitPlanContainer]}>
<Text style={[GenericStyles.fontSize12, styles.visitPlanText]}>In visit plan</Text>
</View>
@@ -226,22 +240,37 @@ const ListItem: React.FC<IListItem> = (props) => {
</View>
)}
<View style={[styles.caseItemInfo]}>
<View style={styles.tag}>
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
{paymentStatus ? (
<View>
<Tag
variant={paymentStatusMapping[paymentStatus]?.variant || TagVariant.alert}
text={(paymentStatusMapping[paymentStatus]?.label || paymentStatus) as string}
/>
</View>
) : null}
{collectionTag ? (
<View style={paymentStatus && GenericStyles.ml8}>
<Tag variant={TagVariant.gray} text={collectionTag} />
</View>
) : null}
</View>
<View style={[styles.tagContainer, widthStyle]}>
{paymentStatus ? (
<View style={[GenericStyles.mr8, GenericStyles.mb8]}>
<Tag
variant={paymentStatusMapping[paymentStatus]?.variant || TagVariant.alert}
text={(paymentStatusMapping[paymentStatus]?.label || paymentStatus) as string}
/>
</View>
) : null}
{collectionTag ? (
<View style={[GenericStyles.mr8]}>
<Tag variant={TagVariant.gray} text={collectionTag} />
</View>
) : null}
{distanceOfCaseItem ? (
<View style={GenericStyles.mb4}>
<Tag
tagIcon={
<LocationDistanceIcon
iconColor={isNearestCaseView ? COLORS.TEXT.BLUE_DARK_2 : COLORS.TEXT.GREY_3}
backgroundColor={
isNearestCaseView ? COLORS.BACKGROUND.BLUE : COLORS.BACKGROUND.BLUE_LIGHT_3
}
/>
}
text={Number(distanceOfCaseItem?.toFixed(1)) + ' KM'}
variant={isNearestCaseView ? TagVariant.darkBlue : TagVariant.darkGray}
/>
</View>
) : null}
</View>
<Heading numberOfLines={1} type={'h5'} bold dark>
{customerName}
@@ -294,8 +323,9 @@ const styles = StyleSheet.create({
address: {
fontSize: 13,
},
tag: {
marginBottom: 8,
tagContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
},
caseStatusText: {
color: COLORS.BACKGROUND.SECONDARY,

View File

@@ -0,0 +1,39 @@
import NewLocationIcon from '@assets/icons/NewLocationIcon';
import { useFocusEffect } from '@react-navigation/native';
import { COLORS } from '@rn-ui-lib/colors';
import Text from '@rn-ui-lib/components/Text';
import { View, StyleSheet } from 'react-native';
const PullToRefreshNearbyCasesHeader = () => {
return (
<View style={styles.pullToRefreshContainer}>
<NewLocationIcon />
<Text style={styles.headerText}>
{'New location detected. Pull to refresh to update list'}
</Text>
</View>
);
};
const styles = StyleSheet.create({
pullToRefreshContainer: {
height: 48,
backgroundColor: COLORS.BACKGROUND.PRIMARY,
padding: 12,
paddingRight: 16,
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'flex-start',
borderTopWidth: 1,
borderTopColor: COLORS.BORDER.PRIMARY,
elevation: 4,
},
headerText: {
marginLeft: 4,
fontSize: 14,
color: COLORS.TEXT.DARK,
},
});
export default PullToRefreshNearbyCasesHeader;

View File

@@ -70,8 +70,7 @@ export const ToastMessages = {
SUCCESS_COPYING_PAYMENT_LINK: 'Payment link has been copied to Clipboard',
FILTERS_APPLIED_SUCCESSFULLY: 'Filters applied successfully',
FEEDBACK_SUBMITTED_OFFLINE: "Feedback will be submitted automatically once you're back online",
FEEDBACK_SUBMISSION_UNDER_PROCESS:
"Submitting feedback. You'll be redirected to customer details shortly.",
FEEDBACK_SUBMISSION_UNDER_PROCESS: "Submitting feedback. You'll be redirected to customer details shortly.",
OFFLINE_MESSAGE: 'You seem to be offline',
PAYMENT_LINK_NOT_GENERATED: 'Payment link could not be generated',
PAYMENT_LINK_RETRY: 'Please retry after an hour for this number',
@@ -102,3 +101,21 @@ export enum BOTTOM_TAB_ROUTES {
}
export const NEARBY_CASES_COUNT = 10;
export const TABS = [
{ key: 'highestOD', label: 'Highest OD' },
{ key: 'nearestCase', label: 'Nearest case' },
];
export const NEARBY_CASES_THRESHOLD_DISTANCE = 1; // 1 km
export enum TABS_KEYS {
HIGHEST_OD = 'highestOD',
NEAREST_CASE = 'nearestCase',
};
export enum TAG_CONTAINER_WIDTH {
VISIT_PLAN_TAG = '75%',
VISIT_PLAN_BUTTON = '85%',
DEFAULT = '100%'
}

View File

@@ -3,8 +3,19 @@ import { getDistanceFromLatLonInKm } from '@components/utlis/commonFunctions';
import { IGeoLocation } from '@interfaces/addressGeolocation.types';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { Address, CaseDetail, FeedbackStatus, INearbyCaseItemObj } from '../caseDetails/interface';
import { BOTTOM_TAB_ROUTES, NEARBY_CASES_COUNT } from './constants';
import {
BOTTOM_TAB_ROUTES,
NEARBY_CASES_COUNT,
NEARBY_CASES_THRESHOLD_DISTANCE,
} from './constants';
import { ICaseItem, IReportee, ISectionListData } from './interface';
import store from '@store';
import {
setCaseReferenceIdToDistanceMap,
setIsPullToRefreshNearbyCasesVisible,
setLocationNearbyCasesListUpdated,
setNearbyCasesList,
} from '@reducers/nearbyCasesSlice';
export const getAttemptedList = (
filteredCasesList: ICaseItem[],
@@ -117,7 +128,7 @@ export const handleTabClick = (nextTab: BOTTOM_TAB_ROUTES) => {
[BOTTOM_TAB_ROUTES.Dashboard]: CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_BUTTON_CLICKED,
[BOTTOM_TAB_ROUTES.Profile]: CLICKSTREAM_EVENT_NAMES.FA_PROFILE_PAGE_BUTTON_CLICKED,
[BOTTOM_TAB_ROUTES.Cases]: CLICKSTREAM_EVENT_NAMES.FA_CASE_LIST_BUTTON_CLICKED,
[BOTTOM_TAB_ROUTES.VisitPlan]: CLICKSTREAM_EVENT_NAMES.FA_VISIT_PLAN_BUTTON_CLICKED
[BOTTOM_TAB_ROUTES.VisitPlan]: CLICKSTREAM_EVENT_NAMES.FA_VISIT_PLAN_BUTTON_CLICKED,
};
const event = clickstreamEvents[nextTab];
@@ -125,3 +136,77 @@ export const handleTabClick = (nextTab: BOTTOM_TAB_ROUTES) => {
addClickstreamEvent(event);
}
};
const handleCheckShowPullToRefreshNearbyCases = (
nearbyCasesList: ICaseItem[],
distanceBetweenNearbyCasesListUpdatedLocationAndCurrentLocation: number
) => {
return (
nearbyCasesList?.length > 0 &&
!isNaN(distanceBetweenNearbyCasesListUpdatedLocationAndCurrentLocation) &&
distanceBetweenNearbyCasesListUpdatedLocationAndCurrentLocation >=
NEARBY_CASES_THRESHOLD_DISTANCE
);
};
export const handleCheckAndUpdatePullToRefreshStateForNearbyCases = () => {
const nearbyCasesList = store?.getState()?.nearbyCasesSlice?.nearbyCasesList || [];
const locationNearbyCasesListUpdated =
store?.getState()?.nearbyCasesSlice?.locationNearbyCasesListUpdated || {};
const deviceGeolocationCoordinate =
store?.getState()?.foregroundService?.deviceGeolocationCoordinate || {};
const distanceBetweenNearbyCasesListUpdatedLocationAndCurrentLocation = getDistanceFromLatLonInKm(
locationNearbyCasesListUpdated,
deviceGeolocationCoordinate
);
if (
handleCheckShowPullToRefreshNearbyCases(
nearbyCasesList,
distanceBetweenNearbyCasesListUpdatedLocationAndCurrentLocation
)
) {
store.dispatch(setIsPullToRefreshNearbyCasesVisible(true));
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_NEARBY_CASES_BANNER_DISPLAY);
return true;
}
return false;
};
export const updateNearbyCasesListAndLocation = (
allCasesList: ICaseItem[],
caseDetails: Record<string, CaseDetail>
) => {
try {
const deviceGeolocationCoordinate = store?.getState()?.foregroundService?.deviceGeolocationCoordinate || {};
let caseIdsToDistancesFromCurrentLocationMap: Map<string, number> = new Map();
allCasesList?.forEach((pinnedId) => {
const caseDetail = caseDetails?.[pinnedId?.caseReferenceId] || {};
const addressLocation = getAddressLocation(caseDetail?.addresses);
if (addressLocation) {
const distanceInKm = getDistanceFromLatLonInKm(
addressLocation,
deviceGeolocationCoordinate
);
if (distanceInKm) {
caseIdsToDistancesFromCurrentLocationMap?.set(pinnedId?.caseReferenceId, distanceInKm);
}
}
});
const casesListCopy = [...allCasesList];
casesListCopy.sort((a, b) => {
const distanceA = caseIdsToDistancesFromCurrentLocationMap.get(a.caseReferenceId) ?? 0;
const distanceB = caseIdsToDistancesFromCurrentLocationMap.get(b.caseReferenceId) ?? 0;
return distanceA - distanceB;
});
store.dispatch(setNearbyCasesList(casesListCopy));
store.dispatch(setLocationNearbyCasesListUpdated(deviceGeolocationCoordinate));
store.dispatch(setIsPullToRefreshNearbyCasesVisible(false));
store.dispatch(setCaseReferenceIdToDistanceMap(caseIdsToDistancesFromCurrentLocationMap));
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_NEARBY_CASES_UPDATE_SUCCESS);
} catch (error) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_NEARBY_CASES_UPDATE_FAILED);
}
return;
};

View File

@@ -29,6 +29,7 @@ import litmusExperimentSlice from '@reducers/litmusExperimentSlice';
import ungroupedAddressesSlice from '@reducers/ungroupedAddressesSlice';
import commonSlice from '@reducers/commonSlice';
import commitmentTrackerSlice from '@reducers/commitmentTrackerSlice';
import nearbyCasesSlice from '@reducers/nearbyCasesSlice';
const rootReducer = combineReducers({
case: caseReducer,
@@ -57,7 +58,8 @@ const rootReducer = combineReducers({
litmusExperiment: litmusExperimentSlice,
ungroupedAddresses: ungroupedAddressesSlice,
common: commonSlice,
commitmentTracker: commitmentTrackerSlice
commitmentTracker: commitmentTrackerSlice,
nearbyCasesSlice: nearbyCasesSlice,
});
const persistConfig = {
@@ -89,7 +91,7 @@ const persistConfig = {
'ungroupedAddresses',
'cosmosSupport',
'common',
'commitmentTracker'
'commitmentTracker',
],
};