diff --git a/src/ImageUploader.tsx b/src/ImageUploader.tsx
new file mode 100644
index 00000000..44702974
--- /dev/null
+++ b/src/ImageUploader.tsx
@@ -0,0 +1,90 @@
+import axios from 'axios';
+import React from 'react';
+import { View, Button, Image } from 'react-native';
+import ImagePicker, { ImageLibraryOptions, launchImageLibrary } from 'react-native-image-picker';
+
+class ImageUploader extends React.Component {
+ state = {
+ image: null,
+ };
+
+ selectImage = () => {
+ const options: ImageLibraryOptions = {
+ mediaType: 'photo',
+ quality: 1,
+ };
+
+ launchImageLibrary(options, (response) => {
+ if (!response.didCancel) {
+ console.log('response:', response);
+ this.setState({ image: response.assets?.[0] });
+ }
+ });
+ };
+
+ uploadImage = async () => {
+ const { image } = this.state;
+
+ if (image) {
+ const formData = new FormData();
+ console.log('image:', image);
+
+ // try {
+ // const response = await fetch(image.uri);
+ // const blobData = await response.blob();
+
+ // formData.append('avatar', blobData, 'avatar.png');
+
+ // const result = await axios.post('https://0ac3-115-111-223-26.in.ngrok.io/upload', formData, {
+ // headers: {
+ // 'Content-Type': 'multipart/form-data',
+ // },
+ // });
+
+ // console.log('Request successful', result.data);
+ // } catch (error) {
+ // console.error('Request failed', error);
+ // }
+ console.log('image', image);
+ formData.append('file[]', {
+ uri: image.uri,
+ name: 'image1.png',
+ type: image.type,
+ });
+ formData.append('file[]', {
+ uri: image.uri,
+ name: 'image2.png',
+ type: image.type,
+ });
+ formData.append('file[]', {
+ uri: image.uri,
+ name: 'image3.png',
+ type: image.type,
+ });
+
+ const response = await axios.post(
+ 'https://8ee6-115-111-223-26.in.ngrok.io/upload',
+ formData,
+ {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ }
+ );
+ }
+ };
+
+ render() {
+ const { image } = this.state;
+
+ return (
+
+
+ {image && }
+
+
+ );
+ }
+}
+
+export default ImageUploader;
diff --git a/src/action/caseApiActions.ts b/src/action/caseApiActions.ts
index 5706f585..b59c4528 100644
--- a/src/action/caseApiActions.ts
+++ b/src/action/caseApiActions.ts
@@ -12,6 +12,9 @@ import { GenericType } from '../common/GenericTypes';
import { addClickstreamEvent } from '../services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../common/Constants';
import { PageRouteEnum } from '../screens/auth/ProtectedRouter';
+import { IDocument, removeDocuments, setDocumentReferenceId } from '../reducer/allCasesSlice';
+import { _map } from '../../RN-UI-LIB/src/utlis/common';
+import { logError } from '../components/utlis/errorUtils';
// TODO: Need to add respective interfaces instead of any
interface IUnifiedData {
@@ -21,6 +24,12 @@ interface IUnifiedData {
repayments: Array;
}
+export interface IUploadImagePayload {
+ interactionId: string;
+ questionKey: string;
+ originalImageDocumentReferenceId: string;
+}
+
export enum UnifiedCaseDetailsTypes {
ADDRESS_AND_GEOLOCATIONS = 'includeAddressesAndGeoLocations',
FEEDBACKS = 'includeFeedbacks',
@@ -135,3 +144,85 @@ export const getCaseUnifiedData =
dispatch(setAddressLoading({ isLoading: false, loanAccountNumbers }));
});
};
+
+const uploadFeedbackImages =
+ (caseId: string, payload: IUploadImagePayload[]) => (dispatch: AppDispatch) => {
+ const url = getApiUrl(ApiKeys.UPLOAD_FEEDBACK_IMAGES);
+ axiosInstance
+ .post(url, payload)
+ .then(() => {
+ dispatch(removeDocuments({ caseId, docs: payload }));
+ })
+ .catch((err) => {
+ // Add to retry queue
+ });
+ };
+
+export const uploadImages =
+ (caseId: string, documents: Record, interactionId: string) =>
+ (dispatch: AppDispatch) => {
+ _map(documents, (questionKey) => {
+ if (!documents) {
+ return;
+ }
+ const fileDoc = documents[questionKey];
+ if (!interactionId || !fileDoc) {
+ return;
+ }
+ const { fileUri } = fileDoc;
+ const formData = new FormData();
+ formData.append('images', {
+ uri: fileUri,
+ name: `image_${caseId}_${new Date().getTime()}`,
+ type: 'image/jpeg',
+ } as any);
+ const url = getApiUrl(ApiKeys.UPLOAD_IMAGES);
+ axiosInstance
+ .post(url, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ })
+ .then((res) => {
+ const originalImageDocumentReferenceId = res?.data?.referenceId;
+ dispatch(
+ setDocumentReferenceId({ caseId, questionKey, originalImageDocumentReferenceId })
+ );
+ dispatch(
+ uploadFeedbackImages(caseId, [
+ {
+ interactionId,
+ questionKey,
+ originalImageDocumentReferenceId,
+ },
+ ])
+ );
+ })
+ .catch((err) => {
+ logError(err as Error, 'Error uploading image to document service');
+ });
+ });
+ };
+
+const getDocsWithAndWithoutReferenceId = (docs: Record) => {
+ const docsWithReferenceId: IDocument[] = [];
+ const docsWithoutReferenceId: IDocument[] = [];
+ _map(docs, (questionKey: string) => {
+ const docRefId = docs?.[questionKey]?.originalImageDocumentReferenceId;
+ if (docRefId) {
+ docsWithReferenceId.push(docs?.[questionKey]);
+ } else {
+ docsWithoutReferenceId.push(docs?.[questionKey]);
+ }
+ });
+ return {
+ docsWithReferenceId,
+ docsWithoutReferenceId,
+ };
+};
+
+export const uploadUnsyncedImages =
+ (caseId: string, docs: Record) => (dispatch: AppDispatch) => {
+ const { docsWithReferenceId, docsWithoutReferenceId } = getDocsWithAndWithoutReferenceId(docs);
+ // dispatch(uploadFeedbackImages(caseId, docs))
+ };
diff --git a/src/action/dataActions.ts b/src/action/dataActions.ts
index 43db1e5b..2bd6c782 100644
--- a/src/action/dataActions.ts
+++ b/src/action/dataActions.ts
@@ -7,7 +7,7 @@ import {
resetTodoList,
setLoading,
setVisitPlansUpdating,
- updateCaseDetail,
+ updateUnsyncedCaseDetail,
updateSingleCase,
} from '../reducer/allCasesSlice';
import {
@@ -24,6 +24,7 @@ import { setFilters } from '../reducer/filtersSlice';
import { toast } from '../../RN-UI-LIB/src/components/toast';
import { ToastMessages } from '../screens/allCases/constants';
import { GenericFunctionArgs } from '../common/GenericTypes';
+import axios from 'axios';
let _signedApiCallBucket: { req: any; added_At: number; callback: GenericFunctionArgs }[] = [];
let _signedApiCallBucketTimer: number = 0;
@@ -72,7 +73,7 @@ export const syncCaseDetail =
payload: any,
updatedCaseDetail?: any,
callbacks?: {
- onSuccessCB?: (data: any, actions?: any) => void;
+ onSuccessCB?: (data: any, actions?: any, interactionId?: string) => void;
onErrorCB?: (e: Error) => void;
},
nextActions?: any
@@ -88,6 +89,7 @@ export const syncCaseDetail =
})
.then((res) => {
const caseType = payload.caseType;
+ const interactionId = res.data?.referenceId;
dispatch(
updateSingleCase({
data: res.data,
@@ -102,7 +104,7 @@ export const syncCaseDetail =
text1: ToastMessages.FEEDBACK_SUCCESSFUL,
});
if (callbacks?.onSuccessCB != null && typeof callbacks?.onSuccessCB === 'function') {
- callbacks?.onSuccessCB(payload.data.answers, nextActions);
+ callbacks?.onSuccessCB(payload.data.answers, nextActions, interactionId);
}
})
.catch((e) => {
@@ -111,9 +113,9 @@ export const syncCaseDetail =
type: 'error',
text1: ToastMessages.FEEDBACK_FAILED,
});
- if (updatedCaseDetail) {
+ if (updatedCaseDetail && !updatedCaseDetail.offlineCaseKey) {
dispatch(
- updateCaseDetail({
+ updateUnsyncedCaseDetail({
caseId: payload.data.caseReferenceId,
updatedCaseDetail,
})
diff --git a/src/components/form/components/ImageUpload.tsx b/src/components/form/components/ImageUpload.tsx
index 14ab58c1..b2392f3c 100644
--- a/src/components/form/components/ImageUpload.tsx
+++ b/src/components/form/components/ImageUpload.tsx
@@ -1,13 +1,15 @@
import React, { useEffect, useState } from 'react';
import { Control, Controller } from 'react-hook-form';
import { ImageBackground, StyleSheet, TouchableOpacity, View } from 'react-native';
-import PhotoUpload from '../../../../RN-UI-LIB/src/components/photoUpload/PhotoUpload';
+import PhotoUpload, {
+ IImageDetails,
+} from '../../../../RN-UI-LIB/src/components/photoUpload/PhotoUpload';
import { useSelector } from 'react-redux';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import DeleteIcon from '../../../../RN-UI-LIB/src/Icons/DeleteIcon';
import { GenericStyles } from '../../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../../RN-UI-LIB/src/styles/colors';
-import { useAppSelector } from '../../../hooks';
+import { useAppDispatch, useAppSelector } from '../../../hooks';
import { RootState } from '../../../store/store';
import { AnswerType } from '../interface';
import ErrorMessage from './ErrorMessage';
@@ -17,6 +19,7 @@ import { CLICKSTREAM_EVENT_NAMES, PrefixJpegBase64Image } from '../../../common/
import { addClickstreamEvent } from '../../../services/clickstreamEventService';
import { isQuestionMandatory, validateInput } from '../services/validation.service';
import { CaseAllocationType } from '../../../screens/allCases/interface';
+import { addDocumentToUpload } from '../../../reducer/allCasesSlice';
interface IOfflineImage {
idx: string;
@@ -51,6 +54,8 @@ const ImageUpload: React.FC = (props) => {
]?.questionContext?.[questionId]?.answer
);
+ const dispatch = useAppDispatch();
+
useEffect(() => {
if (dataFromRedux) {
setImageId(dataFromRedux);
@@ -61,8 +66,20 @@ const ImageUpload: React.FC = (props) => {
return null;
}
- const handleChange = async (clickedImage: string | null, onChange: (...event: any[]) => void) => {
- const data = `${PrefixJpegBase64Image}${clickedImage}`;
+ const addOriginalFileUriToDocs = (caseId: string, fileUri: string, questionKey: string) => {
+ if (!fileUri) {
+ return;
+ }
+ dispatch(addDocumentToUpload({ caseId, fileUri, questionKey }));
+ };
+
+ const handleChange = async (clickedImage: IImageDetails, onChange: (...event: any[]) => void) => {
+ const { base64, uri } = clickedImage;
+ addOriginalFileUriToDocs(caseId, uri, questionId);
+ if (!base64) {
+ return;
+ }
+ const base64Image = `${PrefixJpegBase64Image}${base64}`;
var uniqueId = 'id' + new Date().getTime();
setImageId(uniqueId);
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FORM_ELEMENT_CHANGED, {
@@ -78,8 +95,9 @@ const ImageUpload: React.FC = (props) => {
answer: uniqueId,
type: AnswerType.image,
});
- await OfflineImageDAO.addImage(data, uniqueId);
+ await OfflineImageDAO.addImage(base64Image, uniqueId);
};
+
// TODO : add the validator back when firestore is fixed.
return (
diff --git a/src/components/form/index.tsx b/src/components/form/index.tsx
index 4ac86ef9..63e1233b 100644
--- a/src/components/form/index.tsx
+++ b/src/components/form/index.tsx
@@ -17,7 +17,8 @@ import useIsOnline from '../../hooks/useIsOnline';
import {
getUpdatedAVCaseDetail,
getUpdatedCollectionCaseDetail,
- updateCaseDetail,
+ setDocumentInteractionId,
+ updateUnsyncedCaseDetail,
} from '../../reducer/allCasesSlice';
import { deleteInteraction, deleteJourney, updateInteraction } from '../../reducer/caseReducer';
import { CaseAllocationType } from '../../screens/allCases/interface';
@@ -41,6 +42,8 @@ import { CaptureGeolocation } from './services/geoLocation.service';
import Submit from './Submit';
import { toast } from '../../../RN-UI-LIB/src/components/toast';
import { ToastMessages } from '../../screens/allCases/constants';
+import { uploadImages } from '../../action/caseApiActions';
+import { isEmpty } from '../../../RN-UI-LIB/src/utlis/common';
interface IWidget {
route: {
@@ -58,13 +61,21 @@ const Widget: React.FC = (props) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const { params } = props.route;
const { caseId, journey } = params;
- const caseType = useAppSelector(
- (state) =>
- state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE
+ const { caseType, templateData, caseData, dataToBeValidated, docsToBeUploaded } = useAppSelector(
+ (state) => {
+ const caseType =
+ state.allCases.caseDetails[caseId]?.caseType ||
+ CaseAllocationType.ADDRESS_VERIFICATION_CASE;
+ return {
+ caseType,
+ templateData: state.case.templateData[caseType],
+ caseData: state.allCases.caseDetails[caseId],
+ dataToBeValidated: state.case.caseForm?.[caseId]?.[journey],
+ docsToBeUploaded: state.allCases.docsToBeUploaded?.[caseId]?.documents,
+ };
+ }
);
const name = getWidgetNameFromRoute(props.route.name, caseType);
- const templateData: FormTemplateV1 = useAppSelector((state) => state.case.templateData[caseType]);
- const caseData = useAppSelector((state) => state.allCases.caseDetails[caseId]);
const { sections, conditionActions: widgetConditionActions, isLeaf } = templateData.widget[name];
const sectionMap = templateData.sections;
const [error, setError] = useState();
@@ -80,7 +91,6 @@ const Widget: React.FC = (props) => {
setIsJourneyFirstScreen(isFirst);
}, [templateData, name]);
- const dataToBeValidated = useAppSelector((state) => state.case.caseForm?.[caseId]?.[journey]);
const {
control,
handleSubmit,
@@ -127,7 +137,19 @@ const Widget: React.FC = (props) => {
setError(data);
};
- const onSuccessfulSubmit = (data: any, nextActions?: any) => {
+ // Upload original doc to document service => get docRefId
+ const uploadOriginalDocs = (interactionId: string) => {
+ if (isEmpty(docsToBeUploaded)) {
+ return;
+ }
+ dispatch(setDocumentInteractionId({ caseId, interactionId }));
+ if (isOnline) {
+ dispatch(uploadImages(caseId, docsToBeUploaded, interactionId));
+ } else {
+ }
+ };
+
+ const onSuccessfulSubmit = (data: any, nextActions?: any, interactionId?: string) => {
setIsSubmitting(false);
navigateToScreen(
caseType === CaseAllocationType.COLLECTION_CASE ? 'collectionCaseDetail' : 'caseDetail',
@@ -146,6 +168,9 @@ const Widget: React.FC = (props) => {
nextActions,
})
);
+ if (interactionId) {
+ uploadOriginalDocs(interactionId);
+ }
};
async function fetchLocation(): Promise {
@@ -196,7 +221,7 @@ const Widget: React.FC = (props) => {
);
} else {
dispatch(
- updateCaseDetail({
+ updateUnsyncedCaseDetail({
caseId,
updatedCaseDetail: updatedCase,
})
@@ -253,7 +278,7 @@ const Widget: React.FC = (props) => {
);
} else {
dispatch(
- updateCaseDetail({
+ updateUnsyncedCaseDetail({
caseId,
updatedCaseDetail: updatedCase,
})
diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts
index baf81c23..5ee85b0b 100644
--- a/src/components/utlis/apiHelper.ts
+++ b/src/components/utlis/apiHelper.ts
@@ -47,6 +47,8 @@ export enum ApiKeys {
CASES_SEND_ID = 'CASES_SEND_ID',
FETCH_CASES = 'FETCH_CASES',
GET_FORECLOSURE_AMOUNT = 'GET_FORECLOSURE_AMOUNT',
+ UPLOAD_IMAGES = 'UPLOAD_IMAGES',
+ UPLOAD_FEEDBACK_IMAGES = 'UPLOAD_FEEDBACK_IMAGES',
}
export const API_URLS: Record = {} as Record;
@@ -80,6 +82,8 @@ API_URLS[ApiKeys.CASES_SYNC_STATUS] = '/cases/agents/sync-status';
API_URLS[ApiKeys.CASES_SEND_ID] = '/cases/sync';
API_URLS[ApiKeys.FETCH_CASES] = '/cases/agents/{agentReferenceId}';
API_URLS[ApiKeys.GET_FORECLOSURE_AMOUNT] = '/{loanAccountNumber}/pre-closure-amount';
+API_URLS[ApiKeys.UPLOAD_IMAGES] = '/cases/images';
+API_URLS[ApiKeys.UPLOAD_FEEDBACK_IMAGES] = '/cases/feedback/original-images';
export const API_STATUS_CODE = {
OK: 200,
diff --git a/src/local-server.js b/src/local-server.js
new file mode 100644
index 00000000..e7fb871f
--- /dev/null
+++ b/src/local-server.js
@@ -0,0 +1,28 @@
+const express = require('express');
+const multer = require('multer');
+const path = require('path');
+
+const app = express();
+// const storage = multer.diskStorage({
+// destination: 'uploads/',
+// filename: (req, file, cb) => {
+// cb(null, file.originalname);
+// },
+// });
+const upload = multer({ dest: 'uploads/' });
+
+app.post('/upload', upload.array('file[]'), (req, res) => {
+ const files = req.files;
+ console.log('request.file:::', files);
+ // if (!req.files) {
+ // res.status(400).json({ error: 'No image file provided' });
+ // } else {
+ // const imagePath = req.files[0].path;
+ // res.json({ message: 'Image uploaded successfully', imagePath });
+ // }
+ res.send('Files uploaded successfully');
+});
+
+app.listen(3000, () => {
+ console.log('Server is running on port 3000');
+});
diff --git a/src/reducer/allCasesSlice.ts b/src/reducer/allCasesSlice.ts
index c0946f07..f778f9d6 100644
--- a/src/reducer/allCasesSlice.ts
+++ b/src/reducer/allCasesSlice.ts
@@ -1,6 +1,6 @@
import { createSlice } from '@reduxjs/toolkit';
import { toast } from '../../RN-UI-LIB/src/components/toast';
-import { _map } from '../../RN-UI-LIB/src/utlis/common';
+import { _map, isEmpty } from '../../RN-UI-LIB/src/utlis/common';
import { CLICKSTREAM_EVENT_NAMES, FirestoreUpdateTypes } from '../common/Constants';
import { getCurrentScreen, navigateToScreen } from '../components/utlis/navigationUtlis';
import { CaseUpdates } from '../hooks/useFirestoreUpdates';
@@ -17,9 +17,20 @@ import { addClickstreamEvent } from '../services/clickstreamEventService';
import { getLoanAccountNumber } from '../components/utlis/commonFunctions';
import { getVisitedWidgetsNodeList } from '../components/form/services/forms.service';
import { CollectionCaseWidgetId, CommonCaseWidgetId } from '../types/template.types';
+import { IUploadImagePayload } from '../action/caseApiActions';
export type ICasesMap = { [key: string]: ICaseItem };
+export interface IDocument {
+ questionKey: string;
+ originalImageDocumentReferenceId: string;
+ fileUri?: string;
+}
+
+interface IDocumentDetail {
+ interactionId: string;
+ documents: Record;
+}
interface IAllCasesSlice {
casesList: ICaseItem[];
casesListMap: ICasesMap;
@@ -34,6 +45,8 @@ interface IAllCasesSlice {
newlyPinnedCases: number;
completedCases: number;
caseDetails: Record;
+ unsyncedCaseDetail: Record;
+ docsToBeUploaded: Record;
searchQuery: string;
isOnboarded: boolean;
visitPlansUpdating: boolean;
@@ -57,6 +70,8 @@ const initialState: IAllCasesSlice = {
newlyPinnedCases: 0,
completedCases: 0,
caseDetails: {},
+ unsyncedCaseDetail: {},
+ docsToBeUploaded: {},
searchQuery: '',
isOnboarded: false,
visitPlansUpdating: false,
@@ -382,14 +397,41 @@ const allCasesSlice = createSlice({
}
}
},
- updateCaseDetail: (state, action) => {
+ updateUnsyncedCaseDetail: (state, action) => {
const { caseId, updatedCaseDetail } = action.payload;
let caseKey: string = caseId;
if (updatedCaseDetail.caseType === CaseAllocationType.COLLECTION_CASE) {
caseKey += '_' + Date.now();
updatedCaseDetail.offlineCaseKey = caseKey;
}
- state.caseDetails[caseKey] = updatedCaseDetail;
+ state.unsyncedCaseDetail[caseKey] = updatedCaseDetail;
+ },
+ updateSingleCase: (state, action) => {
+ const { data, id, caseType, offlineCaseKey } = action.payload;
+ if (caseType === CaseAllocationType.COLLECTION_CASE) {
+ const computedKey = offlineCaseKey || id;
+ if (state.caseDetails?.[id]) {
+ state.caseDetails[id] = {
+ ...state.caseDetails[computedKey],
+ isSynced: true,
+ };
+ if (offlineCaseKey) {
+ delete state.unsyncedCaseDetail[offlineCaseKey];
+ }
+ }
+ return;
+ }
+
+ if (data) {
+ state.caseDetails[id] = {
+ ...data,
+ currentTask: data.tasks.find((task) => task.taskType === data.currentTask),
+ isSynced: true,
+ isApiCallMade: false,
+ };
+ } else {
+ state.caseDetails[id].isSynced = false;
+ }
},
setPinnedRank: (state, action) => {
const caseId = action.payload.caseReferenceId;
@@ -457,31 +499,7 @@ const allCasesSlice = createSlice({
state.selectedTodoListCount = 0;
state.selectedTodoListMap = {};
},
- updateSingleCase: (state, action) => {
- const { data, id, caseType, offlineCaseKey } = action.payload;
- if (caseType === CaseAllocationType.COLLECTION_CASE) {
- const computedKey = offlineCaseKey || id;
- if (state.caseDetails?.[id]) {
- state.caseDetails[id] = {
- ...state.caseDetails[computedKey],
- isSynced: true,
- };
- delete state.caseDetails[offlineCaseKey];
- }
- return;
- }
- if (data) {
- state.caseDetails[id] = {
- ...data,
- currentTask: data.tasks.find((task) => task.taskType === data.currentTask),
- isSynced: true,
- isApiCallMade: false,
- };
- } else {
- state.caseDetails[id].isSynced = false;
- }
- },
setFilterList: (state, action) => {
state.filterList = action.payload;
},
@@ -551,6 +569,55 @@ const allCasesSlice = createSlice({
}
});
},
+ addDocumentToUpload: (state, action) => {
+ const { caseId, questionKey, fileUri } = action.payload;
+ const doc = {
+ questionKey,
+ fileUri,
+ originalImageDocumentReferenceId: '',
+ };
+ if (state.docsToBeUploaded?.[caseId]) {
+ const newDocsMap = { ...state.docsToBeUploaded[caseId].documents, doc };
+ state.docsToBeUploaded[caseId].documents = newDocsMap;
+ } else {
+ state.docsToBeUploaded[caseId] = {
+ interactionId: '',
+ documents: {
+ [questionKey]: doc,
+ },
+ };
+ }
+ },
+ removeDocuments: (state, action) => {
+ const { docs, caseId } = action.payload;
+ docs?.forEach((doc: IUploadImagePayload) => {
+ const { questionKey } = doc;
+ // delete respective document
+ if (state.docsToBeUploaded?.[caseId]?.documents?.[questionKey]) {
+ delete state.docsToBeUploaded?.[caseId]?.documents?.[questionKey];
+ }
+ // delete the whole object if no documents remaining
+ if (isEmpty(state.docsToBeUploaded?.[caseId]?.documents)) {
+ delete state.docsToBeUploaded?.[caseId];
+ }
+ });
+ },
+ setDocumentInteractionId: (state, action) => {
+ const { caseId, interactionId } = action.payload;
+ if (state.docsToBeUploaded[caseId]) {
+ state.docsToBeUploaded[caseId].interactionId = interactionId;
+ }
+ },
+ setDocumentReferenceId: (state, action) => {
+ const { caseId, questionKey, originalImageDocumentReferenceId } = action.payload;
+ if (state.docsToBeUploaded[caseId]) {
+ const doc = state.docsToBeUploaded[caseId].documents[questionKey];
+ state.docsToBeUploaded[caseId].documents[questionKey] = {
+ ...doc,
+ originalImageDocumentReferenceId,
+ };
+ }
+ },
},
});
@@ -562,7 +629,7 @@ export const {
deleteIntermediateTodoListItem,
setSelectedTodoListMap,
resetSelectedTodoList,
- updateCaseDetail,
+ updateUnsyncedCaseDetail,
updateSingleCase,
updateCaseDetailsFirestore,
toggleNewlyAddedCase,
@@ -571,6 +638,10 @@ export const {
setVisitPlansUpdating,
resetNewVisitedCases,
syncCasesByFallback,
+ addDocumentToUpload,
+ setDocumentReferenceId,
+ removeDocuments,
+ setDocumentInteractionId,
} = allCasesSlice.actions;
export default allCasesSlice.reducer;
diff --git a/src/screens/caseDetails/interactionsHandler.tsx b/src/screens/caseDetails/interactionsHandler.tsx
index d9e7d054..f8c298a8 100644
--- a/src/screens/caseDetails/interactionsHandler.tsx
+++ b/src/screens/caseDetails/interactionsHandler.tsx
@@ -11,6 +11,7 @@ import {
import { addClickstreamEvent } from '../../services/clickstreamEventService';
import { CaseAllocationType } from '../allCases/interface';
import { CaseDetail } from './interface';
+import { uploadUnsyncedImages } from '../../action/caseApiActions';
export const getUnSyncedCase = (updatedCaseDetail: CaseDetail | undefined): any => {
const caseId = updatedCaseDetail?.id;
@@ -21,7 +22,10 @@ export const getUnSyncedCase = (updatedCaseDetail: CaseDetail | undefined): any
const interactionsHandler = () => {
const dispatch = useAppDispatch();
const isOnline = useIsOnline();
- const allCasesDetails = useAppSelector((state) => state.allCases.caseDetails);
+ const { unsyncedCasesDetails, docsToBeUploaded } = useAppSelector((state) => ({
+ unsyncedCasesDetails: state.allCases.unsyncedCaseDetail,
+ docsToBeUploaded: state.allCases.docsToBeUploaded,
+ }));
const { templateId } = useAppSelector(
(state) => state.case.templateData[CaseAllocationType.ADDRESS_VERIFICATION_CASE]
);
@@ -33,15 +37,16 @@ const interactionsHandler = () => {
const inProgressCaseIds = useRef([]);
useEffect(() => {
- if (!allCasesDetails) return;
+ if (!unsyncedCasesDetails) return;
let notSyncedCases: Array = [];
- _map(allCasesDetails, (el) => {
+ // [DISCUSS]: isApiCalled not getting true anywhere.
+ _map(unsyncedCasesDetails, (el) => {
if (
- allCasesDetails[el]?.isSynced === false &&
- allCasesDetails[el]?.isApiCalled === false &&
+ unsyncedCasesDetails[el]?.isSynced === false &&
+ unsyncedCasesDetails[el]?.isApiCalled === false &&
!inProgressCaseIds.current.includes(el)
) {
- const unSyncedCase = getUnSyncedCase(allCasesDetails[el]);
+ const unSyncedCase = getUnSyncedCase(unsyncedCasesDetails[el]);
notSyncedCases.push(unSyncedCase);
}
});
@@ -49,6 +54,11 @@ const interactionsHandler = () => {
//TODO: use batched api call
for (const caseItem of notSyncedCases) {
if (isOnline) {
+ /**
+ * [DISCUSS]: In non-synced cases offlineCaseKey should be always there ideally.
+ * inProgressCaseIds not removing the processed ID,
+ * this will prevent processing of failed api cases again.
+ */
inProgressCaseIds.current.push(caseItem.offlineCaseKey || caseItem.id);
let modifiedCaseItem: any;
if (caseItem?.caseType === CaseAllocationType.COLLECTION_CASE) {
@@ -60,7 +70,24 @@ const interactionsHandler = () => {
}
}
})();
- }, [allCasesDetails, isOnline]);
+ }, [unsyncedCasesDetails, isOnline]);
+
+ useEffect(() => {
+ if (!isOnline) {
+ return;
+ }
+ if (!docsToBeUploaded) {
+ return;
+ }
+ _map(docsToBeUploaded, (caseId) => {
+ const interactionId = docsToBeUploaded[caseId]?.interactionId;
+ if (!interactionId) {
+ return;
+ }
+ const doc = docsToBeUploaded[caseId].documents;
+ dispatch(uploadUnsyncedImages(caseId, doc));
+ });
+ }, [docsToBeUploaded, isOnline]);
return null;
};
diff --git a/uploads/25ed825bd95fc05adfe97a408d814af0.jpg b/uploads/25ed825bd95fc05adfe97a408d814af0.jpg
new file mode 100644
index 00000000..2b32a2f7
Binary files /dev/null and b/uploads/25ed825bd95fc05adfe97a408d814af0.jpg differ
diff --git a/uploads/40bfe3d5000611c90feb89a53a1a2a5b b/uploads/40bfe3d5000611c90feb89a53a1a2a5b
new file mode 100644
index 00000000..2b32a2f7
Binary files /dev/null and b/uploads/40bfe3d5000611c90feb89a53a1a2a5b differ
diff --git a/uploads/558ba9f23d0e23a0ffc665315ca1ba89 b/uploads/558ba9f23d0e23a0ffc665315ca1ba89
new file mode 100644
index 00000000..2b32a2f7
Binary files /dev/null and b/uploads/558ba9f23d0e23a0ffc665315ca1ba89 differ
diff --git a/uploads/file.jpeg b/uploads/file.jpeg
new file mode 100644
index 00000000..6cb21670
Binary files /dev/null and b/uploads/file.jpeg differ