TP-31697 | Image optimization changes
This commit is contained in:
90
src/ImageUploader.tsx
Normal file
90
src/ImageUploader.tsx
Normal file
@@ -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 (
|
||||
<View>
|
||||
<Button title="Select Image" onPress={this.selectImage} />
|
||||
{image && <Image source={{ uri: image.uri }} style={{ width: 200, height: 200 }} />}
|
||||
<Button title="Upload Image" onPress={this.uploadImage} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ImageUploader;
|
||||
@@ -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<any>;
|
||||
}
|
||||
|
||||
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<string, IDocument>, 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<string, IDocument>) => {
|
||||
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<string, IDocument>) => (dispatch: AppDispatch) => {
|
||||
const { docsWithReferenceId, docsWithoutReferenceId } = getDocsWithAndWithoutReferenceId(docs);
|
||||
// dispatch(uploadFeedbackImages(caseId, docs))
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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<IImageUpload> = (props) => {
|
||||
]?.questionContext?.[questionId]?.answer
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (dataFromRedux) {
|
||||
setImageId(dataFromRedux);
|
||||
@@ -61,8 +66,20 @@ const ImageUpload: React.FC<IImageUpload> = (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<IImageUpload> = (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 (
|
||||
<View style={[GenericStyles.mt12]}>
|
||||
|
||||
@@ -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<IWidget> = (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<IWidget> = (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<IWidget> = (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<IWidget> = (props) => {
|
||||
nextActions,
|
||||
})
|
||||
);
|
||||
if (interactionId) {
|
||||
uploadOriginalDocs(interactionId);
|
||||
}
|
||||
};
|
||||
|
||||
async function fetchLocation(): Promise<Geolocation.GeoCoordinates | undefined> {
|
||||
@@ -196,7 +221,7 @@ const Widget: React.FC<IWidget> = (props) => {
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
updateCaseDetail({
|
||||
updateUnsyncedCaseDetail({
|
||||
caseId,
|
||||
updatedCaseDetail: updatedCase,
|
||||
})
|
||||
@@ -253,7 +278,7 @@ const Widget: React.FC<IWidget> = (props) => {
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
updateCaseDetail({
|
||||
updateUnsyncedCaseDetail({
|
||||
caseId,
|
||||
updatedCaseDetail: updatedCase,
|
||||
})
|
||||
|
||||
@@ -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<ApiKeys, string> = {} as Record<ApiKeys, string>;
|
||||
@@ -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,
|
||||
|
||||
28
src/local-server.js
Normal file
28
src/local-server.js
Normal file
@@ -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');
|
||||
});
|
||||
@@ -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<string, IDocument>;
|
||||
}
|
||||
interface IAllCasesSlice {
|
||||
casesList: ICaseItem[];
|
||||
casesListMap: ICasesMap;
|
||||
@@ -34,6 +45,8 @@ interface IAllCasesSlice {
|
||||
newlyPinnedCases: number;
|
||||
completedCases: number;
|
||||
caseDetails: Record<string, CaseDetail>;
|
||||
unsyncedCaseDetail: Record<string, CaseDetail>;
|
||||
docsToBeUploaded: Record<string, IDocumentDetail>;
|
||||
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;
|
||||
|
||||
@@ -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<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!allCasesDetails) return;
|
||||
if (!unsyncedCasesDetails) return;
|
||||
let notSyncedCases: Array<any> = [];
|
||||
_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;
|
||||
};
|
||||
|
||||
BIN
uploads/25ed825bd95fc05adfe97a408d814af0.jpg
Normal file
BIN
uploads/25ed825bd95fc05adfe97a408d814af0.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
BIN
uploads/40bfe3d5000611c90feb89a53a1a2a5b
Normal file
BIN
uploads/40bfe3d5000611c90feb89a53a1a2a5b
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
BIN
uploads/558ba9f23d0e23a0ffc665315ca1ba89
Normal file
BIN
uploads/558ba9f23d0e23a0ffc665315ca1ba89
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
BIN
uploads/file.jpeg
Normal file
BIN
uploads/file.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
Reference in New Issue
Block a user