NTP-14132| restrict feedback post operative hours
This commit is contained in:
@@ -106,6 +106,7 @@ export const syncCaseDetail =
|
||||
type: 'success',
|
||||
text1: ToastMessages.FEEDBACK_SUCCESSFUL,
|
||||
});
|
||||
//TODO: Aishwarya
|
||||
if (callbacks?.onSuccessCB != null && typeof callbacks?.onSuccessCB === 'function') {
|
||||
callbacks?.onSuccessCB(payload.data.answers, interactionId);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Linking, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import {
|
||||
GeolocationSource,
|
||||
GeolocationSourceMap,
|
||||
@@ -31,6 +31,9 @@ import { IAddressFeedback } from '../../../reducer/addressSlice';
|
||||
import Tag, { TagVariant } from '@rn-ui-lib/components/Tag';
|
||||
import ArrowSolidIcon from '@rn-ui-lib/icons/ArrowSolidIcon';
|
||||
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
|
||||
import { handleClickPostOperativeHours } from '@screens/addressGeolocation/utils/operativeHourUtils';
|
||||
import { handleTimeSync } from '@screens/caseDetails/utils/postOperationalHourActions';
|
||||
import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice';
|
||||
|
||||
interface IGeolocationAddress {
|
||||
address: IGeolocation;
|
||||
@@ -104,9 +107,21 @@ const GeolocationAddress: React.FC<IGeolocationAddress> = ({
|
||||
);
|
||||
return { addressDate, addressTime, lastFeedbackTimestampDate };
|
||||
}, [lastFeedbackForGeolocation, timestamp]);
|
||||
|
||||
const addingNewFeedbackDisabled = useAppSelector(
|
||||
(state) => state.postOperationalHourRestrictionsSlice.postOperationalHourRestrictions
|
||||
);
|
||||
const handleCloseRouting = () => handlePageRouting?.(CaseDetailStackEnum.ADDRESS_GEO);
|
||||
|
||||
useEffect(() => {
|
||||
const syncTime = async () => {
|
||||
const getCurrentTime = await handleTimeSync();
|
||||
if (getCurrentTime?.isPostOperational) {
|
||||
dispatch(setPostOperationalHourRestrictions(getCurrentTime?.isPostOperational));
|
||||
}
|
||||
};
|
||||
syncTime();
|
||||
const intervalId = setInterval(syncTime, 180000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
const handleAddFeedback = () => {
|
||||
if (!caseId) {
|
||||
return;
|
||||
@@ -297,10 +312,10 @@ const GeolocationAddress: React.FC<IGeolocationAddress> = ({
|
||||
{showAddFeedback ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleAddFeedback}
|
||||
onPress={addingNewFeedbackDisabled? handleClickPostOperativeHours: handleAddFeedback}
|
||||
style={GenericStyles.ml20}
|
||||
>
|
||||
<Text style={styles.openMapBtn} bold>
|
||||
<Text style={addingNewFeedbackDisabled? styles.disabledButton : styles.openMapBtn} bold>
|
||||
Add feedback
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
@@ -364,6 +379,13 @@ const styles = StyleSheet.create({
|
||||
tagText: {
|
||||
lineHeight: 18,
|
||||
},
|
||||
disabledButton: {
|
||||
fontSize: 13,
|
||||
lineHeight: 20,
|
||||
color: COLORS.TEXT.BLUE,
|
||||
paddingVertical: 4,
|
||||
opacity: 0.5
|
||||
},
|
||||
});
|
||||
|
||||
export default GeolocationAddress;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ScrollView, StyleSheet, View } from 'react-native';
|
||||
import Geolocation from 'react-native-geolocation-service';
|
||||
@@ -44,6 +44,8 @@ import { NUDGE_BOTTOM_SHEET_DEFAULT_STATE } from './constants';
|
||||
import {useBackHandler} from "@hooks/useBackHandler";
|
||||
import { CALLING_NUDGE } from '@screens/caseDetails/CallingFlow/constants';
|
||||
import { isFunction } from '@components/utlis/commonFunctions';
|
||||
import { handleTimeSync } from '@screens/caseDetails/utils/postOperationalHourActions';
|
||||
import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice';
|
||||
|
||||
interface IWidget {
|
||||
route: {
|
||||
@@ -98,9 +100,22 @@ const Widget: React.FC<IWidget> = (props) => {
|
||||
}
|
||||
setIsJourneyFirstScreen(isFirst);
|
||||
}, [templateData, name]);
|
||||
|
||||
|
||||
const addingNewFeedbackDisabled = useAppSelector((state) => state.postOperationalHourRestrictionsSlice.postOperationalHourRestrictions);
|
||||
const [isSubmitButtonDiabled, setIsSubmitButtonDiabled] = useState(false);
|
||||
useEffect(() => {
|
||||
const syncTime = async () => {
|
||||
const getCurrentTime = await handleTimeSync();
|
||||
if (getCurrentTime?.isPostOperational) {
|
||||
dispatch(setPostOperationalHourRestrictions(getCurrentTime?.isPostOperational));
|
||||
}
|
||||
};
|
||||
syncTime();
|
||||
const intervalId = setInterval(syncTime, 15000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
const handleBackPress = useCallback(() => {
|
||||
if (isSubmitting) {
|
||||
if (isSubmitting && ! addingNewFeedbackDisabled) {
|
||||
toast({
|
||||
type: 'info',
|
||||
text1: ToastMessages.FEEDBACK_SUBMISSION_UNDER_PROCESS,
|
||||
@@ -112,6 +127,13 @@ const Widget: React.FC<IWidget> = (props) => {
|
||||
}, [isSubmitting]);
|
||||
|
||||
useBackHandler(handleBackPress);
|
||||
const handleDisabledButtonClick = () => {
|
||||
setIsSubmitButtonDiabled(true);
|
||||
toast({
|
||||
type: 'error',
|
||||
text1: 'Submission failed! You can add feedback only during work hours (8 AM to 7 PM)',
|
||||
})
|
||||
};
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -366,7 +388,7 @@ const Widget: React.FC<IWidget> = (props) => {
|
||||
}, []);
|
||||
|
||||
const handleCloseIconPress = () => {
|
||||
if (isSubmitting) {
|
||||
if (isSubmitting && !addingNewFeedbackDisabled) {
|
||||
toast({
|
||||
type: 'info',
|
||||
text1: ToastMessages.FEEDBACK_SUBMISSION_UNDER_PROCESS,
|
||||
@@ -464,11 +486,13 @@ const Widget: React.FC<IWidget> = (props) => {
|
||||
<Button
|
||||
style={[styles.autoFlex, styles.mH16]}
|
||||
title={isLeaf ? 'Submit' : 'Next'}
|
||||
showLoader={isLeaf && isSubmitting}
|
||||
disabled={isLeaf && isSubmitting}
|
||||
showLoader={isLeaf && isSubmitting && !addingNewFeedbackDisabled}
|
||||
disabled={(isLeaf && isSubmitting) || isSubmitButtonDiabled}
|
||||
onPress={
|
||||
isLeaf
|
||||
? handleSubmit(submitJourneyWithGeoLocation, onError)
|
||||
? (addingNewFeedbackDisabled
|
||||
? handleDisabledButtonClick
|
||||
: handleSubmit(submitJourneyWithGeoLocation, onError))
|
||||
: handleSubmit(onSubmit, onError)
|
||||
}
|
||||
rightIcon={
|
||||
@@ -479,7 +503,7 @@ const Widget: React.FC<IWidget> = (props) => {
|
||||
</View>
|
||||
<NudgeSuspiciousFeedbackBottomSheet
|
||||
caseId={caseId}
|
||||
successBtnLoader={isSubmitting}
|
||||
successBtnLoader={isSubmitting && !addingNewFeedbackDisabled}
|
||||
nudgeBottomSheetDetails={nudgeBottomSheetDetails}
|
||||
setNudgeBottomSheetDetails={setNudgeBottomSheetDetails}
|
||||
successCallbackFn={handleSubmit(
|
||||
|
||||
24
src/reducer/postOperationalHourRestrictionsSlice.ts
Normal file
24
src/reducer/postOperationalHourRestrictionsSlice.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
const initialState = {
|
||||
postOperationalHourRestrictions: false
|
||||
};
|
||||
|
||||
const postOperationalHourRestrictionsSlice = createSlice({
|
||||
name: 'postOperationalHourRestrictions',
|
||||
initialState,
|
||||
reducers: {
|
||||
setPostOperationalHourRestrictions: (state, action) => {
|
||||
state.postOperationalHourRestrictions = action.payload;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setPostOperationalHourRestrictions
|
||||
} = postOperationalHourRestrictionsSlice.actions;
|
||||
|
||||
export default postOperationalHourRestrictionsSlice.reducer;
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { View, StyleSheet, type ViewStyle, TouchableOpacity } from 'react-native';
|
||||
import dayjs from 'dayjs';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
@@ -19,6 +19,9 @@ import { addClickstreamEvent } from '@services/clickstreamEventService';
|
||||
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
|
||||
import CopyIcon from '@rn-ui-lib/icons/CopyIcon';
|
||||
import { copyAddressToClipboard } from './utils/copyAddressText';
|
||||
import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice';
|
||||
import { handleTimeSync } from '@screens/caseDetails/utils/postOperationalHourActions';
|
||||
import { handleClickPostOperativeHours } from './utils/operativeHourUtils';
|
||||
|
||||
interface IAddressItem {
|
||||
addressItem: IAddress;
|
||||
@@ -58,7 +61,9 @@ function AddressItem({
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const addingNewFeedbackDisabled = useAppSelector(
|
||||
(state) => state.postOperationalHourRestrictionsSlice.postOperationalHourRestrictions
|
||||
);
|
||||
let relativeDistanceBwLatLong = 0;
|
||||
|
||||
const addressGeolocationCoordinated: IGeolocationCoordinate = {
|
||||
@@ -69,6 +74,17 @@ function AddressItem({
|
||||
currentGeolocationCoordinates,
|
||||
addressGeolocationCoordinated
|
||||
);
|
||||
useEffect(() => {
|
||||
const syncTime = async () => {
|
||||
const getCurrentTime = await handleTimeSync();
|
||||
if (getCurrentTime?.isPostOperational) {
|
||||
dispatch(setPostOperationalHourRestrictions(getCurrentTime?.isPostOperational));
|
||||
}
|
||||
};
|
||||
syncTime();
|
||||
const intervalId = setInterval(syncTime, 180000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
const handleAddFeedback = () => {
|
||||
if (prefilledAddressScreenTemplate != null) {
|
||||
@@ -233,10 +249,10 @@ function AddressItem({
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleAddFeedback}
|
||||
onPress={addingNewFeedbackDisabled? handleClickPostOperativeHours: handleAddFeedback}
|
||||
hitSlop={{ top: 25, bottom: 25, left: 15, right: 15 }}
|
||||
>
|
||||
<Text style={styles.actionBtn}>Add Feedback</Text>
|
||||
<Text style={addingNewFeedbackDisabled? styles.disabledButton : styles.actionBtn}>Add Feedback</Text>
|
||||
</TouchableOpacity>
|
||||
{lastFeedbackForAddress?.feedbackPresent ? (
|
||||
<TouchableOpacity
|
||||
@@ -294,6 +310,13 @@ const styles = StyleSheet.create({
|
||||
fontSize: 11,
|
||||
color: COLORS.TEXT.LIGHT,
|
||||
},
|
||||
disabledButton: {
|
||||
fontSize: 13,
|
||||
lineHeight: 20,
|
||||
fontWeight: '500',
|
||||
color: COLORS.TEXT.BLUE,
|
||||
opacity: 0.5
|
||||
}
|
||||
});
|
||||
|
||||
export default AddressItem;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { View, StyleSheet, type ViewStyle, TouchableOpacity, Linking } from 'react-native';
|
||||
import Text from '../../../RN-UI-LIB/src/components/Text';
|
||||
import { type IAddress, type IGeolocationCoordinate } from '../../types/addressGeolocation.types';
|
||||
@@ -24,6 +24,9 @@ import AddressSource from './AddressSource';
|
||||
import relativeDistanceFormatter from './utils/relativeDistanceFormatter';
|
||||
import CopyIcon from '@rn-ui-lib/icons/CopyIcon';
|
||||
import { copyAddressToClipboard } from './utils/copyAddressText';
|
||||
import { handleTimeSync } from '@screens/caseDetails/utils/postOperationalHourActions';
|
||||
import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice';
|
||||
import { handleClickPostOperativeHours } from './utils/operativeHourUtils';
|
||||
|
||||
interface IAddressItem {
|
||||
addressItem: IAddress;
|
||||
@@ -118,6 +121,18 @@ function SimilarAddressItem({
|
||||
}
|
||||
}
|
||||
};
|
||||
const addingNewFeedbackDisabled = useAppSelector((state) => state.postOperationalHourRestrictionsSlice.postOperationalHourRestrictions);
|
||||
useEffect(() => {
|
||||
const syncTime = async () => {
|
||||
const getCurrentTime = await handleTimeSync();
|
||||
if (getCurrentTime?.isPostOperational) {
|
||||
dispatch(setPostOperationalHourRestrictions(getCurrentTime?.isPostOperational));
|
||||
}
|
||||
};
|
||||
syncTime();
|
||||
const intervalId = setInterval(syncTime, 180000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
const copyAddress = () => {
|
||||
copyAddressToClipboard(addressItem?.addressText, caseId);
|
||||
@@ -193,11 +208,11 @@ function SimilarAddressItem({
|
||||
{showAddFeedbackBtn ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={handleAddFeedback}
|
||||
onPress={addingNewFeedbackDisabled ? handleClickPostOperativeHours : handleAddFeedback}
|
||||
hitSlop={{ top: 25, bottom: 25, left: 15, right: 15 }}
|
||||
style={GenericStyles.mh8}
|
||||
>
|
||||
<Text style={styles.actionBtn}>Add Feedback</Text>
|
||||
<Text style={addingNewFeedbackDisabled ? styles.disabledButton :styles.actionBtn}>Add Feedbackj</Text>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
{showOldFeedbackBtn ? (
|
||||
@@ -251,6 +266,13 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '500',
|
||||
color: COLORS.TEXT.BLUE,
|
||||
},
|
||||
disabledButton: {
|
||||
fontSize: 13,
|
||||
lineHeight: 20,
|
||||
fontWeight: '500',
|
||||
color: COLORS.TEXT.BLUE,
|
||||
opacity: 0.5
|
||||
},
|
||||
dotStyle: {
|
||||
fontSize: 11,
|
||||
color: COLORS.TEXT.LIGHT,
|
||||
|
||||
10
src/screens/addressGeolocation/utils/operativeHourUtils.ts
Normal file
10
src/screens/addressGeolocation/utils/operativeHourUtils.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { toast } from "@rn-ui-lib/components/toast";
|
||||
|
||||
|
||||
export const handleClickPostOperativeHours = () => {
|
||||
toast({
|
||||
type: 'error',
|
||||
text1: 'You will be able to add feedback only during work hours (8 AM to 7 PM).',
|
||||
});
|
||||
return;
|
||||
};
|
||||
@@ -14,6 +14,9 @@ import React, { useEffect } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import { CaseDetailStackEnum } from './CaseDetailStack';
|
||||
import { captureLatestDeviceLocation } from '@components/form/services/geoLocation.service';
|
||||
import { handleTimeSync } from './utils/postOperationalHourActions';
|
||||
import { handleClickPostOperativeHours } from '@screens/addressGeolocation/utils/operativeHourUtils';
|
||||
import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice';
|
||||
|
||||
interface ICollectionCaseDetailFooter {
|
||||
caseId: string;
|
||||
@@ -29,7 +32,9 @@ const CollectionCaseDetailFooter = ({ caseId, notificationId }: ICollectionCaseD
|
||||
(state: RootState) => state.case.caseForm?.[caseId]?.[TaskTitleUIMapping.COLLECTION_FEEDBACK]
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const addingNewFeedbackDisabled = useAppSelector(
|
||||
(state) => state.postOperationalHourRestrictionsSlice.postOperationalHourRestrictions
|
||||
);
|
||||
const handleCustomerCall = () => {
|
||||
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CALL_CUSTOMER_CLICKED, {
|
||||
caseId: caseId,
|
||||
@@ -77,6 +82,17 @@ const CollectionCaseDetailFooter = ({ caseId, notificationId }: ICollectionCaseD
|
||||
}
|
||||
);
|
||||
};
|
||||
useEffect(() => {
|
||||
const syncTime = async () => {
|
||||
const getCurrentTime = await handleTimeSync();
|
||||
if (getCurrentTime?.isPostOperational) {
|
||||
dispatch(setPostOperationalHourRestrictions(getCurrentTime?.isPostOperational));
|
||||
}
|
||||
};
|
||||
syncTime();
|
||||
const intervalId = setInterval(syncTime, 180000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// If landed via notification action
|
||||
@@ -116,8 +132,9 @@ const CollectionCaseDetailFooter = ({ caseId, notificationId }: ICollectionCaseD
|
||||
) : null
|
||||
}
|
||||
variant="primary"
|
||||
onPress={handleAddFeedback}
|
||||
onPress={addingNewFeedbackDisabled ? handleClickPostOperativeHours : handleAddFeedback}
|
||||
testID={'test_add_feedback'}
|
||||
isClickableButDisabled={addingNewFeedbackDisabled}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
28
src/screens/caseDetails/utils/postOperationalHourActions.tsx
Normal file
28
src/screens/caseDetails/utils/postOperationalHourActions.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { logError } from '@components/utlis/errorUtils';
|
||||
import { getSyncTime } from '@hooks/capturingApi';
|
||||
|
||||
export const getTodaysDate = async () => {
|
||||
const timestamp = await getSyncTime();
|
||||
const date = new Date(timestamp);
|
||||
return date;
|
||||
};
|
||||
|
||||
export const handleTimeSync = async () => {
|
||||
try {
|
||||
const timestamp = await getSyncTime();
|
||||
|
||||
if (timestamp) {
|
||||
const todaysDate = new Date(timestamp);
|
||||
const currentHour = todaysDate.getTime();
|
||||
const loginStartTime = todaysDate.setHours(8, 0, 0);
|
||||
const loginEndTime = todaysDate.setHours(19, 0, 0);
|
||||
|
||||
return {
|
||||
isPostOperational: currentHour < loginStartTime || currentHour > loginEndTime
|
||||
};
|
||||
}
|
||||
} catch (e: any) {
|
||||
logError(e, 'Error during fetching timestamp from server.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ import activeCallSlice from '@reducers/activeCallSlice';
|
||||
import documentsSlice from '@reducers/documentsSlice';
|
||||
import topFeedbacksSlice from '@reducers/topFeedbacksSlice';
|
||||
import escalationSlice from '@reducers/escalationSlice';
|
||||
import postOperationalHourRestrictionsSlice from '@reducers/postOperationalHourRestrictionsSlice';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
case: caseReducer,
|
||||
@@ -70,6 +71,7 @@ const rootReducer = combineReducers({
|
||||
documentsSlice: documentsSlice,
|
||||
topFeedbacks: topFeedbacksSlice,
|
||||
escalationSlice: escalationSlice,
|
||||
postOperationalHourRestrictionsSlice: postOperationalHourRestrictionsSlice
|
||||
});
|
||||
|
||||
const persistConfig = {
|
||||
|
||||
Reference in New Issue
Block a user