Merge branch 'TP-41687-blacklisted-apps-v3' of github.com:navi-medici/address-verification-app into TP-41687-blacklisted-apps-v3
This commit is contained in:
48
.github/workflows/newBuild.yml
vendored
48
.github/workflows/newBuild.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Create-Apk-QA
|
||||
name: generate-apk
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -8,23 +8,31 @@ on:
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- qa
|
||||
- dev
|
||||
- QA
|
||||
- Prod
|
||||
flavor:
|
||||
description: Choose build flavour
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- fieldAgents
|
||||
- callingAgents
|
||||
type:
|
||||
description: Choose build type
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- debug
|
||||
- release
|
||||
version_code:
|
||||
description: Enter app version code (example, 292)
|
||||
required: false
|
||||
required: true
|
||||
type: string
|
||||
default: "292"
|
||||
version_name:
|
||||
description: Enter app version name (example, 3.2.1)
|
||||
required: false
|
||||
required: true
|
||||
type: string
|
||||
default: "3.2.1"
|
||||
jobs:
|
||||
generate:
|
||||
runs-on: [ default ]
|
||||
@@ -69,21 +77,21 @@ jobs:
|
||||
run: chmod +x android/gradlew
|
||||
- name: Create local.properties
|
||||
run: cd android && touch local.properties && echo "sdk.dir = /home/USERNAME/Android/Sdk" > local.properties
|
||||
- name: Assemble with Stacktrace - QA debug
|
||||
if: ((github.event.inputs.environment == 'qa' || inputs.environment == 'qa') && (github.event.inputs.type == 'debug' || inputs.type == 'debug'))
|
||||
run: yarn move:qa && yarn debug && cd android && ./gradlew assembleDebug
|
||||
- name: Assemble with Stacktrace - Dev debug
|
||||
if: ((github.event.inputs.environment == 'dev' || inputs.environment == 'dev') && (github.event.inputs.type == 'debug' || inputs.type == 'debug'))
|
||||
run: yarn move:dev && yarn debug && cd android && ./gradlew assembleDebug
|
||||
- name: Assemble with Stacktrace - QA release
|
||||
if: ((github.event.inputs.environment == 'qa' || inputs.environment == 'qa') && (github.event.inputs.type == 'release' || inputs.type == 'release'))
|
||||
run: yarn move:qa && cd android && ./gradlew assembleRelease
|
||||
- name: Assemble with Stacktrace - Dev release
|
||||
if: ((github.event.inputs.environment == 'dev' || inputs.environment == 'dev') && (github.event.inputs.type == 'debug' || inputs.type == 'release'))
|
||||
run: yarn move:dev && cd android && ./gradlew assembleRelease
|
||||
- name: Assemble with Stacktrace - Field QA release
|
||||
if: ((github.event.inputs.environment == 'QA' || inputs.environment == 'QA') && (github.event.flavor.type == 'fieldAgents' || inputs.flavor == 'fieldAgents'))
|
||||
run: yarn move:qa && cd android && ./gradlew assemblefieldAgentsQARelease
|
||||
- name: Assemble with Stacktrace - Field PROD release
|
||||
if: ((github.event.inputs.environment == 'Prod' || inputs.environment == 'Prod') && (github.event.flavor.type == 'fieldAgents' || inputs.flavor == 'fieldAgents'))
|
||||
run: yarn move:prod && cd android && ./gradlew assemblefieldAgentsProdRelease
|
||||
- name: Assemble with Stacktrace - Calling QA release
|
||||
if: ((github.event.inputs.environment == 'QA' || inputs.environment == 'QA') && (github.event.flavor.type == 'callingAgents' || inputs.flavor == 'callingAgents'))
|
||||
run: yarn move:qa && cd android && ./gradlew assemblefieldAgentsQARelease
|
||||
- name: Assemble with Stacktrace - Calling PROD release
|
||||
if: ((github.event.inputs.environment == 'Prod' || inputs.environment == 'Prod') && (github.event.flavor.type == 'callingAgents' || inputs.flavor == 'callingAgents'))
|
||||
run: yarn move:prod && cd android && ./gradlew assemblefieldAgentsProdRelease
|
||||
- name: Upload APK as Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: app-${{ github.event.inputs.type || inputs.type }}
|
||||
path: android/app/build/outputs/apk/${{ github.event.inputs.type || inputs.type }}/
|
||||
name: app-${{ github.event.inputs.type || inputs.type }}-v${{ github.event.inputs.version_code || inputs.version_code }}-name-${{github.event.inputs.version_name || inputs.version_name}}
|
||||
path: android/app/build/outputs/apk/${{ github.event.inputs.flavor || inputs.flavor }}${{github.event.inputs.environment || inputs.environment}}/${{github.event.inputs.type || inputs.type}}
|
||||
retention-days: 30
|
||||
|
||||
@@ -313,7 +313,7 @@ dependencies {
|
||||
|
||||
implementation "com.github.anrwatchdog:anrwatchdog:1.4.0"
|
||||
|
||||
implementation 'com.navi.medici:alfred:v1.0.1'
|
||||
implementation 'com.navi.medici:alfred:v1.0.2'
|
||||
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
<queries>
|
||||
<package android:name="com.whatsapp" />
|
||||
<package android:name="com.whatsapp.w4b" />
|
||||
</queries>
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
|
||||
<application
|
||||
|
||||
@@ -21,6 +21,9 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
||||
import com.facebook.react.uimanager.UIBlock;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.navi.alfred.AlfredManager;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
@@ -28,6 +31,7 @@ import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Base64;
|
||||
|
||||
import android.os.Looper;
|
||||
@@ -39,6 +43,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@@ -71,7 +76,7 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule {
|
||||
@Override
|
||||
public void onActivityResult(Activity activity, int i, int i1, @Nullable Intent intent) {
|
||||
if(i1 != RESULT_CANCELED) {
|
||||
if (i == WHATSAPP_SHARE_REQUEST_CODE) {
|
||||
if (i == WHATSAPP_SHARE_REQUEST_CODE && (imageFile!=null)) {
|
||||
new File(Uri.fromFile(imageFile).getPath()).delete();
|
||||
}
|
||||
}
|
||||
@@ -144,6 +149,10 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule {
|
||||
promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void sendBottomSheetOpenSignal(Boolean isBottomSheetOpen) {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setUserId(String userId) {
|
||||
@@ -181,16 +190,29 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void sendBottomSheetOpenSignal(Boolean isBottomSheetOpen) {
|
||||
if (isBottomSheetOpen) {
|
||||
View bottomSheetScreen = LayoutInflater.from(RNContext).inflate(R.layout.bottom_sheet_screen, null);
|
||||
AlfredManager.INSTANCE.measureInflatedView(bottomSheetScreen, 1080, 540);
|
||||
AlfredManager.INSTANCE.setCosmosBottomSheet(bottomSheetScreen);
|
||||
} else {
|
||||
AlfredManager.INSTANCE.setCosmosBottomSheet(null);
|
||||
public void setBottomSheetView(Integer refID) {
|
||||
if (refID != null) {
|
||||
UIManagerModule uiManagerModule = RNContext.getNativeModule(UIManagerModule.class);
|
||||
if (uiManagerModule != null) {
|
||||
try {
|
||||
uiManagerModule.addUIBlock(nativeViewHierarchyManager -> {
|
||||
Log.d("Alfred", "setBottomSheetView nativeViewHierarchyManager:" + nativeViewHierarchyManager);
|
||||
View view = nativeViewHierarchyManager.resolveView(refID);
|
||||
Log.d("Alfred", "setBottomSheetView view:" + view);
|
||||
AlfredManager.INSTANCE.setBottomSheetView(view);
|
||||
});
|
||||
} catch (Exception error) {
|
||||
Log.d("Alfred", "setBottomSheetView error:" + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void clearBottomSheet() {
|
||||
AlfredManager.INSTANCE.clearBottomSheetView();
|
||||
}
|
||||
|
||||
private static File convertBase64ToFile(Context context,String base64Data) {
|
||||
try {
|
||||
byte[] decodedBytes = Base64.decode(base64Data, Base64.DEFAULT);
|
||||
@@ -207,50 +229,87 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isWhatsAppInstalled() {
|
||||
public ArrayList<String> isWhatsAppInstalled() {
|
||||
PackageManager packageManager = RNContext.getPackageManager();
|
||||
List<PackageInfo> packages = packageManager.getInstalledPackages(PackageManager.GET_META_DATA);
|
||||
ArrayList<String> appsInstalled = new ArrayList<String>();
|
||||
|
||||
for (PackageInfo packageInfo : packages) {
|
||||
String packageName = packageInfo.packageName;
|
||||
if(packageName.equals("com.whatsapp")){
|
||||
return true;
|
||||
if(packageName.equals("com.whatsapp") || packageName.equals("com.whatsapp.w4b")){
|
||||
appsInstalled.add(packageName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return appsInstalled;
|
||||
}
|
||||
|
||||
public Intent getWhatsappShareIntent(String message, String imageUrl, String mimeType, String packageName) {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, message);
|
||||
|
||||
if (imageUrl.equals("")) {
|
||||
sendIntent.setType("text/plain");
|
||||
sendIntent.setPackage(packageName);
|
||||
} else {
|
||||
sendIntent.setType(mimeType);
|
||||
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
imageFile = convertBase64ToFile(getReactApplicationContext(), imageUrl);
|
||||
Uri fileUri = FileProvider.getUriForFile(getReactApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", new File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
imageFile.getName()
|
||||
)
|
||||
);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
sendIntent.setPackage(packageName);
|
||||
}
|
||||
|
||||
return sendIntent;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void sendFeedbackToWhatsapp(String message, String imageUrl, String mimeType, Promise promise) {
|
||||
try{
|
||||
if(!isWhatsAppInstalled()){
|
||||
ArrayList<String> appsInstalled = isWhatsAppInstalled();
|
||||
int numberOfAppsInstalled = appsInstalled.size();
|
||||
|
||||
if(numberOfAppsInstalled == 0){
|
||||
promise.reject("errorCode", "1");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, message);
|
||||
|
||||
if(imageUrl.equals("")) {
|
||||
sendIntent.setType("text/plain");
|
||||
sendIntent.setPackage("com.whatsapp");
|
||||
getCurrentActivity().startActivity(sendIntent);
|
||||
} else {
|
||||
sendIntent.setType(mimeType);
|
||||
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
imageFile = convertBase64ToFile(getReactApplicationContext(), imageUrl);
|
||||
Uri fileUri = FileProvider.getUriForFile(getReactApplicationContext(), BuildConfig.APPLICATION_ID + ".provider", new File(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||
imageFile.getName()
|
||||
)
|
||||
);
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
sendIntent.setPackage("com.whatsapp");
|
||||
getCurrentActivity().startActivityForResult(sendIntent, WHATSAPP_SHARE_REQUEST_CODE);
|
||||
else if(numberOfAppsInstalled == 1) {
|
||||
String packageName = appsInstalled.get(0);
|
||||
Intent sendIntent = getWhatsappShareIntent(message, imageUrl, mimeType, packageName);
|
||||
if(getCurrentActivity()!=null) {
|
||||
getCurrentActivity().startActivityForResult(sendIntent, WHATSAPP_SHARE_REQUEST_CODE);
|
||||
}
|
||||
promise.resolve(true);
|
||||
return;
|
||||
}
|
||||
|
||||
else {
|
||||
String packageName1 = appsInstalled.get(0);
|
||||
String packageName2 = appsInstalled.get(1);
|
||||
//Firing two intents, one for WhatsApp, another for WhatsApp business
|
||||
Intent sendIntent1 = getWhatsappShareIntent(message, imageUrl, mimeType, packageName1);
|
||||
Intent sendIntent2 = getWhatsappShareIntent(message, imageUrl, mimeType, packageName2);
|
||||
ArrayList<Intent> appIntents = new ArrayList<>();
|
||||
appIntents.add(sendIntent1);
|
||||
appIntents.add(sendIntent2);
|
||||
|
||||
Intent defaultIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
defaultIntent.setType("text/plain");
|
||||
defaultIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Sharing to WhatsApp");
|
||||
|
||||
Intent chooserIntent = Intent.createChooser(defaultIntent, "Share via");
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, appIntents.toArray(new Parcelable[appIntents.size()]));
|
||||
if (getCurrentActivity() != null) {
|
||||
getCurrentActivity().startActivityForResult(chooserIntent, WHATSAPP_SHARE_REQUEST_CODE);
|
||||
}
|
||||
promise.resolve(true);
|
||||
return;
|
||||
}
|
||||
promise.resolve(true);
|
||||
return;
|
||||
|
||||
} catch (Error e){
|
||||
promise.reject("errorCode","2");
|
||||
|
||||
@@ -9,6 +9,13 @@ interface IPastFeedbacksPayload {
|
||||
addressReferenceIds?: string; // required to fetch past feedback on addresses
|
||||
}
|
||||
|
||||
export interface IFilterPayload {
|
||||
filters: Array<{
|
||||
filterName: string;
|
||||
selectedValue: string[];
|
||||
}>;
|
||||
}
|
||||
|
||||
interface IPastFeedbacksOnAddressesPayload {
|
||||
loanAccountNumber: string;
|
||||
pageNo?: number;
|
||||
@@ -16,12 +23,13 @@ interface IPastFeedbacksOnAddressesPayload {
|
||||
addressReferenceIds?: string;
|
||||
}
|
||||
|
||||
export const getPastFeedbacks = (queryParamsPayload: IPastFeedbacksPayload) => {
|
||||
const url = getApiUrl(ApiKeys.PAST_FEEDBACK);
|
||||
export const getPastFeedbacks = (
|
||||
queryParamsPayload: IPastFeedbacksPayload,
|
||||
filterPayload: IFilterPayload
|
||||
) => {
|
||||
const url = getApiUrl(ApiKeys.PAST_FEEDBACK, {}, queryParamsPayload);
|
||||
return axiosInstance
|
||||
.get(url, {
|
||||
params: queryParamsPayload,
|
||||
})
|
||||
.post(url, filterPayload)
|
||||
.then((response) => {
|
||||
if (response?.data) {
|
||||
return {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import Svg, { Path } from 'react-native-svg';
|
||||
const FilterIcon = () => (
|
||||
import { IconProps } from '../../../RN-UI-LIB/src/Icons/types';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
|
||||
const FilterIcon: React.FC<IconProps> = ({ fillColor = COLORS.BACKGROUND.LIGHT }) => (
|
||||
<Svg width={16} height={18} viewBox="0 0 16 18" fill="none">
|
||||
<Path
|
||||
d="M15.8456 0.780302C15.7312 0.54479 15.5585 0.347411 15.3463 0.209792C15.1341 0.0721637 14.8906 -0.000412239 14.6423 1.76134e-06H1.35695C1.10879 -0.000250239 0.865321 0.0723077 0.653091 0.209774C0.440852 0.34724 0.267994 0.544331 0.153345 0.779573C0.0386958 1.01482 -0.0133408 1.27919 0.00291016 1.54387C0.0191527 1.80855 0.10306 2.06339 0.245487 2.2806L0.265695 2.3112L5.89461 10.2042V17.1C5.89453 17.2629 5.93596 17.4228 6.01426 17.5626C6.09257 17.7024 6.20498 17.8169 6.33937 17.8937C6.47375 17.9706 6.62515 18.007 6.77747 17.9991C6.92971 17.9912 7.07714 17.9393 7.20395 17.8488L9.73 16.0488C9.84527 15.9666 9.93975 15.8552 10.0052 15.7246C10.0705 15.5939 10.1047 15.4481 10.1047 15.3V10.2033L15.7538 2.2833C15.8968 2.06596 15.981 1.81071 15.9971 1.54558C16.0133 1.28045 15.9608 1.01567 15.8456 0.780302Z"
|
||||
fill="#969696"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
@@ -2,24 +2,28 @@ import React from 'react';
|
||||
import BottomSheet, {
|
||||
BottomSheetProps,
|
||||
} from '../../RN-UI-LIB/src/components/bottom_sheet/BottomSheet';
|
||||
import { sendBottomSheetOpenSignal } from '../components/utlis/DeviceUtils';
|
||||
import { NativeSyntheticEvent } from 'react-native';
|
||||
import { clearBottomSheet, setBottomSheetView } from '../components/utlis/DeviceUtils';
|
||||
|
||||
interface IBottomSheetWrapperProps extends BottomSheetProps {}
|
||||
|
||||
const BottomSheetWrapper: React.FC<IBottomSheetWrapperProps> = (props) => {
|
||||
const { children, onShow, onSwipeDownClose, onClose, ...restProps } = props;
|
||||
const onCloseHandler = () => {
|
||||
sendBottomSheetOpenSignal(false);
|
||||
clearBottomSheet();
|
||||
if (typeof onClose === 'function') onClose();
|
||||
};
|
||||
|
||||
const onShowHandler = (event: NativeSyntheticEvent<any>) => {
|
||||
sendBottomSheetOpenSignal(true);
|
||||
if (typeof onShow === 'function') onShow(event);
|
||||
const onAnimationEndHandler = (id: number | null) => {
|
||||
if (!id) return;
|
||||
setBottomSheetView(id);
|
||||
};
|
||||
return (
|
||||
<BottomSheet onShow={onShowHandler} onClose={onCloseHandler} {...restProps}>
|
||||
<BottomSheet
|
||||
onShow={onShow}
|
||||
onClose={onCloseHandler}
|
||||
onBottomSheetAnimationEnd={onAnimationEndHandler}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</BottomSheet>
|
||||
);
|
||||
|
||||
@@ -747,6 +747,7 @@ export const REQUEST_TO_UNBLOCK_FOR_IMPERSONATION = [
|
||||
getApiUrl(ApiKeys.GET_SIGNED_URL),
|
||||
getApiUrl(ApiKeys.GET_SIGNED_URL_FOR_REPORTEE),
|
||||
getApiUrl(ApiKeys.LOGOUT),
|
||||
getApiUrl(ApiKeys.PAST_FEEDBACK),
|
||||
];
|
||||
|
||||
export const NAVI_AGENCY_CODE = '1000';
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import React from 'react';
|
||||
import Dropdown, { IDropdown } from '../../RN-UI-LIB/src/components/dropdown/Dropdown';
|
||||
import { sendBottomSheetOpenSignal } from '../components/utlis/DeviceUtils';
|
||||
import {
|
||||
clearBottomSheet,
|
||||
sendBottomSheetOpenSignal,
|
||||
setBottomSheetView,
|
||||
} from '../components/utlis/DeviceUtils';
|
||||
|
||||
const DropDownWrapper: React.FC<IDropdown> = (props) => {
|
||||
const { onShow, onClose, children, ...remainingProps } = props;
|
||||
const onShowHandler = () => {
|
||||
if (typeof onShow === 'function') onShow();
|
||||
sendBottomSheetOpenSignal(true);
|
||||
};
|
||||
const { onShow, onClose, onAnimationEnd, children, ...remainingProps } = props;
|
||||
|
||||
const onCloseHandler = () => {
|
||||
if (typeof onClose === 'function') onClose();
|
||||
sendBottomSheetOpenSignal(false);
|
||||
clearBottomSheet();
|
||||
};
|
||||
|
||||
const onAnimationEndHandler = (id: number | null) => {
|
||||
if (!id) return;
|
||||
console.log('dropdown opened', id);
|
||||
setBottomSheetView(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown onShow={onShowHandler} onClose={onCloseHandler} {...remainingProps}>
|
||||
<Dropdown onClose={onCloseHandler} onAnimationEnd={onAnimationEndHandler} {...remainingProps}>
|
||||
{children}
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
45
src/common/ModalWrapperForAlfredV2.tsx
Normal file
45
src/common/ModalWrapperForAlfredV2.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Modal, NativeSyntheticEvent, View, findNodeHandle } from 'react-native';
|
||||
import { IModalWrapper } from '../../RN-UI-LIB/src/components/modalWrapper/ModalWrapper';
|
||||
import {
|
||||
clearBottomSheet,
|
||||
sendBottomSheetOpenSignal,
|
||||
setBottomSheetView,
|
||||
} from '../components/utlis/DeviceUtils';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
|
||||
const ModalWrapperForAlfredV2: React.FC<IModalWrapper> = ({ children, ...props }) => {
|
||||
const { onRequestClose, onShow, visible } = props;
|
||||
const modalRef = React.useRef<View>(null);
|
||||
const lastSent = React.useRef(visible);
|
||||
const onRequestCloseHandler = (event: NativeSyntheticEvent<any>) => {
|
||||
if (typeof onRequestClose === 'function') onRequestClose(event);
|
||||
clearBottomSheet();
|
||||
};
|
||||
const onShowHandler = (event: NativeSyntheticEvent<any>) => {
|
||||
if (typeof onShow === 'function') onShow(event);
|
||||
const nodeId = findNodeHandle(modalRef.current);
|
||||
lastSent.current = true;
|
||||
setBottomSheetView(nodeId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
transparent={false}
|
||||
onShow={onShowHandler}
|
||||
animationType="none"
|
||||
onRequestClose={onRequestCloseHandler}
|
||||
{...props}
|
||||
>
|
||||
<View
|
||||
ref={modalRef}
|
||||
collapsable={false}
|
||||
style={[GenericStyles.fill, GenericStyles.whiteBackground]}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalWrapperForAlfredV2;
|
||||
122
src/components/filters/FilterOptions.tsx
Normal file
122
src/components/filters/FilterOptions.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { View } from 'react-native';
|
||||
import React from 'react';
|
||||
import { IFilters, TFilterOptions } from './Filters';
|
||||
import RadioGroup from '../../../RN-UI-LIB/src/components/radio_button/RadioGroup';
|
||||
import RNRadioButton from '../../../RN-UI-LIB/src/components/radio_button/RadioButton';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import Checkbox from '../../../RN-UI-LIB/src/components/chechbox/Checkbox';
|
||||
|
||||
interface IFilterOptions {
|
||||
filters: IFilters;
|
||||
selectedFilter: string;
|
||||
selectedFilterOptions: TFilterOptions;
|
||||
setSelectedFilterOptions: React.Dispatch<React.SetStateAction<TFilterOptions>>;
|
||||
}
|
||||
|
||||
enum FilterTypes {
|
||||
RADIO_GROUP = 'RADIO_GROUP',
|
||||
CHECKBOX_GROUP = 'CHECKBOX_GROUP',
|
||||
}
|
||||
|
||||
const FilterOptions: React.FC<IFilterOptions> = ({
|
||||
filters,
|
||||
selectedFilter,
|
||||
selectedFilterOptions,
|
||||
setSelectedFilterOptions,
|
||||
}) => {
|
||||
if (!selectedFilter || !filters?.[selectedFilter]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleSingleOptionChange = (value: string) => {
|
||||
if (selectedFilter) {
|
||||
const selectedOption = !!selectedFilterOptions?.[selectedFilter]?.filters?.[value];
|
||||
const currentSelectedFilterCount =
|
||||
selectedFilterOptions?.[selectedFilter]?.selectedFilterCount || 0;
|
||||
if (selectedOption && currentSelectedFilterCount === 1) {
|
||||
// delete the selected filter if we are unchecking the only selected option
|
||||
setSelectedFilterOptions((prev) => {
|
||||
const updatedOptions = { ...prev };
|
||||
delete updatedOptions[selectedFilter];
|
||||
return updatedOptions;
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSelectedFilterOptions({
|
||||
...selectedFilterOptions,
|
||||
[selectedFilter]: {
|
||||
filters: {
|
||||
[value]: !selectedOption,
|
||||
},
|
||||
selectedFilterCount: selectedOption ? 0 : 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMultiOptionChange = (isChecked: boolean, values: string) => {
|
||||
if (selectedFilter) {
|
||||
const currentFilters = selectedFilterOptions?.[selectedFilter]?.filters || {};
|
||||
const currentSelectedFilterCount =
|
||||
selectedFilterOptions?.[selectedFilter]?.selectedFilterCount || 0;
|
||||
if (!isChecked && currentSelectedFilterCount === 1) {
|
||||
// delete the selected filter if we are unchecking the only selected option
|
||||
setSelectedFilterOptions((prev) => {
|
||||
const updatedOptions = { ...prev };
|
||||
delete updatedOptions[selectedFilter];
|
||||
return updatedOptions;
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSelectedFilterOptions({
|
||||
...selectedFilterOptions,
|
||||
[selectedFilter]: {
|
||||
filters: {
|
||||
...currentFilters,
|
||||
[values]: isChecked,
|
||||
},
|
||||
selectedFilterCount: isChecked
|
||||
? currentSelectedFilterCount + 1
|
||||
: currentSelectedFilterCount - 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const filter = filters[selectedFilter];
|
||||
const selectionType = filter.filterType;
|
||||
const selectedOptions = selectedFilterOptions[selectedFilter]?.filters || {};
|
||||
|
||||
if (selectionType === FilterTypes.RADIO_GROUP) {
|
||||
const selectedOption = Object.keys(selectedOptions)[0];
|
||||
return (
|
||||
<RadioGroup
|
||||
value={selectedOptions[selectedOption] ? selectedOption : ''}
|
||||
onValueChange={(value) => handleSingleOptionChange(value)}
|
||||
orientation="vertical"
|
||||
>
|
||||
{filter.options.map((option) => (
|
||||
<RNRadioButton key={option.value} value={option.label || ''} id={option.value || ''} />
|
||||
))}
|
||||
</RadioGroup>
|
||||
);
|
||||
} else if (selectionType === FilterTypes.CHECKBOX_GROUP) {
|
||||
return (
|
||||
<>
|
||||
{filter.options.map((option) => (
|
||||
<View key={option.value} style={GenericStyles.mb2}>
|
||||
<Checkbox
|
||||
checked={selectedOptions[option.value] || false}
|
||||
label={option.label || ''}
|
||||
onSelectionChange={(value) => handleMultiOptionChange(value, option.value)}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default FilterOptions;
|
||||
196
src/components/filters/Filters.tsx
Normal file
196
src/components/filters/Filters.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, ScrollView, StyleSheet, PixelRatio, Pressable } from 'react-native';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import Button from '../../../RN-UI-LIB/src/components/Button';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { _map } from '../../../RN-UI-LIB/src/utlis/common';
|
||||
import FilterOptions from './FilterOptions';
|
||||
import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader';
|
||||
|
||||
interface FilterOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface FilterData {
|
||||
filterType: string;
|
||||
displayText: string;
|
||||
options: FilterOption[];
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface IFilters {
|
||||
[key: string]: FilterData;
|
||||
}
|
||||
|
||||
interface IFilterOptions {
|
||||
filters: Record<string, boolean>;
|
||||
selectedFilterCount: number;
|
||||
}
|
||||
|
||||
export type TFilterOptions = Record<string, IFilterOptions | null>;
|
||||
|
||||
const Filters: React.FC<{
|
||||
header: string;
|
||||
filters: IFilters;
|
||||
defaultSelectedFilters: TFilterOptions;
|
||||
closeFilterModal: () => void;
|
||||
onFilterChange: (selectedFilters: TFilterOptions) => void;
|
||||
}> = ({ header, filters, defaultSelectedFilters, closeFilterModal, onFilterChange }) => {
|
||||
// Default select first filter
|
||||
const [selectedFilter, setSelectedFilter] = useState<string>(Object.keys(filters)[0]);
|
||||
const [selectedFilterOptions, setSelectedFilterOptions] =
|
||||
useState<TFilterOptions>(defaultSelectedFilters);
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultSelectedFilters) {
|
||||
setSelectedFilterOptions(defaultSelectedFilters);
|
||||
}
|
||||
}, [defaultSelectedFilters]);
|
||||
|
||||
const handleFilterChange = (filterName: string) => {
|
||||
setSelectedFilter(filterName);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
onFilterChange(selectedFilterOptions);
|
||||
closeFilterModal();
|
||||
};
|
||||
|
||||
const handleClearAll = () => {
|
||||
onFilterChange({});
|
||||
setSelectedFilterOptions({});
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[GenericStyles.fill, GenericStyles.whiteBackground]}>
|
||||
<NavigationHeader title={header} onBack={closeFilterModal} />
|
||||
<View style={[GenericStyles.row, GenericStyles.fill]}>
|
||||
<ScrollView style={styles.leftSection}>
|
||||
{/* @ts-expect-error */}
|
||||
{_map(filters, (filterKey) => {
|
||||
const filter = filters[filterKey];
|
||||
const filterName = filter.name;
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={filterName}
|
||||
onPress={() => handleFilterChange(filterName)}
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.spaceBetween,
|
||||
GenericStyles.ph12,
|
||||
{ paddingVertical: 8 },
|
||||
GenericStyles.br6,
|
||||
GenericStyles.mb8,
|
||||
GenericStyles.alignCenter,
|
||||
{
|
||||
backgroundColor:
|
||||
selectedFilter === filterName
|
||||
? COLORS.BACKGROUND.BLUE
|
||||
: COLORS.BACKGROUND.PRIMARY,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
GenericStyles.fill,
|
||||
{
|
||||
color: selectedFilter === filterName ? COLORS.TEXT.BLUE : COLORS.TEXT.BLACK,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{filter.displayText}
|
||||
</Text>
|
||||
{selectedFilterOptions[filterName]?.selectedFilterCount ? (
|
||||
<View
|
||||
style={[
|
||||
styles.filterCountContainer,
|
||||
selectedFilter === filterName
|
||||
? styles.filterCountSelected
|
||||
: styles.filterCount,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[selectedFilter === filterName && GenericStyles.whiteText]}
|
||||
bold
|
||||
dark
|
||||
small
|
||||
>
|
||||
{selectedFilterOptions[filterName]?.selectedFilterCount}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.filterCountContainer} />
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
<ScrollView style={styles.rightSection}>
|
||||
<FilterOptions
|
||||
filters={filters}
|
||||
selectedFilter={selectedFilter}
|
||||
selectedFilterOptions={selectedFilterOptions}
|
||||
setSelectedFilterOptions={setSelectedFilterOptions}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<View style={GenericStyles.row}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
title="Clear all"
|
||||
onPress={handleClearAll}
|
||||
style={GenericStyles.fill}
|
||||
buttonStyle={styles.btn}
|
||||
/>
|
||||
<Button
|
||||
title="Apply"
|
||||
onPress={handleSubmit}
|
||||
style={GenericStyles.fill}
|
||||
buttonStyle={styles.btn}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
leftSection: {
|
||||
width: '47%',
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: COLORS.BORDER.PRIMARY,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
rightSection: {
|
||||
width: '53%',
|
||||
paddingVertical: 16,
|
||||
},
|
||||
btn: {
|
||||
margin: 0,
|
||||
borderRadius: 0,
|
||||
borderBottomWidth: 0,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: COLORS.BORDER.PRIMARY,
|
||||
},
|
||||
filterCountContainer: {
|
||||
height: PixelRatio.roundToNearestPixel(25),
|
||||
width: PixelRatio.roundToNearestPixel(25),
|
||||
},
|
||||
filterCount: {
|
||||
backgroundColor: COLORS.BACKGROUND.SILVER,
|
||||
borderColor: COLORS.BORDER.PRIMARY,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
alignItems: 'center',
|
||||
},
|
||||
filterCountSelected: {
|
||||
backgroundColor: COLORS.TEXT.BLUE,
|
||||
borderRadius: 20,
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default Filters;
|
||||
@@ -29,6 +29,10 @@ export const alfredSetUserId = (userId: string) => DeviceUtilsModule.setUserId(u
|
||||
export const sendBottomSheetOpenSignal = (e: boolean) =>
|
||||
DeviceUtilsModule.sendBottomSheetOpenSignal(e);
|
||||
|
||||
export const setBottomSheetView = (id: number | null) => DeviceUtilsModule.setBottomSheetView(id);
|
||||
|
||||
export const clearBottomSheet = () => DeviceUtilsModule.clearBottomSheet();
|
||||
|
||||
export const alfredSetEmailId = (emailId: string) => DeviceUtilsModule.setEmailId(emailId);
|
||||
|
||||
// sends feedback data to whatsapp.
|
||||
|
||||
@@ -79,7 +79,7 @@ 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] = '/v3/collection-cases/unified-details/{loanAccountNumber}';
|
||||
API_URLS[ApiKeys.UNGROUPED_ADDRESSES] = '/addresses/ungrouped-v2/{loanAccountNumber}';
|
||||
API_URLS[ApiKeys.PAST_FEEDBACK] = '/feedback';
|
||||
API_URLS[ApiKeys.PAST_FEEDBACK] = '/feedback/filters';
|
||||
API_URLS[ApiKeys.PAST_FEEDBACK_ON_ADDRESSES] = '/feedback/v2';
|
||||
API_URLS[ApiKeys.NOTIFICATIONS] = '/notification/fetch';
|
||||
API_URLS[ApiKeys.NOTIFICATION_ACTION] = '/notification/action';
|
||||
|
||||
@@ -368,10 +368,3 @@ export function getDistanceFromLatLonInKm(
|
||||
const distance = 2 * Math.atan2(Math.sqrt(intermediateResult), Math.sqrt(1 - intermediateResult));
|
||||
return EARTH_RADIUS * distance;
|
||||
}
|
||||
|
||||
export function insertCommasinAmount(amount: number | undefined) {
|
||||
const reversedAmount = amount?.toString().split('').reverse().join('');
|
||||
const groups = reversedAmount?.match(/.{1,3}/g);
|
||||
const result = groups?.join(',').split('').reverse().join('');
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ToastMessages } from '../screens/allCases/constants';
|
||||
import { setForceUninstallData } from '../reducer/metadataSlice';
|
||||
import { logError } from '../components/utlis/errorUtils';
|
||||
import { GenericFunctionArgs } from '../common/GenericTypes';
|
||||
import { setFeedbackFilterTemplate } from '@reducers/feedbackFiltersSlice';
|
||||
|
||||
export interface CaseUpdates {
|
||||
updateType: string;
|
||||
@@ -53,6 +54,7 @@ const useFirestoreUpdates = () => {
|
||||
let filterUnsubscribe: GenericFunctionArgs;
|
||||
let forceUninstallUnsubscribe: GenericFunctionArgs;
|
||||
let lockUnsubscribe: GenericFunctionArgs;
|
||||
let feedbackFiltersUnsubscribe: GenericFunctionArgs;
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -154,6 +156,13 @@ const useFirestoreUpdates = () => {
|
||||
lockData && dispatch(setLockData(lockData));
|
||||
};
|
||||
|
||||
const handleFeedbackFilters = (
|
||||
snapshot: FirebaseFirestoreTypes.DocumentSnapshot<FirebaseFirestoreTypes.DocumentData>
|
||||
) => {
|
||||
const feedbackFilters = snapshot.data();
|
||||
dispatch(setFeedbackFilterTemplate(feedbackFilters));
|
||||
};
|
||||
|
||||
const handleError = (err: any, collectionPath?: string) => {
|
||||
const errMsg = `Error while fetching fireStore snapshot: referenceId: ${user?.referenceId} collectionPath: ${collectionPath}`;
|
||||
logError(err as Error, errMsg);
|
||||
@@ -217,9 +226,15 @@ const useFirestoreUpdates = () => {
|
||||
refId = selectedAgent?.referenceId;
|
||||
}
|
||||
const collectionPath = `filters/${refId}`;
|
||||
|
||||
return subscribeToDoc(handleFilterUpdate, collectionPath);
|
||||
};
|
||||
|
||||
const subscribeToFeedbackFilters = () => {
|
||||
const feedbackFiltersPath = `feedback-filters/v1`;
|
||||
return subscribeToDoc(handleFeedbackFilters, feedbackFiltersPath);
|
||||
};
|
||||
|
||||
const subscribeToUserConfig = () => {
|
||||
const collectionPath = `config/${user?.referenceId}`;
|
||||
return subscribeToDoc(handleConfigUpdate, collectionPath);
|
||||
@@ -241,6 +256,7 @@ const useFirestoreUpdates = () => {
|
||||
avTemplateUnSubscriber = subscribeToAvTemplate();
|
||||
collectionTemplateUnsubscribe = subscribeToCollectionTemplate();
|
||||
lockUnsubscribe = subscribeToLocks();
|
||||
feedbackFiltersUnsubscribe = subscribeToFeedbackFilters();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -264,6 +280,7 @@ const useFirestoreUpdates = () => {
|
||||
avTemplateUnSubscriber && avTemplateUnSubscriber();
|
||||
collectionTemplateUnsubscribe && collectionTemplateUnsubscribe();
|
||||
lockUnsubscribe && lockUnsubscribe();
|
||||
feedbackFiltersUnsubscribe && feedbackFiltersUnsubscribe();
|
||||
};
|
||||
}, [isLoggedIn, user?.referenceId]);
|
||||
|
||||
|
||||
33
src/reducer/feedbackFiltersSlice.ts
Normal file
33
src/reducer/feedbackFiltersSlice.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { TFilterOptions } from '@components/filters/Filters';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { IFilter } from '@screens/allCases/interface';
|
||||
|
||||
interface IFeedbackFiltersSlice {
|
||||
feedbackFiltersTemplate: IFilter | null;
|
||||
selectedCaseFilters: Record<string, TFilterOptions>;
|
||||
}
|
||||
|
||||
const initialState: IFeedbackFiltersSlice = {
|
||||
selectedCaseFilters: {},
|
||||
feedbackFiltersTemplate: null,
|
||||
};
|
||||
|
||||
const FeedbackFiltersSlice = createSlice({
|
||||
name: 'feedbackFilters',
|
||||
initialState,
|
||||
reducers: {
|
||||
setFeedbackFilterTemplate: (state, action) => {
|
||||
if (action.payload) {
|
||||
state.feedbackFiltersTemplate = action.payload;
|
||||
}
|
||||
},
|
||||
setFeedbackFilters: (state, action) => {
|
||||
const { loanAccountNumber, filters } = action.payload;
|
||||
state.selectedCaseFilters[loanAccountNumber] = filters;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setFeedbackFilterTemplate, setFeedbackFilters } = FeedbackFiltersSlice.actions;
|
||||
|
||||
export default FeedbackFiltersSlice.reducer;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { IFeedback } from '../types/feedback.types';
|
||||
import { TFilterOptions } from '../components/filters/Filters';
|
||||
|
||||
interface IFeedbackHistoryState {
|
||||
[loanAccountNumber: string]: {
|
||||
@@ -19,6 +20,7 @@ const FeedbackHistorySlice = createSlice({
|
||||
setFeedbackHistory: (state, action) => {
|
||||
const { loanAccountNumber, feedbacks, totalPages } = action.payload;
|
||||
state[loanAccountNumber] = {
|
||||
...(state[loanAccountNumber] || {}),
|
||||
data: feedbacks,
|
||||
timestamp: new Date().toISOString(),
|
||||
isLoading: false,
|
||||
@@ -32,7 +34,7 @@ const FeedbackHistorySlice = createSlice({
|
||||
const payloadData = action.payload;
|
||||
payloadData.loanAccountNumbers.forEach((loanAccNumber) => {
|
||||
state[loanAccNumber] = {
|
||||
...(state?.[loanAccNumber] || []),
|
||||
...(state?.[loanAccNumber] || {}),
|
||||
isLoading: payloadData.isLoading,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -56,6 +56,7 @@ import BottomSheetWrapper from '../../common/BottomSheetWrapper';
|
||||
import { toast } from '../../../RN-UI-LIB/src/components/toast';
|
||||
import { setFilteredListToast } from '../../reducer/allCasesSlice';
|
||||
import { getFilterCount, getSelectedFilters } from '../Dashboard/utils';
|
||||
import ModalWrapperForAlfredV2 from '@common/ModalWrapperForAlfredV2';
|
||||
|
||||
export const getItem = (item: Array<ICaseItem>, index: number) => item[index];
|
||||
export const ESTIMATED_ITEM_SIZE = 250; // Average height of List item
|
||||
@@ -354,7 +355,7 @@ const CasesList: React.FC<ICasesList> = ({
|
||||
<View style={GenericStyles.ph12}>{listEmptyComponent}</View>
|
||||
)}
|
||||
</View>
|
||||
<ModalWrapperForAlfred
|
||||
<ModalWrapperForAlfredV2
|
||||
animationType="slide"
|
||||
animated
|
||||
onRequestClose={() => {
|
||||
@@ -370,7 +371,7 @@ const CasesList: React.FC<ICasesList> = ({
|
||||
isVisitPlan={isVisitPlan}
|
||||
isAgentDashboard={isAgentDashboard}
|
||||
/>
|
||||
</ModalWrapperForAlfred>
|
||||
</ModalWrapperForAlfredV2>
|
||||
<BottomSheetWrapper
|
||||
HeaderNode={() => (
|
||||
<View style={[...row, GenericStyles.ph16]}>
|
||||
|
||||
@@ -128,14 +128,13 @@ const ProtectedRouter = () => {
|
||||
// Firestore listener hook
|
||||
useFirestoreUpdates();
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
// Watching Position for significant change
|
||||
CaptureGeolocation.watchLocation((location: DeviceLocation) =>
|
||||
dispatch(setDeviceGeolocation(location))
|
||||
dispatch(setDeviceGeolocation(location))
|
||||
);
|
||||
}, []);
|
||||
|
||||
|
||||
if (isLoading) return <FullScreenLoader loading={isLoading} />;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { RefreshControl, ScrollView, StyleSheet, View } from 'react-native';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Modal, RefreshControl, ScrollView, StyleSheet, View } from 'react-native';
|
||||
import Accordion from '../../../../RN-UI-LIB/src/components/accordian/Accordian';
|
||||
import NavigationHeader from '../../../../RN-UI-LIB/src/components/NavigationHeader';
|
||||
import Text from '../../../../RN-UI-LIB/src/components/Text';
|
||||
import { GenericStyles, SCREEN_HEIGHT, getShadowStyle } from '../../../../RN-UI-LIB/src/styles';
|
||||
import { COLORS } from '../../../../RN-UI-LIB/src/styles/colors';
|
||||
import { getPastFeedbacks, getPastFeedbacksOnAddresses } from '../../../action/feedbackActions';
|
||||
import {
|
||||
IFilterPayload,
|
||||
getPastFeedbacks,
|
||||
getPastFeedbacksOnAddresses,
|
||||
} from '../../../action/feedbackActions';
|
||||
import { GenericType } from '../../../common/GenericTypes';
|
||||
import { logError } from '../../../components/utlis/errorUtils';
|
||||
import { goBack } from '../../../components/utlis/navigationUtlis';
|
||||
@@ -25,6 +29,10 @@ import { setFeedbackHistoryLoading } from '../../../reducer/feedbackHistorySlice
|
||||
import SuspenseLoader from '../../../../RN-UI-LIB/src/components/suspense_loader/SuspenseLoader';
|
||||
import LineLoader from '../../../../RN-UI-LIB/src/components/suspense_loader/LineLoader';
|
||||
import NoPastFeedbackIcon from '../../../assets/icons/NoPastFeedbackIcon';
|
||||
import Button from '../../../../RN-UI-LIB/src/components/Button';
|
||||
import FilterIcon from '../../../assets/icons/FilterIcon';
|
||||
import Filters, { TFilterOptions } from '../../../components/filters/Filters';
|
||||
import { _map } from '../../../../RN-UI-LIB/src/utlis/common';
|
||||
import ChevronDown from '../../../assets/icons/ChevronDown';
|
||||
import ChevronUp from '../../../assets/icons/ChevronUp';
|
||||
|
||||
@@ -46,6 +54,21 @@ interface IFeedbackDetailContainer {
|
||||
};
|
||||
}
|
||||
|
||||
const getFiltersPayload = (selectedFilters: TFilterOptions) => {
|
||||
const payload: IFilterPayload = { filters: [] };
|
||||
_map(selectedFilters, (filterName: string) => {
|
||||
const filter = selectedFilters[filterName];
|
||||
if (!filter || !filter.filters) {
|
||||
return;
|
||||
}
|
||||
payload.filters.push({
|
||||
filterName,
|
||||
selectedValue: Object.keys(filter.filters),
|
||||
});
|
||||
});
|
||||
return payload;
|
||||
};
|
||||
|
||||
const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: routeParams }) => {
|
||||
const {
|
||||
params: {
|
||||
@@ -60,13 +83,17 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
|
||||
const isPastFeedbackOnAddress = addressText || addressReferenceIds?.length;
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
||||
const [feedbackFilters, setFeedbackFilters] = useState<TFilterOptions>({});
|
||||
const isOnline = useIsOnline();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { feedbackListFromCache, feedbackTotalPages } = useAppSelector((state: RootState) => ({
|
||||
feedbackListFromCache: state.feedbackHistory?.[loanAccountNumber as string]?.data || [],
|
||||
feedbackTotalPages: state.feedbackHistory?.[loanAccountNumber as string]?.totalPages || 0,
|
||||
}));
|
||||
const { feedbackListFromCache, feedbackTotalPages, feedbackFiltersTemplate } = useAppSelector(
|
||||
(state: RootState) => ({
|
||||
feedbackListFromCache: state.feedbackHistory?.[loanAccountNumber as string]?.data || [],
|
||||
feedbackTotalPages: state.feedbackHistory?.[loanAccountNumber as string]?.totalPages || 0,
|
||||
feedbackFiltersTemplate: state.feedbackFilters?.feedbackFiltersTemplate || {},
|
||||
})
|
||||
);
|
||||
|
||||
const [feedbackList, setFeedbackList] = useState<IFeedback[]>(
|
||||
!isPastFeedbackOnAddress ? feedbackListFromCache : []
|
||||
@@ -75,53 +102,65 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [dataSourceCord, setDataSourceCord] = useState(0);
|
||||
const [ref, setRef] = useState<GenericType>();
|
||||
const [showFilterModal, setShowFilterModal] = useState(false);
|
||||
|
||||
const fetchFeedbacks = useCallback(() => {
|
||||
const getPastFeedbackApiFn = isPastFeedbackOnAddress
|
||||
? getPastFeedbacksOnAddresses
|
||||
: getPastFeedbacks;
|
||||
const fetchFeedbacks = useCallback(
|
||||
(filtersPayload: IFilterPayload) => {
|
||||
const getPastFeedbackApiFn = isPastFeedbackOnAddress
|
||||
? getPastFeedbacksOnAddresses
|
||||
: getPastFeedbacks;
|
||||
|
||||
if (isPastFeedbackOnAddress) {
|
||||
dispatch(
|
||||
setFeedbackHistoryLoading({ loanAccountNumbers: [loanAccountNumber], isLoading: true })
|
||||
);
|
||||
}
|
||||
|
||||
getPastFeedbackApiFn({
|
||||
loan_account_number: loanAccountNumber,
|
||||
page_no: currentPage - 1,
|
||||
page_size: FEEDBACK_PER_PAGE,
|
||||
customerRecahble: false,
|
||||
addressReferenceIds,
|
||||
})
|
||||
.then((res: { data: IFeedback[]; totalPage: number } | GenericType) => {
|
||||
if (res?.data?.length) {
|
||||
setFeedbackList(res.data);
|
||||
setTotalPage(res.totalPage);
|
||||
return;
|
||||
}
|
||||
throw res;
|
||||
})
|
||||
.catch((err) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_NAVIGATION_PAGE_FAILED, {
|
||||
targetPageNumber: currentPage,
|
||||
lan: loanAccountNumber,
|
||||
if (isPastFeedbackOnAddress) {
|
||||
dispatch(
|
||||
setFeedbackHistoryLoading({ loanAccountNumbers: [loanAccountNumber], isLoading: true })
|
||||
);
|
||||
}
|
||||
getPastFeedbackApiFn(
|
||||
{
|
||||
loan_account_number: loanAccountNumber,
|
||||
page_no: currentPage - 1,
|
||||
page_size: FEEDBACK_PER_PAGE,
|
||||
customerRecahble: false,
|
||||
addressReferenceIds,
|
||||
},
|
||||
filtersPayload
|
||||
)
|
||||
.then((res: { data: IFeedback[]; totalPage: number } | GenericType) => {
|
||||
if (res?.data) {
|
||||
setFeedbackList(res.data || []);
|
||||
setTotalPage(res.totalPage || 0);
|
||||
return;
|
||||
}
|
||||
throw res;
|
||||
})
|
||||
.catch((err) => {
|
||||
addClickstreamEvent(
|
||||
CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_NAVIGATION_PAGE_FAILED,
|
||||
{
|
||||
targetPageNumber: currentPage,
|
||||
lan: loanAccountNumber,
|
||||
}
|
||||
);
|
||||
logError(err);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
logError(err);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [currentPage]);
|
||||
},
|
||||
[currentPage]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPastFeedbackOnAddress && currentPage == 1) {
|
||||
setFeedbackList(feedbackListFromCache);
|
||||
} else {
|
||||
setLoading(true);
|
||||
setFeedbackList([]);
|
||||
}
|
||||
fetchFeedbacks();
|
||||
setLoading(true);
|
||||
const filtersPayload = getFiltersPayload(feedbackFilters);
|
||||
fetchFeedbacks(filtersPayload);
|
||||
}, [currentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -139,9 +178,6 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [dataSourceCord, setDataSourceCord] = useState(0);
|
||||
const [ref, setRef] = useState<GenericType>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref || !dataSourceCord) {
|
||||
return;
|
||||
@@ -171,6 +207,19 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
|
||||
});
|
||||
};
|
||||
|
||||
const handleFilterChange = (filters: TFilterOptions) => {
|
||||
const filtersPayload = getFiltersPayload(filters);
|
||||
if (currentPage === 1) {
|
||||
fetchFeedbacks(filtersPayload);
|
||||
setLoading(true);
|
||||
} else {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
setFeedbackFilters(filters);
|
||||
};
|
||||
|
||||
const feedbackFilterCount = Object.keys(feedbackFilters)?.length;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<View style={[GenericStyles.fill, GenericStyles.whiteBackground]}>
|
||||
@@ -178,9 +227,43 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
|
||||
title={!isPastFeedbackOnAddress ? FEEDBACK_PAGE_TITLE : ADDRESS_FEEDBACK_PAGE_TITLE}
|
||||
onBack={goBack}
|
||||
/>
|
||||
{!isPastFeedbackOnAddress ? (
|
||||
<View style={[GenericStyles.mlAuto, GenericStyles.pv12, GenericStyles.ph16]}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
leftIcon={
|
||||
<View style={GenericStyles.mr12}>
|
||||
<FilterIcon
|
||||
fillColor={feedbackFilterCount ? COLORS.TEXT.BLUE : COLORS.BACKGROUND.LIGHT}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
style={getShadowStyle(2)}
|
||||
title="Filters"
|
||||
buttonStyle={[GenericStyles.ph12, { paddingVertical: 8 }]}
|
||||
onPress={() => setShowFilterModal(true)}
|
||||
/>
|
||||
{feedbackFilterCount ? (
|
||||
<View style={[styles.filterCount]}>
|
||||
<Text style={[GenericStyles.whiteText, { marginTop: -3 }]} small bold>
|
||||
{feedbackFilterCount}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<ScrollView
|
||||
refreshControl={<RefreshControl refreshing={loading} onRefresh={fetchFeedbacks} />}
|
||||
style={[GenericStyles.ph16, GenericStyles.mt16]}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={false}
|
||||
onRefresh={() => {
|
||||
const filtersPayload = getFiltersPayload(feedbackFilters);
|
||||
fetchFeedbacks(filtersPayload);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
style={[GenericStyles.ph16, GenericStyles.fill]}
|
||||
ref={(x) => setRef(x)}
|
||||
>
|
||||
<SuspenseLoader
|
||||
@@ -197,112 +280,131 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
|
||||
</>
|
||||
}
|
||||
>
|
||||
<View>
|
||||
{feedbackList?.length ? (
|
||||
<>
|
||||
{isPastFeedbackOnAddress ? (
|
||||
<View style={styles.addressCard}>
|
||||
<Text style={[styles.addressText, { color: COLORS.TEXT.LIGHT }]}>
|
||||
Feedback for
|
||||
</Text>
|
||||
<Text style={[styles.addressText, { color: COLORS.TEXT.DARK }]}>
|
||||
{addressText}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{feedbackList.map((feedback: IFeedback, index) => (
|
||||
<View
|
||||
key={index}
|
||||
onLayout={(event) => {
|
||||
const layout = event.nativeEvent.layout;
|
||||
if (!dataSourceCord && feedback.referenceId === activeFeedbackReferenceId) {
|
||||
setDataSourceCord(layout.y + SCROLL_LAYOUT_OFFSET);
|
||||
}
|
||||
{feedbackList?.length ? (
|
||||
<>
|
||||
{isPastFeedbackOnAddress ? (
|
||||
<View style={styles.addressCard}>
|
||||
<Text style={[styles.addressText, { color: COLORS.TEXT.LIGHT }]}>
|
||||
Feedback for
|
||||
</Text>
|
||||
<Text style={[styles.addressText, { color: COLORS.TEXT.DARK }]}>
|
||||
{addressText}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{feedbackList.map((feedback: IFeedback, index) => (
|
||||
<View
|
||||
key={index}
|
||||
onLayout={(event) => {
|
||||
const layout = event.nativeEvent.layout;
|
||||
if (!dataSourceCord && feedback.referenceId === activeFeedbackReferenceId) {
|
||||
setDataSourceCord(layout.y + SCROLL_LAYOUT_OFFSET);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Accordion
|
||||
accordionStyle={[
|
||||
GenericStyles.br8,
|
||||
getShadowStyle(4),
|
||||
GenericStyles.ph0,
|
||||
GenericStyles.overflowHidden,
|
||||
GenericStyles.ph16,
|
||||
styles.accordianPadding,
|
||||
]}
|
||||
isActive={feedback.referenceId === activeFeedbackReferenceId}
|
||||
touchableDelay={50}
|
||||
touchableOpacity={0.8}
|
||||
accordionHeader={
|
||||
<FeedbackDetailItem
|
||||
key={feedback.referenceId}
|
||||
feedbackItem={feedback}
|
||||
isExpanded={isExpanded}
|
||||
caseId={caseId}
|
||||
/>
|
||||
}
|
||||
customExpandUi={{
|
||||
whenCollapsed: <ChevronDown />,
|
||||
whenExpanded: <ChevronUp />,
|
||||
}}
|
||||
onExpanded={(value) => {
|
||||
setIsExpanded(value);
|
||||
}}
|
||||
>
|
||||
<Accordion
|
||||
accordionStyle={[
|
||||
GenericStyles.br8,
|
||||
getShadowStyle(4),
|
||||
GenericStyles.ph0,
|
||||
GenericStyles.overflowHidden,
|
||||
GenericStyles.ph16,
|
||||
styles.accordianPadding,
|
||||
]}
|
||||
isActive={feedback.referenceId === activeFeedbackReferenceId}
|
||||
touchableDelay={50}
|
||||
touchableOpacity={0.8}
|
||||
accordionHeader={
|
||||
<FeedbackDetailItem
|
||||
key={feedback.referenceId}
|
||||
feedbackItem={feedback}
|
||||
isExpanded={isExpanded}
|
||||
caseId={caseId}
|
||||
/>
|
||||
}
|
||||
customExpandUi={{
|
||||
whenCollapsed: <ChevronDown />,
|
||||
whenExpanded: <ChevronUp />,
|
||||
}}
|
||||
onExpanded={(value) => {
|
||||
setIsExpanded(value);
|
||||
}}
|
||||
>
|
||||
<FeedbackDetailAnswerContainer
|
||||
answerList={feedback.answerViews}
|
||||
activeFeedbackReferenceId={feedback.referenceId}
|
||||
loanAccountNumber={loanAccountNumber}
|
||||
/>
|
||||
</Accordion>
|
||||
</View>
|
||||
))}
|
||||
<Pagination
|
||||
onPageChange={handlePageChange}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPage}
|
||||
onNextPage={(page) =>
|
||||
handlePageChangeClickstream(
|
||||
page,
|
||||
CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_NEXT_PAGE_CLICKED
|
||||
)
|
||||
}
|
||||
onPrevPage={(page) =>
|
||||
handlePageChangeClickstream(
|
||||
page,
|
||||
CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_PREV_PAGE_CLICKED
|
||||
)
|
||||
}
|
||||
onFirstPage={(page) =>
|
||||
handlePageChangeClickstream(
|
||||
page,
|
||||
CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_FIRST_PAGE_CLICKED
|
||||
)
|
||||
}
|
||||
onLastPage={(page) =>
|
||||
handlePageChangeClickstream(
|
||||
page,
|
||||
CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_LAST_PAGE_CLICKED
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
styles.noFeedbackContainer,
|
||||
GenericStyles.centerAligned,
|
||||
GenericStyles.columnDirection,
|
||||
]}
|
||||
>
|
||||
<NoPastFeedbackIcon />
|
||||
<Text style={[styles.textContainer, styles.noFeedbackText]}>
|
||||
No previous feedback found
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
<FeedbackDetailAnswerContainer
|
||||
answerList={feedback.answerViews}
|
||||
activeFeedbackReferenceId={feedback.referenceId}
|
||||
loanAccountNumber={loanAccountNumber}
|
||||
/>
|
||||
</Accordion>
|
||||
</View>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
styles.noFeedbackContainer,
|
||||
GenericStyles.centerAligned,
|
||||
GenericStyles.columnDirection,
|
||||
GenericStyles.fill,
|
||||
]}
|
||||
>
|
||||
<NoPastFeedbackIcon />
|
||||
<Text style={[styles.textContainer, styles.noFeedbackText]}>
|
||||
No previous feedback found
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</SuspenseLoader>
|
||||
</ScrollView>
|
||||
{feedbackList?.length && totalPage > 1 && !loading ? (
|
||||
<Pagination
|
||||
onPageChange={handlePageChange}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPage}
|
||||
onNextPage={(page) =>
|
||||
handlePageChangeClickstream(
|
||||
page,
|
||||
CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_NEXT_PAGE_CLICKED
|
||||
)
|
||||
}
|
||||
onPrevPage={(page) =>
|
||||
handlePageChangeClickstream(
|
||||
page,
|
||||
CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_PREV_PAGE_CLICKED
|
||||
)
|
||||
}
|
||||
onFirstPage={(page) =>
|
||||
handlePageChangeClickstream(
|
||||
page,
|
||||
CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_FIRST_PAGE_CLICKED
|
||||
)
|
||||
}
|
||||
onLastPage={(page) =>
|
||||
handlePageChangeClickstream(
|
||||
page,
|
||||
CLICKSTREAM_EVENT_NAMES.FA_VIEW_PAST_FEEDBACK_LAST_PAGE_CLICKED
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
{!isPastFeedbackOnAddress ? (
|
||||
<Modal
|
||||
animationType="slide"
|
||||
animated
|
||||
onRequestClose={() => {
|
||||
setShowFilterModal((prev) => !prev);
|
||||
}}
|
||||
visible={showFilterModal}
|
||||
>
|
||||
<Filters
|
||||
header="Feedback Filters"
|
||||
defaultSelectedFilters={feedbackFilters}
|
||||
closeFilterModal={() => setShowFilterModal((prev) => !prev)}
|
||||
filters={feedbackFiltersTemplate}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
</Modal>
|
||||
) : null}
|
||||
</View>
|
||||
</Layout>
|
||||
);
|
||||
@@ -329,8 +431,7 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '500',
|
||||
},
|
||||
noFeedbackContainer: {
|
||||
height: SCREEN_HEIGHT - 56,
|
||||
backgroundColor: COLORS.BACKGROUND.PRIMARY,
|
||||
marginTop: '50%',
|
||||
},
|
||||
accordionExpandBtn: {
|
||||
fontSize: 13,
|
||||
@@ -338,6 +439,17 @@ const styles = StyleSheet.create({
|
||||
lineHeight: 20,
|
||||
color: COLORS.TEXT.BLUE,
|
||||
},
|
||||
filterCount: {
|
||||
backgroundColor: COLORS.TEXT.BLUE,
|
||||
width: 18,
|
||||
height: 18,
|
||||
borderRadius: 9,
|
||||
alignItems: 'center',
|
||||
position: 'absolute',
|
||||
opacity: 0.8,
|
||||
top: 7,
|
||||
right: 11,
|
||||
},
|
||||
accordianPadding: {
|
||||
paddingTop: 16,
|
||||
paddingBottom: 8,
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
} from '../../../../RN-UI-LIB/src/utlis/dates';
|
||||
import { CaseDetail, Address as IAddress, IGeolocation, VisitType } from '../interface';
|
||||
import {
|
||||
debounce,
|
||||
getGoogleMapUrl,
|
||||
insertCommasinAmount,
|
||||
sanitizeString,
|
||||
} from '../../../components/utlis/commonFunctions';
|
||||
import {
|
||||
@@ -33,6 +33,7 @@ import { useAppSelector } from '../../../hooks';
|
||||
import { toast } from '../../../../RN-UI-LIB/src/components/toast';
|
||||
import { ToastMessages } from '../../allCases/constants';
|
||||
import { sendFeedbackToWhatsapp } from '../../../components/utlis/DeviceUtils';
|
||||
import { getSanitizedCommaAmount } from '@rn-ui-lib/utils/amount';
|
||||
|
||||
interface IFeedbackDetailItem {
|
||||
feedbackItem: IFeedback;
|
||||
@@ -71,122 +72,133 @@ function getLocationLink(latitude: string, longitude: string): string {
|
||||
return link;
|
||||
}
|
||||
|
||||
const sendToWhatsappNative = (
|
||||
message: string,
|
||||
imageUrl: string,
|
||||
mimeType: string,
|
||||
caseDetails: CaseDetail,
|
||||
agentId: string
|
||||
) => {
|
||||
sendFeedbackToWhatsapp(message, imageUrl, mimeType)
|
||||
.then((res: boolean) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SHARE_SUCCESSFUL, {
|
||||
caseId: caseDetails?.id,
|
||||
agentId: agentId,
|
||||
});
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
if (err.message === '1') {
|
||||
toast({
|
||||
text1: ToastMessages.WHATSAPP_NOT_INSTALLED,
|
||||
type: 'error',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
text1: ToastMessages.WHATSAPP_FEEDBACK_SHARE_FAILURE,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const sendToWhatsapp = (feedbackItem: IFeedback, caseDetails: CaseDetail, agentId: string) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SHARE_FEEDBACK_CLICKED, {
|
||||
caseId: caseDetails?.id,
|
||||
agentId: agentId,
|
||||
});
|
||||
|
||||
var message = `*Visit Feedback* for ${sanitizeString(caseDetails?.customerName)}
|
||||
_${sanitizeString(dateFormat(new Date(feedbackItem?.createdAt), 'DD MMM, YYYY | HH:mm a.'))}_\n
|
||||
*LAN*: ${sanitizeString(caseDetails?.loanAccountNumber)}
|
||||
*DPD Bucket*: ${sanitizeString(caseDetails?.dpdBucket)}
|
||||
*EMI Amount*: ₹${insertCommasinAmount(caseDetails?.outstandingEmiDetails?.[0]?.emiAmount)}\n
|
||||
*Disposition*: ${sanitizeString(feedbackItem?.interactionStatus)}`;
|
||||
|
||||
const ptpDate = feedbackItem?.answerViews.filter((answer) => answer.questionName === 'PTP Date');
|
||||
if (ptpDate.length > 0) {
|
||||
message +=
|
||||
' for ' + sanitizeString(dateFormat(new Date(ptpDate[0].inputDate), 'DD MMM, YYYY')) + '\n\n';
|
||||
} else {
|
||||
message += '\n\n';
|
||||
}
|
||||
|
||||
message += '*Remarks*: ';
|
||||
const answerList = feedbackItem?.answerViews?.filter(
|
||||
(answer) => answer.questionName === 'Comments'
|
||||
);
|
||||
if (answerList.length > 0) {
|
||||
message += sanitizeString(answerList[0]?.inputText) + '\n\n';
|
||||
} else {
|
||||
message += 'N.A.\n\n';
|
||||
}
|
||||
|
||||
{
|
||||
feedbackItem?.metadata?.interactionLatitude &&
|
||||
feedbackItem?.metadata?.interactionLongitude &&
|
||||
FIELD_FEEDBACKS.includes(feedbackItem?.type)
|
||||
? (message +=
|
||||
'*Location of feedback*: ' +
|
||||
sanitizeString(
|
||||
getLocationLink(
|
||||
feedbackItem?.metadata?.interactionLatitude,
|
||||
feedbackItem?.metadata?.interactionLongitude
|
||||
)
|
||||
) +
|
||||
'\n\n')
|
||||
: null;
|
||||
}
|
||||
|
||||
const imagesList = feedbackItem?.answerViews.filter(
|
||||
(answer) => answer.questionTag === OPTION_TAG.IMAGE_UPLOAD
|
||||
);
|
||||
var imageUrl = '';
|
||||
const mimeType = 'image/*';
|
||||
if (imagesList.length > 0) {
|
||||
var imageUri = '';
|
||||
imageUri = imagesList[0]?.inputText ? imagesList[0].inputText : '';
|
||||
let imagePath = '';
|
||||
ReactNativeBlobUtil.config({
|
||||
fileCache: true,
|
||||
})
|
||||
.fetch('GET', imageUri)
|
||||
.then((resp: any) => {
|
||||
if (resp.info().status !== 200) {
|
||||
return '';
|
||||
} else {
|
||||
imagePath = resp.path();
|
||||
return resp.readFile('base64');
|
||||
}
|
||||
})
|
||||
.then((base64Data: any) => {
|
||||
imageUrl = base64Data;
|
||||
sendToWhatsappNative(message, imageUrl, mimeType, caseDetails, agentId);
|
||||
ReactNativeBlobUtil.fs.unlink(imagePath);
|
||||
});
|
||||
} else {
|
||||
sendToWhatsappNative(message, imageUrl, mimeType, caseDetails, agentId);
|
||||
}
|
||||
};
|
||||
|
||||
const FeedbackDetailItem = ({
|
||||
feedbackItem,
|
||||
isExpanded,
|
||||
caseId,
|
||||
hideAddress,
|
||||
}: IFeedbackDetailItem) => {
|
||||
const isGeolocation = feedbackItem?.source?.sourceType === VisitType.GEOLOCATION;
|
||||
const caseDetails = useAppSelector((state) => state.allCases.caseDetails[caseId]);
|
||||
const { agentId } = useAppSelector((state) => ({ agentId: state.user.user?.referenceId!! }));
|
||||
const [isWhastappSendLoading, setIsWhatsappSendLoading] = useState(false);
|
||||
const isGeolocation = feedbackItem?.source?.sourceType === VisitType.GEOLOCATION;
|
||||
|
||||
const sendToWhatsappNative = (
|
||||
message: string,
|
||||
imageUrl: string,
|
||||
mimeType: string,
|
||||
caseDetails: CaseDetail,
|
||||
agentId: string
|
||||
) => {
|
||||
setIsWhatsappSendLoading(true);
|
||||
sendFeedbackToWhatsapp(message, imageUrl, mimeType)
|
||||
.then((res: boolean) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SHARE_SUCCESSFUL, {
|
||||
caseId: caseDetails?.id,
|
||||
agentId: agentId,
|
||||
});
|
||||
setIsWhatsappSendLoading(false);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
setIsWhatsappSendLoading(false);
|
||||
if (err.message === '1') {
|
||||
toast({
|
||||
text1: ToastMessages.WHATSAPP_NOT_INSTALLED,
|
||||
type: 'error',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
text1: ToastMessages.WHATSAPP_FEEDBACK_SHARE_FAILURE,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const sendToWhatsapp = (feedbackItem: IFeedback, caseDetails: CaseDetail, agentId: string) => {
|
||||
setIsWhatsappSendLoading(true);
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_SHARE_FEEDBACK_CLICKED, {
|
||||
caseId: caseDetails?.id,
|
||||
agentId: agentId,
|
||||
});
|
||||
|
||||
let message = `*Visit Feedback* for ${sanitizeString(caseDetails?.customerName)}
|
||||
_${sanitizeString(dateFormat(new Date(feedbackItem?.createdAt), 'DD MMM, YYYY | HH:mm a.'))}_\n
|
||||
*LAN*: ${sanitizeString(caseDetails?.loanAccountNumber)}
|
||||
*DPD Bucket*: ${sanitizeString(caseDetails?.dpdBucket)}
|
||||
*EMI Amount*: ₹${getSanitizedCommaAmount(caseDetails?.currentOutstandingEmi)}\n
|
||||
*Disposition*: ${sanitizeString(feedbackItem?.interactionStatus)}`;
|
||||
|
||||
const ptpDate = feedbackItem?.answerViews.filter(
|
||||
(answer) => answer.questionName === 'PTP Date'
|
||||
);
|
||||
if (ptpDate.length > 0) {
|
||||
message +=
|
||||
' for ' +
|
||||
sanitizeString(dateFormat(new Date(ptpDate[0].inputDate), 'DD MMM, YYYY')) +
|
||||
'\n\n';
|
||||
} else {
|
||||
message += '\n\n';
|
||||
}
|
||||
|
||||
message += '*Remarks*: ';
|
||||
const answerList = feedbackItem?.answerViews?.filter(
|
||||
(answer) => answer.questionName === 'Comments'
|
||||
);
|
||||
if (answerList.length > 0) {
|
||||
message += sanitizeString(answerList[0]?.inputText) + '\n\n';
|
||||
} else {
|
||||
message += 'N.A.\n\n';
|
||||
}
|
||||
|
||||
{
|
||||
feedbackItem?.metadata?.interactionLatitude &&
|
||||
feedbackItem?.metadata?.interactionLongitude &&
|
||||
FIELD_FEEDBACKS.includes(feedbackItem?.type)
|
||||
? (message +=
|
||||
'*Location of feedback*: ' +
|
||||
sanitizeString(
|
||||
getLocationLink(
|
||||
feedbackItem?.metadata?.interactionLatitude,
|
||||
feedbackItem?.metadata?.interactionLongitude
|
||||
)
|
||||
) +
|
||||
'\n\n')
|
||||
: null;
|
||||
}
|
||||
|
||||
const imagesList = feedbackItem?.answerViews.filter(
|
||||
(answer) => answer.questionTag === OPTION_TAG.IMAGE_UPLOAD
|
||||
);
|
||||
let imageUrl = '';
|
||||
const mimeType = 'image/*';
|
||||
if (imagesList.length > 0) {
|
||||
let imageUri = '';
|
||||
imageUri = imagesList[0]?.inputText ? imagesList[0].inputText : '';
|
||||
let imagePath = '';
|
||||
ReactNativeBlobUtil.config({
|
||||
fileCache: true,
|
||||
})
|
||||
.fetch('GET', imageUri)
|
||||
.then((resp: any) => {
|
||||
if (resp.info().status !== 200) {
|
||||
return '';
|
||||
} else {
|
||||
imagePath = resp.path();
|
||||
return resp.readFile('base64');
|
||||
}
|
||||
})
|
||||
.then((base64Data: any) => {
|
||||
imageUrl = base64Data;
|
||||
sendToWhatsappNative(message, imageUrl, mimeType, caseDetails, agentId);
|
||||
ReactNativeBlobUtil.fs.unlink(imagePath);
|
||||
});
|
||||
} else {
|
||||
sendToWhatsappNative(message, imageUrl, mimeType, caseDetails, agentId);
|
||||
}
|
||||
};
|
||||
|
||||
const throttledSendToWhatsapp = React.useRef(debounce(sendToWhatsapp, 500));
|
||||
return (
|
||||
<View style={[styles.addressItem]}>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
|
||||
@@ -273,8 +285,11 @@ const FeedbackDetailItem = ({
|
||||
) : null}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() => sendToWhatsapp(feedbackItem, caseDetails, agentId)}
|
||||
onPress={() => {
|
||||
throttledSendToWhatsapp.current(feedbackItem, caseDetails, agentId);
|
||||
}}
|
||||
style={[GenericStyles.row, styles.BtnPadding]}
|
||||
disabled={isWhastappSendLoading}
|
||||
>
|
||||
<IconLabel
|
||||
text="Share"
|
||||
|
||||
@@ -32,9 +32,9 @@ import configSlice from '../reducer/configSlice';
|
||||
import profileSlice from '../reducer/profileSlice';
|
||||
import reporteesSlice from '../reducer/reporteesSlice';
|
||||
import blacklistedAppsInstalledSlice from '@reducers/blacklistedAppsInstalledSlice';
|
||||
import feedbackFiltersSlice from '@reducers/feedbackFiltersSlice';
|
||||
import agentPerformanceSlice from '../reducer/agentPerformanceSlice';
|
||||
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
case: caseReducer,
|
||||
loginInfo: loginSlice,
|
||||
@@ -56,6 +56,7 @@ const rootReducer = combineReducers({
|
||||
profile: profileSlice,
|
||||
reportees: reporteesSlice,
|
||||
blacklistAppsInstalled: blacklistedAppsInstalledSlice,
|
||||
feedbackFilters: feedbackFiltersSlice,
|
||||
agentPerformance: agentPerformanceSlice,
|
||||
});
|
||||
|
||||
@@ -78,6 +79,7 @@ const persistConfig = {
|
||||
'config',
|
||||
'profile',
|
||||
'foregroundService',
|
||||
'feedbackFilters',
|
||||
],
|
||||
blackList: ['case', 'filters', 'reportees', 'agentPerformance'],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user