NTP-28655| Pause Collection Efforts on Cosmos (#1066)
This commit is contained in:
committed by
GitHub
parent
421dd9d242
commit
ca2575c24d
Submodule RN-UI-LIB updated: 7aa1a0d2ae...69b6c0b76f
@@ -13,7 +13,10 @@ import {
|
||||
setUngroupedAddresses,
|
||||
setUngroupedAddressesLoading,
|
||||
} from '@reducers/ungroupedAddressesSlice';
|
||||
import { setSkipTracingAddresses, setSkipTracingAddressesLoading } from '@reducers/skipTracingAddressesSlice';
|
||||
import {
|
||||
setSkipTracingAddresses,
|
||||
setSkipTracingAddressesLoading,
|
||||
} from '@reducers/skipTracingAddressesSlice';
|
||||
import { VisitType } from '@screens/caseDetails/interface';
|
||||
|
||||
export const getAddressesGeolocation =
|
||||
@@ -157,7 +160,7 @@ export const getSkipTracingAddress =
|
||||
includeFeedbacks,
|
||||
caseReferenceId: caseReferenceId,
|
||||
caseBusinessVertical: caseBusinessVertical,
|
||||
requestSourceTypeFilter: VisitType.SKIP_TRACING
|
||||
requestSourceTypeFilter: VisitType.SKIP_TRACING,
|
||||
}
|
||||
);
|
||||
axiosInstance
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { IconProps } from '@rn-ui-lib/icons/types';
|
||||
import React from 'react';
|
||||
import { Path, Svg } from 'react-native-svg';
|
||||
|
||||
const TagPlaceholderIcon = () => {
|
||||
const TagPlaceholderIcon: React.FC<IconProps> = (props) => {
|
||||
const { width = '70', height = '27', fillColor = COLORS.BACKGROUND.LIGHT_BLUE_1, backColor = COLORS.BACKGROUND.LIGHT_BLUE_4 } = props;
|
||||
return (
|
||||
<Svg width="70" height="27" viewBox="0 0 70 27" fill="none">
|
||||
<Svg width={width} height={height} viewBox="0 0 70 27" fill="none">
|
||||
<Path
|
||||
d="M70 2C70 0.895431 69.1046 0 68 0H2C0.895432 0 0 0.895431 0 2V18C0 19.1046 0.895432 20 2 20H70V2Z"
|
||||
fill="#D1E7FF"
|
||||
fill={fillColor}
|
||||
/>
|
||||
<Path d="M66 20L66 26.5L70 20L66 20Z" fill="#A0CBFD" />
|
||||
<Path d="M66 20L66 26.5L70 20L66 20Z" fill={backColor} />
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -57,6 +57,8 @@ export enum PushNotificationTypes {
|
||||
CUSTOMER_TRIED_CALLING_NOTIFICATION = 'CUSTOMER_TRIED_CALLING_NOTIFICATION',
|
||||
ANOMALY_TRACKER_DETECTION='ANOMALY_TRACKER_DETECTION_V2',
|
||||
ANOMALY_TRACKER_RESOLUTION='ANOMALY_TRACKER_RESOLUTION',
|
||||
COLLECTION_PAUSE_EFFORTS_CASE_PAUSED='COLLECTION_PAUSE_EFFORTS_CASE_PAUSED',
|
||||
CASE_UNPAUSED_NOTIFICATION_TEMPLATE = 'CASE_UNPAUSED_NOTIFICATION_TEMPLATE',
|
||||
}
|
||||
|
||||
type NotificationContent = (notification: INotification) => {
|
||||
@@ -456,6 +458,44 @@ const getAnomalyResolvedNotificationContent = (notification: INotification) => {
|
||||
return { title, body, actions, data, defaultPressAction };
|
||||
};
|
||||
|
||||
//Case paused notification content
|
||||
const getCasePausedNotificationContent = (notification: INotification) => {
|
||||
const { params } = notification || {};
|
||||
const { customerName, date } = params || {};
|
||||
const title = '<span style="color: #1C1C1C">Case paused</span>';
|
||||
const body = `<span style="color: #969696">${customerName}'s case has been paused till ${date}</span>`;
|
||||
const actions = [] as AndroidAction[];
|
||||
const defaultPressAction = actionContentMap[NotificationAction.DEFAULT].pressAction;
|
||||
const data = {
|
||||
caseId: notification?.collectionCaseId || '',
|
||||
templateId: notification?.template?.id,
|
||||
templateName: PushNotificationTypes.COLLECTION_PAUSE_EFFORTS_CASE_PAUSED,
|
||||
notificationId: notification?.id,
|
||||
deepLinks: {},
|
||||
}
|
||||
|
||||
return { title, body, actions, data, defaultPressAction };
|
||||
};
|
||||
|
||||
//Case unpaused notification content
|
||||
const getCaseUnPausedNotificationContent = (notification: INotification) => {
|
||||
const { params } = notification || {};
|
||||
const { customerName } = params || {};
|
||||
const title = '<span style="color: #1C1C1C">Case Unpaused</span>';
|
||||
const body = `<span style="color: #969696">${customerName}'s case has been unpaused</span>`;
|
||||
const actions = [] as AndroidAction[];
|
||||
const defaultPressAction = actionContentMap[NotificationAction.DEFAULT].pressAction;
|
||||
const data = {
|
||||
caseId: notification?.collectionCaseId || '',
|
||||
templateId: notification?.template?.id,
|
||||
templateName: PushNotificationTypes.CASE_UNPAUSED_NOTIFICATION_TEMPLATE,
|
||||
notificationId: notification?.id,
|
||||
deepLinks: {},
|
||||
}
|
||||
|
||||
return { title, body, actions, data, defaultPressAction };
|
||||
};
|
||||
|
||||
// Map of notification templates to notification content
|
||||
export const notificationContentMap: Record<string, NotificationContent> = {
|
||||
[PushNotificationTypes.PAYMENT_MADE_TEMPLATE_V2]: getPaymentMadeNotificationContent,
|
||||
@@ -476,6 +516,8 @@ export const notificationContentMap: Record<string, NotificationContent> = {
|
||||
[PushNotificationTypes.CUSTOMER_TRIED_CALLING_NOTIFICATION]: getCustomerTriedCallingNotificationContent,
|
||||
[PushNotificationTypes.ANOMALY_TRACKER_DETECTION]: getAnomalyDetectedNotificationContent,
|
||||
[PushNotificationTypes.ANOMALY_TRACKER_RESOLUTION]: getAnomalyResolvedNotificationContent,
|
||||
[PushNotificationTypes.COLLECTION_PAUSE_EFFORTS_CASE_PAUSED]: getCasePausedNotificationContent,
|
||||
[PushNotificationTypes.CASE_UNPAUSED_NOTIFICATION_TEMPLATE]: getCaseUnPausedNotificationContent,
|
||||
};
|
||||
|
||||
const notificationDismissed = (notificationEvent: Event, isBgHandler = false) => {
|
||||
|
||||
@@ -7,7 +7,12 @@ import { getCurrentScreen, navigateToScreen } from '../../../components/utlis/na
|
||||
import { useAppDispatch, useAppSelector } from '../../../hooks';
|
||||
import { setPinnedRank, setSelectedTodoListMap } from '../../../reducer/allCasesSlice';
|
||||
import CaseItemAvatar from '../CaseItemAvatar';
|
||||
import { CaseStatuses, ICaseItemAvatarCaseDetailObj, ICaseListItem } from '../interface';
|
||||
import {
|
||||
CaseStatuses,
|
||||
CaseStatusUIMapping,
|
||||
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';
|
||||
@@ -19,11 +24,11 @@ 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';
|
||||
import CaseTag from './CaseTag';
|
||||
|
||||
const CaseListItem: React.FC<ICaseListItem> = (props) => {
|
||||
const {
|
||||
@@ -49,6 +54,7 @@ const CaseListItem: React.FC<ICaseListItem> = (props) => {
|
||||
escalationData,
|
||||
} = caseListItemDetailObj;
|
||||
|
||||
const isCasePaused = caseStatus === CaseStatuses.ON_HOLD;
|
||||
const isVisitPlanStatusLocked = useAppSelector(
|
||||
(state) => state.user?.lock?.visitPlanStatus === VisitPlanStatus.LOCKED
|
||||
);
|
||||
@@ -141,10 +147,10 @@ const CaseListItem: React.FC<ICaseListItem> = (props) => {
|
||||
!isTodoItem &&
|
||||
!isCompleted &&
|
||||
!isTeamLead &&
|
||||
!nearbyCaseView;
|
||||
|
||||
const showInVisitPlanTag = isCaseItemPinnedMainView && !caseCompleted;
|
||||
!nearbyCaseView &&
|
||||
!isCasePaused;
|
||||
|
||||
const showInVisitPlanTag = isCaseItemPinnedMainView && !caseCompleted && !isCasePaused;
|
||||
return (
|
||||
<Pressable
|
||||
style={[
|
||||
@@ -160,6 +166,7 @@ const CaseListItem: React.FC<ICaseListItem> = (props) => {
|
||||
GenericStyles.centerAlignedRow,
|
||||
GenericStyles.justifyStart,
|
||||
isCaseSelected ? styles.backgroundBlue : styles.backgroundSilverLight,
|
||||
isCasePaused ? styles.backgroundRed : null,
|
||||
GenericStyles.w100,
|
||||
GenericStyles.p12,
|
||||
!totalEscalationsCount ? GenericStyles.brt8 : {},
|
||||
@@ -185,8 +192,14 @@ const CaseListItem: React.FC<ICaseListItem> = (props) => {
|
||||
</Pressable>
|
||||
) : null}
|
||||
|
||||
{showInVisitPlanTag ? <VisitPlanTag totalEscalationsCount={totalEscalationsCount} /> : null}
|
||||
|
||||
{showInVisitPlanTag ? <CaseTag totalEscalationsCount={totalEscalationsCount} /> : null}
|
||||
{isCasePaused ? (
|
||||
<CaseTag
|
||||
inputLabel={CaseStatusUIMapping.ON_HOLD}
|
||||
isCasePaused={isCasePaused}
|
||||
totalEscalationsCount={totalEscalationsCount}
|
||||
/>
|
||||
) : null}
|
||||
<View style={GenericStyles.p12}>
|
||||
<Text light ellipsizeMode="tail" style={[GenericStyles.fontSize13, GenericStyles.mb16]}>
|
||||
{address ? address : 'Address not available'}
|
||||
@@ -249,6 +262,9 @@ const styles = StyleSheet.create({
|
||||
backgroundBlueLight: {
|
||||
backgroundColor: COLORS.BACKGROUND.BLUE_LIGHT_3,
|
||||
},
|
||||
backgroundRed: {
|
||||
backgroundColor: COLORS.BACKGROUND.RED,
|
||||
},
|
||||
});
|
||||
|
||||
export default memo(CaseListItem);
|
||||
|
||||
@@ -6,12 +6,14 @@ import React from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
|
||||
interface IVisitPlanTag {
|
||||
interface ICaseTag {
|
||||
totalEscalationsCount: number;
|
||||
inputLabel?: string;
|
||||
isCasePaused?: boolean;
|
||||
}
|
||||
|
||||
const VisitPlanTag = (props: IVisitPlanTag) => {
|
||||
const { totalEscalationsCount } = props;
|
||||
const CaseTag = (props: ICaseTag) => {
|
||||
const { totalEscalationsCount, inputLabel = 'In Visit plan', isCasePaused = false } = props;
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
@@ -20,8 +22,20 @@ const VisitPlanTag = (props: IVisitPlanTag) => {
|
||||
totalEscalationsCount ? styles.top42 : {},
|
||||
]}
|
||||
>
|
||||
<TagPlaceholderIcon />
|
||||
<Text style={[GenericStyles.fontSize10, styles.visitPlanText]}>In visit plan</Text>
|
||||
{isCasePaused ? (
|
||||
<TagPlaceholderIcon fillColor={COLORS.TEXT.RED} width={64} backColor={COLORS.BACKGROUND.LIGHT_RED} />
|
||||
) : (
|
||||
<TagPlaceholderIcon />
|
||||
)}
|
||||
<Text
|
||||
style={[
|
||||
GenericStyles.fontSize10,
|
||||
styles.visitPlanText,
|
||||
isCasePaused && styles.casePausedText,
|
||||
]}
|
||||
>
|
||||
{inputLabel}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -39,8 +53,12 @@ const styles = StyleSheet.create({
|
||||
left: 9,
|
||||
color: COLORS.TEXT.DARK,
|
||||
fontWeight: '500',
|
||||
lineHeight: 18
|
||||
lineHeight: 18,
|
||||
},
|
||||
casePausedText: {
|
||||
left: 16,
|
||||
color: COLORS.TEXT.WHITE,
|
||||
},
|
||||
});
|
||||
|
||||
export default VisitPlanTag;
|
||||
export default CaseTag;
|
||||
@@ -185,7 +185,6 @@ const ListItem: React.FC<IListItem> = (props) => {
|
||||
|
||||
const isCaseItemPinnedMainView = getCaseItemAvatarCaseDetailObj.isPinned && allCasesView;
|
||||
const caseCompleted = COMPLETED_STATUSES.includes(caseStatus);
|
||||
|
||||
const showVisitPlanBtn =
|
||||
!(caseCompleted || isCaseItemPinnedMainView) &&
|
||||
!isTodoItem &&
|
||||
|
||||
@@ -75,6 +75,7 @@ export enum CaseStatuses {
|
||||
CLOSED = 'CLOSED', // if any of the task become verif success
|
||||
FORCE_CLOSED = 'FORCE_CLOSED', // backend force closes the task
|
||||
EXPIRED = 'EXPIRED', // unattended case for 30 days.
|
||||
ON_HOLD='ON_HOLD', // case paused
|
||||
}
|
||||
|
||||
export enum CaseStatusUIMapping {
|
||||
@@ -85,6 +86,7 @@ export enum CaseStatusUIMapping {
|
||||
CLOSED = 'Closed',
|
||||
FORCE_CLOSED = 'Force closed',
|
||||
EXPIRED = 'Expired',
|
||||
ON_HOLD = 'Paused'
|
||||
}
|
||||
|
||||
export enum CaseType {
|
||||
@@ -365,6 +367,7 @@ export interface ICaseListItem {
|
||||
isAgentDashboard?: boolean;
|
||||
nearbyCaseView?: boolean;
|
||||
isVisitPlan?: boolean;
|
||||
isCasePaused?: boolean;
|
||||
}
|
||||
|
||||
export interface ICaseDetailKeyValue {
|
||||
|
||||
44
src/screens/caseDetails/CaseDetailPausedNudge.tsx
Normal file
44
src/screens/caseDetails/CaseDetailPausedNudge.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { Text, StyleSheet, View } from 'react-native';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import ErrorExclamationIcon from '@assets/icons/ErrorExclamationIcon';
|
||||
|
||||
interface CaseDetailPausedNudgeProps {
|
||||
casePausedTillDate: string;
|
||||
}
|
||||
|
||||
const CaseDetailPausedNudge: React.FC<CaseDetailPausedNudgeProps> = ({ casePausedTillDate }) => {
|
||||
return (
|
||||
<View style={[GenericStyles.row, GenericStyles.alignCenter, styles.header]}>
|
||||
<ErrorExclamationIcon />
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={styles.headerLabel}>
|
||||
{`Case paused: No collection efforts should be made\ntill ${casePausedTillDate}`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
textContainer: {},
|
||||
header: {
|
||||
backgroundColor: COLORS.BACKGROUND.RED,
|
||||
height: 64,
|
||||
paddingLeft: 20,
|
||||
paddingRight: 40,
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
width: '100%',
|
||||
},
|
||||
headerLabel: {
|
||||
color: COLORS.BACKGROUND.DARK,
|
||||
marginLeft: 16,
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
lineHeight: 20,
|
||||
},
|
||||
});
|
||||
|
||||
export default CaseDetailPausedNudge;
|
||||
@@ -11,13 +11,15 @@ import { RootState } from '@store';
|
||||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { CaseDetailStackEnum } from './CaseDetailStack';
|
||||
import { CaseStatuses } from '@screens/allCases/interface';
|
||||
|
||||
interface ICollectMoneySection {
|
||||
caseId: string;
|
||||
}
|
||||
|
||||
const CollectMoneySection = ({ caseId }: ICollectMoneySection) => {
|
||||
const caseDetail = useAppSelector((state: RootState) => state.allCases?.caseDetails?.[caseId]) || {};
|
||||
const caseDetail =
|
||||
useAppSelector((state: RootState) => state.allCases?.caseDetails?.[caseId]) || {};
|
||||
const { loanAccountNumber } = caseDetail;
|
||||
|
||||
const collectMoneyCta = () => {
|
||||
@@ -28,7 +30,28 @@ const CollectMoneySection = ({ caseId }: ICollectMoneySection) => {
|
||||
caseId,
|
||||
});
|
||||
};
|
||||
|
||||
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
|
||||
if (isCasePaused)
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.whiteBackground,
|
||||
styles.secondSection,
|
||||
getShadowStyle(2),
|
||||
GenericStyles.mt16,
|
||||
styles.paymentLinkTextContainer,
|
||||
]}
|
||||
>
|
||||
<View style={styles.pausedCaseLeftButtonIcon}>
|
||||
<Text style={[styles.collectLeftStyle, GenericStyles.row, GenericStyles.centerAligned]}>
|
||||
₹
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={[GenericStyles.fw500, GenericStyles.centerAligned, GenericStyles.ml4]}>
|
||||
{'Contact your manager for payment link'}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
@@ -109,6 +132,23 @@ export const styles = StyleSheet.create({
|
||||
justifyContent: 'flex-end',
|
||||
paddingRight: 22,
|
||||
},
|
||||
pausedCaseLeftButtonIcon: {
|
||||
backgroundColor: COLORS.TEXT.RED,
|
||||
borderRadius: 100,
|
||||
height: 20,
|
||||
width: 20,
|
||||
marginRight: 6,
|
||||
display: 'flex',
|
||||
alignContent: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
marginLeft: 8,
|
||||
},
|
||||
paymentLinkTextContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
},
|
||||
});
|
||||
|
||||
export default CollectMoneySection;
|
||||
|
||||
@@ -23,6 +23,10 @@ import { syncActiveCallDetails } from '@actions/callRecordingActions';
|
||||
import { getSkipTracingAddress, getUngroupedAddress } from '@actions/addressGeolocationAction';
|
||||
import EscalationsSection from '@screens/escalations/EscalationsSection';
|
||||
import TopFeedbacks from './feedback/TopFeedbacks';
|
||||
import { CaseStatuses } from '@screens/allCases/interface';
|
||||
import { BUSINESS_DATE_FORMAT, ISO_DATE_FORMAT } from '@rn-ui-lib/utils/dates';
|
||||
import dayjs from 'dayjs';
|
||||
import CaseDetailPausedNudge from './CaseDetailPausedNudge';
|
||||
|
||||
interface ICaseDetails {
|
||||
route: {
|
||||
@@ -43,6 +47,8 @@ const CollectionCaseDetails: React.FC<ICaseDetails> = (props) => {
|
||||
const isCallActive = useAppSelector(
|
||||
(state: RootState) => state?.activeCall?.activeCallDetails?.callActive
|
||||
);
|
||||
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
|
||||
const pausedTillDate = caseDetail?.pausedTillDate;
|
||||
const caseBusinessVertical = useAppSelector(
|
||||
(state) => state?.allCases?.caseDetails?.[caseId]?.businessVertical
|
||||
);
|
||||
@@ -55,7 +61,6 @@ const CollectionCaseDetails: React.FC<ICaseDetails> = (props) => {
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
const [isDocumentsLoading, setIsDocumentsLoading] = React.useState(false);
|
||||
|
||||
const { escalationData } = caseDetail || {};
|
||||
const activeEscalationCount = Number(escalationData?.activeEscalationCount);
|
||||
const pastEscalationCount = Number(escalationData?.pastEscalationCount);
|
||||
@@ -68,6 +73,7 @@ const CollectionCaseDetails: React.FC<ICaseDetails> = (props) => {
|
||||
};
|
||||
}, [caseId]);
|
||||
|
||||
const casePausedTillDate = dayjs(pausedTillDate, ISO_DATE_FORMAT).format(BUSINESS_DATE_FORMAT);
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
if (loanAccountNumber) {
|
||||
@@ -118,6 +124,7 @@ const CollectionCaseDetails: React.FC<ICaseDetails> = (props) => {
|
||||
<SafeAreaView style={[GenericStyles.fill, GenericStyles.whiteBackground]}>
|
||||
<ScrollView style={styles.contentContainer} stickyHeaderIndices={[0]}>
|
||||
<CaseDetailsHeader caseDetail={caseDetail} />
|
||||
{isCasePaused ? <CaseDetailPausedNudge casePausedTillDate={casePausedTillDate} /> : null}
|
||||
<UserDetailsSection caseDetail={caseDetail} isDocumentsLoading={isDocumentsLoading} />
|
||||
<Animated.View
|
||||
style={[
|
||||
|
||||
@@ -7,7 +7,7 @@ import Button from '@rn-ui-lib/components/Button';
|
||||
import ArrowSolidIcon from '@rn-ui-lib/icons/ArrowSolidIcon';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import { MILLISECONDS_IN_A_MINUTE, _map } from '@rn-ui-lib/utils/common';
|
||||
import { CaseAllocationType, TaskTitleUIMapping } from '@screens/allCases/interface';
|
||||
import { CaseAllocationType, CaseStatuses, TaskTitleUIMapping } from '@screens/allCases/interface';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { RootState } from '@store';
|
||||
import React, { useEffect } from 'react';
|
||||
@@ -42,7 +42,7 @@ const CollectionCaseDetailFooter = ({ caseId, notificationId }: ICollectionCaseD
|
||||
caseId,
|
||||
});
|
||||
};
|
||||
|
||||
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
|
||||
const handleAddFeedback = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_CASE_DETAILS_ADD_FEEDBACK_CLICKED, {
|
||||
caseId: caseId,
|
||||
@@ -111,9 +111,10 @@ const CollectionCaseDetailFooter = ({ caseId, notificationId }: ICollectionCaseD
|
||||
variant="secondary"
|
||||
onPress={handleCustomerCall}
|
||||
testID={'test_call_customer'}
|
||||
disabled={isCasePaused}
|
||||
/>
|
||||
<Button
|
||||
style={[addingNewFeedbackDisabled ? styles.disabledButton : styles.feedbackButton]}
|
||||
style={[(addingNewFeedbackDisabled && !isCasePaused) ? styles.disabledButton : styles.feedbackButton]}
|
||||
title={preFilledFormData ? 'Continue feedback' : 'Add new feedback'}
|
||||
rightIcon={
|
||||
preFilledFormData ? (
|
||||
@@ -127,6 +128,7 @@ const CollectionCaseDetailFooter = ({ caseId, notificationId }: ICollectionCaseD
|
||||
testID="test_add_feedback"
|
||||
pressableWidthChange={!addingNewFeedbackDisabled}
|
||||
opacityChangeOnPress={!addingNewFeedbackDisabled}
|
||||
disabled={isCasePaused}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,11 @@ import DocumentDataItem from './DocumentDataItem';
|
||||
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
|
||||
import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader';
|
||||
import { IDocumentItem } from './interface';
|
||||
import { RootState } from '@store';
|
||||
import { useAppSelector } from '@hooks';
|
||||
import { CaseStatuses } from '@screens/allCases/interface';
|
||||
import { NAVI_TAB_NAME } from './constants';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
interface IDocumentItems {
|
||||
lan: string;
|
||||
@@ -22,6 +27,16 @@ const DocumentItems: React.FC<IDocumentItems> = ({
|
||||
isDocumentsLoading,
|
||||
scrollByOffset,
|
||||
}) => {
|
||||
const caseDetail =
|
||||
useAppSelector((state: RootState) => state.allCases?.caseDetails?.[caseId]) || {};
|
||||
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
|
||||
|
||||
const updatedDocumentsList = useMemo(() => {
|
||||
if (isCasePaused) {
|
||||
return documentsList.filter((document) => document?.documentCategory === NAVI_TAB_NAME);
|
||||
}
|
||||
return documentsList;
|
||||
}, [isCasePaused, documentsList]);
|
||||
return (
|
||||
<>
|
||||
<View style={[GenericStyles.ml12, GenericStyles.mr8, GenericStyles.mt8]}>
|
||||
@@ -40,7 +55,7 @@ const DocumentItems: React.FC<IDocumentItems> = ({
|
||||
</>
|
||||
}
|
||||
>
|
||||
{documentsList?.map((document: IDocumentItem, index: number) => {
|
||||
{updatedDocumentsList?.map((document: IDocumentItem, index: number) => {
|
||||
return selectedTab === document.documentCategory ? (
|
||||
<DocumentDataItem
|
||||
lan={lan}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import CustomTabs from '@rn-ui-lib/components/customTabs/CustomTabs';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import store from '@store';
|
||||
import { useEffect, useState } from 'react';
|
||||
import store, { RootState } from '@store';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import DocumentItems from './DocumentItems';
|
||||
import { IDocument, IDocumentItem, Tab } from './interface';
|
||||
import { useAppSelector } from '@hooks';
|
||||
@@ -9,8 +9,8 @@ import { ActivityIndicator, StyleSheet } from 'react-native';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
import { AGENT_TAB_KEY, AGENT_TAB_NAME } from './constants';
|
||||
|
||||
import { AGENT_TAB_KEY, AGENT_TAB_NAME, NAVI_TAB_NAME } from './constants';
|
||||
import { CaseStatuses } from '@screens/allCases/interface';
|
||||
|
||||
interface IDocumentTabs {
|
||||
lan: string;
|
||||
@@ -19,16 +19,21 @@ interface IDocumentTabs {
|
||||
scrollByOffset: (val: number) => void;
|
||||
}
|
||||
|
||||
|
||||
const DocumentTabs: React.FC<IDocumentTabs> = ({ lan, caseId, isDocumentsLoading, scrollByOffset }) => {
|
||||
const DocumentTabs: React.FC<IDocumentTabs> = ({
|
||||
lan,
|
||||
caseId,
|
||||
isDocumentsLoading,
|
||||
scrollByOffset,
|
||||
}) => {
|
||||
const documentsData = useAppSelector((state) => state?.documentsSlice?.documentsData);
|
||||
const data = documentsData?.[lan];
|
||||
const agentsData = useAppSelector((state) => state?.documentsSlice?.agentDocumentsData);
|
||||
|
||||
|
||||
const [documentTabs, setDocumentTabs] = useState<Tab[]>([]);
|
||||
const [documentsList, setDocumentsList] = useState<IDocumentItem[]>([]);
|
||||
const [selectedTab, setSelectedTab] = useState<string>(documentTabs?.[0]?.key || '');
|
||||
|
||||
const caseDetail =
|
||||
useAppSelector((state: RootState) => state.allCases?.caseDetails?.[caseId]) || {};
|
||||
useEffect(() => {
|
||||
const docTabs: Tab[] = [];
|
||||
const docsList: IDocumentItem[] = [];
|
||||
@@ -63,48 +68,47 @@ const DocumentTabs: React.FC<IDocumentTabs> = ({ lan, caseId, isDocumentsLoading
|
||||
}
|
||||
setDocumentsList(docsList);
|
||||
}, [data, agentsData]);
|
||||
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedTab(documentTabs?.[0]?.key || '');
|
||||
}, [documentTabs]);
|
||||
|
||||
const handleTabChange = (tabKey: string) => {
|
||||
addClickstreamEvent(
|
||||
CLICKSTREAM_EVENT_NAMES.FA_DOCUMENT_TAB_CLICKED,
|
||||
{
|
||||
lan: lan,
|
||||
caseId: caseId,
|
||||
tabName: tabKey,
|
||||
}
|
||||
);
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_DOCUMENT_TAB_CLICKED, {
|
||||
lan: lan,
|
||||
caseId: caseId,
|
||||
tabName: tabKey,
|
||||
});
|
||||
setSelectedTab(tabKey);
|
||||
};
|
||||
|
||||
return (
|
||||
isDocumentsLoading ? (
|
||||
<ActivityIndicator
|
||||
size={'large'}
|
||||
style={styles.loader}
|
||||
color={COLORS.BACKGROUND.DARK_BLUE}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<CustomTabs
|
||||
tabs={documentTabs}
|
||||
currentTab={selectedTab}
|
||||
onTabChange={handleTabChange}
|
||||
containerStyle={[GenericStyles.ml20]}
|
||||
/>
|
||||
<DocumentItems
|
||||
lan={lan}
|
||||
caseId={caseId}
|
||||
documentsList={documentsList}
|
||||
selectedTab={selectedTab}
|
||||
isDocumentsLoading={isDocumentsLoading}
|
||||
scrollByOffset={scrollByOffset}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
const updatedDocumentsTab = useMemo(() => {
|
||||
if (isCasePaused) {
|
||||
return documentTabs.filter((tab) => tab.key === NAVI_TAB_NAME);
|
||||
}
|
||||
return documentTabs;
|
||||
}, [isCasePaused, documentTabs]);
|
||||
|
||||
return isDocumentsLoading ? (
|
||||
<ActivityIndicator size={'large'} style={styles.loader} color={COLORS.BACKGROUND.DARK_BLUE} />
|
||||
) : (
|
||||
<>
|
||||
<CustomTabs
|
||||
tabs={updatedDocumentsTab}
|
||||
currentTab={selectedTab}
|
||||
onTabChange={handleTabChange}
|
||||
containerStyle={[GenericStyles.ml20]}
|
||||
/>
|
||||
<DocumentItems
|
||||
lan={lan}
|
||||
caseId={caseId}
|
||||
documentsList={documentsList}
|
||||
selectedTab={selectedTab}
|
||||
isDocumentsLoading={isDocumentsLoading}
|
||||
scrollByOffset={scrollByOffset}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -113,5 +117,5 @@ export default DocumentTabs;
|
||||
const styles = StyleSheet.create({
|
||||
loader: {
|
||||
paddingVertical: 12,
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import { COLORS } from '@rn-ui-lib/colors';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import { GenericStyles, getShadowStyle } from '@rn-ui-lib/styles';
|
||||
import { _map } from '@rn-ui-lib/utils/common';
|
||||
import { InteractionStatuses } from '@screens/allCases/interface';
|
||||
import { CaseStatuses, InteractionStatuses } from '@screens/allCases/interface';
|
||||
import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { RootState } from '@store';
|
||||
import React from 'react';
|
||||
@@ -59,6 +59,7 @@ const FeedbackDetailsSection = ({ caseId }: IFeedbackDetailsSection) => {
|
||||
});
|
||||
return notSyncedCases;
|
||||
};
|
||||
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
|
||||
return (
|
||||
<View style={[GenericStyles.pb24, GenericStyles.w100]}>
|
||||
<View
|
||||
@@ -74,7 +75,8 @@ const FeedbackDetailsSection = ({ caseId }: IFeedbackDetailsSection) => {
|
||||
<Pressable
|
||||
onTouchStart={touchStartHandler}
|
||||
onPress={openAllFeedbacksHandler}
|
||||
style={({ pressed }) => [{ opacity: pressed ? 0.7 : 1 }]}
|
||||
style={({ pressed }) => [{ opacity: isCasePaused ? 0.5 : pressed ? 0.7 : 1 }]}
|
||||
disabled={isCasePaused}
|
||||
>
|
||||
<Text style={[styles.textContainer, styles.feedbackBtn]}>View all</Text>
|
||||
</Pressable>
|
||||
|
||||
@@ -15,7 +15,7 @@ import { CaseDetail, DOCUMENT_TYPE } from './interface';
|
||||
import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '../../common/Constants';
|
||||
import { navigateToScreen } from '../../components/utlis/navigationUtlis';
|
||||
import { ICaseItemAvatarCaseDetailObj } from '../allCases/interface';
|
||||
import { CaseStatuses, ICaseItemAvatarCaseDetailObj } from '../allCases/interface';
|
||||
import CachedImage from '../../common/CachedImage';
|
||||
import useFetchDocument from '../../hooks/useFetchDocument';
|
||||
|
||||
@@ -79,7 +79,7 @@ const UserDetailsSection: React.FC<IUserDetailsSection> = (props) => {
|
||||
const getImageURI = () => {
|
||||
return findDocumentByDocumentType(getDocumentList(caseDetail), DOCUMENT_TYPE.SELFIE)?.uri || '';
|
||||
};
|
||||
|
||||
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
|
||||
const { setRetryForUnsignedDocuments } = useFetchDocument(
|
||||
{
|
||||
caseId: caseDetail?.id,
|
||||
@@ -100,7 +100,7 @@ const UserDetailsSection: React.FC<IUserDetailsSection> = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.contentContainer}>
|
||||
<View style={[styles.contentContainer, isCasePaused? GenericStyles.pt16: null]}>
|
||||
<CachedImage
|
||||
highQualityUri={getImageURI()}
|
||||
cacheFileKey={caseDetail.id}
|
||||
|
||||
@@ -12,6 +12,7 @@ import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { CaseDetailStackEnum } from './CaseDetailStack';
|
||||
import { AddressGeolocationTabEnum, AddressTabType } from '@screens/addressGeolocation/constant';
|
||||
import { CaseStatuses } from '@screens/allCases/interface';
|
||||
|
||||
interface IViewAddressSection {
|
||||
caseId: string;
|
||||
@@ -27,7 +28,7 @@ const ViewAddressSection = ({ caseId }: IViewAddressSection) => {
|
||||
}
|
||||
return AddressGeolocationTabEnum.ADDRESS;
|
||||
};
|
||||
|
||||
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
|
||||
const viewAllAddressHandler = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VIEW_ADDRESSES_CLICKED, {
|
||||
lan: getLoanAccountNumber(caseDetail),
|
||||
@@ -67,6 +68,7 @@ const ViewAddressSection = ({ caseId }: IViewAddressSection) => {
|
||||
underlayColor="transparent"
|
||||
pressableWidthChange={false}
|
||||
opacityChangeOnPress={true}
|
||||
disabled={isCasePaused}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -49,4 +49,5 @@ export enum NAVI_ACCOUNT_SHARE_CHANNELS{
|
||||
}
|
||||
|
||||
export const AGENT_TAB_KEY = 'AGENT';
|
||||
export const AGENT_TAB_NAME = 'Agent';
|
||||
export const AGENT_TAB_NAME = 'Agent';
|
||||
export const NAVI_TAB_NAME = 'NAVI';
|
||||
@@ -22,6 +22,8 @@ import { feedbackTypeIcon } from '@screens/caseDetails/feedback/FeedbackDetailIt
|
||||
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';
|
||||
import { CaseStatuses } from '@screens/allCases/interface';
|
||||
import store from '@store';
|
||||
|
||||
interface IFeedbackListItem {
|
||||
feedbackItem: IFeedback | IUnSyncedFeedbackItem;
|
||||
@@ -58,7 +60,8 @@ const FeedbackListItem: React.FC<IFeedbackListItem> = ({
|
||||
caseDetails: state?.allCases?.caseDetails?.[caseId] || null,
|
||||
}));
|
||||
const throttledSendToWhatsapp = React.useRef(debounce(shareToWhatsapp, 500));
|
||||
|
||||
const caseDetail = store?.getState()?.allCases?.caseDetails?.[caseId] || {};
|
||||
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
|
||||
return (
|
||||
<View style={[!isTopFeedbackItem ? GenericStyles.ph16 : {}]}>
|
||||
<TouchableOpacity
|
||||
@@ -68,6 +71,7 @@ const FeedbackListItem: React.FC<IFeedbackListItem> = ({
|
||||
GenericStyles.pv16,
|
||||
isTopFeedbackItem ? styles.topFeedbackItem : styles.feedbackItem,
|
||||
]}
|
||||
disabled={isCasePaused}
|
||||
>
|
||||
{isTopFeedbackItem ? (
|
||||
<View style={[GenericStyles.absolute]}>
|
||||
@@ -124,13 +128,13 @@ const FeedbackListItem: React.FC<IFeedbackListItem> = ({
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
{!(feedbackItem.isSynced === false || !(feedbackItem as IFeedback)?.referenceId) ? (
|
||||
{!(feedbackItem.isSynced === false || !(feedbackItem as IFeedback)?.referenceId) && !isCasePaused ? (
|
||||
<View style={GenericStyles.alignCenter}>
|
||||
<RightChevronIcon />
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
{feedbackItem?.type === FEEDBACK_TYPE.FIELD_VISIT && !isTopFeedbackItem ? (
|
||||
{feedbackItem?.type === FEEDBACK_TYPE.FIELD_VISIT && !isTopFeedbackItem && !isCasePaused ? (
|
||||
<TouchableOpacity
|
||||
style={styles.ShareButton}
|
||||
hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
|
||||
@@ -144,7 +148,7 @@ const FeedbackListItem: React.FC<IFeedbackListItem> = ({
|
||||
setIsWhatsappSendLoading
|
||||
);
|
||||
}}
|
||||
disabled={isWhastappSendLoading}
|
||||
disabled={isWhastappSendLoading || isCasePaused}
|
||||
>
|
||||
<IconLabel
|
||||
text="Share"
|
||||
|
||||
@@ -327,6 +327,7 @@ export interface CaseDetail {
|
||||
escalationData ?: EscalationData;
|
||||
daysTillDeallocation: number;
|
||||
businessVertical: string;
|
||||
pausedTillDate?: string;
|
||||
}
|
||||
|
||||
export interface recentEscalationDetails {
|
||||
|
||||
@@ -24,12 +24,7 @@ interface UpdateCasesProps {
|
||||
}
|
||||
|
||||
export const updateCases = (props: UpdateCasesProps) => (dispatch: AppDispatch) => {
|
||||
const {
|
||||
selectedAgent,
|
||||
isVisitPlanLocked = false,
|
||||
querySnapshot,
|
||||
caseUpdateList,
|
||||
} = props;
|
||||
const { selectedAgent, isVisitPlanLocked = false, querySnapshot, caseUpdateList } = props;
|
||||
const caseDetails = store?.getState()?.allCases?.caseDetails || {};
|
||||
const casesList = store?.getState()?.allCases?.casesList || [];
|
||||
const loading = store?.getState()?.allCases?.loading || false;
|
||||
|
||||
@@ -63,6 +63,7 @@ export interface INotification {
|
||||
callTime?: number;
|
||||
anomalyId?: number;
|
||||
anomalySubType?: string;
|
||||
pauseTillDate?: string;
|
||||
};
|
||||
template: {
|
||||
id: number;
|
||||
|
||||
@@ -45,6 +45,7 @@ const NotificationTemplate: React.FC<INotificationTemplateProps> = ({ data }) =>
|
||||
todaysPtpCustomerCount,
|
||||
callTime,
|
||||
anomalySubType,
|
||||
pauseTillDate
|
||||
} = params || {};
|
||||
|
||||
switch (templateName) {
|
||||
@@ -396,6 +397,26 @@ const NotificationTemplate: React.FC<INotificationTemplateProps> = ({ data }) =>
|
||||
<Text light>{anomalySubType?.replace(/\[.*?\]/g, '')}</Text>
|
||||
</View>
|
||||
);
|
||||
case NotificationTypes.COLLECTION_PAUSE_EFFORTS_CASE_PAUSED:
|
||||
return (
|
||||
<Text>
|
||||
<Text light>case has been </Text>
|
||||
<Text bold dark>
|
||||
paused
|
||||
</Text>
|
||||
<Text light> till </Text>
|
||||
{pauseTillDate}
|
||||
</Text>
|
||||
);
|
||||
case NotificationTypes.CASE_UNPAUSED_NOTIFICATION_TEMPLATE:
|
||||
return (
|
||||
<Text>
|
||||
<Text light>case has been </Text>
|
||||
<Text bold dark>
|
||||
unpaused
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
default:
|
||||
return <Text>New notification </Text>;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ export enum NotificationTypes {
|
||||
CASE_ESCALATION_NOTIFICATION_TEMPLATE = 'CASE_ESCALATION_NOTIFICATION_TEMPLATE',
|
||||
ANOMALY_TRACKER_DETECTION = 'ANOMALY_TRACKER_DETECTION_V2',
|
||||
ANOMALY_TRACKER_RESOLUTION = 'ANOMALY_TRACKER_RESOLUTION',
|
||||
COLLECTION_PAUSE_EFFORTS_CASE_PAUSED = 'COLLECTION_PAUSE_EFFORTS_CASE_PAUSED',
|
||||
CASE_UNPAUSED_NOTIFICATION_TEMPLATE = 'CASE_UNPAUSED_NOTIFICATION_TEMPLATE'
|
||||
}
|
||||
|
||||
export const NotificationIconsMap = {
|
||||
@@ -131,6 +133,8 @@ export const NotificationIconsMap = {
|
||||
strokeColor={COLORS.BORDER.DARK_GREEN_1}
|
||||
/>
|
||||
),
|
||||
[NotificationTypes.CASE_UNPAUSED_NOTIFICATION_TEMPLATE]: <PaymentSuccessIcon />,
|
||||
[NotificationTypes.COLLECTION_PAUSE_EFFORTS_CASE_PAUSED]: <PaymentFailedIcon />,
|
||||
};
|
||||
|
||||
export enum WidgetStatus {
|
||||
|
||||
Reference in New Issue
Block a user