diff --git a/babel.config.js b/babel.config.js index ae7561b4..dda01a14 100644 --- a/babel.config.js +++ b/babel.config.js @@ -16,7 +16,7 @@ module.exports = { '@constants': './src/constants', '@screens': './src/screens', '@services': './src/services', - '@types': './src/types', + '@interfaces': './src/types', '@common': './src/common', '@assets': './src/assets', '@store': './src/store/store', diff --git a/src/components/utlis/commonFunctions.ts b/src/components/utlis/commonFunctions.ts index 993f96cd..e13e1f6f 100644 --- a/src/components/utlis/commonFunctions.ts +++ b/src/components/utlis/commonFunctions.ts @@ -353,7 +353,7 @@ export function getDistanceFromLatLonInKm( latLong1: IGeolocationCoordinate, latLong2: IGeolocationCoordinate ) { - if (!latLong1.latitude || !latLong1.longitude || !latLong2.latitude || !latLong2.longitude) + if (!latLong1?.latitude || !latLong1?.longitude || !latLong2?.latitude || !latLong2?.longitude) return NaN; const EARTH_RADIUS = 6371; diff --git a/src/screens/allCases/CaseItem.tsx b/src/screens/allCases/CaseItem.tsx index f363f699..415b0748 100644 --- a/src/screens/allCases/CaseItem.tsx +++ b/src/screens/allCases/CaseItem.tsx @@ -1,12 +1,16 @@ import React, { useMemo } from 'react'; -import { Text, View, ViewProps, StyleSheet } from 'react-native'; -import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import { Text, View, ViewProps, StyleSheet, Pressable } from 'react-native'; +import { GenericStyles, getShadowStyle } from '../../../RN-UI-LIB/src/styles'; import { CaseTypes, ICaseItemCaseDetailObj } from './interface'; import ListItem from './ListItem'; import Button from '../../../RN-UI-LIB/src/components/Button'; import { navigateToScreen } from '../../components/utlis/navigationUtlis'; import { useAppSelector } from '../../hooks'; import { FeedbackStatus } from '../caseDetails/interface'; +import { PageRouteEnum } from '@screens/auth/ProtectedRouter'; +import { COLORS } from '@rn-ui-lib/colors'; +import LocationIcon from '@assets/icons/LocationIcon'; +import ArrowRightOutlineIcon from '@rn-ui-lib/icons/ArrowRightOutlineIcon'; interface ICaseItemProps extends ViewProps { caseDetailObj: ICaseItemCaseDetailObj; @@ -16,6 +20,7 @@ interface ICaseItemProps extends ViewProps { shouldBatchAvatar?: boolean; allCasesView?: boolean; isAgentDashboard?: boolean; + nearbyCaseView?: boolean; } const CaseItem: React.FC = ({ @@ -26,9 +31,10 @@ const CaseItem: React.FC = ({ shouldBatchAvatar = false, allCasesView = false, isAgentDashboard = false, + nearbyCaseView = false, ...restProps }) => { - const { ADD_VISIT_PLAN, ATTEMPTED_CASES } = CaseTypes; + const { ADD_VISIT_PLAN, ATTEMPTED_CASES, NEARBY_CASES } = CaseTypes; const { attemptedCount, totalPinnedCount } = useAppSelector((state) => ({ totalPinnedCount: state.allCases.pinnedList.length, attemptedCount: state.allCases.pinnedList.filter( @@ -81,6 +87,34 @@ const CaseItem: React.FC = ({ ); } + case NEARBY_CASES: { + return ( + navigateToScreen(PageRouteEnum.NEARBY_CASES)}> + + + + + View Nearby Cases + + + + + + ); + } default: return ( @@ -91,6 +125,7 @@ const CaseItem: React.FC = ({ isTodoItem={isTodoItem} allCasesView={allCasesView} isAgentDashboard={isAgentDashboard} + nearbyCaseView={nearbyCaseView} /> ); diff --git a/src/screens/allCases/CasesList.tsx b/src/screens/allCases/CasesList.tsx index 502f938d..4d97824a 100644 --- a/src/screens/allCases/CasesList.tsx +++ b/src/screens/allCases/CasesList.tsx @@ -210,6 +210,8 @@ const CasesList: React.FC = ({ const filteredCasesListWithCTA = useMemo(() => { if (!isVisitPlan) { + if (allCasesView && filteredCasesList?.length) + return [ListHeaderItems.NEARBY_CASES as ICaseItem, ...filteredCasesList]; return [...filteredCasesList]; } if (isLockedVisitPlanStatus) { diff --git a/src/screens/allCases/EmptyList.tsx b/src/screens/allCases/EmptyList.tsx index 63f12e72..ed23723f 100644 --- a/src/screens/allCases/EmptyList.tsx +++ b/src/screens/allCases/EmptyList.tsx @@ -1,4 +1,4 @@ -import { StyleSheet, View } from 'react-native'; +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; import React from 'react'; import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; import Heading from '../../../RN-UI-LIB/src/components/Heading'; @@ -22,6 +22,7 @@ interface IEmptyList { isFilterApplied?: boolean; setShowAgentSelectionBottomSheet?: (val: boolean) => void; isAgentDashboard?: boolean; + containerStyle?: StyleProp; } const EmptyList: React.FC = (props) => { @@ -31,6 +32,7 @@ const EmptyList: React.FC = (props) => { isFilterApplied, setShowAgentSelectionBottomSheet, isAgentDashboard, + containerStyle, } = props; const { isLockedVisitPlanStatus, @@ -130,7 +132,7 @@ const EmptyList: React.FC = (props) => { return ( - + {renderIcon()} = (props) => { isTodoItem, shouldBatchAvatar, allCasesView, - isAgentDashboard, + nearbyCaseView, } = props; const { id: caseId, @@ -79,6 +80,7 @@ const ListItem: React.FC = (props) => { interactionStatus, caseVerdict, totalOverdueAmount, + distanceInKm, } = caseListItemDetailObj; const isCollectionCaseType = caseType === CaseAllocationType.COLLECTION_CASE; @@ -179,7 +181,11 @@ const ListItem: React.FC = (props) => { const caseCompleted = COMPLETED_STATUSES.includes(caseStatus); const showVisitPlanBtn = - !(caseCompleted || isCaseItemPinnedMainView) && !isTodoItem && !isCompleted && !isTeamLead; + !(caseCompleted || isCaseItemPinnedMainView) && + !isTodoItem && + !isCompleted && + !isTeamLead && + !nearbyCaseView; return ( @@ -211,6 +217,13 @@ const ListItem: React.FC = (props) => { In visit plan )} + {nearbyCaseView && distanceInKm && ( + + + {distanceInKm}m away + + + )} {isCollectionCaseType ? ( @@ -334,6 +347,17 @@ const styles = StyleSheet.create({ visitPlanText: { color: COLORS.TEXT.BLUE, }, + distanceContainer: { + right: 0, + top: 0, + padding: 8, + backgroundColor: COLORS.BACKGROUND.SILVER, + borderBottomLeftRadius: 4, + borderTopRightRadius: 4, + }, + distanceText: { + color: COLORS.TEXT.BLACK, + }, }); export default memo(ListItem); diff --git a/src/screens/allCases/NearbyCases.tsx b/src/screens/allCases/NearbyCases.tsx new file mode 100644 index 00000000..962ce4dd --- /dev/null +++ b/src/screens/allCases/NearbyCases.tsx @@ -0,0 +1,87 @@ +import React, { useEffect, useState } from 'react'; +import { goBack } from '@components/utlis/navigationUtlis'; +import { useAppSelector } from '@hooks'; +import useRefresh from '@hooks/useRefresh'; +import { useIsFocused } from '@react-navigation/native'; +import NavigationHeader from '@rn-ui-lib/components/NavigationHeader'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import { INearbyCaseItemObj } from '@screens/caseDetails/interface'; +import { FlashList } from '@shopify/flash-list'; +import { ListRenderItemInfo, RefreshControl, StyleSheet, View } from 'react-native'; +import CaseItem from './CaseItem'; +import { ESTIMATED_ITEM_SIZE, ESTIMATED_LIST_SIZE } from './CasesList'; +import EmptyList from './EmptyList'; +import { CaseTypes, ICaseItem } from './interface'; +import { getNearByCases } from './utils'; + +const NearbyCases = () => { + const { pendingList, pinnedList, caseDetails } = useAppSelector((state) => state.allCases); + const { deviceGeolocationCoordinate } = useAppSelector((state) => state.foregroundService); + + const [caseData, setCaseData] = useState>([]); + const isFocused = useIsFocused(); + + const handlePullToRefresh = () => { + const data = getNearByCases( + [...pinnedList, ...pendingList], + caseDetails, + deviceGeolocationCoordinate + ); + setCaseData(data); + }; + + const { refreshing, onRefresh } = useRefresh(handlePullToRefresh); + + useEffect(() => { + if (isFocused) { + onRefresh(); + } + }, [isFocused]); + + const renderListItem = (row: ListRenderItemInfo) => { + const caseDetailItem = row.item as INearbyCaseItemObj; + const { type } = row.item; + return ( + + ); + }; + + return ( + + + + {caseData.length ? ( + } + contentContainerStyle={GenericStyles.p12} + estimatedItemSize={ESTIMATED_ITEM_SIZE} + estimatedListSize={ESTIMATED_LIST_SIZE} + /> + ) : ( + + + + )} + + + ); +}; + +const styles = StyleSheet.create({ + pt0: { + paddingTop: 0, + }, +}); + +export default NearbyCases; diff --git a/src/screens/allCases/constants.ts b/src/screens/allCases/constants.ts index 07dcb724..fc0a9e18 100644 --- a/src/screens/allCases/constants.ts +++ b/src/screens/allCases/constants.ts @@ -27,6 +27,10 @@ export const ListHeaderItems = { type: CaseTypes.ATTEMPTED_CASES, caseReferenceId: '-5', }, + NEARBY_CASES: { + type: CaseTypes.NEARBY_CASES, + caseReferenceId: '-6', + }, }; export const LIST_HEADER_ITEMS = [ diff --git a/src/screens/allCases/interface.ts b/src/screens/allCases/interface.ts index 59200ec7..4e1d54a7 100644 --- a/src/screens/allCases/interface.ts +++ b/src/screens/allCases/interface.ts @@ -24,6 +24,7 @@ export enum CaseTypes { BANNER, ADD_VISIT_PLAN, ATTEMPTED_CASES, + NEARBY_CASES, } export enum caseVerdict { @@ -322,6 +323,7 @@ export interface ICaseItemAvatarCaseDetailObj extends IFetchDocumentCaseDetailOb export interface ICaseItemCaseDetailObj extends CaseDetail { isIntermediateOrSelectedTodoCaseItem?: boolean; + distanceInKm?: number; } export interface ISectionListData { diff --git a/src/screens/allCases/utils.ts b/src/screens/allCases/utils.ts index ca3ab555..30e94be2 100644 --- a/src/screens/allCases/utils.ts +++ b/src/screens/allCases/utils.ts @@ -1,4 +1,6 @@ -import { CaseDetail, FeedbackStatus } from '../caseDetails/interface'; +import { getDistanceFromLatLonInKm } from '@components/utlis/commonFunctions'; +import { IGeoLocation } from '@interfaces/addressGeolocation.types'; +import { CaseDetail, FeedbackStatus, INearbyCaseItemObj } from '../caseDetails/interface'; import { ICaseItem, IReportee, ISectionListData } from './interface'; export const getAttemptedList = ( @@ -59,3 +61,35 @@ export const sectionListTranformData = (agentList: IReportee[]): ISectionListDat return result; }; + +export const getNearByCases = ( + casesList: Array, + caseDetails: Record, + deviceGeolocationCoordinate: IGeoLocation +) => { + let caseDetailsData: Array = []; + casesList?.forEach((pinnedId) => { + const caseDetail = caseDetails?.[pinnedId.caseReferenceId]; + const firstAddresLocation = caseDetail?.addresses?.[0]?.location; + + if (firstAddresLocation) { + const distanceInKm = getDistanceFromLatLonInKm( + firstAddresLocation, + deviceGeolocationCoordinate + ); + + if (distanceInKm) { + caseDetailsData.push({ + ...caseDetail, + distanceInKm: distanceInKm, + }); + } + } + }); + + caseDetailsData?.sort( + (a: INearbyCaseItemObj, b: INearbyCaseItemObj) => a.distanceInKm - b.distanceInKm + ); + + return caseDetailsData?.slice(0, 10); +}; diff --git a/src/screens/auth/ProtectedRouter.tsx b/src/screens/auth/ProtectedRouter.tsx index bdf3125f..0d623a97 100644 --- a/src/screens/auth/ProtectedRouter.tsx +++ b/src/screens/auth/ProtectedRouter.tsx @@ -38,6 +38,7 @@ import { getAgentDetail } from '../../action/authActions'; import { CaptureGeolocation, DeviceLocation } from '@components/form/services/geoLocation.service'; import { setDeviceGeolocation } from '@reducers/foregroundServiceSlice'; import FullScreenLoader from '../../../RN-UI-LIB/src/components/FullScreenLoader'; +import NearbyCases from '@screens/allCases/NearbyCases'; const Stack = createNativeStackNavigator(); @@ -52,6 +53,7 @@ export enum PageRouteEnum { CASH_COLLECTED = 'cashCollected', DASHBOARD_MAIN = 'dashboardMain', FILTERED_CASES = 'filteredCases', + NEARBY_CASES = 'nearbyCases', GEOLOCATION_OLD_FEEDBACKS = 'geolocationOldFeedbacks', } @@ -128,14 +130,13 @@ const ProtectedRouter = () => { // Firestore listener hook useFirestoreUpdates(); - React.useEffect(() => { // Watching Position for significant change CaptureGeolocation.watchLocation((location: DeviceLocation) => - dispatch(setDeviceGeolocation(location)) + dispatch(setDeviceGeolocation(location)) ); }, []); - + if (isLoading) return ; return ( @@ -192,6 +193,16 @@ const ProtectedRouter = () => { }} listeners={getScreenFocusListenerObj} /> + null, + animationDuration: SCREEN_ANIMATION_DURATION, + animation: 'none', + }} + listeners={getScreenFocusListenerObj} + />