Merge pull request #571 from navi-medici/feat/TP-34788
TP-34788 | Cosmos for AM/TLs
This commit is contained in:
7
App.tsx
7
App.tsx
@@ -135,7 +135,10 @@ function App() {
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={<FullScreenLoader loading />} persistor={persistor}>
|
||||
<PersistGate
|
||||
loading={<FullScreenLoader loading isTranslucent={false} />}
|
||||
persistor={persistor}
|
||||
>
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
onStateChange={async (state) => {
|
||||
@@ -157,7 +160,7 @@ function App() {
|
||||
>
|
||||
<StatusBar backgroundColor={COLORS.BACKGROUND.INDIGO_DARK} />
|
||||
<SuspenseLoader
|
||||
fallBack={<FullScreenLoader loading />}
|
||||
fallBack={<FullScreenLoader loading isTranslucent={false} />}
|
||||
loading={!isGlobalDocumentMapLoaded}
|
||||
children={
|
||||
<ErrorBoundary>{permissions ? <AuthRouter /> : <Permissions />}</ErrorBoundary>
|
||||
|
||||
Submodule RN-UI-LIB updated: 3ab183e532...4c7f4f6880
@@ -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')) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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, {
|
||||
|
||||
19
src/action/reporteesActions.ts
Normal file
19
src/action/reporteesActions.ts
Normal file
@@ -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));
|
||||
});
|
||||
};
|
||||
42
src/assets/icons/ArrowDownOutlineIcon.tsx
Normal file
42
src/assets/icons/ArrowDownOutlineIcon.tsx
Normal file
@@ -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<IArrowRightOutlineIcon> = ({
|
||||
width = 24,
|
||||
height = 24,
|
||||
fillColor = COLORS.TEXT.WHITE,
|
||||
}) => {
|
||||
return (
|
||||
<View>
|
||||
<Svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} fill="none">
|
||||
<Mask
|
||||
id="mask0_7859_44896"
|
||||
mask-type="alpha"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
<Rect width={width} height={height} fill="#D9D9D9" />
|
||||
</Mask>
|
||||
<G mask="url(#mask0_7859_44896)">
|
||||
<Path
|
||||
d="M12 14.9758C11.8667 14.9758 11.7417 14.9549 11.625 14.9133C11.5083 14.8716 11.4 14.8008 11.3 14.7008L6.7 10.1008C6.51667 9.91745 6.425 9.68411 6.425 9.40078C6.425 9.11745 6.51667 8.88411 6.7 8.70078C6.88334 8.51745 7.11667 8.42578 7.4 8.42578C7.68334 8.42578 7.91667 8.51745 8.1 8.70078L12 12.6008L15.9 8.70078C16.0833 8.51745 16.3167 8.42578 16.6 8.42578C16.8833 8.42578 17.1167 8.51745 17.3 8.70078C17.4833 8.88411 17.575 9.11745 17.575 9.40078C17.575 9.68411 17.4833 9.91745 17.3 10.1008L12.7 14.7008C12.6 14.8008 12.4917 14.8716 12.375 14.9133C12.2583 14.9549 12.1333 14.9758 12 14.9758Z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArrowDownOutlineIcon;
|
||||
@@ -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),
|
||||
];
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ 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<ITrackingComponent> = ({ 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<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;
|
||||
@@ -291,7 +295,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ 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));
|
||||
|
||||
@@ -156,7 +156,13 @@ const ImageUpload: React.FC<IImageUpload> = (props) => {
|
||||
</ImageBackground>
|
||||
</View>
|
||||
)}
|
||||
<ErrorMessage show={error?.sectionContext?.[sectionId]?.questionContext?.[questionId]} />
|
||||
<ErrorMessage
|
||||
show={
|
||||
error?.widgetContext?.[widgetId]?.sectionContext?.[sectionId]?.questionContext?.[
|
||||
questionId
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
<View style={[GenericStyles.ph8, GenericStyles.pv10]}>
|
||||
<BellIcon />
|
||||
<View style={[styles.notificationBadge, GenericStyles.alignCenter]}>
|
||||
{totalUnreadElements ? (
|
||||
<Text
|
||||
bold
|
||||
small
|
||||
style={[styles.notificationNumber, { width: totalUnreadElements > 9 ? 24 : 16 }]}
|
||||
>
|
||||
{totalUnreadElements > 9 ? '9+' : totalUnreadElements}
|
||||
</Text>
|
||||
) : null}
|
||||
{showNotifications ? (
|
||||
<View style={[GenericStyles.ph8, GenericStyles.pv10]}>
|
||||
<BellIcon />
|
||||
<View style={[styles.notificationBadge, GenericStyles.alignCenter]}>
|
||||
{totalUnreadElements ? (
|
||||
<Text
|
||||
bold
|
||||
small
|
||||
style={[styles.notificationNumber, { width: totalUnreadElements > 9 ? 24 : 16 }]}
|
||||
>
|
||||
{totalUnreadElements > 9 ? '9+' : totalUnreadElements}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</TouchableHighlight>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<ApiKeys, string> = {} as Record<ApiKeys, string>;
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`;
|
||||
};
|
||||
|
||||
@@ -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<ILockData | null>(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();
|
||||
}, []);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
41
src/reducer/reporteesSlice.ts
Normal file
41
src/reducer/reporteesSlice.ts
Normal file
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<View style={[GenericStyles.fill]}>
|
||||
<NavigationHeader
|
||||
@@ -174,53 +180,55 @@ const Profile: React.FC = () => {
|
||||
}
|
||||
/>
|
||||
<ScrollView>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.ph16,
|
||||
GenericStyles.pt16,
|
||||
numberOfCompletedCases === 2 ? { paddingBottom: 6 } : {},
|
||||
]}
|
||||
>
|
||||
{hideUploadImageBtn ? null : <IDCardImageCapture />}
|
||||
{showCompletedCases ? (
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
numberOfCompletedCases ? { paddingBottom: 12 } : GenericStyles.pb16,
|
||||
GenericStyles.ph16,
|
||||
GenericStyles.pt16,
|
||||
numberOfCompletedCases === 2 ? { paddingBottom: 6 } : {},
|
||||
]}
|
||||
>
|
||||
<View style={[GenericStyles.ml4, GenericStyles.mr8]}>
|
||||
<GroupIcon />
|
||||
</View>
|
||||
<Text>Completed cases ({numberOfCompletedCases})</Text>
|
||||
</View>
|
||||
{numberOfCompletedCases
|
||||
? completeCasesList.slice(0, 2).map((caseItem) => {
|
||||
const caseDetailItem = caseDetails[caseItem.caseReferenceId] as CaseDetail;
|
||||
return (
|
||||
<CaseItem
|
||||
key={caseItem.caseReferenceId}
|
||||
caseDetailObj={caseDetailItem}
|
||||
isCompleted={true}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
{numberOfCompletedCases > 2 ? (
|
||||
<Button
|
||||
title="View all completed cases"
|
||||
variant="primaryText"
|
||||
{hideUploadImageBtn ? null : <IDCardImageCapture />}
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.w100,
|
||||
GenericStyles.br8,
|
||||
GenericStyles.mt6,
|
||||
GenericStyles.mb12,
|
||||
GenericStyles.whiteBackground,
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
numberOfCompletedCases ? { paddingBottom: 12 } : GenericStyles.pb16,
|
||||
]}
|
||||
onPress={handleViewAllCases}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
>
|
||||
<View style={[GenericStyles.ml4, GenericStyles.mr8]}>
|
||||
<GroupIcon />
|
||||
</View>
|
||||
<Text>Completed cases ({numberOfCompletedCases})</Text>
|
||||
</View>
|
||||
{numberOfCompletedCases
|
||||
? completeCasesList.slice(0, 2).map((caseItem) => {
|
||||
const caseDetailItem = caseDetails[caseItem.caseReferenceId] as CaseDetail;
|
||||
return (
|
||||
<CaseItem
|
||||
key={caseItem.caseReferenceId}
|
||||
caseDetailObj={caseDetailItem}
|
||||
isCompleted={true}
|
||||
/>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
{numberOfCompletedCases > 2 ? (
|
||||
<Button
|
||||
title="View all completed cases"
|
||||
variant="primaryText"
|
||||
style={[
|
||||
GenericStyles.w100,
|
||||
GenericStyles.br8,
|
||||
GenericStyles.mt6,
|
||||
GenericStyles.mb12,
|
||||
GenericStyles.whiteBackground,
|
||||
]}
|
||||
onPress={handleViewAllCases}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
<View style={[styles.logoutContainer, GenericStyles.whiteBackground]}>
|
||||
<TouchableOpacity
|
||||
onPress={handleLogout}
|
||||
|
||||
@@ -88,58 +88,60 @@ const UngroupedAddressContainer: React.FC<IUngroupedAddress> = ({ route: routePa
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={GenericStyles.fill}>
|
||||
<NavigationHeader title={PAGE_TITLE} onBack={goBack} />
|
||||
<SuspenseLoader
|
||||
loading={loading}
|
||||
fallBack={
|
||||
<>
|
||||
{[...Array(8).keys()].map(() => (
|
||||
<LineLoader
|
||||
width="100%"
|
||||
height={75}
|
||||
style={[GenericStyles.br6, { marginBottom: 20 }]}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
>
|
||||
{ungroupedAddressList?.length ? (
|
||||
<View>
|
||||
{ungroupedAddressList.map((ungroupedAddressItem: IAddress) => (
|
||||
<View>
|
||||
<AddressItem
|
||||
caseId={caseId}
|
||||
showRelativeDistance
|
||||
containerStyle={styles.addressItemContainer}
|
||||
key={ungroupedAddressItem?.id}
|
||||
addressItem={ungroupedAddressItem}
|
||||
showActionButtons
|
||||
handleOldFeedbackRouting={() => {
|
||||
handleOpenOldFeedbacks(ungroupedAddressItem);
|
||||
}}
|
||||
handleCloseRouting={() => {
|
||||
navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams);
|
||||
}}
|
||||
showSource
|
||||
<ScrollView>
|
||||
<SuspenseLoader
|
||||
loading={loading}
|
||||
fallBack={
|
||||
<>
|
||||
{[...Array(8).keys()].map(() => (
|
||||
<LineLoader
|
||||
width="100%"
|
||||
height={75}
|
||||
style={[GenericStyles.br6, { marginBottom: 20 }]}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
styles.noAddressContainer,
|
||||
GenericStyles.centerAligned,
|
||||
GenericStyles.columnDirection,
|
||||
]}
|
||||
>
|
||||
<HomeIcon />
|
||||
<Text style={[styles.textContainer, styles.noAddressText]}>No addresses found</Text>
|
||||
</View>
|
||||
)}
|
||||
</SuspenseLoader>
|
||||
</ScrollView>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
>
|
||||
{ungroupedAddressList?.length ? (
|
||||
<View>
|
||||
{ungroupedAddressList.map((ungroupedAddressItem: IAddress) => (
|
||||
<View>
|
||||
<AddressItem
|
||||
caseId={caseId}
|
||||
showRelativeDistance
|
||||
containerStyle={styles.addressItemContainer}
|
||||
key={ungroupedAddressItem?.id}
|
||||
addressItem={ungroupedAddressItem}
|
||||
showActionButtons
|
||||
handleOldFeedbackRouting={() => {
|
||||
handleOpenOldFeedbacks(ungroupedAddressItem);
|
||||
}}
|
||||
handleCloseRouting={() => {
|
||||
navigateToScreen(PageRouteEnum.ADDITIONAL_ADDRESSES, commonParams);
|
||||
}}
|
||||
showSource
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
styles.noAddressContainer,
|
||||
GenericStyles.centerAligned,
|
||||
GenericStyles.columnDirection,
|
||||
]}
|
||||
>
|
||||
<HomeIcon />
|
||||
<Text style={[styles.textContainer, styles.noAddressText]}>No addresses found</Text>
|
||||
</View>
|
||||
)}
|
||||
</SuspenseLoader>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
88
src/screens/allCases/AgentListItem.tsx
Normal file
88
src/screens/allCases/AgentListItem.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Pressable, StyleSheet, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import ArrowSolidIcon from '../../../RN-UI-LIB/src/Icons/ArrowSolidIcon';
|
||||
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 fuzzySort from '../../../RN-UI-LIB/src/utlis/fuzzySort';
|
||||
import fuzzysort from 'fuzzysort';
|
||||
import { MY_CASE_ITEM, setSelectedAgent } from '../../reducer/userSlice';
|
||||
import { resetFilters } from '../../reducer/filtersSlice';
|
||||
import { setGlobalUserData } from '../../constants/Global';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
|
||||
interface IAgentListItem {
|
||||
agent: IReportee;
|
||||
leftAdornment?: React.ReactNode;
|
||||
searchQuery: string;
|
||||
}
|
||||
|
||||
const AgentListItem: React.FC<IAgentListItem> = ({ agent, leftAdornment, searchQuery }) => {
|
||||
const selectedAgent = useAppSelector((state) => state.user.selectedAgent) || MY_CASE_ITEM;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleAgentSelection = () => {
|
||||
const selectedAgentId = agent.referenceId;
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_AGENT_SELECT_BUTTON_CLICKED, {
|
||||
selectedAgentId,
|
||||
});
|
||||
dispatch(setSelectedAgent(agent));
|
||||
setGlobalUserData({ selectedAgentId });
|
||||
dispatch(resetFilters());
|
||||
dispatch(resetCasesData());
|
||||
dispatch(setShowAgentSelectionBottomSheet(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.justifyContentSpaceBetween,
|
||||
GenericStyles.whiteBackground,
|
||||
GenericStyles.p12,
|
||||
GenericStyles.br8,
|
||||
GenericStyles.border,
|
||||
GenericStyles.alignCenter,
|
||||
styles.shadow,
|
||||
{
|
||||
backgroundColor:
|
||||
agent.referenceId === selectedAgent.referenceId
|
||||
? COLORS.BACKGROUND.BLUE_LIGHT_3
|
||||
: COLORS.BACKGROUND.PRIMARY,
|
||||
},
|
||||
]}
|
||||
onPress={handleAgentSelection}
|
||||
>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
|
||||
{leftAdornment && <View style={[GenericStyles.mr4]}>{leftAdornment}</View>}
|
||||
<Text>
|
||||
{searchQuery.length > 0
|
||||
? fuzzySort.highlight(fuzzysort.single(searchQuery, agent.name), (result: string) => (
|
||||
<Text style={styles.highlight}>{result}</Text>
|
||||
))
|
||||
: agent.name}
|
||||
</Text>
|
||||
</View>
|
||||
<ArrowSolidIcon size={10} fillColor={COLORS.BACKGROUND.LIGHT} rotateY={180} />
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
shadow: {
|
||||
elevation: 1,
|
||||
},
|
||||
highlight: {
|
||||
backgroundColor: COLORS.BACKGROUND.ORANGE,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1,
|
||||
borderColor: COLORS.BORDER.ORANGE,
|
||||
},
|
||||
});
|
||||
|
||||
export default AgentListItem;
|
||||
22
src/screens/allCases/AgentListSearchbar.tsx
Normal file
22
src/screens/allCases/AgentListSearchbar.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import TextInput from '../../../RN-UI-LIB/src/components/TextInput';
|
||||
import SearchIcon from '../../../RN-UI-LIB/src/Icons/SearchIcon';
|
||||
|
||||
interface IAgentListSearchbar {
|
||||
searchQuery: string;
|
||||
handleSearchChange: (val: string) => void;
|
||||
}
|
||||
|
||||
const AgentListSearchbar: React.FC<IAgentListSearchbar> = ({ searchQuery, handleSearchChange }) => {
|
||||
return (
|
||||
<TextInput
|
||||
LeftComponent={<SearchIcon />}
|
||||
onChangeText={handleSearchChange}
|
||||
placeholder="Search by agent"
|
||||
defaultValue={searchQuery}
|
||||
showClearIcon
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentListSearchbar;
|
||||
127
src/screens/allCases/AgentListView.tsx
Normal file
127
src/screens/allCases/AgentListView.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
Animated,
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
SectionList,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import { IReportee, ISectionListData } from './interface';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import AgentListItem from './AgentListItem';
|
||||
import { sectionListTranformData } from './utils';
|
||||
import ProfileSolidIcon from '../../../RN-UI-LIB/src/Icons/ProfileSolidIcon';
|
||||
import { Search } from '../../../RN-UI-LIB/src/utlis/search';
|
||||
import NoCasesFoundIcon from '../../assets/icons/NoCasesFoundIcon';
|
||||
import { MY_CASE_ITEM } from '../../reducer/userSlice';
|
||||
import Heading from '../../../RN-UI-LIB/src/components/Heading';
|
||||
import { GenericType } from '../../common/GenericTypes';
|
||||
|
||||
interface IAgentListView {
|
||||
searchQuery: string;
|
||||
scrollAnimation: Animated.Value;
|
||||
}
|
||||
|
||||
const AgentListView: React.FC<IAgentListView> = ({ scrollAnimation, searchQuery }) => {
|
||||
const { agentsList = [] } = useAppSelector((state) => state.reportees);
|
||||
const listRef = useRef<GenericType>(null);
|
||||
|
||||
const filteredAgentsList: ISectionListData[] = useMemo(() => {
|
||||
const filteredList =
|
||||
searchQuery.length > 0
|
||||
? Search(searchQuery, agentsList, { keys: ['name'] }).map(
|
||||
(item: { obj: IReportee }) => item.obj
|
||||
)
|
||||
: agentsList;
|
||||
return sectionListTranformData(filteredList);
|
||||
}, [searchQuery, agentsList]);
|
||||
|
||||
// Scroll the flatlist to top when search query changes
|
||||
useEffect(() => {
|
||||
if (listRef?.current) {
|
||||
listRef.current.scrollToLocation({
|
||||
sectionIndex: 0,
|
||||
itemIndex: 0,
|
||||
});
|
||||
}
|
||||
}, [searchQuery]);
|
||||
|
||||
const handleListScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
const offsetY = event.nativeEvent.contentOffset.y;
|
||||
scrollAnimation.setValue(offsetY);
|
||||
};
|
||||
|
||||
if (filteredAgentsList.length === 0) {
|
||||
return (
|
||||
<ScrollView contentContainerStyle={[GenericStyles.fill, styles.pt64]}>
|
||||
<View style={GenericStyles.alignCenter}>
|
||||
<NoCasesFoundIcon />
|
||||
<Heading style={GenericStyles.pt16} type="h4">
|
||||
No {agentsList.length === 0 ? 'reportees' : 'results'} found
|
||||
</Heading>
|
||||
{agentsList.length ? <Text light>Try searching something else</Text> : null}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
ref={listRef}
|
||||
sections={filteredAgentsList}
|
||||
keyExtractor={(item, index) => item.referenceId + index}
|
||||
onScroll={handleListScroll}
|
||||
contentContainerStyle={[GenericStyles.ph16, styles.pt8]}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
renderItem={({ item }) => (
|
||||
<Pressable style={[GenericStyles.pb16]}>
|
||||
<AgentListItem
|
||||
agent={item}
|
||||
searchQuery={searchQuery}
|
||||
leftAdornment={
|
||||
item.referenceId === MY_CASE_ITEM.referenceId ? <ProfileSolidIcon /> : null
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
)}
|
||||
renderSectionHeader={({ section: { title } }) => {
|
||||
if (!title) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Pressable
|
||||
style={[GenericStyles.centerAlignedRow, GenericStyles.pb16, GenericStyles.pt16]}
|
||||
>
|
||||
<Text bold dark>
|
||||
{title}
|
||||
</Text>
|
||||
<View style={styles.separatorLine} />
|
||||
</Pressable>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
separatorLine: {
|
||||
height: 1,
|
||||
flex: 1,
|
||||
backgroundColor: COLORS.BORDER.PRIMARY,
|
||||
marginLeft: 8,
|
||||
},
|
||||
pt64: {
|
||||
marginTop: 64,
|
||||
},
|
||||
pt8: {
|
||||
paddingTop: 8,
|
||||
},
|
||||
});
|
||||
|
||||
export default AgentListView;
|
||||
21
src/screens/allCases/AgentListViewLoading.tsx
Normal file
21
src/screens/allCases/AgentListViewLoading.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { View } from 'react-native';
|
||||
import React from 'react';
|
||||
import LineLoader from '../../../RN-UI-LIB/src/components/suspense_loader/LineLoader';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
|
||||
const AgentListViewLoading = () => {
|
||||
return (
|
||||
<View style={[GenericStyles.fill, GenericStyles.whiteBackground, GenericStyles.ph16]}>
|
||||
{[...Array(8).keys()].map((_, index) => (
|
||||
<LineLoader
|
||||
key={index}
|
||||
width={'100%'}
|
||||
height={50}
|
||||
style={[GenericStyles.br6, { marginBottom: 20 }]}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentListViewLoading;
|
||||
83
src/screens/allCases/AgentsListContainer.tsx
Normal file
83
src/screens/allCases/AgentsListContainer.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Animated, View } from 'react-native';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import AgentListSearchbar from './AgentListSearchbar';
|
||||
import { debounce } from '../../components/utlis/commonFunctions';
|
||||
import AgentListView from './AgentListView';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import { getAgentsList } from '../../action/reporteesActions';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import AgentListViewLoading from './AgentListViewLoading';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
|
||||
interface IAgentsListContainer {
|
||||
showAgentSelectionBottomSheet: boolean;
|
||||
}
|
||||
|
||||
const AgentsListContainer: React.FC<IAgentsListContainer> = ({ showAgentSelectionBottomSheet }) => {
|
||||
const [searchQuery, setSearchQuery] = React.useState<string>('');
|
||||
const { isLoading, agentsList } = useAppSelector((state) => state.reportees);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
showAgentSelectionBottomSheet &&
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_AGENT_LIST_BOTTOMSHEET_LOAD_SUCCESS);
|
||||
}, [showAgentSelectionBottomSheet]);
|
||||
|
||||
const scrollAnimation = useRef(new Animated.Value(0)).current;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getAgentsList());
|
||||
}, []);
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
debounce((query: string) => {
|
||||
setSearchQuery(query);
|
||||
}, 200),
|
||||
[]
|
||||
);
|
||||
|
||||
const elevateAnimatedValue = scrollAnimation.interpolate({
|
||||
inputRange: [0, 100],
|
||||
outputRange: [0, 6],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
|
||||
const paddingBottomAnimatedValue = scrollAnimation.interpolate({
|
||||
inputRange: [0, 100],
|
||||
outputRange: [0, 10],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
|
||||
if (isLoading && !agentsList.length) {
|
||||
return <AgentListViewLoading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[GenericStyles.fill, GenericStyles.whiteBackground]}>
|
||||
<Animated.View
|
||||
style={[{ paddingBottom: paddingBottomAnimatedValue }, GenericStyles.overflowHidden]}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
GenericStyles.ph16,
|
||||
GenericStyles.pb16,
|
||||
GenericStyles.whiteBackground,
|
||||
{
|
||||
borderColor: COLORS.BORDER.PRIMARY,
|
||||
elevation: elevateAnimatedValue,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<AgentListSearchbar searchQuery={searchQuery} handleSearchChange={handleSearchChange} />
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
<Animated.View style={[GenericStyles.fill]}>
|
||||
<AgentListView searchQuery={searchQuery} scrollAnimation={scrollAnimation} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentsListContainer;
|
||||
@@ -1,30 +1,30 @@
|
||||
import { Animated, StyleSheet, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import Heading from '../../../RN-UI-LIB/src/components/Heading';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import Filters from './Filters';
|
||||
import NotificationMenu from '../../components/notificationMenu';
|
||||
import HeaderLabel from './HeaderLabel';
|
||||
|
||||
interface ICaseListHeader {
|
||||
filterCount: number;
|
||||
searchQuery: string;
|
||||
handleSearchChange: (val: string) => void;
|
||||
toggleFilterModal: () => void;
|
||||
headerHeight: Animated.AnimatedInterpolation<string | number>;
|
||||
headerLabel: string;
|
||||
showFilters: boolean;
|
||||
setShowAgentSelectionBottomSheet: (val: boolean) => void;
|
||||
filteredListCount: number;
|
||||
isVisitPlan?: boolean;
|
||||
}
|
||||
|
||||
const CaseListHeader: React.FC<ICaseListHeader> = ({
|
||||
searchQuery,
|
||||
filterCount,
|
||||
handleSearchChange,
|
||||
toggleFilterModal,
|
||||
headerHeight,
|
||||
headerLabel,
|
||||
showFilters,
|
||||
setShowAgentSelectionBottomSheet,
|
||||
filteredListCount,
|
||||
isVisitPlan,
|
||||
}) => {
|
||||
return (
|
||||
@@ -40,15 +40,16 @@ const CaseListHeader: React.FC<ICaseListHeader> = ({
|
||||
GenericStyles.pv10,
|
||||
]}
|
||||
>
|
||||
<Heading type="h3" style={[styles.headerLabel]}>
|
||||
{headerLabel}
|
||||
</Heading>
|
||||
<HeaderLabel
|
||||
setShowAgentSelectionBottomSheet={setShowAgentSelectionBottomSheet}
|
||||
filteredListCount={filteredListCount}
|
||||
isVisitPlan={isVisitPlan}
|
||||
/>
|
||||
<NotificationMenu />
|
||||
</View>
|
||||
{showFilters && (
|
||||
<Filters
|
||||
searchQuery={searchQuery}
|
||||
filterCount={filterCount}
|
||||
handleSearchChange={handleSearchChange}
|
||||
toggleFilterModal={toggleFilterModal}
|
||||
isVisitPlan={isVisitPlan}
|
||||
@@ -67,8 +68,5 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
backgroundColor: COLORS.BACKGROUND.INDIGO,
|
||||
},
|
||||
headerLabel: {
|
||||
color: COLORS.BACKGROUND.PRIMARY,
|
||||
},
|
||||
});
|
||||
export default CaseListHeader;
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Modal,
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
@@ -12,12 +13,12 @@ import { GenericStyles, SCREEN_HEIGHT, SCREEN_WIDTH } from '../../../RN-UI-LIB/s
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { RootState } from '../../store/store';
|
||||
import { CaseTypes, ICaseItem, ICaseItemCaseDetailObj } from './interface';
|
||||
import { EmptyListMessages, LIST_HEADER_ITEMS, ListHeaderItems } from './constants';
|
||||
import { LIST_HEADER_ITEMS, ListHeaderItems } from './constants';
|
||||
import CaseItem from './CaseItem';
|
||||
import { Search } from '../../../RN-UI-LIB/src/utlis/search';
|
||||
import FiltersContainer from '../../components/screens/allCases/allCasesFilters/FiltersContainer';
|
||||
import EmptyList from './EmptyList';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import {
|
||||
CLICKSTREAM_EVENT_NAMES,
|
||||
@@ -40,9 +41,15 @@ import { getCurrentScreen } from '../../components/utlis/navigationUtlis';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { FlashList } from '@shopify/flash-list';
|
||||
import { VisitPlanStatus } from '../../reducer/userSlice';
|
||||
import { dateFormat, DAY_MONTH_DATE_FORMAT } from '../../../RN-UI-LIB/src/utlis/dates';
|
||||
import { getAttemptedList, getNonAttemptedList } from './utils';
|
||||
import { GenericType } from '../../common/GenericTypes';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import BottomSheet from '../../../RN-UI-LIB/src/components/bottom_sheet/BottomSheet';
|
||||
import Heading from '../../../RN-UI-LIB/src/components/Heading';
|
||||
import { row } from '../emiSchedule/constants';
|
||||
import CloseIcon from '../../../RN-UI-LIB/src/Icons/CloseIcon';
|
||||
import AgentsListContainer from './AgentsListContainer';
|
||||
import { setShowAgentSelectionBottomSheet } from '../../reducer/reporteesSlice';
|
||||
|
||||
export const getItem = (item: Array<ICaseItem>, index: number) => item[index];
|
||||
export const ESTIMATED_ITEM_SIZE = 250; // Average height of List item
|
||||
@@ -63,16 +70,27 @@ const CasesList: React.FC<ICasesList> = ({ casesList = [], isVisitPlan, allCases
|
||||
selectedTodoListMap,
|
||||
} = useAppSelector((state: RootState) => state.allCases);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const { filters, filterCount, selectedFilters, quickFiltersPresent, isLockedVisitPlanStatus } =
|
||||
useAppSelector((state: RootState) => ({
|
||||
filters: state.filters.filters,
|
||||
filterCount: isVisitPlan ? state.filters.filterCountVisitPlan : state.filters.filterCount,
|
||||
selectedFilters: isVisitPlan
|
||||
? state.filters.selectedFiltersVisitPlan
|
||||
: state.filters.selectedFilters,
|
||||
quickFiltersPresent: state?.filters?.quickFilters?.length > 0,
|
||||
isLockedVisitPlanStatus: state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED,
|
||||
}));
|
||||
const {
|
||||
filters,
|
||||
filterCount,
|
||||
selectedFilters,
|
||||
quickFiltersPresent,
|
||||
isLockedVisitPlanStatus,
|
||||
showAgentSelectionBottomSheet,
|
||||
selectedAgent,
|
||||
} = useAppSelector((state: RootState) => ({
|
||||
filters: state.filters.filters,
|
||||
filterCount: isVisitPlan ? state.filters.filterCountVisitPlan : state.filters.filterCount,
|
||||
selectedFilters: isVisitPlan
|
||||
? state.filters.selectedFiltersVisitPlan
|
||||
: state.filters.selectedFilters,
|
||||
quickFiltersPresent: state?.filters?.quickFilters?.length > 0,
|
||||
isLockedVisitPlanStatus: state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED,
|
||||
selectedAgent: state.user?.selectedAgent,
|
||||
showAgentSelectionBottomSheet: state.reportees.showAgentSelectionBottomSheet,
|
||||
}));
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [showFilterModal, setShowFilterModal] = React.useState<boolean>(false);
|
||||
const flashListRef = useRef<GenericType>(null);
|
||||
@@ -93,6 +111,10 @@ const CasesList: React.FC<ICasesList> = ({ casesList = [], isVisitPlan, allCases
|
||||
}, [])
|
||||
);
|
||||
|
||||
const toggleAgentSelectionBottomSheet = () => {
|
||||
dispatch(setShowAgentSelectionBottomSheet(!showAgentSelectionBottomSheet));
|
||||
};
|
||||
|
||||
const headerHeight = scrollAnimation.interpolate({
|
||||
inputRange: [0, HEADER_SCROLL_DISTANCE],
|
||||
outputRange: isVisitPlan
|
||||
@@ -101,6 +123,10 @@ const CasesList: React.FC<ICasesList> = ({ casesList = [], isVisitPlan, allCases
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setSearchQuery('');
|
||||
}, [selectedAgent]);
|
||||
|
||||
//TODO: clean these different heights
|
||||
const headerHeightQuickFilters = scrollAnimation.interpolate({
|
||||
inputRange: [0, HEADER_SCROLL_DISTANCE_WITH_QUICK_FILTERS],
|
||||
@@ -144,41 +170,12 @@ const CasesList: React.FC<ICasesList> = ({ casesList = [], isVisitPlan, allCases
|
||||
: filteredList;
|
||||
|
||||
const isFilterApplied = filterCount > 0 || searchQuery.length > 0;
|
||||
const getEmptyListMessage = () => {
|
||||
if (isLockedVisitPlanStatus && isVisitPlan) {
|
||||
const currentDate = new Date();
|
||||
return (
|
||||
EmptyListMessages.GENERATING_VISIT_PLAN +
|
||||
' ' +
|
||||
dateFormat(currentDate, DAY_MONTH_DATE_FORMAT) +
|
||||
'...'
|
||||
);
|
||||
}
|
||||
if (isFilterApplied) {
|
||||
return EmptyListMessages.NO_CASES_FOUND;
|
||||
}
|
||||
if (isVisitPlan) {
|
||||
return EmptyListMessages.NO_VISIT_PLANS;
|
||||
}
|
||||
return EmptyListMessages.NO_PENDING_CASES;
|
||||
};
|
||||
|
||||
const getSubMessage = () => {
|
||||
if (isVisitPlan && isLockedVisitPlanStatus) {
|
||||
return EmptyListMessages.GENERATING_VISIT_PLAN_SUB;
|
||||
}
|
||||
if (isFilterApplied) {
|
||||
return EmptyListMessages.NO_CASES_FOUND_SUB;
|
||||
}
|
||||
};
|
||||
|
||||
const listEmptyComponent = (
|
||||
<EmptyList
|
||||
message={getEmptyListMessage()}
|
||||
isVisitPlan={isVisitPlan}
|
||||
isFilterApplied={isFilterApplied}
|
||||
subMessage={getSubMessage()}
|
||||
isLockedVisitPlanStatus={isLockedVisitPlanStatus}
|
||||
setShowAgentSelectionBottomSheet={toggleAgentSelectionBottomSheet}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -272,17 +269,6 @@ const CasesList: React.FC<ICasesList> = ({ casesList = [], isVisitPlan, allCases
|
||||
}
|
||||
setShowFilterModal(!showFilterModal);
|
||||
};
|
||||
const getHeaderLabel = () => {
|
||||
if (isVisitPlan) {
|
||||
if (isLockedVisitPlanStatus) {
|
||||
return 'Generating visit plan';
|
||||
}
|
||||
const currentDate = new Date();
|
||||
const formattedDate = dateFormat(currentDate, DAY_MONTH_DATE_FORMAT);
|
||||
return formattedDate;
|
||||
}
|
||||
return `My Cases (${filteredCasesList.length})`;
|
||||
};
|
||||
|
||||
let listStyle = {};
|
||||
|
||||
@@ -300,31 +286,30 @@ const CasesList: React.FC<ICasesList> = ({ casesList = [], isVisitPlan, allCases
|
||||
}
|
||||
}
|
||||
|
||||
const headerHeightValue = quickFiltersPresent ? headerHeightQuickFilters : headerHeight;
|
||||
const showFilters = isVisitPlan && isLockedVisitPlanStatus ? false : !!casesList.length;
|
||||
|
||||
return (
|
||||
<View style={[GenericStyles.fill, styles.container]}>
|
||||
<CaseListHeader
|
||||
filterCount={filterCount}
|
||||
searchQuery={searchQuery}
|
||||
handleSearchChange={handleSearchChange}
|
||||
toggleFilterModal={toggleFilterModal}
|
||||
headerHeight={quickFiltersPresent ? headerHeightQuickFilters : headerHeight}
|
||||
headerLabel={getHeaderLabel()}
|
||||
showFilters={isVisitPlan && isLockedVisitPlanStatus ? false : !!casesList.length}
|
||||
setShowAgentSelectionBottomSheet={toggleAgentSelectionBottomSheet}
|
||||
filteredListCount={filteredCasesList?.length}
|
||||
showFilters={showFilters}
|
||||
isVisitPlan={isVisitPlan}
|
||||
/>
|
||||
{visitPlansUpdating ? (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.fillOverlay,
|
||||
{ top: quickFiltersPresent ? headerHeightQuickFilters : headerHeight },
|
||||
]}
|
||||
></Animated.View>
|
||||
<Animated.View style={[styles.fillOverlay, { top: headerHeightValue }]}></Animated.View>
|
||||
) : null}
|
||||
<View style={GenericStyles.fill}>
|
||||
{filteredCasesListWithCTA.length ? (
|
||||
<FlashList
|
||||
ref={flashListRef}
|
||||
data={filteredCasesListWithCTA}
|
||||
keyboardShouldPersistTaps={'handled'}
|
||||
scrollEventThrottle={16}
|
||||
contentContainerStyle={listStyle}
|
||||
onScroll={handleListScroll}
|
||||
@@ -353,6 +338,26 @@ const CasesList: React.FC<ICasesList> = ({ casesList = [], isVisitPlan, allCases
|
||||
isVisitPlan={isVisitPlan}
|
||||
/>
|
||||
</Modal>
|
||||
<BottomSheet
|
||||
HeaderNode={() => (
|
||||
<View style={[...row, GenericStyles.ph16]}>
|
||||
<Heading dark type="h4">
|
||||
View team's assigned cases
|
||||
</Heading>
|
||||
<Pressable onPress={toggleAgentSelectionBottomSheet} style={styles.p4}>
|
||||
<CloseIcon color={COLORS.TEXT.LIGHT} />
|
||||
</Pressable>
|
||||
</View>
|
||||
)}
|
||||
heightPercentage={100}
|
||||
allowBackdropClose={false}
|
||||
visible={showAgentSelectionBottomSheet}
|
||||
setVisible={toggleAgentSelectionBottomSheet}
|
||||
>
|
||||
<View style={[GenericStyles.mt16, GenericStyles.fill]}>
|
||||
<AgentsListContainer showAgentSelectionBottomSheet={showAgentSelectionBottomSheet} />
|
||||
</View>
|
||||
</BottomSheet>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -396,6 +401,9 @@ const styles = StyleSheet.create({
|
||||
paddingTop: VISIT_PLAN_HEADER_HEIGHT_MAX_WITH_QUICK_FILTERS,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
p4: {
|
||||
padding: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export default CasesList;
|
||||
|
||||
@@ -41,7 +41,7 @@ const CompletedCase: React.FC = () => {
|
||||
keyExtractor={(item) => item.caseReferenceId}
|
||||
getItemCount={(item) => item.length}
|
||||
getItem={getItem}
|
||||
ListEmptyComponent={<EmptyList message={EmptyListMessages.NO_COMPLETED_CASES} />}
|
||||
ListEmptyComponent={<EmptyList isCompleted />}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -2,31 +2,44 @@ import { StyleSheet, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import Heading from '../../../RN-UI-LIB/src/components/Heading';
|
||||
import EmptyPageCheckIcon from '../../assets/icons/EmptyPageCheckIcon';
|
||||
import Button from '../../../RN-UI-LIB/src/components/Button';
|
||||
import { navigateToScreen } from '../../components/utlis/navigationUtlis';
|
||||
import NoCasesFoundIcon from '../../assets/icons/NoCasesFoundIcon';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import { RootState } from '../../store/store';
|
||||
import GeneratingVisitPlan from '../../assets/icons/GeneratingVisitPlanIcon';
|
||||
import NoCasesFoundIcon from '../../assets/icons/NoCasesFoundIcon';
|
||||
import EmptyPageCheckIcon from '../../assets/icons/EmptyPageCheckIcon';
|
||||
import { MY_CASE_ITEM, VisitPlanStatus } from '../../reducer/userSlice';
|
||||
import { EmptyListMessages } from './constants';
|
||||
import { navigateToScreen } from '../../components/utlis/navigationUtlis';
|
||||
import { dateFormat, DAY_MONTH_DATE_FORMAT } from '../../../RN-UI-LIB/src/utlis/dates';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
|
||||
interface IEmptyList {
|
||||
message: string;
|
||||
subMessage?: string;
|
||||
isCompleted?: boolean;
|
||||
isVisitPlan?: boolean;
|
||||
isFilterApplied?: boolean;
|
||||
isLockedVisitPlanStatus?: boolean;
|
||||
setShowAgentSelectionBottomSheet?: (val: boolean) => void;
|
||||
}
|
||||
|
||||
const EmptyList: React.FC<IEmptyList> = ({
|
||||
message,
|
||||
isVisitPlan,
|
||||
isFilterApplied,
|
||||
subMessage,
|
||||
isLockedVisitPlanStatus,
|
||||
}) => {
|
||||
const handleCreateVisitPlan = () => {
|
||||
navigateToScreen('Cases');
|
||||
};
|
||||
const EmptyList: React.FC<IEmptyList> = (props) => {
|
||||
const { isCompleted, isVisitPlan, isFilterApplied, setShowAgentSelectionBottomSheet } = props;
|
||||
const {
|
||||
isLockedVisitPlanStatus,
|
||||
isCasesLoading,
|
||||
selectedAgent = MY_CASE_ITEM,
|
||||
} = useAppSelector((state: RootState) => ({
|
||||
isLockedVisitPlanStatus: state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED,
|
||||
isCasesLoading: state.allCases.loading,
|
||||
selectedAgent: state.user?.selectedAgent,
|
||||
}));
|
||||
|
||||
const isTeamLead = useAppSelector((state) => state.user.isTeamLead);
|
||||
|
||||
if (isCasesLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderIcon = () => {
|
||||
if (isLockedVisitPlanStatus && isVisitPlan) {
|
||||
@@ -38,6 +51,76 @@ const EmptyList: React.FC<IEmptyList> = ({
|
||||
return <EmptyPageCheckIcon />;
|
||||
};
|
||||
|
||||
const getEmptyListMessage = () => {
|
||||
if (isCompleted) {
|
||||
return EmptyListMessages.NO_COMPLETED_CASES;
|
||||
}
|
||||
if (isLockedVisitPlanStatus && isVisitPlan) {
|
||||
const currentDate = new Date();
|
||||
return (
|
||||
EmptyListMessages.GENERATING_VISIT_PLAN +
|
||||
' ' +
|
||||
dateFormat(currentDate, DAY_MONTH_DATE_FORMAT) +
|
||||
'...'
|
||||
);
|
||||
}
|
||||
if (isFilterApplied) {
|
||||
return EmptyListMessages.NO_CASES_FOUND;
|
||||
}
|
||||
if (isVisitPlan) {
|
||||
return EmptyListMessages.NO_VISIT_PLANS;
|
||||
}
|
||||
|
||||
if (isTeamLead) {
|
||||
if (selectedAgent.referenceId === MY_CASE_ITEM.referenceId) {
|
||||
return EmptyListMessages.NO_ACTIVE_ALLOCATIONS;
|
||||
}
|
||||
return EmptyListMessages.NO_ACTIVE_ALLOCATIONS_SELECTED_AGENT;
|
||||
}
|
||||
|
||||
return EmptyListMessages.NO_PENDING_CASES;
|
||||
};
|
||||
|
||||
const getSubMessage = () => {
|
||||
if (isVisitPlan && isLockedVisitPlanStatus) {
|
||||
return EmptyListMessages.GENERATING_VISIT_PLAN_SUB;
|
||||
}
|
||||
if (isFilterApplied) {
|
||||
return EmptyListMessages.NO_CASES_FOUND_SUB;
|
||||
}
|
||||
if (isTeamLead) {
|
||||
if (selectedAgent.referenceId === MY_CASE_ITEM.referenceId) {
|
||||
return EmptyListMessages.SELECT_AGENT;
|
||||
}
|
||||
return EmptyListMessages.SELECT_AGENT_SELECTED_AGENT;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAgentSelectionCTAClick = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_AGENT_LIST_BUTTON_CLICKED);
|
||||
setShowAgentSelectionBottomSheet && setShowAgentSelectionBottomSheet(true);
|
||||
};
|
||||
|
||||
const getBtnDetails = () => {
|
||||
if (isVisitPlan && !isFilterApplied && !isLockedVisitPlanStatus) {
|
||||
return {
|
||||
btnHandler: () => navigateToScreen('Cases'),
|
||||
btnText: 'Create a visit plan',
|
||||
};
|
||||
}
|
||||
|
||||
if (isTeamLead && setShowAgentSelectionBottomSheet) {
|
||||
return {
|
||||
btnHandler: handleAgentSelectionCTAClick,
|
||||
btnText: 'Select Agent',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const message = getEmptyListMessage();
|
||||
const subMessage = getSubMessage();
|
||||
const btnDetails = getBtnDetails();
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={[GenericStyles.w100, styles.centerAbsolute]}>
|
||||
@@ -56,11 +139,11 @@ const EmptyList: React.FC<IEmptyList> = ({
|
||||
{subMessage}
|
||||
</Text>
|
||||
) : null}
|
||||
{isVisitPlan && !isFilterApplied && !isLockedVisitPlanStatus ? (
|
||||
{btnDetails ? (
|
||||
<Button
|
||||
title={'Create a visit plan'}
|
||||
title={btnDetails.btnText}
|
||||
style={[GenericStyles.w100, GenericStyles.mt24]}
|
||||
onPress={handleCreateVisitPlan}
|
||||
onPress={btnDetails.btnHandler}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
@@ -13,7 +13,6 @@ import { VisitPlanStatus } from '../../reducer/userSlice';
|
||||
import { FeedbackStatus } from '../caseDetails/interface';
|
||||
|
||||
interface IFilters {
|
||||
filterCount: number;
|
||||
searchQuery: string;
|
||||
handleSearchChange: (val: string) => void;
|
||||
toggleFilterModal: () => void;
|
||||
@@ -22,20 +21,22 @@ interface IFilters {
|
||||
|
||||
const Filters: React.FC<IFilters> = ({
|
||||
searchQuery,
|
||||
filterCount,
|
||||
handleSearchChange,
|
||||
toggleFilterModal,
|
||||
isVisitPlan,
|
||||
}) => {
|
||||
const { attemptedCount, totalPinnedCount, isVisitPlanStatusLocked } = useAppSelector((state) => ({
|
||||
totalPinnedCount: state.allCases.pinnedList.length,
|
||||
attemptedCount: state.allCases.pinnedList.filter(
|
||||
(item) =>
|
||||
state.allCases.caseDetails[item.caseReferenceId]?.feedbackStatus ===
|
||||
FeedbackStatus.ATTEMPTED
|
||||
)?.length,
|
||||
isVisitPlanStatusLocked: state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED,
|
||||
}));
|
||||
const { attemptedCount, totalPinnedCount, isVisitPlanStatusLocked, filterCount } = useAppSelector(
|
||||
(state) => ({
|
||||
totalPinnedCount: state.allCases.pinnedList.length,
|
||||
attemptedCount: state.allCases.pinnedList.filter(
|
||||
(item) =>
|
||||
state.allCases.caseDetails[item.caseReferenceId]?.feedbackStatus ===
|
||||
FeedbackStatus.ATTEMPTED
|
||||
)?.length,
|
||||
isVisitPlanStatusLocked: state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED,
|
||||
filterCount: isVisitPlan ? state.filters.filterCountVisitPlan : state.filters.filterCount,
|
||||
})
|
||||
);
|
||||
const getBarWidth = () => {
|
||||
if (!totalPinnedCount) {
|
||||
return 0;
|
||||
@@ -45,22 +46,17 @@ const Filters: React.FC<IFilters> = ({
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.mh20,
|
||||
GenericStyles.ph16,
|
||||
GenericStyles.pb16,
|
||||
GenericStyles.centerAlignedRow,
|
||||
]}
|
||||
>
|
||||
<TextInput
|
||||
style={styles.textInput}
|
||||
LeftComponent={<SearchIcon />}
|
||||
onChangeText={handleSearchChange}
|
||||
placeholder={`Search in ${isVisitPlan ? 'visit plan' : 'my cases'}`}
|
||||
defaultValue={searchQuery}
|
||||
testID="test_search"
|
||||
/>
|
||||
<View style={[GenericStyles.mh16, GenericStyles.pb16, GenericStyles.centerAlignedRow]}>
|
||||
<View style={GenericStyles.fill}>
|
||||
<TextInput
|
||||
LeftComponent={<SearchIcon />}
|
||||
onChangeText={handleSearchChange}
|
||||
placeholder={`Search in ${isVisitPlan ? 'visit plan' : 'my cases'}`}
|
||||
defaultValue={searchQuery}
|
||||
testID="test_search"
|
||||
showClearIcon
|
||||
/>
|
||||
</View>
|
||||
<IconButton
|
||||
style={[GenericStyles.ml8, styles.filterIcon]}
|
||||
testID="filter-btn"
|
||||
@@ -162,7 +158,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
|
||||
textInput: {
|
||||
flexBasis: '75%',
|
||||
flex: 1,
|
||||
},
|
||||
chips: {
|
||||
paddingHorizontal: 18,
|
||||
|
||||
88
src/screens/allCases/HeaderLabel.tsx
Normal file
88
src/screens/allCases/HeaderLabel.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Animated, Easing, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Heading from '../../../RN-UI-LIB/src/components/Heading';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import ArrowDownOutlineIcon from '../../assets/icons/ArrowDownOutlineIcon';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import { RootState } from '../../store/store';
|
||||
import { MY_CASE_ITEM, VisitPlanStatus } from '../../reducer/userSlice';
|
||||
import { dateFormat, DAY_MONTH_DATE_FORMAT } from '../../../RN-UI-LIB/src/utlis/dates';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
|
||||
interface HeaderLabelProps {
|
||||
setShowAgentSelectionBottomSheet: (val: boolean) => void;
|
||||
filteredListCount: number;
|
||||
isVisitPlan?: boolean;
|
||||
}
|
||||
|
||||
const HeaderLabel: React.FC<HeaderLabelProps> = (props) => {
|
||||
const { setShowAgentSelectionBottomSheet, filteredListCount, isVisitPlan } = props;
|
||||
|
||||
const {
|
||||
isLockedVisitPlanStatus,
|
||||
selectedAgent = MY_CASE_ITEM,
|
||||
loading,
|
||||
} = useAppSelector((state: RootState) => ({
|
||||
isLockedVisitPlanStatus: state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED,
|
||||
selectedAgent: state.user.selectedAgent,
|
||||
loading: state.allCases.loading,
|
||||
}));
|
||||
const isTeamLead = useAppSelector((state) => state.user.isTeamLead);
|
||||
|
||||
const handleAgentSelectionCTAClick = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_AGENT_LIST_DROPDOWN_CLICKED);
|
||||
setShowAgentSelectionBottomSheet(true);
|
||||
};
|
||||
|
||||
const getHeaderLabel = () => {
|
||||
if (isVisitPlan) {
|
||||
if (isLockedVisitPlanStatus) {
|
||||
return 'Generating visit plan';
|
||||
}
|
||||
const currentDate = new Date();
|
||||
const formattedDate = dateFormat(currentDate, DAY_MONTH_DATE_FORMAT);
|
||||
return formattedDate;
|
||||
}
|
||||
return `${selectedAgent.name}${!loading ? ` (${filteredListCount})` : ''}`;
|
||||
};
|
||||
|
||||
if (isTeamLead && !isVisitPlan) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={GenericStyles.centerAlignedRow}
|
||||
onPress={handleAgentSelectionCTAClick}
|
||||
>
|
||||
<Heading type="h3" style={[styles.headerLabel]}>
|
||||
{getHeaderLabel()}
|
||||
</Heading>
|
||||
<View style={GenericStyles.mt2}>
|
||||
<ArrowDownOutlineIcon />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Heading type="h3" style={[styles.headerLabel]}>
|
||||
{getHeaderLabel()}
|
||||
</Heading>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
headerLabel: {
|
||||
color: COLORS.BACKGROUND.PRIMARY,
|
||||
},
|
||||
animatedOverlay: {
|
||||
backgroundColor: COLORS.BACKGROUND.PRIMARY,
|
||||
position: 'absolute',
|
||||
width: '110%',
|
||||
height: '90%',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
});
|
||||
|
||||
export default HeaderLabel;
|
||||
@@ -77,6 +77,7 @@ const ListItem: React.FC<IListItem> = (props) => {
|
||||
const isVisitPlanStatusLocked = useAppSelector(
|
||||
(state) => state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED
|
||||
);
|
||||
const isTeamLead = useAppSelector((state) => state.user.isTeamLead);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -170,7 +171,7 @@ const ListItem: React.FC<IListItem> = (props) => {
|
||||
const caseCompleted = COMPLETED_STATUSES.includes(caseStatus);
|
||||
|
||||
const showVisitPlanBtn =
|
||||
!(caseCompleted || isCaseItemPinnedMainView) && !isTodoItem && !isCompleted;
|
||||
!(caseCompleted || isCaseItemPinnedMainView) && !isTodoItem && !isCompleted && !isTeamLead;
|
||||
|
||||
return (
|
||||
<Pressable onPress={handleCaseClick}>
|
||||
|
||||
@@ -45,6 +45,10 @@ export const EmptyListMessages = {
|
||||
NO_CASES_FOUND_SUB: 'Try removing or adding different filters, or search something else',
|
||||
GENERATING_VISIT_PLAN: 'Generating a visit plan for',
|
||||
GENERATING_VISIT_PLAN_SUB: '*Any pending cases from previous visit plan will be added',
|
||||
NO_ACTIVE_ALLOCATIONS: 'You don’t have any active allocations',
|
||||
NO_ACTIVE_ALLOCATIONS_SELECTED_AGENT: 'Selected agent does not have any active allocations',
|
||||
SELECT_AGENT: 'Select an agent to view cases',
|
||||
SELECT_AGENT_SELECTED_AGENT: 'Select another agent to view cases',
|
||||
};
|
||||
|
||||
export const ToastMessages = {
|
||||
|
||||
@@ -29,9 +29,10 @@ const AllCasesMain = () => {
|
||||
);
|
||||
const userState = useAppSelector((state: RootState) => state.user);
|
||||
const dispatch = useAppDispatch();
|
||||
const isTeamLead = useAppSelector((state) => state.user.isTeamLead);
|
||||
|
||||
const HOME_SCREENS: ITabScreen[] = useMemo(
|
||||
() => [
|
||||
const HOME_SCREENS: ITabScreen[] = useMemo(() => {
|
||||
const bottomSheetScreens = [
|
||||
{
|
||||
name: BOTTOM_TAB_ROUTES.Cases,
|
||||
component: () => (
|
||||
@@ -39,19 +40,21 @@ const AllCasesMain = () => {
|
||||
),
|
||||
icon: CasesIcon,
|
||||
},
|
||||
{
|
||||
];
|
||||
if (!isTeamLead) {
|
||||
bottomSheetScreens.push({
|
||||
name: BOTTOM_TAB_ROUTES.VisitPlan,
|
||||
component: () => <CasesList casesList={pinnedList} isVisitPlan />,
|
||||
icon: VisitPlanIcon,
|
||||
},
|
||||
{
|
||||
name: BOTTOM_TAB_ROUTES.Profile,
|
||||
component: () => <Profile />,
|
||||
icon: ProfileIcon,
|
||||
},
|
||||
],
|
||||
[pendingList, pinnedList]
|
||||
);
|
||||
});
|
||||
}
|
||||
bottomSheetScreens.push({
|
||||
name: BOTTOM_TAB_ROUTES.Profile,
|
||||
component: () => <Profile />,
|
||||
icon: ProfileIcon,
|
||||
});
|
||||
return bottomSheetScreens;
|
||||
}, [pendingList, pinnedList, isTeamLead]);
|
||||
|
||||
const onTabPressHandler = (e: any) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TAB_SWITCH, {
|
||||
@@ -76,7 +79,7 @@ const AllCasesMain = () => {
|
||||
<FullScreenLoader loading={loading} />
|
||||
<BottomNavigator
|
||||
screens={HOME_SCREENS}
|
||||
initialRoute={HOME_SCREENS[1].name}
|
||||
initialRoute={isTeamLead ? BOTTOM_TAB_ROUTES.Cases : BOTTOM_TAB_ROUTES.VisitPlan}
|
||||
onTabPress={(e) => onTabPressHandler(e)}
|
||||
/>
|
||||
<CasesActionButtons />
|
||||
|
||||
@@ -320,3 +320,15 @@ export interface ICaseItemAvatarCaseDetailObj extends IFetchDocumentCaseDetailOb
|
||||
export interface ICaseItemCaseDetailObj extends CaseDetail {
|
||||
isIntermediateOrSelectedTodoCaseItem?: boolean;
|
||||
}
|
||||
|
||||
export interface ISectionListData {
|
||||
title: string;
|
||||
data: IReportee[];
|
||||
}
|
||||
|
||||
export interface IReportee {
|
||||
referenceId: string;
|
||||
name: string;
|
||||
agencyCode: string;
|
||||
agencyName: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CaseDetail, FeedbackStatus } from '../caseDetails/interface';
|
||||
import { ICaseItem } from './interface';
|
||||
import { ICaseItem, IReportee, ISectionListData } from './interface';
|
||||
|
||||
export const getAttemptedList = (
|
||||
filteredCasesList: ICaseItem[],
|
||||
@@ -27,3 +27,35 @@ export const getNonAttemptedList = (
|
||||
: true
|
||||
);
|
||||
};
|
||||
|
||||
export const sectionListTranformData = (agentList: IReportee[]): ISectionListData[] => {
|
||||
const result: ISectionListData[] = [];
|
||||
|
||||
// Create an object to map agency names to their corresponding data
|
||||
const agencyMap: { [key: string]: IReportee[] } = {};
|
||||
|
||||
for (const agent of agentList) {
|
||||
const agencyName = agent.agencyName;
|
||||
|
||||
if (!agencyMap[agencyName]) {
|
||||
agencyMap[agencyName] = [];
|
||||
}
|
||||
|
||||
agencyMap[agencyName].push({
|
||||
referenceId: agent.referenceId,
|
||||
name: agent.name,
|
||||
agencyCode: agent.agencyCode,
|
||||
agencyName: agent.agencyName,
|
||||
});
|
||||
}
|
||||
|
||||
// Transform the agencyMap into the desired result format
|
||||
for (const agencyName in agencyMap) {
|
||||
result.push({
|
||||
title: agencyName,
|
||||
data: agencyMap[agencyName],
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ const AuthRouter = () => {
|
||||
agentId: user?.user?.referenceId,
|
||||
deviceType: isTablet() ? DEVICE_TYPE_ENUM.TAB : DEVICE_TYPE_ENUM.MOBILE,
|
||||
isImpersonated: user?.isImpersonated ?? false,
|
||||
selectedAgentId: user?.selectedAgent?.referenceId,
|
||||
});
|
||||
|
||||
// Sets the dispatch for apiHelper
|
||||
|
||||
@@ -32,6 +32,7 @@ import Notifications from '../notifications';
|
||||
import RegisterPayments from '../registerPayements/RegisterPayments';
|
||||
import TodoList from '../todoList/TodoList';
|
||||
import UngroupedAddressContainer from '../addressGeolocation/UngroupedAddressContainer';
|
||||
import { getAgentDetail } from '../../action/authActions';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
@@ -50,10 +51,14 @@ const ProtectedRouter = () => {
|
||||
const { notificationsWithActions } = useAppSelector((state) => state.notifications);
|
||||
const isOnline = useIsOnline();
|
||||
const dispatch = useAppDispatch();
|
||||
const isTeamLead = useAppSelector((state) => state.user.isTeamLead);
|
||||
|
||||
// Gets unified data for new visit plan cases
|
||||
// TODO: Move this to another place
|
||||
useEffect(() => {
|
||||
if (isTeamLead) {
|
||||
return;
|
||||
}
|
||||
if (newVisitedCases?.length) {
|
||||
const loanAccountNumbers: string[] = [];
|
||||
newVisitedCases.forEach((caseId) => {
|
||||
@@ -82,6 +87,7 @@ const ProtectedRouter = () => {
|
||||
useEffect(() => {
|
||||
if (isOnline) {
|
||||
dispatch(getNotifications());
|
||||
dispatch(getAgentDetail());
|
||||
}
|
||||
}, [isOnline]);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ const interactionsHandler = () => {
|
||||
docsToBeUploaded: state.feedbackImages.docsToBeUploaded,
|
||||
}));
|
||||
const { templateId } = useAppSelector(
|
||||
(state) => state.case.templateData[CaseAllocationType.ADDRESS_VERIFICATION_CASE]
|
||||
(state) => state.case.templateData[CaseAllocationType.ADDRESS_VERIFICATION_CASE] || {}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -61,7 +61,6 @@ const OtpInput = () => {
|
||||
otpToken,
|
||||
};
|
||||
dispatch(verifyOTP(payload));
|
||||
reset();
|
||||
};
|
||||
|
||||
const handleBackClick = () => {
|
||||
|
||||
@@ -30,6 +30,7 @@ import foregroundServiceSlice from '../reducer/foregroundServiceSlice';
|
||||
import feedbackImagesSlice from '../reducer/feedbackImagesSlice';
|
||||
import configSlice from '../reducer/configSlice';
|
||||
import profileSlice from '../reducer/profileSlice';
|
||||
import reporteesSlice from '../reducer/reporteesSlice';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
case: caseReducer,
|
||||
@@ -50,6 +51,7 @@ const rootReducer = combineReducers({
|
||||
feedbackImages: feedbackImagesSlice,
|
||||
config: configSlice,
|
||||
profile: profileSlice,
|
||||
reportees: reporteesSlice,
|
||||
});
|
||||
|
||||
const persistConfig = {
|
||||
@@ -72,7 +74,7 @@ const persistConfig = {
|
||||
'profile',
|
||||
'foregroundService',
|
||||
],
|
||||
blackList: ['case', 'filters'],
|
||||
blackList: ['case', 'filters', 'reportees'],
|
||||
};
|
||||
|
||||
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||
|
||||
Reference in New Issue
Block a user