From 958aa823d1c7a72011cd7e50f0b9380a91017769 Mon Sep 17 00:00:00 2001 From: Aman Chaturvedi Date: Thu, 7 Sep 2023 13:34:19 +0530 Subject: [PATCH] TP-34788 | Cosmos for AM/TLs --- App.tsx | 7 +- package.json | 1 - src/action/authActions.ts | 16 ++- src/action/reporteesActions.ts | 19 ++++ src/common/TrackingComponent.tsx | 16 ++- src/components/utlis/apiHelper.ts | 4 + src/hooks/useFirestoreUpdates.ts | 53 +++++++-- src/reducer/allCasesSlice.ts | 28 +---- src/reducer/reporteesSlice.ts | 36 ++++++ src/reducer/userSlice.ts | 22 +++- src/screens/Profile/index.tsx | 93 +++++++++------- src/screens/allCases/AgentListItem.tsx | 79 ++++++++++++++ src/screens/allCases/AgentListSearchbar.tsx | 21 ++++ src/screens/allCases/AgentListView.tsx | 103 ++++++++++++++++++ src/screens/allCases/AgentListViewLoading.tsx | 21 ++++ src/screens/allCases/AgentsListContainer.tsx | 72 ++++++++++++ src/screens/allCases/CasesList.tsx | 3 +- src/screens/allCases/EmptyList.tsx | 16 ++- src/screens/allCases/HeaderLabel.tsx | 64 +++++++++-- src/screens/allCases/ListItem.tsx | 3 +- src/screens/allCases/index.tsx | 8 +- src/screens/allCases/interface.ts | 12 ++ src/screens/allCases/utils.ts | 34 +++++- src/screens/auth/ProtectedRouter.tsx | 4 + 24 files changed, 625 insertions(+), 110 deletions(-) create mode 100644 src/action/reporteesActions.ts create mode 100644 src/reducer/reporteesSlice.ts create mode 100644 src/screens/allCases/AgentListItem.tsx create mode 100644 src/screens/allCases/AgentListSearchbar.tsx create mode 100644 src/screens/allCases/AgentListView.tsx create mode 100644 src/screens/allCases/AgentListViewLoading.tsx create mode 100644 src/screens/allCases/AgentsListContainer.tsx diff --git a/App.tsx b/App.tsx index 751c466c..ab69ddbd 100644 --- a/App.tsx +++ b/App.tsx @@ -120,11 +120,14 @@ const App = () => { return ( - } persistor={persistor}> + } + persistor={persistor} + > } + fallBack={} loading={!isGlobalDocumentMapLoaded} children={ {permissions ? : } diff --git a/package.json b/package.json index 6db56061..31478239 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "@react-navigation/native": "6.1.4", "@react-navigation/native-stack": "6.9.4", "@reduxjs/toolkit": "1.9.1", - "@sanar/react-native-highlight-text": "^1.0.2", "@sentry/react-native": "5.5.0", "@shopify/flash-list": "1.4.3", "@supersami/rn-foreground-service": "^2.1.0", diff --git a/src/action/authActions.ts b/src/action/authActions.ts index 2613a53a..233cf563 100644 --- a/src/action/authActions.ts +++ b/src/action/authActions.ts @@ -1,6 +1,6 @@ import { ToastMessages } from './../screens/allCases/constants'; import 'react-native-get-random-values'; -import { IUser, setAuthData } from '../reducer/userSlice'; +import { IUser, IUserRole, setAgentRole, setAuthData } from '../reducer/userSlice'; import axiosInstance, { ApiKeys, API_STATUS_CODE, getApiUrl } from '../components/utlis/apiHelper'; import { resetLoginForm, @@ -115,6 +115,7 @@ export const verifyGoogleSignIn = (idToken: string) => async (dispatch: AppDispa }); dispatch(setVerifyOTPSuccess('Login Successfully!')); dispatch(resetLoginForm()); + dispatch(getAgentDetail()); } } catch (error: GenericType) { await handleGoogleLogout(); @@ -157,6 +158,7 @@ export const verifyOTP = ); dispatch(setVerifyOTPSuccess('OTP verified')); dispatch(resetLoginForm()); + dispatch(getAgentDetail()); }) .catch((err) => { dispatch(setVerifyOTPError('Invalid OTP entered. Kindly try again')); @@ -229,6 +231,7 @@ export const handleImpersonatedUserLogin = isImpersonated: true, }) ); + dispatch(getAgentDetail()); successCallback?.(); }) .catch((err) => { @@ -241,3 +244,14 @@ export const handleImpersonatedUserLogin = }) .finally(() => finallyCallback?.()); }; + +export const getAgentDetail = () => (dispatch: AppDispatch) => { + const url = getApiUrl(ApiKeys.GET_AGENT_DETAIL); + return axiosInstance.get(url).then((response) => { + if (response.status === API_STATUS_CODE.OK) { + const roles = response?.data?.roles || []; + const isTeamLead = roles?.includes(IUserRole.ROLE_TEAM_LEAD); + dispatch(setAgentRole(isTeamLead)); + } + }); +}; diff --git a/src/action/reporteesActions.ts b/src/action/reporteesActions.ts new file mode 100644 index 00000000..b2345b92 --- /dev/null +++ b/src/action/reporteesActions.ts @@ -0,0 +1,19 @@ +import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper'; +import { setReporteesList, setReporteesLoading } from '../reducer/reporteesSlice'; +import { AppDispatch } from '../store/store'; + +export const getAgentsList = () => (dispatch: AppDispatch) => { + const url = getApiUrl(ApiKeys.REPORTEES); //, null, {pageNo: 1, pageSize: 10, fetchAll: 'true'}); + dispatch(setReporteesLoading(true)); + axiosInstance + .get(url) + .then((res) => { + const reporteesList = res.data?.reportees; + if (reporteesList) { + dispatch(setReporteesList(reporteesList)); + } + }) + .finally(() => { + dispatch(setReporteesLoading(false)); + }); +}; diff --git a/src/common/TrackingComponent.tsx b/src/common/TrackingComponent.tsx index 580ea393..4d913b89 100644 --- a/src/common/TrackingComponent.tsx +++ b/src/common/TrackingComponent.tsx @@ -55,6 +55,7 @@ const TrackingComponent: React.FC = ({ children }) => { const isOnline = useIsOnline(); const dispatch = useAppDispatch(); const appState = useRef(AppState.currentState); + const isTeamLead = useAppSelector((state) => state.user.isTeamLead); const { referenceId, @@ -193,12 +194,6 @@ const TrackingComponent: React.FC = ({ children }) => { delay: 3 * MILLISECONDS_IN_A_MINUTE, // 3 minutes onLoop: true, }, - { - taskId: FOREGROUND_TASKS.FIRESTORE_FALLBACK, - task: handleGetCaseSyncStatus, - delay: 5 * MILLISECONDS_IN_A_MINUTE, // 5 minutes - onLoop: true, - }, { taskId: FOREGROUND_TASKS.UPDATE_AGENT_ACTIVENESS, task: handleUpdateActiveness, @@ -207,6 +202,15 @@ const TrackingComponent: React.FC = ({ 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; diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index 19422a6c..cea154fa 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -54,6 +54,8 @@ export enum ApiKeys { GLOBAL_CONFIG = 'GLOBAL_CONFIG', UPLOAD_IMAGE_ID = 'UPLOAD_IMAGE_ID', GET_DOCUMENTS = 'GET_DOCUMENTS', + REPORTEES = 'REPORTEES', + GET_AGENT_DETAIL = 'GET_AGENT_DETAIL', } export const API_URLS: Record = {} as Record; @@ -93,6 +95,8 @@ API_URLS[ApiKeys.UPLOAD_FEEDBACK_IMAGES] = '/feedback/persist-original-images'; API_URLS[ApiKeys.GLOBAL_CONFIG] = '/global-config'; API_URLS[ApiKeys.UPLOAD_IMAGE_ID] = '/user/documents/selfie'; API_URLS[ApiKeys.GET_DOCUMENTS] = '/user/documents'; +API_URLS[ApiKeys.REPORTEES] = '/user/reportees'; +API_URLS[ApiKeys.GET_AGENT_DETAIL] = '/user/role-info'; export const API_STATUS_CODE = { OK: 200, diff --git a/src/hooks/useFirestoreUpdates.ts b/src/hooks/useFirestoreUpdates.ts index ba7c9825..6b3f1a7c 100644 --- a/src/hooks/useFirestoreUpdates.ts +++ b/src/hooks/useFirestoreUpdates.ts @@ -8,7 +8,7 @@ import { FirestoreUpdateTypes } from '../common/Constants'; import { toast } from '../../RN-UI-LIB/src/components/toast'; import auth from '@react-native-firebase/auth'; import { updateAvTemplateData, updateCollectionTemplateData } from '../reducer/caseReducer'; -import { ILockData, setLockData, VisitPlanStatus } from '../reducer/userSlice'; +import { ILockData, MY_CASE_ITEM, setLockData, VisitPlanStatus } from '../reducer/userSlice'; import { setFilters } from '../reducer/filtersSlice'; import { FormTemplateV1 } from '../types/template.types'; import { ToastMessages } from '../screens/allCases/constants'; @@ -33,12 +33,13 @@ const isUserSignedIn = () => { const useFirestoreUpdates = () => { const { - user: { user, isLoggedIn, sessionDetails, lock }, - allCases: { caseDetails, casesList, loading }, + user: { user, isLoggedIn, sessionDetails, lock, selectedAgent = MY_CASE_ITEM }, + allCases: { caseDetails, casesList }, } = useAppSelector((state: RootState) => ({ user: state.user || {}, allCases: state.allCases, })); + const isTeamLead = useAppSelector((state) => state.user.isTeamLead); const lockRef = useRef(null); useEffect(() => { @@ -155,6 +156,7 @@ const useFirestoreUpdates = () => { const handleError = (err: any, collectionPath?: string) => { const errMsg = `Error while fetching fireStore snapshot: referenceId: ${user?.referenceId} collectionPath: ${collectionPath}`; logError(err as Error, errMsg); + dispatch(setLoading(false)); }; const signInUserToFirebase = () => { @@ -176,12 +178,6 @@ const useFirestoreUpdates = () => { }); }; - const subscribeToCollection = (successCb: GenericFunctionArgs, collectionPath: string) => { - return firestore() - .collection(collectionPath) - .onSnapshot(successCb, (err) => handleError(err, collectionPath)); - }; - const subscribeToDoc = (successCb: GenericFunctionArgs, collectionPath: string) => { return firestore() .doc(collectionPath) @@ -189,7 +185,11 @@ const useFirestoreUpdates = () => { }; const subscribeToCases = () => { - const collectionPath = `allocations/${user?.referenceId}/cases`; + let refId = user?.referenceId; + if (isTeamLead && selectedAgent?.agentReferenceId !== MY_CASE_ITEM.agentReferenceId) { + refId = selectedAgent?.agentReferenceId; + } + const collectionPath = `allocations/${refId}/cases`; return firestore() .collection(collectionPath) .orderBy('totalOverdueAmount', 'asc') // It is descending order only, but acting weirdly. Need to check. @@ -212,7 +212,11 @@ const useFirestoreUpdates = () => { }; const subscribeToFilters = () => { - const collectionPath = `filters/${user?.referenceId}`; + let refId = user?.referenceId; + if (isTeamLead && selectedAgent?.agentReferenceId !== MY_CASE_ITEM.agentReferenceId) { + refId = selectedAgent?.agentReferenceId; + } + const collectionPath = `filters/${refId}`; return subscribeToDoc(handleFilterUpdate, collectionPath); }; @@ -263,6 +267,33 @@ const useFirestoreUpdates = () => { }; }, [isLoggedIn, user?.referenceId]); + useEffect(() => { + if (!isTeamLead) { + return; + } + if ( + !selectedAgent?.agentReferenceId || + selectedAgent?.agentReferenceId === MY_CASE_ITEM.agentReferenceId + ) { + 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]); + useEffect(() => { forceUninstallUnsubscribe = subscribeToForceUninstall(); }, []); diff --git a/src/reducer/allCasesSlice.ts b/src/reducer/allCasesSlice.ts index 383f0815..4756b249 100644 --- a/src/reducer/allCasesSlice.ts +++ b/src/reducer/allCasesSlice.ts @@ -242,9 +242,6 @@ const allCasesSlice = createSlice({ isInitialLoad: boolean; isVisitPlanLocked: boolean; }; - if (state.loading) { - state.loading = false; - } let newVisitCaseLoanIds: string[] = []; let newVisitCollectionCases: string[] = []; let removedVisitedCasesLoanIds: string[] = []; @@ -356,6 +353,9 @@ const allCasesSlice = createSlice({ state.completedList = completedList; state.pinnedList = pinnedList; state.newVisitedCases = newVisitCollectionCases; + if (state.loading) { + state.loading = false; + } if (newVisitCaseLoanIds?.length > 0) { addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VISIT_PLAN_UPDATED, { @@ -503,26 +503,8 @@ const allCasesSlice = createSlice({ state.caseDetails[action.payload].isNewlyAdded = false; } }, - resetCasesData: (state) => { - state.casesList = []; - state.casesListMap = {}; - state.intermediateTodoList = []; - state.intermediateTodoListMap = {}; - state.selectedTodoListCount = 0; - state.selectedTodoListMap = {}; - state.initialPinnedRankCount = 0; - state.pinnedRankCount = 0; - state.loading = false; - state.filterList = []; - state.newlyPinnedCases = 0; - state.completedCases = 0; - state.caseDetails = {}; - state.searchQuery = ''; - state.isOnboarded = state.isOnboarded; - state.newVisitedCases = []; - state.pendingList = []; - state.pinnedList = []; - state.completedList = []; + resetCasesData: () => { + return initialState; }, setVisitPlansUpdating: (state, action) => { state.visitPlansUpdating = action.payload; diff --git a/src/reducer/reporteesSlice.ts b/src/reducer/reporteesSlice.ts new file mode 100644 index 00000000..815dbbc3 --- /dev/null +++ b/src/reducer/reporteesSlice.ts @@ -0,0 +1,36 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { IReportee } from '../screens/allCases/interface'; +import { MY_CASE_ITEM } from './userSlice'; + +interface IReporteesSlice { + agentsList: IReportee[]; + isLoading: boolean; + showAgentSelectionBottomSheet: boolean; +} + +const initialState: IReporteesSlice = { + agentsList: [], + isLoading: false, + showAgentSelectionBottomSheet: false, +}; + +export const userSlice = createSlice({ + name: 'reportees', + initialState, + reducers: { + setReporteesList: (state, action) => { + state.agentsList = [MY_CASE_ITEM, ...(action.payload || [])]; + }, + setReporteesLoading: (state, action) => { + state.isLoading = action.payload; + }, + setShowAgentSelectionBottomSheet: (state, action) => { + state.showAgentSelectionBottomSheet = action.payload; + }, + }, +}); + +export const { setReporteesList, setReporteesLoading, setShowAgentSelectionBottomSheet } = + userSlice.actions; + +export default userSlice.reducer; diff --git a/src/reducer/userSlice.ts b/src/reducer/userSlice.ts index 55f3e75c..7acf14fa 100644 --- a/src/reducer/userSlice.ts +++ b/src/reducer/userSlice.ts @@ -1,5 +1,6 @@ import { createSlice } from '@reduxjs/toolkit'; import { setGlobalUserData } from '../constants/Global'; +import { IReportee } from '../screens/allCases/interface'; interface ISessionDetails { sessionToken: string; @@ -9,8 +10,16 @@ interface ISessionDetails { export enum IUserRole { SUPER_USER = 'ADDRESS_VERIFICATION:API:SUPER_USER', + ROLE_TEAM_LEAD = 'ROLE_TEAM_LEAD', } +export const MY_CASE_ITEM = { + name: 'My Cases', + agentReferenceId: 'MY_CASES', + agencyCode: '', + agencyName: '', +}; + interface IUserDetails { emailId: string; referenceId: string; @@ -47,6 +56,8 @@ export interface IUserSlice extends IUser { clickstreamEvents: IClickstreamEvents[]; isImpersonated: boolean; lock: ILockData; + selectedAgent: IReportee; + isTeamLead: boolean; } const initialState: IUserSlice = { @@ -59,6 +70,8 @@ const initialState: IUserSlice = { lock: { visitPlanStatus: VisitPlanStatus.UNLOCKED, }, + selectedAgent: MY_CASE_ITEM, + isTeamLead: false, }; export const userSlice = createSlice({ @@ -86,9 +99,16 @@ export const userSlice = createSlice({ state.lock = action.payload; } }, + setSelectedAgent: (state, action) => { + state.selectedAgent = action.payload; + }, + setAgentRole: (state, action) => { + state.isTeamLead = action.payload ?? false; + }, }, }); -export const { setAuthData, setDeviceId, setLockData } = userSlice.actions; +export const { setAuthData, setDeviceId, setLockData, setSelectedAgent, setAgentRole } = + userSlice.actions; export default userSlice.reducer; diff --git a/src/screens/Profile/index.tsx b/src/screens/Profile/index.tsx index 6a3d99dd..b7fde190 100644 --- a/src/screens/Profile/index.tsx +++ b/src/screens/Profile/index.tsx @@ -30,7 +30,7 @@ import VersionNumber from 'react-native-version-number'; import { useFocusEffect } from '@react-navigation/native'; import { CaseDetail } from '../caseDetails/interface'; import CaseItem from '../allCases/CaseItem'; -import { IUserRole } from '../../reducer/userSlice'; +import { IUserRole, MY_CASE_ITEM } from '../../reducer/userSlice'; import QuestionMarkIcon from '../../assets/icons/QuestionMarkIcon'; import IDCardImageCapture from './IDCardImageCapture'; import AgentIdCard from './AgentIdCard'; @@ -53,6 +53,8 @@ const Profile: React.FC = () => { caseDetails, supportLink, pendingCases, + selectedAgent, + isTeamLead, } = useAppSelector((state: RootState) => ({ originalImageUri: state.profile.originalImageUri, imageUri: state.profile.imageUri, @@ -65,6 +67,8 @@ const Profile: React.FC = () => { supportLink: state.config.data?.supportLink, isUploadingImage: state.profile.isUploadingImage, pendingCases: state.allCases.pendingList, + selectedAgent: state.user.selectedAgent, + isTeamLead: state.user.isTeamLead, })); useEffect(() => { @@ -132,6 +136,9 @@ const Profile: React.FC = () => { addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ID_CARD_CLICKED); }; + const showCompletedCases = + !isTeamLead || selectedAgent?.agentReferenceId === MY_CASE_ITEM.agentReferenceId; + return ( { } /> - - {hideUploadImageBtn ? null : } + {showCompletedCases ? ( - - - - Completed cases ({numberOfCompletedCases}) - - {numberOfCompletedCases - ? completeCasesList.slice(0, 2).map((caseItem) => { - const caseDetailItem = caseDetails[caseItem.caseReferenceId] as CaseDetail; - return ( - - ); - }) - : null} - {numberOfCompletedCases > 2 ? ( -