diff --git a/App.tsx b/App.tsx index cebb64e0..db22c9ec 100644 --- a/App.tsx +++ b/App.tsx @@ -135,7 +135,10 @@ function App() { return ( - } persistor={persistor}> + } + persistor={persistor} + > { @@ -157,7 +160,7 @@ function App() { > } + fallBack={} loading={!isGlobalDocumentMapLoaded} children={ {permissions ? : } diff --git a/RN-UI-LIB b/RN-UI-LIB index 3ab183e5..4c7f4f68 160000 --- a/RN-UI-LIB +++ b/RN-UI-LIB @@ -1 +1 @@ -Subproject commit 3ab183e53214b6ac198f0376f7723cac2da96d83 +Subproject commit 4c7f4f6880d96bffa856e04d7b2b4d383e42c5cf diff --git a/android/app/build.gradle b/android/app/build.gradle index 848437fe..047ab2e5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -216,11 +216,19 @@ android { } } signingConfigs { - debug { + debug { + if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) { + storeFile file(MYAPP_UPLOAD_STORE_FILE) + storePassword MYAPP_UPLOAD_STORE_PASSWORD + keyAlias MYAPP_UPLOAD_KEY_ALIAS + keyPassword MYAPP_UPLOAD_KEY_PASSWORD + } + else { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' + } } release { if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) { diff --git a/src/action/authActions.ts b/src/action/authActions.ts index 2613a53a..362d3e08 100644 --- a/src/action/authActions.ts +++ b/src/action/authActions.ts @@ -1,6 +1,12 @@ import { ToastMessages } from './../screens/allCases/constants'; import 'react-native-get-random-values'; -import { IUser, setAuthData } from '../reducer/userSlice'; +import { + IUser, + MY_CASE_ITEM, + setAgentRole, + setAuthData, + setSelectedAgent, +} from '../reducer/userSlice'; import axiosInstance, { ApiKeys, API_STATUS_CODE, getApiUrl } from '../components/utlis/apiHelper'; import { resetLoginForm, @@ -18,7 +24,7 @@ import { GLOBAL, setGlobalUserData } from '../constants/Global'; import { resetCasesData } from '../reducer/allCasesSlice'; import { toast } from '../../RN-UI-LIB/src/components/toast'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { clearAllAsyncStorage, setAsyncStorageItem } from '../components/utlis/commonFunctions'; +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'; @@ -28,6 +34,7 @@ import { GoogleSignin } from '@react-native-google-signin/google-signin'; import { resetConfig } from '../reducer/configSlice'; import { resetProfileData } from '../reducer/profileSlice'; import CosmosForegroundService from '../services/foregroundServices/foreground.service'; +import { resetReportees } from '../reducer/reporteesSlice'; export interface GenerateOTPPayload { phoneNumber: string; @@ -115,6 +122,7 @@ export const verifyGoogleSignIn = (idToken: string) => async (dispatch: AppDispa }); dispatch(setVerifyOTPSuccess('Login Successfully!')); dispatch(resetLoginForm()); + dispatch(getAgentDetail()); } } catch (error: GenericType) { await handleGoogleLogout(); @@ -157,6 +165,7 @@ export const verifyOTP = ); dispatch(setVerifyOTPSuccess('OTP verified')); dispatch(resetLoginForm()); + dispatch(getAgentDetail()); }) .catch((err) => { dispatch(setVerifyOTPError('Invalid OTP entered. Kindly try again')); @@ -189,7 +198,13 @@ export const handleLogout = () => async (dispatch: AppDispatch) => { await auth().signOut(); await handleGoogleLogout(); await clearAllAsyncStorage(); - setGlobalUserData({ token: '', agentId: '', deviceId: '', isImpersonated: false }); + setGlobalUserData({ + token: '', + agentId: '', + deviceId: '', + isImpersonated: false, + selectedAgentId: '', + }); dispatch( setAuthData({ sessionDetails: null, @@ -201,6 +216,8 @@ export const handleLogout = () => async (dispatch: AppDispatch) => { dispatch(resetCasesData()); dispatch(resetConfig()); dispatch(resetProfileData()); + dispatch(resetReportees()); + dispatch(setSelectedAgent(MY_CASE_ITEM)); } catch (err) { logError(err as Error, 'Logout clear session details error'); } @@ -229,6 +246,9 @@ export const handleImpersonatedUserLogin = isImpersonated: true, }) ); + dispatch(resetReportees()); + dispatch(setSelectedAgent(MY_CASE_ITEM)); + dispatch(getAgentDetail()); successCallback?.(); }) .catch((err) => { @@ -241,3 +261,13 @@ 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: string[] = response?.data?.roles || []; + dispatch(setAgentRole(roles)); + } + }); +}; diff --git a/src/action/dataActions.ts b/src/action/dataActions.ts index 46574b08..ff57bc11 100644 --- a/src/action/dataActions.ts +++ b/src/action/dataActions.ts @@ -23,6 +23,8 @@ import { setFilters } from '../reducer/filtersSlice'; import { toast } from '../../RN-UI-LIB/src/components/toast'; import { ToastMessages } from '../screens/allCases/constants'; import { GenericFunctionArgs } from '../common/GenericTypes'; +import { GLOBAL } from '../constants/Global'; +import { MY_CASE_ITEM } from '../reducer/userSlice'; let _signedApiCallBucket: { req: any; added_At: number; callback: GenericFunctionArgs }[] = []; let _signedApiCallBucketTimer: number = 0; @@ -210,7 +212,11 @@ async function makeBulkSignedApiRequest( payload: ISignedRequestItem[], callback: GenericFunctionArgs | GenericFunctionArgs[] ) { - const url = getApiUrl(ApiKeys.GET_SIGNED_URL); + let url = getApiUrl(ApiKeys.GET_SIGNED_URL); + const reporteeReferenceId = GLOBAL?.SELECTED_AGENT_ID; + if (reporteeReferenceId && reporteeReferenceId !== MY_CASE_ITEM.referenceId) { + url = getApiUrl(ApiKeys.GET_SIGNED_URL_FOR_REPORTEE, {}, { reporteeReferenceId }); + } _signedApiCallBucket = []; await axiosInstance .post(url, payload, { diff --git a/src/action/reporteesActions.ts b/src/action/reporteesActions.ts new file mode 100644 index 00000000..be9aa73e --- /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); + dispatch(setReporteesLoading(true)); + axiosInstance + .get(url) + .then((res) => { + const reporteesList = res.data?.data; + if (reporteesList) { + dispatch(setReporteesList(reporteesList)); + } + }) + .finally(() => { + dispatch(setReporteesLoading(false)); + }); +}; diff --git a/src/assets/icons/ArrowDownOutlineIcon.tsx b/src/assets/icons/ArrowDownOutlineIcon.tsx new file mode 100644 index 00000000..faac3ec4 --- /dev/null +++ b/src/assets/icons/ArrowDownOutlineIcon.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { View } from 'react-native'; +import Svg, { Path, Mask, G, Rect } from 'react-native-svg'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; + +interface IArrowRightOutlineIcon { + width?: number; + height?: number; + fillColor?: string; +} + +const ArrowDownOutlineIcon: React.FC = ({ + width = 24, + height = 24, + fillColor = COLORS.TEXT.WHITE, +}) => { + return ( + + + + + + + + + + + ); +}; + +export default ArrowDownOutlineIcon; diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 142e725c..07347af7 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -525,6 +525,27 @@ export const CLICKSTREAM_EVENT_NAMES = { name: 'FA_GEOLOCATION_CAPTURING_FAILED', description: 'Geolocation capturing failed', }, + // AM/TLs + FA_AGENT_LIST_DROPDOWN_CLICKED: { + name: 'FA_AGENT_LIST_DROPDOWN_CLICKED', + description: 'Agent list dropdown clicked', + }, + FA_AGENT_LIST_BUTTON_CLICKED: { + name: 'FA_AGENT_LIST_BUTTON_CLICKED', + description: 'Agent list button clicked', + }, + FA_AGENT_LIST_BOTTOMSHEET_LOAD_SUCCESS: { + name: 'FA_AGENT_LIST_BOTTOMSHEET_LOAD_SUCCESS', + description: 'Agent list bottomsheet load success', + }, + FA_AGENT_SELECT_BUTTON_CLICKED: { + name: 'FA_AGENT_SELECT_BUTTON_CLICKED', + description: 'Agent select button clicked', + }, + FA_AGENT_CASE_LOAD_SUCCESS: { + name: 'FA_AGENT_CASE_LOAD_SUCCESS', + description: 'Agent case load success', + }, } as const; export enum MimeType { @@ -634,6 +655,7 @@ export const REQUEST_TYPE_TO_BLOCK_FOR_IMPERSONATION = ['post', 'put', 'patch', export const REQUEST_TO_UNBLOCK_FOR_IMPERSONATION = [ getApiUrl(ApiKeys.GET_SIGNED_URL), + getApiUrl(ApiKeys.GET_SIGNED_URL_FOR_REPORTEE), getApiUrl(ApiKeys.LOGOUT), ]; diff --git a/src/common/TrackingComponent.tsx b/src/common/TrackingComponent.tsx index 231d52f3..5eabd878 100644 --- a/src/common/TrackingComponent.tsx +++ b/src/common/TrackingComponent.tsx @@ -58,6 +58,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, @@ -232,12 +233,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, @@ -252,6 +247,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; @@ -291,7 +295,7 @@ const TrackingComponent: React.FC = ({ children }) => { } await handleGetCaseSyncStatus(); dispatch(getConfigData()); - if (LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES) { + if (!isTeamLead && LAST_SYNC_STATUS !== SyncStatus.FETCH_CASES) { const updatedDetails: ISyncedCases = await fetchCasesToSync(referenceId); if (updatedDetails?.cases?.length) { dispatch(syncCasesByFallback(updatedDetails)); diff --git a/src/components/form/components/ImageUpload.tsx b/src/components/form/components/ImageUpload.tsx index 028c21bb..cb38cf83 100644 --- a/src/components/form/components/ImageUpload.tsx +++ b/src/components/form/components/ImageUpload.tsx @@ -156,7 +156,13 @@ const ImageUpload: React.FC = (props) => { )} - + ); }; diff --git a/src/components/form/services/validation.service.ts b/src/components/form/services/validation.service.ts index 91c7baf8..4740c85d 100644 --- a/src/components/form/services/validation.service.ts +++ b/src/components/form/services/validation.service.ts @@ -4,6 +4,8 @@ export function isQuestionMandatory(question: QuestionV1): boolean { return !!question.metadata?.validators?.['required']?.value; } +const DEFAULT_ERROR_MESSAGE = 'This is a mandatory question'; + export function validateInput(data: { answer: any; type: string }, allRules: any): boolean { let result = true; const currentDate = new Date(); @@ -32,7 +34,7 @@ export function validateInput(data: { answer: any; type: string }, allRules: any data?.answer === undefined || !`${data?.answer}`.trim().length ) { - result = rule.message || 'Required'; + result = rule.message || DEFAULT_ERROR_MESSAGE; break; } } else if (ruleName === Validators.PATTERN) { diff --git a/src/components/notificationMenu/index.tsx b/src/components/notificationMenu/index.tsx index 8ae3c700..369bc32d 100644 --- a/src/components/notificationMenu/index.tsx +++ b/src/components/notificationMenu/index.tsx @@ -8,12 +8,19 @@ import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; import { useAppSelector } from '../../hooks'; import { addClickstreamEvent } from '../../services/clickstreamEventService'; import { getCurrentScreen, pushToScreen } from '../utlis/navigationUtlis'; +import { MY_CASE_ITEM } from '../../reducer/userSlice'; const NotificationMenu = () => { const { totalUnreadElements } = useAppSelector((state) => state.notifications); + const { isTeamLead, selectedAgent = MY_CASE_ITEM } = useAppSelector((state) => state.user); const [isEnabled, setIsEnabled] = useState(false); + const showNotifications = !isTeamLead || selectedAgent?.referenceId === MY_CASE_ITEM.referenceId; + const handleNotificationPress = () => { + if (!showNotifications) { + return; + } if (isEnabled) return; setIsEnabled(true); addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_NOTIFICATION_ICON_CLICK, { @@ -30,20 +37,24 @@ const NotificationMenu = () => { style={GenericStyles.iconContainerButton} onPress={handleNotificationPress} > - - - - {totalUnreadElements ? ( - 9 ? 24 : 16 }]} - > - {totalUnreadElements > 9 ? '9+' : totalUnreadElements} - - ) : null} + {showNotifications ? ( + + + + {totalUnreadElements ? ( + 9 ? 24 : 16 }]} + > + {totalUnreadElements > 9 ? '9+' : totalUnreadElements} + + ) : null} + - + ) : ( + <> + )} ); }; diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index 19422a6c..3cec3a0a 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -54,6 +54,9 @@ 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', + GET_SIGNED_URL_FOR_REPORTEE = 'GET_SIGNED_URL_FOR_REPORTEE', } export const API_URLS: Record = {} as Record; @@ -70,8 +73,9 @@ API_URLS[ApiKeys.GENERATE_PAYMENT_LINK] = '/payments/send-payment-link'; API_URLS[ApiKeys.ADDRESSES_GEOLOCATION] = '/addresses-geolocations'; API_URLS[ApiKeys.NEW_ADDRESS] = '/addresses'; API_URLS[ApiKeys.GET_SIGNED_URL] = '/cases/get-signed-urls'; +API_URLS[ApiKeys.GET_SIGNED_URL_FOR_REPORTEE] = '/cases/get-signed-urls-for-reportee'; API_URLS[ApiKeys.CASE_UNIFIED_DETAILS] = '/v2/collection-cases/unified-details/{loanAccountNumber}'; -API_URLS[ApiKeys.UNGROUPED_ADDRESSES] = '/addresses/ungrouped/{loanAccountNumber}'; +API_URLS[ApiKeys.UNGROUPED_ADDRESSES] = '/addresses/ungrouped-v2/{loanAccountNumber}'; API_URLS[ApiKeys.PAST_FEEDBACK] = '/feedback'; API_URLS[ApiKeys.PAST_FEEDBACK_ON_ADDRESSES] = '/feedback/v2'; API_URLS[ApiKeys.NOTIFICATIONS] = '/notification/fetch'; @@ -93,6 +97,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/all-field-reportees'; +API_URLS[ApiKeys.GET_AGENT_DETAIL] = '/user/role-info'; export const API_STATUS_CODE = { OK: 200, diff --git a/src/constants/Global.ts b/src/constants/Global.ts index 704917cf..8c898702 100644 --- a/src/constants/Global.ts +++ b/src/constants/Global.ts @@ -11,6 +11,7 @@ export const GLOBAL = { AGENT_ID: '', DEVICE_TYPE: DEVICE_TYPE_ENUM.MOBILE, IS_IMPERSONATED: false, + SELECTED_AGENT_ID: '', }; interface IGlobalUserData { @@ -19,13 +20,15 @@ interface IGlobalUserData { agentId?: string; deviceType?: DEVICE_TYPE_ENUM; isImpersonated?: boolean; + selectedAgentId?: string; } export const setGlobalUserData = (userData: IGlobalUserData) => { - const { token, deviceId, agentId, deviceType, isImpersonated } = userData; + const { token, deviceId, agentId, deviceType, isImpersonated, selectedAgentId } = userData; if (!isNullOrUndefined(token)) GLOBAL.SESSION_TOKEN = `${token}`; if (!isNullOrUndefined(deviceId)) GLOBAL.DEVICE_ID = `${deviceId}`; if (!isNullOrUndefined(agentId)) GLOBAL.AGENT_ID = `${agentId}`; if (!isNullOrUndefined(deviceType)) GLOBAL.DEVICE_TYPE = deviceType as DEVICE_TYPE_ENUM; if (!isNullOrUndefined(isImpersonated)) GLOBAL.IS_IMPERSONATED = isImpersonated ?? false; + if (!isNullOrUndefined(selectedAgentId)) GLOBAL.SELECTED_AGENT_ID = `${selectedAgentId}`; }; diff --git a/src/hooks/useFirestoreUpdates.ts b/src/hooks/useFirestoreUpdates.ts index ba7c9825..9d004efa 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(() => { @@ -105,6 +106,7 @@ const useFirestoreUpdates = () => { caseUpdates, isInitialLoad, isVisitPlanLocked: lockRef?.current?.visitPlanStatus === VisitPlanStatus.LOCKED, + selectedAgent: selectedAgent, }) ); !isInitialLoad && showCaseUpdationToast(newlyAddedCases, deletedCases); @@ -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?.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. @@ -212,7 +212,11 @@ const useFirestoreUpdates = () => { }; const subscribeToFilters = () => { - const collectionPath = `filters/${user?.referenceId}`; + let refId = user?.referenceId; + if (isTeamLead && selectedAgent?.referenceId !== MY_CASE_ITEM.referenceId) { + refId = selectedAgent?.referenceId; + } + const collectionPath = `filters/${refId}`; return subscribeToDoc(handleFilterUpdate, collectionPath); }; @@ -263,6 +267,30 @@ const useFirestoreUpdates = () => { }; }, [isLoggedIn, user?.referenceId]); + 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]); + useEffect(() => { forceUninstallUnsubscribe = subscribeToForceUninstall(); }, []); diff --git a/src/reducer/allCasesSlice.ts b/src/reducer/allCasesSlice.ts index 383f0815..bf1ffab6 100644 --- a/src/reducer/allCasesSlice.ts +++ b/src/reducer/allCasesSlice.ts @@ -12,6 +12,7 @@ import { CaseAllocationType, caseVerdict, ICaseItem, + IReportee, } from '../screens/allCases/interface'; import { CaseDetail, CONTEXT_TASK_STATUSES, DOCUMENT_TYPE } from '../screens/caseDetails/interface'; import { addClickstreamEvent } from '../services/clickstreamEventService'; @@ -19,6 +20,7 @@ import { getLoanAccountNumber } from '../components/utlis/commonFunctions'; import { getVisitedWidgetsNodeList } from '../components/form/services/forms.service'; import { CollectionCaseWidgetId, CommonCaseWidgetId } from '../types/template.types'; import { IAvatarUri } from '../action/caseListAction'; +import { MY_CASE_ITEM } from './userSlice'; export type ICasesMap = { [key: string]: ICaseItem }; interface IAllCasesSlice { @@ -237,14 +239,12 @@ const allCasesSlice = createSlice({ state.loading = action.payload; }, updateCaseDetailsFirestore: (state, action) => { - const { caseUpdates, isInitialLoad, isVisitPlanLocked } = action.payload as { + const { caseUpdates, isInitialLoad, isVisitPlanLocked, selectedAgent } = action.payload as { caseUpdates: CaseUpdates[]; isInitialLoad: boolean; isVisitPlanLocked: boolean; + selectedAgent: IReportee; }; - if (state.loading) { - state.loading = false; - } let newVisitCaseLoanIds: string[] = []; let newVisitCollectionCases: string[] = []; let removedVisitedCasesLoanIds: string[] = []; @@ -356,6 +356,14 @@ const allCasesSlice = createSlice({ 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, { @@ -503,26 +511,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/filtersSlice.ts b/src/reducer/filtersSlice.ts index 6d657ba7..1902225f 100644 --- a/src/reducer/filtersSlice.ts +++ b/src/reducer/filtersSlice.ts @@ -73,9 +73,11 @@ const filtersSlice = createSlice({ }); state.filterCountVisitPlan = filterCount; }, + resetFilters: () => initialState, }, }); -export const { setFilters, setSelectedFilters, setSelectedFiltersVisitPlan } = filtersSlice.actions; +export const { setFilters, setSelectedFilters, setSelectedFiltersVisitPlan, resetFilters } = + filtersSlice.actions; export default filtersSlice.reducer; diff --git a/src/reducer/reporteesSlice.ts b/src/reducer/reporteesSlice.ts new file mode 100644 index 00000000..72fc888b --- /dev/null +++ b/src/reducer/reporteesSlice.ts @@ -0,0 +1,41 @@ +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; + }, + resetReportees: () => initialState, + }, +}); + +export const { + setReporteesList, + setReporteesLoading, + setShowAgentSelectionBottomSheet, + resetReportees, +} = userSlice.actions; + +export default userSlice.reducer; diff --git a/src/reducer/userSlice.ts b/src/reducer/userSlice.ts index 55f3e75c..cef83181 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,18 @@ interface ISessionDetails { export enum IUserRole { SUPER_USER = 'ADDRESS_VERIFICATION:API:SUPER_USER', + ROLE_TEAM_LEAD = 'ROLE_TEAM_LEAD', + ROLE_NAVI_FIELD_TEAM_LEAD = 'ROLE_NAVI_FIELD_TEAM_LEAD', + ROLE_NAVI_FIELD_EXTERNAL_TEAM_LEAD = 'ROLE_NAVI_FIELD_EXTERNAL_TEAM_LEAD', } +export const MY_CASE_ITEM = { + name: 'My Cases', + referenceId: 'MY_CASES', + agencyCode: '', + agencyName: '', +}; + interface IUserDetails { emailId: string; referenceId: string; @@ -47,6 +58,8 @@ export interface IUserSlice extends IUser { clickstreamEvents: IClickstreamEvents[]; isImpersonated: boolean; lock: ILockData; + selectedAgent: IReportee; + isTeamLead: boolean; } const initialState: IUserSlice = { @@ -59,6 +72,8 @@ const initialState: IUserSlice = { lock: { visitPlanStatus: VisitPlanStatus.UNLOCKED, }, + selectedAgent: MY_CASE_ITEM, + isTeamLead: false, }; export const userSlice = createSlice({ @@ -86,9 +101,25 @@ export const userSlice = createSlice({ state.lock = action.payload; } }, + setSelectedAgent: (state, action) => { + state.selectedAgent = action.payload; + }, + setAgentRole: (state, action) => { + if (action?.payload?.length) { + state.isTeamLead = action.payload.some((role: IUserRole) => + [ + IUserRole.ROLE_NAVI_FIELD_TEAM_LEAD, + IUserRole.ROLE_NAVI_FIELD_EXTERNAL_TEAM_LEAD, + ].includes(role) + ); + } else { + state.isTeamLead = 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..d2fdcb1e 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,8 @@ const Profile: React.FC = () => { addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ID_CARD_CLICKED); }; + const showCompletedCases = !isTeamLead || selectedAgent?.referenceId === MY_CASE_ITEM.referenceId; + 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 ? ( -