Tele sensei v2 (#778)

* TP-42467 |agent performance v2| Aman Singh

* TP-42467 |lint issue fixed| Aman Singh

* TP-42467 |lint issue fixed| Aman Singh

* TP-42467 |lint issue fixed| Aman Singh

* TP-42467 |lint issue fixed| Aman Singh

* TP-42467 |fixed design issues| Aman Singh

* TP-42467 |fixed design issues| Aman Singh

* TP-42467 |api tested on local | Aman Singh

* TP-42467 |prettier issue fixed| Aman Singh
This commit is contained in:
Aman Singh
2024-01-12 16:55:25 +05:30
committed by GitHub
parent efd524ec45
commit c6d11866f0
21 changed files with 873 additions and 177 deletions

View File

@@ -5,4 +5,5 @@
-webkit-box-orient: vertical;
line-height: 24px;
width: fit-content;
cursor: text;
}

View File

@@ -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<ICellRendererShortNumber> = 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 <EllipsisText text={'-'} lineClamp={lineClamp} />;
}
let shorthandNumber = value == 0 ? '0' : shortNumberNotation(value, significantDigits);
let toolTipContent = formatNumber(value, true);
if (String(value) === shorthandNumber) {
if (isCurrency) {
shorthandNumber = `${shorthandNumber}`;
}
return <EllipsisText text={shorthandNumber ?? '-'} lineClamp={lineClamp} />;
}
if (isCurrency) {
shorthandNumber = `${shorthandNumber}`;
toolTipContent = `${toolTipContent}`;
}
return (
<Tooltip placement="top" hiddenPadding={hiddenPadding} hideStrategy="referenceHidden">
<TooltipTrigger tooltipTriggerClassName="tooltipTriggerWrapper">
<EllipsisText text={shorthandNumber ?? '-'} lineClamp={lineClamp} />
</TooltipTrigger>
<TooltipContent
className="tooltipWrapper"
arrowColor="var(--tooltip-background-color)"
bgColor="var(--tooltip-background-color)"
>
{toolTipContent}
</TooltipContent>
</Tooltip>
);
};
export default CellRendererShortNumber;

View File

@@ -496,7 +496,7 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) {
/>
) : null}
{isTeamLead ? (
{isInternalTeamLead ? (
<>
<SidebarLinks
key={APP_ROUTES.HR_DASHBOARD.path}

View File

@@ -3,8 +3,10 @@ import { Dispatch } from 'redux';
import {
setAgentPerformance,
setApiResponse,
setDespositionSummary,
setLoading,
setLoadingAgentPerformance
setLoadingAgentPerformance,
setLoadingDespositionSummary
} from '../reducer';
export const getAgentPerformanceData = () => (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));
});
};

View File

@@ -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 (
<RenderWithToolTip
value={shortNumberNotation(allocatedCases, 1)}
toolTipContent={value}
/>
);
return <CellRendererShortNumber value={allocatedCases} />;
}
},
{
@@ -66,10 +63,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { unpaidCases } = params.data;
if (!unpaidCases) return <>-</>;
const value = formatNumber(unpaidCases, true);
return (
<RenderWithToolTip value={shortNumberNotation(unpaidCases, 1)} toolTipContent={value} />
);
return <CellRendererShortNumber value={unpaidCases} />;
}
},
// {
@@ -108,7 +102,7 @@ export const AgentPerformanceColumns = [
const timeInHoursAndMinutes = `${hours ? `${hours} min` : ''} ${Math.floor(
averageTalkTimePerCall % 60
)} sec`;
return <>{timeInHoursAndMinutes}</>;
return <TableCellRenderer value={timeInHoursAndMinutes} />;
}
}
]
@@ -125,12 +119,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { totalCalls } = params.data;
if (!totalCalls) return <>-</>;
return (
<RenderWithToolTip
value={shortNumberNotation(totalCalls, 1)}
toolTipContent={formatNumber(totalCalls, true)}
/>
);
return <CellRendererShortNumber value={totalCalls} />;
}
},
{
@@ -144,12 +133,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { longhornCalls } = params.data;
if (!longhornCalls) return <>-</>;
return (
<RenderWithToolTip
value={shortNumberNotation(longhornCalls, 1)}
toolTipContent={formatNumber(longhornCalls, true)}
/>
);
return <CellRendererShortNumber value={longhornCalls} />;
}
},
{
@@ -163,12 +147,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { ameyoCalls } = params.data;
if (!ameyoCalls) return <>-</>;
return (
<RenderWithToolTip
value={shortNumberNotation(ameyoCalls, 1)}
toolTipContent={formatNumber(ameyoCalls, true)}
/>
);
return <CellRendererShortNumber value={ameyoCalls} />;
}
},
{
@@ -182,12 +161,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { selfCalls } = params.data;
if (!selfCalls) return <>-</>;
return (
<RenderWithToolTip
value={shortNumberNotation(selfCalls, 1)}
toolTipContent={formatNumber(selfCalls, true)}
/>
);
return <CellRendererShortNumber value={selfCalls} />;
}
}
]
@@ -206,7 +180,7 @@ export const AgentPerformanceColumns = [
const { coverageOfUnpaidCases } = params.data;
if (!coverageOfUnpaidCases) return <>-</>;
const formattedPercent = shortNumberNotation(coverageOfUnpaidCases) + '%';
return <>{formattedPercent}</>;
return <TableCellRenderer value={formattedPercent} />;
}
},
{
@@ -221,7 +195,7 @@ export const AgentPerformanceColumns = [
const { avgAttemptPerUnpaidCase } = params.data;
if (!avgAttemptPerUnpaidCase) return <>-</>;
const number = formatNumber(avgAttemptPerUnpaidCase, true);
return <>{number}</>;
return <TableCellRenderer value={number} />;
}
},
{
@@ -235,7 +209,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { lanConnectivity } = params.data;
if (!lanConnectivity) return <>-</>;
return <div className={cx(styles.nameField)}>{Number(lanConnectivity).toFixed(2)}%</div>;
return <TableCellRenderer value={`${Number(lanConnectivity).toFixed(2)}%`} />;
}
},
{
@@ -250,7 +224,7 @@ export const AgentPerformanceColumns = [
const { averageUniqueDaysCalledPerCase } = params.data;
if (!averageUniqueDaysCalledPerCase) return <>-</>;
const number = formatNumber(averageUniqueDaysCalledPerCase, true);
return <>{number}</>;
return <TableCellRenderer value={number} />;
}
}
]

View File

@@ -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;
}

View File

@@ -99,7 +99,7 @@ const Index: React.FC<IFilters> = 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<IFilters> = props => {
}, [tableData]);
return (
<div className={cx(styles.agentTableContainer, 'agentInput')}>
<div className={styles.headerContainer}>
<Typography className={styles.header} color="var(--navi-color-gray-c1)" variant="h2">
Agent Input
</Typography>
</div>
<AgentInputIcon className={styles.icon} />
<div className={cx(styles.agentInputTableContainer, 'agentInputTable')}>
<div className={styles.tableContainer}>
<Button
startAdornment={<Download />}
className={styles.downloadReport}
variant="text"
onClick={handleDownloadReport}
disabled={tableData.length === 0}
>
Download Report
</Button>
<div className={styles.tableActions}>
<Typography color="var(--grayscale-2)" variant="h3">
Agents Effort Summary
</Typography>
<Button
startAdornment={<Download />}
className={styles.downloadReport}
variant="text"
onClick={handleDownloadReport}
disabled={tableData.length === 0}
>
Download Report
</Button>
</div>
<AgTable
ref={ref}
columnDefs={columnData}
@@ -207,6 +205,7 @@ const Index: React.FC<IFilters> = props => {
}
suppressRowClickSelection={true}
suppressRowDrag={false}
alternateRowColor="var(--greyscale-content-4)"
/>
</div>
<Loader show={loading} className={'loadingState'} animate={false} />

View File

@@ -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 <RenderWithToolTip value={shortnumber} toolTipContent={value} />;
return <CellRendererShortNumber value={allocatedCases} />;
}
},
{
@@ -82,12 +83,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { allocatedEmi } = params.data;
if (!allocatedEmi) return <>-</>;
return (
<RenderWithToolTip
value={'₹ ' + shortNumberNotation(allocatedEmi, 1)}
toolTipContent={formatAmount(allocatedEmi)}
/>
);
return <CellRendererShortNumber value={allocatedEmi} isCurrency />;
}
}
]
@@ -106,9 +102,7 @@ export const AgentPerformanceColumns = [
const { emiCollectionEfficiency } = params.data;
if (!emiCollectionEfficiency) return <>-</>;
return (
<div className={cx(styles.nameField)}>
{shortNumberNotation(emiCollectionEfficiency, 2)}%
</div>
<TableCellRenderer value={`${shortNumberNotation(emiCollectionEfficiency, 2)}%`} />
);
}
},
@@ -123,9 +117,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { dpdAchievement } = params.data;
if (!dpdAchievement) return <>-</>;
return (
<div className={cx(styles.nameField)}>{shortNumberNotation(dpdAchievement, 2)}%</div>
);
return <TableCellRenderer value={`${shortNumberNotation(dpdAchievement, 2)}%`} />;
}
}
]
@@ -143,12 +135,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { cashCollected } = params.data;
if (!cashCollected) return <>-</>;
return (
<RenderWithToolTip
value={'₹ ' + shortNumberNotation(cashCollected, 1)}
toolTipContent={'₹ ' + formatNumber(cashCollected, true)}
/>
);
return <CellRendererShortNumber value={cashCollected} isCurrency />;
}
},
{
@@ -162,12 +149,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { todayDRR } = params.data;
if (!todayDRR) return <>-</>;
return (
<RenderWithToolTip
value={'₹ ' + shortNumberNotation(todayDRR, 1)}
toolTipContent={'₹ ' + formatNumber(todayDRR, true)}
/>
);
return <CellRendererShortNumber value={todayDRR} isCurrency />;
}
},
{
@@ -181,12 +163,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { todayCashCollected } = params.data;
if (!todayCashCollected) return <>-</>;
return (
<RenderWithToolTip
value={'₹ ' + shortNumberNotation(todayCashCollected, 1)}
toolTipContent={'₹ ' + formatNumber(todayCashCollected, true)}
/>
);
return <CellRendererShortNumber value={todayCashCollected} isCurrency />;
}
},
{
@@ -200,12 +177,7 @@ export const AgentPerformanceColumns = [
cellRenderer: (params: ICellRendererParams) => {
const { collectedEmi } = params.data;
if (!collectedEmi) return <>-</>;
return (
<RenderWithToolTip
value={'₹ ' + shortNumberNotation(collectedEmi, 1)}
toolTipContent={'₹ ' + formatNumber(collectedEmi, true)}
/>
);
return <CellRendererShortNumber value={collectedEmi} isCurrency />;
}
}
]

View File

@@ -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;
}

View File

@@ -96,7 +96,7 @@ const Index: React.FC<IFilters> = 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<IFilters> = 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<IFilters> = 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<IFilters> = props => {
}
suppressScrollOnNewData={true}
suppressRowDrag={false}
alternateRowColor="var(--greyscale-content-4)"
/>
</div>
<Loader show={loading} className={'loadingState'} />

View File

@@ -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 <TableCellRenderer value={agentName} showToolTip toolTipContent={agentName} />;
}
},
{
field: 'dueCycle',
wrapHeaderText: true,
headerName: 'Cycle',
sortable: true,
autoHeaderHeight: true,
width: 90,
pinned: 'left',
cellRenderer: (params: ICellRendererParams) => {
const { dueCycle } = params.data;
return <TableCellRenderer value={dueCycle} />;
}
},
{
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 <CellRendererShortNumber value={unpaidCases} />;
}
}
]
},
{
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 <CellRendererShortNumber value={ptpTodayCount} />;
}
},
{
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 <CellRendererShortNumber isCurrency value={ptpTodayEmi} />;
}
}
]
},
{
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 <CellRendererShortNumber value={ptpCases} />;
}
},
{
field: 'brokenPtp',
wrapHeaderText: true,
headerName: 'BPTP',
sortable: true,
serverSortable: true,
autoHeaderHeight: true,
width: 100,
cellRenderer: (params: ICellRendererParams) => {
const { brokenPtp } = params.data;
return <CellRendererShortNumber value={brokenPtp} />;
}
},
{
field: 'neutral',
wrapHeaderText: true,
headerName: 'Neutral',
sortable: true,
serverSortable: true,
autoHeaderHeight: true,
width: 90,
cellRenderer: (params: ICellRendererParams) => {
const { neutral } = params.data;
return <CellRendererShortNumber value={neutral} />;
}
},
{
field: 'softNonContactable',
wrapHeaderText: true,
headerName: 'Soft NC',
sortable: true,
serverSortable: true,
autoHeaderHeight: true,
width: 100,
cellRenderer: (params: ICellRendererParams) => {
const { softNonContactable } = params.data;
return <CellRendererShortNumber value={softNonContactable} />;
}
},
{
field: 'hardNonContactable',
wrapHeaderText: true,
headerName: 'Hard NC',
sortable: true,
serverSortable: true,
autoHeaderHeight: true,
width: 100,
cellRenderer: (params: ICellRendererParams) => {
const { hardNonContactable } = params.data;
return <CellRendererShortNumber value={hardNonContactable} />;
}
},
{
field: 'dispute',
wrapHeaderText: true,
headerName: 'Dispute / RTP',
sortable: true,
serverSortable: true,
autoHeaderHeight: true,
width: 100,
cellRenderer: (params: ICellRendererParams) => {
const { dispute } = params.data;
return <CellRendererShortNumber value={dispute} />;
}
},
{
field: 'expired',
wrapHeaderText: true,
headerName: 'Expired',
sortable: true,
serverSortable: true,
autoHeaderHeight: true,
width: 100,
cellRenderer: (params: ICellRendererParams) => {
const { expired } = params.data;
return <CellRendererShortNumber value={expired} />;
}
}
]
}
];

View File

@@ -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;
}
}
}

View File

@@ -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<IFilters> = props => {
const { params, updateAndNavigate, url } = props;
const ref = useRef<AgGridReact<any>>(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<any>) => {
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 (
<div className={cx(styles.agentDepositionTableContainer, 'agentDepositionSummary')}>
<div className={styles.tableContainer}>
<div className={styles.tableActions}>
<Typography color="var(--grayscale-2)" variant="h3">
Dispositions Summary
</Typography>
<Button
startAdornment={<Download />}
className={styles.downloadReport}
variant="text"
onClick={handleDownloadReport}
disabled={tableData.length === 0}
>
Download Report
</Button>
</div>
<AgTable
ref={ref}
columnDefs={columnData}
style={{
height: 450
}}
headerHeight={70}
defaultColDef={{
suppressMovable: true,
wrapText: true,
autoHeight: true
}}
theme={'alpine'}
rowData={tableData}
onSortChanged={handleSort}
pagination
suppressPaginationPanel={true}
paginationPageSize={pageSize}
paginationWrapperClasses="paginationWrapperClasses"
animateRows
pinnedTopRowData={pinnedRow}
PaginationComponent={
<Pagination
key={pageSize}
totalCount={tableData.length}
pageSize={pageSize}
currentPage={pageNumber()}
pageSizeOptions={[10, 15, 20, 50]}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
/>
}
suppressRowClickSelection={true}
suppressRowDrag={false}
sizeColumnsToFit
alternateRowColor="var(--greyscale-content-4)"
/>
</div>
<Loader show={loading} className={'loadingState'} animate={false} />
</div>
);
};
export default Index;

View File

@@ -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<IFilters> = props => {
const { params, updateAndNavigate } = props;
@@ -91,7 +94,9 @@ const Index: React.FC<IFilters> = 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<IFilters> = 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);
};

View File

@@ -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;
}
}

View File

@@ -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 (
<div>
<Filter params={params} updateAndNavigate={updateAndNavigate} url={location.hash} />
<AgentPerformance params={params} updateAndNavigate={updateAndNavigate} url={location.hash} />
<AgentInput params={params} updateAndNavigate={updateAndNavigate} url={location.hash} />
<div className={styles.agentTableContainer}>
<div className={styles.headerContainer}>
<Typography className={styles.header} color="var(--navi-color-gray-c1)" variant="h2">
Agent Input
</Typography>
</div>
<AgentInputIcon className={styles.icon} />
<div className={styles.tableContainer}>
<AgentInput params={params} updateAndNavigate={updateAndNavigate} url={location.hash} />
<DespositionSummary
params={params}
updateAndNavigate={updateAndNavigate}
url={location.hash}
/>
</div>
</div>
<Loader show={loading || loadingAgentPerformance} />
</div>
);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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'
}
});

View File

@@ -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<ApiKeys, string> = {} as Record<ApiKeys, string>;

View File

@@ -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' },