diff --git a/src/components/EllipsisText/index.module.scss b/src/components/EllipsisText/index.module.scss index 6550e42f..c0fa38cc 100644 --- a/src/components/EllipsisText/index.module.scss +++ b/src/components/EllipsisText/index.module.scss @@ -5,4 +5,5 @@ -webkit-box-orient: vertical; line-height: 24px; width: fit-content; + cursor: text; } diff --git a/src/components/TableCellRenderer/CellRendererShortNumber.tsx b/src/components/TableCellRenderer/CellRendererShortNumber.tsx new file mode 100644 index 00000000..0c6f6e87 --- /dev/null +++ b/src/components/TableCellRenderer/CellRendererShortNumber.tsx @@ -0,0 +1,64 @@ +import EllipsisText from '@cp/src/components/EllipsisText'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@cp/src/components/TooltipV2/TooltipV2'; +import { formatNumber, shortNumberNotation } from '@cp/src/utils/commonUtils'; +import React from 'react'; + +interface ICellRendererShortNumber { + value: number; + lineClamp?: number; + hiddenPadding?: number; + significantDigits?: number; + isCurrency?: boolean; +} + +const CellRendererShortNumber: React.FC = props => { + const { + value, + lineClamp = 1, + hiddenPadding = 10, + significantDigits = 1, + isCurrency = false + } = props; + + if (!Number.isFinite(value)) { + // eslint-disable-next-line no-console + console.warn('CellRendererShortNumber: value is not a number', typeof value); + } + + if (!value && value !== 0) { + return ; + } + + let shorthandNumber = value == 0 ? '0' : shortNumberNotation(value, significantDigits); + + let toolTipContent = formatNumber(value, true); + + if (String(value) === shorthandNumber) { + if (isCurrency) { + shorthandNumber = `₹${shorthandNumber}`; + } + return ; + } + + if (isCurrency) { + shorthandNumber = `₹${shorthandNumber}`; + toolTipContent = `₹${toolTipContent}`; + } + + return ( + + + + + + {toolTipContent} + + + ); +}; + +export default CellRendererShortNumber; diff --git a/src/components/sidebar/SideNavBar.tsx b/src/components/sidebar/SideNavBar.tsx index db808763..964013dc 100644 --- a/src/components/sidebar/SideNavBar.tsx +++ b/src/components/sidebar/SideNavBar.tsx @@ -496,7 +496,7 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) { /> ) : null} - {isTeamLead ? ( + {isInternalTeamLead ? ( <> (dispatch: Dispatch) => { @@ -44,3 +46,19 @@ export const getAgentInputData = () => (dispatch: Dispatch) => { dispatch(setLoadingAgentPerformance(false)); }); }; + +export const getDespositionSummary = () => (dispatch: Dispatch) => { + dispatch(setLoadingDespositionSummary(true)); + const url = getApiUrl(ApiKeys.GET_DEPOSITIONS); + axiosInstance + .get(url) + .then(response => { + dispatch(setDespositionSummary(response.data)); + }) + .catch(error => { + logError(error, 'getAgentInputData'); + }) + .finally(() => { + dispatch(setLoadingDespositionSummary(false)); + }); +}; diff --git a/src/pages/SenseiTele/components/agentInput/ColumnDefs.tsx b/src/pages/SenseiTele/components/agentInput/ColumnDefs.tsx index d6eca216..649af198 100644 --- a/src/pages/SenseiTele/components/agentInput/ColumnDefs.tsx +++ b/src/pages/SenseiTele/components/agentInput/ColumnDefs.tsx @@ -3,6 +3,9 @@ import { ICellRendererParams } from 'ag-grid-community'; import cx from 'classnames'; import RenderWithToolTip from '../renderWithTooltip'; import styles from './index.module.scss'; +import TableCellRenderer from '@cp/src/components/TableCellRenderer'; +import CellRendererShortNumber from '@cp/src/components/TableCellRenderer/CellRendererShortNumber'; +import { Cell } from 'recharts'; export const AgentPerformanceColumns = [ { @@ -47,13 +50,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { allocatedCases } = params.data; if (!allocatedCases) return <>-; - const value = formatNumber(allocatedCases, true); - return ( - - ); + return ; } }, { @@ -66,10 +63,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { unpaidCases } = params.data; if (!unpaidCases) return <>-; - const value = formatNumber(unpaidCases, true); - return ( - - ); + return ; } }, // { @@ -108,7 +102,7 @@ export const AgentPerformanceColumns = [ const timeInHoursAndMinutes = `${hours ? `${hours} min` : ''} ${Math.floor( averageTalkTimePerCall % 60 )} sec`; - return <>{timeInHoursAndMinutes}; + return ; } } ] @@ -125,12 +119,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { totalCalls } = params.data; if (!totalCalls) return <>-; - return ( - - ); + return ; } }, { @@ -144,12 +133,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { longhornCalls } = params.data; if (!longhornCalls) return <>-; - return ( - - ); + return ; } }, { @@ -163,12 +147,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { ameyoCalls } = params.data; if (!ameyoCalls) return <>-; - return ( - - ); + return ; } }, { @@ -182,12 +161,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { selfCalls } = params.data; if (!selfCalls) return <>-; - return ( - - ); + return ; } } ] @@ -206,7 +180,7 @@ export const AgentPerformanceColumns = [ const { coverageOfUnpaidCases } = params.data; if (!coverageOfUnpaidCases) return <>-; const formattedPercent = shortNumberNotation(coverageOfUnpaidCases) + '%'; - return <>{formattedPercent}; + return ; } }, { @@ -221,7 +195,7 @@ export const AgentPerformanceColumns = [ const { avgAttemptPerUnpaidCase } = params.data; if (!avgAttemptPerUnpaidCase) return <>-; const number = formatNumber(avgAttemptPerUnpaidCase, true); - return <>{number}; + return ; } }, { @@ -235,7 +209,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { lanConnectivity } = params.data; if (!lanConnectivity) return <>-; - return
{Number(lanConnectivity).toFixed(2)}%
; + return ; } }, { @@ -250,7 +224,7 @@ export const AgentPerformanceColumns = [ const { averageUniqueDaysCalledPerCase } = params.data; if (!averageUniqueDaysCalledPerCase) return <>-; const number = formatNumber(averageUniqueDaysCalledPerCase, true); - return <>{number}; + return ; } } ] diff --git a/src/pages/SenseiTele/components/agentInput/index.module.scss b/src/pages/SenseiTele/components/agentInput/index.module.scss index b5004690..34028bff 100644 --- a/src/pages/SenseiTele/components/agentInput/index.module.scss +++ b/src/pages/SenseiTele/components/agentInput/index.module.scss @@ -1,31 +1,11 @@ -.agentTableContainer { - margin-top: 32px; +.agentInputTableContainer { position: relative; - .headerContainer { - display: flex; - justify-content: space-between; - padding-right: 32px; - - .header { - margin-left: 104px; - } - } - - .icon { - position: absolute; - top: -10px; - left: 32px; - } - .tableContainer { - background: linear-gradient(90deg, #fff8f0 0.32%, rgba(255, 250, 245, 0.24) 99.68%); - padding: 48px 32px 100px 32px; - - .downloadReport { - position: absolute; - top: 42px; - right: 37px; + .tableActions { + display: flex; + justify-content: space-between; + margin-bottom: 12px; } } @@ -35,7 +15,7 @@ } :global { - .agentInput { + .agentInputTable { .ag-header-viewport { .ag-header-row-column-group { > div:nth-child(1) { @@ -53,20 +33,6 @@ } > div:nth-child(2) { - background-color: var(--orange-oldlase); - color: var(--orange-dark); - pointer-events: none; - text-align: center; - font-family: Inter; - font-size: 14px; - font-style: normal; - font-weight: 600; - line-height: 20px; - /* 142.857% */ - letter-spacing: -0.175px; - } - - > div:nth-child(3) { background-color: var(--pale-yellow); color: var(--navi-color-yellow-dark); pointer-events: none; @@ -192,14 +158,13 @@ display: none; } - .ag-cell-value { - cursor: text; - } - .ag-floating-top { font-weight: 900; color: var(--navi-color-gray-c1) !important; border-bottom: 0px !important; + .ag-row { + background-color: var(--bg-primary) !important; + } } .ag-sort-indicator-icon { @@ -209,9 +174,11 @@ .ag-row-last { border-bottom: var(--ag-borders-row) var(--ag-row-border-color) !important; } + .ag-header-cell-text { line-height: 18px; } + .ag-horizontal-left-spacer { visibility: hidden; } diff --git a/src/pages/SenseiTele/components/agentInput/index.tsx b/src/pages/SenseiTele/components/agentInput/index.tsx index 5f4df5ed..22f62c60 100644 --- a/src/pages/SenseiTele/components/agentInput/index.tsx +++ b/src/pages/SenseiTele/components/agentInput/index.tsx @@ -99,7 +99,7 @@ const Index: React.FC = props => { const pinnedRow = useMemo(() => { if (tableData.length === 0) return []; const data: IAgentPerformance = { - agentName: 'Overall Input', + agentName: 'Overall', allocatedCases: 0, unpaidCases: 0, avgTalkTimePerCall: 0, @@ -155,24 +155,22 @@ const Index: React.FC = props => { }, [tableData]); return ( -
-
- - Agent Input - -
- - +
- +
+ + Agents Effort Summary + + +
= props => { } suppressRowClickSelection={true} suppressRowDrag={false} + alternateRowColor="var(--greyscale-content-4)" />
diff --git a/src/pages/SenseiTele/components/agentPerformance/ColumnDefs.tsx b/src/pages/SenseiTele/components/agentPerformance/ColumnDefs.tsx index 6cc1c12d..3d175d0a 100644 --- a/src/pages/SenseiTele/components/agentPerformance/ColumnDefs.tsx +++ b/src/pages/SenseiTele/components/agentPerformance/ColumnDefs.tsx @@ -10,6 +10,9 @@ import { levelColorsList, levelConstructList } from '@cp/src/components/AgentprogressMeter/constant'; +import TableCellRenderer from '@cp/src/components/TableCellRenderer'; +import CellRendererShortNumber from '@cp/src/components/TableCellRenderer/CellRendererShortNumber'; +import { Cell } from 'recharts'; export const AgentPerformanceColumns = [ { @@ -65,9 +68,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { allocatedCases } = params.data; if (!allocatedCases) return <>-; - const value = formatNumber(allocatedCases, true); - const shortnumber = shortNumberNotation(allocatedCases, 1); - return ; + return ; } }, { @@ -82,12 +83,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { allocatedEmi } = params.data; if (!allocatedEmi) return <>-; - return ( - - ); + return ; } } ] @@ -106,9 +102,7 @@ export const AgentPerformanceColumns = [ const { emiCollectionEfficiency } = params.data; if (!emiCollectionEfficiency) return <>-; return ( -
- {shortNumberNotation(emiCollectionEfficiency, 2)}% -
+ ); } }, @@ -123,9 +117,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { dpdAchievement } = params.data; if (!dpdAchievement) return <>-; - return ( -
{shortNumberNotation(dpdAchievement, 2)}%
- ); + return ; } } ] @@ -143,12 +135,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { cashCollected } = params.data; if (!cashCollected) return <>-; - return ( - - ); + return ; } }, { @@ -162,12 +149,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { todayDRR } = params.data; if (!todayDRR) return <>-; - return ( - - ); + return ; } }, { @@ -181,12 +163,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { todayCashCollected } = params.data; if (!todayCashCollected) return <>-; - return ( - - ); + return ; } }, { @@ -200,12 +177,7 @@ export const AgentPerformanceColumns = [ cellRenderer: (params: ICellRendererParams) => { const { collectedEmi } = params.data; if (!collectedEmi) return <>-; - return ( - - ); + return ; } } ] diff --git a/src/pages/SenseiTele/components/agentPerformance/index.module.scss b/src/pages/SenseiTele/components/agentPerformance/index.module.scss index b7327444..22802a0b 100644 --- a/src/pages/SenseiTele/components/agentPerformance/index.module.scss +++ b/src/pages/SenseiTele/components/agentPerformance/index.module.scss @@ -51,20 +51,6 @@ } > div:nth-child(2) { - background-color: var(--orange-oldlase); - color: var(--orange-dark); - pointer-events: none; - text-align: center; - font-family: Inter; - font-size: 14px; - font-style: normal; - font-weight: 600; - line-height: 20px; - /* 142.857% */ - letter-spacing: -0.175px; - } - - > div:nth-child(3) { background-color: var(--pale-yellow); color: var(--navi-color-yellow-dark); pointer-events: none; @@ -199,14 +185,15 @@ font-weight: 900; color: var(--navi-color-gray-c1) !important; border-bottom: 0px !important; + .ag-row { + background-color: var(--bg-primary) !important; + } } .ag-sort-indicator-icon { padding-right: 4px; } - .ag-cell-value { - cursor: text; - } + .ag-row-last { border-bottom: var(--ag-borders-row) var(--ag-row-border-color) !important; } diff --git a/src/pages/SenseiTele/components/agentPerformance/index.tsx b/src/pages/SenseiTele/components/agentPerformance/index.tsx index 968d3eba..9b41cb4c 100644 --- a/src/pages/SenseiTele/components/agentPerformance/index.tsx +++ b/src/pages/SenseiTele/components/agentPerformance/index.tsx @@ -96,7 +96,7 @@ const Index: React.FC = props => { const pinnedRow = React.useMemo(() => { const data: IAgent = { - agentName: 'Overall Performance', + agentName: 'Overall', agentReferenceId: ' ', tlReferenceId: ' ', allocatedCases: 0, @@ -114,9 +114,11 @@ const Index: React.FC = props => { agentCycleTargetDPD: 0 }; if (!tableData?.length) return [data]; + let sumProductofEmiAllocatedAndDPDTarget = 0; const mappingWithDifferentCycles: { [key: string]: number } = {}; tableData.forEach((item: IAgent) => { mappingWithDifferentCycles[item.dueCycle] = item.agentCycleTargetDPD; + sumProductofEmiAllocatedAndDPDTarget += item.allocatedEmi * item.agentCycleTargetDPD ?? 0; data.allocatedCases += item.allocatedCases; data.allocatedEmiInThousands += item.allocatedEmiInThousands; data.allocatedEmi += item.allocatedEmi; @@ -138,7 +140,7 @@ const Index: React.FC = props => { distinctCount++; distinctSum += +mappingWithDifferentCycles[key]; } - data.dpdAchievement = data.emiCollectionEfficiency / (distinctSum / distinctCount); + data.dpdAchievement = (data.collectedEmi / sumProductofEmiAllocatedAndDPDTarget) * 100; return [data]; }, [tableData]); @@ -193,6 +195,7 @@ const Index: React.FC = props => { } suppressScrollOnNewData={true} suppressRowDrag={false} + alternateRowColor="var(--greyscale-content-4)" />
diff --git a/src/pages/SenseiTele/components/despositionSummary/ColumnDef.tsx b/src/pages/SenseiTele/components/despositionSummary/ColumnDef.tsx new file mode 100644 index 00000000..bdca8ceb --- /dev/null +++ b/src/pages/SenseiTele/components/despositionSummary/ColumnDef.tsx @@ -0,0 +1,179 @@ +import { ICellRendererParams } from 'ag-grid-community'; +import styles from './index.module.scss'; +import TableCellRenderer from '@cp/src/components/TableCellRenderer'; +import CellRendererShortNumber from '@cp/src/components/TableCellRenderer/CellRendererShortNumber'; + +export const AgentPerformanceColumns = [ + { + headerName: '', + children: [ + { + field: 'agentName', + headerName: 'Agent Name', + maxWidth: 180, + pinned: 'left', + suppressMovable: true, + sortable: true, + cellRenderer: (params: ICellRendererParams) => { + const { agentName } = params.data || {}; + return ; + } + }, + { + field: 'dueCycle', + wrapHeaderText: true, + headerName: 'Cycle', + sortable: true, + autoHeaderHeight: true, + width: 90, + pinned: 'left', + cellRenderer: (params: ICellRendererParams) => { + const { dueCycle } = params.data; + return ; + } + }, + { + field: 'unpaidCases', + wrapHeaderText: true, + headerName: ' Unpaid Cases', + sortable: true, + serverSortable: true, + autoHeaderHeight: true, + cellClass: styles.border, + headerClass: styles.border, + width: 100, + pinned: 'left', + cellRenderer: (params: ICellRendererParams) => { + const { unpaidCases } = params.data; + return ; + } + } + ] + }, + { + headerName: "Today's PTP's", + children: [ + { + field: 'ptpTodayCount', + headerName: 'PTP (Count)', + width: 120, + suppressMovable: true, + sortable: true, + cellRenderer: (params: ICellRendererParams) => { + const { ptpTodayCount } = params.data || {}; + return ; + } + }, + { + field: 'ptpTodayEmi', + wrapHeaderText: true, + headerName: 'PTP (EMI)', + sortable: true, + autoHeaderHeight: true, + width: 120, + cellClass: styles.border, + headerClass: styles.border, + cellRenderer: (params: ICellRendererParams) => { + const { ptpTodayEmi } = params.data; + return ; + } + } + ] + }, + { + headerName: 'Overall Feedback Quality', + children: [ + { + field: 'ptpCases', + wrapHeaderText: true, + headerName: 'PTP', + sortable: true, + serverSortable: true, + autoHeaderHeight: true, + width: 90, + cellRenderer: (params: ICellRendererParams) => { + const { ptpCases } = params.data; + return ; + } + }, + { + field: 'brokenPtp', + wrapHeaderText: true, + headerName: 'BPTP', + sortable: true, + serverSortable: true, + autoHeaderHeight: true, + width: 100, + cellRenderer: (params: ICellRendererParams) => { + const { brokenPtp } = params.data; + return ; + } + }, + { + field: 'neutral', + wrapHeaderText: true, + headerName: 'Neutral', + sortable: true, + serverSortable: true, + autoHeaderHeight: true, + width: 90, + cellRenderer: (params: ICellRendererParams) => { + const { neutral } = params.data; + return ; + } + }, + { + field: 'softNonContactable', + wrapHeaderText: true, + headerName: 'Soft NC', + sortable: true, + serverSortable: true, + autoHeaderHeight: true, + width: 100, + cellRenderer: (params: ICellRendererParams) => { + const { softNonContactable } = params.data; + return ; + } + }, + { + field: 'hardNonContactable', + wrapHeaderText: true, + headerName: 'Hard NC', + sortable: true, + serverSortable: true, + autoHeaderHeight: true, + width: 100, + cellRenderer: (params: ICellRendererParams) => { + const { hardNonContactable } = params.data; + return ; + } + }, + { + field: 'dispute', + wrapHeaderText: true, + headerName: 'Dispute / RTP', + sortable: true, + serverSortable: true, + autoHeaderHeight: true, + width: 100, + cellRenderer: (params: ICellRendererParams) => { + const { dispute } = params.data; + return ; + } + }, + { + field: 'expired', + wrapHeaderText: true, + headerName: 'Expired', + sortable: true, + serverSortable: true, + autoHeaderHeight: true, + width: 100, + cellRenderer: (params: ICellRendererParams) => { + const { expired } = params.data; + return ; + } + } + ] + } +]; diff --git a/src/pages/SenseiTele/components/despositionSummary/index.module.scss b/src/pages/SenseiTele/components/despositionSummary/index.module.scss new file mode 100644 index 00000000..6a210c0c --- /dev/null +++ b/src/pages/SenseiTele/components/despositionSummary/index.module.scss @@ -0,0 +1,189 @@ +.agentDepositionTableContainer { + position: relative; + margin-top: 120px; + + .tableContainer { + .tableActions { + display: flex; + justify-content: space-between; + // padding-right: 32px; + margin-bottom: 12px; + } + } + + .border { + border-right: 1px solid var(--navi-color-gray-border) !important; + } +} + +:global { + .agentDepositionSummary { + .ag-header-viewport { + .ag-header-row-column-group { + > div:nth-child(1) { + background-color: var(--green-light); + color: var(--green-base); + pointer-events: none; + text-align: center; + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: 20px; + /* 142.857% */ + letter-spacing: -0.175px; + } + + > div:nth-child(2) { + background-color: var(--pale-yellow); + color: var(--navi-color-yellow-dark); + pointer-events: none; + text-align: center; + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: 20px; + /* 142.857% */ + letter-spacing: -0.175px; + } + + .ag-header-group-cell-label { + flex: none; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + } + + text-align: center; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: 20px; + letter-spacing: -0.175px; + } + } + + .ag-pinned-left-cols-container { + .ag-cell { + padding-right: 5px !important; + } + } + + .ag-header-row { + .ag-header-cell { + padding-left: 16px; + padding-right: 0px; + } + } + + .ag-row { + .ag-cell { + padding-left: 16px; + padding-right: 0px; + } + } + + .ag-pinned-left-header { + .ag-header-cell { + padding-right: 10px; + } + + .ag-header-group-cell { + background-color: unset !important; + padding-left: 5px; + } + } + + .ag-center-cols-clipper { + min-height: unset !important; + } + + .ag-header-group-cell { + border-right: 1px solid var(--greyscale-light); + padding: 0 !important; + margin: 0 !important; + } + + .ag-header-group-cell-label { + display: flex; + align-items: center; + justify-content: center; + padding: 0; + } + + .ag-header-group-cell { + .ag-header-group-text { + font-size: 14px !important; + } + + :nth-child(1) { + display: inline-block; + align-items: center; + justify-content: center; + text-align: center; + width: 100%; + } + } + + .paginationWrapperClasses { + overflow: unset; + z-index: var(--z-index-pagination-wrapper); + bottom: 4px; + + > div:nth-child(1) { + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + } + } + + .customHeaderManagement { + width: 140px; + text-align: center; + + .ag-header-cell-comp-wrapper { + .ag-header-cell-label { + display: grid; + margin: auto; + } + } + + span { + text-overflow: inherit; + text-wrap: wrap; + width: 42px; // according to figma width + } + } + + .ag-react-container { + display: none; + } + + .ag-floating-top { + font-weight: 900; + color: var(--navi-color-gray-c1) !important; + border-bottom: 0px !important; + + .ag-row { + background-color: var(--bg-primary) !important; + } + } + + .ag-sort-indicator-icon { + padding-right: 4px; + } + + .ag-row-last { + border-bottom: var(--ag-borders-row) var(--ag-row-border-color) !important; + } + + .ag-header-cell-text { + line-height: 18px; + } + + .ag-horizontal-left-spacer { + visibility: hidden; + } + } +} diff --git a/src/pages/SenseiTele/components/despositionSummary/index.tsx b/src/pages/SenseiTele/components/despositionSummary/index.tsx new file mode 100644 index 00000000..b1add996 --- /dev/null +++ b/src/pages/SenseiTele/components/despositionSummary/index.tsx @@ -0,0 +1,192 @@ +import Download from '@cp/src/assets/images/icons/Download'; +import Loader from '@cp/src/components/Loader/Loader'; +import { RootState } from '@cp/src/store'; +import { DateFormat, dateFormat } from '@cp/src/utils/DateHelper'; +import { AgTable, Pagination } from '@navi/web-ui/lib/components'; +import { Button, Typography } from '@navi/web-ui/lib/primitives'; +import { SortChangedEvent } from 'ag-grid-community'; +import { AgGridReact } from 'ag-grid-react'; +import cx from 'classnames'; +import React, { useEffect, useMemo, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { IDepositionSummary, IFilters } from '../../types'; +import { AgentPerformanceColumns } from './ColumnDef'; +import styles from './index.module.scss'; + +const Index: React.FC = props => { + const { params, updateAndNavigate, url } = props; + const ref = useRef>(null); + const [columnData, setColumnData] = React.useState(AgentPerformanceColumns); + const tableData = useSelector((state: RootState) => state.teleSensei.despositionSummaryFilter); + const loading = useSelector((state: RootState) => state.teleSensei.loadingDespositionSummary); + const dispatch = useDispatch(); + const handleSort = (e: SortChangedEvent) => { + if (e?.source === 'gridOptionsChanged') return; + const newParams = { ...params }; + const sortModel = e?.columnApi?.getColumnState(); + const sortedColumns = sortModel?.find(item => item.sort !== null); + if (!sortedColumns) { + delete newParams?.sortColumnDisposition; + delete newParams?.sortOrderDisposition; + newParams.pageNoDisposition = '1'; + updateAndNavigate(newParams); + ref?.current?.api.paginationGoToPage(0); + return; + } + const { colId, sort } = sortedColumns; + newParams.sortColumnDisposition = colId; + newParams.sortOrderDisposition = sort as string; + newParams.pageNoDisposition = '1'; + ref?.current?.api.paginationGoToPage(0); + updateAndNavigate(newParams); + }; + + useEffect(() => { + if (!params?.sortColumnDisposition) { + setColumnData(AgentPerformanceColumns); + } + const newColumnData = AgentPerformanceColumns.map(item => { + const children = item.children?.map(child => { + if (child.field === params?.sortColumnDisposition) { + return { + ...child, + sort: params?.sortOrderDisposition as any + }; + } + return child; + }); + return { + ...item, + children + }; + }); + setColumnData(newColumnData); + }, []); + + const handlePageChange = (e: number) => { + const newParams = { ...params }; + const pageNumber = e.toString(); + newParams.pageNoDisposition = pageNumber; + ref?.current?.api.paginationGoToPage(e - 1); + updateAndNavigate(newParams); + }; + + const handlePageSizeChange = (e: number) => { + const newParams = { ...params }; + const pageSize = e.toString(); + newParams.pageSizeDisposition = pageSize; + ref?.current?.api.paginationSetPageSize(e); + updateAndNavigate(newParams); + }; + + const pageNumber = () => { + ref?.current?.api?.paginationGoToPage(Number(params?.pageNoDisposition) - 1); + return Number(params?.pageNoDisposition); + }; + const pageSize = Number(params?.pageSizeInput); + + const handleDownloadReport = () => { + // if (tableData.length > 500) { + // dispatch(getAgentInputDataCSV()); + // return; + // } + ref?.current?.api?.exportDataAsCsv({ + fileName: `Agent-desposition-${dateFormat(new Date(), DateFormat.FILE_DATE_FORMAT)}` + }); + }; + + const pinnedRow = useMemo(() => { + const data: IDepositionSummary = { + agentName: 'Overall', + agentReferenceId: 'string;', + tlReferenceId: 'string;', + unpaidCases: 0, + ptpCases: 0, + brokenPtp: 0, + neutral: 0, + softNonContactable: 0, + hardNonContactable: 0, + dispute: 0, + expired: 0, + dueCycle: '', + ptpTodayCount: 0, + ptpTodayEmi: 0 + }; + + tableData.forEach((item: IDepositionSummary) => { + data.unpaidCases += item.unpaidCases ?? 0; + data.ptpCases += item.ptpCases ?? 0; + data.brokenPtp += item.brokenPtp ?? 0; + data.neutral += item.neutral ?? 0; + data.softNonContactable += item.softNonContactable ?? 0; + data.hardNonContactable += item.hardNonContactable ?? 0; + data.dispute += item.dispute; + data.expired += item.expired; + data.ptpTodayCount += item.ptpTodayCount ?? 0; + data.ptpTodayEmi += item.ptpTodayEmi ?? 0; + }); + + return [data]; + }, [tableData]); + + return ( +
+
+
+ + Dispositions Summary + + +
+ + } + suppressRowClickSelection={true} + suppressRowDrag={false} + sizeColumnsToFit + alternateRowColor="var(--greyscale-content-4)" + /> +
+ +
+ ); +}; + +export default Index; diff --git a/src/pages/SenseiTele/components/filters/index.tsx b/src/pages/SenseiTele/components/filters/index.tsx index c5abe270..6a0e424b 100644 --- a/src/pages/SenseiTele/components/filters/index.tsx +++ b/src/pages/SenseiTele/components/filters/index.tsx @@ -9,6 +9,9 @@ import { FilterType, IFilters } from '../../types'; import styles from './index.module.scss'; import { DateFormat, dateFormat } from '@cp/src/utils/DateHelper'; import cx from 'classnames'; +import { addClickstreamEvent } from '@cp/src/service/clickStreamEventService'; +import { CLICKSTREAM_EVENT_NAMES } from '@cp/src/service/clickStream.constant'; +import { TABLE_DEFAULT_VALUES } from '../../constants'; const Index: React.FC = props => { const { params, updateAndNavigate } = props; @@ -91,7 +94,9 @@ const Index: React.FC = props => { } updatedParams['pageNoPerformance'] = '1'; updatedParams['pageNoInput'] = '1'; - + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_TELE_SENSEI_FILTERS, { + ...updatedParams + }); updateAndNavigate(updatedParams); }; @@ -99,7 +104,12 @@ const Index: React.FC = props => { const updatedParams = { ...params }; delete updatedParams['agentId']; delete updatedParams['teamLeadId']; - updatedParams['pageNo'] = '1'; + updatedParams['pageNoInput'] = TABLE_DEFAULT_VALUES.page; + updatedParams['pageNoPerformance'] = TABLE_DEFAULT_VALUES.page; + updatedParams['pageNoDisposition'] = TABLE_DEFAULT_VALUES.page; + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_TELE_SENSEI_FILTERS, { + ...updatedParams + }); updateAndNavigate(updatedParams); }; diff --git a/src/pages/SenseiTele/index.module.scss b/src/pages/SenseiTele/index.module.scss new file mode 100644 index 00000000..1baec888 --- /dev/null +++ b/src/pages/SenseiTele/index.module.scss @@ -0,0 +1,35 @@ +.agentTableContainer { + margin-top: 32px; + position: relative; + + .headerContainer { + display: flex; + justify-content: space-between; + padding-right: 32px; + + .header { + margin-left: 104px; + } + } + + .icon { + position: absolute; + top: -10px; + left: 32px; + } + + .tableContainer { + background: linear-gradient(90deg, #fff8f0 0.32%, rgba(255, 250, 245, 0.24) 99.68%); + padding: 48px 32px 100px 32px; + + .downloadReport { + position: absolute; + top: 42px; + right: 37px; + } + } + + .border { + border-right: 1px solid var(--navi-color-gray-border) !important; + } +} diff --git a/src/pages/SenseiTele/index.tsx b/src/pages/SenseiTele/index.tsx index 387ab801..50b6eb94 100644 --- a/src/pages/SenseiTele/index.tsx +++ b/src/pages/SenseiTele/index.tsx @@ -1,25 +1,35 @@ import React, { useEffect } from 'react'; import Filter from './components/filters'; import AgentPerformance from './components/agentPerformance'; -import { getAgentInputData, getAgentPerformanceData } from './action'; +import { getAgentInputData, getAgentPerformanceData, getDespositionSummary } from './action'; import { useDispatch, useSelector } from 'react-redux'; import { createQueryParams, readQueryParams } from '@cp/src/utils/QueryParamsHelper'; -import { IAgent, IAgentPerformance, IAgentPerformanceParams } from './types'; +import { IAgent, IAgentPerformance, IAgentPerformanceParams, IDepositionSummary } from './types'; import { AGENT_PERFORMANCE_TABLE, TABLE_DEFAULT_VALUES, pollingInterval } from './constants'; import { useLocation, useNavigate } from 'react-router'; import AgentInput from './components/agentInput'; import Loader from '@cp/src/components/Loader/Loader'; import store, { RootState } from '@cp/src/store'; -import { setAgentPerformanceFiltered, setFilteredData } from './reducer'; +import { + setAgentPerformanceFiltered, + setDespositionSummaryFilter, + setFilteredData +} from './reducer'; import { poll } from '@cp/src/utils/polling'; import { noop } from '@navi/web-ui/lib/utils/common'; import { getServerTime } from '@cp/src/utils/DateHelper'; +import DespositionSummary from './components/despositionSummary'; +import cx from 'classnames'; +import styles from './index.module.scss'; +import { Typography } from '@navi/web-ui/lib/primitives'; +import AgentInputIcon from '@cp/src/assets/icons/AgentInputIcon'; +import { addClickstreamEvent } from '@cp/src/service/clickStreamEventService'; +import { CLICKSTREAM_EVENT_NAMES } from '@cp/src/service/clickStream.constant'; const Index = () => { const dispatch = useDispatch(); - const { loading, loadingAgentPerformance, agentData, IAgentPerformance } = useSelector( - (state: RootState) => state.teleSensei - ); + const { loading, loadingAgentPerformance, agentData, IAgentPerformance, despositionSummary } = + useSelector((state: RootState) => state.teleSensei); const location = useLocation(); const { [AGENT_PERFORMANCE_TABLE]: params } = readQueryParams(); @@ -35,9 +45,11 @@ const Index = () => { useEffect(() => { dispatch(getAgentInputData()); dispatch(getAgentPerformanceData()); + dispatch(getDespositionSummary()); }, []); useEffect(() => { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_TELE_SENSEI_PAGE_LOAD); const queryParams: IAgentPerformanceParams = { ...params }; if (!queryParams?.pageNoPerformance) { queryParams['pageNoPerformance'] = TABLE_DEFAULT_VALUES.page; @@ -51,6 +63,12 @@ const Index = () => { if (!queryParams?.pageSizeInput) { queryParams['pageSizeInput'] = TABLE_DEFAULT_VALUES.rowsPerPage; } + if (!queryParams?.pageNoDisposition) { + queryParams['pageNoDisposition'] = '1'; + } + if (!queryParams?.pageSizeDisposition) { + queryParams['pageSizeDisposition'] = '10'; + } updateAndNavigate(queryParams); }, []); @@ -59,6 +77,7 @@ const Index = () => { if (!IAgentPerformance) return; let filteredAgentData: IAgent[] = []; let filteredAgentInput: IAgentPerformance[] = []; + let filteredDespositionData: IDepositionSummary[] = []; // filtering this out if both the filter selected if (params?.teamLeadId && params?.agentId) { const dataAfterTLFilter = agentData?.filter( @@ -78,6 +97,15 @@ const Index = () => { ); dispatch(setAgentPerformanceFiltered(filteredAgentInput)); + const dataAfterTLFilterDisposition = despositionSummary?.filter( + agentData => agentData?.tlReferenceId === params?.teamLeadId + ); + + filteredDespositionData = dataAfterTLFilterDisposition?.filter(agentData => + selectedAgents?.some((agentID: string) => agentData?.agentReferenceId === agentID) + ); + dispatch(setDespositionSummaryFilter(filteredDespositionData)); + return; } if (params?.teamLeadId && !params?.agentId) { @@ -90,6 +118,11 @@ const Index = () => { agentData => agentData?.tlReferenceId === params?.teamLeadId ); dispatch(setAgentPerformanceFiltered(filteredAgentInput)); + + filteredDespositionData = despositionSummary?.filter( + agentData => agentData?.tlReferenceId === params?.teamLeadId + ); + dispatch(setDespositionSummaryFilter(filteredDespositionData)); return; } if (!params?.teamLeadId && params?.agentId) { @@ -103,6 +136,11 @@ const Index = () => { selectedAgents?.some((agentID: string) => agentData?.agentReferenceId === agentID) ); dispatch(setAgentPerformanceFiltered(filteredAgentInput)); + + filteredDespositionData = despositionSummary?.filter(agentData => + selectedAgents?.some((agentID: string) => agentData?.agentReferenceId === agentID) + ); + dispatch(setDespositionSummaryFilter(filteredDespositionData)); return; } if (!params?.teamLeadId && !params?.agentId) { @@ -111,15 +149,33 @@ const Index = () => { filteredAgentInput = IAgentPerformance; dispatch(setAgentPerformanceFiltered(filteredAgentInput)); + + filteredDespositionData = despositionSummary; + dispatch(setDespositionSummaryFilter(filteredDespositionData)); return; } - }, [location, agentData, IAgentPerformance]); + }, [location, agentData, IAgentPerformance, despositionSummary]); return (
- +
+
+ + Agent Input + +
+ +
+ + +
+
); diff --git a/src/pages/SenseiTele/reducer/index.ts b/src/pages/SenseiTele/reducer/index.ts index 93140ace..ef70843e 100644 --- a/src/pages/SenseiTele/reducer/index.ts +++ b/src/pages/SenseiTele/reducer/index.ts @@ -7,13 +7,17 @@ import { IAgentData } from '../types'; const initialState: IAgentData = { agentData: [], agentNames: [], - lastRefreshedTimeStamp: '', + lastRefreshedTimeStampPerformance: '', tlNames: [], loading: false, filteredData: [], IAgentPerformance: [], IAgentPerformanceFilter: [], - loadingAgentPerformance: false + loadingAgentPerformance: false, + lastRefreshedTimeStampInput: '', + despositionSummary: [], + loadingDespositionSummary: false, + despositionSummaryFilter: [] }; const TeleSenseiSlice = createSlice({ @@ -44,6 +48,16 @@ const TeleSenseiSlice = createSlice({ }, setAgentPerformanceFiltered(state, action) { state.IAgentPerformanceFilter = action.payload; + }, + setDespositionSummaryFilter(state, action) { + state.despositionSummaryFilter = action.payload; + }, + setLoadingDespositionSummary(state, action) { + state.loadingDespositionSummary = action.payload; + }, + setDespositionSummary(state, action) { + const { data } = action.payload; + state.despositionSummary = data; } } }); @@ -54,7 +68,10 @@ export const { setFilteredData, setAgentPerformance, setLoadingAgentPerformance, - setAgentPerformanceFiltered + setAgentPerformanceFiltered, + setDespositionSummaryFilter, + setLoadingDespositionSummary, + setDespositionSummary } = TeleSenseiSlice.actions; export default TeleSenseiSlice.reducer; diff --git a/src/pages/SenseiTele/types/index.ts b/src/pages/SenseiTele/types/index.ts index b1d27c2d..15bb1464 100644 --- a/src/pages/SenseiTele/types/index.ts +++ b/src/pages/SenseiTele/types/index.ts @@ -14,6 +14,10 @@ export interface IAgentPerformanceParams { sortOrderInput?: string; sortColumnPerformance?: string; sortOrderPerformance?: string; + sortColumnDisposition?: string; + sortOrderDisposition?: string; + pageNoDisposition?: string; + pageSizeDisposition?: string; } export interface IFilters { @@ -61,6 +65,9 @@ export interface IAgentData { loadingAgentPerformance: boolean; IAgentPerformanceFilter: IAgentPerformance[]; lastRefreshedTimeStampInput: string; + despositionSummary: IDepositionSummary[]; + loadingDespositionSummary: boolean; + despositionSummaryFilter: IDepositionSummary[]; } export enum FilterType { @@ -91,3 +98,20 @@ export interface IAgentPerformance { connectedCallsAmeyoLH: number; averageUniqueDaysCalledPerCase: number; } + +export interface IDepositionSummary { + agentName: string; + agentReferenceId: string; + tlReferenceId: string; + unpaidCases: number; + ptpCases: number; + brokenPtp: number; + neutral: number; + softNonContactable: number; + hardNonContactable: number; + dispute: number; + expired: number; + dueCycle: string; + ptpTodayCount: number; + ptpTodayEmi: number; +} diff --git a/src/service/clickStream.constant.ts b/src/service/clickStream.constant.ts index 542da431..a943846d 100644 --- a/src/service/clickStream.constant.ts +++ b/src/service/clickStream.constant.ts @@ -517,6 +517,14 @@ export const CLICKSTREAM_EVENT_NAMES = Object.freeze({ LH_FIELD_DASHBOARD_SIDE_PANEL_CLICK: { name: 'LH_FIELD_DASHBOARD_SIDE_PANEL_CLICK', description: 'Field dashboard side panel click' + }, + LH_TELE_SENSEI_SIDE_PANEL_CLICK: { + name: 'LH_TELE_SENSEI_SIDE_PANEL_CLICK', + description: 'Tele sensei side panel click' + }, + LH_TELE_SENSEI_FILTERS: { + name: 'LH_TELE_SENSEI_FILTERS', + description: 'Tele sensei applied filters' } }); diff --git a/src/utils/ApiHelper.ts b/src/utils/ApiHelper.ts index 5cb28ce2..7033e55e 100644 --- a/src/utils/ApiHelper.ts +++ b/src/utils/ApiHelper.ts @@ -153,7 +153,8 @@ export enum ApiKeys { GET_AGENT_PERFORMANCE, GET_AGENT_INPUT, CONVERT_TIFF_TO_JPEG, - LEGAL_DOCS + LEGAL_DOCS, + GET_DEPOSITIONS } // TODO: try to get rid of `as` @@ -291,6 +292,7 @@ API_URLS[ApiKeys.SET_CHAT_AGENT_STATUS] = '/human-reminder-chat/{status}'; API_URLS[ApiKeys.GET_AGENT_INPUT] = '/team-lead/dashboard/reportees-inputs'; API_URLS[ApiKeys.GET_AGENT_PERFORMANCE] = '/team-lead/dashboard/reportees-performance'; API_URLS[ApiKeys.CONVERT_TIFF_TO_JPEG] = '/v1/documents/convert/tiff-to-jpeg'; +API_URLS[ApiKeys.GET_DEPOSITIONS] = '/team-lead/dashboard/reportees-disposition-summary'; // TODO: try to get rid of `as` const MOCK_API_URLS: Record = {} as Record; diff --git a/src/utils/commonUtils.ts b/src/utils/commonUtils.ts index 6041a189..0e4dd905 100644 --- a/src/utils/commonUtils.ts +++ b/src/utils/commonUtils.ts @@ -67,7 +67,6 @@ export const shortNumberNotation = (value: any = 0, decimals = 2, separator = '' if (!value) return ''; const parsedValue = Number(value); - const abbreviations = [ { threshold: 1e7, symbol: 'Cr' }, { threshold: 1e5, symbol: 'L' },