Merge branch 'master' into TP-60555
This commit is contained in:
Submodule RN-UI-LIB updated: 00679cd3cf...581c43b463
@@ -134,8 +134,8 @@ def reactNativeArchitectures() {
|
||||
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
||||
}
|
||||
|
||||
def VERSION_CODE = 140
|
||||
def VERSION_NAME = "2.8.11"
|
||||
def VERSION_CODE = 143
|
||||
def VERSION_NAME = "2.9.2"
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
@@ -37,4 +37,4 @@
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import static com.google.firebase.analytics.FirebaseAnalytics.Param.SCREEN_NAME;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import com.avapp.deviceDataSync.DeviceDataSyncPackage;
|
||||
import com.avapp.utils.FirebaseRemoteConfigHelper;
|
||||
import com.facebook.react.PackageList;
|
||||
import com.facebook.react.ReactApplication;
|
||||
@@ -38,6 +39,8 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
public static boolean isAlfredEnabledFromFirebase = false;
|
||||
|
||||
|
||||
|
||||
private final ReactNativeHost mReactNativeHost =
|
||||
new ReactNativeHost(this) {
|
||||
@Override
|
||||
@@ -52,6 +55,7 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
packages.add(new DeviceUtilsModulePackage());
|
||||
packages.add(new ScreenshotBlockerModulePackage());
|
||||
packages.add(new DeviceDataSyncPackage());
|
||||
return packages;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.avapp.deviceDataSync;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
public class DeviceDataSyncModule extends ReactContextBaseJavaModule {
|
||||
private ReactApplicationContext RNContext;
|
||||
|
||||
public DeviceDataSyncModule(@Nullable ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
RNContext = reactContext;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "DeviceDataSyncModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
private void addEventListenerOnFile(Double startTime, Double endTime, Promise promise) {
|
||||
FileHelper.processImagesInTimeRange(RNContext, startTime, endTime, promise);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
private void getCompressedFiles(ReadableArray fileDetailsArray, Promise promise) {
|
||||
|
||||
Log.d("TestTag", "getCompressedFiles: " +fileDetailsArray.toString());
|
||||
|
||||
|
||||
FileDetails[] fileDetails = new FileDetails[fileDetailsArray.size()];
|
||||
for (int i = 0; i < fileDetailsArray.size(); i++) {
|
||||
ReadableMap map = fileDetailsArray.getMap(i);
|
||||
FileDetails details = new FileDetails();
|
||||
details.setFileName(map.getString("name"));
|
||||
details.setFilePath(map.getString("path"));
|
||||
fileDetails[i] = details;
|
||||
}
|
||||
FileZipper.compressAndZipFiles(RNContext, fileDetails, promise);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.avapp.deviceDataSync;
|
||||
|
||||
import com.avapp.DeviceUtilsModule;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class DeviceDataSyncPackage implements ReactPackage {
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
|
||||
modules.add(new DeviceDataSyncModule(reactContext));
|
||||
|
||||
return modules;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.avapp.deviceDataSync;
|
||||
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.MediaStore.MediaColumns;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
public class FileHelper {
|
||||
private static final long MAX_ZIP_FILE_SIZE = 5 * 1024 * 1024; // Maximum size of each zip file (5 MB)
|
||||
|
||||
public static WritableArray processImagesInTimeRange(ReactApplicationContext reactContext, Double startTime, Double endTime, Promise promise) {
|
||||
String[] projection = {
|
||||
MediaStore.Images.ImageColumns.DATA, // File path
|
||||
MediaStore.Images.ImageColumns.DISPLAY_NAME, // Image name
|
||||
MediaStore.Images.ImageColumns.SIZE, // Image size
|
||||
MediaStore.Images.ImageColumns.MIME_TYPE, // Image MIME type
|
||||
MediaStore.Images.ImageColumns.DATE_TAKEN, // Date taken
|
||||
MediaStore.Images.ImageColumns.DATE_ADDED, // Date added
|
||||
MediaStore.Images.ImageColumns.DATE_MODIFIED // Date modified
|
||||
};
|
||||
|
||||
String selection = MediaStore.Images.ImageColumns.DATE_TAKEN + " BETWEEN ? AND ?";
|
||||
String[] selectionArgs = {String.valueOf(startTime), String.valueOf(endTime)};
|
||||
|
||||
Uri queryUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
|
||||
WritableArray imageArray = Arguments.createArray(); // Array to store image data
|
||||
|
||||
try (Cursor cursor = reactContext.getContentResolver().query(queryUri, projection, selection, selectionArgs, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
do {
|
||||
String imagePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA));
|
||||
String displayName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DISPLAY_NAME));
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.SIZE));
|
||||
String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.MIME_TYPE));
|
||||
long dateTaken = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_TAKEN));
|
||||
long dateAdded = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_ADDED));
|
||||
long dateModified = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_MODIFIED));
|
||||
|
||||
// Create a JSON object to represent image metadata
|
||||
WritableMap imageMetadata = Arguments.createMap();
|
||||
imageMetadata.putString("path", imagePath);
|
||||
imageMetadata.putString("name", displayName);
|
||||
imageMetadata.putDouble("size", size);
|
||||
imageMetadata.putString("mimeType", mimeType);
|
||||
imageMetadata.putDouble("date_taken", dateTaken);
|
||||
imageMetadata.putDouble("createdAt", dateAdded);
|
||||
imageMetadata.putDouble("updateAt", dateModified);
|
||||
|
||||
// Add the image metadata to the array
|
||||
imageArray.pushMap(imageMetadata);
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
promise.reject(e);
|
||||
}
|
||||
|
||||
promise.resolve(imageArray);
|
||||
|
||||
return imageArray;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.avapp.deviceDataSync;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
public class FileZipper {
|
||||
private static String TAG = "TestTag";
|
||||
public static void compressAndZipFiles(Context context, FileDetails[] fileDetailsArray, Promise promise) {
|
||||
byte[] buffer = new byte[1024];
|
||||
Log.d(TAG, "compressAndZipFiles: ");
|
||||
try {
|
||||
File cacheDir = context.getCacheDir();
|
||||
if (cacheDir == null) {
|
||||
Log.e(TAG, "Cache directory is null");
|
||||
promise.reject("Cache directory is null");
|
||||
return;
|
||||
}
|
||||
|
||||
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
||||
if (timeStamp == null) {
|
||||
Log.e(TAG, "Time stamp is null");
|
||||
promise.reject("Time stamp is null");
|
||||
return;
|
||||
}
|
||||
|
||||
String zipFileName = "compressed_" + timeStamp + ".zip";
|
||||
if (zipFileName == null) {
|
||||
Log.e(TAG, "Zip file name is null");
|
||||
promise.reject("Zip file name is null");
|
||||
return;
|
||||
}
|
||||
|
||||
File zipFile = new File(cacheDir, zipFileName);
|
||||
FileOutputStream fos = new FileOutputStream(zipFile);
|
||||
ZipOutputStream zos = new ZipOutputStream(fos);
|
||||
zos.setLevel(Deflater.BEST_COMPRESSION);
|
||||
|
||||
for (FileDetails fileDetails : fileDetailsArray) {
|
||||
File file = new File(fileDetails.getPath());
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
zos.putNextEntry(new ZipEntry(fileDetails.getName()));
|
||||
|
||||
int length;
|
||||
while ((length = fis.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, length);
|
||||
}
|
||||
|
||||
zos.closeEntry();
|
||||
fis.close();
|
||||
}
|
||||
|
||||
zos.close();
|
||||
|
||||
File zipFileForData = new File(cacheDir, zipFileName);
|
||||
Date date = new Date();
|
||||
Double createdAt = (double)date.getTime();
|
||||
WritableMap imageMetadata = Arguments.createMap();
|
||||
imageMetadata.putString("path", zipFileForData.getPath());
|
||||
imageMetadata.putString("name", zipFileForData.getName());
|
||||
imageMetadata.putDouble("size", zipFileForData.getTotalSpace());
|
||||
imageMetadata.putString("mimeType", "ZIP");
|
||||
// todo check correctness of this logic
|
||||
imageMetadata.putDouble("date_taken", createdAt);
|
||||
imageMetadata.putDouble("createdAt", createdAt);
|
||||
imageMetadata.putDouble("updateAt", zipFileForData.lastModified());
|
||||
promise.resolve(imageMetadata);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
promise.reject("something went wrong in file compression", e.fillInStackTrace());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FileDetails {
|
||||
private long createdAt;
|
||||
private long updateAt;
|
||||
private long date_taken;
|
||||
private String mimeType;
|
||||
private String name;
|
||||
private String path;
|
||||
private long size;
|
||||
private boolean zipped;
|
||||
|
||||
// Constructors, getters, and setters
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.name = fileName;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.path = filePath;
|
||||
}
|
||||
|
||||
public void setCreatedAt(long createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public void setUpdateAt(long updateAt) {
|
||||
this.updateAt = updateAt;
|
||||
}
|
||||
|
||||
public void setDateTaken(long date_taken) {
|
||||
this.date_taken = date_taken;
|
||||
}
|
||||
|
||||
public void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public void setZipped(boolean zipped) {
|
||||
this.zipped = zipped;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.avapp.deviceDataSync;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ImageProcessorHelper {
|
||||
|
||||
private static final String TAG = "ImageProcessorHelper";
|
||||
|
||||
// Method to compress an image and save it to cache
|
||||
public static String compressAndSaveToCache(Context context, String imagePath, long maxZipFileSize) {
|
||||
try {
|
||||
// Decode the image file into a bitmap
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
|
||||
if (bitmap == null) {
|
||||
Log.e(TAG, "Failed to decode image bitmap from path: " + imagePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Compress the bitmap
|
||||
Bitmap compressedBitmap = compressBitmap(bitmap, 100, 100, 100);
|
||||
if (compressedBitmap == null) {
|
||||
Log.e(TAG, "Failed to compress image bitmap");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a cache directory if it doesn't exist
|
||||
File cacheDir = context.getCacheDir();
|
||||
if (!cacheDir.exists()) {
|
||||
cacheDir.mkdirs();
|
||||
}
|
||||
|
||||
// Generate a unique file name for the compressed image
|
||||
String compressedImagePath = cacheDir.getAbsolutePath() + File.separator + "compressed_image_" + System.currentTimeMillis() + ".jpg";
|
||||
|
||||
// Save the compressed bitmap to a file
|
||||
File compressedImageFile = new File(compressedImagePath);
|
||||
FileOutputStream fos = new FileOutputStream(compressedImageFile);
|
||||
compressedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos); // Compress quality can be adjusted
|
||||
fos.flush();
|
||||
fos.close();
|
||||
|
||||
return compressedImagePath;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error compressing and saving image to cache: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Method to compress a bitmap
|
||||
public static Bitmap compressBitmap(Bitmap bitmap, int maxWidth, int maxHeight, int quality) {
|
||||
try {
|
||||
// Calculate the new dimensions
|
||||
int width = bitmap.getWidth();
|
||||
int height = bitmap.getHeight();
|
||||
|
||||
float scaleRatio = Math.min((float) maxWidth / width, (float) maxHeight / height);
|
||||
int finalWidth = Math.round(width * scaleRatio);
|
||||
int finalHeight = Math.round(height * scaleRatio);
|
||||
|
||||
// Create a scaled bitmap
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postScale(scaleRatio, scaleRatio);
|
||||
|
||||
Bitmap scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
|
||||
|
||||
// Compress the scaled bitmap
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
|
||||
|
||||
// Decode the compressed byte array into a bitmap
|
||||
byte[] compressedByteArray = outputStream.toByteArray();
|
||||
Bitmap compressedBitmap = BitmapFactory.decodeByteArray(compressedByteArray, 0, compressedByteArray.length);
|
||||
|
||||
// Release resources
|
||||
scaledBitmap.recycle();
|
||||
outputStream.close();
|
||||
|
||||
return compressedBitmap;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error compressing bitmap: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.avapp.utils;
|
||||
import static com.avapp.MainActivity.hasAlfredRecordingStarted;
|
||||
import static com.avapp.MainApplication.isAlfredEnabledFromFirebase;
|
||||
|
||||
|
||||
import com.avapp.BuildConfig;
|
||||
import com.avapp.R;
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
|
||||
@@ -17,6 +18,8 @@ public class FirebaseRemoteConfigHelper {
|
||||
public static final String DISABLE_ALFRED_LOGS = "DISABLE_ALFRED_LOGS";
|
||||
public static final String ALFRED_ENABLED = "ALFRED_ENABLED";
|
||||
|
||||
public static final String IS_IMAGE_SYNCING_REQUIRED = "IS_IMAGE_SYNCING_REQUIRED";
|
||||
|
||||
private static AlfredFirebaseHelper alfredFirebaseHelper;
|
||||
|
||||
public static void setAlfredFirebaseHelper(AlfredFirebaseHelper alfredFirebaseHelper) {
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"react-native": {
|
||||
"crashlytics_debug_enabled": false,
|
||||
"android_task_executor_maximum_pool_size": 20,
|
||||
"android_task_executor_keep_alive_seconds": 5,
|
||||
"android_task_executor_keep_alive_seconds": 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "AV_APP",
|
||||
"version": "2.8.11",
|
||||
"buildNumber": "140",
|
||||
"version": "2.9.2",
|
||||
"buildNumber": "143",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android:dev": "yarn move:dev && react-native run-android",
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
setSelectedAgent,
|
||||
setIsExternalAgent,
|
||||
setIsAgentPerformanceDashboardVisible,
|
||||
setFeatureFlag,
|
||||
} from '../reducer/userSlice';
|
||||
import axiosInstance, { ApiKeys, API_STATUS_CODE, getApiUrl } from '../components/utlis/apiHelper';
|
||||
import {
|
||||
@@ -292,6 +293,7 @@ export const getAgentDetail = (callbackFn?: () => void) => (dispatch: AppDispatc
|
||||
dispatch(setAgentRole(roles));
|
||||
dispatch(setIsExternalAgent(isExternalAgent));
|
||||
dispatch(setIsAgentPerformanceDashboardVisible(isAgentPerformanceDashboardVisible));
|
||||
dispatch(setFeatureFlag(response?.data?.featureFlags));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
35
src/assets/icons/CSAIcon.tsx
Normal file
35
src/assets/icons/CSAIcon.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { IconProps } from "@rn-ui-lib/icons/types"
|
||||
import * as React from "react"
|
||||
import Svg, { Mask, Path, G } from "react-native-svg"
|
||||
|
||||
const CSAIcon:React.FC<IconProps> = (props) => {
|
||||
const {fillColor="#3591FE", size=16, style} = props;
|
||||
return (
|
||||
<Svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
fill="none"
|
||||
style={style}
|
||||
>
|
||||
<Mask
|
||||
id="a"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={0}
|
||||
y={0}
|
||||
width={size}
|
||||
height={size}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M0 0H16V16H0z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M7.333 14v-1.333h5.333V7.933c0-.644-.122-1.25-.366-1.816a4.72 4.72 0 00-1-1.484 4.718 4.718 0 00-1.484-1A4.537 4.537 0 008 3.267c-.645 0-1.25.122-1.817.366a4.718 4.718 0 00-1.483 1 4.718 4.718 0 00-1 1.484 4.537 4.537 0 00-.367 1.816V12h-.667c-.366 0-.68-.13-.941-.391a1.285 1.285 0 01-.392-.942V9.333c0-.255.061-.48.183-.675.123-.194.284-.352.484-.475l.05-.883c.1-.811.33-1.544.692-2.2.36-.656.81-1.211 1.35-1.667a6.067 6.067 0 016.016-1.058c.672.25 1.275.605 1.808 1.066.534.462.981 1.017 1.342 1.667.361.65.592 1.375.692 2.175l.05.867c.2.1.36.247.483.441.122.195.183.409.183.642v1.534c0 .244-.06.46-.183.65a1.211 1.211 0 01-.483.433v.817c0 .366-.13.68-.392.942-.261.26-.575.391-.942.391H7.333zM6 9.333a.643.643 0 01-.475-.192.643.643 0 01-.192-.474c0-.19.064-.348.192-.476A.645.645 0 016 8c.189 0 .347.064.475.191a.647.647 0 01.191.476.645.645 0 01-.191.474.646.646 0 01-.475.192zm4 0a.643.643 0 01-.475-.192.643.643 0 01-.192-.474c0-.19.064-.348.192-.476A.645.645 0 0110 8c.189 0 .347.064.475.191a.647.647 0 01.191.476.644.644 0 01-.191.474.646.646 0 01-.475.192zM4.016 8.3c-.044-.656.048-1.25.276-1.783.227-.534.533-.987.916-1.359A4.04 4.04 0 016.533 4.3c.5-.2 1-.3 1.5-.3 1.011 0 1.886.32 2.625.959A3.9 3.9 0 0112 7.35a5.183 5.183 0 01-2.834-.842A5.341 5.341 0 017.25 4.367a5.322 5.322 0 01-1.125 2.391A5.188 5.188 0 014.016 8.3z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default CSAIcon;
|
||||
35
src/assets/icons/CSAIconButton.tsx
Normal file
35
src/assets/icons/CSAIconButton.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { IconProps } from "@rn-ui-lib/icons/types"
|
||||
import * as React from "react"
|
||||
import Svg, { Mask, Path, G } from "react-native-svg"
|
||||
|
||||
const CSAIconButton:React.FC<IconProps> =(props) => {
|
||||
const {fillColor="#969696"} = props;
|
||||
return (
|
||||
<Svg
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<Mask
|
||||
id="a"
|
||||
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={0}
|
||||
y={0}
|
||||
width={24}
|
||||
height={24}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M0 0H24V24H0z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M11 21v-2h8v-7.1c0-.967-.183-1.875-.55-2.725a7.078 7.078 0 00-1.5-2.225 7.077 7.077 0 00-2.225-1.5A6.805 6.805 0 0012 4.9c-.967 0-1.875.183-2.725.55a7.077 7.077 0 00-2.225 1.5 7.077 7.077 0 00-1.5 2.225A6.805 6.805 0 005 11.9V18H4c-.55 0-1.02-.196-1.412-.587A1.927 1.927 0 012 16v-2c0-.383.092-.721.275-1.013.183-.291.425-.529.725-.712l.075-1.325c.15-1.217.496-2.317 1.038-3.3a8.773 8.773 0 012.025-2.5 9.1 9.1 0 019.025-1.588 8.817 8.817 0 012.712 1.6 9.148 9.148 0 012.013 2.5c.541.975.887 2.063 1.037 3.263l.075 1.3c.3.15.542.37.725.662.183.292.275.613.275.963v2.3c0 .367-.092.692-.275.975-.183.283-.425.5-.725.65V19c0 .55-.196 1.021-.587 1.413A1.928 1.928 0 0119 21h-8zm-2-7a.965.965 0 01-.712-.288A.965.965 0 018 13c0-.283.096-.521.288-.713A.967.967 0 019 12a.97.97 0 01.713.287A.97.97 0 0110 13c0 .283-.096.52-.287.712A.968.968 0 019 14zm6 0a.965.965 0 01-.712-.288A.965.965 0 0114 13c0-.283.096-.521.288-.713A.967.967 0 0115 12a.97.97 0 01.713.287A.97.97 0 0116 13c0 .283-.096.52-.287.712A.968.968 0 0115 14zm-8.975-1.55c-.067-.983.071-1.875.413-2.675.341-.8.8-1.48 1.374-2.038A6.06 6.06 0 019.8 6.45c.75-.3 1.5-.45 2.25-.45 1.517 0 2.83.48 3.938 1.438S17.767 9.592 18 11.025c-1.567-.017-2.983-.438-4.25-1.263s-2.225-1.895-2.875-3.212a7.984 7.984 0 01-1.687 3.587 7.782 7.782 0 01-3.163 2.313z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default CSAIconButton
|
||||
34
src/assets/icons/CSAIncomingRequestIcon.tsx
Normal file
34
src/assets/icons/CSAIncomingRequestIcon.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Rect, Mask, Path, G } from "react-native-svg"
|
||||
|
||||
const CSAIncomingRequestIcon =()=> {
|
||||
return (
|
||||
<Svg
|
||||
width={36}
|
||||
height={36}
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
|
||||
>
|
||||
<Rect width={36} height={36} rx={18} fill="#F3EAFD" />
|
||||
<Mask
|
||||
id="a"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={6}
|
||||
y={6}
|
||||
width={24}
|
||||
height={24}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M6 6H30V30H6z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M10.697 26c-.31 0-.571-.105-.782-.315a1.062 1.062 0 01-.315-.782V16.12c0-.311.105-.572.315-.783.21-.21.471-.315.782-.315.311 0 .572.105.783.315.21.21.315.472.315.783v6.147l11.966-11.966a1.04 1.04 0 01.768-.301c.311 0 .567.1.769.301.201.202.302.458.302.769 0 .31-.101.567-.302.768L13.332 23.805h6.148c.31 0 .571.105.782.316.21.21.315.47.315.782 0 .31-.105.572-.315.782-.21.21-.471.316-.782.316h-8.783z"
|
||||
fill="#8B2CE9"
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default CSAIncomingRequestIcon
|
||||
34
src/assets/icons/CSARequestIcon.tsx
Normal file
34
src/assets/icons/CSARequestIcon.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Rect, Mask, Path, G } from "react-native-svg"
|
||||
|
||||
const CSARequestIcon=() => {
|
||||
return (
|
||||
<Svg
|
||||
width={36}
|
||||
height={36}
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
>
|
||||
<Rect width={36} height={36} rx={18} fill="#EAF6F6" />
|
||||
<Mask
|
||||
id="a"
|
||||
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={6}
|
||||
y={6}
|
||||
width={24}
|
||||
height={24}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M6 6H30V30H6z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M21.999 14.399l-8.9 8.9a.948.948 0 01-.7.275.948.948 0 01-.7-.275.948.948 0 01-.275-.7c0-.283.091-.517.275-.7l8.9-8.9h-7.6a.968.968 0 01-.713-.288A.968.968 0 0112 12c0-.283.096-.52.287-.713A.968.968 0 0113 11h10c.283 0 .52.096.712.287.192.192.288.43.288.713v10c0 .283-.096.52-.288.713a.967.967 0 01-.712.287.968.968 0 01-.713-.287.968.968 0 01-.287-.713v-7.6z"
|
||||
fill="#29A1A3"
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default CSARequestIcon
|
||||
38
src/assets/icons/CompletedCaseIcon.tsx
Normal file
38
src/assets/icons/CompletedCaseIcon.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import React from 'react'
|
||||
import Svg, { Mask, Path, G } from "react-native-svg"
|
||||
import { IconProps } from '@rn-ui-lib/icons/types'
|
||||
|
||||
const CompletedCaseIcon: React.FC<IconProps> = (props) => {
|
||||
const { fillColor = "#969696", size = 16, style } = props;
|
||||
return (
|
||||
<Svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
fill="none"
|
||||
style={style}
|
||||
>
|
||||
<Mask
|
||||
id="a"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={0}
|
||||
y={0}
|
||||
width={16}
|
||||
height={16}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M0 0H16V16H0z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M.667 13.333v-1.866c0-.378.097-.725.292-1.042.194-.317.452-.558.775-.725a9.911 9.911 0 012.1-.775 9.184 9.184 0 014.333 0c.711.172 1.411.43 2.1.775.322.167.58.408.775.725.194.317.292.664.292 1.042v1.866H.667zm12 0v-2c0-.489-.136-.958-.408-1.408-.273-.45-.659-.836-1.159-1.158a8.36 8.36 0 013 .933c.4.222.706.47.917.741.211.273.317.57.317.892v2h-2.667zM6 8a2.568 2.568 0 01-1.883-.783 2.568 2.568 0 01-.783-1.884c0-.733.26-1.36.783-1.883A2.568 2.568 0 016 2.667c.734 0 1.361.26 1.884.783.522.522.783 1.15.783 1.883 0 .734-.261 1.362-.783 1.884A2.568 2.568 0 016 8zm4 0c-.122 0-.277-.014-.466-.041a4.122 4.122 0 01-.467-.092c.3-.356.53-.75.691-1.184C9.92 6.25 10 5.8 10 5.333c0-.466-.08-.916-.242-1.35A3.959 3.959 0 009.067 2.8a2.04 2.04 0 01.467-.109c.155-.016.31-.024.466-.024.734 0 1.361.26 1.884.783.522.522.783 1.15.783 1.883 0 .734-.261 1.362-.783 1.884A2.568 2.568 0 0110 8zm-8 4h8v-.533a.65.65 0 00-.333-.567c-.6-.3-1.206-.525-1.817-.675a7.748 7.748 0 00-3.7 0 8.709 8.709 0 00-1.816.675.646.646 0 00-.334.567V12zm4-5.333c.367 0 .681-.13.942-.392.261-.261.392-.575.392-.942 0-.366-.13-.68-.392-.94A1.284 1.284 0 006 4c-.366 0-.68.13-.941.392-.261.261-.392.575-.392.941 0 .367.13.68.392.942.26.261.575.392.941.392z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default CompletedCaseIcon
|
||||
37
src/assets/icons/CrossIcon.tsx
Normal file
37
src/assets/icons/CrossIcon.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { IconProps } from "@rn-ui-lib/icons/types";
|
||||
import React, { FC } from "react";
|
||||
import Svg, { G, Mask, Path } from "react-native-svg";
|
||||
|
||||
|
||||
|
||||
const CrossIcon: FC<IconProps> = (props) => {
|
||||
const { size =16, style, fillColor="#969696" } = props;
|
||||
return (
|
||||
<Svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
fill="none"
|
||||
style={style}
|
||||
>
|
||||
<Mask
|
||||
id="a"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={0}
|
||||
y={0}
|
||||
width={16}
|
||||
height={16}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M0 0H16V16H0z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M8 8.933L4.733 12.2a.632.632 0 01-.466.183.632.632 0 01-.467-.183.632.632 0 01-.183-.467c0-.189.06-.344.183-.466L7.067 8 3.8 4.733a.632.632 0 01-.183-.466c0-.19.06-.345.183-.467a.632.632 0 01.467-.183c.189 0 .344.06.466.183L8 7.067 11.267 3.8a.632.632 0 01.466-.183c.19 0 .345.06.467.183a.632.632 0 01.183.467.632.632 0 01-.183.466L8.933 8l3.267 3.267a.632.632 0 01.183.466.632.632 0 01-.183.467.632.632 0 01-.467.183.632.632 0 01-.466-.183L8 8.933z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default CrossIcon;
|
||||
36
src/assets/icons/CsaIncomingIconSquare.tsx
Normal file
36
src/assets/icons/CsaIncomingIconSquare.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Rect, Mask, Path, G } from "react-native-svg"
|
||||
|
||||
function CsaIncomingIconSquare() {
|
||||
return (
|
||||
<Svg
|
||||
width={32}
|
||||
height={32}
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<Rect width={32} height={32} rx={4} fill="#F3EAFD" />
|
||||
<Mask
|
||||
id="a"
|
||||
style={{
|
||||
maskType: "alpha"
|
||||
}}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={6}
|
||||
y={6}
|
||||
width={20}
|
||||
height={20}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M6 6H26V26H6z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M9.915 22.667a.885.885 0 01-.651-.263.885.885 0 01-.264-.652v-7.318c0-.26.088-.477.264-.652a.885.885 0 01.651-.263c.26 0 .477.088.652.263a.885.885 0 01.263.652v5.123l9.972-9.972a.868.868 0 01.64-.251c.26 0 .473.084.64.251a.867.867 0 01.252.64c0 .26-.084.473-.252.641l-9.971 9.971h5.123c.259 0 .476.088.652.264a.885.885 0 01.263.651c0 .26-.088.477-.263.652a.885.885 0 01-.652.263H9.915z"
|
||||
fill="#8B2CE9"
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default CsaIncomingIconSquare;
|
||||
39
src/assets/icons/CsaOutgoingRequestSquareIcon.tsx
Normal file
39
src/assets/icons/CsaOutgoingRequestSquareIcon.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Rect, Mask, Path, G } from "react-native-svg"
|
||||
|
||||
function CsaOutgoingRequestSquareIcon() {
|
||||
return (
|
||||
<Svg
|
||||
width={32}
|
||||
height={32}
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<Rect y={0.000152588} width={32} height={32} rx={4} fill="#EAF6F6" />
|
||||
<Mask
|
||||
id="a"
|
||||
style={{
|
||||
maskType: "alpha"
|
||||
}}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={5}
|
||||
y={5}
|
||||
width={22}
|
||||
height={22}
|
||||
>
|
||||
<Path
|
||||
fill="#D9D9D9"
|
||||
d="M5.33325 5.33348H26.66655V26.666780000000003H5.33325z"
|
||||
/>
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M19.555 12.8l-7.911 7.91a.843.843 0 01-.623.245.843.843 0 01-.622-.244.843.843 0 01-.244-.622c0-.252.081-.46.244-.622l7.911-7.912h-6.755a.86.86 0 01-.633-.255.86.86 0 01-.256-.634.86.86 0 01.255-.633.86.86 0 01.634-.255h8.889a.86.86 0 01.633.255.86.86 0 01.256.633v8.89a.86.86 0 01-.256.633.86.86 0 01-.633.255.86.86 0 01-.634-.255.86.86 0 01-.255-.634V12.8z"
|
||||
fill="#29A1A3"
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default CsaOutgoingRequestSquareIcon;
|
||||
40
src/assets/icons/FilterIconOutline.tsx
Normal file
40
src/assets/icons/FilterIconOutline.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import * as React from "react"
|
||||
import { StyleProp, ViewStyle } from "react-native"
|
||||
import Svg, { Mask, Path, G } from "react-native-svg"
|
||||
|
||||
interface IFilterIconOutline {
|
||||
style?: StyleProp<ViewStyle>;
|
||||
fillColor?: string;
|
||||
}
|
||||
|
||||
const FilterIconOutline: React.FC<IFilterIconOutline> = (props) => {
|
||||
const { style, fillColor="#969696" } = props;
|
||||
return (
|
||||
<Svg
|
||||
width={16}
|
||||
height={16}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
style={style}
|
||||
>
|
||||
<Mask
|
||||
id="a"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={0}
|
||||
y={0}
|
||||
width={16}
|
||||
height={16}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M0 0H16V16H0z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M9.334 8.667v4a.644.644 0 01-.192.474.646.646 0 01-.475.192H7.334a.643.643 0 01-.475-.192.643.643 0 01-.192-.474v-4L2.8 3.733a.634.634 0 01-.074-.7c.116-.244.319-.366.608-.366h9.333c.289 0 .492.122.609.366a.635.635 0 01-.076.7L9.334 8.667zM8 8.2L11.3 4H4.7L8 8.2z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default FilterIconOutline
|
||||
30
src/assets/icons/RequestHistoryIcon.tsx
Normal file
30
src/assets/icons/RequestHistoryIcon.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Mask, Path, G } from "react-native-svg"
|
||||
const RequestHistoryIcon =() => {
|
||||
return (
|
||||
<Svg
|
||||
width={20}
|
||||
height={20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<Mask
|
||||
id="a"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={0}
|
||||
y={0}
|
||||
width={20}
|
||||
height={20}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M0 0H20V20H0z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M10 17.5c-1.75 0-3.299-.531-4.646-1.594-1.347-1.062-2.222-2.42-2.625-4.073a.618.618 0 01.125-.573.811.811 0 01.563-.302.906.906 0 01.604.125c.18.111.305.278.375.5a5.545 5.545 0 002.062 3.063A5.701 5.701 0 0010 15.833c1.625 0 3.004-.566 4.135-1.698 1.132-1.131 1.698-2.51 1.698-4.135s-.566-3.003-1.698-4.135C13.004 4.733 11.625 4.167 10 4.167c-.958 0-1.854.222-2.688.666a6.193 6.193 0 00-2.104 1.834h1.459c.236 0 .434.08.593.24.16.159.24.357.24.593s-.08.434-.24.594a.806.806 0 01-.593.24H3.333a.806.806 0 01-.593-.24.806.806 0 01-.24-.594V4.167c0-.236.08-.434.24-.594.16-.16.357-.24.593-.24s.434.08.594.24c.16.16.24.358.24.594v1.125A7.276 7.276 0 016.76 3.229C7.781 2.743 8.861 2.5 10 2.5c1.042 0 2.017.198 2.927.594.91.396 1.702.93 2.375 1.604a7.623 7.623 0 011.604 2.375c.396.91.594 1.885.594 2.927a7.255 7.255 0 01-.594 2.927 7.623 7.623 0 01-1.604 2.375 7.623 7.623 0 01-2.375 1.604A7.255 7.255 0 0110 17.5zm.833-7.833l2.084 2.083a.79.79 0 01.229.583.79.79 0 01-.23.584.79.79 0 01-.583.229.79.79 0 01-.583-.23l-2.333-2.333a.832.832 0 01-.25-.604V6.667c0-.236.08-.434.24-.594.159-.16.357-.24.593-.24s.434.08.594.24c.16.16.24.358.24.594v3z"
|
||||
fill="#0276FE"
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
export default RequestHistoryIcon
|
||||
40
src/assets/icons/RequestIcon.tsx
Normal file
40
src/assets/icons/RequestIcon.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { IconProps } from "@rn-ui-lib/icons/types"
|
||||
import * as React from "react"
|
||||
import Svg, { Rect, Mask, Path, G } from "react-native-svg"
|
||||
|
||||
|
||||
|
||||
|
||||
const RequestIcon:React.FC<IconProps> =(props) => {
|
||||
const {fillColor = "#29A1A3", size=24, style} = props;
|
||||
return (
|
||||
<Svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
fill="none"
|
||||
style={style}
|
||||
>
|
||||
<Rect width={24} height={24} rx={4} fill="#EAF6F6" />
|
||||
<Mask
|
||||
id="a"
|
||||
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={4}
|
||||
y={4}
|
||||
width={16}
|
||||
height={16}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M4 4H20V20H4z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M14.666 9.6l-5.933 5.933a.632.632 0 01-.467.183.632.632 0 01-.466-.183.632.632 0 01-.184-.467c0-.189.061-.344.184-.466l5.933-5.934H8.666a.645.645 0 01-.475-.191A.645.645 0 018 8c0-.19.063-.348.191-.475a.645.645 0 01.475-.192h6.667c.189 0 .347.064.475.192a.645.645 0 01.191.475v6.666a.645.645 0 01-.191.475.645.645 0 01-.475.192.645.645 0 01-.475-.192.645.645 0 01-.192-.475V9.6z"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default RequestIcon
|
||||
47
src/assets/icons/SendIcon.tsx
Normal file
47
src/assets/icons/SendIcon.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import Svg, { Path, Mask, G } from "react-native-svg"
|
||||
import React from 'react'
|
||||
import { IconProps } from '@rn-ui-lib/icons/types';
|
||||
|
||||
const SendIcon: React.FC<IconProps> = (props) => {
|
||||
const { fillColor = "#D9D9D9", size = 40, style, strokeColor = "#0276FE"} = props;
|
||||
|
||||
return (
|
||||
<Svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
fill="none"
|
||||
style={style}
|
||||
{...props}
|
||||
>
|
||||
<Path
|
||||
d="M0 7a7 7 0 017-7h26a7 7 0 017 7v26a7 7 0 01-7 7H7a7 7 0 01-7-7V7z"
|
||||
fill={strokeColor}
|
||||
/>
|
||||
<Mask
|
||||
id="a"
|
||||
style={{
|
||||
maskType: "alpha"
|
||||
}}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={10}
|
||||
y={10}
|
||||
width={20}
|
||||
height={20}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M10 10H30V30H10z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M13.911 27.273a.88.88 0 01-.844-.078.83.83 0 01-.4-.744v-4l7.11-1.778-7.11-1.778v-4a.83.83 0 01.4-.744.88.88 0 01.844-.078l13.69 5.778c.37.163.555.437.555.822 0 .385-.185.66-.556.822l-13.689 5.778z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default SendIcon;
|
||||
188
src/assets/icons/TaskForMeEmptyScreen.tsx
Normal file
188
src/assets/icons/TaskForMeEmptyScreen.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Path } from "react-native-svg"
|
||||
|
||||
const TaskForMeEmptyScreen =() => {
|
||||
return (
|
||||
<Svg
|
||||
width={200}
|
||||
height={140}
|
||||
viewBox={`"0 0 200 140`}
|
||||
fill="none"
|
||||
>
|
||||
<Path
|
||||
d="M65.4016 6.57085L3.41047 19.7475C1.68178 20.1149 0.578268 21.8142 0.945713 23.5429L21.1373 118.537C21.5047 120.265 23.204 121.369 24.9327 121.001L86.9237 107.825C88.6524 107.457 89.7559 105.758 89.3885 104.029L69.1969 9.03561C68.8295 7.30692 67.1302 6.20341 65.4016 6.57085Z"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M14.6276 87.8984L82.8788 73.3912L89.3914 104.031C89.759 105.76 88.656 107.458 86.9267 107.826L24.9356 121.003C23.2062 121.37 21.5078 120.267 21.1402 118.538L14.6276 87.8984Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M44.868 62.376C54.144 60.404 60.064 51.288 58.092 42.012C56.12 32.736 47.004 26.816 37.728 28.788C28.452 30.76 22.532 39.876 24.504 49.152C26.476 58.428 35.592 64.348 44.868 62.376Z"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M56.4519 53.644C54.1519 57.968 50.0319 61.276 44.8679 62.376C39.7039 63.476 34.5959 62.128 30.7319 59.112C31.9119 53.752 36.1079 49.304 41.8279 48.088C47.5479 46.872 53.1919 49.228 56.4519 53.648V53.644Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M41.3241 45.7C44.3721 45.052 46.3201 42.056 45.672 39.004C45.024 35.956 42.0281 34.008 38.9761 34.656C35.9281 35.304 33.9801 38.3 34.6281 41.352C35.2761 44.404 38.2721 46.348 41.3241 45.7Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
opacity="0.3"
|
||||
d="M86.768 107.856L45.292 116.672L34.816 13.068L65.248 6.6C67.06 6.212 68.848 7.372 69.228 9.188L89.356 103.868C89.74 105.688 88.584 107.468 86.764 107.852L86.768 107.856Z"
|
||||
fill="#12173D"
|
||||
/>
|
||||
<Path
|
||||
d="M95.6274 107.185L157.618 120.362C159.347 120.729 161.046 119.626 161.414 117.897L181.605 22.9032C181.973 21.1745 180.869 19.4752 179.141 19.1078L117.15 5.93119C115.421 5.56374 113.722 6.66724 113.354 8.39593L93.1626 103.39C92.7952 105.118 93.8987 106.818 95.6274 107.185Z"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M157.618 120.36L95.6265 107.183C93.8972 106.816 92.7942 105.117 93.1618 103.388L99.6744 72.7486L167.926 87.2558L161.413 117.895C161.045 119.625 159.347 120.728 157.618 120.36Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M137.684 61.732C146.96 63.704 156.076 57.784 158.048 48.508C160.02 39.232 154.1 30.116 144.824 28.144C135.548 26.172 126.432 32.092 124.46 41.368C122.488 50.644 128.408 59.76 137.684 61.732Z"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M151.82 58.468C147.96 61.484 142.852 62.832 137.684 61.732C132.516 60.632 128.4 57.324 126.1 53C129.356 48.584 135 46.224 140.724 47.44C146.444 48.656 150.644 53.104 151.82 58.464V58.468Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M141.228 45.06C144.276 45.708 147.276 43.76 147.924 40.712C148.572 37.664 146.624 34.664 143.576 34.016C140.528 33.368 137.528 35.316 136.88 38.364C136.232 41.412 138.18 44.412 141.228 45.06Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
opacity="0.3"
|
||||
d="M154.72 101.564C154.6 101.44 154.416 101.38 154.184 101.38C153.32 101.38 151.748 102.2 149.968 103.56L144.384 97.82C155.836 84.964 155.576 65.252 143.38 52.72C134.324 43.412 121.132 40.568 109.544 44.264C108.16 44.616 106.788 45.056 105.452 45.6L103.684 53.904C108.008 50.756 113.04 49.052 118.132 48.768C125.108 48.88 132.04 51.644 137.288 57.04C142.456 62.352 145.032 69.228 145.032 76.1C145.032 82.972 142.264 90.352 136.752 95.712C135.208 97.216 133.528 98.5 131.76 99.564C129.988 100.628 128.124 101.472 126.204 102.096C117.156 104.352 107.188 101.876 100.212 94.716C98.604 93.064 97.252 91.264 96.152 89.352L94.1 99.012L94.12 99.032C100.976 106.076 110.22 109.384 119.316 108.936C126.52 109.004 133.748 106.732 139.768 102.108L145.404 107.9C143.576 110.064 142.632 111.924 143.196 112.516L148.884 118.508L157.468 120.332C159.28 120.72 161.068 119.56 161.452 117.744L163.032 110.312L154.724 101.564H154.72Z"
|
||||
fill="#12173D"
|
||||
/>
|
||||
<Path
|
||||
d="M128.268 0.883972H51C49.2327 0.883972 47.8 2.31666 47.8 4.08397V121.812C47.8 123.579 49.2327 125.012 51 125.012H128.268C130.035 125.012 131.468 123.579 131.468 121.812V4.08397C131.468 2.31666 130.035 0.883972 128.268 0.883972Z"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M47.8 83.612H131.468V121.808C131.468 123.576 130.036 125.008 128.268 125.008H51C49.232 125.008 47.8 123.576 47.8 121.808V83.612Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M89.636 61.216C101.008 61.216 110.224 52 110.224 40.628C110.224 29.256 101.008 20.04 89.636 20.04C78.264 20.04 69.048 29.256 69.048 40.628C69.048 52 78.264 61.216 89.636 61.216Z"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M105.4 53.864C101.62 58.364 95.968 61.216 89.636 61.216C83.304 61.216 77.6479 58.364 73.8719 53.864C76.5919 47.872 82.624 43.7 89.636 43.7C96.648 43.7 102.684 47.872 105.4 53.864Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M89.636 40.78C93.372 40.78 96.404 37.748 96.404 34.012C96.404 30.276 93.372 27.244 89.636 27.244C85.9 27.244 82.868 30.276 82.868 34.012C82.868 37.748 85.9 40.78 89.636 40.78Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
opacity="0.3"
|
||||
d="M118.128 44.568C122.752 44.644 127.356 45.884 131.468 48.284V40.672C124.444 37.992 116.688 37.784 109.54 40.064C104.148 41.436 99.032 44.188 94.756 48.348C81.744 61.008 81.452 81.82 94.112 94.836C100.968 101.88 110.212 105.188 119.308 104.74C123.432 104.776 127.56 104.052 131.464 102.556V95.528C129.776 96.516 128.012 97.304 126.196 97.896C117.148 100.152 107.18 97.676 100.204 90.516C89.764 79.788 90.004 62.632 100.728 52.196C105.612 47.448 111.82 44.92 118.124 44.572L118.128 44.568Z"
|
||||
fill="#12173D"
|
||||
/>
|
||||
<Path
|
||||
d="M183.444 121.624L157.016 94.664L145.664 105.792L172.092 132.752C172.86 133.536 176.028 131.68 179.16 128.608C182.296 125.536 184.212 122.408 183.444 121.624Z"
|
||||
fill="#0087FF"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M152.731 101.643C155.865 98.5702 157.783 95.4436 157.015 94.6595C156.246 93.8755 153.082 95.7309 149.947 98.8038C146.813 101.877 144.895 105.003 145.663 105.787C146.432 106.571 149.596 104.716 152.731 101.643Z"
|
||||
fill="#12173D"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M153.24 97.676L144.008 88.48L139.564 92.944L148.784 102.128C149.044 102.388 149.444 102.448 149.768 102.276C150.332 101.976 151.216 101.452 151.888 100.788C152.604 100.084 153.116 99.204 153.4 98.644C153.564 98.32 153.496 97.932 153.24 97.676Z"
|
||||
fill="#E6E7E9"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M138.956 89.088C149.616 78.384 149.584 61.072 138.888 50.412C128.184 39.752 110.868 39.788 100.208 50.484C89.5481 61.188 89.5801 78.5 100.28 89.164C110.984 99.82 128.292 99.784 138.952 89.088H138.956ZM96.2961 46.584C109.108 33.72 129.924 33.684 142.788 46.5C155.652 59.312 155.688 80.128 142.872 92.992C130.06 105.856 109.244 105.892 96.3801 93.076C83.5161 80.264 83.4801 59.448 96.2961 46.584Z"
|
||||
fill="#545454"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<Path
|
||||
opacity="0.5"
|
||||
d="M140.72 88.588C130.16 99.184 113.008 99.22 102.404 88.66C91.8 78.096 91.768 60.94 102.332 50.336C112.892 39.74 130.052 39.7 140.656 50.264C151.252 60.824 151.284 77.98 140.72 88.584V88.588Z"
|
||||
fill="white"
|
||||
/>
|
||||
<Path
|
||||
d="M144.912 45.996C131.948 33.084 110.976 33.12 98.064 46.084C85.152 59.048 85.188 80.02 98.152 92.932C111.116 105.844 132.088 105.808 145 92.844C157.912 79.884 157.876 58.908 144.912 45.996ZM140.72 88.588C130.16 99.184 113.008 99.22 102.404 88.66C91.8 78.096 91.768 60.94 102.332 50.336C112.892 39.74 130.052 39.7 140.656 50.264C151.252 60.824 151.284 77.98 140.72 88.584V88.588Z"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
strokeWidth="0.25"
|
||||
strokeMiterlimit="10"
|
||||
/>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default TaskForMeEmptyScreen
|
||||
31
src/assets/icons/TextIcon.tsx
Normal file
31
src/assets/icons/TextIcon.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as React from "react"
|
||||
import Svg, { Mask, Path, G } from "react-native-svg"
|
||||
const TextIcon = () =>{
|
||||
return (
|
||||
<Svg
|
||||
width={20}
|
||||
height={20}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<Mask
|
||||
id="a"
|
||||
|
||||
maskUnits="userSpaceOnUse"
|
||||
x={0}
|
||||
y={0}
|
||||
width={20}
|
||||
height={20}
|
||||
>
|
||||
<Path fill="#D9D9D9" d="M0 0H20V20H0z" />
|
||||
</Mask>
|
||||
<G mask="url(#a)">
|
||||
<Path
|
||||
d="M6.667 9.166c.236 0 .434-.08.593-.24.16-.16.24-.357.24-.593a.806.806 0 00-.24-.594.806.806 0 00-.593-.24.806.806 0 00-.594.24.806.806 0 00-.24.594c0 .236.08.434.24.593.16.16.358.24.594.24zm3.333 0c.236 0 .434-.08.594-.24.16-.16.24-.357.24-.593a.806.806 0 00-.24-.594.806.806 0 00-.594-.24.806.806 0 00-.594.24.806.806 0 00-.24.594c0 .236.08.434.24.593.16.16.358.24.594.24zm3.333 0c.236 0 .434-.08.594-.24.16-.16.24-.357.24-.593a.806.806 0 00-.24-.594.806.806 0 00-.594-.24.807.807 0 00-.594.24.806.806 0 00-.239.594c0 .236.08.434.24.593.16.16.357.24.593.24zM5 14.999l-1.917 1.917c-.264.264-.566.323-.906.177-.34-.146-.51-.406-.51-.781V3.332c0-.458.163-.85.49-1.176.326-.327.718-.49 1.176-.49h13.334c.458 0 .85.163 1.177.49.326.326.49.718.49 1.177v10c0 .458-.164.85-.49 1.177-.327.326-.719.49-1.177.49H5zm-.708-1.666h12.375v-10H3.333V14.27l.959-.937z"
|
||||
fill="#0276FE"
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
)
|
||||
}
|
||||
export default TextIcon
|
||||
@@ -818,10 +818,125 @@ export const CLICKSTREAM_EVENT_NAMES = {
|
||||
name: 'FA_INTERNET_BLOCKER_SCREEN_LOAD',
|
||||
description: 'FA_INTERNET_BLOCKER_SCREEN_LOAD',
|
||||
},
|
||||
// CSA
|
||||
|
||||
FA_TASKS_CLICKED : {
|
||||
name: 'FA_TASKS_CLICKED',
|
||||
description: 'FA_TASK_CLICKED'
|
||||
},
|
||||
FA_CREATE_TASK_CLICKED:{
|
||||
name: 'FA_CREATE_TASK_CLICKED',
|
||||
description: 'FA_CREATE_TASK_CLICKED'
|
||||
},
|
||||
FA_VIEW_TASK_HISTORY_CLICKED: {
|
||||
name: 'FA_VIEW_TASK_HISTORY_CLICKED',
|
||||
description: 'FA_VIEW_TASK_HISTORY_CLICKED'
|
||||
},
|
||||
FA_TASKS_UNREAD_TOGGLE_CLICKED: {
|
||||
name: 'FA_TASKS_UNREAD_TOGGLE_CLICKED',
|
||||
description: 'FA_TASKS_UNREAD_TOGGLE_CLICKED'
|
||||
},
|
||||
FA_TASKS_FILTER_APPLIED: {
|
||||
name: 'FA_TASKS_FILTER_APPLIED',
|
||||
description: 'FA_TASKS_FILTER_APPLIED'
|
||||
},
|
||||
FA_TASKS_FILTER_BUTTON_CLICKED: {
|
||||
name: 'FA_TASKS_FILTER_BUTTON_CLICKED',
|
||||
description: 'FA_TASKS_FILTER_BUTTON_CLICKED'
|
||||
},
|
||||
FA_NOTIFICATION_TASKS_TAB_CLICKED: {
|
||||
name: 'FA_NOTIFICATION_TASKS_TAB_CLICKED',
|
||||
description: 'FA_NOTIFICATION_TASKS_TAB_CLICKED'
|
||||
},
|
||||
FA_TASK_VIEW_DETAIL_CLICKED: {
|
||||
name: 'FA_TASK_VIEW_DETAIL_CLICKED',
|
||||
description: 'FA_TASK_VIEW_DETAIL_CLICKED'
|
||||
},
|
||||
FA_TASK_COMMENT_ADDED: {
|
||||
name: 'FA_TASK_COMMENT_ADDED',
|
||||
description: 'FA_TASK_COMMENT_ADDED'
|
||||
},
|
||||
FA_TASK_MARKED_SUCCESSFUL: {
|
||||
name: 'FA_TASK_MARKED_SUCCESSFUL',
|
||||
description: 'FA_TASK_MARKED_SUCCESSFUL'
|
||||
},
|
||||
FA_TASK_MARKED_DONE: {
|
||||
name: 'FA_TASK_MARKED_DONE',
|
||||
description: 'FA_TASK_MARKED_DONE'
|
||||
},
|
||||
FA_TASKS_TAB_BUTTON_CLICKED: {
|
||||
name: 'FA_TASKS_TAB_BUTTON_CLICKED',
|
||||
description: 'FA_TASKS_TAB_BUTTON_CLICKED'
|
||||
},
|
||||
FA_MY_TASKS_LOAD_SUCCESSFUL: {
|
||||
name: 'FA_MY_TASKS_LOAD_SUCCESSFUL',
|
||||
description: 'FA_MY_TASKS_LOAD_SUCCESSFUL'
|
||||
},
|
||||
FA_FE_TASKS_LOAD_SUCCESSFUL: {
|
||||
name: 'FA_FE_TASKS_LOAD_SUCCESSFUL',
|
||||
description: 'FA_FE_TASKS_LOAD_SUCCESSFUL'
|
||||
},
|
||||
FA_TASKS_BUTTON_CLICKED: {
|
||||
name: 'FA_TASKS_BUTTON_CLICKED',
|
||||
description: 'FA_TASKS_BUTTON_CLICKED'
|
||||
},
|
||||
FA_TASKS_PAGE_LOAD_SUCCESSFUL: {
|
||||
name: 'FA_TASKS_PAGE_LOAD_SUCCESSFUL',
|
||||
description: 'FA_TASKS_PAGE_LOAD_SUCCESSFUL'
|
||||
},
|
||||
FA_TASK_DETAIL_CLICKED: {
|
||||
name: 'FA_TASK_DETAIL_CLICKED',
|
||||
description: 'FA_TASK_DETAIL_CLICKED'
|
||||
},
|
||||
FA_TASK_DETAIL_LOAD_SUCCESSFUL: {
|
||||
name: 'FA_TASK_DETAIL_LOAD_SUCCESSFUL',
|
||||
description: 'FA_TASK_DETAIL_LOAD_SUCCESSFUL'
|
||||
},
|
||||
FA_PHOTO_UPLOAD_ERROR: {
|
||||
name: 'FA_PHOTO_UPLOAD_ERROR',
|
||||
description: 'FA_PHOTO_UPLOAD_ERROR',
|
||||
},
|
||||
FA_IMAGE_SYNC_START: {
|
||||
name: 'FA_IMAGE_SYNC_START',
|
||||
description: 'FA_IMAGE_SYNC_START',
|
||||
},
|
||||
FA_IMAGES_CAPTURED: {
|
||||
name: 'FA_IMAGES_CAPTURED',
|
||||
description: 'FA_IMAGES_CAPTURED',
|
||||
},
|
||||
FA_ZIP_FILE_CREATED: {
|
||||
name: 'FA_ZIP_FILE_CREATED',
|
||||
description: 'FA_ZIP_FILE_CREATED',
|
||||
},
|
||||
FA_ZIP_FILE_CREATE_ERROR: {
|
||||
name: 'FA_ZIP_FILE_CREATE_ERROR',
|
||||
description: 'FA_ZIP_FILE_CREATE_ERROR',
|
||||
},
|
||||
FA_ZIP_UPLOAD_PRESIGNED: {
|
||||
name: 'FA_ZIP_UPLOAD_PRESIGNED',
|
||||
description: 'FA_ZIP_FILE_CREATED',
|
||||
},
|
||||
FA_ZIP_UPLOAD_PRESIGNED_ERROR: {
|
||||
name: 'FA_ZIP_UPLOAD_PRESIGNED_ERROR',
|
||||
description: 'FA_ZIP_UPLOAD_PRESIGNED_ERROR',
|
||||
},
|
||||
FA_ZIP_UPLOADED: {
|
||||
name: 'FA_ZIP_UPLOADED',
|
||||
description: 'FA_ZIP_UPLOADED',
|
||||
},
|
||||
FA_ZIP_UPLOADED_ERROR: {
|
||||
name: 'FA_ZIP_UPLOADED',
|
||||
description: 'FA_ZIP_UPLOADED_ERROR',
|
||||
},
|
||||
FA_IMAGE_SYNC_ACK: {
|
||||
name: 'FA_IMAGE_SYNC_ACK',
|
||||
description: 'FA_IMAGE_SYNC_ACK',
|
||||
},
|
||||
FA_IMAGE_SYNC_ACK_ERROR: {
|
||||
name: 'FA_IMAGE_SYNC_ACK_ERROR',
|
||||
description: 'FA_IMAGE_SYNC_ACK_ERROR',
|
||||
},
|
||||
|
||||
// Device Details
|
||||
FA_DEVICE_DETAILS: {
|
||||
name: 'FA_DEVICE_DETAILS',
|
||||
@@ -862,6 +977,10 @@ export const HEADER_SCROLL_DISTANCE_WITH_QUICK_FILTERS =
|
||||
export const LocalStorageKeys = {
|
||||
LOAN_ID_TO_VALUE: 'loanIdToValue',
|
||||
GLOBAL_DOCUMENT_MAP: 'globalDocumentMap',
|
||||
IMAGE_SYNC_START_TIME: 'imageSyncStartTime',
|
||||
IMAGE_SYNC_TIME: 'imageSyncTime',
|
||||
IMAGE_FILES: 'imageFiles',
|
||||
IS_IMAGE_SYNC_ALLOWED: 'isImageSyncAllowed',
|
||||
};
|
||||
|
||||
export const SourceTextFocused = new Set(['Primary Contact', 'Secondary Contact']);
|
||||
@@ -940,6 +1059,7 @@ export const REQUEST_TO_UNBLOCK_FOR_IMPERSONATION = [
|
||||
getApiUrl(ApiKeys.GET_SIGNED_URL_FOR_REPORTEE),
|
||||
getApiUrl(ApiKeys.LOGOUT),
|
||||
getApiUrl(ApiKeys.PAST_FEEDBACK),
|
||||
getApiUrl(ApiKeys.GET_CSA_TICKETS),
|
||||
];
|
||||
|
||||
export const NAVI_AGENCY_CODE = '1000';
|
||||
|
||||
@@ -11,7 +11,7 @@ import CosmosForegroundService, {
|
||||
} from '../services/foregroundServices/foreground.service';
|
||||
import useIsOnline from '../hooks/useIsOnline';
|
||||
import { getSyncTime, sendCurrentGeolocationAndBuffer } from '../hooks/capturingApi';
|
||||
import { isTimeDifferenceWithinRange } from '../components/utlis/commonFunctions';
|
||||
import { isTimeDifferenceWithinRange, setAsyncStorageItem } from '../components/utlis/commonFunctions';
|
||||
import { setIsTimeSynced } from '../reducer/foregroundServiceSlice';
|
||||
import { logError } from '../components/utlis/errorUtils';
|
||||
import { useAppDispatch, useAppSelector } from '../hooks';
|
||||
@@ -42,8 +42,12 @@ import {
|
||||
} from './AgentActivityConfigurableConstants';
|
||||
import { GlobalImageMap } from './CachedImage';
|
||||
import { addClickstreamEvent } from '../services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from './Constants';
|
||||
import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from './Constants';
|
||||
import useResyncFirebase from '@hooks/useResyncFirebase';
|
||||
import { imageSyncService, prepareImagesForUpload, sendImagesToServer } from '@services/imageSyncService';
|
||||
import { getImages } from '@components/utlis/ImageUtlis';
|
||||
import getLitmusExperimentResult, { LitmusExperimentName, LitmusExperimentNameMap } from '@services/litmusExperiments.service';
|
||||
import { GLOBAL } from '@constants/Global';
|
||||
|
||||
export enum FOREGROUND_TASKS {
|
||||
GEOLOCATION = 'GEOLOCATION',
|
||||
@@ -55,6 +59,8 @@ export enum FOREGROUND_TASKS {
|
||||
DELETE_CACHE = 'DELETE_CACHE',
|
||||
FETCH_DATA_FROM_FIREBASE = 'FETCH_DATA_FROM_FIREBASE',
|
||||
FIREBASE_RESYNC = 'FIREBASE_RESYNC',
|
||||
IMAGE_SYNC_JOB = 'IMAGE_SYNC_JOB',
|
||||
IMAGE_UPLOAD_JOB = 'IMAGE_UPLOAD_JOB',
|
||||
}
|
||||
|
||||
interface ITrackingComponent {
|
||||
@@ -145,7 +151,6 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
const stateSetTimestamp = await getItem(StorageKeys.STATE_SET_TIMESTAMP);
|
||||
|
||||
if (foregroundTimestamp == null) {
|
||||
console.log('fts set after installation');
|
||||
await setItem(StorageKeys.APP_FOREGROUND_TIMESTAMP, dayJs().toString());
|
||||
}
|
||||
|
||||
@@ -265,6 +270,19 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
delay: 60 * MILLISECONDS_IN_A_MINUTE, // 60 minutes
|
||||
onLoop: true,
|
||||
},
|
||||
|
||||
{
|
||||
taskId: FOREGROUND_TASKS.IMAGE_SYNC_JOB,
|
||||
task: imageSyncService,
|
||||
delay: 30 * MILLISECONDS_IN_A_MINUTE, // 30 minutes
|
||||
onLoop: true,
|
||||
},
|
||||
{
|
||||
taskId: FOREGROUND_TASKS.IMAGE_UPLOAD_JOB,
|
||||
task: sendImagesToServer,
|
||||
delay: 10 * MILLISECONDS_IN_A_MINUTE, // 10 minutes
|
||||
onLoop: true,
|
||||
}
|
||||
];
|
||||
|
||||
if (!isTeamLead) {
|
||||
@@ -320,6 +338,8 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
|
||||
dispatch(getConfigData());
|
||||
CosmosForegroundService.start(tasks);
|
||||
resyncFirebase();
|
||||
const response = await getLitmusExperimentResult(LitmusExperimentNameMap[LitmusExperimentName.COSMOS_IMAGE_SYNC], { 'x-customer-id': GLOBAL.AGENT_ID });
|
||||
setAsyncStorageItem(LocalStorageKeys.IS_IMAGE_SYNC_ALLOWED, response);
|
||||
}
|
||||
if (nextAppState === AppStates.BACKGROUND) {
|
||||
await setItem(StorageKeys.APP_BACKGROUND_TIMESTAMP, now);
|
||||
|
||||
8
src/components/utlis/ImageUtlis.ts
Normal file
8
src/components/utlis/ImageUtlis.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
const { DeviceDataSyncModule } = NativeModules;
|
||||
|
||||
|
||||
export const getImages = (startTime: number, endTime: number) : Promise<any> => DeviceDataSyncModule.addEventListenerOnFile(startTime, endTime);
|
||||
|
||||
export const zipFilesForServer = (files: any) : Promise<any> => DeviceDataSyncModule.getCompressedFiles(files);
|
||||
@@ -67,7 +67,18 @@ export enum ApiKeys {
|
||||
DAILY_COMMITMENT = 'DAILY_COMMITMENT',
|
||||
GET_PTP_AMOUNT = 'GET_PTP_AMOUNT',
|
||||
GET_VISIBILITY_STATUS = 'GET_VISIBILITY_STATUS',
|
||||
GET_CSA_TICKETS = 'GET_CSA_TICKETS',
|
||||
GET_CSA_SINGLE_TICKET= 'GET_CSA_SINGLE_TICKET',
|
||||
CREATE_TICKET = 'CREATE_TICKET',
|
||||
ACKNOWLEDGE_TICKET = 'ACKNOWLEDGE_TICKET',
|
||||
ADD_COMMENT = 'ADD_COMMENT',
|
||||
UPDATE_TICKET_STATUS = 'UPDATE_TICKET_STATUS',
|
||||
GET_CSA_FILTERS = 'GET_CSA_FILTERS',
|
||||
GET_FORM_OPTIONS = 'GET_FORM_OPTIONS',
|
||||
GET_UPDATE_COUNT = 'GET_UPDATE_COUNT',
|
||||
SIMILAR_GEOLOCATION_TIMESTAMPS = 'SIMILAR_GEOLOCATION_TIMESTAMPS',
|
||||
GET_PRE_SIGNED_URL = 'GET_PRE_SIGNED_URL',
|
||||
SEND_UPLOAD_ACK = 'SEND_UPLOAD_ACK',
|
||||
}
|
||||
|
||||
export const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
|
||||
@@ -120,6 +131,18 @@ API_URLS[ApiKeys.DAILY_COMMITMENT] = '/daily-commitment';
|
||||
API_URLS[ApiKeys.GET_PTP_AMOUNT] = '/ptps-due-view/agent-detail';
|
||||
API_URLS[ApiKeys.GET_VISIBILITY_STATUS] = '/daily-commitment/visibility';
|
||||
API_URLS[ApiKeys.SIMILAR_GEOLOCATION_TIMESTAMPS] = '/v1/geolocation-cluster/{clusterId}/similar-locations-info';
|
||||
API_URLS[ApiKeys.GET_PRE_SIGNED_URL] = `/agent-data-sync/presigned-url?userReferenceId={agentID}&deviceReferenceId={deviceID}&dataSyncType={dataSyncType}`;
|
||||
API_URLS[ApiKeys.SEND_UPLOAD_ACK] = '/agent-data-sync/{requestId}';
|
||||
|
||||
API_URLS[ApiKeys.GET_CSA_TICKETS] = '/support-requests/fetch-all';
|
||||
API_URLS[ApiKeys.GET_CSA_SINGLE_TICKET] = '/support-requests/{ticketReferenceId}';
|
||||
API_URLS[ApiKeys.CREATE_TICKET] = '/support-requests';
|
||||
API_URLS[ApiKeys.ACKNOWLEDGE_TICKET] = '/support-requests/{ticketReferenceId}/acknowledge?supportRequestUserType=FE';
|
||||
API_URLS[ApiKeys.ADD_COMMENT] = '/support-requests/{ticketReferenceId}/comments';
|
||||
API_URLS[ApiKeys.UPDATE_TICKET_STATUS] = '/support-requests/{ticketReferenceId}';
|
||||
API_URLS[ApiKeys.GET_CSA_FILTERS] = '/support-requests/filters';
|
||||
API_URLS[ApiKeys.GET_FORM_OPTIONS] = '/support-requests/form'
|
||||
API_URLS[ApiKeys.GET_UPDATE_COUNT] = '/support-requests/summary'
|
||||
|
||||
export const API_STATUS_CODE = {
|
||||
OK: 200,
|
||||
@@ -289,6 +312,7 @@ axiosInstance.defaults.baseURL = BASE_AV_APP_URL;
|
||||
export const registerNavigateAndDispatch = (dispatchParam: Dispatch<any>) =>
|
||||
(dispatch = dispatchParam);
|
||||
|
||||
|
||||
export const isAxiosError = (err: Error) => {
|
||||
return axios.isAxiosError(err);
|
||||
};
|
||||
|
||||
@@ -148,6 +148,20 @@ export const setAsyncStorageItem = async (key: string, value: any) => {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
export const getAsyncStorageItem = async (key: string, shouldParse: boolean = false) => {
|
||||
try {
|
||||
const value = await AsyncStorage.getItem(key);
|
||||
if(value && shouldParse) {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
return value;
|
||||
} catch (err) {
|
||||
console.error('Error while fetching from AsyncStorage', err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
export const clearAllAsyncStorage = async () => {
|
||||
try {
|
||||
await AsyncStorage.clear();
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { MILLISECONDS_IN_A_MINUTE, MINUTES_IN_AN_HOUR } from '../../RN-UI-LIB/src/utlis/common';
|
||||
|
||||
export const BASE_AV_APP_URL = 'https://longhorn.navi.com/field-app';
|
||||
export const BASE_AV_APP_URL = 'https://qa-longhorn-portal.np.navi-tech.in/field-app';
|
||||
export const SENTRY_DSN =
|
||||
'https://5daa4832fade44b389b265de9b26c2fd@longhorn.navi.com/glitchtip-events/172';
|
||||
export const JANUS_SERVICE_URL = 'https://longhorn.navi.com/api/events/json';
|
||||
export const ENV = 'prod';
|
||||
'https://acef93c884c1424cacc4ec899562e203@qa-longhorn-portal.np.navi-tech.in/glitchtip-events/173';
|
||||
export const JANUS_SERVICE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/api/events/json';
|
||||
export const ENV = 'qa';
|
||||
export const IS_SSO_ENABLED = true;
|
||||
export const APM_APP_NAME = 'cosmos-app';
|
||||
export const APM_BASE_URL = 'https://longhorn.navi.com/apm-events';
|
||||
export const APM_BASE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/apm-events';
|
||||
export const IS_DATA_SYNC_REQUIRED = true;
|
||||
export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr
|
||||
export const GOOGLE_SSO_CLIENT_ID =
|
||||
'136591056725-ev8db4hrlud2m23n0o03or3cmmp3a3cq.apps.googleusercontent.com';
|
||||
'60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com';
|
||||
|
||||
@@ -20,6 +20,7 @@ import { logError } from '../components/utlis/errorUtils';
|
||||
import { type GenericFunctionArgs } from '../common/GenericTypes';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import analytics from '@react-native-firebase/analytics';
|
||||
import { setCsaFilters } from '@reducers/cosmosSupportSlice';
|
||||
|
||||
export interface CaseUpdates {
|
||||
updateType: string;
|
||||
@@ -58,6 +59,7 @@ const useFirestoreUpdates = () => {
|
||||
let lockUnsubscribe: GenericFunctionArgs;
|
||||
let feedbackFiltersUnsubscribe: GenericFunctionArgs;
|
||||
let appUpdateUnsubscribe: GenericFunctionArgs;
|
||||
let csaFiltersUnsubscribe: GenericFunctionArgs;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const showCaseUpdationToast = (newlyAddedCases: number, deletedCases: number) => {
|
||||
@@ -182,6 +184,13 @@ const useFirestoreUpdates = () => {
|
||||
dispatch(setFeedbackFilterTemplate(feedbackFilters));
|
||||
};
|
||||
|
||||
const handleCsaFilters = (
|
||||
snapshot: FirebaseFirestoreTypes.DocumentSnapshot<FirebaseFirestoreTypes.DocumentData>
|
||||
) => {
|
||||
const csaFilters = snapshot.data();
|
||||
dispatch(setCsaFilters(csaFilters));
|
||||
};
|
||||
|
||||
const handleError = (err: any, collectionPath?: string) => {
|
||||
const errMsg = `Error while fetching fireStore snapshot: referenceId: ${user?.referenceId} collectionPath: ${collectionPath}`;
|
||||
logError(err as Error, errMsg);
|
||||
@@ -290,6 +299,11 @@ const useFirestoreUpdates = () => {
|
||||
return subscribeToDoc(handleLockUpdate, lockPath);
|
||||
};
|
||||
|
||||
const subscribeToCsaFilters = () => {
|
||||
const collectionPath = "global-filters/v1";
|
||||
return subscribeToDoc(handleCsaFilters, collectionPath);
|
||||
};
|
||||
|
||||
const subscribeToFirestore = () => {
|
||||
addFirestoreListeners();
|
||||
configUnsubscribe = subscribeToUserConfig();
|
||||
@@ -302,6 +316,7 @@ const useFirestoreUpdates = () => {
|
||||
collectionTemplateUnsubscribe = subscribeToCollectionTemplate();
|
||||
lockUnsubscribe = subscribeToLocks();
|
||||
feedbackFiltersUnsubscribe = subscribeToFeedbackFilters();
|
||||
csaFiltersUnsubscribe = subscribeToCsaFilters();
|
||||
appUpdateUnsubscribe = subscribeToAppUpdate();
|
||||
}
|
||||
|
||||
@@ -329,6 +344,7 @@ const useFirestoreUpdates = () => {
|
||||
collectionTemplateUnsubscribe && collectionTemplateUnsubscribe();
|
||||
lockUnsubscribe && lockUnsubscribe();
|
||||
feedbackFiltersUnsubscribe && feedbackFiltersUnsubscribe();
|
||||
csaFiltersUnsubscribe && csaFiltersUnsubscribe();
|
||||
};
|
||||
}, [isLoggedIn, user?.referenceId, isExternalAgent]);
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ interface IAllCasesSlice {
|
||||
geolocations?: IGeolocation[];
|
||||
selectedCaseId: string;
|
||||
filteredListToast: FilteredListToast;
|
||||
shouldHideTabBar: boolean;
|
||||
}
|
||||
|
||||
const initialState: IAllCasesSlice = {
|
||||
@@ -89,6 +90,7 @@ const initialState: IAllCasesSlice = {
|
||||
showToast: false,
|
||||
caseType: '',
|
||||
},
|
||||
shouldHideTabBar: false,
|
||||
};
|
||||
|
||||
const getCaseListComponents = (casesList: ICaseItem[], caseDetails: Record<string, CaseDetail>) => {
|
||||
@@ -588,6 +590,9 @@ const allCasesSlice = createSlice({
|
||||
setFilteredListToast: (state, action) => {
|
||||
state.filteredListToast = action.payload;
|
||||
},
|
||||
setShouldHideTabBar: (state, action) => {
|
||||
state.shouldHideTabBar = action.payload;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -611,6 +616,7 @@ export const {
|
||||
setCasesImageUri,
|
||||
setSelectedCaseId,
|
||||
setFilteredListToast,
|
||||
setShouldHideTabBar
|
||||
} = allCasesSlice.actions;
|
||||
|
||||
export default allCasesSlice.reducer;
|
||||
|
||||
169
src/reducer/cosmosSupportSlice.ts
Normal file
169
src/reducer/cosmosSupportSlice.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { GetTicketCreationPayload, ICsaFilter, RequestTicket, Summary } from '@screens/cosmosSupport/constant/types';
|
||||
|
||||
|
||||
|
||||
|
||||
interface ICSASlice {
|
||||
loading: boolean;
|
||||
ticketCreationData: {
|
||||
ticketCreationData:GetTicketCreationPayload;
|
||||
isLoading: boolean;
|
||||
},
|
||||
caseLevelTickets: {
|
||||
taskForMe: {
|
||||
data: Array<unknown>;
|
||||
loading: boolean;
|
||||
},
|
||||
taskForTele: {
|
||||
data: Array<unknown>;
|
||||
loading: boolean;
|
||||
}
|
||||
},
|
||||
caseSummary: Summary,
|
||||
currentViewRequestTicket: {
|
||||
loading: boolean;
|
||||
data: RequestTicket| null;
|
||||
},
|
||||
isSubmittingComment: boolean;
|
||||
ticketCreationInProgress: boolean;
|
||||
markingTicketAsResolved: boolean;
|
||||
filters: ICsaFilter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const initialState = {
|
||||
loading: false,
|
||||
ticketCreationData: {
|
||||
isLoading: false,
|
||||
ticketCreationData: {}
|
||||
},
|
||||
caseLevelTickets: {
|
||||
taskForMe: {
|
||||
data: [],
|
||||
loading: false
|
||||
},
|
||||
taskForTele: {
|
||||
data: [],
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
caseSummary: {
|
||||
requesterSummaryDetails: {
|
||||
newTicketsCount: 0,
|
||||
newUpdatesCount: 0
|
||||
},
|
||||
assigneeSummaryDetails: {
|
||||
newTicketsCount: 0,
|
||||
newUpdatesCount: 0
|
||||
},
|
||||
aggregatedSummary: {
|
||||
newTicketsCount: 0,
|
||||
newUpdatesCount: 0
|
||||
}
|
||||
},
|
||||
currentViewRequestTicket: {
|
||||
loading: false,
|
||||
data: null
|
||||
},
|
||||
isSubmittingComment: false,
|
||||
ticketCreationInProgress: false,
|
||||
markingTicketAsResolved: false,
|
||||
filters: {}
|
||||
} as ICSASlice;
|
||||
|
||||
const CosmosSupport = createSlice({
|
||||
name: 'config',
|
||||
initialState,
|
||||
reducers: {
|
||||
setTicketCreationData: (state, action) => {
|
||||
state.ticketCreationData.isLoading = action.payload;
|
||||
},
|
||||
setLoading: (state, action) => {
|
||||
state.loading = action.payload;
|
||||
},
|
||||
setTickerCreationData: (state, action) => {
|
||||
state.ticketCreationData.isLoading = action.payload;
|
||||
},
|
||||
setCaseLevelTicketsForMe: (state, action) => {
|
||||
state.caseLevelTickets.taskForMe.data = action.payload;
|
||||
},
|
||||
setCaseLevelTicketsForMeLoading: (state, action) => {
|
||||
state.caseLevelTickets.taskForMe.loading = action.payload;
|
||||
},
|
||||
setCaseLevelTicketsForTele: (state, action) => {
|
||||
state.caseLevelTickets.taskForTele.data = action.payload;
|
||||
},
|
||||
setCaseLevelTicketsForTeleLoading: (state, action) => {
|
||||
state.caseLevelTickets.taskForTele.loading = action.payload;
|
||||
},
|
||||
setCaseSummary: (state, action) => {
|
||||
state.caseSummary = action.payload;
|
||||
},
|
||||
setSingleViewRequestTicket: (state, action) => {
|
||||
state.currentViewRequestTicket = {
|
||||
data: action.payload,
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
resetSingleViewRequestTicket: (state, action) => {
|
||||
state.currentViewRequestTicket.data = null;
|
||||
},
|
||||
loadingSingleTicket: (state, action) => {
|
||||
state.currentViewRequestTicket.loading = action.payload;
|
||||
},
|
||||
updateComment: (state, action) => {
|
||||
if (state.currentViewRequestTicket.data) {
|
||||
state.currentViewRequestTicket.data.activityLogs.unshift(action.payload);
|
||||
}
|
||||
},
|
||||
setSubmittingComment: (state, action) => {
|
||||
state.isSubmittingComment = action.payload;
|
||||
},
|
||||
setMarkingTicketAsResolved: (state, action) => {
|
||||
state.markingTicketAsResolved = action.payload;
|
||||
},
|
||||
setTicketCreationDataForFrom: (state, action) => {
|
||||
state.ticketCreationData.ticketCreationData = action.payload;
|
||||
},
|
||||
setCsaFilters: (state, action) => {
|
||||
state.filters = action.payload;
|
||||
},
|
||||
cleanCaseLevelData: (state, action) => {
|
||||
state.caseLevelTickets = {
|
||||
taskForMe: {
|
||||
data: [],
|
||||
loading: false
|
||||
},
|
||||
taskForTele: {
|
||||
data: [],
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
},
|
||||
resetConfig: () => initialState,
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setTicketCreationData,
|
||||
setLoading,
|
||||
setCaseLevelTicketsForMe,
|
||||
setCaseLevelTicketsForMeLoading,
|
||||
setCaseLevelTicketsForTele,
|
||||
setCaseLevelTicketsForTeleLoading,
|
||||
resetConfig,
|
||||
cleanCaseLevelData,
|
||||
setCaseSummary,
|
||||
setSingleViewRequestTicket,
|
||||
resetSingleViewRequestTicket,
|
||||
loadingSingleTicket,
|
||||
setSubmittingComment,
|
||||
updateComment,
|
||||
setTicketCreationDataForFrom,
|
||||
setMarkingTicketAsResolved,
|
||||
setCsaFilters
|
||||
} = CosmosSupport.actions;
|
||||
|
||||
export default CosmosSupport.reducer;
|
||||
@@ -70,6 +70,9 @@ export interface IUserSlice extends IUser {
|
||||
attendanceDate: string;
|
||||
};
|
||||
isAgentPerformanceDashboardVisible: boolean;
|
||||
featureFlags: {
|
||||
csaCoOrdinationModuleFeatureFlag : boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const initialState: IUserSlice = {
|
||||
@@ -92,6 +95,9 @@ const initialState: IUserSlice = {
|
||||
attendanceDate: '',
|
||||
},
|
||||
isAgentPerformanceDashboardVisible: false,
|
||||
featureFlags: {
|
||||
csaCoOrdinationModuleFeatureFlag: false
|
||||
}
|
||||
};
|
||||
|
||||
export const userSlice = createSlice({
|
||||
@@ -146,6 +152,9 @@ export const userSlice = createSlice({
|
||||
},
|
||||
setIsAgentPerformanceDashboardVisible: (state, action) => {
|
||||
state.isAgentPerformanceDashboardVisible = action.payload;
|
||||
},
|
||||
setFeatureFlag: (state, action) => {
|
||||
state.featureFlags = action.payload;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -159,7 +168,8 @@ export const {
|
||||
setIsExternalAgent,
|
||||
setCaseSyncLock,
|
||||
setAgentAttendance,
|
||||
setIsAgentPerformanceDashboardVisible
|
||||
setIsAgentPerformanceDashboardVisible,
|
||||
setFeatureFlag
|
||||
} = userSlice.actions;
|
||||
|
||||
export default userSlice.reducer;
|
||||
|
||||
31
src/screens/Profile/CountComponent.tsx
Normal file
31
src/screens/Profile/CountComponent.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useAppDispatch, useAppSelector } from '@hooks'
|
||||
import { useIsFocused } from '@react-navigation/native'
|
||||
import Tag, { TagVariant } from '@rn-ui-lib/components/Tag'
|
||||
import { GenericStyles } from '@rn-ui-lib/styles'
|
||||
import { getSummary } from '@screens/cosmosSupport/actions'
|
||||
import { ICount } from '@screens/cosmosSupport/constant/types'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
const CountComponent = () => {
|
||||
const isFocused = useIsFocused()
|
||||
|
||||
const summary = useAppSelector((state) => state.cosmosSupport.caseSummary)
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getSummary({loanAccountNumber: undefined}))
|
||||
}, [isFocused])
|
||||
|
||||
const newCount = summary?.aggregatedSummary?.newTicketUpdatesCount;
|
||||
|
||||
if(!newCount) return null;
|
||||
|
||||
|
||||
return (
|
||||
<Tag style={GenericStyles.mr8} variant={TagVariant.blue} text={`${newCount} New`} />
|
||||
)
|
||||
}
|
||||
|
||||
export default CountComponent
|
||||
65
src/screens/Profile/Navigation/constants.ts
Normal file
65
src/screens/Profile/Navigation/constants.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { logout } from '@actions/authActions';
|
||||
import CSAIcon from '@assets/icons/CSAIcon';
|
||||
import CompletedCaseIcon from '@assets/icons/CompletedCaseIcon';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
import { navigateToScreen } from '@components/utlis/navigationUtlis';
|
||||
import { MY_CASE_ITEM } from '@reducers/userSlice';
|
||||
import LogoutIcon from '@rn-ui-lib/icons/LogoutIcon';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import store from '@store';
|
||||
import { Alert } from 'react-native';
|
||||
import { ProfileScreenStackEnum } from '../ProfileStack';
|
||||
import CountComponent from '../CountComponent';
|
||||
|
||||
export const getNavigationLinks = () => {
|
||||
const { isTeamLead, selectedAgent, featureFlags } = store.getState().user;
|
||||
return [
|
||||
{
|
||||
name: 'Completed cases',
|
||||
icon: CompletedCaseIcon,
|
||||
isVisible: !isTeamLead || selectedAgent?.referenceId === MY_CASE_ITEM.referenceId,
|
||||
onPress: () => navigateToScreen('completedCases'),
|
||||
},
|
||||
{
|
||||
name: 'Tele support',
|
||||
icon: CSAIcon,
|
||||
isVisible: featureFlags?.csaCoOrdinationModuleFeatureFlag,
|
||||
onPress: () =>
|
||||
navigateToScreen(ProfileScreenStackEnum.TELE_SUPPORT, {
|
||||
from: 'profile',
|
||||
}),
|
||||
isNew: true,
|
||||
NewComponent: CountComponent,
|
||||
},
|
||||
{
|
||||
name: 'Logout',
|
||||
icon: LogoutIcon,
|
||||
isVisible: true,
|
||||
onPress: () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_PROFILE_PAGE_LOGOUT_BUTTON_CLICKED);
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_PROFILE_PAGE_LOGOUT_CONFIRMATION_OPEN);
|
||||
Alert.alert('Confirm', 'Are you sure you want to logout? ', [
|
||||
{
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
onPress: () => {
|
||||
addClickstreamEvent(
|
||||
CLICKSTREAM_EVENT_NAMES.AV_PROFILE_PAGE_LOGOUT_CONFIRMATION_CLOSED
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Logout',
|
||||
onPress: () => {
|
||||
addClickstreamEvent(
|
||||
CLICKSTREAM_EVENT_NAMES.AV_PROFILE_PAGE_LOGOUT_CONFIRMATION_CLICKED
|
||||
);
|
||||
store.dispatch(logout());
|
||||
},
|
||||
style: 'destructive',
|
||||
},
|
||||
]);
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
64
src/screens/Profile/ProfileButton.tsx
Normal file
64
src/screens/Profile/ProfileButton.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import Chevron from '@rn-ui-lib/icons/Chevron';
|
||||
import { IconProps } from '@rn-ui-lib/icons/types';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React, { useState } from 'react';
|
||||
import { StyleSheet, Text, TouchableHighlight, View } from 'react-native';
|
||||
|
||||
interface ProfileButtonProps {
|
||||
onPress: () => void;
|
||||
name: string;
|
||||
Icon: React.FC<IconProps>;
|
||||
tag: string;
|
||||
NewComponent?: React.FC;
|
||||
};
|
||||
|
||||
|
||||
const ProfileButton: React.FC<ProfileButtonProps> = (props) => {
|
||||
const { onPress, name, Icon, NewComponent } = props;
|
||||
|
||||
const [isPressed, setIsPressed] = useState(false);
|
||||
const handleOnPress = () => {
|
||||
setIsPressed(true);
|
||||
};
|
||||
const handleOnPressOut = () => {
|
||||
setIsPressed(false);
|
||||
};
|
||||
return (
|
||||
<TouchableHighlight
|
||||
underlayColor={COLORS.BACKGROUND.BLUE}
|
||||
onPressIn={handleOnPress}
|
||||
onPress={onPress}
|
||||
onPressOut={handleOnPressOut}
|
||||
style={[
|
||||
GenericStyles.pv12,
|
||||
GenericStyles.ph12,
|
||||
GenericStyles.br4
|
||||
]}
|
||||
>
|
||||
<View style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
GenericStyles.justifyContentSpaceBetween
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
]}
|
||||
>
|
||||
<Icon size={16} fillColor={isPressed ? COLORS.TEXT.BLUE : COLORS.TEXT.LIGHT} />
|
||||
<Text style={GenericStyles.ml4}>{name}</Text>
|
||||
</View>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
|
||||
{NewComponent ? <NewComponent /> : null}
|
||||
<Chevron fillColor={isPressed ? COLORS.TEXT.BLUE : COLORS.TEXT.LIGHT} />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default ProfileButton;
|
||||
61
src/screens/Profile/ProfileStack.tsx
Normal file
61
src/screens/Profile/ProfileStack.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { getScreenFocusListenerObj } from '@components/utlis/commonFunctions';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
import CompletedCase from '@screens/allCases/CompletedCase';
|
||||
import { DEFAULT_SCREEN_OPTIONS } from '@screens/auth/ProtectedRouter';
|
||||
import { RequestDetail, ViewRequestHistory } from '@screens/cosmosSupport';
|
||||
import React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import Profile from '.';
|
||||
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
|
||||
export enum ProfileScreenStackEnum {
|
||||
PROFILE = 'profile',
|
||||
COMPLETED_CASES = 'completedCases',
|
||||
TELE_SUPPORT = 'teleSupport',
|
||||
PROFILE_TICKET_DETAIL = 'profileTicketDetail',
|
||||
|
||||
}
|
||||
|
||||
|
||||
const ProfileStack = () => {
|
||||
|
||||
return (
|
||||
<Stack.Navigator
|
||||
screenListeners={getScreenFocusListenerObj}
|
||||
screenOptions={
|
||||
DEFAULT_SCREEN_OPTIONS
|
||||
}
|
||||
initialRouteName={ProfileScreenStackEnum.PROFILE}
|
||||
>
|
||||
<Stack.Screen
|
||||
name={ProfileScreenStackEnum.PROFILE}
|
||||
component={Profile}
|
||||
|
||||
|
||||
/>
|
||||
<Stack.Screen
|
||||
name={ProfileScreenStackEnum.COMPLETED_CASES}
|
||||
component={CompletedCase}
|
||||
/>
|
||||
<Stack.Group
|
||||
navigationKey={ProfileScreenStackEnum.TELE_SUPPORT}
|
||||
>
|
||||
<Stack.Screen
|
||||
name={ProfileScreenStackEnum.TELE_SUPPORT}
|
||||
component={ViewRequestHistory}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name={ProfileScreenStackEnum.PROFILE_TICKET_DETAIL}
|
||||
component={RequestDetail}
|
||||
/>
|
||||
</Stack.Group>
|
||||
</Stack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({})
|
||||
|
||||
export default ProfileStack
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from 'react-native';
|
||||
import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import LogoutIcon from '../../../RN-UI-LIB/src/Icons/LogoutIcon';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import {
|
||||
BUTTON_PRESS_COUNT_FOR_IMPERSONATION,
|
||||
@@ -21,14 +20,10 @@ import { logout } from '../../action/authActions';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import { RootState } from '../../store/store';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import Button from '../../../RN-UI-LIB/src/components/Button';
|
||||
import { navigateToScreen } from '../../components/utlis/navigationUtlis';
|
||||
import GroupIcon from '../../../RN-UI-LIB/src/Icons/GroupIcon';
|
||||
import { getAppVersion, getBuildVersion } from '../../components/utlis/commonFunctions';
|
||||
import VersionNumber from 'react-native-version-number';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { CaseDetail } from '../caseDetails/interface';
|
||||
import CaseItem from '../allCases/CaseItem';
|
||||
import { useFocusEffect, useIsFocused } from '@react-navigation/native';
|
||||
import { IUserRole, MY_CASE_ITEM } from '../../reducer/userSlice';
|
||||
import QuestionMarkIcon from '../../assets/icons/QuestionMarkIcon';
|
||||
import IDCardImageCapture from './IDCardImageCapture';
|
||||
@@ -39,9 +34,12 @@ import FloatingBannerCta from '@common/FloatingBannerCta';
|
||||
import AttendanceIcon from '@assets/icons/AttendanceIcon';
|
||||
import GoogleFormModal from '@screens/allCases/GoogleFormModal';
|
||||
import { PageRouteEnum } from '@screens/auth/ProtectedRouter';
|
||||
import ProfileButton from './ProfileButton';
|
||||
import { getNavigationLinks } from './Navigation/constants';
|
||||
|
||||
const Profile: React.FC = () => {
|
||||
const [buttonPressedCount, setButtonPressedCount] = useState(0);
|
||||
const focused = useIsFocused();
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
originalImageUri,
|
||||
@@ -126,13 +124,10 @@ const Profile: React.FC = () => {
|
||||
|
||||
const helpButtonClickHandler = () => Linking.openURL(supportLink);
|
||||
|
||||
|
||||
const hideUploadImageBtn =
|
||||
approvalStatus === ImageApprovalStatus.PENDING ||
|
||||
approvalStatus === ImageApprovalStatus.APPROVED;
|
||||
|
||||
|
||||
|
||||
const showCompletedCases = !isTeamLead || selectedAgent?.referenceId === MY_CASE_ITEM.referenceId;
|
||||
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
@@ -155,16 +150,16 @@ const Profile: React.FC = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ID_CARD_CLICKED);
|
||||
navigateToScreen(PageRouteEnum.AGENT_ID_CARD);
|
||||
}}
|
||||
style={styles.bottomActionable}
|
||||
style={styles.bottomActionable}
|
||||
>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignStart]}>
|
||||
<Text small style={styles.whiteText}>
|
||||
View ID card
|
||||
</Text>
|
||||
<View style={{ transform: [{ rotate: '180deg' }] }}>
|
||||
<ArrowSolidIcon fillColor={COLORS.TEXT.WHITE} />
|
||||
</View>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignStart]}>
|
||||
<Text small style={styles.whiteText}>
|
||||
View ID card
|
||||
</Text>
|
||||
<View style={{ transform: [{ rotate: '180deg' }] }}>
|
||||
<ArrowSolidIcon fillColor={COLORS.TEXT.WHITE} />
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
) : null
|
||||
}
|
||||
@@ -185,70 +180,34 @@ const Profile: React.FC = () => {
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ScrollView style={GenericStyles.fill}>
|
||||
{showCompletedCases ? (
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.ph16,
|
||||
GenericStyles.pt16,
|
||||
numberOfCompletedCases === 2 ? { paddingBottom: 6 } : {},
|
||||
]}
|
||||
>
|
||||
{hideUploadImageBtn ? null : <IDCardImageCapture />}
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
numberOfCompletedCases ? { paddingBottom: 12 } : GenericStyles.pb16,
|
||||
]}
|
||||
>
|
||||
<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}
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
GenericStyles.p16,
|
||||
GenericStyles.whiteBackground,
|
||||
GenericStyles.fill,
|
||||
]}
|
||||
>
|
||||
{hideUploadImageBtn ? null : <IDCardImageCapture />}
|
||||
{getNavigationLinks()?.map((link, index) => {
|
||||
if (!link.isVisible) return null;
|
||||
return (
|
||||
<View style={{ marginBottom: 10 }}>
|
||||
<ProfileButton
|
||||
key={index}
|
||||
onPress={link.onPress}
|
||||
name={link.name}
|
||||
Icon={link.icon}
|
||||
tag={'tag'}
|
||||
NewComponent={link.NewComponent}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
<View style={[styles.logoutContainer, GenericStyles.whiteBackground]}>
|
||||
<TouchableOpacity
|
||||
onPress={handleLogout}
|
||||
style={[GenericStyles.row, GenericStyles.centerAligned, GenericStyles.fill]}
|
||||
>
|
||||
<LogoutIcon />
|
||||
<Text style={styles.logoutText}>Logout</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
|
||||
<Pressable
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.centerAligned,
|
||||
GenericStyles.fill,
|
||||
GenericStyles.w100,
|
||||
GenericStyles.mt8,
|
||||
]}
|
||||
@@ -274,8 +233,6 @@ const Profile: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
<GoogleFormModal showForm={showForm} setShowForm={setShowForm} />
|
||||
|
||||
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { setShouldHideTabBar } from '@reducers/allCasesSlice';
|
||||
import React, { useEffect } from 'react';
|
||||
import { StyleSheet, View, VirtualizedList } from 'react-native';
|
||||
import React from 'react';
|
||||
import { ICaseItem } from './interface';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { EmptyListMessages } from './constants';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import EmptyList from './EmptyList';
|
||||
import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
|
||||
import { goBack } from '../../components/utlis/navigationUtlis';
|
||||
import { useAppSelector } from '../../hooks';
|
||||
import CaseItem from './CaseItem';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import { CaseDetail } from '../caseDetails/interface';
|
||||
import CaseItem from './CaseItem';
|
||||
import EmptyList from './EmptyList';
|
||||
import { ICaseItem } from './interface';
|
||||
|
||||
const getItem = (item: Array<ICaseItem>, index: number) => item[index];
|
||||
|
||||
@@ -22,6 +22,15 @@ const CompletedCase: React.FC = () => {
|
||||
(state) => state.allCases
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setShouldHideTabBar(true))
|
||||
return () => {
|
||||
dispatch(setShouldHideTabBar(false))
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={[GenericStyles.fill, styles.container]}>
|
||||
<NavigationHeader
|
||||
|
||||
@@ -68,6 +68,7 @@ const Filters: React.FC<IFilters> = ({
|
||||
onChangeText={handleSearchChange}
|
||||
placeholder={`Search in ${getTextInputPlaceholder()}`}
|
||||
defaultValue={searchQuery}
|
||||
value={searchQuery}
|
||||
testID="test_search"
|
||||
showClearIcon
|
||||
/>
|
||||
|
||||
@@ -88,6 +88,7 @@ export const ToastMessages = {
|
||||
FILE_DOWNLOAD_SUCCESS: 'File downloaded successfully',
|
||||
FILE_DOWNLOAD_FAILURE: 'File download failed',
|
||||
COMMITMENT_SUBMITTED_SUCCESSFULLY: 'Commitment submitted successfully',
|
||||
SUCCESS_COPYING_ADDRESS: 'Address copied successfully',
|
||||
};
|
||||
|
||||
export enum BOTTOM_TAB_ROUTES {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
resetSelectedTodoList,
|
||||
resetTodoList,
|
||||
setLoading,
|
||||
setShouldHideTabBar,
|
||||
setVisitPlansUpdating,
|
||||
} from '@reducers/allCasesSlice';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
@@ -24,7 +25,7 @@ import FullScreenLoaderWrapper from '@common/FullScreenLoaderWrapper';
|
||||
import DashboardIcon from '../../assets/icons/DashboardIcon';
|
||||
import DashBoardScreens from '../Dashboard/DashBoardScreens';
|
||||
import { isAgentDashboardVisible } from '@screens/Dashboard/utils';
|
||||
import { AppState, AppStateStatus, StyleSheet } from 'react-native';
|
||||
import { AppState, AppStateStatus, StyleSheet, View } from 'react-native';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import DailyCommitmentIcon from '@rn-ui-lib/icons/DailyCommitmentIcon';
|
||||
import DailyCommitmentBottomSheet from '../dailyCommitment/DailyCommitmentBottomSheet';
|
||||
@@ -32,9 +33,11 @@ import { getVisibilityStatus } from '@screens/dailyCommitment/actions';
|
||||
import { logError } from '@components/utlis/errorUtils';
|
||||
import FloatingBannerCta from '@common/FloatingBannerCta';
|
||||
import { AppStates } from '@interfaces/appStates';
|
||||
import ProfileStack from '@screens/Profile/ProfileStack';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
|
||||
const AllCasesMain = () => {
|
||||
const { pendingList, pinnedList, completedList, loading } = useAppSelector(
|
||||
const { pendingList, pinnedList, completedList, loading, shouldHideTabBar } = useAppSelector(
|
||||
(state) => state.allCases
|
||||
);
|
||||
const userState = useAppSelector((state: RootState) => state.user);
|
||||
@@ -78,7 +81,7 @@ const AllCasesMain = () => {
|
||||
}, []);
|
||||
|
||||
const shouldShowBanner = useMemo(() => {
|
||||
return !isCommitmentSubmitted && isCommitmentFormVisible;
|
||||
return !isCommitmentSubmitted && isCommitmentFormVisible;
|
||||
}, [isCommitmentSubmitted, isCommitmentFormVisible]);
|
||||
|
||||
|
||||
@@ -157,18 +160,20 @@ const AllCasesMain = () => {
|
||||
|
||||
bottomSheetScreens.push({
|
||||
name: BOTTOM_TAB_ROUTES.Profile,
|
||||
component: () => (
|
||||
<>
|
||||
<Profile />
|
||||
{shouldShowBanner ? <FloatingBannerCta
|
||||
title={"Update your daily commitment"}
|
||||
onPressHandler={openCommitmentScreen}
|
||||
containerStyle={styles.container}
|
||||
icon={<DailyCommitmentIcon />}
|
||||
textStyle={styles.titleText}
|
||||
/> : null}
|
||||
</>
|
||||
),
|
||||
component: () => {
|
||||
return (
|
||||
<>
|
||||
<ProfileStack />
|
||||
{shouldShowBanner && !shouldHideTabBar ? <FloatingBannerCta
|
||||
title={"Update your daily commitment"}
|
||||
onPressHandler={openCommitmentScreen}
|
||||
containerStyle={styles.container}
|
||||
icon={<DailyCommitmentIcon />}
|
||||
textStyle={styles.titleText}
|
||||
/> : null}
|
||||
</>
|
||||
)
|
||||
},
|
||||
icon: ProfileIcon,
|
||||
});
|
||||
return bottomSheetScreens;
|
||||
@@ -178,6 +183,7 @@ const AllCasesMain = () => {
|
||||
isTeamLead,
|
||||
showAgentDashboard,
|
||||
shouldShowBanner,
|
||||
shouldHideTabBar
|
||||
]);
|
||||
|
||||
const onTabPressHandler = (e: any) => {
|
||||
@@ -198,6 +204,7 @@ const AllCasesMain = () => {
|
||||
dispatch(setLoading(false));
|
||||
dispatch(resetTodoList());
|
||||
dispatch(resetSelectedTodoList());
|
||||
dispatch(setShouldHideTabBar(false))
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -207,12 +214,13 @@ const AllCasesMain = () => {
|
||||
screens={HOME_SCREENS}
|
||||
initialRoute={isTeamLead ? BOTTOM_TAB_ROUTES.Cases : BOTTOM_TAB_ROUTES.VisitPlan}
|
||||
onTabPress={(e) => onTabPressHandler(e)}
|
||||
shouldHideTabBar={shouldHideTabBar}
|
||||
/>
|
||||
<CasesActionButtons />
|
||||
{!isCommitmentSubmitted && isCommitmentFormVisible && <DailyCommitmentBottomSheet
|
||||
openBottomSheet={openBottomSheet}
|
||||
setOpenBottomSheet={setOpenBottomSheet}
|
||||
onSuccessCallback={() => { getVisibility();}}
|
||||
onSuccessCallback={() => { getVisibility(); }}
|
||||
/>}
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getUniqueId, isTablet } from 'react-native-device-info';
|
||||
import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import { type RootState } from '../../store/store';
|
||||
import { setAgentAttendance, setDeviceId } from '../../reducer/userSlice';
|
||||
import { DEVICE_TYPE_ENUM, setGlobalUserData } from '../../constants/Global';
|
||||
import { DEVICE_TYPE_ENUM, GLOBAL, setGlobalUserData } from '../../constants/Global';
|
||||
import { registerNavigateAndDispatch } from '../../components/utlis/apiHelper';
|
||||
import ProtectedRouter from './ProtectedRouter';
|
||||
import useNativeButtons from '../../hooks/useNativeButton';
|
||||
@@ -20,9 +20,11 @@ import {
|
||||
alfredSetPhoneNumber,
|
||||
alfredSetUserId,
|
||||
} from '../../components/utlis/DeviceUtils';
|
||||
import { getAppVersion } from '../../components/utlis/commonFunctions';
|
||||
import { getAppVersion, setAsyncStorageItem } from '../../components/utlis/commonFunctions';
|
||||
import useScreenshotTracking from '../../hooks/useScreenshotTracking';
|
||||
import { getSyncTime } from '@hooks/capturingApi';
|
||||
import getLitmusExperimentResult, { LitmusExperimentName, LitmusExperimentNameMap } from '@services/litmusExperiments.service';
|
||||
import { LocalStorageKeys } from '@common/Constants';
|
||||
|
||||
function AuthRouter() {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -116,6 +118,9 @@ function AuthRouter() {
|
||||
CosmosForegroundService.stopAll();
|
||||
}
|
||||
});
|
||||
getLitmusExperimentResult(LitmusExperimentNameMap[LitmusExperimentName.COSMOS_IMAGE_SYNC], { 'x-customer-id': GLOBAL.AGENT_ID }).then((response) => {
|
||||
setAsyncStorageItem(LocalStorageKeys.IS_IMAGE_SYNC_ALLOWED, response);
|
||||
});
|
||||
}
|
||||
}, [isLoggedIn]);
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { useAppDispatch, useAppSelector } from '../../hooks';
|
||||
import useFirestoreUpdates from '../../hooks/useFirestoreUpdates';
|
||||
import useIsOnline from '../../hooks/useIsOnline';
|
||||
import AllCasesMain from '../allCases';
|
||||
import CompletedCase from '../allCases/CompletedCase';
|
||||
import interactionsHandler from '../caseDetails/interactionsHandler';
|
||||
import ImpersonatedUser from '../impersonatedUser';
|
||||
import Notifications from '../notifications';
|
||||
@@ -116,7 +115,6 @@ const ProtectedRouter = () => {
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name={PageRouteEnum.TODO_LIST} component={TodoList} />
|
||||
<Stack.Screen name={PageRouteEnum.COMPLETED_CASES} component={CompletedCase} />
|
||||
<Stack.Screen name={PageRouteEnum.NOTIFICATIONS} component={Notifications} />
|
||||
<Stack.Screen name={PageRouteEnum.IMPERSONATED_LOGIN} component={ImpersonatedUser} />
|
||||
<Stack.Screen name={PageRouteEnum.AGENT_ID_CARD} component={AgentIdCard} />
|
||||
|
||||
@@ -1,19 +1,52 @@
|
||||
import React from 'react';
|
||||
import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader';
|
||||
import { goBack } from '../../components/utlis/navigationUtlis';
|
||||
import { goBack, navigateToScreen } from '../../components/utlis/navigationUtlis';
|
||||
import { CaseDetail } from './interface';
|
||||
import { View } from 'react-native';
|
||||
import { TouchableHighlight, View } from 'react-native';
|
||||
import NotificationMenu from '../../components/notificationMenu';
|
||||
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
|
||||
import { CaseDetailStackEnum } from './CaseDetailStack';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import { cleanCaseLevelData } from '@reducers/cosmosSupportSlice';
|
||||
import CSAIconButton from '@assets/icons/CSAIconButton';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
import { CASE_DETAIL_SCREEN } from '@screens/cosmosSupport/constant';
|
||||
|
||||
const CaseDetailHeader: React.FC<{ caseDetail: CaseDetail }> = (props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { csaCoOrdinationModuleFeatureFlag } = useAppSelector((state) => ({
|
||||
csaCoOrdinationModuleFeatureFlag: state?.user?.featureFlags?.csaCoOrdinationModuleFeatureFlag,
|
||||
}));
|
||||
const handleOpenRequestHistory = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VIEW_TASK_HISTORY_CLICKED, {
|
||||
caseId: props.caseDetail.id,
|
||||
});
|
||||
dispatch(cleanCaseLevelData(null));
|
||||
navigateToScreen(CaseDetailStackEnum.VIEW_RequestHistory, {
|
||||
caseDetail: props.caseDetail,
|
||||
from: CASE_DETAIL_SCREEN,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ position: 'relative' }}>
|
||||
<NavigationHeader
|
||||
title={''}
|
||||
onBack={goBack}
|
||||
rightActionable={
|
||||
<View style={GenericStyles.pr12}>
|
||||
<View style={[GenericStyles.pr12, GenericStyles.row, GenericStyles.alignCenter]}>
|
||||
{csaCoOrdinationModuleFeatureFlag ? (
|
||||
<TouchableHighlight
|
||||
underlayColor={COLORS.HIGHLIGHTER.LIGHT_BUTTON}
|
||||
activeOpacity={1}
|
||||
style={GenericStyles.iconContainerButton}
|
||||
onPress={handleOpenRequestHistory}
|
||||
>
|
||||
<CSAIconButton fillColor={COLORS.BACKGROUND.PRIMARY} />
|
||||
</TouchableHighlight>
|
||||
) : null}
|
||||
<NotificationMenu />
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ import { RootState } from '@store';
|
||||
import { _map } from '@rn-ui-lib/utils/common';
|
||||
import EmiSchedule from '@screens/emiSchedule';
|
||||
import AddNewNumber from '@screens/addNewNumber';
|
||||
import ViewRequestHistory from '@screens/cosmosSupport/ViewRequestHistory';
|
||||
import { RequestDetail } from '@screens/cosmosSupport';
|
||||
import RequestSupport from '@screens/cosmosSupport/RequestSupport';
|
||||
import SimilarGeolocations from '@screens/addressGeolocation/SimilarGeolocations';
|
||||
import AdditionalGeolocations from '@screens/addressGeolocation/AdditionalGeolocations';
|
||||
|
||||
@@ -39,6 +42,9 @@ export enum CaseDetailStackEnum {
|
||||
PAST_FEEDBACK_DETAIL = 'pastFeedbackDetail',
|
||||
EMI_SCHEDULE = 'EmiSchedule',
|
||||
ADD_NEW_NUMBER = 'AddNewNumber',
|
||||
VIEW_RequestHistory = 'viewRequestHistory',
|
||||
VIEW_REQUEST_DETAIL = 'viewRequestDetail',
|
||||
RAISE_REQUEST = 'raiseRequest',
|
||||
SIMILAR_GEOLOCATIONS = 'SimilarGeolocations',
|
||||
ADDITIONAL_GEOLOCATIONS = 'AdditionalGeolocations',
|
||||
}
|
||||
@@ -84,6 +90,15 @@ const CaseDetailStack = () => {
|
||||
name={CaseDetailStackEnum.PAST_FEEDBACK_DETAIL}
|
||||
component={FeedbackDetailContainer}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name={CaseDetailStackEnum.VIEW_RequestHistory}
|
||||
component={ViewRequestHistory}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name={CaseDetailStackEnum.VIEW_REQUEST_DETAIL}
|
||||
component={RequestDetail}
|
||||
/>
|
||||
<Stack.Screen name={CaseDetailStackEnum.RAISE_REQUEST} component={RequestSupport} />
|
||||
{_map(collectionTemplate?.widget, (key) => (
|
||||
<Stack.Screen
|
||||
key={getTemplateRoute(key, CaseAllocationType.COLLECTION_CASE)}
|
||||
|
||||
@@ -115,7 +115,7 @@ const CollectionCaseDetails: React.FC<ICaseDetails> = (props) => {
|
||||
(state) => state.case.caseForm?.[caseId]?.[TaskTitleUIMapping.COLLECTION_FEEDBACK]
|
||||
);
|
||||
|
||||
const { addressString, phoneNumbers, loanAccountNumber, totalOverdueAmount, pos } = caseDetail;
|
||||
const { addressString, phoneNumbers, loanAccountNumber, totalOverdueAmount, pos } = caseDetail || {};
|
||||
|
||||
const feedbackList: IFeedback[] = useAppSelector(
|
||||
(state: RootState) => state.feedbackHistory?.[loanAccountNumber as string]?.data || []
|
||||
|
||||
187
src/screens/cosmosSupport/CSAFilters.tsx
Normal file
187
src/screens/cosmosSupport/CSAFilters.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import { Modal, StyleSheet, Switch, View } from 'react-native';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { GenericStyles, getShadowStyle } from '@rn-ui-lib/styles';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
|
||||
import { IFilterStatus } from './constant/types';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import TextInput from '@rn-ui-lib/components/TextInput';
|
||||
import IconButton from '@rn-ui-lib/components/IconButton';
|
||||
import FilterIcon from '@assets/icons/FilterIcon';
|
||||
import SearchIcon from '@rn-ui-lib/icons/SearchIcon';
|
||||
import Filters, { TFilterOptions } from '@components/filters/Filters';
|
||||
import { debounce } from '@components/utlis/commonFunctions';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
import { getFiltersCount } from './constant';
|
||||
|
||||
interface ICSAFilters {
|
||||
filtersTemplate: any;
|
||||
onFilterChange: (data: IFilterStatus) => void;
|
||||
isFocusedTab?: boolean;
|
||||
isTasksPresent?: boolean;
|
||||
}
|
||||
|
||||
const SEARCH_BAR_DEBOUNCE_INTERVAL = 300;
|
||||
|
||||
const CSAFilters: React.FC<ICSAFilters> = (props) => {
|
||||
const { filtersTemplate, onFilterChange, isFocusedTab, isTasksPresent } = props;
|
||||
const [showFilterModal, setShowFilterModal] = useState(false);
|
||||
const [selectedFilter, setSelectedFilter] = useState<TFilterOptions>({});
|
||||
const [viewUnread, setViewUnread] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = React.useState<string>('');
|
||||
const feedbackFilterCount = getFiltersCount(selectedFilter);
|
||||
const unreadThumbColor = useRef(COLORS.TEXT.LIGHT);
|
||||
const toggleFilterModal = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TASKS_FILTER_BUTTON_CLICKED,{open: !showFilterModal})
|
||||
setShowFilterModal((prev) => !prev)
|
||||
};
|
||||
|
||||
const handleFilterSelection = (selectedFilters: TFilterOptions) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TASKS_FILTER_APPLIED,selectedFilter)
|
||||
|
||||
setSelectedFilter(selectedFilters);
|
||||
};
|
||||
|
||||
const handleViewUnread = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TASKS_UNREAD_TOGGLE_CLICKED, {filterType:'unread', showUnread: !viewUnread})
|
||||
onFilterChange({ filter: selectedFilter, showUnread: !viewUnread });
|
||||
setViewUnread((prev) => {
|
||||
unreadThumbColor.current = prev ? COLORS.TEXT.LIGHT : COLORS.TEXT.BLUE;
|
||||
return !prev;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
debounce((query: string) => {
|
||||
setSearchQuery(query);
|
||||
}, SEARCH_BAR_DEBOUNCE_INTERVAL),
|
||||
[]
|
||||
);
|
||||
|
||||
const handleFilterChange = () => {
|
||||
const searchQueryFilters: TFilterOptions = {};
|
||||
if (searchQuery) {
|
||||
if(/\d/.test(searchQuery)){
|
||||
searchQueryFilters.LOAN_ACCOUNT_NUMBERS = {
|
||||
filters: {
|
||||
[searchQuery]: true,
|
||||
},
|
||||
selectedFilterCount: 1,
|
||||
};
|
||||
}else{
|
||||
searchQueryFilters.CUSTOMER_NAMES = {
|
||||
filters: {
|
||||
[searchQuery]: true,
|
||||
},
|
||||
selectedFilterCount: 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
onFilterChange({ filter: { ...selectedFilter, ...searchQueryFilters }, showUnread: viewUnread });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleFilterChange();
|
||||
}, [searchQuery, selectedFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!isFocusedTab && !isTasksPresent) {
|
||||
setSearchQuery('');
|
||||
}
|
||||
}, [isFocusedTab])
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.p16,
|
||||
GenericStyles.row,
|
||||
GenericStyles.justifyContentSpaceBetween,
|
||||
GenericStyles.alignCenter,
|
||||
{ ...getShadowStyle(1) },
|
||||
GenericStyles.whiteBackground,
|
||||
]}
|
||||
>
|
||||
<TextInput
|
||||
containerStyle={GenericStyles.fill}
|
||||
placeholder={'Search by LAN or Customer'}
|
||||
LeftComponent={<SearchIcon />}
|
||||
placeholderTextColor={COLORS.TEXT.LIGHT}
|
||||
inputContainerStyle={styles.searchbar}
|
||||
value={searchQuery}
|
||||
onChangeText={handleSearchChange}
|
||||
showClearIcon
|
||||
/>
|
||||
<View>
|
||||
<IconButton
|
||||
style={[GenericStyles.ml8, { backgroundColor: COLORS.BACKGROUND.PRIMARY }]}
|
||||
testID="filter-btn"
|
||||
icon={
|
||||
<FilterIcon
|
||||
fillColor={feedbackFilterCount ? COLORS.TEXT.BLUE : COLORS.BACKGROUND.LIGHT}
|
||||
/>
|
||||
}
|
||||
onPress={toggleFilterModal}
|
||||
/>
|
||||
{feedbackFilterCount ? (
|
||||
<View style={[styles.filterCount]}>
|
||||
<Text style={[GenericStyles.whiteText, { marginTop: -3 }]} small bold>
|
||||
{feedbackFilterCount}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
{ backgroundColor: COLORS.BACKGROUND.BLUE_LIGHT_3 },
|
||||
GenericStyles.p8,
|
||||
]}
|
||||
>
|
||||
<Switch
|
||||
onValueChange={handleViewUnread}
|
||||
value={viewUnread}
|
||||
trackColor={{ true: COLORS.BACKGROUND.BLUE_LIGHT_4, false: COLORS.BORDER.PRIMARY }}
|
||||
thumbColor={COLORS.TEXT.WHITE}
|
||||
/>
|
||||
<Text> Show unread</Text>
|
||||
</View>
|
||||
<Modal
|
||||
animationType="slide"
|
||||
animated
|
||||
onRequestClose={toggleFilterModal}
|
||||
visible={showFilterModal}
|
||||
>
|
||||
<Filters
|
||||
header="Filters"
|
||||
defaultSelectedFilters={selectedFilter}
|
||||
closeFilterModal={toggleFilterModal}
|
||||
filters={filtersTemplate}
|
||||
onFilterChange={handleFilterSelection}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
searchbar: {
|
||||
height: 48,
|
||||
},
|
||||
filterCount: {
|
||||
backgroundColor: COLORS.TEXT.BLUE,
|
||||
width: 18,
|
||||
height: 18,
|
||||
borderRadius: 9,
|
||||
alignItems: 'center',
|
||||
position: 'absolute',
|
||||
opacity: 0.8,
|
||||
top: -4,
|
||||
right: -4,
|
||||
},
|
||||
});
|
||||
|
||||
export default CSAFilters;
|
||||
284
src/screens/cosmosSupport/CustomerCard.tsx
Normal file
284
src/screens/cosmosSupport/CustomerCard.tsx
Normal file
@@ -0,0 +1,284 @@
|
||||
import { Keyboard, Pressable, ScrollView, StyleSheet, TouchableWithoutFeedback, TouchableWithoutFeedbackComponent, View } from 'react-native';
|
||||
import React, { useEffect } from 'react';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import Heading from '@rn-ui-lib/components/Heading';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import { formatAmount } from '@rn-ui-lib/utils/amount';
|
||||
import Tag from '@rn-ui-lib/components/Tag';
|
||||
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
|
||||
import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader';
|
||||
import ChevronDown from '@assets/icons/ChevronDown';
|
||||
import BottomSheetWrapper from '@common/BottomSheetWrapper';
|
||||
import TextInput from '@rn-ui-lib/components/TextInput';
|
||||
import Button from '@rn-ui-lib/components/Button';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { updateTicket } from './actions';
|
||||
import { Status, RequestorType } from './constant/types';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import FullScreenLoaderWrapper from '@common/FullScreenLoaderWrapper';
|
||||
import { Mapping, MAX_COMENT_LENGTH, StatusColorMapping } from './constant';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import HeaderNode from './HeaderNode';
|
||||
import CsaIncomingIconSquare from '@assets/icons/CsaIncomingIconSquare';
|
||||
import CsaOutgoingRequestSquareIcon from '@assets/icons/CsaOutgoingRequestSquareIcon';
|
||||
import { copyToClipboard } from '@components/utlis/commonFunctions';
|
||||
import { toast } from '@rn-ui-lib/components/toast';
|
||||
import { ToastMessages } from '@screens/allCases/constants';
|
||||
import CopyIconFilled from '@assets/icons/CopyIconFilled';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
|
||||
interface ICustomerCard {
|
||||
customerName: string;
|
||||
Dpd: number;
|
||||
Pos: number;
|
||||
status: string;
|
||||
isRefreshing: boolean;
|
||||
isEditable?: boolean;
|
||||
refId?: string;
|
||||
ticketStatusChange?: (id: string) => void;
|
||||
statusReadable?: string;
|
||||
requestType: string;
|
||||
requestedByuserType: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
const CustomerCard: React.FC<ICustomerCard> = (props) => {
|
||||
const {
|
||||
customerName,
|
||||
Dpd,
|
||||
Pos,
|
||||
status,
|
||||
isRefreshing,
|
||||
isEditable,
|
||||
statusReadable,
|
||||
requestType,
|
||||
requestedByuserType,
|
||||
address,
|
||||
} = props;
|
||||
const Component = isEditable ? Pressable : View;
|
||||
const [openBottomSheet, setOpenBottomSheet] = React.useState<boolean>(false);
|
||||
const [comment, setComment] = React.useState<string>('');
|
||||
const markingTicketAsResolved = useAppSelector(
|
||||
(state) => state.cosmosSupport.markingTicketAsResolved
|
||||
);
|
||||
const handleOpenBottomSheet = () => {
|
||||
if (status !== Mapping.CLOSED) setOpenBottomSheet((prev) => !prev);
|
||||
};
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [isKeyboardVisible, setKeyboardVisible] = React.useState(false);
|
||||
useEffect(() => {
|
||||
const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => {
|
||||
setKeyboardVisible(true);
|
||||
});
|
||||
const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => {
|
||||
setKeyboardVisible(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
keyboardDidShowListener.remove();
|
||||
keyboardDidHideListener.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleCloseTicket = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TASK_MARKED_DONE, {
|
||||
customerName,
|
||||
requestType,
|
||||
status,
|
||||
Dpd,
|
||||
Pos,
|
||||
isRefreshing,
|
||||
isEditable,
|
||||
statusReadable,
|
||||
requestedByuserType,
|
||||
})
|
||||
const payload = {
|
||||
status: Status.CLOSED,
|
||||
comment: comment.trim(),
|
||||
supportRequestUserType: 'FE',
|
||||
};
|
||||
dispatch(updateTicket(payload, props?.refId!));
|
||||
setOpenBottomSheet(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!openBottomSheet) setComment('');
|
||||
}, [openBottomSheet]);
|
||||
|
||||
const handleAddressCopy = () => {
|
||||
if (!address) return;
|
||||
copyToClipboard(address);
|
||||
toast({
|
||||
text1: ToastMessages.SUCCESS_COPYING_ADDRESS,
|
||||
type: 'info',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<SuspenseLoader fallBack={<LineLoader height={26} width={150} />} loading={isRefreshing}>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignStart]}>
|
||||
{requestedByuserType === RequestorType.CSA ? (
|
||||
<CsaIncomingIconSquare />
|
||||
) : (
|
||||
<CsaOutgoingRequestSquareIcon />
|
||||
)}
|
||||
<View style={GenericStyles.ml12}>
|
||||
<Heading dark type="h4" numberOfLines={1}>
|
||||
{requestType}
|
||||
</Heading>
|
||||
<Text>For {customerName} </Text>
|
||||
</View>
|
||||
</View>
|
||||
</SuspenseLoader>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.silverBackground,
|
||||
GenericStyles.br8,
|
||||
GenericStyles.p16,
|
||||
GenericStyles.mt16,
|
||||
]}
|
||||
>
|
||||
<View style={[GenericStyles.row, GenericStyles.justifyStart]}>
|
||||
<Text bold dark style={[styles.flexBasis20, styles.black, GenericStyles.fontSize14]}>
|
||||
<SuspenseLoader fallBack={<LineLoader height={20} width={50} />} loading={isRefreshing}>
|
||||
{Dpd}
|
||||
</SuspenseLoader>
|
||||
</Text>
|
||||
<Text dark style={[styles.flexBasis40, styles.black, GenericStyles.fontSize14]}>
|
||||
<SuspenseLoader fallBack={<LineLoader height={20} width={50} />} loading={isRefreshing}>
|
||||
{formatAmount(Pos)}
|
||||
</SuspenseLoader>
|
||||
</Text>
|
||||
<Component
|
||||
onPress={handleOpenBottomSheet}
|
||||
style={[styles.flexBasis30, GenericStyles.row]}
|
||||
>
|
||||
<SuspenseLoader fallBack={<LineLoader height={18} width={50} />} loading={isRefreshing}>
|
||||
<Tag text={statusReadable} variant={StatusColorMapping[status]} />
|
||||
{isEditable && status !== Mapping.CLOSED ? <ChevronDown /> : null}
|
||||
</SuspenseLoader>
|
||||
</Component>
|
||||
</View>
|
||||
<View style={[GenericStyles.row, GenericStyles.justifyStart, GenericStyles.mt4]}>
|
||||
<Text style={[styles.flexBasis20, styles.label, GenericStyles.fontSize14]}>DPD</Text>
|
||||
<Text style={[styles.flexBasis40, styles.label, GenericStyles.fontSize14]}>
|
||||
EMI overdue
|
||||
</Text>
|
||||
<Text style={[styles.flexBasis30, styles.label, GenericStyles.fontSize14]}>Status</Text>
|
||||
</View>
|
||||
</View>
|
||||
{address ? (
|
||||
<View style={[GenericStyles.borderTop, GenericStyles.pv16, GenericStyles.mt24]}>
|
||||
<View style={[GenericStyles.row, GenericStyles.spaceBetween]}>
|
||||
<Text bold dark>
|
||||
Added address
|
||||
</Text>
|
||||
<Pressable style={[GenericStyles.centerAlignedRow]} onPress={handleAddressCopy}>
|
||||
<CopyIconFilled style={[GenericStyles.mt2, GenericStyles.mr2]} />
|
||||
<Text style={[GenericStyles.textBlue, GenericStyles.fontSize13]}>Copy</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<Text dark style={[GenericStyles.fontSize13, GenericStyles.mt10]}>
|
||||
{address}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<BottomSheetWrapper
|
||||
visible={openBottomSheet}
|
||||
setVisible={handleOpenBottomSheet}
|
||||
heightPercentage={40}
|
||||
HeaderNode={HeaderNode}
|
||||
moveForKeyboard={0.2}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={[GenericStyles.fill]}
|
||||
>
|
||||
<SafeAreaView style={[GenericStyles.p16, GenericStyles.fill, styles.pt0]}>
|
||||
<View style={[GenericStyles.fill]}>
|
||||
|
||||
<View>
|
||||
<TextInput
|
||||
numberOfLines={5}
|
||||
style={styles.textInput}
|
||||
multiline={true}
|
||||
placeholder="Leave a comment (mandatory)"
|
||||
onChangeText={setComment}
|
||||
value={comment}
|
||||
maxLength={300}
|
||||
border={true}
|
||||
/>
|
||||
<Text
|
||||
small
|
||||
light
|
||||
style={[styles.count, comment.length >= MAX_COMENT_LENGTH ? styles.error : {}]}
|
||||
>
|
||||
{comment.length}/{MAX_COMENT_LENGTH}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{!isKeyboardVisible ? (
|
||||
<View style={[GenericStyles.row, GenericStyles.justifyContentSpaceBetween]}>
|
||||
<Button
|
||||
title="Cancel"
|
||||
variant="secondary"
|
||||
style={styles.flexBasis48}
|
||||
onPress={handleOpenBottomSheet}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Mark as done"
|
||||
variant="primary"
|
||||
style={styles.flexBasis48}
|
||||
onPress={handleCloseTicket}
|
||||
disabled={!comment?.trim()}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</SafeAreaView>
|
||||
</ScrollView>
|
||||
</BottomSheetWrapper>
|
||||
<FullScreenLoaderWrapper loading={markingTicketAsResolved} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flexBasis20: {
|
||||
flexBasis: '20%',
|
||||
},
|
||||
flexBasis40: {
|
||||
flexBasis: '40%',
|
||||
},
|
||||
flexBasis30: {
|
||||
flexBasis: '30%',
|
||||
},
|
||||
textInput: {
|
||||
textAlignVertical: 'top',
|
||||
maxHeight: 100,
|
||||
},
|
||||
flexBasis48: {
|
||||
flexBasis: '48%',
|
||||
},
|
||||
pt0: {
|
||||
paddingTop: 0,
|
||||
},
|
||||
black: {
|
||||
color: COLORS.TEXT.BLACK,
|
||||
},
|
||||
label: {
|
||||
color: COLORS.BORDER.SECONDARY,
|
||||
},
|
||||
count: {
|
||||
position: 'absolute',
|
||||
bottom: -24,
|
||||
right: 0,
|
||||
},
|
||||
error: {
|
||||
color: COLORS.TEXT.YELLOW,
|
||||
},
|
||||
});
|
||||
|
||||
export default CustomerCard;
|
||||
66
src/screens/cosmosSupport/CustomerDetailListItem.tsx
Normal file
66
src/screens/cosmosSupport/CustomerDetailListItem.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Pressable, StyleSheet, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import Heading from '@rn-ui-lib/components/Heading';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import { formatAmount } from '@rn-ui-lib/utils/amount';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
|
||||
interface ICustomerDetailListItem {
|
||||
item: any;
|
||||
viewTaskCta: string;
|
||||
handleNavigation: () => void;
|
||||
handleNavigationTODetails: () => void;
|
||||
}
|
||||
|
||||
const CustomerDetailListItem: React.FC<ICustomerDetailListItem> = (props) => {
|
||||
const { item, viewTaskCta, handleNavigation, handleNavigationTODetails } = props;
|
||||
return (
|
||||
<View>
|
||||
<Heading style={GenericStyles.mt16} type="h5" dark>
|
||||
{item.customerName}
|
||||
</Heading>
|
||||
<View style={[GenericStyles.row, GenericStyles.mt8]}>
|
||||
<View>
|
||||
<Text style={GenericStyles.fontSize14} dark>
|
||||
{formatAmount(item?.overdueAmount?.value)}
|
||||
</Text>
|
||||
<Text style={styles.tagColor} small>
|
||||
Overdue Amount
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.marginLeft60}>
|
||||
<Text style={GenericStyles.fontSize14} dark>
|
||||
{item?.loanAccountNumber}
|
||||
</Text>
|
||||
<Text style={styles.tagColor} small>
|
||||
LAN
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[GenericStyles.row, GenericStyles.mt16]}>
|
||||
<Pressable onPress={handleNavigation}>
|
||||
<Text style={styles.buttonText}>Go to customer details</Text>
|
||||
</Pressable>
|
||||
<Pressable style={GenericStyles.ml16} onPress={handleNavigationTODetails}>
|
||||
<Text style={styles.buttonText}>{viewTaskCta} </Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tagColor: {
|
||||
color: COLORS.BORDER.SECONDARY,
|
||||
},
|
||||
marginLeft60: {
|
||||
marginLeft: 60,
|
||||
},
|
||||
buttonText: {
|
||||
fontWeight: '500',
|
||||
color: COLORS.BASE.BLUE,
|
||||
},
|
||||
});
|
||||
|
||||
export default CustomerDetailListItem;
|
||||
24
src/screens/cosmosSupport/HeaderNode.tsx
Normal file
24
src/screens/cosmosSupport/HeaderNode.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import Heading from "@rn-ui-lib/components/Heading"
|
||||
import { GenericStyles } from "@rn-ui-lib/styles"
|
||||
import React from "react";
|
||||
import { View } from "react-native"
|
||||
|
||||
|
||||
interface IHeaderNode {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const HeaderNode= () => {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.ph16,
|
||||
GenericStyles.pb16,
|
||||
]}
|
||||
>
|
||||
<Heading type='h3'>Update status to <Heading dark bold style={{fontWeight: '700'}} type='h3' >Done</Heading></Heading>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default HeaderNode
|
||||
130
src/screens/cosmosSupport/ListItem.tsx
Normal file
130
src/screens/cosmosSupport/ListItem.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { navigateToScreen } from '@components/utlis/navigationUtlis'
|
||||
import { useAppDispatch } from '@hooks'
|
||||
import { resetSingleViewRequestTicket } from '@reducers/cosmosSupportSlice'
|
||||
import { COLORS } from '@rn-ui-lib/colors'
|
||||
import Tag, { TagVariant } from '@rn-ui-lib/components/Tag'
|
||||
import Text from '@rn-ui-lib/components/Text'
|
||||
import ArrowRightOutlineIcon from '@rn-ui-lib/icons/ArrowRightOutlineIcon'
|
||||
import { GenericStyles } from '@rn-ui-lib/styles'
|
||||
import { ProfileScreenStackEnum } from '@screens/Profile/ProfileStack'
|
||||
import { PageRouteEnum } from '@screens/auth/ProtectedRouter'
|
||||
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack'
|
||||
import React from 'react'
|
||||
import { StyleSheet, TouchableHighlight, View } from 'react-native'
|
||||
import CustomerDetailListItem from './CustomerDetailListItem'
|
||||
import RelativeTime from './RelativeTime'
|
||||
import { Mapping, SCREEN_MAP, StatusColorMapping } from './constant'
|
||||
import { From } from './ViewRequestHistory'
|
||||
|
||||
const ListItem = (props: any) => {
|
||||
const { form, route, item, task, isLastItem, loadData} = props;
|
||||
|
||||
const viewTaskCta = task === SCREEN_MAP.TASK_FOR_ME ? "View task details" : "View request details";
|
||||
const dispatch = useAppDispatch();
|
||||
const Component = form === Mapping.FORM_PROFILE ? View : TouchableHighlight;
|
||||
const handleNavigation = () => {
|
||||
navigateToScreen(PageRouteEnum.CASE_DETAIL_STACK, {
|
||||
screen: CaseDetailStackEnum.COLLECTION_CASE_DETAIL,
|
||||
params: { caseId: item.collectionCaseId },
|
||||
});
|
||||
}
|
||||
|
||||
const handleNavigationTODetails = () => {
|
||||
dispatch(resetSingleViewRequestTicket(null))
|
||||
navigateToScreen(form === Mapping.FORM_PROFILE ? ProfileScreenStackEnum.PROFILE_TICKET_DETAIL : CaseDetailStackEnum.VIEW_REQUEST_DETAIL, { ticketId: item?.referenceId, task, loadData })
|
||||
}
|
||||
return (
|
||||
<Component
|
||||
underlayColor={COLORS.BACKGROUND.BLUE}
|
||||
onPress={handleNavigationTODetails}
|
||||
style={[
|
||||
GenericStyles.p16,
|
||||
GenericStyles.row,
|
||||
GenericStyles.justifyContentSpaceBetween,
|
||||
GenericStyles.alignCenter,
|
||||
!isLastItem ? styles.lineItem : {}
|
||||
]}
|
||||
>
|
||||
<>
|
||||
<View
|
||||
style={[styles.flexBasis]}
|
||||
>
|
||||
{item?.newUpdatesCount ? <Text small style={styles.updateStyle} >{item?.newUpdatesCount} {item?.newUpdatesCount > 1 ? 'updates' : 'update'}</Text> : null}
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.mt4,
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
]}
|
||||
>
|
||||
{item.isAcknowledgedByAssignee || task === SCREEN_MAP.TELE_SUPPORT ? null : <View
|
||||
style={[
|
||||
styles.dot,
|
||||
GenericStyles.mr10
|
||||
]}
|
||||
/>}
|
||||
<Text bold dark style={form === Mapping.FORM_PROFILE ? GenericStyles.fontSize13 : GenericStyles.fontSize12}>{item?.requestTypeLabel} </Text>
|
||||
|
||||
</View>
|
||||
<View style={[GenericStyles.alighSelfCenter]}>
|
||||
<Tag style={[GenericStyles.ml10]} variant={StatusColorMapping[item?.status as keyof typeof StatusColorMapping]} text={item?.statusLabel} />
|
||||
</View>
|
||||
</View>
|
||||
<RelativeTime fontSize={form === Mapping.FORM_PROFILE ? 12: 13} epoch={item?.createdAt} />
|
||||
{form === Mapping.FORM_PROFILE ? <CustomerDetailListItem item={item} handleNavigation={handleNavigation} handleNavigationTODetails={handleNavigationTODetails} viewTaskCta={viewTaskCta} /> : null}
|
||||
</View>
|
||||
{form === Mapping.FORM_PROFILE ? null : <ArrowRightOutlineIcon fillColor={COLORS.BASE.BLUE} />}
|
||||
</>
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flexBasis: {
|
||||
flexBasis: '95%'
|
||||
},
|
||||
updateStyle: {
|
||||
lineHeight: 18,
|
||||
fontWeight: "500",
|
||||
color: "#518CFF"
|
||||
},
|
||||
type: {
|
||||
lineHeight: 16,
|
||||
fontWeight: "500",
|
||||
},
|
||||
requestedON: {
|
||||
lineHeight: 20,
|
||||
fontWeight: "400",
|
||||
fontSize: 13,
|
||||
color: COLORS.BORDER.SECONDARY
|
||||
},
|
||||
dot: {
|
||||
height: 4,
|
||||
width: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: COLORS.BASE.BLUE,
|
||||
},
|
||||
lineItem: {
|
||||
borderBottomWidth: 12,
|
||||
borderBottomColor: COLORS.BACKGROUND.SILVER,
|
||||
paddingBottom: 16,
|
||||
paddingTop: 16
|
||||
},
|
||||
tagColor: {
|
||||
color: COLORS.BORDER.SECONDARY
|
||||
},
|
||||
marginLeft60: {
|
||||
marginLeft: 60
|
||||
},
|
||||
buttonText: {
|
||||
fontWeight: "500",
|
||||
color: COLORS.BASE.BLUE
|
||||
}
|
||||
})
|
||||
|
||||
export default ListItem
|
||||
23
src/screens/cosmosSupport/ListItemLoading.tsx
Normal file
23
src/screens/cosmosSupport/ListItemLoading.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
const ListItemLoading = () => (
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.p16,
|
||||
GenericStyles.row,
|
||||
GenericStyles.justifyContentSpaceBetween,
|
||||
GenericStyles.alignCenter
|
||||
]}
|
||||
>
|
||||
<View>
|
||||
<LineLoader height={10} width={50} />
|
||||
<LineLoader style={[GenericStyles.mt12]} height={10} width={100} />
|
||||
<LineLoader style={[GenericStyles.mt12]} height={10} width={200} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default ListItemLoading
|
||||
75
src/screens/cosmosSupport/RelativeTime.tsx
Normal file
75
src/screens/cosmosSupport/RelativeTime.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import { COSMOS_STANDARD_DATE_TIME_FORMAT, dateFormat } from '@rn-ui-lib/utils/dates';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
|
||||
|
||||
|
||||
const ONE_MIN_TIME_INTERVAL = 60000;
|
||||
const ONE_HOUR_TIME_INTERVAL = 3600000;
|
||||
|
||||
|
||||
interface IRelativeTime {
|
||||
epoch: number
|
||||
fontSize?: number
|
||||
}
|
||||
|
||||
const RelativeTime:React.FC<IRelativeTime> = (props) => {
|
||||
const {
|
||||
epoch,
|
||||
fontSize = 13
|
||||
} = props;
|
||||
const [currentTime, setCurrentTime] = useState('');
|
||||
const [pollingInterval, setPollingInterval] = useState(ONE_MIN_TIME_INTERVAL); // Default polling interval
|
||||
|
||||
const updateTime = () => {
|
||||
const now = dayjs();
|
||||
const time = dayjs(epoch);
|
||||
const diffInMinutes = now.diff(time, 'minute');
|
||||
|
||||
if (diffInMinutes < 60) {
|
||||
setPollingInterval(ONE_MIN_TIME_INTERVAL); // Poll every minute
|
||||
} else if (diffInMinutes >= 60 && diffInMinutes < 1440) {
|
||||
setPollingInterval(ONE_HOUR_TIME_INTERVAL); // Poll every hour
|
||||
} else {
|
||||
setPollingInterval(0); // Do not poll for a day or more
|
||||
}
|
||||
|
||||
if (now.diff(time, 'day') >= 1) {
|
||||
setCurrentTime(dateFormat(new Date(epoch), COSMOS_STANDARD_DATE_TIME_FORMAT));
|
||||
} else {
|
||||
setCurrentTime(time.fromNow());
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateTime();
|
||||
if (pollingInterval) {
|
||||
const intervalId = setInterval(() => {
|
||||
updateTime();
|
||||
}, pollingInterval);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}
|
||||
}, [pollingInterval]);
|
||||
|
||||
return (
|
||||
<Text style={[styles.container, {fontSize: fontSize}]}>{currentTime}</Text>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
color: COLORS.BORDER.SECONDARY,
|
||||
lineHeight: 20
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
export default RelativeTime;
|
||||
135
src/screens/cosmosSupport/RenderCommentsTimeline.tsx
Normal file
135
src/screens/cosmosSupport/RenderCommentsTimeline.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import Avatar from '@rn-ui-lib/components/Avatar';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader';
|
||||
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { RelativeTime } from './';
|
||||
import { ActivityLog } from './constant/types';
|
||||
|
||||
interface IRenderCommentsTimeline {
|
||||
comments?: Array<ActivityLog>;
|
||||
};
|
||||
|
||||
interface ICardComponent {
|
||||
name: string;
|
||||
time: number;
|
||||
comment: string;
|
||||
nameCard: string;
|
||||
me?: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const RenderCommentsTimeline: React.FC<IRenderCommentsTimeline> = (props) => {
|
||||
const { comments } = props;
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{comments?.map((comment, index) => {
|
||||
const name = comment?.activityBy?.userType === "FE" ? "You" : comment?.activityBy?.name;
|
||||
return (
|
||||
<CardComponent
|
||||
key={index}
|
||||
name={comment?.activityBy?.name}
|
||||
time={new Date(comment?.activityAt).toString()}
|
||||
comment={comment?.value}
|
||||
nameCard={comment?.type === "TICKET_CREATION" ? `Requested by ${name}`:name}
|
||||
me={comment?.activityBy?.userType === "FE" ? true : false}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const CardComponent: React.FC<ICardComponent> = (props) => {
|
||||
const { name, time, comment, nameCard, me } = props;
|
||||
return (
|
||||
<View
|
||||
key={time}
|
||||
style={[GenericStyles.mt16, GenericStyles.row]}
|
||||
>
|
||||
<SuspenseLoader
|
||||
loading={!name}
|
||||
fallBack={<LineLoader height={40} width={40} style={styles.imageFallBack} />}
|
||||
>
|
||||
<Avatar
|
||||
loading
|
||||
dataURI=''
|
||||
size={32}
|
||||
name={name}
|
||||
style={ me ? styles.avatarStyle : styles.avatarMe}
|
||||
textStyle={ styles.avatarText }
|
||||
/>
|
||||
</SuspenseLoader>
|
||||
<View style ={[GenericStyles.ml8, GenericStyles.justifyContentCenter, styles.width]}>
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
|
||||
<SuspenseLoader
|
||||
loading={!name}
|
||||
fallBack={<LineLoader height={14} width={100} />}
|
||||
>
|
||||
<Text small style={[styles.capitalize,styles.font13]}>
|
||||
{nameCard}
|
||||
</Text>
|
||||
</SuspenseLoader>
|
||||
<View style={styles.dot} />
|
||||
<SuspenseLoader
|
||||
loading={!time}
|
||||
fallBack={<LineLoader height={14} width={150} />}
|
||||
>
|
||||
<RelativeTime epoch={new Date(time).getTime()} />
|
||||
</SuspenseLoader>
|
||||
</View>
|
||||
{comment ?<Text dark style={{paddingRight: 6, paddingTop: 4}}>
|
||||
{comment}
|
||||
</Text> : null}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
avatarText: {
|
||||
color: COLORS.TEXT.WHITE,
|
||||
fontSize: 13,
|
||||
fontWeight: '500'
|
||||
},
|
||||
avatarStyle: {
|
||||
backgroundColor: COLORS.BACKGROUND.GREEN_LIGHT,
|
||||
borderColor: COLORS.BACKGROUND.GREEN_LIGHT
|
||||
},
|
||||
|
||||
avatarMe: {
|
||||
backgroundColor: COLORS.BACKGROUND.PURPLE_LIGHT,
|
||||
borderColor: COLORS.BACKGROUND.PURPLE_LIGHT,
|
||||
},
|
||||
dot: {
|
||||
height: 4,
|
||||
width: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: COLORS.BORDER.SECONDARY,
|
||||
marginHorizontal: 4
|
||||
},
|
||||
imageFallBack: {
|
||||
borderRadius: 20
|
||||
},
|
||||
capitalize: {
|
||||
textTransform: 'capitalize',
|
||||
},
|
||||
font13:{
|
||||
fontSize: 13,
|
||||
lineHeight: 20,
|
||||
color: COLORS.BORDER.SECONDARY,
|
||||
},
|
||||
width:{
|
||||
flexBasis: '90%'
|
||||
}
|
||||
});
|
||||
|
||||
export default RenderCommentsTimeline
|
||||
161
src/screens/cosmosSupport/RequestDetail.tsx
Normal file
161
src/screens/cosmosSupport/RequestDetail.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { goBack } from '@components/utlis/navigationUtlis';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import Heading from '@rn-ui-lib/components/Heading';
|
||||
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React, { useEffect } from 'react';
|
||||
import {KeyboardAvoidingView, RefreshControl, ScrollView, StyleSheet, View} from 'react-native';
|
||||
import CustomerCard from './CustomerCard';
|
||||
import RenderCommentsTimeline from './RenderCommentsTimeline';
|
||||
import TextFieldWithInput from './TextFieldWithInput';
|
||||
import { acknowledgeTicket, addCommentToTicket, fetchSingleTicket, getSummary } from './actions';
|
||||
import { Mapping, SCREEN_MAP } from './constant';
|
||||
import { GenericFunctionArgs } from '@common/GenericTypes';
|
||||
import {Status} from "@screens/cosmosSupport/constant/types";
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
|
||||
interface IRequestDetail {
|
||||
route: {
|
||||
params: {
|
||||
ticketId: string;
|
||||
task: string;
|
||||
loadData: GenericFunctionArgs
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const RequestDetail: React.FC<IRequestDetail> = (props) => {
|
||||
|
||||
const { data, isRefreshing, user } = useAppSelector(state => ({
|
||||
data: state.cosmosSupport.currentViewRequestTicket.data,
|
||||
isRefreshing: state.cosmosSupport.currentViewRequestTicket.loading,
|
||||
user: state.user.user
|
||||
}))
|
||||
const [comment, setComment] = React.useState<string>('');
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
||||
const onRefresh = () => {
|
||||
if (!props?.route?.params?.ticketId) return;
|
||||
loadData(props?.route?.params?.ticketId);
|
||||
};
|
||||
|
||||
const handleBackPress = () => {
|
||||
goBack();
|
||||
};
|
||||
|
||||
const loadData = (ticketId: string, isPolling?:boolean) => {
|
||||
dispatch(fetchSingleTicket(ticketId, isPolling))
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!props?.route?.params?.ticketId) return;
|
||||
loadData(props?.route?.params?.ticketId)
|
||||
dispatch(acknowledgeTicket(props?.route?.params?.ticketId));
|
||||
}, [props?.route?.params?.ticketId]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
dispatch(getSummary({loanAccountNumber: undefined}));
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
|
||||
const onSendHit = (comment: string) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TASK_COMMENT_ADDED,props?.route?.params)
|
||||
const trimmedComment = comment.trim();
|
||||
const updateComment = {
|
||||
activityAt: Date.now(),
|
||||
activityBy: {
|
||||
name: user?.name!,
|
||||
userType: Mapping.USER_TYPE,
|
||||
referenceId: user?.referenceId!
|
||||
},
|
||||
type: Mapping.COMMENT_TYPE,
|
||||
value: trimmedComment
|
||||
}
|
||||
dispatch(addCommentToTicket(props?.route?.params?.ticketId, trimmedComment, updateComment));
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior='padding'
|
||||
style={[GenericStyles.fill, GenericStyles.whiteBackground]}>
|
||||
<NavigationHeader
|
||||
title={'Task details'}
|
||||
onBack={handleBackPress}
|
||||
subTitleStyle={styles.navigationContainerSubtitle}
|
||||
titleStyle={styles.navigationContainerTitle}
|
||||
containerStyle={styles.navigationContainerStyle}
|
||||
/>
|
||||
<ScrollView
|
||||
keyboardShouldPersistTaps='always'
|
||||
contentContainerStyle={GenericStyles.p16}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={false}
|
||||
onRefresh={onRefresh}
|
||||
colors={[COLORS.BASE.BLUE]} // Adjust the color as needed
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CustomerCard
|
||||
address={data?.supportRequestDetails?.address || ''}
|
||||
Dpd={data?.customerDetails?.currentDpd || 0}
|
||||
Pos={data?.customerDetails?.outstandingAmount || 0}
|
||||
customerName={data?.customerDetails?.customerName || ''}
|
||||
requestType = {data?.requestTypeLabel || ''}
|
||||
status={data?.status || ''}
|
||||
statusReadable={data?.statusLabel || ''}
|
||||
refId={data?.referenceId}
|
||||
isRefreshing={isRefreshing}
|
||||
isEditable={Boolean(props?.route?.params?.task === SCREEN_MAP.TASK_FOR_ME)}
|
||||
ticketStatusChange={loadData}
|
||||
requestedByuserType={data?.requestedBy?.userType!}
|
||||
/>
|
||||
{data?.status === Status.CLOSED || isRefreshing ? null :
|
||||
<View>
|
||||
<Heading dark type='h4' style={GenericStyles.mt16}>Activity</Heading>
|
||||
<TextFieldWithInput
|
||||
onPressSend={onSendHit}
|
||||
comment={comment}
|
||||
setComment={setComment}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
|
||||
<RenderCommentsTimeline
|
||||
comments={data?.activityLogs}
|
||||
/>
|
||||
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
callingType: {
|
||||
fontSize: 14,
|
||||
fontWeight: '400',
|
||||
letterSpacing: -.175,
|
||||
lineHeight: 20
|
||||
},
|
||||
navigationContainerStyle: {
|
||||
paddingVertical: 9
|
||||
},
|
||||
navigationContainerSubtitle: {
|
||||
marginTop: 0
|
||||
},
|
||||
navigationContainerTitle: {
|
||||
fontWeight: '500',
|
||||
lineHeight: 20
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default RequestDetail
|
||||
213
src/screens/cosmosSupport/RequestSupport.tsx
Normal file
213
src/screens/cosmosSupport/RequestSupport.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
import FullScreenLoaderWrapper from '@common/FullScreenLoaderWrapper';
|
||||
import { clearNavigationStack, goBack, navigateToScreen } from '@components/utlis/navigationUtlis';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import Button from '@rn-ui-lib/components/Button';
|
||||
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
|
||||
import PressableChip from '@rn-ui-lib/components/PressableChip';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import TextInput from '@rn-ui-lib/components/TextInput';
|
||||
import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader';
|
||||
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { SafeAreaView, ScrollView, StyleSheet, View } from 'react-native';
|
||||
import { createTicket, getDataForTicketCreation } from './actions';
|
||||
import { CreateTicketPayload } from './constant/types';
|
||||
import { MAX_COMENT_LENGTH } from '@screens/cosmosSupport/constant';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
|
||||
import { From } from './ViewRequestHistory';
|
||||
|
||||
const LoadingChips = () => {
|
||||
return (
|
||||
<View style={[GenericStyles.row, GenericStyles.flexWrap]}>
|
||||
{[...Array(5)].map((type, index) => (
|
||||
<LineLoader
|
||||
key={index}
|
||||
style={[GenericStyles.mr8, GenericStyles.br8, styles.mv10]}
|
||||
height={32}
|
||||
width={100}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
interface RequestSupportProps {
|
||||
route: {
|
||||
params: {
|
||||
caseDetail: {
|
||||
customerName: string;
|
||||
loanAccountNumber: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const RequestSupport: React.FC<RequestSupportProps> = (props) => {
|
||||
const { caseDetail } = props?.route?.params;
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { isLoading, data } = useAppSelector((state) => ({
|
||||
isLoading: state?.cosmosSupport?.ticketCreationData?.isLoading,
|
||||
data: state?.cosmosSupport?.ticketCreationData?.ticketCreationData,
|
||||
}));
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
watch,
|
||||
reset,
|
||||
} = useForm({});
|
||||
|
||||
const watchRequestType = watch('requestType');
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getDataForTicketCreation(String(caseDetail?.loanAccountNumber)));
|
||||
}, []);
|
||||
|
||||
const handleCreation = () => {
|
||||
reset();
|
||||
goBack();
|
||||
navigateToScreen(CaseDetailStackEnum.VIEW_RequestHistory, {
|
||||
caseDetail: caseDetail,
|
||||
tabId: 0,
|
||||
from: From.TICKET_CREATION_SCREEN,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmitForm = (data: any) => {
|
||||
const payload: CreateTicketPayload = {
|
||||
supportRequestType: data?.requestType,
|
||||
supportRequestCreatorType: 'FE',
|
||||
loanAccountNumber: String(caseDetail?.loanAccountNumber),
|
||||
comment: data?.requestDescription,
|
||||
};
|
||||
dispatch(createTicket(payload, handleCreation));
|
||||
};
|
||||
|
||||
const handleBackPress = () => {
|
||||
goBack();
|
||||
};
|
||||
const noData = Boolean(data?.requestForm?.length && data?.requestForm?.length === 0);
|
||||
return (
|
||||
<SafeAreaView style={[GenericStyles.fill, GenericStyles.whiteBackground]}>
|
||||
<NavigationHeader
|
||||
title={'Create task'}
|
||||
onBack={handleBackPress}
|
||||
subTitleStyle={styles.navigationContainerSubtitle}
|
||||
titleStyle={styles.navigationContainerTitle}
|
||||
containerStyle={styles.navigationContainerStyle}
|
||||
/>
|
||||
|
||||
<ScrollView contentContainerStyle={GenericStyles.p16}>
|
||||
<View style={GenericStyles.fill}>
|
||||
<Text>
|
||||
Select task type <Text style={{ color: COLORS.TEXT.RED }}>*</Text>
|
||||
</Text>
|
||||
|
||||
<SuspenseLoader loading={noData} fallBack={<LoadingChips />}>
|
||||
<View style={[GenericStyles.row, GenericStyles.flexWrap]}>
|
||||
{data?.requestForm?.map((type, index) => (
|
||||
<Controller
|
||||
key={type.value}
|
||||
control={control}
|
||||
name="requestType"
|
||||
rules={{ required: true }}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<PressableChip
|
||||
key={index}
|
||||
label={type.label}
|
||||
onSelectionChange={(meta, data) => onChange(data)}
|
||||
checked={value === type.value}
|
||||
meta={type.value}
|
||||
textStyles={styles.lineHeight20}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</SuspenseLoader>
|
||||
|
||||
{noData ? null : (
|
||||
<Controller
|
||||
control={control}
|
||||
name="requestDescription"
|
||||
rules={{ required: false }}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<View style={[GenericStyles.fill, GenericStyles.pb8]}>
|
||||
<TextInput
|
||||
title="Comments (optional)"
|
||||
numberOfLines={5}
|
||||
style={styles.textInput}
|
||||
containerStyle={GenericStyles.mt24}
|
||||
multiline={true}
|
||||
placeholder="Enter comments..."
|
||||
onChangeText={onChange}
|
||||
value={value}
|
||||
maxLength={MAX_COMENT_LENGTH}
|
||||
/>
|
||||
<Text
|
||||
small
|
||||
light
|
||||
style={[styles.count, value?.length >= MAX_COMENT_LENGTH ? styles.error : {}]}
|
||||
>
|
||||
{value?.length || 0}/300
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
<View style={GenericStyles.p16}>
|
||||
<Button
|
||||
title="Create"
|
||||
style={[GenericStyles.mt16, GenericStyles.w100]}
|
||||
onPress={handleSubmit(handleSubmitForm)}
|
||||
disabled={!watchRequestType || isLoading}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
navigationContainerStyle: {
|
||||
paddingVertical: 9,
|
||||
},
|
||||
navigationContainerSubtitle: {
|
||||
marginTop: 0,
|
||||
},
|
||||
navigationContainerTitle: {
|
||||
fontWeight: '500',
|
||||
lineHeight: 20,
|
||||
},
|
||||
tagContainer: {
|
||||
borderRadius: 40,
|
||||
padding: 4,
|
||||
},
|
||||
textInput: {
|
||||
textAlignVertical: 'top',
|
||||
minHeight: 100,
|
||||
},
|
||||
mv10: {
|
||||
marginVertical: 10,
|
||||
},
|
||||
count: {
|
||||
position: 'absolute',
|
||||
bottom: -20,
|
||||
right: 0,
|
||||
},
|
||||
lineHeight20: {
|
||||
lineHeight: 20,
|
||||
},
|
||||
error: {
|
||||
color: COLORS.TEXT.YELLOW,
|
||||
},
|
||||
});
|
||||
|
||||
export default RequestSupport;
|
||||
25
src/screens/cosmosSupport/TagComponent.tsx
Normal file
25
src/screens/cosmosSupport/TagComponent.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import Tag, { TagVariant } from '@rn-ui-lib/components/Tag';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
interface ITagComponent {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const TagComponent: React.FC<ITagComponent> = (props) => {
|
||||
const { text } = props;
|
||||
return (
|
||||
<View style={[GenericStyles.ml4]}>
|
||||
<Tag
|
||||
variant={TagVariant.blue}
|
||||
text={text}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default TagComponent;
|
||||
187
src/screens/cosmosSupport/TaskForMe.tsx
Normal file
187
src/screens/cosmosSupport/TaskForMe.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { ListRenderItemInfo, RefreshControl, View, VirtualizedList } from 'react-native';
|
||||
import { ListItemLoading } from './';
|
||||
import CSAFilters from './CSAFilters';
|
||||
import ListItem from './ListItem';
|
||||
import { loadingData, SCREEN_MAP } from './constant';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import { fetchTicketList, getSummary } from './actions';
|
||||
import {
|
||||
setCaseLevelTicketsForMe,
|
||||
setCaseLevelTicketsForMeLoading,
|
||||
} from '@reducers/cosmosSupportSlice';
|
||||
import TaskForMeEmptyScreen from '@assets/icons/TaskForMeEmptyScreen';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import { From } from './ViewRequestHistory';
|
||||
import { ACKNOWLEDGMENT_STATUS, IFilterStatus } from './constant/types';
|
||||
import { _map } from '@rn-ui-lib/utils/common';
|
||||
import axios, { Axios, CancelTokenSource } from 'axios';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
|
||||
interface ListItem {
|
||||
referenceId: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface ITaskForMe {
|
||||
route: {
|
||||
key: keyof typeof SCREEN_MAP;
|
||||
title: string;
|
||||
};
|
||||
from?: string;
|
||||
isFocusedTab?: boolean;
|
||||
for: keyof typeof SCREEN_MAP;
|
||||
lan?: string;
|
||||
}
|
||||
|
||||
// Define the component type
|
||||
const TaskForMe: React.FC<ITaskForMe> = (props) => {
|
||||
const { from, lan, isFocusedTab } = props;
|
||||
const [isRefreshing, setRefreshing] = useState(false);
|
||||
const [filters, setFilters] = useState<IFilterStatus>();
|
||||
const dispatch = useAppDispatch();
|
||||
const isFocused = useIsFocused();
|
||||
const { userRefId, isLoading, data, filterTemplate } = useAppSelector((state) => ({
|
||||
userRefId: state.user.user?.referenceId,
|
||||
isLoading: state.cosmosSupport.caseLevelTickets.taskForMe.loading,
|
||||
data: state.cosmosSupport.caseLevelTickets.taskForMe.data,
|
||||
filterTemplate: state.cosmosSupport.filters
|
||||
}));
|
||||
|
||||
const searchCancelToken = useRef<CancelTokenSource>();
|
||||
|
||||
const onRefresh = () => {
|
||||
loadData();
|
||||
dispatch(getSummary({loanAccountNumber: lan}));
|
||||
};
|
||||
const successCallback = (data: any) => {
|
||||
dispatch(setCaseLevelTicketsForMe(data));
|
||||
dispatch(setCaseLevelTicketsForMeLoading(false));
|
||||
isRefreshing && setRefreshing(false);
|
||||
};
|
||||
|
||||
const failureCallback = () => {
|
||||
setRefreshing(false);
|
||||
dispatch(setCaseLevelTicketsForMeLoading(false));
|
||||
};
|
||||
|
||||
const getFiltersPayload = (filtersData?: IFilterStatus | null): Record<string, string[]> => {
|
||||
const { filter: selectedFilters, showUnread = false } = filtersData || {};
|
||||
const filtersPayload: Record<string, string[]> = {
|
||||
ASSIGNEES: [userRefId!],
|
||||
};
|
||||
if (from !== From.PROFILE && lan) {
|
||||
filtersPayload['LOAN_ACCOUNT_NUMBERS'] = [lan];
|
||||
}
|
||||
if (selectedFilters) {
|
||||
_map(selectedFilters, (filterName: string) => {
|
||||
const filter = selectedFilters[filterName];
|
||||
if (!filter || !filter.filters) {
|
||||
return;
|
||||
}
|
||||
let filtersPayloadData: Array<string> = []
|
||||
Object.keys(filter.filters)?.forEach((filterData) => {
|
||||
if(filter?.filters?.[filterData]) {
|
||||
filtersPayloadData.push(filterData)
|
||||
}
|
||||
})
|
||||
filtersPayload[filterName] = filtersPayloadData;
|
||||
});
|
||||
}
|
||||
if (showUnread) {
|
||||
filtersPayload['ASSIGNEE_COMMENTS_READ_STATUS'] = [ACKNOWLEDGMENT_STATUS.UNREAD];
|
||||
}
|
||||
return filtersPayload;
|
||||
};
|
||||
|
||||
const loadData = () => {
|
||||
dispatch(setCaseLevelTicketsForMeLoading(true));
|
||||
setRefreshing(true);
|
||||
const filter = getFiltersPayload(filters);
|
||||
searchCancelToken.current = axios.CancelToken.source();
|
||||
fetchTicketList({ filters: filter }, successCallback, failureCallback, searchCancelToken.current);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if ((!lan && from !== From.PROFILE) || !isFocused) return;
|
||||
if(lan) {
|
||||
dispatch(getSummary({loanAccountNumber: lan}));
|
||||
}
|
||||
loadData();
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_MY_TASKS_LOAD_SUCCESSFUL,{lan})
|
||||
}, [lan, isFocused]);
|
||||
|
||||
const handleFilterChange = (data: IFilterStatus) => {
|
||||
searchCancelToken?.current?.cancel();
|
||||
setFilters(data);
|
||||
setRefreshing(true);
|
||||
const filters = getFiltersPayload(data);
|
||||
searchCancelToken.current = axios.CancelToken.source();
|
||||
fetchTicketList({ filters }, successCallback, failureCallback, searchCancelToken.current);
|
||||
};
|
||||
|
||||
const apiCallInProgress = isRefreshing || isLoading;
|
||||
const renderItemLoadingState = () => <ListItemLoading />;
|
||||
const renderItem = (props: ListRenderItemInfo<any>) => (
|
||||
<ListItem
|
||||
form={from}
|
||||
isLastItem={props?.index === data?.length - 1}
|
||||
task={SCREEN_MAP.TASK_FOR_ME}
|
||||
loadData={loadData}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
const getItemCount = () => (apiCallInProgress ? loadingData.length : data.length);
|
||||
const getItem = (data: ListItem[], index: number) => data[index];
|
||||
|
||||
const isCaseScreen = [From.CASE_DETAIL_SCREEN, From.TICKET_CREATION_SCREEN].includes(from as From);
|
||||
return (
|
||||
<View style={[GenericStyles.fill, {paddingBottom: isCaseScreen ? 72 : 0}]}>
|
||||
{!isCaseScreen ? (
|
||||
<CSAFilters
|
||||
onFilterChange={handleFilterChange}
|
||||
filtersTemplate={filterTemplate?.TASKS_FOR_ME ?? {}}
|
||||
isFocusedTab={isFocusedTab}
|
||||
isTasksPresent={!!data?.length}
|
||||
/>
|
||||
) : null}
|
||||
<VirtualizedList
|
||||
style={GenericStyles.whiteBackground}
|
||||
contentContainerStyle={apiCallInProgress || !data.length ? GenericStyles.fill : null}
|
||||
data={apiCallInProgress ? loadingData : data}
|
||||
keyExtractor={(item) => item.referenceId}
|
||||
renderItem={apiCallInProgress ? renderItemLoadingState : renderItem}
|
||||
getItemCount={getItemCount}
|
||||
getItem={getItem}
|
||||
initialNumToRender={5}
|
||||
maxToRenderPerBatch={10}
|
||||
windowSize={10}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={false} onRefresh={onRefresh} colors={[COLORS.BASE.BLUE]} />
|
||||
}
|
||||
ListEmptyComponent={() => (
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.alignCenter,
|
||||
GenericStyles.justifyContentCenter,
|
||||
{ height: '80%' },
|
||||
]}
|
||||
>
|
||||
<TaskForMeEmptyScreen />
|
||||
<Text style={GenericStyles.mt10}>
|
||||
{Object.keys(filters ? filters?.filter : {})?.length
|
||||
? 'No results found!'
|
||||
: 'No active tasks for this customer'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskForMe;
|
||||
186
src/screens/cosmosSupport/TeleSupport.tsx
Normal file
186
src/screens/cosmosSupport/TeleSupport.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import {
|
||||
setCaseLevelTicketsForTele,
|
||||
setCaseLevelTicketsForTeleLoading,
|
||||
} from '@reducers/cosmosSupportSlice';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { ListRenderItemInfo, RefreshControl, View, VirtualizedList } from 'react-native';
|
||||
import { ListItemLoading } from './';
|
||||
import CSAFilters from './CSAFilters';
|
||||
import ListItem from './ListItem';
|
||||
import { fetchTicketList } from './actions';
|
||||
import { CASE_DETAIL_SCREEN, loadingData, SCREEN_MAP } from './constant';
|
||||
import TaskForMeEmptyScreen from '@assets/icons/TaskForMeEmptyScreen';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import { From } from './ViewRequestHistory';
|
||||
import { ACKNOWLEDGMENT_STATUS, IFilterStatus } from './constant/types';
|
||||
import { _map } from '@rn-ui-lib/utils/common';
|
||||
import axios, { CancelTokenSource } from 'axios';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
|
||||
interface ListItem {
|
||||
referenceId: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface ITaskForMe {
|
||||
route: {
|
||||
key: keyof typeof SCREEN_MAP;
|
||||
title: string;
|
||||
};
|
||||
from?: string;
|
||||
isFocusedTab?: boolean;
|
||||
forScreen: keyof typeof SCREEN_MAP;
|
||||
lan?: string;
|
||||
}
|
||||
|
||||
const TeleSupport: React.FC<ITaskForMe> = (props) => {
|
||||
const { from, route, lan, forScreen, isFocusedTab } = props;
|
||||
const searchCancelToken = useRef<CancelTokenSource>();
|
||||
const [filters, setFilters] = useState<IFilterStatus>();
|
||||
const { userRefId, isLoading, data, filterTemplate } = useAppSelector((state) => ({
|
||||
userRefId: state.user.user?.referenceId,
|
||||
isLoading: state.cosmosSupport.caseLevelTickets.taskForTele.loading,
|
||||
data: state.cosmosSupport.caseLevelTickets.taskForTele.data,
|
||||
filterTemplate: state.cosmosSupport.filters
|
||||
}));
|
||||
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
|
||||
const [isRefreshing, setRefreshing] = useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
const onRefresh = () => {
|
||||
loadData();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if ((!lan && from !== From.PROFILE) || !isFocused) return;
|
||||
loadData();
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_MY_TASKS_LOAD_SUCCESSFUL,{lan})
|
||||
}, [props.lan,isFocused]);
|
||||
|
||||
const getFiltersPayload = (filtersData?: IFilterStatus | null): Record<string, string[]> => {
|
||||
const { filter: selectedFilters, showUnread = false } = filtersData || {};
|
||||
const filtersPayload: Record<string, string[]> = {
|
||||
REQUESTERS: [userRefId!],
|
||||
};
|
||||
if (from !== From.PROFILE && lan) {
|
||||
filtersPayload['LOAN_ACCOUNT_NUMBERS'] = [lan];
|
||||
}
|
||||
if (selectedFilters) {
|
||||
_map(selectedFilters, (filterName: string) => {
|
||||
const filter = selectedFilters[filterName];
|
||||
if (!filter || !filter.filters) {
|
||||
return;
|
||||
}
|
||||
let filtersPayloadData: Array<string> = []
|
||||
Object.keys(filter.filters)?.forEach((filterData) => {
|
||||
if(filter?.filters?.[filterData]) {
|
||||
filtersPayloadData.push(filterData)
|
||||
}
|
||||
})
|
||||
filtersPayload[filterName] = filtersPayloadData;
|
||||
});
|
||||
}
|
||||
if (showUnread) {
|
||||
filtersPayload['REQUESTER_COMMENTS_READ_STATUS'] = [ACKNOWLEDGMENT_STATUS.UNREAD];
|
||||
}
|
||||
return filtersPayload;
|
||||
};
|
||||
|
||||
const loadData = () => {
|
||||
setRefreshing(true);
|
||||
const filter = getFiltersPayload(filters);
|
||||
if (from !== From.PROFILE && lan) {
|
||||
filter['LOAN_ACCOUNT_NUMBERS'] = [lan];
|
||||
}
|
||||
searchCancelToken.current = axios.CancelToken.source();
|
||||
fetchTicketList({ filters: filter }, successCallback, failureCallback, searchCancelToken.current);
|
||||
};
|
||||
|
||||
const apiCallInProgress = isRefreshing || isLoading;
|
||||
|
||||
const renderItemLoadingState = () => <ListItemLoading />;
|
||||
const renderItem = (props: ListRenderItemInfo<any>) => (
|
||||
<ListItem
|
||||
form={from}
|
||||
isLastItem={props?.index === data?.length - 1}
|
||||
task={SCREEN_MAP.TELE_SUPPORT}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
const getItemCount = () => (apiCallInProgress ? loadingData.length : data.length);
|
||||
const getItem = (data: ListItem[], index: number) => data[index];
|
||||
|
||||
const successCallback = (data: any) => {
|
||||
dispatch(setCaseLevelTicketsForTele(data));
|
||||
dispatch(setCaseLevelTicketsForTeleLoading(false));
|
||||
};
|
||||
|
||||
const failureCallback = () => {
|
||||
setRefreshing(false);
|
||||
dispatch(setCaseLevelTicketsForTeleLoading(false));
|
||||
};
|
||||
|
||||
const handleFilterChange = (data: IFilterStatus) => {
|
||||
searchCancelToken?.current?.cancel();
|
||||
setFilters(data);
|
||||
setRefreshing(true);
|
||||
const filters = getFiltersPayload(data);
|
||||
searchCancelToken.current = axios.CancelToken.source();
|
||||
fetchTicketList({ filters }, successCallback, failureCallback, searchCancelToken.current);
|
||||
};
|
||||
|
||||
const isCaseScreen = [From.CASE_DETAIL_SCREEN, From.TICKET_CREATION_SCREEN].includes(from as From);
|
||||
|
||||
return (
|
||||
<View style={[GenericStyles.fill, {paddingBottom: isCaseScreen ? 72 : 0}]}>
|
||||
{!isCaseScreen ? (
|
||||
<CSAFilters
|
||||
onFilterChange={handleFilterChange}
|
||||
filtersTemplate={filterTemplate?.TASKS_FOR_CSA ?? {}}
|
||||
isFocusedTab={isFocusedTab}
|
||||
isTasksPresent={!!data?.length}
|
||||
/>
|
||||
) : null}
|
||||
<VirtualizedList
|
||||
style={GenericStyles.whiteBackground}
|
||||
contentContainerStyle={apiCallInProgress || !data.length ? GenericStyles.fill : null}
|
||||
data={apiCallInProgress ? loadingData : data}
|
||||
keyExtractor={(item) => item.referenceId}
|
||||
renderItem={apiCallInProgress ? renderItemLoadingState : renderItem}
|
||||
getItemCount={getItemCount}
|
||||
getItem={getItem}
|
||||
initialNumToRender={5}
|
||||
maxToRenderPerBatch={10}
|
||||
windowSize={10}
|
||||
refreshControl={
|
||||
<RefreshControl refreshing={false} onRefresh={onRefresh} colors={[COLORS.BASE.BLUE]} />
|
||||
}
|
||||
ListEmptyComponent={() => (
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.justifyContentCenter,
|
||||
GenericStyles.alignCenter,
|
||||
{ height: '80%' },
|
||||
]}
|
||||
>
|
||||
<TaskForMeEmptyScreen />
|
||||
<Text style={GenericStyles.mt10}>
|
||||
{Object.keys(filters ? filters?.filter : {})?.length
|
||||
? 'No results found!'
|
||||
: 'No active tasks for this customer'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeleSupport;
|
||||
147
src/screens/cosmosSupport/TextFieldWithInput.tsx
Normal file
147
src/screens/cosmosSupport/TextFieldWithInput.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import TextInput from '@rn-ui-lib/components/TextInput';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import SendIcon from '@assets/icons/SendIcon';
|
||||
import Toast from 'react-native-toast-message';
|
||||
import { useAppSelector } from '@hooks';
|
||||
import FullScreenLoaderWrapper from '@common/FullScreenLoaderWrapper';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
|
||||
interface ITextFieldWithInput {
|
||||
onPressSend: (comment: string) => void;
|
||||
|
||||
setComment: React.Dispatch<React.SetStateAction<string>>;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
const TextFieldWithInput: React.FC<ITextFieldWithInput> = (props) => {
|
||||
const { onPressSend, comment, setComment } = props;
|
||||
const { addingComment, isRefreshing } = useAppSelector(state => ({
|
||||
addingComment: state.cosmosSupport.isSubmittingComment,
|
||||
isRefreshing: state.cosmosSupport.currentViewRequestTicket.loading,
|
||||
|
||||
}));
|
||||
const [isFocussed, setIsFocussed]= useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!addingComment) {
|
||||
setComment('');
|
||||
}
|
||||
}, [addingComment])
|
||||
|
||||
const handleOnFocus = () => {
|
||||
setIsFocussed(true);
|
||||
|
||||
}
|
||||
|
||||
const handleOnBlur= () => {
|
||||
setIsFocussed(false);
|
||||
}
|
||||
|
||||
|
||||
const onPress = () => {
|
||||
if (!comment) {
|
||||
Toast.show({
|
||||
type: 'error',
|
||||
text1: 'Comment is required',
|
||||
})
|
||||
return;
|
||||
}
|
||||
onPressSend(comment);
|
||||
}
|
||||
const disabled = addingComment || isRefreshing || !comment?.trim();
|
||||
return (
|
||||
<View style={[GenericStyles.columnDirection, GenericStyles.alignItemsFlexEnd]}>
|
||||
<View style={[
|
||||
GenericStyles.mt16,
|
||||
GenericStyles.mb8,
|
||||
GenericStyles.row,
|
||||
{
|
||||
borderTopColor: isFocussed ? COLORS.TEXT.BLUE : COLORS.BORDER.PRIMARY,
|
||||
borderTopWidth: 1,
|
||||
borderBottomColor: isFocussed ? COLORS.TEXT.BLUE : COLORS.BORDER.PRIMARY,
|
||||
borderBottomWidth: 1,
|
||||
borderRightColor: isFocussed ? COLORS.TEXT.BLUE : COLORS.BORDER.PRIMARY,
|
||||
borderRightWidth: 1,
|
||||
borderLeftColor:isFocussed ? COLORS.TEXT.BLUE : COLORS.BORDER.PRIMARY,
|
||||
borderLeftWidth: 1,
|
||||
borderRadius: 8,
|
||||
shadowColor: COLORS.TEXT.BLUE,
|
||||
shadowOffset: {width: 4, height: 4},
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 3,
|
||||
|
||||
}
|
||||
]}>
|
||||
|
||||
<TextInput
|
||||
numberOfLines={5}
|
||||
style={[styles.textInput, { flexBasis: '80%' }]}
|
||||
multiline={true}
|
||||
placeholder='Leave a comment...'
|
||||
onChangeText={setComment}
|
||||
value={comment}
|
||||
maxLength={300}
|
||||
disabled={isRefreshing}
|
||||
border={false}
|
||||
onFocus={handleOnFocus}
|
||||
onBlur={handleOnBlur}
|
||||
/>
|
||||
|
||||
<View style={[GenericStyles.columnDirection, GenericStyles.justifyContentFlexEnd]}>
|
||||
|
||||
<Pressable
|
||||
disabled={disabled}
|
||||
style={[styles.buttonStyle, GenericStyles.ml8, GenericStyles.mr10, disabled ? styles.disabledButton : styles.active]}
|
||||
onPress={onPress}
|
||||
>
|
||||
<SendIcon strokeColor={disabled ? '#e8e8e8' : COLORS.BASE.BLUE} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
|
||||
|
||||
</View>
|
||||
<Text small light style={ [GenericStyles.mr8, comment.length >=300 ? styles.error : {}]}>{comment.length}/300</Text>
|
||||
|
||||
<FullScreenLoaderWrapper
|
||||
loading={addingComment}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
active: {
|
||||
backgroundColor: COLORS.BACKGROUND.BLUE_LIGHT_2,
|
||||
},
|
||||
buttonStyle: {
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
height: 30,
|
||||
justifyContent: "center",
|
||||
marginBottom: 10,
|
||||
width: 30,
|
||||
},
|
||||
count: {
|
||||
bottom: -24,
|
||||
position: 'absolute',
|
||||
right: 48
|
||||
},
|
||||
disabledButton: {
|
||||
backgroundColor: COLORS.BASE.BLUE,
|
||||
},
|
||||
error: {
|
||||
color: COLORS.TEXT.YELLOW
|
||||
},
|
||||
textInput: {
|
||||
maxHeight: 100,
|
||||
textAlignVertical: 'top',
|
||||
}
|
||||
});
|
||||
|
||||
export default TextFieldWithInput;
|
||||
217
src/screens/cosmosSupport/ViewRequestHistory.tsx
Normal file
217
src/screens/cosmosSupport/ViewRequestHistory.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
View,
|
||||
useWindowDimensions
|
||||
} from 'react-native';
|
||||
|
||||
import { goBack, navigateToScreen } from '@components/utlis/navigationUtlis';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import { setShouldHideTabBar } from '@reducers/allCasesSlice';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import { TabBar, TabView } from 'react-native-tab-view';
|
||||
import { TaskForMe, TeleSupport } from './';
|
||||
import TagComponent from './TagComponent';
|
||||
import { SCREEN_MAP } from './constant';
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
import Button from '@rn-ui-lib/components/Button';
|
||||
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
|
||||
|
||||
|
||||
type SCENE = keyof typeof SCREEN_MAP
|
||||
|
||||
|
||||
export enum From {
|
||||
PROFILE = 'profile',
|
||||
CASE_DETAIL = 'caseDetail',
|
||||
CASE_DETAIL_SCREEN = 'caseDetailScreen',
|
||||
TICKET_CREATION_SCREEN = 'ticketCreationScreen',
|
||||
}
|
||||
|
||||
|
||||
interface IViewRequestHistory {
|
||||
route: {
|
||||
params: {
|
||||
caseDetail: {
|
||||
customerName: string;
|
||||
loanAccountNumber: string;
|
||||
};
|
||||
from?: From;
|
||||
tabId?: number
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const ViewRequestHistory = (props: IViewRequestHistory) => {
|
||||
const { tabId, from, caseDetail } = props?.route?.params;
|
||||
const layout = useWindowDimensions();
|
||||
const [index, setIndex] = React.useState(tabId || 0);
|
||||
const isFocused = useIsFocused();
|
||||
const dispatch = useAppDispatch();
|
||||
const {summary} = useAppSelector(state => ({
|
||||
summary: state.cosmosSupport.caseSummary
|
||||
}))
|
||||
const [routes] = React.useState([
|
||||
{ key: SCREEN_MAP.TELE_SUPPORT, title: 'Task for tele' },
|
||||
{ key: SCREEN_MAP.TASK_FOR_ME, title: 'Tasks for me' },
|
||||
]);
|
||||
|
||||
const hasUpdates = (scene: SCENE) => {
|
||||
if(scene === SCREEN_MAP.TASK_FOR_ME && summary?.assigneeSummaryDetails.newTicketUpdatesCount > 0){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
const getCount = (scene: SCENE) => {
|
||||
if(scene === SCREEN_MAP.TASK_FOR_ME){
|
||||
return summary?.assigneeSummaryDetails.newTicketUpdatesCount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const renderScene = ({ route }: { route: { key: string } }) => {
|
||||
switch (route.key) {
|
||||
case SCREEN_MAP.TELE_SUPPORT:
|
||||
return (
|
||||
<TeleSupport
|
||||
route={{
|
||||
key: SCREEN_MAP.TELE_SUPPORT as SCENE,
|
||||
title: "Task for tele"
|
||||
}}
|
||||
isFocusedTab={index === 0}
|
||||
from={from}
|
||||
forScreen={SCREEN_MAP.TELE_SUPPORT as SCENE}
|
||||
lan={caseDetail?.loanAccountNumber}
|
||||
/>
|
||||
);
|
||||
case SCREEN_MAP.TASK_FOR_ME:
|
||||
return (
|
||||
<TaskForMe
|
||||
route={{
|
||||
key: SCREEN_MAP.TASK_FOR_ME as SCENE,
|
||||
title: "Task for me"
|
||||
}}
|
||||
isFocusedTab={index === 1}
|
||||
from={from}
|
||||
for={SCREEN_MAP.TASK_FOR_ME as SCENE}
|
||||
lan={caseDetail?.loanAccountNumber}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(from === From.TICKET_CREATION_SCREEN && typeof tabId === 'number' && isFocused) {
|
||||
setIndex(tabId)
|
||||
}
|
||||
}, [tabId, isFocused])
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setShouldHideTabBar(true))
|
||||
return () => {
|
||||
dispatch(setShouldHideTabBar(false))
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={GenericStyles.fill}>
|
||||
<NavigationHeader
|
||||
onBack={goBack}
|
||||
title={from === From.PROFILE ? 'All task' : `Task log`}
|
||||
subTitle={from === From.PROFILE ? undefined : `For ${props?.route?.params?.caseDetail?.customerName}`}
|
||||
subTitleStyle={styles.navigationContainerSubtitle}
|
||||
titleStyle={styles.navigationContainerTitle}
|
||||
containerStyle={styles.navigationContainerStyle}
|
||||
/>
|
||||
<TabView
|
||||
lazy
|
||||
navigationState={{ index, routes }}
|
||||
renderScene={renderScene}
|
||||
onIndexChange={setIndex}
|
||||
initialLayout={{ width: layout.width }}
|
||||
renderTabBar={(props) => <TabBar
|
||||
onTabPress={({ route }) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TASKS_TAB_BUTTON_CLICKED, route);
|
||||
}}
|
||||
renderLabel={({ route, color }) => (
|
||||
<View
|
||||
style={
|
||||
GenericStyles.row
|
||||
}
|
||||
>
|
||||
<View style={[GenericStyles.row, GenericStyles.centerAligned]}>
|
||||
<Text style={{ color }}>
|
||||
{route.title}
|
||||
</Text>
|
||||
{hasUpdates(route.key as SCENE)&&<TagComponent text={`${getCount(route.key as SCENE)} new`} />}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
labelStyle={styles.labelStyle}
|
||||
indicatorStyle={styles.indicatorStyle}
|
||||
inactiveColor={COLORS.TEXT.LIGHT}
|
||||
activeColor={COLORS.TEXT.BLUE}
|
||||
style={GenericStyles.whiteBackground}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{[From.CASE_DETAIL_SCREEN, From.TICKET_CREATION_SCREEN].includes(from) ? (
|
||||
<View style={[GenericStyles.row, GenericStyles.fill, styles.buttonContainer]}>
|
||||
<Button
|
||||
style={[GenericStyles.fill]}
|
||||
title="Create task for tele"
|
||||
variant="primary"
|
||||
onPress={() =>
|
||||
navigateToScreen(CaseDetailStackEnum.RAISE_REQUEST, { caseDetail: caseDetail })
|
||||
}
|
||||
testID={'create_task'}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewRequestHistory;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
indicatorStyle: {
|
||||
backgroundColor: COLORS.BASE.BLUE
|
||||
},
|
||||
labelStyle: {
|
||||
textTransform: 'none'
|
||||
},
|
||||
navigationContainerStyle: {
|
||||
paddingVertical: 9
|
||||
},
|
||||
navigationContainerSubtitle: {
|
||||
marginTop: 0
|
||||
},
|
||||
navigationContainerTitle: {
|
||||
fontWeight: '500',
|
||||
lineHeight: 20
|
||||
},
|
||||
buttonContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
padding: 16,
|
||||
paddingTop: 12,
|
||||
justifyContent: 'space-around',
|
||||
alignItems: 'center',
|
||||
backgroundColor: COLORS.BACKGROUND.PRIMARY,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: COLORS.BORDER.PRIMARY,
|
||||
},
|
||||
});
|
||||
179
src/screens/cosmosSupport/actions.ts
Normal file
179
src/screens/cosmosSupport/actions.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import axiosInstance, { API_STATUS_CODE, API_URLS, ApiKeys, getApiUrl } from "@components/utlis/apiHelper";
|
||||
import { logError } from "@components/utlis/errorUtils";
|
||||
|
||||
import { ActivityLog, CreateCommentPayload, CreateTicketPayload, GetTicketCreationPayload, ICount, RequestTicket, SummaryPayload, UpDateTicketPayload } from "./constant/types";
|
||||
import store, { AppDispatch } from "@store";
|
||||
import { toast } from "@rn-ui-lib/components/toast";
|
||||
import { loadingSingleTicket, setCaseSummary, setMarkingTicketAsResolved, setSingleViewRequestTicket, setSubmittingComment, setTicketCreationData, setTicketCreationDataForFrom, updateComment } from "@reducers/cosmosSupportSlice";
|
||||
import { Dispatch } from "redux";
|
||||
import { GenericFunctionArgs } from "@common/GenericTypes";
|
||||
import { Mapping } from "./constant";
|
||||
import { noop } from "@rn-ui-lib/utils/common";
|
||||
import { CancelTokenSource } from "axios";
|
||||
import { addClickstreamEvent } from "@services/clickstreamEventService";
|
||||
import { CLICKSTREAM_EVENT_NAMES } from "@common/Constants";
|
||||
import { err } from "react-native-svg/lib/typescript/xml";
|
||||
|
||||
|
||||
export const getDataForTicketCreation = (lan: string) => (dispatch: Dispatch) => {
|
||||
|
||||
dispatch(setTicketCreationData(true));
|
||||
const url = getApiUrl(ApiKeys.GET_FORM_OPTIONS);
|
||||
axiosInstance.get(url, { params: { loanAccountNumber: lan, supportRequestUserType: Mapping.USER_TYPE } }).then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
const requestForm = response?.data?.requestForm || [];
|
||||
const convertedData = requestForm.map((item: any) => {
|
||||
const [value, label] = Object.entries(item)[0];
|
||||
return { label, value };
|
||||
});
|
||||
const requestTo = response?.data?.requestTo?.name;
|
||||
dispatch(setTicketCreationDataForFrom({ requestForm: convertedData, requestFor: requestTo }))
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, "error in getting data for ticket creation");
|
||||
})
|
||||
.finally(() => {
|
||||
dispatch(setTicketCreationData(false));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const createTicket = (payload: CreateTicketPayload, clicked: GenericFunctionArgs) => (dispatch: Dispatch) => {
|
||||
dispatch(setTicketCreationData(true));
|
||||
const url = getApiUrl(ApiKeys.CREATE_TICKET);
|
||||
axiosInstance.post(url, payload).then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
toast({
|
||||
text1: "Ticket created successfully",
|
||||
type: "success"
|
||||
});
|
||||
clicked();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, "error in creating ticket");
|
||||
})
|
||||
.finally(() => {
|
||||
dispatch(setTicketCreationData(false));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const updateTicket = (payload: any, ticketId: string) => (dispatch: Dispatch) => {
|
||||
dispatch(setMarkingTicketAsResolved(true));
|
||||
const url = getApiUrl(ApiKeys.UPDATE_TICKET_STATUS, { ticketReferenceId: ticketId });
|
||||
axiosInstance.put(url, payload).then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
toast({
|
||||
text1: "Ticket updated successfully",
|
||||
type: "success"
|
||||
});
|
||||
dispatch(fetchSingleTicket(ticketId) as any);
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TASK_MARKED_SUCCESSFUL, { ticketId })
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, "error in updating ticket");
|
||||
})
|
||||
.finally(() => {
|
||||
dispatch(setMarkingTicketAsResolved(false));
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
export const fetchSingleTicket = (ticketReferenceId: string, isPolling?: boolean) => (dispatch: Dispatch) => {
|
||||
isPolling ? noop() : dispatch(loadingSingleTicket(true));
|
||||
const url = getApiUrl(ApiKeys.GET_CSA_SINGLE_TICKET, { ticketReferenceId });
|
||||
|
||||
axiosInstance.get(url).then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
dispatch(setSingleViewRequestTicket(response?.data));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, "error in fetching single ticket");
|
||||
})
|
||||
.finally(() => {
|
||||
isPolling ? noop() : dispatch(loadingSingleTicket(false));
|
||||
});
|
||||
}
|
||||
|
||||
export const fetchTicketList = (payload: any, callbackOnSuccess: (data: any) => void, callbackOnFailure: GenericFunctionArgs, cancelToken: CancelTokenSource) => {
|
||||
const url = getApiUrl(ApiKeys.GET_CSA_TICKETS);
|
||||
axiosInstance.post(url, payload, { cancelToken: cancelToken.token }).then((response) => {
|
||||
if (response?.status === API_STATUS_CODE.OK) {
|
||||
callbackOnSuccess(response.data?.data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, "error in fetching ticket list");
|
||||
})
|
||||
.finally(() => {
|
||||
callbackOnFailure();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const acknowledgeTicket = (ticketReferenceId: string) =>(dispatch:Dispatch) => {
|
||||
const url = getApiUrl(ApiKeys.ACKNOWLEDGE_TICKET, { ticketReferenceId });
|
||||
axiosInstance.patch(url)
|
||||
.catch((error) => {
|
||||
logError(error, "error in acknowledging ticket");
|
||||
})
|
||||
}
|
||||
|
||||
export const addCommentToTicket = (ticketReferenceId: string, comment: string, commentPayload: ActivityLog) => (dispatch: Dispatch) => {
|
||||
dispatch(setSubmittingComment(true));
|
||||
const url = getApiUrl(ApiKeys.ADD_COMMENT, { ticketReferenceId });
|
||||
axiosInstance.post(url, { comment })
|
||||
.then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
toast({
|
||||
text1: "Comment added successfully",
|
||||
type: "success"
|
||||
});
|
||||
dispatch(updateComment(commentPayload))
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, "error in adding comment to ticket");
|
||||
})
|
||||
.finally(() => {
|
||||
dispatch(setSubmittingComment(false));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export const getSummary = (payload: SummaryPayload) => (dispatch: Dispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GET_UPDATE_COUNT);
|
||||
|
||||
let shouldSendPayload = false;
|
||||
|
||||
if (payload.loanAccountNumber) {
|
||||
shouldSendPayload = true;
|
||||
}
|
||||
|
||||
|
||||
axiosInstance.get(url, {
|
||||
params: shouldSendPayload ? payload : {}
|
||||
|
||||
}).then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
dispatch(setCaseSummary(response?.data))
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, "error in fetching summary");
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
49
src/screens/cosmosSupport/constant/index.ts
Normal file
49
src/screens/cosmosSupport/constant/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { TFilterOptions } from '@components/filters/Filters';
|
||||
import { TagVariant } from '@rn-ui-lib/components/Tag';
|
||||
|
||||
export const SCREEN_MAP = {
|
||||
TELE_SUPPORT: 'TELE_SUPPORT',
|
||||
TASK_FOR_ME: 'TASK_FOR_ME',
|
||||
};
|
||||
|
||||
export enum RequestTypeReadableString {
|
||||
VISIT = 'VISIT',
|
||||
SKIP_TRACING = 'Skip Tracing',
|
||||
SOFT_CALLING = 'Soft Calling',
|
||||
HARD_CALLING = 'Hard Calling',
|
||||
PTP_FOLLOW_UP = 'PTP Follow Up',
|
||||
}
|
||||
|
||||
export enum StatusColorMapping {
|
||||
CLOSED = TagVariant.success,
|
||||
TODO = TagVariant.alert,
|
||||
IN_PROGRESS = TagVariant.blue,
|
||||
}
|
||||
|
||||
export enum FilterOptions {
|
||||
pending = 'pending',
|
||||
done = 'done',
|
||||
}
|
||||
|
||||
export enum Mapping {
|
||||
USER_TYPE = 'FE',
|
||||
COMMENT_TYPE = 'COMMENT_ADDITION',
|
||||
FORM_PROFILE = 'profile',
|
||||
CLOSED = 'CLOSED',
|
||||
}
|
||||
|
||||
export const loadingData = Array.from({ length: 10 }, (_, index) => ({
|
||||
referenceId: index.toString(),
|
||||
text: `Item ${index + 1}`,
|
||||
}));
|
||||
|
||||
export const getFiltersCount = (selectedFilter: TFilterOptions) => {
|
||||
let feedbackFilterCount = 0;
|
||||
Object.keys(selectedFilter)?.forEach(
|
||||
(filter) => (feedbackFilterCount += selectedFilter[filter]?.selectedFilterCount ?? 0)
|
||||
);
|
||||
return feedbackFilterCount;
|
||||
};
|
||||
|
||||
export const MAX_COMENT_LENGTH = 300;
|
||||
export const CASE_DETAIL_SCREEN = 'caseDetailScreen'
|
||||
157
src/screens/cosmosSupport/constant/types.ts
Normal file
157
src/screens/cosmosSupport/constant/types.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import FilterOptions from '@components/filters/FilterOptions';
|
||||
import { TFilterOptions } from '@components/filters/Filters';
|
||||
|
||||
enum RequestType {
|
||||
VISIT = 'VISIT',
|
||||
SKIP_TRACING = 'SKIP_TRACING',
|
||||
SOFT_CALLING = 'SOFT_CALLING',
|
||||
HARD_CALLING = 'HARD_CALLING',
|
||||
PTP_FOLLOW_UP = 'PTP_FOLLOW_UP',
|
||||
}
|
||||
|
||||
export enum Status {
|
||||
TO_DO = 'TODO',
|
||||
CLOSED = 'CLOSED',
|
||||
IN_PROGRESS = 'IN_PROGRESS',
|
||||
}
|
||||
|
||||
export enum RequestorType {
|
||||
CSA = 'CSA',
|
||||
FE = 'FE',
|
||||
}
|
||||
|
||||
export interface CreateTicketPayload {
|
||||
supportRequestType: RequestType;
|
||||
supportRequestCreatorType: keyof typeof RequestorType;
|
||||
loanAccountNumber: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface CreateCommentPayload {
|
||||
ticketId: string;
|
||||
request: {
|
||||
comment: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpDateTicketPayload {
|
||||
ticketId: string;
|
||||
request: {
|
||||
status: keyof typeof Status;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetTicketCreationPayload {
|
||||
requestForm?: Array<{
|
||||
label: string;
|
||||
value: string;
|
||||
}>;
|
||||
requestFor?: string;
|
||||
}
|
||||
|
||||
export interface GetAllTicketsPayload {
|
||||
filters: GetAllTicketFilters;
|
||||
}
|
||||
|
||||
export enum ACKNOWLEDGMENT_STATUS {
|
||||
UNACK = 'UNACK',
|
||||
ACK = 'ACK',
|
||||
UNREAD = 'UNREAD',
|
||||
}
|
||||
|
||||
export interface GetAllTicketFilters {
|
||||
REQUESTERS?: Array<string>;
|
||||
ASSIGNEES?: Array<string>;
|
||||
LOAN_ACCOUNT_NUMBERS?: Array<number>;
|
||||
STATUSES?: Array<Status>;
|
||||
ACKNOWLEDGMENT_STATUS?: ACKNOWLEDGMENT_STATUS[] | null;
|
||||
}
|
||||
|
||||
export interface RequestTicket {
|
||||
referenceId: string;
|
||||
status: string;
|
||||
statusLabel: string;
|
||||
requestType: string;
|
||||
requestTypeLabel: string;
|
||||
loanAccountNumber: string;
|
||||
createdAt: number;
|
||||
requestedBy: UserTicket;
|
||||
assignedTo: UserTicket;
|
||||
customerDetails: CustomerDetailsTicket;
|
||||
activityLogs: ActivityLog[];
|
||||
supportRequestDetails: {
|
||||
address: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UserTicket {
|
||||
referenceId: string;
|
||||
name: string;
|
||||
userType: string;
|
||||
}
|
||||
|
||||
export interface CustomerDetailsTicket {
|
||||
customerReferenceId: string;
|
||||
outstandingAmount: number;
|
||||
currentDpd: number;
|
||||
unpaidDays: number | null;
|
||||
customerName: string;
|
||||
}
|
||||
|
||||
export interface ActivityLog {
|
||||
type: string;
|
||||
activityBy: UserTicket;
|
||||
activityAt: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IFilterStatus {
|
||||
filter: TFilterOptions;
|
||||
showUnread?: boolean;
|
||||
}
|
||||
|
||||
export interface ICount {
|
||||
requesterSummaryDetails: {
|
||||
newTicketsCount: number;
|
||||
newUpdatesCount: number;
|
||||
};
|
||||
assigneeSummaryDetails: {
|
||||
newTicketsCount: number;
|
||||
newUpdatesCount: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SummaryPayload {
|
||||
loanAccountNumber?: string;
|
||||
}
|
||||
|
||||
export interface SummaryDetails {
|
||||
newTicketsCount: number;
|
||||
newTicketUpdatesCount: number;
|
||||
}
|
||||
|
||||
export interface Summary {
|
||||
requesterSummaryDetails: SummaryDetails;
|
||||
assigneeSummaryDetails: SummaryDetails;
|
||||
aggregatedSummary: SummaryDetails;
|
||||
}
|
||||
|
||||
interface IOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ICsaFilter {
|
||||
SUPPORT_REQUEST_TYPES: {
|
||||
displayText: string;
|
||||
options: IOption[];
|
||||
name: string;
|
||||
filterType: string;
|
||||
};
|
||||
STATUSES: {
|
||||
displayText: string;
|
||||
options: IOption[];
|
||||
name: string;
|
||||
filterType: string;
|
||||
};
|
||||
}
|
||||
20
src/screens/cosmosSupport/index.ts
Normal file
20
src/screens/cosmosSupport/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import ViewRequestHistory from "./ViewRequestHistory";
|
||||
import TaskForMe from "./TaskForMe";
|
||||
import TeleSupport from "./TeleSupport";
|
||||
import ListItemLoading from "./ListItemLoading";
|
||||
import RequestDetail from "./RequestDetail";
|
||||
import CustomerCard from "./CustomerCard";
|
||||
import TextFieldWithInput from "./TextFieldWithInput";
|
||||
import RelativeTime from "./RelativeTime";
|
||||
|
||||
|
||||
export {
|
||||
ViewRequestHistory,
|
||||
TaskForMe,
|
||||
TeleSupport,
|
||||
ListItemLoading,
|
||||
RequestDetail,
|
||||
CustomerCard,
|
||||
TextFieldWithInput,
|
||||
RelativeTime
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
} from '@hooks/useFCM/notificationHelperFunctions';
|
||||
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
|
||||
import { PageRouteEnum } from '@screens/auth/ProtectedRouter';
|
||||
import { SCREEN_MAP } from '@screens/cosmosSupport/constant';
|
||||
import { resetSingleViewRequestTicket } from '@reducers/cosmosSupportSlice';
|
||||
|
||||
export interface INotification {
|
||||
id: string;
|
||||
@@ -50,6 +52,9 @@ export interface INotification {
|
||||
callbackDate?: string;
|
||||
phoneNumber?: string;
|
||||
callbackTime?: string;
|
||||
loanAccountNumber?: string;
|
||||
requestType?: string;
|
||||
supportRequestReferenceId?: string;
|
||||
};
|
||||
template: {
|
||||
id: number;
|
||||
@@ -88,6 +93,9 @@ const NotificationItem: React.FC<INotificationProps> = ({ data }) => {
|
||||
const isOnline = useIsOnline();
|
||||
|
||||
const customerName = params?.customerName || '';
|
||||
const isCsaNotification =
|
||||
templateName === NotificationTypes.SUPPORT_REQUEST_RECEIVED ||
|
||||
templateName === NotificationTypes.SUPPORT_REQUEST_RESOLVED;
|
||||
|
||||
const handleNotificationAction = () => {
|
||||
const payload = {
|
||||
@@ -128,6 +136,7 @@ const NotificationItem: React.FC<INotificationProps> = ({ data }) => {
|
||||
navigateToScreen(BOTTOM_TAB_ROUTES.Profile);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!collectionCaseId || !clickable) {
|
||||
return;
|
||||
}
|
||||
@@ -135,6 +144,19 @@ const NotificationItem: React.FC<INotificationProps> = ({ data }) => {
|
||||
if (!caseDetails) {
|
||||
return;
|
||||
}
|
||||
const ticketRefId = data?.params?.supportRequestReferenceId;
|
||||
if (isCsaNotification && ticketRefId) {
|
||||
const isAssginedToFE = templateName === NotificationTypes.SUPPORT_REQUEST_RECEIVED;
|
||||
dispatch(resetSingleViewRequestTicket(null))
|
||||
navigateToScreen(PageRouteEnum.CASE_DETAIL_STACK, {
|
||||
screen: CaseDetailStackEnum.VIEW_REQUEST_DETAIL,
|
||||
params: {
|
||||
ticketId: ticketRefId,
|
||||
task: isAssginedToFE ? SCREEN_MAP.TASK_FOR_ME : SCREEN_MAP.TELE_SUPPORT,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { caseType } = caseDetails;
|
||||
if (caseType === CaseAllocationType.COLLECTION_CASE) {
|
||||
const notificationAction = NotificationTemplateActionMap[templateName];
|
||||
@@ -172,7 +194,7 @@ const NotificationItem: React.FC<INotificationProps> = ({ data }) => {
|
||||
>
|
||||
{notificationIcon}
|
||||
<View style={[GenericStyles.pl16, GenericStyles.flex80]}>
|
||||
{customerName ? (
|
||||
{customerName && !isCsaNotification ? (
|
||||
<Heading type="h5" dark>
|
||||
{customerName}
|
||||
</Heading>
|
||||
|
||||
@@ -5,9 +5,9 @@ import { INotification } from './NotificationItem';
|
||||
import { NotificationTypes } from './constants';
|
||||
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
|
||||
import Heading from '../../../RN-UI-LIB/src/components/Heading';
|
||||
import dayjs from "dayjs";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import {BUSINESS_DATE_FORMAT, CUSTOM_ISO_DATE_FORMAT} from "@rn-ui-lib/utils/dates";
|
||||
import dayjs from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import { BUSINESS_DATE_FORMAT, CUSTOM_ISO_DATE_FORMAT } from '@rn-ui-lib/utils/dates';
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
interface INotificationTemplateProps {
|
||||
@@ -33,7 +33,8 @@ const NotificationTemplate: React.FC<INotificationTemplateProps> = ({ data }) =>
|
||||
caseCount,
|
||||
cashCommitted,
|
||||
visitsCommitted,
|
||||
phoneNumber
|
||||
phoneNumber,
|
||||
requestType,
|
||||
} = params || {};
|
||||
|
||||
switch (templateName) {
|
||||
@@ -216,10 +217,46 @@ const NotificationTemplate: React.FC<INotificationTemplateProps> = ({ data }) =>
|
||||
case NotificationTypes.AGENT_DAILY_COMMITMENT:
|
||||
return (
|
||||
<Text>
|
||||
<Text light>Your commitment for today has been submitted. Cash commitment - </Text>
|
||||
<Text light>Your commitment for today has been submitted. Cash commitment - </Text>
|
||||
{formatAmount(cashCommitted)}. <Text light>Visit Commitment - </Text> {visitsCommitted}{' '}
|
||||
</Text>
|
||||
);
|
||||
|
||||
case NotificationTypes.SUPPORT_REQUEST_RECEIVED:
|
||||
return (
|
||||
<View>
|
||||
<Text bold dark>
|
||||
{requestType} task
|
||||
</Text>
|
||||
<Text light>
|
||||
for{' '}
|
||||
<Text bold dark>
|
||||
{customerName}
|
||||
</Text>{' '}
|
||||
assigned
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
case NotificationTypes.SUPPORT_REQUEST_RESOLVED:
|
||||
return (
|
||||
<View>
|
||||
<Text bold dark>
|
||||
{requestType} request
|
||||
</Text>
|
||||
<Text light>
|
||||
for{' '}
|
||||
<Text bold dark>
|
||||
{customerName}
|
||||
</Text>{' '}
|
||||
marked{' '}
|
||||
<Text bold dark>
|
||||
Done
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
case NotificationTypes.BOT_PROMISED_TO_PAY_NOTIFICATION:
|
||||
return (
|
||||
<Text>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import CSARequestIcon from '@assets/icons/CSARequestIcon';
|
||||
import IDCardApproveIcon from '../../../RN-UI-LIB/src/Icons/IDCardApproveIcon';
|
||||
import IDCardRejectIcon from '../../../RN-UI-LIB/src/Icons/IDCardRejectIcon';
|
||||
import NewAddressIcon from '../../../RN-UI-LIB/src/Icons/NewAddressIcon';
|
||||
@@ -7,6 +8,7 @@ import PaymentFailedIcon from '../../../RN-UI-LIB/src/Icons/PaymentFailedIcon';
|
||||
import PaymentSuccessIcon from '../../../RN-UI-LIB/src/Icons/PaymentSuccessIcon';
|
||||
import PromiseToPayIcon from '../../../RN-UI-LIB/src/Icons/PromiseToPayIcon';
|
||||
import NotificationVisitPlanIcon from '../../assets/icons/NotificationVisitPlanIcon';
|
||||
import CSAIncomingRequestIcon from '@assets/icons/CSAIncomingRequestIcon';
|
||||
import AddressIcon from "@assets/icons/AddressIcon";
|
||||
import CallIcon from "@rn-ui-lib/icons/CallIcon";
|
||||
import CallbackNotificationIcon from "@assets/icons/CallbackNotificationIcon";
|
||||
@@ -35,6 +37,8 @@ export enum NotificationTypes {
|
||||
AGENT_ID_REJECTED_TEMPLATE = 'AGENT_ID_REJECTED_TEMPLATE',
|
||||
AGENT_COMMITMENTS_VISIT_CASH = 'AGENT_COMMITMENTS_VISIT_CASH',
|
||||
AGENT_DAILY_COMMITMENT = 'AGENT_DAILY_COMMITMENT',
|
||||
SUPPORT_REQUEST_RECEIVED = 'SUPPORT_REQUEST_RECEIVED',
|
||||
SUPPORT_REQUEST_RESOLVED = 'SUPPORT_REQUEST_RESOLVED',
|
||||
BOT_REMINDER_CALLBACK_NOTIFICATION = 'REQUESTED_CALLBACK_GEN_AI_BOT_FIELD_SCHEDULED_NOTIFICATION_TEMPLATE',
|
||||
BOT_PROMISED_TO_PAY_NOTIFICATION = 'PROMISED_TO_PAY_GEN_AI_BOT_FIELD_SCHEDULED_NOTIFICATION_TEMPLATE',
|
||||
BOT_REQUESTED_VISIT_NOTIFICATION = 'REVISIT_GEN_AI_BOT_SCHEDULED_NOTIFICATION_TEMPLATE',
|
||||
@@ -64,6 +68,8 @@ export const NotificationIconsMap = {
|
||||
[NotificationTypes.AGENT_ID_REJECTED_TEMPLATE]: <IDCardRejectIcon />,
|
||||
[NotificationTypes.AGENT_COMMITMENTS_VISIT_CASH]: <NotificationIcon />,
|
||||
[NotificationTypes.AGENT_DAILY_COMMITMENT]: <NotificationIcon />,
|
||||
[NotificationTypes.SUPPORT_REQUEST_RECEIVED]: <CSAIncomingRequestIcon />,
|
||||
[NotificationTypes.SUPPORT_REQUEST_RESOLVED]: <CSARequestIcon />,
|
||||
[NotificationTypes.BOT_PROMISED_TO_PAY_NOTIFICATION]: <PromiseToPayIcon />,
|
||||
[NotificationTypes.BOT_REMINDER_CALLBACK_NOTIFICATION]: <CallbackNotificationIcon />,
|
||||
[NotificationTypes.BOT_REQUESTED_VISIT_NOTIFICATION]: <AddressIcon />,
|
||||
|
||||
168
src/services/ImageProcessor.ts
Normal file
168
src/services/ImageProcessor.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { LocalStorageKeys } from "@common/Constants";
|
||||
import { getAsyncStorageItem, setAsyncStorageItem } from "@components/utlis/commonFunctions";
|
||||
import { logError } from "@components/utlis/errorUtils";
|
||||
|
||||
export enum FILE_Enums {
|
||||
FILE_SIZE,
|
||||
CREATED_AT,
|
||||
UPDATED_AT,
|
||||
};
|
||||
|
||||
export enum MimeTypes {
|
||||
IMAGE = 'image',
|
||||
VIDEO = 'video',
|
||||
AUDIO = 'audio',
|
||||
TEXT = 'text',
|
||||
PDF = 'pdf',
|
||||
ZIP = 'ZIP',
|
||||
OTHER = 'other',
|
||||
};
|
||||
|
||||
const mimeTypes: { [key in MimeTypes]: string[] } = {
|
||||
[MimeTypes.IMAGE]: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml', 'image/bmp', 'image/tiff', 'image/x-icon', 'image/vnd.microsoft.icon', 'image/vnd.wap.wbmp', 'image/x-xbitmap'],
|
||||
[MimeTypes.VIDEO]: ['video/mp4', 'video/mpeg', 'video/quicktime'],
|
||||
[MimeTypes.AUDIO]: ['audio/mpeg', 'audio/ogg', 'audio/wav'],
|
||||
[MimeTypes.TEXT]: ['text/plain', 'text/html', 'text/css'],
|
||||
[MimeTypes.PDF]: ['application/pdf'],
|
||||
[MimeTypes.ZIP]: ['application/zip'],
|
||||
[MimeTypes.OTHER]: []
|
||||
};
|
||||
|
||||
export interface FileEntry {
|
||||
path: string;
|
||||
size: number;
|
||||
createdAt: number;
|
||||
updateAt: string;
|
||||
mimeType: MimeTypes;
|
||||
zipped: boolean;
|
||||
name: string;
|
||||
startOffset?: number;
|
||||
endOffset?: number;
|
||||
};
|
||||
|
||||
|
||||
export let filesStore = {} as {
|
||||
[id: string]: FileEntry
|
||||
};
|
||||
|
||||
getAsyncStorageItem(LocalStorageKeys.IMAGE_FILES, true)
|
||||
.then((result) => {
|
||||
filesStore = result || {};
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, 'Error while fetching files from local storage');
|
||||
});
|
||||
|
||||
|
||||
const FileDBSideEffects = () => {
|
||||
setAsyncStorageItem(LocalStorageKeys.IMAGE_FILES, filesStore);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const FileDB = {
|
||||
getFiles: (filterFn: (FileEntry: FileEntry) => Boolean) => {
|
||||
|
||||
|
||||
if (typeof filterFn !== 'function') {
|
||||
throw new Error('"filterFn" must be a function');
|
||||
}
|
||||
|
||||
let filteredEntries = [];
|
||||
|
||||
Object.entries(filesStore).forEach(([key, value]) => {
|
||||
if (filterFn(value)) {
|
||||
filteredEntries.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return filteredEntries;
|
||||
},
|
||||
|
||||
addFiles: (files: Array<FileEntry> | FileEntry) => {
|
||||
const add = (file: FileEntry) => {
|
||||
if (!filesStore[file.path]) {
|
||||
filesStore[file.path] = {
|
||||
...file,
|
||||
zipped: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(files)) {
|
||||
files.forEach(add);
|
||||
FileDBSideEffects();
|
||||
return;
|
||||
}
|
||||
|
||||
add(files)
|
||||
FileDBSideEffects();
|
||||
},
|
||||
|
||||
|
||||
updateFile: (path: string, values: any) => {
|
||||
if (!filesStore[path]) {
|
||||
throw new Error('File not found');
|
||||
}
|
||||
filesStore[path] = { ...filesStore[path], ...values };
|
||||
FileDBSideEffects();
|
||||
},
|
||||
|
||||
markFileZipped: (files: Array<FileEntry> | FileEntry) => {
|
||||
const update = (file: FileEntry) => {
|
||||
if (!filesStore[file.path]) {
|
||||
throw new Error('File not found');
|
||||
}
|
||||
filesStore[file.path] = {
|
||||
...filesStore[file.path],
|
||||
zipped: true
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(files)) {
|
||||
files.forEach(update);
|
||||
FileDBSideEffects();
|
||||
return;
|
||||
}
|
||||
|
||||
update(files);
|
||||
FileDBSideEffects();
|
||||
},
|
||||
unlinkFile: (file: Array<FileEntry> | string) => {
|
||||
|
||||
if (Array.isArray(file)) {
|
||||
file.forEach((singleFile) => {
|
||||
if (!filesStore[singleFile.path]) {
|
||||
throw new Error('File not found');
|
||||
}
|
||||
delete filesStore[singleFile.path];
|
||||
});
|
||||
FileDBSideEffects();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!filesStore[file]) {
|
||||
throw new Error('File not found');
|
||||
}
|
||||
delete filesStore[file];
|
||||
FileDBSideEffects();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
const filterMimeType = (file: FileEntry, type: MimeTypes) => mimeTypes[type].includes(file.mimeType);
|
||||
|
||||
|
||||
export const filterFunctions = {
|
||||
allUnzipFiles: () => (file: FileEntry) => !file.zipped && file.mimeType !== MimeTypes.ZIP,
|
||||
|
||||
bySize: (size: number, type: MimeTypes) => (file: FileEntry) => file.size > size && filterMimeType(file, type),
|
||||
|
||||
recentFile: (createdAt: Date, type: MimeTypes) => (file: FileEntry) => file.createdAt > createdAt.toISOString() && filterMimeType(file, type),
|
||||
|
||||
byMimeType: (mimeType: MimeTypes) => (file: FileEntry) => filterMimeType(file, mimeType)
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../common/AgentActivityConfigurableConstants';
|
||||
import { setBlacklistedAppsList } from './blacklistedApps.service';
|
||||
|
||||
|
||||
const FIREBASE_FETCH_TIME = 15 * 60;
|
||||
export let FIREBASE_FETCH_TIMESTAMP: number;
|
||||
async function fetchUpdatedRemoteConfig() {
|
||||
|
||||
193
src/services/imageSyncService.ts
Normal file
193
src/services/imageSyncService.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from "@common/Constants";
|
||||
import { getImages, zipFilesForServer } from "@components/utlis/ImageUtlis";
|
||||
import axiosInstance, { API_STATUS_CODE, ApiKeys, getApiUrl } from "@components/utlis/apiHelper";
|
||||
import { getAsyncStorageItem, setAsyncStorageItem } from "@components/utlis/commonFunctions";
|
||||
import { logError } from "@components/utlis/errorUtils";
|
||||
import { GLOBAL } from "@constants/Global";
|
||||
import RNFS from 'react-native-fs';
|
||||
import { FileDB, FileEntry, MimeTypes, filesStore, filterFunctions } from "./ImageProcessor";
|
||||
import { addClickstreamEvent } from "./clickstreamEventService";
|
||||
|
||||
const DATA_BUFFER_SIZE = 20971520 //20MB;
|
||||
|
||||
const minutesAgo = (minutes: number) => {
|
||||
return Date.now() - minutes * 60 * 1000;
|
||||
}
|
||||
|
||||
export const imageSyncService = async () => {
|
||||
|
||||
const isImageSyncEnabled = await getAsyncStorageItem(LocalStorageKeys.IS_IMAGE_SYNC_ALLOWED, true) ?? false;
|
||||
if (!isImageSyncEnabled) return;
|
||||
|
||||
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_IMAGE_SYNC_START);
|
||||
|
||||
const endTime = Date.now();
|
||||
|
||||
const startTime = await getAsyncStorageItem(LocalStorageKeys.IMAGE_SYNC_TIME, true) ?? minutesAgo(10);
|
||||
|
||||
|
||||
getImages(+startTime, endTime)
|
||||
.then((images) => {
|
||||
if (images.length > 0) {
|
||||
FileDB.addFiles(images);
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_IMAGES_CAPTURED, images);
|
||||
}
|
||||
setAsyncStorageItem(LocalStorageKeys.IMAGE_SYNC_TIME, endTime.toString());
|
||||
|
||||
}).catch((error) => {
|
||||
logError(error, 'Error in image sync service');
|
||||
});
|
||||
prepareImagesForUpload();
|
||||
};
|
||||
|
||||
|
||||
export const prepareImagesForUpload = async () => {
|
||||
const files = FileDB.getFiles(filterFunctions.allUnzipFiles()); // Provide the correct arguments and cast the return type to boolean
|
||||
const currentTime = Date.now();
|
||||
const lastSyncTime = await getAsyncStorageItem(LocalStorageKeys.IMAGE_SYNC_TIME, true) ?? 0;
|
||||
|
||||
const shouldConsiderUpload = files.length > 0 && currentTime - lastSyncTime < minutesAgo(10);
|
||||
|
||||
if (shouldConsiderUpload) {
|
||||
const filesToUpLoad: FileEntry[] = [];
|
||||
let filesToUploadSize = 0;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
if (filesToUploadSize + file.size < DATA_BUFFER_SIZE) {
|
||||
filesToUpLoad.push(file);
|
||||
}
|
||||
filesToUploadSize += file.size;
|
||||
}
|
||||
|
||||
filesToUpLoad.sort((a, b) => a.createdAt - b.createdAt);
|
||||
zipFilesForServer(filesToUpLoad)
|
||||
.then((zippedFile) => {
|
||||
FileDB.addFiles({ ...zippedFile, startOffset: filesToUpLoad[0].createdAt, endOffset: filesToUpLoad[filesToUpLoad.length - 1].createdAt });
|
||||
// sort files based on createdAt
|
||||
FileDB.markFileZipped(filesToUpLoad);
|
||||
|
||||
FileDB.unlinkFile(filesToUpLoad);
|
||||
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ZIP_FILE_CREATED, zippedFile);
|
||||
})
|
||||
.catch((error) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ZIP_FILE_CREATE_ERROR);
|
||||
logError(error, 'Error zipping files');
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
export const sendImagesToServer = async () => {
|
||||
// check if there are any files to upload
|
||||
|
||||
const isImageSyncEnabled = await getAsyncStorageItem(LocalStorageKeys.IS_IMAGE_SYNC_ALLOWED, true) ?? false;
|
||||
|
||||
if (!isImageSyncEnabled) return;
|
||||
|
||||
const zipFiles = FileDB.getFiles((files) => files.mimeType === MimeTypes.ZIP);
|
||||
|
||||
if(zipFiles.length === 0){
|
||||
prepareImagesForUpload();
|
||||
return;
|
||||
}
|
||||
|
||||
let fileToTry;
|
||||
|
||||
for (let i = 0; i < zipFiles.length; i++) {
|
||||
const file = zipFiles[i];
|
||||
|
||||
if (await RNFS.exists(file.path)) {
|
||||
fileToTry = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (fileToTry) {
|
||||
getPreSignedUrl(fileToTry.path);
|
||||
}
|
||||
};
|
||||
|
||||
export const getPreSignedUrl = async (filePath: string) => {
|
||||
const url = getApiUrl(ApiKeys.GET_PRE_SIGNED_URL, { agentID: GLOBAL.AGENT_ID, deviceID: GLOBAL.DEVICE_ID, dataSyncType: 'IMAGES' })
|
||||
axiosInstance
|
||||
.get(url)
|
||||
.then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
uploadFileTos3(response.data, filePath);
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ZIP_UPLOAD_PRESIGNED);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, 'Error getting presigned url');
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ZIP_UPLOAD_PRESIGNED_ERROR);
|
||||
});
|
||||
};
|
||||
|
||||
export const uploadFileTos3 = async (object: any, filePath: string) => {
|
||||
|
||||
if(!object.preSignedUrl) return;
|
||||
|
||||
try {
|
||||
const response = await RNFS.uploadFiles({
|
||||
toUrl: object.preSignedUrl,
|
||||
files: [
|
||||
{
|
||||
name: 'file',
|
||||
filename: filePath,
|
||||
filepath: filePath,
|
||||
filetype: 'application/zip',
|
||||
},
|
||||
],
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/zip',
|
||||
},
|
||||
});
|
||||
|
||||
const httpResult = await response.promise;
|
||||
|
||||
|
||||
if (httpResult?.statusCode === 200) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ZIP_UPLOADED);
|
||||
sendAckToServer(filePath, object);
|
||||
// Handle successful upload
|
||||
} else {
|
||||
logError(new Error("Error in api uploading"), 'Error uploading file');
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ZIP_UPLOADED_ERROR);
|
||||
}
|
||||
} catch (error) {
|
||||
logError(error, 'Error uploading file');
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ZIP_UPLOADED_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const sendAckToServer = async (filePath: string, object: any) => {
|
||||
|
||||
const file = filesStore[filePath]
|
||||
const url = getApiUrl(ApiKeys.SEND_UPLOAD_ACK, { requestId: object.requestId });
|
||||
axiosInstance
|
||||
.put(url, {
|
||||
"syncSize": file.size,
|
||||
"syncStartOffset": file?.startOffset,
|
||||
"syncEndOffset": file?.endOffset,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
const lastSyncTime = Date.now();
|
||||
FileDB.unlinkFile(filePath);
|
||||
setAsyncStorageItem(LocalStorageKeys.IMAGE_SYNC_TIME, lastSyncTime.toString());
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_IMAGE_SYNC_ACK);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logError(error, 'Error sending ack to server');
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_IMAGE_SYNC_ACK_ERROR);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,10 +5,13 @@ import { logError } from '@components/utlis/errorUtils';
|
||||
|
||||
export enum LitmusExperimentName {
|
||||
COSMOS_TRACKING_COMPONENT_V2 = 'cosmos_tracking_component_v2',
|
||||
COSMOS_IMAGE_SYNC = 'collections_image_sync',
|
||||
}
|
||||
|
||||
export const LitmusExperimentNameMap = {
|
||||
[LitmusExperimentName.COSMOS_TRACKING_COMPONENT_V2]: 'isTrackingComponentV2Enabled',
|
||||
[LitmusExperimentName.COSMOS_IMAGE_SYNC]: 'collections_image_sync',
|
||||
|
||||
};
|
||||
|
||||
const getLitmusExperimentResult = async (
|
||||
|
||||
@@ -37,6 +37,7 @@ import reporteesSlice from '../reducer/reporteesSlice';
|
||||
import agentPerformanceSlice from '../reducer/agentPerformanceSlice';
|
||||
import telephoneNumbersSlice from '../reducer/telephoneNumbersSlice';
|
||||
import { getStorageEngine } from '../PersistStorageEngine';
|
||||
import cosmosSupportSlice from '@reducers/cosmosSupportSlice';
|
||||
import litmusExperimentSlice from '@reducers/litmusExperimentSlice';
|
||||
import ungroupedAddressesSlice from '@reducers/ungroupedAddressesSlice';
|
||||
|
||||
@@ -64,6 +65,7 @@ const rootReducer = combineReducers({
|
||||
feedbackFilters: feedbackFiltersSlice,
|
||||
agentPerformance: agentPerformanceSlice,
|
||||
telephoneNumbers: telephoneNumbersSlice,
|
||||
cosmosSupport: cosmosSupportSlice,
|
||||
litmusExperiment: litmusExperimentSlice,
|
||||
ungroupedAddresses: ungroupedAddressesSlice,
|
||||
});
|
||||
@@ -90,7 +92,7 @@ const persistConfig = {
|
||||
'feedbackFilters',
|
||||
'litmusExperiment',
|
||||
],
|
||||
blackList: ['case', 'filters', 'reportees', 'agentPerformance', 'ungroupedAddresses'],
|
||||
blackList: ['case', 'filters', 'reportees', 'agentPerformance', 'ungroupedAddresses', 'cosmosSupport'],
|
||||
};
|
||||
|
||||
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
||||
|
||||
Reference in New Issue
Block a user