NTP-22476 | Training module integration

This commit is contained in:
Aman Chaturvedi
2025-01-17 18:46:42 +05:30
parent e24ac80c7c
commit 5e585d66c6
9 changed files with 111 additions and 78 deletions

View File

@@ -0,0 +1,34 @@
import axiosInstance, { ApiKeys, getApiUrl } from '@components/utlis/apiHelper';
import {
setTrainingMaterialData,
setTrainingMaterialLoading,
} from '@reducers/trainingMaterialSlice';
import { AppDispatch } from '@store';
export const getTrainingMaterialList = () => (dispatch: AppDispatch) => {
dispatch(setTrainingMaterialLoading(true));
const url = getApiUrl(ApiKeys.GET_TRAINING_MATERIAL_LIST);
axiosInstance
.get(url)
.then((res) => {
if (res.data) {
dispatch(setTrainingMaterialLoading(false));
if (res?.data) {
dispatch(setTrainingMaterialData(res.data));
}
}
})
.finally(() => {
dispatch(setTrainingMaterialLoading(false));
});
};
export const getTrainingMaterialDetails = async (docRefId: string) => {
try {
const url = getApiUrl(ApiKeys.GET_TRAINING_MATERIAL_DETAILS, { docRefId });
const response = await axiosInstance.get(url);
return response.data;
} catch (error) {
throw error;
}
};

View File

@@ -7,6 +7,7 @@ import { IPdfRenderer } from './interfaces';
import RNFetchBlob from 'react-native-blob-util';
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
import Text from '@rn-ui-lib/components/Text';
import { isFunction } from '@components/utlis/commonFunctions';
const ERROR_STATE = 'ERROR';
@@ -38,7 +39,8 @@ const PdfRenderer: React.FC<IPdfRenderer> = ({ docId, url, onPageChange }) => {
}, [url]);
const handlePageChange = (pageNumber: number) => {
onPageChange?.(pageNumber);
if (isNaN(pageNumber) || !isFunction(onPageChange)) return;
onPageChange(pageNumber + 1);
};
return (

View File

@@ -112,6 +112,8 @@ export enum ApiKeys {
GET_REPAYMENTS = 'GET_REPAYMENTS',
GET_FEEDBACK_HISTORY = 'GET_FEEDBACK_HISTORY',
GET_PRIORTIY_FEEDBACK = 'GET_PRIORTIY_FEEDBACK',
GET_TRAINING_MATERIAL_LIST = 'GET_TRAINING_MATERIAL_LIST',
GET_TRAINING_MATERIAL_DETAILS = 'GET_TRAINING_MATERIAL_DETAILS',
}
export const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
@@ -164,8 +166,7 @@ API_URLS[ApiKeys.GET_PERFORMANCE_METRICS] = '/allocation-cycle/agent-performance
API_URLS[ApiKeys.GET_CASH_COLLECTED] = '/allocation-cycle/cash-collected-split';
API_URLS[ApiKeys.GET_TELEPHONE_NUMBERS] =
'/v2/collection-cases/telephones-view/{loanAccountNumber}';
API_URLS[ApiKeys.GET_TELEPHONE_NUMBERS_V2] =
'/collections/telephones-agent-call-activity-view';
API_URLS[ApiKeys.GET_TELEPHONE_NUMBERS_V2] = '/collections/telephones-agent-call-activity-view';
API_URLS[ApiKeys.FIRESTORE_INCONSISTENCY_INFO] = '/cases/sync-status';
API_URLS[ApiKeys.FIRESTORE_INCONSISTENCY_INFO_V2] = '/cases/v2/sync-status';
API_URLS[ApiKeys.GET_CASE_DETAILS_FROM_API] =
@@ -208,14 +209,15 @@ API_URLS[ApiKeys.SEND_COMMUNICATION_NAVI_ACCOUNT] = '/navi-communications/{loanA
API_URLS[ApiKeys.GENERATE_DYNAMIC_DOCUMENT] = '/documents/generate/{loanAccountNumber}';
API_URLS[ApiKeys.ALL_ESCALATIONS] = '/customer-escalation';
API_URLS[ApiKeys.DOWNLOAD_LATEST_APP] = 'https://longhorn.navi.com/api/app/download';
API_URLS[ApiKeys.GET_UNGROUPED_ADDRESSES] =
'/collection-cases/ungrouped/addresses';
API_URLS[ApiKeys.GET_UNGROUPED_ADDRESSES] = '/collection-cases/ungrouped/addresses';
API_URLS[ApiKeys.GET_GROUPED_ADDRESSES_AND_GEOLOCATIONS] =
'/collection-cases/grouped/addresses-geo-locations';
API_URLS[ApiKeys.GET_EMI_SCHEDULE] = '/collection-cases/emiSchedule';
API_URLS[ApiKeys.GET_REPAYMENTS] = '/collection-cases/repayments';
API_URLS[ApiKeys.GET_FEEDBACK_HISTORY] = '/feedback/filters';
API_URLS[ApiKeys.GET_PRIORTIY_FEEDBACK] = '/feedback/case-status';
API_URLS[ApiKeys.GET_TRAINING_MATERIAL_LIST] = '/training-page/content-list';
API_URLS[ApiKeys.GET_TRAINING_MATERIAL_DETAILS] = '/training-page/{docRefId}';
export const API_STATUS_CODE = {
OK: 200,
@@ -228,7 +230,7 @@ export const API_STATUS_CODE = {
INTERNAL_SERVER_ERROR: 500,
TOO_MANY_REQUESTS: 429,
GONE: 410,
POST_OPERATIVE_HOURS_ACTIVITY: 451
POST_OPERATIVE_HOURS_ACTIVITY: 451,
};
export const UNAUTHORIZED_VALUES = [API_STATUS_CODE.UNAUTHORIZED, API_STATUS_CODE.FORBIDDEN];
@@ -353,9 +355,9 @@ axiosInstance.interceptors.response.use(
);
if (
(config?.headers?.donotHandleError ||
donotHandleErrorOnStatusCode.includes(error?.response?.status)) &&
donotHandleErrorOnStatusCode.includes(error?.response?.status)) &&
// Logout even donotHandleError is true when status code is 401, 403
!config?.headers?.autoLogoutOnUnauthorized
!config?.headers?.autoLogoutOnUnauthorized
) {
return Promise.reject(error);
}

View File

@@ -1,42 +1,14 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import {
ITrainingMaterial,
TrainingMaterialContentType,
} from '@screens/trainingMaterial/interfaces';
import { ITrainingMaterial } from '@screens/trainingMaterial/interfaces';
interface ITrainingMaterialSlice {
loading: boolean;
data: ITrainingMaterial[];
}
const MOCK_DATA = [
{
documentReferenceId: 'doc1',
topic: 'If Customer is demanding settlement',
contentType: TrainingMaterialContentType.PDF,
createdAt: '2021-06-02T00:00:00.000Z',
isNewMaterial: true,
metadata: {
duration: '',
pageCount: '10 Pages',
},
},
{
documentReferenceId: 'doc2',
topic: 'How to fill daily commitment',
contentType: TrainingMaterialContentType.VIDEO,
createdAt: '2021-06-02T00:00:00.000Z',
isNewMaterial: false,
metadata: {
duration: '10 min',
pageCount: '',
},
},
];
const initialState: ITrainingMaterialSlice = {
loading: false,
data: MOCK_DATA,
data: [],
};
const TrainingMaterialSlice = createSlice({

View File

@@ -11,6 +11,7 @@ import TrainingMaterialListItem from './TrainingMaterialListItem';
import { setShouldHideTabBar } from '@reducers/commonSlice';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { getTrainingMaterialList } from '@actions/TrainingMaterialAction';
const TrainingMaterial = () => {
const loading = useAppSelector((state) => state.trainingMaterial.loading);
@@ -20,6 +21,7 @@ const TrainingMaterial = () => {
useEffect(() => {
dispatch(setShouldHideTabBar(true));
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TRAINING_MATERIAL_LIST_SCREEN_LOADED);
dispatch(getTrainingMaterialList());
return () => {
dispatch(setShouldHideTabBar(false));
};
@@ -39,11 +41,8 @@ const TrainingMaterial = () => {
<View style={[GenericStyles.pv16, GenericStyles.fill]}>
<ScrollView>
<View style={GenericStyles.ph16}>
{trainingMaterial.map((item) => (
<TrainingMaterialListItem
key={item.documentReferenceId}
trainingMaterialData={item}
/>
{trainingMaterial?.map((item) => (
<TrainingMaterialListItem key={item.referenceId} trainingMaterialData={item} />
))}
</View>
</ScrollView>

View File

@@ -3,12 +3,15 @@ import Layout from '@screens/layout/Layout';
import React, { useEffect } from 'react';
import { ITrainingMaterialDetail, TrainingMaterialContentType } from './interfaces';
import { goBack } from '@components/utlis/navigationUtlis';
import { View } from 'react-native';
import { ActivityIndicator, View } from 'react-native';
import { GenericStyles } from '@rn-ui-lib/styles';
import WebViewVideoPlayer from '@components/webViewVideoPlayer/WebViewVideoPlayer';
import PdfRenderer from '@components/pdfRenderer/PdfRenderer';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { getTrainingMaterialDetails } from '@actions/TrainingMaterialAction';
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
import { COLORS } from '@rn-ui-lib/colors';
const TrainingMaterialDetail: React.FC<ITrainingMaterialDetail> = (props) => {
const {
@@ -16,38 +19,55 @@ const TrainingMaterialDetail: React.FC<ITrainingMaterialDetail> = (props) => {
params: { trainingMaterialData },
},
} = props;
const { topic, contentType, documentReferenceId } = trainingMaterialData;
const isVideo = contentType === TrainingMaterialContentType.VIDEO;
const { title, fileType, referenceId } = trainingMaterialData;
const isVideo = fileType === TrainingMaterialContentType.VIDEO;
const [fileDetails, setFileDetails] = React.useState<any>({});
const getFileDetails = async () => {
try {
setFileDetails({ ...fileDetails, loading: true, showError: false });
const data = await getTrainingMaterialDetails(referenceId);
setFileDetails({ ...data, loading: false });
} catch {
setFileDetails({ ...fileDetails, loading: false, showError: true });
}
};
useEffect(() => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TRAINING_MATERIAL_ITEM_LOADED, {
contentType,
documentReferenceId,
fileType,
referenceId,
});
getFileDetails();
}, []);
const handlePageChange = (pageNumber: number) => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TRAINING_MATERIAL_PDF_PAGE_CHANGED, {
pageNumber,
documentReferenceId,
referenceId,
});
};
return (
<Layout>
<NavigationHeader title={topic} onBack={goBack} />
<NavigationHeader title={title} onBack={goBack} />
<View
style={[GenericStyles.fill, GenericStyles.whiteBackground, GenericStyles.centerAlignedRow]}
>
{isVideo ? (
<WebViewVideoPlayer url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4" />
) : (
<PdfRenderer
docId={documentReferenceId}
onPageChange={handlePageChange}
url="https://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf"
/>
)}
<SuspenseLoader
fallBack={<ActivityIndicator size={'large'} color={COLORS.BASE.BLUE} />}
loading={fileDetails?.loading}
>
{isVideo ? (
<WebViewVideoPlayer url={fileDetails?.signedUri} />
) : (
<PdfRenderer
docId={fileDetails?.referenceId}
onPageChange={handlePageChange}
url={fileDetails?.signedUri}
/>
)}
</SuspenseLoader>
</View>
</Layout>
);

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { ITrainingMaterialListItem } from './interfaces';
import { ITrainingMaterialListItem, TrainingMaterialContentType } from './interfaces';
import { Pressable, StyleSheet, View } from 'react-native';
import { GenericStyles } from '@rn-ui-lib/styles';
import { TrainingMaterialContentMap } from './constants';
@@ -16,14 +16,14 @@ import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
const TrainingMaterialListItem: React.FC<ITrainingMaterialListItem> = ({
trainingMaterialData,
}) => {
const { contentType, topic, metadata, createdAt, isNewMaterial, documentReferenceId } =
const { fileType, title, metadata, createdAt, isNewMaterial, referenceId } =
trainingMaterialData || {};
const { icon, interaction, metadataKey } = TrainingMaterialContentMap[contentType] || {};
const { icon, interaction, metadataKey } = TrainingMaterialContentMap[fileType] || {};
const metadataValue = metadata?.[metadataKey];
const handleMaterialPress = () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TRAINING_MATERIAL_ITEM_CLICKED, {
documentReferenceId,
referenceId,
});
navigateToScreen(ProfileScreenStackEnum.TRAINING_MATERIAL_DETAIL, {
trainingMaterialData,
@@ -47,24 +47,25 @@ const TrainingMaterialListItem: React.FC<ITrainingMaterialListItem> = ({
<View style={[GenericStyles.row, GenericStyles.fill]}>
{icon}
<View style={GenericStyles.ml8}>
<Text dark style={[GenericStyles.lh18]}>
{topic}
<Text dark style={styles.lh14}>
{title}
</Text>
<View style={GenericStyles.row}>
<Text small light>
{interaction}
<Text small>{interaction}</Text>
<Text style={styles.bullet} small>
{' '}
{' '}
</Text>
<Text small>
{fileType === TrainingMaterialContentType.PDF
? `${metadataValue} pages`
: metadataValue}
</Text>
<Text style={styles.bullet} small>
{' '}
{' '}
</Text>
<Text small light>
{metadataValue}
</Text>
<Text style={styles.bullet}> </Text>
<Text small light>
{dateFormat(new Date(createdAt), BUSINESS_DATE_FORMAT)}
</Text>
<Text small>{dateFormat(new Date(createdAt), BUSINESS_DATE_FORMAT)}</Text>
</View>
</View>
</View>
@@ -89,6 +90,9 @@ const styles = StyleSheet.create({
bullet: {
color: COLORS.TEXT.GREY_1,
},
lh14: {
lineHeight: 14,
}
});
export default TrainingMaterialListItem;

View File

@@ -8,7 +8,7 @@ export const TrainingMaterialContentMap = {
[TrainingMaterialContentType.PDF]: {
interaction: 'Reading',
icon: <TextMaterialIcon />,
metadataKey: 'pageCount',
metadataKey: 'totalPages',
component: PDFFullScreen,
},
[TrainingMaterialContentType.VIDEO]: {

View File

@@ -4,9 +4,9 @@ export enum TrainingMaterialContentType {
}
export interface ITrainingMaterial {
documentReferenceId: string;
topic: string;
contentType: TrainingMaterialContentType;
referenceId: string;
title: string;
fileType: TrainingMaterialContentType;
createdAt: string;
isNewMaterial: boolean;
metadata: Record<string, string>;