diff --git a/src/assets/icons/EscalationUserIcon.tsx b/src/assets/icons/EscalationUserIcon.tsx new file mode 100644 index 00000000..7712b8f3 --- /dev/null +++ b/src/assets/icons/EscalationUserIcon.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { IconProps } from './types'; + +const EscalationUserIcon: React.FC = props => { + const { width = 24, height = 24 } = props; + return ( + + + + + ); +}; + +export default EscalationUserIcon; diff --git a/src/assets/images/icons/EscalationNotifIcon.tsx b/src/assets/images/icons/EscalationNotifIcon.tsx new file mode 100644 index 00000000..646ba74b --- /dev/null +++ b/src/assets/images/icons/EscalationNotifIcon.tsx @@ -0,0 +1,19 @@ +const EscalationNotifIcon = () => { + return ( + + + + + + + + + + + ); +}; + +export default EscalationNotifIcon; diff --git a/src/components/Notifications/FcmNotification.tsx b/src/components/Notifications/FcmNotification.tsx index 701f40ad..48d1aec2 100644 --- a/src/components/Notifications/FcmNotification.tsx +++ b/src/components/Notifications/FcmNotification.tsx @@ -11,6 +11,8 @@ import { RootState } from '../../store'; import NotificationBell from '../../assets/images/icons/NotificationBell'; import Typography from '@primitives/Typography'; import cx from 'classnames'; +import { getReporteesWithAllocation } from '../sidebar/actions/escalationsAction'; +import { setShowLogoutModal } from '@cp/src/reducers/commonSlice'; interface FcmNotificationProps { notification: any; @@ -22,6 +24,10 @@ const FcmNotification = ({ notification }: FcmNotificationProps) => { const navigate = useNavigate(); const widget = notificationTemplate?.widgets?.[0]; const phoneNumber = useSelector((state: RootState) => state.common.userData?.phoneNumber); + const agentReferenceId = useSelector((state: RootState) => state.common.userData?.referenceId); + const countOfEscalationEnabled = useSelector( + (state: RootState) => state?.common?.featureFlags?.countOfEscalationEnabled + ); const handleNotificationClick = () => { const { params } = notification; if ( @@ -31,6 +37,9 @@ const FcmNotification = ({ notification }: FcmNotificationProps) => { ].includes(notificationTemplate?.templateName) ) { navigate(APP_ROUTES.CASES.path); + } else if (TemplateTypes.COLLECTION_ESCALATION_RAISED === notificationTemplate?.templateName) { + dispatch(getReporteesWithAllocation(agentReferenceId || '', countOfEscalationEnabled)); + dispatch(setShowLogoutModal(true)); } else { navigateToCaseDetails( params?.customerReferenceId, diff --git a/src/components/Notifications/NotificationList.tsx b/src/components/Notifications/NotificationList.tsx index 60a9ccff..91a7854e 100644 --- a/src/components/Notifications/NotificationList.tsx +++ b/src/components/Notifications/NotificationList.tsx @@ -26,6 +26,8 @@ import { interpolatePathParams } from '@cp/src/utils/interpolate'; import { TAB_KEYS as RIGHT_SIDE_TABS } from '@cp/src/pages/CaseDetails/constants/RightSidebar.constant'; import { TAB_KEYS } from '@cp/src/pages/CaseDetails/constants'; import { AnomalyStatus, AnomalyTableKeys } from '@cp/src/pages/AnomalyTracker/types'; +import { getReporteesWithAllocation } from '../sidebar/actions/escalationsAction'; +import { setShowLogoutModal } from '@cp/src/reducers/commonSlice'; const NotificationList: React.FC = ({ toggleNotifications, @@ -39,6 +41,10 @@ const NotificationList: React.FC = ({ const templatesMap = useSelector((state: RootState) => state?.notification.templatesMap); const phoneNumber = useSelector((state: RootState) => state.common.userData?.phoneNumber); + const agentReferenceId = useSelector((state: RootState) => state.common.userData?.referenceId); + const countOfEscalationEnabled = useSelector( + (state: RootState) => state?.common?.featureFlags?.countOfEscalationEnabled + ); const handleNotificationClick = ( notification: NotificationsData, @@ -91,6 +97,9 @@ const NotificationList: React.FC = ({ } }); navigate(`${APP_ROUTES_PATHS_WITH_PATH_PARAM.ANOMALY_TRACKER.open}${queryParams}`); + } else if (templateName === TemplateTypes.COLLECTION_ESCALATION_RAISED) { + dispatch(getReporteesWithAllocation(agentReferenceId || '', countOfEscalationEnabled)); + dispatch(setShowLogoutModal(true)); } else { navigateToCaseDetails( params?.customerReferenceId, diff --git a/src/components/Notifications/constants.tsx b/src/components/Notifications/constants.tsx index 6e46e44f..60844d68 100644 --- a/src/components/Notifications/constants.tsx +++ b/src/components/Notifications/constants.tsx @@ -16,6 +16,7 @@ import FeeReappliedIcon from '@cp/src/assets/icons/FeeReappliedIcon'; import InfoNotification from '@cp/src/assets/images/icons/InfoNotification'; import AnomalyResolvedIcon from '@cp/src/assets/images/icons/AnomalyResolvedIcon'; import AnomalyDetectedIcon from '@cp/src/assets/images/icons/AnomalyDetectedIcon'; +import EscalationNotifIcon from '@cp/src/assets/images/icons/EscalationNotifIcon'; export enum NotificationTypes { LONGHORN_NOTIFICATION = 'LONGHORN_NOTIFICATION', @@ -68,7 +69,8 @@ export const TemplateTypes = { ANOMALY_TRACKER_DETECTION: 'ANOMALY_TRACKER_DETECTION_V2', ANOMALY_TRACKER_RESOLUTION: 'ANOMALY_TRACKER_RESOLUTION', COLLECTION_PAUSE_EFFORTS_CASE_PAUSED: 'COLLECTION_PAUSE_EFFORTS_CASE_PAUSED', - COLLECTION_UNPAUSE_EFFORT_EVENT: 'COLLECTION_UNPAUSE_EFFORT_EVENT' + COLLECTION_UNPAUSE_EFFORT_EVENT: 'COLLECTION_UNPAUSE_EFFORT_EVENT', + COLLECTION_ESCALATION_RAISED: 'COLLECTION_ESCALATION_RAISED' }; export const PaymentSuccessTemplateTypes = [ @@ -123,7 +125,8 @@ export const NotificationTabTemplateMap = { TemplateTypes.FIELD_BOT_REVISIT_SCHEDULED_NOTIFICATION_TEMPLATE, TemplateTypes.MESSAGE_WAIVE_UNHOLD_NOTIFICATION_TEMPLATE, TemplateTypes.COLLECTION_PAUSE_EFFORTS_CASE_PAUSED, - TemplateTypes.COLLECTION_UNPAUSE_EFFORT_EVENT + TemplateTypes.COLLECTION_UNPAUSE_EFFORT_EVENT, + TemplateTypes.COLLECTION_ESCALATION_RAISED ], [NotificationTabTypes.All]: [ TemplateTypes.PAYMENT_MADE_TEMPLATE, @@ -156,7 +159,8 @@ export const NotificationTabTemplateMap = { TemplateTypes.ANOMALY_TRACKER_DETECTION, TemplateTypes.ANOMALY_TRACKER_RESOLUTION, TemplateTypes.COLLECTION_PAUSE_EFFORTS_CASE_PAUSED, - TemplateTypes.COLLECTION_UNPAUSE_EFFORT_EVENT + TemplateTypes.COLLECTION_UNPAUSE_EFFORT_EVENT, + TemplateTypes.COLLECTION_ESCALATION_RAISED ], [NotificationTabTypes.Tasks]: [ TemplateTypes.SUPPORT_REQUEST_RECEIVED, @@ -310,6 +314,10 @@ export const TemplateInfoMap = { [TemplateTypes.ANOMALY_TRACKER_RESOLUTION]: { label: 'Anomaly resolved', icon: + }, + [TemplateTypes.COLLECTION_ESCALATION_RAISED]: { + label: 'Collection Escalation Raised', + icon: } }; diff --git a/src/components/sidebar/SideNavBar.module.scss b/src/components/sidebar/SideNavBar.module.scss index 3b253cf6..b6072ee3 100644 --- a/src/components/sidebar/SideNavBar.module.scss +++ b/src/components/sidebar/SideNavBar.module.scss @@ -173,7 +173,7 @@ .logoutContainer { position: absolute; display: flex; - width: 220px; + width: 260px; padding: 16px; flex-direction: column; align-items: flex-start; @@ -304,3 +304,9 @@ cursor: pointer; text-align: center; } + +.horizontalLine { + width: 100%; + height: 1px; + background: var(--navi-color-gray-border); +} diff --git a/src/components/sidebar/SideNavBar.tsx b/src/components/sidebar/SideNavBar.tsx index ab0524ed..88e09580 100644 --- a/src/components/sidebar/SideNavBar.tsx +++ b/src/components/sidebar/SideNavBar.tsx @@ -1,13 +1,10 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import Typography from '@navi/web-ui/lib/primitives/Typography/index'; -import cx from 'classnames'; import { Link, useLocation } from 'react-router-dom'; -// styles import styles from './SideNavBar.module.scss'; - -// functions, components and utlis +import cx from 'classnames'; import NaviNewLogoIcon from '@navi/web-ui/lib/icons/NaviLogoIcon/NaviNewLogoIcon'; -import SideBarItems, { DASHBOARD_URL, HideSideBar } from './SideBarItems'; +import SideBarItems, { HideSideBar } from './SideBarItems'; import SidebarLinks, { clearParamsMap } from './SidebarLinks'; import AgentIcon from '../../assets/images/icons/AgentIcon'; import { useDispatch, useSelector } from 'react-redux'; @@ -15,7 +12,8 @@ import { RootState } from '../../store'; import { CosmosSyncBlockStatus, resetHumanReminderCustomerDetails, - setAuthData + setAuthData, + setShowLogoutModal } from '../../reducers/commonSlice'; import { setShowOTPScreen } from '../../pages/auth/reducers/authSlice'; import { logOut } from 'src/pages/auth/AuthActions'; @@ -25,12 +23,7 @@ import CircularProgress from '../ProgressBars/circularProgress/CircularProgress' import Tooltip, { directions } from '../Tooltip'; import { AmeyoCollapsibleToolbarV3 } from '@cp/components/Ameyo/AmeyoCollapsibleToolbarV3'; import Switch from '@navi/web-ui/lib/primitives/Switch/Switch'; -import { - refreshCallData, - setCallId, - setIsAgentOnline, - setManualLoginCreds -} from 'src/reducers/humanReminderSlice'; +import { refreshCallData, setManualLoginCreds } from 'src/reducers/humanReminderSlice'; import { ExtensionHandler } from 'src/utils/extension.utils'; import { AMEYO_STATUS_CODES } from 'src/service/naviExtension.service'; import APP_ROUTES, { APP_ROUTES_PATHS_WITH_PATH_PARAM } from 'src/layout/Routes'; @@ -50,7 +43,6 @@ import { LOCAL_STORAGE_KEYS } from '../../constants/StorageKeys'; import useLocalStorageObserver from '../../hooks/useLocalStorageObserver'; import { Roles } from 'src/pages/auth/constants/AuthConstants'; import GenerateAmeyoPasswordIcon from '../../assets/icons/GenerateAmeyoPasswordIcon'; -import AgencyPincodeMappingIcon from '../../assets/icons/AgencyPincodeMappingIcon'; import WhatsappChatIcon from '../../assets/icons/WhatsappChatIcon'; import { agentOnlineToggleHandler } from '../../pages/WhatsappChatbot/actions'; import { @@ -73,22 +65,24 @@ import AllocationNavbarIcon from '@cp/src/assets/images/icons/AllocationNavbarIc import EnachIcon from '@cp/src/assets/icons/EnachIcon'; import { getAgentDocuments } from './actions/downloadDRA'; import { downloadDocumentFromS3WithoughtPreview } from '@cp/src/utils/DocumentDownloadFromS3'; -import { AGENT_DOCUMENT_TYPE } from './constants/constants'; +import { DOCUMENTS_MAP } from './constants/constants'; import CustomLogoutIcon from '@cp/src/assets/icons/CustomLogoutIcon'; import CustomUserIcon from '@cp/src/assets/icons/CustomUserIcon'; import AgencyIcon from '@cp/src/assets/icons/AgencyIcon'; import RefreshIcon from '@icons/RefreshIcon'; import AgencyOperationsIcon from '@cp/assets/icons/AgencyOperationsIcon'; -import { noop } from '@utils/common'; import { AmeyoCollapsibleToolbarV4 } from '@cp/components/Ameyo/AmeyoCollapsibleToolbarV4'; import logoutFromAmeyo from '@cp/utils/logoutFromAmeyo'; import AnomalyTrackerIcon from '@cp/src/assets/icons/AnomalyTrackerIcon'; -import axiosInstance, { ApiKeys, getApiUrl, logError } from '@cp/src/utils/ApiHelper'; +import { logError } from '@cp/src/utils/ApiHelper'; import sdkHelper from '@cp/src/pages/DashboardV3/utils/sdkHelper'; import { setAgentAvailability } from '@cp/src/pages/DashboardV2/dc-97/HumanReminderAction'; import { AGENT_AVAILABILITY } from '@cp/src/pages/DashboardV2/dc-97/dc97Constant'; import isNaviAgency from '@cp/src/utils/isNaviAgency'; import UserDocuments from './UserDocuments'; +import EscalationUserIcon from '@cp/src/assets/icons/EscalationUserIcon'; +import { Tag } from '@navi/web-ui/lib/primitives'; +import { getReporteesWithAllocation } from './actions/escalationsAction'; interface ISideNavbarProps { isDc97User?: boolean; @@ -159,6 +153,7 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { (state: RootState) => state?.idCardApprovals?.totalPendingApprovalsNotificationCount ); const agencyCode = useSelector((state: RootState) => state?.common?.userData?.agencyCode); + const agentReferenceId = useSelector((state: RootState) => state?.common?.userData?.referenceId); const percentCompletedCases = useSelector( (state: RootState) => state.leaderboard.percentCompletedRecords )?.reduce( @@ -178,13 +173,10 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { const isTeleInhouseTeamLead = userData?.roles?.includes(Roles.ROLE_NAVI_TELE_INHOUSE_TEAM_LEAD); const disableAmeyoToolbarFlag = window?.config?.DISABLE_AMEYO_TOOLBAR === 'true' || isDc97User; - const [showLogout, setShowLogout] = useState(false); const [disableLogout, setDisableLogout] = useState(false); const [showNotifications, setShowNotifications] = useState(false); const dispatch = useDispatch(); const isTeleChatAgent = userData?.roles?.includes(Roles.ROLE_TELE_CHAT_AGENT); - // const percentage = 30; - const unsubscribe = React.useRef<() => void>(); const agentId = userData?.referenceId; const agentsData = useSelector((state: RootState) => state?.agentDocumentSlice?.documentsData); const isAgentWithInOperativeHours = useSelector( @@ -196,6 +188,13 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { const isAgencyCappingEnabled = useSelector( (store: RootState) => store?.common?.featureFlags?.agencyCappingEnabled ); + const agentEscalationCount = useSelector( + (state: RootState) => state?.common?.agentEscalationCount + ); + const countOfEscalationEnabled = useSelector( + (state: RootState) => state?.common?.featureFlags?.countOfEscalationEnabled + ); + const showLogoutModal = useSelector((state: RootState) => state?.common?.showLogoutModal); const [isAmeyoNewIntegrationEnabled, setIsAmeyoNewIntegrationEnabled] = useState(false); const [isHRCNewIntegration, setIsHRCNewIntegration] = useState(false); @@ -245,7 +244,6 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { localStorage.removeItem(LOCAL_STORAGE_KEYS.LONGHORN_FIREBASE_TOKEN); localStorage.removeItem(CURRENT_CHAT_REF_ID_KEY); localStorage.removeItem(HRC_CHAT_AGENT_ONLINE_STATUS); - // localStorage.removeItem(chatMessageContentKey); logOut(); }; @@ -345,14 +343,16 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { addClickstreamEvent(clickStreamEvent); }; - const handleDashboardClick = () => { - toggleNotifications(false); - }; - const openFeedbackForm = () => { window.open(feedbackFormUrl, '_blank'); }; + const notifications = useSelector((store: RootState) => store?.notification.notifications); + + useEffect(() => { + dispatch(getReporteesWithAllocation(agentReferenceId || '', countOfEscalationEnabled)); + }, [notifications]); + const TooltipContent = () => (
Cases Completed
@@ -397,17 +397,16 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { dispatch(getAgentDocuments()); }, [agentId]); - const documentDra = agentsData?.find( - doc => doc.documentType === AGENT_DOCUMENT_TYPE.DRA_CERTIFICATE + const documents = agentsData?.filter( + doc => DOCUMENTS_MAP[doc.documentType as keyof typeof DOCUMENTS_MAP] ); - const handleDownloadDraClick = (url: string, name: string) => { - addClickstreamEvent(AGENT_LOGOUT_CLICK.LH_DOCUMENT_DOWNLOAD_CLICKED, { - documentType: agentsData?.map(item => item.docContentType), - documentName: agentsData?.map(item => item.documentName) - }); - downloadDocumentFromS3WithoughtPreview(url, name, `Cannot Download the document`); - }; + const isDraCertificatePresent = documents?.length; + + const showHorizontalLine = + countOfEscalationEnabled && isDraCertificatePresent && agentEscalationCount > 0; + + const showEscalationCount = countOfEscalationEnabled && agentEscalationCount > 0; // TODO:: REMOVE THIS CODE AFTER ONCE THINGS ARE STABLE const isUserManagementRole = @@ -421,7 +420,7 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { className={cx(styles.sideNavBarMain, { [styles.sideNavBarMain__hovered]: showNotifications })} - onMouseLeave={() => setShowLogout(false)} + onMouseLeave={() => dispatch(setShowLogoutModal(false))} >
- {showLogout ? ( + {showLogoutModal ? (
@@ -484,7 +483,7 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) {
{ - setShowLogout(prev => !prev); + dispatch(setShowLogoutModal(!showLogoutModal)); dispatch(getAgentDocuments()); }} > @@ -506,7 +505,7 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { className={cx(styles.sideNavBarMain, { [styles.sideNavBarMain__hovered]: showNotifications })} - onMouseLeave={() => setShowLogout(false)} + onMouseLeave={() => dispatch(setShowLogoutModal(false))} >
) : null} - {/* {isTeleInhouseTeamLead ? ( <> ) : null} */} + {enachProcessingEnabled ? ( <> ) : null} {showParamount ? ( @@ -862,26 +862,50 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { /> ) : null}
- {showLogout ? ( + {showLogoutModal ? (
- -
- - {user?.name} - - - {user?.email} - + {showEscalationCount ? ( + + ) : ( + + )} + +
+
+ + {user?.name} + + + {user?.email} + +
+ {showEscalationCount ? ( + <> +
+ +
+ + ) : null}
+ {showHorizontalLine ?
: null}
@@ -940,11 +964,14 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) {
{ - setShowLogout(prev => !prev); + dispatch(setShowLogoutModal(!showLogoutModal)); dispatch(getAgentDocuments()); + dispatch( + getReporteesWithAllocation(agentReferenceId || '', countOfEscalationEnabled) + ); }} > - + {showEscalationCount ? : } {user?.name} diff --git a/src/components/sidebar/UserDocuments/UserDocument.tsx b/src/components/sidebar/UserDocuments/UserDocument.tsx index 9ee09ac1..53f8b706 100644 --- a/src/components/sidebar/UserDocuments/UserDocument.tsx +++ b/src/components/sidebar/UserDocuments/UserDocument.tsx @@ -29,12 +29,11 @@ const UserDocument = (props: IUserDocument) => { onClick={handleDownloadDraClick} >
- +
- {document?.documentName} + {`Download ${document?.documentName}`}
-
); }; diff --git a/src/components/sidebar/actions/escalationsAction.ts b/src/components/sidebar/actions/escalationsAction.ts new file mode 100644 index 00000000..0cef8458 --- /dev/null +++ b/src/components/sidebar/actions/escalationsAction.ts @@ -0,0 +1,21 @@ +import { Dispatch } from '@reduxjs/toolkit'; +import axiosInstance, { ApiKeys, getApiUrl, logError } from '../../../utils/ApiHelper'; +import { setAgentEscalationCount } from '@cp/src/reducers/commonSlice'; + +export const getReporteesWithAllocation = + (agentReferenceId: string, countOfEscalationEnabled?: boolean) => (dispatch: Dispatch) => { + if (!countOfEscalationEnabled) { + return; + } + const url = getApiUrl(ApiKeys.ESCALATION_COUNT, { agentReferenceId }); + axiosInstance + .get(url) + .then(res => { + if (res?.data) { + dispatch(setAgentEscalationCount(res?.data?.count)); + } + }) + .catch(err => { + logError(err); + }); + }; diff --git a/src/reducers/commonSlice.ts b/src/reducers/commonSlice.ts index 09bdbca2..6c01dace 100644 --- a/src/reducers/commonSlice.ts +++ b/src/reducers/commonSlice.ts @@ -358,6 +358,8 @@ export interface CommonState { callingWindow?: CallingWindow; lastSyncPermissionStatus?: boolean; shouldNotHidePauseCasesTab: boolean; + agentEscalationCount: number; + showLogoutModal?: boolean; } export const CHECK_LOGIN = 'CHECK_LOGIN'; @@ -430,7 +432,9 @@ const initialState = { shouldNotHidePauseCasesTab: false, isTeamLead: false, isCiuAgent: false, - lastSyncPermissionStatus: false + lastSyncPermissionStatus: false, + agentEscalationCount: 0, + showLogoutModal: false } as CommonState; setGlobalUserData({ token: initialState.userData.token }); @@ -632,6 +636,12 @@ export const commonSlice = createSlice({ }, setCallingWindow(state, action) { state.callingWindow = action.payload; + }, + setAgentEscalationCount(state, action) { + state.agentEscalationCount = action.payload; + }, + setShowLogoutModal(state, action) { + state.showLogoutModal = action.payload; } } }); @@ -679,7 +689,9 @@ export const { setSlashCallStatusFromExtension, setAgentBusinessVertical, setCallingWindow, - setLastSyncPermissionStatus + setLastSyncPermissionStatus, + setAgentEscalationCount, + setShowLogoutModal } = commonSlice.actions; export default commonSlice.reducer; diff --git a/src/utils/ApiHelper.ts b/src/utils/ApiHelper.ts index 76b19e6e..5906aa4e 100644 --- a/src/utils/ApiHelper.ts +++ b/src/utils/ApiHelper.ts @@ -313,7 +313,8 @@ export enum ApiKeys { UPLOAD_MEDIA, CUSTOMER_CONFIDENCE, GET_PIN_CODES_DETAILS, - ADD_NEW_ADDRESS + ADD_NEW_ADDRESS, + ESCALATION_COUNT } // TODO: try to get rid of `as` @@ -622,6 +623,7 @@ API_URLS[ApiKeys.UPLOAD_MEDIA] = 'v2/interactions/upload/media'; API_URLS[ApiKeys.CUSTOMER_CONFIDENCE] = '/telephone-configurations'; API_URLS[ApiKeys.GET_PIN_CODES_DETAILS] = '/v1/pincodes/{pinCode}'; API_URLS[ApiKeys.ADD_NEW_ADDRESS] = '/address'; +API_URLS[ApiKeys.ESCALATION_COUNT] = '/agent-escalation/{agentReferenceId}/count'; // TODO: try to get rid of `as` const MOCK_API_URLS: Record = {} as Record; MOCK_API_URLS[ApiKeys.PEOPLE] = 'people.json';