TP-63767 | Showing incentive on longhorn (#991)

* TP-63746|Kunal|PR clean up

* TP-63767|Kunal| showing incentive earned on leaderboard and dashboard

* TP-63767|Kunal| showing incentive earned on leaderboard and dashboard

* TP-11|Kunal| created fixed height virtualization component

* TP-11|Kunal| created fixed height virtualization component

* TP-63767|Kunal|Showing incentive on longhorn

* TP-63767|Kunal|Showing incentive on longhorn

* TP-63767|Kunal|Showing incentive on longhorn

* TP-63767|Kunal|resolved PR comments

* TP-63767|Kunal|resolved PR comments

* TP-63767|Kunal|resolved PR comments

* TP-63767|Kunal|resolved PR comments

* TP-111|Kunal|added handling for tele lite agents

* Revert "TP-111|Kunal|added handling for tele lite agents"

This reverts commit c21ebf17dec4a17cc09aa3a1ad7ea0b4b8581725.
This commit is contained in:
Kunal Sharma
2024-06-12 18:01:18 +05:30
committed by GitHub
parent 9b21063ae8
commit 3f9efdffe1
10 changed files with 297 additions and 52 deletions

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="60" fill="none"><path fill="url(#a)" d="M0 0h150v60H0z"/><path stroke="#E6EFE7" stroke-width=".5" d="M2.25 2.25h145.5v55.5H2.25z"/><path fill="#C4E3C6" stroke="#3F6244" stroke-width=".5" d="M18.825 51.75A16.783 16.783 0 0 0 7.25 40.175v-20.35A16.783 16.783 0 0 0 18.825 8.25h112.75a16.784 16.784 0 0 0 11.575 11.575v20.35a16.784 16.784 0 0 0-11.575 11.575H18.825Z"/><defs><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="matrix(0 -49.5 128.409 0 75 30)" gradientUnits="userSpaceOnUse"><stop stop-color="#6C9D62"/><stop offset="1" stop-color="#AEE2B6"/></radialGradient></defs></svg>

After

Width:  |  Height:  |  Size: 655 B

View File

@@ -47,6 +47,7 @@
--border: #e8e8e8;
--border-warm: #e9e7e6;
--border-cold: #e6e7e9;
--border-cold-light: #e6efe7;
--border-greyscale: #e7e7e7;
--white-smoke: #f3f3f3;
@@ -107,6 +108,8 @@
--pop-up-highlight: #fee5c4;
--geolocation-stepper-ellipse-color: #67acfe;
--incentive-green: #2b5a2a;
// black base order
--black-base: #1a1a1a;
@@ -159,6 +162,7 @@
--z-index-ameyo-disconnect-btn: 99;
--z-index-leaderboard-overlay: 99;
--z-index-leaderboard: 100;
--z-index-leaderboard-tooltip: 1200;
--z-index-default-layout-open-cosmos-alert: 100;
--z-index-ameyo-reallocation-loader: 100;
--z-index-autocomplete-dropdown: 100;

View File

@@ -1,11 +1,14 @@
import React, { useEffect, useMemo, useState } from 'react';
import Highcharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official';
import { ILeaderboard } from 'src/reducers/leaderboardSlice';
import { ILeaderboard, IncentiveData } from 'src/reducers/leaderboardSlice';
import styles from './Leaderboard.module.scss';
import { dateFormat, monthNames } from 'src/utils/DateHelper';
import { shortNumberNotationStartingWithLakhs } from 'src/utils/commonUtils';
import { RightArrow } from 'src/assets/icons/RightArrowThin';
import { dateFormat } from 'src/utils/DateHelper';
import {
getNumberFormat,
shortNumberNotationStartingWithLakhs,
shortNumberNotationWithoutTrailingZeroesAndRounding
} from 'src/utils/commonUtils';
import ToggleTabs from '../toggleTabs/ToggleTabs';
import { ITabsOption, SelectPickerOptionProps } from '../interfaces';
import cx from 'classnames';
@@ -18,6 +21,16 @@ import { addClickstreamEvent } from '../../service/clickStreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '../../service/clickStream.constant';
import { Typography } from '@navi/web-ui/lib/primitives';
import RadioGroup from '@navi/web-ui/lib/components/RadioGroup';
import InfoIconOutlined from '@cp/assets/images/icons/InfoIconOutlined';
import {
Directions,
Tooltip as TooltipV2,
TooltipContent,
TooltipTrigger
} from '@cp/components/TooltipV2/TooltipV2';
import { INCENTIVE_BUCKET_GROUP } from '@cp/components/leaderboard/leaderboardConstant';
import Loader from '@cp/components/Loader/Loader';
interface CashEarnedChartHCProps extends ILeaderboard {
titleContainerClass?: string;
pieChartContainerClass?: string;
@@ -102,22 +115,29 @@ const CashEarnedChartHC: React.FC<CashEarnedChartHCProps> = props => {
pieChartContainerClass,
animation = true,
showDropdown = false,
renderedOn
renderedOn,
incentiveData,
prevMonthIncentiveData,
monthlyIncentiveData
} = props;
const dispatch = useDispatch();
const MONTH_OPTIONS = getMonthOptions();
const referenceId = useSelector((state: RootState) => state.common.userData.referenceId);
const leaderboardLoading = useSelector(
(state: RootState) => state.leaderboard.leaderboardLoading
);
const [view, setView] = React.useState<'curr' | 'prev'>('curr');
const [cashCollectedDropdownSelectedOption, setCashCollectedDropdownSelectedOption] = useState(
MONTH_OPTIONS[MONTH_OPTIONS.length - 1]
);
const handleViewChange = () => setView(prev => (prev == 'curr' ? 'prev' : 'curr'));
const buttonPosition = view == 'curr' ? { left: 16 } : { right: 16 };
const DEFAULT_OPTION = 'Total';
const [selectedRadioOption, setSelectedRadioOption] = useState(DEFAULT_OPTION);
const DEFAULT_OPTION = INCENTIVE_BUCKET_GROUP.TOTAL;
const [selectedRadioOption, setSelectedRadioOption] =
useState<INCENTIVE_BUCKET_GROUP>(DEFAULT_OPTION);
const handleRadioChange = (event: React.SyntheticEvent) => {
const value = (event?.target as HTMLInputElement)?.value;
setSelectedRadioOption(value);
setSelectedRadioOption(value as INCENTIVE_BUCKET_GROUP);
if (referenceId) {
dispatch(
getLeaderboardDetails(
@@ -235,19 +255,19 @@ const CashEarnedChartHC: React.FC<CashEarnedChartHCProps> = props => {
const DEFAULT_OPTIONS = [
{
id: 'Total',
value: 'Total',
label: 'Total'
id: INCENTIVE_BUCKET_GROUP.TOTAL,
value: INCENTIVE_BUCKET_GROUP.TOTAL,
label: INCENTIVE_BUCKET_GROUP.TOTAL
},
{
id: '1-30',
value: '1-30',
label: '1-30'
id: INCENTIVE_BUCKET_GROUP.DPD_1_TO_30,
value: INCENTIVE_BUCKET_GROUP.DPD_1_TO_30,
label: INCENTIVE_BUCKET_GROUP.DPD_1_TO_30
},
{
id: '30+',
value: '30+',
label: '30+'
id: INCENTIVE_BUCKET_GROUP.DPD_30_PLUS,
value: INCENTIVE_BUCKET_GROUP.DPD_30_PLUS,
label: INCENTIVE_BUCKET_GROUP.DPD_30_PLUS
}
];
@@ -263,6 +283,41 @@ const CashEarnedChartHC: React.FC<CashEarnedChartHCProps> = props => {
}
return noData;
}, [data]);
const [selectedIncentiveData, setIncentiveData] = useState<IncentiveData | undefined>();
const totalCashCollectedForSelectedRadioOption = useMemo(
() =>
selectedRadioOption === INCENTIVE_BUCKET_GROUP.DPD_1_TO_30
? selectedIncentiveData?.total1To30DaysCashCollected
: selectedRadioOption === INCENTIVE_BUCKET_GROUP.DPD_30_PLUS
? selectedIncentiveData?.total30PlusDaysCashCollected
: selectedIncentiveData?.thresholdForIncentive,
[selectedRadioOption, selectedIncentiveData]
);
const totalIncentiveEarnedForSelectedRadioOption = useMemo(
() =>
selectedRadioOption === INCENTIVE_BUCKET_GROUP.DPD_1_TO_30
? selectedIncentiveData?.total1To30DaysIncentiveEarned
: selectedRadioOption === INCENTIVE_BUCKET_GROUP.DPD_30_PLUS
? selectedIncentiveData?.total30PlusDaysIncentiveEarned
: selectedIncentiveData?.incentiveEarned,
[selectedRadioOption, selectedIncentiveData]
);
useEffect(() => {
if (showDropdown) {
setIncentiveData(monthlyIncentiveData);
}
}, [monthlyIncentiveData]);
useEffect(() => {
if (view === 'curr') {
setIncentiveData(incentiveData);
} else {
setIncentiveData(prevMonthIncentiveData);
}
}, [view]);
return (
<>
<div className={styles.tabsContainer}>
@@ -299,15 +354,67 @@ const CashEarnedChartHC: React.FC<CashEarnedChartHCProps> = props => {
{getFirstDate()} - {getLastDate(view)}
</b>
</span>
) : null}
</div>
</div>
<div className={cx(styles.titleContainer, titleContainerClass)}>
{showDropdown ? (
<>
) : (
<span className={styles.titleWithDropdown}>Cash Collection Breakup</span>
</>
) : null}
)}
</div>
<div className={styles.incentiveHeader}>
<Typography variant="h4" className="font-bold">
Incentive Earned
</Typography>
<TooltipV2 placement={showDropdown ? Directions.RIGHT : Directions.TOP}>
<TooltipTrigger tooltipTriggerClassName="tooltipTriggerWrapper">
<InfoIconOutlined fillColor="var(--blue-base)" />
</TooltipTrigger>
<TooltipContent className="p-[8px] bg-[var(--grayscale-1)] z-[var(--z-index-leaderboard-tooltip)] rounded-[4px] w-[360px]">
{selectedRadioOption === INCENTIVE_BUCKET_GROUP.TOTAL ? (
<div className="flex justify-between">
<Typography color="var(--text-primary)" className="text-[13px]" variant="p4">
Total Cash Collected (a)
</Typography>
<Typography color="var(--text-primary)" className="text-[13px]" variant="p4">
{getNumberFormat(selectedIncentiveData?.totalCashCollected, 2) || `₹0`}
</Typography>
</div>
) : (
<></>
)}
<div className={cx('flex justify-between', 'mb-[6px]')}>
<Typography color="var(--text-primary)" className="text-[13px]" variant="p4">
{selectedRadioOption !== INCENTIVE_BUCKET_GROUP.TOTAL
? `Total ${selectedRadioOption} cash collected`
: `Threshold for incentives (b)`}
</Typography>
<Typography color="var(--text-primary)" className="text-[13px]" variant="p4">
{getNumberFormat(totalCashCollectedForSelectedRadioOption, 2) || `₹0`}
</Typography>
</div>
{selectedRadioOption === INCENTIVE_BUCKET_GROUP.TOTAL ? (
<div className={cx('py-[6px]', 'flex', 'justify-between', styles.tooltipBorder)}>
<Typography color="var(--text-primary)" className="text-[13px]" variant="p4">
Amount eligible for incentives (a-b)
</Typography>
<Typography color="var(--text-primary)" className="text-[13px]" variant="p4">
{getNumberFormat(selectedIncentiveData?.amountEligibleForIncentive, 2) || `₹0`}
</Typography>
</div>
) : (
<></>
)}
<div className="flex justify-between">
<Typography color="var(--text-primary)" className="text-[13px]" variant="p4">
{selectedRadioOption !== INCENTIVE_BUCKET_GROUP.TOTAL
? `Incentive earned on ${selectedRadioOption} cases`
: `Incentives earned (${selectedIncentiveData?.incentivePercentage}% of eligible
amount)`}
</Typography>
<Typography color="var(--text-primary)" className="text-[13px]" variant="p4">
{getNumberFormat(totalIncentiveEarnedForSelectedRadioOption, 2) || `₹0`}
</Typography>
</div>
</TooltipContent>
</TooltipV2>
</div>
</div>
<div
className={cx({
@@ -317,12 +424,30 @@ const CashEarnedChartHC: React.FC<CashEarnedChartHCProps> = props => {
})}
>
<div className={styles.pieChartContainer}>
<HighchartsReact highcharts={Highcharts} options={options} />
{noDataInPieChart && (
<Typography className={styles.noDataText} variant="p5">
No cash collected for 30+ cases
</Typography>
)}
<div
className={cx(styles.incentiveSectionContainer, 'mb-[50px]', {
'w-[35%]': showDropdown,
'w-[30%]': !showDropdown
})}
>
<div className={styles.svgContainer}>
{shortNumberNotationWithoutTrailingZeroesAndRounding(
totalIncentiveEarnedForSelectedRadioOption,
2,
'',
1e5,
true
) || `₹0`}
</div>
</div>
<div className="pr-[10px]">
<HighchartsReact highcharts={Highcharts} options={options} />
{noDataInPieChart && (
<Typography className={cx(styles.noDataText)} variant="p5">
No cash collected for 30+ cases
</Typography>
)}
</div>
</div>
<RadioGroup
className={styles.radioContainer}
@@ -332,7 +457,11 @@ const CashEarnedChartHC: React.FC<CashEarnedChartHCProps> = props => {
options={DEFAULT_OPTIONS}
labelClassName=""
/>
<Typography variant="p5" className={cx(styles.incentiveDisclaimerText)}>
*Subject to month end adjustments
</Typography>
</div>
<Loader show={leaderboardLoading || false} className={styles.loadingState} animate={false} />
</>
);
};

View File

@@ -15,7 +15,8 @@
align-items: center;
z-index: var(--z-index-leaderboard);
box-sizing: border-box;
max-width: 700px;
min-width: 695px;
max-width: 800px;
.header {
height: 44px;
@@ -24,8 +25,15 @@
display: flex;
flex-direction: row;
justify-content: space-between;
justify-self: center;
align-items: center;
cursor: pointer;
width: 100%;
.separator {
height: 24px;
width: 1px;
border-right: 1px solid var(--navigation-blue-c2);
}
}
&.leaderBoardActive {
@@ -482,7 +490,28 @@
.pieChartContainer {
width: 100%;
display: flex;
justify-content: center;
justify-content: space-between;
align-items: center;
.incentiveSectionContainer {
border-right: 1px solid var(--light-grayscale-border);
height: 132px;
display: flex;
justify-content: center;
align-items: center;
.svgContainer {
height: 60px;
width: 150px;
display: flex;
justify-content: center;
align-items: center;
background: url('/src/assets/images/incentive-note.svg');
color: var(--incentive-green);
font-size: 20px;
font-weight: 700;
line-height: 28px;
letter-spacing: -0.4px;
}
}
}
.pieChartDashboardWidth {
@@ -613,8 +642,17 @@
flex-direction: row-reverse;
justify-content: space-between;
align-items: center;
padding: 0 3px 0 24px;
padding: 0 10px 0 10px;
background: var(--bg-primary);
.incentiveHeader {
width: 30%;
text-align: left;
display: flex;
align-items: center;
gap: 4px;
.incentiveText {
}
}
}
.radioContainer {
@@ -690,10 +728,32 @@
}
}
.incentiveDisclaimerText {
color: var(--grayscale-2);
position: absolute;
left: 10px;
font-size: 10px;
}
.noDataText {
color: var(--grayscale-3);
padding-left: 20px;
position: absolute;
top: 180px;
left: auto;
left: 51.5%;
}
.tooltipBorder {
border-top: 1px dashed var(--grayscale-2);
border-bottom: 1px dashed var(--grayscale-2);
}
.loadingState {
width: 100%;
height: 120%;
position: absolute;
top: 0;
right: 0;
background-color: rgba(255, 255, 255, 0.6);
animation: fadeIn 120ms;
}

View File

@@ -128,12 +128,15 @@ const Leaderboard: React.FC<LeaderboardProps> = props => {
})}
>
<GridRow onClick={handleOnClick} className={cx(styles.header)}>
<div style={{ width: 'fit-content', marginRight: 12 }}>
<div style={{ display: 'flex' }}>
<Typography variant="p5" className="flex items-center">
<span title={name ?? ''} className={cx(styles.ellipsis, 'mr-[16px]')}>
{name ? `${name}` : ''}
</span>
</Typography>
<div className={styles.separator} />
<div className="w-fit">
<div className="flex">
<Typography className={cx(styles.mr8)} variant="p5">
<span title={name ?? ''} className={styles.ellipsis} style={{ marginRight: 16 }}>
{name ? `${name}` : ''}
</span>
<span className={styles.headerTextWrapper}>
{`Last ${leaderboard?.noOfDaysCashCollected} days cash collected`}
<span className={styles.cashCollectedAmount}>
@@ -160,6 +163,7 @@ const Leaderboard: React.FC<LeaderboardProps> = props => {
</Typography>
</div>
</div>
<div className={styles.separator} />
<div style={{ width: 'fit-content' }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Typography className={cx(styles.mr8)} variant="p5">
@@ -195,7 +199,7 @@ const Leaderboard: React.FC<LeaderboardProps> = props => {
className={styles.content}
style={{ justifyContent: 'right', flexDirection: 'column', padding: 0 }}
>
<GridRow style={{ display: 'flex', flexDirection: 'row' }}>
<GridRow className="flex justify-center">
{performanceData ? (
<GridColumn className={styles.left} style={{ alignItems: 'left' }}>
<Typography variant="p5" className={styles.graphHeading}>

View File

@@ -2,3 +2,15 @@ export enum PERFORMANCE_CHART_TYPE {
LAST_30_DAYS = 'LAST_30_DAYS',
LAST_7_DAYS = 'LAST_7_DAYS'
}
export enum INCENTIVE_BUCKET_GROUP {
TOTAL = 'Total',
DPD_1_TO_30 = '1-30',
DPD_30_PLUS = '30+'
}
export const INCENTIVE_MONTH_KEYS = {
incentiveData: 'incentiveData',
prevMonthIncentiveData: 'prevMonthIncentiveData',
monthlyIncentiveData: 'monthlyIncentiveData'
};

View File

@@ -393,7 +393,7 @@ const DashBoard = () => {
) : null}
<GridColumn md={6} className={cx(styles.paddingRight_12, styles.paddingBottom_16)}>
<GridContainer className={cx(styles.cardShadow, styles.paddingTop_16)}>
<GridContainer className={cx(styles.cardShadow, styles.paddingTop_16, 'px-0')}>
<CashEarnedChartHC
titleContainerClass={styles.cashEarnedChartHCtitleClass}
pieChartContainerClass={styles.cashEarnedChartHCpieChartClass}

View File

@@ -15,7 +15,8 @@ import {
setMonthlyPerformance,
setPercentCompletedCases,
setPromiseCount,
setWeeklyPerformance
setWeeklyPerformance,
showLoader
} from '../../reducers/leaderboardSlice';
import axiosInstance, {
ApiKeys,
@@ -357,6 +358,7 @@ export const getLeaderboardDetails = (
const filterParam = `?filter=${params}`;
const url = getApiUrl(ApiKeys.GET_LEADERBOARD_DETAILS, { agentReferenceId }) + filterParam;
return async function (dispatch: (arg0: { payload: any; type: string }) => void) {
dispatch(showLoader(true));
await axiosInstance
.get(encodeURI(url), {
headers: { donotHandleError: true },
@@ -366,6 +368,7 @@ export const getLeaderboardDetails = (
dispatch(setLeaderboard(response?.data));
})
.catch(err => {
dispatch(showLoader(false));
logError(err, 'Getting error from getLeaderboardDetails :: ' + url);
});
};

View File

@@ -23,6 +23,10 @@ export interface ILeaderboard {
totalCurrentMonthCashCollected: any;
totalPrevMonthCashCollected: any;
isExpanded: boolean;
incentiveData?: IncentiveData;
prevMonthIncentiveData?: IncentiveData;
monthlyIncentiveData?: IncentiveData;
leaderboardLoading: boolean;
}
export interface IPromiseCount {
@@ -65,6 +69,18 @@ export interface IDataSet {
lowerLevelCashCollectedInLast30Days: string | null;
}
export interface IncentiveData {
totalCashCollected?: number;
thresholdForIncentive?: number;
amountEligibleForIncentive?: number;
incentivePercentage?: number;
incentiveEarned?: number;
total1To30DaysCashCollected?: number;
total30PlusDaysCashCollected?: number;
total1To30DaysIncentiveEarned?: number;
total30PlusDaysIncentiveEarned?: number;
}
const initialState = {
agentLevelRanking: 0,
maxLevel: 0,
@@ -82,7 +98,8 @@ const initialState = {
levelLowerLimit: 0
}
],
isExpanded: false
isExpanded: false,
leaderboardLoading: false
} as unknown as ILeaderboard;
const COLOR_MAPPING = {
@@ -111,7 +128,10 @@ const notificationSlice = createSlice({
totalPrevMonthCashCollected,
todaysCashCollected,
noOfDaysCashCollected,
dateWhenRankingVisible
dateWhenRankingVisible,
incentiveData,
prevMonthIncentiveData,
monthlyIncentiveData
} = action.payload;
// todo make it action.payload
state.agentLevelRanking = agentLevelRanking;
@@ -140,6 +160,10 @@ const notificationSlice = createSlice({
state.totalMonthlyCashCollected = monthlyCashCollected?.totalMonthlyCashCollected;
state.totalCurrentMonthCashCollected = totalCurrentMonthCashCollected;
state.totalPrevMonthCashCollected = totalPrevMonthCashCollected;
state.incentiveData = incentiveData;
state.prevMonthIncentiveData = prevMonthIncentiveData;
state.monthlyIncentiveData = monthlyIncentiveData;
state.leaderboardLoading = false;
},
setMonthlyPerformance(state, action) {
// state.performance = payload;
@@ -191,6 +215,9 @@ const notificationSlice = createSlice({
},
setIsExpanded(state, action) {
state.isExpanded = action.payload;
},
showLoader(state, action) {
state.leaderboardLoading = action.payload;
}
}
});
@@ -202,7 +229,8 @@ export const {
setPercentCompletedCases,
setPromiseCount,
setCallbridgeData,
setIsExpanded
setIsExpanded,
showLoader
} = notificationSlice.actions;
export default notificationSlice.reducer;

View File

@@ -17,6 +17,7 @@ import { getMaxInputDate } from '../pages/LiveLocationTracker/utils';
import { DateFormat } from './DateHelper';
export const casesPath = `/cases/details/lan/{accountNumber}/customer/{customerReferenceId}/tab/overview`;
export const isObjectEmpty = (object: any) => {
for (const prop in object) {
// eslint-disable-next-line no-prototype-builtins
@@ -30,7 +31,8 @@ export enum DEVICES {
IOS = 'IOS'
}
export const getNumberFormat = (value: number | bigint, fractionalDigits: number) => {
export const getNumberFormat = (value: number | bigint | undefined, fractionalDigits: number) => {
if (!value) return;
return new Intl.NumberFormat('en-IN', {
style: 'currency',
maximumFractionDigits: fractionalDigits,
@@ -126,18 +128,20 @@ export const toFixedWithoughtRoundOff = (value = 0, decimals = 2): string => {
export const shortNumberNotationWithoutTrailingZeroesAndRounding = (
value = 0,
decimals = 2,
separator = ''
separator = '',
minThreshold = 1e5,
useNumberFormat = false
) => {
if (typeof value !== 'number') return '';
const parsedValue = Number(value);
if (parsedValue >= 1e7) {
if (parsedValue >= 1e7 && minThreshold <= 1e7) {
return '₹' + toFixedWithoughtRoundOff(parsedValue / 1e7, decimals) + `${separator}Cr`;
} else if (parsedValue >= 1e5) {
} else if (parsedValue >= 1e5 && minThreshold <= 1e5) {
return '₹' + toFixedWithoughtRoundOff(parsedValue / 1e5, decimals) + `${separator}L`;
} else if (parsedValue >= 1e3) {
} else if (parsedValue >= 1e3 && minThreshold <= 1e3) {
return '₹' + toFixedWithoughtRoundOff(parsedValue / 1e3, decimals) + `${separator}K`;
}
return '₹' + parsedValue;
return useNumberFormat ? getNumberFormat(parsedValue, decimals) : '₹' + parsedValue;
};
export const shortNumberNotationStartingWithLakhs = (