NTP-7914 | Feedback Section Revamp (#1009)

This commit is contained in:
Mantri Ramkishor
2024-11-15 17:21:49 +05:30
committed by GitHub
parent f5b0bda6cb
commit 8959d58c88
18 changed files with 447 additions and 145 deletions

View File

@@ -134,8 +134,8 @@ def reactNativeArchitectures() {
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
def VERSION_CODE = 213
def VERSION_NAME = "2.14.13"
def VERSION_CODE = 214
def VERSION_NAME = "2.15.0"
android {
ndkVersion rootProject.ext.ndkVersion

View File

@@ -1,7 +1,7 @@
{
"name": "AV_APP",
"version": "2.14.13",
"buildNumber": "213",
"version": "2.15.0",
"buildNumber": "214",
"private": true,
"scripts": {
"android:dev": "yarn move:dev && react-native run-android",

View File

@@ -1,5 +1,7 @@
import { AppDispatch } from '@store';
import axiosInstance, { ApiKeys, getApiUrl } from '../components/utlis/apiHelper';
import { logError } from '../components/utlis/errorUtils';
import { setTopFeedbacks, setTopFeedbacksLoading } from '@reducers/topFeedbacksSlice';
interface IPastFeedbacksPayload {
loan_account_number: string;
@@ -70,3 +72,28 @@ export const getPastFeedbacksOnAddresses = (pastFeedbackPayload: IPastFeedbacksP
logError(err);
});
};
export const getTopFeedbacks = (loanAccountNumber: string) => (dispatch: AppDispatch) => {
// TODO: Change API Endpoint
const url = getApiUrl(ApiKeys.PAST_FEEDBACK_ON_ADDRESSES);
dispatch(setTopFeedbacksLoading({ loanAccountNumber, isLoading: true }));
return axiosInstance
.get(url, {
params: { loanAccountNumber },
})
.then((response) => {
dispatch(
setTopFeedbacks({
loanAccountNumber,
feedbacks: [
response?.data?.data?.currentMonthFeedbackStatus,
response?.data?.data?.lastMonthFeedbackStatus,
],
})
);
})
.catch((err) => {
dispatch(setTopFeedbacksLoading({ loanAccountNumber, isLoading: false }));
logError(err);
});
};

View File

@@ -0,0 +1,19 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
function NotAttemptedIcon() {
return (
<Svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<Path
d="M14.2292 8.57982C12.0192 8.57982 10.2292 6.78982 10.2292 4.57982C10.2292 4.22982 10.2692 3.88982 10.3692 3.57982H5.10922C4.06922 3.57982 3.19922 4.42982 3.19922 5.48982V16.0998C3.19922 17.1598 4.06922 18.0098 5.10922 18.0098H13.4992C14.5592 18.0098 15.4092 17.1598 15.4092 16.0998V8.40982C15.0392 8.52982 14.6392 8.58982 14.2292 8.58982V8.57982ZM9.25922 13.4798H6.72922C6.44922 13.4798 6.19922 13.2398 6.19922 12.9498C6.19922 12.6598 6.43922 12.4198 6.72922 12.4198H9.25922C9.55922 12.4198 9.78922 12.6498 9.78922 12.9498C9.78922 13.2498 9.55922 13.4798 9.25922 13.4798ZM11.3692 10.3898H6.72922C6.44922 10.3898 6.19922 10.1498 6.19922 9.85982C6.19922 9.56982 6.43922 9.32982 6.72922 9.32982H11.3692C11.6492 9.32982 11.8992 9.55982 11.8992 9.85982C11.8992 10.1598 11.6592 10.3898 11.3692 10.3898ZM13.4992 3.56982H11.8492C11.7092 3.87982 11.6392 4.21982 11.6392 4.56982C11.6392 5.98982 12.7992 7.15982 14.2292 7.15982C14.6492 7.15982 15.0492 7.04982 15.4092 6.85982V5.47982C15.4092 4.41982 14.5592 3.56982 13.4992 3.56982Z"
fill="#969696"
/>
<Path
d="M16.7987 4.57977C16.7987 5.57977 16.2287 6.44977 15.4087 6.86977C15.0487 7.05977 14.6487 7.16977 14.2287 7.16977C12.8087 7.16977 11.6387 6.00977 11.6387 4.57977C11.6387 4.22977 11.7087 3.87977 11.8487 3.57977C12.2387 2.64977 13.1587 2.00977 14.2187 2.00977C15.6387 2.00977 16.7987 3.15977 16.7987 4.58977V4.57977Z"
fill="#969696"
/>
</Svg>
);
}
export default NotAttemptedIcon;

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { G, Mask, Path, Rect, Svg } from 'react-native-svg';
const RightChevronIcon = () => {
return (
<Svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<Mask id="mask0_3190_8548" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
<Rect width="20" height="20" fill="#D9D9D9" />
</Mask>
<G mask="url(#mask0_3190_8548)">
<Path
d="M7.25065 14.4166C7.09787 14.2638 7.02148 14.0694 7.02148 13.8333C7.02148 13.5972 7.09787 13.4027 7.25065 13.25L10.5007 9.99996L7.25065 6.74996C7.09787 6.59718 7.02148 6.40274 7.02148 6.16663C7.02148 5.93051 7.09787 5.73607 7.25065 5.58329C7.40343 5.43051 7.59787 5.35413 7.83398 5.35413C8.07009 5.35413 8.26454 5.43051 8.41732 5.58329L12.2507 9.41663C12.334 9.49996 12.3932 9.59024 12.4282 9.68746C12.4626 9.78468 12.4798 9.88885 12.4798 9.99996C12.4798 10.1111 12.4626 10.2152 12.4282 10.3125C12.3932 10.4097 12.334 10.5 12.2507 10.5833L8.41732 14.4166C8.26454 14.5694 8.07009 14.6458 7.83398 14.6458C7.59787 14.6458 7.40343 14.5694 7.25065 14.4166Z"
fill="#0276FE"
/>
</G>
</Svg>
);
};
export default RightChevronIcon;

View File

@@ -3,13 +3,13 @@ import Svg, { Mask, Path, G, Rect } from 'react-native-svg';
function SmsIcon() {
return (
<Svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<Mask id="mask0_16298_100611" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<Rect width="16" height="16" fill="#D9D9D9" />
<Svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<Mask id="mask0_3186_34194" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
<Rect width="20" height="20" fill="white" />
</Mask>
<G mask="url(#mask0_16298_100611)">
<G mask="url(#mask0_3186_34194)">
<Path
d="M4.69788 9.33008H8.68535C8.87286 9.33008 9.03328 9.26299 9.16662 9.12883C9.29995 8.99466 9.36662 8.82841 9.36662 8.63008C9.36662 8.44119 9.29717 8.2773 9.15828 8.13841C9.0194 7.99952 8.8562 7.93008 8.66868 7.93008H4.68122C4.49371 7.93008 4.33328 7.99716 4.19995 8.13133C4.06662 8.26549 3.99995 8.43174 3.99995 8.63008C3.99995 8.81897 4.06939 8.98286 4.20828 9.12174C4.34717 9.26063 4.51037 9.33008 4.69788 9.33008ZM4.69735 7.36341H11.3192C11.5064 7.36341 11.6666 7.29633 11.8 7.16216C11.9333 7.02799 12 6.86174 12 6.66341C12 6.47452 11.9305 6.31063 11.7916 6.17174C11.6527 6.03286 11.4897 5.96341 11.3026 5.96341H4.68068C4.49353 5.96341 4.33328 6.03049 4.19995 6.16466C4.06662 6.29883 3.99995 6.46508 3.99995 6.66341C3.99995 6.8523 4.06939 7.01619 4.20828 7.15508C4.34717 7.29397 4.51019 7.36341 4.69735 7.36341ZM4.69735 5.39674H11.3192C11.5064 5.39674 11.6666 5.32966 11.8 5.19549C11.9333 5.06133 12 4.89508 12 4.69674C12 4.50786 11.9305 4.34397 11.7916 4.20508C11.6527 4.06619 11.4897 3.99674 11.3026 3.99674H4.68068C4.49353 3.99674 4.33328 4.06383 4.19995 4.19799C4.06662 4.33216 3.99995 4.49841 3.99995 4.69674C3.99995 4.88563 4.06939 5.04952 4.20828 5.18841C4.34717 5.3273 4.51019 5.39674 4.69735 5.39674ZM1.33328 12.9801V2.73008C1.33328 2.34508 1.47037 2.01549 1.74453 1.74133C2.0187 1.46716 2.34828 1.33008 2.73328 1.33008H13.2666C13.6516 1.33008 13.9812 1.46716 14.2554 1.74133C14.5295 2.01549 14.6666 2.34508 14.6666 2.73008V10.5967C14.6666 10.9817 14.5295 11.3113 14.2554 11.5855C13.9812 11.8597 13.6516 11.9967 13.2666 11.9967H3.99995L2.53328 13.4634C2.31106 13.6856 2.0555 13.7372 1.76662 13.6181C1.47773 13.499 1.33328 13.2863 1.33328 12.9801Z"
d="M5.87177 11.6648H10.8561C11.0905 11.6648 11.291 11.5809 11.4577 11.4132C11.6244 11.2455 11.7077 11.0377 11.7077 10.7898C11.7077 10.5537 11.6209 10.3488 11.4473 10.1752C11.2737 10.0016 11.0697 9.91479 10.8353 9.91479H5.85093C5.61654 9.91479 5.41602 9.99865 5.24935 10.1664C5.08268 10.3341 4.99935 10.5419 4.99935 10.7898C4.99935 11.0259 5.08615 11.2308 5.25977 11.4044C5.43338 11.578 5.63738 11.6648 5.87177 11.6648ZM5.8711 9.20646H14.1484C14.3824 9.20646 14.5827 9.12261 14.7494 8.9549C14.916 8.78719 14.9994 8.57938 14.9994 8.33146C14.9994 8.09535 14.9125 7.89049 14.7389 7.71688C14.5653 7.54327 14.3615 7.45646 14.1276 7.45646H5.85027C5.61632 7.45646 5.41602 7.54032 5.24935 7.70802C5.08268 7.87573 4.99935 8.08355 4.99935 8.33146C4.99935 8.56757 5.08615 8.77243 5.25977 8.94604C5.43338 9.11966 5.63716 9.20646 5.8711 9.20646ZM5.8711 6.74813H14.1484C14.3824 6.74813 14.5827 6.66427 14.7494 6.49657C14.916 6.32886 14.9994 6.12105 14.9994 5.87313C14.9994 5.63702 14.9125 5.43216 14.7389 5.25854C14.5653 5.08493 14.3615 4.99813 14.1276 4.99813H5.85027C5.61632 4.99813 5.41602 5.08198 5.24935 5.24969C5.08268 5.4174 4.99935 5.62521 4.99935 5.87313C4.99935 6.10924 5.08615 6.3141 5.25977 6.48771C5.43338 6.66132 5.63716 6.74813 5.8711 6.74813ZM1.66602 16.2273V3.41479C1.66602 2.93354 1.83737 2.52157 2.18008 2.17886C2.52279 1.83615 2.93477 1.66479 3.41602 1.66479H16.5827C17.0639 1.66479 17.4759 1.83615 17.8186 2.17886C18.1613 2.52157 18.3327 2.93354 18.3327 3.41479V13.2481C18.3327 13.7294 18.1613 14.1414 17.8186 14.4841C17.4759 14.8268 17.0639 14.9981 16.5827 14.9981H4.99935L3.16602 16.8315C2.88824 17.1092 2.56879 17.1737 2.20768 17.0248C1.84657 16.8759 1.66602 16.6101 1.66602 16.2273Z"
fill="#969696"
/>
</G>

View File

@@ -0,0 +1,44 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface ITopFeedback {
status: string;
color: string;
referenceId: string;
offset: number;
createdAt: string;
type: string;
}
interface ITopFeedbackState {
[loanAccountNumber: string]: {
feedbacks: ITopFeedback[];
isLoading: boolean;
};
}
const initialState: ITopFeedbackState = {};
const TopFeedbacksSlice = createSlice({
name: 'topFeedbacks',
initialState,
reducers: {
setTopFeedbacks: (state, action) => {
const { loanAccountNumber, feedbacks } = action.payload || {};
state[loanAccountNumber] = {
...(state[loanAccountNumber] || {}),
feedbacks,
};
},
setTopFeedbacksLoading: (state, action) => {
const { loanAccountNumber, isLoading } = action.payload || {};
state[loanAccountNumber] = {
...(state?.[loanAccountNumber] || {}),
isLoading: isLoading,
};
},
},
});
export const { setTopFeedbacks, setTopFeedbacksLoading } = TopFeedbacksSlice.actions;
export default TopFeedbacksSlice.reducer;

View File

@@ -60,63 +60,45 @@ const FeedbackDetailsSection = ({ caseId }: IFeedbackDetailsSection) => {
return notSyncedCases;
};
return (
<View
style={[
GenericStyles.whiteBackground,
styles.secondSection,
getShadowStyle(2),
GenericStyles.mt16,
// Change Here
GenericStyles.pv24,
]}
>
<View style={[GenericStyles.pb24, GenericStyles.w100]}>
<View
style={[
GenericStyles.row,
GenericStyles.alignCenter,
GenericStyles.ph24,
GenericStyles.pb16,
GenericStyles.justifyContentSpaceBetween,
GenericStyles.ph8,
GenericStyles.pv16,
]}
>
<View style={GenericStyles.row}>
<View style={GenericStyles.flex60}>
<Text style={[styles.textContainer, GenericStyles.fontSize14]}>Feedbacks</Text>
</View>
{feedbackList?.length ? (
<View style={GenericStyles.flex40}>
<Pressable
onTouchStart={touchStartHandler}
onPress={openAllFeedbacksHandler}
style={({ pressed }) => [GenericStyles.flex20, { opacity: pressed ? 0.7 : 1 }]}
>
<Text style={[styles.textContainer, styles.feedbackBtn]}>Open all feedbacks</Text>
</Pressable>
</View>
) : null}
</View>
<Text style={[styles.textContainer, GenericStyles.fontSize14]}>Recent feedbacks</Text>
{feedbackList?.length ? (
<Pressable
onTouchStart={touchStartHandler}
onPress={openAllFeedbacksHandler}
style={({ pressed }) => [{ opacity: pressed ? 0.7 : 1 }]}
>
<Text style={[styles.textContainer, styles.feedbackBtn]}>View all</Text>
</Pressable>
) : null}
</View>
<View style={[GenericStyles.whiteBackground, styles.secondSection, getShadowStyle(2)]}>
<FeedbackListContainer
feedbackList={[...getUnSyncedFeedback(), ...feedbackList]?.splice(0, 5)}
loanAccountNumber={loanAccountNumber}
caseId={caseId}
/>
</View>
<View style={GenericStyles.borderTop} />
<FeedbackListContainer
feedbackList={[...getUnSyncedFeedback(), ...feedbackList]?.splice(0, 5)}
loanAccountNumber={loanAccountNumber}
caseId={caseId}
/>
</View>
);
};
export const styles = StyleSheet.create({
secondSection: {
alignSelf: 'center',
borderRadius: 16,
width: '100%',
position: 'relative',
overflow: 'hidden',
},
textContainer: {
fontSize: 13,
lineHeight: 18,
color: COLORS.TEXT.LIGHT,
color: COLORS.TEXT.BLACK,
},
feedbackBtn: {
fontWeight: '500',

View File

@@ -50,6 +50,7 @@ interface IFeedbackDetailContainer {
addressText?: string;
activeFeedbackReferenceId?: string;
caseId: string;
pageNo?: number;
};
};
}
@@ -77,6 +78,7 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
addressReferenceIds,
addressText,
caseId,
pageNo = 1,
},
} = routeParams;
@@ -101,7 +103,7 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
const [totalPage, setTotalPage] = useState(!isPastFeedbackOnAddress ? feedbackTotalPages : 0);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [currentPage, setCurrentPage] = useState(pageNo);
const [dataSourceCord, setDataSourceCord] = useState(0);
const [ref, setRef] = useState<GenericType>();
const [showFilterModal, setShowFilterModal] = useState(false);
@@ -169,6 +171,10 @@ const FeedbackDetailContainer: React.FC<IFeedbackDetailContainer> = ({ route: ro
}
}, [isOnline]);
useEffect(() => {
setCurrentPage(pageNo ?? 1);
}, [pageNo])
useEffect(() => {
if (isPastFeedbackOnAddress) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ADDRESS_FEEDBACK_LANDED, {

View File

@@ -1,4 +1,4 @@
import React, { useRef, ReactNode, useEffect, useState } from 'react';
import React, { ReactNode, useState } from 'react';
import { View, StyleSheet, TouchableOpacity, Linking, Platform, NativeModules } from 'react-native';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import { GenericStyles } from '../../../../RN-UI-LIB/src/styles';
@@ -8,7 +8,7 @@ import {
BUSINESS_TIME_FORMAT,
dateFormat,
} from '../../../../RN-UI-LIB/src/utlis/dates';
import { Address, CaseDetail, Address as IAddress, IGeolocation, VisitType } from '../interface';
import { Address, Address as IAddress, IGeolocation, VisitType } from '../interface';
import {
debounce,
getGoogleMapUrl,
@@ -18,7 +18,6 @@ import {
FIELD_FEEDBACKS,
ICallingFeedback,
IFeedback,
OPTION_TAG,
} from '../../../types/feedback.types';
import MapIcon from '../../../../RN-UI-LIB/src/Icons/MapIcon';
import { FEEDBACK_TYPE } from '../../../types/feedback.types';
@@ -31,7 +30,7 @@ import WhatsAppFeedbackShareIcon from '../../../assets/icons/WhatsAppIcon';
import { useAppSelector } from '../../../hooks';
import { shareToWhatsapp } from '../../../services/FeedbackWhatsApp';
import SmsIcon from "@assets/icons/SmsIcon";
import Button from '@rn-ui-lib/components/Button';
import NotAttemptedIcon from '@assets/icons/NotAttemptedIcon';
interface IFeedbackDetailItem {
feedbackItem: IFeedback;
@@ -46,6 +45,7 @@ export const feedbackTypeIcon: Record<FEEDBACK_TYPE, ReactNode> = {
SELF_CALL: <CallIcon fillColor={COLORS.TEXT.LIGHT} />,
CALL_BRIDGE: <CallIcon fillColor={COLORS.TEXT.LIGHT} />,
GEN_AI_BOT_FIELD: <SmsIcon />,
NOT_ATTEMPTED: <NotAttemptedIcon />
};
const getAddress = (address?: Address) => {

View File

@@ -8,12 +8,13 @@ import { GenericFunctionArgs } from '../../../common/GenericTypes';
import Heading from '../../../../RN-UI-LIB/src/components/Heading';
import LoadingIcon from '../../../../RN-UI-LIB/src/Icons/LoadingIcon';
import FeedbackListItem from './FeedbackListItem';
import { useAppDispatch, useAppSelector } from '../../../hooks';
import { useAppDispatch } from '../../../hooks';
import useIsOnline from '../../../hooks/useIsOnline';
import NoFeedbackIcon from '../../../assets/icons/NoFeedbackIcon';
import { RootState } from '../../../store/store';
import OfflineIcon from '../../../../RN-UI-LIB/src/Icons/OfflineIcon';
import { getCaseUnifiedData, UnifiedCaseDetailsTypes } from '../../../action/caseApiActions';
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
import FeedbackLoading from './FeedbackLoading';
interface IFeedbackListContainer {
loanAccountNumber: string;
@@ -93,17 +94,19 @@ const FeedbackListContainer: React.FC<IFeedbackListContainer> = ({
}
return (
<View>
{feedbackList.map((feedbackItem: IFeedback | IUnSyncedFeedbackItem, idx: number) => (
<FeedbackListItem
key={feedbackItem.createdAt}
feedbackItem={feedbackItem}
loanAccountNumber={loanAccountNumber}
showHorizontalLine={++idx !== feedbackList.length}
caseId={caseId}
/>
))}
</View>
<SuspenseLoader loading={false} fallBack={<FeedbackLoading />}>
<View>
{feedbackList.map((feedbackItem: IFeedback | IUnSyncedFeedbackItem, idx: number) => (
<FeedbackListItem
key={feedbackItem.createdAt}
feedbackItem={feedbackItem}
loanAccountNumber={loanAccountNumber}
showHorizontalLine={++idx !== feedbackList.length}
caseId={caseId}
/>
))}
</View>
</SuspenseLoader>
);
};

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import Chevron from '../../../../RN-UI-LIB/src/Icons/Chevron';
import RightChevronIcon from '@assets/icons/RightChevronIcon';
import UnsyncedIcon from '../../../../RN-UI-LIB/src/Icons/UnsyncedIcon';
import { GenericStyles } from '../../../../RN-UI-LIB/src/styles';
import { COLORS } from '../../../../RN-UI-LIB/src/styles/colors';
@@ -15,12 +15,14 @@ import { useAppSelector } from '@hooks';
import { shareToWhatsapp } from '../../../services/FeedbackWhatsApp';
import { CaseDetailStackEnum } from '../CaseDetailStack';
import { feedbackTypeIcon } from '@screens/caseDetails/feedback/FeedbackDetailItem';
import Tag, { TagVariant } from '@rn-ui-lib/components/Tag';
interface IFeedbackListItem {
feedbackItem: IFeedback | IUnSyncedFeedbackItem;
showHorizontalLine?: boolean;
loanAccountNumber: string;
caseId: string;
isTopFeedbackItem?: boolean;
}
const FeedbackListItem: React.FC<IFeedbackListItem> = ({
@@ -28,15 +30,19 @@ const FeedbackListItem: React.FC<IFeedbackListItem> = ({
loanAccountNumber,
caseId,
showHorizontalLine = true,
isTopFeedbackItem = false,
}) => {
const handleRouting = (route: CaseDetailStackEnum, params: object | undefined = undefined) => {
const handleRouting = () => {
if (!(feedbackItem as IFeedback).referenceId) return;
const commonParams = {
loanAccountNumber,
activeFeedbackReferenceId: (feedbackItem as IFeedback).referenceId,
caseId: caseId,
};
navigateToScreen(route, { ...params, ...commonParams });
navigateToScreen(CaseDetailStackEnum.PAST_FEEDBACK_DETAIL, {
...commonParams,
pageNo: isTopFeedbackItem ? (feedbackItem as IFeedback)?.offset : 1,
});
};
const [isWhastappSendLoading, setIsWhatsappSendLoading] = useState(false);
const { agentId, caseDetails } = useAppSelector((state) => ({
@@ -46,80 +52,93 @@ const FeedbackListItem: React.FC<IFeedbackListItem> = ({
const throttledSendToWhatsapp = React.useRef(debounce(shareToWhatsapp, 500));
return (
<View style={[GenericStyles.ph16]}>
<View style={[!isTopFeedbackItem ? GenericStyles.ph16 : {}]}>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => handleRouting(CaseDetailStackEnum.PAST_FEEDBACK_DETAIL)}
activeOpacity={!(feedbackItem as IFeedback)?.referenceId ? 1 : 0.7}
onPress={handleRouting}
style={[
GenericStyles.row,
GenericStyles.alignCenter,
showHorizontalLine ? GenericStyles.pb16 : GenericStyles.pb4,
GenericStyles.pv16,
isTopFeedbackItem ? styles.topFeedbackItem : styles.feedbackItem,
]}
>
{feedbackItem.isSynced === false ? (
<View style={GenericStyles.pr16}>
<UnsyncedIcon />
{isTopFeedbackItem ? (
<View style={[GenericStyles.absolute]}>
<Tag
text={(feedbackItem as IFeedback)?.tagTitle}
variant={TagVariant.lightBlue}
style={!showHorizontalLine ? styles.currentMonthFeedback : styles.lastMonthfeedback}
/>
</View>
) : null}
<View style={styles.feedBox}>
<View style={styles.textBox}>
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
{feedbackItem.type && feedbackTypeIcon[feedbackItem.type] ? (
<View
style={[
GenericStyles.mr8,
feedbackItem.type !== FEEDBACK_TYPE.GEN_AI_BOT_FIELD ? styles.ml_4 : null,
]}
>
{feedbackTypeIcon[feedbackItem.type]}
</View>
) : null}
<Text
numberOfLines={1}
ellipsizeMode="tail"
<View style={[GenericStyles.row, GenericStyles.alignCenter]}>
{feedbackItem.isSynced === false ? (
<View style={GenericStyles.pr16}>
<UnsyncedIcon />
</View>
) : null}
<View style={GenericStyles.alignCenter}>
{feedbackItem.type && feedbackTypeIcon[feedbackItem.type] ? (
<View
style={[
GenericStyles.pb4,
GenericStyles.mr16,
styles.textHeading,
feedbackItem.type === FEEDBACK_TYPE.GEN_AI_BOT_FIELD ? styles.capitalized : null,
GenericStyles.mr12,
GenericStyles.alignCenter,
GenericStyles.justifyContentCenter,
styles.feedbackIcon,
feedbackItem.type !== FEEDBACK_TYPE.GEN_AI_BOT_FIELD ? styles.ml_4 : null,
]}
>
{sanitizeString(feedbackItem.interactionStatus)}
</Text>
</View>
<Text style={styles.subText}>
{sanitizeString(
`${dateFormat(new Date(feedbackItem?.createdAt), BUSINESS_DATE_FORMAT)}`
)}
</Text>
{feedbackTypeIcon[feedbackItem.type]}
</View>
) : null}
</View>
{feedbackItem?.type === 'FIELD_VISIT' ? (
<View style={styles.ShareButton}>
<TouchableOpacity
hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
activeOpacity={0.7}
onPress={(e) => {
e.stopPropagation();
throttledSendToWhatsapp.current(
feedbackItem,
caseDetails,
agentId,
setIsWhatsappSendLoading
);
}}
disabled={isWhastappSendLoading}
>
<IconLabel
text="Share"
icon={<WhatsAppFeedbackShareIcon width={15} height={15} />}
textStyle={styles.ButtonText}
/>
</TouchableOpacity>
<View style={styles.feedBox}>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={[
styles.textHeading,
feedbackItem.type === FEEDBACK_TYPE.GEN_AI_BOT_FIELD ? styles.capitalized : null,
]}
>
{sanitizeString(feedbackItem.interactionStatus)}
</Text>
{feedbackItem?.createdAt ? (
<Text style={styles.subText}>
{sanitizeString(
`${dateFormat(new Date(feedbackItem?.createdAt), BUSINESS_DATE_FORMAT)}`
)}
</Text>
) : null}
</View>
{!(feedbackItem.isSynced === false || !(feedbackItem as IFeedback)?.referenceId) ? (
<View style={GenericStyles.alignCenter}>
<RightChevronIcon />
</View>
) : null}
</View>
{!(feedbackItem.isSynced === false) ? <Chevron /> : null}
{feedbackItem?.type === FEEDBACK_TYPE.FIELD_VISIT ? (
<TouchableOpacity
style={styles.ShareButton}
hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
activeOpacity={0.7}
onPress={(e) => {
e.stopPropagation();
throttledSendToWhatsapp.current(
feedbackItem,
caseDetails,
agentId,
setIsWhatsappSendLoading
);
}}
disabled={isWhastappSendLoading}
>
<IconLabel
text="Share"
icon={<WhatsAppFeedbackShareIcon width={15} height={15} />}
textStyle={styles.ButtonText}
/>
</TouchableOpacity>
) : null}
</TouchableOpacity>
{showHorizontalLine ? <View style={[GenericStyles.borderTop, GenericStyles.w100]} /> : null}
</View>
@@ -129,9 +148,9 @@ const FeedbackListItem: React.FC<IFeedbackListItem> = ({
const styles = StyleSheet.create({
textHeading: {
fontSize: 14,
lineHeight: 30,
paddingTop: 4,
color: COLORS.TEXT.DARK,
lineHeight: 18,
marginBottom: 2,
color: COLORS.TEXT.BLACK,
},
capitalized: {
textTransform: 'capitalize',
@@ -142,18 +161,13 @@ const styles = StyleSheet.create({
color: COLORS.TEXT.LIGHT,
},
feedBox: {
flexBasis: '94%',
},
textBox: {
marginLeft: 10,
marginTop: 5,
flexBasis: '80%',
marginRight: 14,
},
ShareButton: {
width: 75,
height: 35,
padding: 5,
justifyContent: 'flex-start',
marginLeft: 5,
marginTop: 5,
marginLeft: 36,
alignSelf: 'flex-start',
},
ButtonText: {
color: COLORS.BASE.BLUE,
@@ -161,6 +175,29 @@ const styles = StyleSheet.create({
ml_4: {
marginLeft: -4,
},
feedbackIcon: {
width: 28,
height: 28,
backgroundColor: COLORS.BACKGROUND.SILVER,
borderRadius: 4,
},
feedbackItem: {
paddingTop: 16,
paddingHorizontal: 0,
},
topFeedbackItem: {
paddingTop: 40,
paddingHorizontal: 16,
},
currentMonthFeedback: {
borderRadius: 0,
borderBottomRightRadius: 8,
},
lastMonthfeedback: {
borderRadius: 0,
borderBottomRightRadius: 8,
borderTopLeftRadius: 7,
},
});
export default FeedbackListItem;

View File

@@ -0,0 +1,60 @@
import Tag, { TagVariant } from '@rn-ui-lib/components/Tag';
import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader';
import { GenericStyles } from '@rn-ui-lib/styles';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { IFeedbackLoading, TOP_FEEDBACK_MAP } from './types';
const FeedbackLoading = (props: IFeedbackLoading) => {
const { isTopFeedbackItem = false, arrayLength = 5 } = props;
return (
<View>
{Array.from({ length: arrayLength }).map((_, idx) => (
<View style={[!isTopFeedbackItem ? GenericStyles.ph16 : {}]}>
<View
style={[
GenericStyles.pv16,
isTopFeedbackItem ? styles.topFeedbackItem : styles.feedbackItem,
]}
>
{isTopFeedbackItem ? (
<View style={[GenericStyles.absolute]}>
<Tag
text={TOP_FEEDBACK_MAP[idx]}
variant={TagVariant.lightBlue}
style={idx === 0 ? styles.lastMonthfeedback : styles.currentMonthFeedback}
/>
</View>
) : null}
<LineLoader key={idx} width={'100%'} height={36} />
</View>
{idx !== arrayLength ? (
<View style={[GenericStyles.borderTop, GenericStyles.w100]} />
) : null}
</View>
))}
</View>
);
};
const styles = StyleSheet.create({
currentMonthFeedback: {
borderRadius: 0,
borderBottomRightRadius: 8,
},
lastMonthfeedback: {
borderRadius: 0,
borderBottomRightRadius: 8,
borderTopLeftRadius: 7,
},
feedbackItem: {
paddingTop: 16,
paddingHorizontal: 0,
},
topFeedbackItem: {
paddingTop: 38,
paddingHorizontal: 16,
},
});
export default FeedbackLoading;

View File

@@ -0,0 +1,90 @@
import React, { useEffect } from 'react';
import FeedbackListItem from './FeedbackListItem';
import { useAppDispatch, useAppSelector } from '@hooks';
import { RootState } from '@store';
import { StyleSheet, View } from 'react-native';
import { GenericStyles, getShadowStyle } from '@rn-ui-lib/styles';
import Text from '@rn-ui-lib/components/Text';
import { COLORS } from '@rn-ui-lib/colors';
import SuspenseLoader from '@rn-ui-lib/components/suspense_loader/SuspenseLoader';
import FeedbackLoading from './FeedbackLoading';
import { getTopFeedbacks } from '@actions/feedbackActions';
interface ITopFeedbacks {
caseId: string;
}
const TopFeedbacks = (props: ITopFeedbacks) => {
const { caseId } = props;
const dispatch = useAppDispatch();
const caseDetail = useAppSelector((state: RootState) => state.allCases.caseDetails[caseId]) || {};
const { loanAccountNumber } = caseDetail || {};
const feedbackList = useAppSelector(
(state: RootState) => state.topFeedbacks?.[loanAccountNumber as string]?.feedbacks || []
);
const isLoading = useAppSelector(
(state: RootState) => state.topFeedbacks?.[loanAccountNumber as string]?.isLoading || false
);
useEffect(() => {
dispatch(getTopFeedbacks(loanAccountNumber));
}, []);
if (!feedbackList?.length) return null;
return (
<View style={GenericStyles.pb8}>
<View
style={[
GenericStyles.row,
GenericStyles.alignCenter,
GenericStyles.justifyContentCenter,
GenericStyles.pb16,
GenericStyles.pt24,
]}
>
<View style={[GenericStyles.borderTop, GenericStyles.flex35]} />
<Text
dark
style={[
GenericStyles.flex30,
GenericStyles.centerAlignedText,
GenericStyles.fontSize16,
GenericStyles.fw600,
styles.feedbackHeading,
]}
>
Feedbacks
</Text>
<View style={[GenericStyles.borderTop, GenericStyles.flex35]} />
</View>
<View style={[GenericStyles.whiteBackground, GenericStyles.br8, getShadowStyle(2)]}>
<SuspenseLoader
loading={isLoading && !feedbackList?.length}
fallBack={<FeedbackLoading arrayLength={2} isTopFeedbackItem />}
>
{feedbackList?.map((feedbackItem: any, idx: number) => (
<FeedbackListItem
key={feedbackItem.createdAt}
feedbackItem={feedbackItem}
loanAccountNumber={loanAccountNumber}
showHorizontalLine={++idx !== feedbackList?.length}
caseId={caseId}
isTopFeedbackItem
/>
))}
</SuspenseLoader>
</View>
</View>
);
};
const styles = StyleSheet.create({
feedbackHeading: { color: COLORS.TEXT.BLACK },
border: {
borderWidth: 1,
borderColor: COLORS.BORDER.PRIMARY,
},
});
export default TopFeedbacks;

View File

@@ -0,0 +1,9 @@
export const TOP_FEEDBACK_MAP: { [key: number]: string } = {
0: 'Current month status',
1: 'Last month status',
};
export interface IFeedbackLoading {
isTopFeedbackItem?: boolean;
arrayLength?: number;
}

View File

@@ -33,6 +33,7 @@ import commitmentTrackerSlice from '@reducers/commitmentTrackerSlice';
import nearbyCasesSlice from '@reducers/nearbyCasesSlice';
import activeCallSlice from '@reducers/activeCallSlice';
import documentsSlice from '@reducers/documentsSlice';
import topFeedbacksSlice from '@reducers/topFeedbacksSlice';
import escalationSlice from '@reducers/escalationSlice';
const rootReducer = combineReducers({
@@ -67,6 +68,7 @@ const rootReducer = combineReducers({
nearbyCasesSlice: nearbyCasesSlice,
activeCall: activeCallSlice,
documentsSlice: documentsSlice,
topFeedbacks: topFeedbacksSlice,
escalationSlice: escalationSlice,
});

View File

@@ -46,6 +46,7 @@ export enum FEEDBACK_TYPE {
CALL_BRIDGE = 'CALL_BRIDGE',
SELF_CALL = 'SELF_CALL',
GEN_AI_BOT_FIELD = 'GEN_AI_BOT_FIELD',
NOT_ATTEMPTED = 'NOT_ATTEMPTED'
}
export interface FIELD_FEEDBACK_METADATA {
@@ -75,6 +76,8 @@ export interface IFeedback {
agentReferenceId: string;
source: Address | IGeolocation | ICallingFeedback;
interactionStatus: InteractionStatuses;
tagTitle?: string;
offset?: number;
}
export interface IUnSyncedFeedbackItem {