NTP-59601 | Anomaly Tracker for Inhouse
This commit is contained in:
34
src/assets/icons/AnomalyIcon.tsx
Normal file
34
src/assets/icons/AnomalyIcon.tsx
Normal 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;
|
||||
112
src/assets/icons/NoAnomaliesFoundIcon.tsx
Normal file
112
src/assets/icons/NoAnomaliesFoundIcon.tsx
Normal 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;
|
||||
18
src/assets/icons/UpdateIcon.tsx
Normal file
18
src/assets/icons/UpdateIcon.tsx
Normal 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;
|
||||
@@ -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, {
|
||||
|
||||
49
src/reducer/anomalyTrackerSlice.ts
Normal file
49
src/reducer/anomalyTrackerSlice.ts
Normal 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;
|
||||
106
src/screens/Dashboard/AnomalyTracker/AnomaliesDetailsList.tsx
Normal file
106
src/screens/Dashboard/AnomalyTracker/AnomaliesDetailsList.tsx
Normal 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;
|
||||
29
src/screens/Dashboard/AnomalyTracker/AnomalyCardBody.tsx
Normal file
29
src/screens/Dashboard/AnomalyTracker/AnomalyCardBody.tsx
Normal 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;
|
||||
40
src/screens/Dashboard/AnomalyTracker/AnomalyCardHeader.tsx
Normal file
40
src/screens/Dashboard/AnomalyTracker/AnomalyCardHeader.tsx
Normal 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;
|
||||
30
src/screens/Dashboard/AnomalyTracker/AnomalyDetailsItem.tsx
Normal file
30
src/screens/Dashboard/AnomalyTracker/AnomalyDetailsItem.tsx
Normal 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;
|
||||
65
src/screens/Dashboard/AnomalyTracker/AnomalyOverviewCard.tsx
Normal file
65
src/screens/Dashboard/AnomalyTracker/AnomalyOverviewCard.tsx
Normal 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;
|
||||
43
src/screens/Dashboard/AnomalyTracker/AnomlayTracker.tsx
Normal file
43
src/screens/Dashboard/AnomalyTracker/AnomlayTracker.tsx
Normal 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;
|
||||
@@ -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;
|
||||
17
src/screens/Dashboard/AnomalyTracker/constants.ts
Normal file
17
src/screens/Dashboard/AnomalyTracker/constants.ts
Normal 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';
|
||||
56
src/screens/Dashboard/AnomalyTracker/utils.ts
Normal file
56
src/screens/Dashboard/AnomalyTracker/utils.ts
Normal 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)));
|
||||
};
|
||||
@@ -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 />
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user