From 9f68b361ec937d6397751217b8f5da5af6ac439f Mon Sep 17 00:00:00 2001 From: Ashish Deo Date: Mon, 8 Jan 2024 19:47:49 +0530 Subject: [PATCH] Added Commitment Tracker (#652) --- src/common/Constants.ts | 11 + src/components/utlis/apiHelper.ts | 8 +- src/reducer/userSlice.ts | 2 +- src/screens/allCases/constants.ts | 1 + src/screens/allCases/index.tsx | 99 +++++++- src/screens/auth/AuthRouter.tsx | 1 + .../DailyCommitmentBottomSheet.tsx | 221 ++++++++++++++++++ src/screens/dailyCommitment/ErrorMessages.tsx | 33 +++ src/screens/dailyCommitment/actions.ts | 67 ++++++ src/screens/dailyCommitment/constants.ts | 16 ++ .../dailyCommitment/headerComponent.tsx | 42 ++++ .../notifications/NotificationItem.tsx | 2 + .../notifications/NotificationTemplate.tsx | 19 +- src/screens/notifications/constants.tsx | 5 + src/types/commitmentTracker.types.ts | 16 ++ 15 files changed, 529 insertions(+), 14 deletions(-) create mode 100644 src/screens/dailyCommitment/DailyCommitmentBottomSheet.tsx create mode 100644 src/screens/dailyCommitment/ErrorMessages.tsx create mode 100644 src/screens/dailyCommitment/actions.ts create mode 100644 src/screens/dailyCommitment/constants.ts create mode 100644 src/screens/dailyCommitment/headerComponent.tsx create mode 100644 src/types/commitmentTracker.types.ts diff --git a/src/common/Constants.ts b/src/common/Constants.ts index bb6d8701..a937bfe3 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -672,6 +672,17 @@ export const CLICKSTREAM_EVENT_NAMES = { description: 'FA_NEARBY_CASE_CLICKED', }, + FA_DAILY_COMMITMENT_CLICKED: { + name: 'FA_DAILY_COMMITMENT_CLICKED', + description: 'FA_DAILY_COMMITMENT_CLICKED', + }, + FA_DAILY_COMMITMENT_SUBMITTED: { + name: 'FA_DAILY_COMMITMENT_SUBMITTED', + description: 'FA_DAILY_COMMITMENT_SUBMITTED', + }, + + + FA_SNAPSHOT_LISTENER: { name: 'FA_SNAPSHOT_LISTENER', description: 'FA_SNAPSHOT_LISTENER', diff --git a/src/components/utlis/apiHelper.ts b/src/components/utlis/apiHelper.ts index 0682bd4f..06ab2286 100644 --- a/src/components/utlis/apiHelper.ts +++ b/src/components/utlis/apiHelper.ts @@ -64,6 +64,9 @@ export enum ApiKeys { GET_TELEPHONE_NUMBERS = 'GET_TELEPHONE_NUMBERS', FIRESTORE_INCONSISTENCY_INFO = 'FIRESTORE_INCONSISTENCY_INFO', GET_CASE_DETAILS_FROM_API = 'GET_CASE_DETAILS_FROM_API', + DAILY_COMMITMENT = 'DAILY_COMMITMENT', + GET_PTP_AMOUNT = 'GET_PTP_AMOUNT', + GET_VISIBILITY_STATUS = 'GET_VISIBILITY_STATUS', } export const API_URLS: Record = {} as Record; @@ -112,8 +115,9 @@ API_URLS[ApiKeys.GET_CASH_COLLECTED] = '/allocation-cycle/cash-collected-split'; API_URLS[ApiKeys.GET_TELEPHONE_NUMBERS] = '/v2/collection-cases/telephones-view/{loanAccountNumber}'; API_URLS[ApiKeys.FIRESTORE_INCONSISTENCY_INFO] = '/cases/sync-status'; API_URLS[ApiKeys.GET_CASE_DETAILS_FROM_API] = '/collection-cases/minimal-collection-case-view/{caseId}'; - - +API_URLS[ApiKeys.DAILY_COMMITMENT] = '/daily-commitment'; +API_URLS[ApiKeys.GET_PTP_AMOUNT] = '/ptps-due-view/agent-detail'; +API_URLS[ApiKeys.GET_VISIBILITY_STATUS] = '/daily-commitment/visibility'; export const API_STATUS_CODE = { OK: 200, diff --git a/src/reducer/userSlice.ts b/src/reducer/userSlice.ts index 26cd78d7..ac16270c 100644 --- a/src/reducer/userSlice.ts +++ b/src/reducer/userSlice.ts @@ -68,7 +68,7 @@ export interface IUserSlice extends IUser { agentAttendance: { showAttendanceBanner: boolean; attendanceDate: string; - } + }; } const initialState: IUserSlice = { diff --git a/src/screens/allCases/constants.ts b/src/screens/allCases/constants.ts index dfc7a90d..ccc2ffd2 100644 --- a/src/screens/allCases/constants.ts +++ b/src/screens/allCases/constants.ts @@ -87,6 +87,7 @@ export const ToastMessages = { WHATSAPP_NOT_INSTALLED: 'WhatsApp is not installed on your device', FILE_DOWNLOAD_SUCCESS: 'File downloaded successfully', FILE_DOWNLOAD_FAILURE: 'File download failed', + COMMITMENT_SUBMITTED_SUCCESSFULLY: 'Commitment submitted successfully', }; export enum BOTTOM_TAB_ROUTES { diff --git a/src/screens/allCases/index.tsx b/src/screens/allCases/index.tsx index df5654ec..e42f7632 100644 --- a/src/screens/allCases/index.tsx +++ b/src/screens/allCases/index.tsx @@ -1,6 +1,7 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useAppDispatch, useAppSelector } from '@hooks'; import CasesList from './CasesList'; +import Text from '../../../RN-UI-LIB/src/components/Text'; import { RootState } from '@store'; import { initCrashlytics } from '@utils/firebaseUtils'; import Layout from '../layout/Layout'; @@ -10,7 +11,6 @@ import Profile from '../Profile'; import ProfileIcon from '@rn-ui-lib/icons/ProfileIcon'; import VisitPlanIcon from '@rn-ui-lib/icons/VisitPlanIcon'; import CasesActionButtons from './CasesActionButtons'; -import FullScreenLoader from '@rn-ui-lib/components/FullScreenLoader'; import { getCurrentScreen } from '@utils/navigationUtlis'; import { resetSelectedTodoList, @@ -21,11 +21,17 @@ import { import { addClickstreamEvent } from '@services/clickstreamEventService'; import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants'; import { BOTTOM_TAB_ROUTES } from './constants'; -import { getSelfieDocument } from '@actions/profileActions'; import FullScreenLoaderWrapper from '@common/FullScreenLoaderWrapper'; import DashboardIcon from '../../assets/icons/DashboardIcon'; import DashBoardScreens from '../Dashboard/DashBoardScreens'; import { isAgentDashboardVisible } from '@screens/Dashboard/utils'; +import { View, StyleSheet, TouchableOpacity } from 'react-native'; +import { COLORS } from '@rn-ui-lib/colors'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import DailyCommitmentIcon from '@rn-ui-lib/icons/DailyCommitmentIcon'; +import AddCommitment from '../dailyCommitment/DailyCommitmentBottomSheet'; +import { getVisibilityStatus } from '@screens/dailyCommitment/actions'; +import { logError } from '@components/utlis/errorUtils'; const AllCasesMain = () => { const { pendingList, pinnedList, completedList, loading } = useAppSelector( @@ -35,15 +41,57 @@ const AllCasesMain = () => { const dispatch = useAppDispatch(); const isTeamLead = useAppSelector((state) => state.user.isTeamLead); - const showAgentDashboard = isAgentDashboardVisible(); + const [openBottomSheet, setOpenBottomSheet] = useState(false); + const [isCommitmentFormVisible, setIsCommitmentFormVisible] = useState(false); + const [isCommitmentSubmitted, setIsCommitmentSubmitted] = useState(true); + + + useEffect(() => { + getVisibilityStatus() + .then((response) => { + setIsCommitmentSubmitted(response?.isCommitmentFilled); + setIsCommitmentFormVisible(response?.isCommitmentBannerVisible); + }) + .catch((err) => { + logError(err); + }); + }, [isCommitmentSubmitted,isCommitmentFormVisible]); + + const shouldShowBanner = useMemo(() => { + return !isTeamLead && !isCommitmentSubmitted && isCommitmentFormVisible; + }, [isCommitmentSubmitted, isCommitmentFormVisible, isTeamLead]); + + + const openCommitmentScreen = () => { + setOpenBottomSheet(true); + }; + const CommitmentComponent = () => { + return ( + <> + + + + Update your daily commitment + + + + ); + }; const HOME_SCREENS: ITabScreen[] = useMemo(() => { const bottomSheetScreens = [ { name: BOTTOM_TAB_ROUTES.Cases, component: () => ( - + <> + + {shouldShowBanner ? : null} + ), icon: CasesIcon, }, @@ -51,7 +99,12 @@ const AllCasesMain = () => { if (!isTeamLead) { bottomSheetScreens.push({ name: BOTTOM_TAB_ROUTES.VisitPlan, - component: () => , + component: () => ( + <> + + {shouldShowBanner ? : null} + + ), icon: VisitPlanIcon, }); } @@ -59,18 +112,34 @@ const AllCasesMain = () => { if (showAgentDashboard) { bottomSheetScreens.push({ name: BOTTOM_TAB_ROUTES.Dashboard, - component: () => , + component: () => ( + <> + + {shouldShowBanner ? : null} + + ), icon: DashboardIcon, }); } bottomSheetScreens.push({ name: BOTTOM_TAB_ROUTES.Profile, - component: () => , + component: () => ( + <> + + {shouldShowBanner ? : null} + + ), icon: ProfileIcon, }); return bottomSheetScreens; - }, [pendingList, pinnedList, isTeamLead, showAgentDashboard]); + }, [ + pendingList, + pinnedList, + isTeamLead, + showAgentDashboard, + shouldShowBanner, + ]); const onTabPressHandler = (e: any) => { const nextTab = e?.data?.routeName; @@ -99,8 +168,18 @@ const AllCasesMain = () => { onTabPress={(e) => onTabPressHandler(e)} /> + ); }; +const styles = StyleSheet.create({ + container: { + backgroundColor: COLORS.BACKGROUND.TEAL, + height: 40, + }, + updateText: { + color: COLORS.TEXT.TEAL, + }, +}); -export default AllCasesMain; +export default AllCasesMain; \ No newline at end of file diff --git a/src/screens/auth/AuthRouter.tsx b/src/screens/auth/AuthRouter.tsx index 3c18f905..899686ef 100644 --- a/src/screens/auth/AuthRouter.tsx +++ b/src/screens/auth/AuthRouter.tsx @@ -67,6 +67,7 @@ function AuthRouter() { }; const CHECK_ATTENDANCE_TIME = 10000; + useEffect(() => { const appStateChange = AppState.addEventListener('change', async (change) => { if (change !== 'active') return; diff --git a/src/screens/dailyCommitment/DailyCommitmentBottomSheet.tsx b/src/screens/dailyCommitment/DailyCommitmentBottomSheet.tsx new file mode 100644 index 00000000..6db7762d --- /dev/null +++ b/src/screens/dailyCommitment/DailyCommitmentBottomSheet.tsx @@ -0,0 +1,221 @@ +import { Keyboard, SafeAreaView, View, StyleSheet, TouchableOpacity } from 'react-native'; +import React, { useEffect, useState } from 'react'; +import Text from '../../../RN-UI-LIB/src/components/Text'; +import TextInput, { TextInputMaskType } from '../../../RN-UI-LIB/src/components/TextInput'; +import Button from '../../../RN-UI-LIB/src/components/Button'; +import { GenericStyles } from '../../../RN-UI-LIB/src/styles'; +import { Controller, FieldErrors, useForm } from 'react-hook-form'; +import { isValidAmountEntered } from '../../components/utlis/commonFunctions'; +import { addCommitmentApi, getTodaysPtpAmount} from './actions'; +import Heading from '@rn-ui-lib/components/Heading'; +import DailyCommitmentIcon from '@rn-ui-lib/icons/DailyCommitmentIcon'; +import BottomSheetWrapper from '@common/BottomSheetWrapper'; +import CloseIcon from '@rn-ui-lib/icons/CloseIcon'; +import { COLORS } from '@rn-ui-lib/colors'; +import { EXAMPLE_VALUES } from './constants'; +import { useAppDispatch} from '@hooks'; +import { formatAmount } from '@rn-ui-lib/utils/amount'; +import { getErrorCashMessage, getErrorVisitMessage } from './ErrorMessages'; +import { IAddCommitment, IAddCommitmentProps } from '@interfaces/commitmentTracker.types'; +import HeaderComponent from './headerComponent'; +import { logError } from '@components/utlis/errorUtils'; + +const AddCommitment: React.FC = ({ openBottomSheet, setOpenBottomSheet }) => { + const [loading, setLoading] = useState(false); + const { + control, + formState: { errors, dirtyFields }, + trigger, + reset, + handleSubmit, + } = useForm({ + mode: 'onChange', + }); + const [submitErrors, setSubmitErrors] = useState>({}); + const [todaysPtpAmount, setTodaysPtpAmount] = useState(1000); + useEffect(() => { + getTodaysPtpAmount() + .then((response) => { + setTodaysPtpAmount(response?.todaysPtpAmount); + }) + .catch((err) => {logError(err);}); + }, []); + + const onBack = () => { + setOpenBottomSheet((prev) => !prev); + setSubmitErrors({}); + reset(); + }; + + const visitError = Boolean( + dirtyFields?.visitsCommitted && + (errors?.visitsCommitted || submitErrors?.visitsCommitted) + ); + const cashError = Boolean( + dirtyFields?.cashCommitted && + (errors?.cashCommitted || submitErrors?.cashCommitted) + ); + + const errorCashMessage = getErrorCashMessage(errors); + const errorVisitMessage = getErrorVisitMessage(errors); + + const [isKeyboardVisible, setKeyboardVisible] = useState(false); + useEffect(() => { + const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () => { + setKeyboardVisible(true); + }); + const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { + setKeyboardVisible(false); + }); + + return () => { + keyboardDidShowListener.remove(); + keyboardDidHideListener.remove(); + }; + }, []); + const dispatch = useAppDispatch(); + const addCtaHandler = (data: IAddCommitment) => { + setLoading(true); + dispatch( + addCommitmentApi(data, () => { + setLoading(false); + }) + ); + onBack(); + }; + const handleError = (data: FieldErrors) => { + setSubmitErrors(data); + }; + + return ( + setOpenBottomSheet((prev) => !prev)} + moveForKeyboard={0.2} + HeaderNode={() => } + > + + + + + Cash collection commitment + * + + + ( + { + onChange(number); + trigger(); + }} + keyboardType="numeric" + placeholder={EXAMPLE_VALUES.DUMMY_PTP_AMOUNT} + placeholderTextColor='COLORS.TEXT.LIGHT' + required={true} + maskType={TextInputMaskType.CURRENCY} + error={cashError} + errorMessage={errorCashMessage} + /> + )} + name="cashCommitted" + rules={{ + required: true, + validate: (value) => isValidAmountEntered(Number(value)), + min: 0, + max: 1e6, + }} + /> + + + + PTP amount for today: {formatAmount(todaysPtpAmount)} + + + + + Number of visits * + + ( + { + onChange(number); + trigger(); + }} + keyboardType="numeric" + placeholder={EXAMPLE_VALUES.DUMMY_VISIT_COUNT} + placeholderTextColor='COLORS.TEXT.LIGHT' + maskType={TextInputMaskType.NUMBER} + error={visitError} + errorMessage={errorVisitMessage} + required={true} + /> + )} + name="visitsCommitted" + rules={{ + required: true, + validate: (value) => isValidAmountEntered(Number(value)), + pattern: /^[0-9]*$/, + min: 0, + max: 99, + }} + /> + + + +