From a530b4e9ede4586f55d1c32147886ef77ea41ece Mon Sep 17 00:00:00 2001 From: Mantri Ramkishor Date: Tue, 3 Dec 2024 19:15:10 +0530 Subject: [PATCH] NTP-8094 | Case Card Revamp (#983) Co-authored-by: aishwarya.srivastava --- android/app/build.gradle | 4 +- package.json | 4 +- src/action/caseApiActions.ts | 3 +- src/action/feedbackActions.ts | 20 +- src/assets/icons/LocationDistanceIcon.tsx | 6 +- src/assets/icons/TagPlaceholderIcon.tsx | 16 ++ src/components/form/index.tsx | 2 + src/components/utlis/apiHelper.ts | 2 + src/reducer/topFeedbacksSlice.ts | 14 +- src/screens/allCases/CaseItem.tsx | 4 +- .../allCases/CaseItem/CaseDetailKeyValue.tsx | 41 +++ .../allCases/CaseItem/CaseListItem.tsx | 254 ++++++++++++++++++ src/screens/allCases/CaseItem/CaseStatus.tsx | 57 ++++ .../allCases/CaseItem/FeedbackStatus.tsx | 77 ++++++ .../allCases/CaseItem/VisitPlanTag.tsx | 46 ++++ src/screens/allCases/CasesList.tsx | 14 +- .../allCases/Escalation/Escalation.tsx | 41 +++ .../allCases/Escalation/EscalationItem.tsx | 62 +++++ src/screens/allCases/interface.ts | 26 ++ src/screens/allCases/utils.ts | 22 ++ .../caseDetails/CollectionCaseDetail.tsx | 3 +- .../feedback/FeedbackDetailContainer.tsx | 4 +- .../caseDetails/feedback/FeedbackListItem.tsx | 16 +- .../caseDetails/feedback/TopFeedbacks.tsx | 8 +- .../feedback/pastFeedbackCommon.ts | 1 + src/screens/caseDetails/interface.ts | 11 +- src/types/feedback.types.ts | 2 + 27 files changed, 712 insertions(+), 48 deletions(-) create mode 100644 src/assets/icons/TagPlaceholderIcon.tsx create mode 100644 src/screens/allCases/CaseItem/CaseDetailKeyValue.tsx create mode 100644 src/screens/allCases/CaseItem/CaseListItem.tsx create mode 100644 src/screens/allCases/CaseItem/CaseStatus.tsx create mode 100644 src/screens/allCases/CaseItem/FeedbackStatus.tsx create mode 100644 src/screens/allCases/CaseItem/VisitPlanTag.tsx create mode 100644 src/screens/allCases/Escalation/Escalation.tsx create mode 100644 src/screens/allCases/Escalation/EscalationItem.tsx create mode 100644 src/screens/caseDetails/feedback/pastFeedbackCommon.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index b70bda37..715aab80 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -113,8 +113,8 @@ def jscFlavor = 'org.webkit:android-jsc:+' def enableHermes = project.ext.react.get("enableHermes", false); -def VERSION_CODE = 219 -def VERSION_NAME = "2.15.5" +def VERSION_CODE = 220 +def VERSION_NAME = "2.15.6" android { namespace "com.avapp" diff --git a/package.json b/package.json index 9bb29820..de005beb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "AV_APP", - "version": "2.15.5", - "buildNumber": "219", + "version": "2.15.6", + "buildNumber": "220", "private": true, "scripts": { "android:dev": "yarn move:dev && react-native run-android", diff --git a/src/action/caseApiActions.ts b/src/action/caseApiActions.ts index 12eb0bbd..a546b76a 100644 --- a/src/action/caseApiActions.ts +++ b/src/action/caseApiActions.ts @@ -9,6 +9,7 @@ import { logError } from '../components/utlis/errorUtils'; import { type IDocument, removeDocumentByQuestionKey } from '../reducer/feedbackImagesSlice'; import { addClickstreamEvent } from '@services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants'; +import { PAST_FEEDBACK_PAGE_SIZE } from '@screens/caseDetails/feedback/pastFeedbackCommon'; export const getRepaymentsData = (loanAccountNumber: string) => (dispatch: AppDispatch) => { dispatch(setRepaymentsLoading({ loanAccountNumber, isLoading: true })); @@ -81,7 +82,7 @@ export const getFeedbackHistory = (loanAccountNumber: string) => (dispatch: AppD { loan_account_number: loanAccountNumber, page_no: 0, - page_size: 5, + page_size: PAST_FEEDBACK_PAGE_SIZE, } ); axiosInstance diff --git a/src/action/feedbackActions.ts b/src/action/feedbackActions.ts index 5d486026..e4cdf8a9 100644 --- a/src/action/feedbackActions.ts +++ b/src/action/feedbackActions.ts @@ -77,27 +77,23 @@ export const getPastFeedbacksOnAddresses = (pastFeedbackPayload: IPastFeedbacksP }); }; -export const getTopFeedbacks = (loanAccountNumber: string) => (dispatch: AppDispatch) => { - // TODO: Change API Endpoint - const url = getApiUrl(ApiKeys.PAST_FEEDBACK_ON_ADDRESSES); - dispatch(setTopFeedbacksLoading({ loanAccountNumber, isLoading: true })); +export const getTopFeedbacks = (caseId: string) => (dispatch: AppDispatch) => { + const url = getApiUrl(ApiKeys.GET_PRIORTIY_FEEDBACK); + dispatch(setTopFeedbacksLoading({ caseId, isLoading: true })); return axiosInstance .get(url, { - params: { loanAccountNumber }, + params: { caseReferenceId: caseId }, }) .then((response) => { dispatch( setTopFeedbacks({ - loanAccountNumber, - feedbacks: [ - response?.data?.data?.currentMonthFeedbackStatus, - response?.data?.data?.lastMonthFeedbackStatus, - ], + caseId, + feedbacks: response?.data || [], }) ); }) .catch((err) => { - dispatch(setTopFeedbacksLoading({ loanAccountNumber, isLoading: false })); logError(err); - }); + }) + .finally(() => dispatch(setTopFeedbacksLoading({ caseId, isLoading: false }))); }; diff --git a/src/assets/icons/LocationDistanceIcon.tsx b/src/assets/icons/LocationDistanceIcon.tsx index ae5e0b6c..b8931f64 100644 --- a/src/assets/icons/LocationDistanceIcon.tsx +++ b/src/assets/icons/LocationDistanceIcon.tsx @@ -12,10 +12,10 @@ const LocationDistanceIcon = (props: ILocationDistanceIcon) => { const backgroundColor = props?.backgroundColor || COLORS.BACKGROUND.BLUE; return ( - + - - + + diff --git a/src/assets/icons/TagPlaceholderIcon.tsx b/src/assets/icons/TagPlaceholderIcon.tsx new file mode 100644 index 00000000..c2633f54 --- /dev/null +++ b/src/assets/icons/TagPlaceholderIcon.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Path, Svg } from 'react-native-svg'; + +const TagPlaceholderIcon = () => { + return ( + + + + + ); +}; + +export default TagPlaceholderIcon; diff --git a/src/components/form/index.tsx b/src/components/form/index.tsx index d3b783ea..ab3a49f0 100644 --- a/src/components/form/index.tsx +++ b/src/components/form/index.tsx @@ -44,6 +44,7 @@ import { NUDGE_BOTTOM_SHEET_DEFAULT_STATE } from './constants'; import { useBackHandler } from '@hooks/useBackHandler'; import { CALLING_NUDGE } from '@screens/caseDetails/CallingFlow/constants'; import { isFunction } from '@components/utlis/commonFunctions'; +import { getTopFeedbacks } from '@actions/feedbackActions'; import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice'; import { getSyncTime } from '@hooks/capturingApi'; @@ -228,6 +229,7 @@ const Widget: React.FC = (props) => { caseId, }); } + dispatch(getTopFeedbacks(caseId)); dispatch( deleteJourney({ caseId, diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index e0145515..2a82052b 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -111,6 +111,7 @@ export enum ApiKeys { GET_EMI_SCHEDULE = 'GET_EMI_SCHEDULE', GET_REPAYMENTS = 'GET_REPAYMENTS', GET_FEEDBACK_HISTORY = 'GET_FEEDBACK_HISTORY', + GET_PRIORTIY_FEEDBACK = 'GET_PRIORTIY_FEEDBACK', } export const API_URLS: Record = {} as Record; @@ -215,6 +216,7 @@ API_URLS[ApiKeys.GET_GROUPED_ADDRESSES_AND_GEOLOCATIONS] = API_URLS[ApiKeys.GET_EMI_SCHEDULE] = '/collection-cases/{loanAccountNumber}/emiSchedule'; API_URLS[ApiKeys.GET_REPAYMENTS] = '/collection-cases/{loanAccountNumber}/repayments'; API_URLS[ApiKeys.GET_FEEDBACK_HISTORY] = '/feedback/filters'; +API_URLS[ApiKeys.GET_PRIORTIY_FEEDBACK] = 'feedback/case-status'; export const API_STATUS_CODE = { OK: 200, diff --git a/src/reducer/topFeedbacksSlice.ts b/src/reducer/topFeedbacksSlice.ts index 774ca90d..ccf8d149 100644 --- a/src/reducer/topFeedbacksSlice.ts +++ b/src/reducer/topFeedbacksSlice.ts @@ -4,7 +4,7 @@ interface ITopFeedback { status: string; color: string; referenceId: string; - offset: number; + orderOffset: number; createdAt: string; type: string; } @@ -23,16 +23,16 @@ const TopFeedbacksSlice = createSlice({ initialState, reducers: { setTopFeedbacks: (state, action) => { - const { loanAccountNumber, feedbacks } = action.payload || {}; - state[loanAccountNumber] = { - ...(state[loanAccountNumber] || {}), + const { caseId, feedbacks } = action.payload || {}; + state[caseId] = { + ...(state[caseId] || {}), feedbacks, }; }, setTopFeedbacksLoading: (state, action) => { - const { loanAccountNumber, isLoading } = action.payload || {}; - state[loanAccountNumber] = { - ...(state?.[loanAccountNumber] || {}), + const { caseId, isLoading } = action.payload || {}; + state[caseId] = { + ...(state?.[caseId] || {}), isLoading: isLoading, }; }, diff --git a/src/screens/allCases/CaseItem.tsx b/src/screens/allCases/CaseItem.tsx index fad5c6cb..32fbbaa5 100644 --- a/src/screens/allCases/CaseItem.tsx +++ b/src/screens/allCases/CaseItem.tsx @@ -2,7 +2,6 @@ import React, { useMemo } from 'react'; import { Text, View, ViewProps, StyleSheet, Pressable } from 'react-native'; import { GenericStyles, getShadowStyle } from '../../../RN-UI-LIB/src/styles'; import { CaseTypes, ICaseItemCaseDetailObj } from './interface'; -import ListItem from './ListItem'; import Button from '../../../RN-UI-LIB/src/components/Button'; import { navigateToScreen } from '../../components/utlis/navigationUtlis'; import { useAppSelector } from '../../hooks'; @@ -13,6 +12,7 @@ import LocationIcon from '@assets/icons/LocationIcon'; import ArrowRightOutlineIcon from '@rn-ui-lib/icons/ArrowRightOutlineIcon'; import { addClickstreamEvent } from '@services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants'; +import CaseListItem from './CaseItem/CaseListItem'; interface ICaseItemProps extends ViewProps { caseDetailObj: ICaseItemCaseDetailObj; @@ -125,7 +125,7 @@ const CaseItem: React.FC = ({ default: return ( - { + const { title, value, showDot = false } = props; + return ( + + + + {title} + {' '} + {value ?? '--'} + + {showDot ? : null} + + ); +}; + +const styles = StyleSheet.create({ + caseStatusText: { + color: COLORS.TEXT.GREY_32465B, + fontWeight: '500', + }, + caseStatusTextTitle: { + color: COLORS.TEXT.LIGHT, + fontWeight: '500', + }, + dot: { + height: 4, + width: 4, + borderRadius: 2, + marginHorizontal: 8, + backgroundColor: COLORS.TEXT.GREY_1, + }, +}); + +export default CaseDetailKeyValue; diff --git a/src/screens/allCases/CaseItem/CaseListItem.tsx b/src/screens/allCases/CaseItem/CaseListItem.tsx new file mode 100644 index 00000000..e75df1d3 --- /dev/null +++ b/src/screens/allCases/CaseItem/CaseListItem.tsx @@ -0,0 +1,254 @@ +import React, { memo, useMemo } from 'react'; +import { Pressable, StyleSheet, View } from 'react-native'; +import Heading from '../../../../RN-UI-LIB/src/components/Heading'; +import Text from '../../../../RN-UI-LIB/src/components/Text'; +import { GenericStyles, getShadowStyle } from '../../../../RN-UI-LIB/src/styles'; +import { getCurrentScreen, navigateToScreen } from '../../../components/utlis/navigationUtlis'; +import { useAppDispatch, useAppSelector } from '../../../hooks'; +import { setPinnedRank, setSelectedTodoListMap } from '../../../reducer/allCasesSlice'; +import CaseItemAvatar from '../CaseItemAvatar'; +import { CaseStatuses, ICaseItemAvatarCaseDetailObj, ICaseListItem } from '../interface'; +import { COLORS } from '../../../../RN-UI-LIB/src/styles/colors'; +import { CLICKSTREAM_EVENT_NAMES } from '../../../common/Constants'; +import { addClickstreamEvent } from '../../../services/clickstreamEventService'; +import { formatAmount } from '../../../../RN-UI-LIB/src/utlis/amount'; +import RoundCheckIcon from '../../../../RN-UI-LIB/src/Icons/RoundCheckIcon'; +import { getDocumentList, pluralise } from '../../../components/utlis/commonFunctions'; +import { toast } from '../../../../RN-UI-LIB/src/components/toast'; +import { COMPLETED_STATUSES, ToastMessages } from '../constants'; +import { VisitPlanStatus } from '../../../reducer/userSlice'; +import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack'; +import { PageRouteEnum } from '@screens/auth/ProtectedRouter'; +import VisitPlanTag from './VisitPlanTag'; +import CaseStatus from './CaseStatus'; +import FeedbackStatus from './FeedbackStatus'; +import CaseDetailKeyValue from './CaseDetailKeyValue'; +import Escalation from '../Escalation/Escalation'; + +const CaseListItem: React.FC = (props) => { + const { + caseListItemDetailObj, + isCompleted, + isTodoItem, + shouldBatchAvatar, + allCasesView, + nearbyCaseView, + isVisitPlan, + } = props; + const { + caseReferenceId: caseId, + isIntermediateOrSelectedTodoCaseItem, + caseStatus, + caseType, + dpdBucket, + dpdCycle, + daysTillDeallocation, + pinRank, + isSynced, + totalOverdueAmount, + escalationData, + } = caseListItemDetailObj; + + const isVisitPlanStatusLocked = useAppSelector( + (state) => state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED + ); + const is1To30FieldAgent = useAppSelector((state) => state.user?.is1To30FieldAgent); + const isTeamLead = useAppSelector((state) => state.user.isTeamLead); + const activeEscalationCount = Number(escalationData?.activeEscalationCount ?? 0); + const pastEscalationCount = Number(escalationData?.pastEscalationCount ?? 0); + const totalEscalationsCount = activeEscalationCount + pastEscalationCount; + + const dispatch = useAppDispatch(); + + const handleAvatarClick = () => { + if (isTodoItem || caseStatus === CaseStatuses.CLOSED) { + return; + } + if (isVisitPlanStatusLocked) { + toast({ + type: 'info', + text1: ToastMessages.CASES_SELECTION_DISABLED, + }); + return; + } + if (pinRank) { + dispatch( + setSelectedTodoListMap({ + pinRank, + caseReferenceId: caseId, + }) + ); + } else { + dispatch( + setPinnedRank({ + caseReferenceId: caseId, + }) + ); + } + }; + + const handleCaseClick = async () => { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_CASE_LIST_CASE_CLICKED, { + caseId, + screen: getCurrentScreen().name === 'Profile' ? 'Completed Cases' : getCurrentScreen().name, // todo: need to update use router + caseType, + }); + if (nearbyCaseView) { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_NEARBY_CASE_CLICKED, { + caseId, + }); + } + navigateToScreen(PageRouteEnum.CASE_DETAIL_STACK, { + screen: CaseDetailStackEnum.COLLECTION_CASE_DETAIL, + params: { caseId }, + }); + }; + + const isCaseSelected = !isTodoItem && !!isIntermediateOrSelectedTodoCaseItem; + const address = + caseListItemDetailObj?.currentTask?.metadata?.addressLine || + caseListItemDetailObj?.addressString; + const customerName = + caseListItemDetailObj.customerInfo?.name || + caseListItemDetailObj.customerInfo?.customerName || + caseListItemDetailObj.customerName; + + const getCaseItemAvatarCaseDetailObj = useMemo( + (): ICaseItemAvatarCaseDetailObj => ({ + isPinned: pinRank ? true : false, + isCaseSynced: isSynced as boolean, + customerName: customerName, + caseId, + documentList: getDocumentList(caseListItemDetailObj) || [], + caseType: caseType, + imageUri: caseListItemDetailObj?.imageUri || '', + }), + [ + caseType, + isSynced, + pinRank, + caseListItemDetailObj?.customerInfo?.documents, + caseListItemDetailObj?.documents, + caseListItemDetailObj?.imageUri, + ] + ); + + const isCaseItemPinnedMainView = getCaseItemAvatarCaseDetailObj.isPinned && allCasesView; + const caseCompleted = COMPLETED_STATUSES.includes(caseStatus); + + const showVisitPlanBtn = + !(caseCompleted || isCaseItemPinnedMainView) && + !isTodoItem && + !isCompleted && + !isTeamLead && + !nearbyCaseView; + + const showInVisitPlanTag = isCaseItemPinnedMainView && !caseCompleted; + + return ( + + + + + + + + {customerName} + + + + + + + {showVisitPlanBtn ? ( + + + + ) : null} + + {showInVisitPlanTag ? : null} + + + + {address ? address : 'Address not available'} + + {is1To30FieldAgent ? ( + + + + + + + + ) : ( + + + + + )} + + + + ); +}; + +const styles = StyleSheet.create({ + listItem: { + backgroundColor: COLORS.BACKGROUND.PRIMARY, + borderRadius: 8, + marginVertical: 10, + position: 'relative', + }, + selectBtn: { + position: 'absolute', + paddingTop: 12, + right: 0, + paddingRight: 12, + width: 80, + height: 80, + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'flex-end', + }, + backgroundBlue: { + backgroundColor: COLORS.BACKGROUND.BLUE, + }, + backgroundSilverLight: { + backgroundColor: COLORS.BACKGROUND.SILVER_LIGHT_3, + }, + backgroundBlueLight: { + backgroundColor: COLORS.BACKGROUND.BLUE_LIGHT_3, + }, +}); + +export default memo(CaseListItem); diff --git a/src/screens/allCases/CaseItem/CaseStatus.tsx b/src/screens/allCases/CaseItem/CaseStatus.tsx new file mode 100644 index 00000000..fff0c925 --- /dev/null +++ b/src/screens/allCases/CaseItem/CaseStatus.tsx @@ -0,0 +1,57 @@ +import { GenericStyles } from '@rn-ui-lib/styles'; +import React from 'react'; +import { View } from 'react-native'; +import { paymentStatusMapping } from '../utils'; +import Tag, { TagVariant } from '@rn-ui-lib/components/Tag'; +import LocationDistanceIcon from '@assets/icons/LocationDistanceIcon'; +import { COLORS } from '@rn-ui-lib/colors'; +import { useAppSelector } from '@hooks'; +import { TABS_KEYS } from '../constants'; +import { ICaseStatus } from '../interface'; + +const CaseStatus = (props: ICaseStatus) => { + const { caseListItemDetailObj, isVisitPlan } = props; + const { collectionTag, paymentStatus, caseReferenceId } = caseListItemDetailObj || {}; + const distanceMapOfNearbyCases = + useAppSelector((state) => state.nearbyCasesSlice.caseReferenceIdToDistanceMap) || {}; + const selectedTab = useAppSelector((state) => state?.nearbyCasesSlice?.sortTabSelected); + + const distanceOfCaseItem = distanceMapOfNearbyCases.get(caseReferenceId); + const isNearestCaseView = selectedTab === TABS_KEYS.NEAREST_CASE; + + return ( + + {paymentStatus ? ( + + + + ) : null} + {collectionTag ? ( + + + + ) : null} + + {!isVisitPlan && distanceOfCaseItem ? ( + + } + style={GenericStyles.pl2} + text={Number(distanceOfCaseItem?.toFixed(1)) + ' KM'} + variant={isNearestCaseView ? TagVariant.darkGray1 : TagVariant.white} + /> + ) : null} + + ); +}; + +export default CaseStatus; diff --git a/src/screens/allCases/CaseItem/FeedbackStatus.tsx b/src/screens/allCases/CaseItem/FeedbackStatus.tsx new file mode 100644 index 00000000..4679b3c3 --- /dev/null +++ b/src/screens/allCases/CaseItem/FeedbackStatus.tsx @@ -0,0 +1,77 @@ +import { COLORS } from '@rn-ui-lib/colors'; +import Text from '@rn-ui-lib/components/Text'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import React from 'react'; +import { StyleSheet } from 'react-native'; +import { View } from 'react-native'; +import { IFeedbackStatus } from '../interface'; +import { feedbackStatusColorMapping } from '../utils'; + +const FeedbackStatus = (props: IFeedbackStatus) => { + const { caseListItemDetailObj } = props; + const { currentMonthFeedbackStatus, lastMonthFeedbackStatus } = caseListItemDetailObj || {}; + + return ( + + + + + + Current month + + + {currentMonthFeedbackStatus?.status + ? currentMonthFeedbackStatus?.status + : 'Not attempted'} + + + + + Last month + + + {lastMonthFeedbackStatus?.status ? lastMonthFeedbackStatus?.status : 'Not attempted'} + + + + + ); +}; + +const styles = StyleSheet.create({ + feedbackStatus: { + lineHeight: 18, + marginTop: 4, + color: COLORS.TEXT.BLACK, + fontWeight: '500', + }, + dashedBorder: { + borderTopWidth: 1, + borderColor: COLORS.BORDER.PRIMARY, + borderStyle: 'dashed', + }, +}); + +export default FeedbackStatus; diff --git a/src/screens/allCases/CaseItem/VisitPlanTag.tsx b/src/screens/allCases/CaseItem/VisitPlanTag.tsx new file mode 100644 index 00000000..c0ea3bdb --- /dev/null +++ b/src/screens/allCases/CaseItem/VisitPlanTag.tsx @@ -0,0 +1,46 @@ +import TagPlaceholderIcon from '@assets/icons/TagPlaceholderIcon'; +import { COLORS } from '@rn-ui-lib/colors'; +import Text from '@rn-ui-lib/components/Text'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import React from 'react'; +import { StyleSheet } from 'react-native'; +import { View } from 'react-native'; + +interface IVisitPlanTag { + totalEscalationsCount: number; +} + +const VisitPlanTag = (props: IVisitPlanTag) => { + const { totalEscalationsCount } = props; + return ( + + + In visit plan + + ); +}; + +const styles = StyleSheet.create({ + visitPlanContainer: { + right: -4, + top: 12, + }, + top42: { + top: 42, + }, + visitPlanText: { + position: 'absolute', + left: 9, + color: COLORS.TEXT.DARK, + fontWeight: '500', + lineHeight: 18 + }, +}); + +export default VisitPlanTag; diff --git a/src/screens/allCases/CasesList.tsx b/src/screens/allCases/CasesList.tsx index f0b0b189..f459cb9d 100644 --- a/src/screens/allCases/CasesList.tsx +++ b/src/screens/allCases/CasesList.tsx @@ -410,7 +410,7 @@ const CasesList: React.FC = ({ {visitPlansUpdating ? ( ) : null} - + {filteredCasesListWithCTA.length > 0 ? ( { + const { escalationData } = props; + const isActiveEscalationCase = Number(escalationData?.activeEscalationCount) > 0; + const activeEscalationCount = Number(escalationData?.activeEscalationCount); + const pastEscalationCount = Number(escalationData?.pastEscalationCount); + const totalEscalationsCount = activeEscalationCount + pastEscalationCount; + + if (!escalationData || totalEscalationsCount === 0) { + return null; + } + + return ( + + {isActiveEscalationCase ? ( + + ) : ( + + )} + + ); +}; + +export default Escalation; diff --git a/src/screens/allCases/Escalation/EscalationItem.tsx b/src/screens/allCases/Escalation/EscalationItem.tsx new file mode 100644 index 00000000..264348e4 --- /dev/null +++ b/src/screens/allCases/Escalation/EscalationItem.tsx @@ -0,0 +1,62 @@ +import FlagIcon from '@assets/icons/FlagIcon'; +import { COLORS } from '@rn-ui-lib/colors'; +import Text from '@rn-ui-lib/components/Text'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import React from 'react'; +import { StyleSheet } from 'react-native'; +import { View } from 'react-native'; + +interface IEscalationItem { + escalationText: string; + isActiveEscalationCase?: boolean; +} + +const EscalationItem = (props: IEscalationItem) => { + const { escalationText, isActiveEscalationCase = false } = props; + return ( + + + + {escalationText} + + + ); +}; + +const styles = StyleSheet.create({ + escalationContainer: { + height: 30, + paddingLeft: 12, + paddingRight: 8, + borderTopLeftRadius: 8, + borderTopRightRadius: 8, + borderWidth: 1, + }, + escalationText: { + marginLeft: 4, + }, + redContainer: { + borderColor: COLORS.BORDER.RED, + backgroundColor: COLORS.BACKGROUND.RED, + }, + yellowContainer: { + borderColor: COLORS.BORDER.YELLOW, + backgroundColor: COLORS.BACKGROUND.YELLOW_LIGHT, + }, +}); + +export default EscalationItem; diff --git a/src/screens/allCases/interface.ts b/src/screens/allCases/interface.ts index 848aebbf..ed99540e 100644 --- a/src/screens/allCases/interface.ts +++ b/src/screens/allCases/interface.ts @@ -355,3 +355,29 @@ export interface IEscalationSummary { activeEscalationCount: number; recentEscalationDetails: IRecentEscalationDetails; } + +export interface ICaseListItem { + caseListItemDetailObj: ICaseItemCaseDetailObj; + isTodoItem?: boolean; + isCompleted?: boolean; + shouldBatchAvatar?: boolean; + allCasesView?: boolean; + isAgentDashboard?: boolean; + nearbyCaseView?: boolean; + isVisitPlan?: boolean; +} + +export interface ICaseDetailKeyValue { + title: string; + value: string; + showDot?: boolean; +} + +export interface ICaseStatus { + caseListItemDetailObj: ICaseItemCaseDetailObj; + isVisitPlan?: boolean; +} + +export interface IFeedbackStatus{ + caseListItemDetailObj: ICaseItemCaseDetailObj; +} \ No newline at end of file diff --git a/src/screens/allCases/utils.ts b/src/screens/allCases/utils.ts index fc1070e6..42e18ce2 100644 --- a/src/screens/allCases/utils.ts +++ b/src/screens/allCases/utils.ts @@ -8,6 +8,7 @@ import { FeedbackStatus, IDocumentItem, INearbyCaseItemObj, + PaymentStatus, } from '../caseDetails/interface'; import { BOTTOM_TAB_ROUTES, @@ -28,6 +29,8 @@ import { logError } from '@components/utlis/errorUtils'; import { setAgentsDocumentsData, setDocumentsData } from '@reducers/documentsSlice'; import { useWindowDimensions } from 'react-native'; import { toast } from '@rn-ui-lib/components/toast'; +import { TagVariant } from '@rn-ui-lib/components/Tag'; +import { COLORS } from '@rn-ui-lib/colors'; export const getAttemptedList = ( filteredCasesList: ICaseItem[], @@ -322,3 +325,22 @@ export const calculateBottomSheetHeight = (rowLength = 0) => { const dynamicHeight = ((rowLength * rowHeight + headerOffset) / SCREEN_HEIGHT) * 100; return Math.min(dynamicHeight, maxHeightPercentage); }; + +export const paymentStatusMapping: Record< + PaymentStatus, + { label: PaymentStatus | string; variant: TagVariant } +> = { + [PaymentStatus.Paid]: { label: PaymentStatus.Paid, variant: TagVariant.success }, + [PaymentStatus['Partially Paid']]: { + label: PaymentStatus['Partially Paid'], + variant: TagVariant.yellow, + }, + [PaymentStatus.Unpaid]: { label: PaymentStatus.Unpaid, variant: TagVariant.alert }, + [PaymentStatus.Closed]: { label: PaymentStatus.Closed, variant: TagVariant.error }, +}; + +export const feedbackStatusColorMapping = { + green: COLORS.TEXT.GREEN, + red: COLORS.TEXT.RED, + gray: COLORS.TEXT.BLACK, +}; diff --git a/src/screens/caseDetails/CollectionCaseDetail.tsx b/src/screens/caseDetails/CollectionCaseDetail.tsx index b5ed21f7..cd59c21e 100644 --- a/src/screens/caseDetails/CollectionCaseDetail.tsx +++ b/src/screens/caseDetails/CollectionCaseDetail.tsx @@ -22,6 +22,7 @@ import { COLORS } from '@rn-ui-lib/colors'; import { syncActiveCallDetails } from '@actions/callRecordingActions'; import { getUngroupedAddress } from '@actions/addressGeolocationAction'; import EscalationsSection from '@screens/escalations/EscalationsSection'; +import TopFeedbacks from './feedback/TopFeedbacks'; interface ICaseDetails { route: { @@ -59,7 +60,6 @@ const CollectionCaseDetails: React.FC = (props) => { useEffect(() => { if (caseId) dispatch(setSelectedCaseId(caseId)); - dispatch(getFeedbackHistory(loanAccountNumber)); return () => { dispatch(setSelectedCaseId('')); }; @@ -130,6 +130,7 @@ const CollectionCaseDetails: React.FC = (props) => { ) : null} + diff --git a/src/screens/caseDetails/feedback/FeedbackDetailContainer.tsx b/src/screens/caseDetails/feedback/FeedbackDetailContainer.tsx index 93804bb8..e6d2dec2 100644 --- a/src/screens/caseDetails/feedback/FeedbackDetailContainer.tsx +++ b/src/screens/caseDetails/feedback/FeedbackDetailContainer.tsx @@ -35,12 +35,12 @@ import Filters, { TFilterOptions } from '../../../components/filters/Filters'; import { _map } from '../../../../RN-UI-LIB/src/utlis/common'; import ChevronDown from '../../../assets/icons/ChevronDown'; import ChevronUp from '../../../assets/icons/ChevronUp'; +import { PAST_FEEDBACK_PAGE_SIZE } from '@screens/caseDetails/feedback/pastFeedbackCommon'; const FEEDBACK_PAGE_TITLE = 'All feedbacks'; const ADDRESS_FEEDBACK_PAGE_TITLE = 'Address feedback'; const SCROLL_LAYOUT_OFFSET = 10; -const FEEDBACK_PER_PAGE = 5; interface IFeedbackDetailContainer { route: { @@ -123,7 +123,7 @@ const FeedbackDetailContainer: React.FC = ({ route: ro { loan_account_number: loanAccountNumber, page_no: currentPage - 1, - page_size: FEEDBACK_PER_PAGE, + page_size: PAST_FEEDBACK_PAGE_SIZE, customerRecahble: false, addressReferenceIds, }, diff --git a/src/screens/caseDetails/feedback/FeedbackListItem.tsx b/src/screens/caseDetails/feedback/FeedbackListItem.tsx index f4c06320..37d82f4c 100644 --- a/src/screens/caseDetails/feedback/FeedbackListItem.tsx +++ b/src/screens/caseDetails/feedback/FeedbackListItem.tsx @@ -16,6 +16,8 @@ import { shareToWhatsapp } from '../../../services/FeedbackWhatsApp'; import { CaseDetailStackEnum } from '../CaseDetailStack'; import { feedbackTypeIcon } from '@screens/caseDetails/feedback/FeedbackDetailItem'; import Tag, { TagVariant } from '@rn-ui-lib/components/Tag'; +import { feedbackStatusColorMapping } from '@screens/allCases/utils'; +import { PAST_FEEDBACK_PAGE_SIZE } from '@screens/caseDetails/feedback/pastFeedbackCommon'; interface IFeedbackListItem { feedbackItem: IFeedback | IUnSyncedFeedbackItem; @@ -41,7 +43,9 @@ const FeedbackListItem: React.FC = ({ }; navigateToScreen(CaseDetailStackEnum.PAST_FEEDBACK_DETAIL, { ...commonParams, - pageNo: isTopFeedbackItem ? (feedbackItem as IFeedback)?.offset : 1, + pageNo: isTopFeedbackItem + ? Math.ceil(((feedbackItem as IFeedback)?.orderOffset ?? 0) / PAST_FEEDBACK_PAGE_SIZE) + : 1, }); }; const [isWhastappSendLoading, setIsWhatsappSendLoading] = useState(false); @@ -98,6 +102,12 @@ const FeedbackListItem: React.FC = ({ style={[ styles.textHeading, feedbackItem.type === FEEDBACK_TYPE.GEN_AI_BOT_FIELD ? styles.capitalized : null, + { + color: + feedbackStatusColorMapping[ + feedbackItem?.color as keyof typeof feedbackStatusColorMapping + ] ?? COLORS.TEXT.BLACK, + }, ]} > {sanitizeString(feedbackItem.interactionStatus)} @@ -116,7 +126,7 @@ const FeedbackListItem: React.FC = ({ ) : null} - {feedbackItem?.type === FEEDBACK_TYPE.FIELD_VISIT ? ( + {feedbackItem?.type === FEEDBACK_TYPE.FIELD_VISIT && !isTopFeedbackItem ? ( { const caseDetail = useAppSelector((state: RootState) => state.allCases.caseDetails[caseId]) || {}; const { loanAccountNumber } = caseDetail || {}; const feedbackList = useAppSelector( - (state: RootState) => state.topFeedbacks?.[loanAccountNumber as string]?.feedbacks || [] + (state: RootState) => state.topFeedbacks?.[caseId as string]?.feedbacks || [] ); const isLoading = useAppSelector( - (state: RootState) => state.topFeedbacks?.[loanAccountNumber as string]?.isLoading || false + (state: RootState) => state.topFeedbacks?.[caseId as string]?.isLoading || false ); useEffect(() => { - dispatch(getTopFeedbacks(loanAccountNumber)); + dispatch(getTopFeedbacks(caseId)); }, []); if (!feedbackList?.length) return null; @@ -60,7 +60,7 @@ const TopFeedbacks = (props: ITopFeedbacks) => { } > {feedbackList?.map((feedbackItem: any, idx: number) => ( diff --git a/src/screens/caseDetails/feedback/pastFeedbackCommon.ts b/src/screens/caseDetails/feedback/pastFeedbackCommon.ts new file mode 100644 index 00000000..9b41d751 --- /dev/null +++ b/src/screens/caseDetails/feedback/pastFeedbackCommon.ts @@ -0,0 +1 @@ +export const PAST_FEEDBACK_PAGE_SIZE = 5; diff --git a/src/screens/caseDetails/interface.ts b/src/screens/caseDetails/interface.ts index bd25fef7..040bb48c 100644 --- a/src/screens/caseDetails/interface.ts +++ b/src/screens/caseDetails/interface.ts @@ -262,6 +262,11 @@ export interface EmploymentDetails { employerName: string; } +export interface FeedbackStatusObj { + status: string; + color: string +} + export interface CaseDetail { id: string; allocationReferenceId?: string; @@ -316,7 +321,9 @@ export interface CaseDetail { employmentDetail?: EmploymentDetails; unpaidDays?: number; addressStringType?: string; - escalationData ?: escalationData; + currentMonthFeedbackStatus?: FeedbackStatusObj; + lastMonthFeedbackStatus?:FeedbackStatusObj; + escalationData ?: EscalationData; daysTillDeallocation: number; } @@ -325,7 +332,7 @@ export interface recentEscalationDetails { customerVoice : string; } -export interface escalationData { +export interface EscalationData { activeEscalationCount : number; pastEscalationCount : number; recentEscalationDetails : recentEscalationDetails; diff --git a/src/types/feedback.types.ts b/src/types/feedback.types.ts index 5fa66fe8..4a597af8 100644 --- a/src/types/feedback.types.ts +++ b/src/types/feedback.types.ts @@ -78,6 +78,7 @@ export interface IFeedback { interactionStatus: InteractionStatuses; tagTitle?: string; offset?: number; + color?: string; } export interface IUnSyncedFeedbackItem { @@ -85,6 +86,7 @@ export interface IUnSyncedFeedbackItem { createdAt: string; isSynced: boolean; type?: FEEDBACK_TYPE; + color?: string; } export const CALLING_FEEDBACKS = [FEEDBACK_TYPE.CALL_BRIDGE, FEEDBACK_TYPE.SELF_CALL];