TP-77180 | Self serve UI for pausing collections (#1119)
* TP-77180 | Self serve UI for pausing collections * TP-77180 | Self serve UI for pausing collections * TP-77180 | Self serve UI for pausing collections * TP-77180 | Self serve UI for pausing collections * TP-77180 | Self serve UI for pausing collections * TP-77180 | Hover button color change fix * TP-77180 | Hover button color change fix * TP-77180 | Hover button color change fix * TP-77180 | UAT FIxes * TP-77180 | UAT FIxes 1 * TP-77180 | UAT FIxes 1 * TP-77180 | UAT FIxes 2 * TP-77180 | ready for PR review * TP-77180 | prettier * TP-77180 | Merge to master fix * TP-77180 | Merge to master fix * TP-77180 | lint fix * TP-77180 | self serve UI ready for PR review * TP-77180 | self serve UI ready for PR review * TP-77180 | pr review fix v1 * TP-77180 | pr review fix v2 * TP-77180 | added feature flag check * TP-77180 | added feature flag check * TP-77180 | PR review fixes * TP-77180 | submodule update
This commit is contained in:
23
src/assets/icons/PauseIcon.tsx
Normal file
23
src/assets/icons/PauseIcon.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { IconProps } from './types';
|
||||
|
||||
const PauseIcon = (props: IconProps) => {
|
||||
const { width = '21', height = '20' } = props;
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 21 20"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.16667 15.8327C8.08333 15.8327 8.83333 15.0827 8.83333 14.166V5.83268C8.83333 4.91602 8.08333 4.16602 7.16667 4.16602C6.25 4.16602 5.5 4.91602 5.5 5.83268V14.166C5.5 15.0827 6.25 15.8327 7.16667 15.8327ZM12.1667 5.83268V14.166C12.1667 15.0827 12.9167 15.8327 13.8333 15.8327C14.75 15.8327 15.5 15.0827 15.5 14.166V5.83268C15.5 4.91602 14.75 4.16602 13.8333 4.16602C12.9167 4.16602 12.1667 4.91602 12.1667 5.83268Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PauseIcon;
|
||||
27
src/assets/images/icons/InfoNotification.tsx
Normal file
27
src/assets/images/icons/InfoNotification.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { IconProps } from '../../icons/types';
|
||||
|
||||
const InfoNotification = (props: IconProps) => {
|
||||
const { width = 36, height = 36 } = props;
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="36" height="36" rx="18" fill="#E6F1FF" />
|
||||
<mask id="mask0_2225_23276" maskUnits="userSpaceOnUse" x="8" y="8" width="20" height="20">
|
||||
<rect x="8" y="8" width="20" height="20" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_2225_23276)">
|
||||
<path
|
||||
d="M17.9998 22.166C18.2359 22.166 18.434 22.086 18.594 21.926C18.7534 21.7666 18.8332 21.5688 18.8332 21.3327V17.9785C18.8332 17.7424 18.7534 17.548 18.594 17.3952C18.434 17.2424 18.2359 17.166 17.9998 17.166C17.7637 17.166 17.5659 17.2457 17.4065 17.4052C17.2465 17.5652 17.1665 17.7632 17.1665 17.9993V21.3535C17.1665 21.5896 17.2465 21.7841 17.4065 21.9368C17.5659 22.0896 17.7637 22.166 17.9998 22.166ZM17.9998 15.4993C18.2359 15.4993 18.434 15.4193 18.594 15.2593C18.7534 15.0999 18.8332 14.9021 18.8332 14.666C18.8332 14.4299 18.7534 14.2318 18.594 14.0718C18.434 13.9124 18.2359 13.8327 17.9998 13.8327C17.7637 13.8327 17.5659 13.9124 17.4065 14.0718C17.2465 14.2318 17.1665 14.4299 17.1665 14.666C17.1665 14.9021 17.2465 15.0999 17.4065 15.2593C17.5659 15.4193 17.7637 15.4993 17.9998 15.4993ZM17.9998 26.3327C16.8471 26.3327 15.7637 26.1138 14.7498 25.676C13.7359 25.2388 12.854 24.6452 12.104 23.8952C11.354 23.1452 10.7604 22.2632 10.3232 21.2493C9.88539 20.2355 9.6665 19.1521 9.6665 17.9993C9.6665 16.8466 9.88539 15.7632 10.3232 14.7493C10.7604 13.7355 11.354 12.8535 12.104 12.1035C12.854 11.3535 13.7359 10.7596 14.7498 10.3218C15.7637 9.88463 16.8471 9.66602 17.9998 9.66602C19.1526 9.66602 20.2359 9.88463 21.2498 10.3218C22.2637 10.7596 23.1457 11.3535 23.8957 12.1035C24.6457 12.8535 25.2393 13.7355 25.6765 14.7493C26.1143 15.7632 26.3332 16.8466 26.3332 17.9993C26.3332 19.1521 26.1143 20.2355 25.6765 21.2493C25.2393 22.2632 24.6457 23.1452 23.8957 23.8952C23.1457 24.6452 22.2637 25.2388 21.2498 25.676C20.2359 26.1138 19.1526 26.3327 17.9998 26.3327Z"
|
||||
fill="#0276FE"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoNotification;
|
||||
@@ -1,13 +1,14 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styles from './index.module.scss';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { CalendarIcon } from '@navi/web-ui/lib/icons';
|
||||
import { Typography } from '@navi/web-ui/lib/primitives';
|
||||
import { DATE_TIME_TYPE, DateTimeFormat } from './constants';
|
||||
import cx from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
import { DATE_TIME_TYPE } from './constants';
|
||||
import useOutsideClick from '@hooks/useOutsideClick';
|
||||
import ClockIcon from '@icons/ClockIcon';
|
||||
import DateTimePickerLabel from '@cp/components/DateTimePicker/DateTimePickerLabel';
|
||||
import styles from './index.module.scss';
|
||||
import cx from 'classnames';
|
||||
import FooterContainer from './Footer';
|
||||
import HeadingContainer from './Heading';
|
||||
|
||||
interface DatePickerProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
heading?: string;
|
||||
@@ -18,6 +19,7 @@ interface DatePickerProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
inputClasses?: string;
|
||||
iconColor?: string;
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
footer?: string;
|
||||
}
|
||||
|
||||
const DateTimePickerComponent: React.FC<DatePickerProps> = ({
|
||||
@@ -31,6 +33,7 @@ const DateTimePickerComponent: React.FC<DatePickerProps> = ({
|
||||
iconColor,
|
||||
onChange,
|
||||
disabled = false,
|
||||
footer,
|
||||
...restProps
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -79,11 +82,7 @@ const DateTimePickerComponent: React.FC<DatePickerProps> = ({
|
||||
className={cx(styles.datePickerInput, className, { [styles.disabled]: disabled })}
|
||||
ref={datePickerContainerRef}
|
||||
>
|
||||
{heading ? (
|
||||
<Typography variant={'p4'} className={styles.headerText}>
|
||||
{heading}
|
||||
</Typography>
|
||||
) : null}
|
||||
{heading ? <HeadingContainer heading={heading} /> : null}
|
||||
|
||||
<div
|
||||
className={cx(styles.datePickerContainer, containerClasses, {
|
||||
@@ -126,6 +125,7 @@ const DateTimePickerComponent: React.FC<DatePickerProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{footer ? <FooterContainer footer={footer} /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
17
src/components/DateTimePicker/Footer.tsx
Normal file
17
src/components/DateTimePicker/Footer.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Typography } from '@navi/web-ui/lib/primitives';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
type FooterContainerProps = {
|
||||
footer: string;
|
||||
};
|
||||
|
||||
const FooterContainer = (props: FooterContainerProps) => {
|
||||
const { footer } = props;
|
||||
return (
|
||||
<Typography variant={'p4'} className={styles.footerText}>
|
||||
{footer}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
export default FooterContainer;
|
||||
17
src/components/DateTimePicker/Heading.tsx
Normal file
17
src/components/DateTimePicker/Heading.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Typography } from '@navi/web-ui/lib/primitives';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
type HeadingContainerProps = {
|
||||
heading: string;
|
||||
};
|
||||
|
||||
const HeadingContainer = (props: HeadingContainerProps) => {
|
||||
const { heading } = props;
|
||||
return (
|
||||
<Typography variant={'p4'} className={styles.headerText}>
|
||||
{heading}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeadingContainer;
|
||||
@@ -49,4 +49,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footerText {
|
||||
margin-top: 8px;
|
||||
color: var(--navi-color-gray-c3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ const NotificationList: React.FC<NotificationListProps> = ({
|
||||
if (
|
||||
[
|
||||
TemplateTypes.CASE_ALLOCATED_NOTIFICATION_TEMPLATE,
|
||||
TemplateTypes.CASE_DE_ALLOCATED_NOTIFICATION_TEMPLATE
|
||||
TemplateTypes.CASE_DE_ALLOCATED_NOTIFICATION_TEMPLATE,
|
||||
TemplateTypes.COLLECTION_PAUSE_EFFORTS_CASE_DEALLOCATED
|
||||
].includes(templateName)
|
||||
) {
|
||||
navigate(APP_ROUTES.CASES.path);
|
||||
|
||||
@@ -13,6 +13,7 @@ import AmeyoCallNotPickedIcon from 'src/assets/images/icons/AmyoCallNotPickedIco
|
||||
import RaiseRequestIcon from '@cp/src/assets/icons/RaiseRequestIcon';
|
||||
import ReceiveRequestIcon from '@cp/src/assets/icons/ReceiveRequestIcon';
|
||||
import FeeReappliedIcon from '@cp/src/assets/icons/FeeReappliedIcon';
|
||||
import InfoNotification from '@cp/src/assets/images/icons/InfoNotification';
|
||||
|
||||
export enum NotificationTypes {
|
||||
LONGHORN_NOTIFICATION = 'LONGHORN_NOTIFICATION',
|
||||
@@ -59,7 +60,8 @@ export const TemplateTypes = {
|
||||
'REVISIT_GEN_AI_BOT_SCHEDULED_NOTIFICATION_TEMPLATE',
|
||||
SUPPORT_REQUEST_RECEIVED: 'SUPPORT_REQUEST_RECEIVED',
|
||||
SUPPORT_REQUEST_RESOLVED: 'SUPPORT_REQUEST_RESOLVED',
|
||||
MESSAGE_WAIVE_UNHOLD_NOTIFICATION_TEMPLATE: 'MESSAGE_WAIVE_UNHOLD_NOTIFICATION_TEMPLATE'
|
||||
MESSAGE_WAIVE_UNHOLD_NOTIFICATION_TEMPLATE: 'MESSAGE_WAIVE_UNHOLD_NOTIFICATION_TEMPLATE',
|
||||
COLLECTION_PAUSE_EFFORTS_CASE_DEALLOCATED: 'COLLECTION_PAUSE_EFFORTS_CASE_DEALLOCATED'
|
||||
};
|
||||
|
||||
export const PaymentSuccessTemplateTypes = [
|
||||
@@ -108,6 +110,7 @@ export const NotificationTabTemplateMap = {
|
||||
TemplateTypes.LONGHORN_ALERTS_NEW_ENQUIRY,
|
||||
TemplateTypes.CASE_ALLOCATED_NOTIFICATION_TEMPLATE,
|
||||
TemplateTypes.CASE_DE_ALLOCATED_NOTIFICATION_TEMPLATE,
|
||||
TemplateTypes.COLLECTION_PAUSE_EFFORTS_CASE_DEALLOCATED,
|
||||
TemplateTypes.BUSY_SCHEDULED_NOTIFICATION_TEMPLATE,
|
||||
TemplateTypes.REVISIT_SCHEDULED_NOTIFICATION_TEMPLATE,
|
||||
TemplateTypes.FIELD_BOT_REVISIT_SCHEDULED_NOTIFICATION_TEMPLATE,
|
||||
@@ -128,6 +131,7 @@ export const NotificationTabTemplateMap = {
|
||||
TemplateTypes.LONGHORN_ALERTS_NEW_PHONE_NUMBER_ADDED,
|
||||
TemplateTypes.CASE_ALLOCATED_NOTIFICATION_TEMPLATE,
|
||||
TemplateTypes.CASE_DE_ALLOCATED_NOTIFICATION_TEMPLATE,
|
||||
TemplateTypes.COLLECTION_PAUSE_EFFORTS_CASE_DEALLOCATED,
|
||||
TemplateTypes.PAYMENT_FAILED_TEMPLATE_V2,
|
||||
TemplateTypes.PAYMENT_MADE_TEMPLATE_V2,
|
||||
TemplateTypes.CUSTOMER_CALLED_TEMPLATE_V2,
|
||||
@@ -215,7 +219,7 @@ export const TemplateInfoMap = {
|
||||
icon: <ErrorIcon />
|
||||
},
|
||||
[TemplateTypes.LONGHORN_ALERTS_CHANGE_IN_DELIQUENCY_ACCOUNTS]: {
|
||||
label: 'Change in deliquency accounts',
|
||||
label: 'Change in delinquency accounts',
|
||||
icon: <ErrorIcon />
|
||||
},
|
||||
[TemplateTypes.LONGHORN_ALERTS_NEW_PHONE_NUMBER_ADDED]: {
|
||||
@@ -231,7 +235,7 @@ export const TemplateInfoMap = {
|
||||
icon: <ErrorIcon />
|
||||
},
|
||||
[TemplateTypes.LONGHORN_ALERTS_CHANGE_IN_DELIQUENCY]: {
|
||||
label: 'Change in deliquency',
|
||||
label: 'Change in delinquency',
|
||||
icon: <ErrorIcon />
|
||||
},
|
||||
[TemplateTypes.CASE_ALLOCATED_NOTIFICATION_TEMPLATE]: {
|
||||
@@ -239,9 +243,13 @@ export const TemplateInfoMap = {
|
||||
icon: <AllocationIcon />
|
||||
},
|
||||
[TemplateTypes.CASE_DE_ALLOCATED_NOTIFICATION_TEMPLATE]: {
|
||||
label: 'Case Deallocated',
|
||||
label: 'Case De-allocated',
|
||||
icon: <AllocationIcon />
|
||||
},
|
||||
[TemplateTypes.COLLECTION_PAUSE_EFFORTS_CASE_DEALLOCATED]: {
|
||||
label: 'Case De-allocated',
|
||||
icon: <InfoNotification />
|
||||
},
|
||||
[TemplateTypes.AMEYO_CALL_DROP]: {
|
||||
label: 'Ameyo Call Drop',
|
||||
icon: <AmeyoCallDropIcon />
|
||||
|
||||
@@ -46,7 +46,6 @@ import InactiveTextContainer from './components/form/InactiveTextContainer';
|
||||
import styles from './AgentForm.module.scss';
|
||||
import cx from 'classnames';
|
||||
import { Typography } from '@navi/web-ui/lib/primitives';
|
||||
import UploadComponent from '@cp/components/UploadComponent/UploadComponent';
|
||||
import AgentFormUploadControl from '@cp/pages/AllAgents/AgentForm/components/form/AgentFormUploadControl';
|
||||
import AgentFormDownloadDraCertificate from '@cp/pages/AllAgents/AgentForm/components/form/AgentFormDownloadDraCertificate';
|
||||
|
||||
|
||||
46
src/pages/CaseDetails/components/Pause/action.ts
Normal file
46
src/pages/CaseDetails/components/Pause/action.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import axiosInstance, { ApiKeys, getApiUrl, logError } from '@cp/src/utils/ApiHelper';
|
||||
import { toast } from '@navi/web-ui/lib/primitives/Toast';
|
||||
import { PAUSE_ACTION_MESSAGE } from './const';
|
||||
import { isFunction } from '@cp/src/utils/commonUtils';
|
||||
|
||||
export const getLanPauseStatus = async (caseId: string) => {
|
||||
try {
|
||||
const url = getApiUrl(ApiKeys.GET_PAUSE_STATUS, { caseId });
|
||||
const response = await axiosInstance.get(url);
|
||||
return response?.data;
|
||||
} catch (error) {
|
||||
return logError(error);
|
||||
}
|
||||
};
|
||||
|
||||
interface PauseActionInput {
|
||||
caseId: string;
|
||||
payload: any;
|
||||
setLoading: (loading: boolean) => void;
|
||||
successCallback: () => void;
|
||||
}
|
||||
|
||||
export const submitPauseAction = ({
|
||||
caseId,
|
||||
payload,
|
||||
setLoading,
|
||||
successCallback
|
||||
}: PauseActionInput) => {
|
||||
const url = getApiUrl(ApiKeys.PAUSE_ACTION, { caseId });
|
||||
|
||||
if (isFunction(setLoading)) setLoading(true);
|
||||
|
||||
axiosInstance
|
||||
.post(url, payload)
|
||||
.then(() => {
|
||||
toast.success(PAUSE_ACTION_MESSAGE.COLLECTIONS_PAUSED);
|
||||
successCallback();
|
||||
})
|
||||
.catch(error => {
|
||||
toast.error(PAUSE_ACTION_MESSAGE.FAILED_TO_PAUSE);
|
||||
logError(error);
|
||||
})
|
||||
.finally(() => {
|
||||
isFunction(setLoading) && setLoading(false);
|
||||
});
|
||||
};
|
||||
51
src/pages/CaseDetails/components/Pause/const.ts
Normal file
51
src/pages/CaseDetails/components/Pause/const.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
export enum PauseType {
|
||||
RBI_ESCALATION = 'RBI_ESCALATION',
|
||||
CUSTOMER_GOODWILL = 'CUSTOMER_GOODWILL',
|
||||
OTHERS = 'OTHERS'
|
||||
}
|
||||
|
||||
export const PAUSE_REASON_TYPES = [
|
||||
{
|
||||
value: PauseType.RBI_ESCALATION,
|
||||
label: 'RBI Escalation'
|
||||
},
|
||||
{
|
||||
value: PauseType.CUSTOMER_GOODWILL,
|
||||
label: 'Customer Goodwill'
|
||||
},
|
||||
{
|
||||
value: PauseType.OTHERS,
|
||||
label: 'Others'
|
||||
}
|
||||
];
|
||||
|
||||
export interface PauseFormValues {
|
||||
pauseDate: string;
|
||||
reason: PauseType | null;
|
||||
details?: string;
|
||||
}
|
||||
|
||||
export const defaultValues = {
|
||||
pauseDate: '',
|
||||
reason: null,
|
||||
details: ''
|
||||
};
|
||||
|
||||
export enum PAUSE_MESSAGE {
|
||||
UPDATE_PAUSE = 'Update pause on this LAN',
|
||||
PAUSE_COLLECTIONS = 'Pause collections for this LAN',
|
||||
UPDATE_PAUSE_DAYS = 'Update pause days',
|
||||
PAUSE_TILL = 'Pause till',
|
||||
PAUSE_IMMEDIATE_TODAY = 'The pause will be immediate from today',
|
||||
DATE_ERROR = 'Please enter some other date',
|
||||
REASON_UPDATE = 'Reason for update',
|
||||
REASON_PAUSE = 'Reason for pause',
|
||||
OTHER_REASONS = 'Other reasons',
|
||||
UPDATE_PAUSE_DATE = 'Update pause date',
|
||||
PAUSE_COLLECTIONS_EFFORTS = 'Pause collections efforts'
|
||||
}
|
||||
|
||||
export enum PAUSE_ACTION_MESSAGE {
|
||||
FAILED_TO_PAUSE = 'Failed to pause collections',
|
||||
COLLECTIONS_PAUSED = 'Collections paused successfully'
|
||||
}
|
||||
67
src/pages/CaseDetails/components/Pause/index.module.scss
Normal file
67
src/pages/CaseDetails/components/Pause/index.module.scss
Normal file
@@ -0,0 +1,67 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.feedback {
|
||||
overflow-y: scroll;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 0 16px 16px 16px;
|
||||
flex: 1;
|
||||
|
||||
.pauseDatePicker {
|
||||
margin-top: 8px;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.pauseDatePickerInput {
|
||||
border-radius: 8px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
font-size: 16px;
|
||||
color: var(--navi-bordered-input-text-color);
|
||||
}
|
||||
|
||||
.textAreaContainer {
|
||||
min-width: auto;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.questions {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
align-self: flex-start;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.chip {
|
||||
padding: 4px 12px;
|
||||
font-size: 16px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.chipActiveColor {
|
||||
background-color: var(--blue-bg);
|
||||
border: 1px solid var(--blue-border);
|
||||
|
||||
.text {
|
||||
color: var(--blue-base);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submitButtonSection {
|
||||
width: -webkit-fill-available;
|
||||
padding: 16px 16px;
|
||||
border-top: 1px solid var(--navi-color-gray-border);
|
||||
height: fit-content;
|
||||
}
|
||||
}
|
||||
210
src/pages/CaseDetails/components/Pause/index.tsx
Normal file
210
src/pages/CaseDetails/components/Pause/index.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Chip, TextArea, Typography } from '@navi/web-ui/lib/primitives';
|
||||
import DateTimePickerComponent from '@cp/src/components/DateTimePicker/DateTimePickerComponent';
|
||||
import { DATE_TIME_TYPE } from '@cp/src/components/DateTimePicker/constants';
|
||||
import { DateFormat, getFormatDate, getFormattedDateWithFullYear } from '@cp/src/utils/DateHelper';
|
||||
import { defaultValues, PAUSE_MESSAGE, PAUSE_REASON_TYPES, PauseFormValues } from './const';
|
||||
import InfoFilledIcon from '@cp/src/assets/icons/InfoFilledIcon';
|
||||
import { getLanPauseStatus, submitPauseAction } from './action';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '@cp/src/store';
|
||||
import { createKey } from '@cp/src/utils/CaseDetail.utils';
|
||||
import { logError } from '@cp/src/utils/ApiHelper';
|
||||
import PauseIcon from '@cp/src/assets/icons/PauseIcon';
|
||||
import styles from './index.module.scss';
|
||||
import cx from 'classnames';
|
||||
|
||||
const PauseAction = () => {
|
||||
const todayDate = getFormatDate(new Date(), DateFormat.YYYY_MM_DD);
|
||||
const threeMonthsLaterDate = getFormatDate(
|
||||
new Date(new Date().setMonth(new Date().getMonth() + 3)),
|
||||
DateFormat.YYYY_MM_DD
|
||||
);
|
||||
|
||||
const [isSelectedActionUpdatePause, setIsSelectedActionUpdatePause] = useState(false);
|
||||
const [pausedDate, setPausedDate] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const { loanId = '', customerId = '' } = useParams();
|
||||
const { pageDetail } = useSelector((state: RootState) => ({
|
||||
pageDetail: state.caseDetail.pageData?.[createKey(loanId, customerId)]
|
||||
}));
|
||||
const caseReferenceId = pageDetail?.details?.data?.caseReferenceId || '';
|
||||
|
||||
const handleSubmitState = (state: boolean) => {
|
||||
setIsSubmitting(state);
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
formState: { errors },
|
||||
control
|
||||
} = useForm<PauseFormValues>({
|
||||
defaultValues
|
||||
});
|
||||
|
||||
const fetchPauseStatus = async () => {
|
||||
try {
|
||||
const getPauseStatusData = await getLanPauseStatus(caseReferenceId);
|
||||
setIsSelectedActionUpdatePause(getPauseStatusData?.isPaused);
|
||||
setPausedDate(getPauseStatusData?.pauseTillDate);
|
||||
} catch (error) {
|
||||
logError(error, 'Error while fetching pause status');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!caseReferenceId) return;
|
||||
fetchPauseStatus();
|
||||
}, [caseReferenceId]);
|
||||
|
||||
const handleSubmitForm = (data: PauseFormValues) => {
|
||||
const { pauseDate, reason } = data;
|
||||
const payload = {
|
||||
pauseEffortsTillDate: pauseDate,
|
||||
pauseEffortsReason: reason === 'OTHERS' ? data.details : reason
|
||||
};
|
||||
|
||||
submitPauseAction({
|
||||
caseId: caseReferenceId,
|
||||
payload: payload,
|
||||
setLoading: handleSubmitState,
|
||||
successCallback: fetchPauseStatus
|
||||
});
|
||||
reset(defaultValues);
|
||||
};
|
||||
|
||||
const isFormDisabled =
|
||||
!caseReferenceId.length ||
|
||||
isSubmitting ||
|
||||
!watch('pauseDate') ||
|
||||
!watch('reason') ||
|
||||
(watch('reason') === 'OTHERS' && !watch('details'));
|
||||
|
||||
return (
|
||||
<div className={styles.container} key={isSelectedActionUpdatePause as unknown as string}>
|
||||
<div className={styles.feedback}>
|
||||
<div className={'mt-6 mb-4'}>
|
||||
<Typography variant="h3">
|
||||
{isSelectedActionUpdatePause
|
||||
? PAUSE_MESSAGE.UPDATE_PAUSE
|
||||
: PAUSE_MESSAGE.PAUSE_COLLECTIONS}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
{isSelectedActionUpdatePause ? (
|
||||
<div className="flex bg-[var(--navi-color-gray-bg-secondary)] pt-3 pb-3 pr-3.5 pl-3 rounded">
|
||||
<InfoFilledIcon width={20} height={20} fill={'var(--blue-base)'} />
|
||||
|
||||
<Typography variant="p4" className="ml-2">
|
||||
Collections efforts have been paused till{' '}
|
||||
<Typography as="span" variant="h5" color="var(--navi-color-gray-c1)">
|
||||
{pausedDate ? getFormattedDateWithFullYear(new Date(pausedDate)) : ''}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="pauseDate"
|
||||
rules={{
|
||||
validate: value =>
|
||||
!(isSelectedActionUpdatePause && value === pausedDate) || PAUSE_MESSAGE.DATE_ERROR
|
||||
}}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<DateTimePickerComponent
|
||||
key={isSubmitting as unknown as string}
|
||||
type={DATE_TIME_TYPE.DATE}
|
||||
min={todayDate}
|
||||
max={threeMonthsLaterDate}
|
||||
value={value}
|
||||
className={'mt-4'}
|
||||
containerClasses={styles.pauseDatePicker}
|
||||
inputClasses={styles.pauseDatePickerInput}
|
||||
onChange={onChange}
|
||||
required
|
||||
heading={
|
||||
isSelectedActionUpdatePause
|
||||
? PAUSE_MESSAGE.UPDATE_PAUSE_DAYS
|
||||
: PAUSE_MESSAGE.PAUSE_TILL
|
||||
}
|
||||
footer={isSelectedActionUpdatePause ? '' : PAUSE_MESSAGE.PAUSE_IMMEDIATE_TODAY}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{errors?.pauseDate && (
|
||||
<Typography variant="p4" color="var(--navi-color-red-base)">
|
||||
{errors?.pauseDate?.message}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<div className={'mt-4 mb-1'}>
|
||||
<Typography as="span" variant="p4" color="var(--navi-color-gray-c2)">
|
||||
{isSelectedActionUpdatePause ? PAUSE_MESSAGE.REASON_UPDATE : PAUSE_MESSAGE.REASON_PAUSE}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="reason"
|
||||
rules={{ required: true }}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<div className={styles.questions}>
|
||||
{PAUSE_REASON_TYPES.map(pauseType => (
|
||||
<Chip
|
||||
key={pauseType.value}
|
||||
label={pauseType.label}
|
||||
wrapperClasses={cx([
|
||||
styles.chip,
|
||||
{ [styles.chipActiveColor]: pauseType.value === value }
|
||||
])}
|
||||
labelClasses={styles.text}
|
||||
onChipClick={() => onChange(pauseType.value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
{watch('reason') === 'OTHERS' ? (
|
||||
<TextArea
|
||||
{...register('details', { required: true })}
|
||||
placeholder=""
|
||||
id="comments"
|
||||
resize
|
||||
fullWidth
|
||||
required
|
||||
label="Other reasons"
|
||||
rows={4}
|
||||
containerClassName={styles.textAreaContainer}
|
||||
className={cx(styles.textAreaContainer, 'mt-4')}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className={styles.submitButtonSection}>
|
||||
<Button
|
||||
fullWidth
|
||||
type={'submit'}
|
||||
onClick={handleSubmit(handleSubmitForm)}
|
||||
className="bg-[var(--navi-color-red-base)]"
|
||||
startAdornment={<PauseIcon />}
|
||||
disabled={isFormDisabled}
|
||||
>
|
||||
{isSelectedActionUpdatePause
|
||||
? PAUSE_MESSAGE.UPDATE_PAUSE_DATE
|
||||
: PAUSE_MESSAGE.PAUSE_COLLECTIONS_EFFORTS}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(PauseAction);
|
||||
@@ -17,12 +17,22 @@ const RightSidebar = () => {
|
||||
}));
|
||||
const location = useLocation();
|
||||
|
||||
const [selectedTabKey, setSelectedTabKey] = useState(TAB_KEYS.FEEDBACK);
|
||||
const isCollectionsPauseRoleAvailable = featureFlags?.pauseCollectionEffortsFeatureFlag || false;
|
||||
|
||||
const getInitialTabKey = () => {
|
||||
return isCollectionsPauseRoleAvailable ? TAB_KEYS.PAUSE : TAB_KEYS.FEEDBACK;
|
||||
};
|
||||
|
||||
const [selectedTabKey, setSelectedTabKey] = useState(getInitialTabKey);
|
||||
|
||||
useEffect(() => {
|
||||
const selectedTab = location?.state?.rightSideTab;
|
||||
|
||||
if (selectedTab && featureFlags?.csaCoOrdinationModuleFeatureFlag)
|
||||
if (
|
||||
selectedTab &&
|
||||
(featureFlags?.csaCoOrdinationModuleFeatureFlag ||
|
||||
featureFlags?.pauseCollectionEffortsFeatureFlag)
|
||||
)
|
||||
setSelectedTabKey(selectedTab);
|
||||
}, [location.key]);
|
||||
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import CsaTicketing from '../../CsaTicketing';
|
||||
import PauseAction from '../components/Pause';
|
||||
import Payments from '../components/Payments';
|
||||
import FeedbackForm from '../feedbackForm';
|
||||
|
||||
export const TAB_KEYS = {
|
||||
FEEDBACK: 'feedback',
|
||||
PAYMENTS: 'payments',
|
||||
TASKS: 'tasks'
|
||||
TASKS: 'tasks',
|
||||
PAUSE: 'pause'
|
||||
};
|
||||
|
||||
export const LEFT_SIDE_TABS = [
|
||||
{
|
||||
key: TAB_KEYS.PAUSE,
|
||||
value: 'Pause',
|
||||
component: PauseAction,
|
||||
featureFlagKey: 'pauseCollectionEffortsFeatureFlag'
|
||||
},
|
||||
{
|
||||
key: TAB_KEYS.FEEDBACK,
|
||||
value: 'Feedback',
|
||||
|
||||
@@ -451,3 +451,9 @@ export const getLastMonthDate = () => {
|
||||
lastMonth.setMonth(lastMonth.getMonth() - 1);
|
||||
return lastMonth;
|
||||
};
|
||||
|
||||
export const getNextThreeMonthDate = () => {
|
||||
const lastMonth = new Date();
|
||||
lastMonth.setMonth(lastMonth.getMonth() + 3);
|
||||
return lastMonth;
|
||||
};
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
/* identical to box height, or 154% */
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--greyscale-2);
|
||||
margin-bottom: 8px;
|
||||
|
||||
@@ -837,7 +837,6 @@ const FeedbackFrom = (props: FeedbackFromProps) => {
|
||||
</div>
|
||||
<div className={styles.submitButtonSection}>
|
||||
{!isDisableNextPreviousCaseAction && !isTeamLead ? <NextPreviousCaseActions /> : null}
|
||||
{/* {!disableSubmitButton ? ( */}
|
||||
<Button
|
||||
disabled={disableSubmitFlag}
|
||||
fullWidth
|
||||
@@ -846,7 +845,6 @@ const FeedbackFrom = (props: FeedbackFromProps) => {
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
{/* // ) : null} */}
|
||||
</div>
|
||||
<Loader show={feedback.loading || false} className={styles.loadingState} animate={false} />
|
||||
</div>
|
||||
|
||||
@@ -90,7 +90,8 @@ export enum Roles {
|
||||
ROLE_GLOBAL_ACCESS = 'ROLE_GLOBAL_ACCESS',
|
||||
ROLE_BANK_STATEMENT_ACCESS = 'ROLE_BANK_STATEMENT_ACCESS',
|
||||
ROLE_IN_HOUSE_CSA_AGENT = 'ROLE_INHOUSE_CSA',
|
||||
ROLE_EXTERNAL_CSA = 'ROLE_EXTERNAL_CSA'
|
||||
ROLE_EXTERNAL_CSA = 'ROLE_EXTERNAL_CSA',
|
||||
ROLE_COLLECTION_EFFORTS_PAUSE_ACCESS = 'ROLE_COLLECTION_EFFORTS_PAUSE_ACCESS'
|
||||
}
|
||||
|
||||
export const agentStateAndLanguageEditRoles = [
|
||||
|
||||
@@ -265,7 +265,9 @@ export enum ApiKeys {
|
||||
DOCUMENT_UPLOAD_ACK_URL,
|
||||
GENERATE_DYNAMIC_DOCUMENT_URL,
|
||||
GET_MASKED_TELEPHONE,
|
||||
FETCH_AGENT_DOCUMENTS
|
||||
FETCH_AGENT_DOCUMENTS,
|
||||
GET_PAUSE_STATUS,
|
||||
PAUSE_ACTION
|
||||
}
|
||||
|
||||
// TODO: try to get rid of `as`
|
||||
@@ -532,6 +534,8 @@ API_URLS[ApiKeys.DOWNLOAD_ENACH_FILE] = '/v2/file-uploads/download-url/{referenc
|
||||
API_URLS[ApiKeys.GET_DOCUMENT_UPLOAD_URL] = 'v2/file-uploads/upload-url';
|
||||
API_URLS[ApiKeys.DOCUMENT_UPLOAD_ACK_URL] = '/v2/file-uploads/upload-ack';
|
||||
API_URLS[ApiKeys.GET_MASKED_TELEPHONE] = 'collections/get-masked-telephone';
|
||||
API_URLS[ApiKeys.GET_PAUSE_STATUS] = '/v1/case-efforts/pause/status/{caseId}';
|
||||
API_URLS[ApiKeys.PAUSE_ACTION] = '/v1/case-efforts/pause/{caseId}';
|
||||
API_URLS[ApiKeys.GENERATE_DYNAMIC_DOCUMENT_URL] = 'v1/documents/generate';
|
||||
|
||||
// TODO: try to get rid of `as`
|
||||
|
||||
Submodule web-ui-library updated: d58e27f371...58388435d2
Reference in New Issue
Block a user