Merge branch 'all_cases_case_details_api_integration' of github.cmd.navi-tech.in:medici/Address-Verification-App into filters

# Conflicts:
#	src/reducer/allCasesSlice.ts
#	src/screens/allCases/AllCases.tsx
This commit is contained in:
kunalsharma
2022-12-27 19:32:47 +05:30
34 changed files with 1197 additions and 401 deletions

View File

@@ -15,21 +15,11 @@ import Login from './src/screens/login';
import OtpInput from './src/screens/login/OtpInput';
import TodoList from './src/screens/todoList/TodoList';
import { RootState } from './src/store/store';
import Profile from './src/screens/Profile';
import interactionsHandler from './src/screens/caseDetails/interactionsHandler';
const Stack = createNativeStackNavigator();
const LoginScreen = () => <Login />;
const OTPScreen = () => <OtpInput />;
// const HomeScreen = () => (
// <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
// <SafeAreaView>
// <RenderingEngine data={data} userData={userData} />
// </SafeAreaView>
// </View>
// );
const ProtectedRouter = () => {
const user = useSelector(
(state: RootState) => state.user,
@@ -39,7 +29,7 @@ const ProtectedRouter = () => {
// for setting user token in global.ts for api calling's
setGlobalUserData(sessionDetails?.sessionToken);
const d = interactionsHandler()
const dispatch = useAppDispatch();
if (!deviceId) {
@@ -57,6 +47,14 @@ const ProtectedRouter = () => {
header: () => null,
}}
/>
<Stack.Screen
name="Profile"
component={Profile}
options={{
header: () => null,
animation: 'slide_from_right'
}}
/>
<Stack.Screen
name="caseDetail"
component={CaseDetails}
@@ -71,6 +69,7 @@ const ProtectedRouter = () => {
component={Widget}
options={{
header: () => null,
animation: 'slide_from_right'
}}
/>
))}
@@ -79,6 +78,7 @@ const ProtectedRouter = () => {
component={TodoList}
options={{
header: () => null,
animation: 'slide_from_bottom'
}}
/>
</>
@@ -86,14 +86,14 @@ const ProtectedRouter = () => {
<>
<Stack.Screen
name="Login"
component={LoginScreen}
component={Login}
options={{
header: () => null,
}}
/>
<Stack.Screen
name="OTP"
component={OTPScreen}
component={OtpInput}
options={{
header: () => null,
}}

View File

@@ -1,6 +1,7 @@
import {IUser, setAuthData} from '../reducer/userSlice';
import axiosInstance, {ApiKeys, getApiUrl} from '../components/utlis/apiHelper';
import {
resetLoginForm,
setFormLoading,
setOTPError,
setShowOTPScreen,
@@ -10,8 +11,8 @@ import {
import {Dispatch} from '@reduxjs/toolkit';
import {navigateToScreen} from '../components/utlis/navigationUtlis';
import {AxiosResponse} from 'axios';
import { updateCaseList } from './dataActions';
import { setCasesListData } from '../reducer/allCasesSlice';
import {AppDispatch} from '../store/store';
import {setGlobalUserData} from '../constants/Global';
export interface GenerateOTPPayload {
phoneNumber: string;
@@ -71,8 +72,26 @@ export const verifyOTP =
}),
);
dispatch(setVerifyOTPSuccess('OTP verified'));
dispatch(resetLoginForm());
})
.catch(err => {
dispatch(setVerifyOTPError('Invalid OTP, try again'));
});
};
export const logout = () => (dispatch: AppDispatch) => {
const url = getApiUrl(ApiKeys.LOGOUT);
axiosInstance
.get(url)
.then(response => {
setGlobalUserData('');
dispatch(
setAuthData({
sessionDetails: null,
user: null,
isLoggedIn: false,
}),
);
})
.catch(err => console.log(err));
};

View File

@@ -1,24 +1,68 @@
import {Dispatch} from 'redux';
import axiosInstance, {ApiKeys, getApiUrl} from '../components/utlis/apiHelper';
import {setCasesListData} from '../reducer/allCasesSlice';
import {navigateToScreen} from '../components/utlis/navigationUtlis';
import {
resetTodoList,
setCasesListData,
setLoading,
setTodoListOffline,
} from '../reducer/allCasesSlice';
import {ICaseItem} from '../screens/allCases/interface';
import {AppDispatch} from '../store/store';
export const getAllCases = () => (dispatch: Dispatch) => {
export const getAllCases = () => (dispatch: AppDispatch) => {
const url = getApiUrl(ApiKeys.ALL_CASES);
axiosInstance.get(url).then(async response => {
const caseDetails = await getAllCaseDetails(response.data)
dispatch(setCasesListData({allCases : response.data, details: caseDetails }));
dispatch(setLoading(true));
axiosInstance.get(url).then(response => {
dispatch(getAllCaseDetails(response.data));
});
};
export const getAllCaseDetails = async (data: Array<ICaseItem>) => {
const caseList = data.map(caseItem => caseItem.caseReferenceId);
const url = getApiUrl(ApiKeys.CASE_DETAIL);
try {
const result = await axiosInstance.get(url, {params: {caseIds: caseList.join(',')}})
console.log(result)
return await result.data;
} catch (error) {
return {};
}
export const getAllCaseDetails =
(data: Array<ICaseItem>) => (dispatch: AppDispatch) => {
const caseList = data.map(caseItem => caseItem.caseReferenceId);
const url = getApiUrl(ApiKeys.CASE_DETAIL);
axiosInstance
.get(url, {
params: {caseIds: caseList.join(',')},
})
.then(response => {
dispatch(
setCasesListData({allCases: data, details: response.data}),
);
dispatch(resetTodoList());
})
.catch(err => console.log(err));
};
export const postPinnedList =
(pinnedCases: ICaseItem[], updatedCaseList: ICaseItem[]) =>
(dispatch: AppDispatch) => {
dispatch(setTodoListOffline(updatedCaseList));
navigateToScreen('Home');
const payload = pinnedCases
.map(caseItem => caseItem.caseReferenceId)
.join('&pins=');
const url = getApiUrl(ApiKeys.PINNED_CASES);
axiosInstance
.post(url + `?pins=${payload}`)
.then(response => {
dispatch(getAllCases());
})
.catch(err => {
dispatch(setTodoListOffline(updatedCaseList));
});
};
export const syncCaseDetail = (data: any) => (dispatch: AppDispatch) => {
const url = getApiUrl(ApiKeys.FEEDBACK);
console.log(data);
axiosInstance
.post(url, {
id: data.id,
...data,
})
.then(res => console.log(res.data))
.catch(err => console.log(err, 'error'));
console.log(data);
};

View File

@@ -24,7 +24,7 @@ const IconLabel: React.FC<IconLabelProps> = props => {
<View style={[styles.icon, iconStyle]}>
{icon ? icon : <BulletIcon />}
</View>
<Text style={textStyle}>{text}</Text>
<Text light style={textStyle}>{text}</Text>
</View>
);
};

View File

@@ -0,0 +1,49 @@
import {View, StyleSheet} from 'react-native';
import React from 'react';
import {GenericStyles, getShadowStyle} from '../../../RN-UI-LIB/src/styles';
import Text from '../../../RN-UI-LIB/src/components/Text';
import {COLORS} from '../../../RN-UI-LIB/src/styles/colors';
import InfoIcon from '../../../RN-UI-LIB/src/Icons/InfoIcon';
interface IFloatingInfoText {
top?: number;
bottom?: number;
message: string;
}
const FloatingInfoText: React.FC<IFloatingInfoText> = ({message, top, bottom}) => {
return (
<View
style={[
styles.container,
GenericStyles.centerAlignedRow,
{top, bottom},
]}>
<View style={[styles.textContainer, getShadowStyle(2), GenericStyles.centerAlignedRow]}>
<InfoIcon color={COLORS.BASE.BLUE} />
<Text small style={styles.text}>
{message}
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
position: 'absolute',
width: '100%',
},
textContainer: {
paddingHorizontal: 30,
paddingVertical: 3,
borderRadius: 4,
backgroundColor: COLORS.BACKGROUND.BLUE,
},
text: {
color: COLORS.BASE.BLUE,
marginLeft: 10
},
});
export default FloatingInfoText;

View File

@@ -1,18 +1,18 @@
import React, { useEffect } from 'react';
import { View } from 'react-native';
import React, {useEffect} from 'react';
import {View} from 'react-native';
import { useState } from 'react';
import { Control, Controller } from 'react-hook-form';
import { useSelector } from 'react-redux';
import RNDropDown from '../../../../RN-UI-LIB/src/components/dropdown/Dropdown';
import RNOptions from '../../../../RN-UI-LIB/src/components/dropdown/Options';
import {useState} from 'react';
import {Control, Controller} from 'react-hook-form';
import {useSelector} from 'react-redux';
import RNCheckboxGroup from '../../../../RN-UI-LIB/src/components/chechbox/CheckboxGroup';
import {ICheckboxOption} from '../../../../RN-UI-LIB/src/components/chechbox/types';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import { GenericStyles } from '../../../../RN-UI-LIB/src/styles';
import template from '../../../data/RealTemplateData.json';
import { RootState } from '../../../store/store';
import {GenericStyles} from '../../../../RN-UI-LIB/src/styles';
import {RootState} from '../../../store/store';
import {AnswerType} from '../interface';
import QuestionRenderingEngine from '../QuestionRenderingEngine';
import ErrorMessage from './ErrorMessage';
import RNCheckboxGroup from '../../../../RN-UI-LIB/src/components/chechbox/CheckboxGroup';
import {useAppSelector } from '../../../hooks';
interface ICheckBoxGroup {
questionType: string;
@@ -27,6 +27,7 @@ interface ICheckBoxGroup {
const CheckBoxGroup: React.FC<ICheckBoxGroup> = props => {
const {questionId, widgetId, journeyId, caseId, sectionId, error} = props;
const template = useAppSelector(state => state.case.templateData)
const question =
template.questions[questionId as keyof typeof template.questions];
const options = template.options;
@@ -36,9 +37,7 @@ const CheckBoxGroup: React.FC<ICheckBoxGroup> = props => {
const dataFromRedux = useSelector(
(state: RootState) =>
state.case.caseForm?.[caseId]?.[journeyId]?.[widgetId]?.[
sectionId
]?.[questionId],
state.case.caseForm?.[caseId]?.[journeyId]?.widgetContext?.[widgetId]?.sectionContext?.[sectionId]?.questionContext?.[questionId]
);
useEffect(() => {
@@ -48,27 +47,6 @@ const CheckBoxGroup: React.FC<ICheckBoxGroup> = props => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataFromRedux]);
// const dispatch = useDispatch();
// const answer = useSelector(
// (state: RootState) =>
// state.counter.caseForm?.[caseId]?.[journeyId]?.[widgetId]?.[
// sectionId
// ]?.[questionId] || 'not_found',
// );
// const registerValue = (id: string) => {
// dispatch(
// updateInteraction({
// caseId,
// journeyId,
// widgetId,
// questionId,
// answer: id,
// sectionId,
// }),
// );
// };
const computeNextQuestion = (optionId: keyof typeof options) => {
if (options[optionId]?.associateQuestions.length < 1) {
setAssociatedQuestions([]);
@@ -77,30 +55,52 @@ const CheckBoxGroup: React.FC<ICheckBoxGroup> = props => {
setAssociatedQuestions(options[optionId]?.associateQuestions);
};
const optiosList = question.options.map(option =>({label: options[option].text, value: option}));
const optiosList = question.options.map((option: any) => ({
label: options[option].text,
value: option,
}));
const handleChange = (
change: Array<ICheckboxOption> | null,
onChange: (...event: any[]) => void,
) => {
const values = change?.map(item => item.value);
onChange({
answer: values,
type: AnswerType.array,
});
};
return (
<View style={GenericStyles.mt12}>
{question.text ?<Text dark bold>
{question.text}{' '}
{question.type === 'mandatory' && (
<Text style={GenericStyles.redText}>*</Text>
)}
</Text>: null}
{question.text ? (
<Text dark bold>
{question.text}{' '}
{question.type === 'mandatory' && (
<Text style={GenericStyles.redText}>*</Text>
)}
</Text>
) : null}
<Controller
control={props.control}
rules={{
required: question.type === 'mandatory',
}}
render={({field: {onChange, value}}) => {
console.log(value)
return(
<RNCheckboxGroup onSelectionChange={(cange => {const values = cange?.map(item=>item.value); onChange(values)})} defaultValue={value} options={optiosList} />
)
console.log(value);
return (
<RNCheckboxGroup
onSelectionChange={change =>
handleChange(change, onChange)
}
defaultValue={value?.answer}
options={optiosList}
/>
);
}}
name={`${sectionId}.${questionId}`}
name={`widgetContext.${widgetId}.sectionContext.${props.sectionId}.questionContext.${questionId}`}
/>
<ErrorMessage show={error?.[sectionId]?.[questionId]} />
<ErrorMessage show={error?.sectionContext?.[sectionId]?.questionContext?.[questionId]} />
{associatedQuestions?.map((nextQuestion: string, index) => {
if (
template.questions[

View File

@@ -4,17 +4,15 @@ import {View} from 'react-native';
import {useState} from 'react';
import {Control, Controller} from 'react-hook-form';
import {useSelector} from 'react-redux';
import RNRadioButton from '../../../../RN-UI-LIB/src/components/radio_button/RadioButton';
import RadioChip from '../../../../RN-UI-LIB/src/components/radio_button/RadioChip';
import RadioGroup from '../../../../RN-UI-LIB/src/components/radio_button/RadioGroup';
import RNDropDown from '../../../../RN-UI-LIB/src/components/dropdown/Dropdown';
import RNOptions from '../../../../RN-UI-LIB/src/components/dropdown/Options';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import {GenericStyles} from '../../../../RN-UI-LIB/src/styles';
import template from '../../../data/RealTemplateData.json';
import {RootState} from '../../../store/store';
import QuestionRenderingEngine from '../QuestionRenderingEngine';
import ErrorMessage from './ErrorMessage';
import RNDropDown from '../../../../RN-UI-LIB/src/components/dropdown/Dropdown';
import RNOptions from '../../../../RN-UI-LIB/src/components/dropdown/Options';
import {AnswerType, Options} from '../interface';
import { useAppSelector } from '../../../hooks';
interface IDropDown {
questionType: string;
@@ -29,6 +27,7 @@ interface IDropDown {
const DropDown: React.FC<IDropDown> = props => {
const {questionId, widgetId, journeyId, caseId, sectionId, error} = props;
const template = useAppSelector(state => state.case.templateData);
const question =
template.questions[questionId as keyof typeof template.questions];
const options = template.options;
@@ -38,9 +37,7 @@ const DropDown: React.FC<IDropDown> = props => {
const dataFromRedux = useSelector(
(state: RootState) =>
state.case.caseForm?.[caseId]?.[journeyId]?.[widgetId]?.[
sectionId
]?.[questionId],
state.case.caseForm?.[caseId]?.[journeyId]?.widgetContext?.[widgetId]?.sectionContext?.[sectionId]?.questionContext?.[questionId]
);
useEffect(() => {
@@ -50,27 +47,6 @@ const DropDown: React.FC<IDropDown> = props => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataFromRedux]);
// const dispatch = useDispatch();
// const answer = useSelector(
// (state: RootState) =>
// state.counter.caseForm?.[caseId]?.[journeyId]?.[widgetId]?.[
// sectionId
// ]?.[questionId] || 'not_found',
// );
// const registerValue = (id: string) => {
// dispatch(
// updateInteraction({
// caseId,
// journeyId,
// widgetId,
// questionId,
// answer: id,
// sectionId,
// }),
// );
// };
const computeNextQuestion = (optionId: keyof typeof options) => {
if (options[optionId]?.associateQuestions.length < 1) {
setAssociatedQuestions([]);
@@ -79,6 +55,16 @@ const DropDown: React.FC<IDropDown> = props => {
setAssociatedQuestions(options[optionId]?.associateQuestions);
};
const handleChange = (
change: string | null,
onChange: (...event: any[]) => void,
) => {
onChange({
answer: change,
type: AnswerType.option,
});
};
return (
<View style={GenericStyles.mt12}>
<Text dark bold style={GenericStyles.mb12}>
@@ -93,24 +79,25 @@ const DropDown: React.FC<IDropDown> = props => {
required: question.type === 'mandatory',
}}
render={({field: {onChange, value}}) => (
<RNDropDown bottomSheetHeight={question.options.length -1 * 10} onValueChange={onChange} value={value}>
{question.options.map(option => {
return(
<RNOptions
id={option}
label={
options[
option as keyof typeof options
]?.text
}
/>
)})}
<RNDropDown
bottomSheetHeight={question.options.length - 1 * 10}
onValueChange={change => handleChange(change, onChange)}
value={value?.answer}>
{question.options.map((option: keyof typeof options) => {
return (
<RNOptions
id={option as string}
label={
options[option]?.text
}
/>
);
})}
</RNDropDown>
)}
name={`${sectionId}.${questionId}`}
name={`widgetContext.${widgetId}.sectionContext.${props.sectionId}.questionContext.${questionId}`}
/>
<ErrorMessage show={error?.[sectionId]?.[questionId]} />
<ErrorMessage show={error?.sectionContext?.[sectionId]?.questionContext?.[questionId]} />
{associatedQuestions?.map((nextQuestion: string, index) => {
if (
template.questions[

View File

@@ -1,23 +1,23 @@
import React, { useEffect } from 'react';
import { Control, Controller } from 'react-hook-form';
import {
Image,
ImageBackground,
Pressable,
StyleSheet,
View,
View
} from 'react-native';
import React, {useEffect} from 'react';
import {Control, Controller} from 'react-hook-form';
import CameraClickPicture from '../../../../RN-UI-LIB/src/components/camera_click_picture/CameraClickPicture';
import template from '../../../data/RealTemplateData.json';
import {GenericStyles} from '../../../../RN-UI-LIB/src/styles';
import {useState} from 'react';
import {useSelector} from 'react-redux';
import {RootState} from '../../../store/store';
import ErrorMessage from './ErrorMessage';
import DeleteIcon from '../../../../RN-UI-LIB/src/Icons/DeleteIcon';
import {COLORS} from '../../../../RN-UI-LIB/src/styles/colors';
import { useState } from 'react';
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 { RootState } from '../../../store/store';
import { AnswerType } from '../interface';
import ErrorMessage from './ErrorMessage';
interface IImageUpload {
questionType: string;
@@ -33,13 +33,12 @@ interface IImageUpload {
const ImageUpload: React.FC<IImageUpload> = props => {
const {questionId, error, sectionId, caseId, journeyId, widgetId} = props;
const [image, setImage] = useState('');
const template = useAppSelector(state => state.case.templateData);
const question =
template.questions[questionId as keyof typeof template.questions];
const dataFromRedux = useSelector(
(state: RootState) =>
state.case.caseForm?.[caseId]?.[journeyId]?.[widgetId]?.[
sectionId
]?.[questionId],
state.case.caseForm?.[caseId]?.[journeyId]?.widgetContext?.[widgetId]?.sectionContext?.[sectionId]?.questionContext?.[questionId]
);
useEffect(() => {
@@ -51,6 +50,19 @@ const ImageUpload: React.FC<IImageUpload> = props => {
if (!question) {
return null;
}
const handleChange = (
clickedImage: string | null,
onChange: (...event: any[]) => void,
) => {
const data = `data:image/jpeg;base64,${clickedImage}`;
setImage(data);
onChange({
answer: data,
type: AnswerType.text,
});
};
return (
<View style={[GenericStyles.mt12]}>
<Text dark bold style={GenericStyles.mb12}>
@@ -67,14 +79,10 @@ const ImageUpload: React.FC<IImageUpload> = props => {
}}
render={({field: {onChange}}) => (
<CameraClickPicture
onPictureClickSuccess={clickedImage => {
const data = `data:image/jpeg;base64,${clickedImage}`;
setImage(data);
onChange(data);
}}
onPictureClickSuccess={clickedImage => handleChange(clickedImage, onChange)}
/>
)}
name={`${props.sectionId}.${questionId}`}
name={`widgetContext.${widgetId}.sectionContext.${props.sectionId}.questionContext.${questionId}`}
/>
) : (
<View>
@@ -87,7 +95,7 @@ const ImageUpload: React.FC<IImageUpload> = props => {
</ImageBackground>
</View>
)}
<ErrorMessage show={error?.[sectionId]?.[questionId]} />
<ErrorMessage show={error?.sectionContext?.[sectionId]?.questionContext?.[questionId]} />
</View>
);
};

View File

@@ -9,10 +9,11 @@ import RadioChip from '../../../../RN-UI-LIB/src/components/radio_button/RadioCh
import RadioGroup from '../../../../RN-UI-LIB/src/components/radio_button/RadioGroup';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import {GenericStyles} from '../../../../RN-UI-LIB/src/styles';
import template from '../../../data/RealTemplateData.json';
import {RootState} from '../../../store/store';
import QuestionRenderingEngine from '../QuestionRenderingEngine';
import ErrorMessage from './ErrorMessage';
import {AnswerType} from '../interface';
import {useAppSelector} from '../../../hooks';
interface IRadioButton {
questionType: string;
@@ -27,8 +28,8 @@ interface IRadioButton {
const RadioButton: React.FC<IRadioButton> = props => {
const {questionId, widgetId, journeyId, caseId, sectionId, error} = props;
const question =
template.questions[questionId as keyof typeof template.questions];
const template = useAppSelector(state => state.case.templateData);
const question = template.questions[questionId];
const options = template.options;
const [associatedQuestions, setAssociatedQuestions] = useState<
Array<string>
@@ -36,9 +37,9 @@ const RadioButton: React.FC<IRadioButton> = props => {
const dataFromRedux = useSelector(
(state: RootState) =>
state.case.caseForm?.[caseId]?.[journeyId]?.[widgetId]?.[
sectionId
]?.[questionId],
state.case.caseForm?.[caseId]?.[journeyId]?.widgetContext?.[
widgetId
]?.sectionContext?.[sectionId]?.questionContext?.[questionId],
);
useEffect(() => {
@@ -48,27 +49,6 @@ const RadioButton: React.FC<IRadioButton> = props => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataFromRedux]);
// const dispatch = useDispatch();
// const answer = useSelector(
// (state: RootState) =>
// state.counter.caseForm?.[caseId]?.[journeyId]?.[widgetId]?.[
// sectionId
// ]?.[questionId] || 'not_found',
// );
// const registerValue = (id: string) => {
// dispatch(
// updateInteraction({
// caseId,
// journeyId,
// widgetId,
// questionId,
// answer: id,
// sectionId,
// }),
// );
// };
const computeNextQuestion = (optionId: keyof typeof options) => {
if (options[optionId]?.associateQuestions.length < 1) {
setAssociatedQuestions([]);
@@ -77,6 +57,17 @@ const RadioButton: React.FC<IRadioButton> = props => {
setAssociatedQuestions(options[optionId]?.associateQuestions);
};
const handleChange = (
change: string | null,
onChange: (...event: any[]) => void,
) => {
computeNextQuestion(change as keyof typeof options);
onChange({
answer: change,
type: AnswerType.option,
});
};
return (
<View style={GenericStyles.mt12}>
<Text dark bold>
@@ -92,43 +83,42 @@ const RadioButton: React.FC<IRadioButton> = props => {
}}
render={({field: {onChange, value}}) => (
<RadioGroup
value={value}
onValueChange={change => {
onChange(change);
computeNextQuestion(change as keyof typeof options);
// registerValue(change);
}}
value={value?.answer}
onValueChange={change => handleChange(change, onChange)}
orientation={
question.metadata.orientation || 'verticle'
}>
{question?.metadata.buttonType === 'button'
? question.options.map(option => {
return(
<RNRadioButton
id={option}
value={
options[
option as keyof typeof options
]?.text
}
/>
)})
: question.options.map(option => (
? question.options.map((option: keyof typeof options) => {
return (
<RNRadioButton
id={option as string}
value={
options[option]?.text
}
/>
);
})
: question.options.map((option: keyof typeof options) => (
<RadioChip
id={option}
id={option as string}
value={
options[
option as keyof typeof options
]?.text
options[option]?.text
}
/>
))}
</RadioGroup>
)}
name={`${sectionId}.${questionId}`}
name={`widgetContext.${widgetId}.sectionContext.${props.sectionId}.questionContext.${questionId}`}
/>
<ErrorMessage show={error?.[sectionId]?.[questionId]} />
{associatedQuestions.map((nextQuestion: string, index) => {
<ErrorMessage
show={
error?.widgetContext?.[widgetId]?.sectionContext?.[
sectionId
]?.questionContext?.[questionId]
}
/>
{associatedQuestions?.map((nextQuestion: string, index) => {
if (
template.questions[
nextQuestion as keyof typeof template.questions
@@ -140,7 +130,7 @@ const RadioButton: React.FC<IRadioButton> = props => {
questionType={
template.questions[
nextQuestion as keyof typeof template.questions
].input_type
].inputType
}
questionId={nextQuestion}
name={widgetId}

View File

@@ -3,9 +3,10 @@ import React from 'react';
import {Control, Controller} from 'react-hook-form';
import {GenericStyles} from '../../../../RN-UI-LIB/src/styles';
import StarRating from '../../../../RN-UI-LIB/src/components/star_rating/StarRating';
import template from '../../../data/RealTemplateData.json';
import ErrorMessage from './ErrorMessage';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import { AnswerType } from '../interface';
import { useAppSelector } from '../../../hooks';
interface IRating {
questionType: string;
questionId: string;
@@ -18,13 +19,22 @@ interface IRating {
}
const Rating: React.FC<IRating> = props => {
const {questionId, error, sectionId} = props;
const {questionId, error, sectionId, widgetId} = props;
const template = useAppSelector(state => state.case.templateData);
const question =
template.questions[questionId as keyof typeof template.questions];
if (!question) {
return null;
}
const handleChange = (text : number, onChange : (...event: any[]) => void) => {
onChange({
answer: text,
type: AnswerType.number
})
}
return (
<View style={[GenericStyles.mt12]}>
<Text dark bold style={GenericStyles.mb12}>
@@ -40,14 +50,14 @@ const Rating: React.FC<IRating> = props => {
}}
render={({field: {onChange, value}}) => (
<StarRating
onSelectionChange={onChange}
defaultValue={value}
onSelectionChange={(value)=> handleChange(value,onChange)}
defaultValue={value?.answer}
maxRating={5}
/>
)}
name={`${props.sectionId}.${questionId}`}
name={`widgetContext.${widgetId}.sectionContext.${props.sectionId}.questionContext.${questionId}`}
/>
<ErrorMessage show={error?.[sectionId]?.[questionId]} />
<ErrorMessage show={error?.sectionContext?.[sectionId]?.questionContext?.[questionId]} />
</View>
);
};

View File

@@ -2,11 +2,12 @@ import {View} from 'react-native';
import React from 'react';
import {Control, Controller} from 'react-hook-form';
import template from '../../../data/RealTemplateData.json';
import RNTextArea from '../../../../RN-UI-LIB/src/components/TextArea';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import {GenericStyles} from '../../../../RN-UI-LIB/src/styles';
import ErrorMessage from './ErrorMessage';
import { AnswerType } from '../interface';
import { useAppSelector } from '../../../hooks';
interface ITextArea {
questionType: string;
@@ -20,13 +21,21 @@ interface ITextArea {
}
const TextArea: React.FC<ITextArea> = props => {
const {questionId, error, sectionId} = props;
const {questionId, error, sectionId, widgetId} = props;
const template = useAppSelector(state => state.case.templateData);
const question =
template.questions[questionId as keyof typeof template.questions];
if (!question) {
return null;
}
const handleChange = (text : string, onChange : (...event: any[]) => void) => {
onChange({
answer: text,
type: AnswerType.text
})
}
return (
<View style={[GenericStyles.mt12]}>
<Text dark bold>
@@ -42,16 +51,16 @@ const TextArea: React.FC<ITextArea> = props => {
}}
render={({field: {onChange, value}}) => (
<RNTextArea
onChangeText={onChange}
value={value}
onChangeText={(text)=> handleChange(text, onChange)}
value={value?.answer}
containerStyle={[GenericStyles.mt12]}
numberOfLines={3}
title=""
/>
)}
name={`${props.sectionId}.${questionId}`}
name={`widgetContext.${widgetId}.sectionContext.${props.sectionId}.questionContext.${questionId}`}
/>
<ErrorMessage show={error?.[sectionId]?.[questionId]} />
<ErrorMessage show={error?.sectionContext?.[sectionId]?.questionContext?.[questionId]} />
</View>
);
};

View File

@@ -4,9 +4,10 @@ import RNTextInput from '../../../../RN-UI-LIB/src/components/TextInput';
import {GenericStyles} from '../../../../RN-UI-LIB/src/styles';
import {Control, Controller} from 'react-hook-form';
import template from '../../../data/RealTemplateData.json';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import ErrorMessage from './ErrorMessage';
import { AnswerType } from '../interface';
import { useAppSelector } from '../../../hooks';
interface ITextInput {
questionType: string;
@@ -20,13 +21,21 @@ interface ITextInput {
}
const TextInput: React.FC<ITextInput> = props => {
const {questionId, error, sectionId} = props;
const {questionId, error, sectionId, widgetId} = props;
const template = useAppSelector(state => state.case.templateData);
const question =
template.questions[questionId as keyof typeof template.questions];
if (!question) {
return null;
}
const handleChange = (text : string, onChange : (...event: any[]) => void) => {
onChange({
answer: text,
type: AnswerType.text
})
}
return (
<View style={[GenericStyles.mt12]}>
<Text dark bold>
@@ -42,14 +51,14 @@ const TextInput: React.FC<ITextInput> = props => {
}}
render={({field: {onChange, value}}) => (
<RNTextInput
onChangeText={onChange}
value={value}
onChangeText={(text) => handleChange(text, onChange)}
value={value?.answer}
containerStyle={[GenericStyles.mt12]}
/>
)}
name={`${props.sectionId}.${questionId}`}
name={`widgetContext.${widgetId}.sectionContext.${props.sectionId}.questionContext.${questionId}`}
/>
<ErrorMessage show={error?.[sectionId]?.[questionId]} />
<ErrorMessage show={error?.widgetContext?.[widgetId]?.sectionContext?.[sectionId]?.questionContext?.[questionId]} />
</View>
);
};

View File

@@ -6,20 +6,16 @@ import {useDispatch, useSelector} from 'react-redux';
import Button from '../../../RN-UI-LIB/src/components/Button';
import Heading from '../../../RN-UI-LIB/src/components/Heading';
import Text from '../../../RN-UI-LIB/src/components/Text';
import BackArrowIcon from '../../../RN-UI-LIB/src/Icons/BackArrowIcon';
import CloseIcon from '../../../RN-UI-LIB/src/Icons/CloseIcon';
import {GenericStyles, getShadowStyle} from '../../../RN-UI-LIB/src/styles';
import {COLORS} from '../../../RN-UI-LIB/src/styles/colors';
import Realjson from '../../data/RealTemplateData.json';
import {updateInteraction} from '../../reducer/caseReducre';
import {useAppSelector} from '../../hooks';
import { updateCaseDetail } from '../../reducer/allCasesSlice';
import {deleteInteraction, updateInteraction} from '../../reducer/caseReducre';
import {RootState} from '../../store/store';
import {goBack, navigateToScreen} from '../utlis/navigationUtlis';
import {
ConditionType,
IDecision,
IEvaluateLeaf,
IEvaluateLeafJourney,
ILeaf,
} from './interface';
import {ConditionType, IDecision, ILeaf} from './interface';
import RenderQuestion from './RenderQuestion';
interface IWidget {
@@ -35,21 +31,25 @@ interface IWidget {
const Widget: React.FC<IWidget> = props => {
const {name, params} = props.route;
const {caseId, journey} = params;
const {sections, transitionRules, isLeaf, isNoBrainer} =
Realjson.widget[name as keyof typeof Realjson.widget];
const sectionMap = Realjson.sections;
const templateData = useAppSelector(state => state.case.templateData);
const caseData = useAppSelector(state => state.allCases.caseDetails[caseId]);
const {sections, transitionRules, isLeaf} = templateData.widget[name];
const sectionMap = templateData.sections;
const {actions, conditions} = transitionRules;
const [error, setError] = useState();
const dispatch = useDispatch();
const dataToBeValidated = useSelector(
(state: RootState) => state.case.caseForm?.[caseId]?.[journey]?.[name],
const dataToBeValidated = useAppSelector(
state => state.case.caseForm?.[caseId]?.[journey],
);
console.log(dataToBeValidated);
const journeyData = useSelector(
(state: RootState) => state.case.caseForm?.[caseId]?.[journey],
);
console.log(journeyData);
const {control, handleSubmit} = useForm({
defaultValues: dataToBeValidated,
@@ -58,7 +58,11 @@ const Widget: React.FC<IWidget> = props => {
const evaluateLeaf = (leaf: ILeaf, data: any): boolean => {
switch (leaf.operator) {
case 'MATCHES':
return leaf.right === data[leaf.section][leaf.left];
return (
leaf.right ===
data?.widgetContext?.[name]?.sectionContext?.[leaf.section]
?.questionContext?.[leaf.left]?.answer
);
default:
return false;
}
@@ -69,7 +73,9 @@ const Widget: React.FC<IWidget> = props => {
case 'MATCHES':
return (
leaf.right ===
journeyData[leaf.widgetId][leaf.section][leaf.left]
journeyData.widgetContext[leaf.widgetId]?.sectionContext?.[
leaf.section
]?.questionContext?.[leaf.left]
);
default:
return false;
@@ -99,7 +105,7 @@ const Widget: React.FC<IWidget> = props => {
const handleValidation = (rules: any, data: any) => {
let nextScreenName = '';
if (!isNoBrainer) {
if (rules !== null) {
if (rules.conditionType === 'LEAF_NODE') {
const answer = String(evaluateLeaf(rules, data));
nextScreenName = actions[answer as keyof typeof actions];
@@ -117,6 +123,7 @@ const Widget: React.FC<IWidget> = props => {
};
const onSubmit = (data: any) => {
console.log(data, 'submit');
dispatch(
updateInteraction({
caseId,
@@ -141,16 +148,31 @@ const Widget: React.FC<IWidget> = props => {
answer: data,
}),
);
const rules = Realjson.journey[journey].transitionRules.conditions;
const screeenAction = Realjson.journey[journey].transitionRules.actions;
let nextScreenName = '';
const rules =
templateData.journey[journey as keyof typeof templateData.journey]
.transitionRules.conditions;
const screeenAction =
templateData.journey[journey as keyof typeof templateData.journey]
.transitionRules.actions;
let nextActions = '';
if (rules.condition_type === 'LEAF_NODE') {
nextScreenName = screeenAction[evaluateLeafJourney(rules)];
nextActions = screeenAction[String(evaluateLeafJourney(rules as ILeaf))];
} else {
const answer = String(evaluateComposite(rules));
nextScreenName = screeenAction[answer];
const answer = String(evaluateComposite(rules as IDecision, data));
nextActions = screeenAction[answer];
}
console.log(nextActions);
// dispatch some actions to redux store
dispatch(updateCaseDetail({
caseId,
journeyId: journey,
widgetId: name,
answer: data,
caseData: caseData,
nextActions
}))
navigateToScreen('Home', {
journey: journey,
caseId,
@@ -187,7 +209,7 @@ const Widget: React.FC<IWidget> = props => {
GenericStyles.p16,
GenericStyles.whiteBackground,
]}>
{sections.map((section, index: number) => (
{sections.map((section: any, index: number) => (
<View
key={section + index}
style={[
@@ -226,7 +248,17 @@ const Widget: React.FC<IWidget> = props => {
variant={'secondary'}
style={styles.fb45}
title={'Back'}
onPress={goBack}
onPress={() => {
dispatch(
deleteInteraction({
caseId,
journeyId: journey,
widgetId: name,
}),
);
goBack();
}}
leftIcon={<BackArrowIcon size={10} />}
/>
<Button
style={styles.fb45}

View File

@@ -50,3 +50,17 @@ export interface ILeaf {
section: string;
widgetId: string;
}
export enum AnswerType {
text = 'text',
option = 'text',
number = 'text',
array = 'option'
}
export interface Options {
text: string;
associatedQuestions: Array<string>;
metadata: any;
}

View File

@@ -17,7 +17,10 @@ export enum ApiKeys {
GENERATE_OTP,
VERIFY_OTP,
ALL_CASES,
CASE_DETAIL
CASE_DETAIL,
PINNED_CASES,
LOGOUT,
FEEDBACK
}
const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
@@ -25,6 +28,9 @@ API_URLS[ApiKeys.GENERATE_OTP] = '/auth/otp/generate';
API_URLS[ApiKeys.VERIFY_OTP] = '/auth/otp/verify';
API_URLS[ApiKeys.ALL_CASES] = '/cases/all-cases';
API_URLS[ApiKeys.CASE_DETAIL] = '/cases/get-cases';
API_URLS[ApiKeys.PINNED_CASES] = '/cases/pin';
API_URLS[ApiKeys.LOGOUT] = '/auth/logout';
API_URLS[ApiKeys.FEEDBACK] = '/cases/feedback';
const MOCK_API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
@@ -74,7 +80,6 @@ const errorsToRetry = [];
const axiosInstance = axios.create();
axiosInstance.interceptors.request.use(request => {
console.log(request)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
request.retry = request?.retry < 4 ? request.retry : 3;

View File

@@ -5,6 +5,9 @@ export const navigationRef: RefObject<any> = React.createRef();
// if screen already exists then navigate to it otherwise push screen
export const navigateToScreen = (name: string, params: object = {}) => {
if(navigationRef.current?.getCurrentRoute()?.name === name) {
return;
}
navigationRef.current?.navigate(name, params);
}

View File

@@ -39,8 +39,14 @@
}
},
"actions": {
"true": "verified",
"false": "not verified"
"true": {
"status": "VERIFIED_SUCCESS",
"nextJourney": ""
},
"false":{
"status": "VERIFICATION_FAILED",
"nextJourney": "PERMANENT_ADDRESS_VERIFICATION_TASK"
}
}
},
"isLeaf": false
@@ -105,9 +111,8 @@
"isLeaf": true
},
"w4": {
"isNoBrainer": true,
"transitionRules": {
"conditions":{},
"conditions":null,
"actions": {
"true": "w6",
"false": "w6"
@@ -122,9 +127,8 @@
"isLeaf": false
},
"w5": {
"isNoBrainer": true,
"transitionRules": {
"conditions":{},
"conditions":null,
"actions": {
"true": "w4",
"false": "w4"
@@ -136,9 +140,8 @@
"isLeaf": false
},
"w6":{
"isNoBrainer": true,
"transitionRules": {
"conditions":{},
"conditions":null,
"actions": {
"true": "w6",
"false": "w6"

View File

@@ -15,6 +15,8 @@ interface IAllCasesSlice {
casesListMap: ICasesMap;
intermediateTodoList: ICaseItem[];
intermediateTodoListMap: ICasesMap;
selectedTodoListMap: ICasesMap;
selectedTodoListCount: number;
initialPinnedRankCount: number;
pinnedRankCount: number;
loading: boolean;
@@ -32,6 +34,8 @@ const initialState: IAllCasesSlice = {
casesListMap: {},
intermediateTodoList: [],
intermediateTodoListMap: {},
selectedTodoListCount: 0,
selectedTodoListMap: {},
initialPinnedRankCount: 0,
pinnedRankCount: 0,
loading: false,
@@ -134,13 +138,13 @@ const getPinnedListDetails = (casesList: ICaseItem[]) => {
let maxPinnedRank = 0;
const pinnedList: ICaseItem[] = [];
casesList.forEach(caseItem => {
const {pinnedRank} = caseItem;
if (!pinnedRank) {
const {pinRank} = caseItem;
if (pinRank === null || pinRank === undefined) {
return;
}
pinnedList.push(caseItem);
if (pinnedRank > maxPinnedRank) {
maxPinnedRank = pinnedRank;
if (pinRank > maxPinnedRank) {
maxPinnedRank = pinRank;
}
});
return {pinnedList, maxPinnedRank};
@@ -150,12 +154,19 @@ const allCasesSlice = createSlice({
name: 'cases',
initialState,
reducers: {
setLoading: (state, action) => {
state.loading = action.payload;
},
setCasesListData: (state, action) => {
const {allCases, details} = action.payload;
if (details?.length) {
const initialValue = {...state.caseDetails};
const detailsData = details.reduce((prev: any, item: any) => {
prev[item.id] = item;
prev[item.id] = {
...item,
isSynced: true,
};
return prev;
}, initialValue);
state.caseDetails = detailsData;
@@ -164,7 +175,60 @@ const allCasesSlice = createSlice({
const {pinnedList, maxPinnedRank} = getPinnedListDetails(allCases);
state.pinnedList = pinnedList;
state.pinnedRankCount = maxPinnedRank;
state.loading = false;
},
updateCaseDetail: (state, action) => {
const {caseId, journeyId, answer, caseData, nextActions} =
action.payload;
console.log(action.payload);
const updatedValue = {...caseData};
updatedValue.isSynced = false;
if (!updatedValue.context) {
updatedValue.context = {
taskSequence: updatedValue.tasks.map(task => task.taskType),
currentTask: nextActions.nextJourney,
taskContext: {
[journeyId]: [
{
taskStatus: nextActions.status,
...answer,
},
],
},
};
}
if (updatedValue.context.taskContext?.[journeyId]) {
const journey = [
...updatedValue.context.taskContext?.[journeyId],
{
taskStatus: nextActions.status,
...answer,
},
];
updatedValue.context = {
currentTask: nextActions.nextJourney,
taskContext: {
[journeyId]: journey,
},
};
} else {
const taskContext = updatedValue.taskContext;
(taskContext[journeyId] = [
{
taskStatus: nextActions.status,
...answer,
},
]),
(updatedValue.context = {
currentTask: nextActions.nextJourney,
taskContext,
});
}
state.caseDetails[caseId] = updatedValue;
},
setPinnedRank: (state, action) => {
const caseId = action.payload.caseReferenceId;
const isCasePresent = state.intermediateTodoListMap[caseId];
@@ -175,14 +239,14 @@ const allCasesSlice = createSlice({
state.pinnedRankCount++;
state.newlyPinnedCases++;
const selectedCase = {...action.payload};
selectedCase.pinnedRank = state.pinnedRankCount;
selectedCase.pinRank = state.pinnedRankCount;
state.intermediateTodoListMap[caseId] = selectedCase;
}
},
proceedToTodoList: state => {
state.intermediateTodoList = Object.values(
state.intermediateTodoListMap,
).sort((caseA, caseB) => caseA.pinnedRank - caseB.pinnedRank);
).sort((caseA, caseB) => caseA.pinRank - caseB.pinRank);
navigateToScreen('TodoList');
},
deleteIntermediateTodoListItem: (state, action) => {
@@ -208,23 +272,28 @@ const allCasesSlice = createSlice({
state.newlyPinnedCases = 0;
state.pinnedRankCount = state.initialPinnedRankCount;
},
setTodoList: state => {
state.newlyPinnedCases = 0;
const list = state.casesList.map(caseItem => {
const pinnedItem =
state.intermediateTodoListMap[caseItem.caseReferenceId];
return {
...caseItem,
pinnedRank: pinnedItem
? pinnedItem.pinnedRank
: caseItem.pinnedRank,
};
});
state.casesList = list;
setTodoListOffline: (state, action) => {
state.casesList = action.payload;
state.intermediateTodoListMap = {};
state.newlyPinnedCases = 0;
navigateToScreen('Home');
},
setSelectedTodoListMap: (state, action) => {
const caseId = action.payload.caseReferenceId;
const isCasePresent = state.selectedTodoListMap[caseId];
if (isCasePresent) {
delete state.selectedTodoListMap[caseId];
state.selectedTodoListCount--;
} else {
state.selectedTodoListCount++;
const selectedCase = {...action.payload};
state.selectedTodoListMap[caseId] = selectedCase;
}
},
resetSelectedTodoList: (state) => {
state.selectedTodoListCount = 0;
state.selectedTodoListMap = {};
},
setFilters: (state , action) => {
state.filters = {...state.filters , ...action.payload}
},
@@ -242,9 +311,10 @@ const allCasesSlice = createSlice({
});
export const {
setLoading,
setCasesListData,
setPinnedRank,
setTodoList,
setTodoListOffline,
resetTodoList,
filterData,
proceedToTodoList,
@@ -252,7 +322,10 @@ export const {
setSelectedFilters,
setFilterList,
clearSelectedFilters,
setFilters
setFilters,
setSelectedTodoListMap,
resetSelectedTodoList,
updateCaseDetail,
} = allCasesSlice.actions;
export default allCasesSlice.reducer;

View File

@@ -7,26 +7,23 @@ interface ICaseReducer {
caseForm: {
[caseId: string]: {
[journeyId: string]: {
[sectionId: string]: {
"widgetContext":{
[widgetId: string]: {
[questionId: string]: string;
"sectionContext":{
[sectionId: string]: {
"questionContext":{
[questionId: string]: string;
}
};
}
};
};
};
};
};
toBeSynced: {
[caseId: string]: {
[journeyId: string]: {
[sectionId: string]: {
[widgetId: string]: {
[questionId: string]: string;
};
};
}
};
};
};
toBeSynced: any;
allCases: Array<Data>;
templateData: any;
}
const initialState = {
@@ -148,7 +145,8 @@ const initialState = {
GEOLOCATION: [],
},
},
};
templateData: {}
} as ICaseReducer;
export const caseSlice = createSlice({
name: 'case',
@@ -167,14 +165,24 @@ export const caseSlice = createSlice({
data[caseId] = {};
}
if (!data[caseId][journeyId]) {
// @ts-ignore
data[caseId][journeyId] = {};
}
if (!data[caseId][journeyId][widgetId]) {
data[caseId][journeyId][widgetId] = {};
}
data[caseId][journeyId][widgetId] = answer;
data[caseId][journeyId] = answer;
state.caseForm = data;
},
deleteInteraction: (state, action) => {
const {caseId, journeyId, widgetId, answer} = action.payload;
console.log({caseId, journeyId, widgetId, answer})
const data = state.caseForm;
delete data[caseId][journeyId].widgetContext[widgetId];
state.caseForm = data;
},
updateTemplateData: (state, action) => {
state.templateData = action.payload;
},
setAllCases: (state, action) => {
state.allCases = action.payload;
},
@@ -185,7 +193,7 @@ export const caseSlice = createSlice({
},
});
export const {increaseByOne, decreaseByOne, updateInteraction, setAllCases} =
export const {increaseByOne, decreaseByOne, updateInteraction, setAllCases, deleteInteraction, updateTemplateData} =
caseSlice.actions;
export default caseSlice.reducer;

View File

@@ -37,6 +37,9 @@ const loginSlice = createSlice({
setFormLoading: (state, action) => {
state.isLoading = action.payload;
},
resetLoginForm: state => {
state = {...initialState}
},
},
});
@@ -46,6 +49,7 @@ export const {
setVerifyOTPSuccess,
setShowOTPScreen,
setFormLoading,
resetLoginForm
} = loginSlice.actions;
export default loginSlice.reducer;

View File

@@ -0,0 +1,44 @@
import {StyleSheet, View} from 'react-native';
import React from 'react';
import {GenericStyles} from '../../../RN-UI-LIB/src/styles';
import Avatar from '../../../RN-UI-LIB/src/components/Avatar';
import Text from '../../../RN-UI-LIB/src/components/Text';
import {COLORS} from '../../../RN-UI-LIB/src/styles/colors';
import Heading from '../../../RN-UI-LIB/src/components/Heading';
import {useSelector} from 'react-redux';
import {RootState} from '../../store/store';
const AccountDetails = () => {
const {phoneNumber, name} = useSelector(
(state: RootState) => state.user.user!!,
);
return (
<View style={[GenericStyles.row, styles.accountDetails]}>
{/* Need URI for the profile pic */}
<Avatar name={'Profile'} dataURI={'uri'} />
<View style={styles.detailsContainer}>
<Heading type="h5" dark bold>
Account Details
</Heading>
<Text>{name}</Text>
<Text light small>
Logged in via {phoneNumber}
</Text>
</View>
</View>
);
};
export default AccountDetails;
const styles = StyleSheet.create({
accountDetails: {
borderBottomColor: COLORS.BORDER.PRIMARY,
borderBottomWidth: 1,
},
detailsContainer: {
marginLeft: 12,
marginBottom: 20,
alignSelf: 'flex-start',
},
});

View File

@@ -0,0 +1,42 @@
import {Pressable, StyleSheet, View} from 'react-native';
import React from 'react';
import Heading from '../../../RN-UI-LIB/src/components/Heading';
import {GenericStyles} from '../../../RN-UI-LIB/src/styles';
import {COLORS} from '../../../RN-UI-LIB/src/styles/colors';
import {navigateToScreen} from '../../components/utlis/navigationUtlis';
import ArrowBackSmallIcon from '../../../RN-UI-LIB/src/Icons/ArrowBackSmallIcon';
const ProfileHeader = () => {
const handleBackIconPress = () => {
navigateToScreen('Home');
};
return (
<View
style={[
GenericStyles.row,
GenericStyles.alignCenter,
styles.header,
]}>
<Pressable onPress={handleBackIconPress}>
<ArrowBackSmallIcon />
</Pressable>
<Heading type="h5" style={styles.headerLabel}>
Profile
</Heading>
</View>
);
};
export default ProfileHeader;
const styles = StyleSheet.create({
header: {
backgroundColor: COLORS.BACKGROUND.HEADER,
height: 56,
paddingLeft: 26,
},
headerLabel: {
color: COLORS.BACKGROUND.PRIMARY,
marginLeft: 16,
},
});

View File

@@ -0,0 +1,61 @@
import {Alert, Pressable, StyleSheet, View} from 'react-native';
import React from 'react';
import {GenericStyles} from '../../../RN-UI-LIB/src/styles';
import Text from '../../../RN-UI-LIB/src/components/Text';
import AccountDetails from './AccountDetails';
import {useAppDispatch} from '../../hooks';
import {logout} from '../../action/authActions';
import ProfileHeader from './ProfileHeader';
import LogoutIcon from '../../../RN-UI-LIB/src/Icons/LogoutIcon';
const Profile = () => {
const dispatch = useAppDispatch();
const handleLogout = () => {
Alert.alert('Confirm', 'Are you sure you want to logout? ', [
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Logout',
onPress: () => dispatch(logout()),
style: 'destructive',
},
]);
};
return (
<View style={GenericStyles.fill}>
<View>
<ProfileHeader />
</View>
<View style={GenericStyles.p16}>
<View style={GenericStyles.mb24}>
<AccountDetails />
</View>
<Pressable
onPress={handleLogout}
style={[
GenericStyles.row,
GenericStyles.alignCenter,
styles.logoutContainer,
]}>
<LogoutIcon />
<Text style={styles.logoutText}>Logout</Text>
</Pressable>
</View>
</View>
);
};
export default Profile;
const styles = StyleSheet.create({
logoutContainer: {
marginLeft: 10,
},
logoutText: {
marginLeft: 6,
},
});

View File

@@ -12,13 +12,15 @@ import {CaseStatuses, ICaseItem, CaseTypes} from './interface';
import {COMPLETED_STATUSES, ListHeaderItems} from './contants';
import CaseItem from './CaseItem';
import {Search} from "../../../RN-UI-LIB/src/utlis/search";
import CasesActionButtons from './CasesActionButtons';
import FloatingInfoText from '../../components/floatingInfoText';
export const Separator = () => <View style={styles.separator} />;
export const getItem = (item: Array<ICaseItem>, index: number) => item[index];
const AllCases = () => {
const {casesList, newlyPinnedCases} = useSelector(
const {casesList, newlyPinnedCases, selectedTodoListCount} = useSelector(
(state: RootState) => state.allCases,
);
const dispatch = useDispatch();
@@ -54,10 +56,13 @@ const AllCases = () => {
//extracting data for todo list and other cases
filteredList.forEach(caseItem => {
if (caseItem.pinnedRank) {
pinnedList.push({...caseItem, type: CaseTypes.TODO});
} else {
if (
caseItem.pinRank === null ||
caseItem.pinRank === undefined
) {
otherList.push({...caseItem, type: CaseTypes.CASE});
} else {
pinnedList.push({...caseItem, type: CaseTypes.TODO});
}
});
@@ -73,11 +78,6 @@ const AllCases = () => {
compiledList.push(...otherList);
}
pinnedList.sort(
(caseItemA, caseItemB) =>
caseItemA.pinnedRank - caseItemB.pinnedRank,
);
let completedPinnedCasesCount = 0;
pinnedList.forEach(todo => {
if (todo.caseStatus === CaseStatuses.CLOSED) {
@@ -95,14 +95,6 @@ const AllCases = () => {
};
}, [filteredList]);
const handleTodoProceedClick = () => {
dispatch(proceedToTodoList());
};
const handleCancelTodoList = () => {
dispatch(resetTodoList());
};
const handleSearchChange = (query: string) => {
const filterList = query.length > 0 ? Search(query , filteredList || [] , { keys: ['customerInfo.name']}).map(filteredListItem => filteredListItem.obj) : filteredList;
setFilteredList(filterList);
@@ -133,27 +125,14 @@ const AllCases = () => {
) : (
<Text>Nothing to show</Text>
)}
{newlyPinnedCases ? (
<View
style={[
GenericStyles.row,
GenericStyles.justifyContentSpaceBetween,
GenericStyles.p16,
styles.actionBtns,
]}>
<Button
variant={'secondary'}
style={styles.fb45}
title={'Cancel'}
onPress={handleCancelTodoList}
/>
<Button
style={styles.fb45}
title={'Proceed'}
onPress={handleTodoProceedClick}
/>
</View>
) : null}
{newlyPinnedCases || selectedTodoListCount ? (
<CasesActionButtons />
) : (
<FloatingInfoText
bottom={24}
message="Press on customer image to select cases"
/>
)}
</View>
);
};
@@ -168,9 +147,6 @@ const styles = StyleSheet.create({
backgroundColor: COLORS.BACKGROUND.PRIMARY,
borderTopWidth: 1,
},
fb45: {
flexBasis: '45%',
},
});
export default AllCases;

View File

@@ -0,0 +1,109 @@
import {StyleSheet, View} from 'react-native';
import React from 'react';
import {GenericStyles} from '../../../RN-UI-LIB/src/styles';
import {COLORS} from '../../../RN-UI-LIB/src/styles/colors';
import Button from '../../../RN-UI-LIB/src/components/Button';
import {useSelector} from 'react-redux';
import {
proceedToTodoList,
resetSelectedTodoList,
resetTodoList,
} from '../../reducer/allCasesSlice';
import {RootState} from '../../store/store';
import {CaseTypes, ICaseItem} from './interface';
import {useAppDispatch} from '../../hooks';
import {postPinnedList} from '../../action/dataActions';
export const CasesActionButtons = () => {
const dispatch = useAppDispatch();
const {
newlyPinnedCases,
selectedTodoListCount,
selectedTodoListMap,
casesList,
} = useSelector((state: RootState) => state.allCases);
const handleTodoProceedClick = () => {
dispatch(proceedToTodoList());
};
const handleCancelTodoList = () => {
dispatch(resetTodoList());
};
const handleRemovePinnedList = () => {
const updatedPinnedList: ICaseItem[] = [];
const updatedCaseList: ICaseItem[] = casesList.map(caseItem => {
const {caseReferenceId, pinRank} = caseItem;
if (selectedTodoListMap[caseReferenceId]) {
caseItem.pinRank = null;
caseItem.type = CaseTypes.CASE;
} else if (pinRank !== null && pinRank !== undefined) {
updatedPinnedList.push(caseItem);
}
return caseItem;
});
dispatch(resetSelectedTodoList());
dispatch(postPinnedList(updatedPinnedList, updatedCaseList));
};
if (!newlyPinnedCases && !selectedTodoListCount) {
return null;
}
const renderActions = () => {
if (newlyPinnedCases) {
return (
<>
<Button
variant={'secondary'}
style={styles.fb45}
title={'Cancel'}
onPress={handleCancelTodoList}
/>
<Button
style={styles.fb45}
title={'Proceed'}
onPress={handleTodoProceedClick}
/>
</>
);
}
if (selectedTodoListCount) {
return (
<Button
style={GenericStyles.fill}
title={'Remove from list'}
onPress={handleRemovePinnedList}
buttonStyle={styles.removeBtn}
/>
);
}
};
return (
<View
style={[
GenericStyles.row,
GenericStyles.justifyContentSpaceBetween,
GenericStyles.p16,
styles.actionBtns,
]}>
{renderActions()}
</View>
);
};
const styles = StyleSheet.create({
actionBtns: {
borderTopColor: COLORS.BORDER.PRIMARY,
backgroundColor: COLORS.BACKGROUND.PRIMARY,
borderTopWidth: 1,
},
fb45: {
flexBasis: '45%',
},
removeBtn: {
backgroundColor: COLORS.TEXT.RED,
},
});
export default CasesActionButtons;

View File

@@ -1,43 +1,62 @@
import {Pressable, StyleSheet, View} from 'react-native';
import React from 'react';
import {CaseStatuses, ICaseItem, TaskTitleUIMapping, CaseTypes} from './interface';
import {
CaseStatuses,
ICaseItem,
TaskTitleUIMapping,
CaseTypes,
} from './interface';
import Avatar from '../../../RN-UI-LIB/src/components/Avatar';
import Text from '../../../RN-UI-LIB/src/components/Text';
import Heading from '../../../RN-UI-LIB/src/components/Heading';
import {GenericStyles} from '../../../RN-UI-LIB/src/styles';
import {useDispatch, useSelector} from 'react-redux';
import {setPinnedRank} from '../../reducer/allCasesSlice';
import {
setPinnedRank,
setSelectedTodoListMap,
} from '../../reducer/allCasesSlice';
import {RootState} from '../../store/store';
import RoundCheckIcon from '../../icons/RoundCheckIcon';
import {navigateToScreen} from '../../components/utlis/navigationUtlis';
interface IListItem {
caseData: ICaseItem;
// True if it's from the inside todo screen
isTodoItem?: boolean;
compleatedList?: boolean;
}
const ListItem: React.FC<IListItem> = props => {
const {caseData, compleatedList, isTodoItem} = props;
const {type, caseReferenceId: caseId, customerInfo} = caseData;
const {type, caseReferenceId: caseId} = caseData;
const dispatch = useDispatch();
if (!compleatedList && caseData.caseStatus === CaseStatuses.CLOSED) {
return null;
}
const {intermediateTodoListMap} = useSelector(
(state: RootState) => state.allCases,
);
const {
intermediateTodoListMap,
selectedTodoListMap,
newlyPinnedCases,
selectedTodoListCount,
} = useSelector((state: RootState) => state.allCases);
const handleAvatarClick = () => {
if (isTodoItem || caseData.caseStatus === CaseStatuses.CLOSED) {
return;
}
if (type === CaseTypes.CASE && selectedTodoListCount > 0) {
return;
}
if (type === CaseTypes.TODO && newlyPinnedCases > 0) {
return;
}
if (type === CaseTypes.CASE) {
dispatch(setPinnedRank(caseData));
return;
} else if (type === CaseTypes.TODO) {
dispatch(setSelectedTodoListMap(caseData));
// select todo item
}
};
@@ -46,7 +65,9 @@ const ListItem: React.FC<IListItem> = props => {
navigateToScreen('caseDetail', {caseId});
};
const caseSelected = !isTodoItem && intermediateTodoListMap?.[caseId];
const caseSelected =
!isTodoItem &&
(intermediateTodoListMap?.[caseId] || selectedTodoListMap?.[caseId]);
const address = caseData.currentTask.metadata?.address;
const poneNumber = caseData.currentTask.metadata?.primaryPhoneNumber;
@@ -73,7 +94,9 @@ const ListItem: React.FC<IListItem> = props => {
GenericStyles.ph16,
styles.pv12,
GenericStyles.row,
GenericStyles.whiteBackground,
caseSelected
? GenericStyles.silverBackground
: GenericStyles.whiteBackground,
]}>
<Pressable
style={[styles.avatarContainer, styles.alignSelf]}
@@ -93,9 +116,7 @@ const ListItem: React.FC<IListItem> = props => {
</Heading>
<Text dark ellipsizeMode="tail" numberOfLines={2}>
{TaskTitleUIMapping[caseData.currentTask.title]}:{' '}
<Text>
{displayAddress}
</Text>
<Text>{displayAddress}</Text>
</Text>
{/* TODO write color coding logic with tag component */}
{/* {caseData.caseVerdict !== caseVerdict.NEW && (

View File

@@ -1,19 +1,58 @@
import React, {useEffect} from 'react';
import {StyleSheet, View} from 'react-native';
import {Pressable, StyleSheet, View} from 'react-native';
import Heading from '../../../RN-UI-LIB/src/components/Heading';
import Tabs from '../../../RN-UI-LIB/src/components/tabs/Tabs';
import Text from '../../../RN-UI-LIB/src/components/Text';
import ArrowBackSmallIcon from '../../../RN-UI-LIB/src/Icons/ArrowBackSmallIcon';
import AvatarIcon from '../../../RN-UI-LIB/src/Icons/AvatarIcon';
import NaviLogoIcon from '../../../RN-UI-LIB/src/Icons/NaviLogoIcon';
import {GenericStyles} from '../../../RN-UI-LIB/src/styles';
import {COLORS} from '../../../RN-UI-LIB/src/styles/colors';
import {getAllCases} from '../../action/dataActions';
import {navigateToScreen} from '../../components/utlis/navigationUtlis';
import {useAppDispatch, useAppSelector} from '../../hooks';
import {
resetSelectedTodoList,
resetTodoList,
} from '../../reducer/allCasesSlice';
import AllCases from './AllCases';
import ComplatedCase from './ComplatedCase';
import {CaseStatuses} from './interface';
import RealJson from '../../data/RealTemplateData.json';
import { updateTemplateData } from '../../reducer/caseReducre';
const LogoActions = () => {
const {newlyPinnedCases, selectedTodoListCount} = useAppSelector(
state => state.allCases,
);
const dispatch = useAppDispatch();
if (!newlyPinnedCases && !selectedTodoListCount) {
return <NaviLogoIcon />;
}
const handleBackPressIfTodoSelected = () => {
dispatch(newlyPinnedCases ? resetTodoList() : resetSelectedTodoList());
};
const count = newlyPinnedCases || selectedTodoListCount;
return (
<View style={GenericStyles.centerAlignedRow}>
<Pressable onPress={handleBackPressIfTodoSelected}>
<ArrowBackSmallIcon />
</Pressable>
<Text style={[styles.text, styles.selectedText]} bold light>
{count} selected
</Text>
</View>
);
};
const AllCasesMain = () => {
const data = useAppSelector(state => state.allCases.casesList);
const completed = data.filter(
const {casesList, newlyPinnedCases, selectedTodoListCount} = useAppSelector(
state => state.allCases,
);
const completed = casesList.filter(
caseData =>
caseData.caseStatus === CaseStatuses.CLOSED ||
caseData.caseStatus === CaseStatuses.EXPIRED ||
@@ -23,7 +62,7 @@ const AllCasesMain = () => {
const tabItems = [
{
key: 'first',
title: `Pending (${data.length - completed.length})`,
title: `Pending (${casesList.length - completed.length})`,
content: AllCases,
},
{
@@ -37,20 +76,31 @@ const AllCasesMain = () => {
dispatch(getAllCases());
}, []);
const handleProfileIconPress = () => {
navigateToScreen('Profile');
};
useEffect(() => {
dispatch(updateTemplateData(RealJson));
}, []);
return (
<View style={GenericStyles.fill}>
<View
style={[
styles.headerTop,
GenericStyles.p16,
GenericStyles.row,
GenericStyles.centerAlignedRow,
GenericStyles.justifyContentSpaceBetween,
]}>
<NaviLogoIcon />
<Heading type={'h5'} style={styles.text} bold light>
My allocations
</Heading>
<View style={styles.iconSkeliton} />
<LogoActions />
{!newlyPinnedCases && !selectedTodoListCount && (
<Heading type={'h5'} style={styles.text} bold light>
My allocations
</Heading>
)}
<Pressable onPress={handleProfileIconPress}>
<AvatarIcon />
</Pressable>
</View>
<Tabs
selectedTab="first"
@@ -74,6 +124,10 @@ const styles = StyleSheet.create({
height: 64,
backgroundColor: COLORS.BACKGROUND.HEADER,
},
selectedText: {
marginLeft: 18,
fontSize: 16,
},
});
export default AllCasesMain;

View File

@@ -79,7 +79,7 @@ export const caseVerdictAndColor = {
export interface ICaseItem {
type?: CaseTypes; // this is for maintaing frontend
pinnedRank: number;
pinRank: number | null;
updatedAt: any;
allocatedAt: any;
caseReferenceId: string;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { SafeAreaView, StyleSheet, View } from 'react-native';
import { Alert, SafeAreaView, StyleSheet, View } from 'react-native';
import Button from '../../../RN-UI-LIB/src/components/Button';
import BackArrowIcon from '../../../RN-UI-LIB/src/Icons/BackArrowIcon';
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
@@ -20,8 +20,7 @@ interface ICaseDetails {
const CaseDetails: React.FC<ICaseDetails> = (props) => {
const {route : { params : {caseId}}} = props;
const detailObject = useAppSelector(state => state.allCases.caseDetails[caseId])
console.log(detailObject);
const detailObject = useAppSelector(state => state.allCases.caseDetails[caseId]);
return (
<SafeAreaView style={[GenericStyles.fill, GenericStyles.whiteBackground]}>
@@ -31,12 +30,23 @@ const CaseDetails: React.FC<ICaseDetails> = (props) => {
<View style={GenericStyles.p16}>
<UserDetailsSection caseDetail={detailObject} />
<TaskStepper caseDetail={detailObject} />
<Button
{/* <Button
style={{}}
title="Trigger journey"
variant="secondary"
onPress={() => navigateToScreen('w1', {caseId: 'labkjisbldfyuk', journey: 'COMMUNICATION_ADDRESS_VERIFICATION_TASK'})}
/>
onPress={() => Alert.alert("Confirm" , 'Are you sure you want to logout?', [
{
text: 'Cancle',
onPress: (value?: string) => console.log(value),
style: 'default'
},
{
text: 'Logout',
onPress: (value?: string) => console.log(value),
style: 'cancel'
},
])}
/> */}
</View>
</SafeAreaView>
);

View File

@@ -1,66 +1,108 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { useSelector } from 'react-redux';
import React, { useCallback, useState } from 'react';
import {
Image, Modal,
Pressable,
SafeAreaView,
StyleSheet,
View
} from 'react-native';
import Avatar from '../../../RN-UI-LIB/src/components/Avatar';
import Heading from '../../../RN-UI-LIB/src/components/Heading';
import Text from '../../../RN-UI-LIB/src/components/Text';
import CloseIcon from '../../../RN-UI-LIB/src/Icons/CloseIcon';
import { GenericStyles } from '../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import { dateFormat } from '../../../RN-UI-LIB/src/utlis/dates';
import CalenderIcon from '../../assets/icons/CalenderIcon';
import DocumentIcon from '../../assets/icons/DocumentIcon';
import IconLabel from '../../common/IconLabel';
import { RootState } from '../../store/store';
import { CaseDetail, LoanAccountStatusUIMapping, LoanTypeUIMapping } from './interface';
interface IUserDetailsSection{
caseDetail : any;
interface IUserDetailsSection {
caseDetail: CaseDetail;
}
const UserDetailsSection: React.FC<IUserDetailsSection> = (props) => {
const UserDetailsSection: React.FC<IUserDetailsSection> = props => {
const {caseDetail} = props;
const {customerInfo, loanDetails} = caseDetail;
const disbursalDate = loanDetails.disbursalDate;
const allocationDate = loanDetails.allocationDate;
const disbursalDate = dateFormat(new Date(loanDetails.disbursalDate), "DD MMM, YYYY");
const allocationDate = dateFormat(new Date(caseDetail.allocatedAt), "DD MMM, YYYY");
const [openImage, setOpenImage] = useState<boolean>(false);
const handleOpenClose = useCallback(() => {
setOpenImage(prev => !prev);
}, []);
return (
<View style={[GenericStyles.row, GenericStyles.pb16, styles.container]}>
<View style={[styles.avatarContainer]}>
<Avatar
name={customerInfo.customerName}
dataURI={customerInfo.imageURL}
/>
<Pressable onPress={handleOpenClose}>
<Avatar
size={60}
name={customerInfo.customerName}
dataURI={customerInfo.imageURL}
/>
</Pressable>
</View>
<View style={[styles.infoContainer]}>
<Heading dark type="h4">
{customerInfo.customerName}
</Heading>
<View style={[GenericStyles.row]}>
<View style={[GenericStyles.row, GenericStyles.mb8]}>
<IconLabel
containerStyle={{flex: 0.5}}
icon={<DocumentIcon />}
text={loanDetails.loanType}
text={LoanTypeUIMapping[loanDetails.loanType]}
/>
<IconLabel
text={loanDetails.loanAccountStatus}
containerStyle={{flex: 0.5}}
containerStyle={GenericStyles.ml10}
text={LoanAccountStatusUIMapping[loanDetails.loanAccountStatus]}
/>
</View>
<View style={[GenericStyles.row]}>
<View style={[GenericStyles.row, GenericStyles.mb8]}>
<IconLabel
icon={<CalenderIcon />}
text={disbursalDate}
containerStyle={{flex: 0.5}}
/>
<IconLabel
text={`${loanDetails.tenureMonths} months`}
containerStyle={{flex: 0.5}}
text={`${loanDetails.tenureMonths} ${loanDetails.tenureMonths > 1 ? "Months" : "Month"}`}
containerStyle={GenericStyles.ml10}
/>
</View>
<View style={[GenericStyles.pt16]}>
<Text style={[GenericStyles.fontSize15]}>
<Text light style={[GenericStyles.fontSize15]}>
Allocated on {allocationDate}
</Text>
</View>
</View>
<Modal animationType={'slide'} visible={openImage} onRequestClose={handleOpenClose}>
<SafeAreaView
style={[
GenericStyles.fill,
{backgroundColor: COLORS.BACKGROUND.HEADER},
]}>
<View style={GenericStyles.p16}>
<Pressable onPress={handleOpenClose}>
<CloseIcon />
</Pressable>
</View>
<View
style={[
GenericStyles.fill,
GenericStyles.alignCenter,
GenericStyles.row
]}>
<Image
style={[
GenericStyles.fill,
styles.imageStyle,
]}
source={{uri: customerInfo.imageURL}}
resizeMode={'contain'}
/>
</View>
</SafeAreaView>
</Modal>
</View>
);
};
@@ -78,6 +120,12 @@ const styles = StyleSheet.create({
infoContainer: {
flex: 0.6,
},
imageStyle:{
height: 300
},
backgroundColor:{
backgroundColor: COLORS.BACKGROUND.HEADER
}
});
export default UserDetailsSection;

View File

@@ -0,0 +1,28 @@
import { StyleSheet, Text, View } from 'react-native'
import React, { useEffect } from 'react'
import { useAppDispatch, useAppSelector } from '../../hooks'
import { _map } from '../../../RN-UI-LIB/src/utlis/common';
import { syncCaseDetail } from '../../action/dataActions';
const interactionsHandler = () => {
const dispatch = useAppDispatch();
const allCasesDetails = useAppSelector(state => state.allCases.caseDetails);
useEffect(() => {
const notSyncedCases: Array<any> = [];
_map(allCasesDetails, (el)=> {
if(!allCasesDetails[el].isSynced){
const caseId = allCasesDetails[el].id
notSyncedCases.push({...allCasesDetails[el], caseId})
}
})
console.log(notSyncedCases.length, "not synced")
notSyncedCases.map((caseItem)=> dispatch(syncCaseDetail(caseItem)))
}, [allCasesDetails])
return null
}
export default interactionsHandler

View File

@@ -0,0 +1,121 @@
export enum LoanType {
PERSONAL_LOAN = "PERSONAL_LOAN",
HOUSE_LOAN = "HOUSE_LOAN"
}
export enum LoanAccountStatus {
ACTIVE = "ACTIVE",
CLOSE = "CLOSE"
}
export enum LoanAccountStatusUIMapping {
ACTIVE = "Active",
CLOSE = "Close"
}
export enum LoanTypeUIMapping {
PERSONAL_LOAN = 'Personal loan',
HOUSE_LOAN = 'House loan'
}
export interface LoanDetails {
loanAccountNumber: string;
disbursalDate: string;
disbursementAmount: number;
firstDueDate: string;
tenureMonths: number;
loanType: LoanType;
loanAccountStatus: LoanAccountStatus;
productCode: string;
}
export interface CustomerInfo {
customerReferenceId: string;
customerName: string;
primaryPhoneNumber: string;
imageURL: string;
geoLocation: string;
}
export interface Address {
referenceId: string;
houseNumber: string;
lineOne: string;
lineTwo: string;
locality: string;
street: string;
city: string;
state: string;
pinCode: string;
type: string;
source: string;
current: boolean;
permanent: boolean;
zipCode?: any;
addressQualityStatus?: any;
}
export interface Metadata {
'@class': string;
address: Address;
geoLocation: string;
primaryPhoneNumber: string;
}
export interface Task {
taskId: number;
taskType: string;
metadata: Metadata;
}
export interface Q1 {
optionId: string;
text: string;
}
export interface QuestionContext {
q1: Q1[];
}
export interface S1 {
questionContext: QuestionContext;
}
export interface SectionContext {
s1: S1;
}
export interface W1 {
sectionContext: SectionContext;
}
export interface WidgetContext {
w1: W1;
}
export interface COMMUNICATIONADDRESSVERIFICATIONTASK {
taskStatus: string;
widgetContext: WidgetContext;
}
export interface TaskContext {
COMMUNICATION_ADDRESS_VERIFICATION_TASK: COMMUNICATIONADDRESSVERIFICATIONTASK[];
}
export interface Context {
taskSequence: string[];
currentTask: string;
taskContext: TaskContext;
}
export interface CaseDetail {
id: string;
caseStatus: string;
createdAt: number;
updatedAt: number;
allocatedAt: number;
loanDetails: LoanDetails;
customerInfo: CustomerInfo;
tasks: Task[];
context: Context;
}

View File

@@ -10,10 +10,7 @@ import TextInput from '../../../RN-UI-LIB/src/components/TextInput';
import BackArrowIcon from '../../../RN-UI-LIB/src/Icons/BackArrowIcon';
import {GenericStyles} from '../../../RN-UI-LIB/src/styles';
import {COLORS} from '../../../RN-UI-LIB/src/styles/colors';
import {
verifyOTP,
VerifyOTPPayload,
} from '../../action/authActions';
import {verifyOTP, VerifyOTPPayload} from '../../action/authActions';
import {useAppDispatch} from '../../hooks';
import {RootState} from '../../store/store';
import {navigateToScreen} from '../../components/utlis/navigationUtlis';
@@ -72,7 +69,7 @@ const OtpInput = () => {
render={({field: {onChange, onBlur, value}}) => (
<TextInput
keyboardType="phone-pad"
placeholder="Enter OTP"
placeholder="Enter 4 digits OTP"
onBlur={onBlur}
onChangeText={value => onChange(value)}
value={value}

View File

@@ -20,22 +20,40 @@ import Button from '../../../RN-UI-LIB/src/components/Button';
import {
deleteIntermediateTodoListItem,
resetTodoList,
setPinnedRank,
setTodoList,
} from '../../reducer/allCasesSlice';
import {navigateToScreen} from '../../components/utlis/navigationUtlis';
import SwipeableContainer from '../../components/SwipeableContainer';
import {postPinnedList} from '../../action/dataActions';
import {useAppDispatch} from '../../hooks';
const TodoList = () => {
const {
pinnedList = [],
intermediateTodoList = [],
casesList,
intermediateTodoListMap,
loading,
} = useSelector((state: RootState) => state.allCases);
const dispatch = useDispatch();
const dispatch = useAppDispatch();
const handleCreateTodoList = () => {
dispatch(setTodoList());
const updatedPinnedList: ICaseItem[] = [];
const updatedCaseList = casesList.map(caseItem => {
const pinnedItem =
intermediateTodoListMap[caseItem.caseReferenceId];
if (caseItem.pinRank !== null && caseItem.pinRank !== undefined) {
updatedPinnedList.push(caseItem);
}
if (pinnedItem) {
updatedPinnedList.push(pinnedItem);
}
return pinnedItem ? pinnedItem : caseItem;
});
const sortedPinnedList: ICaseItem[] = updatedPinnedList.sort(
(caseA, caseB) => caseA.pinRank - caseB.pinRank,
);
dispatch(postPinnedList(sortedPinnedList, updatedCaseList));
};
const handleCancelTodoList = () => {