NTP-59601 | Anomaly Tracker for Inhouse

This commit is contained in:
aishwarya.srivastava
2025-05-05 19:55:09 +05:30
parent d4764af421
commit 35330d2488
18 changed files with 699 additions and 7 deletions

View File

@@ -0,0 +1,34 @@
import * as React from 'react';
import Svg, { Path, Rect } from 'react-native-svg';
const AnomalyTrackerIcon = () => (
<Svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<Rect width="20" height="20" rx="4" fill="#FFEFDB" />
<Path
d="M13.7627 13.7617L16.4995 16.4986"
stroke="#F0C52B"
stroke-width="1.45905"
stroke-miterlimit="10"
/>
<Path
d="M3.92969 7.26413C4.8213 5.05864 6.98676 3.50098 9.51078 3.50098C12.836 3.50098 15.525 6.20361 15.525 9.51519C15.525 12.8268 12.8429 15.5226 9.51078 15.5226C7.15763 15.5226 5.11444 14.1658 4.12734 12.1929"
stroke="#F0C52B"
stroke-width="1.45905"
stroke-miterlimit="10"
/>
<Path
d="M3.5 9.58284H5.76474C6.20263 9.58284 6.60632 9.32284 6.81842 8.91231L7.43421 7.74916C7.66 7.3181 8.24842 7.40705 8.35789 7.89284L9.23368 11.7107C9.34316 12.1965 9.95211 12.2855 10.1642 11.8339L11.1768 9.71968"
stroke="#F0C52B"
stroke-width="1.45905"
stroke-miterlimit="10"
/>
<Path
d="M11.5467 9.80913C12.0119 9.80913 12.3882 9.43282 12.3882 8.96756C12.3882 8.50229 12.0119 8.12598 11.5467 8.12598C11.0814 8.12598 10.7051 8.50229 10.7051 8.96756C10.7051 9.43282 11.0814 9.80913 11.5467 9.80913Z"
stroke="#F0C52B"
stroke-width="1.45905"
stroke-miterlimit="10"
/>
</Svg>
);
export default AnomalyTrackerIcon;

View File

@@ -0,0 +1,112 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
const NoAnomaliesFoundIcon = () => (
<Svg width="156" height="182" viewBox="0 0 156 182" fill="none">
<Path
d="M102.453 56.7171L28.559 59.9935C28.0106 60.0216 27.5747 60.4856 27.6029 61.0481L32.0323 161.111C32.0604 161.659 32.5244 162.095 33.0869 162.067L106.981 158.791C107.529 158.763 107.965 158.299 107.937 157.736L103.508 57.6733C103.48 57.1249 103.016 56.689 102.453 56.7171Z"
fill="#DBDBDB"
stroke="black"
stroke-width="0.321519"
stroke-linecap="round"
stroke-linejoin="round"
/>
<Path
d="M113.942 51.2875H39.9773C39.4148 51.2875 38.9648 51.7375 38.9648 52.2999V152.461C38.9648 153.024 39.4148 153.474 39.9773 153.474H113.956C114.518 153.474 114.968 153.024 114.968 152.461V52.2859C114.968 51.7234 114.518 51.2734 113.956 51.2734L113.942 51.2875Z"
fill="white"
stroke="black"
stroke-width="0.321519"
stroke-linecap="round"
stroke-linejoin="round"
/>
<Path
d="M49.6235 72.9434H87.7447V79.3695H49.6235V72.9434Z"
fill="#DBDBDB"
stroke="black"
stroke-width="0.321519"
stroke-linecap="round"
stroke-linejoin="round"
/>
<Path
d="M49.6094 88.6377H106.32V95.0639H49.6094V88.6377Z"
fill="#DBDBDB"
stroke="black"
stroke-width="0.321519"
stroke-linecap="round"
stroke-linejoin="round"
/>
<Path
d="M121.774 135.082L117.133 138.612L124.755 148.652L129.395 145.122L121.774 135.082Z"
fill="#545454"
stroke="black"
stroke-width="0.321519"
stroke-linecap="round"
stroke-linejoin="round"
/>
<Path
d="M122.617 134.548L117.977 138.077L125.598 148.117L130.239 144.588L122.617 134.548Z"
fill="white"
stroke="black"
stroke-width="0.321519"
stroke-linecap="round"
stroke-linejoin="round"
/>
<Path
opacity="0.3"
d="M127.553 141.41L123.264 144.672L125.542 147.682L129.831 144.419L127.553 141.41Z"
fill="#12173D"
/>
<Path
opacity="0.3"
d="M126.513 97.6774C116.262 84.1782 97.0252 81.5346 83.526 91.7856C70.0268 102.037 67.3832 121.273 77.6342 134.772C87.8851 148.271 107.121 150.915 120.621 140.664C134.12 130.413 136.763 111.163 126.513 97.6774ZM117.246 136.22C106.207 144.601 90.4725 142.45 82.0777 131.411C73.6969 120.373 75.8484 104.624 86.9008 96.2431C97.9392 87.8624 113.688 90.0138 122.069 101.066C130.45 112.105 128.284 127.854 117.246 136.234V136.22Z"
fill="#12173D"
/>
<Path
d="M119.594 134.237C108.556 142.618 92.8206 140.466 84.4258 129.428C76.045 118.39 78.1965 102.641 89.2489 94.2598C100.287 85.879 116.036 88.0305 124.417 99.0829C132.798 110.121 130.632 125.87 119.594 134.251V134.237Z"
fill="white"
fill-opacity="0.6"
/>
<Path
d="M128.861 95.695C118.61 82.1958 99.3734 79.5522 85.8742 89.8031C72.375 100.054 69.7314 119.29 79.9823 132.79C90.2333 146.289 109.47 148.932 122.969 138.681C136.468 128.431 139.112 109.194 128.861 95.695ZM119.594 134.238C108.556 142.619 92.8206 140.467 84.4258 129.429C76.0451 118.39 78.1965 102.641 89.249 94.2607C100.287 85.8799 116.036 88.0314 124.417 99.0838C132.798 110.122 130.632 125.871 119.594 134.252V134.238Z"
fill="#E6F1FF"
stroke="black"
stroke-width="0.321519"
stroke-linecap="round"
stroke-linejoin="round"
/>
<Path
d="M122.84 151.634C121.912 150.382 122.179 148.625 123.417 147.697L128.577 143.844C129.829 142.916 131.586 143.169 132.514 144.42L153.565 172.783C154.493 174.034 154.226 175.792 152.988 176.72L147.828 180.573C146.576 181.501 144.818 181.248 143.89 179.996L122.84 151.634Z"
fill="#545454"
stroke="black"
stroke-width="0.321519"
stroke-linecap="round"
stroke-linejoin="round"
/>
<Path
d="M123.559 151.084C122.631 149.833 122.898 148.075 124.136 147.147L129.296 143.294C130.548 142.366 132.306 142.619 133.234 143.87L154.284 172.233C155.212 173.484 154.945 175.242 153.707 176.17L148.547 180.023C147.295 180.951 145.538 180.698 144.61 179.446L123.559 151.084Z"
fill="white"
stroke="black"
stroke-width="0.321519"
stroke-linecap="round"
stroke-linejoin="round"
/>
<Path
d="M19.7173 176.142H9.14314"
stroke="#1C1C1C"
stroke-width="0.285714"
stroke-linejoin="round"
/>
<Path
d="M58.4912 176.142H26.7688"
stroke="#1C1C1C"
stroke-width="0.285714"
stroke-linejoin="round"
/>
<Path
d="M134.857 63.5703H153.46"
stroke="#1C1C1C"
stroke-width="0.228571"
stroke-linejoin="round"
/>
</Svg>
);
export default NoAnomaliesFoundIcon;

View File

@@ -0,0 +1,18 @@
import * as React from 'react';
import Svg, { G, Mask, Path, Rect } from 'react-native-svg';
const UpdateIcon = () => (
<Svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<Mask id="mask0_6963_151737" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<Rect width="16" height="16" fill="#D9D9D9" />
</Mask>
<G mask="url(#mask0_6963_151737)">
<Path
d="M12.8667 5.94967L10.0333 3.14967L10.9667 2.21634C11.2222 1.96079 11.5362 1.83301 11.9087 1.83301C12.2807 1.83301 12.5944 1.96079 12.85 2.21634L13.7833 3.14967C14.0389 3.40523 14.1722 3.71367 14.1833 4.07501C14.1944 4.4359 14.0722 4.74412 13.8167 4.99967L12.8667 5.94967ZM2.66667 13.9997C2.47778 13.9997 2.31956 13.9357 2.192 13.8077C2.064 13.6801 2 13.5219 2 13.333V11.4497C2 11.3608 2.01667 11.2748 2.05 11.1917C2.08333 11.1081 2.13333 11.033 2.2 10.9663L9.06667 4.09967L11.9 6.93301L5.03333 13.7997C4.96667 13.8663 4.89178 13.9163 4.80867 13.9497C4.72511 13.983 4.63889 13.9997 4.55 13.9997H2.66667Z"
fill="#0276FE"
/>
</G>
</Svg>
);
export default UpdateIcon;

View File

@@ -119,8 +119,10 @@ export enum ApiKeys {
GET_FEEDBACK_ADDRESSES = 'GET_FEEDBACK_ADDRESSES',
GET_TRAINING_MATERIAL_LIST = 'GET_TRAINING_MATERIAL_LIST',
GET_TRAINING_MATERIAL_DETAILS = 'GET_TRAINING_MATERIAL_DETAILS',
SELF_CALL_ACK= '/api/v1/self-call',
GET_CASES_GEOLOCATION_DISTANCE_FROM_AGENT_LOCATION = "GET_CASES_GEOLOCATION_DISTANCE_FROM_AGENT_LOCATION"
SELF_CALL_ACK = '/api/v1/self-call',
GET_CASES_GEOLOCATION_DISTANCE_FROM_AGENT_LOCATION = 'GET_CASES_GEOLOCATION_DISTANCE_FROM_AGENT_LOCATION',
GET_ANOMALY_DETAILS = 'GET_ANOMALY_DETAILS',
GET_ANOMALY_ACTIVITY_LOG = 'GET_ANOMALY_ACTIVITY_LOG',
}
export const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
@@ -229,7 +231,10 @@ API_URLS[ApiKeys.GET_TRAINING_MATERIAL_DETAILS] = '/training-page/{docRefId}';
API_URLS[ApiKeys.SELF_CALL_ACK] = '/sync-data/self-call-metadata';
API_URLS[ApiKeys.GET_TOP_ADDRESSES] = '/collection-cases/unified-locations';
API_URLS[ApiKeys.GET_FEEDBACK_ADDRESSES] = '/collection-cases/unified-locations/lite';
API_URLS[ApiKeys.GET_CASES_GEOLOCATION_DISTANCE_FROM_AGENT_LOCATION] = '/geolocation-distance/single-source'
API_URLS[ApiKeys.GET_ANOMALY_DETAILS] = '/anomaly-tracker';
API_URLS[ApiKeys.GET_ANOMALY_ACTIVITY_LOG] = '/anomaly-tracker/activity-logs/{anomalyReferenceId}';
API_URLS[ApiKeys.GET_CASES_GEOLOCATION_DISTANCE_FROM_AGENT_LOCATION] =
'/geolocation-distance/single-source';
export const API_STATUS_CODE = {
OK: 200,
@@ -360,7 +365,7 @@ axiosInstance.interceptors.response.use(
const url = response?.config?.url;
const apiKey = getKeyByValue(url, API_URLS);
sendApiToClickstreamEvent(response, milliseconds, false);
if(apiKey) {
if (apiKey) {
logger({
msg: 'api response error',
extras: {
@@ -370,7 +375,7 @@ axiosInstance.interceptors.response.use(
endpoint: API_URLS[apiKey as keyof typeof API_URLS],
method: response?.config?.method || '',
},
type: 'error'
type: 'error',
});
}
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_API_FAILED, {

View File

@@ -0,0 +1,49 @@
import { createSlice } from '@reduxjs/toolkit';
const initialState: any = {
openAnomaliesList: {},
closedAnomaliesList: {},
anomalyDetailsLoading: false,
anomalyDetails: {
data: {},
loading: false,
},
actionLoading: false,
acitvityLogs: {
data: [],
loading: false,
},
};
const anomalyTrackerSlice = createSlice({
name: 'anomalyTracker',
initialState,
reducers: {
setOpenAnomalyList: (state, action) => {
state.openAnomaliesList = action.payload;
},
setClosedAnomalyList: (state, action) => {
state.closedAnomaliesList = action.payload;
},
setAnomalyDetailsLoading: (state, action) => {
state.anomalyDetailsLoading = action.payload;
},
setActivityLogs: (state, action) => {
state.acitvityLogs.data = action.payload;
},
setActivityLogsLoading: (state, action) => {
state.acitvityLogs.loading = action.payload;
},
},
});
export const {
setOpenAnomalyList,
setClosedAnomalyList,
setAnomalyDetailsLoading,
setRcaReasons,
setActivityLogs,
setActivityLogsLoading,
} = anomalyTrackerSlice.actions;
export default anomalyTrackerSlice.reducer;

View File

@@ -0,0 +1,106 @@
import React from 'react';
import { View, StyleSheet, ActivityIndicator, FlatList } from 'react-native';
import AnomalyDetailsItem from './AnomalyDetailsItem';
import { useAppSelector } from '@hooks';
import { AnomalyType } from './constants';
import { GenericStyles } from '@rn-ui-lib/styles';
import { COLORS } from '@rn-ui-lib/colors';
import Text from '@rn-ui-lib/components/Text';
import NoAnomaliesFoundIcon from '@assets/icons/NoAnomaliesFoundIcon';
import Heading from '@rn-ui-lib/components/Heading';
interface IAnomaliesDetailsListProps {
anomalyType: string;
}
const AnomaliesDetailsList = ({ anomalyType }: IAnomaliesDetailsListProps) => {
const openAnomaliesData = useAppSelector(
(state) => state?.anomalyTracker?.openAnomaliesList?.data || []
);
const closedAnomaliesData = useAppSelector(
(state) => state?.anomalyTracker?.closedAnomaliesList?.data || []
);
const isLoading = useAppSelector((state) => state?.anomalyTracker?.anomalyDetailsLoading);
if (isLoading) {
return (
<View style={[GenericStyles.fill, GenericStyles.centerAlignedRow]}>
<ActivityIndicator size="large" color={COLORS.BASE.BLUE} />
</View>
);
}
if (anomalyType === AnomalyType.OPEN) {
return (
<>
{openAnomaliesData?.length > 0 ? (
<FlatList
data={openAnomaliesData}
keyExtractor={(item) => item?.anomalyId}
renderItem={({ item }) => <AnomalyDetailsItem item={item} anomalyType={anomalyType} />}
contentContainerStyle={[GenericStyles.pb16]}
/>
) : (
<View style={[GenericStyles.w100, styles.centerAbsolute]}>
<NoAnomaliesFoundIcon />
<View style={[GenericStyles.mt12, styles.text]}>
<Heading
dark
bold
type="h3"
style={[GenericStyles.mt16, GenericStyles.centerAlignedText]}
>
No open issues
</Heading>
<Text small dark style={[GenericStyles.centerAlignedText, GenericStyles.fontSize12]}>
You have closed all the issues
</Text>
</View>
</View>
)}
</>
);
}
return (
<>
{closedAnomaliesData?.length > 0 ? (
<FlatList
data={closedAnomaliesData}
keyExtractor={(item) => item?.anomalyId}
renderItem={({ item }) => <AnomalyDetailsItem item={item} anomalyType={anomalyType} />}
contentContainerStyle={[GenericStyles.pb16]}
/>
) : (
<View style={[GenericStyles.w100, styles.centerAbsolute]}>
<NoAnomaliesFoundIcon />
<View style={[GenericStyles.mt12, styles.text]}>
<Heading
dark
bold
type="h3"
style={[GenericStyles.mt16, GenericStyles.centerAlignedText]}
>
No closed issues
</Heading>
<Text small dark style={[GenericStyles.centerAlignedText, GenericStyles.fontSize12]}>
Closed issues are shown here
</Text>
</View>
</View>
)}
</>
);
};
const styles = StyleSheet.create({
centerAbsolute: {
// height: '100%',
marginTop: 160,
justifyContent: 'center',
alignItems: 'center',
},
text: {
width: '100%',
},
});
export default AnomaliesDetailsList;

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import SingleAnomalyDetails from './SingleAnomalyDetails';
import RcaEtaContainer from './RcaEtaContainer';
interface IAnomalyCardBodyProps {
item: any;
anomalyType: string;
}
const AnomalyCardBody = (props: IAnomalyCardBodyProps) => {
const { item, anomalyType } = props;
return (
<View style={styles.container}>
<SingleAnomalyDetails item={item} anomalyType={anomalyType} />
<RcaEtaContainer item={item} anomalyType={anomalyType} />
</View>
);
};
const styles = StyleSheet.create({
container: {
borderBottomRightRadius: 6,
borderBottomLeftRadius: 6,
paddingHorizontal: 16,
paddingTop: 20,
paddingBottom: 16,
},
});
export default AnomalyCardBody;

View File

@@ -0,0 +1,40 @@
import { COLORS } from '@rn-ui-lib/colors';
import { GenericStyles, getShadowStyle } from '@rn-ui-lib/styles';
import Text from '@rn-ui-lib/components/Text';
import React from 'react';
import { View, StyleSheet } from 'react-native';
const AnomalyCardHeader = (props: any) => {
const { item } = props;
return (
<View style={styles.container}>
<Text style={styles.text} dark>
{item?.type}
</Text>
<View style={styles.priorityLabel}>
<Text style={{ fontSize: 12, color: COLORS.TEXT.WHITE }}>{item?.priority}</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor: COLORS.BACKGROUND.RED,
borderTopRadius: 6,
paddingHorizontal: 16,
paddingVertical: 12,
alignItems: 'center',
},
priorityLabel: {
borderRadius: 4,
backgroundColor: COLORS.TEXT.RED,
paddingHorizontal: 8,
paddingVertical: 1,
},
text: { fontSize: 14, fontWeight: '700', color: COLORS.TEXT.DARK },
});
export default AnomalyCardHeader;

View File

@@ -0,0 +1,30 @@
import { COLORS } from '@rn-ui-lib/colors';
import { GenericStyles, getShadowStyle } from '@rn-ui-lib/styles';
import React from 'react';
import { View, StyleSheet } from 'react-native';
import AnomalyCardBody from './AnomalyCardBody';
import AnomalyCardHeader from './AnomalyCardHeader';
interface IAnomalyDetailsItemProps {
anomalyType: string;
item: any;
}
const AnomalyDetailsItem = (props: IAnomalyDetailsItemProps) => {
const { anomalyType, item } = props;
return (
<View style={styles.container}>
<AnomalyCardHeader item={item} />
<AnomalyCardBody item={item} anomalyType={anomalyType} />
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: COLORS.BACKGROUND.PRIMARY,
borderRadius: 8,
marginHorizontal: 16,
marginTop: 16,
...getShadowStyle(2),
},
});
export default AnomalyDetailsItem;

View File

@@ -0,0 +1,65 @@
import React from 'react';
import { Pressable, View, StyleSheet } from 'react-native';
import Text from '@rn-ui-lib/components/Text';
import AnomalyIcon from '@assets/icons/AnomalyIcon';
import { navigateToScreen } from '@components/utlis/navigationUtlis';
import { PageRouteEnum } from '@screens/auth/ProtectedRouter';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { GenericStyles, getShadowStyle } from '@rn-ui-lib/styles';
import { COLORS } from '@rn-ui-lib/colors';
import Chevron from '@rn-ui-lib/icons/Chevron';
import { useAppSelector } from '@hooks';
const AnomalyOverviewCard = () => {
const totalOpenAnomalies =
useAppSelector((state) => state?.anomalyTracker?.openAnomaliesList?.pages?.totalElements);
const onClick = () => {
navigateToScreen(PageRouteEnum.CASE_DETAIL_STACK, {
screen: CaseDetailStackEnum.ANOMALY_TRACKER,
});
};
return (
<View style={styles.container}>
<View style={styles.textContainer}>
<AnomalyIcon />
<Text style={styles.text}>{totalOpenAnomalies} Open issues</Text>
</View>
<Pressable onPress={onClick} style={styles.textContainer}>
<Text style={styles.buttonText}>View</Text>
<View style={styles.rightIcon}>
<Chevron fillColor={COLORS.TEXT.BLUE} />
</View>
</Pressable>
</View>
);
};
const styles = StyleSheet.create({
container: {
display: 'flex',
flexDirection: 'row',
borderRadius: 4,
marginHorizontal: 16,
...getShadowStyle(2),
backgroundColor: COLORS.BACKGROUND.ORANGE,
borderWidth: 1,
borderColor: COLORS.BORDER.ORANGE,
marginBottom: 16,
padding: 12,
justifyContent: 'space-between',
},
textContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 6,
},
rightIcon: {
marginTop: 3,
},
text: { fontSize: 13, fontWeight: '600' },
buttonText: { color: COLORS.TEXT.BLUE, fontWeight: '600', fontSize: 13 },
});
export default AnomalyOverviewCard;

View File

@@ -0,0 +1,43 @@
import React, { useEffect, useState } from 'react';
import { anomalyPageTitle, AnomalyType, TABS } from './constants';
import { GenericStyles, getShadowStyle } from '@rn-ui-lib/styles';
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
import CustomTabs from '@rn-ui-lib/components/customTabs/CustomTabs';
import { goBack } from '@components/utlis/navigationUtlis';
import AnomaliesDetailsList from './AnomaliesDetailsList';
import { useAppDispatch } from '@hooks';
import { getAnomalyDetails } from './utils';
interface IAnomalyTracker {}
const AnomalyTracker: React.FC<IAnomalyTracker> = (props: IAnomalyTracker) => {
const [currentTab, setCurrentTab] = useState<string>(AnomalyType.OPEN);
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(getAnomalyDetails(AnomalyType.OPEN, {}, true));
});
const handleTabChange = (tab: string) => {
if (tab === currentTab) return;
if (tab === AnomalyType.OPEN) {
dispatch(getAnomalyDetails(AnomalyType.OPEN, {}, true));
} else {
dispatch(getAnomalyDetails(AnomalyType.RESOLVED, {}, true));
}
setCurrentTab(tab);
};
return (
<>
<NavigationHeader title={anomalyPageTitle} onBack={goBack} />
<CustomTabs
tabs={TABS}
currentTab={currentTab}
onTabChange={handleTabChange}
containerStyle={[getShadowStyle(2), GenericStyles.pt12]}
/>
<AnomaliesDetailsList anomalyType={currentTab} />
</>
);
};
export default AnomalyTracker;

View File

@@ -0,0 +1,66 @@
import { COLORS } from '@rn-ui-lib/colors';
import Text from '@rn-ui-lib/components/Text';
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { sanitizeString } from '@components/utlis/commonFunctions';
import { BUSINESS_DATE_FORMAT, dateFormat } from '@rn-ui-lib/utils/dates';
import { AnomalyType } from './constants';
import DaysTillEscalationComponent from './DaysTillEscalationComponent';
interface ISingleAnomalyDetailsProps {
item: any;
anomalyType: string;
}
const SingleAnomalyDetails = (props: ISingleAnomalyDetailsProps) => {
const { item, anomalyType } = props;
return (
<View style={styles.container}>
<View>
<Text style={styles.textLabel}>Created on</Text>
<Text style={styles.date}>
{sanitizeString(dateFormat(new Date(item?.createdAt), BUSINESS_DATE_FORMAT))}
</Text>
</View>
{anomalyType === AnomalyType.OPEN ? (
<View style={styles.daysTillEscalationText}>
<Text style={[styles.textLabel, styles.textAlign, styles.w60]}>Days till Escalation</Text>
<DaysTillEscalationComponent
dayTillEscalation={item?.daysRemaining}
progressPercent={item?.escalationPercentage}
/>
</View>
) : (
<View>
<Text style={[styles.textLabel, styles.textAlign]}>Resolved on</Text>
<Text style={[styles.date, styles.textAlign]}>
{sanitizeString(dateFormat(new Date(item?.resolution?.at), BUSINESS_DATE_FORMAT))}
</Text>
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
paddingBottom: 16,
alignItems: 'center',
},
priorityLabel: {
borderRadius: 4,
backgroundColor: COLORS.TEXT.RED,
paddingHorizontal: 6,
paddingVertical: 1,
},
textLabel: { fontSize: 12, color: COLORS.TEXT.LIGHT },
date: { fontSize: 14, color: COLORS.TEXT.BLACK, fontWeight: '600' },
daysTillEscalationText: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 8 },
textAlign: { textAlign: 'right' },
w60: { width: 60 },
});
export default SingleAnomalyDetails;

View File

@@ -0,0 +1,17 @@
export enum AnomalyType {
OPEN = 'OPEN',
RESOLVED = 'RESOLVED',
}
export const TABS = [
{
key: AnomalyType.OPEN,
label: 'Open',
},
{
key: AnomalyType.RESOLVED,
label: 'Closed',
},
];
export const anomalyPageTitle = 'Your issues';

View File

@@ -0,0 +1,56 @@
import axiosInstance, { API_STATUS_CODE, ApiKeys, getApiUrl } from '@components/utlis/apiHelper';
import { AnomalyType } from './constants';
import { logError } from '@components/utlis/errorUtils';
import { AppDispatch } from '@store';
import {
setActivityLogs,
setActivityLogsLoading,
setAnomalyDetailsLoading,
setClosedAnomalyList,
setOpenAnomalyList,
} from '@reducers/anomalyTrackerSlice';
export const getAnomalyDetails =
(anomalyType: string, payload: {}, isLoading: boolean) => (dispatch: AppDispatch) => {
const url = getApiUrl(ApiKeys.GET_ANOMALY_DETAILS);
if (isLoading) dispatch(setAnomalyDetailsLoading(true));
axiosInstance
.get(url, {
baseURL: 'https://qa-longhorn-portal.np.navi-ppl.in/api/longhorn', //TODO: remove this
params: { status: anomalyType },
})
.then((res) => {
if (res?.status === API_STATUS_CODE.OK) {
if (anomalyType === AnomalyType.OPEN) {
dispatch(setOpenAnomalyList(res?.data));
return;
}
dispatch(setClosedAnomalyList(res?.data));
}
})
.catch((err) => {
logError(err);
})
.finally(() => {
dispatch(setAnomalyDetailsLoading(false));
});
};
export const getActivityLogs = (anomalyReferenceId: string) => (dispatch: AppDispatch) => {
const url = getApiUrl(ApiKeys.GET_ANOMALY_ACTIVITY_LOG, { anomalyReferenceId });
dispatch(setActivityLogsLoading(true));
axiosInstance
.get(url, {
baseURL: 'https://qa-longhorn-portal.np.navi-ppl.in/api/longhorn', //TODO: remove this
})
.then((response) => {
if (response.status === API_STATUS_CODE.OK) {
dispatch(setActivityLogs(response?.data));
}
})
.catch((err) => {
logError(err);
})
.finally(() => dispatch(setActivityLogsLoading(false)));
};

View File

@@ -3,6 +3,7 @@ import React from 'react';
import InternalAgentPerformanceCard from './InternalAgentPerformanceCard';
import PerformanceMeter from './PerformanceMeter';
import PerformanceOverview from './PerformanceOverview';
import AnomalyOverviewCard from './AnomalyTracker/AnomalyOverviewCard';
const InternalAgentDashboard = () => {
const performanceData = useAppSelector((state) => state.agentPerformance.performanceData);
@@ -14,9 +15,17 @@ const InternalAgentDashboard = () => {
atleastOneEmiCollected: cases?.atleastOneEmiCollected,
totalEmi: cases?.totalEmi,
};
const totalOpenAnomalies = useAppSelector(
(state) => state?.anomalyTracker?.openAnomaliesList?.pages?.totalElements
);
const totalClosedAnomalies = useAppSelector(
(state) => state?.anomalyTracker?.closedAnomaliesList?.pages?.totalElements
);
const isOpenOrClosedAnomaliesPresent = totalOpenAnomalies + totalClosedAnomalies > 0;
return (
<>
{isOpenOrClosedAnomaliesPresent ? <AnomalyOverviewCard /> : null}
<PerformanceOverview performanceOverviewData={internalAgentPerformanceOverview} />
<PerformanceMeter />
<InternalAgentPerformanceCard />

View File

@@ -16,6 +16,8 @@ import { addClickstreamEvent } from '../../services/clickstreamEventService';
import DashboardHeader from './DashboardHeader';
import ExternalAgentDashboard from './ExternalAgentDashboard';
import InternalAgentDashboard from './InternalAgentDashboard';
import { getAnomalyDetails } from './AnomalyTracker/utils';
import { AnomalyType } from './AnomalyTracker/constants';
const Dashboard = () => {
const [refreshing, setRefreshing] = React.useState(false);
@@ -27,7 +29,13 @@ const Dashboard = () => {
const fetchAgentPerformanceMetrics = () => {
setIsLoading(true);
dispatch(getPerformanceMetrics(Object.keys(caseDetailsIds ?? {}), isExternalAgent, setIsLoading));
dispatch(
getPerformanceMetrics(Object.keys(caseDetailsIds ?? {}), isExternalAgent, setIsLoading)
);
if (!isExternalAgent) {
dispatch(getAnomalyDetails(AnomalyType.OPEN, {}, false));
dispatch(getAnomalyDetails(AnomalyType.RESOLVED, {}, false));
}
};
useEffect(() => {

View File

@@ -31,6 +31,7 @@ import CallCustomer from './CallCustomer';
import TopAddresses from '@screens/addresses/topAddresses/TopAddresses';
import OtherAddresses from '@screens/addresses/otherAddresses/OtherAddresses';
import Escalations from '@screens/escalations/Escalations';
import AnomalyTracker from '@screens/Dashboard/AnomalyTracker/AnomlayTracker';
const Stack = createNativeStackNavigator();
@@ -59,6 +60,7 @@ export enum CaseDetailStackEnum {
TOP_ADDRESSES = 'TopAddresses',
OTHER_ADDRESSES = 'OtherAddresses',
ESCALATIONS = 'Escalations',
ANOMALY_TRACKER = "ANOMALY_TRACKER",
}
const CaseDetailStack = () => {
@@ -124,6 +126,7 @@ const CaseDetailStack = () => {
))}
<Stack.Screen name={CaseDetailStackEnum.TOP_ADDRESSES} component={TopAddresses} />
<Stack.Screen name={CaseDetailStackEnum.OTHER_ADDRESSES} component={OtherAddresses} />
<Stack.Screen name={CaseDetailStackEnum.ANOMALY_TRACKER} component={AnomalyTracker} />
</Stack.Navigator>
);
};

View File

@@ -40,6 +40,7 @@ import skipTracingAddressesSlice from '@reducers/skipTracingAddressesSlice';
import trainingMaterialSlice from '@reducers/trainingMaterialSlice';
import feedbackFormSlice from '@reducers/feedbackFormSlice';
import topAddressesSlice from '@reducers/topAddressesSlice';
import anomalyTrackerSlice from '@reducers/anomalyTrackerSlice';
const rootReducer = combineReducers({
case: caseReducer,
@@ -79,7 +80,8 @@ const rootReducer = combineReducers({
postOperationalHourRestrictionsSlice: postOperationalHourRestrictionsSlice,
trainingMaterial: trainingMaterialSlice,
feedbackForm: feedbackFormSlice,
topAddresses: topAddressesSlice
topAddresses: topAddressesSlice,
anomalyTracker: anomalyTrackerSlice,
});
const persistConfig = {