TP-20843 | firestore service and cases redux slice refactor (#1050)
This commit is contained in:
@@ -27,7 +27,7 @@ import { clearAllAsyncStorage } from '../components/utlis/commonFunctions';
|
||||
import { logError } from '../components/utlis/errorUtils';
|
||||
import auth from '@react-native-firebase/auth';
|
||||
import foregroundService from '../services/foregroundServices/foreground.service';
|
||||
import { loggedOutCurrentUser } from '../hooks/useFirestoreUpdates';
|
||||
import { loggedOutCurrentUser } from '@hooks/useFirestoreUpdates';
|
||||
import { GenericFunctionArgs, GenericType } from '../common/GenericTypes';
|
||||
import { GoogleSignin } from '@react-native-google-signin/google-signin';
|
||||
import { resetConfig } from '../reducer/configSlice';
|
||||
@@ -38,6 +38,7 @@ import { resetPerformanceData } from '@reducers/agentPerformanceSlice';
|
||||
import { clearStorageEngine } from '../PersistStorageEngine';
|
||||
import { resetNearbyCasesData } from '@reducers/nearbyCasesSlice';
|
||||
import { resetActiveCallData } from '@reducers/activeCallSlice';
|
||||
import { firestoreService } from '@services/firestoreService';
|
||||
|
||||
export interface GenerateOTPPayload {
|
||||
phoneNumber: string;
|
||||
@@ -198,6 +199,7 @@ export const handleGoogleLogout = async () => {
|
||||
|
||||
const firebaseSignout = async () => {
|
||||
try {
|
||||
await firestoreService.unsubscribeAll();
|
||||
await auth().signOut();
|
||||
} catch (error) {
|
||||
logError(error as Error, 'Firebase signout error');
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper';
|
||||
import { logError } from '../components/utlis/errorUtils';
|
||||
import { VisitPlanStatus } from '../reducer/userSlice';
|
||||
import { FilterResponse } from '../screens/allCases/interface';
|
||||
import { CaseDetail } from '../screens/caseDetails/interface';
|
||||
|
||||
export enum SyncStatus {
|
||||
SEND_CASES = 'SEND_CASES',
|
||||
FETCH_CASES = 'FETCH_CASES',
|
||||
SKIP = 'SKIP',
|
||||
}
|
||||
|
||||
interface IFilterSync {
|
||||
filterComponentList: FilterResponse[];
|
||||
}
|
||||
|
||||
export interface ISyncedCases {
|
||||
cases: CaseDetail[];
|
||||
filters: IFilterSync;
|
||||
deletedCaseIds: string[];
|
||||
payloadCreatedAt: number;
|
||||
}
|
||||
|
||||
interface ICases {
|
||||
caseId: string;
|
||||
caseViewCreatedAt?: number;
|
||||
}
|
||||
|
||||
export interface ISyncCaseIdPayload {
|
||||
agentId: string;
|
||||
cases: ICases[];
|
||||
}
|
||||
|
||||
interface ICasesSyncStatus {
|
||||
syncStatus: SyncStatus;
|
||||
visitPlanStatus: VisitPlanStatus;
|
||||
}
|
||||
|
||||
export const getCasesSyncStatus = async (userReferenceId: string) => {
|
||||
try {
|
||||
const url = getApiUrl(ApiKeys.CASES_SYNC_STATUS, {}, { userReferenceId });
|
||||
const response: AxiosResponse<ICasesSyncStatus> = await axiosInstance.get(url, {
|
||||
headers: { donotHandleError: true },
|
||||
});
|
||||
return response?.data;
|
||||
} catch (err) {
|
||||
logError(err as Error, 'Error getting sync status');
|
||||
}
|
||||
};
|
||||
|
||||
export const sendSyncCaseIds = async (payload: ISyncCaseIdPayload) => {
|
||||
try {
|
||||
const url = getApiUrl(ApiKeys.CASES_SEND_ID);
|
||||
const response = await axiosInstance.post(url, payload, {
|
||||
headers: { donotHandleError: true },
|
||||
});
|
||||
return response?.data;
|
||||
} catch (err) {
|
||||
logError(err as Error, 'Error sending case ids sync');
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchCasesToSync = async (agentReferenceId: string) => {
|
||||
//disabling this function since its conflicting with the new sync logic
|
||||
return null;
|
||||
try {
|
||||
const url = getApiUrl(ApiKeys.FETCH_CASES, { agentReferenceId });
|
||||
const response = await axiosInstance.get(url, { headers: { donotHandleError: true } });
|
||||
return response?.data;
|
||||
} catch (err) {
|
||||
logError(err as Error, 'Error fetching cases to be synced');
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
let ACTIVITY_TIME_ON_APP: number = 5; //5 seconds
|
||||
let ACTIVITY_TIME_WINDOW_HIGH: number = 10; //10 minutes
|
||||
let ACTIVITY_TIME_WINDOW_MEDIUM: number = 30; //30 minutes
|
||||
let ENABLE_FIRESTORE_RESYNC = false;
|
||||
let FIRESTORE_RESYNC_INTERVAL_IN_MINUTES = 15;
|
||||
let DATA_SYNC_JOB_INTERVAL_IN_MINUTES = 10; // 10 minutes
|
||||
let IMAGE_UPLOAD_JOB_INTERVAL_IN_MINUTES = 10; // 10 minutes
|
||||
@@ -14,7 +13,6 @@ export const getActivityTimeOnApp = () => ACTIVITY_TIME_ON_APP;
|
||||
export const getActivityTimeWindowHigh = () => ACTIVITY_TIME_WINDOW_HIGH;
|
||||
export const getActivityTimeWindowMedium = () => ACTIVITY_TIME_WINDOW_MEDIUM;
|
||||
|
||||
export const getEnableFirestoreResync = () => ENABLE_FIRESTORE_RESYNC;
|
||||
export const getFirestoreResyncIntervalInMinutes = () => FIRESTORE_RESYNC_INTERVAL_IN_MINUTES;
|
||||
export const getDataSyncJobIntervalInMinutes = () => DATA_SYNC_JOB_INTERVAL_IN_MINUTES;
|
||||
export const getImageUploadJobIntervalInMinutes = () => IMAGE_UPLOAD_JOB_INTERVAL_IN_MINUTES;
|
||||
@@ -37,10 +35,6 @@ export const setActivityTimeWindowMedium = (activityTimeWindowMedium: number) =>
|
||||
ACTIVITY_TIME_WINDOW_MEDIUM = activityTimeWindowMedium;
|
||||
};
|
||||
|
||||
export const setEnableFirestoreResync = (enableFirestoreResync: boolean) => {
|
||||
ENABLE_FIRESTORE_RESYNC = enableFirestoreResync;
|
||||
};
|
||||
|
||||
export const setFirestoreResyncIntervalInMinutes = (firestoreResyncIntervalInMinutes: number) => {
|
||||
FIRESTORE_RESYNC_INTERVAL_IN_MINUTES = firestoreResyncIntervalInMinutes;
|
||||
};
|
||||
|
||||
@@ -17,18 +17,7 @@ import { useAppDispatch, useAppSelector } from '../hooks';
|
||||
import { dataSyncService } from '../services/dataSync.service';
|
||||
import { DATA_SYNC_TIME_INTERVAL, IS_DATA_SYNC_REQUIRED } from '../constants/config';
|
||||
import useIsLocationEnabled from '../hooks/useIsLocationEnabled';
|
||||
import {
|
||||
type ISyncCaseIdPayload,
|
||||
type ISyncedCases,
|
||||
SyncStatus,
|
||||
fetchCasesToSync,
|
||||
getCasesSyncStatus,
|
||||
sendSyncCaseIds,
|
||||
} from '../action/firebaseFallbackActions';
|
||||
import { getSyncCaseIds } from '../components/utlis/firebaseFallbackUtils';
|
||||
import { syncCasesByFallback } from '../reducer/allCasesSlice';
|
||||
import { MILLISECONDS_IN_A_MINUTE, noop } from '../../RN-UI-LIB/src/utlis/common';
|
||||
import { setCaseSyncLock, setLockData } from '../reducer/userSlice';
|
||||
import { MILLISECONDS_IN_A_MINUTE} from '../../RN-UI-LIB/src/utlis/common';
|
||||
import { getConfigData } from '../action/configActions';
|
||||
import { AppStates } from '../types/appStates';
|
||||
import { StorageKeys } from '../types/storageKeys';
|
||||
@@ -37,7 +26,6 @@ import {
|
||||
getActivityTimeOnApp,
|
||||
getActivityTimeWindowMedium,
|
||||
getActivityTimeWindowHigh,
|
||||
getEnableFirestoreResync,
|
||||
getDataSyncJobIntervalInMinutes,
|
||||
getImageUploadJobIntervalInMinutes,
|
||||
getVideoUploadJobIntervalInMinutes,
|
||||
@@ -47,7 +35,7 @@ import {
|
||||
} from './AgentActivityConfigurableConstants';
|
||||
import { GlobalImageMap } from './CachedImage';
|
||||
import { addClickstreamEvent } from '../services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from './Constants';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from './Constants';
|
||||
import useResyncFirebase from '@hooks/useResyncFirebase';
|
||||
import { imageSyncService, sendImagesToServer } from '@services/imageSyncService';
|
||||
import { sendAudiosToServer } from '@services/audioSyncService';
|
||||
@@ -82,7 +70,6 @@ interface ITrackingComponent {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
let LAST_SYNC_STATUS = 'SKIP';
|
||||
const ACTIVITY_TIME_WINDOW = 10; // 10 minutes
|
||||
|
||||
const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
@@ -90,11 +77,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const appState = useRef(AppState.currentState);
|
||||
|
||||
const isTeamLead = useAppSelector((state) => state.user.isTeamLead);
|
||||
const caseSyncLock = useAppSelector((state) => state?.user?.caseSyncLock);
|
||||
const referenceId = useAppSelector((state) => state.user.user?.referenceId!);
|
||||
const pendingList = useAppSelector((state) => state.allCases.pendingList);
|
||||
const pinnedList = useAppSelector((state) => state.allCases.pinnedList);
|
||||
|
||||
const handleTimeSync = async () => {
|
||||
try {
|
||||
@@ -113,46 +96,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
|
||||
const resyncFirebase = useResyncFirebase();
|
||||
|
||||
const handleGetCaseSyncStatus = async () => {
|
||||
if (caseSyncLock || getEnableFirestoreResync()) {
|
||||
return;
|
||||
}
|
||||
dispatch(setCaseSyncLock(true));
|
||||
try {
|
||||
if (!isOnline) {
|
||||
return;
|
||||
}
|
||||
const { syncStatus, visitPlanStatus } = (await getCasesSyncStatus(referenceId)) ?? {};
|
||||
if (syncStatus) {
|
||||
// Keep track of the last status received
|
||||
LAST_SYNC_STATUS = syncStatus;
|
||||
}
|
||||
if (syncStatus === SyncStatus.SEND_CASES) {
|
||||
const cases = getSyncCaseIds([...pendingList, ...pinnedList]);
|
||||
const payload: ISyncCaseIdPayload = {
|
||||
agentId: referenceId,
|
||||
cases,
|
||||
};
|
||||
sendSyncCaseIds(payload);
|
||||
} else if (syncStatus === SyncStatus.FETCH_CASES) {
|
||||
const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId);
|
||||
if (updatedDetails?.cases?.length) {
|
||||
dispatch(syncCasesByFallback(updatedDetails));
|
||||
}
|
||||
}
|
||||
if (visitPlanStatus) {
|
||||
dispatch(
|
||||
setLockData({
|
||||
visitPlanStatus,
|
||||
})
|
||||
);
|
||||
}
|
||||
dispatch(setCaseSyncLock(false));
|
||||
} catch (e) {
|
||||
logError(e as Error, 'Error during fetching case sync status');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateActivity = async () => {
|
||||
const foregroundTimestamp = await getItem(StorageKeys.APP_FOREGROUND_TIMESTAMP);
|
||||
const backgroundTimestamp = await getItem(StorageKeys.APP_BACKGROUND_TIMESTAMP);
|
||||
@@ -163,7 +106,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
}
|
||||
|
||||
const foregroundTime = dayJs(foregroundTimestamp);
|
||||
const backgroundTime = dayJs(backgroundTimestamp);
|
||||
const stateSetTime = dayJs(stateSetTimestamp);
|
||||
|
||||
const diffBetweenCurrentTimeAndForegroundTime =
|
||||
@@ -323,15 +265,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
},
|
||||
];
|
||||
|
||||
if (!isTeamLead) {
|
||||
tasks.push({
|
||||
taskId: FOREGROUND_TASKS.FIRESTORE_FALLBACK,
|
||||
task: handleGetCaseSyncStatus,
|
||||
delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes
|
||||
onLoop: true,
|
||||
});
|
||||
}
|
||||
|
||||
const handleDataSync = () => {
|
||||
if (!isOnline) {
|
||||
return;
|
||||
@@ -371,7 +304,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
if (nextAppState === AppStates.ACTIVE) {
|
||||
await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, now);
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_APP_FOREGROUND, { now });
|
||||
handleGetCaseSyncStatus();
|
||||
handleTimeSync();
|
||||
dispatch(getConfigData());
|
||||
CosmosForegroundService.start(tasks);
|
||||
@@ -385,22 +317,11 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
appState.current = nextAppState;
|
||||
};
|
||||
|
||||
// Fetch cases on login initially and set data
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!referenceId) {
|
||||
return;
|
||||
}
|
||||
await handleGetCaseSyncStatus();
|
||||
dispatch(getConfigData());
|
||||
const isFirestoreResyncEnabled = getEnableFirestoreResync();
|
||||
if (!isTeamLead && LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES && !isFirestoreResyncEnabled) {
|
||||
const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId);
|
||||
if (updatedDetails?.cases?.length) {
|
||||
dispatch(syncCasesByFallback(updatedDetails));
|
||||
}
|
||||
}
|
||||
})();
|
||||
if (!referenceId) {
|
||||
return;
|
||||
}
|
||||
dispatch(getConfigData());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import firestore, { type FirebaseFirestoreTypes } from '@react-native-firebase/firestore';
|
||||
import { type FirebaseFirestoreTypes } from '@react-native-firebase/firestore';
|
||||
import auth from '@react-native-firebase/auth';
|
||||
import { setFeedbackFilterTemplate } from '@reducers/feedbackFiltersSlice';
|
||||
import { InteractionManager } from 'react-native';
|
||||
import { useAppDispatch, useAppSelector } from '.';
|
||||
import { setLoading, updateCaseDetailsFirestore } from '../reducer/allCasesSlice';
|
||||
import { setLoading } from '../reducer/allCasesSlice';
|
||||
import { type CaseDetail } from '../screens/caseDetails/interface';
|
||||
import { CLICKSTREAM_EVENT_NAMES, FirestoreUpdateTypes } from '../common/Constants';
|
||||
import { toast } from '../../RN-UI-LIB/src/components/toast';
|
||||
import { updateCollectionTemplateData } from '../reducer/caseReducer';
|
||||
import { type ILockData, MY_CASE_ITEM, setLockData, VisitPlanStatus } from '../reducer/userSlice';
|
||||
import { setFilters } from '../reducer/filtersSlice';
|
||||
import { ToastMessages } from '../screens/allCases/constants';
|
||||
import { setCurrentProdAPK } from '../reducer/metadataSlice';
|
||||
import { logError } from '../components/utlis/errorUtils';
|
||||
import { type GenericFunctionArgs } from '../common/GenericTypes';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { setCsaFilters } from '@reducers/cosmosSupportSlice';
|
||||
import { setActiveCallData, setCallingFeedbackNudgeBottomSheet } from '@reducers/activeCallSlice';
|
||||
import { FEEDBACK_NUDGE_STATUS } from '@screens/caseDetails/CallingFlow/interfaces';
|
||||
import { updateCases } from '@screens/caseDetails/utils/caseDetailsUtils';
|
||||
import { firestoreService } from '@services/firestoreService';
|
||||
|
||||
export interface CaseUpdates {
|
||||
updateType: string;
|
||||
@@ -31,13 +29,12 @@ export const loggedOutCurrentUser = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const { subscribeToDoc, subscribeToCollection, unsubscribeAll } = firestoreService;
|
||||
|
||||
const isUserSignedIn = () => !!auth().currentUser;
|
||||
|
||||
const useFirestoreUpdates = () => {
|
||||
const isTeamLead = useAppSelector((state) => state.user.isTeamLead);
|
||||
const caseDetails = useAppSelector((state) => state.allCases.caseDetails);
|
||||
const casesList = useAppSelector((state) => state.allCases.casesList);
|
||||
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const isLoggedIn = useAppSelector((state) => state.user.isLoggedIn);
|
||||
const sessionDetails = useAppSelector((state) => state.user.sessionDetails);
|
||||
@@ -50,82 +47,17 @@ const useFirestoreUpdates = () => {
|
||||
useEffect(() => {
|
||||
lockRef.current = lock;
|
||||
}, [lock]);
|
||||
|
||||
let casesUnsubscribe: GenericFunctionArgs;
|
||||
let collectionTemplateUnsubscribe: GenericFunctionArgs;
|
||||
let filterUnsubscribe: GenericFunctionArgs;
|
||||
let lockUnsubscribe: GenericFunctionArgs;
|
||||
let feedbackFiltersUnsubscribe: GenericFunctionArgs;
|
||||
let appUpdateUnsubscribe: GenericFunctionArgs;
|
||||
let csaFiltersUnsubscribe: GenericFunctionArgs;
|
||||
let activeCallDetailsUnsubscribe: GenericFunctionArgs;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const showCaseUpdationToast = (newlyAddedCases: number, deletedCases: number) => {
|
||||
let toastConfig: any = null;
|
||||
const addedCasesText = newlyAddedCases
|
||||
? `${newlyAddedCases} new case${newlyAddedCases > 1 ? 's' : ''} allocated`
|
||||
: '';
|
||||
const deletedCasesText = deletedCases
|
||||
? `${deletedCases} case${deletedCases > 1 ? 's' : ''} de-allocated`
|
||||
: '';
|
||||
if (newlyAddedCases && deletedCases) {
|
||||
toastConfig = {
|
||||
type: 'info',
|
||||
text1: `${addedCasesText} & ${deletedCasesText}`,
|
||||
};
|
||||
} else if (newlyAddedCases) {
|
||||
toastConfig = { type: 'success', text1: addedCasesText };
|
||||
} else if (deletedCases) {
|
||||
toastConfig = { type: 'error', text1: deletedCasesText };
|
||||
}
|
||||
if (toastConfig) {
|
||||
toast(toastConfig);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCasesUpdate = async (querySnapshot: FirebaseFirestoreTypes.QuerySnapshot) => {
|
||||
let newlyAddedCases = 0;
|
||||
let deletedCases = 0;
|
||||
const caseUpdates: CaseUpdates[] = [];
|
||||
querySnapshot
|
||||
.docChanges()
|
||||
.forEach((documentSnapshot: FirebaseFirestoreTypes.DocumentChange) => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
const updateType = documentSnapshot.type;
|
||||
const updatedCaseDetail = documentSnapshot.doc.data() as CaseDetail;
|
||||
if (updateType === FirestoreUpdateTypes.ADDED) {
|
||||
if (!caseDetails[updatedCaseDetail.id]) {
|
||||
newlyAddedCases++;
|
||||
caseUpdates.push({ updateType, updatedCaseDetail });
|
||||
}
|
||||
} else {
|
||||
if (updateType === FirestoreUpdateTypes.REMOVED) {
|
||||
deletedCases++;
|
||||
}
|
||||
caseUpdates.push({ updateType, updatedCaseDetail });
|
||||
}
|
||||
});
|
||||
});
|
||||
const isInitialLoad = casesList.length === 0;
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
requestAnimationFrame(() => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
dispatch(
|
||||
updateCaseDetailsFirestore({
|
||||
caseUpdates,
|
||||
isInitialLoad,
|
||||
isVisitPlanLocked: lockRef?.current?.visitPlanStatus === VisitPlanStatus.LOCKED,
|
||||
selectedAgent,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
!isInitialLoad && showCaseUpdationToast(newlyAddedCases, deletedCases);
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SNAPSHOT_LISTENER, {
|
||||
snapshot_path: 'handleCasesUpdate',
|
||||
});
|
||||
const isVisitPlanLocked = lockRef?.current?.visitPlanStatus === VisitPlanStatus.LOCKED;
|
||||
dispatch(
|
||||
updateCases({
|
||||
selectedAgent,
|
||||
isVisitPlanLocked,
|
||||
querySnapshot,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleCollectionTemplateUpdate = (
|
||||
@@ -166,17 +98,12 @@ const useFirestoreUpdates = () => {
|
||||
snapshot: FirebaseFirestoreTypes.DocumentSnapshot<FirebaseFirestoreTypes.DocumentData>
|
||||
) => {
|
||||
const activeCallDetails = snapshot.data();
|
||||
if(activeCallDetails?.feedbackNudgeStatus === FEEDBACK_NUDGE_STATUS.PENDING) {
|
||||
if (activeCallDetails?.feedbackNudgeStatus === FEEDBACK_NUDGE_STATUS.PENDING) {
|
||||
dispatch(setCallingFeedbackNudgeBottomSheet(true));
|
||||
}
|
||||
dispatch(setActiveCallData(activeCallDetails));
|
||||
};
|
||||
|
||||
const handleError = (err: any, collectionPath?: string) => {
|
||||
const errMsg = `Error while fetching fireStore snapshot: referenceId: ${user?.referenceId} collectionPath: ${collectionPath}`;
|
||||
logError(err as Error, errMsg);
|
||||
};
|
||||
|
||||
const handleAppUpdate = (
|
||||
snapshot: FirebaseFirestoreTypes.DocumentSnapshot<FirebaseFirestoreTypes.DocumentData>
|
||||
) => {
|
||||
@@ -184,107 +111,51 @@ const useFirestoreUpdates = () => {
|
||||
dispatch(setCurrentProdAPK(configData));
|
||||
};
|
||||
|
||||
const subscribeToAppUpdate = () => {
|
||||
const collectionPath = 'app-state/app-update';
|
||||
return subscribeToDoc(handleAppUpdate, collectionPath);
|
||||
};
|
||||
|
||||
const signInUserToFirebase = () => {
|
||||
if (!sessionDetails) {
|
||||
dispatch(setLoading(false));
|
||||
return;
|
||||
}
|
||||
auth()
|
||||
.signInWithCustomToken(sessionDetails?.firebaseToken)
|
||||
.then((userCredential) => {
|
||||
addFirestoreListeners();
|
||||
})
|
||||
.catch((error) => {
|
||||
firestoreService.signInUserToFirebase(
|
||||
sessionDetails.firebaseToken,
|
||||
subscribeToFirestore,
|
||||
() => {
|
||||
dispatch(setLoading(false));
|
||||
logError(error as Error, 'Error in signInUserToFirebase');
|
||||
toast({
|
||||
type: 'error',
|
||||
text1: ToastMessages.FIRESTORE_SIGNIN_FAILED,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const subscribeToDoc = (successCb: GenericFunctionArgs, collectionPath: string) =>
|
||||
firestore()
|
||||
.doc(collectionPath)
|
||||
.onSnapshot(
|
||||
async (data) => {
|
||||
successCb(data);
|
||||
},
|
||||
(err) => {
|
||||
handleError(err, collectionPath);
|
||||
}
|
||||
);
|
||||
|
||||
const subscribeToCases = () => {
|
||||
let refId = user?.referenceId;
|
||||
if (isTeamLead && selectedAgent?.referenceId !== MY_CASE_ITEM.referenceId) {
|
||||
refId = selectedAgent?.referenceId;
|
||||
}
|
||||
const collectionPath = `allocations/${refId}/cases`;
|
||||
return firestore()
|
||||
.collection(collectionPath)
|
||||
.orderBy('totalOverdueAmount', 'asc') // It is descending order only, but acting weirdly. Need to check.
|
||||
.onSnapshot(handleCasesUpdate, (err) => {
|
||||
handleError(err, collectionPath);
|
||||
});
|
||||
};
|
||||
|
||||
const subscribeToCollectionTemplate = () => {
|
||||
const collectionPath = `template/${isExternalAgent ? 'external' : 'inhouse'}_template`;
|
||||
return subscribeToDoc(handleCollectionTemplateUpdate, collectionPath);
|
||||
};
|
||||
|
||||
const subscribeToFilters = () => {
|
||||
let refId = user?.referenceId;
|
||||
if (isTeamLead && selectedAgent?.referenceId !== MY_CASE_ITEM.referenceId) {
|
||||
refId = selectedAgent?.referenceId;
|
||||
}
|
||||
const collectionPath = `filters/${refId}`;
|
||||
|
||||
return subscribeToDoc(handleFilterUpdate, collectionPath);
|
||||
};
|
||||
|
||||
const subscribeToFeedbackFilters = () => {
|
||||
const feedbackFiltersPath = `feedback-filters/v2`;
|
||||
return subscribeToDoc(handleFeedbackFilters, feedbackFiltersPath);
|
||||
};
|
||||
|
||||
const subscribeToLocks = () => {
|
||||
const lockPath = `locks/${user?.referenceId}`;
|
||||
return subscribeToDoc(handleLockUpdate, lockPath);
|
||||
};
|
||||
|
||||
const subscribeToCsaFilters = () => {
|
||||
const collectionPath = 'global-filters/v1';
|
||||
return subscribeToDoc(handleCsaFilters, collectionPath);
|
||||
};
|
||||
|
||||
const subscribeToActiveCallDetailsUnsubscribe = () => {
|
||||
const refId = user?.referenceId;
|
||||
const collectionPath = `allocations/${refId}/agentActivity/callDetails`;
|
||||
return subscribeToDoc(handleActiveCallDetails, collectionPath);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const subscribeToFirestore = () => {
|
||||
addFirestoreListeners();
|
||||
let refId = user?.referenceId;
|
||||
if (isTeamLead && selectedAgent?.referenceId !== MY_CASE_ITEM.referenceId) {
|
||||
refId = selectedAgent?.referenceId;
|
||||
}
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
subscribeToCollection(
|
||||
`allocations/${refId}/cases`,
|
||||
(ref) => ref.orderBy('totalOverdueAmount', 'asc'),
|
||||
handleCasesUpdate
|
||||
);
|
||||
subscribeToDoc(`filters/${refId}`, handleFilterUpdate);
|
||||
subscribeToDoc(
|
||||
`template/${isExternalAgent ? 'external' : 'inhouse'}_template`,
|
||||
handleCollectionTemplateUpdate
|
||||
);
|
||||
subscribeToDoc(`locks/${user?.referenceId}`, handleLockUpdate);
|
||||
subscribeToDoc(`feedback-filters/v2`, handleFeedbackFilters);
|
||||
subscribeToDoc('global-filters/v1', handleCsaFilters);
|
||||
subscribeToDoc(
|
||||
`allocations/${user?.referenceId}/agentActivity/callDetails`,
|
||||
handleActiveCallDetails
|
||||
);
|
||||
subscribeToDoc('app-state/app-update', handleAppUpdate);
|
||||
});
|
||||
};
|
||||
|
||||
function addFirestoreListeners() {
|
||||
casesUnsubscribe = subscribeToCases();
|
||||
filterUnsubscribe = subscribeToFilters();
|
||||
collectionTemplateUnsubscribe = subscribeToCollectionTemplate();
|
||||
lockUnsubscribe = subscribeToLocks();
|
||||
feedbackFiltersUnsubscribe = subscribeToFeedbackFilters();
|
||||
csaFiltersUnsubscribe = subscribeToCsaFilters();
|
||||
activeCallDetailsUnsubscribe = subscribeToActiveCallDetailsUnsubscribe();
|
||||
appUpdateUnsubscribe = subscribeToAppUpdate();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!user?.referenceId) {
|
||||
return;
|
||||
@@ -294,48 +165,12 @@ const useFirestoreUpdates = () => {
|
||||
return;
|
||||
}
|
||||
if (isUserSignedIn()) {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
subscribeToFirestore();
|
||||
});
|
||||
subscribeToFirestore();
|
||||
} else {
|
||||
dispatch(setLoading(true));
|
||||
signInUserToFirebase();
|
||||
}
|
||||
return () => {
|
||||
casesUnsubscribe && casesUnsubscribe();
|
||||
filterUnsubscribe && filterUnsubscribe();
|
||||
collectionTemplateUnsubscribe && collectionTemplateUnsubscribe();
|
||||
lockUnsubscribe && lockUnsubscribe();
|
||||
feedbackFiltersUnsubscribe && feedbackFiltersUnsubscribe();
|
||||
csaFiltersUnsubscribe && csaFiltersUnsubscribe();
|
||||
activeCallDetailsUnsubscribe && activeCallDetailsUnsubscribe();
|
||||
appUpdateUnsubscribe && appUpdateUnsubscribe();
|
||||
};
|
||||
}, [isLoggedIn, user?.referenceId, isExternalAgent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTeamLead) {
|
||||
return;
|
||||
}
|
||||
if (!selectedAgent?.referenceId) {
|
||||
return;
|
||||
}
|
||||
if (!isLoggedIn || !sessionDetails?.firebaseToken) {
|
||||
loggedOutCurrentUser();
|
||||
return;
|
||||
}
|
||||
// unsubscribe from previous agent's cases
|
||||
casesUnsubscribe && casesUnsubscribe();
|
||||
filterUnsubscribe && filterUnsubscribe();
|
||||
dispatch(setLoading(true));
|
||||
// subscribe to new agent's cases
|
||||
subscribeToCases();
|
||||
subscribeToFilters();
|
||||
return () => {
|
||||
casesUnsubscribe && casesUnsubscribe();
|
||||
filterUnsubscribe && filterUnsubscribe();
|
||||
};
|
||||
}, [selectedAgent, isTeamLead]);
|
||||
}, [isLoggedIn, user?.referenceId, isExternalAgent, selectedAgent]);
|
||||
};
|
||||
|
||||
export default useFirestoreUpdates;
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import firestore from '@react-native-firebase/firestore';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import store, { type RootState } from '@store';
|
||||
import { updateCaseDetailsFirestore } from '@reducers/allCasesSlice';
|
||||
import { useAppDispatch } from '@hooks';
|
||||
import store from '@store';
|
||||
import { CLICKSTREAM_EVENT_NAMES, FirestoreUpdateTypes, SyncedSource } from '@common/Constants';
|
||||
import axiosInstance, { ApiKeys, getApiUrl } from '@utils/apiHelper';
|
||||
import { getSyncCaseIds } from '@utils/firebaseFallbackUtils';
|
||||
import { logError } from '@utils/errorUtils';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { GenericObject } from '@common/GenericTypes';
|
||||
import { setLastFirebaseResyncTimestamp } from '@reducers/metadataSlice';
|
||||
import dayJs from 'dayjs';
|
||||
import {
|
||||
getEnableFirestoreResync,
|
||||
getFirestoreResyncIntervalInMinutes,
|
||||
} from '@common/AgentActivityConfigurableConstants';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { updateCases } from '@screens/caseDetails/utils/caseDetailsUtils';
|
||||
import { CaseAllocationType } from '@screens/allCases/interface';
|
||||
import { CaseDetail } from '@screens/caseDetails/interface';
|
||||
import { CaseUpdates } from './useFirestoreUpdates';
|
||||
import { getFirestoreResyncIntervalInMinutes } from '@common/AgentActivityConfigurableConstants';
|
||||
|
||||
const selectedAgentReferenceIDForMyCases = 'MY_CASES';
|
||||
|
||||
@@ -25,9 +24,9 @@ type CasesToFetchPayload = {
|
||||
updatedCaseIds: string[];
|
||||
};
|
||||
};
|
||||
|
||||
const useResyncFirebase = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const refId = store?.getState()?.user?.user?.referenceId || '';
|
||||
const selectedAgent = store?.getState()?.user?.selectedAgent;
|
||||
const selectedAgentRefId = store?.getState()?.user?.selectedAgent?.referenceId || '';
|
||||
@@ -46,36 +45,126 @@ const useResyncFirebase = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const updateCaseInRedux = (
|
||||
updateType: FirestoreUpdateTypes,
|
||||
caseDetails: GenericObject,
|
||||
selectedAgent: GenericObject
|
||||
const fetchFirestoreCases = async (
|
||||
caseIds: string[],
|
||||
casesPath: string,
|
||||
updateType: string = FirestoreUpdateTypes.ADDED
|
||||
) => {
|
||||
const caseDocs = await firestore()
|
||||
.collection(casesPath)
|
||||
.where('caseReferenceId', 'in', caseIds)
|
||||
.get();
|
||||
|
||||
const firebaseAllocatedCases: CaseUpdates[] = [];
|
||||
|
||||
caseDocs?.forEach((doc) => {
|
||||
const firebaseCase = doc.data() as CaseDetail;
|
||||
if (!firebaseCase?.caseReferenceId) {
|
||||
return;
|
||||
}
|
||||
firebaseAllocatedCases.push({
|
||||
updateType,
|
||||
updatedCaseDetail: firebaseCase,
|
||||
});
|
||||
void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, {
|
||||
[updateType === FirestoreUpdateTypes.ADDED ? 'added' : 'update']:
|
||||
firebaseCase.caseReferenceId,
|
||||
syncedSource: SyncedSource.FIREBASE,
|
||||
});
|
||||
});
|
||||
|
||||
// If firebase case is not found, fetch from API
|
||||
const firebaseCaseIdsSet = new Set(
|
||||
firebaseAllocatedCases.map((firebaseCase) => firebaseCase.updatedCaseDetail.caseReferenceId)
|
||||
);
|
||||
|
||||
const caseIdsToFetch = caseIds.filter((caseId) => !firebaseCaseIdsSet.has(caseId));
|
||||
|
||||
for (const caseId of caseIdsToFetch) {
|
||||
try {
|
||||
const res = await _getCaseDetailsFromApi(caseId);
|
||||
const caseDetails = res?.data;
|
||||
firebaseAllocatedCases.push({
|
||||
updateType,
|
||||
updatedCaseDetail: caseDetails,
|
||||
});
|
||||
void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, {
|
||||
updated: caseId,
|
||||
syncedSource: SyncedSource.API,
|
||||
});
|
||||
} catch (err) {
|
||||
logError(err as Error, 'Error fetching cases from firestore');
|
||||
}
|
||||
}
|
||||
|
||||
return firebaseAllocatedCases;
|
||||
};
|
||||
|
||||
const addCasesToRedux = async (allocatedCases: string[], casesPath: string) => {
|
||||
if (!allocatedCases.length) {
|
||||
return;
|
||||
}
|
||||
const firebaseAllocatedCases = await fetchFirestoreCases(allocatedCases, casesPath);
|
||||
dispatch(
|
||||
updateCaseDetailsFirestore({
|
||||
caseUpdates: [
|
||||
{
|
||||
updateType: updateType,
|
||||
updatedCaseDetail: caseDetails,
|
||||
},
|
||||
],
|
||||
isInitialLoad: false,
|
||||
isVisitPlanLocked: false,
|
||||
updateCases({
|
||||
selectedAgent,
|
||||
caseUpdateList: firebaseAllocatedCases,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const removeCasesFromRedux = (unallocatedCases: string[]) => {
|
||||
if (!unallocatedCases.length) {
|
||||
return;
|
||||
}
|
||||
const unallocatedCasesUpdates: CaseUpdates[] = [];
|
||||
unallocatedCases.forEach((caseId: string) => {
|
||||
if (!caseId) {
|
||||
return null;
|
||||
}
|
||||
unallocatedCasesUpdates.push({
|
||||
updateType: FirestoreUpdateTypes.REMOVED,
|
||||
updatedCaseDetail: {
|
||||
caseType: CaseAllocationType.COLLECTION_CASE,
|
||||
caseReferenceId: caseId,
|
||||
id: '',
|
||||
pinRank: null,
|
||||
caseViewCreatedAt: 0,
|
||||
} as CaseDetail,
|
||||
});
|
||||
void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, { deleted: caseId });
|
||||
});
|
||||
dispatch(
|
||||
updateCases({
|
||||
selectedAgent,
|
||||
caseUpdateList: unallocatedCasesUpdates,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const modifyCasesInRedux = async (updatedCases: string[], casesPath: string) => {
|
||||
if (!updatedCases.length) {
|
||||
return;
|
||||
}
|
||||
const firebaseUpdatedCases = await fetchFirestoreCases(
|
||||
updatedCases,
|
||||
casesPath,
|
||||
FirestoreUpdateTypes.MODIFIED
|
||||
);
|
||||
dispatch(
|
||||
updateCases({
|
||||
selectedAgent,
|
||||
caseUpdateList: firebaseUpdatedCases,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return async (): Promise<void> => {
|
||||
console.log('firebase resync called');
|
||||
const now = dayJs().toString();
|
||||
const FIRST_DATE = new Date(1970, 1, 1);
|
||||
const lastFirebaseResyncTimestamp =
|
||||
(await AsyncStorage.getItem('lastFirebaseResyncTimestamp')) || dayJs(FIRST_DATE).toString();
|
||||
const minutesSinceLastResync = dayJs(now).diff(dayJs(lastFirebaseResyncTimestamp), 'minutes');
|
||||
if (!getEnableFirestoreResync()) {
|
||||
return;
|
||||
}
|
||||
if (minutesSinceLastResync < getFirestoreResyncIntervalInMinutes()) {
|
||||
return;
|
||||
}
|
||||
@@ -93,117 +182,16 @@ const useResyncFirebase = () => {
|
||||
}
|
||||
);
|
||||
|
||||
const allocatedCases = casesToFetch?.data?.allocatedCaseIds;
|
||||
const unallocatedCases = casesToFetch?.data?.deallocatedCaseIds;
|
||||
const updatedCases = casesToFetch?.data?.updatedCaseIds;
|
||||
const allocatedCases = casesToFetch?.data?.allocatedCaseIds || [];
|
||||
const unallocatedCases = casesToFetch?.data?.deallocatedCaseIds || [];
|
||||
const updatedCases = casesToFetch?.data?.updatedCaseIds || [];
|
||||
|
||||
allocatedCases.forEach((caseId: string) => {
|
||||
if (!caseId) {
|
||||
return null;
|
||||
}
|
||||
firestore()
|
||||
.collection(casesPath)
|
||||
.doc(caseId.toString())
|
||||
.get({ source: 'server' })
|
||||
.then((res) => {
|
||||
const firebaseCase = res?.data() || {};
|
||||
if (!firebaseCase?.caseReferenceId) {
|
||||
throw new Error('could not find case in firebase');
|
||||
}
|
||||
dispatch(
|
||||
updateCaseDetailsFirestore({
|
||||
caseUpdates: [
|
||||
{
|
||||
updateType: FirestoreUpdateTypes.ADDED,
|
||||
updatedCaseDetail: firebaseCase,
|
||||
},
|
||||
],
|
||||
isInitialLoad: false,
|
||||
isVisitPlanLocked: false,
|
||||
selectedAgent,
|
||||
})
|
||||
);
|
||||
void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, {
|
||||
added: caseId,
|
||||
syncedSource: SyncedSource.FIREBASE,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
logError(err as Error, 'Error fetching cases from firestore');
|
||||
void _getCaseDetailsFromApi(caseId).then((res: { data: GenericObject }) => {
|
||||
const caseDetails = res?.data;
|
||||
updateCaseInRedux(FirestoreUpdateTypes.ADDED, caseDetails, selectedAgent);
|
||||
void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, {
|
||||
updated: caseId,
|
||||
syncedSource: SyncedSource.API,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
unallocatedCases.forEach((caseId: string) => {
|
||||
if (!caseId) {
|
||||
return null;
|
||||
}
|
||||
dispatch(
|
||||
updateCaseDetailsFirestore({
|
||||
caseUpdates: [
|
||||
{
|
||||
updateType: FirestoreUpdateTypes.REMOVED,
|
||||
updatedCaseDetail: {
|
||||
caseType: '',
|
||||
caseReferenceId: caseId,
|
||||
id: '',
|
||||
pinRank: '',
|
||||
caseViewCreatedAt: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
isInitialLoad: false,
|
||||
isVisitPlanLocked: false,
|
||||
selectedAgent,
|
||||
})
|
||||
);
|
||||
void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, { deleted: caseId });
|
||||
});
|
||||
|
||||
updatedCases.forEach((caseId: string) => {
|
||||
if (!caseId) {
|
||||
return null;
|
||||
}
|
||||
firestore()
|
||||
.collection(casesPath)
|
||||
.doc(caseId.toString())
|
||||
.get({ source: 'server' })
|
||||
.then((res) => {
|
||||
const firebaseCase = res?.data() || {};
|
||||
|
||||
if (!firebaseCase?.caseReferenceId) {
|
||||
throw new Error('could not find case in firebase');
|
||||
}
|
||||
updateCaseInRedux(FirestoreUpdateTypes.MODIFIED, firebaseCase, selectedAgent);
|
||||
void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, {
|
||||
updated: caseId,
|
||||
syncedSource: SyncedSource.FIREBASE,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
void _getCaseDetailsFromApi(caseId).then((res: { data: GenericObject }) => {
|
||||
const caseDetails = res?.data || {};
|
||||
updateCaseInRedux(FirestoreUpdateTypes.MODIFIED, caseDetails, selectedAgent);
|
||||
void addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_FIREBASE_RESYNC, {
|
||||
updated: caseId,
|
||||
syncedSource: SyncedSource.API,
|
||||
});
|
||||
});
|
||||
logError(err as Error, 'Error fetching cases from firestore');
|
||||
console.log('cases err:', err);
|
||||
});
|
||||
});
|
||||
addCasesToRedux(allocatedCases, casesPath);
|
||||
removeCasesFromRedux(unallocatedCases);
|
||||
modifyCasesInRedux(updatedCases, casesPath);
|
||||
|
||||
dispatch(setLastFirebaseResyncTimestamp(dayJs().toString()));
|
||||
await AsyncStorage.setItem('lastFirebaseResyncTimestamp', dayJs().toString());
|
||||
|
||||
await Promise.resolve();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,49 +1,32 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { toast } from '../../RN-UI-LIB/src/components/toast';
|
||||
import { _map } from '../../RN-UI-LIB/src/utlis/common';
|
||||
import {
|
||||
findDocumentByDocumentType,
|
||||
getLoanAccountNumber,
|
||||
} from '../components/utlis/commonFunctions';
|
||||
import { CLICKSTREAM_EVENT_NAMES, FirestoreUpdateTypes } from '../common/Constants';
|
||||
import { getCurrentScreen, navigateToScreen } from '../components/utlis/navigationUtlis';
|
||||
import { type CaseUpdates } from '../hooks/useFirestoreUpdates';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../common/Constants';
|
||||
import { navigateToScreen } from '../components/utlis/navigationUtlis';
|
||||
import { COMPLETED_STATUSES } from '../screens/allCases/constants';
|
||||
|
||||
import { CaseAllocationType, type ICaseItem, type IReportee } from '../screens/allCases/interface';
|
||||
import {
|
||||
type CaseDetail,
|
||||
DOCUMENT_TYPE,
|
||||
type IGeolocation,
|
||||
} from '../screens/caseDetails/interface';
|
||||
import { CaseAllocationType, type ICaseItem } from '../screens/allCases/interface';
|
||||
import { type CaseDetail } from '../screens/caseDetails/interface';
|
||||
import { addClickstreamEvent } from '../services/clickstreamEventService';
|
||||
import { getVisitedWidgetsNodeList } from '../components/form/services/forms.service';
|
||||
import { CollectionCaseWidgetId, CommonCaseWidgetId } from '../types/template.types';
|
||||
import { type IAvatarUri } from '../action/caseListAction';
|
||||
import { MY_CASE_ITEM } from './userSlice';
|
||||
|
||||
export type ICasesMap = Record<string, ICaseItem>;
|
||||
|
||||
interface IAllCasesSlice {
|
||||
casesList: ICaseItem[];
|
||||
casesListMap: ICasesMap;
|
||||
intermediateTodoList: ICaseItem[];
|
||||
intermediateTodoListMap: ICasesMap;
|
||||
selectedTodoListMap: ICasesMap;
|
||||
selectedTodoListCount: number;
|
||||
initialPinnedRankCount: number;
|
||||
pinnedRankCount: number;
|
||||
loading: boolean;
|
||||
newlyPinnedCases: number;
|
||||
completedCases: number;
|
||||
caseDetails: Record<string, CaseDetail>;
|
||||
searchQuery: string;
|
||||
visitPlansUpdating: boolean;
|
||||
pendingList: ICaseItem[];
|
||||
completedList: ICaseItem[];
|
||||
pinnedList: ICaseItem[];
|
||||
newVisitedCases: string[];
|
||||
geolocations?: IGeolocation[];
|
||||
selectedCaseId: string;
|
||||
allCasesViewSearchQuery: string;
|
||||
visitPlanSearchQuery: string;
|
||||
@@ -51,45 +34,24 @@ interface IAllCasesSlice {
|
||||
|
||||
const initialState: IAllCasesSlice = {
|
||||
casesList: [],
|
||||
casesListMap: {},
|
||||
intermediateTodoList: [],
|
||||
intermediateTodoListMap: {},
|
||||
selectedTodoListCount: 0,
|
||||
selectedTodoListMap: {},
|
||||
initialPinnedRankCount: 0,
|
||||
pinnedRankCount: 0,
|
||||
loading: false,
|
||||
newlyPinnedCases: 0,
|
||||
completedCases: 0,
|
||||
caseDetails: {},
|
||||
searchQuery: '',
|
||||
visitPlansUpdating: false,
|
||||
pendingList: [],
|
||||
completedList: [],
|
||||
pinnedList: [],
|
||||
newVisitedCases: [],
|
||||
selectedCaseId: '',
|
||||
allCasesViewSearchQuery: '',
|
||||
visitPlanSearchQuery: '',
|
||||
};
|
||||
|
||||
const getCaseListComponents = (casesList: ICaseItem[], caseDetails: Record<string, CaseDetail>) => {
|
||||
const pendingList: ICaseItem[] = [];
|
||||
const completedList: ICaseItem[] = [];
|
||||
const pinnedList: ICaseItem[] = [];
|
||||
casesList.forEach((item) => {
|
||||
const { caseReferenceId, pinRank } = item;
|
||||
const { caseStatus } = caseDetails[caseReferenceId] || {};
|
||||
const isCaseCompleted = COMPLETED_STATUSES.includes(caseStatus);
|
||||
isCaseCompleted
|
||||
? completedList.push(item)
|
||||
: pinRank
|
||||
? pinnedList.push(item)
|
||||
: pendingList.push(item);
|
||||
});
|
||||
return { pendingList, completedList, pinnedList };
|
||||
};
|
||||
|
||||
export const getUpdatedCollectionCaseDetail = ({
|
||||
caseData,
|
||||
answer,
|
||||
@@ -101,8 +63,6 @@ export const getUpdatedCollectionCaseDetail = ({
|
||||
updatedValue.isSynced = false;
|
||||
updatedValue.isApiCalled = false;
|
||||
updatedValue.taskStatus = 'completed';
|
||||
// @deprecating
|
||||
const { visitedWidgets } = answer;
|
||||
const allWidget = answer.widgetContext;
|
||||
const widgetContext = {};
|
||||
|
||||
@@ -127,16 +87,6 @@ export const getUpdatedCollectionCaseDetail = ({
|
||||
return updatedValue;
|
||||
};
|
||||
|
||||
const getCaseListItem = (
|
||||
caseReferenceId: string,
|
||||
pinRank?: number | null,
|
||||
caseViewCreatedAt?: number
|
||||
) => ({
|
||||
caseReferenceId,
|
||||
pinRank: pinRank || null,
|
||||
caseViewCreatedAt,
|
||||
});
|
||||
|
||||
const allCasesSlice = createSlice({
|
||||
name: 'cases',
|
||||
initialState,
|
||||
@@ -144,166 +94,21 @@ const allCasesSlice = createSlice({
|
||||
setLoading: (state, action) => {
|
||||
state.loading = action.payload;
|
||||
},
|
||||
updateCaseDetailsFirestore: (state, action) => {
|
||||
const { caseUpdates, isInitialLoad, isVisitPlanLocked, selectedAgent } = action.payload as {
|
||||
caseUpdates: CaseUpdates[];
|
||||
isInitialLoad: boolean;
|
||||
isVisitPlanLocked: boolean;
|
||||
selectedAgent: IReportee;
|
||||
};
|
||||
const newVisitCaseLoanIds: string[] = [];
|
||||
const newVisitCollectionCases: string[] = [];
|
||||
const removedVisitedCasesLoanIds: string[] = [];
|
||||
caseUpdates.forEach(({ updateType, updatedCaseDetail }) => {
|
||||
const { caseType, caseReferenceId, id, pinRank, caseViewCreatedAt } = updatedCaseDetail;
|
||||
|
||||
const caseId = caseReferenceId || id;
|
||||
switch (updateType) {
|
||||
case FirestoreUpdateTypes.MODIFIED: {
|
||||
const index = state.casesList?.findIndex(
|
||||
(caseItem) => caseItem.caseReferenceId?.toString() === caseId?.toString()
|
||||
);
|
||||
if (index !== -1) {
|
||||
if (pinRank && !state.casesList[index].pinRank) {
|
||||
// this is a new visit case
|
||||
newVisitCaseLoanIds.push(
|
||||
state.caseDetails[caseId]?.loanAccountNumber ??
|
||||
state.caseDetails[caseId]?.loanDetails?.loanAccountNumber ??
|
||||
0
|
||||
);
|
||||
if (caseType === CaseAllocationType.COLLECTION_CASE) {
|
||||
newVisitCollectionCases.push(caseId);
|
||||
}
|
||||
}
|
||||
if (!pinRank && state.casesList[index].pinRank) {
|
||||
// this is a removed visit case
|
||||
removedVisitedCasesLoanIds.push(
|
||||
state.caseDetails[caseId]?.loanAccountNumber ??
|
||||
state.caseDetails[caseId]?.loanDetails?.loanAccountNumber ??
|
||||
0
|
||||
);
|
||||
}
|
||||
state.casesList[index] = {
|
||||
...state.casesList[index],
|
||||
caseReferenceId: caseId,
|
||||
pinRank: pinRank || null,
|
||||
caseViewCreatedAt: caseViewCreatedAt,
|
||||
};
|
||||
}
|
||||
let currentTask = null;
|
||||
if (caseType !== CaseAllocationType.COLLECTION_CASE) {
|
||||
const { tasks, currentTask: updatedCurrentTask } = updatedCaseDetail;
|
||||
currentTask = tasks?.find((task) => task.taskType === (updatedCurrentTask as string));
|
||||
}
|
||||
state.caseDetails[caseId] = {
|
||||
...updatedCaseDetail,
|
||||
currentTask,
|
||||
isSynced: true,
|
||||
imageUri: state.caseDetails[caseId]?.imageUri,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case FirestoreUpdateTypes.ADDED: {
|
||||
if (state.caseDetails[caseId]) {
|
||||
return;
|
||||
}
|
||||
if (pinRank && caseType === CaseAllocationType.COLLECTION_CASE) {
|
||||
newVisitCollectionCases.push(caseId);
|
||||
}
|
||||
const caseListItem = getCaseListItem(caseId, pinRank, caseViewCreatedAt);
|
||||
state.casesList.unshift(caseListItem);
|
||||
let currentTask = null;
|
||||
if (caseType !== CaseAllocationType.COLLECTION_CASE) {
|
||||
const { tasks, currentTask: updatedCurrentTask } = updatedCaseDetail;
|
||||
currentTask = tasks?.find(
|
||||
(task) => task?.taskType === (updatedCurrentTask as string)
|
||||
);
|
||||
}
|
||||
const imageUri =
|
||||
findDocumentByDocumentType(
|
||||
updatedCaseDetail.documents,
|
||||
DOCUMENT_TYPE.OPTIMIZED_SELFIE
|
||||
)?.uri || '';
|
||||
state.caseDetails[caseId] = {
|
||||
...updatedCaseDetail,
|
||||
currentTask,
|
||||
isSynced: true,
|
||||
isNewlyAdded: !isInitialLoad,
|
||||
imageUri,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case FirestoreUpdateTypes.REMOVED: {
|
||||
const index = state.casesList.findIndex(
|
||||
(caseItem) => caseItem.caseReferenceId?.toString() === caseId?.toString()
|
||||
);
|
||||
const currentScreen = getCurrentScreen();
|
||||
// Redirect to home screen if the case deletes which the agent is seeing
|
||||
if (currentScreen?.name === 'caseDetail') {
|
||||
const { caseId: id } = currentScreen.params;
|
||||
if (id === caseId) {
|
||||
navigateToScreen('Home');
|
||||
}
|
||||
}
|
||||
if (index !== -1) {
|
||||
state.casesList.splice(index, 1);
|
||||
}
|
||||
delete state.caseDetails[caseId];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
const { pendingList, completedList, pinnedList } = getCaseListComponents(
|
||||
state.casesList,
|
||||
state.caseDetails
|
||||
);
|
||||
updateCaseDetailsFromFirestore: (state, action) => {
|
||||
const {
|
||||
updatedCasesList,
|
||||
updatedCaseDetails,
|
||||
updatedLoading,
|
||||
pendingList,
|
||||
completedList,
|
||||
pinnedList,
|
||||
} = action.payload;
|
||||
state.casesList = updatedCasesList;
|
||||
state.caseDetails = updatedCaseDetails;
|
||||
state.loading = updatedLoading;
|
||||
state.pendingList = pendingList;
|
||||
state.completedList = completedList;
|
||||
state.pinnedList = pinnedList;
|
||||
state.newVisitedCases = newVisitCollectionCases;
|
||||
if (state.loading) {
|
||||
if (selectedAgent && selectedAgent.referenceId !== MY_CASE_ITEM.referenceId) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_AGENT_CASE_LOAD_SUCCESS, {
|
||||
selectedAgent: selectedAgent.referenceId,
|
||||
});
|
||||
}
|
||||
state.loading = false;
|
||||
}
|
||||
|
||||
if (newVisitCaseLoanIds?.length > 0) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VISIT_PLAN_UPDATED, {
|
||||
newPinCases: [...newVisitCaseLoanIds],
|
||||
currentPinCases: pinnedList.map((item) =>
|
||||
getLoanAccountNumber(state.caseDetails[item?.caseReferenceId])
|
||||
),
|
||||
});
|
||||
if (!isVisitPlanLocked) {
|
||||
toast({
|
||||
type: 'info',
|
||||
text1: `${newVisitCaseLoanIds.length} case${
|
||||
newVisitCaseLoanIds.length > 1 ? 's' : ''
|
||||
} added to the visit plan`,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (removedVisitedCasesLoanIds.length > 0) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VISIT_PLAN_UPDATED, {
|
||||
newUnpinCases: [...removedVisitedCasesLoanIds],
|
||||
currentPinCases: pinnedList.map((item) =>
|
||||
getLoanAccountNumber(state.caseDetails[item?.caseReferenceId])
|
||||
),
|
||||
});
|
||||
if (!isVisitPlanLocked) {
|
||||
toast({
|
||||
type: 'info',
|
||||
text1: `${removedVisitedCasesLoanIds.length} case${
|
||||
removedVisitedCasesLoanIds.length > 1 ? 's' : ''
|
||||
} removed from the visit plan`,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
updateCaseDetail: (state, action) => {
|
||||
const { caseKey, updatedCaseDetail } = action.payload;
|
||||
@@ -348,7 +153,7 @@ const allCasesSlice = createSlice({
|
||||
resetTodoList: (state) => {
|
||||
state.intermediateTodoListMap = {};
|
||||
state.newlyPinnedCases = 0;
|
||||
state.pinnedRankCount = state.initialPinnedRankCount;
|
||||
state.pinnedRankCount = 0;
|
||||
},
|
||||
setSelectedTodoListMap: (state, action: { payload: ICaseItem }) => {
|
||||
const caseId = action.payload.caseReferenceId;
|
||||
@@ -405,10 +210,6 @@ const allCasesSlice = createSlice({
|
||||
state.caseDetails[id].isSynced = false;
|
||||
}
|
||||
},
|
||||
updateCaseDetailBeforeApiCall: (state, action) => {
|
||||
const { caseId } = action.payload;
|
||||
state.caseDetails[caseId].isApiCalled = false;
|
||||
},
|
||||
toggleNewlyAddedCase: (state, action) => {
|
||||
if (state.caseDetails[action.payload]) {
|
||||
state.caseDetails[action.payload].isNewlyAdded = false;
|
||||
@@ -418,44 +219,6 @@ const allCasesSlice = createSlice({
|
||||
setVisitPlansUpdating: (state, action) => {
|
||||
state.visitPlansUpdating = action.payload;
|
||||
},
|
||||
resetNewVisitedCases: (state) => {
|
||||
state.newVisitedCases = [];
|
||||
},
|
||||
syncCasesByFallback: (state, action) => {
|
||||
const { cases = [], deletedCaseIds = [], payloadCreatedAt } = action.payload;
|
||||
cases.forEach((caseItem: CaseDetail | null) => {
|
||||
if (!caseItem) {
|
||||
return;
|
||||
}
|
||||
const { caseViewCreatedAt, caseReferenceId, isSynced, pinRank } = caseItem;
|
||||
const isCaseAlreadyPresent = state.caseDetails[caseReferenceId];
|
||||
if (
|
||||
!isCaseAlreadyPresent ||
|
||||
(isSynced && caseViewCreatedAt && caseViewCreatedAt < payloadCreatedAt)
|
||||
) {
|
||||
const caseListItem = getCaseListItem(caseReferenceId, pinRank, caseViewCreatedAt);
|
||||
state.casesList.unshift(caseListItem);
|
||||
const imageUri = isCaseAlreadyPresent
|
||||
? state.caseDetails[caseReferenceId]?.imageUri
|
||||
: findDocumentByDocumentType(caseItem.documents, DOCUMENT_TYPE.OPTIMIZED_SELFIE)?.uri ||
|
||||
'';
|
||||
state.caseDetails[caseReferenceId] = { ...caseItem, isSynced: true, imageUri };
|
||||
}
|
||||
});
|
||||
const { pendingList, completedList, pinnedList } = getCaseListComponents(
|
||||
state.casesList,
|
||||
state.caseDetails
|
||||
);
|
||||
state.pendingList = pendingList;
|
||||
state.completedList = completedList;
|
||||
state.pinnedList = pinnedList;
|
||||
deletedCaseIds.forEach((caseItem: CaseDetail) => {
|
||||
const { caseViewCreatedAt, caseReferenceId } = caseItem;
|
||||
if (caseViewCreatedAt && caseViewCreatedAt < payloadCreatedAt) {
|
||||
delete state.caseDetails[caseReferenceId];
|
||||
}
|
||||
});
|
||||
},
|
||||
setCasesImageUri: (state, action) => {
|
||||
const imageUris: IAvatarUri[] = action.payload;
|
||||
imageUris.forEach(({ caseId, imageUri }) => {
|
||||
@@ -486,17 +249,14 @@ export const {
|
||||
resetSelectedTodoList,
|
||||
updateCaseDetail,
|
||||
updateSingleCase,
|
||||
updateCaseDetailsFirestore,
|
||||
toggleNewlyAddedCase,
|
||||
resetCasesData,
|
||||
updateCaseDetailBeforeApiCall,
|
||||
setVisitPlansUpdating,
|
||||
resetNewVisitedCases,
|
||||
syncCasesByFallback,
|
||||
setCasesImageUri,
|
||||
setSelectedCaseId,
|
||||
setAllCasesViewSearchQuery,
|
||||
setVisitPlanSearchQuery,
|
||||
updateCaseDetailsFromFirestore,
|
||||
} = allCasesSlice.actions;
|
||||
|
||||
export default allCasesSlice.reducer;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import { setShowAgentSelectionBottomSheet } from '../../reducer/reporteesSlice';
|
||||
import { IReportee } from './interface';
|
||||
import { resetCasesData } from '../../reducer/allCasesSlice';
|
||||
import { resetCasesData, setLoading } from '@reducers/allCasesSlice';
|
||||
import fuzzySort from '../../../RN-UI-LIB/src/utlis/fuzzySort';
|
||||
import fuzzysort from 'fuzzysort';
|
||||
import { MY_CASE_ITEM, setSelectedAgent } from '../../reducer/userSlice';
|
||||
@@ -15,6 +15,7 @@ import { resetFilters } from '../../reducer/filtersSlice';
|
||||
import { setGlobalUserData } from '../../constants/Global';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
import { firestoreService } from '@services/firestoreService';
|
||||
|
||||
interface IAgentListItem {
|
||||
agent: IReportee;
|
||||
@@ -22,8 +23,17 @@ interface IAgentListItem {
|
||||
searchQuery: string;
|
||||
}
|
||||
|
||||
const unsubscribeFromPreviousDoc = (agentId?: string) => {
|
||||
if (!agentId) {
|
||||
return;
|
||||
}
|
||||
firestoreService.unsubscribeFromPath(`allocations/${agentId}/cases`);
|
||||
firestoreService.unsubscribeFromPath(`filters/${agentId}`);
|
||||
};
|
||||
|
||||
const AgentListItem: React.FC<IAgentListItem> = ({ agent, leftAdornment, searchQuery }) => {
|
||||
const selectedAgent = useAppSelector((state) => state.user.selectedAgent) || MY_CASE_ITEM;
|
||||
const userReferenceId = useAppSelector((state) => state.user.user?.referenceId);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleAgentSelection = () => {
|
||||
@@ -31,10 +41,16 @@ const AgentListItem: React.FC<IAgentListItem> = ({ agent, leftAdornment, searchQ
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_AGENT_SELECT_BUTTON_CLICKED, {
|
||||
selectedAgentId,
|
||||
});
|
||||
if (selectedAgentId !== MY_CASE_ITEM.referenceId) {
|
||||
unsubscribeFromPreviousDoc(selectedAgent.referenceId);
|
||||
} else {
|
||||
unsubscribeFromPreviousDoc(userReferenceId);
|
||||
}
|
||||
dispatch(setSelectedAgent(agent));
|
||||
setGlobalUserData({ selectedAgentId });
|
||||
dispatch(resetFilters());
|
||||
dispatch(resetCasesData());
|
||||
dispatch(setLoading(true));
|
||||
dispatch(setShowAgentSelectionBottomSheet(false));
|
||||
};
|
||||
|
||||
|
||||
@@ -125,7 +125,6 @@ const AllCasesMain = () => {
|
||||
initCrashlytics(userState);
|
||||
}
|
||||
dispatch(setVisitPlansUpdating(false));
|
||||
dispatch(setLoading(false));
|
||||
dispatch(resetTodoList());
|
||||
dispatch(resetSelectedTodoList());
|
||||
}, []);
|
||||
|
||||
253
src/screens/caseDetails/utils/caseDetailsUtils.tsx
Normal file
253
src/screens/caseDetails/utils/caseDetailsUtils.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
import { FirebaseFirestoreTypes } from '@react-native-firebase/firestore';
|
||||
import { ICaseItem, IReportee } from '@screens/allCases/interface';
|
||||
import { InteractionManager } from 'react-native';
|
||||
import { CaseDetail, DOCUMENT_TYPE } from '../interface';
|
||||
import { CLICKSTREAM_EVENT_NAMES, FirestoreUpdateTypes } from '@common/Constants';
|
||||
import store, { AppDispatch } from '@store';
|
||||
import {
|
||||
findDocumentByDocumentType,
|
||||
getLoanAccountNumber,
|
||||
} from '@components/utlis/commonFunctions';
|
||||
import { getCurrentScreen, navigateToScreen } from '@components/utlis/navigationUtlis';
|
||||
import { COMPLETED_STATUSES } from '@screens/allCases/constants';
|
||||
import { MY_CASE_ITEM } from '@reducers/userSlice';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { toast } from '@rn-ui-lib/components/toast';
|
||||
import { updateCaseDetailsFromFirestore } from '@reducers/allCasesSlice';
|
||||
import { _map } from '@rn-ui-lib/utils/common';
|
||||
|
||||
interface UpdateCasesProps {
|
||||
selectedAgent: IReportee | null;
|
||||
isVisitPlanLocked?: boolean;
|
||||
querySnapshot?: FirebaseFirestoreTypes.QuerySnapshot;
|
||||
caseUpdateList?: Array<{ updateType: string; updatedCaseDetail: CaseDetail }>;
|
||||
}
|
||||
|
||||
export const updateCases = (props: UpdateCasesProps) => (dispatch: AppDispatch) => {
|
||||
const {
|
||||
selectedAgent,
|
||||
isVisitPlanLocked = false,
|
||||
querySnapshot,
|
||||
caseUpdateList,
|
||||
} = props;
|
||||
const caseDetails = store?.getState()?.allCases?.caseDetails || {};
|
||||
const casesList = store?.getState()?.allCases?.casesList || [];
|
||||
const loading = store?.getState()?.allCases?.loading || false;
|
||||
const isInitialLoad = !casesList.length;
|
||||
let newlyAddedCases = 0;
|
||||
let deletedCases = 0;
|
||||
let updatedCaseDetails = { ...caseDetails };
|
||||
const newVisitCaseLoanIds: string[] = [];
|
||||
const removedVisitedCasesLoanIds: string[] = [];
|
||||
const newVisitCollectionCases: string[] = [];
|
||||
let updatedLoading = loading;
|
||||
|
||||
const getCaseListComponents = (caseDetails: Record<string, CaseDetail>) => {
|
||||
const pendingList: ICaseItem[] = [];
|
||||
const completedList: ICaseItem[] = [];
|
||||
const pinnedList: ICaseItem[] = [];
|
||||
const updatedCasesList: ICaseItem[] = [];
|
||||
_map(caseDetails, (caseReferenceId: string) => {
|
||||
if (!caseDetails?.[caseReferenceId]) {
|
||||
return;
|
||||
}
|
||||
const { caseStatus, pinRank, caseViewCreatedAt } = caseDetails?.[caseReferenceId];
|
||||
const isCaseCompleted = COMPLETED_STATUSES.includes(caseStatus);
|
||||
const caseItem = { caseReferenceId, pinRank, caseViewCreatedAt } as ICaseItem;
|
||||
updatedCasesList.push(caseItem);
|
||||
isCaseCompleted
|
||||
? completedList.push(caseItem)
|
||||
: pinRank
|
||||
? pinnedList.push(caseItem)
|
||||
: pendingList.push(caseItem);
|
||||
});
|
||||
return { pendingList, completedList, pinnedList, updatedCasesList };
|
||||
};
|
||||
|
||||
const handleCaseAddition = (newCaseDetail: CaseDetail) => {
|
||||
const { caseReferenceId, id, pinRank } = newCaseDetail || {};
|
||||
const caseId = caseReferenceId || id;
|
||||
if (caseDetails?.[caseId]) {
|
||||
return;
|
||||
}
|
||||
newlyAddedCases++;
|
||||
const newVisitCollectionCases: string[] = [];
|
||||
if (pinRank) {
|
||||
newVisitCollectionCases.push(caseId);
|
||||
}
|
||||
const imageUri =
|
||||
findDocumentByDocumentType(newCaseDetail.documents, DOCUMENT_TYPE.OPTIMIZED_SELFIE)?.uri ||
|
||||
'';
|
||||
const newCaseDetails = {
|
||||
...newCaseDetail,
|
||||
isSynced: true,
|
||||
isNewlyAdded: !isInitialLoad,
|
||||
imageUri,
|
||||
};
|
||||
updatedCaseDetails = { ...updatedCaseDetails, [caseId]: newCaseDetails };
|
||||
};
|
||||
|
||||
const handleCaseModification = (newCaseDetail: CaseDetail) => {
|
||||
const { caseReferenceId, id, pinRank, caseViewCreatedAt } = newCaseDetail || {};
|
||||
const caseId = caseReferenceId || id;
|
||||
if (!updatedCaseDetails?.[caseId]) {
|
||||
return;
|
||||
}
|
||||
const caseDetail = updatedCaseDetails[caseId];
|
||||
if (pinRank && !caseDetail?.pinRank) {
|
||||
// this is a new visit case
|
||||
newVisitCaseLoanIds.push(caseDetails[caseId]?.loanAccountNumber);
|
||||
newVisitCollectionCases.push(caseId);
|
||||
}
|
||||
if (!pinRank && caseDetail?.pinRank) {
|
||||
// this is a removed visit case
|
||||
removedVisitedCasesLoanIds.push(caseDetails[caseId]?.loanAccountNumber);
|
||||
}
|
||||
const newCaseDetails = {
|
||||
...newCaseDetail,
|
||||
isSynced: true,
|
||||
imageUri: caseDetail?.imageUri,
|
||||
};
|
||||
updatedCaseDetails = { ...updatedCaseDetails, [caseId]: newCaseDetails };
|
||||
};
|
||||
|
||||
const handleCaseRemoval = (newCaseDetail: CaseDetail) => {
|
||||
const { caseReferenceId, id } = newCaseDetail || {};
|
||||
const caseId = caseReferenceId || id;
|
||||
if (!updatedCaseDetails?.[caseId]) {
|
||||
return;
|
||||
}
|
||||
const currentScreen = getCurrentScreen();
|
||||
// Redirect to home screen if the case deletes which the agent is seeing
|
||||
if (currentScreen?.name === 'caseDetail') {
|
||||
const { caseId: id } = currentScreen.params;
|
||||
if (id === caseId) {
|
||||
navigateToScreen('Home');
|
||||
}
|
||||
}
|
||||
delete updatedCaseDetails[caseId];
|
||||
deletedCases++;
|
||||
};
|
||||
|
||||
const updateCaseDetails = (newCaseDetail: CaseDetail, updateType: string) => {
|
||||
switch (updateType) {
|
||||
case FirestoreUpdateTypes.ADDED:
|
||||
handleCaseAddition(newCaseDetail);
|
||||
break;
|
||||
case FirestoreUpdateTypes.MODIFIED:
|
||||
handleCaseModification(newCaseDetail);
|
||||
break;
|
||||
case FirestoreUpdateTypes.REMOVED:
|
||||
handleCaseRemoval(newCaseDetail);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
||||
const showCaseUpdationToast = (newlyAddedCases: number, deletedCases: number) => {
|
||||
let toastConfig: any = null;
|
||||
const addedCasesText = newlyAddedCases
|
||||
? `${newlyAddedCases} new case${newlyAddedCases > 1 ? 's' : ''} allocated`
|
||||
: '';
|
||||
const deletedCasesText = deletedCases
|
||||
? `${deletedCases} case${deletedCases > 1 ? 's' : ''} de-allocated`
|
||||
: '';
|
||||
if (newlyAddedCases && deletedCases) {
|
||||
toastConfig = {
|
||||
type: 'info',
|
||||
text1: `${addedCasesText} & ${deletedCasesText}`,
|
||||
};
|
||||
} else if (newlyAddedCases) {
|
||||
toastConfig = { type: 'success', text1: addedCasesText };
|
||||
} else if (deletedCases) {
|
||||
toastConfig = { type: 'error', text1: deletedCasesText };
|
||||
}
|
||||
if (toastConfig) {
|
||||
toast(toastConfig);
|
||||
}
|
||||
};
|
||||
|
||||
const processCases = async () => {
|
||||
if (caseUpdateList?.length) {
|
||||
// If we are providing cases list directly
|
||||
caseUpdateList.forEach((caseUpdate) => {
|
||||
const { updateType, updatedCaseDetail } = caseUpdate;
|
||||
updateCaseDetails(updatedCaseDetail, updateType);
|
||||
});
|
||||
} else {
|
||||
// If we are providing querySnapshot from firestore subscription
|
||||
await querySnapshot
|
||||
?.docChanges()
|
||||
?.forEach((documentSnapshot: FirebaseFirestoreTypes.DocumentChange) => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
const updateType = documentSnapshot.type;
|
||||
const updatedCaseDetail = documentSnapshot.doc.data() as CaseDetail;
|
||||
updateCaseDetails(updatedCaseDetail, updateType);
|
||||
});
|
||||
});
|
||||
}
|
||||
const { pendingList, completedList, pinnedList, updatedCasesList } =
|
||||
getCaseListComponents(updatedCaseDetails);
|
||||
if (updatedLoading) {
|
||||
if (selectedAgent && selectedAgent.referenceId !== MY_CASE_ITEM.referenceId) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_AGENT_CASE_LOAD_SUCCESS, {
|
||||
selectedAgent: selectedAgent.referenceId,
|
||||
});
|
||||
}
|
||||
updatedLoading = false;
|
||||
}
|
||||
|
||||
if (newVisitCaseLoanIds?.length > 0) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VISIT_PLAN_UPDATED, {
|
||||
newPinCases: [...newVisitCaseLoanIds],
|
||||
currentPinCases: pinnedList.map((item) =>
|
||||
getLoanAccountNumber(updatedCaseDetails[item?.caseReferenceId])
|
||||
),
|
||||
});
|
||||
if (!isVisitPlanLocked) {
|
||||
toast({
|
||||
type: 'info',
|
||||
text1: `${newVisitCaseLoanIds.length} case${
|
||||
newVisitCaseLoanIds.length > 1 ? 's' : ''
|
||||
} added to the visit plan`,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (removedVisitedCasesLoanIds.length > 0) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VISIT_PLAN_UPDATED, {
|
||||
newUnpinCases: [...removedVisitedCasesLoanIds],
|
||||
currentPinCases: pinnedList.map((item) =>
|
||||
getLoanAccountNumber(updatedCaseDetails[item?.caseReferenceId])
|
||||
),
|
||||
});
|
||||
if (!isVisitPlanLocked) {
|
||||
toast({
|
||||
type: 'info',
|
||||
text1: `${removedVisitedCasesLoanIds.length} case${
|
||||
removedVisitedCasesLoanIds.length > 1 ? 's' : ''
|
||||
} removed from the visit plan`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(
|
||||
updateCaseDetailsFromFirestore({
|
||||
updatedCasesList,
|
||||
updatedCaseDetails,
|
||||
updatedLoading,
|
||||
pendingList,
|
||||
completedList,
|
||||
pinnedList,
|
||||
})
|
||||
);
|
||||
};
|
||||
requestAnimationFrame(() => {
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
await processCases();
|
||||
!isInitialLoad && showCaseUpdationToast(newlyAddedCases, deletedCases);
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SNAPSHOT_LISTENER, {
|
||||
snapshot_path: 'handleCasesUpdate',
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
setActivityTimeOnApp,
|
||||
setActivityTimeWindowHigh,
|
||||
setActivityTimeWindowMedium,
|
||||
setEnableFirestoreResync,
|
||||
setFirestoreResyncIntervalInMinutes,
|
||||
setAudioUploadJobIntervalInMinutes,
|
||||
setCalendarAndAccountsUploadJobIntervalInMinutes,
|
||||
@@ -40,9 +39,6 @@ async function fetchUpdatedRemoteConfig() {
|
||||
.getValue('ACTIVITY_TIME_WINDOW_MEDIUM')
|
||||
.asNumber();
|
||||
const BLACKLISTED_APPS = remoteConfig().getValue('BLACKLISTED_APPS').asString();
|
||||
const ENABLE_FIRESTORE_RESYNC = remoteConfig()
|
||||
.getValue('ENABLE_FIRESTORE_RESYNC')
|
||||
.asBoolean();
|
||||
const FIRESTORE_RESYNC_INTERVAL_IN_MINUTES = remoteConfig()
|
||||
.getValue('FIRESTORE_RESYNC_INTERVAL_IN_MINUTES')
|
||||
.asNumber();
|
||||
@@ -68,7 +64,6 @@ async function fetchUpdatedRemoteConfig() {
|
||||
setActivityTimeWindowHigh(ACTIVITY_TIME_WINDOW_HIGH);
|
||||
setActivityTimeWindowMedium(ACTIVITY_TIME_WINDOW_MEDIUM);
|
||||
setBlacklistedAppsList(BLACKLISTED_APPS);
|
||||
setEnableFirestoreResync(ENABLE_FIRESTORE_RESYNC);
|
||||
setFirestoreResyncIntervalInMinutes(FIRESTORE_RESYNC_INTERVAL_IN_MINUTES);
|
||||
|
||||
if(DATA_SYNC_JOB_INTERVAL_IN_MINUTES) setDataSyncJobIntervalInMinutes(DATA_SYNC_JOB_INTERVAL_IN_MINUTES);
|
||||
|
||||
69
src/services/firestoreService.js
Normal file
69
src/services/firestoreService.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import firestore from '@react-native-firebase/firestore';
|
||||
import auth from '@react-native-firebase/auth';
|
||||
import { logError } from '../components/utlis/errorUtils';
|
||||
|
||||
const subscriptions = new Map();
|
||||
|
||||
const subscribeToDoc = (collectionPath, successCb) => {
|
||||
if (subscriptions.has(collectionPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unsubscribe = firestore()
|
||||
.doc(collectionPath)
|
||||
.onSnapshot(successCb, (error) => {
|
||||
logError(error, `Error in firetore subscription: ${collectionPath}`);
|
||||
});
|
||||
|
||||
subscriptions.set(collectionPath, unsubscribe);
|
||||
};
|
||||
|
||||
const subscribeToCollection = (collectionPath, queryFn, successCb) => {
|
||||
if (subscriptions.has(collectionPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ref = firestore().collection(collectionPath);
|
||||
if (queryFn) {
|
||||
ref = queryFn(ref);
|
||||
}
|
||||
|
||||
const unsubscribe = ref.onSnapshot(successCb, (error) => {
|
||||
logError(error, `Error in firestore subscription: ${collectionPath}`);
|
||||
});
|
||||
|
||||
subscriptions.set(collectionPath, unsubscribe);
|
||||
};
|
||||
|
||||
const unsubscribeFromPath = (collectionPath) => {
|
||||
const unsubscribe = subscriptions.get(collectionPath);
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
subscriptions.delete(collectionPath);
|
||||
}
|
||||
};
|
||||
|
||||
const unsubscribeAll = () => {
|
||||
subscriptions.forEach((unsubscribe, path) => {
|
||||
unsubscribe();
|
||||
});
|
||||
subscriptions.clear();
|
||||
};
|
||||
|
||||
const signInUserToFirebase = async (firebaseToken, onSuccess, onError) => {
|
||||
try {
|
||||
await auth().signInWithCustomToken(firebaseToken);
|
||||
onSuccess && onSuccess();
|
||||
} catch (error) {
|
||||
onError && onError(error);
|
||||
logError(error, 'Error in signInUserToFirebase');
|
||||
}
|
||||
};
|
||||
|
||||
export const firestoreService = {
|
||||
subscribeToDoc,
|
||||
subscribeToCollection,
|
||||
unsubscribeFromPath,
|
||||
unsubscribeAll,
|
||||
signInUserToFirebase,
|
||||
};
|
||||
Reference in New Issue
Block a user