NTP-68615 | Go live anomaly tracker (#1187)
This commit is contained in:
committed by
GitHub
parent
9ef129f749
commit
8e80b34cea
Submodule RN-UI-LIB updated: efa590cd27...ede6bd382e
@@ -1,33 +1,38 @@
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { IconProps } from '@rn-ui-lib/icons/types';
|
||||
import * as React from 'react';
|
||||
import Svg, { Path, Rect } from 'react-native-svg';
|
||||
import Svg, { Path } from 'react-native-svg';
|
||||
|
||||
const AnomalyTrackerIcon = () => (
|
||||
<Svg width="18" height="19" viewBox="0 0 18 19" fill="none">
|
||||
<Path
|
||||
d="M13.8232 14.8232L17.332 18.332"
|
||||
stroke="#C8A311"
|
||||
stroke-width="1.6"
|
||||
stroke-miterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M1.21973 6.49058C2.36282 3.66302 5.13905 1.66602 8.37497 1.66602C12.6381 1.66602 16.0855 5.13093 16.0855 9.37654C16.0855 13.6222 12.6469 17.0783 8.37497 17.0783C5.35812 17.0783 2.73864 15.3388 1.47313 12.8096"
|
||||
stroke="#C8A311"
|
||||
stroke-width="1.6"
|
||||
stroke-miterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M0.666016 9.46345H3.56952C4.13093 9.46345 4.64847 9.13012 4.9204 8.6038L5.70988 7.11257C5.99935 6.55994 6.75374 6.67398 6.89409 7.29679L8.01689 12.1915C8.15724 12.8143 8.93795 12.9284 9.20988 12.3494L10.5081 9.63889"
|
||||
stroke="#C8A311"
|
||||
stroke-width="1.6"
|
||||
stroke-miterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M10.9832 9.7536C11.5797 9.7536 12.0622 9.27114 12.0622 8.67465C12.0622 8.07816 11.5797 7.5957 10.9832 7.5957C10.3868 7.5957 9.9043 8.07816 9.9043 8.67465C9.9043 9.27114 10.3868 9.7536 10.9832 9.7536Z"
|
||||
stroke="#C8A311"
|
||||
stroke-width="1.6"
|
||||
stroke-miterlimit="10"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
const AnomalyTrackerIcon: React.FC<IconProps> = (props) => {
|
||||
const { fillColor = COLORS.TEXT.YELLOW_LIGHT, width = 32, height = 36 } = props;
|
||||
return (
|
||||
<Svg width="18" height="19" viewBox="0 0 18 19" fill="none">
|
||||
<Path
|
||||
d="M13.8232 14.8232L17.332 18.332"
|
||||
stroke={fillColor}
|
||||
stroke-width="1.6"
|
||||
stroke-miterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M1.21973 6.49058C2.36282 3.66302 5.13905 1.66602 8.37497 1.66602C12.6381 1.66602 16.0855 5.13093 16.0855 9.37654C16.0855 13.6222 12.6469 17.0783 8.37497 17.0783C5.35812 17.0783 2.73864 15.3388 1.47313 12.8096"
|
||||
stroke={fillColor}
|
||||
stroke-width="1.6"
|
||||
stroke-miterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M0.666016 9.46345H3.56952C4.13093 9.46345 4.64847 9.13012 4.9204 8.6038L5.70988 7.11257C5.99935 6.55994 6.75374 6.67398 6.89409 7.29679L8.01689 12.1915C8.15724 12.8143 8.93795 12.9284 9.20988 12.3494L10.5081 9.63889"
|
||||
stroke={fillColor}
|
||||
stroke-width="1.6"
|
||||
stroke-miterlimit="10"
|
||||
/>
|
||||
<Path
|
||||
d="M10.9832 9.7536C11.5797 9.7536 12.0622 9.27114 12.0622 8.67465C12.0622 8.07816 11.5797 7.5957 10.9832 7.5957C10.3868 7.5957 9.9043 8.07816 9.9043 8.67465C9.9043 9.27114 10.3868 9.7536 10.9832 9.7536Z"
|
||||
stroke={fillColor}
|
||||
stroke-width="1.6"
|
||||
stroke-miterlimit="10"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnomalyTrackerIcon;
|
||||
|
||||
43
src/assets/icons/EscalatedAnomalyIcon.tsx
Normal file
43
src/assets/icons/EscalatedAnomalyIcon.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import Svg, { ClipPath, Defs, G, Path, Rect } from 'react-native-svg';
|
||||
|
||||
const EscalatedAnomalyIcon = () => {
|
||||
return (
|
||||
<Svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<G clip-Path="url(#clip0_7461_158159)">
|
||||
<Path
|
||||
d="M11.2716 1.49986C11.1517 1.26585 10.9695 1.06947 10.7452 0.932346C10.5209 0.795216 10.2631 0.722656 10.0001 0.722656C9.73722 0.722656 9.47939 0.795216 9.25506 0.932346C9.03073 1.06947 8.84859 1.26585 8.72872 1.49986L0.87157 17.2141C0.761985 17.4316 0.70981 17.6734 0.720002 17.9169C0.730193 18.1601 0.802413 18.3967 0.929803 18.6043C1.05719 18.8119 1.23552 18.9834 1.44786 19.1026C1.66019 19.2219 1.89948 19.2849 2.143 19.2856H17.8572C18.1008 19.2849 18.3401 19.2219 18.5524 19.1026C18.7648 18.9834 18.9431 18.8119 19.0705 18.6043C19.1978 18.3967 19.2701 18.1601 19.2802 17.9169C19.2905 17.6734 19.2382 17.4316 19.1287 17.2141L11.2716 1.49986Z"
|
||||
fill="#F98600"
|
||||
/>
|
||||
<Path
|
||||
d="M10 7.14258V11.7854"
|
||||
stroke="#FEE7CC"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M10.0002 15.7143C9.80297 15.7143 9.64307 15.5544 9.64307 15.3571C9.64307 15.1599 9.80297 15 10.0002 15"
|
||||
stroke="#FEE7CC"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<Path
|
||||
d="M10 15.7143C10.1972 15.7143 10.3571 15.5544 10.3571 15.3571C10.3571 15.1599 10.1972 15 10 15"
|
||||
stroke="#FEE7CC"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</G>
|
||||
<Defs>
|
||||
<ClipPath id="clip0_7461_158159">
|
||||
<Rect width="20" height="20" fill="white" />
|
||||
</ClipPath>
|
||||
</Defs>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default EscalatedAnomalyIcon;
|
||||
158
src/common/FormInput.tsx
Normal file
158
src/common/FormInput.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import React from 'react';
|
||||
import { Control, Controller, ControllerRenderProps } from 'react-hook-form';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import {
|
||||
BUSINESS_DATE_FORMAT,
|
||||
CUSTOM_ISO_DATE_FORMAT,
|
||||
dateFormat,
|
||||
ISO_DATE_FORMAT,
|
||||
} from '@rn-ui-lib/utils/dates';
|
||||
import dayjs from 'dayjs';
|
||||
import WebBasedDatePicker from '@rn-ui-lib/components/WebBasedDatePicker';
|
||||
import ErrorMessage from '@components/form/components/ErrorMessage';
|
||||
import { AnswerType } from '@components/form/interface';
|
||||
import RadioGroup from '@rn-ui-lib/components/radio_button/RadioGroup';
|
||||
import { IOption, IEtaFormData, IRcaFormData } from '@screens/Dashboard/AnomalyTracker/interfaces';
|
||||
import TextInput from '@rn-ui-lib/components/TextInput';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import RNRadioButton from '@rn-ui-lib/components/radio_button/RadioButton';
|
||||
|
||||
interface IFormInputProps {
|
||||
control: Control<IEtaFormData | IRcaFormData, any>;
|
||||
question: {
|
||||
text: string;
|
||||
type: AnswerType;
|
||||
};
|
||||
maxDate?: string;
|
||||
name: 'at' | 'comment' | 'reason';
|
||||
isQuestionMandatory?: boolean;
|
||||
placeholder?: string;
|
||||
keyboardType?: 'default' | 'number-pad' | 'email-address' | 'phone-pad';
|
||||
answerOptions?: IOption[];
|
||||
rules?: {};
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
const FormInput = (props: IFormInputProps) => {
|
||||
const {
|
||||
control,
|
||||
maxDate,
|
||||
question,
|
||||
name,
|
||||
isQuestionMandatory = false,
|
||||
placeholder,
|
||||
keyboardType = 'default',
|
||||
answerOptions,
|
||||
rules,
|
||||
maxLength = 300,
|
||||
} = props;
|
||||
|
||||
const todaysDate = dateFormat(new Date(), ISO_DATE_FORMAT);
|
||||
|
||||
const renderInput = (
|
||||
field: ControllerRenderProps<IEtaFormData | IRcaFormData, 'at' | 'comment' | 'reason'>,
|
||||
hasError: boolean
|
||||
) => {
|
||||
const { onChange, value, onBlur } = field;
|
||||
switch (question.type) {
|
||||
case AnswerType.date:
|
||||
let dateString = value;
|
||||
if (dateString) {
|
||||
const parsedDate = dayjs(dateString, CUSTOM_ISO_DATE_FORMAT, true);
|
||||
dateString = parsedDate.format(ISO_DATE_FORMAT);
|
||||
}
|
||||
return (
|
||||
<WebBasedDatePicker
|
||||
displayFormat={BUSINESS_DATE_FORMAT}
|
||||
containerStyle={styles.inputContainerStyle}
|
||||
type="date"
|
||||
onChange={onChange}
|
||||
outputFormat={ISO_DATE_FORMAT}
|
||||
min={todaysDate}
|
||||
max={maxDate}
|
||||
/>
|
||||
);
|
||||
case AnswerType.option:
|
||||
return (
|
||||
<RadioGroup value={value} onValueChange={onChange} orientation="vertical">
|
||||
{answerOptions?.map((option: IOption) => {
|
||||
return (
|
||||
<RNRadioButton
|
||||
key={option.value}
|
||||
id={option.value}
|
||||
value={option.label}
|
||||
containerStyle={GenericStyles.containerStyle}
|
||||
textStyle={GenericStyles.mr16}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</RadioGroup>
|
||||
);
|
||||
|
||||
case AnswerType.text:
|
||||
return (
|
||||
<TextInput
|
||||
style={GenericStyles.fill}
|
||||
value={value}
|
||||
maxLength={maxLength}
|
||||
onChangeText={onChange}
|
||||
keyboardType={keyboardType}
|
||||
placeholder={placeholder || 'Enter here'}
|
||||
onBlur={onBlur}
|
||||
error={hasError}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<TextInput
|
||||
style={GenericStyles.fill}
|
||||
value={value}
|
||||
maxLength={maxLength}
|
||||
onChangeText={onChange}
|
||||
keyboardType={keyboardType}
|
||||
placeholder={placeholder || 'Enter here'}
|
||||
onBlur={onBlur}
|
||||
error={hasError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[GenericStyles.ph16, GenericStyles.mt20]}>
|
||||
<Text dark bold style={GenericStyles.mb8}>
|
||||
{question?.text}
|
||||
{isQuestionMandatory && <Text style={GenericStyles.redText}>*</Text>}
|
||||
</Text>
|
||||
<Controller
|
||||
control={control}
|
||||
rules={rules}
|
||||
name={name}
|
||||
render={({ field, fieldState }) => (
|
||||
<>
|
||||
{renderInput(field, !!fieldState?.error)}
|
||||
{fieldState?.error?.message ? (
|
||||
<ErrorMessage show={{ message: fieldState.error.message }} />
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
inputContainerStyle: {
|
||||
backgroundColor: COLORS.BACKGROUND.PRIMARY,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
padding: 0,
|
||||
borderColor: COLORS.BORDER.PRIMARY,
|
||||
color: COLORS.TEXT.BLACK,
|
||||
},
|
||||
});
|
||||
|
||||
export default FormInput;
|
||||
@@ -126,6 +126,8 @@ export enum ApiKeys {
|
||||
FEEDBACK_ORIGINAL_IMAGE_ACK = 'FEEDBACK_ORIGINAL_IMAGE_ACK',
|
||||
GET_ANOMALY_DETAILS = 'GET_ANOMALY_DETAILS',
|
||||
GET_ANOMALY_ACTIVITY_LOG = 'GET_ANOMALY_ACTIVITY_LOG',
|
||||
GET_ANOMALY_RCA_QUESTION = 'GET_ANOMALY_RCA_QUESTION',
|
||||
UPDATE_ANOMALY_ACTION = 'UPDATE_ANOMALY_ACTION',
|
||||
}
|
||||
|
||||
export const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
|
||||
@@ -241,6 +243,9 @@ API_URLS[ApiKeys.GET_PRE_SIGNED_URL_FOR_FEEDBACK_IMAGE] = '/file-upload/presigne
|
||||
API_URLS[ApiKeys.FEEDBACK_ORIGINAL_IMAGE_ACK] = '/file-upload/acknowledge';
|
||||
API_URLS[ApiKeys.GET_ANOMALY_DETAILS] = '/anomaly-tracker';
|
||||
API_URLS[ApiKeys.GET_ANOMALY_ACTIVITY_LOG] = '/anomaly-tracker/activity-logs/{anomalyReferenceId}';
|
||||
API_URLS[ApiKeys.UPDATE_ANOMALY_ACTION] = '/anomaly-tracker/action/{anomalyReferenceId}';
|
||||
API_URLS[ApiKeys.GET_ANOMALY_RCA_QUESTION] =
|
||||
'/anomaly-tracker/question-tree/rca/{anomalyReferenceId}';
|
||||
|
||||
export const API_STATUS_CODE = {
|
||||
OK: 200,
|
||||
|
||||
@@ -1,8 +1,44 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { ActivityLog, AnomalyItem, AnomalyResponse, IOption } from '@screens/Dashboard/AnomalyTracker/interfaces';
|
||||
|
||||
const initialState = {
|
||||
openAnomaliesList: {},
|
||||
closedAnomaliesList: {},
|
||||
interface AnomalyTrackerState {
|
||||
openAnomaliesList: AnomalyResponse;
|
||||
closedAnomaliesList: AnomalyResponse;
|
||||
anomalyDetailsLoading: boolean;
|
||||
anomalyDetails: {
|
||||
data: AnomalyItem;
|
||||
loading: boolean;
|
||||
};
|
||||
actionLoading: boolean;
|
||||
acitvityLogs: {
|
||||
data: ActivityLog[];
|
||||
loading: boolean;
|
||||
};
|
||||
rcaReasons: Array<{
|
||||
options: IOption[] | undefined; label: string; value: string
|
||||
}>;
|
||||
updatingForm: boolean;
|
||||
}
|
||||
|
||||
const initialState: AnomalyTrackerState = {
|
||||
openAnomaliesList: {
|
||||
data: [],
|
||||
pages: {
|
||||
pageNo: 0,
|
||||
totalPages: 0,
|
||||
pageSize: 0,
|
||||
totalElements: 0
|
||||
}
|
||||
},
|
||||
closedAnomaliesList: {
|
||||
data: [],
|
||||
pages: {
|
||||
pageNo: 0,
|
||||
totalPages: 0,
|
||||
pageSize: 0,
|
||||
totalElements: 0
|
||||
}
|
||||
},
|
||||
anomalyDetailsLoading: false,
|
||||
anomalyDetails: {
|
||||
data: {},
|
||||
@@ -13,6 +49,8 @@ const initialState = {
|
||||
data: [],
|
||||
loading: false,
|
||||
},
|
||||
rcaReasons: [],
|
||||
updatingForm: false,
|
||||
};
|
||||
|
||||
const anomalyTrackerSlice = createSlice({
|
||||
@@ -34,6 +72,12 @@ const anomalyTrackerSlice = createSlice({
|
||||
setActivityLogsLoading: (state, action) => {
|
||||
state.acitvityLogs.loading = action.payload;
|
||||
},
|
||||
setRcaReasons: (state, action) => {
|
||||
state.rcaReasons = action.payload;
|
||||
},
|
||||
setUpdatingForm: (state, action) => {
|
||||
state.updatingForm = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -44,6 +88,7 @@ export const {
|
||||
setRcaReasons,
|
||||
setActivityLogs,
|
||||
setActivityLogsLoading,
|
||||
setUpdatingForm,
|
||||
} = anomalyTrackerSlice.actions;
|
||||
|
||||
export default anomalyTrackerSlice.reducer;
|
||||
|
||||
@@ -76,6 +76,7 @@ export interface IUserSlice extends IUser {
|
||||
fieldAgentPerformanceDashboardEnabled: boolean;
|
||||
isCallRecordingCosmosExotelEnabled: boolean;
|
||||
isCosmosDiallerEnabled: boolean;
|
||||
enableFieldAppAnomalyTracker: boolean;
|
||||
};
|
||||
employeeId: string;
|
||||
is1To30FieldAgent: boolean;
|
||||
@@ -111,6 +112,7 @@ const initialState: IUserSlice = {
|
||||
fieldAgentPerformanceDashboardEnabled: false,
|
||||
isCallRecordingCosmosExotelEnabled: false,
|
||||
isCosmosDiallerEnabled: false,
|
||||
enableFieldAppAnomalyTracker: false,
|
||||
},
|
||||
employeeId: '',
|
||||
is1To30FieldAgent: false,
|
||||
|
||||
@@ -35,7 +35,7 @@ const AnomaliesDetailsList = ({ anomalyType }: IAnomaliesDetailsListProps) => {
|
||||
{openAnomaliesData?.length > 0 ? (
|
||||
<FlatList
|
||||
data={openAnomaliesData}
|
||||
keyExtractor={(item) => item?.anomalyId}
|
||||
keyExtractor={(item) => item?.anomalyId as string}
|
||||
renderItem={({ item }) => <AnomalyDetailsItem item={item} anomalyType={anomalyType} />}
|
||||
contentContainerStyle={[GenericStyles.pb16]}
|
||||
/>
|
||||
@@ -65,7 +65,7 @@ const AnomaliesDetailsList = ({ anomalyType }: IAnomaliesDetailsListProps) => {
|
||||
{closedAnomaliesData?.length > 0 ? (
|
||||
<FlatList
|
||||
data={closedAnomaliesData}
|
||||
keyExtractor={(item) => item?.anomalyId}
|
||||
keyExtractor={(item) => item?.anomalyId as string}
|
||||
renderItem={({ item }) => <AnomalyDetailsItem item={item} anomalyType={anomalyType} />}
|
||||
contentContainerStyle={[GenericStyles.pb16]}
|
||||
/>
|
||||
|
||||
@@ -5,34 +5,47 @@ 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 { 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 totalOpenAnomalies = useAppSelector(
|
||||
(state) => state?.anomalyTracker?.openAnomaliesList?.pages?.totalElements
|
||||
);
|
||||
|
||||
const onClick = () => {
|
||||
navigateToScreen(PageRouteEnum.CASE_DETAIL_STACK, {
|
||||
screen: CaseDetailStackEnum.ANOMALY_TRACKER,
|
||||
});
|
||||
};
|
||||
const areAllIssuesResolved = totalOpenAnomalies === 0;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Pressable
|
||||
onPress={onClick}
|
||||
style={[
|
||||
styles.container,
|
||||
areAllIssuesResolved ? styles.closedAnomalyState : styles.openAnomalyState,
|
||||
]}
|
||||
>
|
||||
<View style={styles.textContainer}>
|
||||
<AnomalyIcon />
|
||||
<Text style={styles.text}>{totalOpenAnomalies} Open issues</Text>
|
||||
<AnomalyIcon
|
||||
fillColor={areAllIssuesResolved ? COLORS.TEXT.GREEN : COLORS.TEXT.YELLOW_LIGHT}
|
||||
/>
|
||||
<Text style={styles.text}>
|
||||
{areAllIssuesResolved ? 'All issues are closed' : `${totalOpenAnomalies} Open issues`}
|
||||
</Text>
|
||||
</View>
|
||||
<Pressable onPress={onClick} style={styles.textContainer}>
|
||||
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={styles.buttonText}>View</Text>
|
||||
<View style={styles.rightIcon}>
|
||||
<Chevron fillColor={COLORS.TEXT.BLUE} />
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,12 +53,10 @@ const styles = StyleSheet.create({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
borderRadius: 4,
|
||||
borderRadius: 6,
|
||||
marginHorizontal: 16,
|
||||
...getShadowStyle(2),
|
||||
backgroundColor: COLORS.BACKGROUND.ORANGE,
|
||||
borderWidth: 1,
|
||||
borderColor: COLORS.BORDER.ORANGE,
|
||||
marginBottom: 16,
|
||||
padding: 12,
|
||||
justifyContent: 'space-between',
|
||||
@@ -56,6 +67,14 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
},
|
||||
openAnomalyState: {
|
||||
backgroundColor: COLORS.BACKGROUND.ORANGE,
|
||||
borderColor: COLORS.BORDER.ORANGE,
|
||||
},
|
||||
closedAnomalyState: {
|
||||
backgroundColor: COLORS.BACKGROUND.GREEN,
|
||||
borderColor: COLORS.BORDER.GREEN,
|
||||
},
|
||||
rightIcon: {
|
||||
marginTop: 3,
|
||||
},
|
||||
|
||||
103
src/screens/Dashboard/AnomalyTracker/AnomalyTrackerActions.ts
Normal file
103
src/screens/Dashboard/AnomalyTracker/AnomalyTrackerActions.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
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,
|
||||
setRcaReasons,
|
||||
setUpdatingForm,
|
||||
} from '@reducers/anomalyTrackerSlice';
|
||||
import { IEtaFormPayload, IRcaFormPayload } from './interfaces';
|
||||
import { toast } from '@rn-ui-lib/components/toast';
|
||||
import { ToastMessages } from '@screens/allCases/constants';
|
||||
|
||||
export const getAnomalyDetails =
|
||||
(anomalyType: string, isLoading: boolean) => (dispatch: AppDispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GET_ANOMALY_DETAILS);
|
||||
if (isLoading) dispatch(setAnomalyDetailsLoading(true));
|
||||
axiosInstance
|
||||
.get(url, {
|
||||
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)
|
||||
.then((response) => {
|
||||
if (response?.status === API_STATUS_CODE.OK) {
|
||||
dispatch(setActivityLogs(response?.data));
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toast({
|
||||
text1: ToastMessages.GENERIC_ERROR_TOAST,
|
||||
type: 'error',
|
||||
});
|
||||
})
|
||||
.finally(() => dispatch(setActivityLogsLoading(false)));
|
||||
};
|
||||
|
||||
export const getRcaReasons =
|
||||
(
|
||||
anomalyReferenceId: string,
|
||||
setIsRcaReasonsLoading: React.Dispatch<React.SetStateAction<boolean>>
|
||||
) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
const url = getApiUrl(ApiKeys.GET_ANOMALY_RCA_QUESTION, { anomalyReferenceId });
|
||||
axiosInstance
|
||||
.get(url)
|
||||
.then((response) => {
|
||||
if (response?.status === API_STATUS_CODE.OK) {
|
||||
dispatch(setRcaReasons(response?.data));
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
logError(err);
|
||||
})
|
||||
.finally(() => setIsRcaReasonsLoading(false));
|
||||
};
|
||||
|
||||
export const updateAnomaly =
|
||||
(
|
||||
anomalyReferenceId: string,
|
||||
payload: IEtaFormPayload | IRcaFormPayload,
|
||||
callbackFn: () => void
|
||||
) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
const url = getApiUrl(ApiKeys.UPDATE_ANOMALY_ACTION, { anomalyReferenceId });
|
||||
dispatch(setUpdatingForm(true));
|
||||
axiosInstance
|
||||
.put(url, payload)
|
||||
.then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
callbackFn?.();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
logError(err);
|
||||
})
|
||||
.finally(() => dispatch(setUpdatingForm(false)));
|
||||
};
|
||||
@@ -6,24 +6,22 @@ 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';
|
||||
import { getAnomalyDetails } from './AnomalyTrackerActions';
|
||||
|
||||
interface IAnomalyTracker {}
|
||||
|
||||
const AnomalyTracker: React.FC<IAnomalyTracker> = (props: IAnomalyTracker) => {
|
||||
const AnomalyTracker = () => {
|
||||
const [currentTab, setCurrentTab] = useState<string>(AnomalyType.OPEN);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getAnomalyDetails(AnomalyType.OPEN, {}, true));
|
||||
dispatch(getAnomalyDetails(AnomalyType.OPEN, true));
|
||||
}, []);
|
||||
|
||||
const handleTabChange = (tab: string) => {
|
||||
if (tab === currentTab) return;
|
||||
if (tab === AnomalyType.OPEN) {
|
||||
dispatch(getAnomalyDetails(AnomalyType.OPEN, {}, true));
|
||||
dispatch(getAnomalyDetails(AnomalyType.OPEN, true));
|
||||
} else {
|
||||
dispatch(getAnomalyDetails(AnomalyType.RESOLVED, {}, true));
|
||||
dispatch(getAnomalyDetails(AnomalyType.RESOLVED, true));
|
||||
}
|
||||
setCurrentTab(tab);
|
||||
};
|
||||
@@ -36,6 +34,7 @@ const AnomalyTracker: React.FC<IAnomalyTracker> = (props: IAnomalyTracker) => {
|
||||
currentTab={currentTab}
|
||||
onTabChange={handleTabChange}
|
||||
containerStyle={[getShadowStyle(2), GenericStyles.pt12]}
|
||||
tabContainerStyle={GenericStyles.ml16}
|
||||
/>
|
||||
<AnomaliesDetailsList anomalyType={currentTab} />
|
||||
</>
|
||||
|
||||
@@ -9,9 +9,11 @@ import UpdateIcon from '@assets/icons/UpdateIcon';
|
||||
interface IBottomsheetHeaderProps {
|
||||
title: string;
|
||||
handleClose: () => void;
|
||||
showUpdateButton?: boolean;
|
||||
handleUpdateButtonClick: () => void;
|
||||
}
|
||||
const BottomsheetHeader = (props: IBottomsheetHeaderProps) => {
|
||||
const { title, handleClose } = props;
|
||||
const { title, handleClose, showUpdateButton = false, handleUpdateButtonClick } = props;
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
@@ -25,15 +27,17 @@ const BottomsheetHeader = (props: IBottomsheetHeaderProps) => {
|
||||
<Heading dark type="h3">
|
||||
View {title}
|
||||
</Heading>
|
||||
<Pressable
|
||||
onPress={() => {}} //TODO - Redirect toRCA ETA form
|
||||
style={[styles.title, styles.gap4]}
|
||||
>
|
||||
<UpdateIcon />
|
||||
<Heading dark type="h5" style={styles.buttonLabel}>
|
||||
Update
|
||||
</Heading>
|
||||
</Pressable>
|
||||
{showUpdateButton ? (
|
||||
<Pressable
|
||||
onPress={handleUpdateButtonClick}
|
||||
style={[styles.title, styles.gap4]}
|
||||
>
|
||||
<UpdateIcon />
|
||||
<Heading dark type="h5" style={styles.buttonLabel}>
|
||||
Update
|
||||
</Heading>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
<Pressable onPress={handleClose}>
|
||||
<CloseIcon color={COLORS.TEXT.LIGHT} />
|
||||
|
||||
@@ -3,6 +3,7 @@ import { View, StyleSheet } from 'react-native';
|
||||
import Text from '@rn-ui-lib/components/Text';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import AnimatedCircularLoaderIcon from '@assets/icons/AnimatedCircularLoaderIcon';
|
||||
import EscalatedAnomalyIcon from '@assets/icons/EscalatedAnomalyIcon';
|
||||
|
||||
const DaysTillEscalationComponent = (props: {
|
||||
dayTillEscalation: number;
|
||||
@@ -19,16 +20,13 @@ const DaysTillEscalationComponent = (props: {
|
||||
circumference={circumference}
|
||||
progressPercent={dayTillEscalation > 0 ? progressPercent : 0}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transform: [{ translateX: 82 }],
|
||||
}}
|
||||
>
|
||||
<View style={styles.escalationTextContainer}>
|
||||
{dayTillEscalation > 0 ? (
|
||||
<Text style={styles.textContainer}>{dayTillEscalation}</Text>
|
||||
) : (
|
||||
<Text style={styles.textContainer}>0</Text>
|
||||
<View style={styles.iconContainer}>
|
||||
<EscalatedAnomalyIcon />
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
@@ -36,5 +34,12 @@ const DaysTillEscalationComponent = (props: {
|
||||
};
|
||||
const styles = StyleSheet.create({
|
||||
textContainer: { fontSize: 14, color: COLORS.TEXT.YELLOW, fontWeight: '700' },
|
||||
escalationTextContainer: {
|
||||
position: 'absolute',
|
||||
transform: [{ translateX: 82 }],
|
||||
},
|
||||
iconContainer: {
|
||||
transform: [{ translateY: 6 }, { translateX: 4 }],
|
||||
},
|
||||
});
|
||||
export default DaysTillEscalationComponent;
|
||||
|
||||
151
src/screens/Dashboard/AnomalyTracker/Forms.tsx/EtaForm.tsx
Normal file
151
src/screens/Dashboard/AnomalyTracker/Forms.tsx/EtaForm.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet, SafeAreaView, ScrollView, ActivityIndicator } from 'react-native';
|
||||
import NavigationHeader, { Icon } from '@rn-ui-lib/components/NavigationHeader';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import { goBack, navigateToScreen } from '@components/utlis/navigationUtlis';
|
||||
import Button from '@rn-ui-lib/components/Button';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import FormInput from '@common/FormInput';
|
||||
import { AnswerType } from '@components/form/interface';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import { getAnomalyDetails, updateAnomaly } from '../AnomalyTrackerActions';
|
||||
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
|
||||
import { PageRouteEnum } from '@screens/auth/ProtectedRouter';
|
||||
import { toast } from '@rn-ui-lib/components/toast';
|
||||
import { dateFormat, ISO_DATE_FORMAT } from '@rn-ui-lib/utils/dates';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { AnomalyType, VALIDATION_ERROR_MESSAGES } from '../constants';
|
||||
import { IEtaFormData } from '../interfaces';
|
||||
import { getLastDateOfNextMonth } from './utils';
|
||||
|
||||
interface IEtaForm {
|
||||
route: {
|
||||
params: {
|
||||
selectedAnomalyId: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
const EtaForm = (props: IEtaForm) => {
|
||||
const { selectedAnomalyId } = props.route.params || {};
|
||||
const showLoader = useAppSelector((state) => state?.anomalyTracker?.updatingForm);
|
||||
const dispatch = useAppDispatch();
|
||||
const {
|
||||
control,
|
||||
reset,
|
||||
formState: { isValid },
|
||||
handleSubmit,
|
||||
} = useForm<IEtaFormData>({
|
||||
defaultValues: {
|
||||
at: '',
|
||||
reason: '',
|
||||
},
|
||||
mode: 'onBlur',
|
||||
});
|
||||
const successCallback = () => {
|
||||
reset();
|
||||
toast({
|
||||
text1: 'ETA submitted successfully',
|
||||
type: 'success',
|
||||
});
|
||||
dispatch(getAnomalyDetails(AnomalyType.OPEN, true));
|
||||
navigateToScreen(PageRouteEnum.CASE_DETAIL_STACK, {
|
||||
screen: CaseDetailStackEnum.ANOMALY_TRACKER,
|
||||
});
|
||||
};
|
||||
const submitETA = (data: IEtaFormData) => {
|
||||
dispatch(updateAnomaly(selectedAnomalyId, { eta: data }, successCallback));
|
||||
};
|
||||
|
||||
const maxDate = dateFormat(getLastDateOfNextMonth(), ISO_DATE_FORMAT);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[GenericStyles.fill]}>
|
||||
<NavigationHeader
|
||||
title="Submit ETA"
|
||||
titleStyle={styles.header}
|
||||
onBack={goBack}
|
||||
icon={Icon.close}
|
||||
/>
|
||||
{showLoader ? (
|
||||
<View style={[GenericStyles.fill, GenericStyles.centerAlignedRow]}>
|
||||
<ActivityIndicator size="large" color={COLORS.BASE.BLUE} />
|
||||
</View>
|
||||
) : (
|
||||
<View style={[GenericStyles.fill, GenericStyles.justifyContentFlexEnd]}>
|
||||
<ScrollView
|
||||
contentContainerStyle={[GenericStyles.fill, GenericStyles.justifyContentFlexEnd]}
|
||||
>
|
||||
<View style={GenericStyles.mb20}>
|
||||
<FormInput
|
||||
control={control}
|
||||
question={{
|
||||
text: 'Select Date',
|
||||
type: AnswerType.date,
|
||||
}}
|
||||
name="at"
|
||||
isQuestionMandatory={true}
|
||||
placeholder="Select date"
|
||||
maxDate={maxDate}
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: VALIDATION_ERROR_MESSAGES.DATE,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FormInput
|
||||
control={control}
|
||||
question={{
|
||||
text: 'Enter reason',
|
||||
type: AnswerType.text,
|
||||
}}
|
||||
name="reason"
|
||||
isQuestionMandatory={true}
|
||||
placeholder="Enter reason"
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: VALIDATION_ERROR_MESSAGES.REASON_REQUIRED,
|
||||
},
|
||||
minLength: {
|
||||
value: 20,
|
||||
message: VALIDATION_ERROR_MESSAGES.MIN_LENGTH,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
GenericStyles.spaceBetween,
|
||||
GenericStyles.elevation10,
|
||||
GenericStyles.p16,
|
||||
GenericStyles.whiteBackground,
|
||||
]}
|
||||
>
|
||||
<Button
|
||||
title={'Submit'}
|
||||
style={[GenericStyles.w100]}
|
||||
disabled={!isValid}
|
||||
showLoader={false}
|
||||
onPress={handleSubmit(submitETA)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
export const styles = StyleSheet.create({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end',
|
||||
borderTopColor: COLORS.BORDER.PRIMARY,
|
||||
borderBottomColor: COLORS.BORDER.PRIMARY,
|
||||
},
|
||||
header: { fontSize: 16, fontWeight: '600' },
|
||||
});
|
||||
export default EtaForm;
|
||||
156
src/screens/Dashboard/AnomalyTracker/Forms.tsx/RcaForm.tsx
Normal file
156
src/screens/Dashboard/AnomalyTracker/Forms.tsx/RcaForm.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, StyleSheet, SafeAreaView, ScrollView, ActivityIndicator } from 'react-native';
|
||||
import NavigationHeader, { Icon } from '@rn-ui-lib/components/NavigationHeader';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import { goBack, navigateToScreen } from '@components/utlis/navigationUtlis';
|
||||
import Button from '@rn-ui-lib/components/Button';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import FormInput from '@common/FormInput';
|
||||
import { AnswerType } from '@components/form/interface';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import { getAnomalyDetails, getRcaReasons, updateAnomaly } from '../AnomalyTrackerActions';
|
||||
import { toast } from '@rn-ui-lib/components/toast';
|
||||
import { PageRouteEnum } from '@screens/auth/ProtectedRouter';
|
||||
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { AnomalyType, VALIDATION_ERROR_MESSAGES } from '../constants';
|
||||
import { IRcaFormData } from '../interfaces';
|
||||
|
||||
interface IRcaForm {
|
||||
route: {
|
||||
params: {
|
||||
selectedAnomalyId: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const RcaForm = (props: IRcaForm) => {
|
||||
const { selectedAnomalyId } = props.route.params || {};
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const {
|
||||
control,
|
||||
reset,
|
||||
formState: { isValid },
|
||||
handleSubmit,
|
||||
} = useForm<IRcaFormData>({
|
||||
defaultValues: {
|
||||
comment: '',
|
||||
reason: '',
|
||||
},
|
||||
mode: 'onBlur',
|
||||
});
|
||||
const [rcaReasonsLoading, setIsRcaReasonsLoading] = useState(true);
|
||||
const rcaReasons = useAppSelector((state) => state?.anomalyTracker?.rcaReasons);
|
||||
const showLoader = useAppSelector((state) => state?.anomalyTracker?.updatingForm);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAnomalyId) {
|
||||
dispatch(getRcaReasons(selectedAnomalyId, setIsRcaReasonsLoading));
|
||||
}
|
||||
}, [selectedAnomalyId]);
|
||||
|
||||
const successCallback = () => {
|
||||
reset();
|
||||
toast({
|
||||
text1: 'RCA submitted successfully',
|
||||
type: 'success',
|
||||
});
|
||||
dispatch(getAnomalyDetails(AnomalyType.OPEN, true));
|
||||
navigateToScreen(PageRouteEnum.CASE_DETAIL_STACK, {
|
||||
screen: CaseDetailStackEnum.ANOMALY_TRACKER,
|
||||
});
|
||||
};
|
||||
const submitRCA = (data: IRcaFormData) => {
|
||||
dispatch(updateAnomaly(selectedAnomalyId, { rca: data }, successCallback));
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={GenericStyles.fill}>
|
||||
<NavigationHeader
|
||||
title="Submit RCA"
|
||||
titleStyle={styles.header}
|
||||
onBack={goBack}
|
||||
icon={Icon.close}
|
||||
/>
|
||||
{showLoader || rcaReasonsLoading ? (
|
||||
<View style={[GenericStyles.fill, GenericStyles.centerAlignedRow]}>
|
||||
<ActivityIndicator size="large" color={COLORS.BASE.BLUE} />
|
||||
</View>
|
||||
) : (
|
||||
<View style={[GenericStyles.fill, GenericStyles.justifyContentFlexEnd]}>
|
||||
<ScrollView contentContainerStyle={[GenericStyles.justifyContentFlexEnd]}>
|
||||
<View style={GenericStyles.mb20}>
|
||||
<FormInput
|
||||
control={control}
|
||||
question={{
|
||||
text: rcaReasons?.[0]?.label || 'Select reason',
|
||||
type: AnswerType.option,
|
||||
}}
|
||||
name="reason"
|
||||
isQuestionMandatory={true}
|
||||
answerOptions={rcaReasons?.[0]?.options}
|
||||
placeholder="Select reason"
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: VALIDATION_ERROR_MESSAGES.REASON_REQUIRED,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FormInput
|
||||
control={control}
|
||||
question={{
|
||||
text: 'Enter reason',
|
||||
type: AnswerType.text,
|
||||
}}
|
||||
name="comment"
|
||||
isQuestionMandatory={true}
|
||||
placeholder="Enter here"
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: VALIDATION_ERROR_MESSAGES.REQUIRED,
|
||||
},
|
||||
minLength: {
|
||||
value: 20,
|
||||
message: VALIDATION_ERROR_MESSAGES.MIN_LENGTH,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
<View
|
||||
style={[
|
||||
GenericStyles.row,
|
||||
GenericStyles.alignCenter,
|
||||
GenericStyles.spaceBetween,
|
||||
GenericStyles.elevation10,
|
||||
GenericStyles.p16,
|
||||
GenericStyles.whiteBackground,
|
||||
]}
|
||||
>
|
||||
<Button
|
||||
title={'Submit'}
|
||||
style={[GenericStyles.w100]}
|
||||
disabled={!isValid}
|
||||
showLoader={false}
|
||||
onPress={handleSubmit(submitRCA)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
export const styles = StyleSheet.create({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end',
|
||||
borderTopColor: COLORS.BORDER.PRIMARY,
|
||||
borderBottomColor: COLORS.BORDER.PRIMARY,
|
||||
},
|
||||
header: { fontSize: 16, fontWeight: '600' },
|
||||
});
|
||||
export default RcaForm;
|
||||
6
src/screens/Dashboard/AnomalyTracker/Forms.tsx/utils.ts
Normal file
6
src/screens/Dashboard/AnomalyTracker/Forms.tsx/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const getLastDateOfNextMonth = () => {
|
||||
const todaysDate = new Date();
|
||||
const nextMonth = new Date(todaysDate.getFullYear(), todaysDate.getMonth() + 1, 1);
|
||||
const lastDateOfNextMonth = new Date(nextMonth.getFullYear(), nextMonth.getMonth() + 1, 0);
|
||||
return lastDateOfNextMonth;
|
||||
};
|
||||
@@ -1,82 +1,103 @@
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import React, { useState } from 'react';
|
||||
import { View, StyleSheet, ScrollView, Pressable } from 'react-native';
|
||||
import BottomSheetWrapper from '@common/BottomSheetWrapper';
|
||||
import { AnomalyType } from './constants';
|
||||
import { useAppDispatch } from '@hooks';
|
||||
import { getActivityLogs } from './utils';
|
||||
import dayjs from 'dayjs';
|
||||
import { BUSINESS_DATE_FORMAT } from '@rn-ui-lib/utils/dates';
|
||||
import RcaEtaActionRow from './RcaEtaActionRow';
|
||||
import BottomsheetHeader from './BottomsheetHeader';
|
||||
import ViewRcaEtaDetails from './ViewRcaEtaDetails';
|
||||
import { AnomalyItem } from './interfaces';
|
||||
|
||||
interface IRcaEtaContainerProps {
|
||||
item: AnomalyItem;
|
||||
anomalyType: string;
|
||||
}
|
||||
import { ANOMALY_ACTIONS, IRcaEtaContainerProps } from './interfaces';
|
||||
import { navigateToScreen } from '@components/utlis/navigationUtlis';
|
||||
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
|
||||
const RcaEtaContainer = (props: IRcaEtaContainerProps) => {
|
||||
const { item, anomalyType } = props;
|
||||
const [showETABottomSheet, setShowETABottomSheet] = useState(false);
|
||||
const [showRCABottomSheet, setShowRCABottomSheet] = useState(false);
|
||||
const [selectedAnomalyId, setSelectedAnomalyId] = useState('');
|
||||
const dispatch = useAppDispatch();
|
||||
const selectedAnomalyId = item?.anomalyReferenceId as string;
|
||||
|
||||
const isEtaFilled = item?.estimatedTime || anomalyType === AnomalyType.RESOLVED;
|
||||
const isRcaFilled = item?.rca || anomalyType === AnomalyType.RESOLVED;
|
||||
|
||||
const onETAClick = () => {
|
||||
if (!(item?.estimatedTime || anomalyType === AnomalyType.RESOLVED)) return; //TODO - Redirect to fill ETA form
|
||||
setSelectedAnomalyId(item?.anomalyReferenceId as string);
|
||||
if (!isEtaFilled) {
|
||||
navigateToScreen(CaseDetailStackEnum.ETA_FORM, { selectedAnomalyId });
|
||||
return;
|
||||
}
|
||||
setShowETABottomSheet(true);
|
||||
};
|
||||
|
||||
const onRCAClick = () => {
|
||||
if (!(item?.rca || anomalyType === AnomalyType.RESOLVED)) return; //TODO - Redirect to fill ETA form
|
||||
setSelectedAnomalyId(item?.anomalyReferenceId as string);
|
||||
if (!isRcaFilled) {
|
||||
navigateToScreen(CaseDetailStackEnum.RCA_FORM, { selectedAnomalyId });
|
||||
return;
|
||||
}
|
||||
setShowRCABottomSheet(true);
|
||||
};
|
||||
|
||||
const etaStatus = item?.estimatedTime
|
||||
? dayjs(item?.estimatedTime?.at)?.format(BUSINESS_DATE_FORMAT)
|
||||
: 'Unfilled';
|
||||
|
||||
const handleClose = () => {
|
||||
setShowRCABottomSheet(false);
|
||||
if (showRCABottomSheet) {
|
||||
setShowRCABottomSheet(false);
|
||||
return;
|
||||
}
|
||||
setShowETABottomSheet(false);
|
||||
setSelectedAnomalyId('');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAnomalyId) dispatch(getActivityLogs(selectedAnomalyId));
|
||||
}, [selectedAnomalyId]);
|
||||
const handleUpdateButtonClick = () => {
|
||||
if (showRCABottomSheet) {
|
||||
navigateToScreen(CaseDetailStackEnum.RCA_FORM, { selectedAnomalyId });
|
||||
} else {
|
||||
navigateToScreen(CaseDetailStackEnum.ETA_FORM, { selectedAnomalyId });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<RcaEtaActionRow
|
||||
anomalyType={anomalyType}
|
||||
label={'RCA'}
|
||||
label={ANOMALY_ACTIONS.RCA}
|
||||
onClick={onRCAClick}
|
||||
status={item?.rca ? 'Filled' : 'Unfilled'}
|
||||
statusColor={{ color: item?.rca ? COLORS.TEXT.GREEN : COLORS.TEXT.RED }}
|
||||
buttonLabel={item?.rca || anomalyType === AnomalyType.RESOLVED ? 'View' : 'Fill'}
|
||||
buttonLabel={isRcaFilled ? 'View' : 'Fill'}
|
||||
/>
|
||||
<View style={styles.line} />
|
||||
<RcaEtaActionRow
|
||||
anomalyType={anomalyType}
|
||||
label={'ETA'}
|
||||
label={ANOMALY_ACTIONS.ETA}
|
||||
onClick={onETAClick}
|
||||
status={etaStatus}
|
||||
statusColor={{ color: item?.estimatedTime ? COLORS.TEXT.GREEN : COLORS.TEXT.RED }}
|
||||
buttonLabel={item?.estimatedTime || anomalyType === AnomalyType.RESOLVED ? 'View' : 'Fill'}
|
||||
buttonLabel={isEtaFilled ? 'View' : 'Fill'}
|
||||
/>
|
||||
<BottomSheetWrapper
|
||||
visible={showETABottomSheet || showRCABottomSheet}
|
||||
HeaderNode={() => (
|
||||
<BottomsheetHeader title={showRCABottomSheet ? 'RCA' : 'ETA'} handleClose={handleClose} />
|
||||
<View style={GenericStyles.mb8}>
|
||||
<BottomsheetHeader
|
||||
title={showRCABottomSheet ? ANOMALY_ACTIONS.RCA : ANOMALY_ACTIONS.ETA}
|
||||
handleClose={handleClose}
|
||||
showUpdateButton={anomalyType === AnomalyType.OPEN}
|
||||
handleUpdateButtonClick={handleUpdateButtonClick}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
allowBackdropClose={true}
|
||||
setVisible={handleClose}
|
||||
heightPercentage={showETABottomSheet ? 26 : 30}
|
||||
heightPercentage={showETABottomSheet ? 28 : 32}
|
||||
>
|
||||
<ViewRcaEtaDetails showETABottomSheet={showETABottomSheet} />
|
||||
<ScrollView>
|
||||
<Pressable>
|
||||
<ViewRcaEtaDetails showETABottomSheet={showETABottomSheet} selectedAnomalyId={selectedAnomalyId}/>
|
||||
</Pressable>
|
||||
</ScrollView>
|
||||
</BottomSheetWrapper>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -23,12 +23,18 @@ const SingleAnomalyDetails = (props: ISingleAnomalyDetailsProps) => {
|
||||
<View>
|
||||
<Text style={styles.textLabel}>Created on</Text>
|
||||
<Text style={styles.date}>
|
||||
{sanitizeString(dateFormat(new Date(item?.createdAt as string), BUSINESS_DATE_FORMAT))}
|
||||
{sanitizeString(
|
||||
item?.createdAt
|
||||
? dateFormat(new Date(item?.createdAt as string), BUSINESS_DATE_FORMAT)
|
||||
: ''
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
{anomalyType === AnomalyType.OPEN ? (
|
||||
<View style={styles.daysTillEscalationText}>
|
||||
<Text style={[GenericStyles.mr4, styles.textLabel, styles.textAlign, styles.w60]}>Days till Escalation</Text>
|
||||
<Text style={[GenericStyles.mr4, styles.textLabel, styles.textAlign, styles.w60]}>
|
||||
Days till Escalation
|
||||
</Text>
|
||||
<DaysTillEscalationComponent
|
||||
dayTillEscalation={item?.daysRemaining as number}
|
||||
progressPercent={item?.escalationPercentage as number}
|
||||
@@ -38,7 +44,11 @@ const SingleAnomalyDetails = (props: ISingleAnomalyDetailsProps) => {
|
||||
<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))}
|
||||
{sanitizeString(
|
||||
item?.resolution?.at
|
||||
? dateFormat(new Date(item?.resolution?.at), BUSINESS_DATE_FORMAT)
|
||||
: ''
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -1,57 +1,87 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import React, { useEffect } from 'react';
|
||||
import { View, StyleSheet, ActivityIndicator } from 'react-native';
|
||||
import RcaEtaQuestionRenderer from './RcaEtaQuestionRenderer';
|
||||
import dayjs from 'dayjs';
|
||||
import { BUSINESS_DATE_FORMAT } from '@rn-ui-lib/utils/dates';
|
||||
import { useAppSelector } from '@hooks';
|
||||
import { useAppDispatch, useAppSelector } from '@hooks';
|
||||
import { GenericStyles } from '@rn-ui-lib/styles';
|
||||
import { DATE_TIME_FORMAT, RCA_ETA_FORM_QUESTIONS } from './constants';
|
||||
import { ActivityLog, ANOMALY_ACTIONS, EtaActivityLog, RcaActivityLog } from './interfaces';
|
||||
import { COLORS } from '@rn-ui-lib/colors';
|
||||
import { getActivityLogs } from './AnomalyTrackerActions';
|
||||
|
||||
const ViewRcaEtaDetails = (props: { showETABottomSheet: boolean }) => {
|
||||
const { showETABottomSheet } = props;
|
||||
const anomalyDetails = useAppSelector((state) => state?.anomalyTracker?.anomalyDetails?.data);
|
||||
const { estimatedTime } = anomalyDetails || {};
|
||||
const rcaDetails = useAppSelector((state) => state?.anomalyTracker?.acitvityLogs?.data); //To do same for eta after backend changes
|
||||
const { details, type } = rcaDetails?.[0] || {};
|
||||
const ViewRcaEtaDetails = (props: { showETABottomSheet: boolean; selectedAnomalyId: string }) => {
|
||||
const { showETABottomSheet, selectedAnomalyId } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const activityLogs: ActivityLog[] | undefined = useAppSelector(
|
||||
(state) => state?.anomalyTracker?.acitvityLogs?.data
|
||||
);
|
||||
const activityLogsLoading = useAppSelector(
|
||||
(state) => state?.anomalyTracker?.acitvityLogs?.loading
|
||||
);
|
||||
|
||||
if (showETABottomSheet)
|
||||
useEffect(() => {
|
||||
dispatch(getActivityLogs(selectedAnomalyId));
|
||||
}, []);
|
||||
|
||||
if (activityLogsLoading)
|
||||
return (
|
||||
<View style={[GenericStyles.mh16, styles.mv24]}>
|
||||
<RcaEtaQuestionRenderer questionText={RCA_ETA_FORM_QUESTIONS.FILLED_BY} answerText={''} />
|
||||
<RcaEtaQuestionRenderer
|
||||
questionText={RCA_ETA_FORM_QUESTIONS.DATE}
|
||||
answerText={dayjs(estimatedTime?.at)?.format(BUSINESS_DATE_FORMAT)}
|
||||
/>
|
||||
<RcaEtaQuestionRenderer
|
||||
questionText={RCA_ETA_FORM_QUESTIONS.REASON}
|
||||
answerText={estimatedTime?.reason}
|
||||
/>
|
||||
<View style={[GenericStyles.fill, GenericStyles.centerAlignedRow, styles.position]}>
|
||||
<ActivityIndicator size="large" color={COLORS.BASE.BLUE} />
|
||||
</View>
|
||||
);
|
||||
|
||||
if (showETABottomSheet) {
|
||||
const etaData = activityLogs?.find(
|
||||
(item) => item.type === ANOMALY_ACTIONS.ETA
|
||||
) as EtaActivityLog;
|
||||
const { details: etaDetails } = etaData || {};
|
||||
|
||||
return (
|
||||
<View style={[GenericStyles.mh16, styles.mv24]}>
|
||||
<RcaEtaQuestionRenderer
|
||||
questionText={RCA_ETA_FORM_QUESTIONS.FILLED_BY}
|
||||
answerText={etaDetails?.name}
|
||||
/>
|
||||
<RcaEtaQuestionRenderer
|
||||
questionText={RCA_ETA_FORM_QUESTIONS.DATE}
|
||||
answerText={etaDetails?.at ? dayjs(etaDetails?.at)?.format(BUSINESS_DATE_FORMAT) : ''}
|
||||
/>
|
||||
<RcaEtaQuestionRenderer
|
||||
questionText={RCA_ETA_FORM_QUESTIONS.REASON}
|
||||
answerText={etaDetails?.reason}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const rcaData = activityLogs?.find((item) => item.type === ANOMALY_ACTIONS.RCA) as RcaActivityLog;
|
||||
const { details: rcaDetails } = rcaData || {};
|
||||
|
||||
return (
|
||||
<View style={[GenericStyles.mh16, styles.mv24]}>
|
||||
<View style={[GenericStyles.mh16, styles.mv8]}>
|
||||
<RcaEtaQuestionRenderer
|
||||
questionText={RCA_ETA_FORM_QUESTIONS.FILLED_BY}
|
||||
answerText={details?.name}
|
||||
answerText={rcaDetails?.name}
|
||||
/>
|
||||
<RcaEtaQuestionRenderer
|
||||
questionText={RCA_ETA_FORM_QUESTIONS.DATE_TIME}
|
||||
answerText={dayjs(details?.at).format(DATE_TIME_FORMAT)}
|
||||
answerText={rcaDetails?.at ? dayjs(rcaDetails?.at).format(DATE_TIME_FORMAT) : ''}
|
||||
/>
|
||||
<RcaEtaQuestionRenderer
|
||||
questionText={RCA_ETA_FORM_QUESTIONS.TYPE}
|
||||
answerText={details?.type}
|
||||
answerText={rcaDetails?.reason}
|
||||
/>
|
||||
<RcaEtaQuestionRenderer
|
||||
questionText={RCA_ETA_FORM_QUESTIONS.COMMENTS}
|
||||
answerText={details?.comment}
|
||||
answerText={rcaDetails?.comment || ''}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const styles = StyleSheet.create({
|
||||
mv24: { paddingVertical: 24 },
|
||||
mv8: { marginVertical: 8 },
|
||||
position: { flex: 1, marginTop: 62 },
|
||||
});
|
||||
|
||||
export default ViewRcaEtaDetails;
|
||||
|
||||
@@ -40,3 +40,15 @@ export const RCA_ETA_FORM_QUESTIONS = {
|
||||
DATE: 'Date',
|
||||
REASON: 'Reason',
|
||||
};
|
||||
|
||||
export const VALIDATION_ERROR_MESSAGES = {
|
||||
REQUIRED: 'This field is required',
|
||||
MIN_LENGTH: 'Enter at least 20 characters',
|
||||
REASON_REQUIRED: 'Please select a reason',
|
||||
DATE: 'Please select a date',
|
||||
};
|
||||
|
||||
export const FORM_STATUS = {
|
||||
FILLED: 'Filled',
|
||||
UNFILLED: 'Unfilled',
|
||||
};
|
||||
|
||||
@@ -7,15 +7,108 @@ export interface AnomalyItem {
|
||||
subtype?: string;
|
||||
priority?: string;
|
||||
anomalyDetectedOn?: string;
|
||||
raisedFor?: {};
|
||||
raisedFor?: {
|
||||
name: string;
|
||||
id: string;
|
||||
allocations: number;
|
||||
agencyCode: string;
|
||||
agencyName: string;
|
||||
phoneNumber: string;
|
||||
};
|
||||
anomalyReferenceId?: string;
|
||||
resolution?: {};
|
||||
estimatedTime?: {};
|
||||
rca?: {};
|
||||
resolution?: {
|
||||
at: string;
|
||||
type: string;
|
||||
name: string;
|
||||
};
|
||||
estimatedTime?: IEstimatedTime;
|
||||
rca?: IRca;
|
||||
resolutionTime?: string;
|
||||
resolutionType?: string;
|
||||
}
|
||||
|
||||
export interface PagesData {
|
||||
pageNo: number;
|
||||
totalPages: number;
|
||||
pageSize: number;
|
||||
totalElements: number;
|
||||
}
|
||||
|
||||
export interface AnomalyResponse {
|
||||
data: AnomalyItem[];
|
||||
pages: PagesData;
|
||||
}
|
||||
|
||||
export interface AnomalyCardHeaderProps {
|
||||
item: AnomalyItem;
|
||||
}
|
||||
|
||||
export interface IEstimatedTime {
|
||||
at: string;
|
||||
by: string;
|
||||
reason: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface IRca {
|
||||
at: string;
|
||||
by: string;
|
||||
}
|
||||
|
||||
export interface ActivityDetails {
|
||||
name: string;
|
||||
at: string;
|
||||
by: string;
|
||||
reason: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface RcaActivityLog {
|
||||
type: ANOMALY_ACTIONS.RCA;
|
||||
details: ActivityDetails;
|
||||
}
|
||||
|
||||
export interface EtaActivityLog {
|
||||
type: ANOMALY_ACTIONS.ETA;
|
||||
details: ActivityDetails;
|
||||
}
|
||||
|
||||
export type ActivityLog = RcaActivityLog | EtaActivityLog;
|
||||
|
||||
export interface IRcaEtaContainerProps {
|
||||
item: AnomalyItem;
|
||||
anomalyType: string;
|
||||
}
|
||||
|
||||
export interface IEtaFormPayload {
|
||||
eta: {
|
||||
at: string;
|
||||
reason: string;
|
||||
};
|
||||
}
|
||||
export interface IRcaFormPayload {
|
||||
rca: {
|
||||
comment: string;
|
||||
reason: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export enum ANOMALY_ACTIONS {
|
||||
RCA = 'RCA',
|
||||
ETA = 'ETA',
|
||||
}
|
||||
|
||||
export interface IEtaFormData {
|
||||
at: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface IRcaFormData {
|
||||
comment: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
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, {
|
||||
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)
|
||||
.then((response) => {
|
||||
if (response.status === API_STATUS_CODE.OK) {
|
||||
dispatch(setActivityLogs(response?.data));
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
logError(err);
|
||||
})
|
||||
.finally(() => dispatch(setActivityLogsLoading(false)));
|
||||
};
|
||||
@@ -20,11 +20,16 @@ const InternalAgentDashboard = () => {
|
||||
const totalClosedAnomalies =
|
||||
useAppSelector((state) => state?.anomalyTracker?.closedAnomaliesList?.pages?.totalElements) ||
|
||||
0;
|
||||
const enableFieldAppAnomalyTracker = useAppSelector(
|
||||
(state) => state?.user?.featureFlags?.enableFieldAppAnomalyTracker
|
||||
);
|
||||
const isOpenOrClosedAnomaliesPresent = totalOpenAnomalies + totalClosedAnomalies > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpenOrClosedAnomaliesPresent ? <AnomalyOverviewCard /> : null}
|
||||
{enableFieldAppAnomalyTracker && isOpenOrClosedAnomaliesPresent ? (
|
||||
<AnomalyOverviewCard />
|
||||
) : null}
|
||||
<PerformanceOverview performanceOverviewData={internalAgentPerformanceOverview} />
|
||||
<PerformanceMeter />
|
||||
<InternalAgentPerformanceCard />
|
||||
|
||||
@@ -16,7 +16,7 @@ import { addClickstreamEvent } from '../../services/clickstreamEventService';
|
||||
import DashboardHeader from './DashboardHeader';
|
||||
import ExternalAgentDashboard from './ExternalAgentDashboard';
|
||||
import InternalAgentDashboard from './InternalAgentDashboard';
|
||||
import { getAnomalyDetails } from './AnomalyTracker/utils';
|
||||
import { getAnomalyDetails } from './AnomalyTracker/AnomalyTrackerActions';
|
||||
import { AnomalyType } from './AnomalyTracker/constants';
|
||||
|
||||
const Dashboard = () => {
|
||||
@@ -32,15 +32,14 @@ const Dashboard = () => {
|
||||
dispatch(
|
||||
getPerformanceMetrics(Object.keys(caseDetailsIds ?? {}), isExternalAgent, setIsLoading)
|
||||
);
|
||||
// TODO: Uncomment when api is added by backend
|
||||
// if (!isExternalAgent) {
|
||||
// dispatch(getAnomalyDetails(AnomalyType.OPEN, {}, false));
|
||||
// dispatch(getAnomalyDetails(AnomalyType.RESOLVED, {}, false));
|
||||
// }
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAgentPerformanceMetrics();
|
||||
if (!isExternalAgent) {
|
||||
dispatch(getAnomalyDetails(AnomalyType.OPEN, false));
|
||||
dispatch(getAnomalyDetails(AnomalyType.RESOLVED, false));
|
||||
}
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_PERFORMANCE_DASHBOARD_PAGE_LOAD, {});
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ import OtherAddresses from '@screens/addresses/otherAddresses/OtherAddresses';
|
||||
import Escalations from '@screens/escalations/Escalations';
|
||||
import TopAddressMapView from '@screens/MapView/TopAddress';
|
||||
import AnomalyTracker from '@screens/Dashboard/AnomalyTracker/AnomlayTracker';
|
||||
import RcaForm from '@screens/Dashboard/AnomalyTracker/Forms.tsx/RcaForm';
|
||||
import EtaForm from '@screens/Dashboard/AnomalyTracker/Forms.tsx/EtaForm';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
@@ -62,7 +64,9 @@ export enum CaseDetailStackEnum {
|
||||
MAP_VIEW_TOP_ADDRESS = 'mapViewTopAddress',
|
||||
OTHER_ADDRESSES = 'OtherAddresses',
|
||||
ESCALATIONS = 'Escalations',
|
||||
ANOMALY_TRACKER = 'ANOMALY_TRACKER',
|
||||
ANOMALY_TRACKER = "anomalyTracker",
|
||||
RCA_FORM = 'rcaForm',
|
||||
ETA_FORM = 'etaForm',
|
||||
}
|
||||
|
||||
const CaseDetailStack = () => {
|
||||
@@ -130,6 +134,8 @@ const CaseDetailStack = () => {
|
||||
<Stack.Screen name={CaseDetailStackEnum.OTHER_ADDRESSES} component={OtherAddresses} />
|
||||
<Stack.Screen name={CaseDetailStackEnum.MAP_VIEW_TOP_ADDRESS} component={TopAddressMapView} />
|
||||
<Stack.Screen name={CaseDetailStackEnum.ANOMALY_TRACKER} component={AnomalyTracker} />
|
||||
<Stack.Screen name={CaseDetailStackEnum.RCA_FORM} component={RcaForm} />
|
||||
<Stack.Screen name={CaseDetailStackEnum.ETA_FORM} component={EtaForm} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -127,6 +127,9 @@ const getRevivalNotificationHeading = {
|
||||
|
||||
const NotificationItem: React.FC<INotificationProps> = ({ data }) => {
|
||||
const { id, clickable, template, scheduledAt, collectionCaseId, params, headerLabel } = data;
|
||||
const enableFieldAppAnomalyTracker = useAppSelector(
|
||||
(state) => state?.user?.featureFlags?.enableFieldAppAnomalyTracker
|
||||
);
|
||||
if (headerLabel) {
|
||||
return (
|
||||
<Text dark style={[GenericStyles.ph24, GenericStyles.pv12, styles.darkBg]} bold>
|
||||
@@ -200,6 +203,15 @@ const NotificationItem: React.FC<INotificationProps> = ({ data }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (templateName === NotificationTypes.ANOMALY_TRACKER_DETECTION) {
|
||||
if (enableFieldAppAnomalyTracker) {
|
||||
navigateToScreen(PageRouteEnum.CASE_DETAIL_STACK, {
|
||||
screen: CaseDetailStackEnum.ANOMALY_TRACKER,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const agentRevivalNotificationAction = AgentRevivalNotificationTemplateActionMap[templateName];
|
||||
if (agentRevivalNotificationAction) {
|
||||
handleRevivalNotificationNavigation(agentRevivalNotificationAction as string, id);
|
||||
|
||||
Reference in New Issue
Block a user