NTP-22476 | Training module (#1064)
This commit is contained in:
@@ -113,8 +113,8 @@ def jscFlavor = 'org.webkit:android-jsc:+'
|
||||
def enableHermes = project.ext.react.get("enableHermes", false);
|
||||
|
||||
|
||||
def VERSION_CODE = 231
|
||||
def VERSION_NAME = "2.16.8"
|
||||
def VERSION_CODE = 232
|
||||
def VERSION_NAME = "2.16.9"
|
||||
|
||||
android {
|
||||
namespace "com.avapp"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "AV_APP",
|
||||
"version": "2.16.8",
|
||||
"buildNumber": "231",
|
||||
"version": "2.16.9",
|
||||
"buildNumber": "232",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android:dev": "yarn move:dev && react-native run-android",
|
||||
|
||||
34
src/action/TrainingMaterialAction.ts
Normal file
34
src/action/TrainingMaterialAction.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
13
src/assets/icons/BookIcon.tsx
Normal file
13
src/assets/icons/BookIcon.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import Svg, { Path } from 'react-native-svg';
|
||||
|
||||
const BookIcon = () => (
|
||||
<Svg width={16} height={16} viewBox="0 0 16 16" fill="none">
|
||||
<Path
|
||||
d="M4.49935 14.6668C3.99935 14.6668 3.56879 14.4973 3.20768 14.1584C2.84657 13.8196 2.66602 13.4001 2.66602 12.9001V3.60011C2.66602 3.17789 2.79657 2.80011 3.05768 2.46678C3.31879 2.13345 3.66046 1.92234 4.08268 1.83345L10.666 0.533447V11.2001L4.34935 12.4668C4.24935 12.489 4.16602 12.5418 4.09935 12.6251C4.03268 12.7084 3.99935 12.8001 3.99935 12.9001C3.99935 13.0223 4.04935 13.1251 4.14935 13.2084C4.24935 13.2918 4.36602 13.3334 4.49935 13.3334H11.9993V2.66678H13.3327V14.6668H4.49935ZM5.99935 10.7834L9.33268 10.1334V2.16678L5.99935 2.81678V10.7834ZM4.66602 11.0501V3.08345L4.41602 3.13345C4.29379 3.15567 4.19379 3.20845 4.11602 3.29178C4.03824 3.37511 3.99935 3.47789 3.99935 3.60011V11.2168C4.0549 11.1946 4.11324 11.1751 4.17435 11.1584C4.23546 11.1418 4.29379 11.1279 4.34935 11.1168L4.66602 11.0501Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default BookIcon;
|
||||
@@ -1,7 +1,9 @@
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { IconProps } from '@rn-ui-lib/icons/types';
|
||||
import React from 'react';
|
||||
import { G, Mask, Path, Rect, Svg } from 'react-native-svg';
|
||||
|
||||
const RightChevronIcon = () => {
|
||||
const RightChevronIcon: React.FC<IconProps> = ({ fillColor = COLORS.TEXT.BLUE }) => {
|
||||
return (
|
||||
<Svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<Mask id="mask0_3190_8548" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||
@@ -10,7 +12,7 @@ const RightChevronIcon = () => {
|
||||
<G mask="url(#mask0_3190_8548)">
|
||||
<Path
|
||||
d="M7.25065 14.4166C7.09787 14.2638 7.02148 14.0694 7.02148 13.8333C7.02148 13.5972 7.09787 13.4027 7.25065 13.25L10.5007 9.99996L7.25065 6.74996C7.09787 6.59718 7.02148 6.40274 7.02148 6.16663C7.02148 5.93051 7.09787 5.73607 7.25065 5.58329C7.40343 5.43051 7.59787 5.35413 7.83398 5.35413C8.07009 5.35413 8.26454 5.43051 8.41732 5.58329L12.2507 9.41663C12.334 9.49996 12.3932 9.59024 12.4282 9.68746C12.4626 9.78468 12.4798 9.88885 12.4798 9.99996C12.4798 10.1111 12.4626 10.2152 12.4282 10.3125C12.3932 10.4097 12.334 10.5 12.2507 10.5833L8.41732 14.4166C8.26454 14.5694 8.07009 14.6458 7.83398 14.6458C7.59787 14.6458 7.40343 14.5694 7.25065 14.4166Z"
|
||||
fill="#0276FE"
|
||||
fill={fillColor}
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
|
||||
13
src/assets/icons/TextMaterialIcon.tsx
Normal file
13
src/assets/icons/TextMaterialIcon.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import Svg, { Path } from 'react-native-svg';
|
||||
|
||||
const TextMaterialIcon = () => (
|
||||
<Svg width={16} height={16} viewBox="0 0 16 16" fill="none">
|
||||
<Path
|
||||
d="M0.5 15.5V13.8333H15.5V15.5H0.5ZM0.5 12.1667V3.83333H8.83333V12.1667H0.5ZM0.5 2.16667V0.5H15.5V2.16667H0.5ZM2.16667 10.5H7.16667V5.5H2.16667V10.5ZM10.5 12.1667V10.5H15.5V12.1667H10.5ZM10.5 8.83333V7.16667H15.5V8.83333H10.5ZM10.5 5.5V3.83333H15.5V5.5H10.5Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default TextMaterialIcon;
|
||||
20
src/assets/icons/VideoIcon.tsx
Normal file
20
src/assets/icons/VideoIcon.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import Svg, { G, Path, Defs, ClipPath, Rect } from 'react-native-svg';
|
||||
|
||||
const VideoIcon = () => (
|
||||
<Svg width={20} height={20} viewBox="0 0 20 20" fill="none">
|
||||
<G clipPath="url(#clip0_3668_44660)">
|
||||
<Path
|
||||
d="M8.33268 13.7501L13.3327 10.0001L8.33268 6.25008V13.7501ZM9.99935 1.66675C5.39935 1.66675 1.66602 5.40008 1.66602 10.0001C1.66602 14.6001 5.39935 18.3334 9.99935 18.3334C14.5993 18.3334 18.3327 14.6001 18.3327 10.0001C18.3327 5.40008 14.5993 1.66675 9.99935 1.66675ZM9.99935 16.6667C6.32435 16.6667 3.33268 13.6751 3.33268 10.0001C3.33268 6.32508 6.32435 3.33341 9.99935 3.33341C13.6743 3.33341 16.666 6.32508 16.666 10.0001C16.666 13.6751 13.6743 16.6667 9.99935 16.6667Z"
|
||||
fill="#969696"
|
||||
/>
|
||||
</G>
|
||||
<Defs>
|
||||
<ClipPath id="clip0_3668_44660">
|
||||
<Rect width={20} height={20} fill="white" />
|
||||
</ClipPath>
|
||||
</Defs>
|
||||
</Svg>
|
||||
);
|
||||
|
||||
export default VideoIcon;
|
||||
@@ -464,9 +464,9 @@ export const CLICKSTREAM_EVENT_NAMES = {
|
||||
name: 'FA_VIEW_PAST_FEEDBACK_PREV_PAGE_FAILED',
|
||||
description: 'FA_VIEW_PAST_FEEDBACK_PREV_PAGE_FAILED',
|
||||
},
|
||||
FA_VIEW_PHOTO_CLICKED: {
|
||||
name: 'FA_VIEW_PHOTO_CLICKED',
|
||||
description: 'FA_VIEW_PHOTO_CLICKED'
|
||||
FA_VIEW_PHOTO_CLICKED: {
|
||||
name: 'FA_VIEW_PHOTO_CLICKED',
|
||||
description: 'FA_VIEW_PHOTO_CLICKED',
|
||||
},
|
||||
FA_CUSTOMER_DOCUMENT_CLICKED: {
|
||||
name: 'FA_CUSTOMER_DOCUMENT_CLICKED',
|
||||
@@ -513,7 +513,6 @@ export const CLICKSTREAM_EVENT_NAMES = {
|
||||
name: 'FA_VIEW_ALL_ESCALATIONS_SCREEN_LANDED',
|
||||
description: 'FA_VIEW_ALL_ESCALATIONS_SCREEN_LANDED',
|
||||
},
|
||||
|
||||
|
||||
// Notifications
|
||||
FA_NOTIFICATION_ICON_CLICK: {
|
||||
@@ -810,7 +809,7 @@ export const CLICKSTREAM_EVENT_NAMES = {
|
||||
name: 'FA_FETCHED_CUSTOMER_DOCUMENTS',
|
||||
description: 'FA_FETCHED_CUSTOMER_DOCUMENTS',
|
||||
},
|
||||
FA_FETCH_CUSTOMER_DOCUMENTS_FAILED: {
|
||||
FA_FETCH_CUSTOMER_DOCUMENTS_FAILED: {
|
||||
name: 'FA_FETCHED_CUSTOMER_DOCUMENTS_FAILED',
|
||||
description: 'FA_FETCHED_CUSTOMER_DOCUMENTS_FAILED',
|
||||
},
|
||||
@@ -837,7 +836,7 @@ export const CLICKSTREAM_EVENT_NAMES = {
|
||||
FA_CHANNEL_CLICKED_SHARE_JOURNEY: {
|
||||
name: 'FA_CHANNEL_CLICKED_SHARE_JOURNEY',
|
||||
description: 'FA_CHANNEL_CLICKED_SHARE_JOURNEY',
|
||||
},
|
||||
},
|
||||
FA_PHONE_NUMBER_CLICKED_SHARE_JOURNEY: {
|
||||
name: 'FA_PHONE_NUMBER_CLICKED_SHARE_JOURNEY',
|
||||
description: 'FA_PHONE_NUMBER_CLICKED_SHARE_JOURNEY',
|
||||
@@ -1137,7 +1136,7 @@ export const CLICKSTREAM_EVENT_NAMES = {
|
||||
},
|
||||
LITMUS_EXPERIMENT: {
|
||||
name: 'LITMUS_EXPERIMENT',
|
||||
description: 'LITMUS_EXPERIMENT'
|
||||
description: 'LITMUS_EXPERIMENT',
|
||||
},
|
||||
|
||||
//FEE WAIVE CLICKSTREAM EVENTS
|
||||
@@ -1298,44 +1297,44 @@ export const CLICKSTREAM_EVENT_NAMES = {
|
||||
description: 'Call banner clicked',
|
||||
},
|
||||
FA_READ_PERMISSION_ERROR: {
|
||||
name: 'ERROR_IN_FETCHING_READ_PERMISSION',
|
||||
description: 'Error in fetching read permission'
|
||||
name: 'ERROR_IN_FETCHING_READ_PERMISSION',
|
||||
description: 'Error in fetching read permission',
|
||||
},
|
||||
FA_READ_PERMISSION_NOT_PROVIDED: {
|
||||
name: 'FA_READ_PERMISSION_NOT_PROVIDED',
|
||||
description: 'Read permission not provided'
|
||||
name: 'FA_READ_PERMISSION_NOT_PROVIDED',
|
||||
description: 'Read permission not provided',
|
||||
},
|
||||
FA_CALLING_FEEDBACK_NUDGE_LOADED: {
|
||||
name: 'FA_CALLING_FEEDBACK_NUDGE_LOADED',
|
||||
description: 'Calling feedback nudge loaded'
|
||||
description: 'Calling feedback nudge loaded',
|
||||
},
|
||||
FA_CALLING_FEEDBACK_NUDGE_FEEDBACK_BUTTON_CLICKED: {
|
||||
name: 'FA_CALLING_FEEDBACK_NUDGE_FEEDBACK_BUTTON_CLICKED',
|
||||
description: 'Fill feedback button clicked'
|
||||
description: 'Fill feedback button clicked',
|
||||
},
|
||||
FA_CALLING_FEEDBACK_NUDGE_CLOSED: {
|
||||
name: 'FA_CALLING_FEEDBACK_NUDGE_CLOSED',
|
||||
description: 'Feedback nudge closed'
|
||||
description: 'Feedback nudge closed',
|
||||
},
|
||||
FA_INSTALLING_CODEPUSH: {
|
||||
name : 'FA_INSTALLING_CODEPUSH',
|
||||
description: 'Codepush installation started'
|
||||
name: 'FA_INSTALLING_CODEPUSH',
|
||||
description: 'Codepush installation started',
|
||||
},
|
||||
FA_CODEPUSH_DEFAULT_STATUS: {
|
||||
name : 'FA_CODEPUSH_DEFAULT_STATUS',
|
||||
description: 'Codepush default fallback case'
|
||||
name: 'FA_CODEPUSH_DEFAULT_STATUS',
|
||||
description: 'Codepush default fallback case',
|
||||
},
|
||||
FA_CODEPUSH_UNKNOWN_ERROR: {
|
||||
name : 'FA_CODEPUSH_UNKNOWN_ERROR',
|
||||
description: 'Codepush unknown error'
|
||||
name: 'FA_CODEPUSH_UNKNOWN_ERROR',
|
||||
description: 'Codepush unknown error',
|
||||
},
|
||||
FA_FEEDBACK_IMAGE_NOT_FOUND: {
|
||||
name: 'FA_FEEDBACK_IMAGE_NOT_FOUND',
|
||||
description: 'Feedback image not found'
|
||||
description: 'Feedback image not found',
|
||||
},
|
||||
FA_UNSYNC_FEEDBACK_CAPTURED: {
|
||||
name: 'FA_UNSYNC_FEEDBACK_CAPTURED',
|
||||
description: 'Unsync feedback captured'
|
||||
description: 'Unsync feedback captured',
|
||||
},
|
||||
FA_UNSYNC_FEEDBACK_CAPTURING: {
|
||||
name: 'FA_UNSYNC_FEEDBACK_CAPTURING',
|
||||
@@ -1359,66 +1358,92 @@ export const CLICKSTREAM_EVENT_NAMES = {
|
||||
},
|
||||
FA_API_FAILED: {
|
||||
name: 'FA_API_FAILED',
|
||||
description: 'API failed'
|
||||
description: 'API failed',
|
||||
},
|
||||
|
||||
// Apk Update
|
||||
FA_APK_UPDATE_DOWNLOAD_STARTED: {
|
||||
name: 'FA_APK_UPDATE_DOWNLOAD_STARTED',
|
||||
description: 'APK update download started'
|
||||
description: 'APK update download started',
|
||||
},
|
||||
FA_APK_UPDATE_DOWNLOAD_SUCCESS: {
|
||||
name: 'FA_APK_UPDATE_DOWNLOAD_SUCCESS',
|
||||
description: 'APK update download completed'
|
||||
description: 'APK update download completed',
|
||||
},
|
||||
FA_APK_UPDATE_DOWNLOAD_FAILED: {
|
||||
name: 'FA_APK_UPDATE_DOWNLOAD_FAILED',
|
||||
description: 'APK update download failed'
|
||||
description: 'APK update download failed',
|
||||
},
|
||||
FA_APK_UPDATE_BUTTON_CLICKED: {
|
||||
name: 'FA_APK_UPDATE_BUTTON_CLICKED',
|
||||
description: 'APK update button clicked'
|
||||
description: 'APK update button clicked',
|
||||
},
|
||||
FA_APK_UPDATE_INSTALL_STARTED: {
|
||||
name: 'FA_APK_UPDATE_INSTALL_STARTED',
|
||||
description: 'APK update installation started'
|
||||
description: 'APK update installation started',
|
||||
},
|
||||
FA_APK_UPDATE_INSTALL_FAILED: {
|
||||
name: 'FA_APK_UPDATE_INSTALL_FAILED',
|
||||
description: 'APK update installation failed'
|
||||
description: 'APK update installation failed',
|
||||
},
|
||||
FA_APK_UPDATE_FALLBACK_TRIGGERED: {
|
||||
name: 'FA_APK_UPDATE_FALLBACK_TRIGGERED',
|
||||
description: 'APK update fallback triggered'
|
||||
description: 'APK update fallback triggered',
|
||||
},
|
||||
FA_APK_UPDATE_CORRUPTED_FILE_DOWNLOADED: {
|
||||
name: 'FA_APK_UPDATE_CORRUPTED_FILE_DOWNLOADED',
|
||||
description: 'APK update corrupted file downloaded'
|
||||
description: 'APK update corrupted file downloaded',
|
||||
},
|
||||
FA_APK_UPDATE_INSTALL_SUCCESS: {
|
||||
name: 'FA_APK_UPDATE_INSTALL_SUCCESS',
|
||||
description: 'APK update installation success'
|
||||
description: 'APK update installation success',
|
||||
},
|
||||
|
||||
FA_POST_OPERATIVE_HOURS_SCREEN_LOADED: {
|
||||
name: 'FA_POST_OPERATIVE_HOURS_SCREEN_LOADED',
|
||||
description: 'Post operative hours screen loaded'
|
||||
description: 'Post operative hours screen loaded',
|
||||
},
|
||||
|
||||
FA_PERSIST_ORIGINAL_IMAGE_FAILURE: {
|
||||
name: 'FA_PERSIST_ORIGINAL_IMAGE_FAILURE',
|
||||
description: 'Failed to persist original image'
|
||||
description: 'Failed to persist original image',
|
||||
},
|
||||
|
||||
// Filter coachmarks
|
||||
FA_FILTER_COACHMARKS_LOADED: {
|
||||
name: 'FA_FILTER_COACHMARKS_LOADED',
|
||||
description: 'Filter coachmarks loaded'
|
||||
description: 'Filter coachmarks loaded',
|
||||
},
|
||||
FA_FILTER_COACHMARKS_FAILED: {
|
||||
name: 'FA_FILTER_COACHMARKS_FAILED',
|
||||
description: 'Filter coachmarks failed'
|
||||
}
|
||||
description: 'Filter coachmarks failed',
|
||||
},
|
||||
|
||||
// Training module
|
||||
FA_PROFILE_PAGE_TRAINING_MATERIAL_CLICKED: {
|
||||
name: 'FA_PROFILE_PAGE_TRAINING_MATERIAL_CLICKED',
|
||||
description: 'Training material clicked',
|
||||
},
|
||||
FA_TRAINING_MATERIAL_LIST_SCREEN_LOADED: {
|
||||
name: 'FA_TRAINING_MATERIAL_LIST_SCREEN_LOADED',
|
||||
description: 'Training material screen loaded',
|
||||
},
|
||||
FA_TRAINING_MATERIAL_ITEM_CLICKED: {
|
||||
name: 'FA_TRAINING_MATERIAL_ITEM_CLICKED',
|
||||
description: 'Training material item clicked',
|
||||
},
|
||||
FA_TRAINING_MATERIAL_ITEM_LOADED: {
|
||||
name: 'FA_TRAINING_MATERIAL_ITEM_LOADED',
|
||||
description: 'Training material item loaded',
|
||||
},
|
||||
FA_TRAINING_MATERIAL_ITEM_CLOSED: {
|
||||
name: 'FA_TRAINING_MATERIAL_ITEM_CLOSED',
|
||||
description: 'Training material item closed',
|
||||
},
|
||||
FA_TRAINING_MATERIAL_PDF_PAGE_CHANGED: {
|
||||
name: 'FA_TRAINING_MATERIAL_PDF_PAGE_CHANGED',
|
||||
description: 'Training material PDF page changed',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export enum MimeType {
|
||||
@@ -1565,4 +1590,4 @@ export const API_ERROR_MESSAGE = 'Oops! something went wrong';
|
||||
export enum BuildFlavours {
|
||||
FIELD_AGENTS = 'fieldAgents',
|
||||
CALLING_AGENTS = 'callingAgents',
|
||||
}
|
||||
}
|
||||
|
||||
8
src/common/NewTag.tsx
Normal file
8
src/common/NewTag.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import Tag, { TagVariant } from '@rn-ui-lib/components/Tag';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
|
||||
const NewTag = () => {
|
||||
return <Tag style={GenericStyles.mr10} variant={TagVariant.blue} text="New" />;
|
||||
};
|
||||
|
||||
export default NewTag;
|
||||
92
src/components/pdfRenderer/PdfRenderer.tsx
Normal file
92
src/components/pdfRenderer/PdfRenderer.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ActivityIndicator, SafeAreaView, StyleSheet } from 'react-native';
|
||||
import PdfRendererView from 'react-native-pdf-renderer';
|
||||
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';
|
||||
import { getFileType } from '@screens/caseDetails/PDFUtil';
|
||||
import { DocumentContentType } from '@screens/caseDetails/interface';
|
||||
|
||||
const ERROR_STATE = 'ERROR';
|
||||
|
||||
const PdfRenderer: React.FC<IPdfRenderer> = ({ docId, onPageChange, getFileUri }) => {
|
||||
const [pdfFilePath, setPdfFilePath] = useState('');
|
||||
const isLoading = !pdfFilePath;
|
||||
const error = pdfFilePath === ERROR_STATE;
|
||||
|
||||
const checkIfFileExists = async () => {
|
||||
const cacheDirectory = RNFetchBlob.fs.dirs.CacheDir;
|
||||
const cacheFilePath = `${cacheDirectory}/${docId}.pdf`;
|
||||
const doesFileExist = await RNFetchBlob.fs.exists(cacheFilePath);
|
||||
return { doesFileExist, cacheFilePath };
|
||||
};
|
||||
|
||||
const saveFileToCache = async () => {
|
||||
const { doesFileExist, cacheFilePath } = await checkIfFileExists();
|
||||
if (doesFileExist) {
|
||||
setPdfFilePath(cacheFilePath);
|
||||
return;
|
||||
}
|
||||
const url = await getFileUri();
|
||||
if (!url) {
|
||||
setPdfFilePath(ERROR_STATE);
|
||||
return;
|
||||
}
|
||||
const highQualityResponse = await RNFetchBlob.fetch('GET', url);
|
||||
if (highQualityResponse.respInfo.status !== 200) {
|
||||
setPdfFilePath(ERROR_STATE);
|
||||
} else if (highQualityResponse.respInfo.status === 200) {
|
||||
const highQualityImageBase64 = await highQualityResponse.base64();
|
||||
const documentType = await getFileType(highQualityImageBase64);
|
||||
if (documentType !== DocumentContentType.PDF) {
|
||||
setPdfFilePath(ERROR_STATE);
|
||||
return;
|
||||
}
|
||||
await RNFetchBlob.fs.writeFile(cacheFilePath, highQualityImageBase64, 'base64');
|
||||
const exists = await RNFetchBlob.fs.exists(cacheFilePath);
|
||||
if (exists) {
|
||||
setPdfFilePath(cacheFilePath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
saveFileToCache();
|
||||
}, []);
|
||||
|
||||
const handlePageChange = (pageNumber: number) => {
|
||||
if (isNaN(pageNumber) || !isFunction(onPageChange)) return;
|
||||
onPageChange(pageNumber + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={GenericStyles.fill}>
|
||||
<SuspenseLoader loading={isLoading} fallBack={<ActivityIndicator size='large' color={COLORS.BASE.BLUE} />}>
|
||||
{error ? (
|
||||
<Text>Failed to load PDF</Text>
|
||||
) : (
|
||||
<PdfRendererView
|
||||
style={[styles.pdf]}
|
||||
source={`file:/${pdfFilePath}`}
|
||||
distanceBetweenPages={16}
|
||||
maxZoom={5}
|
||||
onPageChange={handlePageChange}
|
||||
/>
|
||||
)}
|
||||
</SuspenseLoader>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
pdf: {
|
||||
flex: 1,
|
||||
backgroundColor: COLORS.BACKGROUND.GREY_LIGHT_2,
|
||||
},
|
||||
});
|
||||
|
||||
export default PdfRenderer;
|
||||
5
src/components/pdfRenderer/interfaces.ts
Normal file
5
src/components/pdfRenderer/interfaces.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface IPdfRenderer {
|
||||
docId: string;
|
||||
getFileUri: () => Promise<string>;
|
||||
onPageChange?: (pageNumber: number) => void;
|
||||
}
|
||||
@@ -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_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);
|
||||
}
|
||||
|
||||
49
src/components/webViewVideoPlayer/WebViewVideoPlayer.tsx
Normal file
49
src/components/webViewVideoPlayer/WebViewVideoPlayer.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import WebView from 'react-native-webview';
|
||||
import { IWebViewVideoPlayer } from './interfaces';
|
||||
import { videoPlayerHTML } from './constants';
|
||||
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
|
||||
import { ActivityIndicator } from 'react-native';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
|
||||
const ERROR_STATE = 'ERROR';
|
||||
|
||||
const WebViewVideoPlayer: React.FC<IWebViewVideoPlayer> = ({ getVideoUrl }) => {
|
||||
const [url, setUrl] = React.useState<string>('');
|
||||
const isLoading = !url;
|
||||
const error = url === ERROR_STATE;
|
||||
|
||||
const fetchVideoUrl = async () => {
|
||||
const url = await getVideoUrl();
|
||||
if (!url) {
|
||||
setUrl(ERROR_STATE);
|
||||
return;
|
||||
}
|
||||
setUrl(url);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchVideoUrl();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SuspenseLoader
|
||||
fallBack={<ActivityIndicator size={'large'} color={COLORS.BASE.BLUE} />}
|
||||
loading={isLoading}
|
||||
>
|
||||
{error ? (
|
||||
<Text>Not able to load the video</Text>
|
||||
) : (
|
||||
<WebView
|
||||
source={{ html: videoPlayerHTML(url) }}
|
||||
style={{ flex: 1 }}
|
||||
allowsFullscreenVideo={true}
|
||||
mediaPlaybackRequiresUserAction={false}
|
||||
/>
|
||||
)}
|
||||
</SuspenseLoader>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebViewVideoPlayer;
|
||||
11
src/components/webViewVideoPlayer/constants.ts
Normal file
11
src/components/webViewVideoPlayer/constants.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const videoPlayerHTML = (url: string) => `<html>
|
||||
<head>
|
||||
<meta name="viewport"
|
||||
content="initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
</head><body>
|
||||
|
||||
<video id="video" style={border-radius: 8px} width="100%" height="100%" controls controlsList="nodownload" onclick="postMessage('fullscreen')">
|
||||
<source src="${url}" type='video/mp4'>
|
||||
</video>
|
||||
|
||||
</body></html>`;
|
||||
3
src/components/webViewVideoPlayer/interfaces.ts
Normal file
3
src/components/webViewVideoPlayer/interfaces.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface IWebViewVideoPlayer {
|
||||
getVideoUrl: () => Promise<string>;
|
||||
}
|
||||
30
src/reducer/trainingMaterialSlice.ts
Normal file
30
src/reducer/trainingMaterialSlice.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { ITrainingMaterial } from '@screens/trainingMaterial/interfaces';
|
||||
|
||||
interface ITrainingMaterialSlice {
|
||||
loading: boolean;
|
||||
data: ITrainingMaterial[];
|
||||
}
|
||||
|
||||
const initialState: ITrainingMaterialSlice = {
|
||||
loading: false,
|
||||
data: [],
|
||||
};
|
||||
|
||||
const TrainingMaterialSlice = createSlice({
|
||||
name: 'trainingMaterial',
|
||||
initialState,
|
||||
reducers: {
|
||||
setTrainingMaterialLoading: (state, action: PayloadAction<boolean>) => {
|
||||
state.loading = action.payload;
|
||||
},
|
||||
setTrainingMaterialData: (state, action: PayloadAction<ITrainingMaterial[]>) => {
|
||||
state.data = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setTrainingMaterialLoading, setTrainingMaterialData } =
|
||||
TrainingMaterialSlice.actions;
|
||||
|
||||
export default TrainingMaterialSlice.reducer;
|
||||
@@ -10,6 +10,8 @@ import store from '@store';
|
||||
import { Alert } from 'react-native';
|
||||
import { ProfileScreenStackEnum } from '../ProfileStack';
|
||||
import CountComponent from '../CountComponent';
|
||||
import BookIcon from '@assets/icons/BookIcon';
|
||||
import NewTag from '@common/NewTag';
|
||||
|
||||
export const getNavigationLinks = () => {
|
||||
const { isTeamLead, selectedAgent, featureFlags } = store?.getState().user;
|
||||
@@ -37,6 +39,16 @@ export const getNavigationLinks = () => {
|
||||
isNew: true,
|
||||
NewComponent: CountComponent,
|
||||
},
|
||||
{
|
||||
name: 'Training material',
|
||||
icon: BookIcon,
|
||||
isVisible: true,
|
||||
onPress: () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_PROFILE_PAGE_TRAINING_MATERIAL_CLICKED);
|
||||
navigateToScreen(ProfileScreenStackEnum.TRAINING_MATERIAL);
|
||||
},
|
||||
NewComponent: NewTag
|
||||
},
|
||||
{
|
||||
name: 'Logout',
|
||||
icon: LogoutIcon,
|
||||
|
||||
@@ -9,6 +9,8 @@ import Profile from '.';
|
||||
import AgentIdCard from './AgentIdCard';
|
||||
import MyDocuments from './MyDocuments';
|
||||
import PDFFullScreen from '@screens/caseDetails/PDFFullScreen';
|
||||
import TrainingMaterial from '@screens/trainingMaterial/TrainingMaterial';
|
||||
import TrainingMaterialDetail from '@screens/trainingMaterial/TrainingMaterialDetail';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
@@ -20,6 +22,8 @@ export enum ProfileScreenStackEnum {
|
||||
AGENT_ID_CARD = 'agentIdCard',
|
||||
MY_DOCUMENTS = 'myDocuments',
|
||||
PDF_FULL = 'pdfFull',
|
||||
TRAINING_MATERIAL = 'trainingMaterial',
|
||||
TRAINING_MATERIAL_DETAIL = 'trainingMaterialDetail',
|
||||
}
|
||||
|
||||
const ProfileStack = () => {
|
||||
@@ -47,6 +51,12 @@ const ProfileStack = () => {
|
||||
component={PDFFullScreen}
|
||||
options={{ ...DEFAULT_SCREEN_OPTIONS, orientation: 'all' }}
|
||||
/>
|
||||
<Stack.Screen name={ProfileScreenStackEnum.TRAINING_MATERIAL} component={TrainingMaterial} />
|
||||
<Stack.Screen
|
||||
name={ProfileScreenStackEnum.TRAINING_MATERIAL_DETAIL}
|
||||
component={TrainingMaterialDetail}
|
||||
options={{ ...DEFAULT_SCREEN_OPTIONS, orientation: 'all' }}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
55
src/screens/trainingMaterial/TrainingMaterial.tsx
Normal file
55
src/screens/trainingMaterial/TrainingMaterial.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { goBack } from '@components/utlis/navigationUtlis';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
|
||||
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import Layout from '@screens/layout/Layout';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ActivityIndicator, ScrollView, View } from 'react-native';
|
||||
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);
|
||||
const trainingMaterial = useAppSelector((state) => state.trainingMaterial.data);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setShouldHideTabBar(true));
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TRAINING_MATERIAL_LIST_SCREEN_LOADED);
|
||||
dispatch(getTrainingMaterialList());
|
||||
return () => {
|
||||
dispatch(setShouldHideTabBar(false));
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<NavigationHeader title="Training Material" onBack={goBack} />
|
||||
<SuspenseLoader
|
||||
loading={loading}
|
||||
fallBack={
|
||||
<View style={[GenericStyles.fill, GenericStyles.centerAlignedRow]}>
|
||||
<ActivityIndicator size={'large'} color={COLORS.BASE.BLUE} />
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<View style={[GenericStyles.pv16, GenericStyles.fill]}>
|
||||
<ScrollView>
|
||||
<View style={GenericStyles.ph16}>
|
||||
{trainingMaterial?.map((item) => (
|
||||
<TrainingMaterialListItem key={item.referenceId} trainingMaterialData={item} />
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SuspenseLoader>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrainingMaterial;
|
||||
72
src/screens/trainingMaterial/TrainingMaterialDetail.tsx
Normal file
72
src/screens/trainingMaterial/TrainingMaterialDetail.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
|
||||
import Layout from '@screens/layout/Layout';
|
||||
import React, { useEffect } from 'react';
|
||||
import { TrainingMaterialDetailProps, TrainingMaterialContentType } from './interfaces';
|
||||
import { goBack } from '@components/utlis/navigationUtlis';
|
||||
import { 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';
|
||||
|
||||
const TrainingMaterialDetail: React.FC<TrainingMaterialDetailProps> = (props) => {
|
||||
const {
|
||||
route: {
|
||||
params: { trainingMaterialData },
|
||||
},
|
||||
} = props;
|
||||
const { title, fileType, referenceId } = trainingMaterialData || {};
|
||||
const isVideo = fileType === TrainingMaterialContentType.VIDEO;
|
||||
|
||||
const getFileUri = async () => {
|
||||
try {
|
||||
const data = await getTrainingMaterialDetails(referenceId);
|
||||
return data?.signedUri;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TRAINING_MATERIAL_ITEM_LOADED, {
|
||||
fileType,
|
||||
referenceId,
|
||||
});
|
||||
return () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TRAINING_MATERIAL_ITEM_CLOSED, {
|
||||
fileType,
|
||||
referenceId,
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handlePageChange = (pageNumber: number) => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TRAINING_MATERIAL_PDF_PAGE_CHANGED, {
|
||||
pageNumber,
|
||||
referenceId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<NavigationHeader title={title} onBack={goBack} />
|
||||
<View
|
||||
style={[GenericStyles.fill, GenericStyles.whiteBackground, GenericStyles.centerAlignedRow]}
|
||||
>
|
||||
{isVideo ? (
|
||||
<WebViewVideoPlayer getVideoUrl={getFileUri} />
|
||||
) : (
|
||||
<PdfRenderer
|
||||
docId={referenceId}
|
||||
onPageChange={handlePageChange}
|
||||
getFileUri={getFileUri}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrainingMaterialDetail;
|
||||
99
src/screens/trainingMaterial/TrainingMaterialListItem.tsx
Normal file
99
src/screens/trainingMaterial/TrainingMaterialListItem.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import { ITrainingMaterialListItem, TrainingMaterialContentType } from './interfaces';
|
||||
import { Pressable, StyleSheet, View } from 'react-native';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import { TrainingMaterialContentMap } from './constants';
|
||||
import RightChevronIcon from '@assets/icons/RightChevronIcon';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { BUSINESS_DATE_FORMAT, dateFormat } from '@rn-ui-lib/utils/dates';
|
||||
import Tag, { TagVariant } from '@rn-ui-lib/components/Tag';
|
||||
import { navigateToScreen } from '@components/utlis/navigationUtlis';
|
||||
import { ProfileScreenStackEnum } from '@screens/Profile/ProfileStack';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
import { pluralise } from '@components/utlis/commonFunctions';
|
||||
|
||||
const TrainingMaterialListItem: React.FC<ITrainingMaterialListItem> = ({
|
||||
trainingMaterialData,
|
||||
}) => {
|
||||
const { fileType, title, metadata, createdAt, isNewMaterial, referenceId } =
|
||||
trainingMaterialData || {};
|
||||
const { icon, interaction, metadataKey } = TrainingMaterialContentMap[fileType] || {};
|
||||
const metadataValue = metadata?.[metadataKey];
|
||||
|
||||
const handleMaterialPress = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TRAINING_MATERIAL_ITEM_CLICKED, {
|
||||
referenceId,
|
||||
});
|
||||
navigateToScreen(ProfileScreenStackEnum.TRAINING_MATERIAL_DETAIL, {
|
||||
trainingMaterialData,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={[
|
||||
GenericStyles.mb8,
|
||||
GenericStyles.whiteBackground,
|
||||
GenericStyles.br8,
|
||||
GenericStyles.overflowHidden,
|
||||
GenericStyles.p16,
|
||||
GenericStyles.elevation2,
|
||||
]}
|
||||
onPress={handleMaterialPress}
|
||||
>
|
||||
{isNewMaterial ? <Tag variant={TagVariant.blue} text="New" style={styles.newTag} /> : null}
|
||||
<View style={[GenericStyles.row, GenericStyles.spaceBetween, GenericStyles.alignCenter]}>
|
||||
<View style={[GenericStyles.row, GenericStyles.fill]}>
|
||||
{icon}
|
||||
<View style={GenericStyles.ml8}>
|
||||
<Text dark style={styles.lh14} numberOfLines={3}>
|
||||
{title}
|
||||
</Text>
|
||||
<View style={GenericStyles.row}>
|
||||
<Text small>{interaction}</Text>
|
||||
<Text style={styles.bullet} small>
|
||||
{' '}
|
||||
●{' '}
|
||||
</Text>
|
||||
<Text small>
|
||||
{fileType === TrainingMaterialContentType.PDF
|
||||
? `${metadataValue} ${pluralise(metadataValue as number, 'page', 'pages')}`
|
||||
: metadataValue}
|
||||
</Text>
|
||||
<Text style={styles.bullet} small>
|
||||
{' '}
|
||||
●{' '}
|
||||
</Text>
|
||||
<Text small>{dateFormat(new Date(createdAt), BUSINESS_DATE_FORMAT)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={GenericStyles.ml16}>
|
||||
<RightChevronIcon fillColor={COLORS.TEXT.LIGHT} />
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
newTag: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
borderTopWidth: 0,
|
||||
borderRightWidth: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
bullet: {
|
||||
color: COLORS.TEXT.GREY_1,
|
||||
},
|
||||
lh14: {
|
||||
lineHeight: 14,
|
||||
},
|
||||
});
|
||||
|
||||
export default TrainingMaterialListItem;
|
||||
20
src/screens/trainingMaterial/constants.tsx
Normal file
20
src/screens/trainingMaterial/constants.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import TextMaterialIcon from '@assets/icons/TextMaterialIcon';
|
||||
import { TrainingMaterialContentType } from './interfaces';
|
||||
import VideoIcon from '@assets/icons/VideoIcon';
|
||||
import PDFFullScreen from '@screens/caseDetails/PDFFullScreen';
|
||||
import WebViewVideoPlayer from '@components/webViewVideoPlayer/WebViewVideoPlayer';
|
||||
|
||||
export const TrainingMaterialContentMap = {
|
||||
[TrainingMaterialContentType.PDF]: {
|
||||
interaction: 'Reading',
|
||||
icon: <TextMaterialIcon />,
|
||||
metadataKey: 'totalPages',
|
||||
component: PDFFullScreen,
|
||||
},
|
||||
[TrainingMaterialContentType.VIDEO]: {
|
||||
interaction: 'Video',
|
||||
icon: <VideoIcon />,
|
||||
metadataKey: 'duration',
|
||||
component: WebViewVideoPlayer,
|
||||
},
|
||||
};
|
||||
32
src/screens/trainingMaterial/interfaces.ts
Normal file
32
src/screens/trainingMaterial/interfaces.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export enum TrainingMaterialContentType {
|
||||
PDF = 'PDF',
|
||||
VIDEO = 'VIDEO',
|
||||
}
|
||||
|
||||
export interface ITrainingMaterial {
|
||||
referenceId: string;
|
||||
title: string;
|
||||
fileType: TrainingMaterialContentType;
|
||||
createdAt: string;
|
||||
isNewMaterial: boolean;
|
||||
metadata: Record<string, string | number>;
|
||||
}
|
||||
|
||||
export interface ITrainingMaterialListItem {
|
||||
trainingMaterialData: ITrainingMaterial;
|
||||
}
|
||||
|
||||
export interface TrainingMaterialDetailProps {
|
||||
route: {
|
||||
params: {
|
||||
trainingMaterialData: ITrainingMaterial;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITrainingMaterialDetail {
|
||||
signedUri: string;
|
||||
referenceId: string;
|
||||
loading: boolean;
|
||||
showError: boolean;
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import topFeedbacksSlice from '@reducers/topFeedbacksSlice';
|
||||
import escalationSlice from '@reducers/escalationSlice';
|
||||
import postOperationalHourRestrictionsSlice from '@reducers/postOperationalHourRestrictionsSlice';
|
||||
import skipTracingAddressesSlice from '@reducers/skipTracingAddressesSlice';
|
||||
import trainingMaterialSlice from '@reducers/trainingMaterialSlice';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
case: caseReducer,
|
||||
@@ -73,7 +74,8 @@ const rootReducer = combineReducers({
|
||||
documentsSlice: documentsSlice,
|
||||
topFeedbacks: topFeedbacksSlice,
|
||||
escalationSlice: escalationSlice,
|
||||
postOperationalHourRestrictionsSlice: postOperationalHourRestrictionsSlice
|
||||
postOperationalHourRestrictionsSlice: postOperationalHourRestrictionsSlice,
|
||||
trainingMaterial: trainingMaterialSlice,
|
||||
});
|
||||
|
||||
const persistConfig = {
|
||||
@@ -97,7 +99,7 @@ const persistConfig = {
|
||||
'feedbackFilters',
|
||||
'litmusExperiment',
|
||||
'activeCall',
|
||||
'appUpdate'
|
||||
'appUpdate',
|
||||
],
|
||||
blackList: [
|
||||
'case',
|
||||
|
||||
Reference in New Issue
Block a user