From f7ea668ed4eee854ce196974e19415789a8d0ae6 Mon Sep 17 00:00:00 2001 From: yashmantri Date: Wed, 16 Aug 2023 15:18:39 +0530 Subject: [PATCH 01/21] TP-36903 | Agent Performance Dashboard V1 --- src/action/agentPerformanceAction.ts | 52 +++++++ src/assets/icons/CashCollectedIcon.tsx | 19 +++ src/assets/icons/DashboardIcon.tsx | 24 +++ src/assets/icons/EmiCollectedIcon.tsx | 33 ++++ src/assets/icons/FilledStarIcon.tsx | 33 ++++ src/common/Constants.ts | 6 + .../allCases/allCasesFilters/FilterUtils.ts | 6 +- src/components/utlis/apiHelper.ts | 6 + src/reducer/agentPerformanceSlice.ts | 52 +++++++ src/reducer/allCasesSlice.ts | 15 ++ src/reducer/userSlice.ts | 7 +- src/screens/Dashboard/DashboardHeader.tsx | 60 ++++++++ src/screens/Dashboard/PerformanceCard.tsx | 115 ++++++++++++++ src/screens/Dashboard/PerformanceItem.tsx | 36 +++++ src/screens/Dashboard/PerformanceOverview.tsx | 98 ++++++++++++ src/screens/Dashboard/constants.ts | 20 +++ src/screens/Dashboard/index.tsx | 113 ++++++++++++++ src/screens/Dashboard/interface.ts | 76 +++++++++ src/screens/Dashboard/utils.ts | 112 ++++++++++++++ src/screens/allCases/CasesList.tsx | 29 +++- src/screens/allCases/constants.ts | 1 + src/screens/allCases/index.tsx | 34 ++-- src/screens/auth/ProtectedRouter.tsx | 34 +++- .../cashCollected/CashCollectedItem.tsx | 101 ++++++++++++ src/screens/cashCollected/index.tsx | 145 ++++++++++++++++++ src/screens/cashCollected/interface.ts | 13 ++ src/store/store.ts | 2 + 27 files changed, 1225 insertions(+), 17 deletions(-) create mode 100644 src/action/agentPerformanceAction.ts create mode 100644 src/assets/icons/CashCollectedIcon.tsx create mode 100644 src/assets/icons/DashboardIcon.tsx create mode 100644 src/assets/icons/EmiCollectedIcon.tsx create mode 100644 src/assets/icons/FilledStarIcon.tsx create mode 100644 src/reducer/agentPerformanceSlice.ts create mode 100644 src/screens/Dashboard/DashboardHeader.tsx create mode 100644 src/screens/Dashboard/PerformanceCard.tsx create mode 100644 src/screens/Dashboard/PerformanceItem.tsx create mode 100644 src/screens/Dashboard/PerformanceOverview.tsx create mode 100644 src/screens/Dashboard/constants.ts create mode 100644 src/screens/Dashboard/index.tsx create mode 100644 src/screens/Dashboard/interface.ts create mode 100644 src/screens/Dashboard/utils.ts create mode 100644 src/screens/cashCollected/CashCollectedItem.tsx create mode 100644 src/screens/cashCollected/index.tsx create mode 100644 src/screens/cashCollected/interface.ts diff --git a/src/action/agentPerformanceAction.ts b/src/action/agentPerformanceAction.ts new file mode 100644 index 00000000..752749f3 --- /dev/null +++ b/src/action/agentPerformanceAction.ts @@ -0,0 +1,52 @@ +import axiosInstance, { ApiKeys, API_STATUS_CODE, getApiUrl } from '../components/utlis/apiHelper'; +import { logError } from '../components/utlis/errorUtils'; + +export const getAgentDetail = () => { + const url = getApiUrl(ApiKeys.GET_AGENT_DETAIL); + return axiosInstance + .get(url) + .then((response) => { + if (response.status === API_STATUS_CODE.OK) { + return response.data; + } + throw response; + }) + .catch((err) => { + logError(err); + throw new Error(err); + }); +}; + +export const getPerformanceMetrics = () => { + const url = getApiUrl(ApiKeys.GET_PERFORMANCE_METRICS); + return axiosInstance + .get(url) + .then((response) => { + if (response.status === API_STATUS_CODE.OK) { + return response.data; + } + throw response; + }) + .catch((err) => { + logError(err); + throw new Error(err); + }); +}; + +export const getCashCollectedSplit = (payload: any) => { + const url = getApiUrl(ApiKeys.GET_CASH_COLLECTED); + return axiosInstance + .get(url, { + params: payload, + }) + .then((response) => { + if (response.status === API_STATUS_CODE.OK) { + return response.data; + } + throw response; + }) + .catch((err) => { + logError(err); + throw new Error(err); + }); +}; diff --git a/src/assets/icons/CashCollectedIcon.tsx b/src/assets/icons/CashCollectedIcon.tsx new file mode 100644 index 00000000..c53dc945 --- /dev/null +++ b/src/assets/icons/CashCollectedIcon.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import Svg, { Rect, G, Path } from 'react-native-svg'; + +const CashCollectedIcon = () => { + return ( + + + + + + + ); +}; + +export default CashCollectedIcon; diff --git a/src/assets/icons/DashboardIcon.tsx b/src/assets/icons/DashboardIcon.tsx new file mode 100644 index 00000000..0a3e5f70 --- /dev/null +++ b/src/assets/icons/DashboardIcon.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; +import { ITabIconProps } from '../../../RN-UI-LIB/src/components/bottomNavigator'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; + +const DashboardIcon: React.FC = ({ + size = 20, + color = COLORS.TEXT.LIGHT, + focused, +}) => { + return ( + + + + + ); +}; +export default DashboardIcon; diff --git a/src/assets/icons/EmiCollectedIcon.tsx b/src/assets/icons/EmiCollectedIcon.tsx new file mode 100644 index 00000000..c73f6736 --- /dev/null +++ b/src/assets/icons/EmiCollectedIcon.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import Svg, { Rect, G, Path, Mask } from 'react-native-svg'; + +const EmiCollectedIcon = () => { + return ( + + + + + + + + + + + + + + ); +}; + +export default EmiCollectedIcon; diff --git a/src/assets/icons/FilledStarIcon.tsx b/src/assets/icons/FilledStarIcon.tsx new file mode 100644 index 00000000..f9a8c1a8 --- /dev/null +++ b/src/assets/icons/FilledStarIcon.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import Svg, { Rect, G, Path, Mask } from 'react-native-svg'; + +const FilledStarIcon = () => { + return ( + + + + + + + + + + + + + + ); +}; + +export default FilledStarIcon; diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 8b02119b..909cda4f 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -521,6 +521,12 @@ export const CLICKSTREAM_EVENT_NAMES = { name: 'FA_SUBMIT_ANYWAYS_FAILED', description: 'Feedback submit anyway failed', }, + + // Agent Dashboard + AD_FIREBASE_SYNC_ISSUE: { + name: 'AD_FIREBASE_SYNC_ISSUE', + description: 'Agent dashboard firebase sync issues', + }, } as const; export enum MimeType { diff --git a/src/components/screens/allCases/allCasesFilters/FilterUtils.ts b/src/components/screens/allCases/allCasesFilters/FilterUtils.ts index c354c72d..212ec55e 100644 --- a/src/components/screens/allCases/allCasesFilters/FilterUtils.ts +++ b/src/components/screens/allCases/allCasesFilters/FilterUtils.ts @@ -26,12 +26,12 @@ export const evaluateFilterForCases = ( }); Object.keys(selectedFilters).forEach((key) => { const fieldToCompareIdx = - filters[key].fieldsToCompare.length > 1 + filters[key]?.fieldsToCompare.length > 1 ? filters[key].fieldsToCompare.findIndex((field) => { return field.caseType === caseRecord.caseType; }) : 0; - switch (filters[key].filterType) { + switch (filters[key]?.filterType) { case FILTER_TYPES.DATE: switch (filters[key].operator) { case CONDITIONAL_OPERATORS.EQUALS: @@ -63,7 +63,7 @@ export const evaluateFilterForCases = ( break; } case FILTER_TYPES.STRING: - switch (filters[key].operator) { + switch (filters[key]?.operator) { case CONDITIONAL_OPERATORS.EQUALS: if (selectedFilters[key]) { if (typeof selectedFilters[key] === 'string') { diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index 19422a6c..64c6b240 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -54,6 +54,9 @@ export enum ApiKeys { GLOBAL_CONFIG = 'GLOBAL_CONFIG', UPLOAD_IMAGE_ID = 'UPLOAD_IMAGE_ID', GET_DOCUMENTS = 'GET_DOCUMENTS', + GET_AGENT_DETAIL = 'GET_AGENT_DETAIL', + GET_PERFORMANCE_METRICS = 'GET_PERFORMANCE_METRICS', + GET_CASH_COLLECTED = 'GET_CASH_COLLECTED', } export const API_URLS: Record = {} as Record; @@ -93,6 +96,9 @@ API_URLS[ApiKeys.UPLOAD_FEEDBACK_IMAGES] = '/feedback/persist-original-images'; API_URLS[ApiKeys.GLOBAL_CONFIG] = '/global-config'; API_URLS[ApiKeys.UPLOAD_IMAGE_ID] = '/user/documents/selfie'; API_URLS[ApiKeys.GET_DOCUMENTS] = '/user/documents'; +API_URLS[ApiKeys.GET_AGENT_DETAIL] = '/agent-info'; +API_URLS[ApiKeys.GET_PERFORMANCE_METRICS] = '/agent-performance'; +API_URLS[ApiKeys.GET_CASH_COLLECTED] = '/cash-collected-split'; export const API_STATUS_CODE = { OK: 200, diff --git a/src/reducer/agentPerformanceSlice.ts b/src/reducer/agentPerformanceSlice.ts new file mode 100644 index 00000000..c01a1ac1 --- /dev/null +++ b/src/reducer/agentPerformanceSlice.ts @@ -0,0 +1,52 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { CashCollectedDataType, PerformanceDataType } from '../screens/Dashboard/interface'; + +interface AgentPerformanceInterface { + performanceData: PerformanceDataType; + cashCollectedData: Array; +} +const initialState: AgentPerformanceInterface = { + performanceData: { + cases: { + visitedCases: 0, + unvisitedCaseIds: [], + unvisitedCases: 0, + contactableCases: 0, + nonContactableCaseIds: [], + nonContactableCases: 0, + totalPtp: 0, + nonPtpCaseIds: [], + nonPtp: 0, + convertedPtp: 0, + brokenPtpCaseIds: [], + brokenPtp: 0, + totalEmi: 0, + atleastOneEmiCollected: 0, + }, + totalCashCollected: 0, + totalCashCollectedCaseIds: [], + performanceLevel: { + currentLevel: 0, + totalLevel: 0, + }, + lastUpdatedAt: '', + }, + cashCollectedData: [], +}; + +const agentPerformanceSlice = createSlice({ + name: 'agentPerformance', + initialState, + reducers: { + setPerformanceData: (state, action) => { + state.performanceData = action.payload; + }, + setCashCollectedData: (state, action) => { + state.cashCollectedData = action.payload; + }, + }, +}); + +export const { setPerformanceData, setCashCollectedData } = agentPerformanceSlice.actions; + +export default agentPerformanceSlice.reducer; diff --git a/src/reducer/allCasesSlice.ts b/src/reducer/allCasesSlice.ts index 867694a7..7c681a35 100644 --- a/src/reducer/allCasesSlice.ts +++ b/src/reducer/allCasesSlice.ts @@ -21,6 +21,12 @@ import { CollectionCaseWidgetId, CommonCaseWidgetId } from '../types/template.ty import { IAvatarUri } from '../action/caseListAction'; export type ICasesMap = { [key: string]: ICaseItem }; + +interface FilteredListToast { + showToast: boolean; + caseType: string; +} + interface IAllCasesSlice { casesList: ICaseItem[]; casesListMap: ICasesMap; @@ -42,6 +48,7 @@ interface IAllCasesSlice { completedList: ICaseItem[]; pinnedList: ICaseItem[]; newVisitedCases: string[]; + filteredListToast: FilteredListToast; } const initialState: IAllCasesSlice = { @@ -65,6 +72,10 @@ const initialState: IAllCasesSlice = { completedList: [], pinnedList: [], newVisitedCases: [], + filteredListToast: { + showToast: false, + caseType: '', + }, }; const getCaseListComponents = (casesList: ICaseItem[], caseDetails: Record) => { @@ -573,6 +584,9 @@ const allCasesSlice = createSlice({ } }); }, + setFilteredListToast: (state, action) => { + state.filteredListToast = action.payload; + }, }, }); @@ -594,6 +608,7 @@ export const { resetNewVisitedCases, syncCasesByFallback, setCasesImageUri, + setFilteredListToast, } = allCasesSlice.actions; export default allCasesSlice.reducer; diff --git a/src/reducer/userSlice.ts b/src/reducer/userSlice.ts index b7fe08af..9a64fb09 100644 --- a/src/reducer/userSlice.ts +++ b/src/reducer/userSlice.ts @@ -47,6 +47,7 @@ export interface IUserSlice extends IUser { clickstreamEvents: IClickstreamEvents[]; isImpersonated: boolean; lock: ILockData; + isExternalAgent: boolean; } const initialState: IUserSlice = { @@ -59,6 +60,7 @@ const initialState: IUserSlice = { lock: { visitPlanStatus: VisitPlanStatus.UNLOCKED, }, + isExternalAgent: false, }; export const userSlice = createSlice({ @@ -86,9 +88,12 @@ export const userSlice = createSlice({ state.lock = action.payload; } }, + setIsExternalAgent: (state, action) => { + state.isExternalAgent = action.payload; + }, }, }); -export const { setAuthData, setDeviceId, setLockData } = userSlice.actions; +export const { setAuthData, setDeviceId, setLockData, setIsExternalAgent } = userSlice.actions; export default userSlice.reducer; diff --git a/src/screens/Dashboard/DashboardHeader.tsx b/src/screens/Dashboard/DashboardHeader.tsx new file mode 100644 index 00000000..8fed0bfc --- /dev/null +++ b/src/screens/Dashboard/DashboardHeader.tsx @@ -0,0 +1,60 @@ +import { Animated, StyleSheet, View } from 'react-native'; +import React from 'react'; +import Heading from '../../../RN-UI-LIB/src/components/Heading'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import NotificationMenu from '../../components/notificationMenu'; +import Text from '../../../RN-UI-LIB/src/components/Text'; +import { PerfomanceHeaderTitle } from './constants'; +import { useAppSelector } from '../../hooks'; +import { sanitizeString } from '../../components/utlis/commonFunctions'; +import { BUSINESS_TIME_FORMAT, dateFormat } from '../../../RN-UI-LIB/src/utlis/dates'; + +const DashboardHeader = () => { + const performanceData = useAppSelector((state) => state.agentPerformance.performanceData); + const { lastUpdatedAt } = performanceData; + return ( + + + + + {PerfomanceHeaderTitle} + + + Last updated at{' '} + {sanitizeString(`${dateFormat(new Date(lastUpdatedAt), BUSINESS_TIME_FORMAT)}`)} + + + + + + ); +}; + +const styles = StyleSheet.create({ + dashboardHeaderContainer: { + justifyContent: 'flex-end', + position: 'absolute', + top: 0, + zIndex: 10, + width: '100%', + height: 'auto', + backgroundColor: COLORS.BACKGROUND.INDIGO, + }, + headerLabel: { + color: COLORS.BACKGROUND.PRIMARY, + }, + subtitle: { + color: COLORS.TEXT.GREY_1, + }, +}); + +export default DashboardHeader; diff --git a/src/screens/Dashboard/PerformanceCard.tsx b/src/screens/Dashboard/PerformanceCard.tsx new file mode 100644 index 00000000..a67a9588 --- /dev/null +++ b/src/screens/Dashboard/PerformanceCard.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { StyleSheet, TouchableOpacity, View } from 'react-native'; +import Heading from '../../../RN-UI-LIB/src/components/Heading'; +import Text from '../../../RN-UI-LIB/src/components/Text'; +import Chevron from '../../../RN-UI-LIB/src/Icons/Chevron'; +import { GenericStyles, getShadowStyle } from '../../../RN-UI-LIB/src/styles'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import { navigateToScreen } from '../../components/utlis/navigationUtlis'; +import { useAppDispatch, useAppSelector } from '../../hooks'; +import { setFilteredListToast } from '../../reducer/allCasesSlice'; +import { setSelectedFilters } from '../../reducer/filtersSlice'; +import { BOTTOM_TAB_ROUTES } from '../allCases/constants'; +import { CurrentAllocationStatsFilterMap, CurrentAllocationStatsMap } from './interface'; +import { getPerformanceDetailFilter, getPerformanceDetails } from './utils'; + +const PerformanceCard = () => { + const { performanceData, filters } = useAppSelector((state) => ({ + performanceData: state.agentPerformance.performanceData, + filters: state.filters.filters, + })); + const { cases } = performanceData || {}; + const performanceDetails = getPerformanceDetails(cases); + + const dispatch = useAppDispatch(); + + return ( + + {performanceDetails.map((item) => ( + { + dispatch( + setSelectedFilters( + getPerformanceDetailFilter( + item.redirectionType, + Object.keys(filters['COMMON']?.filters)?.includes( + CurrentAllocationStatsFilterMap[item.redirectionType] + ) + ) + ) + ); + dispatch( + setFilteredListToast({ + showToast: true, + caseType: CurrentAllocationStatsMap[item.redirectionType], + }) + ); + navigateToScreen(BOTTOM_TAB_ROUTES.Cases); + }} + style={[ + getShadowStyle(2), + GenericStyles.br6, + GenericStyles.mt16, + GenericStyles.p12, + styles.pressableCard, + ]} + > + {item.totalConverted} + {item.convertedText} + + + + {item.totalNotConverted} + {item.notConvertedText} + + + + + + + ))} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flexWrap: 'wrap', + justifyContent: 'space-between', + paddingTop: 10, + }, + title: { + fontSize: 13, + marginTop: 8, + marginBottom: 8, + }, + totalCount: { + color: COLORS.TEXT.DARK, + fontSize: 36, + fontWeight: '600', + paddingTop: 16, + }, + rightIcon: { + marginTop: 2, + }, + subTitle: { + color: COLORS.BASE.BLUE, + }, + fw700: { + fontWeight: '700', + }, + pressableCard: { + backgroundColor: COLORS.BACKGROUND.PRIMARY, + width: '47%', + }, +}); + +export default PerformanceCard; diff --git a/src/screens/Dashboard/PerformanceItem.tsx b/src/screens/Dashboard/PerformanceItem.tsx new file mode 100644 index 00000000..0a20dabc --- /dev/null +++ b/src/screens/Dashboard/PerformanceItem.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Pressable, StyleSheet, View } from 'react-native'; +import Text from '../../../RN-UI-LIB/src/components/Text'; +import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import { navigateToScreen } from '../../components/utlis/navigationUtlis'; +import { PerformanceItemProps } from './interface'; + +const PerformanceItem = (props: PerformanceItemProps) => { + const { title, icon, navigateTo, rightSideView } = props; + return ( + navigateTo && navigateToScreen(navigateTo)} + > + + {icon} + {title} + + {rightSideView} + + ); +}; + +const styles = StyleSheet.create({ + performanceContainer: { + marginTop: 14, + marginBottom: 14, + }, +}); + +export default PerformanceItem; diff --git a/src/screens/Dashboard/PerformanceOverview.tsx b/src/screens/Dashboard/PerformanceOverview.tsx new file mode 100644 index 00000000..5df2997a --- /dev/null +++ b/src/screens/Dashboard/PerformanceOverview.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import Text from '../../../RN-UI-LIB/src/components/Text'; +import Chevron from '../../../RN-UI-LIB/src/Icons/Chevron'; +import { GenericStyles, getShadowStyle } from '../../../RN-UI-LIB/src/styles'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import CashCollectedIcon from '../../assets/icons/CashCollectedIcon'; +import EmiCollectedIcon from '../../assets/icons/EmiCollectedIcon'; +import PerformanceItem from './PerformanceItem'; +import FilledStarIcon from '../../assets/icons/FilledStarIcon'; +import { PerformanceDetails } from './constants'; +import { PageRouteEnum } from '../auth/ProtectedRouter'; +import { useAppSelector } from '../../hooks'; +import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount'; + +const PerformanceOverview = () => { + const performanceData = useAppSelector((state) => state.agentPerformance.performanceData); + const { totalCashCollected = 0, performanceLevel, cases } = performanceData || {}; + + return ( + + } + navigateTo={PageRouteEnum.CASH_COLLECTED} + rightSideView={ + + + {formatAmount(totalCashCollected, false)} + + + + + + } + /> + + } + rightSideView={ + + + {cases?.atleastOneEmiCollected}{' '} + + / {cases?.totalEmi} + + } + /> + + } + rightSideView={ + + + {performanceLevel?.currentLevel}{' '} + + + / {performanceLevel?.totalLevel} + + + } + /> + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginTop: 100, + backgroundColor: COLORS.BACKGROUND.PRIMARY, + }, + leftContent: { + color: COLORS.TEXT.DARK, + }, + rightContent: { + color: COLORS.TEXT.BLACK, + }, + rightIcon: { + marginLeft: 10, + marginTop: 2, + }, + fw700: { + fontWeight: '700', + }, +}); + +export default PerformanceOverview; diff --git a/src/screens/Dashboard/constants.ts b/src/screens/Dashboard/constants.ts new file mode 100644 index 00000000..e3e320f1 --- /dev/null +++ b/src/screens/Dashboard/constants.ts @@ -0,0 +1,20 @@ +export const PerformanceDetails = { + totalCashCollected: 'Total cash collected', + caseWithAtleast1Emi: 'Cases with atleast 1 EMI collected', + performanceLevel: 'Performance level', +}; + +export const PerformanceCardDetails = { + visitedCases: 'Visited Cases', + unVisitedCases: 'unvisited', + contactableCases: 'Contactable cases', + nonContactableCases: 'non contactable', + totalPtp: 'Total PTPs', + nonPtp: 'non PTPs', + ptpConverted: 'PTPs converted', + brokenPtp: 'broken PTPs', +}; + +export const PerfomanceHeaderTitle = 'Performance summary'; + +export const NO_CASH_COLLECTED_FOUND = 'No Cash Collected'; diff --git a/src/screens/Dashboard/index.tsx b/src/screens/Dashboard/index.tsx new file mode 100644 index 00000000..52b59fb1 --- /dev/null +++ b/src/screens/Dashboard/index.tsx @@ -0,0 +1,113 @@ +import React, { useEffect, useState } from 'react'; +import { + ActivityIndicator, + RefreshControl, + SafeAreaView, + ScrollView, + StyleSheet, + View, +} from 'react-native'; +import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import { getPerformanceMetrics } from '../../action/agentPerformanceAction'; +import { logError } from '../../components/utlis/errorUtils'; +import { useAppDispatch, useAppSelector } from '../../hooks'; +import { setPerformanceData } from '../../reducer/agentPerformanceSlice'; +import DashboardHeader from './DashboardHeader'; +import PerformanceCard from './PerformanceCard'; +import PerformanceOverview from './PerformanceOverview'; +import { getFiltersCount } from './utils'; + +const Dashboard = () => { + const [refreshing, setRefreshing] = React.useState(false); + const [isLoading, setIsLoading] = useState(false); + const caseDetailsIds = useAppSelector((state) => Object.keys(state.allCases.caseDetails)); + + const dispatch = useAppDispatch(); + + const fetchAgentPerformanceMetrics = (callbackFn?: () => void) => { + setIsLoading(true); + getPerformanceMetrics() + .then((res) => { + const { + visitedCases, + contactableCases, + ptpCases, + convertedPtpCases, + atLeastOneEmiCollectedCases, + } = res?.casesSplit; + const { totalLevel, currentLevel } = res?.performanceLevel; + const { unVisitedCount, nonContactableCount, nonPtpCount, brokenPtpCount } = + getFiltersCount(caseDetailsIds, res?.casesSplit); + + dispatch( + setPerformanceData({ + cases: { + visitedCases, + unvisitedCases: unVisitedCount, + contactableCases, + nonContactableCases: nonContactableCount, + totalPtp: ptpCases, + nonPtp: nonPtpCount, + convertedPtp: convertedPtpCases, + brokenPtp: brokenPtpCount, + totalEmi: res?.data?.allCases, + atleastOneEmiCollected: atLeastOneEmiCollectedCases, + }, + performanceLevel: { + totalLevel, + currentLevel, + }, + totalCashCollected: res?.data?.totalCashCollected, + lastUpdatedAt: res?.data?.lastUpdatedAt, + }) + ); + }) + .catch((err) => { + logError(err); + }) + .finally(() => { + callbackFn && callbackFn(); + setIsLoading(false); + }); + }; + + useEffect(() => { + fetchAgentPerformanceMetrics(); + }, []); + + const onRefresh = React.useCallback(() => { + setRefreshing(true); + fetchAgentPerformanceMetrics(() => setRefreshing(false)); + }, []); + + if (isLoading && !refreshing) + return ( + + + + ); + + return ( + + + + } + > + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: COLORS.BACKGROUND.GREY_LIGHT, + position: 'relative', + }, +}); + +export default Dashboard; diff --git a/src/screens/Dashboard/interface.ts b/src/screens/Dashboard/interface.ts new file mode 100644 index 00000000..797df5d6 --- /dev/null +++ b/src/screens/Dashboard/interface.ts @@ -0,0 +1,76 @@ +import React from 'react'; + +export interface CasesType { + visitedCases: number; + unvisitedCases: number; + contactableCases: number; + nonContactableCases: number; + totalPtp: number; + nonPtp: number; + convertedPtp: number; + brokenPtp: number; + totalEmi: number; + atleastOneEmiCollected: number; + unvisitedCaseIds: Array; + nonContactableCaseIds: Array; + nonPtpCaseIds: Array; + brokenPtpCaseIds: Array; +} + +interface PerformanceLevelType { + totalLevel: number; + currentLevel: number; +} + +export interface PerformanceItemProps { + title: string; + icon: React.ReactNode; + navigateTo?: string; + rightSideView: React.ReactNode; +} + +export interface PerformanceDataType { + cases: CasesType; + performanceLevel: PerformanceLevelType; + totalCashCollected: number; + totalCashCollectedCaseIds: Array; + lastUpdatedAt: string; +} + +export interface PerformanceProps { + performanceData: PerformanceDataType | undefined; +} + +export enum CurrentAllocationStats { + UNVSIITED = 'UNVSIITED', + NON_CONTACTABLE = 'NON_CONTACTABLE', + NON_PTP = 'NON_PTP', + BROKEN_PTP = 'BROKEN_PTP', +} + +export enum CurrentAllocationStatsFilter { + VISIT_STATUS = 'VISIT_STATUS', + CONTACTABLE_STATUS = 'CONTACTABLE_STATUS', + PTP_STATUS = 'PTP_STATUS', + PTP_CONVERSION_STATUS = 'PTP_CONVERSION_STATUS', +} + +export interface CashCollectedDataType { + amountCollected: number; + isClosed: boolean; + caseId: string; +} + +export const CurrentAllocationStatsMap = { + [CurrentAllocationStats.UNVSIITED]: 'unvsited', + [CurrentAllocationStats.NON_CONTACTABLE]: 'non contactable', + [CurrentAllocationStats.NON_PTP]: 'non ptp', + [CurrentAllocationStats.BROKEN_PTP]: 'broken ptp', +}; + +export const CurrentAllocationStatsFilterMap = { + [CurrentAllocationStats.UNVSIITED]: CurrentAllocationStatsFilter.VISIT_STATUS, + [CurrentAllocationStats.NON_CONTACTABLE]: CurrentAllocationStatsFilter.CONTACTABLE_STATUS, + [CurrentAllocationStats.NON_PTP]: CurrentAllocationStatsFilter.PTP_STATUS, + [CurrentAllocationStats.BROKEN_PTP]: CurrentAllocationStatsFilter.PTP_CONVERSION_STATUS, +}; diff --git a/src/screens/Dashboard/utils.ts b/src/screens/Dashboard/utils.ts new file mode 100644 index 00000000..04b74807 --- /dev/null +++ b/src/screens/Dashboard/utils.ts @@ -0,0 +1,112 @@ +import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; +import { addClickstreamEvent } from '../../services/clickstreamEventService'; +import { CashCollectedItemType } from '../cashCollected/interface'; +import { PerformanceCardDetails } from './constants'; +import { CasesType, CurrentAllocationStats } from './interface'; + +export const getPerformanceDetailFilter = (item: CurrentAllocationStats, applyFilter: boolean) => { + if (!applyFilter) return []; + + switch (item) { + case CurrentAllocationStats.UNVSIITED: + return { VISIT_STATUS: { FALSE: true } }; + + case CurrentAllocationStats.NON_CONTACTABLE: + return { CONTACTABLE_STATUS: { FALSE: true } }; + + case CurrentAllocationStats.NON_PTP: + return { PTP_STATUS: { FALSE: true } }; + + case CurrentAllocationStats.BROKEN_PTP: + return { PTP_CONVERSION_STATUS: { TRUE: true } }; + + default: + break; + } +}; + +export const getPerformanceDetails = (cases: CasesType) => { + const { + visitedCases, + unvisitedCases, + contactableCases, + nonContactableCases, + totalPtp, + nonPtp, + convertedPtp, + brokenPtp, + } = cases || {}; + return [ + { + totalConverted: visitedCases, + convertedText: PerformanceCardDetails.visitedCases, + totalNotConverted: unvisitedCases, + notConvertedText: PerformanceCardDetails.unVisitedCases, + redirectionType: CurrentAllocationStats.UNVSIITED, + }, + { + totalConverted: contactableCases, + convertedText: PerformanceCardDetails.contactableCases, + totalNotConverted: nonContactableCases, + notConvertedText: PerformanceCardDetails.nonContactableCases, + redirectionType: CurrentAllocationStats.NON_CONTACTABLE, + }, + { + totalConverted: totalPtp, + convertedText: PerformanceCardDetails.totalPtp, + totalNotConverted: nonPtp, + notConvertedText: PerformanceCardDetails.nonPtp, + redirectionType: CurrentAllocationStats.NON_PTP, + }, + { + totalConverted: convertedPtp, + convertedText: PerformanceCardDetails.ptpConverted, + totalNotConverted: brokenPtp, + notConvertedText: PerformanceCardDetails.brokenPtp, + redirectionType: CurrentAllocationStats.BROKEN_PTP, + }, + ]; +}; + +export const getFiltersCount = (caseDetailsIds: Array, cases: CasesType) => { + const { unvisitedCaseIds, nonContactableCaseIds, nonPtpCaseIds, brokenPtpCaseIds } = cases; + + const unVisitedCount = caseDetailsIds.filter((caseId: string) => + unvisitedCaseIds.includes(caseId) + ).length; + const nonContactableCount = caseDetailsIds.filter((caseId: string) => + nonContactableCaseIds.includes(caseId) + ).length; + const nonPtpCount = caseDetailsIds.filter((caseId: string) => + nonPtpCaseIds.includes(caseId) + ).length; + const brokenPtpCount = caseDetailsIds.filter((caseId: string) => + brokenPtpCaseIds.includes(caseId) + ).length; + + if ( + unvisitedCaseIds.length !== unVisitedCount || + nonContactableCaseIds.length !== nonContactableCount || + nonPtpCaseIds.length !== nonPtpCount || + brokenPtpCaseIds.length !== brokenPtpCount + ) { + const caseIds = [ + ...unvisitedCaseIds, + ...nonContactableCaseIds, + ...nonPtpCaseIds, + ...brokenPtpCaseIds, + ]; + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AD_FIREBASE_SYNC_ISSUE, { + caseIds: caseIds.filter((caseId) => !caseDetailsIds.includes(caseId)), + }); + } + + return { unVisitedCount, nonContactableCount, nonPtpCount, brokenPtpCount }; +}; + +export const getCashCollectedData = ( + cashCollectedRes: Array, + caseDetailsIds: Array +) => { + return cashCollectedRes?.filter((cashData) => caseDetailsIds.includes(cashData.caseId)); +}; diff --git a/src/screens/allCases/CasesList.tsx b/src/screens/allCases/CasesList.tsx index 0145aae2..4922c12c 100644 --- a/src/screens/allCases/CasesList.tsx +++ b/src/screens/allCases/CasesList.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Animated, ListRenderItem, @@ -19,7 +19,7 @@ import CaseItem from './CaseItem'; import { Search } from '../../../RN-UI-LIB/src/utlis/search'; import FiltersContainer from '../../components/screens/allCases/allCasesFilters/FiltersContainer'; import EmptyList from './EmptyList'; -import { useAppSelector } from '../../hooks'; +import { useAppDispatch, useAppSelector } from '../../hooks'; import { addClickstreamEvent } from '../../services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES, @@ -44,6 +44,8 @@ import { FlashList } from '@shopify/flash-list'; import { VisitPlanStatus } from '../../reducer/userSlice'; import { dateFormat, DAY_MONTH_DATE_FORMAT } from '../../../RN-UI-LIB/src/utlis/dates'; import { getAttemptedList, getNonAttemptedList } from './utils'; +import { toast } from '../../../RN-UI-LIB/src/components/toast'; +import { setFilteredListToast } from '../../reducer/allCasesSlice'; export const getItem = (item: Array, index: number) => item[index]; export const ESTIMATED_ITEM_SIZE = 250; // Average height of List item @@ -73,11 +75,15 @@ const CasesList: React.FC = ({ casesList = [], isVisitPlan }) => { quickFiltersPresent: state?.filters?.quickFilters?.length > 0, isLockedVisitPlanStatus: state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED, })); + const { showToast = false, caseType = '' } = + useAppSelector((state: RootState) => state.allCases.filteredListToast) || {}; const [showFilterModal, setShowFilterModal] = React.useState(false); const scrollAnimation = useRef(new Animated.Value(0)).current; + const dispatch = useAppDispatch(); + const firePageLoadEvent = () => { if (getCurrentScreen()?.name === 'Cases') { addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_CASE_LIST_LOAD); @@ -217,6 +223,25 @@ const CasesList: React.FC = ({ casesList = [], isVisitPlan }) => { intermediateTodoListMap, ]); + useEffect(() => { + if (showToast) { + const filteredCasesCount = filteredCasesList.length; + toast({ + type: 'info', + text1: `${filteredCasesCount} ${caseType} case${ + filteredCasesCount > 1 ? 's' : '' + } have been filtered`, + visibilityTime: 1, + }); + dispatch( + setFilteredListToast({ + showToast: false, + caseType: '', + }) + ); + } + }, [filteredCasesList]); + const handleSearchChange = useCallback( debounce((query: string) => { setSearchQuery(query); diff --git a/src/screens/allCases/constants.ts b/src/screens/allCases/constants.ts index 5fad1303..a1f0ae60 100644 --- a/src/screens/allCases/constants.ts +++ b/src/screens/allCases/constants.ts @@ -77,4 +77,5 @@ export enum BOTTOM_TAB_ROUTES { Cases = 'Cases', VisitPlan = 'Visit plan', Profile = 'Profile', + Dashboard = 'Dashboard', } diff --git a/src/screens/allCases/index.tsx b/src/screens/allCases/index.tsx index a0770892..75a52d3d 100644 --- a/src/screens/allCases/index.tsx +++ b/src/screens/allCases/index.tsx @@ -22,14 +22,18 @@ import { addClickstreamEvent } from '../../services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; import { BOTTOM_TAB_ROUTES } from './constants'; import { getSelfieDocument } from '../../action/profileActions'; +import Dashboard from '../Dashboard'; +import DashboardIcon from '../../assets/icons/DashboardIcon'; const AllCasesMain = () => { const { pendingList, pinnedList, loading } = useAppSelector((state) => state.allCases); const userState = useAppSelector((state: RootState) => state.user); + const isExternalAgent = useAppSelector((state: RootState) => state.user.isExternalAgent); + const dispatch = useAppDispatch(); - const HOME_SCREENS: ITabScreen[] = useMemo( - () => [ + const HOME_SCREENS: ITabScreen[] = useMemo(() => { + const screens = [ { name: BOTTOM_TAB_ROUTES.Cases, component: () => , @@ -40,14 +44,24 @@ const AllCasesMain = () => { component: () => , icon: VisitPlanIcon, }, - { - name: BOTTOM_TAB_ROUTES.Profile, - component: () => , - icon: ProfileIcon, - }, - ], - [pendingList, pinnedList] - ); + ]; + + if (!isExternalAgent) { + screens.push({ + name: BOTTOM_TAB_ROUTES.Dashboard, + component: () => , + icon: DashboardIcon, + }); + } + + screens.push({ + name: BOTTOM_TAB_ROUTES.Profile, + component: () => , + icon: ProfileIcon, + }); + + return screens; + }, [pendingList, pinnedList]); const onTabPressHandler = (e: any) => { addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TAB_SWITCH, { diff --git a/src/screens/auth/ProtectedRouter.tsx b/src/screens/auth/ProtectedRouter.tsx index e7d4f679..c5f820de 100644 --- a/src/screens/auth/ProtectedRouter.tsx +++ b/src/screens/auth/ProtectedRouter.tsx @@ -1,5 +1,5 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { _map } from '../../../RN-UI-LIB/src/utlis/common'; import { UnifiedCaseDetailsTypes, getCaseUnifiedData } from '../../action/caseApiActions'; @@ -32,6 +32,10 @@ import Notifications from '../notifications'; import RegisterPayments from '../registerPayements/RegisterPayments'; import TodoList from '../todoList/TodoList'; import UngroupedAddressContainer from '../addressGeolocation/UngroupedAddressContainer'; +import CashCollected from '../cashCollected'; +import { getAgentDetail } from '../../action/agentPerformanceAction'; +import { setIsExternalAgent } from '../../reducer/userSlice'; +import FullScreenLoader from '../../../RN-UI-LIB/src/components/FullScreenLoader'; const Stack = createNativeStackNavigator(); @@ -43,6 +47,7 @@ export enum PageRouteEnum { EMI_SCHEDULE = 'EmiSchedule', PAST_FEEDBACK_DETAIL = 'pastFeedbackDetail', ADDITIONAL_ADDRESSES = 'additionalAddresses', + CASH_COLLECTED = 'cashCollected', } const ProtectedRouter = () => { @@ -50,6 +55,7 @@ const ProtectedRouter = () => { const { notificationsWithActions } = useAppSelector((state) => state.notifications); const isOnline = useIsOnline(); const dispatch = useAppDispatch(); + const [isLoading, setIsLoading] = useState(false); // Gets unified data for new visit plan cases // TODO: Move this to another place @@ -106,6 +112,22 @@ const ProtectedRouter = () => { // Firestore listener hook useFirestoreUpdates(); + useEffect(() => { + if (isOnline) { + setIsLoading(true); + getAgentDetail() + .then((res) => { + dispatch(setIsExternalAgent(res?.isExternalAgent)); + }) + .catch(() => { + dispatch(setIsExternalAgent(false)); + }) + .finally(() => setIsLoading(false)); + } + }, [isOnline]); + + if (isLoading) return ; + return ( { }} listeners={getScreenFocusListenerObj} /> + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + listeners={getScreenFocusListenerObj} + /> ); }; diff --git a/src/screens/cashCollected/CashCollectedItem.tsx b/src/screens/cashCollected/CashCollectedItem.tsx new file mode 100644 index 00000000..a85774d7 --- /dev/null +++ b/src/screens/cashCollected/CashCollectedItem.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { StyleSheet, TouchableOpacity, View } from 'react-native'; +import Avatar from '../../../RN-UI-LIB/src/components/Avatar'; +import Text from '../../../RN-UI-LIB/src/components/Text'; +import Chevron from '../../../RN-UI-LIB/src/Icons/Chevron'; +import { GenericStyles, getShadowStyle } from '../../../RN-UI-LIB/src/styles'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount'; +import { sanitizeString } from '../../components/utlis/commonFunctions'; +import { navigateToScreen } from '../../components/utlis/navigationUtlis'; +import { PageRouteEnum } from '../auth/ProtectedRouter'; +import { CashCollectedItemProps } from './interface'; + +const CashCollectedItem = (props: CashCollectedItemProps) => { + const { cashCollectedItem, isLast, caseDetails } = props; + const { amountCollected = 0, isClosed, caseId } = cashCollectedItem || {}; + const { totalOverdueAmount = 0, imageUri = '', customerName = '' } = caseDetails || {}; + + return ( + navigateToScreen(PageRouteEnum.COLLECTION_CASE_DETAIL, { caseId })} + style={[ + getShadowStyle(2), + GenericStyles.row, + GenericStyles.alignCenter, + GenericStyles.br6, + GenericStyles.p16, + GenericStyles.mt16, + GenericStyles.ml16, + GenericStyles.mr16, + isLast && GenericStyles.mb24, + styles.cashCollectedContainer, + ]} + > + + + + {sanitizeString(customerName)} + + Collected :{' '} + = totalOverdueAmount ? styles.green : styles.red}> + {formatAmount(amountCollected, false)} + + + + Current outstanding :{' '} + {formatAmount(totalOverdueAmount, false)} + + + + + {isClosed && ( + + Foreclosed + + )} + + ); +}; + +const styles = StyleSheet.create({ + cashCollectedContainer: { + backgroundColor: COLORS.BACKGROUND.PRIMARY, + }, + textHeading: { + fontSize: 14, + fontWeight: '700', + color: COLORS.TEXT.DARK, + }, + subText: { + fontSize: 12, + color: COLORS.TEXT.BLACK, + }, + overdueAmountColor: { + color: COLORS.TEXT.LIGHT, + }, + green: { + color: COLORS.TEXT.GREEN, + }, + red: { + color: COLORS.TEXT.RED, + }, + foreclosedContainer: { + right: 0, + top: 0, + paddingLeft: 6, + paddingRight: 6, + backgroundColor: COLORS.BACKGROUND.TEAL, + borderBottomLeftRadius: 4, + borderTopRightRadius: 4, + }, + foreclosedText: { + color: COLORS.TEXT.TEAL, + }, + flexBasis96: { + flexBasis: '96%', + }, +}); + +export default CashCollectedItem; diff --git a/src/screens/cashCollected/index.tsx b/src/screens/cashCollected/index.tsx new file mode 100644 index 00000000..6b02a7f6 --- /dev/null +++ b/src/screens/cashCollected/index.tsx @@ -0,0 +1,145 @@ +import React, { useEffect, useState } from 'react'; +import { FlatList, RefreshControl, SafeAreaView, StyleSheet, View } from 'react-native'; +import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader'; +import LineLoader from '../../../RN-UI-LIB/src/components/suspense_loader/LineLoader'; +import SuspenseLoader from '../../../RN-UI-LIB/src/components/suspense_loader/SuspenseLoader'; +import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; +import { _map } from '../../../RN-UI-LIB/src/utlis/common'; +import { getCashCollectedSplit } from '../../action/agentPerformanceAction'; +import NotificationMenu from '../../components/notificationMenu'; +import { logError } from '../../components/utlis/errorUtils'; +import { goBack } from '../../components/utlis/navigationUtlis'; +import { useAppDispatch, useAppSelector } from '../../hooks'; +import { setCashCollectedData } from '../../reducer/agentPerformanceSlice'; +import { RootState } from '../../store/store'; +import EmptyList from '../allCases/EmptyList'; +import { NO_CASH_COLLECTED_FOUND } from '../Dashboard/constants'; +import { getCashCollectedData } from '../Dashboard/utils'; +import Layout from '../layout/Layout'; +import CashCollectedItem from './CashCollectedItem'; + +const CashCollected = () => { + const cashCollectedData = useAppSelector((state) => state.agentPerformance.cashCollectedData); + + const [isLoading, setIsLoading] = useState(false); + const [paginationDetails, setPaginationDetails] = useState({ + pageNo: 0, + totalPages: 0, + pageSize: 0, + }); + + const { + allCases: { caseDetails }, + } = useAppSelector((state: RootState) => ({ + allCases: state.allCases, + })); + + const caseDetailsIds = Object.keys(caseDetails); + + const dispatch = useAppDispatch(); + + const fetchCashCollectedData = (pageNo: number, callbackFn?: () => void) => { + setIsLoading(true); + getCashCollectedSplit({ pageNo, pageSize: 10 }) + .then((res) => { + const cashData = res?.data; + setPaginationDetails(res?.pages); + const cashCollected = getCashCollectedData(cashData, caseDetailsIds); + dispatch(setCashCollectedData(cashCollected)); + }) + .catch((err) => { + logError(err); + }) + .finally(() => { + setIsLoading(false); + callbackFn && callbackFn(); + }); + }; + + const fetchMoreData = () => { + const hasMoreData = paginationDetails.pageNo < paginationDetails.totalPages; + if (hasMoreData) { + setPaginationDetails({ ...paginationDetails, pageNo: paginationDetails.pageNo + 1 }); + } + }; + + useEffect(() => { + if (paginationDetails.pageNo !== 0) fetchCashCollectedData(paginationDetails.pageNo); + }, [paginationDetails.pageNo]); + + const [refreshing, setRefreshing] = React.useState(false); + + const onRefresh = React.useCallback(() => { + setRefreshing(true); + fetchCashCollectedData(0, () => setRefreshing(false)); + }, []); + + useEffect(() => { + fetchCashCollectedData(0); + }, []); + + return ( + + + + + + } + /> + + {[...Array(9).keys()].map(() => ( + + ))} + + } + > + {cashCollectedData?.length > 0 ? ( + } + renderItem={({ item, index }) => ( + + )} + onEndReached={fetchMoreData} + onEndReachedThreshold={0} + /> + ) : ( + + + + )} + + + + ); +}; +const styles = StyleSheet.create({ + container: { + backgroundColor: COLORS.BACKGROUND.GREY_LIGHT, + position: 'relative', + }, + centerAbsolute: { + height: '80%', + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 4, + }, +}); + +export default CashCollected; diff --git a/src/screens/cashCollected/interface.ts b/src/screens/cashCollected/interface.ts new file mode 100644 index 00000000..410fc215 --- /dev/null +++ b/src/screens/cashCollected/interface.ts @@ -0,0 +1,13 @@ +import { CaseDetail } from '../caseDetails/interface'; + +export interface CashCollectedItemType { + amountCollected: number; + isClosed: boolean; + caseId: string; +} + +export interface CashCollectedItemProps { + cashCollectedItem: CashCollectedItemType; + isLast: boolean; + caseDetails: CaseDetail; +} diff --git a/src/store/store.ts b/src/store/store.ts index dc515532..b4a7bcb9 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -30,6 +30,7 @@ import foregroundServiceSlice from '../reducer/foregroundServiceSlice'; import feedbackImagesSlice from '../reducer/feedbackImagesSlice'; import configSlice from '../reducer/configSlice'; import profileSlice from '../reducer/profileSlice'; +import agentPerformanceSlice from '../reducer/agentPerformanceSlice'; const rootReducer = combineReducers({ case: caseReducer, @@ -50,6 +51,7 @@ const rootReducer = combineReducers({ feedbackImages: feedbackImagesSlice, config: configSlice, profile: profileSlice, + agentPerformance: agentPerformanceSlice, }); const persistConfig = { From 7629903957318c05ff045866cf7699a491ce804e Mon Sep 17 00:00:00 2001 From: yashmantri Date: Wed, 16 Aug 2023 15:52:13 +0530 Subject: [PATCH 02/21] TP-36903 | Filters Updated --- .../allCasesFilters/FiltersContainer.tsx | 136 +++++++++--------- src/screens/Dashboard/interface.ts | 8 +- src/screens/Dashboard/utils.ts | 8 +- 3 files changed, 77 insertions(+), 75 deletions(-) diff --git a/src/components/screens/allCases/allCasesFilters/FiltersContainer.tsx b/src/components/screens/allCases/allCasesFilters/FiltersContainer.tsx index cc92b051..3c888d49 100644 --- a/src/components/screens/allCases/allCasesFilters/FiltersContainer.tsx +++ b/src/components/screens/allCases/allCasesFilters/FiltersContainer.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { TouchableOpacity, View } from 'react-native'; +import { ScrollView, TouchableOpacity, View } from 'react-native'; import { GenericStyles } from '../../../../../RN-UI-LIB/src/styles'; import Heading from '../../../../../RN-UI-LIB/src/components/Heading'; import Text from '../../../../../RN-UI-LIB/src/components/Text'; @@ -116,77 +116,79 @@ const FiltersContainer: React.FC = (props) => { - {filterGroupKeys.map((filterGroupKey) => ( - <> - - - {filters[filterGroupKey].headerText} - - - - {filterKeys[filterGroupKey].map((filterKey) => ( - { - setSelectedFilterKey({ - filterGroup: filterGroupKey, - filterKey, - }); - setFilterSearchString(''); - addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FILTERS_TAB_CLICKED, { - currentFilterTab: selectedFilterKey, - }); - }} - > - + {filterGroupKeys.map((filterGroupKey) => ( + <> + + + {filters[filterGroupKey].headerText} + + + + {filterKeys[filterGroupKey].map((filterKey) => ( + { + setSelectedFilterKey({ + filterGroup: filterGroupKey, + filterKey, + }); + setFilterSearchString(''); + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FILTERS_TAB_CLICKED, { + currentFilterTab: selectedFilterKey, + }); + }} > - {filters[filterGroupKey].filters[filterKey].displayText} - - {selectedFiltersMap[filterKey] && ( - - + {selectedFiltersMap[filterKey] && ( + - {typeof selectedFiltersMap[filterKey] === 'object' - ? Object.keys(selectedFiltersMap[filterKey]).length - : 1} - - - )} - - ))} - - - ))} + + {typeof selectedFiltersMap[filterKey] === 'object' + ? Object.keys(selectedFiltersMap[filterKey]).length + : 1} + + + )} + + ))} + + + ))} + {selectedFilterKey && ( diff --git a/src/screens/Dashboard/interface.ts b/src/screens/Dashboard/interface.ts index 797df5d6..764ecc81 100644 --- a/src/screens/Dashboard/interface.ts +++ b/src/screens/Dashboard/interface.ts @@ -50,9 +50,9 @@ export enum CurrentAllocationStats { export enum CurrentAllocationStatsFilter { VISIT_STATUS = 'VISIT_STATUS', - CONTACTABLE_STATUS = 'CONTACTABLE_STATUS', + CONTACTABILITY = 'CONTACTABILITY', PTP_STATUS = 'PTP_STATUS', - PTP_CONVERSION_STATUS = 'PTP_CONVERSION_STATUS', + PTP_BREAKAGE = 'PTP_BREAKAGE', } export interface CashCollectedDataType { @@ -70,7 +70,7 @@ export const CurrentAllocationStatsMap = { export const CurrentAllocationStatsFilterMap = { [CurrentAllocationStats.UNVSIITED]: CurrentAllocationStatsFilter.VISIT_STATUS, - [CurrentAllocationStats.NON_CONTACTABLE]: CurrentAllocationStatsFilter.CONTACTABLE_STATUS, + [CurrentAllocationStats.NON_CONTACTABLE]: CurrentAllocationStatsFilter.CONTACTABILITY, [CurrentAllocationStats.NON_PTP]: CurrentAllocationStatsFilter.PTP_STATUS, - [CurrentAllocationStats.BROKEN_PTP]: CurrentAllocationStatsFilter.PTP_CONVERSION_STATUS, + [CurrentAllocationStats.BROKEN_PTP]: CurrentAllocationStatsFilter.PTP_BREAKAGE, }; diff --git a/src/screens/Dashboard/utils.ts b/src/screens/Dashboard/utils.ts index 04b74807..7ef17bcf 100644 --- a/src/screens/Dashboard/utils.ts +++ b/src/screens/Dashboard/utils.ts @@ -9,16 +9,16 @@ export const getPerformanceDetailFilter = (item: CurrentAllocationStats, applyFi switch (item) { case CurrentAllocationStats.UNVSIITED: - return { VISIT_STATUS: { FALSE: true } }; + return { VISIT_STATUS: { false: true } }; case CurrentAllocationStats.NON_CONTACTABLE: - return { CONTACTABLE_STATUS: { FALSE: true } }; + return { CONTACTABILITY: { false: true } }; case CurrentAllocationStats.NON_PTP: - return { PTP_STATUS: { FALSE: true } }; + return { PTP_STATUS: { false: true } }; case CurrentAllocationStats.BROKEN_PTP: - return { PTP_CONVERSION_STATUS: { TRUE: true } }; + return { PTP_BREAKAGE: { true: true } }; default: break; From eb1e78b747508874c522c4e058b6136fe94ca02b Mon Sep 17 00:00:00 2001 From: yashmantri Date: Thu, 17 Aug 2023 13:17:14 +0530 Subject: [PATCH 03/21] TP-36903 | Minor Bug fixes --- src/components/utlis/apiHelper.ts | 6 +++--- src/reducer/agentPerformanceSlice.ts | 2 +- src/screens/Dashboard/DashboardHeader.tsx | 2 +- src/screens/Dashboard/constants.ts | 2 +- src/screens/Dashboard/index.tsx | 6 +++--- src/screens/Dashboard/interface.ts | 6 +++--- src/screens/allCases/CasesList.tsx | 2 +- src/screens/cashCollected/index.tsx | 8 ++++++-- 8 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index 64c6b240..9078a312 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -96,9 +96,9 @@ API_URLS[ApiKeys.UPLOAD_FEEDBACK_IMAGES] = '/feedback/persist-original-images'; API_URLS[ApiKeys.GLOBAL_CONFIG] = '/global-config'; API_URLS[ApiKeys.UPLOAD_IMAGE_ID] = '/user/documents/selfie'; API_URLS[ApiKeys.GET_DOCUMENTS] = '/user/documents'; -API_URLS[ApiKeys.GET_AGENT_DETAIL] = '/agent-info'; -API_URLS[ApiKeys.GET_PERFORMANCE_METRICS] = '/agent-performance'; -API_URLS[ApiKeys.GET_CASH_COLLECTED] = '/cash-collected-split'; +API_URLS[ApiKeys.GET_AGENT_DETAIL] = '/user/role-info'; +API_URLS[ApiKeys.GET_PERFORMANCE_METRICS] = '/allocation-cycle/agent-performance'; +API_URLS[ApiKeys.GET_CASH_COLLECTED] = '/allocation-cycle/cash-collected-split'; export const API_STATUS_CODE = { OK: 200, diff --git a/src/reducer/agentPerformanceSlice.ts b/src/reducer/agentPerformanceSlice.ts index c01a1ac1..aa3948b0 100644 --- a/src/reducer/agentPerformanceSlice.ts +++ b/src/reducer/agentPerformanceSlice.ts @@ -29,7 +29,7 @@ const initialState: AgentPerformanceInterface = { currentLevel: 0, totalLevel: 0, }, - lastUpdatedAt: '', + lastUpdatedAt: 0, }, cashCollectedData: [], }; diff --git a/src/screens/Dashboard/DashboardHeader.tsx b/src/screens/Dashboard/DashboardHeader.tsx index 8fed0bfc..0d232dbd 100644 --- a/src/screens/Dashboard/DashboardHeader.tsx +++ b/src/screens/Dashboard/DashboardHeader.tsx @@ -30,7 +30,7 @@ const DashboardHeader = () => { Last updated at{' '} - {sanitizeString(`${dateFormat(new Date(lastUpdatedAt), BUSINESS_TIME_FORMAT)}`)} + {sanitizeString(`${dateFormat(new Date(lastUpdatedAt ?? 0), BUSINESS_TIME_FORMAT)}`)} diff --git a/src/screens/Dashboard/constants.ts b/src/screens/Dashboard/constants.ts index e3e320f1..9ddaa7f8 100644 --- a/src/screens/Dashboard/constants.ts +++ b/src/screens/Dashboard/constants.ts @@ -17,4 +17,4 @@ export const PerformanceCardDetails = { export const PerfomanceHeaderTitle = 'Performance summary'; -export const NO_CASH_COLLECTED_FOUND = 'No Cash Collected'; +export const NO_CASH_COLLECTED_FOUND = 'No cash collected'; diff --git a/src/screens/Dashboard/index.tsx b/src/screens/Dashboard/index.tsx index 52b59fb1..e157fa3a 100644 --- a/src/screens/Dashboard/index.tsx +++ b/src/screens/Dashboard/index.tsx @@ -51,15 +51,15 @@ const Dashboard = () => { nonPtp: nonPtpCount, convertedPtp: convertedPtpCases, brokenPtp: brokenPtpCount, - totalEmi: res?.data?.allCases, + totalEmi: res?.allCases, atleastOneEmiCollected: atLeastOneEmiCollectedCases, }, performanceLevel: { totalLevel, currentLevel, }, - totalCashCollected: res?.data?.totalCashCollected, - lastUpdatedAt: res?.data?.lastUpdatedAt, + totalCashCollected: res?.totalCashCollected, + lastUpdatedAt: res?.lastUpdatedAt, }) ); }) diff --git a/src/screens/Dashboard/interface.ts b/src/screens/Dashboard/interface.ts index 764ecc81..2ec3359e 100644 --- a/src/screens/Dashboard/interface.ts +++ b/src/screens/Dashboard/interface.ts @@ -34,7 +34,7 @@ export interface PerformanceDataType { performanceLevel: PerformanceLevelType; totalCashCollected: number; totalCashCollectedCaseIds: Array; - lastUpdatedAt: string; + lastUpdatedAt: number; } export interface PerformanceProps { @@ -64,8 +64,8 @@ export interface CashCollectedDataType { export const CurrentAllocationStatsMap = { [CurrentAllocationStats.UNVSIITED]: 'unvsited', [CurrentAllocationStats.NON_CONTACTABLE]: 'non contactable', - [CurrentAllocationStats.NON_PTP]: 'non ptp', - [CurrentAllocationStats.BROKEN_PTP]: 'broken ptp', + [CurrentAllocationStats.NON_PTP]: 'non PTP', + [CurrentAllocationStats.BROKEN_PTP]: 'broken PTP', }; export const CurrentAllocationStatsFilterMap = { diff --git a/src/screens/allCases/CasesList.tsx b/src/screens/allCases/CasesList.tsx index 4922c12c..a1c2be10 100644 --- a/src/screens/allCases/CasesList.tsx +++ b/src/screens/allCases/CasesList.tsx @@ -231,7 +231,7 @@ const CasesList: React.FC = ({ casesList = [], isVisitPlan }) => { text1: `${filteredCasesCount} ${caseType} case${ filteredCasesCount > 1 ? 's' : '' } have been filtered`, - visibilityTime: 1, + visibilityTime: 3000, }); dispatch( setFilteredListToast({ diff --git a/src/screens/cashCollected/index.tsx b/src/screens/cashCollected/index.tsx index 6b02a7f6..4c2c50eb 100644 --- a/src/screens/cashCollected/index.tsx +++ b/src/screens/cashCollected/index.tsx @@ -3,17 +3,18 @@ import { FlatList, RefreshControl, SafeAreaView, StyleSheet, View } from 'react- import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader'; import LineLoader from '../../../RN-UI-LIB/src/components/suspense_loader/LineLoader'; import SuspenseLoader from '../../../RN-UI-LIB/src/components/suspense_loader/SuspenseLoader'; +import Text from '../../../RN-UI-LIB/src/components/Text'; import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; import { _map } from '../../../RN-UI-LIB/src/utlis/common'; import { getCashCollectedSplit } from '../../action/agentPerformanceAction'; +import NoRepaymentsFoundIcon from '../../assets/icons/NoRepaymentsFoundIcon'; import NotificationMenu from '../../components/notificationMenu'; import { logError } from '../../components/utlis/errorUtils'; import { goBack } from '../../components/utlis/navigationUtlis'; import { useAppDispatch, useAppSelector } from '../../hooks'; import { setCashCollectedData } from '../../reducer/agentPerformanceSlice'; import { RootState } from '../../store/store'; -import EmptyList from '../allCases/EmptyList'; import { NO_CASH_COLLECTED_FOUND } from '../Dashboard/constants'; import { getCashCollectedData } from '../Dashboard/utils'; import Layout from '../layout/Layout'; @@ -121,7 +122,10 @@ const CashCollected = () => { /> ) : ( - + + + {NO_CASH_COLLECTED_FOUND} + )} From 885953586505435fc30aa29b887eb82529e2200d Mon Sep 17 00:00:00 2001 From: yashmantri Date: Thu, 17 Aug 2023 15:25:56 +0530 Subject: [PATCH 04/21] TP-36903 | Pagination Issue Fixed --- src/reducer/agentPerformanceSlice.ts | 17 ++- src/screens/Dashboard/interface.ts | 5 + .../cashCollected/CashCollectedItem.tsx | 2 +- src/screens/cashCollected/index.tsx | 107 ++++++++++-------- 4 files changed, 78 insertions(+), 53 deletions(-) diff --git a/src/reducer/agentPerformanceSlice.ts b/src/reducer/agentPerformanceSlice.ts index aa3948b0..b79fb288 100644 --- a/src/reducer/agentPerformanceSlice.ts +++ b/src/reducer/agentPerformanceSlice.ts @@ -1,9 +1,9 @@ import { createSlice } from '@reduxjs/toolkit'; -import { CashCollectedDataType, PerformanceDataType } from '../screens/Dashboard/interface'; +import { CashCollectedData, PerformanceDataType } from '../screens/Dashboard/interface'; interface AgentPerformanceInterface { performanceData: PerformanceDataType; - cashCollectedData: Array; + cashCollectedData: CashCollectedData; } const initialState: AgentPerformanceInterface = { performanceData: { @@ -31,7 +31,10 @@ const initialState: AgentPerformanceInterface = { }, lastUpdatedAt: 0, }, - cashCollectedData: [], + cashCollectedData: { + data: [], + isLoading: false, + }, }; const agentPerformanceSlice = createSlice({ @@ -42,11 +45,15 @@ const agentPerformanceSlice = createSlice({ state.performanceData = action.payload; }, setCashCollectedData: (state, action) => { - state.cashCollectedData = action.payload; + state.cashCollectedData.data = action.payload; + }, + setIsloading: (state, action) => { + state.cashCollectedData.isLoading = action.payload; }, }, }); -export const { setPerformanceData, setCashCollectedData } = agentPerformanceSlice.actions; +export const { setPerformanceData, setCashCollectedData, setIsloading } = + agentPerformanceSlice.actions; export default agentPerformanceSlice.reducer; diff --git a/src/screens/Dashboard/interface.ts b/src/screens/Dashboard/interface.ts index 2ec3359e..760c6d04 100644 --- a/src/screens/Dashboard/interface.ts +++ b/src/screens/Dashboard/interface.ts @@ -37,6 +37,11 @@ export interface PerformanceDataType { lastUpdatedAt: number; } +export interface CashCollectedData { + data: Array; + isLoading: boolean; +} + export interface PerformanceProps { performanceData: PerformanceDataType | undefined; } diff --git a/src/screens/cashCollected/CashCollectedItem.tsx b/src/screens/cashCollected/CashCollectedItem.tsx index a85774d7..33faa6de 100644 --- a/src/screens/cashCollected/CashCollectedItem.tsx +++ b/src/screens/cashCollected/CashCollectedItem.tsx @@ -26,7 +26,7 @@ const CashCollectedItem = (props: CashCollectedItemProps) => { GenericStyles.alignCenter, GenericStyles.br6, GenericStyles.p16, - GenericStyles.mt16, + GenericStyles.mt10, GenericStyles.ml16, GenericStyles.mr16, isLast && GenericStyles.mb24, diff --git a/src/screens/cashCollected/index.tsx b/src/screens/cashCollected/index.tsx index 4c2c50eb..b57965bf 100644 --- a/src/screens/cashCollected/index.tsx +++ b/src/screens/cashCollected/index.tsx @@ -1,8 +1,14 @@ import React, { useEffect, useState } from 'react'; -import { FlatList, RefreshControl, SafeAreaView, StyleSheet, View } from 'react-native'; +import { + ActivityIndicator, + FlatList, + RefreshControl, + SafeAreaView, + StyleSheet, + View, +} from 'react-native'; +import FullScreenLoader from '../../../RN-UI-LIB/src/components/FullScreenLoader'; import NavigationHeader from '../../../RN-UI-LIB/src/components/NavigationHeader'; -import LineLoader from '../../../RN-UI-LIB/src/components/suspense_loader/LineLoader'; -import SuspenseLoader from '../../../RN-UI-LIB/src/components/suspense_loader/SuspenseLoader'; import Text from '../../../RN-UI-LIB/src/components/Text'; import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; @@ -13,7 +19,7 @@ import NotificationMenu from '../../components/notificationMenu'; import { logError } from '../../components/utlis/errorUtils'; import { goBack } from '../../components/utlis/navigationUtlis'; import { useAppDispatch, useAppSelector } from '../../hooks'; -import { setCashCollectedData } from '../../reducer/agentPerformanceSlice'; +import { setCashCollectedData, setIsloading } from '../../reducer/agentPerformanceSlice'; import { RootState } from '../../store/store'; import { NO_CASH_COLLECTED_FOUND } from '../Dashboard/constants'; import { getCashCollectedData } from '../Dashboard/utils'; @@ -22,12 +28,12 @@ import CashCollectedItem from './CashCollectedItem'; const CashCollected = () => { const cashCollectedData = useAppSelector((state) => state.agentPerformance.cashCollectedData); - - const [isLoading, setIsLoading] = useState(false); + const { data, isLoading } = cashCollectedData || {}; const [paginationDetails, setPaginationDetails] = useState({ pageNo: 0, totalPages: 0, pageSize: 0, + totalElements: 0, }); const { @@ -41,25 +47,29 @@ const CashCollected = () => { const dispatch = useAppDispatch(); const fetchCashCollectedData = (pageNo: number, callbackFn?: () => void) => { - setIsLoading(true); + dispatch(setIsloading(true)); getCashCollectedSplit({ pageNo, pageSize: 10 }) .then((res) => { const cashData = res?.data; setPaginationDetails(res?.pages); const cashCollected = getCashCollectedData(cashData, caseDetailsIds); - dispatch(setCashCollectedData(cashCollected)); + if (res?.pages?.pageNo === 0) { + dispatch(setCashCollectedData(cashCollected)); + return; + } + dispatch(setCashCollectedData([...cashCollectedData.data, ...cashCollected])); }) .catch((err) => { logError(err); }) .finally(() => { - setIsLoading(false); + dispatch(setIsloading(true)); callbackFn && callbackFn(); }); }; const fetchMoreData = () => { - const hasMoreData = paginationDetails.pageNo < paginationDetails.totalPages; + const hasMoreData = paginationDetails.pageNo < paginationDetails.totalPages - 1; if (hasMoreData) { setPaginationDetails({ ...paginationDetails, pageNo: paginationDetails.pageNo + 1 }); } @@ -80,6 +90,8 @@ const CashCollected = () => { fetchCashCollectedData(0); }, []); + if (isLoading && !data.length) return ; + return ( @@ -92,43 +104,41 @@ const CashCollected = () => { } /> - - {[...Array(9).keys()].map(() => ( - - ))} - - } - > - {cashCollectedData?.length > 0 ? ( - } - renderItem={({ item, index }) => ( - - )} - onEndReached={fetchMoreData} - onEndReachedThreshold={0} - /> - ) : ( - - - - {NO_CASH_COLLECTED_FOUND} - - - )} - + {data?.length > 0 ? ( + } + renderItem={({ item, index }) => ( + + )} + onEndReached={fetchMoreData} + onEndReachedThreshold={0} + ListFooterComponent={ + data?.length && data.length < paginationDetails.totalElements ? ( + + {isLoading ? : null} + + ) : null + } + /> + ) : ( + + + + {NO_CASH_COLLECTED_FOUND} + + + )} ); @@ -138,6 +148,9 @@ const styles = StyleSheet.create({ backgroundColor: COLORS.BACKGROUND.GREY_LIGHT, position: 'relative', }, + loadingContainer: { + height: 10, + }, centerAbsolute: { height: '80%', justifyContent: 'center', From 2fed05d25f9683d534c23f34ea6b6502dbb04ddd Mon Sep 17 00:00:00 2001 From: yashmantri Date: Thu, 17 Aug 2023 16:47:30 +0530 Subject: [PATCH 05/21] TP-36903 | PR comments Resolved --- src/action/agentPerformanceAction.ts | 144 ++++++++++++++++------ src/screens/Dashboard/DashboardHeader.tsx | 41 +++--- src/screens/Dashboard/PerformanceCard.tsx | 45 ++++--- src/screens/Dashboard/index.tsx | 47 +------ src/screens/Dashboard/interface.ts | 20 +++ src/screens/auth/ProtectedRouter.tsx | 10 +- src/screens/cashCollected/index.tsx | 31 ++--- src/store/store.ts | 2 +- 8 files changed, 185 insertions(+), 155 deletions(-) diff --git a/src/action/agentPerformanceAction.ts b/src/action/agentPerformanceAction.ts index 752749f3..adafdd32 100644 --- a/src/action/agentPerformanceAction.ts +++ b/src/action/agentPerformanceAction.ts @@ -1,52 +1,122 @@ import axiosInstance, { ApiKeys, API_STATUS_CODE, getApiUrl } from '../components/utlis/apiHelper'; import { logError } from '../components/utlis/errorUtils'; +import { + setCashCollectedData, + setIsloading, + setPerformanceData, +} from '../reducer/agentPerformanceSlice'; +import { setIsExternalAgent } from '../reducer/userSlice'; +import { + CashCollectedData, + CashCollectedSplitPayload, + PaginationDetails, +} from '../screens/Dashboard/interface'; +import { getCashCollectedData, getFiltersCount } from '../screens/Dashboard/utils'; +import { AppDispatch } from '../store/store'; -export const getAgentDetail = () => { +export const getAgentDetail = (callbackFn: () => void) => (dispatch: AppDispatch) => { const url = getApiUrl(ApiKeys.GET_AGENT_DETAIL); return axiosInstance .get(url) .then((response) => { if (response.status === API_STATUS_CODE.OK) { - return response.data; + dispatch(setIsExternalAgent(response?.data?.isExternalAgent)); } - throw response; }) .catch((err) => { logError(err); - throw new Error(err); - }); + }) + .finally(() => callbackFn()); }; -export const getPerformanceMetrics = () => { - const url = getApiUrl(ApiKeys.GET_PERFORMANCE_METRICS); - return axiosInstance - .get(url) - .then((response) => { - if (response.status === API_STATUS_CODE.OK) { - return response.data; - } - throw response; - }) - .catch((err) => { - logError(err); - throw new Error(err); - }); -}; +export const getPerformanceMetrics = + ( + caseDetailsIds: Array, + setIsLoading: (isLoading: boolean) => void, + callbackFn?: () => void + ) => + (dispatch: AppDispatch) => { + const url = getApiUrl(ApiKeys.GET_PERFORMANCE_METRICS); + return axiosInstance + .get(url) + .then((response) => { + if (response.status === API_STATUS_CODE.OK) { + const { + visitedCases, + contactableCases, + ptpCases, + convertedPtpCases, + atLeastOneEmiCollectedCases, + } = response?.data?.casesSplit; + const { totalLevel, currentLevel } = response?.data?.performanceLevel; + const { unVisitedCount, nonContactableCount, nonPtpCount, brokenPtpCount } = + getFiltersCount(caseDetailsIds, response?.data?.casesSplit); -export const getCashCollectedSplit = (payload: any) => { - const url = getApiUrl(ApiKeys.GET_CASH_COLLECTED); - return axiosInstance - .get(url, { - params: payload, - }) - .then((response) => { - if (response.status === API_STATUS_CODE.OK) { - return response.data; - } - throw response; - }) - .catch((err) => { - logError(err); - throw new Error(err); - }); -}; + dispatch( + setPerformanceData({ + cases: { + visitedCases, + unvisitedCases: unVisitedCount, + contactableCases, + nonContactableCases: nonContactableCount, + totalPtp: ptpCases, + nonPtp: nonPtpCount, + convertedPtp: convertedPtpCases, + brokenPtp: brokenPtpCount, + totalEmi: response?.data?.allCases, + atleastOneEmiCollected: atLeastOneEmiCollectedCases, + }, + performanceLevel: { + totalLevel, + currentLevel, + }, + totalCashCollected: response?.data?.totalCashCollected, + lastUpdatedAt: response?.data?.lastUpdatedAt, + }) + ); + } + return; + }) + .catch((err) => { + logError(err); + }) + .finally(() => { + callbackFn && callbackFn(); + setIsLoading(false); + }); + }; + +export const getCashCollectedSplit = + ( + payload: CashCollectedSplitPayload, + caseDetailsIds: Array, + cashCollectedData: CashCollectedData, + setPaginationDetails: (data: PaginationDetails) => void, + callbackFn?: () => void + ) => + (dispatch: AppDispatch) => { + const url = getApiUrl(ApiKeys.GET_CASH_COLLECTED); + return axiosInstance + .get(url, { + params: payload, + }) + .then((response) => { + if (response.status === API_STATUS_CODE.OK) { + const cashData = response?.data?.data; + setPaginationDetails(response?.data?.pages); + const cashCollected = getCashCollectedData(cashData, caseDetailsIds); + if (response?.data?.pages?.pageNo === 0) { + dispatch(setCashCollectedData(cashCollected)); + return; + } + dispatch(setCashCollectedData([...cashCollectedData.data, ...cashCollected])); + } + }) + .catch((err) => { + logError(err); + }) + .finally(() => { + dispatch(setIsloading(false)); + callbackFn && callbackFn(); + }); + }; diff --git a/src/screens/Dashboard/DashboardHeader.tsx b/src/screens/Dashboard/DashboardHeader.tsx index 0d232dbd..3027169a 100644 --- a/src/screens/Dashboard/DashboardHeader.tsx +++ b/src/screens/Dashboard/DashboardHeader.tsx @@ -14,28 +14,27 @@ const DashboardHeader = () => { const performanceData = useAppSelector((state) => state.agentPerformance.performanceData); const { lastUpdatedAt } = performanceData; return ( - - - - - {PerfomanceHeaderTitle} - - - Last updated at{' '} - {sanitizeString(`${dateFormat(new Date(lastUpdatedAt ?? 0), BUSINESS_TIME_FORMAT)}`)} - - - + + + + {PerfomanceHeaderTitle} + + + Last updated at{' '} + {sanitizeString(`${dateFormat(new Date(lastUpdatedAt ?? 0), BUSINESS_TIME_FORMAT)}`)} + - + + ); }; diff --git a/src/screens/Dashboard/PerformanceCard.tsx b/src/screens/Dashboard/PerformanceCard.tsx index a67a9588..ba62a2ce 100644 --- a/src/screens/Dashboard/PerformanceCard.tsx +++ b/src/screens/Dashboard/PerformanceCard.tsx @@ -10,7 +10,11 @@ import { useAppDispatch, useAppSelector } from '../../hooks'; import { setFilteredListToast } from '../../reducer/allCasesSlice'; import { setSelectedFilters } from '../../reducer/filtersSlice'; import { BOTTOM_TAB_ROUTES } from '../allCases/constants'; -import { CurrentAllocationStatsFilterMap, CurrentAllocationStatsMap } from './interface'; +import { + CurrentAllocationStatsFilterMap, + CurrentAllocationStatsMap, + PerformanceDetailsType, +} from './interface'; import { getPerformanceDetailFilter, getPerformanceDetails } from './utils'; const PerformanceCard = () => { @@ -23,30 +27,31 @@ const PerformanceCard = () => { const dispatch = useAppDispatch(); + const handlePress = (item: PerformanceDetailsType) => { + dispatch( + setSelectedFilters( + getPerformanceDetailFilter( + item.redirectionType, + Object.keys(filters['COMMON']?.filters)?.includes( + CurrentAllocationStatsFilterMap[item.redirectionType] + ) + ) + ) + ); + dispatch( + setFilteredListToast({ + showToast: true, + caseType: CurrentAllocationStatsMap[item.redirectionType], + }) + ); + navigateToScreen(BOTTOM_TAB_ROUTES.Cases); + }; return ( {performanceDetails.map((item) => ( { - dispatch( - setSelectedFilters( - getPerformanceDetailFilter( - item.redirectionType, - Object.keys(filters['COMMON']?.filters)?.includes( - CurrentAllocationStatsFilterMap[item.redirectionType] - ) - ) - ) - ); - dispatch( - setFilteredListToast({ - showToast: true, - caseType: CurrentAllocationStatsMap[item.redirectionType], - }) - ); - navigateToScreen(BOTTOM_TAB_ROUTES.Cases); - }} + onPress={() => handlePress(item)} style={[ getShadowStyle(2), GenericStyles.br6, diff --git a/src/screens/Dashboard/index.tsx b/src/screens/Dashboard/index.tsx index e157fa3a..8e06cffa 100644 --- a/src/screens/Dashboard/index.tsx +++ b/src/screens/Dashboard/index.tsx @@ -10,13 +10,10 @@ import { import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; import { getPerformanceMetrics } from '../../action/agentPerformanceAction'; -import { logError } from '../../components/utlis/errorUtils'; import { useAppDispatch, useAppSelector } from '../../hooks'; -import { setPerformanceData } from '../../reducer/agentPerformanceSlice'; import DashboardHeader from './DashboardHeader'; import PerformanceCard from './PerformanceCard'; import PerformanceOverview from './PerformanceOverview'; -import { getFiltersCount } from './utils'; const Dashboard = () => { const [refreshing, setRefreshing] = React.useState(false); @@ -27,49 +24,7 @@ const Dashboard = () => { const fetchAgentPerformanceMetrics = (callbackFn?: () => void) => { setIsLoading(true); - getPerformanceMetrics() - .then((res) => { - const { - visitedCases, - contactableCases, - ptpCases, - convertedPtpCases, - atLeastOneEmiCollectedCases, - } = res?.casesSplit; - const { totalLevel, currentLevel } = res?.performanceLevel; - const { unVisitedCount, nonContactableCount, nonPtpCount, brokenPtpCount } = - getFiltersCount(caseDetailsIds, res?.casesSplit); - - dispatch( - setPerformanceData({ - cases: { - visitedCases, - unvisitedCases: unVisitedCount, - contactableCases, - nonContactableCases: nonContactableCount, - totalPtp: ptpCases, - nonPtp: nonPtpCount, - convertedPtp: convertedPtpCases, - brokenPtp: brokenPtpCount, - totalEmi: res?.allCases, - atleastOneEmiCollected: atLeastOneEmiCollectedCases, - }, - performanceLevel: { - totalLevel, - currentLevel, - }, - totalCashCollected: res?.totalCashCollected, - lastUpdatedAt: res?.lastUpdatedAt, - }) - ); - }) - .catch((err) => { - logError(err); - }) - .finally(() => { - callbackFn && callbackFn(); - setIsLoading(false); - }); + dispatch(getPerformanceMetrics(caseDetailsIds, setIsLoading, callbackFn)); }; useEffect(() => { diff --git a/src/screens/Dashboard/interface.ts b/src/screens/Dashboard/interface.ts index 760c6d04..3dc927a7 100644 --- a/src/screens/Dashboard/interface.ts +++ b/src/screens/Dashboard/interface.ts @@ -79,3 +79,23 @@ export const CurrentAllocationStatsFilterMap = { [CurrentAllocationStats.NON_PTP]: CurrentAllocationStatsFilter.PTP_STATUS, [CurrentAllocationStats.BROKEN_PTP]: CurrentAllocationStatsFilter.PTP_BREAKAGE, }; + +export interface PerformanceDetailsType { + totalConverted: number; + convertedText: string; + totalNotConverted: number; + notConvertedText: string; + redirectionType: CurrentAllocationStats; +} + +export interface PaginationDetails { + pageNo: number; + totalPages: number; + pageSize: number; + totalElements: number; +} + +export interface CashCollectedSplitPayload { + pageNo: number; + pageSize: number; +} diff --git a/src/screens/auth/ProtectedRouter.tsx b/src/screens/auth/ProtectedRouter.tsx index c5f820de..17f09e17 100644 --- a/src/screens/auth/ProtectedRouter.tsx +++ b/src/screens/auth/ProtectedRouter.tsx @@ -34,7 +34,6 @@ import TodoList from '../todoList/TodoList'; import UngroupedAddressContainer from '../addressGeolocation/UngroupedAddressContainer'; import CashCollected from '../cashCollected'; import { getAgentDetail } from '../../action/agentPerformanceAction'; -import { setIsExternalAgent } from '../../reducer/userSlice'; import FullScreenLoader from '../../../RN-UI-LIB/src/components/FullScreenLoader'; const Stack = createNativeStackNavigator(); @@ -115,14 +114,7 @@ const ProtectedRouter = () => { useEffect(() => { if (isOnline) { setIsLoading(true); - getAgentDetail() - .then((res) => { - dispatch(setIsExternalAgent(res?.isExternalAgent)); - }) - .catch(() => { - dispatch(setIsExternalAgent(false)); - }) - .finally(() => setIsLoading(false)); + dispatch(getAgentDetail(() => setIsLoading(false))); } }, [isOnline]); diff --git a/src/screens/cashCollected/index.tsx b/src/screens/cashCollected/index.tsx index b57965bf..ce51f4b7 100644 --- a/src/screens/cashCollected/index.tsx +++ b/src/screens/cashCollected/index.tsx @@ -16,13 +16,11 @@ import { _map } from '../../../RN-UI-LIB/src/utlis/common'; import { getCashCollectedSplit } from '../../action/agentPerformanceAction'; import NoRepaymentsFoundIcon from '../../assets/icons/NoRepaymentsFoundIcon'; import NotificationMenu from '../../components/notificationMenu'; -import { logError } from '../../components/utlis/errorUtils'; import { goBack } from '../../components/utlis/navigationUtlis'; import { useAppDispatch, useAppSelector } from '../../hooks'; -import { setCashCollectedData, setIsloading } from '../../reducer/agentPerformanceSlice'; +import { setIsloading } from '../../reducer/agentPerformanceSlice'; import { RootState } from '../../store/store'; import { NO_CASH_COLLECTED_FOUND } from '../Dashboard/constants'; -import { getCashCollectedData } from '../Dashboard/utils'; import Layout from '../layout/Layout'; import CashCollectedItem from './CashCollectedItem'; @@ -48,24 +46,15 @@ const CashCollected = () => { const fetchCashCollectedData = (pageNo: number, callbackFn?: () => void) => { dispatch(setIsloading(true)); - getCashCollectedSplit({ pageNo, pageSize: 10 }) - .then((res) => { - const cashData = res?.data; - setPaginationDetails(res?.pages); - const cashCollected = getCashCollectedData(cashData, caseDetailsIds); - if (res?.pages?.pageNo === 0) { - dispatch(setCashCollectedData(cashCollected)); - return; - } - dispatch(setCashCollectedData([...cashCollectedData.data, ...cashCollected])); - }) - .catch((err) => { - logError(err); - }) - .finally(() => { - dispatch(setIsloading(true)); - callbackFn && callbackFn(); - }); + dispatch( + getCashCollectedSplit( + { pageNo, pageSize: 10 }, + caseDetailsIds, + cashCollectedData, + setPaginationDetails, + callbackFn + ) + ); }; const fetchMoreData = () => { diff --git a/src/store/store.ts b/src/store/store.ts index d81087f1..d114c427 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -74,7 +74,7 @@ const persistConfig = { 'profile', 'foregroundService', ], - blackList: ['case', 'filters'], + blackList: ['case', 'filters', 'agentPerformance'], }; const persistedReducer = persistReducer(persistConfig, rootReducer); From c2f76a48c172c0b86a35bff936b6aac6ba242ea1 Mon Sep 17 00:00:00 2001 From: yashmantri Date: Thu, 17 Aug 2023 18:24:52 +0530 Subject: [PATCH 06/21] TP-36903 | Updated Package Json and Few Css Fixes --- android/app/build.gradle | 4 ++-- package.json | 2 +- src/screens/Dashboard/PerformanceCard.tsx | 2 +- src/screens/Dashboard/PerformanceOverview.tsx | 10 +++++++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1da3aac4..b9576199 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -131,8 +131,8 @@ def reactNativeArchitectures() { return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] } -def VERSION_CODE = 76 -def VERSION_NAME = "2.3.3" +def VERSION_CODE = 77 +def VERSION_NAME = "2.3.4" android { ndkVersion rootProject.ext.ndkVersion diff --git a/package.json b/package.json index c8a003d7..d96fda63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "AV_APP", - "version": "2.3.3", + "version": "2.3.4", "private": true, "scripts": { "android:dev": "yarn move:dev && react-native run-android", diff --git a/src/screens/Dashboard/PerformanceCard.tsx b/src/screens/Dashboard/PerformanceCard.tsx index ba62a2ce..8c89286e 100644 --- a/src/screens/Dashboard/PerformanceCard.tsx +++ b/src/screens/Dashboard/PerformanceCard.tsx @@ -32,7 +32,7 @@ const PerformanceCard = () => { setSelectedFilters( getPerformanceDetailFilter( item.redirectionType, - Object.keys(filters['COMMON']?.filters)?.includes( + Object.keys(filters?.['COMMON']?.filters ?? {})?.includes( CurrentAllocationStatsFilterMap[item.redirectionType] ) ) diff --git a/src/screens/Dashboard/PerformanceOverview.tsx b/src/screens/Dashboard/PerformanceOverview.tsx index 5df2997a..a2790cf4 100644 --- a/src/screens/Dashboard/PerformanceOverview.tsx +++ b/src/screens/Dashboard/PerformanceOverview.tsx @@ -34,7 +34,15 @@ const PerformanceOverview = () => { navigateTo={PageRouteEnum.CASH_COLLECTED} rightSideView={ - + {formatAmount(totalCashCollected, false)} From d9cb0bab742b1bc92089480f2090915970113320 Mon Sep 17 00:00:00 2001 From: yashmantri Date: Thu, 17 Aug 2023 19:59:17 +0530 Subject: [PATCH 07/21] TP-36903 | PR Comments Resolved --- src/action/agentPerformanceAction.ts | 5 ++- src/common/Constants.ts | 4 +-- src/screens/Dashboard/DashBoardScreens.tsx | 34 +++++++++++++++++++ src/screens/Dashboard/PerformanceOverview.tsx | 10 +++--- src/screens/Dashboard/constants.ts | 2 ++ src/screens/Dashboard/utils.ts | 27 +++++++-------- src/screens/allCases/index.tsx | 4 +-- src/screens/auth/ProtectedRouter.tsx | 12 +------ src/screens/cashCollected/index.tsx | 4 +-- 9 files changed, 62 insertions(+), 40 deletions(-) create mode 100644 src/screens/Dashboard/DashBoardScreens.tsx diff --git a/src/action/agentPerformanceAction.ts b/src/action/agentPerformanceAction.ts index adafdd32..3671dbf7 100644 --- a/src/action/agentPerformanceAction.ts +++ b/src/action/agentPerformanceAction.ts @@ -47,8 +47,8 @@ export const getPerformanceMetrics = ptpCases, convertedPtpCases, atLeastOneEmiCollectedCases, - } = response?.data?.casesSplit; - const { totalLevel, currentLevel } = response?.data?.performanceLevel; + } = response?.data?.casesSplit || {}; + const { totalLevel, currentLevel } = response?.data?.performanceLevel || {}; const { unVisitedCount, nonContactableCount, nonPtpCount, brokenPtpCount } = getFiltersCount(caseDetailsIds, response?.data?.casesSplit); @@ -75,7 +75,6 @@ export const getPerformanceMetrics = }) ); } - return; }) .catch((err) => { logError(err); diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 448a47bf..6b170dc0 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -527,8 +527,8 @@ export const CLICKSTREAM_EVENT_NAMES = { }, // Agent Dashboard - AD_FIREBASE_SYNC_ISSUE: { - name: 'AD_FIREBASE_SYNC_ISSUE', + FA_AD_FIREBASE_SYNC_ISSUE: { + name: 'FA_AD_FIREBASE_SYNC_ISSUE', description: 'Agent dashboard firebase sync issues', }, } as const; diff --git a/src/screens/Dashboard/DashBoardScreens.tsx b/src/screens/Dashboard/DashBoardScreens.tsx new file mode 100644 index 00000000..01bc16b3 --- /dev/null +++ b/src/screens/Dashboard/DashBoardScreens.tsx @@ -0,0 +1,34 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import React from 'react'; +import Dashboard from '.'; +import { SCREEN_ANIMATION_DURATION } from '../../common/Constants'; +import { PageRouteEnum } from '../auth/ProtectedRouter'; +import CashCollected from '../cashCollected'; + +const Stack = createNativeStackNavigator(); + +function DashBoardScreens() { + return ( + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + > + null, + animation: 'none', + animationDuration: SCREEN_ANIMATION_DURATION, + }} + /> + + + ); +} + +export default DashBoardScreens; diff --git a/src/screens/Dashboard/PerformanceOverview.tsx b/src/screens/Dashboard/PerformanceOverview.tsx index a2790cf4..6da92b92 100644 --- a/src/screens/Dashboard/PerformanceOverview.tsx +++ b/src/screens/Dashboard/PerformanceOverview.tsx @@ -36,12 +36,7 @@ const PerformanceOverview = () => { {formatAmount(totalCashCollected, false)} @@ -101,6 +96,9 @@ const styles = StyleSheet.create({ fw700: { fontWeight: '700', }, + width150: { + maxWidth: 150, + }, }); export default PerformanceOverview; diff --git a/src/screens/Dashboard/constants.ts b/src/screens/Dashboard/constants.ts index 9ddaa7f8..fbe07d8f 100644 --- a/src/screens/Dashboard/constants.ts +++ b/src/screens/Dashboard/constants.ts @@ -18,3 +18,5 @@ export const PerformanceCardDetails = { export const PerfomanceHeaderTitle = 'Performance summary'; export const NO_CASH_COLLECTED_FOUND = 'No cash collected'; + +export const PAGE_SIZE = 10; diff --git a/src/screens/Dashboard/utils.ts b/src/screens/Dashboard/utils.ts index 7ef17bcf..6f51162b 100644 --- a/src/screens/Dashboard/utils.ts +++ b/src/screens/Dashboard/utils.ts @@ -69,20 +69,19 @@ export const getPerformanceDetails = (cases: CasesType) => { }; export const getFiltersCount = (caseDetailsIds: Array, cases: CasesType) => { - const { unvisitedCaseIds, nonContactableCaseIds, nonPtpCaseIds, brokenPtpCaseIds } = cases; + const { unvisitedCaseIds, nonContactableCaseIds, nonPtpCaseIds, brokenPtpCaseIds } = cases || {}; - const unVisitedCount = caseDetailsIds.filter((caseId: string) => - unvisitedCaseIds.includes(caseId) - ).length; - const nonContactableCount = caseDetailsIds.filter((caseId: string) => - nonContactableCaseIds.includes(caseId) - ).length; - const nonPtpCount = caseDetailsIds.filter((caseId: string) => - nonPtpCaseIds.includes(caseId) - ).length; - const brokenPtpCount = caseDetailsIds.filter((caseId: string) => - brokenPtpCaseIds.includes(caseId) - ).length; + let unVisitedCount = 0, + nonContactableCount = 0, + nonPtpCount = 0, + brokenPtpCount = 0; + + caseDetailsIds.forEach((caseId) => { + unVisitedCount += Number(unvisitedCaseIds.includes(caseId)); + nonContactableCount += Number(nonContactableCaseIds.includes(caseId)); + nonPtpCount += Number(nonPtpCaseIds.includes(caseId)); + brokenPtpCount += Number(brokenPtpCaseIds.includes(caseId)); + }); if ( unvisitedCaseIds.length !== unVisitedCount || @@ -96,7 +95,7 @@ export const getFiltersCount = (caseDetailsIds: Array, cases: CasesType) ...nonPtpCaseIds, ...brokenPtpCaseIds, ]; - addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AD_FIREBASE_SYNC_ISSUE, { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_AD_FIREBASE_SYNC_ISSUE, { caseIds: caseIds.filter((caseId) => !caseDetailsIds.includes(caseId)), }); } diff --git a/src/screens/allCases/index.tsx b/src/screens/allCases/index.tsx index 75a52d3d..91b7f5e0 100644 --- a/src/screens/allCases/index.tsx +++ b/src/screens/allCases/index.tsx @@ -22,8 +22,8 @@ import { addClickstreamEvent } from '../../services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; import { BOTTOM_TAB_ROUTES } from './constants'; import { getSelfieDocument } from '../../action/profileActions'; -import Dashboard from '../Dashboard'; import DashboardIcon from '../../assets/icons/DashboardIcon'; +import DashBoardScreens from '../Dashboard/DashBoardScreens'; const AllCasesMain = () => { const { pendingList, pinnedList, loading } = useAppSelector((state) => state.allCases); @@ -49,7 +49,7 @@ const AllCasesMain = () => { if (!isExternalAgent) { screens.push({ name: BOTTOM_TAB_ROUTES.Dashboard, - component: () => , + component: () => , icon: DashboardIcon, }); } diff --git a/src/screens/auth/ProtectedRouter.tsx b/src/screens/auth/ProtectedRouter.tsx index 17f09e17..36ae5c1c 100644 --- a/src/screens/auth/ProtectedRouter.tsx +++ b/src/screens/auth/ProtectedRouter.tsx @@ -32,7 +32,6 @@ import Notifications from '../notifications'; import RegisterPayments from '../registerPayements/RegisterPayments'; import TodoList from '../todoList/TodoList'; import UngroupedAddressContainer from '../addressGeolocation/UngroupedAddressContainer'; -import CashCollected from '../cashCollected'; import { getAgentDetail } from '../../action/agentPerformanceAction'; import FullScreenLoader from '../../../RN-UI-LIB/src/components/FullScreenLoader'; @@ -47,6 +46,7 @@ export enum PageRouteEnum { PAST_FEEDBACK_DETAIL = 'pastFeedbackDetail', ADDITIONAL_ADDRESSES = 'additionalAddresses', CASH_COLLECTED = 'cashCollected', + DASHBOARD_MAIN = 'dashboardMain', } const ProtectedRouter = () => { @@ -320,16 +320,6 @@ const ProtectedRouter = () => { }} listeners={getScreenFocusListenerObj} /> - null, - animation: 'none', - animationDuration: SCREEN_ANIMATION_DURATION, - }} - listeners={getScreenFocusListenerObj} - /> ); }; diff --git a/src/screens/cashCollected/index.tsx b/src/screens/cashCollected/index.tsx index ce51f4b7..0070e082 100644 --- a/src/screens/cashCollected/index.tsx +++ b/src/screens/cashCollected/index.tsx @@ -20,7 +20,7 @@ import { goBack } from '../../components/utlis/navigationUtlis'; import { useAppDispatch, useAppSelector } from '../../hooks'; import { setIsloading } from '../../reducer/agentPerformanceSlice'; import { RootState } from '../../store/store'; -import { NO_CASH_COLLECTED_FOUND } from '../Dashboard/constants'; +import { NO_CASH_COLLECTED_FOUND, PAGE_SIZE } from '../Dashboard/constants'; import Layout from '../layout/Layout'; import CashCollectedItem from './CashCollectedItem'; @@ -48,7 +48,7 @@ const CashCollected = () => { dispatch(setIsloading(true)); dispatch( getCashCollectedSplit( - { pageNo, pageSize: 10 }, + { pageNo, pageSize: PAGE_SIZE }, caseDetailsIds, cashCollectedData, setPaginationDetails, From 53ce06976b53afa9b5182e12945a810ac13aee84 Mon Sep 17 00:00:00 2001 From: yashmantri Date: Fri, 18 Aug 2023 00:45:03 +0530 Subject: [PATCH 08/21] TP-36903 | UAT Fixes --- src/screens/Dashboard/DashBoardScreens.tsx | 8 ++++++++ src/screens/Dashboard/PerformanceCard.tsx | 4 ++-- src/screens/Dashboard/PerformanceOverview.tsx | 2 +- src/screens/allCases/CasesList.tsx | 7 +++++++ src/screens/auth/ProtectedRouter.tsx | 1 + src/screens/cashCollected/CashCollectedItem.tsx | 6 ++++-- src/screens/cashCollected/index.tsx | 7 ++++++- 7 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/screens/Dashboard/DashBoardScreens.tsx b/src/screens/Dashboard/DashBoardScreens.tsx index 01bc16b3..b1d214e2 100644 --- a/src/screens/Dashboard/DashBoardScreens.tsx +++ b/src/screens/Dashboard/DashBoardScreens.tsx @@ -2,12 +2,16 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'; import React from 'react'; import Dashboard from '.'; import { SCREEN_ANIMATION_DURATION } from '../../common/Constants'; +import { useAppSelector } from '../../hooks'; +import CasesList from '../allCases/CasesList'; import { PageRouteEnum } from '../auth/ProtectedRouter'; import CashCollected from '../cashCollected'; const Stack = createNativeStackNavigator(); function DashBoardScreens() { + const { pendingList } = useAppSelector((state) => state.allCases); + return ( + } + /> ); } diff --git a/src/screens/Dashboard/PerformanceCard.tsx b/src/screens/Dashboard/PerformanceCard.tsx index 8c89286e..a3a70ce0 100644 --- a/src/screens/Dashboard/PerformanceCard.tsx +++ b/src/screens/Dashboard/PerformanceCard.tsx @@ -9,7 +9,7 @@ import { navigateToScreen } from '../../components/utlis/navigationUtlis'; import { useAppDispatch, useAppSelector } from '../../hooks'; import { setFilteredListToast } from '../../reducer/allCasesSlice'; import { setSelectedFilters } from '../../reducer/filtersSlice'; -import { BOTTOM_TAB_ROUTES } from '../allCases/constants'; +import { PageRouteEnum } from '../auth/ProtectedRouter'; import { CurrentAllocationStatsFilterMap, CurrentAllocationStatsMap, @@ -44,7 +44,7 @@ const PerformanceCard = () => { caseType: CurrentAllocationStatsMap[item.redirectionType], }) ); - navigateToScreen(BOTTOM_TAB_ROUTES.Cases); + navigateToScreen(PageRouteEnum.FILTERED_CASES); }; return ( diff --git a/src/screens/Dashboard/PerformanceOverview.tsx b/src/screens/Dashboard/PerformanceOverview.tsx index 6da92b92..04388dbb 100644 --- a/src/screens/Dashboard/PerformanceOverview.tsx +++ b/src/screens/Dashboard/PerformanceOverview.tsx @@ -38,7 +38,7 @@ const PerformanceOverview = () => { numberOfLines={1} style={[styles.fw700, styles.leftContent, GenericStyles.fontSize16, styles.width150]} > - {formatAmount(totalCashCollected, false)} + {formatAmount(Number(totalCashCollected.toFixed(2)), false)} diff --git a/src/screens/allCases/CasesList.tsx b/src/screens/allCases/CasesList.tsx index a1c2be10..0a3c6765 100644 --- a/src/screens/allCases/CasesList.tsx +++ b/src/screens/allCases/CasesList.tsx @@ -46,6 +46,7 @@ import { dateFormat, DAY_MONTH_DATE_FORMAT } from '../../../RN-UI-LIB/src/utlis/ import { getAttemptedList, getNonAttemptedList } from './utils'; import { toast } from '../../../RN-UI-LIB/src/components/toast'; import { setFilteredListToast } from '../../reducer/allCasesSlice'; +import { setSelectedFilters } from '../../reducer/filtersSlice'; export const getItem = (item: Array, index: number) => item[index]; export const ESTIMATED_ITEM_SIZE = 250; // Average height of List item @@ -310,6 +311,12 @@ const CasesList: React.FC = ({ casesList = [], isVisitPlan }) => { } } + useEffect(() => { + return () => { + !showToast && dispatch(setSelectedFilters({})); + }; + }, []); + return ( { diff --git a/src/screens/cashCollected/CashCollectedItem.tsx b/src/screens/cashCollected/CashCollectedItem.tsx index 33faa6de..3017d286 100644 --- a/src/screens/cashCollected/CashCollectedItem.tsx +++ b/src/screens/cashCollected/CashCollectedItem.tsx @@ -40,12 +40,14 @@ const CashCollectedItem = (props: CashCollectedItemProps) => { Collected :{' '} = totalOverdueAmount ? styles.green : styles.red}> - {formatAmount(amountCollected, false)} + {formatAmount(Number(amountCollected.toFixed(2)), false)} Current outstanding :{' '} - {formatAmount(totalOverdueAmount, false)} + + {formatAmount(Number(totalOverdueAmount.toFixed(2)), false)} + diff --git a/src/screens/cashCollected/index.tsx b/src/screens/cashCollected/index.tsx index 0070e082..ba4bd5b6 100644 --- a/src/screens/cashCollected/index.tsx +++ b/src/screens/cashCollected/index.tsx @@ -79,7 +79,12 @@ const CashCollected = () => { fetchCashCollectedData(0); }, []); - if (isLoading && !data.length) return ; + if (isLoading && !data.length) + return ( + + + + ); return ( From e945a3cce4e9750315613579354491203d51c335 Mon Sep 17 00:00:00 2001 From: yashmantri Date: Fri, 18 Aug 2023 11:39:29 +0530 Subject: [PATCH 09/21] TP-36903 | Minor Bug Fix --- src/screens/cashCollected/CashCollectedItem.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/screens/cashCollected/CashCollectedItem.tsx b/src/screens/cashCollected/CashCollectedItem.tsx index 3017d286..46311055 100644 --- a/src/screens/cashCollected/CashCollectedItem.tsx +++ b/src/screens/cashCollected/CashCollectedItem.tsx @@ -45,9 +45,7 @@ const CashCollectedItem = (props: CashCollectedItemProps) => { Current outstanding :{' '} - - {formatAmount(Number(totalOverdueAmount.toFixed(2)), false)} - + {formatAmount(totalOverdueAmount, false)} From 15592f0f6ac4cd3c05e5967ab5512b94b02e502b Mon Sep 17 00:00:00 2001 From: yashmantri Date: Fri, 18 Aug 2023 13:22:48 +0530 Subject: [PATCH 10/21] TP-36903 | Dashbord Clickstream Event Added --- src/common/Constants.ts | 52 ++++++++++++++++++++- src/components/utlis/navigationUtlis.ts | 6 +++ src/reducer/filtersSlice.ts | 30 +++++++++++- src/screens/Dashboard/DashBoardScreens.tsx | 2 +- src/screens/Dashboard/PerformanceCard.tsx | 8 ++-- src/screens/Dashboard/PerformanceItem.tsx | 15 +++++- src/screens/Dashboard/index.tsx | 3 ++ src/screens/Dashboard/utils.ts | 37 ++++++++++++++- src/screens/allCases/CasesActionButtons.tsx | 2 +- src/screens/allCases/CasesList.tsx | 19 ++++---- src/screens/allCases/index.tsx | 5 +- src/screens/cashCollected/index.tsx | 9 ++++ src/screens/todoList/TodoList.tsx | 10 +++- 13 files changed, 176 insertions(+), 22 deletions(-) diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 6b170dc0..719768b9 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -527,10 +527,58 @@ export const CLICKSTREAM_EVENT_NAMES = { }, // Agent Dashboard - FA_AD_FIREBASE_SYNC_ISSUE: { - name: 'FA_AD_FIREBASE_SYNC_ISSUE', + FA_PERFORMANCE_DASHBOARD_FIREBASE_SYNC_ISSUE: { + name: 'FA_PERFORMANCE_DASHBOARD_FIREBASE_SYNC_ISSUE', description: 'Agent dashboard firebase sync issues', }, + FA_PERFORMANCE_DASHBOARD_BUTTON_CLICKED: { + name: 'FA_PERFORMANCE_DASHBOARD_BUTTON_CLICKED', + description: 'Performance Dashboard Button Clicked', + }, + FA_PERFORMANCE_DASHBOARD_PAGE_LOAD: { + name: 'FA_PERFORMANCE_DASHBOARD_PAGE_LOAD', + description: 'Performance Dashboard Page Load', + }, + FA_PERFORMANCE_DASHBOARD_CASH_COLLECTED_CLICKED: { + name: 'FA_PERFORMANCE_DASHBOARD_CASH_COLLECTED_CLICKED', + description: 'Performance Dashboard Cash Collected Clicked', + }, + FA_PERFORMANCE_DASHBOARD_CASH_COLLECTED_CASE_LOAD: { + name: 'FA_PERFORMANCE_DASHBOARD_CASH_COLLECTED_CASE_LOAD', + description: 'Performance Dashboard Cash Collected Case Load', + }, + FA_PERFORMANCE_DASHBOARD_UNVISITED_CASES_CLICKED: { + name: 'FA_PERFORMANCE_DASHBOARD_UNVISITED_CASES_CLICKED', + description: 'Performance Dashboard Unvisited Cases Clicked', + }, + FA_PERFORMANCE_DASHBOARD_UNVISITED_CASES_LOAD: { + name: 'FA_PERFORMANCE_DASHBOARD_UNVISITED_CASES_LOAD', + description: 'Performance Dashboard Button Clicked', + }, + FA_PERFORMANCE_DASHBOARD_NON_CONTACTABLE_CASES_CLICKED: { + name: 'FA_PERFORMANCE_DASHBOARD_NON_CONTACTABLE_CASES_CLICKED', + description: 'Performance Dashboard Non Contactable Cases Clicked', + }, + FA_PERFORMANCE_DASHBOARD_NON_CONTACTABLE_CASES_LOAD: { + name: 'FA_PERFORMANCE_DASHBOARD_NON_CONTACTABLE_CASES_LOAD', + description: 'Performance Dashboard Non Contactable Cases Load', + }, + FA_PERFORMANCE_DASHBOARD_NON_PTP_CASES_CLICKED: { + name: 'FA_PERFORMANCE_DASHBOARD_NON_PTP_CASES_CLICKED', + description: 'Performance Dashboard Non PTP Cases Clicked', + }, + FA_PERFORMANCE_DASHBOARD_NON_PTP_CASES_LOAD: { + name: 'FA_PERFORMANCE_DASHBOARD_NON_PTP_CASES_LOAD', + description: 'Performance Dashboard Non PTP Cases Load', + }, + FA_PERFORMANCE_DASHBOARD_BROKEN_PTP_CASES_CLICKED: { + name: 'FA_PERFORMANCE_DASHBOARD_BROKEN_PTP_CASES_CLICKED', + description: 'Performance Dashboard Broken PTP Cases Clicked', + }, + FA_PERFORMANCE_DASHBOARD_BROKEN_PTP_CASES_LOAD: { + name: 'FA_PERFORMANCE_DASHBOARD_BROKEN_PTP_CASES_LOAD', + description: 'Performance Dashboard Broken PTP Cases Load', + }, } as const; export enum MimeType { diff --git a/src/components/utlis/navigationUtlis.ts b/src/components/utlis/navigationUtlis.ts index a2d5b195..75012a9d 100644 --- a/src/components/utlis/navigationUtlis.ts +++ b/src/components/utlis/navigationUtlis.ts @@ -54,3 +54,9 @@ export function getWidgetNameFromRoute(routeName: string, caseType: CaseAllocati : TemplateRoutePrefix.AV.length ); } + +export const clearNavigationStack = (stackScreenName: string) => { + navigationRef?.current?.reset({ + routes: [{ index: 0, name: stackScreenName }], + }); +}; diff --git a/src/reducer/filtersSlice.ts b/src/reducer/filtersSlice.ts index 6d657ba7..824d2802 100644 --- a/src/reducer/filtersSlice.ts +++ b/src/reducer/filtersSlice.ts @@ -11,6 +11,7 @@ interface IFiltersSlice { selectedFiltersVisitPlan: Record; filterCount: number; filterCountVisitPlan: number; + selectedAgentDashboardFilter: Record; } const initialState: IFiltersSlice = { @@ -20,6 +21,7 @@ const initialState: IFiltersSlice = { filterCountVisitPlan: 0, selectedFilters: {}, selectedFiltersVisitPlan: {}, + selectedAgentDashboardFilter: {}, }; const filtersSlice = createSlice({ @@ -73,9 +75,35 @@ const filtersSlice = createSlice({ }); state.filterCountVisitPlan = filterCount; }, + setSelectedAgentDashboardFilter: (state, action) => { + state.selectedAgentDashboardFilter = { + ...action.payload, + }; + let filterCount = 0; + _map(state.selectedAgentDashboardFilter, (filterKey) => { + switch (typeof state.selectedAgentDashboardFilter[filterKey]) { + case 'object': + filterCount += + state.selectedAgentDashboardFilter[filterKey] && + Object.keys(state.selectedAgentDashboardFilter[filterKey]).length; + break; + case 'string': + filterCount += 1; + break; + default: + break; + } + }); + state.filterCount = filterCount; + }, }, }); -export const { setFilters, setSelectedFilters, setSelectedFiltersVisitPlan } = filtersSlice.actions; +export const { + setFilters, + setSelectedFilters, + setSelectedFiltersVisitPlan, + setSelectedAgentDashboardFilter, +} = filtersSlice.actions; export default filtersSlice.reducer; diff --git a/src/screens/Dashboard/DashBoardScreens.tsx b/src/screens/Dashboard/DashBoardScreens.tsx index b1d214e2..bb70b061 100644 --- a/src/screens/Dashboard/DashBoardScreens.tsx +++ b/src/screens/Dashboard/DashBoardScreens.tsx @@ -33,7 +33,7 @@ function DashBoardScreens() { } + component={() => } /> ); diff --git a/src/screens/Dashboard/PerformanceCard.tsx b/src/screens/Dashboard/PerformanceCard.tsx index a3a70ce0..206a65c1 100644 --- a/src/screens/Dashboard/PerformanceCard.tsx +++ b/src/screens/Dashboard/PerformanceCard.tsx @@ -8,14 +8,15 @@ import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; import { navigateToScreen } from '../../components/utlis/navigationUtlis'; import { useAppDispatch, useAppSelector } from '../../hooks'; import { setFilteredListToast } from '../../reducer/allCasesSlice'; -import { setSelectedFilters } from '../../reducer/filtersSlice'; +import { setSelectedAgentDashboardFilter } from '../../reducer/filtersSlice'; +import { addClickstreamEvent } from '../../services/clickstreamEventService'; import { PageRouteEnum } from '../auth/ProtectedRouter'; import { CurrentAllocationStatsFilterMap, CurrentAllocationStatsMap, PerformanceDetailsType, } from './interface'; -import { getPerformanceDetailFilter, getPerformanceDetails } from './utils'; +import { getClickedEventName, getPerformanceDetailFilter, getPerformanceDetails } from './utils'; const PerformanceCard = () => { const { performanceData, filters } = useAppSelector((state) => ({ @@ -29,7 +30,7 @@ const PerformanceCard = () => { const handlePress = (item: PerformanceDetailsType) => { dispatch( - setSelectedFilters( + setSelectedAgentDashboardFilter( getPerformanceDetailFilter( item.redirectionType, Object.keys(filters?.['COMMON']?.filters ?? {})?.includes( @@ -45,6 +46,7 @@ const PerformanceCard = () => { }) ); navigateToScreen(PageRouteEnum.FILTERED_CASES); + addClickstreamEvent(getClickedEventName(item.redirectionType), {}); }; return ( diff --git a/src/screens/Dashboard/PerformanceItem.tsx b/src/screens/Dashboard/PerformanceItem.tsx index 0a20dabc..c55cc5a9 100644 --- a/src/screens/Dashboard/PerformanceItem.tsx +++ b/src/screens/Dashboard/PerformanceItem.tsx @@ -2,11 +2,24 @@ import React from 'react'; import { Pressable, StyleSheet, View } from 'react-native'; import Text from '../../../RN-UI-LIB/src/components/Text'; import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; import { navigateToScreen } from '../../components/utlis/navigationUtlis'; +import { addClickstreamEvent } from '../../services/clickstreamEventService'; import { PerformanceItemProps } from './interface'; const PerformanceItem = (props: PerformanceItemProps) => { const { title, icon, navigateTo, rightSideView } = props; + + const handlePress = () => { + if (navigateTo) { + navigateToScreen(navigateTo); + addClickstreamEvent( + CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_CASH_COLLECTED_CLICKED, + {} + ); + } + }; + return ( { GenericStyles.spaceBetween, styles.performanceContainer, ]} - onPress={() => navigateTo && navigateToScreen(navigateTo)} + onPress={handlePress} > {icon} diff --git a/src/screens/Dashboard/index.tsx b/src/screens/Dashboard/index.tsx index 8e06cffa..101ac000 100644 --- a/src/screens/Dashboard/index.tsx +++ b/src/screens/Dashboard/index.tsx @@ -10,7 +10,9 @@ import { import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; import { COLORS } from '../../../RN-UI-LIB/src/styles/colors'; import { getPerformanceMetrics } from '../../action/agentPerformanceAction'; +import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants'; import { useAppDispatch, useAppSelector } from '../../hooks'; +import { addClickstreamEvent } from '../../services/clickstreamEventService'; import DashboardHeader from './DashboardHeader'; import PerformanceCard from './PerformanceCard'; import PerformanceOverview from './PerformanceOverview'; @@ -29,6 +31,7 @@ const Dashboard = () => { useEffect(() => { fetchAgentPerformanceMetrics(); + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_PAGE_LOAD, {}); }, []); const onRefresh = React.useCallback(() => { diff --git a/src/screens/Dashboard/utils.ts b/src/screens/Dashboard/utils.ts index 6f51162b..8dbe1309 100644 --- a/src/screens/Dashboard/utils.ts +++ b/src/screens/Dashboard/utils.ts @@ -95,7 +95,7 @@ export const getFiltersCount = (caseDetailsIds: Array, cases: CasesType) ...nonPtpCaseIds, ...brokenPtpCaseIds, ]; - addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_AD_FIREBASE_SYNC_ISSUE, { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_FIREBASE_SYNC_ISSUE, { caseIds: caseIds.filter((caseId) => !caseDetailsIds.includes(caseId)), }); } @@ -109,3 +109,38 @@ export const getCashCollectedData = ( ) => { return cashCollectedRes?.filter((cashData) => caseDetailsIds.includes(cashData.caseId)); }; + +export const getClickedEventName = (item: CurrentAllocationStats) => { + switch (item) { + case CurrentAllocationStats.UNVSIITED: + return CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_UNVISITED_CASES_CLICKED; + + case CurrentAllocationStats.NON_CONTACTABLE: + return CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_NON_CONTACTABLE_CASES_CLICKED; + + case CurrentAllocationStats.NON_PTP: + return CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_NON_PTP_CASES_CLICKED; + + case CurrentAllocationStats.BROKEN_PTP: + return CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_BROKEN_PTP_CASES_CLICKED; + } +}; + +export const getPageLoadEventName = (caseType: string) => { + switch (caseType) { + case 'unvsited': + return CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_UNVISITED_CASES_LOAD; + + case 'non contactable': + return CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_NON_CONTACTABLE_CASES_LOAD; + + case 'non PTP': + return CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_NON_PTP_CASES_LOAD; + + case 'broken PTP': + return CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_BROKEN_PTP_CASES_LOAD; + + default: + return CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_UNVISITED_CASES_LOAD; + } +}; diff --git a/src/screens/allCases/CasesActionButtons.tsx b/src/screens/allCases/CasesActionButtons.tsx index 082da8c0..eb9c6615 100644 --- a/src/screens/allCases/CasesActionButtons.tsx +++ b/src/screens/allCases/CasesActionButtons.tsx @@ -107,7 +107,7 @@ export const CasesActionButtons: React.FC = () => { return null; } const renderActions = () => { - if (newlyPinnedCases && currentRoute === 'Cases') { + if (newlyPinnedCases && ['Cases', 'filteredCases'].includes(currentRoute)) { return ( <>