TP-42467 | External Agency Dashboards V1: AM - Overall and Field Governance (#787)
* TP-42467 | Inital Commit * TP-42467 | Table CHanges * TP-42467 | Table Changes * TP-42467 | Redux Setup + Actions Added * TP-42467 | Table Changes * TP-42467 | Routing Changes + Tabs Changes + Table Changes * TP-42467 | Agent Name Options support added * TP-42467 | Code Refactored * TP-42467 | Column Def changes * TP-42467 | API Contract Changes * TP-42467 | API helper changes * TP-42467 | Css Fixes + Agent Map Location Entry point added * TP-42467 | Clickstream Added + Tab Issues Fixes + Feature Flags Added * TP-42467 | Code Refactored * TP-42467 | Mocks Added * TP-42467 | Last Updated Logic Added * TP-42467 | Polling Added * TP-42461 |removed comments| Aman Singh * TP-42467 |lint issues| Aman Singh * TP-42467 | Table UI flickering fix * TP-42467 | Filter changes for DPD Table * TP-42467 | Filter changes + Query Params Changing + Refactoring + Routing * TP-42467 | API Fix + Loading State Fix + Table changes * TP-42467 | Path Param Changes * TP-42467 | API Integration * TP-42467 | Code Refactored * TP-42467 | Minor fix * TP-42467 | Contract Changes + Code Refactored * TP-42467 | Contract Changes * TP-42467 | UAT Fixes * TP-42467 | UAT Fixes * TP-42467 | Time format fix * TP-42467 | Bug Fixes * TP-42467 | UAT Fixes * TP-42467 | Code Refactored * TP-42467 | Bug Fix * TP-42467 | PR comments reolved * TP-42467 | endpoint change * TP-42567 | UAT Fix * TP-42467 | web ui library update --------- Co-authored-by: aman.singh <aman.singh@navi.com> Co-authored-by: Varnit Goyal <varnit.goyal@navi.com>
This commit is contained in:
26
__mocks__/agencyAndStateOptions.json
Normal file
26
__mocks__/agencyAndStateOptions.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"agencies": [
|
||||
{
|
||||
"label": "All",
|
||||
"value": "ALL"
|
||||
},
|
||||
{
|
||||
"label": "Navi",
|
||||
"value": "1000"
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
{
|
||||
"label": "All",
|
||||
"value": "ALL"
|
||||
},
|
||||
{
|
||||
"label": "Karnataka",
|
||||
"value": "KA"
|
||||
},
|
||||
{
|
||||
"label": "Gujarat",
|
||||
"value": "GJ"
|
||||
}
|
||||
]
|
||||
}
|
||||
41
__mocks__/agencyDetails.json
Normal file
41
__mocks__/agencyDetails.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"performanceData": {
|
||||
"data": [
|
||||
{
|
||||
"agencyName": "AC1",
|
||||
"agencyCode": "agency-code-1",
|
||||
"coverageThirtyNinetyLansPercent": 1,
|
||||
"coverageNinetyOneEightyLansPercent": 4.1212210,
|
||||
"genuineCoverageThirtyNinetyLansPercent": 5.1321210,
|
||||
"genuineCoverageNinetyOneEightyLansPercent": 2.121210,
|
||||
"visitsPerDayPerAgent": 2.1321325,
|
||||
"genuineVisitsPerDayPerAgent": 2.13213210,
|
||||
"ptpsGenerated": 20,
|
||||
"distanceTravelledPerDayPerAgent": 15.12321220,
|
||||
"appUsagePerDayPerAgent": 3.132123210,
|
||||
"brokenPtpPercentage": 5.123212320
|
||||
},
|
||||
{
|
||||
"agencyName": "AC2",
|
||||
"agencyCode": "agency-code-2",
|
||||
"coverageThirtyNinetyLansPercent": null,
|
||||
"coverageNinetyOneEightyLansPercent": 7.0,
|
||||
"genuineCoverageThirtyNinetyLansPercent": 20.0,
|
||||
"genuineCoverageNinetyOneEightyLansPercent": 5.0,
|
||||
"visitsPerDayPerAgent": 7.5,
|
||||
"genuineVisitsPerDayPerAgent": 2.0,
|
||||
"ptpsGenerated": 14,
|
||||
"distanceTravelledPerDayPerAgent": 30.0,
|
||||
"appUsagePerDayPerAgent": 400.0,
|
||||
"brokenPtpPercentage": 33.33
|
||||
}
|
||||
],
|
||||
"pages": {
|
||||
"pageNo": 0,
|
||||
"totalPages": 1,
|
||||
"pageSize": 10,
|
||||
"totalElements": 2
|
||||
}
|
||||
},
|
||||
"lastUpdatedAt": 1705336097367
|
||||
}
|
||||
8
__mocks__/agencyOptions.json
Normal file
8
__mocks__/agencyOptions.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"agency": [
|
||||
{
|
||||
"label": "Agency Name",
|
||||
"value": "AGENCY_CODE"
|
||||
}
|
||||
]
|
||||
}
|
||||
59
__mocks__/agentPerformance.json
Normal file
59
__mocks__/agentPerformance.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"performanceData": {
|
||||
"data": [
|
||||
{
|
||||
"agentName": "Pratham",
|
||||
"agencyName": "TEst 1",
|
||||
"agentReferenceId": "278d39c2-a2ae-4d69-91d8-515ec7907ee8",
|
||||
"totalLansAllocated": null,
|
||||
"coverageThirtyNinetyLansPercent": 5.123132,
|
||||
"coverageNinetyOneEightyLansPercent": 2.123132,
|
||||
"genuineCoverageThirtyNinetyLansPercent": 3.123132,
|
||||
"genuineCoverageNinetyOneEightyLansPercent": 1.123132,
|
||||
"visitsPerDay": 2.512322,
|
||||
"genuineVisitsPerDay": 2.123132,
|
||||
"ptpsGenerated": 1123132,
|
||||
"distanceTravelledPerDay": 15.123132,
|
||||
"appUsagePerDay": 3.123132,
|
||||
"brokenPtpPercentage": 5.123132
|
||||
},
|
||||
{
|
||||
"agentName": "Raghav",
|
||||
"agentReferenceId": "780ec066-547f-4fac-82e8-1fd97c9c91d2",
|
||||
"totalLansAllocated": null,
|
||||
"coverageThirtyNinetyLansPercent": 50.0,
|
||||
"coverageNinetyOneEightyLansPercent": 25.0,
|
||||
"genuineCoverageThirtyNinetyLansPercent": 30.0,
|
||||
"genuineCoverageNinetyOneEightyLansPercent": 15.0,
|
||||
"visitsPerDay": 2.5,
|
||||
"genuineVisitsPerDay": 2.0,
|
||||
"ptpsGenerated": 10,
|
||||
"distanceTravelledPerDay": 15.0,
|
||||
"appUsagePerDay": 3.0,
|
||||
"brokenPtpPercentage": 5.0
|
||||
},
|
||||
{
|
||||
"agentName": "Shubham",
|
||||
"agentReferenceId": "4075acc6-53c8-4204-9ad2-192313387d9b",
|
||||
"totalLansAllocated": null,
|
||||
"coverageThirtyNinetyLansPercent": 15.0,
|
||||
"coverageNinetyOneEightyLansPercent": 7.0,
|
||||
"genuineCoverageThirtyNinetyLansPercent": 20.0,
|
||||
"genuineCoverageNinetyOneEightyLansPercent": 5.0,
|
||||
"visitsPerDay": 7.5,
|
||||
"genuineVisitsPerDay": 2.0,
|
||||
"ptpsGenerated": 14,
|
||||
"distanceTravelledPerDay": 30.0,
|
||||
"appUsagePerDay": 400.0,
|
||||
"brokenPtpPercentage": 33.33
|
||||
}
|
||||
],
|
||||
"pages": {
|
||||
"pageNo": 0,
|
||||
"totalPages": 1,
|
||||
"pageSize": 10,
|
||||
"totalElements": 3
|
||||
}
|
||||
},
|
||||
"lastUpdatedAt": 1705336097367
|
||||
}
|
||||
6
__mocks__/agentsOptions.json
Normal file
6
__mocks__/agentsOptions.json
Normal file
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"label": "Agent 1",
|
||||
"value": "agent"
|
||||
}
|
||||
]
|
||||
@@ -1,14 +1,26 @@
|
||||
{
|
||||
"referenceId": "654615f9-8f88-46de-a1d8-707e87e82a2e",
|
||||
"phoneNumber": "8632426173",
|
||||
"name": "abcd",
|
||||
"agencyCode": "TL369",
|
||||
"agencyName": "AllocationDeallocation",
|
||||
"active": true,
|
||||
"roles": [
|
||||
"ROLE_CALLING_AGENT"
|
||||
],
|
||||
"naviUser": false,
|
||||
"agenciesReporting": [],
|
||||
"email": "abcd@navi.com"
|
||||
}
|
||||
"referenceId": "654615f9-8f88-46de-a1d8-707e87e82a2e",
|
||||
"phoneNumber": "8632426173",
|
||||
"name": "abcd",
|
||||
"agencyCode": "TL369",
|
||||
"agencyName": "AllocationDeallocation",
|
||||
"active": true,
|
||||
"roles": [
|
||||
"ROLE_CALLING_AGENT",
|
||||
"ROLE_NAVI_FIELD_AGENCY_TEAM_LEAD",
|
||||
"ROLE_NAVI_FIELD_TEAM_LEAD",
|
||||
"ROLE_NAVI_FIELD_EXTERNAL_TEAM_LEAD"
|
||||
],
|
||||
"naviUser": false,
|
||||
"agenciesReporting": [],
|
||||
"email": "abcd@navi.com",
|
||||
"featureFlags": {
|
||||
"performanceDashboard": true,
|
||||
"externalAgencyPerformanceDashboard": true,
|
||||
"overallPerformanceTab": true,
|
||||
"fieldGovernanceTab": true,
|
||||
"overallPerformanceOverallMetricsTable": true,
|
||||
"fieldGovernanceAgencyPerformanceTable": true,
|
||||
"fieldGovernanceAgentPerformanceTable": true
|
||||
}
|
||||
}
|
||||
|
||||
50
__mocks__/dpdBucket.json
Normal file
50
__mocks__/dpdBucket.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"performanceData": {
|
||||
"data": [
|
||||
{
|
||||
"dpdBucket": "DPD 61-90",
|
||||
"performanceBand": 3,
|
||||
"lansAllocated": 934,
|
||||
"agentAllocationPercentage": 1,
|
||||
"emiOverdueAmount": 10609064,
|
||||
"targetCEPercentage": 13.322222,
|
||||
"targetOverdueAmount": 1424932.3232,
|
||||
"emiCEPercentage": 2.222222,
|
||||
"emiOverdueCollected": 244058,
|
||||
"achievementPercentage": 10.3378,
|
||||
"lmsdAchievedPercentage": 37.386836,
|
||||
"nonEnachCashCollected": 280880
|
||||
},
|
||||
{
|
||||
"dpdBucket": "DPD 61-90",
|
||||
"performanceBand": 3,
|
||||
"lansAllocated": 932,
|
||||
"agentAllocationPercentage": 1,
|
||||
"emiOverdueAmount": 10609064,
|
||||
"targetCEPercentage": 13,
|
||||
"targetOverdueAmount": 1424932,
|
||||
"emiCEPercentage": 2,
|
||||
"emiOverdueCollected": 244058,
|
||||
"achievementPercentage": 0,
|
||||
"lmsdAchievedPercentage": 37,
|
||||
"nonEnachCashCollected": 280880
|
||||
},
|
||||
{
|
||||
"dpdBucket": "DPD 61-90",
|
||||
"performanceBand": 3,
|
||||
"lansAllocated": 948,
|
||||
"agentAllocationPercentage": 1,
|
||||
"emiOverdueAmount": 10609064,
|
||||
"targetCEPercentage": 13,
|
||||
"targetOverdueAmount": 1424932,
|
||||
"emiCEPercentage": 2,
|
||||
"emiOverdueCollected": 244058,
|
||||
"achievementPercentage": 0,
|
||||
"lmsdAchievedPercentage": 37,
|
||||
"nonEnachCashCollected": 280880
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
"lastUpdatedAt": 1705336097367
|
||||
}
|
||||
@@ -76,6 +76,7 @@
|
||||
--blue-base: #0276fe;
|
||||
--blue-light: #3591fe;
|
||||
--blue-dark: #025ecb;
|
||||
--blue-light-table: #f7f8fc;
|
||||
--blue-border: #cce4ff;
|
||||
--blue-selected: #d1e5ff;
|
||||
--blue-bg: #e6f1ff;
|
||||
@@ -126,6 +127,8 @@
|
||||
|
||||
// Z-indices
|
||||
--z-index-pagination-wrapper: 1;
|
||||
--z-index-external-sensei-toggle-tabs: 2;
|
||||
--z-index-external-sensei-header: 3;
|
||||
--z-index-dropdown-picker: 10;
|
||||
--z-index-side-logout-btn: 42;
|
||||
--z-index-ameyo-disconnect-btn: 99;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: 24px;
|
||||
width: fit-content;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
input {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
& .filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -8,27 +8,39 @@ interface IRenderWithToolTip {
|
||||
showToolTip?: boolean;
|
||||
lineClamp?: number;
|
||||
hiddenPadding?: number;
|
||||
ellipsisWrapperClass?: string;
|
||||
}
|
||||
|
||||
const TableCellRenderer: React.FC<IRenderWithToolTip> = props => {
|
||||
const { value, toolTipContent, showToolTip, lineClamp = 1, hiddenPadding = 10 } = props;
|
||||
const {
|
||||
value,
|
||||
toolTipContent,
|
||||
showToolTip,
|
||||
lineClamp = 1,
|
||||
hiddenPadding = 10,
|
||||
ellipsisWrapperClass
|
||||
} = props;
|
||||
|
||||
if (!showToolTip) {
|
||||
return <EllipsisText text={value ?? '-'} lineClamp={lineClamp} />;
|
||||
return (
|
||||
<EllipsisText
|
||||
text={value ?? '-'}
|
||||
lineClamp={lineClamp}
|
||||
ellipsisWrapperClass={ellipsisWrapperClass}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" hiddenPadding={hiddenPadding} hideStrategy="referenceHidden">
|
||||
<TooltipTrigger tooltipTriggerClassName="tooltipTriggerWrapper">
|
||||
<EllipsisText text={value ?? '-'} lineClamp={lineClamp} />
|
||||
<EllipsisText
|
||||
text={value ?? '-'}
|
||||
lineClamp={lineClamp}
|
||||
ellipsisWrapperClass={ellipsisWrapperClass}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className="tooltipWrapper"
|
||||
arrowColor="var(--tooltip-background-color)"
|
||||
bgColor="var(--tooltip-background-color)"
|
||||
>
|
||||
{toolTipContent}
|
||||
</TooltipContent>
|
||||
<TooltipContent className="tooltipWrapper">{toolTipContent}</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -74,12 +74,14 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) {
|
||||
isInternalTeamLead,
|
||||
fieldTLAndClusterAndZonalManagers,
|
||||
isAmeyoUtilityVisible,
|
||||
isAmeyoGeneratePasswordVisible
|
||||
isAmeyoGeneratePasswordVisible,
|
||||
featureFlags
|
||||
} = useSelector((state: RootState) => ({
|
||||
user: state.common.userData,
|
||||
isPerformanceDashboardVisible: state?.common?.isPerformanceDashboardVisible,
|
||||
isIdCardApprovalVisible: state?.common?.isIdCardApprovalVisible,
|
||||
isPincodeMappingVisible: state?.common?.isPincodeMappingVisible,
|
||||
featureFlags: state?.common?.featureFlags,
|
||||
isTeamLead: state?.common?.isTeamLead,
|
||||
isInternalTeamLead: state?.common?.isInternalTeamLead,
|
||||
fieldTLAndClusterAndZonalManagers: state?.common?.fieldTLAndClusterAndZonalManagers,
|
||||
@@ -87,6 +89,8 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) {
|
||||
isAmeyoGeneratePasswordVisible: state?.common?.isAmeyoGeneratePasswordVisible
|
||||
}));
|
||||
|
||||
const { externalAgencyPerformanceDashboard } = featureFlags || {};
|
||||
|
||||
const newDashboard = user?.revampedDashboardViewAllowed;
|
||||
const {
|
||||
isCallConnected,
|
||||
@@ -382,7 +386,11 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) {
|
||||
activeIcon={<PDIcon />}
|
||||
inActiveIcon={<PDIcon fillColor="var(--navigation-blue-c2)" />}
|
||||
name={'Dashboard'}
|
||||
route={APP_ROUTES.PERFORMANCE_DASHBOARD.path}
|
||||
route={
|
||||
externalAgencyPerformanceDashboard
|
||||
? APP_ROUTES.SENSEI_EXTERNAL.relativePath
|
||||
: APP_ROUTES.PERFORMANCE_DASHBOARD.path
|
||||
}
|
||||
linkClickHandler={linkClickHandler}
|
||||
currentPathname={pathname}
|
||||
currentSearch={search}
|
||||
|
||||
@@ -9,19 +9,24 @@ interface ToggleTabsProps {
|
||||
onTabClick: (val: string | boolean) => void;
|
||||
tabOptions: ITabsOption[];
|
||||
size?: FontType;
|
||||
containerClass?: string;
|
||||
activeTabClass?: string;
|
||||
}
|
||||
|
||||
const ToggleTabs: React.FC<ToggleTabsProps> = props => {
|
||||
const { selected, onTabClick, tabOptions, size } = props;
|
||||
const { selected, onTabClick, tabOptions, size, containerClass, activeTabClass } = props;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={cx(styles.container, containerClass)}>
|
||||
<div className={styles.feedbackTabsWrapper}>
|
||||
{tabOptions.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => onTabClick(item.value)}
|
||||
className={cx(styles.tabItem, selected === item.value ? styles.active : '')}
|
||||
className={cx(styles.tabItem, {
|
||||
[styles.active]: selected === item.value,
|
||||
[`${activeTabClass}`]: selected === item.value
|
||||
})}
|
||||
>
|
||||
<Typography variant={size ? size : 'h4'}>{item.label}</Typography>
|
||||
</div>
|
||||
|
||||
@@ -97,7 +97,10 @@ const HideTopNavBarRoutes = [
|
||||
APP_ROUTES.AGENCY_PINCODE_MAPPING.path,
|
||||
APP_ROUTES.WHATSAPP_CHATBOT.path,
|
||||
APP_ROUTES.SENSEI_TELE.path,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSAI.daily_planning,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.daily_planning,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.overall_performance,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.field_governance,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_daily_planning,
|
||||
APP_ROUTES.AMEYO_UTILITY.path,
|
||||
APP_ROUTES.GENERATE_AMEYO_PASSWORD.path,
|
||||
APP_ROUTES.AMEYO_UPLOAD_NUMBERS.path
|
||||
|
||||
@@ -19,10 +19,14 @@ import SenseiTele from '../pages/SenseiTele';
|
||||
|
||||
const Cases = React.lazy(() => import('../pages/Cases/components/Cases'));
|
||||
const NotFound = React.lazy(() => import('../pages/404/NotFound'));
|
||||
const ExternalDashboardSensei = React.lazy(() => import('../pages/ExternalDashboardSensei'));
|
||||
|
||||
export const APP_ROUTES_PATHS_WITH_PATH_PARAM = {
|
||||
SENSAI: {
|
||||
daily_planning: '/sensei/daily_planning'
|
||||
SENSEI: {
|
||||
daily_planning: '/sensei/daily_planning',
|
||||
overall_performance: '/sensei-external/overall_performance',
|
||||
field_governance: '/sensei-external/field_governance',
|
||||
external_daily_planning: '/sensei-external/daily_planning'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -66,6 +70,11 @@ const APP_ROUTES = {
|
||||
path: '/live-location-tracker',
|
||||
element: <LiveLocationTracker />
|
||||
},
|
||||
SENSEI_EXTERNAL: {
|
||||
path: '/sensei-external/:tabId',
|
||||
relativePath: APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.overall_performance,
|
||||
element: <ExternalDashboardSensei />
|
||||
},
|
||||
REALLOCATION: {
|
||||
path: '/reallocation',
|
||||
element: <Reallocation />,
|
||||
@@ -73,7 +82,7 @@ const APP_ROUTES = {
|
||||
},
|
||||
SENSEI: {
|
||||
path: '/sensei/:tabId',
|
||||
relativePath: APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSAI.daily_planning,
|
||||
relativePath: APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.daily_planning,
|
||||
element: <Sensei />
|
||||
},
|
||||
AGENT_AVAILABILITY: {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { RootState } from '@cp/src/store';
|
||||
import { readQueryParams } from '@cp/src/utils/QueryParamsHelper';
|
||||
import { useSelector } from 'react-redux';
|
||||
import AgencyDetailsTableMtd from '../components/AgencyDetailsTableExternal/AgencyDetailsTableMtd';
|
||||
import AgencyDetailsTableToday from '../components/AgencyDetailsTableExternal/AgencyDetailsTableToday';
|
||||
import { FIELD_GOVERNANCE_TABS_VALUE } from '../types';
|
||||
import cx from 'classnames';
|
||||
import styles from './fieldGovernance.module.scss';
|
||||
import { TabsKey } from '../constants';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
const AgencyDetailsTable = () => {
|
||||
const params = readQueryParams();
|
||||
const { featureFlags } = useSelector((state: RootState) => ({
|
||||
featureFlags: state?.common?.featureFlags
|
||||
}));
|
||||
const { fieldGovernanceAgencyPerformanceTable } = featureFlags || {};
|
||||
const selectedTab = params?.fieldGovernanceTab?.selectedTab;
|
||||
const { tabId = '' } = useParams();
|
||||
const isToday = selectedTab === FIELD_GOVERNANCE_TABS_VALUE.TODAY;
|
||||
|
||||
const isFieldGovernance = tabId === TabsKey.FIELD_GOVERNANCE;
|
||||
const isMtdTableVisible = !isToday && isFieldGovernance && !!selectedTab;
|
||||
const isTodayTableVisible = isToday && isFieldGovernance && !!selectedTab;
|
||||
|
||||
if (fieldGovernanceAgencyPerformanceTable) {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={cx({
|
||||
[styles.tableVisible]: isMtdTableVisible,
|
||||
[styles.tableHidden]: isTodayTableVisible
|
||||
})}
|
||||
>
|
||||
<AgencyDetailsTableMtd isVisible={isMtdTableVisible} />
|
||||
</div>
|
||||
<div
|
||||
className={cx({
|
||||
[styles.tableVisible]: isTodayTableVisible,
|
||||
[styles.tableHidden]: isMtdTableVisible
|
||||
})}
|
||||
>
|
||||
<AgencyDetailsTableToday isVisible={isTodayTableVisible} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default AgencyDetailsTable;
|
||||
@@ -0,0 +1,63 @@
|
||||
import { RootState } from '@cp/src/store';
|
||||
import { readQueryParams } from '@cp/src/utils/QueryParamsHelper';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import AgentPerformanceTableMtd from '../components/AgentPerformanceTableExternal/AgentPerformanceTableMtd';
|
||||
import AgentPerformanceTableToday from '../components/AgentPerformanceTableExternal/AgentPerformanceTableToday';
|
||||
import { FIELD_GOVERNANCE_TABS_VALUE } from '../types';
|
||||
import cx from 'classnames';
|
||||
import styles from './fieldGovernance.module.scss';
|
||||
import { TabsKey } from '../constants';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { getAgencyOptions } from '../actions/ExternalDashboardSenseiActions';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const AgentPerformanceTable = () => {
|
||||
const params = readQueryParams();
|
||||
const { featureFlags } = useSelector((state: RootState) => ({
|
||||
featureFlags: state?.common?.featureFlags
|
||||
}));
|
||||
const { fieldGovernanceAgentPerformanceTable } = featureFlags || {};
|
||||
const selectedTab = params?.fieldGovernanceTab?.selectedTab;
|
||||
const { tabId = '' } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isToday = selectedTab === FIELD_GOVERNANCE_TABS_VALUE.TODAY;
|
||||
const isFieldGovernance = tabId === TabsKey.FIELD_GOVERNANCE;
|
||||
|
||||
const isMtdTableVisible = !isToday && isFieldGovernance && !!selectedTab;
|
||||
const isTodayTableVisible = isToday && isFieldGovernance && !!selectedTab;
|
||||
|
||||
useEffect(() => {
|
||||
if (fieldGovernanceAgentPerformanceTable && isFieldGovernance) {
|
||||
dispatch(getAgencyOptions());
|
||||
}
|
||||
}, [isFieldGovernance]);
|
||||
|
||||
if (fieldGovernanceAgentPerformanceTable) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cx({
|
||||
[styles.tableVisible]: isMtdTableVisible,
|
||||
[styles.tableHidden]: isTodayTableVisible
|
||||
})}
|
||||
>
|
||||
<AgentPerformanceTableMtd isVisible={isMtdTableVisible} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cx({
|
||||
[styles.tableVisible]: isTodayTableVisible,
|
||||
[styles.tableHidden]: isMtdTableVisible
|
||||
})}
|
||||
>
|
||||
<AgentPerformanceTableToday isVisible={isTodayTableVisible} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default AgentPerformanceTable;
|
||||
@@ -0,0 +1,26 @@
|
||||
.governanceTabWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.toggleTabsContainer {
|
||||
padding-top: 16px;
|
||||
z-index: var(--z-index-external-sensei-toggle-tabs);
|
||||
}
|
||||
|
||||
.activeTab {
|
||||
box-shadow: var(--box-shadow-2);
|
||||
}
|
||||
|
||||
.tableVisible {
|
||||
visibility: visible;
|
||||
position: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tableHidden {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
82
src/pages/ExternalDashboardSensei/FieldGovernance/index.tsx
Normal file
82
src/pages/ExternalDashboardSensei/FieldGovernance/index.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { createQueryParams, readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import styles from './fieldGovernance.module.scss';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { FIELD_GOVERNANCE_TABS, FIELD_GOVERNANCE_TABS_VALUE } from '../types';
|
||||
import ToggleTabs from 'src/components/toggleTabs/ToggleTabs';
|
||||
import { QueryParamTypeMapping, TabsKey } from '../constants';
|
||||
import { setInitialGovernanceTabParams } from '../utils';
|
||||
import { addClickstreamEvent } from '@cp/src/service/clickStreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@cp/src/service/clickStream.constant';
|
||||
import AgencyDetailsTable from './AgencyDetailsTable';
|
||||
import AgentPerformanceTable from './AgentPerformanceTable';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '@cp/src/store';
|
||||
|
||||
const FieldGovernance = () => {
|
||||
const navigate = useNavigate();
|
||||
const params = readQueryParams();
|
||||
const { featureFlags } = useSelector((state: RootState) => ({
|
||||
featureFlags: state?.common?.featureFlags
|
||||
}));
|
||||
const selectedTab = params?.fieldGovernanceTab?.selectedTab;
|
||||
const { tabId = '' } = useParams();
|
||||
|
||||
const governanceTabRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (tabId === TabsKey.FIELD_GOVERNANCE) {
|
||||
setInitialGovernanceTabParams(params, navigate, featureFlags);
|
||||
governanceTabRef.current?.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}, [tabId]);
|
||||
|
||||
const onTabClickHandler = (val: string | boolean) => {
|
||||
const queryParams = { ...params };
|
||||
// Reseting Params - Page Details
|
||||
if (queryParams[QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE]) {
|
||||
queryParams[QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE].pageNumber = '1';
|
||||
queryParams[QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE].pageSize = '10';
|
||||
}
|
||||
|
||||
if (queryParams[QueryParamTypeMapping.AGENCY_DETAILS_TABLE]) {
|
||||
queryParams[QueryParamTypeMapping.AGENCY_DETAILS_TABLE].pageNumber = '1';
|
||||
queryParams[QueryParamTypeMapping.AGENCY_DETAILS_TABLE].pageSize = '10';
|
||||
}
|
||||
queryParams.fieldGovernanceTab.selectedTab = val;
|
||||
const updatedParams = createQueryParams(queryParams);
|
||||
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_FIELD_DASHBOARD_GOVERNANCE_TAB_SWITCH, {
|
||||
currentTab: selectedTab,
|
||||
newTab: val
|
||||
});
|
||||
navigate(updatedParams);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.governanceTabWrapper} ref={governanceTabRef}>
|
||||
<ToggleTabs
|
||||
containerClass={styles.toggleTabsContainer}
|
||||
activeTabClass={styles.activeTab}
|
||||
selected={selectedTab}
|
||||
tabOptions={[
|
||||
{
|
||||
label: FIELD_GOVERNANCE_TABS.MTD,
|
||||
value: FIELD_GOVERNANCE_TABS_VALUE.MTD
|
||||
},
|
||||
{
|
||||
label: FIELD_GOVERNANCE_TABS.TODAY,
|
||||
value: FIELD_GOVERNANCE_TABS_VALUE.TODAY
|
||||
}
|
||||
]}
|
||||
onTabClick={val => onTabClickHandler(val)}
|
||||
/>
|
||||
<AgencyDetailsTable />
|
||||
<AgentPerformanceTable />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FieldGovernance;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { RootState } from '@cp/src/store';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import DpdBucketTable from '../components/DpdBucketTableExternal';
|
||||
import { TabsKey } from '../constants';
|
||||
|
||||
const OverallPerformance = () => {
|
||||
const overallTabRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { featureFlags } = useSelector((state: RootState) => ({
|
||||
featureFlags: state?.common?.featureFlags
|
||||
}));
|
||||
const { overallPerformanceOverallMetricsTable } = featureFlags || {};
|
||||
const { tabId = '' } = useParams();
|
||||
|
||||
const isOverallPerformance = tabId === TabsKey.OVERALL_PERFORMANCE;
|
||||
|
||||
useEffect(() => {
|
||||
if (isOverallPerformance) {
|
||||
overallTabRef.current?.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}, [tabId]);
|
||||
|
||||
return (
|
||||
<div ref={overallTabRef}>
|
||||
{overallPerformanceOverallMetricsTable && <DpdBucketTable isVisible={isOverallPerformance} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OverallPerformance;
|
||||
@@ -0,0 +1,192 @@
|
||||
import { Dispatch } from '@reduxjs/toolkit';
|
||||
import axiosInstance, { ApiKeys, API_STATUS_CODE, getApiUrl, logError } from 'src/utils/ApiHelper';
|
||||
import {
|
||||
setAgencyDetailsTable,
|
||||
setAgencyNameOptions,
|
||||
setAgentPerformanceTable,
|
||||
setAgentPerformanceTlTable,
|
||||
setAgentsOptions,
|
||||
setDpdBucketTable,
|
||||
setLoadingAgencyDetails,
|
||||
setLoadingAgentPerformance,
|
||||
setLoadingAgentPerformanceTl,
|
||||
setLoadingDpdBucket,
|
||||
setStateOptions,
|
||||
setTablesLastUpdatedAt
|
||||
} from '../reducers/ExternalDashboardSenseiSlice';
|
||||
import { readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import { FIELD_GOVERNANCE_TABS_VALUE } from '../types';
|
||||
|
||||
export const getDpdBucketData = () => (dispatch: Dispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GET_DPD_BUCKET_DATA);
|
||||
const params = readQueryParams();
|
||||
dispatch(setLoadingDpdBucket(true));
|
||||
const { agencyCode = 'ALL', stateName = 'ALL' } = params?.dpdBucketTable || {};
|
||||
axiosInstance
|
||||
.get(url, {
|
||||
params: {
|
||||
stateName,
|
||||
agencyCode
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
const { performanceData, lastUpdatedAt } = response?.data || {};
|
||||
dispatch(setDpdBucketTable({ data: performanceData }));
|
||||
dispatch(
|
||||
setTablesLastUpdatedAt({
|
||||
dpdBucketLastUpdatedAt: lastUpdatedAt
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(setDpdBucketTable({ data: [] }));
|
||||
logError(err);
|
||||
})
|
||||
.finally(() => {
|
||||
dispatch(setLoadingDpdBucket(false));
|
||||
});
|
||||
};
|
||||
|
||||
export const getAgencyAndStateOptions = () => (dispatch: Dispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GET_AGENCY_NAME_AND_STATE_OPTIONS);
|
||||
const params = readQueryParams();
|
||||
const { stateName = 'ALL' } = params?.dpdBucketTable || {};
|
||||
axiosInstance
|
||||
.get(url, {
|
||||
params: {
|
||||
stateName
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
dispatch(setAgencyNameOptions(response?.data?.agencies));
|
||||
dispatch(setStateOptions(response?.data?.states));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
logError(err);
|
||||
});
|
||||
};
|
||||
|
||||
export const getAgencyOptions = () => (dispatch: Dispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GET_AGENCY_NAME_OPTIONS);
|
||||
axiosInstance
|
||||
.get(url)
|
||||
.then(response => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
const { agencies = [] } = response?.data || {};
|
||||
dispatch(setAgentsOptions(agencies));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
logError(err);
|
||||
});
|
||||
};
|
||||
|
||||
export const getAgencyDetailsData = () => (dispatch: Dispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GET_AGENCY_DETAILS_DATA);
|
||||
const params = readQueryParams();
|
||||
dispatch(setLoadingAgencyDetails(true));
|
||||
const { pageSize = '10', pageNumber = '1' } = params?.agencyDetailsTable || {};
|
||||
const { selectedTab } = params?.fieldGovernanceTab || {};
|
||||
|
||||
const isToday = selectedTab === FIELD_GOVERNANCE_TABS_VALUE.TODAY;
|
||||
axiosInstance
|
||||
.get(url, {
|
||||
params: {
|
||||
pageSize,
|
||||
pageNo: pageNumber - 1, // backend pagination starts from 0 so we are subtracting 1
|
||||
viewType: selectedTab
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
const { performanceData, lastUpdatedAt } = response?.data || {};
|
||||
|
||||
dispatch(setAgencyDetailsTable({ data: performanceData, isToday }));
|
||||
dispatch(
|
||||
setTablesLastUpdatedAt({
|
||||
agencyDetailsLastUpdatedAt: lastUpdatedAt
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(setAgencyDetailsTable({ data: [] }));
|
||||
logError(err);
|
||||
})
|
||||
.finally(() => {
|
||||
dispatch(setLoadingAgencyDetails(false));
|
||||
});
|
||||
};
|
||||
|
||||
export const getAgentPerformanceData = () => (dispatch: Dispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GET_AGENT_PERFORMANCE_DATA);
|
||||
const params = readQueryParams();
|
||||
dispatch(setLoadingAgentPerformance(true));
|
||||
const { agencyCode, pageSize = '10', pageNumber = '1' } = params?.agentPerformanceTable || {};
|
||||
const { selectedTab } = params?.fieldGovernanceTab || {};
|
||||
const isToday = selectedTab === FIELD_GOVERNANCE_TABS_VALUE.TODAY;
|
||||
|
||||
axiosInstance
|
||||
.get(url, {
|
||||
params: {
|
||||
agencyCode,
|
||||
pageSize,
|
||||
pageNo: pageNumber - 1, // backend pagination starts from 0 so we are subtracting 1
|
||||
viewType: selectedTab
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
const { performanceData, lastUpdatedAt } = response?.data || {};
|
||||
|
||||
dispatch(setAgentPerformanceTable({ data: performanceData, isToday }));
|
||||
dispatch(
|
||||
setTablesLastUpdatedAt({
|
||||
agentPerformanceLastUpdatedAt: lastUpdatedAt
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(setAgentPerformanceTable({ data: [] }));
|
||||
logError(err);
|
||||
})
|
||||
.finally(() => {
|
||||
dispatch(setLoadingAgentPerformance(false));
|
||||
});
|
||||
};
|
||||
|
||||
export const getAgentPerformanceTLData = () => (dispatch: Dispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GET_AGENT_PERFORMANCE_TL_DATA);
|
||||
const params = readQueryParams();
|
||||
dispatch(setLoadingAgentPerformanceTl(true));
|
||||
const { dpdBucket = 'ALL' } = params?.agentPerformanceTlTable || {};
|
||||
axiosInstance
|
||||
.get(url, {
|
||||
params: {
|
||||
dpdBucket
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
const { performanceData, lastUpdatedAt } = response?.data || {};
|
||||
dispatch(setAgentPerformanceTlTable({ data: performanceData }));
|
||||
dispatch(
|
||||
setTablesLastUpdatedAt({
|
||||
agentPerformanceTlLastUpdatedAt: lastUpdatedAt
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch(setAgentPerformanceTlTable({ data: [] }));
|
||||
logError(err);
|
||||
})
|
||||
.finally(() => {
|
||||
dispatch(setLoadingAgentPerformanceTl(false));
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import Loader from 'src/components/Loader/Loader';
|
||||
import { readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import RenderAgTable from '../RenderAgTable';
|
||||
import styles from './styles.module.scss';
|
||||
import commonStyles from '../commonStyles.module.scss';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RootState } from 'src/store';
|
||||
import { getAgencyDetailsData } from '../../actions/ExternalDashboardSenseiActions';
|
||||
import { AGENCY_DETAILS_COLUMNS } from '../ColumnDefs';
|
||||
import {
|
||||
FIELD_GOVERNANCE_TABLE_HEIGHT,
|
||||
POLLING_TIME,
|
||||
QueryParamTypeMapping
|
||||
} from '../../constants';
|
||||
import cx from 'classnames';
|
||||
import React, { useEffect } from 'react';
|
||||
import { poll } from '@cp/src/utils/polling';
|
||||
import { noop } from '@navi/web-ui/lib/utils/common';
|
||||
|
||||
interface IAgencyDetailsTableMtd {
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
const AgencyDetailsTableMtd: React.FC<IAgencyDetailsTableMtd> = props => {
|
||||
const { isVisible } = props;
|
||||
const params = readQueryParams();
|
||||
const { agencyDetailsTable } = useSelector((store: RootState) => ({
|
||||
agencyDetailsTable: store?.externalDashboardSensei?.agencyDetailsTable
|
||||
}));
|
||||
const unsubscribe = React.useRef<() => void>();
|
||||
const { agencyDetailsMtdData, loading } = agencyDetailsTable || {};
|
||||
const { data = [], pages } = agencyDetailsMtdData || {};
|
||||
|
||||
const totalElements = pages?.totalElements || 0;
|
||||
const currentPage = Number(params?.agencyDetailsTable?.pageNumber) || 1;
|
||||
const pageSize = Number(params?.agencyDetailsTable?.pageSize) || 10;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
unsubscribe.current = poll(
|
||||
() => dispatch(getAgencyDetailsData()),
|
||||
json => json,
|
||||
POLLING_TIME,
|
||||
noop
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
unsubscribe.current?.();
|
||||
};
|
||||
}, [isVisible]);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.agencyDetailsContainer, { [styles.noData]: !totalElements })}>
|
||||
<div className="stickyHeader">
|
||||
<div className={commonStyles.tableHeaderTitle}>Agency details</div>
|
||||
</div>
|
||||
<RenderAgTable
|
||||
data={data}
|
||||
colDefs={AGENCY_DETAILS_COLUMNS}
|
||||
pageDetails={{ totalElements, currentPage, pageSize }}
|
||||
tableName={QueryParamTypeMapping.AGENCY_DETAILS_TABLE}
|
||||
callBackFn={() => dispatch(getAgencyDetailsData())}
|
||||
tableHeight={FIELD_GOVERNANCE_TABLE_HEIGHT}
|
||||
/>
|
||||
<Loader
|
||||
show={loading}
|
||||
className={cx(commonStyles.loadingState, { [commonStyles.paginationLoading]: pageSize })}
|
||||
animate={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgencyDetailsTableMtd;
|
||||
@@ -0,0 +1,77 @@
|
||||
import Loader from 'src/components/Loader/Loader';
|
||||
import { readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import RenderAgTable from '../RenderAgTable';
|
||||
import styles from './styles.module.scss';
|
||||
import commonStyles from '../commonStyles.module.scss';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getAgencyDetailsData } from '../../actions/ExternalDashboardSenseiActions';
|
||||
import { TODAY_AGENCY_DETAILS_COLUMNS } from '../ColumnDefs';
|
||||
import { RootState } from '@cp/src/store';
|
||||
import {
|
||||
FIELD_GOVERNANCE_TABLE_HEIGHT,
|
||||
POLLING_TIME,
|
||||
QueryParamTypeMapping
|
||||
} from '../../constants';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { poll } from '@cp/src/utils/polling';
|
||||
import { noop } from '@navi/web-ui/lib/utils/common';
|
||||
import cx from 'classnames';
|
||||
|
||||
interface IAgencyDetailsTableToday {
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
const AgencyDetailsTableToday: React.FC<IAgencyDetailsTableToday> = props => {
|
||||
const { isVisible } = props;
|
||||
const params = readQueryParams();
|
||||
const { agencyDetailsTable } = useSelector((store: RootState) => ({
|
||||
agencyDetailsTable: store?.externalDashboardSensei?.agencyDetailsTable
|
||||
}));
|
||||
|
||||
const { agencyDetailsTodayData, loading } = agencyDetailsTable || {};
|
||||
const { data = [], pages } = agencyDetailsTodayData || {};
|
||||
const unsubscribe = useRef<() => void>();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const totalElements = pages?.totalElements || 0;
|
||||
const currentPage = Number(params?.agencyDetailsTable?.pageNumber) || 1;
|
||||
const pageSize = Number(params?.agencyDetailsTable?.pageSize) || 10;
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
unsubscribe.current = poll(
|
||||
() => dispatch(getAgencyDetailsData()),
|
||||
json => json,
|
||||
POLLING_TIME,
|
||||
noop
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
unsubscribe.current?.();
|
||||
};
|
||||
}, [isVisible]);
|
||||
|
||||
return (
|
||||
<div className={styles.agencyDetailsContainer}>
|
||||
<div className="stickyHeader">
|
||||
<div className={commonStyles.tableHeaderTitle}>Agency details</div>
|
||||
</div>
|
||||
<RenderAgTable
|
||||
data={data}
|
||||
colDefs={TODAY_AGENCY_DETAILS_COLUMNS}
|
||||
pageDetails={{ totalElements, currentPage, pageSize }}
|
||||
tableName={QueryParamTypeMapping.AGENCY_DETAILS_TABLE}
|
||||
callBackFn={() => dispatch(getAgencyDetailsData())}
|
||||
tableHeight={FIELD_GOVERNANCE_TABLE_HEIGHT}
|
||||
/>
|
||||
<Loader
|
||||
show={loading}
|
||||
className={cx(commonStyles.loadingState, { [commonStyles.paginationLoading]: pageSize })}
|
||||
animate={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgencyDetailsTableToday;
|
||||
@@ -0,0 +1,8 @@
|
||||
.agencyDetailsContainer {
|
||||
position: relative;
|
||||
margin-bottom: 90px;
|
||||
}
|
||||
|
||||
.noData {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import Loader from 'src/components/Loader/Loader';
|
||||
import { readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import styles from './styles.module.scss';
|
||||
import RenderAgTable from '../RenderAgTable';
|
||||
import FilterWrapper from '../FilterWrapper';
|
||||
import commonStyles from '../commonStyles.module.scss';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RootState } from 'src/store';
|
||||
import { getAgentPerformanceData } from '../../actions/ExternalDashboardSenseiActions';
|
||||
import { AGENCY_PERFORMANCE_COLUMNS } from '../ColumnDefs';
|
||||
import {
|
||||
FIELD_GOVERNANCE_TABLE_HEIGHT,
|
||||
filterTitle,
|
||||
filterTypeMapping,
|
||||
POLLING_TIME,
|
||||
QueryParamTypeMapping
|
||||
} from '../../constants';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { poll } from '@cp/src/utils/polling';
|
||||
import { noop } from '@navi/web-ui/lib/utils/common';
|
||||
import cx from 'classnames';
|
||||
|
||||
interface IAgentPerformanceTableMtd {
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
const AgentPerformanceTableMtd: React.FC<IAgentPerformanceTableMtd> = props => {
|
||||
const { isVisible } = props;
|
||||
const params = readQueryParams();
|
||||
const { agentPerformanceTable, agentOptions } = useSelector((store: RootState) => ({
|
||||
agentPerformanceTable: store?.externalDashboardSensei?.agentPerformanceTable,
|
||||
agentOptions: store?.externalDashboardSensei?.agentsOptions
|
||||
}));
|
||||
|
||||
const { agentPerformanceMtdData, loading } = agentPerformanceTable || {};
|
||||
const { data = [], pages } = agentPerformanceMtdData || {};
|
||||
const unsubscribe = useRef<() => void>();
|
||||
|
||||
const totalElements = pages?.totalElements || 0;
|
||||
const currentPage = Number(params?.agentPerformanceTable?.pageNumber) || 1;
|
||||
const pageSize = Number(params?.agentPerformanceTable?.pageSize) || 10;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
unsubscribe.current = poll(
|
||||
() => dispatch(getAgentPerformanceData()),
|
||||
json => json,
|
||||
POLLING_TIME,
|
||||
noop
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
unsubscribe.current?.();
|
||||
};
|
||||
}, [isVisible]);
|
||||
|
||||
return (
|
||||
<div className={styles.agentPerformanceContainer}>
|
||||
<div className={cx(styles.tableHeader, 'stickyHeader')}>
|
||||
<div className={commonStyles.tableHeaderTitle}>Agent Performance</div>
|
||||
<FilterWrapper
|
||||
title={filterTitle.agency}
|
||||
options={agentOptions}
|
||||
filterType={filterTypeMapping.agencyCode}
|
||||
tableType={QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE}
|
||||
callBackFn={() => {
|
||||
dispatch(getAgentPerformanceData());
|
||||
}}
|
||||
isPaginatedTable
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<RenderAgTable
|
||||
data={data}
|
||||
colDefs={AGENCY_PERFORMANCE_COLUMNS}
|
||||
pageDetails={{ totalElements, currentPage, pageSize }}
|
||||
tableName={QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE}
|
||||
callBackFn={() => dispatch(getAgentPerformanceData())}
|
||||
tableHeight={FIELD_GOVERNANCE_TABLE_HEIGHT}
|
||||
/>
|
||||
<Loader
|
||||
show={loading}
|
||||
className={cx(commonStyles.loadingState, { [commonStyles.paginationLoading]: pageSize })}
|
||||
animate={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentPerformanceTableMtd;
|
||||
@@ -0,0 +1,92 @@
|
||||
import Loader from 'src/components/Loader/Loader';
|
||||
import { readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import styles from './styles.module.scss';
|
||||
import RenderAgTable from '../RenderAgTable';
|
||||
import FilterWrapper from '../FilterWrapper';
|
||||
import commonStyles from '../commonStyles.module.scss';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getAgentPerformanceData } from '../../actions/ExternalDashboardSenseiActions';
|
||||
import { TODAY_AGENCY_PERFORMANCE_COLUMNS } from '../ColumnDefs';
|
||||
import { RootState } from '@cp/src/store';
|
||||
import {
|
||||
FIELD_GOVERNANCE_TABLE_HEIGHT,
|
||||
filterTitle,
|
||||
filterTypeMapping,
|
||||
POLLING_TIME,
|
||||
QueryParamTypeMapping
|
||||
} from '../../constants';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { poll } from '@cp/src/utils/polling';
|
||||
import { noop } from '@navi/web-ui/lib/utils/common';
|
||||
import cx from 'classnames';
|
||||
|
||||
interface IAgentPerformanceTableToday {
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
const AgentPerformanceTableToday: React.FC<IAgentPerformanceTableToday> = props => {
|
||||
const { isVisible } = props;
|
||||
const params = readQueryParams();
|
||||
const { agentPerformanceTable, agentOptions } = useSelector((store: RootState) => ({
|
||||
agentPerformanceTable: store?.externalDashboardSensei?.agentPerformanceTable,
|
||||
agentOptions: store?.externalDashboardSensei?.agentsOptions
|
||||
}));
|
||||
const unsubscribe = useRef<() => void>();
|
||||
|
||||
const { agentPerformanceTodayData, loading } = agentPerformanceTable || {};
|
||||
const { data = [], pages } = agentPerformanceTodayData || {};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const totalElements = pages?.totalElements || 0;
|
||||
const currentPage = Number(params?.agentPerformanceTable?.pageNumber) || 1;
|
||||
const pageSize = Number(params?.agentPerformanceTable?.pageSize) || 10;
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
unsubscribe.current = poll(
|
||||
() => dispatch(getAgentPerformanceData()),
|
||||
json => json,
|
||||
POLLING_TIME,
|
||||
noop
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
unsubscribe.current?.();
|
||||
};
|
||||
}, [isVisible]);
|
||||
|
||||
return (
|
||||
<div className={styles.agentPerformanceContainer}>
|
||||
<div className={cx(styles.tableHeader, 'stickyHeader')}>
|
||||
<div className={commonStyles.tableHeaderTitle}>Agent Performance</div>
|
||||
<FilterWrapper
|
||||
title={filterTitle.agency}
|
||||
options={agentOptions}
|
||||
filterType={filterTypeMapping.agencyCode}
|
||||
tableType={QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE}
|
||||
callBackFn={() => {
|
||||
dispatch(getAgentPerformanceData());
|
||||
}}
|
||||
isPaginatedTable
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<RenderAgTable
|
||||
data={data}
|
||||
colDefs={TODAY_AGENCY_PERFORMANCE_COLUMNS}
|
||||
pageDetails={{ totalElements, currentPage, pageSize }}
|
||||
tableName={QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE}
|
||||
callBackFn={() => dispatch(getAgentPerformanceData())}
|
||||
tableHeight={FIELD_GOVERNANCE_TABLE_HEIGHT}
|
||||
/>
|
||||
<Loader
|
||||
show={loading}
|
||||
className={cx(commonStyles.loadingState, { [commonStyles.paginationLoading]: pageSize })}
|
||||
animate={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentPerformanceTableToday;
|
||||
@@ -0,0 +1,8 @@
|
||||
.agentPerformanceContainer {
|
||||
position: relative;
|
||||
margin-bottom: 92px;
|
||||
}
|
||||
|
||||
.tableHeader {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
886
src/pages/ExternalDashboardSensei/components/ColumnDefs.tsx
Normal file
886
src/pages/ExternalDashboardSensei/components/ColumnDefs.tsx
Normal file
@@ -0,0 +1,886 @@
|
||||
import TableCellRenderer from '@cp/src/components/TableCellRenderer';
|
||||
import CellRendererShortNumber from '@cp/src/components/TableCellRenderer/CellRendererShortNumber';
|
||||
import { ColDefsType } from '@navi/web-ui/lib/components/AgTable/types';
|
||||
import { GridReadyEvent, ICellRendererParams } from 'ag-grid-community';
|
||||
import { formatNumber, pluralisation, toFixedValueNotation } from 'src/utils/commonUtils';
|
||||
|
||||
export const handleGridReady = (event: GridReadyEvent<any>, gridRef: any) => {
|
||||
gridRef.current = event;
|
||||
event.api.sizeColumnsToFit();
|
||||
};
|
||||
|
||||
const secondsToHM = (seconds: number) => {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
|
||||
if (!hours && !minutes) return '0 min';
|
||||
|
||||
return `${hours > 0 ? pluralisation(`${hours} hr`, hours, 's') : ''} ${
|
||||
minutes > 0 ? pluralisation(`${minutes} min`, minutes, 's') : ''
|
||||
}`;
|
||||
};
|
||||
|
||||
export const DPD_BUCKET_COLUMNS = [
|
||||
{
|
||||
headerComponent: () => <>DPD bucket</>,
|
||||
field: 'dpdBucket',
|
||||
width: 120,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { dpdBucket } = params.data || {};
|
||||
return <TableCellRenderer value={dpdBucket} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Performance band</>,
|
||||
field: 'performanceBand',
|
||||
width: 140,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { performanceBand } = params?.data || {};
|
||||
return <TableCellRenderer value={performanceBand ?? ' '} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>LANs allocated</>,
|
||||
field: 'lansAllocated',
|
||||
width: 110,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { lansAllocated } = params?.data || {};
|
||||
const parsedValue = formatNumber(lansAllocated ?? 0);
|
||||
return <TableCellRenderer value={parsedValue} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Agent allocation</>,
|
||||
field: 'agentAllocationPercentage',
|
||||
width: 120,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { agentAllocationPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(agentAllocationPercentage);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>EMI OD</>,
|
||||
field: 'emiOverdueAmount',
|
||||
width: 100,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { emiOverdueAmount } = params?.data || {};
|
||||
return <CellRendererShortNumber value={emiOverdueAmount} isCurrency significantDigits={2} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Target CE</>,
|
||||
field: 'targetCEPercentage',
|
||||
width: 90,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { targetCEPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(targetCEPercentage);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Target OD <br /> amount
|
||||
</>
|
||||
),
|
||||
field: 'targetOverdueAmount',
|
||||
width: 115,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { targetOverdueAmount } = params?.data || {};
|
||||
return (
|
||||
<CellRendererShortNumber value={targetOverdueAmount} isCurrency significantDigits={2} />
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>EMI CE</>,
|
||||
field: 'emiCEPercentage',
|
||||
width: 80,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { emiCEPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(emiCEPercentage);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
EMI OD <br /> collected
|
||||
</>
|
||||
),
|
||||
field: 'emiOverdueCollected',
|
||||
width: 110,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { emiOverdueCollected } = params?.data || {};
|
||||
return (
|
||||
<CellRendererShortNumber value={emiOverdueCollected} isCurrency significantDigits={2} />
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Achievement</>,
|
||||
field: 'achievementPercentage',
|
||||
width: 130,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { achievementPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(achievementPercentage);
|
||||
return (
|
||||
<TableCellRenderer
|
||||
value={parsedValue ? `${parsedValue}%` : ''}
|
||||
ellipsisWrapperClass={
|
||||
achievementPercentage >= 100 ? 'text-[var(--green-base)]' : 'text-[var(--red-base)]'
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
LMSD <br /> achievement
|
||||
</>
|
||||
),
|
||||
field: 'lmsdAchievedPercentage',
|
||||
width: 125,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { lmsdAchievedPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(lmsdAchievedPercentage);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Non-Enach <br /> cash collected
|
||||
</>
|
||||
),
|
||||
field: 'nonEnachCashCollected',
|
||||
width: 140,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { nonEnachCashCollected } = params?.data || {};
|
||||
return (
|
||||
<CellRendererShortNumber value={nonEnachCashCollected} isCurrency significantDigits={2} />
|
||||
);
|
||||
}
|
||||
}
|
||||
] as ColDefsType[];
|
||||
|
||||
export const AGENCY_PERFORMANCE_COLUMNS = [
|
||||
{
|
||||
headerComponent: () => <>Agent</>,
|
||||
field: 'agentName',
|
||||
width: 200,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { agentName, agencyName } = params.data || {};
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<TableCellRenderer value={agentName} toolTipContent={agentName} showToolTip />
|
||||
<div className="text-[--navi-color-gray-c3] text-xs">
|
||||
<TableCellRenderer value={agencyName} toolTipContent={agencyName} showToolTip />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
LANs <br />
|
||||
allocated
|
||||
</>
|
||||
),
|
||||
field: 'totalAllocatedLans',
|
||||
width: 90,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { totalAllocatedLans } = params?.data || {};
|
||||
const parsedValue = formatNumber(totalAllocatedLans ?? 0);
|
||||
return <TableCellRenderer value={parsedValue} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
31-90 <br />
|
||||
coverage
|
||||
</>
|
||||
),
|
||||
field: 'coverageThirtyOneNinetyLansPercent',
|
||||
width: 100,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { coverageThirtyOneNinetyLansPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(coverageThirtyOneNinetyLansPercent);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
90+ <br />
|
||||
coverage
|
||||
</>
|
||||
),
|
||||
field: 'coverageNinetyPlusLansPercent',
|
||||
width: 90,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { coverageNinetyPlusLansPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(coverageNinetyPlusLansPercent);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
31-90 genuine <br />
|
||||
coverage
|
||||
</>
|
||||
),
|
||||
field: 'genuineCoverageThirtyOneNinetyLansPercent',
|
||||
width: 120,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { genuineCoverageThirtyOneNinetyLansPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(genuineCoverageThirtyOneNinetyLansPercent);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
90+ genuine <br />
|
||||
coverage
|
||||
</>
|
||||
),
|
||||
field: 'genuineCoverageNinetyPlusLansPercent',
|
||||
width: 130,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { genuineCoverageNinetyPlusLansPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(genuineCoverageNinetyPlusLansPercent);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Visits <br />
|
||||
/day
|
||||
</>
|
||||
),
|
||||
field: 'visitsPerDay',
|
||||
width: 60,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { visitsPerDay } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(visitsPerDay);
|
||||
return <TableCellRenderer value={parsedValue} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Genuine <br />
|
||||
visits/day
|
||||
</>
|
||||
),
|
||||
field: 'genuineVisitsPerDay',
|
||||
width: 90,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { genuineVisitsPerDay } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(genuineVisitsPerDay);
|
||||
return <TableCellRenderer value={parsedValue} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
PTPs <br />
|
||||
generated
|
||||
</>
|
||||
),
|
||||
field: 'ptpsGenerated',
|
||||
width: 100,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { ptpsGenerated, ptpsGeneratedPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(ptpsGeneratedPercent);
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<TableCellRenderer value={ptpsGenerated} />
|
||||
<div className="text-[--navi-color-gray-c3] text-xs">
|
||||
<TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Broken <br />
|
||||
PTP
|
||||
</>
|
||||
),
|
||||
field: 'brokenPtpPercentage',
|
||||
width: 70,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { brokenPtpPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(brokenPtpPercentage);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Distance <br />
|
||||
travelled/day
|
||||
</>
|
||||
),
|
||||
field: 'distanceTravelledPerDay',
|
||||
width: 110,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { distanceTravelledPerDay } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(distanceTravelledPerDay);
|
||||
return (
|
||||
<TableCellRenderer
|
||||
value={
|
||||
parsedValue
|
||||
? pluralisation(
|
||||
`${formatNumber(Number(parsedValue))} km`,
|
||||
distanceTravelledPerDay,
|
||||
's'
|
||||
)
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
App usage <br />
|
||||
time/day
|
||||
</>
|
||||
),
|
||||
field: 'appUsagePerDay',
|
||||
width: 110,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { appUsagePerDay } = params?.data || {};
|
||||
return <TableCellRenderer value={secondsToHM(appUsagePerDay)} />;
|
||||
}
|
||||
}
|
||||
] as ColDefsType[];
|
||||
|
||||
export const TODAY_AGENCY_PERFORMANCE_COLUMNS = [
|
||||
{
|
||||
headerComponent: () => <>Agent</>,
|
||||
field: 'agentName',
|
||||
width: 240,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { agentName, agencyName } = params.data || {};
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<TableCellRenderer value={agentName} toolTipContent={agentName} showToolTip />
|
||||
<div className="text-[--navi-color-gray-c3] text-xs">
|
||||
<TableCellRenderer value={agencyName} toolTipContent={agencyName} showToolTip />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Visits</>,
|
||||
field: 'visitsToday',
|
||||
width: 160,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { visitsToday } = params?.data || {};
|
||||
return <TableCellRenderer value={visitsToday} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Today’s PTP</>,
|
||||
field: 'ptpsToday',
|
||||
width: 180,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { ptpsToday } = params?.data || {};
|
||||
return <TableCellRenderer value={ptpsToday} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Today’s PTP visit</>,
|
||||
field: 'ptpsVisitedToday',
|
||||
width: 150,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { ptpsVisitedToday } = params?.data || {};
|
||||
return <TableCellRenderer value={ptpsVisitedToday} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Partially/Fully Paid Accounts</>,
|
||||
field: 'accountsPaidToday',
|
||||
width: 150,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { accountsPaidToday } = params?.data || {};
|
||||
return <TableCellRenderer value={accountsPaidToday} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>OD collected</>,
|
||||
field: 'odCollectedToday',
|
||||
width: 160,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { odCollectedToday } = params?.data || {};
|
||||
return <CellRendererShortNumber value={odCollectedToday} isCurrency significantDigits={2} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Total cash collected</>,
|
||||
field: 'cashCollectedToday',
|
||||
width: 160,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { cashCollectedToday } = params?.data || {};
|
||||
return (
|
||||
<CellRendererShortNumber value={cashCollectedToday} isCurrency significantDigits={2} />
|
||||
);
|
||||
}
|
||||
}
|
||||
] as ColDefsType[];
|
||||
|
||||
export const AGENCY_DETAILS_COLUMNS = [
|
||||
{
|
||||
headerComponent: () => <>Agency</>,
|
||||
field: 'agencyName',
|
||||
width: 200,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { agencyName } = params.data || {};
|
||||
return <TableCellRenderer value={agencyName} toolTipContent={agencyName} showToolTip />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
31-90 <br />
|
||||
coverage
|
||||
</>
|
||||
),
|
||||
field: 'coverageThirtyOneNinetyLansPercent',
|
||||
width: 90,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { coverageThirtyOneNinetyLansPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(coverageThirtyOneNinetyLansPercent);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
90+ <br />
|
||||
coverage
|
||||
</>
|
||||
),
|
||||
field: 'coverageNinetyPlusLansPercent',
|
||||
width: 90,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { coverageNinetyPlusLansPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(coverageNinetyPlusLansPercent);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
31-90 genuine <br />
|
||||
coverage
|
||||
</>
|
||||
),
|
||||
field: 'genuineCoverageThirtyOneNinetyLansPercent',
|
||||
width: 90,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { genuineCoverageThirtyOneNinetyLansPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(genuineCoverageThirtyOneNinetyLansPercent);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
90+ genuine <br />
|
||||
coverage
|
||||
</>
|
||||
),
|
||||
field: 'genuineCoverageNinetyPlusLansPercent',
|
||||
width: 110,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { genuineCoverageNinetyPlusLansPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(genuineCoverageNinetyPlusLansPercent);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Visits/agent/ <br />
|
||||
day
|
||||
</>
|
||||
),
|
||||
field: 'visitsPerDayPerAgent',
|
||||
width: 120,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { visitsPerDayPerAgent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(visitsPerDayPerAgent);
|
||||
return <TableCellRenderer value={parsedValue} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Genuine visits <br />
|
||||
/agent/day
|
||||
</>
|
||||
),
|
||||
field: 'genuineVisitsPerDayPerAgent',
|
||||
width: 130,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { genuineVisitsPerDayPerAgent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(genuineVisitsPerDayPerAgent);
|
||||
return <TableCellRenderer value={parsedValue} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
PTPs <br />
|
||||
generated
|
||||
</>
|
||||
),
|
||||
field: 'ptpsGenerated',
|
||||
width: 110,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { ptpsGenerated, ptpsGeneratedPercent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(ptpsGeneratedPercent);
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<TableCellRenderer value={ptpsGenerated} />
|
||||
<div className="text-[--navi-color-gray-c3] text-xs">
|
||||
<TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Broken <br />
|
||||
PTP
|
||||
</>
|
||||
),
|
||||
field: 'brokenPtpPercentage',
|
||||
width: 90,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { brokenPtpPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(brokenPtpPercentage);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
Distance travelled <br />
|
||||
/agent/day
|
||||
</>
|
||||
),
|
||||
field: 'distanceTravelledPerDayPerAgent',
|
||||
width: 140,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { distanceTravelledPerDayPerAgent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(distanceTravelledPerDayPerAgent);
|
||||
return (
|
||||
<TableCellRenderer
|
||||
value={
|
||||
parsedValue
|
||||
? pluralisation(
|
||||
`${formatNumber(Number(parsedValue))} km`,
|
||||
distanceTravelledPerDayPerAgent,
|
||||
's'
|
||||
)
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => (
|
||||
<>
|
||||
App usage time/ <br />
|
||||
agent/day
|
||||
</>
|
||||
),
|
||||
field: 'appUsagePerDayPerAgent',
|
||||
width: 135,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { appUsagePerDayPerAgent } = params?.data || {};
|
||||
return <TableCellRenderer value={secondsToHM(appUsagePerDayPerAgent)} />;
|
||||
}
|
||||
}
|
||||
] as ColDefsType[];
|
||||
|
||||
export const TODAY_AGENCY_DETAILS_COLUMNS = [
|
||||
{
|
||||
headerComponent: () => <>Agency</>,
|
||||
field: 'agencyName',
|
||||
width: 240,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { agencyName } = params.data || {};
|
||||
return <TableCellRenderer value={agencyName} toolTipContent={agencyName} showToolTip />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Visits/agent</>,
|
||||
field: 'visitsTodayPerAgent',
|
||||
width: 160,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { visitsTodayPerAgent } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(visitsTodayPerAgent);
|
||||
return <TableCellRenderer value={parsedValue} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Today’s PTP accounts</>,
|
||||
field: 'totalPtpsToday',
|
||||
width: 180,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { totalPtpsToday } = params?.data || {};
|
||||
return <TableCellRenderer value={totalPtpsToday} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Today’s PTP visit</>,
|
||||
field: 'totalPtpsVisitedToday',
|
||||
width: 150,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { totalPtpsVisitedToday } = params?.data || {};
|
||||
return <TableCellRenderer value={totalPtpsVisitedToday} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Partially/Fully Paid Accounts</>,
|
||||
field: 'totalAccountsPaidToday',
|
||||
width: 150,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { totalAccountsPaidToday } = params?.data || {};
|
||||
return <TableCellRenderer value={totalAccountsPaidToday} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>OD collected</>,
|
||||
field: 'totalOdCollectedToday',
|
||||
width: 160,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { totalOdCollectedToday } = params?.data || {};
|
||||
return (
|
||||
<CellRendererShortNumber value={totalOdCollectedToday} isCurrency significantDigits={2} />
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerComponent: () => <>Total cash collected</>,
|
||||
field: 'totalCashCollectedToday',
|
||||
width: 160,
|
||||
wrapHeaderText: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { totalCashCollectedToday } = params?.data || {};
|
||||
return (
|
||||
<CellRendererShortNumber value={totalCashCollectedToday} isCurrency significantDigits={2} />
|
||||
);
|
||||
}
|
||||
}
|
||||
] as ColDefsType[];
|
||||
|
||||
export const AGENT_PERFORMANCE_COLUMNS = [
|
||||
{
|
||||
field: 'agentName',
|
||||
headerName: 'Agent Name',
|
||||
width: 240,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { agentName } = params.data || {};
|
||||
return <TableCellRenderer value={agentName} toolTipContent={agentName} showToolTip />;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'lansAllocated',
|
||||
headerName: 'LANs allocated',
|
||||
width: 160,
|
||||
wrapHeaderText: true,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { lansAllocated } = params?.data || {};
|
||||
const parsedValue = formatNumber(lansAllocated ?? 0);
|
||||
return <TableCellRenderer value={parsedValue} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: 'EMI OD',
|
||||
field: 'emiOverdueAmount',
|
||||
width: 180,
|
||||
wrapHeaderText: true,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { emiOverdueAmount } = params?.data || {};
|
||||
return <CellRendererShortNumber value={emiOverdueAmount} isCurrency significantDigits={2} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: 'Target CE',
|
||||
field: 'targetCEPercentage',
|
||||
width: 90,
|
||||
wrapHeaderText: true,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { targetCEPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(targetCEPercentage);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: 'Taregt OD amount',
|
||||
field: 'targetOverdueAmount',
|
||||
width: 150,
|
||||
wrapHeaderText: true,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { targetOverdueAmount } = params?.data || {};
|
||||
return (
|
||||
<CellRendererShortNumber value={targetOverdueAmount} isCurrency significantDigits={2} />
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: 'EMI CE',
|
||||
field: 'emiCEPercentage',
|
||||
width: 150,
|
||||
wrapHeaderText: true,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { emiCEPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(emiCEPercentage);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: 'EMI OD collected',
|
||||
field: 'emiOverdueCollected',
|
||||
width: 160,
|
||||
wrapHeaderText: true,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { emiOverdueCollected } = params?.data || {};
|
||||
return (
|
||||
<CellRendererShortNumber value={emiOverdueCollected} isCurrency significantDigits={2} />
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: 'Achievement',
|
||||
field: 'achievementPercentage',
|
||||
width: 160,
|
||||
wrapHeaderText: true,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { achievementPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(achievementPercentage);
|
||||
return (
|
||||
<TableCellRenderer
|
||||
value={parsedValue ? `${parsedValue}%` : ''}
|
||||
ellipsisWrapperClass={
|
||||
achievementPercentage >= 100 ? 'text-[var(--green-base)]' : 'text-[var(--red-base)]'
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: 'LMSD achievement',
|
||||
field: 'lmsdAchievedPercentage',
|
||||
width: 125,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { lmsdAchievedPercentage } = params?.data || {};
|
||||
const parsedValue = toFixedValueNotation(lmsdAchievedPercentage);
|
||||
return <TableCellRenderer value={parsedValue ? `${parsedValue}%` : ''} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: 'Non-Enach cash collected',
|
||||
field: 'nonEnachCashCollected',
|
||||
width: 140,
|
||||
wrapHeaderText: true,
|
||||
wrapText: true,
|
||||
sortable: true,
|
||||
cellRenderer: (params: ICellRendererParams) => {
|
||||
const { nonEnachCashCollected } = params?.data || {};
|
||||
return (
|
||||
<CellRendererShortNumber value={nonEnachCashCollected} isCurrency significantDigits={2} />
|
||||
);
|
||||
}
|
||||
}
|
||||
] as ColDefsType[];
|
||||
@@ -0,0 +1,124 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import Loader from 'src/components/Loader/Loader';
|
||||
import FilterWrapper from '../FilterWrapper';
|
||||
import RenderAgTable from '../RenderAgTable';
|
||||
import styles from './styles.module.scss';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RootState } from 'src/store';
|
||||
import {
|
||||
getAgencyAndStateOptions,
|
||||
getDpdBucketData
|
||||
} from '../../actions/ExternalDashboardSenseiActions';
|
||||
import { DPD_BUCKET_COLUMNS } from '../ColumnDefs';
|
||||
import commonStyles from '../commonStyles.module.scss';
|
||||
import {
|
||||
alternativeColorStyles,
|
||||
DPD_BUCKET_TABLE_HEIGHT,
|
||||
filterTitle,
|
||||
filterTypeMapping,
|
||||
rowIndexZeroStyles,
|
||||
QueryParamTypeMapping,
|
||||
POLLING_TIME
|
||||
} from '../../constants';
|
||||
import { poll } from '@cp/src/utils/polling';
|
||||
import { noop } from '@navi/web-ui/lib/utils/common';
|
||||
import cx from 'classnames';
|
||||
|
||||
interface IDpdBucketTable {
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
const DpdBucketTable: React.FC<IDpdBucketTable> = props => {
|
||||
const { isVisible } = props;
|
||||
const { dpdBucketTable, agencyNameOptions, stateOptions, featureflags } = useSelector(
|
||||
(store: RootState) => ({
|
||||
dpdBucketTable: store?.externalDashboardSensei?.dpdBucketTable,
|
||||
agencyNameOptions: store?.externalDashboardSensei?.agencyNameOptions,
|
||||
stateOptions: store?.externalDashboardSensei?.stateOptions,
|
||||
featureflags: store?.common?.featureFlags
|
||||
})
|
||||
);
|
||||
|
||||
const { overallPerformanceAgentPerformanceTable } = featureflags;
|
||||
|
||||
const { dpdBucketData, loading } = dpdBucketTable || {};
|
||||
const { data = [] } = dpdBucketData || {};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const unsubscribe = useRef<() => void>();
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
// Fetching Agency Name and State Name Dropdown
|
||||
dispatch(getAgencyAndStateOptions());
|
||||
|
||||
unsubscribe.current = poll(
|
||||
() => dispatch(getDpdBucketData()),
|
||||
json => json,
|
||||
POLLING_TIME,
|
||||
noop
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
unsubscribe.current?.();
|
||||
};
|
||||
}, [isVisible]);
|
||||
|
||||
const filterCallbackFn = () => {
|
||||
if (!overallPerformanceAgentPerformanceTable) {
|
||||
dispatch(getAgencyAndStateOptions());
|
||||
}
|
||||
dispatch(getDpdBucketData());
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.dpdBucketContainer}>
|
||||
<div className={cx(styles.tableHeader, 'stickyHeader')}>
|
||||
<FilterWrapper
|
||||
title={filterTitle.state}
|
||||
options={stateOptions}
|
||||
filterType={filterTypeMapping.stateName}
|
||||
tableType={QueryParamTypeMapping.DPD_BUCKET_TABLE}
|
||||
callBackFn={filterCallbackFn}
|
||||
disabled={loading}
|
||||
/>
|
||||
{!overallPerformanceAgentPerformanceTable && (
|
||||
<FilterWrapper
|
||||
title={filterTitle.agencyName}
|
||||
options={agencyNameOptions}
|
||||
filterType={filterTypeMapping.agencyCode}
|
||||
tableType={QueryParamTypeMapping.DPD_BUCKET_TABLE}
|
||||
callBackFn={() => {
|
||||
dispatch(getDpdBucketData());
|
||||
}}
|
||||
disabled={loading}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative">
|
||||
<RenderAgTable
|
||||
data={data}
|
||||
colDefs={DPD_BUCKET_COLUMNS}
|
||||
tableName={QueryParamTypeMapping.DPD_BUCKET_TABLE}
|
||||
tableHeight={DPD_BUCKET_TABLE_HEIGHT}
|
||||
getRowStyle={params => {
|
||||
// if index is 0 applying different styles
|
||||
if (params.node.rowIndex === 0) {
|
||||
return rowIndexZeroStyles;
|
||||
}
|
||||
|
||||
// for alternative color
|
||||
if (params.node.rowIndex && params.node.rowIndex % 2 === 0) {
|
||||
return alternativeColorStyles;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Loader show={loading} className={commonStyles.loadingState} animate={false} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DpdBucketTable;
|
||||
@@ -0,0 +1,9 @@
|
||||
.dpdBucketContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tableHeader {
|
||||
padding: 16px 0 8px 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import Filter from '@cp/src/components/Filter';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@cp/src/service/clickStream.constant';
|
||||
import { addClickstreamEvent } from '@cp/src/service/clickStreamEventService';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { SelectPickerOptionProps, SelectPickerValue } from 'src/components/interfaces';
|
||||
import { createQueryParams, readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import { filterTypeMapping } from '../constants';
|
||||
import { IFilterWrapper } from '../types';
|
||||
import styles from './commonStyles.module.scss';
|
||||
import Loader from '@cp/src/components/Loader/Loader';
|
||||
|
||||
const FilterWrapper: React.FC<IFilterWrapper> = props => {
|
||||
const {
|
||||
title,
|
||||
options,
|
||||
filterType,
|
||||
tableType,
|
||||
callBackFn,
|
||||
isPaginatedTable = false,
|
||||
disabled = false,
|
||||
isSingleSelect = true
|
||||
} = props;
|
||||
const params = readQueryParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const selectedValue = params?.[tableType]?.[filterType];
|
||||
const selectedLabel = useMemo(
|
||||
() => options?.find(a => a.value === selectedValue)?.label,
|
||||
[params, options]
|
||||
);
|
||||
const { tabId = '' } = useParams();
|
||||
|
||||
const onSelectionChange = (selectedFilter: any) => {
|
||||
if (!params) return;
|
||||
let updatedSelectedFilter = '';
|
||||
if (!isSingleSelect) {
|
||||
updatedSelectedFilter = selectedFilter?.map((filter: any) => filter.value).join(',');
|
||||
} else {
|
||||
updatedSelectedFilter = selectedFilter.value;
|
||||
}
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_FIELD_DASHBOARD_FILTER_APPLIED, {
|
||||
section: tabId,
|
||||
filterType: filterType,
|
||||
oldValue: selectedValue ?? null,
|
||||
newValue: updatedSelectedFilter ?? null
|
||||
});
|
||||
const updatedParams = { ...params };
|
||||
updatedParams[tableType] = {
|
||||
...updatedParams[tableType],
|
||||
[filterType]: updatedSelectedFilter
|
||||
};
|
||||
if (updatedParams[tableType].agencyCode && filterType === filterTypeMapping.stateName) {
|
||||
delete updatedParams[tableType].agencyCode;
|
||||
}
|
||||
if (isPaginatedTable) updatedParams[tableType].pageNumber = '1';
|
||||
const paramsData = createQueryParams(updatedParams);
|
||||
navigate(paramsData);
|
||||
callBackFn?.();
|
||||
};
|
||||
|
||||
const onClickHandler = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_FIELD_DASHBOARD_FILTER_CLICKED, {
|
||||
section: tabId,
|
||||
filterType: filterType,
|
||||
existingValue: selectedValue ?? null
|
||||
});
|
||||
};
|
||||
|
||||
const selectedValues = selectedValue?.split(',');
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Filter
|
||||
key={`${filterType}_${selectedValue}`}
|
||||
title={selectedLabel ?? title}
|
||||
options={options as unknown as SelectPickerOptionProps[]}
|
||||
onClick={onClickHandler}
|
||||
onSelectionChange={change => onSelectionChange(change as any)}
|
||||
isSingleSelect={isSingleSelect}
|
||||
showSearchBar
|
||||
filterClass={styles.filterContainer}
|
||||
containerAppliedClass={styles.titleClass}
|
||||
selectedValue={selectedValue}
|
||||
selectedValues={selectedValues}
|
||||
/>
|
||||
<Loader show={disabled} className={styles.loadingState} animate={false} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterWrapper;
|
||||
@@ -0,0 +1,47 @@
|
||||
$headerzIndex: 3;
|
||||
|
||||
.header {
|
||||
padding: 24px 24px 16px;
|
||||
display: flex;
|
||||
top: 0;
|
||||
position: sticky;
|
||||
z-index: $headerzIndex;
|
||||
background-color: var(--bg-primary);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.headerTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lastUpdatedAt {
|
||||
color: var(--navi-color-gray-c3);
|
||||
}
|
||||
|
||||
.lastRefreshedContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.refresh {
|
||||
cursor: pointer;
|
||||
|
||||
.refreshWrapper {
|
||||
color: var(--blue-base);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.refreshIcon {
|
||||
margin-right: 4px;
|
||||
|
||||
fill {
|
||||
color: var(--blue-base);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
108
src/pages/ExternalDashboardSensei/components/Header/index.tsx
Normal file
108
src/pages/ExternalDashboardSensei/components/Header/index.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import LocationIcon from '@cp/src/assets/icons/Location';
|
||||
import APP_ROUTES from '@cp/src/layout/Routes';
|
||||
import { LIVE_LOCATION_SOURCE } from '@cp/src/pages/LiveLocationTracker/constants/LiveLocatonTrackerConstants';
|
||||
import { getLiveLocationParams } from '@cp/src/pages/LiveLocationTracker/utils';
|
||||
import { showAgentsLiveLocation } from '@cp/src/pages/PerformanceDashboard/PerformanceDashboard';
|
||||
import {
|
||||
PERFORMANCE_DASHBOARD_CLICKSTREAM,
|
||||
PERFORMANCE_DASHBOARD_EVENTS
|
||||
} from '@cp/src/service/clickStream.constant';
|
||||
import { addClickstreamEvent } from '@cp/src/service/clickStreamEventService';
|
||||
import { RootState } from '@cp/src/store';
|
||||
import { DateFormat, dateFormat } from '@cp/src/utils/DateHelper';
|
||||
import { RefreshIcon } from '@navi/web-ui/lib/icons';
|
||||
import { Button, Typography } from '@navi/web-ui/lib/primitives';
|
||||
import { useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import {
|
||||
getAgencyDetailsData,
|
||||
getAgentPerformanceData,
|
||||
getAgentPerformanceTLData,
|
||||
getDpdBucketData
|
||||
} from '../../actions/ExternalDashboardSenseiActions';
|
||||
import { TabsKey } from '../../constants';
|
||||
import { getLastUpdatedAt } from '../../utils';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const Header = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { tabId = '' } = useParams();
|
||||
|
||||
const { allLastUpdatedAt, userData, featureFlags } = useSelector((state: RootState) => ({
|
||||
allLastUpdatedAt: state?.externalDashboardSensei?.allLastUpdatedAt,
|
||||
userData: state?.common?.userData,
|
||||
featureFlags: state?.common?.featureFlags
|
||||
}));
|
||||
|
||||
const {
|
||||
overallPerformanceOverallMetricsTable,
|
||||
fieldGovernanceAgencyPerformanceTable,
|
||||
fieldGovernanceAgentPerformanceTable,
|
||||
overallPerformanceAgentPerformanceTable
|
||||
} = featureFlags || {};
|
||||
|
||||
const lastUpdatedAt = useMemo(
|
||||
() => getLastUpdatedAt(allLastUpdatedAt, tabId),
|
||||
[tabId, allLastUpdatedAt]
|
||||
);
|
||||
const { roles } = userData || {};
|
||||
const handleRefresh = () => {
|
||||
switch (tabId) {
|
||||
case TabsKey.OVERALL_PERFORMANCE:
|
||||
if (overallPerformanceOverallMetricsTable) dispatch(getDpdBucketData());
|
||||
if (overallPerformanceAgentPerformanceTable) dispatch(getAgentPerformanceTLData());
|
||||
break;
|
||||
case TabsKey.FIELD_GOVERNANCE:
|
||||
if (fieldGovernanceAgencyPerformanceTable) dispatch(getAgencyDetailsData());
|
||||
if (fieldGovernanceAgentPerformanceTable) dispatch(getAgentPerformanceData());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const enableAgentLiveLocation = useMemo(() => showAgentsLiveLocation(roles), [roles]);
|
||||
|
||||
const handleLiveLocationClick = () => {
|
||||
addClickstreamEvent(
|
||||
PERFORMANCE_DASHBOARD_CLICKSTREAM[PERFORMANCE_DASHBOARD_EVENTS.LH_AGENT_TRACKING_CLICKED],
|
||||
{ userId: userData?.referenceId }
|
||||
);
|
||||
const updatedParams = getLiveLocationParams(LIVE_LOCATION_SOURCE.EXTERNAL_SENSEI);
|
||||
navigate(`${APP_ROUTES.LIVE_LOCATION_TRACKER.path}${updatedParams}`);
|
||||
};
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className={styles.headerTitle}>
|
||||
<Typography variant="h3">Agent Dashboard</Typography>
|
||||
<Typography variant="p5" className={styles.lastUpdatedAt}>
|
||||
Last updated at{' '}
|
||||
{lastUpdatedAt
|
||||
? dateFormat(new Date(lastUpdatedAt), DateFormat.LONG_DATE_FORMAT_WITH_TIME)
|
||||
: '--'}
|
||||
</Typography>
|
||||
<div className={styles.refresh} onClick={handleRefresh}>
|
||||
<RefreshIcon className={styles.refreshIcon} color="var(--navi-color-blue-base)" />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.lastRefreshedContainer}>
|
||||
{enableAgentLiveLocation ? (
|
||||
<div>
|
||||
<Button
|
||||
onClick={handleLiveLocationClick}
|
||||
startAdornment={<LocationIcon />}
|
||||
variant="text"
|
||||
>
|
||||
Agent location
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
102
src/pages/ExternalDashboardSensei/components/RenderAgTable.tsx
Normal file
102
src/pages/ExternalDashboardSensei/components/RenderAgTable.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@cp/src/service/clickStream.constant';
|
||||
import { addClickstreamEvent } from '@cp/src/service/clickStreamEventService';
|
||||
import AgTable from '@navi/web-ui/lib/components/AgTable/AgTable';
|
||||
import { DropDownPosition } from '@navi/web-ui/lib/components/Pagination/constant';
|
||||
import Pagination from '@navi/web-ui/lib/components/Pagination/Pagination';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { createQueryParams, readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import { IRenderAgTable } from '../types';
|
||||
import { handleGridReady } from './ColumnDefs';
|
||||
import styles from './commonStyles.module.scss';
|
||||
|
||||
const RenderAgTable: React.FC<IRenderAgTable> = props => {
|
||||
const { colDefs, data, pageDetails, tableName, tableHeight = 400, callBackFn, ...rest } = props;
|
||||
const params = readQueryParams();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
|
||||
const { tabId = '' } = useParams();
|
||||
|
||||
const handlePageChange = (pageNo: number) => {
|
||||
if (!params) return;
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_FIELD_DASHBOARD_PAGE_CHANGED, {
|
||||
newPageNo: pageNo,
|
||||
section: tabId,
|
||||
oldPageNo: pageDetails?.currentPage,
|
||||
tableName: tableName
|
||||
});
|
||||
const updatedParams = { ...params };
|
||||
updatedParams[tableName].pageNumber = String(pageNo);
|
||||
const paramsData = createQueryParams(updatedParams);
|
||||
navigate(paramsData);
|
||||
callBackFn?.();
|
||||
};
|
||||
|
||||
const handleSizeChange = (pageSize: number) => {
|
||||
if (!params) return;
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_FIELD_DASHBOARD_PAGE_SIZE_CHANGED, {
|
||||
newPageSize: pageSize,
|
||||
section: tabId,
|
||||
oldPageSize: pageDetails?.pageSize,
|
||||
tableName: tableName
|
||||
});
|
||||
const updatedParams = { ...params };
|
||||
updatedParams[tableName].pageNumber = '1'; // on change setting it to default page number 1
|
||||
updatedParams[tableName].pageSize = String(pageSize);
|
||||
const paramsData = createQueryParams(updatedParams);
|
||||
navigate(paramsData);
|
||||
callBackFn?.();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (gridRef?.current?.api?.sizeColumnsToFit) {
|
||||
gridRef?.current?.api?.sizeColumnsToFit();
|
||||
}
|
||||
}, [tabId]);
|
||||
|
||||
return (
|
||||
<div className="isolate">
|
||||
<AgTable
|
||||
ref={gridRef}
|
||||
key={tableName}
|
||||
className="senseiAgTable"
|
||||
columnDefs={colDefs}
|
||||
rowData={data}
|
||||
onGridReady={e => handleGridReady(e, gridRef)}
|
||||
defaultColDef={{
|
||||
suppressMovable: true,
|
||||
wrapText: true,
|
||||
autoHeight: true
|
||||
}}
|
||||
style={{
|
||||
height: tableHeight,
|
||||
marginTop: 8
|
||||
}}
|
||||
headerHeight={72}
|
||||
theme={'alpine'}
|
||||
sizeColumnsToFit
|
||||
alternateRowColor="#F7F8FC"
|
||||
paginationWrapperClasses={styles.paginationWrapperClasses}
|
||||
PaginationComponent={
|
||||
pageDetails ? (
|
||||
<Pagination
|
||||
key={location.search}
|
||||
pageSize={pageDetails?.pageSize}
|
||||
currentPage={pageDetails?.currentPage}
|
||||
totalCount={pageDetails?.totalElements}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handleSizeChange}
|
||||
pageNumberDropDownPosition={DropDownPosition.TOP}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenderAgTable;
|
||||
@@ -0,0 +1,117 @@
|
||||
@import '../../../assets/styles/animations.scss';
|
||||
|
||||
.handleEllipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.loadingState {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
filter: blur(5px);
|
||||
animation: fadeIn 120ms;
|
||||
}
|
||||
|
||||
.paginationLoading {
|
||||
height: calc(100% + 64px); // Pagination Height
|
||||
}
|
||||
|
||||
.tableHeaderTitle {
|
||||
color: var(--navi-color-gray-c1);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.2px;
|
||||
padding: 14px 0 8px 0;
|
||||
}
|
||||
|
||||
.filterContainer {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.paginationWrapperClasses {
|
||||
overflow: unset;
|
||||
bottom: 4px;
|
||||
z-index: 2;
|
||||
|
||||
> div:nth-child(1) {
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.senseiAgTable {
|
||||
.ag-header {
|
||||
background-color: var(--grayscale-6);
|
||||
|
||||
div {
|
||||
color: var(--greyscale-3) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ag-header-row {
|
||||
.ag-header-cell {
|
||||
padding-left: 16px;
|
||||
padding-right: 0px !important;
|
||||
}
|
||||
|
||||
.ag-header-cell:last-child {
|
||||
padding-right: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ag-row {
|
||||
.ag-cell {
|
||||
padding-left: 16px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.ag-center-cols-clipper {
|
||||
min-height: unset !important;
|
||||
}
|
||||
|
||||
.ag-header-group-cell {
|
||||
border-right: 1px solid var(--navi-color-gray-border);
|
||||
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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stickyHeader {
|
||||
position: sticky;
|
||||
background: var(--bg-primary);
|
||||
width: 100%;
|
||||
top: 72px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
63
src/pages/ExternalDashboardSensei/constants.ts
Normal file
63
src/pages/ExternalDashboardSensei/constants.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { MILLI_IN_MIN } from '@cp/src/utils/DateHelper';
|
||||
|
||||
export enum QueryParamTypeMapping {
|
||||
AGENCY_DETAILS_TABLE = 'agencyDetailsTable',
|
||||
AGENT_PERFORMANCE_TABLE = 'agentPerformanceTable',
|
||||
DPD_BUCKET_TABLE = 'dpdBucketTable',
|
||||
FIELD_GOVERNANCE_TAB = 'fieldGovernanceTab',
|
||||
EXTERNAL_SENSEI = 'externalSensei',
|
||||
AGENT_PERFORMANCE_TL_TABLE = 'agentPerformanceTlTable'
|
||||
}
|
||||
|
||||
export const filterTypeMapping = {
|
||||
agencyCode: 'agencyCode',
|
||||
stateName: 'stateName',
|
||||
dpdBucket: 'dpdBucket'
|
||||
};
|
||||
|
||||
export const filterTitle = {
|
||||
agency: 'Agency',
|
||||
agencyName: 'Select agency',
|
||||
state: 'Select state',
|
||||
dpdBucket: 'All DPD Buckets'
|
||||
};
|
||||
|
||||
export enum TabsKey {
|
||||
OVERALL_PERFORMANCE = 'overall_performance',
|
||||
FIELD_GOVERNANCE = 'field_governance',
|
||||
DAILY_PLANNING = 'daily_planning'
|
||||
}
|
||||
|
||||
export const rowIndexZeroStyles = {
|
||||
fontWeight: 500,
|
||||
background: 'none',
|
||||
color: 'var(--navi-color-gray-c1)'
|
||||
};
|
||||
|
||||
export const alternativeColorStyles = {
|
||||
background: 'var(--blue-light-table)',
|
||||
fontWeight: 'normal'
|
||||
};
|
||||
|
||||
export const DPD_BUCKET_OPTIONS = [
|
||||
{
|
||||
label: 'DPD 31 - 60',
|
||||
value: '31-60'
|
||||
},
|
||||
{
|
||||
label: 'DPD 61 - 90',
|
||||
value: '61-90'
|
||||
},
|
||||
{
|
||||
label: 'DPD 91 - 180',
|
||||
value: '91-180'
|
||||
},
|
||||
{
|
||||
label: 'DPD 180 and above',
|
||||
value: '180+'
|
||||
}
|
||||
];
|
||||
|
||||
export const FIELD_GOVERNANCE_TABLE_HEIGHT = 634.75;
|
||||
export const DPD_BUCKET_TABLE_HEIGHT = 354.75;
|
||||
export const POLLING_TIME = 30 * MILLI_IN_MIN; // 30 mins;
|
||||
23
src/pages/ExternalDashboardSensei/index.module.scss
Normal file
23
src/pages/ExternalDashboardSensei/index.module.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
.content {
|
||||
position: relative;
|
||||
height: calc(100vh - 64px); // Main Header (64px)
|
||||
}
|
||||
|
||||
.tabContainerClass {
|
||||
box-shadow: 0px 6px 11px 0px rgba(90, 90, 90, 0.08);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-index-external-sensei-header);
|
||||
background-color: var(--bg-primary);
|
||||
|
||||
&:first-child {
|
||||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabContent {
|
||||
padding: 0 24px;
|
||||
height: fit-content;
|
||||
height: calc(100vh - 100px); // Main Header (64px) + Tabs Header (36px)
|
||||
overflow-y: scroll;
|
||||
}
|
||||
90
src/pages/ExternalDashboardSensei/index.tsx
Normal file
90
src/pages/ExternalDashboardSensei/index.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import Tabs, { TabItem } from '@navi/web-ui/lib/components/Tabs';
|
||||
import { useEffect } from 'react';
|
||||
import { TabItemKey } from '@navi/web-ui/lib/components/Tabs/types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import styles from './index.module.scss';
|
||||
import { getTabs } from './utils';
|
||||
import { TabsKey } from './constants';
|
||||
import { RootState } from '@cp/src/store';
|
||||
import { addClickstreamEvent } from '@cp/src/service/clickStreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@cp/src/service/clickStream.constant';
|
||||
import Header from './components/Header';
|
||||
import APP_ROUTES from '@cp/src/layout/Routes';
|
||||
import { interpolatePathParams } from '@cp/src/utils/interpolate';
|
||||
|
||||
const ExternalDashboardSensei = () => {
|
||||
const navigate = useNavigate();
|
||||
const { tabId = '' } = useParams();
|
||||
const { featureFlags } = useSelector((state: RootState) => ({
|
||||
featureFlags: state?.common?.featureFlags
|
||||
}));
|
||||
|
||||
const dashboardTabs = getTabs(featureFlags, tabId);
|
||||
const handleTabChange = (updateTabId: TabItemKey) => {
|
||||
if (tabId !== updateTabId) {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_FIELD_DASHBOARD_TAB_SWITCH, {
|
||||
currentTab: tabId,
|
||||
newTab: updateTabId
|
||||
});
|
||||
const updatedUrl = interpolatePathParams(APP_ROUTES.SENSEI_EXTERNAL.path, {
|
||||
tabId: String(updateTabId)
|
||||
});
|
||||
navigate(updatedUrl);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// If no tab access is there redirect the user to first screen
|
||||
if (!dashboardTabs.length) {
|
||||
navigate('/');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handling the edge case if the url is shared but the user don't have the tab access so selecting the first tab
|
||||
const isTabAccessNotAvailable = tabId && !dashboardTabs.find(tab => tab.key === tabId);
|
||||
// And handling the case where tabId is not there and we are selecting the first tab for which user has the access
|
||||
const noTabSelected = ![
|
||||
TabsKey.FIELD_GOVERNANCE,
|
||||
TabsKey.OVERALL_PERFORMANCE,
|
||||
TabsKey.DAILY_PLANNING
|
||||
].includes(tabId as TabsKey);
|
||||
|
||||
if (isTabAccessNotAvailable || noTabSelected) {
|
||||
navigate(dashboardTabs?.[0]?.relativePath);
|
||||
}
|
||||
}, [tabId, dashboardTabs]);
|
||||
|
||||
useEffect(() => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_FIELD_DASHBOARD_PAGE_LAND, {
|
||||
tab: tabId
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<div className={styles.content}>
|
||||
{tabId && dashboardTabs.length ? (
|
||||
<Tabs
|
||||
tabsClassName={styles.tabContainerClass}
|
||||
selectedTabKey={tabId}
|
||||
onTabChange={handleTabChange}
|
||||
contentClassName={styles.tabContent}
|
||||
preserveContent
|
||||
>
|
||||
{dashboardTabs.map(tab => {
|
||||
return (
|
||||
<TabItem key={tab.key} label={tab.value}>
|
||||
{tab.component}
|
||||
</TabItem>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExternalDashboardSensei;
|
||||
@@ -0,0 +1,104 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { IExternalSenseiDashboard } from '../types';
|
||||
|
||||
const initialState: IExternalSenseiDashboard = {
|
||||
dpdBucketTable: {
|
||||
dpdBucketData: {},
|
||||
loading: false
|
||||
},
|
||||
agentPerformanceTlTable: {
|
||||
agentPerformanceTlData: {},
|
||||
loading: false
|
||||
},
|
||||
agencyDetailsTable: {
|
||||
agencyDetailsMtdData: {},
|
||||
agencyDetailsTodayData: {},
|
||||
loading: false
|
||||
},
|
||||
agentPerformanceTable: {
|
||||
agentPerformanceMtdData: {},
|
||||
agentPerformanceTodayData: {},
|
||||
loading: false
|
||||
},
|
||||
agencyNameOptions: [],
|
||||
stateOptions: [],
|
||||
agentsOptions: [],
|
||||
allLastUpdatedAt: {
|
||||
dpdBucketLastUpdatedAt: 0,
|
||||
agencyDetailsLastUpdatedAt: 0,
|
||||
agentPerformanceLastUpdatedAt: 0,
|
||||
agentPerformanceTlLastUpdatedAt: 0
|
||||
}
|
||||
};
|
||||
|
||||
const externalDashboardSenseiSlice = createSlice({
|
||||
name: 'externalDashboardSensei',
|
||||
initialState,
|
||||
reducers: {
|
||||
setDpdBucketTable: (state, action) => {
|
||||
const { data } = action.payload;
|
||||
state.dpdBucketTable.dpdBucketData = data;
|
||||
},
|
||||
setLoadingDpdBucket: (state, action) => {
|
||||
state.dpdBucketTable.loading = action.payload;
|
||||
},
|
||||
setAgentPerformanceTlTable: (state, action) => {
|
||||
const { data } = action.payload;
|
||||
state.agentPerformanceTlTable.agentPerformanceTlData = data;
|
||||
},
|
||||
setLoadingAgentPerformanceTl: (state, action) => {
|
||||
state.agentPerformanceTlTable.loading = action.payload;
|
||||
},
|
||||
setAgencyDetailsTable: (state, action) => {
|
||||
const { data, isToday } = action.payload;
|
||||
if (isToday) {
|
||||
state.agencyDetailsTable.agencyDetailsTodayData = data;
|
||||
return;
|
||||
}
|
||||
state.agencyDetailsTable.agencyDetailsMtdData = data;
|
||||
},
|
||||
setLoadingAgencyDetails: (state, action) => {
|
||||
state.agencyDetailsTable.loading = action.payload;
|
||||
},
|
||||
setAgentPerformanceTable: (state, action) => {
|
||||
const { data, isToday } = action.payload;
|
||||
if (isToday) {
|
||||
state.agentPerformanceTable.agentPerformanceTodayData = data;
|
||||
return;
|
||||
}
|
||||
state.agentPerformanceTable.agentPerformanceMtdData = data;
|
||||
},
|
||||
setLoadingAgentPerformance: (state, action) => {
|
||||
state.agentPerformanceTable.loading = action.payload;
|
||||
},
|
||||
setAgencyNameOptions: (state, action) => {
|
||||
state.agencyNameOptions = action.payload;
|
||||
},
|
||||
setStateOptions: (state, action) => {
|
||||
state.stateOptions = action.payload;
|
||||
},
|
||||
setAgentsOptions: (state, action) => {
|
||||
state.agentsOptions = action.payload;
|
||||
},
|
||||
setTablesLastUpdatedAt: (state, action) => {
|
||||
state.allLastUpdatedAt = { ...state.allLastUpdatedAt, ...action.payload };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const {
|
||||
setDpdBucketTable,
|
||||
setLoadingDpdBucket,
|
||||
setAgentPerformanceTlTable,
|
||||
setLoadingAgentPerformanceTl,
|
||||
setAgencyDetailsTable,
|
||||
setLoadingAgencyDetails,
|
||||
setAgentPerformanceTable,
|
||||
setLoadingAgentPerformance,
|
||||
setAgencyNameOptions,
|
||||
setStateOptions,
|
||||
setAgentsOptions,
|
||||
setTablesLastUpdatedAt
|
||||
} = externalDashboardSenseiSlice.actions;
|
||||
|
||||
export default externalDashboardSenseiSlice.reducer;
|
||||
194
src/pages/ExternalDashboardSensei/types.ts
Normal file
194
src/pages/ExternalDashboardSensei/types.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { IPagination } from '@cp/src/components/interfaces';
|
||||
import { AgTableProps, ColDefsType } from '@navi/web-ui/lib/components/AgTable/types';
|
||||
import { QueryParamTypeMapping } from './constants';
|
||||
|
||||
export enum FIELD_GOVERNANCE_TABS {
|
||||
MTD = 'MTD',
|
||||
TODAY = 'Today'
|
||||
}
|
||||
|
||||
export enum FIELD_GOVERNANCE_TABS_VALUE {
|
||||
MTD = 'MTD',
|
||||
TODAY = 'TODAY'
|
||||
}
|
||||
|
||||
export interface IDpdBucketData {
|
||||
dpdBucket: string;
|
||||
performanceBand: number;
|
||||
lansAllocated: number;
|
||||
agentAllocationPercentage: number;
|
||||
emiOverdueAmount: number;
|
||||
targetCEPercentage: number;
|
||||
targetOverdueAmount: number;
|
||||
emiCEPercentage: number;
|
||||
emiOverdueCollected: number;
|
||||
achievementPercentage: number;
|
||||
lmsdAchievedPercentage: number;
|
||||
nonEnachCashCollected: number;
|
||||
}
|
||||
|
||||
export interface IDpdBucketDataTable {
|
||||
data?: IDpdBucketData;
|
||||
}
|
||||
|
||||
export interface IDpdBucketTable {
|
||||
dpdBucketData: IDpdBucketDataTable;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export interface IAgentPerformanceTlData {
|
||||
agentName: string;
|
||||
lansAllocated: number;
|
||||
emiOverdueAmount: number;
|
||||
targetCEPercentage: number;
|
||||
targetOverdueAmount: number;
|
||||
emiCEPercentage: number;
|
||||
emiOverdueCollected: number;
|
||||
achievementPercentage: number;
|
||||
lmsdAchievedPercentage: number;
|
||||
nonEnachCashCollected: number;
|
||||
}
|
||||
|
||||
export interface IAgentPerformanceTlDataTable {
|
||||
data?: IAgentPerformanceTlData;
|
||||
}
|
||||
|
||||
export interface IAgentPerformanceTlTable {
|
||||
agentPerformanceTlData: IAgentPerformanceTlDataTable;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export interface IAgencyDetailsMtdData {
|
||||
agencyName?: string;
|
||||
agencyCode?: string;
|
||||
coverageThirtyNinetyLansPercent?: number;
|
||||
coverageNinetyOneEightyLansPercent?: number;
|
||||
genuineCoverageThirtyNinetyLansPercent?: number;
|
||||
genuineCoverageNinetyOneEightyLansPercent?: number;
|
||||
visitsPerDayPerAgent?: number;
|
||||
genuineVisitsPerDayPerAgent?: number;
|
||||
ptpsGenerated?: number;
|
||||
distanceTravelledPerDayPerAgent?: number;
|
||||
appUsagePerDayPerAgent?: number;
|
||||
brokenPtpPercentage?: number;
|
||||
}
|
||||
|
||||
export interface IAgencyDetailsMtdTable {
|
||||
data?: IAgencyDetailsMtdData;
|
||||
pages?: IPagination;
|
||||
}
|
||||
|
||||
export interface IAgencyDetailsTodayData {
|
||||
agencyName: string;
|
||||
agencyCode: string;
|
||||
visitsTodayPerAgent: number;
|
||||
totalPtpsToday: number;
|
||||
totalPtpsVisitedToday: number;
|
||||
totalAccountsPaidToday: number;
|
||||
totalOdCollectedToday: number;
|
||||
totalCashCollectedToday: number;
|
||||
}
|
||||
|
||||
export interface IAgencyDetailsTodayTable {
|
||||
data?: IAgencyDetailsTodayData;
|
||||
pages?: IPagination;
|
||||
}
|
||||
|
||||
export interface IAgencyDetailsTable {
|
||||
agencyDetailsMtdData: IAgencyDetailsMtdTable;
|
||||
agencyDetailsTodayData: IAgencyDetailsTodayTable;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export interface IAgentPerformanceMtdData {
|
||||
agentName: string;
|
||||
agentReferenceId: string;
|
||||
totalLansAllocated: number;
|
||||
coverageThirtyNinetyLansPercent: number;
|
||||
coverageNinetyOneEightyLansPercent: number;
|
||||
genuineCoverageThirtyNinetyLansPercent: number;
|
||||
genuineCoverageNinetyOneEightyLansPercent: number;
|
||||
visitsPerDay: number;
|
||||
genuineVisitsPerDay: number;
|
||||
ptpsGenerated: number;
|
||||
distanceTravelledPerDay: number;
|
||||
appUsagePerDay: number;
|
||||
brokenPtpPercentage: number;
|
||||
}
|
||||
|
||||
export interface IAgentPerformanceMtdTable {
|
||||
data?: IAgentPerformanceMtdData;
|
||||
pages?: IPagination;
|
||||
}
|
||||
|
||||
export interface IAgentPerformanceTodayData {
|
||||
agentName: string;
|
||||
agentReferenceId: string;
|
||||
visitsToday: number;
|
||||
ptpsToday: number;
|
||||
ptpsVisitedToday: number;
|
||||
accountsPaidToday: number;
|
||||
odCollectedToday: number;
|
||||
cashCollectedToday: number;
|
||||
}
|
||||
|
||||
export interface IAgentPerformanceTodayTable {
|
||||
data?: IAgentPerformanceTodayData;
|
||||
pages?: IPagination;
|
||||
}
|
||||
|
||||
export interface IAgentPerformanceTable {
|
||||
agentPerformanceMtdData: IAgentPerformanceMtdTable;
|
||||
agentPerformanceTodayData: IAgentPerformanceTodayTable;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export interface IOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IAllLastUpdateAt {
|
||||
dpdBucketLastUpdatedAt: number;
|
||||
agencyDetailsLastUpdatedAt: number;
|
||||
agentPerformanceLastUpdatedAt: number;
|
||||
agentPerformanceTlLastUpdatedAt: number;
|
||||
}
|
||||
|
||||
export interface IExternalSenseiDashboard {
|
||||
dpdBucketTable: IDpdBucketTable;
|
||||
agentPerformanceTlTable: IAgentPerformanceTlTable;
|
||||
agencyDetailsTable: IAgencyDetailsTable;
|
||||
agentPerformanceTable: IAgentPerformanceTable;
|
||||
agencyNameOptions: Array<IOption>;
|
||||
stateOptions: Array<IOption>;
|
||||
agentsOptions: Array<IOption>;
|
||||
allLastUpdatedAt: IAllLastUpdateAt;
|
||||
}
|
||||
|
||||
export interface IPageDetails {
|
||||
totalElements: number;
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface IRenderAgTable extends AgTableProps {
|
||||
colDefs: ColDefsType[];
|
||||
// Ag Table expects any so declared it as any
|
||||
data: any;
|
||||
tableName: string;
|
||||
tableHeight: number;
|
||||
pageDetails?: IPageDetails;
|
||||
callBackFn?: () => void;
|
||||
}
|
||||
|
||||
export interface IFilterWrapper {
|
||||
title: string;
|
||||
options: IOption[];
|
||||
filterType: string;
|
||||
tableType: QueryParamTypeMapping;
|
||||
callBackFn: () => void;
|
||||
isPaginatedTable?: boolean;
|
||||
disabled?: boolean;
|
||||
isSingleSelect?: boolean;
|
||||
}
|
||||
130
src/pages/ExternalDashboardSensei/utils.tsx
Normal file
130
src/pages/ExternalDashboardSensei/utils.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { IFeatureFlags } from '@cp/src/reducers/commonSlice';
|
||||
import { createQueryParams, IData } from '@cp/src/utils/QueryParamsHelper';
|
||||
import { NavigateFunction } from 'react-router-dom';
|
||||
import { QueryParamTypeMapping, TabsKey } from './constants';
|
||||
import FieldGovernance from './FieldGovernance';
|
||||
import OverallPerformance from './OverallPerformance';
|
||||
import { FIELD_GOVERNANCE_TABS_VALUE, IAllLastUpdateAt } from './types';
|
||||
|
||||
export const getTabs = (featureFlags: IFeatureFlags, tabId: string) => {
|
||||
const { overallPerformanceTab, fieldGovernanceTab } = featureFlags || {};
|
||||
|
||||
const tabs = [];
|
||||
|
||||
if (overallPerformanceTab) {
|
||||
tabs.push({
|
||||
key: TabsKey.OVERALL_PERFORMANCE,
|
||||
value: 'Overall Performance',
|
||||
component: <OverallPerformance />,
|
||||
relativePath: '/sensei-external/overall_performance'
|
||||
});
|
||||
}
|
||||
|
||||
if (fieldGovernanceTab) {
|
||||
tabs.push({
|
||||
key: TabsKey.FIELD_GOVERNANCE,
|
||||
value: 'Field Governance',
|
||||
component: <FieldGovernance />,
|
||||
relativePath: '/sensei-external/field_governance'
|
||||
});
|
||||
}
|
||||
return tabs;
|
||||
};
|
||||
|
||||
export const setInitialGovernanceTabParams = (
|
||||
params: IData,
|
||||
navigate: NavigateFunction,
|
||||
featureFlags: IFeatureFlags
|
||||
) => {
|
||||
const { fieldGovernanceAgencyPerformanceTable, fieldGovernanceAgentPerformanceTable } =
|
||||
featureFlags || {};
|
||||
|
||||
const queryParams = { ...params };
|
||||
|
||||
if (fieldGovernanceAgencyPerformanceTable) {
|
||||
if (!queryParams?.[QueryParamTypeMapping.AGENCY_DETAILS_TABLE])
|
||||
queryParams[QueryParamTypeMapping.AGENCY_DETAILS_TABLE] = {};
|
||||
|
||||
if (!queryParams[QueryParamTypeMapping.AGENCY_DETAILS_TABLE]?.pageNumber) {
|
||||
queryParams[QueryParamTypeMapping.AGENCY_DETAILS_TABLE].pageNumber = '1';
|
||||
}
|
||||
if (!queryParams[QueryParamTypeMapping.AGENCY_DETAILS_TABLE]?.pageSize) {
|
||||
queryParams[QueryParamTypeMapping.AGENCY_DETAILS_TABLE].pageSize = '10';
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldGovernanceAgentPerformanceTable) {
|
||||
if (!queryParams?.[QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE])
|
||||
queryParams[QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE] = {};
|
||||
|
||||
if (!queryParams[QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE]?.pageNumber) {
|
||||
queryParams[QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE].pageNumber = '1';
|
||||
}
|
||||
if (!queryParams[QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE]?.pageSize) {
|
||||
queryParams[QueryParamTypeMapping.AGENT_PERFORMANCE_TABLE].pageSize = '10';
|
||||
}
|
||||
}
|
||||
|
||||
if (!queryParams?.[QueryParamTypeMapping.FIELD_GOVERNANCE_TAB])
|
||||
queryParams[QueryParamTypeMapping.FIELD_GOVERNANCE_TAB] = {};
|
||||
|
||||
if (!queryParams[QueryParamTypeMapping.FIELD_GOVERNANCE_TAB]?.selectedTab) {
|
||||
queryParams[QueryParamTypeMapping.FIELD_GOVERNANCE_TAB].selectedTab =
|
||||
FIELD_GOVERNANCE_TABS_VALUE.MTD;
|
||||
}
|
||||
const updatedParams = createQueryParams(queryParams);
|
||||
navigate(updatedParams);
|
||||
};
|
||||
|
||||
export const setInitialOverallPerformanceTabParams = (
|
||||
params: IData,
|
||||
navigate: NavigateFunction,
|
||||
featureFlags: IFeatureFlags
|
||||
) => {
|
||||
const queryParams = { ...params };
|
||||
const { overallPerformanceOverallMetricsTable } = featureFlags || {};
|
||||
|
||||
if (overallPerformanceOverallMetricsTable) {
|
||||
if (!queryParams?.[QueryParamTypeMapping.DPD_BUCKET_TABLE])
|
||||
queryParams[QueryParamTypeMapping.DPD_BUCKET_TABLE] = {};
|
||||
|
||||
if (!queryParams[QueryParamTypeMapping.DPD_BUCKET_TABLE]?.stateName) {
|
||||
queryParams[QueryParamTypeMapping.DPD_BUCKET_TABLE].stateName = 'ALL';
|
||||
}
|
||||
if (!queryParams[QueryParamTypeMapping.DPD_BUCKET_TABLE]?.agencyCode) {
|
||||
queryParams[QueryParamTypeMapping.DPD_BUCKET_TABLE].agencyCode = 'ALL';
|
||||
}
|
||||
}
|
||||
|
||||
const updatedParams = createQueryParams(queryParams);
|
||||
navigate(updatedParams);
|
||||
};
|
||||
|
||||
export const getLastUpdatedAt = (allLastUpdatedAt: IAllLastUpdateAt, tabId: string) => {
|
||||
const {
|
||||
dpdBucketLastUpdatedAt,
|
||||
agencyDetailsLastUpdatedAt,
|
||||
agentPerformanceLastUpdatedAt,
|
||||
agentPerformanceTlLastUpdatedAt
|
||||
} = allLastUpdatedAt;
|
||||
|
||||
if (tabId === TabsKey.OVERALL_PERFORMANCE) {
|
||||
if (!dpdBucketLastUpdatedAt) return agentPerformanceTlLastUpdatedAt;
|
||||
if (!agentPerformanceTlLastUpdatedAt) return dpdBucketLastUpdatedAt;
|
||||
|
||||
if (dpdBucketLastUpdatedAt < agentPerformanceTlLastUpdatedAt) {
|
||||
return dpdBucketLastUpdatedAt;
|
||||
}
|
||||
return agentPerformanceTlLastUpdatedAt;
|
||||
}
|
||||
|
||||
if (tabId === TabsKey.FIELD_GOVERNANCE) {
|
||||
if (!agencyDetailsLastUpdatedAt) return agentPerformanceLastUpdatedAt;
|
||||
if (!agentPerformanceLastUpdatedAt) return agencyDetailsLastUpdatedAt;
|
||||
|
||||
if (agencyDetailsLastUpdatedAt < agentPerformanceLastUpdatedAt) {
|
||||
return agencyDetailsLastUpdatedAt;
|
||||
}
|
||||
return agentPerformanceLastUpdatedAt;
|
||||
}
|
||||
};
|
||||
@@ -7,7 +7,10 @@ import RefreshIcon from '../../../../assets/icons/RefreshIcon';
|
||||
import { Button } from '@primitives';
|
||||
import { FilterTypes } from '../../constants/LiveLocationTrackerInterfaces';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AGENT_LIVE_LOCATION } from '../../constants/LiveLocatonTrackerConstants';
|
||||
import {
|
||||
AGENT_LIVE_LOCATION,
|
||||
LIVE_LOCATION_SOURCE
|
||||
} from '../../constants/LiveLocatonTrackerConstants';
|
||||
import { createQueryParams, readQueryParams } from '../../../../utils/QueryParamsHelper';
|
||||
import { getAgentsLocations } from '../../actions/LiveLocationTrackerActions';
|
||||
import { RootState } from '../../../../store';
|
||||
@@ -35,6 +38,11 @@ function TopBar() {
|
||||
if (!queryParams?.AGENTID) {
|
||||
// clear selected agent and map pin locations
|
||||
dispatch(resetMapData());
|
||||
|
||||
if (queryParams?.SOURCE === LIVE_LOCATION_SOURCE.EXTERNAL_SENSEI) {
|
||||
navigate(APP_ROUTES.SENSEI_EXTERNAL.relativePath);
|
||||
return;
|
||||
}
|
||||
navigate(APP_ROUTES.PERFORMANCE_DASHBOARD.path);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,3 +83,8 @@ export const locationsPinFilters = [
|
||||
export const DEFAULT_TEXT = '--';
|
||||
|
||||
export const MIN_CHAR_LENGTH_FOR_TOOLTIP = 20;
|
||||
|
||||
export enum LIVE_LOCATION_SOURCE {
|
||||
EXTERNAL_SENSEI = 'external_sensei',
|
||||
PERFORMANCE_DASHBOARD = 'performance_dashboard'
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { NavigateFunction } from 'react-router-dom';
|
||||
import { formatEpochTime } from 'src/utils/DateHelper';
|
||||
import styles from './components/AgentMarker/agentMarker.module.scss';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { LocalStorage } from '@cp/src/utils/StorageUtils';
|
||||
|
||||
export const getAllocationPins = (
|
||||
data: Record<string, IAgentAllocation>,
|
||||
@@ -218,3 +219,21 @@ export const loadCustomPopup = (type?: string) => {
|
||||
}
|
||||
return CustomPopup;
|
||||
};
|
||||
|
||||
export const getLiveLocationParams = (source: string) => {
|
||||
// Get the current live-location-tracker-params from local storage
|
||||
const currentParams = LocalStorage.getItem('live-location-tracker-params') || '';
|
||||
|
||||
const paramRegex = /SOURCE\*AGENT_LIVE_LOCATION=[^&]*/;
|
||||
const hasParam = paramRegex.test(currentParams);
|
||||
|
||||
let updatedParams;
|
||||
if (hasParam) {
|
||||
updatedParams = currentParams.replace(paramRegex, `SOURCE*${AGENT_LIVE_LOCATION}=${source}`);
|
||||
} else {
|
||||
updatedParams =
|
||||
currentParams + (currentParams ? '&' : '?') + `SOURCE*${AGENT_LIVE_LOCATION}=${source}`;
|
||||
}
|
||||
|
||||
return updatedParams;
|
||||
};
|
||||
|
||||
@@ -45,10 +45,12 @@ import {
|
||||
import { Button } from '@primitives';
|
||||
import LocationIcon from '../../assets/icons/Location';
|
||||
import Routes from '../../layout/Routes';
|
||||
import { LIVE_LOCATION_SOURCE } from '../LiveLocationTracker/constants/LiveLocatonTrackerConstants';
|
||||
import { getLiveLocationParams } from '../LiveLocationTracker/utils';
|
||||
|
||||
export const PERFORMANCE_DASHBOARD = 'performanceDashboard';
|
||||
|
||||
const showAgentsLiveLocation = (roles: string[] | undefined) => {
|
||||
export const showAgentsLiveLocation = (roles: string[] | undefined) => {
|
||||
let show = false;
|
||||
if (!roles) return show;
|
||||
// for loop that returns flag as soon as it finds the role
|
||||
@@ -401,11 +403,9 @@ const PerformanceDashboard = () => {
|
||||
PERFORMANCE_DASHBOARD_CLICKSTREAM[PERFORMANCE_DASHBOARD_EVENTS.LH_AGENT_TRACKING_CLICKED],
|
||||
{ userId: user?.referenceId }
|
||||
);
|
||||
// navigate to the old url if live-location-tracker-params is there
|
||||
navigate(
|
||||
Routes.LIVE_LOCATION_TRACKER.path +
|
||||
(localStorage.getItem('live-location-tracker-params') || '')
|
||||
);
|
||||
const updatedParams = getLiveLocationParams(LIVE_LOCATION_SOURCE.PERFORMANCE_DASHBOARD);
|
||||
// Navigate to the new URL with the updated parameters
|
||||
navigate(Routes.LIVE_LOCATION_TRACKER.path + updatedParams);
|
||||
};
|
||||
|
||||
const enableAgentLiveLocation = useMemo(() => showAgentsLiveLocation(roles), [roles]);
|
||||
|
||||
@@ -219,6 +219,10 @@ export interface GenerateAmeyoPassword {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface IFeatureFlags {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
export interface CommonState {
|
||||
userData: User;
|
||||
toast?: ToastData;
|
||||
@@ -242,7 +246,8 @@ export interface CommonState {
|
||||
isInternalTeamLead?: boolean;
|
||||
isCallingAgent?: boolean;
|
||||
feedbackFilters: string;
|
||||
serverDate: Date | string;
|
||||
serverDate: Date;
|
||||
featureFlags: IFeatureFlags;
|
||||
isAmeyoUtilityVisible?: boolean;
|
||||
isAmeyoGeneratePasswordVisible?: boolean;
|
||||
}
|
||||
@@ -286,6 +291,7 @@ const initialState = {
|
||||
isAmeyoUtilityVisible: false,
|
||||
isAmeyoGeneratePasswordVisible: false,
|
||||
isIdCardApprovalVisible: false,
|
||||
featureFlags: {},
|
||||
humanReminderCustomerDetailsLoading: false,
|
||||
generateAmeyoPassword: {
|
||||
ameyoKey: '',
|
||||
@@ -312,12 +318,8 @@ export const commonSlice = createSlice({
|
||||
const roles = action.payload?.roles;
|
||||
const isNaviUser = action.payload?.naviUser;
|
||||
if (roles?.length) {
|
||||
const agencyPerformanceDashboardVisible =
|
||||
roles?.includes(Roles.ROLE_NAVI_FIELD_EXTERNAL_TEAM_LEAD) ||
|
||||
roles?.includes(Roles.ROLE_NAVI_FIELD_TEAM_LEAD) ||
|
||||
roles?.includes(Roles.ROLE_NAVI_FIELD_AGENCY_TEAM_LEAD);
|
||||
|
||||
state.isPerformanceDashboardVisible = agencyPerformanceDashboardVisible;
|
||||
state.isPerformanceDashboardVisible =
|
||||
action.payload?.featureFlags?.performanceDashboard ?? false;
|
||||
|
||||
const isIdCardApprovalVisible =
|
||||
roles?.includes(Roles.ROLE_NAVI_FIELD_EXTERNAL_TEAM_LEAD) ||
|
||||
@@ -328,6 +330,8 @@ export const commonSlice = createSlice({
|
||||
const isAmeyoGeneratePasswordVisible =
|
||||
action.payload?.featureFlags?.ameyoGeneratePasswordFlag ?? false;
|
||||
|
||||
state.featureFlags = action.payload?.featureFlags;
|
||||
|
||||
state.isIdCardApprovalVisible = isIdCardApprovalVisible;
|
||||
state.isAmeyoUtilityVisible = isAmeyoUtilityVisible;
|
||||
state.isAmeyoGeneratePasswordVisible = isAmeyoGeneratePasswordVisible;
|
||||
|
||||
@@ -573,6 +573,36 @@ export const CLICKSTREAM_EVENT_NAMES = Object.freeze({
|
||||
LH_AMEYO_PONG_RECEIVED_LONGHORN: {
|
||||
name: 'LH_AMEYO_PONG_RECEIVED_LONGHORN',
|
||||
description: 'When pong is received from longhorn.'
|
||||
},
|
||||
|
||||
// External Dashboard - Sensei
|
||||
LH_FIELD_DASHBOARD_FILTER_CLICKED: {
|
||||
name: 'LH_FIELD_DASHBOARD_FILTER_CLICKED',
|
||||
description: 'Field dashboard filter clicked'
|
||||
},
|
||||
LH_FIELD_DASHBOARD_FILTER_APPLIED: {
|
||||
name: 'LH_FIELD_DASHBOARD_FILTER_APPLIED',
|
||||
description: 'Field dashboard filter applied'
|
||||
},
|
||||
LH_FIELD_DASHBOARD_PAGE_CHANGED: {
|
||||
name: 'LH_FIELD_DASHBOARD_PAGE_CHANGED',
|
||||
description: 'Field dashboard page changed'
|
||||
},
|
||||
LH_FIELD_DASHBOARD_PAGE_SIZE_CHANGED: {
|
||||
name: 'LH_FIELD_DASHBOARD_PAGE_SIZE_CHANGED',
|
||||
description: 'Field dashboard page size changed'
|
||||
},
|
||||
LH_FIELD_DASHBOARD_TAB_SWITCH: {
|
||||
name: 'LH_FIELD_DASHBOARD_TAB_SWITCH',
|
||||
description: 'Field dashboard tab switch'
|
||||
},
|
||||
LH_FIELD_DASHBOARD_PAGE_LAND: {
|
||||
name: 'LH_FIELD_DASHBOARD_PAGE_LAND',
|
||||
description: 'Field dashboard page land'
|
||||
},
|
||||
LH_FIELD_DASHBOARD_GOVERNANCE_TAB_SWITCH: {
|
||||
name: 'LH_FIELD_DASHBOARD_GOVERNANCE_TAB_SWITCH',
|
||||
description: 'Field dashboard governance tab switch'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import ameyoUtilitySlice from '../pages/AmeyoUtility/reducer/ameyoUtilitySlice';
|
||||
import whatsappChatbotSlice from '../reducers/whatsappChatbotSlice';
|
||||
import TeleSenseiSlice from '@cp/src/pages/SenseiTele/reducer';
|
||||
import Sensei from '../pages/sensei/reducers';
|
||||
import ExternalDashboardSenseiSlice from 'src/pages/ExternalDashboardSensei/reducers/ExternalDashboardSenseiSlice';
|
||||
|
||||
export const reducer = combineReducers({
|
||||
common: commonReducer,
|
||||
@@ -41,6 +42,7 @@ export const reducer = combineReducers({
|
||||
reallocation: reallocationSlice,
|
||||
teamLeadDashboard: TeamLeadSlice,
|
||||
liveLocationTracker: LiveLocationTrackerSlice,
|
||||
externalDashboardSensei: ExternalDashboardSenseiSlice,
|
||||
agentAvailabilitySlice: AgentAvailabilitySlice,
|
||||
leaveManagement: LeaveManagementSlice,
|
||||
agencyPincodeMapping: agencyPincodeMappingSlice,
|
||||
|
||||
@@ -160,7 +160,13 @@ export enum ApiKeys {
|
||||
CONVERT_TIFF_TO_JPEG,
|
||||
LEGAL_DOCS,
|
||||
GET_DEPOSITIONS,
|
||||
REGISTER_FCM_TOKEN
|
||||
REGISTER_FCM_TOKEN,
|
||||
GET_DPD_BUCKET_DATA,
|
||||
GET_AGENCY_NAME_AND_STATE_OPTIONS,
|
||||
GET_AGENCY_NAME_OPTIONS,
|
||||
GET_AGENCY_DETAILS_DATA,
|
||||
GET_AGENT_PERFORMANCE_DATA,
|
||||
GET_AGENT_PERFORMANCE_TL_DATA
|
||||
}
|
||||
|
||||
// TODO: try to get rid of `as`
|
||||
@@ -305,6 +311,18 @@ API_URLS[ApiKeys.GET_AGENT_PERFORMANCE] = '/team-lead/dashboard/reportees-perfor
|
||||
API_URLS[ApiKeys.CONVERT_TIFF_TO_JPEG] = '/v1/documents/convert/tiff-to-jpeg';
|
||||
API_URLS[ApiKeys.REGISTER_FCM_TOKEN] = '/fcm';
|
||||
API_URLS[ApiKeys.GET_DEPOSITIONS] = '/team-lead/dashboard/reportees-disposition-summary';
|
||||
API_URLS[ApiKeys.GET_DPD_BUCKET_DATA] =
|
||||
'/longhorn/external-agency-performance-dashboard/overall-performance/overall-metrics';
|
||||
API_URLS[ApiKeys.GET_AGENCY_NAME_AND_STATE_OPTIONS] =
|
||||
'/longhorn/external-agency-performance-dashboard/overall-performance/overall-metrics/filter';
|
||||
API_URLS[ApiKeys.GET_AGENCY_NAME_OPTIONS] =
|
||||
'/longhorn/external-agency-performance-dashboard/field-governance/reporting-agency/filter';
|
||||
API_URLS[ApiKeys.GET_AGENCY_DETAILS_DATA] =
|
||||
'/longhorn/external-agency-performance-dashboard/field-governance/agency';
|
||||
API_URLS[ApiKeys.GET_AGENT_PERFORMANCE_DATA] =
|
||||
'/longhorn/external-agency-performance-dashboard/field-governance/agent';
|
||||
API_URLS[ApiKeys.GET_AGENT_PERFORMANCE_TL_DATA] =
|
||||
'/longhorn/external-agency-performance-dashboard/overall-performance/agent-performance';
|
||||
|
||||
// TODO: try to get rid of `as`
|
||||
const MOCK_API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
|
||||
@@ -338,6 +356,12 @@ MOCK_API_URLS[ApiKeys.ACTIVE_TELE_USERS] = 'activeTeleUsers.json';
|
||||
MOCK_API_URLS[ApiKeys.GET_REALLOCATION_HISTORY] = 'reallocationHistory.json';
|
||||
MOCK_API_URLS[ApiKeys.AGENT_CASES_LIST] = 'agentCasesList.json';
|
||||
MOCK_API_URLS[ApiKeys.AGENT_LOCATIONS] = 'agentLocations.json';
|
||||
MOCK_API_URLS[ApiKeys.GET_DPD_BUCKET_DATA] = 'dpdBucket.json';
|
||||
MOCK_API_URLS[ApiKeys.GET_AGENCY_NAME_AND_STATE_OPTIONS] = 'agencyAndStateOptions.json';
|
||||
MOCK_API_URLS[ApiKeys.GET_AGENCY_NAME_OPTIONS] = 'agencyOptions.json';
|
||||
MOCK_API_URLS[ApiKeys.GET_AGENCY_DETAILS_DATA] = 'agencyDetails.json';
|
||||
MOCK_API_URLS[ApiKeys.GET_AGENT_PERFORMANCE_DATA] = 'agentPerformance.json';
|
||||
MOCK_API_URLS[ApiKeys.GET_AGENT_PERFORMANCE_TL_DATA] = 'agentPerformanceTl.json';
|
||||
|
||||
let navigate: NavigateFunction;
|
||||
let dispatch: Dispatch<any>;
|
||||
@@ -486,9 +510,11 @@ const excludedRedirectionEndPointsList = [
|
||||
'/cases/details/lan',
|
||||
APP_ROUTES.AGENT_AVAILABILITY.path,
|
||||
APP_ROUTES.SENSEI_TELE.path,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSAI.daily_planning,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.daily_planning,
|
||||
APP_ROUTES.AMEYO_UTILITY.path,
|
||||
APP_ROUTES.AMEYO_UPLOAD_NUMBERS.path
|
||||
APP_ROUTES.AMEYO_UPLOAD_NUMBERS.path,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.overall_performance,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.field_governance
|
||||
];
|
||||
|
||||
const checkIsValidRedirection = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IFilterSchema } from '../components/interfaces';
|
||||
|
||||
type IconvertIntoMap = IterableIterator<[string, string]>;
|
||||
type IData = {
|
||||
export type IData = {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
@@ -397,3 +397,15 @@ export const getFileDownload = (url: string) => {
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
};
|
||||
|
||||
export const toFixedValueNotation = (value: string, decimal = 2) => {
|
||||
if (!value) return '0';
|
||||
|
||||
const parsedValue = Number(value);
|
||||
|
||||
const result = isWholeNumber(parsedValue)
|
||||
? Math.trunc(parsedValue).toString()
|
||||
: parsedValue.toFixed(decimal);
|
||||
|
||||
return result.replace(/\.0$/, ''); // Remove trailing .0 for whole numbers
|
||||
};
|
||||
|
||||
Submodule web-ui-library updated: 4f22f22f13...a99fbe86a9
Reference in New Issue
Block a user