NTP-50799 | Top 5 Addresses (#1120)

Co-authored-by: Aman Chaturvedi <aman.chaturvedi@navi.com>
This commit is contained in:
Mantri Ramkishor
2025-04-02 18:56:16 +05:30
committed by GitHub
parent e369714128
commit a0723cde1b
76 changed files with 2787 additions and 79 deletions

View File

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

View File

@@ -1 +1 @@
250
251

View File

@@ -1 +1 @@
2.18.7
2.18.8

View File

@@ -1 +1 @@
309
310

View File

@@ -1 +1 @@
100.2.5
100.2.6

View File

@@ -1,7 +1,7 @@
{
"name": "AV_APP",
"version": "2.18.7",
"buildNumber": "250",
"version": "2.18.8",
"buildNumber": "251",
"private": true,
"scripts": {
"android:dev": "yarn move:dev && react-native run-android",

View File

@@ -12,6 +12,7 @@ dayjs.extend(timezone);
export const CoachMarkFeatures = {
CASE_STATUS_FILTERS: 'caseStatusFilters',
TOP_5_ADDRESSES: 'top5Addresses'
};
export const showCoachMark = async (

View File

@@ -0,0 +1,56 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
const AddFeedbackIcon = () => (
<Svg width={24} height={24} viewBox="0 0 24 24" fill="none">
<Path
d="M16.6237 15.346V18.9944C16.6237 19.3066 16.3693 19.561 16.0571 19.561H4.38524C4.34361 19.561 4.30302 19.574 4.26912 19.5981L1.81612 21.3474C1.68373 21.4418 1.5 21.3472 1.5 21.1846V8.59976C1.5 8.28758 1.75437 8.0332 2.06656 8.0332H9.34427"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M5.03906 11.3638H10.5395"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M5.10742 13.8613H12.914"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M5.10742 16.3589H9.64565"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M17.2485 12.8906C20.1472 12.8906 22.4971 10.5408 22.4971 7.64209C22.4971 4.7434 20.1472 2.39355 17.2485 2.39355C14.3498 2.39355 12 4.7434 12 7.64209C12 10.5408 14.3498 12.8906 17.2485 12.8906Z"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M17.1387 5.09814V9.96579"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M19.5727 7.53076H14.7051"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
export default AddFeedbackIcon;

View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import Svg, { Mask, Rect, G, Path } from 'react-native-svg';
const CopyOutlineIcon = () => (
<Svg width={19} height={18} viewBox="0 0 19 18" fill="none">
<Mask id="mask0_4968_87583" maskUnits="userSpaceOnUse" x={0} y={0} width={19} height={18}>
<Rect x={0.599609} width={18} height={18} fill="#D9D9D9" />
</Mask>
<G mask="url(#mask0_4968_87583)">
<Path
d="M4.34961 16.5C3.93711 16.5 3.58386 16.3533 3.28986 16.0597C2.99636 15.7657 2.84961 15.4125 2.84961 15V4.5H4.34961V15H12.5996V16.5H4.34961ZM7.34961 13.5C6.93711 13.5 6.58411 13.3533 6.29061 13.0597C5.99661 12.7657 5.84961 12.4125 5.84961 12V3C5.84961 2.5875 5.99661 2.23425 6.29061 1.94025C6.58411 1.64675 6.93711 1.5 7.34961 1.5H14.0996C14.5121 1.5 14.8654 1.64675 15.1594 1.94025C15.4529 2.23425 15.5996 2.5875 15.5996 3V12C15.5996 12.4125 15.4529 12.7657 15.1594 13.0597C14.8654 13.3533 14.5121 13.5 14.0996 13.5H7.34961ZM7.34961 12H14.0996V3H7.34961V12Z"
fill="#0276FE"
/>
</G>
</Svg>
);
export default CopyOutlineIcon;

View File

@@ -0,0 +1,27 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
const GoogleMapIcon = () => (
<Svg width={17} height={17} viewBox="0 0 17 17" fill="none">
<Path
d="M12.318 9.18624H12.3213C12.3213 9.18624 11.0647 11.0296 10.008 12.3996C9.09466 13.5829 8.82799 14.5962 8.75133 15.0862C8.72133 15.2662 8.57466 15.3996 8.39799 15.3996C8.22133 15.3996 8.07466 15.2662 8.04466 15.0862C7.968 14.5962 7.70133 13.5829 6.78799 12.3996C6.64799 12.2162 6.50466 12.0262 6.36133 11.8329L9.81133 7.73291L12.588 4.43958C12.8947 5.08291 13.0647 5.80291 13.0647 6.56624C13.0647 7.53291 12.788 8.42958 12.318 9.18624Z"
fill="#48B564"
/>
<Path
d="M9.81191 7.73311L6.36191 11.8331C5.42191 10.5731 4.47525 9.18645 4.47525 9.18645H4.47858C4.37858 9.02645 4.28858 8.86311 4.21191 8.69311L6.98525 5.39978C6.72191 5.71645 6.56525 6.12311 6.56525 6.56645C6.56525 7.57978 7.38525 8.39978 8.39858 8.39978C8.96858 8.39978 9.47858 8.13978 9.81191 7.73311Z"
fill="#FCC60E"
/>
<Path
d="M9.86462 1.98019L7.00796 5.37352L4.82129 3.46685C5.67462 2.40685 6.96129 1.73352 8.39796 1.73352C8.91129 1.73352 9.40462 1.82019 9.86462 1.98019Z"
fill="#2C85EB"
/>
<Path
d="M7.00811 5.37285L6.98478 5.39952L4.21145 8.69285C3.90478 8.04952 3.73145 7.32952 3.73145 6.56619C3.73145 5.38619 4.14145 4.30285 4.82145 3.46619L7.00811 5.37285Z"
fill="#ED5748"
/>
<Path
d="M9.81202 7.73283C10.0754 7.41616 10.232 7.00949 10.232 6.56616C10.232 5.55283 9.41202 4.73283 8.39868 4.73283C7.82868 4.73283 7.31868 4.99283 6.98535 5.39949L9.86535 1.97949C11.062 2.38616 12.042 3.28283 12.5887 4.43949L9.81202 7.73283Z"
fill="#5695F6"
/>
</Svg>
);
export default GoogleMapIcon;

View File

@@ -0,0 +1,45 @@
import * as React from 'react';
import Svg, { Path, Ellipse } from 'react-native-svg';
const LocationOnMap3DIcon = () => (
<Svg width={75} height={52} viewBox="0 0 75 52" fill="none">
<Path
d="M24.656 13.3572L23.3821 21.4176H5.81543L6.86956 16.9304C7.36 14.8381 9.22622 13.3572 11.3759 13.3572H24.656Z"
fill="#E8E8E8"
/>
<Path d="M22.6055 24.7131L20.5896 40.0314H1.44336L5.04204 24.7131H22.6055Z" fill="#CCF1D6" />
<Path
d="M19.968 43.4326L19.0636 51.3402H4.63382C1.64977 51.3402 -0.557209 48.5568 0.124312 45.6491L0.643415 43.4326H19.968Z"
fill="#CCE4FF"
/>
<Path
d="M73.26 50.3246L50.6934 40.8088L52.7124 32.7866H71.8524L74.8746 45.6495C75.3045 47.4775 74.5912 49.2546 73.26 50.3246Z"
fill="#CCE4FF"
/>
<Path
d="M71.0886 29.5316L49.6302 29.4933L47.1271 40.0314H24.1592L26.019 24.7131H69.958L71.0886 29.5316Z"
fill="#BFE8FF"
/>
<Path
d="M69.1842 21.4176H26.9521L27.9394 13.3572H63.6237C65.7734 13.3572 67.6396 14.8381 68.13 16.9304L69.1842 21.4176Z"
fill="#CCE4FF"
/>
<Path d="M66.2478 51.3402H22.1592L23.3821 43.4326H48.5888L66.2478 51.3402Z" fill="#CCE4FF" />
<Ellipse
cx={37.3337}
cy={27.0834}
rx={6.33371}
ry={1.58343}
fill="#21002A"
fillOpacity={0.22}
/>
<Path
d="M47.678 9.77394C47.4519 4.1912 42.7386 -0.235502 37.0476 0.00971786C31.5062 0.248569 27.0477 5.15297 27.3215 10.6911C27.3821 11.9172 27.6559 13.0828 28.1145 14.1592C30.0636 19.363 34.6973 24.968 36.5985 27.226C37.0667 27.7833 37.9074 27.7705 38.3596 27.2005C40.1845 24.9012 46.057 17.6114 47.3563 12.7866C47.3723 12.7229 47.3882 12.6593 47.4041 12.5924C47.42 12.5255 47.436 12.465 47.4455 12.3981C47.5506 11.93 47.6207 11.4491 47.6557 10.9618C47.6557 10.9427 47.6557 10.9236 47.6589 10.9077C47.6748 10.6688 47.6876 10.43 47.6876 10.1879C47.6876 10.0383 47.6844 9.90451 47.6748 9.77075H47.678V9.77394ZM37.4998 14.6688C34.8055 14.6688 32.6209 12.4841 32.6209 9.78986C32.6209 7.09562 34.8055 4.91094 37.4998 4.91094C40.194 4.91094 42.3787 7.09562 42.3787 9.78986C42.3787 12.4841 40.194 14.6688 37.4998 14.6688Z"
fill="#ED5656"
/>
<Path
d="M37.4961 14.6685C40.1907 14.6685 42.375 12.4841 42.375 9.78957C42.375 7.09501 40.1907 4.91064 37.4961 4.91064C34.8016 4.91064 32.6172 7.09501 32.6172 9.78957C32.6172 12.4841 34.8016 14.6685 37.4961 14.6685Z"
fill="white"
/>
</Svg>
);
export default LocationOnMap3DIcon;

View File

@@ -0,0 +1,48 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
const LocationOnMapIcon = () => (
<Svg width={55} height={48} viewBox="0 0 55 48" fill="none">
<Path
d="M46.8202 12.8304V39.0476C46.8202 39.6382 46.2952 40.0866 45.7156 39.9773L35.2156 38.1288C35.0953 38.107 34.9859 38.107 34.8656 38.1288L22.6594 40.6007C22.5609 40.6116 22.4625 40.6226 22.364 40.6116L11.0219 39.2882C10.5406 39.2335 10.1797 38.8288 10.1797 38.3476V12.1633C10.1797 11.5726 10.7047 11.1351 11.2844 11.2336L13.5156 11.6054L15.3422 11.9117L17.7484 12.3273L22.3203 13.0929C22.4406 13.1148 22.5719 13.1148 22.7031 13.082L24.0375 12.7429L28.489 11.6164L28.9593 11.4961L34.8437 9.99763C34.975 9.96482 35.1062 9.96482 35.2375 9.99763L46.0437 11.9117C46.4921 11.9883 46.8202 12.382 46.8202 12.8414V12.8304Z"
fill="#F7F7F7"
stroke="#A1A7A5"
strokeWidth={0.20669}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M34.8322 9.99832L22.5166 13.1264V40.5248C22.5166 40.5248 22.5604 40.6123 22.615 40.6014L35.04 38.0858V10.1624C35.04 10.053 34.9306 9.96551 34.8322 9.99832Z"
fill="#EEEEEE"
stroke="#A1A7A5"
strokeWidth={0.20669}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
opacity={0.2}
d="M20.6797 3.61051C18.1859 3.71989 15.9766 5.10895 14.6969 7.11051C14.5875 7.67925 14.5328 8.28082 14.5656 8.88238C14.6094 9.79019 14.8172 10.6433 15.1453 11.4417C15.2 11.5949 15.2656 11.7589 15.3422 11.9121L13.5156 11.6058C13.5703 12.4699 13.7672 13.2792 14.0953 14.0449C15.5281 17.873 18.9406 22.0074 20.3515 23.6698C20.6906 24.0745 21.314 24.0745 21.6422 23.648C22.9875 21.9527 27.3187 16.5824 28.2703 13.0277C28.2812 12.9949 28.2812 12.9621 28.2922 12.9292C28.2922 12.9074 28.3031 12.8964 28.3031 12.8855C28.325 12.8308 28.3359 12.7871 28.3359 12.7433C28.4234 12.3933 28.4672 12.0433 28.489 11.6824V11.6167C28.5109 11.4636 28.5109 11.2777 28.5109 11.1136V10.8074C28.3468 6.68394 24.8687 3.43551 20.6797 3.61051ZM18.4703 8.28082V8.21519C18.4703 6.23551 20.0781 4.61676 22.0687 4.61676C24.0594 4.61676 25.6562 6.23551 25.6562 8.21519C25.6562 9.2105 25.2515 10.1074 24.6062 10.7527V10.8183C24.6062 11.5292 24.3984 12.1855 24.0375 12.7433C23.4031 13.7496 22.2765 14.4167 21.0078 14.4167C19.5641 14.4167 18.3172 13.5527 17.7484 12.3277C17.5297 11.8683 17.4094 11.3542 17.4094 10.8183C17.4094 9.823 17.8141 8.92613 18.4703 8.28082Z"
fill="#7A7F88"
/>
<Path
d="M29.5604 8.20359C29.3963 4.0911 25.9182 0.831732 21.7292 1.00673C18.262 1.15986 15.3526 3.80673 14.6964 7.10984C14.587 7.67859 14.5323 8.28015 14.5651 8.88172C14.6089 9.78953 14.8167 10.6427 15.1448 11.4411C15.1995 11.5942 15.2651 11.7583 15.3417 11.9114C16.862 15.5973 20.0557 19.4692 21.401 21.0661C21.751 21.4817 22.3635 21.4708 22.7026 21.0442C23.7526 19.7098 26.6401 16.1223 28.2917 12.9286C28.5542 12.4364 28.7838 11.9551 28.9588 11.4958C29.112 11.1239 29.2432 10.763 29.3307 10.4239C29.3417 10.3802 29.3526 10.3255 29.3635 10.2817C29.3745 10.238 29.3854 10.1833 29.3963 10.1395C29.4729 9.78953 29.5276 9.43953 29.5495 9.07859V9.04578C29.5604 8.87078 29.5713 8.68484 29.5713 8.50984C29.5713 8.40047 29.5713 8.30203 29.5604 8.20359ZM24.4198 10.5661C23.8182 11.1786 22.987 11.5505 22.0682 11.5505C20.2417 11.5505 18.7651 10.0848 18.7323 8.28015V8.21453C18.7323 6.37703 20.2198 4.8786 22.0682 4.8786C23.9167 4.8786 25.3932 6.37703 25.3932 8.21453C25.3932 9.13328 25.0214 9.96453 24.4198 10.5661Z"
fill="#CACDCC"
stroke="#A1A7A5"
strokeWidth={0.20669}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M51.75 8.98894C51.0642 8.98894 50.5 8.43584 50.5 7.73894C50.5 7.04204 51.0531 6.5 51.75 6.5C52.4469 6.5 53 7.0531 53 7.75C53 8.4469 52.4469 9 51.75 9V8.98894Z"
fill="#ECECEC"
/>
<Path
d="M30.2386 47.5799C29.7005 47.5799 29.2578 47.1459 29.2578 46.5991C29.2578 46.0523 29.6918 45.627 30.2386 45.627C30.7854 45.627 31.2194 46.0609 31.2194 46.6077C31.2194 47.1546 30.7854 47.5885 30.2386 47.5885V47.5799Z"
fill="#ECECEC"
/>
<Path d="M3.25781 21.4968H0.257812" stroke="#CACDCC" strokeWidth={0.24848} />
<Path d="M1.75391 23V20" stroke="#CACDCC" strokeWidth={0.24848} />
<Path d="M54.8967 31.1801H51.1387" stroke="#CACDCC" strokeWidth={0.311263} />
<Path d="M53.0127 33.0627V29.3047" stroke="#CACDCC" strokeWidth={0.311263} />
</Svg>
);
export default LocationOnMapIcon;

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
const LollipopIcon = () => (
<Svg width={16} height={16} viewBox="0 0 16 16" fill="none">
<Path
d="M7.97656 7.81836V13.9998"
stroke="#21002A"
strokeWidth={0.75}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M7.98047 8.5C9.77539 8.5 11.2305 7.04493 11.2305 5.25C11.2305 3.45507 9.77539 2 7.98047 2C6.18554 2 4.73047 3.45507 4.73047 5.25C4.73047 7.04493 6.18554 8.5 7.98047 8.5Z"
fill="#E92C2C"
/>
<Path
d="M7 5C7.55228 5 8 4.55228 8 4C8 3.44772 7.55228 3 7 3C6.44772 3 6 3.44772 6 4C6 4.55228 6.44772 5 7 5Z"
fill="#FBD5D5"
/>
</Svg>
);
export default LollipopIcon;

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { G, Mask, Path, Rect, Svg } from 'react-native-svg';
const MapDirectionFilledIcon = () => {
return (
<Svg width="20" height="20" viewBox="0 0 20 20" fill="none">
<Mask id="mask0_1267_17979" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
<Rect width="20" height="20" fill="#D9D9D9" />
</Mask>
<G mask="url(#mask0_1267_17979)">
<Path
d="M8.33268 10H11.2493V11.0833C11.2493 11.2778 11.3327 11.4097 11.4993 11.4792C11.666 11.5486 11.8188 11.5139 11.9577 11.375L13.5827 9.75C13.7493 9.58334 13.8327 9.38889 13.8327 9.16667C13.8327 8.94445 13.7493 8.75001 13.5827 8.58334L11.9577 6.95834C11.8188 6.81945 11.666 6.78473 11.4993 6.85417C11.3327 6.92362 11.2493 7.05556 11.2493 7.25001V8.33334H7.49935C7.26324 8.33334 7.06532 8.4132 6.9056 8.57292C6.74588 8.73264 6.66602 8.93056 6.66602 9.16667V11.6667C6.66602 11.9028 6.74588 12.1007 6.9056 12.2604C7.06532 12.4201 7.26324 12.5 7.49935 12.5C7.73546 12.5 7.93338 12.4201 8.0931 12.2604C8.25282 12.1007 8.33268 11.9028 8.33268 11.6667V10ZM9.99935 18.3333C9.79102 18.3333 9.58615 18.2917 9.38477 18.2083C9.18338 18.125 8.99935 18 8.83268 17.8333L2.16602 11.1667C1.99935 11 1.87435 10.816 1.79102 10.6146C1.70768 10.4132 1.66602 10.2083 1.66602 10C1.66602 9.79167 1.70768 9.58681 1.79102 9.38542C1.87435 9.18403 1.99935 9.00001 2.16602 8.83334L8.83268 2.16667C8.99935 2.00001 9.18338 1.87501 9.38477 1.79167C9.58615 1.70834 9.79102 1.66667 9.99935 1.66667C10.2077 1.66667 10.4125 1.70834 10.6139 1.79167C10.8153 1.87501 10.9993 2.00001 11.166 2.16667L17.8327 8.83334C17.9993 9.00001 18.1243 9.18403 18.2077 9.38542C18.291 9.58681 18.3327 9.79167 18.3327 10C18.3327 10.2083 18.291 10.4132 18.2077 10.6146C18.1243 10.816 17.9993 11 17.8327 11.1667L11.166 17.8333C10.9993 18 10.8153 18.125 10.6139 18.2083C10.4125 18.2917 10.2077 18.3333 9.99935 18.3333Z"
fill="#0276FE"
/>
</G>
</Svg>
);
};
export default MapDirectionFilledIcon;

View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import Svg, { Mask, Rect, G, Path } from 'react-native-svg';
const MapDirectionIcon = () => (
<Svg width={21} height={20} viewBox="0 0 21 20" fill="none">
<Mask id="mask0_4968_87584" maskUnits="userSpaceOnUse" x={0} y={0} width={21} height={20}>
<Rect x={0.599609} width={20} height={20} fill="#D9D9D9" />
</Mask>
<G mask="url(#mask0_4968_87584)">
<Path
d="M8.7243 9.79038H12.0576V10.7214C12.0576 10.8838 12.129 10.9957 12.2716 11.0573C12.4142 11.1186 12.5443 11.0906 12.6618 10.9731L13.9983 9.63642C14.133 9.50045 14.2003 9.34177 14.2003 9.16038C14.2003 8.97885 14.1319 8.8201 13.9951 8.68413L12.6618 7.35767C12.5443 7.24017 12.4142 7.21059 12.2716 7.26892C12.129 7.32725 12.0576 7.43802 12.0576 7.60121V8.54038H8.22742C8.01409 8.54038 7.8352 8.61253 7.69076 8.75684C7.54645 8.90128 7.4743 9.08017 7.4743 9.2935V11.6654C7.4743 11.8425 7.53423 11.9909 7.65409 12.1106C7.77395 12.2304 7.92242 12.2904 8.09951 12.2904C8.27673 12.2904 8.42513 12.2304 8.54471 12.1106C8.66444 11.9909 8.7243 11.8425 8.7243 11.6654V9.79038ZM10.601 17.8031C10.4129 17.8031 10.2276 17.7654 10.0451 17.6902C9.86263 17.6149 9.69589 17.5019 9.54492 17.3512L3.2468 11.0531C3.0961 10.9028 2.98312 10.7368 2.90784 10.555C2.83256 10.3732 2.79492 10.1883 2.79492 10.0004C2.79492 9.81232 2.83256 9.62704 2.90784 9.44454C2.98312 9.26204 3.0961 9.09531 3.2468 8.94434L9.54492 2.64621C9.6952 2.49552 9.86124 2.38253 10.043 2.30725C10.2249 2.23197 10.4097 2.19434 10.5976 2.19434C10.7857 2.19434 10.971 2.23197 11.1535 2.30725C11.336 2.38253 11.5027 2.49552 11.6537 2.64621L17.9518 8.94434C18.1025 9.09461 18.2155 9.26065 18.2908 9.44246C18.366 9.62427 18.4037 9.80913 18.4037 9.99704C18.4037 10.1851 18.366 10.3704 18.2908 10.5529C18.2155 10.7354 18.1025 10.9021 17.9518 11.0531L11.6537 17.3512C11.5034 17.5019 11.3374 17.6149 11.1555 17.6902C10.9737 17.7654 10.7889 17.8031 10.601 17.8031ZM10.423 16.4891C10.4711 16.5372 10.5299 16.5612 10.5993 16.5612C10.6687 16.5612 10.7275 16.5372 10.7755 16.4891L17.0897 10.175C17.1378 10.1269 17.1618 10.0682 17.1618 9.99871C17.1618 9.92927 17.1378 9.87052 17.0897 9.82246L10.7755 3.50829C10.7275 3.46024 10.6687 3.43621 10.5993 3.43621C10.5299 3.43621 10.4711 3.46024 10.423 3.50829L4.10888 9.82246C4.06082 9.87052 4.0368 9.92927 4.0368 9.99871C4.0368 10.0682 4.06082 10.1269 4.10888 10.175L10.423 16.4891Z"
fill="#0276FE"
/>
</G>
</Svg>
);
export default MapDirectionIcon;

View File

@@ -0,0 +1,56 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
const OldFeedbacksIcon = () => (
<Svg width={24} height={24} viewBox="0 0 24 24" fill="none">
<Path
d="M17.5298 15.1372V19.0042C17.5298 19.3351 17.2602 19.6047 16.9293 19.6047H4.55428C4.51265 19.6047 4.47206 19.6177 4.43816 19.6418L1.81612 21.5117C1.68373 21.6061 1.5 21.5114 1.5 21.3488V7.98674C1.5 7.65585 1.76961 7.38623 2.10051 7.38623H9.26888"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M5.25 10.917H10.5346"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M5.32227 13.5645H13.0512"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M5.32227 16.2119H10.1324"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M13.1929 9.91056C14.0317 11.4503 15.6634 12.496 17.548 12.496C20.2828 12.496 22.5006 10.2783 22.5006 7.54342C22.5006 4.80858 20.2828 2.59082 17.548 2.59082C15.0556 2.59082 12.9927 4.43269 12.6465 6.82927"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M14.6341 5.93871L12.5432 7.09133L11.3906 5.00049"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
<Path
d="M17.4219 5.7041V8.01378L18.9042 9.49612"
stroke="#0276FE"
strokeWidth={1.12}
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
export default OldFeedbacksIcon;

View File

@@ -0,0 +1,16 @@
import * as React from 'react';
import Svg, { Path, Rect } from 'react-native-svg';
const RankTagIcon = () => (
<Svg width={54} height={27} viewBox="0 0 54 27" fill="none">
<Path d="M0 0H44V20H0V0Z" fill="#0276FE" />
<Path d="M4 20L4 26.5L0 20L4 20Z" fill="#025ECB" />
<Rect x={34.5} y={0.5} width={19} height={19} rx={9.5} fill="#E6F1FF" stroke="#025ECB" />
<Path
d="M6.85073 14V6.72727H9.578C10.1367 6.72727 10.6055 6.82434 10.9843 7.01847C11.3654 7.21259 11.653 7.48485 11.8472 7.83523C12.0437 8.18324 12.1419 8.58925 12.1419 9.05327C12.1419 9.51965 12.0425 9.92448 11.8436 10.2678C11.6471 10.6087 11.3571 10.8726 10.9736 11.0597C10.5901 11.2443 10.119 11.3366 9.56024 11.3366H7.61777V10.2429H9.38269C9.70939 10.2429 9.97691 10.1979 10.1852 10.108C10.3936 10.0156 10.5475 9.88187 10.6469 9.70668C10.7487 9.52912 10.7996 9.31132 10.7996 9.05327C10.7996 8.79522 10.7487 8.57505 10.6469 8.39276C10.5451 8.2081 10.39 8.06842 10.1817 7.97372C9.97336 7.87666 9.70466 7.82812 9.37559 7.82812H8.1682V14H6.85073ZM10.6078 10.7045L12.4083 14H10.9381L9.16962 10.7045H10.6078ZM14.7674 14.1101C14.4218 14.1101 14.1105 14.0485 13.8335 13.9254C13.5588 13.8 13.341 13.6153 13.1801 13.3714C13.0214 13.1276 12.9421 12.8269 12.9421 12.4695C12.9421 12.1617 12.999 11.9072 13.1126 11.706C13.2262 11.5047 13.3813 11.3438 13.5778 11.223C13.7743 11.1023 13.9956 11.0111 14.2418 10.9496C14.4904 10.8857 14.7473 10.8395 15.0124 10.8111C15.332 10.7779 15.5913 10.7483 15.7901 10.7223C15.989 10.6939 16.1334 10.6513 16.2234 10.5945C16.3157 10.5353 16.3619 10.4441 16.3619 10.321V10.2997C16.3619 10.0322 16.2826 9.82505 16.124 9.67827C15.9653 9.53149 15.7369 9.4581 15.4386 9.4581C15.1237 9.4581 14.874 9.52675 14.6893 9.66406C14.507 9.80137 14.3839 9.96354 14.32 10.1506L13.1197 9.98011C13.2144 9.64867 13.3706 9.37169 13.5884 9.14915C13.8062 8.92424 14.0726 8.75616 14.3874 8.64489C14.7023 8.53125 15.0503 8.47443 15.4315 8.47443C15.6943 8.47443 15.9559 8.50521 16.2163 8.56676C16.4767 8.62831 16.7146 8.73011 16.9301 8.87216C17.1455 9.01184 17.3183 9.20241 17.4485 9.44389C17.5811 9.68537 17.6474 9.98722 17.6474 10.3494V14H16.4116V13.2507H16.369C16.2909 13.4022 16.1808 13.5443 16.0387 13.6768C15.899 13.8071 15.7227 13.9124 15.5096 13.9929C15.2989 14.071 15.0515 14.1101 14.7674 14.1101ZM15.1012 13.1655C15.3593 13.1655 15.583 13.1146 15.7724 13.0128C15.9618 12.9086 16.1074 12.7713 16.2092 12.6009C16.3133 12.4304 16.3654 12.2446 16.3654 12.0433V11.4006C16.3252 11.4337 16.2565 11.4645 16.1595 11.4929C16.0648 11.5213 15.9582 11.5462 15.8399 11.5675C15.7215 11.5888 15.6043 11.6077 15.4883 11.6243C15.3723 11.6409 15.2717 11.6551 15.1865 11.6669C14.9947 11.6929 14.8231 11.7356 14.6715 11.7947C14.52 11.8539 14.4005 11.9368 14.3129 12.0433C14.2253 12.1475 14.1815 12.2824 14.1815 12.4482C14.1815 12.6849 14.2679 12.8636 14.4407 12.9844C14.6135 13.1051 14.8337 13.1655 15.1012 13.1655ZM20.1255 10.804V14H18.84V8.54545H20.0687V9.4723H20.1326C20.2581 9.1669 20.4581 8.92424 20.7328 8.74432C21.0097 8.56439 21.3518 8.47443 21.759 8.47443C22.1355 8.47443 22.4633 8.55492 22.7427 8.71591C23.0244 8.87689 23.2422 9.11009 23.3961 9.41548C23.5524 9.72088 23.6293 10.0914 23.6269 10.527V14H22.3414V10.7259C22.3414 10.3613 22.2467 10.076 22.0573 9.87003C21.8703 9.66406 21.6111 9.56108 21.2796 9.56108C21.0547 9.56108 20.8547 9.6108 20.6795 9.71023C20.5067 9.80729 20.3705 9.94815 20.2711 10.1328C20.174 10.3175 20.1255 10.5412 20.1255 10.804ZM26.0056 12.2884L26.0021 10.7365H26.208L28.1683 8.54545H29.6704L27.2592 11.2301H26.9928L26.0056 12.2884ZM24.8338 14V6.72727H26.1193V14H24.8338ZM28.257 14L26.4815 11.5178L27.348 10.6122L29.7947 14H28.257Z"
fill="white"
/>
</Svg>
);
export default RankTagIcon;

View File

@@ -0,0 +1,15 @@
import * as React from 'react';
import Svg, { Path } from 'react-native-svg';
const TickSmallIcon = () => (
<Svg width={20} height={20} viewBox="0 0 20 20" fill="none">
<Path
d="M5.56934 10.2172L8.24744 12.77L14.3241 6.67773"
stroke="white"
strokeWidth={1.4}
strokeLinecap="round"
strokeLinejoin="round"
/>
</Svg>
);
export default TickSmallIcon;

View File

@@ -0,0 +1,18 @@
import * as React from 'react';
import Svg, { G, Mask, Path, Rect } from 'react-native-svg';
const TickSmallSolidIcon = () => (
<Svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<Rect width="12" height="12" rx="6" fill="#0276FE" />
<Mask id="mask0_4968_87067" maskUnits="userSpaceOnUse" x="0" y="0" width="12" height="12">
<Rect width="12" height="12" fill="#D9D9D9" />
</Mask>
<G mask="url(#mask0_4968_87067)">
<Path
d="M4.77486 7.575L9.01236 3.3375C9.11236 3.2375 9.22903 3.1875 9.36236 3.1875C9.49569 3.1875 9.61236 3.2375 9.71236 3.3375C9.81236 3.4375 9.86236 3.55625 9.86236 3.69375C9.86236 3.83125 9.81236 3.95 9.71236 4.05L5.12486 8.65C5.02486 8.75 4.90819 8.8 4.77486 8.8C4.64153 8.8 4.52486 8.75 4.42486 8.65L2.27486 6.5C2.17486 6.4 2.12694 6.28125 2.13111 6.14375C2.13528 6.00625 2.18736 5.8875 2.28736 5.7875C2.38736 5.6875 2.50611 5.6375 2.64361 5.6375C2.78111 5.6375 2.89986 5.6875 2.99986 5.7875L4.77486 7.575Z"
fill="white"
/>
</G>
</Svg>
);
export default TickSmallSolidIcon;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1346,27 +1346,27 @@ export const CLICKSTREAM_EVENT_NAMES = {
},
FA_UNSYNC_FEEDBACK_CAPTURED: {
name: 'FA_UNSYNC_FEEDBACK_CAPTURED',
description: 'Unsync feedback captured'
description: 'Unsync feedback captured',
},
FA_UNSYNC_FEEDBACK_CAPTURING: {
name: 'FA_UNSYNC_FEEDBACK_CAPTURING',
description: 'Feedback capturing'
description: 'Feedback capturing',
},
FA_UNSYNC_FEEDBACK_CAPTURE_SUCCESS: {
name: 'FA_UNSYNC_FEEDBACK_CAPTURE_SUCCESS',
description: 'Feedback capture success'
description: 'Feedback capture success',
},
FA_FEEDBACK_SUCCESS: {
name: 'FA_FEEDBACK_SUCCESS',
description: 'Feedback capture success'
description: 'Feedback capture success',
},
FA_ORIGINAL_IMAGE_UPLOADING: {
name: 'FA_ORIGINAL_IMAGE_UPLOADING',
description: 'Original image uploading'
description: 'Original image uploading',
},
FA_WRONG_QUESTION_KEY: {
name: 'FA_WRONG_QUESTION_KEY',
description: 'Wrong question key'
description: 'Wrong question key',
},
FA_API_FAILED: {
name: 'FA_API_FAILED',
@@ -1495,7 +1495,45 @@ export const CLICKSTREAM_EVENT_NAMES = {
FA_COSMOS_CONNECTED_CALL_EVENTS: {
name: 'FA_COSMOS_CONNECTED_CALL_EVENTS',
description: 'Cosmos connected call events',
}
},
// Top Addresses
FA_VIEW_TOP_ADDRESSES_CLICKED: {
name: 'FA_VIEW_TOP_ADDRESSES_CLICKED',
description: 'View top addresses clicked',
},
FA_TOP_ADDRESSES_SCREEN_LOADED: {
name: 'FA_TOP_ADDRESSES_SCREEN_LOADED',
description: 'Top addresses screen loaded',
},
FA_DIRECTIONS_BUTTON_CLICKED: {
name: 'FA_DIRECTIONS_BUTTON_CLICKED',
description: 'Directions button clicked',
},
FA_OPEN_MAP_BUTTON_CLICKED: {
name: 'FA_OPEN_MAP_BUTTON_CLICKED',
description: 'Open map button clicked',
},
FA_VIEW_SIMILAR_ADDRESSES_BUTTON_CLICKED: {
name: 'FA_VIEW_SIMILAR_ADDRESSES_BUTTON_CLICKED',
description: 'View similar addresses button clicked',
},
FA_VIEW_OTHER_ADDRESSES_CLICKED: {
name: 'FA_VIEW_OTHER_ADDRESSES_CLICKED',
description: 'View other addresses clicked',
},
FA_OTHER_ADDRESSES_SCREEN_LOADED: {
name: 'FA_OTHER_ADDRESSES_SCREEN_LOADED',
description: 'Top addresses screen loaded',
},
FA_CAROUSEL_ADDRESS_PAGINATION_CLICKED: {
name: 'FA_CAROUSEL_ADDRESS_PAGINATION_CLICKED',
description: 'Carousel address pagination clicked',
},
FA_CAROUSEL_ADDRESS_SWIPE_GESTURE: {
name: 'FA_CAROUSEL_ADDRESS_SWIPE_GESTURE',
description: 'Carousel address swipe gesture',
},
} as const;
export enum MimeType {
@@ -1635,7 +1673,6 @@ export const HIT_SLOP = {
right: 8,
};
export const SELF_CALL_SHARED_PREFERENCE_KEY = 'selfCallSharedPreference';
export const LITMUS_URL = 'https://longhorn.navi.com/litmus';

View File

@@ -1,11 +1,7 @@
import React, { useEffect } from 'react';
import React from 'react';
import { Modal, NativeSyntheticEvent, View, findNodeHandle } from 'react-native';
import { IModalWrapper } from '../../RN-UI-LIB/src/components/modalWrapper/ModalWrapper';
import {
clearBottomSheet,
sendBottomSheetOpenSignal,
setBottomSheetView,
} from '../components/utlis/DeviceUtils';
import { clearBottomSheet, setBottomSheetView } from '../components/utlis/DeviceUtils';
import { GenericStyles } from '@rn-ui-lib/styles';
const ModalWrapperForAlfredV2: React.FC<IModalWrapper> = ({ children, ...props }) => {

41
src/common/ShadowLine.tsx Normal file
View File

@@ -0,0 +1,41 @@
import { StyleSheet, Text, View } from 'react-native';
import React from 'react';
const ShadowLine = () => {
return (
<>
<View style={styles.line1} />
<View style={styles.line2} />
<View style={styles.line3} />
</>
);
};
const styles = StyleSheet.create({
line1: {
position: 'absolute',
top: -1,
left: 0,
right: 0,
height: 1,
backgroundColor: 'rgba(0,0,0,0.05)',
},
line2: {
position: 'absolute',
top: -2,
left: 0,
right: 0,
height: 1,
backgroundColor: 'rgba(0,0,0,0.025)',
},
line3: {
position: 'absolute',
top: -3,
left: 0,
right: 0,
height: 1,
backgroundColor: 'rgba(0,0,0,0.0125)',
},
});
export default ShadowLine;

View File

@@ -8,6 +8,7 @@ export const CopilotStep = ({
name,
order,
text,
subText,
children,
active = true,
width,
@@ -63,6 +64,7 @@ export const CopilotStep = ({
registerStep({
name,
text,
subText,
order,
measure,
wrapperRef,

View File

@@ -54,6 +54,11 @@ export const Tooltip: React.FC<ITooltip> = ({ labels }) => {
<Text testID="stepDescription" style={styles.tooltipText}>
{currentStep?.text}
</Text>
{currentStep?.subText ? (
<Text testID="stepDescription" style={styles.tooltipSubText}>
{currentStep?.subText}
</Text>
) : null}
<View
style={[
GenericStyles.row,

View File

@@ -28,6 +28,12 @@ export const styles = StyleSheet.create({
fontSize: 16,
fontWeight: '700',
},
tooltipSubText: {
color: COLORS.TEXT.WHITE,
fontSize: 14,
fontWeight: '500',
marginTop: 8,
},
nextBtn: {
paddingHorizontal: 21,
paddingVertical: 4,

View File

@@ -10,6 +10,7 @@ export interface Step {
wrapperRef: React.RefObject<NativeMethods>;
measure: () => Promise<LayoutRectangle>;
text: ReactNode;
subText?: ReactNode;
isCircularHighlight?: boolean;
noHighlightArea?: boolean;
}
@@ -105,6 +106,7 @@ export interface CopilotStepProps {
name: string;
order: number;
text: ReactNode;
subText?: ReactNode;
children: React.ReactElement<any>;
active?: boolean;
width?: number;

View File

@@ -0,0 +1,93 @@
import React from 'react';
import { FlatList, View } from 'react-native';
import CarouselItem from './CarouselItem';
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated';
import { GenericStyles, SCREEN_WIDTH } from '@rn-ui-lib/styles';
import Pagination from './Pagination';
import { ICarousel } from './interfaces';
import { GenericType } from '@common/GenericTypes';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
const Carousel: React.FC<ICarousel> = ({
data,
gapBetweenItems,
sideItemScale,
horizontalPadding,
renderItem,
isLoading,
}) => {
const scrollX = useSharedValue(0);
const canMomentum = React.useRef(false);
const [activePageNumber, setActivePageNumber] = React.useState(1);
const flatListRef = React.useRef<FlatList<GenericType>>(null);
const onScrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollX.value = event.contentOffset.x;
},
});
const scrollTo = (index: number) => {
flatListRef.current?.scrollToOffset({
offset: index * SCREEN_WIDTH,
animated: true,
});
setActivePageNumber(index + 1);
};
const handleMomentumScrollBegin = () => {
canMomentum.current = true;
};
const handleMomentumScrollEnd = () => {
if (canMomentum.current) {
const index = Math.round(scrollX.value / SCREEN_WIDTH);
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CAROUSEL_ADDRESS_SWIPE_GESTURE, {
page: index,
});
setActivePageNumber(index + 1);
}
canMomentum.current = false;
};
return (
<View style={GenericStyles.fill}>
<Animated.FlatList
ref={flatListRef}
data={data}
renderItem={({ item, index }) => (
<CarouselItem
item={item}
scrollX={scrollX}
index={index}
gapBetweenItems={gapBetweenItems}
sideItemScale={sideItemScale}
horizontalPadding={horizontalPadding}
renderItem={renderItem}
/>
)}
horizontal
scrollEnabled={!isLoading}
pagingEnabled
showsHorizontalScrollIndicator={false}
onScroll={onScrollHandler}
removeClippedSubviews={false}
onMomentumScrollBegin={handleMomentumScrollBegin}
onMomentumScrollEnd={handleMomentumScrollEnd}
/>
<View style={[GenericStyles.centerAlignedRow, GenericStyles.pb16]}>
<Pagination
data={data}
scrollX={scrollX}
scrollTo={scrollTo}
activePageNumber={activePageNumber}
isLoading={isLoading}
/>
</View>
</View>
);
};
export default Carousel;

View File

@@ -0,0 +1,62 @@
import { StyleSheet, View } from 'react-native';
import React from 'react';
import { SCREEN_WIDTH } from '@rn-ui-lib/styles';
import Animated, { Extrapolation, interpolate, useAnimatedStyle } from 'react-native-reanimated';
import { COLORS } from '@rn-ui-lib/colors';
import { GAP_BETWEEN_ITEMS, HORIZONTAL_PADDING, SIDE_ITEM_SCALE } from './constants';
import { ICarouselItem } from './interfaces';
const CarouselItem: React.FC<ICarouselItem> = ({
item,
scrollX,
index,
horizontalPadding = HORIZONTAL_PADDING,
gapBetweenItems = GAP_BETWEEN_ITEMS,
sideItemScale = SIDE_ITEM_SCALE,
renderItem,
}) => {
const TRANSLATE_X = horizontalPadding + gapBetweenItems + ((1 - 0.8) / 2) * SCREEN_WIDTH;
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: interpolate(
scrollX.value,
[(index - 1) * SCREEN_WIDTH, index * SCREEN_WIDTH, (index + 1) * SCREEN_WIDTH],
[-TRANSLATE_X, 0, TRANSLATE_X],
Extrapolation.CLAMP
),
},
{
scale: interpolate(
scrollX.value,
[(index - 1) * SCREEN_WIDTH, index * SCREEN_WIDTH, (index + 1) * SCREEN_WIDTH],
[sideItemScale, 1, sideItemScale],
Extrapolation.CLAMP
),
},
],
};
});
return (
<Animated.View style={[styles.container, animatedStyle]}>
<View style={styles.listItem}>{renderItem({ item, index })}</View>
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
paddingVertical: 16,
paddingHorizontal: HORIZONTAL_PADDING,
width: SCREEN_WIDTH,
},
listItem: {
height: '100%',
borderRadius: 20,
elevation: 6,
backgroundColor: COLORS.BACKGROUND.PRIMARY,
},
});
export default CarouselItem;

View File

@@ -0,0 +1,37 @@
import { StyleSheet, View } from 'react-native';
import { IPagination } from './interfaces';
import PaginationItem from './PaginationItem';
import { CopilotStep } from '@components/Tour/components/CopilotStep';
const Pagination: React.FC<IPagination> = ({ data, activePageNumber, scrollTo, isLoading }) => {
if (!data?.length) return null;
return (
<CopilotStep text="👇 Click below to navigate between Top 5 Addresses." order={3} name="step 3">
<View style={styles.pagination}>
{data?.map((_, index) => (
<PaginationItem
key={index}
index={index}
scrollTo={scrollTo}
activePageNumber={activePageNumber}
isLastItem={index === data.length - 1}
isVisited={data?.[index]?.visited}
isLoading={isLoading}
/>
))}
</View>
</CopilotStep>
);
};
const styles = StyleSheet.create({
pagination: {
flexDirection: 'row',
alignItems: 'center',
borderRadius: 20,
padding: 8,
backgroundColor: 'rgba(50, 70, 91, 0.40)',
},
});
export default Pagination;

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { Pressable, StyleSheet, View } from 'react-native';
import { IPaginationItem } from './interfaces';
import { COLORS } from '@rn-ui-lib/colors';
import Text from '@rn-ui-lib/components/Text';
import TickSmallSolidIcon from '@assets/icons/TickSmallSolidIcon';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
const PaginationItem: React.FC<IPaginationItem> = ({
index,
activePageNumber,
isLastItem,
scrollTo,
isVisited,
isLoading,
}) => {
const pageNumber = index + 1;
const isActivePage = activePageNumber === pageNumber;
return (
<Pressable
onPress={() => {
if (isLoading) return;
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CAROUSEL_ADDRESS_PAGINATION_CLICKED, {
page: index,
});
scrollTo(index);
}}
style={isLoading && styles.opacity70}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
>
<View style={[styles.numberCTA, isLastItem ? styles.mr0 : {}]}>
<Text style={[styles.numberText, isActivePage ? styles.activeNumberText : {}]}>
{isLastItem ? 'Others' : pageNumber}
</Text>
{isVisited ? (
<View style={styles.paginationContainer}>
<TickSmallSolidIcon />
</View>
) : null}
</View>
</Pressable>
);
};
const styles = StyleSheet.create({
numberCTA: {
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
paginationContainer: {
position: 'absolute',
top: 14,
left: 10,
},
activeNumberText: {
backgroundColor: COLORS.BACKGROUND.PRIMARY,
color: COLORS.TEXT.DARK,
borderColor: COLORS.BORDER.PRIMARY,
},
numberText: {
fontSize: 13,
color: COLORS.TEXT.WHITE,
paddingHorizontal: 5,
height: 20,
textAlign: 'center',
textAlignVertical: 'center',
lineHeight: 16,
borderRadius: 10,
borderWidth: 1,
borderColor: 'transparent',
},
mr0: {
marginRight: 0,
},
opacity70: {
opacity: 0.7,
},
});
export default PaginationItem;

View File

@@ -0,0 +1,3 @@
export const HORIZONTAL_PADDING = 30;
export const SIDE_ITEM_SCALE = 0.8;
export const GAP_BETWEEN_ITEMS = 12;

View File

@@ -0,0 +1,40 @@
import { GenericType } from '@common/GenericTypes';
import { SharedValue } from 'react-native-reanimated';
type RenderItem = (data: { item: GenericType; index: number }) => React.ReactNode;
export interface ICarouselItem {
item: GenericType;
scrollX: SharedValue<number>;
index: number;
renderItem: RenderItem;
horizontalPadding?: number;
sideItemScale?: number;
gapBetweenItems?: number;
}
export interface ICarousel {
data: GenericType[];
horizontalPadding?: number;
gapBetweenItems?: number;
sideItemScale?: number;
renderItem: RenderItem;
isLoading: boolean;
}
export interface IPagination {
data: GenericType[];
scrollX: SharedValue<number>;
activePageNumber: number;
scrollTo: (index: number) => void;
isLoading: boolean;
}
export interface IPaginationItem {
index: number;
activePageNumber: number;
isLastItem: boolean;
scrollTo: (index: number) => void;
isVisited: boolean;
isLoading: boolean;
}

View File

@@ -7,13 +7,12 @@ import { GenericStyles, SCREEN_WIDTH } from '../../../RN-UI-LIB/src/styles';
import { GenericObject, GenericType } from '../../common/GenericTypes';
import StarRating from '../../../RN-UI-LIB/src/components/star_rating/StarRating';
import { getPhoneNumberString, memoize } from '../utlis/commonFunctions';
import {
getImageHeightWrtAspectRatio,
} from '../../services/casePayload.transformer';
import { getImageHeightWrtAspectRatio } from '../../services/casePayload.transformer';
import { formatAmount } from '../../../RN-UI-LIB/src/utlis/amount';
import GeolocationAddressAnswer from './components/GeolocationAddressAnswer';
import dayjs from 'dayjs';
import { BUSINESS_DATE_FORMAT, CUSTOM_ISO_DATE_FORMAT } from '@rn-ui-lib/utils/dates';
import AddressAnswer from './components/AddressAnswer';
import { CaseAllocationType } from '@screens/allCases/interface';
const RATING_COMPONENT = 'Rating';
const MAX_RATING = 5;
@@ -51,7 +50,9 @@ const DarkBoldText = ({ text }: IDarkBoldText) => {
const AnswerRender: React.FC<IAnswerRender> = (props) => {
const { answer, visited, section, caseId, journey, metaData, questionId } = props;
const data = useAppSelector((state) => state.case.caseForm?.[caseId]?.[journey]);
const caseType = useAppSelector((state) => state.allCases.caseDetails[caseId]?.caseType);
const caseType = useAppSelector(
(state) => state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.COLLECTION_CASE
);
const templateData = useAppSelector((state) => state.case.templateData[caseType]);
const mobileNumbers =
useAppSelector((state) => state?.telephoneNumbers?.telephoneNumbers?.[caseId]) || [];
@@ -122,7 +123,7 @@ const AnswerRender: React.FC<IAnswerRender> = (props) => {
}
return <DarkBoldText text={answer.answer} />;
case AnswerType.image:
if(!imageDoc?.fileUri) return null;
if (!imageDoc?.fileUri) return null;
return (
<ImageBackground
style={[styles.image, { height: Number(imageHeightWrtAspectRatio) || 350 }]}
@@ -133,9 +134,7 @@ const AnswerRender: React.FC<IAnswerRender> = (props) => {
></ImageBackground>
);
case AnswerType.address:
return (
<GeolocationAddressAnswer caseId={caseId} addressId={answer?.answer} metadata={metaData} />
);
return <AddressAnswer caseId={caseId} addressId={answer?.answer} />;
case AnswerType.phoneNumber:
return <DarkBoldText text={getPhoneNumberStringFromNumber(answer.answer)} />;
case AnswerType.date:

View File

@@ -35,7 +35,7 @@ const Submit: React.FC<ISubmit> = (props) => {
const data = useAppSelector((state) => state.case.caseForm?.[caseId]?.[journey]);
const caseType = useAppSelector(
(state) =>
state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE
state.allCases.caseDetails[caseId]?.caseType || CaseAllocationType.COLLECTION_CASE
);
const templateData = useAppSelector((state) => state.case.templateData[caseType]);
const sections = templateData?.sections;

View File

@@ -0,0 +1,31 @@
import React, { useMemo } from 'react';
import { useAppSelector } from '../../../hooks';
import Text from '../../../../RN-UI-LIB/src/components/Text';
import { RootState } from '../../../store/store';
import { GenericStyles } from '@rn-ui-lib/styles';
import { View } from 'react-native';
interface IAddressAnswer {
caseId: string;
addressId: string;
}
const AddressAnswer: React.FC<IAddressAnswer> = ({ caseId, addressId }) => {
const feedbackAddresses = useAppSelector(
(state: RootState) => state.topAddresses?.[caseId]?.feedbackAddresses
);
const address = useMemo(() => {
return feedbackAddresses?.find((address) => address.referenceId === addressId);
}, [feedbackAddresses, addressId]);
return (
<View>
<Text dark style={[GenericStyles.fw700]}>
{[address?.pinCode, address?.city].filter(Boolean).join(', ') || '-'}
</Text>
<Text>{address?.addressText?.trim()}</Text>
</View>
);
};
export default AddressAnswer;

View File

@@ -0,0 +1,123 @@
import { getAddressString } from '@components/utlis/commonFunctions';
import Text from '@rn-ui-lib/components/Text';
import RadioGroup from '@rn-ui-lib/components/radio_button/RadioGroup';
import { GenericStyles } from '@rn-ui-lib/styles';
import React from 'react';
import RNRadioButton from '@rn-ui-lib/components/radio_button/RadioButton';
import { TouchableOpacity, View } from 'react-native';
import { useAppDispatch, useAppSelector } from '@hooks';
import { RootState } from '@store';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { AnswerType } from '@components/form/interface';
import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader';
import LoadingIcon from '@rn-ui-lib/icons/LoadingIcon';
import { COLORS } from '@rn-ui-lib/colors';
import NoLocationsIcon from '@rn-ui-lib/icons/NoLocationIcon';
import { getFeedbackAddresses } from '@screens/addresses/actions';
import { IAddressSelectionBody } from './interface';
const AddressSelectionBody = (props: IAddressSelectionBody) => {
const { caseId, questionId, error, widgetId, sectionId, control, questionType, value, onChange } =
props;
const feedbackAddresses = useAppSelector(
(state: RootState) => state.topAddresses?.[caseId]?.feedbackAddresses
);
const isFeedbackAddressesLoading = useAppSelector(
(state: RootState) => state.topAddresses?.[caseId]?.isFeedbackAddressesLoading
);
const dispatch = useAppDispatch();
const handleChange = (change: string) => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_FORM_ELEMENT_CHANGED, {
caseId,
questionType,
value: change,
questionId,
error,
sectionId,
widgetId,
});
onChange({
answer: change,
type: AnswerType.address,
});
};
const getAddresses = () => {
dispatch(getFeedbackAddresses(caseId));
};
if (isFeedbackAddressesLoading) {
return (
<View style={GenericStyles.mt10}>
{[...Array(7).keys()].map((_, index) => (
<LineLoader
key={index}
width="100%"
height={50}
style={[GenericStyles.br8, GenericStyles.mb20]}
/>
))}
</View>
);
}
if (!feedbackAddresses?.length) {
return (
<View
style={[
GenericStyles.fill,
GenericStyles.whiteBackground,
GenericStyles.mt10,
GenericStyles.br8,
GenericStyles.pv24,
]}
>
<View style={[GenericStyles.centerAlignedRow, GenericStyles.ph16, GenericStyles.mb12]}>
<NoLocationsIcon />
<Text light style={GenericStyles.ml4}>
No addresses found
</Text>
</View>
<TouchableOpacity
activeOpacity={0.7}
onPress={getAddresses}
style={[GenericStyles.row, GenericStyles.centerAligned]}
>
<Text style={[GenericStyles.p12]}>
<LoadingIcon fillColor={COLORS.BASE.BLUE} />
</Text>
<Text style={GenericStyles.textBlue}>Retry</Text>
</TouchableOpacity>
</View>
);
}
return (
<RadioGroup value={value?.answer} onValueChange={handleChange} orientation="vertical">
{feedbackAddresses?.map((address) => {
return (
<RNRadioButton
key={address?.referenceId}
id={address?.referenceId}
value={getAddressString(address)}
customOptionTemplate={
<View>
<Text dark style={[GenericStyles.fw700]}>
{[address.pinCode, address.city].filter(Boolean).join(', ') || '-'}
</Text>
<Text>{address?.addressText?.trim()}</Text>
</View>
}
containerStyle={GenericStyles.containerStyle}
/>
);
})}
</RadioGroup>
);
};
export default AddressSelectionBody;

View File

@@ -0,0 +1,47 @@
import { View } from 'react-native';
import React from 'react';
import { useAppSelector } from '../../../../hooks';
import { CaseAllocationType } from '../../../../screens/allCases/interface';
import { isQuestionMandatory, validateInput } from '../../services/validation.service';
import { Control, Controller } from 'react-hook-form';
import ErrorMessage from '../ErrorMessage';
import { GenericStyles } from '../../../../../RN-UI-LIB/src/styles';
import Text from '@rn-ui-lib/components/Text';
import AddressSelectionBody from './AddressSelectionBody';
import { IAddressSelectionV2 } from './interface';
const AddressSelectionV2: React.FC<IAddressSelectionV2> = (props) => {
const { questionId, error, widgetId, sectionId, control } = props;
const template = useAppSelector(
(state) => state.case.templateData[CaseAllocationType.COLLECTION_CASE]
);
const question = template?.questions?.[questionId];
const controllerName = `widgetContext.${widgetId}.sectionContext.${sectionId}.questionContext.${questionId}`;
return (
<View>
<Text dark bold>
{question?.text || ' '}
{isQuestionMandatory(question) && <Text style={GenericStyles.redText}>*</Text>}
</Text>
<ErrorMessage
show={
error?.widgetContext?.[widgetId]?.sectionContext?.[sectionId]?.questionContext?.[
questionId
]
}
/>
<Controller
key={controllerName}
control={control}
rules={{ validate: (data) => validateInput(data, question.metadata.validators) }}
render={({ field: { onChange, value } }) => (
<AddressSelectionBody value={value} onChange={onChange} {...props} />
)}
name={controllerName}
/>
</View>
);
};
export default AddressSelectionV2;

View File

@@ -0,0 +1,18 @@
import { GenericType } from '@common/GenericTypes';
import { Control } from 'react-hook-form';
export interface IAddressSelectionV2 {
questionType: string;
questionId: string;
widgetId: string;
journeyId: string;
caseId: string;
sectionId: string;
control: Control<GenericType, GenericType>;
error: GenericType;
}
export interface IAddressSelectionBody extends IAddressSelectionV2 {
value: GenericType;
onChange: (...event: GenericType[]) => void;
}

View File

@@ -32,7 +32,7 @@ const PhoneNumberSelection: React.FC<IPhoneNumberSelection> = (props) => {
const { caseId, questionId, error, widgetId, sectionId, control, questionType } = props;
const currentCase = useAppSelector((state) => state.allCases?.caseDetails?.[caseId]) || {};
const { mobileNumbers, loading } = useMobileNumbers(caseId);
const caseType = currentCase?.caseType || CaseAllocationType.ADDRESS_VERIFICATION_CASE;
const caseType = currentCase?.caseType || CaseAllocationType.COLLECTION_CASE;
const template = useAppSelector((state) => state.case.templateData[caseType]);
const question = template.questions[questionId];
const handleChange = (change: string | null, onChange: (...event: any[]) => void) => {

View File

@@ -6,10 +6,10 @@ import Rating from '../components/Rating';
import TextArea from '../components/TextArea';
import TextInput from '../components/TextInput';
import Dropdown from '../components/Dropdown';
import AddressSelection from '../components/AddressSelection';
import PhoneNumberSelection from '../components/PhoneNumberSelection';
import DateInput from '../components/DateInput';
import TimeInput from '../components/TimeInput';
import AddressSelectionV2 from '../components/AddressSelection/AddressSelectionV2';
export const FormComponentList = {
TextInput,
@@ -20,7 +20,7 @@ export const FormComponentList = {
Rating,
Dropdown,
CheckboxGroup,
AddressSelection,
AddressSelection: AddressSelectionV2,
PhoneNumberSelection,
DateInput,
TimeInput,

View File

@@ -8,9 +8,7 @@ export const getCollectionFeedbackOnAddressPreDefinedJourney = (
isGeolocation?: boolean
) => {
try {
const templateStr = isGeolocation
? JSON.stringify(templateJson.geolocationTemplate)
: JSON.stringify(templateJson.addressTemplate);
const templateStr = JSON.stringify(templateJson.addressTemplate);
const updatedStr = templateStr.replace(key, value);
return JSON.parse(updatedStr);
} catch (err) {

View File

@@ -113,6 +113,8 @@ export enum ApiKeys {
GET_REPAYMENTS = 'GET_REPAYMENTS',
GET_FEEDBACK_HISTORY = 'GET_FEEDBACK_HISTORY',
GET_PRIORTIY_FEEDBACK = 'GET_PRIORTIY_FEEDBACK',
GET_TOP_ADDRESSES = 'GET_TOP_ADDRESSES',
GET_FEEDBACK_ADDRESSES = 'GET_FEEDBACK_ADDRESSES',
GET_TRAINING_MATERIAL_LIST = 'GET_TRAINING_MATERIAL_LIST',
GET_TRAINING_MATERIAL_DETAILS = 'GET_TRAINING_MATERIAL_DETAILS',
SELF_CALL_ACK= '/api/v1/self-call'
@@ -204,7 +206,6 @@ API_URLS[ApiKeys.CALL_CUSTOMER] = '/call-recording/v2/call-request';
API_URLS[ApiKeys.SYNC_ACTIVE_CALL_DETAILS] = '/call-recording/call-status';
API_URLS[ApiKeys.GET_CALL_HISTORY] = '/call-recording/v3/call-history';
API_URLS[ApiKeys.SYNC_CALL_FEEDBACK_NUDGE_DETAILS] =
'/call-recording/acknowledge-feedback-nudge/{callId}';
API_URLS[ApiKeys.FETCH_CUSTOMER_DOCUMENTS] = '/documents/{loanAccountNumber}';
API_URLS[ApiKeys.FETCH_AGENT_DOCUMENTS] = '/documents/agent';
@@ -223,6 +224,8 @@ API_URLS[ApiKeys.GET_PRIORTIY_FEEDBACK] = '/feedback/case-status';
API_URLS[ApiKeys.GET_TRAINING_MATERIAL_LIST] = '/training-page/content-list';
API_URLS[ApiKeys.GET_TRAINING_MATERIAL_DETAILS] = '/training-page/{docRefId}';
API_URLS[ApiKeys.SELF_CALL_ACK] = '/sync-data/self-call-metadata';
API_URLS[ApiKeys.GET_TOP_ADDRESSES] = '/collection-cases/unified-locations';
API_URLS[ApiKeys.GET_FEEDBACK_ADDRESSES] = '/collection-cases/unified-locations/lite';
export const API_STATUS_CODE = {
OK: 200,

View File

@@ -410,6 +410,11 @@ export const getGoogleMapUrl = (latitude: string | number, longitude: string | n
return `https://www.google.com/maps/search/${latitude},+${longitude}`;
};
export const getGoogleMapUrlForAddressText = (address: string) => {
if (!address) return;
return `https://www.google.com/maps/search/?api=1&query=${address}`;
};
export const isValidAmountEntered = (value: number) => {
return typeof value === 'number' && !isNaN(value);
};

View File

@@ -0,0 +1,91 @@
import { createSlice } from '@reduxjs/toolkit';
import {
IFeedbackAddress,
IFeedbackAddressMap,
ILocationData,
} from '@screens/addresses/interfaces';
type ITopAddressesSlice = Record<
string,
{
addresses: ILocationData[];
totalLocationEntities: number;
otherAddresses: ILocationData[];
isTopAddressesLoading: boolean;
isOtherAddressesLoading: boolean;
feedbackAddresses: IFeedbackAddress[];
feedbackAddressesMap: Record<string, IFeedbackAddressMap>;
isFeedbackAddressesLoading: boolean;
}
>;
const initialState: ITopAddressesSlice = {};
const TopAddressesSlice = createSlice({
name: 'topAddresses',
initialState,
reducers: {
setTopAddresses: (state, action) => {
const { caseId, addresses = [], totalLocationEntities } = action.payload;
state[caseId] = {
...(state?.[caseId] || {}),
addresses,
totalLocationEntities,
};
},
setTopAddressesLoading: (state, action) => {
const { caseId, isLoading } = action.payload;
state[caseId] = {
...(state?.[caseId] || {}),
isTopAddressesLoading: isLoading,
};
},
setOtherAddresses: (state, action) => {
const { caseId, addresses = [] } = action.payload;
state[caseId] = {
...(state?.[caseId] || {}),
otherAddresses: addresses,
};
},
setOtherAddressesLoading: (state, action) => {
const { caseId, isLoading } = action.payload;
state[caseId] = {
...(state?.[caseId] || {}),
isOtherAddressesLoading: isLoading,
};
},
setFeedbackAddresses: (state, action) => {
const { caseId, addresses = [] } = action.payload;
const feedbackAddressesMap = addresses?.reduce(
(acc: Record<string, IFeedbackAddressMap>, address: IFeedbackAddress) => {
acc[address.referenceId] = { locationType: address?.locationType };
return acc;
},
{}
);
state[caseId] = {
...(state?.[caseId] || {}),
feedbackAddresses: addresses,
feedbackAddressesMap,
};
},
setFeedbackAddressesLoading: (state, action) => {
const { caseId, isLoading } = action.payload;
state[caseId] = {
...(state?.[caseId] || {}),
isFeedbackAddressesLoading: isLoading,
};
},
},
});
export const {
setTopAddresses,
setTopAddressesLoading,
setOtherAddresses,
setOtherAddressesLoading,
setFeedbackAddresses,
setFeedbackAddressesLoading,
} = TopAddressesSlice.actions;
export default TopAddressesSlice.reducer;

View File

@@ -0,0 +1,87 @@
import axiosInstance, { ApiKeys, getApiUrl } from '@components/utlis/apiHelper';
import {
setFeedbackAddresses,
setFeedbackAddressesLoading,
setOtherAddresses,
setOtherAddressesLoading,
setTopAddresses,
setTopAddressesLoading,
} from '@reducers/topAddressesSlice';
import { AppDispatch } from '@store';
import { PAGE_END } from './constants';
export const getTopAddresses =
(caseId: string, start: number, end: number) => (dispatch: AppDispatch) => {
dispatch(setTopAddressesLoading({ caseId, isLoading: true }));
const url = getApiUrl(ApiKeys.GET_TOP_ADDRESSES);
axiosInstance
.get(url, {
params: { caseReferenceId: caseId, startingRank: start, endingRank: end },
})
.then((res) => {
if (res?.data) {
const { unifiedLocations = [], totalLocationEntities = 0 } = res?.data || {};
dispatch(
setTopAddresses({
caseId,
addresses: unifiedLocations,
totalLocationEntities: totalLocationEntities - 5,
})
);
}
})
.catch((error) => {})
.finally(() => {
dispatch(setTopAddressesLoading({ caseId, isLoading: false }));
});
};
export const getOtherAddresses = (caseId: string) => (dispatch: AppDispatch) => {
dispatch(setOtherAddressesLoading({ caseId, isLoading: true }));
const url = getApiUrl(ApiKeys.GET_TOP_ADDRESSES);
axiosInstance
.get(url, {
params: {
caseReferenceId: caseId,
startingRank: PAGE_END + 1,
},
})
.then((res) => {
if (res?.data) {
const { unifiedLocations = [] } = res?.data || {};
dispatch(
setOtherAddresses({
caseId,
addresses: unifiedLocations,
})
);
}
})
.finally(() => {
dispatch(setOtherAddressesLoading({ caseId, isLoading: false }));
});
};
export const getFeedbackAddresses = (caseId: string) => (dispatch: AppDispatch) => {
dispatch(setFeedbackAddressesLoading({ caseId, isLoading: true }));
const url = getApiUrl(ApiKeys.GET_FEEDBACK_ADDRESSES);
axiosInstance
.get(url, {
params: {
caseReferenceId: caseId,
},
})
.then((res) => {
if (res?.data) {
dispatch(
setFeedbackAddresses({
caseId,
addresses: res.data || [],
})
);
}
})
.finally(() => {
dispatch(setFeedbackAddressesLoading({ caseId, isLoading: false }));
});
};

View File

@@ -0,0 +1,93 @@
import { Linking, Pressable, StyleSheet, View } from 'react-native';
import React from 'react';
import Text from '@rn-ui-lib/components/Text';
import { ITopAddressItemAddressString, LocationType } from '../interfaces';
import LollipopIcon from '@assets/icons/LollipopIcon';
import { GenericStyles } from '@rn-ui-lib/styles';
import { copyAddressToClipboard } from '@screens/addressGeolocation/utils/copyAddressText';
import MapDirectionIcon from '@assets/icons/MapDirectionIcon';
import { getGoogleMapUrl, getGoogleMapUrlForAddressText } from '@components/utlis/commonFunctions';
import CopyOutlineIcon from '@assets/icons/CopyOutlineIcon';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
const AddressItemAddressString = (props: ITopAddressItemAddressString) => {
const { locationDetails, showMapIcon = true, caseId, actionContainerStyle } = props;
const { addressText, latitude, longitude, locationSubType } = locationDetails || {};
const handleCopyAddress = () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COPY_ADDRESS_CLICKED, {
caseId,
addressText,
});
copyAddressToClipboard(addressText);
};
const handleOpenLocation = () => {
const isGeoLocation =
locationSubType === LocationType.GEO_LOCATION ||
locationSubType === LocationType.SKIP_TRACE_ADDRESS;
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_OPEN_MAP_BUTTON_CLICKED, {
caseId,
...(isGeoLocation ? { latitude, longitude } : { addressText }),
locationSubType,
});
const mapUrl = isGeoLocation
? getGoogleMapUrl(latitude, longitude)
: getGoogleMapUrlForAddressText(addressText);
if (mapUrl) {
return Linking.openURL(mapUrl);
}
};
return (
<View style={styles.container}>
<View style={[GenericStyles.mt4, GenericStyles.mr2]}>
<LollipopIcon />
</View>
<View style={styles.fs1}>
<Text dark>{addressText}</Text>
<View
style={[
GenericStyles.row,
GenericStyles.mt16,
GenericStyles.alignCenter,
actionContainerStyle,
]}
>
<Pressable onPress={handleCopyAddress} hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}>
<CopyOutlineIcon />
</Pressable>
{showMapIcon ? (
<Pressable
onPress={handleOpenLocation}
style={GenericStyles.ml20}
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
>
<MapDirectionIcon />
</Pressable>
) : null}
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
marginHorizontal: 16,
paddingTop: 8,
},
p4: {
padding: 4,
},
fs1: {
flexShrink: 1,
},
});
export default AddressItemAddressString;

View File

@@ -0,0 +1,64 @@
import { View } from 'react-native';
import React from 'react';
import { ITopAddressItemFeedback } from '../interfaces';
import { GenericStyles } from '@rn-ui-lib/styles';
import Text from '@rn-ui-lib/components/Text';
import dayjs from 'dayjs';
import { BUSINESS_DATE_FORMAT } from '@rn-ui-lib/utils/dates';
import { getFeedbackColors } from '../utils';
const AddressItemFeedback = (props: ITopAddressItemFeedback) => {
const { latestFeedback } = props;
const {
latestFeedbackStatus,
latestFeedbackTimestamp,
feedbackColourCode,
feedbackBgColourCode,
promiseToPayDate,
} = latestFeedback || {};
const { feedbackColor, feedbackBgColor } = getFeedbackColors(
latestFeedbackStatus,
feedbackColourCode,
feedbackBgColourCode
);
return (
<View
style={[
GenericStyles.ph16,
GenericStyles.pv12,
GenericStyles.borderBottom,
{ backgroundColor: feedbackBgColor },
]}
>
<Text small>
Last feedback{' '}
{latestFeedbackTimestamp ? (
<Text small style={[GenericStyles.fw700, GenericStyles.lh18]}>
({dayjs(latestFeedbackTimestamp).format(BUSINESS_DATE_FORMAT)})
</Text>
) : null}
</Text>
<Text
numberOfLines={1}
ellipsizeMode="tail"
small
style={[
GenericStyles.fw700,
GenericStyles.lh18,
GenericStyles.mt4,
{ color: feedbackColor },
]}
>
{latestFeedbackStatus
? `${latestFeedbackStatus} ${
promiseToPayDate ? `on ${dayjs(promiseToPayDate).format(BUSINESS_DATE_FORMAT)}` : ''
}`
: 'Unvisited address'}
</Text>
</View>
);
};
export default AddressItemFeedback;

View File

@@ -0,0 +1,92 @@
import { StyleSheet, View } from 'react-native';
import React, { useMemo } from 'react';
import { ITopAddressItemHeader } from '../interfaces';
import Text from '@rn-ui-lib/components/Text';
import { GenericStyles } from '@rn-ui-lib/styles';
import { useAppSelector } from '@hooks';
import { getDistanceFromLatLonInKm } from '@components/utlis/commonFunctions';
import relativeDistanceFormatter from '@screens/addressGeolocation/utils/relativeDistanceFormatter';
import TopAddressItemRankTag from '../topAddresses/TopAddressItemRankTag';
import { COLORS } from '@rn-ui-lib/colors';
import { CopilotStep } from '@components/Tour/components/CopilotStep';
const AddressItemHeader = (props: ITopAddressItemHeader) => {
const { locationDetails, isOtherAddressView, containerStyle, isCoachMarkVisible } = props;
const { pinCode, city, latitude, longitude, rank, visited } = locationDetails || {};
const deviceGeolocationCoordinate = useAppSelector(
(state) => state.foregroundService?.deviceGeolocationCoordinate
);
const relativeDistanceBwLatLong = useMemo(() => {
const distance = getDistanceFromLatLonInKm(deviceGeolocationCoordinate, {
latitude,
longitude,
});
return `${relativeDistanceFormatter(distance)} km`;
}, [deviceGeolocationCoordinate]);
const addressString = useMemo(() => {
return [pinCode, city].filter(Boolean).join(', ');
}, [pinCode, city]);
return (
<View style={[styles.container, GenericStyles.borderBottom, containerStyle]}>
{isOtherAddressView ? (
<Text small style={styles.rankCircle}>
{rank}
</Text>
) : isCoachMarkVisible ? (
<CopilotStep
text="👋 Introducing Ranked Top 5 Addresses!"
subText="Visit as per ranks to reach the customer."
order={1}
name="step 1"
>
<View>
<TopAddressItemRankTag rank={rank} visited={visited} />
</View>
</CopilotStep>
) : (
<TopAddressItemRankTag rank={rank} visited={visited} />
)}
<View style={[GenericStyles.row, GenericStyles.pr16]}>
<Text
ellipsizeMode="tail"
numberOfLines={1}
dark
style={[GenericStyles.fw700, GenericStyles.ml8, styles.mw70]}
>
{addressString ? addressString : '-'}
</Text>
<Text style={GenericStyles.ml8}>({relativeDistanceBwLatLong})</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginLeft: -5,
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
},
rankCircle: {
marginLeft: 21,
width: 23,
height: 23,
borderRadius: 13,
backgroundColor: COLORS.BACKGROUND.SILVER,
borderWidth: 1,
borderColor: COLORS.BORDER.PRIMARY,
textAlignVertical: 'center',
textAlign: 'center',
lineHeight: 17,
fontSize: 10,
},
mw70: {
maxWidth: '70%',
},
});
export default AddressItemHeader;

View File

@@ -0,0 +1,14 @@
import { TagVariant } from '@rn-ui-lib/components/Tag';
export enum LocationSourceMap {
VKYC = 'VKYC',
DATA_SUTRAM = 'Skip Tracing',
PRIMARY = 'Allocated address',
}
export const LocationSourceTagVariantMap: Record<string, TagVariant> = {
'ALLOCATED_ADDRESS': TagVariant.white,
};
export const PAGE_START = 1;
export const PAGE_END = 5;

View File

@@ -0,0 +1,166 @@
import { IPredefinedAddressScreenTemplate } from '@interfaces/template.types';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { StyleProp, ViewStyle } from 'react-native';
export interface ILabel {
label: string;
val: string;
}
export interface ILatestFeedback {
locationReferenceId: string;
latestFeedbackStatus: string;
latestFeedbackStatusTag: string;
latestFeedbackTimestamp: number;
feedbackColourCode: string;
feedbackBgColourCode: string;
isSuspicious: boolean;
feedbackPresent: boolean;
promiseToPayDate: string;
}
export interface ILocationData {
referenceId: string;
customerReferenceId: string;
addressText: string;
pinCode: string;
city: string;
latitude: number;
longitude: number;
visited: boolean;
rank: number;
timestamp: number;
similarLocationEntities: ISimilarAddressEntity[];
labels: ILabel[];
relatedLocationIds: string[];
latestFeedback: ILatestFeedback;
locationSubType: string;
}
export interface ILocationDetails {
locationDetails: ILocationData;
}
export interface ITopAddressItem extends ILocationDetails {
caseId: string;
isCoachMarkVisible: boolean;
}
export interface ITopAddressItemHeader {
locationDetails: ILocationData;
isCoachMarkVisible?: boolean;
isOtherAddressView?: boolean;
containerStyle?: StyleProp<ViewStyle>;
}
export interface ITopAddressItemFeedback {
latestFeedback: ILatestFeedback;
}
export interface ITopAddressItemTags {
clusterLabels: ILabel[];
}
export interface ITopAddressItemAddressString {
locationDetails: ILocationData;
showMapIcon?: boolean;
caseId: string;
actionContainerStyle?: StyleProp<ViewStyle>;
}
export interface ITopAddressItemBottomActions extends ILocationDetails {
caseId: string;
}
export interface ITopAddressItemRankTag {
rank: number;
visited: boolean;
}
export interface ITopAddressSimilarAddresses extends ILocationDetails {
caseId: string;
}
interface ISimilarAddressEntity {
id: string;
addressText: string;
pinCode: string;
city: string;
state: string;
source: string;
order: number;
capturedAt: number;
}
export interface ITopAddressSimilarAddressCard {
similarAddressDetails: ISimilarAddressEntity;
caseId: string;
}
export interface IOtherAddressItem extends ILocationDetails {
caseId: string;
}
export interface IFeedbackAddress {
referenceId: string;
addressText: string;
city: string;
pinCode: string;
rank: number;
source: string;
capturedAt: string;
locationType: string;
}
export interface IFeedbackAddressMap {
locationType: string;
}
export enum LocationType {
GEO_LOCATION = 'GEO_LOCATION',
SKIP_TRACE_ADDRESS = 'SKIP_TRACE_ADDRESS',
}
export interface IOtherAddressActions extends ILocationDetails {
caseId: string;
}
export interface IOtherAddresses {
route: {
params: {
loanAccountNumber: string;
caseId: string;
};
};
}
export interface IOtherAddressList {
caseId: string;
}
export interface ITopAddress {
route: {
params: {
loanAccountNumber: string;
caseId: string;
};
};
}
export interface ITopAddressOtherAddressesItem {
caseId: string;
}
export interface IOpenOldFeedbacks {
loanAccountNumber: string;
addressText: string;
relatedLocationIds: string[];
referenceId: string;
caseId: string;
}
export interface INavigateToAddFeedbackScreen {
prefilledAddressScreenTemplate: IPredefinedAddressScreenTemplate | undefined;
caseId: string;
referenceId: string;
screen: CaseDetailStackEnum;
}

View File

@@ -0,0 +1,108 @@
import { StyleSheet, View } from 'react-native';
import React from 'react';
import { GenericStyles } from '@rn-ui-lib/styles';
import OldFeedbacksIcon from '@assets/icons/OldFeedbacksIcon';
import { COLORS } from '@rn-ui-lib/colors';
import AddFeedbackIcon from '@assets/icons/AddFeedbackIcon';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { useAppDispatch, useAppSelector } from '@hooks';
import Button from '@rn-ui-lib/components/Button';
import { CaseAllocationType } from '@screens/allCases/interface';
import { handlePostOperativeHourActivities } from '@screens/addressGeolocation/utils/operativeHourUtils';
import { ToastMessages } from '@screens/allCases/constants';
import { IOtherAddressActions } from '../interfaces';
import { navigateToAddFeedbackScreen, openOldFeedbacks } from '../utils';
const OtherAddressActions = (props: IOtherAddressActions) => {
const { locationDetails, caseId } = props;
const { addressText, relatedLocationIds, referenceId } = locationDetails || {};
const loanAccountNumber = useAppSelector(
(state) => state?.allCases?.caseDetails?.[caseId]?.loanAccountNumber
);
const addingNewFeedbackDisabled = useAppSelector(
(state) => state?.postOperationalHourRestrictionsSlice?.postOperationalHourRestrictions
);
const prefilledAddressScreenTemplate = useAppSelector(
(state) =>
state.case.templateData[CaseAllocationType.COLLECTION_CASE]?.prefilledAddressScreenTemplate
);
const dispatch = useAppDispatch();
const handleOpenOldFeedbacks = () => {
openOldFeedbacks({
loanAccountNumber,
addressText: addressText,
relatedLocationIds: relatedLocationIds,
referenceId: referenceId,
caseId,
});
};
const handleAddFeedback = () => {
dispatch(
navigateToAddFeedbackScreen({
prefilledAddressScreenTemplate,
caseId,
referenceId,
screen: CaseDetailStackEnum.OTHER_ADDRESSES,
})
);
};
const handleDisableAddFeedback = () => {
handlePostOperativeHourActivities(
ToastMessages.DISABLE_ADD_FEEDBACK_AFTER_POST_OPERATIVE_HOURS
);
};
return (
<View
style={[
GenericStyles.pb8,
GenericStyles.pt16,
GenericStyles.ph16,
GenericStyles.row,
GenericStyles.spaceBetween,
GenericStyles.alignCenter,
]}
>
<Button
title="Old feedbacks"
variant="primaryText"
pressableWidthChange={false}
onPress={handleOpenOldFeedbacks}
leftIcon={<OldFeedbacksIcon />}
opacityChangeOnPress
underlayColor="transparent"
textStyle={[GenericStyles.fontSize13, GenericStyles.ml4]}
/>
<View style={styles.divider} />
<Button
title="Add feedback"
variant="primaryText"
pressableWidthChange={false}
onPress={addingNewFeedbackDisabled ? handleDisableAddFeedback : handleAddFeedback}
leftIcon={<AddFeedbackIcon />}
opacityChangeOnPress={!addingNewFeedbackDisabled}
underlayColor="transparent"
style={addingNewFeedbackDisabled && styles.disabledButton}
textStyle={[GenericStyles.fontSize13, GenericStyles.ml4]}
/>
</View>
);
};
const styles = StyleSheet.create({
divider: {
height: 28,
width: 1,
backgroundColor: COLORS.BORDER.PRIMARY,
},
disabledButton: {
color: COLORS.TEXT.BLUE,
opacity: 0.5,
},
});
export default OtherAddressActions;

View File

@@ -0,0 +1,38 @@
import { StyleSheet, View } from 'react-native';
import React from 'react';
import { COLORS } from '@rn-ui-lib/colors';
import AddressItemHeader from '@screens/addresses/common/AddressItemHeader';
import AddressItemFeedback from '@screens/addresses/common/AddressItemFeedback';
import AddressItemAddressString from '@screens/addresses/common/AddressItemAddressString';
import OtherAddressActions from './OtherAddressActions';
import { GenericStyles } from '@rn-ui-lib/styles';
import TopAddressSimilarAddresses from '../similarAddresses/TopAddressSimilarAddresses';
import { IOtherAddressItem } from '../interfaces';
const OtherAddressItem = (props: IOtherAddressItem) => {
const { locationDetails, caseId } = props;
return (
<View style={styles.container}>
<AddressItemHeader
locationDetails={locationDetails}
isOtherAddressView={true}
containerStyle={[GenericStyles.pv16]}
/>
<AddressItemFeedback latestFeedback={locationDetails?.latestFeedback} />
<AddressItemAddressString locationDetails={locationDetails} caseId={caseId} />
<TopAddressSimilarAddresses locationDetails={locationDetails} caseId={caseId} />
<OtherAddressActions locationDetails={locationDetails} caseId={caseId} />
</View>
);
};
const styles = StyleSheet.create({
container: {
borderRadius: 20,
elevation: 2,
backgroundColor: COLORS.BACKGROUND.PRIMARY,
marginBottom: 16,
},
});
export default OtherAddressItem;

View File

@@ -0,0 +1,60 @@
import React from 'react';
import { ActivityIndicator, FlatList, RefreshControl, View } from 'react-native';
import OtherAddressItem from './OtherAddressItem';
import { GenericStyles } from '@rn-ui-lib/styles';
import Text from '@rn-ui-lib/components/Text';
import { useAppDispatch, useAppSelector } from '@hooks';
import { COLORS } from '@rn-ui-lib/colors';
import { getOtherAddresses } from '../actions';
import { IOtherAddressList } from '../interfaces';
const OtherAddressList = (props: IOtherAddressList) => {
const { caseId } = props;
const otherAddresses = useAppSelector(
(state) => state.topAddresses?.[caseId]?.otherAddresses || []
);
const isOtherAddressesLoading = useAppSelector(
(state) => state.topAddresses?.[caseId]?.isOtherAddressesLoading
);
const dispatch = useAppDispatch();
const onRefresh = React.useCallback(() => {
dispatch(getOtherAddresses(caseId));
}, []);
if (isOtherAddressesLoading) {
return (
<View style={[GenericStyles.fill, GenericStyles.centerAlignedRow]}>
<ActivityIndicator size="large" color={COLORS.BASE.BLUE} />
</View>
);
}
return (
<View style={GenericStyles.pb16}>
{otherAddresses?.length > 0 ? (
<FlatList
data={otherAddresses}
refreshControl={<RefreshControl refreshing={false} onRefresh={onRefresh} />}
contentContainerStyle={[GenericStyles.p16, GenericStyles.mb24]}
renderItem={({ item }) => <OtherAddressItem locationDetails={item} caseId={caseId} />}
/>
) : (
<View
style={[
GenericStyles.fill,
GenericStyles.justifyContentCenter,
GenericStyles.alignCenter,
]}
>
<Text light style={[GenericStyles.mt16]} dark>
No addresses found
</Text>
</View>
)}
</View>
);
};
export default OtherAddressList;

View File

@@ -0,0 +1,45 @@
import { View } from 'react-native';
import React, { useEffect } from 'react';
import Layout from '@screens/layout/Layout';
import { GenericStyles } from '@rn-ui-lib/styles';
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
import { goBack } from '@components/utlis/navigationUtlis';
import { useAppDispatch, useAppSelector } from '@hooks';
import OtherAddressList from './OtherAddressList';
import { getOtherAddresses } from '../actions';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { IOtherAddresses } from '../interfaces';
const OtherAddresses = ({ route: routeParams }: IOtherAddresses) => {
const {
params: { caseId },
} = routeParams;
const otherAddresses = useAppSelector(
(state) => state.topAddresses?.[caseId]?.otherAddresses || []
);
const dispatch = useAppDispatch();
useEffect(() => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_OTHER_ADDRESSES_SCREEN_LOADED, {
caseId,
});
dispatch(getOtherAddresses(caseId));
}, []);
const totalAddresses = otherAddresses?.length;
return (
<Layout>
<View style={[GenericStyles.fill, GenericStyles.pb24, GenericStyles.silverBackground]}>
<NavigationHeader
title={`Other addresses ${totalAddresses ? `(${totalAddresses})` : ''}`}
onBack={goBack}
/>
<OtherAddressList caseId={caseId} />
</View>
</Layout>
);
};
export default OtherAddresses;

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { Pressable, StyleSheet, View } from 'react-native';
import { GenericStyles } from '@rn-ui-lib/styles';
import { COLORS } from '@rn-ui-lib/colors';
import Text from '@rn-ui-lib/components/Text';
import { sanitizeString } from '@components/utlis/commonFunctions';
import { copyAddressToClipboard } from '@screens/addressGeolocation/utils/copyAddressText';
import CopyOutlineIcon from '@assets/icons/CopyOutlineIcon';
import { BUSINESS_DATE_FORMAT, dateFormat } from '@rn-ui-lib/utils/dates';
import { PRIMARY_SOURCE_MAPPING } from '@screens/addressGeolocation/constant';
import { ITopAddressSimilarAddressCard } from '../interfaces';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
const TopAddressSimilarAddressCard = (props: ITopAddressSimilarAddressCard) => {
const { similarAddressDetails, caseId } = props;
const handleCopyAddress = () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_COPY_ADDRESS_CLICKED, {
caseId,
address: similarAddressDetails?.addressText,
});
copyAddressToClipboard(similarAddressDetails?.addressText);
};
return (
<View style={[styles.container, GenericStyles.columnDirection, , { flex: 1 }]}>
<Text dark style={[GenericStyles.fw700]}>
{sanitizeString(
[similarAddressDetails?.pinCode, similarAddressDetails?.city].filter(Boolean).join(', ')
)}
</Text>
<Text dark>{similarAddressDetails?.addressText}</Text>
<Pressable
onPress={handleCopyAddress}
style={[GenericStyles.mt10]}
hitSlop={{ top: 8, right: 8, bottom: 8, left: 8 }}
>
<CopyOutlineIcon />
</Pressable>
{similarAddressDetails?.capturedAt || similarAddressDetails?.source ? (
<View style={[GenericStyles.mt10, GenericStyles.row, GenericStyles.alignCenter]}>
{similarAddressDetails?.capturedAt ? (
<Text small style={GenericStyles.lh18}>
{sanitizeString(
`${dateFormat(new Date(similarAddressDetails?.capturedAt), BUSINESS_DATE_FORMAT)}`
)}
</Text>
) : null}
{similarAddressDetails?.capturedAt && similarAddressDetails?.source ? (
<View style={styles.bullet} />
) : null}
{similarAddressDetails.source ? (
<Text small style={GenericStyles.lh18}>
{PRIMARY_SOURCE_MAPPING[
similarAddressDetails.source as keyof typeof PRIMARY_SOURCE_MAPPING
] ?? similarAddressDetails?.source}
</Text>
) : null}
</View>
) : null}
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: COLORS.BACKGROUND.SILVER,
marginHorizontal: 16,
marginVertical: 8,
borderRadius: 8,
padding: 16,
},
bullet: {
width: 4,
height: 4,
borderRadius: 4,
backgroundColor: COLORS.BACKGROUND.LIGHT,
marginHorizontal: 6,
},
});
export default TopAddressSimilarAddressCard;

View File

@@ -0,0 +1,94 @@
import { ScrollView, StyleSheet, TouchableOpacity, View, useWindowDimensions } from 'react-native';
import React, { useState } from 'react';
import { GenericStyles } from '@rn-ui-lib/styles';
import { pluralise } from '@components/utlis/commonFunctions';
import { COLORS } from '@rn-ui-lib/colors';
import RightChevronIcon from '@assets/icons/RightChevronIcon';
import BottomSheetWrapper from '@common/BottomSheetWrapper';
import Text from '@rn-ui-lib/components/Text';
import TopAddressSimilarAddressCard from './TopAddressSimilarAddressCard';
import { ToastContainer, toastConfigs } from '@rn-ui-lib/components/toast';
import { ITopAddressSimilarAddresses } from '../interfaces';
import AddressItemHeader from '../common/AddressItemHeader';
import AddressItemAddressString from '../common/AddressItemAddressString';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { addClickstreamEvent } from '@services/clickstreamEventService';
const TopAddressSimilarAddresses = (props: ITopAddressSimilarAddresses) => {
const { locationDetails, caseId } = props;
const { similarLocationEntities = [] } = locationDetails || {};
const showSimilarAddresses = similarLocationEntities?.length;
const [showBottomSheet, setShowBottomSheet] = useState(false);
const handleOpenSimilarLocations = () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VIEW_SIMILAR_ADDRESSES_BUTTON_CLICKED, {
caseId,
addressReferenceId: locationDetails?.referenceId,
});
setShowBottomSheet(true);
};
const handleClose = () => {
setShowBottomSheet(false);
};
const toastPosition = useWindowDimensions().height;
if (!showSimilarAddresses) return null;
return (
<View style={GenericStyles.mt16}>
<TouchableOpacity
activeOpacity={0.7}
onPress={handleOpenSimilarLocations}
style={[GenericStyles.row, GenericStyles.alignCenter, styles.container]}
>
<Text style={[GenericStyles.textBlue, GenericStyles.fontSize13]}>
View similar {pluralise(similarLocationEntities?.length, 'address', 'addresses')} (
{similarLocationEntities?.length})
</Text>
<View style={GenericStyles.mt2}>
<RightChevronIcon fillColor={COLORS.BASE.BLUE} />
</View>
</TouchableOpacity>
<BottomSheetWrapper
heightPercentage={90}
visible={showBottomSheet}
HeaderNode={() => (
<AddressItemHeader
containerStyle={styles.bottomSheetHeader}
locationDetails={locationDetails}
isOtherAddressView
/>
)}
allowBackdropClose={true}
setVisible={handleClose}
>
<ScrollView>
<AddressItemAddressString
locationDetails={locationDetails}
showMapIcon={false}
actionContainerStyle={GenericStyles.mb16}
caseId={caseId}
/>
{similarLocationEntities?.map((entity, index) => (
<TopAddressSimilarAddressCard similarAddressDetails={entity} caseId={caseId} />
))}
</ScrollView>
<View style={[GenericStyles.relative, { bottom: toastPosition }]}>
<ToastContainer config={toastConfigs} position="top" />
</View>
</BottomSheetWrapper>
</View>
);
};
export default TopAddressSimilarAddresses;
const styles = StyleSheet.create({
container: {
marginLeft: 34,
},
bottomSheetHeader: {
marginTop: -4,
paddingBottom: 16,
},
});

View File

@@ -0,0 +1,46 @@
import { ScrollView, View } from 'react-native';
import React from 'react';
import { ITopAddressItem } from '../interfaces';
import AddressItemHeader from '../common/AddressItemHeader';
import AddressItemFeedback from '../common/AddressItemFeedback';
import TopAddressItemTags from './TopAddressItemTags';
import AddressItemAddressString from '../common/AddressItemAddressString';
import TopAddressItemBottomActions from './TopAddressItemBottomActions';
import { GenericStyles } from '@rn-ui-lib/styles';
import TopAddressSimilarAddresses from '../similarAddresses/TopAddressSimilarAddresses';
import { useCopilot } from '@components/Tour/contexts/CopilotProvider';
import { useAppSelector } from '@hooks';
import { RootState } from '@store';
import { CoachMarkFeatures, showCoachMark } from '@actions/filterActions';
const TopAddressItem = (props: ITopAddressItem) => {
const { locationDetails, caseId, isCoachMarkVisible } = props;
const copilot = useCopilot();
const userId = useAppSelector((state: RootState) => state.user?.user?.referenceId);
const serverTimestamp = useAppSelector(
(state: RootState) => state.foregroundService.serverTimestamp
);
const startTour = () => {
if (userId && copilot.totalStepsNumber > 0) {
showCoachMark(CoachMarkFeatures.TOP_5_ADDRESSES, userId, serverTimestamp, copilot.start);
}
};
return (
<View style={GenericStyles.fill} onLayout={startTour}>
<AddressItemHeader
locationDetails={locationDetails}
isCoachMarkVisible={isCoachMarkVisible}
/>
<ScrollView>
<AddressItemFeedback latestFeedback={locationDetails?.latestFeedback} />
<TopAddressItemTags clusterLabels={locationDetails?.labels} />
<AddressItemAddressString locationDetails={locationDetails} caseId={caseId} />
<TopAddressSimilarAddresses locationDetails={locationDetails} caseId={caseId} />
</ScrollView>
<TopAddressItemBottomActions locationDetails={locationDetails} caseId={caseId} />
</View>
);
};
export default TopAddressItem;

View File

@@ -0,0 +1,90 @@
import { StyleSheet, View } from 'react-native';
import React from 'react';
import { GenericStyles } from '@rn-ui-lib/styles';
import Button from '@rn-ui-lib/components/Button';
import { ITopAddressItemBottomActions } from '../interfaces';
import ShadowLine from '@common/ShadowLine';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { useAppDispatch, useAppSelector } from '@hooks';
import { handlePostOperativeHourActivities } from '@screens/addressGeolocation/utils/operativeHourUtils';
import { ToastMessages } from '@screens/allCases/constants';
import { CaseAllocationType } from '@screens/allCases/interface';
import { COLORS } from '@rn-ui-lib/colors';
import { navigateToAddFeedbackScreen, openOldFeedbacks } from '../utils';
const TopAddressItemBottomActions = (props: ITopAddressItemBottomActions) => {
const { locationDetails, caseId } = props;
const { addressText, relatedLocationIds, referenceId } = locationDetails || {};
const addingNewFeedbackDisabled = useAppSelector(
(state) => state?.postOperationalHourRestrictionsSlice?.postOperationalHourRestrictions
);
const prefilledAddressScreenTemplate = useAppSelector(
(state) =>
state.case.templateData[CaseAllocationType.COLLECTION_CASE]?.prefilledAddressScreenTemplate
);
const loanAccountNumber = useAppSelector(
(state) => state?.allCases?.caseDetails?.[caseId]?.loanAccountNumber
);
const dispatch = useAppDispatch();
const handleOpenOldFeedbacks = () => {
openOldFeedbacks({
loanAccountNumber,
addressText: addressText,
relatedLocationIds: relatedLocationIds,
referenceId: referenceId,
caseId,
});
};
const handleDisableAddFeedback = () => {
handlePostOperativeHourActivities(
ToastMessages.DISABLE_ADD_FEEDBACK_AFTER_POST_OPERATIVE_HOURS
);
};
const handleAddFeedback = () => {
dispatch(
navigateToAddFeedbackScreen({
prefilledAddressScreenTemplate,
caseId,
referenceId,
screen: CaseDetailStackEnum.TOP_ADDRESSES,
})
);
};
return (
<View style={[GenericStyles.p16, GenericStyles.row]}>
<ShadowLine />
<Button
title="Old feedbacks"
variant="secondary"
pressableWidthChange={false}
onPress={handleOpenOldFeedbacks}
style={GenericStyles.fill}
/>
<Button
title="Add feedback"
pressableWidthChange={false}
onPress={addingNewFeedbackDisabled ? handleDisableAddFeedback : handleAddFeedback}
style={[
GenericStyles.fill,
GenericStyles.ml16,
addingNewFeedbackDisabled ? styles.disabledButton : null,
]}
/>
</View>
);
};
const styles = StyleSheet.create({
disabledButton: {
color: COLORS.TEXT.BLUE,
opacity: 0.5,
backgroundColor: COLORS.BACKGROUND.PRIMARY,
borderColor: COLORS.BORDER.PRIMARY,
},
});
export default TopAddressItemBottomActions;

View File

@@ -0,0 +1,42 @@
import { StyleSheet, View } from 'react-native';
import React from 'react';
import { ITopAddressItemRankTag } from '../interfaces';
import RankTagIcon from '@assets/icons/RankTagIcon';
import Text from '@rn-ui-lib/components/Text';
import VisitedTagIcon from '@assets/icons/VisitedTagIcon';
import { GenericStyles } from '@rn-ui-lib/styles';
import { COLORS } from '@rn-ui-lib/colors';
const TopAddressItemRankTag = (props: ITopAddressItemRankTag) => {
const { rank, visited } = props;
return (
<View style={GenericStyles.mt8}>
{visited ? (
<VisitedTagIcon />
) : (
<View style={styles.rankTagContainer}>
<RankTagIcon />
<Text small style={styles.rankText}>
{rank}
</Text>
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
rankTagContainer: {
position: 'relative',
alignSelf: 'flex-start',
},
rankText: {
position: 'absolute',
top: -2,
right: 6,
fontWeight: 'bold',
color: COLORS.TEXT.BLUE_DARK,
},
});
export default TopAddressItemRankTag;

View File

@@ -0,0 +1,36 @@
import { StyleSheet, View } from 'react-native';
import React from 'react';
import { ILabel, ITopAddressItemTags } from '../interfaces';
import { COLORS } from '@rn-ui-lib/colors';
import Tag, { TagVariant } from '@rn-ui-lib/components/Tag';
import { LocationSourceTagVariantMap } from '../constants';
const TopAddressItemTags = (props: ITopAddressItemTags) => {
const { clusterLabels = [] } = props;
if (clusterLabels?.length === 0) {
return null;
}
return (
<View style={styles.container}>
{clusterLabels?.map((clusterLabel: ILabel) => (
<Tag
key={clusterLabel?.label}
text={clusterLabel?.label}
variant={LocationSourceTagVariantMap[clusterLabel?.val] ?? TagVariant.violet}
/>
))}
</View>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: COLORS.BACKGROUND.GREY_LIGHT_3,
paddingHorizontal: 16,
paddingVertical: 12,
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
},
});
export default TopAddressItemTags;

View File

@@ -0,0 +1,71 @@
import { StyleSheet, View } from 'react-native';
import React from 'react';
import { GenericStyles } from '@rn-ui-lib/styles';
import Text from '@rn-ui-lib/components/Text';
import LocationOnMap3DIcon from '@assets/icons/LocationOnMap3DIcon';
import Button from '@rn-ui-lib/components/Button';
import { COLORS } from '@rn-ui-lib/colors';
import Chevron from '@rn-ui-lib/icons/Chevron';
import { navigateToScreen } from '@components/utlis/navigationUtlis';
import { useAppSelector } from '@hooks';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { ITopAddressOtherAddressesItem } from '../interfaces';
const TopAddressOtherAddressesItem = (props: ITopAddressOtherAddressesItem) => {
const { caseId } = props;
const totalLocationEntities = useAppSelector(
(state) => state.topAddresses?.[caseId]?.totalLocationEntities || 0
);
const navigateToOtherAddresses = () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VIEW_OTHER_ADDRESSES_CLICKED, {
caseId,
totalLocationEntities,
});
navigateToScreen(CaseDetailStackEnum.OTHER_ADDRESSES, { caseId });
};
return (
<View style={GenericStyles.fill}>
<View style={[GenericStyles.borderBottom, GenericStyles.p16]}>
<Text dark style={[GenericStyles.fw700]}>
Other Addresses
</Text>
</View>
<View
style={[GenericStyles.fill, GenericStyles.alignCenter, GenericStyles.justifyContentCenter]}
>
<LocationOnMap3DIcon />
<Text style={[GenericStyles.mt24]}>
{totalLocationEntities > 0 ? totalLocationEntities : 'No'} more addresses
</Text>
<Button
title="View"
rightIcon={
<View style={[GenericStyles.ml8, GenericStyles.mt2]}>
<Chevron fillColor={COLORS.TEXT.WHITE} />
</View>
}
disabled={totalLocationEntities <= 0}
style={[GenericStyles.alighSelfCenter, GenericStyles.mt16]}
buttonStyle={[styles.w100, styles.pv4, GenericStyles.centerAlignedRow]}
textStyle={GenericStyles.fontSize13}
pressableWidthChange={false}
onPress={navigateToOtherAddresses}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
w100: {
width: 100,
},
pv4: {
paddingVertical: 8,
},
});
export default TopAddressOtherAddressesItem;

View File

@@ -0,0 +1,125 @@
import { Pressable, StyleSheet, View } from 'react-native';
import React, { useEffect } from 'react';
import Layout from '@screens/layout/Layout';
import { GenericStyles } from '@rn-ui-lib/styles';
import NavigationHeader from '@rn-ui-lib/components/NavigationHeader';
import { goBack, navigateToScreen } from '@components/utlis/navigationUtlis';
import Carousel from '@components/carousel/Carousel';
import TopAddressItem from './TopAddressItem';
import TopAddressOtherAddressesItem from './TopAddressOtherAddressesItem';
import PlusIcon from '@rn-ui-lib/icons/PlusIcon';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { COLORS } from '@rn-ui-lib/colors';
import Text from '@rn-ui-lib/components/Text';
import { getTopAddresses } from '../actions';
import { useAppDispatch, useAppSelector } from '@hooks';
import LineLoader from '@rn-ui-lib/components/suspense_loader/LineLoader';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { ITopAddress } from '../interfaces';
import { PAGE_END, PAGE_START } from '../constants';
import { CopilotProvider } from '@components/Tour/contexts/CopilotProvider';
import { CopilotStep } from '@components/Tour/components/CopilotStep';
import Lottie from '@rn-ui-lib/components/lottie/Lottie';
const TopAddresses = ({ route: routeParams }: ITopAddress) => {
const {
params: { caseId },
} = routeParams;
const isTopAddressesLoading = useAppSelector(
(state) => state.topAddresses?.[caseId]?.isTopAddressesLoading
);
const addresses = useAppSelector((state) => state.topAddresses?.[caseId]?.addresses || []);
const dispatch = useAppDispatch();
// Added empty object to show "Other Addresses" at the end
const updatedAddresses = [...addresses, {}];
const handleNewAddressCta = () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ADD_NEW_ADDRESS_CLICKED, {
caseId,
});
navigateToScreen(CaseDetailStackEnum.NEW_ADDRESS);
};
useEffect(() => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_TOP_ADDRESSES_SCREEN_LOADED, {
caseId,
});
dispatch(getTopAddresses(caseId, PAGE_START, PAGE_END));
}, []);
return (
<CopilotProvider>
<Layout>
<View style={[GenericStyles.fill, GenericStyles.blueBackground]}>
<NavigationHeader
title="Top addresses"
onBack={goBack}
rightActionable={
<CopilotStep text="Tap above to add a new address 👆" order={4} name="step 4">
<Pressable
onPress={handleNewAddressCta}
style={({ pressed }) => [styles.btnContainer, { opacity: pressed ? 0.7 : 1 }]}
>
<PlusIcon />
<Text style={[GenericStyles.whiteText, GenericStyles.lh18]}>New</Text>
</Pressable>
</CopilotStep>
}
/>
<Carousel
data={isTopAddressesLoading ? Array.from({ length: 6 }) : updatedAddresses}
isLoading={isTopAddressesLoading}
renderItem={({ item, index }) => {
if (isTopAddressesLoading) {
return (
<View>
<LineLoader height={'100%'} width={'100%'} style={styles.loader} />
</View>
);
}
const isLastItem = updatedAddresses?.length - 1 === index;
return !isLastItem ? (
<TopAddressItem
locationDetails={item}
caseId={caseId}
isCoachMarkVisible={index === 0}
/>
) : (
<TopAddressOtherAddressesItem caseId={caseId} />
);
}}
/>
<CopilotStep
text="Swipe left to go to next address."
order={2}
name="step 2"
noHighlightArea
>
<Lottie source={require('@assets/lottie/SwipeAnimation.json')} />
</CopilotStep>
</View>
</Layout>
</CopilotProvider>
);
};
const styles = StyleSheet.create({
btnContainer: {
flexDirection: 'row',
gap: 8,
alignItems: 'center',
color: COLORS.TEXT.WHITE,
borderWidth: 1,
height: 28,
borderColor: COLORS.BACKGROUND.PRIMARY,
borderRadius: 4,
marginVertical: 14,
marginHorizontal: 16,
paddingHorizontal: 14,
},
loader: { borderRadius: 20, backgroundColor: '#929090' },
});
export default TopAddresses;

View File

@@ -0,0 +1,76 @@
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import { getTemplateRoute, navigateToScreen } from '@components/utlis/navigationUtlis';
import { CaseDetailStackEnum } from '@screens/caseDetails/CaseDetailStack';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import { INavigateToAddFeedbackScreen, IOpenOldFeedbacks } from './interfaces';
import { CaseAllocationType, TaskTitleUIMapping } from '@screens/allCases/interface';
import { getCollectionFeedbackOnAddressPreDefinedJourney } from '@components/utlis/addressGeolocationUtils';
import { updatePreDefinedCaseFormJourney } from '@reducers/caseReducer';
import { AppDispatch } from '@store';
import { COLORS } from '@rn-ui-lib/colors';
export const openOldFeedbacks = ({
loanAccountNumber,
addressText,
relatedLocationIds,
referenceId,
caseId,
}: IOpenOldFeedbacks) => {
const commonParams = {
loanAccountNumber,
addressText: addressText,
addressReferenceIds: relatedLocationIds?.join(','),
};
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ADDRESS_OLD_FEEDBACK_CLICKED, {
caseId,
addressId: referenceId,
});
navigateToScreen(CaseDetailStackEnum.PAST_FEEDBACK_DETAIL, commonParams);
};
export const navigateToAddFeedbackScreen =
({ prefilledAddressScreenTemplate, caseId, referenceId, screen }: INavigateToAddFeedbackScreen) =>
(dispatch: AppDispatch) => {
if (prefilledAddressScreenTemplate != null) {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_ADDRESS_ADD_FEEDBACK_CLICKED, {
caseId,
addressId: referenceId,
});
const addressKey = '{{addressReferenceId}}';
const { visitedWidgets, widgetContext } = getCollectionFeedbackOnAddressPreDefinedJourney(
prefilledAddressScreenTemplate,
addressKey,
referenceId
);
dispatch(
updatePreDefinedCaseFormJourney({
caseId,
journeyId: TaskTitleUIMapping.COLLECTION_FEEDBACK,
visitedWidgets,
widgetContext,
})
);
if (visitedWidgets?.length) {
const lastVisitedWidget = visitedWidgets[visitedWidgets.length - 1];
navigateToScreen(getTemplateRoute(lastVisitedWidget, CaseAllocationType.COLLECTION_CASE), {
caseId,
journey: TaskTitleUIMapping.COLLECTION_FEEDBACK,
handleCloseRouting: () => navigateToScreen(screen, { caseId }),
});
}
}
};
export const getFeedbackColors = (
latestFeedbackStatus: string,
feedbackColourCode?: string,
feedbackBgColourCode?: string
) => {
return {
feedbackColor: latestFeedbackStatus ? feedbackColourCode ?? COLORS.TEXT.BLACK : COLORS.TEXT.RED,
feedbackBgColor: latestFeedbackStatus
? feedbackBgColourCode ?? COLORS.BACKGROUND.SILVER
: COLORS.BACKGROUND.LIGHT_RED_1,
};
};

View File

@@ -0,0 +1,102 @@
import MapDirectionFilledIcon from '@assets/icons/MapDirectionFilledIcon';
import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants';
import {
getDistanceFromLatLonInKm,
getGoogleMapUrlForAddressText,
} from '@components/utlis/commonFunctions';
import { useAppSelector } from '@hooks';
import { IGeolocationCoordinate } from '@interfaces/addressGeolocation.types';
import { COLORS } from '@rn-ui-lib/colors';
import Button from '@rn-ui-lib/components/Button';
import Text from '@rn-ui-lib/components/Text';
import { toast } from '@rn-ui-lib/components/toast';
import { GenericStyles } from '@rn-ui-lib/styles';
import relativeDistanceFormatter from '@screens/addressGeolocation/utils/relativeDistanceFormatter';
import { addClickstreamEvent } from '@services/clickstreamEventService';
import React, { useMemo } from 'react';
import { Linking, StyleSheet, View } from 'react-native';
interface IAllocatedAddressDetails {
isCasePaused: boolean;
addressLocation: IGeolocationCoordinate;
caseId: string;
addressString?: string;
}
const AllocatedAddressDetails = (props: IAllocatedAddressDetails) => {
const { isCasePaused, addressLocation, addressString, caseId } = props;
const deviceGeolocationCoordinate = useAppSelector(
(state) => state.foregroundService?.deviceGeolocationCoordinate
);
const relativeDistanceBwLatLong = useMemo(() => {
const distance = getDistanceFromLatLonInKm(deviceGeolocationCoordinate, {
latitude: addressLocation?.latitude,
longitude: addressLocation?.longitude,
});
return `${relativeDistanceFormatter(distance)} km`;
}, [deviceGeolocationCoordinate]);
const openLocation = () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_DIRECTIONS_BUTTON_CLICKED, {
caseId,
});
if (!addressString) {
toast({ type: 'error', text1: 'Address not available' });
return;
}
const mapUrl = getGoogleMapUrlForAddressText(addressString);
if (!mapUrl) return;
return Linking.openURL(mapUrl);
};
return (
<View
style={[
GenericStyles.mb8,
GenericStyles.centerAlignedRow,
GenericStyles.justifyContentSpaceBetween,
]}
>
<View style={GenericStyles.centerAlignedRow}>
<Text style={styles.details}>Allocated Address</Text>
<View style={styles.bullet} />
<Text style={styles.details}>{relativeDistanceBwLatLong}</Text>
</View>
<Button
onPress={openLocation}
title="Directions"
variant="primaryText"
buttonStyle={styles.pv0}
textStyle={[GenericStyles.fontSize13, GenericStyles.fw500]}
leftIcon={
<View style={GenericStyles.mr4}>
<MapDirectionFilledIcon />
</View>
}
underlayColor="transparent"
pressableWidthChange={false}
opacityChangeOnPress={true}
disabled={isCasePaused}
/>
</View>
);
};
export const styles = StyleSheet.create({
bullet: {
width: 5,
height: 5,
borderRadius: 4,
backgroundColor: COLORS.TEXT.GREY_5,
marginHorizontal: 6,
},
details: {
fontSize: 13,
color: COLORS.TEXT.GREY_5,
},
pv0: {
paddingVertical: 0,
},
});
export default AllocatedAddressDetails;

View File

@@ -28,6 +28,8 @@ import AdditionalGeolocations from '@screens/addressGeolocation/AdditionalGeoloc
import FeeWaiver from '@screens/emiSchedule/FeeWaiver';
import FeeWaiverHistory from '@screens/emiSchedule/FeeWaiverHistory';
import CallCustomer from './CallCustomer';
import TopAddresses from '@screens/addresses/topAddresses/TopAddresses';
import OtherAddresses from '@screens/addresses/otherAddresses/OtherAddresses';
import Escalations from '@screens/escalations/Escalations';
const Stack = createNativeStackNavigator();
@@ -54,6 +56,8 @@ export enum CaseDetailStackEnum {
FEE_WAIVER = 'FeeWaiver',
FEE_WAIVER_HISTORY = 'FeeWaiverHistory',
CALL_CUSTOMER = 'CallCustomer',
TOP_ADDRESSES = 'TopAddresses',
OTHER_ADDRESSES = 'OtherAddresses',
ESCALATIONS = 'Escalations',
}
@@ -118,6 +122,8 @@ const CaseDetailStack = () => {
component={Widget}
/>
))}
<Stack.Screen name={CaseDetailStackEnum.TOP_ADDRESSES} component={TopAddresses} />
<Stack.Screen name={CaseDetailStackEnum.OTHER_ADDRESSES} component={OtherAddresses} />
</Stack.Navigator>
);
};

View File

@@ -27,6 +27,8 @@ import { CaseStatuses } from '@screens/allCases/interface';
import { BUSINESS_DATE_FORMAT, dateFormat, ISO_DATE_FORMAT } from '@rn-ui-lib/utils/dates';
import dayjs from 'dayjs';
import CaseDetailPausedNudge from './CaseDetailPausedNudge';
import { captureLatestDeviceLocation } from '@components/form/services/geoLocation.service';
import { getFeedbackAddresses } from '@screens/addresses/actions';
import { getForeclosureAmount } from '@actions/feedbackActions';
interface ICaseDetails {
@@ -44,7 +46,8 @@ const CollectionCaseDetails: React.FC<ICaseDetails> = (props) => {
params: { caseId, notificationId },
},
} = props;
const caseDetail = useAppSelector((state: RootState) => state.allCases.caseDetails?.[caseId]) || {};
const caseDetail =
useAppSelector((state: RootState) => state.allCases.caseDetails?.[caseId]) || {};
const isCallActive = useAppSelector(
(state: RootState) => state?.activeCall?.activeCallDetails?.callActive
);
@@ -77,10 +80,11 @@ const CollectionCaseDetails: React.FC<ICaseDetails> = (props) => {
const casePausedTillDate = dayjs(pausedTillDate, ISO_DATE_FORMAT).format(BUSINESS_DATE_FORMAT);
useFocusEffect(
React.useCallback(() => {
if (caseId) {
dispatch(captureLatestDeviceLocation(caseId));
dispatch(getFeedbackAddresses(caseId));
}
if (loanAccountNumber) {
dispatch(getAddressesAndGeolocations(loanAccountNumber, caseId, caseBusinessVertical));
dispatch(getUngroupedAddress(loanAccountNumber, caseId, caseBusinessVertical));
dispatch(getSkipTracingAddress(loanAccountNumber, caseId, caseBusinessVertical));
dispatch(getFeedbackHistory(loanAccountNumber));
}
}, [loanAccountNumber])

View File

@@ -163,7 +163,7 @@ export const styles = StyleSheet.create({
},
emiCardCtas: {
color: COLORS.TEXT.BLUE,
fontSize: 12,
fontSize: 13,
lineHeight: 15,
marginRight: 10,
},

View File

@@ -13,6 +13,8 @@ import { StyleSheet, View } from 'react-native';
import { CaseDetailStackEnum } from './CaseDetailStack';
import { AddressGeolocationTabEnum, AddressTabType } from '@screens/addressGeolocation/constant';
import { CaseStatuses } from '@screens/allCases/interface';
import LollipopIcon from '@assets/icons/LollipopIcon';
import AllocatedAddressDetails from './AllocatedAddressDetails';
interface IViewAddressSection {
caseId: string;
@@ -20,7 +22,13 @@ interface IViewAddressSection {
const ViewAddressSection = ({ caseId }: IViewAddressSection) => {
const caseDetail = useAppSelector((state: RootState) => state.allCases.caseDetails[caseId]) || {};
const { addressString, loanAccountNumber, customerReferenceId, addressStringType } = caseDetail;
const {
addressString,
loanAccountNumber,
customerReferenceId,
addressStringType,
addressLocation,
} = caseDetail;
const getTabName = () => {
if (addressStringType === AddressTabType.GEO_LOCATION) {
@@ -31,7 +39,7 @@ const ViewAddressSection = ({ caseId }: IViewAddressSection) => {
const isCasePaused = caseDetail?.caseStatus === CaseStatuses.ON_HOLD;
const viewAllAddressHandler = () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VIEW_ADDRESSES_CLICKED, {
lan: getLoanAccountNumber(caseDetail),
lan: loanAccountNumber,
});
const commonParams = {
loanAccountNumber,
@@ -42,6 +50,17 @@ const ViewAddressSection = ({ caseId }: IViewAddressSection) => {
navigateToScreen(CaseDetailStackEnum.ADDRESS_GEO, commonParams);
};
const viewTopAddressHandler = () => {
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_VIEW_TOP_ADDRESSES_CLICKED, {
lan: loanAccountNumber,
caseId
});
navigateToScreen(CaseDetailStackEnum.TOP_ADDRESSES, {
caseId,
loanAccountNumber,
});
};
return (
<View
style={[
@@ -53,23 +72,38 @@ const ViewAddressSection = ({ caseId }: IViewAddressSection) => {
GenericStyles.pr16,
]}
>
<Text style={[GenericStyles.fw500]}>{addressString}</Text>
<Button
onPress={viewAllAddressHandler}
title="View all addresses"
variant="primaryText"
textStyle={[GenericStyles.fontSize12, GenericStyles.fw500]}
style={styles.mb5}
rightIcon={
<View style={styles.chevronContainer}>
<Chevron />
</View>
}
underlayColor="transparent"
pressableWidthChange={false}
opacityChangeOnPress={true}
disabled={isCasePaused}
<AllocatedAddressDetails
isCasePaused={isCasePaused}
addressLocation={addressLocation}
addressString={addressString}
caseId={caseId}
/>
<View style={styles.container}>
<View style={[GenericStyles.mt4, GenericStyles.mr2]}>
<LollipopIcon />
</View>
<View style={styles.fs1}>
<Text dark style={[GenericStyles.fw500]}>
{addressString}
</Text>
<Button
onPress={viewTopAddressHandler}
title="View top addresses"
variant="primaryText"
textStyle={[GenericStyles.fontSize13, GenericStyles.fw500]}
style={styles.mb5}
rightIcon={
<View style={styles.chevronContainer}>
<Chevron />
</View>
}
underlayColor="transparent"
pressableWidthChange={false}
opacityChangeOnPress={true}
disabled={isCasePaused}
/>
</View>
</View>
</View>
);
};
@@ -86,6 +120,12 @@ export const styles = StyleSheet.create({
mb5: {
marginBottom: 5,
},
container: {
flexDirection: 'row',
},
fs1: {
flexShrink: 1,
},
});
export default ViewAddressSection;

View File

@@ -292,7 +292,7 @@ export interface CaseDetail {
dpdBucket: string;
dpdCycle: string;
addresses?: Address[];
addressLocation?: IGeolocationCoordinate;
addressLocation: IGeolocationCoordinate;
currentAllocationReferenceId: string;
customerReferenceId: string;
caseViewCreatedAt?: number;

View File

@@ -1,4 +1,3 @@
import { StyleSheet, Text, View } from 'react-native';
import React, { PropsWithChildren } from 'react';
import Offline from './Offline';
import CallInfo from './CallInfo';
@@ -26,5 +25,3 @@ const Layout: React.FC<PropsWithChildren> = (props) => {
};
export default Layout;
const styles = StyleSheet.create({});

View File

@@ -23,7 +23,7 @@ import Layout from '../layout/Layout';
import { GOOGLE_SSO_CLIENT_ID, IS_SSO_ENABLED } from '../../constants/config';
import { COLORS } from '../../../RN-UI-LIB/src/styles/colors';
import { GoogleSignin, User as GoogleSigninUser } from '@react-native-google-signin/google-signin';
import { GenericType} from '../../common/GenericTypes';
import { GenericType } from '../../common/GenericTypes';
import { logError } from '../../components/utlis/errorUtils';
import GoogleIcon from '../../assets/icons/GoogleIcon';
import { toast } from '../../../RN-UI-LIB/src/components/toast';
@@ -52,13 +52,15 @@ function Login() {
formState: { isValid },
} = useForm<ILoginForm>();
const dispatch = useAppDispatch();
const { phoneNumber, OTPError, isLoading, otpToken, deviceId } = useSelector((state: RootState) => ({
phoneNumber: state.loginInfo.phoneNumber,
otpToken: state.loginInfo.otpToken,
OTPError: state.loginInfo.OTPError,
isLoading: state.loginInfo.isLoading,
deviceId: state.user?.deviceId,
}));
const { phoneNumber, OTPError, isLoading, otpToken, deviceId } = useSelector(
(state: RootState) => ({
phoneNumber: state.loginInfo.phoneNumber,
otpToken: state.loginInfo.otpToken,
OTPError: state.loginInfo.OTPError,
isLoading: state.loginInfo.isLoading,
deviceId: state.user?.deviceId,
})
);
useEffect(() => {
isValid &&
@@ -68,8 +70,8 @@ function Login() {
}, [isValid]);
useEffect(() => {
if(deviceId){
setGlobalUserData({deviceId});
if (deviceId) {
setGlobalUserData({ deviceId });
}
addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.AV_LOGIN_SCREEN_LOAD);
}, []);
@@ -87,7 +89,7 @@ function Login() {
try {
await GoogleSignin.hasPlayServices();
const userInfo: GoogleSigninUser = await GoogleSignin.signIn();
const serverAuthCode = userInfo?.data?.serverAuthCode || userInfo?.serverAuthCode
const serverAuthCode = userInfo?.data?.serverAuthCode || userInfo?.serverAuthCode;
if (serverAuthCode) {
toast({
text1: ToastMessages.FETCHING_USER_DATA,

View File

@@ -46,6 +46,8 @@ export const extractQuestionContext = async (
let isBase64ImageAvailable = true;
const docsData =
store?.getState()?.feedbackImages?.intermediateDocsToBeUploaded?.[caseReferenceId]?.documents;
const feedbackAddressesMap =
store?.getState()?.topAddresses?.[caseReferenceId]?.feedbackAddressesMap;
const { widgetContext } = answer;
let newDocsData: GenericObject = {};
for (const widgetKey in widgetContext) {
@@ -63,10 +65,21 @@ export const extractQuestionContext = async (
if (answer.type === AnswerType.date) {
answer = { ...answer, answer: answer.answer.split('-').reverse().join('-') };
}
if (answer.type === AnswerType.address) {
const locationType = feedbackAddressesMap?.[answer.answer]?.locationType;
answer = {
...answer,
metadata: {
locationType: locationType,
},
type: AnswerType.text,
};
}
if (
answer.type === AnswerType.date ||
answer.type === AnswerType.phoneNumber ||
answer.type === AnswerType.address ||
answer.type === AnswerType.time
) {
answer = { ...answer, type: AnswerType.text };

View File

@@ -39,6 +39,7 @@ import postOperationalHourRestrictionsSlice from '@reducers/postOperationalHourR
import skipTracingAddressesSlice from '@reducers/skipTracingAddressesSlice';
import trainingMaterialSlice from '@reducers/trainingMaterialSlice';
import feedbackFormSlice from '@reducers/feedbackFormSlice';
import topAddressesSlice from '@reducers/topAddressesSlice';
const rootReducer = combineReducers({
case: caseReducer,
@@ -78,6 +79,7 @@ const rootReducer = combineReducers({
postOperationalHourRestrictionsSlice: postOperationalHourRestrictionsSlice,
trainingMaterial: trainingMaterialSlice,
feedbackForm: feedbackFormSlice,
topAddresses: topAddressesSlice
});
const persistConfig = {