TP-50439 map enhancements v2 (#878)
* TP-50439 | map enhancements * TP-50439 | Map enhancements v2 * TP-50439 | fix missing icon houston * TP-50439 | revert config files changes * TP-50439 | revert debug extension console log * TP-50439 | fix * TP-50439 | fixes * TP-50439 | fixes * TP-50439 | zindex var * TP-50439 | live location external routing changes * TP-50439 | external Live Location Fixes * TP-50439 | internal map view fixes * TP-50439 | fixes * TP-50439 | revert api helper * TP-50439 | submodule update * TP-50439 | feedback selection fix * TP-50439 | fix --------- Co-authored-by: yashmantri <mantri.ramkishor@navi.com>
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
},
|
||||
"rules": {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/display-name": "off",
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
window.config = {
|
||||
BFF_SERVICE_BASE_URL: '/api',
|
||||
EXTENSION_PLUGIN_USERS_LIST: [],
|
||||
APM_BASE_URL: 'https://apm-server.np.navi-sa.in/',
|
||||
APM_APP_NAME: 'collections-portal',
|
||||
AUTH_BASE_URL: 'https://dev-dark-knight.np.navi-sa.in',
|
||||
AUTH_CLIENT_ID: 'KBDpUxvr5S',
|
||||
SENTRY_DSN: 'https://6f03f79661684b70a2e501dde312402d@glitchtip.cmd.navi-tech.in/126',
|
||||
BUILD_TIME: 0,
|
||||
ENABLE_SSO: "true",
|
||||
ENV: "dev",
|
||||
FCM_apiKey: 'AIzaSyAmUlPwmxtjehTcPBOH3HGxGP-BdWvC9JY',
|
||||
FCM_authDomain: 'longhornchat-qa.firebaseapp.com',
|
||||
FCM_databaseURL: 'https://address-verification-app-default-rtdb.firebaseio.com',
|
||||
FCM_projectId: 'longhornchat-qa',
|
||||
FCM_storageBucket: 'longhornchat-qa.appspot.com',
|
||||
FCM_messagingSenderId: '767234038375',
|
||||
FCM_appId: '1:767234038375:web:a6804341cbaa889b5c12c2',
|
||||
FCM_measurementId: 'G-CV730KBYP6',
|
||||
FCM_VapidKey: 'BBWfszq15lfSiA2IdSIi9mwo8vK52D47wlQBSHyMbdyaufuy5b13DAvNETyB-dvxROz5C_JKuOobbEfZt4Dpvzk',
|
||||
DISABLE_AMEYO_TOOLBAR: "false",
|
||||
GOOGLE_CAPTCHA_SITE_KEY: "6LezfLIlAAAAABGHea7siv00VaZhRjfcPoCEI6_c",
|
||||
GOOGLE_MAPS_KEY: '',
|
||||
GOOGLE_MAP_ID: '',
|
||||
HRC_CALL_AUTO_ACCEPT_TIMEOUT: "2",
|
||||
BFF_SERVICE_BASE_URL: '/api',
|
||||
EXTENSION_PLUGIN_USERS_LIST: [],
|
||||
APM_BASE_URL: 'https://apm-server.np.navi-sa.in/',
|
||||
APM_APP_NAME: 'collections-portal',
|
||||
AUTH_BASE_URL: 'https://dev-dark-knight.np.navi-sa.in',
|
||||
AUTH_CLIENT_ID: 'KBDpUxvr5S',
|
||||
SENTRY_DSN: 'https://6f03f79661684b70a2e501dde312402d@glitchtip.cmd.navi-tech.in/126',
|
||||
BUILD_TIME: 0,
|
||||
ENABLE_SSO: 'true',
|
||||
ENV: 'dev',
|
||||
FCM_apiKey: 'AIzaSyAmUlPwmxtjehTcPBOH3HGxGP-BdWvC9JY',
|
||||
FCM_authDomain: 'longhornchat-qa.firebaseapp.com',
|
||||
FCM_databaseURL: 'https://address-verification-app-default-rtdb.firebaseio.com',
|
||||
FCM_projectId: 'longhornchat-qa',
|
||||
FCM_storageBucket: 'longhornchat-qa.appspot.com',
|
||||
FCM_messagingSenderId: '767234038375',
|
||||
FCM_appId: '1:767234038375:web:a6804341cbaa889b5c12c2',
|
||||
FCM_measurementId: 'G-CV730KBYP6',
|
||||
FCM_VapidKey:
|
||||
'BBWfszq15lfSiA2IdSIi9mwo8vK52D47wlQBSHyMbdyaufuy5b13DAvNETyB-dvxROz5C_JKuOobbEfZt4Dpvzk',
|
||||
DISABLE_AMEYO_TOOLBAR: 'false',
|
||||
GOOGLE_CAPTCHA_SITE_KEY: '6LezfLIlAAAAABGHea7siv00VaZhRjfcPoCEI6_c',
|
||||
GOOGLE_MAPS_KEY: '',
|
||||
GOOGLE_MAP_ID: '',
|
||||
HRC_CALL_AUTO_ACCEPT_TIMEOUT: '2'
|
||||
};
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
--z-index-external-sensei-toggle-tabs: 3;
|
||||
--z-index-external-sensei-header: 4;
|
||||
--z-index-dropdown-picker: 10;
|
||||
--z-index-map-options: 10;
|
||||
--z-index-side-logout-btn: 42;
|
||||
--z-index-ameyo-disconnect-btn: 99;
|
||||
--z-index-leaderboard-overlay: 99;
|
||||
@@ -150,6 +151,7 @@
|
||||
--z-index-autocomplete-dropdown: 100;
|
||||
--z-index-ameyo-collapsible-toolbar: 100;
|
||||
--z-index-autocomplete-picker: 100;
|
||||
--z-index-maps-loader-overlay: 100;
|
||||
--z-index-sticky-note: 999;
|
||||
--z-index-audio-player: 1001;
|
||||
--z-index-leaderboard-overlay-active: 1002;
|
||||
|
||||
@@ -17,6 +17,7 @@ const SingleAutocompleteDropdown = forwardRef<HTMLInputElement, ISingleAutocompl
|
||||
error,
|
||||
disabled,
|
||||
sortOptions,
|
||||
hideCloseOption,
|
||||
customSortFunction,
|
||||
customOptionTemplate,
|
||||
onSearch,
|
||||
@@ -209,7 +210,7 @@ const SingleAutocompleteDropdown = forwardRef<HTMLInputElement, ISingleAutocompl
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.iconsContainer}>
|
||||
{selectedOption && !disabled ? (
|
||||
{selectedOption && !disabled && !hideCloseOption ? (
|
||||
<CloseIcon className={styles.clearAll} onClick={handleClearAll} />
|
||||
) : null}
|
||||
<ArrowIcon width={9} />
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface ISingleAutocompleteDropdown extends ComponentPropsWithRef<'inpu
|
||||
error?: string;
|
||||
disabled?: boolean;
|
||||
sortOptions?: boolean;
|
||||
hideCloseOption?: boolean;
|
||||
onSearch?: (searchTerm: string) => void;
|
||||
onSelectionChange: (selectedOptions: IOption | null) => void;
|
||||
customOptionTemplate?: (option: IOption) => React.ReactNode;
|
||||
|
||||
@@ -16,7 +16,9 @@ export const DASHBOARD_URL = location.origin + '/calling-agent/dashboard';
|
||||
export const ADMIN_CASES_URL = location.origin + '/admin';
|
||||
|
||||
export const persistQueryParamsRoutes = [
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_allocation_view
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_allocation_view,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_live_location,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.live_location
|
||||
];
|
||||
|
||||
export default SideBarItems;
|
||||
|
||||
@@ -25,7 +25,7 @@ import Switch from '@navi/web-ui/lib/primitives/Switch/Switch';
|
||||
import { refreshCallData, setManualLoginCreds } from 'src/reducers/humanReminderSlice';
|
||||
import { ExtensionHandler } from 'src/utils/extension.utils';
|
||||
import { AMEYO_STATUS_CODES } from 'src/service/naviExtension.service';
|
||||
import APP_ROUTES from 'src/layout/Routes';
|
||||
import APP_ROUTES, { APP_ROUTES_PATHS_WITH_PATH_PARAM } from 'src/layout/Routes';
|
||||
import { toast } from '@navi/web-ui/lib/primitives/Toast/core/toast';
|
||||
import {
|
||||
AMEYO_SESSION_EVENTS,
|
||||
@@ -404,7 +404,7 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) {
|
||||
route={
|
||||
externalAgencyPerformanceDashboard
|
||||
? APP_ROUTES.SENSEI_EXTERNAL.relativePath
|
||||
: APP_ROUTES.PERFORMANCE_DASHBOARD.path
|
||||
: APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.daily_planning
|
||||
}
|
||||
linkClickHandler={linkClickHandler}
|
||||
currentPathname={pathname}
|
||||
@@ -510,19 +510,6 @@ function SideNavBar({ isDc97User, isHRCChatUser }: ISideNavbarProps) {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{fieldTLAndClusterAndZonalManagers ? (
|
||||
<SidebarLinks
|
||||
key={APP_ROUTES.SENSEI.path}
|
||||
activeIcon={<AgencyPincodeMappingIcon />}
|
||||
inActiveIcon={<AgencyPincodeMappingIcon />}
|
||||
name={'Tracker'}
|
||||
route={APP_ROUTES.SENSEI.relativePath}
|
||||
linkClickHandler={linkClickHandler}
|
||||
currentPathname={pathname}
|
||||
currentSearch={search}
|
||||
clickStreamEvent={CLICKSTREAM_EVENT_NAMES.LH_FIELD_DASHBOARD_SIDE_PANEL_CLICK}
|
||||
/>
|
||||
) : null}
|
||||
{isPincodeMappingVisible ? (
|
||||
<SidebarLinks
|
||||
key={APP_ROUTES.AGENCY_PINCODE_MAPPING.path}
|
||||
|
||||
@@ -105,6 +105,8 @@ const HideTopNavBarRoutes = [
|
||||
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_PATHS_WITH_PATH_PARAM.SENSEI.live_location,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_live_location,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_allocation_view,
|
||||
APP_ROUTES.AMEYO_UTILITY.path,
|
||||
APP_ROUTES.GENERATE_AMEYO_PASSWORD.path,
|
||||
|
||||
@@ -27,6 +27,8 @@ export const APP_ROUTES_PATHS_WITH_PATH_PARAM = {
|
||||
overall_performance: '/sensei-external/overall_performance',
|
||||
field_governance: '/sensei-external/field_governance',
|
||||
external_daily_planning: '/sensei-external/daily_planning',
|
||||
external_live_location: '/sensei-external/live_location',
|
||||
live_location: '/sensei/live_location',
|
||||
overall_metric: '/sensei/overall_metric',
|
||||
external_allocation_view: '/sensei-external/allocation_view'
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ $headerzIndex: 3;
|
||||
.headerTitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@@ -20,27 +21,13 @@ $headerzIndex: 3;
|
||||
color: var(--navi-color-gray-c3);
|
||||
}
|
||||
|
||||
.lastRefreshedContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.refresh {
|
||||
cursor: pointer;
|
||||
.refreshIcon {
|
||||
margin-right: 4px;
|
||||
|
||||
.refreshWrapper {
|
||||
color: var(--blue-base);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.refreshIcon {
|
||||
margin-right: 4px;
|
||||
|
||||
fill {
|
||||
color: var(--blue-base);
|
||||
}
|
||||
fill {
|
||||
color: var(--blue-base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
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 { getCaseAllocationViewData } from '@cp/src/pages/MapAllocationView/actions/CaseAllocationActions';
|
||||
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 { readQueryParams } from '@cp/src/utils/QueryParamsHelper';
|
||||
import { RefreshIcon } from '@navi/web-ui/lib/icons';
|
||||
import { Button, Typography } from '@navi/web-ui/lib/primitives';
|
||||
import { Typography } from '@navi/web-ui/lib/primitives';
|
||||
import { useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
@@ -24,24 +14,36 @@ import {
|
||||
getDpdBucketData
|
||||
} from '../../actions/ExternalDashboardSenseiActions';
|
||||
import { TabsKey } from '../../constants';
|
||||
import { getLastUpdatedAt } from '../../utils';
|
||||
import {
|
||||
getFieldGovernanceLastUpdatedAt,
|
||||
getLastUpdatedAt,
|
||||
getOverallPerformanceLastUpdatedAt
|
||||
} from '../../utils';
|
||||
import styles from './index.module.scss';
|
||||
import { fetchCommitments, fetchPTPDetails } from '@cp/src/pages/sensei/actions';
|
||||
import { AGENT_LIVE_LOCATION } from '@cp/src/pages/LiveLocationTracker/constants/LiveLocatonTrackerConstants';
|
||||
import { getAgentDetails } from '@cp/src/pages/LiveLocationTracker/utils';
|
||||
import { getAgentsLocations } from '@cp/src/pages/LiveLocationTracker/actions/LiveLocationTrackerActions';
|
||||
import { FilterTypes } from '@cp/src/pages/LiveLocationTracker/constants/LiveLocationTrackerInterfaces';
|
||||
|
||||
const Header = () => {
|
||||
const queryParams = readQueryParams();
|
||||
const agentLiveLocationParams = queryParams?.[AGENT_LIVE_LOCATION] || {};
|
||||
const navigate = useNavigate();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { tabId = '' } = useParams();
|
||||
|
||||
const { allLastUpdatedAt, userData, featureFlags, dailyPlanningLastUpdatedAt } = useSelector(
|
||||
(state: RootState) => ({
|
||||
allLastUpdatedAt: state?.externalDashboardSensei?.allLastUpdatedAt,
|
||||
userData: state?.common?.userData,
|
||||
featureFlags: state?.common?.featureFlags,
|
||||
dailyPlanningLastUpdatedAt: state?.sensai?.dailyPlanning?.commitmentsTable?.lastUpdatedAt
|
||||
})
|
||||
);
|
||||
const {
|
||||
allLastUpdatedAt,
|
||||
featureFlags,
|
||||
dailyPlanningLastUpdatedAt,
|
||||
liveLocationTrackerLastUpdatedAt
|
||||
} = useSelector((state: RootState) => ({
|
||||
allLastUpdatedAt: state?.externalDashboardSensei?.allLastUpdatedAt,
|
||||
featureFlags: state?.common?.featureFlags,
|
||||
dailyPlanningLastUpdatedAt: state?.sensai?.dailyPlanning?.commitmentsTable?.lastUpdatedAt,
|
||||
liveLocationTrackerLastUpdatedAt: state.liveLocationTracker?.lastUpdatedAt
|
||||
}));
|
||||
|
||||
const {
|
||||
overallPerformanceOverallMetricsTable,
|
||||
@@ -51,79 +53,103 @@ const Header = () => {
|
||||
agentAllocationMapViewFlag
|
||||
} = featureFlags || {};
|
||||
|
||||
const lastUpdatedAt = useMemo(
|
||||
() => getLastUpdatedAt({ ...allLastUpdatedAt, dailyPlanningLastUpdatedAt }, tabId),
|
||||
[tabId, allLastUpdatedAt, dailyPlanningLastUpdatedAt]
|
||||
);
|
||||
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;
|
||||
case TabsKey.DAILY_PLANNING:
|
||||
dispatch(fetchCommitments());
|
||||
dispatch(fetchPTPDetails());
|
||||
break;
|
||||
const { lastUpdatedAt, handleRefresh } = useMemo(() => {
|
||||
let handleRefresh = null;
|
||||
let lastUpdatedAt = null;
|
||||
|
||||
case TabsKey.ALLOCATION_VIEW:
|
||||
if (agentAllocationMapViewFlag) dispatch(getCaseAllocationViewData());
|
||||
switch (tabId) {
|
||||
case TabsKey.DAILY_PLANNING: {
|
||||
handleRefresh = () => {
|
||||
dispatch(fetchCommitments());
|
||||
dispatch(fetchPTPDetails());
|
||||
};
|
||||
lastUpdatedAt = dailyPlanningLastUpdatedAt;
|
||||
break;
|
||||
}
|
||||
case TabsKey.OVERALL_PERFORMANCE: {
|
||||
handleRefresh = () => {
|
||||
if (overallPerformanceOverallMetricsTable) dispatch(getDpdBucketData());
|
||||
if (overallPerformanceAgentPerformanceTable) dispatch(getAgentPerformanceTLData());
|
||||
};
|
||||
lastUpdatedAt = getOverallPerformanceLastUpdatedAt(allLastUpdatedAt);
|
||||
break;
|
||||
}
|
||||
case TabsKey.FIELD_GOVERNANCE: {
|
||||
handleRefresh = () => {
|
||||
if (fieldGovernanceAgencyPerformanceTable) dispatch(getAgencyDetailsData());
|
||||
if (fieldGovernanceAgentPerformanceTable) dispatch(getAgentPerformanceData());
|
||||
};
|
||||
lastUpdatedAt = getFieldGovernanceLastUpdatedAt(allLastUpdatedAt);
|
||||
break;
|
||||
}
|
||||
case TabsKey.ALLOCATION_VIEW: {
|
||||
const isAllocationFiltersSelected = Object.keys(queryParams?.allocationView || {})?.length;
|
||||
if (!isAllocationFiltersSelected) return { lastUpdatedAt: null, handleRefresh: null };
|
||||
handleRefresh = () => {
|
||||
if (agentAllocationMapViewFlag) dispatch(getCaseAllocationViewData());
|
||||
};
|
||||
lastUpdatedAt = allLastUpdatedAt?.allocationViewLastUpdatedAt;
|
||||
break;
|
||||
}
|
||||
case TabsKey.LIVE_LOCATION:
|
||||
if (!agentLiveLocationParams?.AGENCY) {
|
||||
return { lastUpdatedAt: null, handleRefresh: null };
|
||||
}
|
||||
handleRefresh = () => {
|
||||
if (agentLiveLocationParams?.AGENTID) {
|
||||
dispatch(
|
||||
getAgentDetails(
|
||||
{
|
||||
date: agentLiveLocationParams?.DATE,
|
||||
referenceId: agentLiveLocationParams?.AGENTID
|
||||
},
|
||||
navigate
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const selectedTeamLead = agentLiveLocationParams[FilterTypes.TEAM_LEAD];
|
||||
const selectedAgency = agentLiveLocationParams[FilterTypes.AGENCY];
|
||||
const payload = {
|
||||
agencyCodes: [selectedAgency],
|
||||
teamLeadReferenceIds: selectedTeamLead ? [selectedTeamLead] : []
|
||||
};
|
||||
dispatch(getAgentsLocations(payload, false));
|
||||
}
|
||||
};
|
||||
lastUpdatedAt = liveLocationTrackerLastUpdatedAt;
|
||||
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}`);
|
||||
};
|
||||
|
||||
const queryParams = readQueryParams();
|
||||
const isAllocationFiltersSelected = Object.keys(queryParams?.allocationView || {})?.length;
|
||||
const showExtraInfo = tabId === TabsKey.ALLOCATION_VIEW ? isAllocationFiltersSelected : true;
|
||||
return { lastUpdatedAt, handleRefresh };
|
||||
}, [
|
||||
tabId,
|
||||
queryParams?.SELECTED_FEEDBACK_ID,
|
||||
allLastUpdatedAt,
|
||||
dailyPlanningLastUpdatedAt,
|
||||
liveLocationTrackerLastUpdatedAt
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className={styles.headerTitle}>
|
||||
<Typography variant="h3">Agent Dashboard</Typography>
|
||||
{showExtraInfo ? (
|
||||
<>
|
||||
<div className="flex gap-2">
|
||||
{lastUpdatedAt ? (
|
||||
<Typography variant="p5" className={styles.lastUpdatedAt}>
|
||||
Last updated at{' '}
|
||||
{lastUpdatedAt
|
||||
? dateFormat(new Date(lastUpdatedAt), DateFormat.LONG_DATE_FORMAT_WITH_TIME)
|
||||
: '--'}
|
||||
</Typography>
|
||||
) : null}
|
||||
{handleRefresh ? (
|
||||
<div className={styles.refresh} onClick={handleRefresh}>
|
||||
<RefreshIcon className={styles.refreshIcon} color="var(--navi-color-blue-base)" />
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={styles.lastRefreshedContainer}>
|
||||
{enableAgentLiveLocation ? (
|
||||
<div>
|
||||
<Button
|
||||
onClick={handleLiveLocationClick}
|
||||
startAdornment={<LocationIcon />}
|
||||
variant="text"
|
||||
>
|
||||
Live location tracker
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ export enum TabsKey {
|
||||
OVERALL_PERFORMANCE = 'overall_performance',
|
||||
FIELD_GOVERNANCE = 'field_governance',
|
||||
DAILY_PLANNING = 'daily_planning',
|
||||
LIVE_LOCATION = 'live_location',
|
||||
ALLOCATION_VIEW = 'allocation_view'
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ 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, { APP_ROUTES_PATHS_WITH_PATH_PARAM } from '@cp/src/layout/Routes';
|
||||
import APP_ROUTES from '@cp/src/layout/Routes';
|
||||
import { interpolatePathParams } from '@cp/src/utils/interpolate';
|
||||
import { LocalStorage } from '@cp/src/utils/StorageUtils';
|
||||
|
||||
@@ -26,12 +26,7 @@ const ExternalDashboardSensei = () => {
|
||||
const paramsMap: Record<string, string> = LocalStorage.getItem('paramsMap') || {};
|
||||
|
||||
const handleTabChange = (updateTabId: TabItemKey) => {
|
||||
let currentParams = '';
|
||||
if (tabId !== updateTabId) {
|
||||
if (updateTabId === TabsKey.ALLOCATION_VIEW) {
|
||||
currentParams =
|
||||
paramsMap[APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_allocation_view] || '';
|
||||
}
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.LH_FIELD_DASHBOARD_TAB_SWITCH, {
|
||||
currentTab: tabId,
|
||||
newTab: updateTabId
|
||||
@@ -39,6 +34,7 @@ const ExternalDashboardSensei = () => {
|
||||
const updatedUrl = interpolatePathParams(APP_ROUTES.SENSEI_EXTERNAL.path, {
|
||||
tabId: String(updateTabId)
|
||||
});
|
||||
const currentParams = paramsMap[updatedUrl] || '';
|
||||
navigate(updatedUrl + currentParams);
|
||||
}
|
||||
};
|
||||
@@ -57,17 +53,14 @@ const ExternalDashboardSensei = () => {
|
||||
TabsKey.FIELD_GOVERNANCE,
|
||||
TabsKey.OVERALL_PERFORMANCE,
|
||||
TabsKey.DAILY_PLANNING,
|
||||
TabsKey.ALLOCATION_VIEW
|
||||
TabsKey.ALLOCATION_VIEW,
|
||||
TabsKey.LIVE_LOCATION
|
||||
].includes(tabId as TabsKey);
|
||||
|
||||
if (isTabAccessNotAvailable || noTabSelected) {
|
||||
const tabId = dashboardTabs?.[0]?.relativePath;
|
||||
let currentParams = '';
|
||||
if (tabId.includes(TabsKey.ALLOCATION_VIEW)) {
|
||||
currentParams =
|
||||
paramsMap[APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_allocation_view] || '';
|
||||
}
|
||||
navigate(tabId + currentParams);
|
||||
const relativePath = dashboardTabs?.[0]?.relativePath;
|
||||
const currentParams = paramsMap[relativePath] || '';
|
||||
navigate(relativePath + currentParams);
|
||||
}
|
||||
}, [tabId, dashboardTabs]);
|
||||
|
||||
|
||||
@@ -155,6 +155,7 @@ export interface IAllLastUpdateAt {
|
||||
agentPerformanceTlLastUpdatedAt: number;
|
||||
dailyPlanningLastUpdatedAt: number;
|
||||
allocationViewLastUpdatedAt: number;
|
||||
liveLocationTrackerLastUpdatedAt: string | Date;
|
||||
}
|
||||
|
||||
export interface IExternalSenseiDashboard {
|
||||
|
||||
@@ -7,10 +7,16 @@ import AllocationView from '../MapAllocationView';
|
||||
import OverallPerformance from './OverallPerformance';
|
||||
import { FIELD_GOVERNANCE_TABS_VALUE, IAllLastUpdateAt } from './types';
|
||||
import ExternalDailyPlanning from './components/DailyPlanning';
|
||||
import LiveLocationTracker from '../LiveLocationTracker';
|
||||
|
||||
export const getTabs = (featureFlags: IFeatureFlags, tabId: string) => {
|
||||
const { overallPerformanceTab, fieldGovernanceTab, fcmDashboard, agentAllocationMapViewFlag } =
|
||||
featureFlags || {};
|
||||
const {
|
||||
overallPerformanceTab,
|
||||
fieldGovernanceTab,
|
||||
fcmDashboard,
|
||||
agentAllocationMapViewFlag,
|
||||
liveLocationTrackerFeatureFlag
|
||||
} = featureFlags || {};
|
||||
|
||||
const tabs = [];
|
||||
|
||||
@@ -41,6 +47,15 @@ export const getTabs = (featureFlags: IFeatureFlags, tabId: string) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (liveLocationTrackerFeatureFlag) {
|
||||
tabs.push({
|
||||
key: TabsKey.LIVE_LOCATION,
|
||||
value: 'Live location tracker',
|
||||
component: <LiveLocationTracker />,
|
||||
relativePath: '/sensei-external/live_location'
|
||||
});
|
||||
}
|
||||
|
||||
if (agentAllocationMapViewFlag) {
|
||||
tabs.push({
|
||||
key: TabsKey.ALLOCATION_VIEW,
|
||||
@@ -121,6 +136,30 @@ export const setInitialOverallPerformanceTabParams = (
|
||||
navigate(updatedParams);
|
||||
};
|
||||
|
||||
export const getOverallPerformanceLastUpdatedAt = (allLastUpdatedAt: IAllLastUpdateAt) => {
|
||||
const { dpdBucketLastUpdatedAt, agentPerformanceTlLastUpdatedAt } = allLastUpdatedAt;
|
||||
|
||||
if (!dpdBucketLastUpdatedAt) return agentPerformanceTlLastUpdatedAt;
|
||||
if (!agentPerformanceTlLastUpdatedAt) return dpdBucketLastUpdatedAt;
|
||||
|
||||
if (dpdBucketLastUpdatedAt < agentPerformanceTlLastUpdatedAt) {
|
||||
return dpdBucketLastUpdatedAt;
|
||||
}
|
||||
return agentPerformanceTlLastUpdatedAt;
|
||||
};
|
||||
|
||||
export const getFieldGovernanceLastUpdatedAt = (allLastUpdatedAt: IAllLastUpdateAt) => {
|
||||
const { agencyDetailsLastUpdatedAt, agentPerformanceLastUpdatedAt } = allLastUpdatedAt;
|
||||
|
||||
if (!agencyDetailsLastUpdatedAt) return agentPerformanceLastUpdatedAt;
|
||||
if (!agentPerformanceLastUpdatedAt) return agencyDetailsLastUpdatedAt;
|
||||
|
||||
if (agencyDetailsLastUpdatedAt < agentPerformanceLastUpdatedAt) {
|
||||
return agencyDetailsLastUpdatedAt;
|
||||
}
|
||||
return agentPerformanceLastUpdatedAt;
|
||||
};
|
||||
|
||||
export const getLastUpdatedAt = (allLastUpdatedAt: IAllLastUpdateAt, tabId: string) => {
|
||||
const {
|
||||
dpdBucketLastUpdatedAt,
|
||||
@@ -128,7 +167,8 @@ export const getLastUpdatedAt = (allLastUpdatedAt: IAllLastUpdateAt, tabId: stri
|
||||
agentPerformanceLastUpdatedAt,
|
||||
agentPerformanceTlLastUpdatedAt,
|
||||
dailyPlanningLastUpdatedAt,
|
||||
allocationViewLastUpdatedAt
|
||||
allocationViewLastUpdatedAt,
|
||||
liveLocationTrackerLastUpdatedAt
|
||||
} = allLastUpdatedAt;
|
||||
|
||||
if (tabId === TabsKey.OVERALL_PERFORMANCE) {
|
||||
@@ -158,4 +198,7 @@ export const getLastUpdatedAt = (allLastUpdatedAt: IAllLastUpdateAt, tabId: stri
|
||||
if (tabId === TabsKey.ALLOCATION_VIEW) {
|
||||
return allocationViewLastUpdatedAt;
|
||||
}
|
||||
if (tabId === TabsKey.LIVE_LOCATION) {
|
||||
return liveLocationTrackerLastUpdatedAt;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,14 +3,22 @@ import useMap from '../../hooks/useMap';
|
||||
import styles from './agentMarker.module.scss';
|
||||
import Tooltip from '../Tooltip';
|
||||
import { getNameInitials } from 'src/utils/commonUtils';
|
||||
import { AgentMarkerProps, ColorMap } from '../../constants/LiveLocationTrackerInterfaces';
|
||||
import { useSelector } from 'react-redux';
|
||||
import {
|
||||
AgentMarkerProps,
|
||||
ColorMap,
|
||||
LOCATION_TYPE_FILTERS
|
||||
} from '../../constants/LiveLocationTrackerInterfaces';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RootState } from '../../../../store';
|
||||
import { addClickstreamEvent } from 'src/service/clickStreamEventService';
|
||||
import { AgentTrackingEvents } from 'src/service/clickStream.constant';
|
||||
import AgentMarkerIcon from './AgentMarkerIcon';
|
||||
import { shouldZoomIn, loadCustomPopup } from '../../utils';
|
||||
import { ICustomPopup } from '../../types';
|
||||
import { AGENT_LIVE_LOCATION, PinColorMapping } from '../../constants/LiveLocatonTrackerConstants';
|
||||
import { setSelectedFeedback } from '../../reducers/LiveLocationTrackerSlice';
|
||||
import { createQueryParams, readQueryParams } from '@cp/src/utils/QueryParamsHelper';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export const colorMap: ColorMap = {
|
||||
yellow: {
|
||||
@@ -38,19 +46,11 @@ export interface InfoWindowRef {
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
const AgentMarker = ({
|
||||
color,
|
||||
position,
|
||||
name,
|
||||
lastUpdatedAt,
|
||||
referenceId,
|
||||
type,
|
||||
lanNo,
|
||||
interactionId,
|
||||
interactionStatus,
|
||||
activeCustomPopup,
|
||||
refId
|
||||
}: AgentMarkerProps) => {
|
||||
export const AgentMarker = ({ agentLocation, activeCustomPopup, position }: AgentMarkerProps) => {
|
||||
const { name, referenceId, type, lanNo, interactionId, activityLevel, location, zIndex } =
|
||||
agentLocation;
|
||||
const refId = referenceId || lanNo || interactionId || '';
|
||||
const color = PinColorMapping[activityLevel] ?? 'red';
|
||||
const map = useMap();
|
||||
const markerRef = useRef(null);
|
||||
const markerInstance: any = useRef(null);
|
||||
@@ -59,15 +59,24 @@ const AgentMarker = ({
|
||||
const initials = getNameInitials(name);
|
||||
const isInfoWindowVisible = useRef(false);
|
||||
const customPopupRef = useRef<ICustomPopup | null>(null);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { [AGENT_LIVE_LOCATION]: queryParams = {} } = readQueryParams();
|
||||
|
||||
const { selectedAgent, isShowAllInfoWindow, userId, selectedFeedback } = useSelector(
|
||||
(store: RootState) => ({
|
||||
const { selectedAgent, isShowAllInfoWindow, userId, selectedFeedback, agentAllocations } =
|
||||
useSelector((store: RootState) => ({
|
||||
selectedAgent: store?.liveLocationTracker?.selectedAgent,
|
||||
isShowAllInfoWindow: store?.liveLocationTracker?.showAllInfoWindows,
|
||||
userId: store?.common?.userData?.referenceId,
|
||||
selectedFeedback: store?.liveLocationTracker?.selectedFeedback
|
||||
})
|
||||
);
|
||||
selectedFeedback: store?.liveLocationTracker?.selectedFeedback,
|
||||
agentAllocations: store?.liveLocationTracker?.agentAllocations?.data
|
||||
}));
|
||||
|
||||
const selectedFeedbackRef = useRef(selectedFeedback);
|
||||
|
||||
useEffect(() => {
|
||||
selectedFeedbackRef.current = selectedFeedback;
|
||||
}, [selectedFeedback]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -103,6 +112,33 @@ const AgentMarker = ({
|
||||
}
|
||||
}, [selectedFeedback]);
|
||||
|
||||
const feedbackClickHandler = () => {
|
||||
if (!lanNo) {
|
||||
return;
|
||||
}
|
||||
const selectedFeedback = selectedFeedbackRef.current;
|
||||
const data = agentAllocations?.[lanNo];
|
||||
const isSelectedFeedbackClicked = selectedFeedback?.interactionId === interactionId;
|
||||
dispatch(
|
||||
isSelectedFeedbackClicked
|
||||
? setSelectedFeedback(null)
|
||||
: setSelectedFeedback({
|
||||
lanNo,
|
||||
interactionId,
|
||||
feedbackLocation: location,
|
||||
caseLocation: data?.location,
|
||||
isSuspicious: type === LOCATION_TYPE_FILTERS.SUSPICIOUS_FEEDBACK
|
||||
})
|
||||
);
|
||||
const newQueryParams = {
|
||||
...queryParams
|
||||
};
|
||||
const url = createQueryParams({
|
||||
[AGENT_LIVE_LOCATION]: newQueryParams
|
||||
});
|
||||
navigate(url);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (map?.state?.mapLoaderInstance) {
|
||||
map?.state?.mapLoaderInstance
|
||||
@@ -111,7 +147,8 @@ const AgentMarker = ({
|
||||
markerInstance.current = new AdvancedMarkerElement({
|
||||
position: position,
|
||||
map: map.state.mapInstance,
|
||||
content: markerRef.current
|
||||
content: markerRef.current,
|
||||
zIndex
|
||||
});
|
||||
const CustomPopup = loadCustomPopup(type);
|
||||
if (tooltipRef.current) {
|
||||
@@ -133,6 +170,12 @@ const AgentMarker = ({
|
||||
name
|
||||
}
|
||||
});
|
||||
if (
|
||||
type === LOCATION_TYPE_FILTERS.GENUINE_FEEDBACK ||
|
||||
type === LOCATION_TYPE_FILTERS.SUSPICIOUS_FEEDBACK
|
||||
) {
|
||||
feedbackClickHandler();
|
||||
}
|
||||
} else {
|
||||
addClickstreamEvent(AgentTrackingEvents.LH_AGENT_TRACKING_PIN_CLICKED, {
|
||||
userId,
|
||||
@@ -181,12 +224,8 @@ const AgentMarker = ({
|
||||
<AgentMarkerIcon type={type} color={color} isFocused={isFocused} initials={initials} />
|
||||
<div className={styles.tooltip}>
|
||||
<Tooltip
|
||||
name={name}
|
||||
lastUpdateActivity={lastUpdatedAt}
|
||||
ref={tooltipRef}
|
||||
type={type}
|
||||
lanNo={lanNo}
|
||||
interactionStatus={interactionStatus}
|
||||
agentLocation={agentLocation}
|
||||
closeTooltip={handleCloseTooltip}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { BorderedInput } from '@navi/web-ui/lib/primitives';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -15,7 +14,6 @@ import styles from './index.module.scss';
|
||||
import { _map } from 'src/utils/commonUtils';
|
||||
import { IFeedback, LOCATION_TYPE_FILTERS } from '../../constants/LiveLocationTrackerInterfaces';
|
||||
import { setSelectedFeedback } from '../../reducers/LiveLocationTrackerSlice';
|
||||
import useMap from '../../hooks/useMap';
|
||||
import DateTimePickerComponent from '@cp/src/components/DateTimePicker/DateTimePickerComponent';
|
||||
import { DATE_TIME_TYPE } from '@cp/src/components/DateTimePicker/constants';
|
||||
|
||||
@@ -35,6 +33,7 @@ const AccordionBody = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const polylineRef = useRef<any>(null);
|
||||
const feedbackRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
||||
|
||||
const { [AGENT_LIVE_LOCATION]: queryParams = {} } = readQueryParams();
|
||||
|
||||
@@ -86,6 +85,19 @@ const AccordionBody = () => {
|
||||
return lanKeys;
|
||||
}, [agentAllocations]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedFeedback && !queryParams?.SELECTED_FEEDBACK_ID) {
|
||||
// scroll to the selected feedback view
|
||||
const feedbackRef = feedbackRefs.current[selectedFeedback.lanNo];
|
||||
if (feedbackRef) {
|
||||
feedbackRef.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [selectedFeedback, queryParams?.SELECTED_FEEDBACK_ID]);
|
||||
|
||||
const feedbackClickHandler = (lanNo: string, feedback: IFeedback) => {
|
||||
const data = agentAllocations?.[lanNo];
|
||||
const isSelectedFeedbackClicked = selectedFeedback?.interactionId === feedback?.interactionId;
|
||||
@@ -103,7 +115,7 @@ const AccordionBody = () => {
|
||||
);
|
||||
addClickstreamEvent(
|
||||
isSelectedFeedbackClicked
|
||||
? AgentTrackingEvents.LH_MAP_FEEDBACK_CLICKED
|
||||
? AgentTrackingEvents.LH_MAP_FEEDBACK_DOUBLE_CLICKED
|
||||
: AgentTrackingEvents.LH_MAP_FEEDBACK_CLICKED,
|
||||
{
|
||||
userId,
|
||||
@@ -129,7 +141,7 @@ const AccordionBody = () => {
|
||||
|
||||
if (selectedAgent) {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className={styles.dateFilterContainer}>
|
||||
<div className={styles.activityLog}>ACTIVITY LOG</div>
|
||||
|
||||
@@ -143,18 +155,21 @@ const AccordionBody = () => {
|
||||
containerClasses={styles.activityDatePicker}
|
||||
/>
|
||||
</div>
|
||||
{agentAllocationsLoading ? null : lanKeys?.length ? (
|
||||
lanKeys.map(lanNo => (
|
||||
<FeedbackDetail
|
||||
key={lanNo}
|
||||
lanNo={lanNo}
|
||||
feedbackClickHandler={feedback => feedbackClickHandler(lanNo, feedback)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className={styles.noDataFound}>No Feedbacks Found</div>
|
||||
)}
|
||||
</>
|
||||
<div className="overflow-y-auto max-h-[calc(100vh-312px)]">
|
||||
{agentAllocationsLoading ? null : lanKeys?.length ? (
|
||||
lanKeys.map(lanNo => (
|
||||
<FeedbackDetail
|
||||
key={lanNo}
|
||||
ref={el => (feedbackRefs.current[lanNo] = el)}
|
||||
lanNo={lanNo}
|
||||
feedbackClickHandler={feedback => feedbackClickHandler(lanNo, feedback)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className={styles.noDataFound}>No Feedbacks Found</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,9 +184,11 @@ const AccordionBody = () => {
|
||||
))}
|
||||
</div>
|
||||
{agentLocations?.length ? (
|
||||
agentLocations?.map(item => (
|
||||
<AgentDetail key={item.referenceId} item={item} containerStyle={styles.agentHover} />
|
||||
))
|
||||
<div className="overflow-y-auto max-h-[calc(100vh-248px)]">
|
||||
{agentLocations?.map(item => (
|
||||
<AgentDetail key={item.referenceId} item={item} containerStyle={styles.agentHover} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.noDataFound}>No Agents Found</div>
|
||||
)}
|
||||
|
||||
@@ -36,7 +36,7 @@ const AgentDetail = (props: AgentDetailProps) => {
|
||||
userId: state?.common?.userData?.referenceId
|
||||
}));
|
||||
const { selectedAgent } = liveLocationTracker || {};
|
||||
const { name = '', activityLevel, referenceId, lastUpdatedAt } = item || {};
|
||||
const { name = '', activityLevel, referenceId, lastUpdatedAt, totalAllocatedCases } = item || {};
|
||||
|
||||
const color = PinColorMapping[activityLevel];
|
||||
|
||||
@@ -133,6 +133,7 @@ const AgentDetail = (props: AgentDetailProps) => {
|
||||
last seen {date} {time}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.agentLastSeen}>Allocated: {totalAllocatedCases || 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
{showNextIcon && <ArrowRightSolidIcon width={14} />}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import FeedbackTriangleIcon from 'src/assets/icons/FeedbackTriangleIcon';
|
||||
import RedirectionIcon from 'src/assets/icons/RedirectionIcon';
|
||||
import APP_ROUTES from 'src/layout/Routes';
|
||||
import { TAB_KEYS } from 'src/pages/CaseDetails/constants';
|
||||
@@ -20,7 +20,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from 'src/components/TooltipV
|
||||
import ChatBubbleExclamation from 'src/assets/images/icons/ChatBubbleExclamation';
|
||||
import ChatBubble from 'src/assets/images/icons/ChatBubble';
|
||||
|
||||
const FeedbackDetail = (props: FeedbackDetailProps) => {
|
||||
const FeedbackDetail = React.forwardRef<HTMLDivElement, FeedbackDetailProps>((props, ref) => {
|
||||
const { lanNo, feedbackClickHandler } = props;
|
||||
const {
|
||||
agentAllocations: { data: agentAllocations },
|
||||
@@ -63,6 +63,7 @@ const FeedbackDetail = (props: FeedbackDetailProps) => {
|
||||
const isFeedbackSelected = selectedFeedback?.interactionId === feedback?.interactionId;
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
key={feedback?.interactionId}
|
||||
className={cx(styles.feedbackItemWrapper, {
|
||||
[styles.feedbackSelected]: isFeedbackSelected
|
||||
@@ -123,6 +124,6 @@ const FeedbackDetail = (props: FeedbackDetailProps) => {
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default FeedbackDetail;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.agentsAccordionContainer {
|
||||
width: 260px;
|
||||
font-family: 'Inter';
|
||||
|
||||
.accordion {
|
||||
border-radius: 8px;
|
||||
@@ -19,10 +20,7 @@
|
||||
|
||||
.accordionBody {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-height: calc(100vh - 230px);
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +151,6 @@
|
||||
.dateFilterContainer {
|
||||
padding: 12px 12px 6px 12px;
|
||||
border-bottom: 1px solid var(--navi-color-gray-border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--navi-color-gray-bg-primary);
|
||||
|
||||
.activityLog {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.googleMapContainer {
|
||||
width: 100%;
|
||||
height: calc(100vh - 60px);
|
||||
height: calc(100vh - 100px); // 100px is the height of the header
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
@import '../../../../assets/styles/animations';
|
||||
|
||||
.blurMapContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
|
||||
.blurMapImg {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
filter: blur(12.5px);
|
||||
}
|
||||
}
|
||||
|
||||
.mapPlaceholderContainer {
|
||||
padding: 20px 16px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 25%;
|
||||
transform: translate(-50%);
|
||||
|
||||
span {
|
||||
color: var(--black-base);
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
border: 8px solid var(--grayscale-background-secondary);
|
||||
border-top: 8px solid var(--navi-color-blue-light);
|
||||
border-radius: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
.autoCompleteMainContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.autoCompleteContainer {
|
||||
background: white;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.optionLabel {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.0125em;
|
||||
color: var(--navi-color-gray-c1);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.selectPickerWrapper {
|
||||
padding: 0;
|
||||
max-height: calc((100vh - 100px - 25%) / 2);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import styles from './index.module.scss';
|
||||
import { Typography } from '@primitives';
|
||||
import { SelectPickerValue } from '@cp/src/components/interfaces';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { QueryParamTypeMapping } from '../../../ExternalDashboardSensei/constants';
|
||||
import { createQueryParams, readQueryParams } from '@cp/src/utils/QueryParamsHelper';
|
||||
import { SingleSelectAutoComplete } from '@navi/web-ui/lib/components';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RootState } from '@cp/src/store';
|
||||
import { addClickstreamEvent } from '@cp/src/service/clickStreamEventService';
|
||||
import { AgentTrackingEvents } from '@cp/src/service/clickStream.constant';
|
||||
import AllocationMapPlaceholder from '@cp/src/pages/MapAllocationView/components/AllocationMapPlaceholder';
|
||||
import { AGENT_LIVE_LOCATION } from '../../constants/LiveLocatonTrackerConstants';
|
||||
import { FilterTypes } from '../../constants/LiveLocationTrackerInterfaces';
|
||||
import { useEffect } from 'react';
|
||||
import { getFilterData } from '../../actions/LiveLocationTrackerActions';
|
||||
|
||||
const LocationAgencySearch = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const params = readQueryParams();
|
||||
const { filterTypes, filterData } = useSelector((state: RootState) => state.liveLocationTracker);
|
||||
const { data = [] } = filterData['AGENCY'];
|
||||
useEffect(() => {
|
||||
// Get agency filter data
|
||||
dispatch(getFilterData());
|
||||
}, []);
|
||||
|
||||
const { caseAllocation } = useSelector((state: RootState) => ({
|
||||
caseAllocation: state.caseAllocation
|
||||
}));
|
||||
const { reportingAgencies } = caseAllocation || {};
|
||||
|
||||
const agencyDropdownHandler = (action: SelectPickerValue) => {
|
||||
if (!action) return;
|
||||
const queryParams = { ...params };
|
||||
|
||||
if (!queryParams?.[AGENT_LIVE_LOCATION]) queryParams[AGENT_LIVE_LOCATION] = {};
|
||||
|
||||
if (!queryParams[AGENT_LIVE_LOCATION]?.[FilterTypes.AGENCY]) {
|
||||
queryParams[AGENT_LIVE_LOCATION][FilterTypes.AGENCY] = action;
|
||||
}
|
||||
|
||||
// addClickstreamEvent(AgentTrackingEvents.LH_MAP_AUTOCOMPLETE_FILTER_APPLIED, {
|
||||
// value: action,
|
||||
// page: ALLOCATION_PAGE
|
||||
// });
|
||||
const updatedParams = createQueryParams(queryParams);
|
||||
navigate(updatedParams);
|
||||
};
|
||||
|
||||
return (
|
||||
<AllocationMapPlaceholder showImage>
|
||||
<Typography variant="h2" as="span">
|
||||
Select an agency to view locations
|
||||
</Typography>
|
||||
<SingleSelectAutoComplete
|
||||
placeholder="Select agency"
|
||||
noResultTitle=""
|
||||
options={data}
|
||||
mainContainerClassName={styles.autoCompleteMainContainer}
|
||||
containerClassName={styles.autoCompleteContainer}
|
||||
selectPickerWrapperClass={styles.selectPickerWrapper}
|
||||
updateSelectedOptions={agencyDropdownHandler}
|
||||
variant="bordered"
|
||||
customOptionTemplate={option => <div className={styles.optionLabel}>{option.label}</div>}
|
||||
/>
|
||||
</AllocationMapPlaceholder>
|
||||
);
|
||||
};
|
||||
|
||||
export default LocationAgencySearch;
|
||||
@@ -1,10 +1,10 @@
|
||||
.blurMapContainer {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
z-index: var(--z-index-maps-loader-overlay);
|
||||
|
||||
.blurMapImg {
|
||||
object-fit: cover;
|
||||
@@ -29,6 +29,9 @@
|
||||
}
|
||||
|
||||
.blurImageWrapper {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,7 @@ import AgentMarker from '../AgentMarker';
|
||||
import useMap from '../../hooks/useMap';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from 'src/store';
|
||||
import {
|
||||
AGENT_LIVE_LOCATION,
|
||||
FilterMap,
|
||||
PinColorMapping
|
||||
} from '../../constants/LiveLocatonTrackerConstants';
|
||||
import { AGENT_LIVE_LOCATION, FilterMap } from '../../constants/LiveLocatonTrackerConstants';
|
||||
import styles from './index.module.scss';
|
||||
import { readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import {
|
||||
@@ -18,8 +14,6 @@ import {
|
||||
} from '../../constants/LiveLocationTrackerInterfaces';
|
||||
import { INHOUSE_AGENCY_CODE } from 'src/components/constant';
|
||||
import MapPlaceholder from '../MapPlaceholder';
|
||||
import AgentsAccordion from '../AgentsAccordion';
|
||||
import PinFilters from '../PinFilters';
|
||||
import { ICustomPopup } from '../../types';
|
||||
|
||||
const defaultMapOptions = {
|
||||
@@ -48,7 +42,6 @@ function MapView() {
|
||||
const {
|
||||
filterTypes,
|
||||
loadingFilters,
|
||||
selectedAgent,
|
||||
agentLocations,
|
||||
mapLocations,
|
||||
agentLocationsLoading,
|
||||
@@ -84,7 +77,7 @@ function MapView() {
|
||||
map.state.mapInstance.setCenter(bounds.getCenter()); //or use custom center
|
||||
map.state.mapInstance.fitBounds(bounds);
|
||||
const currentZoomLevel = map.state.mapInstance.getZoom();
|
||||
const newLevel = currentZoomLevel - 1;
|
||||
const newLevel = currentZoomLevel - 2;
|
||||
map.state.mapInstance.setZoom(newLevel);
|
||||
});
|
||||
}
|
||||
@@ -165,17 +158,19 @@ function MapView() {
|
||||
}, [queryParams?.DATE]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!queryParams.SELECTED_FEEDBACK_ID && polylineRef.current) {
|
||||
if (!queryParams?.SELECTED_FEEDBACK_ID && polylineRef.current) {
|
||||
polylineRef.current.setMap(null);
|
||||
return;
|
||||
}
|
||||
zoomOverFeedback(selectedFeedback?.feedbackLocation, selectedFeedback?.caseLocation);
|
||||
drawLineBetweenFeedbackAndCase(
|
||||
selectedFeedback?.feedbackLocation,
|
||||
selectedFeedback?.caseLocation,
|
||||
selectedFeedback?.isSuspicious
|
||||
);
|
||||
}, [queryParams?.SELECTED_FEEDBACK_ID]);
|
||||
if (queryParams?.SELECTED_FEEDBACK_ID) {
|
||||
zoomOverFeedback(selectedFeedback?.feedbackLocation, selectedFeedback?.caseLocation);
|
||||
drawLineBetweenFeedbackAndCase(
|
||||
selectedFeedback?.feedbackLocation,
|
||||
selectedFeedback?.caseLocation,
|
||||
selectedFeedback?.isSuspicious
|
||||
);
|
||||
}
|
||||
}, [queryParams?.SELECTED_FEEDBACK_ID, selectedFeedback]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapLocations?.length) {
|
||||
@@ -212,66 +207,39 @@ function MapView() {
|
||||
|
||||
const showBlurMap = !mapLocations?.length && !loadingFilters && !isFiltersSelected();
|
||||
|
||||
const googleMapsContainer = useMemo(
|
||||
() => (
|
||||
const googleMapsContainer = useMemo(() => {
|
||||
return (
|
||||
<GoogleMapsContainer mapOptions={defaultMapOptions}>
|
||||
<div>
|
||||
{mapLocations?.map(agentLocation => {
|
||||
const {
|
||||
location,
|
||||
name = '',
|
||||
activityLevel,
|
||||
referenceId,
|
||||
lastUpdatedAt,
|
||||
type,
|
||||
lanNo,
|
||||
interactionId,
|
||||
interactionStatus
|
||||
} = agentLocation || {};
|
||||
const { location } = agentLocation || {};
|
||||
const { latitude: lat, longitude: lng } = location || {};
|
||||
if (!lat || !lng) {
|
||||
return null;
|
||||
}
|
||||
const refId = referenceId || lanNo || interactionId || '';
|
||||
const color = PinColorMapping[activityLevel] ?? 'red';
|
||||
return (
|
||||
<AgentMarker
|
||||
name={name}
|
||||
type={type}
|
||||
color={color}
|
||||
refId={refId}
|
||||
key={referenceId}
|
||||
lanNo={lanNo}
|
||||
key={agentLocation.referenceId}
|
||||
agentLocation={agentLocation}
|
||||
position={{ lat: Number(lat), lng: Number(lng) }}
|
||||
lastUpdatedAt={lastUpdatedAt}
|
||||
referenceId={referenceId}
|
||||
interactionId={interactionId}
|
||||
interactionStatus={interactionStatus}
|
||||
activeCustomPopup={activeCustomPopup}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</GoogleMapsContainer>
|
||||
),
|
||||
[mapLocations]
|
||||
);
|
||||
);
|
||||
}, [mapLocations]);
|
||||
|
||||
return (
|
||||
<div className={styles.mapContainer}>
|
||||
<div className={styles.mapContainer} tabIndex={-1}>
|
||||
{googleMapsContainer}
|
||||
{filteredAgentLocations?.length === 0 && !agentLocationsLoading ? (
|
||||
{!filteredAgentLocations?.length && !agentLocationsLoading ? (
|
||||
<MapPlaceholder showImage text={'Location data not found for selected filters !'} />
|
||||
) : null}
|
||||
{showBlurMap && !agentLocationsLoading ? (
|
||||
<MapPlaceholder showImage text={'Select a filter to view location of agents'} />
|
||||
) : null}
|
||||
{agentLocations?.length && (
|
||||
<div className={styles.mapToolbarContainer}>
|
||||
<AgentsAccordion />
|
||||
</div>
|
||||
)}
|
||||
{selectedAgent && <PinFilters />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
.pinFiltersContainer {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 292px; // width of Agents Panel
|
||||
background-color: var(--bg-primary);
|
||||
padding: 8px 8px 8px 12px;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import styles from './index.module.scss';
|
||||
import cx from 'classnames';
|
||||
import { createQueryParams, readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
@@ -25,11 +25,20 @@ const PinFilters = () => {
|
||||
const { [AGENT_LIVE_LOCATION]: queryParams = {} } = readQueryParams();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
// map of selected filters
|
||||
const locationTypeParams = useMemo(() => {
|
||||
const locationTypeArray = queryParams?.LOCATION_TYPE?.split(',') || [];
|
||||
const locationTypeParams: Record<string, boolean> = {};
|
||||
locationTypeArray.forEach((locationType: LOCATION_TYPE_FILTERS) => {
|
||||
locationTypeParams[locationType] = true;
|
||||
});
|
||||
return locationTypeParams;
|
||||
}, [queryParams?.LOCATION_TYPE]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!agentLocationHistory?.loading && !agentAllocations?.loading) {
|
||||
const { data, disabledFilters } = getMapLocations(
|
||||
queryParams?.LOCATION_TYPE,
|
||||
locationTypeParams,
|
||||
agentAllocations?.data,
|
||||
agentLocationHistory?.data
|
||||
);
|
||||
@@ -48,13 +57,42 @@ const PinFilters = () => {
|
||||
|
||||
const filterClickHandler = (value: string) => {
|
||||
if (disabledFilters[value]) return;
|
||||
|
||||
let updatedFilters = queryParams?.LOCATION_TYPE;
|
||||
|
||||
if (value === LOCATION_TYPE_FILTERS.ALL && updatedFilters === LOCATION_TYPE_FILTERS.ALL) {
|
||||
return;
|
||||
}
|
||||
|
||||
addClickstreamEvent(AgentTrackingEvents.LH_MAP_FILTER_CLICKED, {
|
||||
userId,
|
||||
agentId: queryParams?.AGENTID,
|
||||
filterValue: value
|
||||
});
|
||||
|
||||
if (locationTypeParams?.[value]) {
|
||||
// remove filter if already applied
|
||||
updatedFilters = updatedFilters?.replace(value, '');
|
||||
} else {
|
||||
if (value === LOCATION_TYPE_FILTERS.ALL) {
|
||||
// remove other filter if all filter is applied
|
||||
updatedFilters = LOCATION_TYPE_FILTERS.ALL;
|
||||
} else {
|
||||
// remove all filter if any other filter is applied
|
||||
updatedFilters = updatedFilters?.replace(LOCATION_TYPE_FILTERS.ALL, '');
|
||||
updatedFilters += `,${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
// remove leading and trailing commas
|
||||
updatedFilters = updatedFilters?.replace(/^,+|,+$/g, '');
|
||||
|
||||
const url = createQueryParams({
|
||||
[AGENT_LIVE_LOCATION]: { ...queryParams, LOCATION_TYPE: value, SELECTED_FEEDBACK_ID: '' }
|
||||
[AGENT_LIVE_LOCATION]: {
|
||||
...queryParams,
|
||||
LOCATION_TYPE: updatedFilters || LOCATION_TYPE_FILTERS.ALL,
|
||||
SELECTED_FEEDBACK_ID: ''
|
||||
}
|
||||
});
|
||||
navigate(url);
|
||||
};
|
||||
@@ -63,10 +101,10 @@ const PinFilters = () => {
|
||||
<div className={styles.pinFiltersContainer}>
|
||||
<div className={styles.filterBy}>Filter by: </div>
|
||||
{locationsPinFilters.map(filter => (
|
||||
<>
|
||||
<React.Fragment key={filter.value}>
|
||||
<div
|
||||
className={cx(styles.filterItem, {
|
||||
[styles.active]: queryParams?.LOCATION_TYPE === filter.value,
|
||||
[styles.active]: locationTypeParams?.[filter.value],
|
||||
[styles.disabled]: disabledFilters[filter.value]
|
||||
})}
|
||||
onClick={() => filterClickHandler(filter?.value)}
|
||||
@@ -75,7 +113,7 @@ const PinFilters = () => {
|
||||
<div>{filter.label}</div>
|
||||
</div>
|
||||
{filter.value === LOCATION_TYPE_FILTERS.ALL && <div className={styles.verticalLine} />}
|
||||
</>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,46 +1,83 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@navi/web-ui/lib/primitives';
|
||||
import styles from './tooltip.module.scss';
|
||||
import { forwardRef } from 'react';
|
||||
import { DateFormat, dateFormat, formatDate, isToday } from 'src/utils/DateHelper';
|
||||
import { LOCATION_TYPE_FILTERS } from '../../constants/LiveLocationTrackerInterfaces';
|
||||
import {
|
||||
IAgentLocation,
|
||||
LOCATION_TYPE_FILTERS
|
||||
} from '../../constants/LiveLocationTrackerInterfaces';
|
||||
import RedirectionIcon from 'src/assets/icons/RedirectionIcon';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import APP_ROUTES from 'src/layout/Routes';
|
||||
import { TAB_KEYS } from 'src/pages/CaseDetails/constants';
|
||||
import { RootState } from 'src/store';
|
||||
import { interpolatePathParams } from 'src/utils/interpolate';
|
||||
import { CloseIcon } from '@navi/web-ui/lib/icons';
|
||||
import { getMaxInputDate } from '../../utils';
|
||||
import { createQueryParams, readQueryParams } from '@cp/src/utils/QueryParamsHelper';
|
||||
import { AGENT_LIVE_LOCATION } from '../../constants/LiveLocatonTrackerConstants';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { setSelectedAgent } from '../../reducers/LiveLocationTrackerSlice';
|
||||
import {
|
||||
getAgentAllocations,
|
||||
getAgentLocationHistory
|
||||
} from '../../actions/LiveLocationTrackerActions';
|
||||
|
||||
type ToolTipProps = {
|
||||
name: string;
|
||||
lastUpdateActivity: string;
|
||||
style?: any;
|
||||
type?: LOCATION_TYPE_FILTERS;
|
||||
lanNo?: string;
|
||||
interactionStatus?: string;
|
||||
agentLocation: IAgentLocation;
|
||||
style?: React.CSSProperties;
|
||||
closeTooltip: () => void;
|
||||
};
|
||||
|
||||
function Tooltip(props: ToolTipProps, ref: any) {
|
||||
const Tooltip = forwardRef((props: ToolTipProps, ref: any) => {
|
||||
const { agentLocation, style, closeTooltip }: ToolTipProps = props;
|
||||
const {
|
||||
name,
|
||||
lastUpdateActivity,
|
||||
style,
|
||||
lastUpdatedAt,
|
||||
type,
|
||||
lanNo,
|
||||
interactionStatus,
|
||||
closeTooltip
|
||||
}: ToolTipProps = props;
|
||||
|
||||
const date = isToday(lastUpdateActivity)
|
||||
? 'Today'
|
||||
: formatDate(lastUpdateActivity, DateFormat.DD_MMM);
|
||||
const time = dateFormat(new Date(lastUpdateActivity), DateFormat.HH_mm_ampm) || '---';
|
||||
addressText,
|
||||
phoneNumber,
|
||||
referenceId
|
||||
} = agentLocation;
|
||||
const date = isToday(lastUpdatedAt) ? 'Today' : formatDate(lastUpdatedAt, DateFormat.DD_MMM);
|
||||
const time = dateFormat(new Date(lastUpdatedAt), DateFormat.HH_mm_ampm) || '---';
|
||||
const { liveLocationTracker, userId } = useSelector((state: RootState) => ({
|
||||
liveLocationTracker: state.liveLocationTracker,
|
||||
userId: state?.common?.userData?.referenceId
|
||||
}));
|
||||
const { selectedAgent } = liveLocationTracker || {};
|
||||
|
||||
const {
|
||||
agentAllocations: { data: agentAllocations }
|
||||
} = useSelector((state: RootState) => state.liveLocationTracker);
|
||||
|
||||
const { [AGENT_LIVE_LOCATION]: queryParams = {} } = readQueryParams();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const selectAgentHandler = () => {
|
||||
if (!selectedAgent) {
|
||||
const maxInputDate = getMaxInputDate('YYYY-MM-DD');
|
||||
const payload = { date: maxInputDate, referenceId };
|
||||
|
||||
const url = createQueryParams({
|
||||
[AGENT_LIVE_LOCATION]: {
|
||||
...queryParams,
|
||||
AGENTID: referenceId,
|
||||
LOCATION_TYPE: LOCATION_TYPE_FILTERS.ALL,
|
||||
DATE: maxInputDate
|
||||
}
|
||||
});
|
||||
navigate(url);
|
||||
dispatch(setSelectedAgent(agentLocation));
|
||||
dispatch(getAgentLocationHistory(payload));
|
||||
dispatch(getAgentAllocations(payload));
|
||||
}
|
||||
};
|
||||
|
||||
const viewCaseHandler = () => {
|
||||
if (lanNo) {
|
||||
const data = agentAllocations?.[lanNo];
|
||||
@@ -68,9 +105,12 @@ function Tooltip(props: ToolTipProps, ref: any) {
|
||||
<Typography variant="h4" className={styles.lanNo}>
|
||||
{lanNo}
|
||||
</Typography>
|
||||
<Typography variant="h4" className={styles.addressText}>
|
||||
{addressText}
|
||||
</Typography>
|
||||
<div className={styles.viewCaseCtaWrapper} onClick={viewCaseHandler}>
|
||||
<div className={styles.viewCaseCta}>View Case</div>
|
||||
<RedirectionIcon width={12} height={12} />
|
||||
<RedirectionIcon width={14} height={14} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -82,8 +122,21 @@ function Tooltip(props: ToolTipProps, ref: any) {
|
||||
<CloseIcon />
|
||||
</div>
|
||||
{name && (
|
||||
<Typography variant="h4" className={styles.nameText}>
|
||||
{name}
|
||||
<div className="flex items-center gap-1 justify-center pr-4">
|
||||
<Typography variant="h4" className={styles.nameText}>
|
||||
{name}
|
||||
</Typography>
|
||||
<RedirectionIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="cursor-pointer"
|
||||
onClick={selectAgentHandler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{phoneNumber && (
|
||||
<Typography variant="h4" className={styles.phoneNumber}>
|
||||
{phoneNumber}
|
||||
</Typography>
|
||||
)}
|
||||
{interactionStatus && (
|
||||
@@ -98,6 +151,6 @@ function Tooltip(props: ToolTipProps, ref: any) {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default forwardRef(Tooltip);
|
||||
export default Tooltip;
|
||||
|
||||
@@ -4,17 +4,15 @@
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
.nameText {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.2px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 16px;
|
||||
text-align: center;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.lastUpdatedAt {
|
||||
@@ -24,7 +22,8 @@
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.13px;
|
||||
color: var(--navi-color-gray-c2);
|
||||
margin-right: 16px;
|
||||
padding-right: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.casesNameText {
|
||||
@@ -33,11 +32,11 @@
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.12px;
|
||||
margin-right: 16px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.lanNo {
|
||||
color: var(--navi-color-gray-c3);
|
||||
color: var(--navi-color-gray-c2);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
@@ -50,13 +49,33 @@
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.12px;
|
||||
margin-right: 16px;
|
||||
text-align: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.phoneNumber {
|
||||
color: var(--navi-color-gray-c2);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.addressText {
|
||||
color: var(--navi-color-gray-c3);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
margin-top: 4px;
|
||||
letter-spacing: -0.12px;
|
||||
}
|
||||
|
||||
.viewCaseCtaWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 2px;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -71,7 +90,7 @@
|
||||
|
||||
.closeIcon {
|
||||
position: absolute;
|
||||
padding: 8px;
|
||||
padding: 8px 8px 4px 4px;
|
||||
top: 2px;
|
||||
right: 0px;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
getAgentsLocations,
|
||||
getFilterData,
|
||||
getFilterTypes
|
||||
} from '../../actions/LiveLocationTrackerActions';
|
||||
import { getFilterData } from '../../actions/LiveLocationTrackerActions';
|
||||
import { RootState } from 'src/store';
|
||||
import { FilterTypes } from '../../constants/LiveLocationTrackerInterfaces';
|
||||
import { _map } from 'src/utils/commonUtils';
|
||||
import styles from './TopBar.module.scss';
|
||||
import { FilterMap, AGENT_LIVE_LOCATION } from '../../constants/LiveLocatonTrackerConstants';
|
||||
import { createQueryParams, readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
setAgentLocations,
|
||||
setFilterData,
|
||||
@@ -19,73 +15,22 @@ import {
|
||||
setSelectedAgent
|
||||
} from '../../reducers/LiveLocationTrackerSlice';
|
||||
import SingleAutocompleteDropdown from 'src/components/AutocompleteDropdown/SingleAutocompleteDropdown';
|
||||
import { INHOUSE_AGENCY_CODE } from 'src/components/constant';
|
||||
import { addClickstreamEvent } from 'src/service/clickStreamEventService';
|
||||
import { AgentTrackingEvents } from 'src/service/clickStream.constant';
|
||||
|
||||
const Filters = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { filterTypes, filterData, agentLocations } = useSelector(
|
||||
(state: RootState) => state.liveLocationTracker
|
||||
);
|
||||
const { filterTypes, filterData } = useSelector((state: RootState) => state.liveLocationTracker);
|
||||
const userId = useSelector((state: RootState) => state.common.userData?.referenceId);
|
||||
const [searchParams] = useSearchParams();
|
||||
const { filters } = filterTypes;
|
||||
const { [AGENT_LIVE_LOCATION]: queryParams = {} } = readQueryParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const pinsPayload = useMemo(() => {
|
||||
const selectedAgency =
|
||||
queryParams[FilterTypes.AGENCY] || filterData[FilterTypes.AGENCY]?.data[0]?.value;
|
||||
const selectedTeamLead = queryParams[FilterTypes.TEAM_LEAD];
|
||||
return {
|
||||
agencyCodes: [selectedAgency],
|
||||
teamLeadReferenceIds: selectedTeamLead ? [selectedTeamLead] : []
|
||||
};
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
// Get agency filter data
|
||||
dispatch(getFilterData());
|
||||
}, [filterTypes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!filters?.length) {
|
||||
dispatch(getFilterTypes());
|
||||
return;
|
||||
}
|
||||
|
||||
const applyFilterRecursively = (filterType: FilterTypes) => {
|
||||
const selectedFilter = queryParams[filterType];
|
||||
const { nextFilter } = FilterMap[filterType];
|
||||
|
||||
if (!selectedFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If next filter is not selected or not in the list of filters, we need to fetch the pins data
|
||||
if (!nextFilter || !filters.includes(nextFilter)) {
|
||||
dispatch(getAgentsLocations(pinsPayload));
|
||||
return;
|
||||
}
|
||||
|
||||
if (filterType === FilterTypes.AGENCY) {
|
||||
dispatch(getAgentsLocations(pinsPayload));
|
||||
}
|
||||
|
||||
const payload = {
|
||||
type: nextFilter,
|
||||
agencyCodes: [selectedFilter]
|
||||
};
|
||||
dispatch(getFilterData(payload));
|
||||
|
||||
// recursively call the function to apply the next filter
|
||||
applyFilterRecursively(nextFilter);
|
||||
};
|
||||
|
||||
applyFilterRecursively(FilterTypes.AGENCY);
|
||||
}, [queryParams?.AGENCY, queryParams?.TEAM_LEAD, filters]);
|
||||
|
||||
const handleFilterChange = (value: string, filterType: FilterTypes) => {
|
||||
dispatch(setMapLocations({ data: [] }));
|
||||
const nextFilter = FilterMap[filterType]?.nextFilter;
|
||||
@@ -173,6 +118,7 @@ const Filters = () => {
|
||||
containerClasses={styles.dropdown}
|
||||
disabled={disabled}
|
||||
sortOptions
|
||||
hideCloseOption={filterType === FilterTypes.AGENCY}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -67,12 +67,11 @@
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
|
||||
.dropdown {
|
||||
background-color: var(--bg-primary);
|
||||
box-shadow: var(--box-shadow-3) !important;
|
||||
width: 250px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
@@ -15,17 +15,12 @@ import { createQueryParams, readQueryParams } from '../../../../utils/QueryParam
|
||||
import { getAgentsLocations } from '../../actions/LiveLocationTrackerActions';
|
||||
import { RootState } from '../../../../store';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import isToday from 'dayjs/plugin/isToday';
|
||||
import usePolling from '../../../../hooks/usePolling';
|
||||
import { getAgentDetails } from '../../utils';
|
||||
import { resetMapData, setSelectedAgent } from '../../reducers/LiveLocationTrackerSlice';
|
||||
import { INHOUSE_AGENCY_CODE } from 'src/components/constant';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(isToday);
|
||||
|
||||
function TopBar() {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface IAgentAllocation {
|
||||
customerName: string;
|
||||
location: ILocation;
|
||||
feedbacks: IFeedback[];
|
||||
addressText: string;
|
||||
}
|
||||
|
||||
export interface ILocation {
|
||||
@@ -54,6 +55,7 @@ export interface IAgentLocationHistory {
|
||||
referenceId: string;
|
||||
location: ILocation;
|
||||
epochTimestamp: number;
|
||||
zIndex?: number;
|
||||
}
|
||||
|
||||
export interface IAgentLocation {
|
||||
@@ -68,6 +70,8 @@ export interface IAgentLocation {
|
||||
lanNo?: string;
|
||||
interactionId?: string;
|
||||
interactionStatus?: string;
|
||||
addressText?: string;
|
||||
totalAllocatedCases: number;
|
||||
}
|
||||
|
||||
interface IFilterTypes {
|
||||
@@ -91,6 +95,8 @@ export interface MapPinLocation {
|
||||
interactionId?: string;
|
||||
interactionStatus?: string;
|
||||
referenceId?: string;
|
||||
addressText?: string;
|
||||
zIndex?: number;
|
||||
}
|
||||
|
||||
export enum FilterTypes {
|
||||
@@ -142,20 +148,12 @@ export type ColorMap = Record<
|
||||
>;
|
||||
|
||||
export interface AgentMarkerProps {
|
||||
color: string;
|
||||
name: string;
|
||||
lastUpdatedAt: string;
|
||||
position: {
|
||||
lat: number;
|
||||
lng: number;
|
||||
};
|
||||
referenceId: string;
|
||||
type?: LOCATION_TYPE_FILTERS;
|
||||
lanNo?: string;
|
||||
interactionId?: string;
|
||||
interactionStatus?: string;
|
||||
activeCustomPopup: React.MutableRefObject<ICustomPopup | null>;
|
||||
refId: string;
|
||||
agentLocation: IAgentLocation;
|
||||
}
|
||||
|
||||
export enum LOCATION_TYPE_FILTERS {
|
||||
|
||||
@@ -2,7 +2,31 @@
|
||||
position: relative;
|
||||
|
||||
.overlay {
|
||||
z-index: 10;
|
||||
z-index: var(--z-index-maps-loader-overlay);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.agentFilters {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
left: 298px;
|
||||
z-index: var(--z-index-map-options);
|
||||
}
|
||||
|
||||
.agentAccordion {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
left: 24px;
|
||||
z-index: var(--z-index-map-options);
|
||||
}
|
||||
|
||||
.pinFilters {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
left: 298px;
|
||||
z-index: var(--z-index-map-options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import TopBar from './components/TopBar';
|
||||
import MapView from './components/MapView';
|
||||
import Footer from './components/Footer/Footer';
|
||||
import styles from './index.module.scss';
|
||||
import Loader from 'src/components/Loader/Loader';
|
||||
import { RootState } from 'src/store';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import cx from 'classnames';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { addClickstreamEvent } from 'src/service/clickStreamEventService';
|
||||
import { AgentTrackingEvents } from 'src/service/clickStream.constant';
|
||||
import MapPlaceholder from './components/MapPlaceholder';
|
||||
import { readQueryParams } from 'src/utils/QueryParamsHelper';
|
||||
import { AGENT_LIVE_LOCATION } from './constants/LiveLocatonTrackerConstants';
|
||||
import { AGENT_LIVE_LOCATION, FilterMap } from './constants/LiveLocatonTrackerConstants';
|
||||
import Filters from './components/TopBar/Filters';
|
||||
import AgentsAccordion from './components/AgentsAccordion';
|
||||
import PinFilters from './components/PinFilters';
|
||||
import {
|
||||
getAgentsLocations,
|
||||
getFilterData,
|
||||
getFilterTypes
|
||||
} from './actions/LiveLocationTrackerActions';
|
||||
import { FilterTypes } from './constants/LiveLocationTrackerInterfaces';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { resetMapData } from './reducers/LiveLocationTrackerSlice';
|
||||
import LocationAgencySearch from './components/LocationAgencySearch';
|
||||
|
||||
function LiveLocationTracker() {
|
||||
const {
|
||||
@@ -20,20 +31,42 @@ function LiveLocationTracker() {
|
||||
selectedAgent,
|
||||
agentLocationHistory: { loading: isAgentLocationHistoryloading },
|
||||
agentAllocations: { loading: isAgentAllocationsloading },
|
||||
agentLocationsLoading
|
||||
agentLocations,
|
||||
agentLocationsLoading,
|
||||
filterTypes,
|
||||
filterData
|
||||
} = useSelector((state: RootState) => ({
|
||||
loadingFilters: state.liveLocationTracker.loadingFilters,
|
||||
userId: state?.common?.userData?.referenceId,
|
||||
selectedAgent: state.liveLocationTracker.selectedAgent,
|
||||
agentLocationHistory: state.liveLocationTracker.agentLocationHistory,
|
||||
agentAllocations: state.liveLocationTracker.agentAllocations,
|
||||
agentLocationsLoading: state.liveLocationTracker.agentLocationsLoading
|
||||
agentLocationsLoading: state.liveLocationTracker.agentLocationsLoading,
|
||||
agentLocations: state.liveLocationTracker.agentLocations,
|
||||
filterTypes: state.liveLocationTracker.filterTypes,
|
||||
filterData: state.liveLocationTracker.filterData
|
||||
}));
|
||||
|
||||
const { filters } = filterTypes || {};
|
||||
const dispatch = useDispatch();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { [AGENT_LIVE_LOCATION]: queryParams = {} } = readQueryParams();
|
||||
|
||||
const pinsPayload = useMemo(() => {
|
||||
const selectedAgency =
|
||||
queryParams[FilterTypes.AGENCY] || filterData[FilterTypes.AGENCY]?.data[0]?.value;
|
||||
const selectedTeamLead = queryParams[FilterTypes.TEAM_LEAD];
|
||||
return {
|
||||
agencyCodes: [selectedAgency],
|
||||
teamLeadReferenceIds: selectedTeamLead ? [selectedTeamLead] : []
|
||||
};
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
addClickstreamEvent(AgentTrackingEvents.LH_AGENT_TRACKING_PAGE_LOAD, { userId });
|
||||
return () => {
|
||||
// reset map data on unmount
|
||||
dispatch(resetMapData());
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -41,18 +74,78 @@ function LiveLocationTracker() {
|
||||
localStorage.setItem('live-location-tracker-params', window.location.search);
|
||||
}, [queryParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!filters?.length) {
|
||||
dispatch(getFilterTypes());
|
||||
return;
|
||||
}
|
||||
|
||||
const applyFilterRecursively = (filterType: FilterTypes) => {
|
||||
const selectedFilter = queryParams[filterType];
|
||||
const { nextFilter } = FilterMap[filterType];
|
||||
|
||||
if (!selectedFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If next filter is not selected or not in the list of filters, we need to fetch the pins data
|
||||
if (!nextFilter || !filters.includes(nextFilter)) {
|
||||
dispatch(getAgentsLocations(pinsPayload));
|
||||
return;
|
||||
}
|
||||
|
||||
if (filterType === FilterTypes.AGENCY) {
|
||||
dispatch(getAgentsLocations(pinsPayload));
|
||||
}
|
||||
|
||||
const payload = {
|
||||
type: nextFilter,
|
||||
agencyCodes: [selectedFilter]
|
||||
};
|
||||
dispatch(getFilterData(payload));
|
||||
|
||||
// recursively call the function to apply the next filter
|
||||
applyFilterRecursively(nextFilter);
|
||||
};
|
||||
|
||||
applyFilterRecursively(FilterTypes.AGENCY);
|
||||
}, [queryParams?.AGENCY, queryParams?.TEAM_LEAD, filters]);
|
||||
|
||||
const mapLoading =
|
||||
isAgentAllocationsloading || isAgentLocationHistoryloading || agentLocationsLoading;
|
||||
|
||||
if (!queryParams?.AGENCY)
|
||||
return (
|
||||
<div className={cx(styles.container, 'h-[calc(100vh-100px)]')}>
|
||||
<LocationAgencySearch />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<TopBar />
|
||||
{!selectedAgent && !mapLoading ? (
|
||||
<div className={styles.agentFilters}>
|
||||
<Filters />
|
||||
</div>
|
||||
) : null}
|
||||
{!mapLoading ? (
|
||||
<div className={styles.agentAccordion}>
|
||||
<AgentsAccordion />
|
||||
</div>
|
||||
) : null}
|
||||
{selectedAgent && !mapLoading ? (
|
||||
<div className={styles.pinFilters}>
|
||||
<PinFilters />
|
||||
</div>
|
||||
) : null}
|
||||
<MapView />
|
||||
<Footer />
|
||||
<Loader show={loadingFilters} className={cx('overlay', styles.overlay)} animate={false} />
|
||||
{(isAgentAllocationsloading || isAgentLocationHistoryloading || agentLocationsLoading) && (
|
||||
{mapLoading && (
|
||||
<MapPlaceholder
|
||||
text={
|
||||
agentLocationsLoading
|
||||
? 'Fetching agents locations'
|
||||
: `Fetching all of ${selectedAgent?.name}'s locations`
|
||||
selectedAgent?.name
|
||||
? `Fetching all of ${selectedAgent?.name}'s locations`
|
||||
: 'Fetching agents locations'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -112,6 +112,7 @@ const liveLocationTrackerSlice = createSlice({
|
||||
...state.agentAllocations,
|
||||
...action.payload
|
||||
};
|
||||
state.lastUpdatedAt = new Date(getServerTime()).toString();
|
||||
},
|
||||
setSelectedFeedback: (state, action) => {
|
||||
state.selectedFeedback = action.payload;
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
AgentDetailsPayload,
|
||||
IAgentAllocation,
|
||||
IAgentLocationHistory,
|
||||
IFeedback,
|
||||
ISelectedFeedback,
|
||||
LOCATION_TYPE_FILTERS,
|
||||
MapPinLocation
|
||||
@@ -17,37 +16,53 @@ import styles from './components/AgentMarker/agentMarker.module.scss';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { LocalStorage } from '@cp/src/utils/StorageUtils';
|
||||
|
||||
enum MapPinsZIndices {
|
||||
CASES = 1,
|
||||
AGENT = 2,
|
||||
GENUINE_FEEDBACK = 3,
|
||||
SUSPICIOUS_FEEDBACK = 4
|
||||
}
|
||||
|
||||
export const getAllocationPins = (
|
||||
data: Record<string, IAgentAllocation>,
|
||||
agentLocationHistory: IAgentLocationHistory[]
|
||||
) => {
|
||||
const casesLocation: MapPinLocation[] = [];
|
||||
const feedbacksLocation: IFeedback[] = [];
|
||||
const feedbacksLocation = [];
|
||||
const suspiciousFeedback: MapPinLocation[] = [];
|
||||
const genuineFeedback: MapPinLocation[] = [];
|
||||
const agentLocations: IAgentLocationHistory[] = [];
|
||||
for (const lanNo in data) {
|
||||
if (data?.[lanNo]?.location) {
|
||||
casesLocation.push({
|
||||
type: LOCATION_TYPE_FILTERS.CASES,
|
||||
location: data?.[lanNo]?.location,
|
||||
lanNo,
|
||||
name: data?.[lanNo]?.customerName
|
||||
name: data?.[lanNo]?.customerName,
|
||||
addressText: data?.[lanNo]?.addressText,
|
||||
zIndex: MapPinsZIndices.CASES
|
||||
});
|
||||
}
|
||||
|
||||
if (data?.[lanNo]?.feedbacks) {
|
||||
feedbacksLocation.push(...data[lanNo].feedbacks);
|
||||
const feedbacks = data[lanNo].feedbacks.map(feedback => ({
|
||||
...feedback,
|
||||
lanNo
|
||||
}));
|
||||
feedbacksLocation.push(...feedbacks);
|
||||
}
|
||||
}
|
||||
|
||||
feedbacksLocation?.forEach((feedback: IFeedback) => {
|
||||
feedbacksLocation?.forEach(feedback => {
|
||||
if (feedback.suspicious) {
|
||||
suspiciousFeedback?.push({
|
||||
type: LOCATION_TYPE_FILTERS.SUSPICIOUS_FEEDBACK,
|
||||
location: feedback?.location,
|
||||
interactionId: feedback?.interactionId,
|
||||
lastUpdatedAt: formatEpochTime(feedback?.epochTimestamp),
|
||||
interactionStatus: feedback?.interactionStatus
|
||||
interactionStatus: feedback?.interactionStatus,
|
||||
lanNo: feedback?.lanNo,
|
||||
zIndex: MapPinsZIndices.SUSPICIOUS_FEEDBACK
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -56,13 +71,24 @@ export const getAllocationPins = (
|
||||
location: feedback?.location,
|
||||
interactionId: feedback?.interactionId,
|
||||
lastUpdatedAt: formatEpochTime(feedback?.epochTimestamp),
|
||||
interactionStatus: feedback?.interactionStatus
|
||||
interactionStatus: feedback?.interactionStatus,
|
||||
lanNo: feedback?.lanNo,
|
||||
zIndex: MapPinsZIndices.GENUINE_FEEDBACK
|
||||
});
|
||||
});
|
||||
|
||||
agentLocationHistory?.forEach((locationData: IAgentLocationHistory) => {
|
||||
agentLocations.push({
|
||||
...locationData,
|
||||
zIndex: MapPinsZIndices.AGENT
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
casesLocation,
|
||||
suspiciousFeedback,
|
||||
genuineFeedback,
|
||||
agentLocations,
|
||||
disabledFilters: {
|
||||
[LOCATION_TYPE_FILTERS.AGENT]: !agentLocationHistory?.length,
|
||||
[LOCATION_TYPE_FILTERS.CASES]: !casesLocation?.length,
|
||||
@@ -86,38 +112,39 @@ export const getAgentLocationFormattedData = (data: IAgentLocationHistory[]) =>
|
||||
};
|
||||
|
||||
export const getMapLocations = (
|
||||
locationFilter: string,
|
||||
locationFilters: Record<string, boolean>,
|
||||
agentAllocations: Record<string, IAgentAllocation> | null,
|
||||
agentLocationHistory: IAgentLocationHistory[]
|
||||
) => {
|
||||
const { casesLocation, suspiciousFeedback, genuineFeedback, disabledFilters } = getAllocationPins(
|
||||
agentAllocations ?? {},
|
||||
agentLocationHistory
|
||||
);
|
||||
const { casesLocation, suspiciousFeedback, genuineFeedback, agentLocations, disabledFilters } =
|
||||
getAllocationPins(agentAllocations ?? {}, agentLocationHistory);
|
||||
|
||||
switch (locationFilter) {
|
||||
case LOCATION_TYPE_FILTERS.AGENT:
|
||||
return { data: agentLocationHistory, disabledFilters };
|
||||
// Initialize an empty array to hold the combined data for all selected filters
|
||||
let combinedData: Array<MapPinLocation | IAgentLocationHistory> = [];
|
||||
|
||||
case LOCATION_TYPE_FILTERS.GENUINE_FEEDBACK:
|
||||
return { data: genuineFeedback, disabledFilters };
|
||||
|
||||
case LOCATION_TYPE_FILTERS.SUSPICIOUS_FEEDBACK:
|
||||
return { data: suspiciousFeedback, disabledFilters };
|
||||
|
||||
case LOCATION_TYPE_FILTERS.CASES:
|
||||
return { data: casesLocation, disabledFilters };
|
||||
default:
|
||||
return {
|
||||
data: [
|
||||
...casesLocation,
|
||||
...agentLocationHistory,
|
||||
...genuineFeedback,
|
||||
...suspiciousFeedback
|
||||
],
|
||||
disabledFilters
|
||||
};
|
||||
// Check if the array includes specific filters and add the corresponding data
|
||||
if (locationFilters?.[LOCATION_TYPE_FILTERS.CASES]) {
|
||||
combinedData = [...combinedData, ...casesLocation];
|
||||
}
|
||||
if (locationFilters?.[LOCATION_TYPE_FILTERS.AGENT]) {
|
||||
combinedData = [...combinedData, ...agentLocations];
|
||||
}
|
||||
if (locationFilters?.[LOCATION_TYPE_FILTERS.GENUINE_FEEDBACK]) {
|
||||
combinedData = [...combinedData, ...genuineFeedback];
|
||||
}
|
||||
if (locationFilters?.[LOCATION_TYPE_FILTERS.SUSPICIOUS_FEEDBACK]) {
|
||||
combinedData = [...combinedData, ...suspiciousFeedback];
|
||||
}
|
||||
|
||||
// If no specific filters are selected or the ALL filter is selected, combine all data
|
||||
if (locationFilters?.[LOCATION_TYPE_FILTERS.ALL]) {
|
||||
combinedData = [...casesLocation, ...agentLocations, ...genuineFeedback, ...suspiciousFeedback];
|
||||
}
|
||||
|
||||
return {
|
||||
data: combinedData,
|
||||
disabledFilters
|
||||
};
|
||||
};
|
||||
|
||||
export const getMaxInputDate = (format: string) => {
|
||||
|
||||
@@ -68,7 +68,7 @@ export enum Roles {
|
||||
ROLE_TELE_ALLOCATIONS_ACCESS = 'ROLE_TELE_ALLOCATIONS_ACCESS',
|
||||
ROLE_COLLECTION_HEAD = 'ROLE_COLLECTION_HEAD',
|
||||
NAVI_INHOUSE_FIELD_CLUSTER_MANAGER = 'NAVI_INHOUSE_FIELD_CLUSTER_MANAGER',
|
||||
NAVI_INHOUSE_FIELD_ZONAL_MANAGER = 'NAVI_INHOUSE_FIELD_ZONAL_MANAGER',
|
||||
NAVI_INHOUSE_FIELD_ZONAL_MANAGER = 'ROLE_NAVI_INHOUSE_FIELD_ZONAL_MANAGER',
|
||||
ROLE_NAVI_FIELD_AGENCY_TEAM_LEAD = 'ROLE_NAVI_FIELD_AGENCY_TEAM_LEAD',
|
||||
ROLE_HUMAN_REMINDER_TEAM_LEAD = 'ROLE_HUMAN_REMINDER_TEAM_LEAD',
|
||||
ROLE_HUMAN_REMINDER_CHAT_AGENT = 'ROLE_HUMAN_REMINDER_CHAT_AGENT',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Tabs, { TabItem } from '@navi/web-ui/lib/components/Tabs';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { TABS as SENSEI_TABS, TAB_KEYS } from './Tabs';
|
||||
|
||||
import { RootState } from '@cp/src/store';
|
||||
@@ -11,19 +11,49 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import APP_ROUTES from 'src/layout/Routes';
|
||||
import { DateFormat, dateFormat } from 'src/utils/DateHelper';
|
||||
import { fetchCommitments, fetchPTPDetails } from './actions';
|
||||
import {
|
||||
fetchCommitments,
|
||||
fetchPTPDetails,
|
||||
getOverallMetricInput,
|
||||
getOverallMetricOutput
|
||||
} from './actions';
|
||||
import styles from './index.module.scss';
|
||||
import { getAgentDetails, getLiveLocationParams } from '../LiveLocationTracker/utils';
|
||||
import { AGENT_LIVE_LOCATION } from '../LiveLocationTracker/constants/LiveLocatonTrackerConstants';
|
||||
import { readQueryParams } from '@cp/src/utils/QueryParamsHelper';
|
||||
import { FilterTypes } from '../LiveLocationTracker/constants/LiveLocationTrackerInterfaces';
|
||||
import { getAgentsLocations } from '../LiveLocationTracker/actions/LiveLocationTrackerActions';
|
||||
import LiveLocationTracker from '../LiveLocationTracker';
|
||||
import { LocalStorage } from '@cp/src/utils/StorageUtils';
|
||||
|
||||
const Index = () => {
|
||||
const { tabId = '' } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { lastUpdatedAt } = useSelector((state: RootState) => ({
|
||||
lastUpdatedAt: state?.sensai?.dailyPlanning?.commitmentsTable?.lastUpdatedAt
|
||||
const { [AGENT_LIVE_LOCATION]: queryParams = {} } = readQueryParams();
|
||||
const {
|
||||
lastUpdatedAtDailyPlanning,
|
||||
lastUpdatedAtOverallMetric,
|
||||
lastUpdatedAtLiveLocation,
|
||||
featureFlags
|
||||
} = useSelector((state: RootState) => ({
|
||||
lastUpdatedAtDailyPlanning: state?.sensai?.dailyPlanning?.commitmentsTable?.lastUpdatedAt,
|
||||
lastUpdatedAtOverallMetric: state?.sensai?.overallMetrics?.inputTable?.lastUpdatedAt,
|
||||
lastUpdatedAtLiveLocation: state?.liveLocationTracker?.lastUpdatedAt,
|
||||
featureFlags: state?.common?.featureFlags
|
||||
}));
|
||||
const handleTabChange = (tabId: TabItemKey) => {
|
||||
// to add this function once we have other tabs
|
||||
navigate(interpolatePathParams(APP_ROUTES.SENSEI.path, { tabId }));
|
||||
const { liveLocationTrackerFeatureFlag } = featureFlags || {};
|
||||
|
||||
const paramsMap: Record<string, string> = LocalStorage.getItem('paramsMap') || {};
|
||||
|
||||
const handleTabChange = (updateTabId: TabItemKey) => {
|
||||
if (tabId !== updateTabId) {
|
||||
const updatedUrl = interpolatePathParams(APP_ROUTES.SENSEI.path, {
|
||||
tabId: String(updateTabId)
|
||||
});
|
||||
const currentParams = paramsMap[updatedUrl] || '';
|
||||
navigate(updatedUrl + currentParams);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -32,41 +62,98 @@ const Index = () => {
|
||||
}
|
||||
}, [tabId]);
|
||||
|
||||
const handleRefresh = () => {
|
||||
const { lastUpdatedAt, handleRefresh } = useMemo(() => {
|
||||
let handleRefresh = null;
|
||||
let lastUpdatedAt = null;
|
||||
|
||||
switch (tabId) {
|
||||
case TAB_KEYS.DAILY_PLANNING:
|
||||
dispatch(fetchCommitments());
|
||||
dispatch(fetchPTPDetails());
|
||||
case TAB_KEYS.DAILY_PLANNING: {
|
||||
handleRefresh = () => {
|
||||
dispatch(fetchCommitments());
|
||||
dispatch(fetchPTPDetails());
|
||||
};
|
||||
lastUpdatedAt = lastUpdatedAtDailyPlanning;
|
||||
break;
|
||||
}
|
||||
case TAB_KEYS.OVERALL_METRIC: {
|
||||
handleRefresh = () => {
|
||||
dispatch(getOverallMetricOutput());
|
||||
dispatch(getOverallMetricInput());
|
||||
};
|
||||
lastUpdatedAt = lastUpdatedAtOverallMetric;
|
||||
break;
|
||||
}
|
||||
case TAB_KEYS.LIVE_LOCATION: {
|
||||
if (!queryParams?.AGENCY) {
|
||||
return { lastUpdatedAt: null, handleRefresh: null };
|
||||
}
|
||||
handleRefresh = () => {
|
||||
if (queryParams?.AGENTID) {
|
||||
dispatch(
|
||||
getAgentDetails(
|
||||
{ date: queryParams?.DATE, referenceId: queryParams?.AGENTID },
|
||||
navigate
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const selectedTeamLead = queryParams[FilterTypes.TEAM_LEAD];
|
||||
const selectedAgency = queryParams[FilterTypes.AGENCY];
|
||||
const payload = {
|
||||
agencyCodes: [selectedAgency],
|
||||
teamLeadReferenceIds: selectedTeamLead ? [selectedTeamLead] : []
|
||||
};
|
||||
dispatch(getAgentsLocations(payload, false));
|
||||
}
|
||||
};
|
||||
lastUpdatedAt = lastUpdatedAtLiveLocation;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return { lastUpdatedAt, handleRefresh };
|
||||
}, [tabId, queryParams?.SELECTED_FEEDBACK_ID]);
|
||||
|
||||
const senseiTabs = useMemo(() => {
|
||||
const tabs = [...SENSEI_TABS];
|
||||
if (liveLocationTrackerFeatureFlag) {
|
||||
tabs.push({
|
||||
key: TAB_KEYS.LIVE_LOCATION,
|
||||
value: 'Live location tracker',
|
||||
component: <LiveLocationTracker />
|
||||
});
|
||||
}
|
||||
return tabs;
|
||||
}, [featureFlags]);
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.header}>
|
||||
<Typography variant="h3">FCM Dashboard</Typography>
|
||||
<div className={styles.lastRefreshedContainer}>
|
||||
<Typography variant="p5">
|
||||
{dateFormat(new Date(lastUpdatedAt), DateFormat.LONG_DATE_FORMAT_WITH_TIME)}
|
||||
</Typography>
|
||||
<div className={styles.refresh} onClick={handleRefresh}>
|
||||
<Typography as={'span'} variant="h4" className={styles.refreshWrapper}>
|
||||
<RefreshIcon className={styles.refreshIcon} color="var(--navi-color-blue-base)" />
|
||||
Refresh
|
||||
{lastUpdatedAt ? (
|
||||
<Typography variant="p5">
|
||||
{dateFormat(new Date(lastUpdatedAt), DateFormat.LONG_DATE_FORMAT_WITH_TIME)}
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
{handleRefresh ? (
|
||||
<div className={styles.refresh} onClick={handleRefresh}>
|
||||
<Typography as={'span'} variant="h4" className={styles.refreshWrapper}>
|
||||
<RefreshIcon className={styles.refreshIcon} color="var(--navi-color-blue-base)" />
|
||||
Refresh
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
tabsClassName={styles.tabContainerClass}
|
||||
selectedTabKey={tabId}
|
||||
onTabChange={handleTabChange}
|
||||
contentClassName={styles.tabContent}
|
||||
>
|
||||
{SENSEI_TABS.map(tab => {
|
||||
{senseiTabs.map(tab => {
|
||||
return (
|
||||
<TabItem key={tab.key} label={tab.value}>
|
||||
{tab.component}
|
||||
|
||||
@@ -530,6 +530,8 @@ const excludedRedirectionEndPointsList = [
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.field_governance,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_daily_planning,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.overall_metric,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.live_location,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_live_location,
|
||||
APP_ROUTES_PATHS_WITH_PATH_PARAM.SENSEI.external_allocation_view
|
||||
];
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import isTodayDayJs from 'dayjs/plugin/isToday';
|
||||
dayjs.extend(relativeTime);
|
||||
dayjs.extend(isTodayDayJs);
|
||||
|
||||
export const monthNames = [
|
||||
'Jan',
|
||||
|
||||
@@ -352,6 +352,7 @@ export function copyToClipboard(value: string) {
|
||||
}
|
||||
|
||||
export const getNameInitials = (name: string) => {
|
||||
if (!name) return '';
|
||||
const initials = name.match(/\b\w/g) || [];
|
||||
return ((initials.shift() || '') + (initials.pop() || '')).toUpperCase();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user