NTP-14132 | add new feedback restrictions (#1022)

This commit is contained in:
Aishwarya Srivastava
2024-12-03 18:17:46 +05:30
committed by GitHub
17 changed files with 159 additions and 30 deletions

View File

@@ -113,8 +113,8 @@ def jscFlavor = 'org.webkit:android-jsc:+'
def enableHermes = project.ext.react.get("enableHermes", false);
def VERSION_CODE = 218
def VERSION_NAME = "2.15.4"
def VERSION_CODE = 219
def VERSION_NAME = "2.15.5"
android {
namespace "com.avapp"

View File

@@ -1,7 +1,7 @@
{
"name": "AV_APP",
"version": "2.15.4",
"buildNumber": "218",
"version": "2.15.5",
"buildNumber": "219",
"private": true,
"scripts": {
"android:dev": "yarn move:dev && react-native run-android",

View File

@@ -57,6 +57,8 @@ import { getSyncUrl } from '@services/syncJsonDataToBe';
import { handleCheckAndUpdatePullToRefreshStateForNearbyCases } from '@screens/allCases/utils';
import { getWifiDetailsSyncUrl } from '@components/utlis/WifiDetails';
import useFirestoreUpdates from '@hooks/useFirestoreUpdates';
import { handlePostOperativeHourActivity } from '@screens/caseDetails/utils/postOperationalHourActions';
import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice';
export enum FOREGROUND_TASKS {
GEOLOCATION = 'GEOLOCATION',
@@ -104,6 +106,7 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
if (timestamp) {
const isTimeDifferenceLess = isTimeDifferenceWithinRange(timestamp, 5);
dispatch(setIsTimeSynced(isTimeDifferenceLess));
dispatch(setPostOperationalHourRestrictions(handlePostOperativeHourActivity(timestamp)));
}
} catch (e: any) {
logError(e, 'Error during fetching timestamp from server.');

View File

@@ -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,7 @@ 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';
interface IGeolocationAddress {
address: IGeolocation;
@@ -104,7 +105,9 @@ const GeolocationAddress: React.FC<IGeolocationAddress> = ({
);
return { addressDate, addressTime, lastFeedbackTimestampDate };
}, [lastFeedbackForGeolocation, timestamp]);
const addingNewFeedbackDisabled = useAppSelector(
(state) => state?.postOperationalHourRestrictionsSlice?.postOperationalHourRestrictions
);
const handleCloseRouting = () => handlePageRouting?.(CaseDetailStackEnum.ADDRESS_GEO);
const handleAddFeedback = () => {
@@ -297,10 +300,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 +367,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;

View File

@@ -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';
@@ -41,9 +41,11 @@ import NavigationHeader, { Icon } from '../../../RN-UI-LIB/src/components/Naviga
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { useNavigation, useRoute } from '@react-navigation/native';
import { NUDGE_BOTTOM_SHEET_DEFAULT_STATE } from './constants';
import {useBackHandler} from "@hooks/useBackHandler";
import { useBackHandler } from '@hooks/useBackHandler';
import { CALLING_NUDGE } from '@screens/caseDetails/CallingFlow/constants';
import { isFunction } from '@components/utlis/commonFunctions';
import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice';
import { getSyncTime } from '@hooks/capturingApi';
interface IWidget {
route: {
@@ -197,7 +199,7 @@ const Widget: React.FC<IWidget> = (props) => {
journey: journey,
caseId,
handleCloseRouting,
from
from,
});
};
@@ -257,11 +259,13 @@ const Widget: React.FC<IWidget> = (props) => {
widgetId: name,
}
);
fetchLocation().then((location) => {
fetchLocation()
.then((location) => {
if (location) {
return handleSubmitJourney(data, location);
}
}).catch((err) => {
})
.catch((err) => {
setIsSubmitting(false);
});
};
@@ -279,6 +283,13 @@ const Widget: React.FC<IWidget> = (props) => {
suspiciousFeedbackMessage: errObj?.errorCode,
});
}
if (errObj?.statusCode === API_STATUS_CODE.POST_OPERATIVE_HOURS_ACTIVITY) {
toast({
type: 'error',
text1: ToastMessages.POST_OPERATIVE_HOURS_ACTIVITY,
});
navigateToScreen(CaseDetailStackEnum.COLLECTION_CASE_DETAIL, { caseId });
}
};
const handleSubmitJourney = async (data: any, coords: Geolocation.GeoCoordinates) => {
@@ -307,12 +318,12 @@ const Widget: React.FC<IWidget> = (props) => {
unSyncedCase,
nudgeBottomSheetDetails?.showNudgeBottomSheet
);
if(!transformedPayload?.data?.answers) {
if (!transformedPayload?.data?.answers) {
toast({
type: 'error',
text1: ToastMessages.FEEDBACK_IMAGE_NOT_FOUND,
});
onErrorSubmit({}, transformedPayload)
onErrorSubmit({}, transformedPayload);
return;
}
dispatch(
@@ -465,7 +476,7 @@ const Widget: React.FC<IWidget> = (props) => {
style={[styles.autoFlex, styles.mH16]}
title={isLeaf ? 'Submit' : 'Next'}
showLoader={isLeaf && isSubmitting}
disabled={isLeaf && isSubmitting}
disabled={(isLeaf && isSubmitting)}
onPress={
isLeaf
? handleSubmit(submitJourneyWithGeoLocation, onError)

View File

@@ -227,6 +227,7 @@ export const API_STATUS_CODE = {
INTERNAL_SERVER_ERROR: 500,
TOO_MANY_REQUESTS: 429,
GONE: 410,
POST_OPERATIVE_HOURS_ACTIVITY: 451
};
export const UNAUTHORIZED_VALUES = [API_STATUS_CODE.UNAUTHORIZED, API_STATUS_CODE.FORBIDDEN];

View 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;

View File

@@ -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,7 @@ 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 { handleClickPostOperativeHours } from './utils/operativeHourUtils';
interface IAddressItem {
addressItem: IAddress;
@@ -58,7 +59,9 @@ function AddressItem({
);
const dispatch = useAppDispatch();
const addingNewFeedbackDisabled = useAppSelector(
(state) => state?.postOperationalHourRestrictionsSlice?.postOperationalHourRestrictions
);
let relativeDistanceBwLatLong = 0;
const addressGeolocationCoordinated: IGeolocationCoordinate = {
@@ -233,10 +236,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 +297,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;

View File

@@ -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,7 @@ import AddressSource from './AddressSource';
import relativeDistanceFormatter from './utils/relativeDistanceFormatter';
import CopyIcon from '@rn-ui-lib/icons/CopyIcon';
import { copyAddressToClipboard } from './utils/copyAddressText';
import { handleClickPostOperativeHours } from './utils/operativeHourUtils';
interface IAddressItem {
addressItem: IAddress;
@@ -118,7 +119,8 @@ function SimilarAddressItem({
}
}
};
const addingNewFeedbackDisabled = useAppSelector((state) => state?.postOperationalHourRestrictionsSlice?.postOperationalHourRestrictions);
const copyAddress = () => {
copyAddressToClipboard(addressItem?.addressText, caseId);
};
@@ -193,11 +195,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 Feedback</Text>
</TouchableOpacity>
) : null}
{showOldFeedbackBtn ? (
@@ -251,6 +253,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,

View File

@@ -0,0 +1,16 @@
import { COLORS } from '@rn-ui-lib/colors';
import { toast } from '@rn-ui-lib/components/toast';
import InfoIcon from '@rn-ui-lib/icons/InfoIcon';
import { ToastMessages } from '@screens/allCases/constants';
export const handleClickPostOperativeHours = () => {
toast({
type: 'custom',
text1: ToastMessages.DISABLE_ADD_FEEDBACK_AFTER_POST_OPERATIVE_HOURS,
props: {
customBackgroundColor: COLORS.BACKGROUND.ORANGE_LIGHT,
customIcon: <InfoIcon color={COLORS.TEXT.ORANGE_DARK}/>
},
});
return;
};

View File

@@ -95,6 +95,8 @@ export const ToastMessages = {
WHATSAPP_SHARE_SUCCESS: 'Document shared successfully via WhatsApp',
WHATSAPP_SHARE_FAILURE: 'Document sharing failed via WhatsApp',
ERROR_FETCHING_MULTILINGUAL_DOC : 'Error fetching multilingual document',
POST_OPERATIVE_HOURS_ACTIVITY: 'Submission failed! You can add feedback only during work hours (8 AM to 6:55 PM)',
DISABLE_ADD_FEEDBACK_AFTER_POST_OPERATIVE_HOURS: 'You will be able to add feedback only during work hours (8 AM to 6:55 PM)'
};
export enum BOTTOM_TAB_ROUTES {

View File

@@ -26,6 +26,9 @@ import { getSyncTime } from '@hooks/capturingApi';
import ModalWrapperForAlfredV2 from '@common/ModalWrapperForAlfredV2';
import IdCardApproved from '@screens/AgentIdCard/IdCardStatus/IdCardApproved';
import CallingFeedbackNudgeBottomSheet from '@screens/caseDetails/CallingFlow/BottomSheets/CallingFeedbackNudgeBottomSheet';
import { handlePostOperativeHourActivity } from '@screens/caseDetails/utils/postOperationalHourActions';
import { setPostOperationalHourRestrictions } from '@reducers/postOperationalHourRestrictionsSlice';
import { logError } from '@components/utlis/errorUtils';
function AuthRouter() {
const dispatch = useAppDispatch();
@@ -67,7 +70,15 @@ function AuthRouter() {
}
};
const CHECK_ATTENDANCE_TIME = 10000;
const syncTimeToCheckPostOperativeHours = async () => {
try {
const timestamp = await getSyncTime();
const getActivityStatus = handlePostOperativeHourActivity(timestamp);
dispatch(setPostOperationalHourRestrictions(getActivityStatus));
} catch (error) {
logError(error as Error);
}
};
useEffect(() => {
const appStateChange = AppState.addEventListener('change', async (change) => {
if (change !== 'active') return;
@@ -82,6 +93,7 @@ function AuthRouter() {
}
alfredSetCodePushVersion(getAppVersion());
});
syncTimeToCheckPostOperativeHours();
return () => {
appStateChange.remove();
};

View File

@@ -14,6 +14,7 @@ import React, { useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import { CaseDetailStackEnum } from './CaseDetailStack';
import { captureLatestDeviceLocation } from '@components/form/services/geoLocation.service';
import { handleClickPostOperativeHours } from '@screens/addressGeolocation/utils/operativeHourUtils';
interface ICollectionCaseDetailFooter {
caseId: string;
@@ -29,7 +30,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,
@@ -106,7 +109,7 @@ const CollectionCaseDetailFooter = ({ caseId, notificationId }: ICollectionCaseD
testID={'test_call_customer'}
/>
<Button
style={[styles.feedbackButton]}
style={[ addingNewFeedbackDisabled ? styles.disabledButton : styles.feedbackButton]}
title={preFilledFormData ? 'Continue feedback' : 'Add new feedback'}
rightIcon={
preFilledFormData ? (
@@ -116,8 +119,10 @@ const CollectionCaseDetailFooter = ({ caseId, notificationId }: ICollectionCaseD
) : null
}
variant="primary"
onPress={handleAddFeedback}
testID={'test_add_feedback'}
onPress={addingNewFeedbackDisabled ? handleClickPostOperativeHours : handleAddFeedback}
testID="test_add_feedback"
pressableWidthChange={!addingNewFeedbackDisabled}
opacityChangeOnPress={!addingNewFeedbackDisabled}
/>
</View>
);
@@ -132,6 +137,14 @@ export const styles = StyleSheet.create({
flex: 1,
marginLeft: 6,
},
disabledButton: {
flex: 1,
marginLeft: 6,
color: COLORS.TEXT.BLUE,
opacity: 0.5,
backgroundColor: COLORS.BACKGROUND.PRIMARY,
borderColor: COLORS.BORDER.PRIMARY,
},
buttonContainer: {
position: 'absolute',
bottom: 0,

View File

@@ -10,6 +10,7 @@ import { CaseAllocationType } from '../allCases/interface';
import { CaseDetail } from './interface';
import { getFeedbackHistory, uploadImages } from '../../action/caseApiActions';
import { setDocumentInteractionId } from '../../reducer/feedbackImagesSlice';
import { handlePostOperativeHourActivity } from './utils/postOperationalHourActions';
export const getUnSyncedCase = (updatedCaseDetail: CaseDetail | undefined): any => {
const caseId = updatedCaseDetail?.id;
@@ -72,6 +73,9 @@ const interactionsHandler = () => {
if (!modifiedCaseItem?.data?.answers) {
return;
}
const feedbackSubmissionTime = modifiedCaseItem?.data?.timestamp;
if (handlePostOperativeHourActivity(feedbackSubmissionTime)) return;
dispatch(
syncCaseDetail(modifiedCaseItem, {
onSuccessCB: (_, interactionId) =>

View File

@@ -0,0 +1,12 @@
export const handlePostOperativeHourActivity = (timestamp: number)=> {
if (timestamp) {
const todaysDate = new Date(timestamp);
const loginStartTime = new Date(todaysDate).setHours(8, 0, 0, 0);
const loginEndTime = new Date(todaysDate).setHours(18, 55, 0, 0);
const currentTime = todaysDate.getTime();
const isPostOperational = currentTime < loginStartTime || currentTime > loginEndTime;
return isPostOperational;
}
return false;
};

View File

@@ -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 = {