From bdaaf99f2718dfcdd201e686973e9f5407a2e4e9 Mon Sep 17 00:00:00 2001 From: Kunal Sharma Date: Mon, 17 Jun 2024 19:44:45 +0530 Subject: [PATCH] TP-111|Kunal|HRC EMI CARD revamp (#1003) * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp * TP-111|Kunal|HRC EMI CARD revamp --- src/assets/icons/HourglassIconV2.tsx | 45 +++ src/components/Popper/Popper.tsx | 86 +++--- .../CustomerSummary/index.module.scss | 19 ++ .../components/EmiSchedule/EmiBreakup.tsx | 2 +- src/pages/Dashboard/AmountDueCard.tsx | 284 +++++++++++++++--- src/pages/Dashboard/AmountdueCard.module.scss | 14 + src/pages/Dashboard/HrCustomerDetails.tsx | 17 +- src/reducers/commonSlice.ts | 78 +++-- 8 files changed, 431 insertions(+), 114 deletions(-) create mode 100644 src/assets/icons/HourglassIconV2.tsx diff --git a/src/assets/icons/HourglassIconV2.tsx b/src/assets/icons/HourglassIconV2.tsx new file mode 100644 index 00000000..d3899d89 --- /dev/null +++ b/src/assets/icons/HourglassIconV2.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; + +const HourglassIconV2 = () => ( + + + + + + + + + + + +); +export default HourglassIconV2; diff --git a/src/components/Popper/Popper.tsx b/src/components/Popper/Popper.tsx index 2dec090b..69adbf5d 100644 --- a/src/components/Popper/Popper.tsx +++ b/src/components/Popper/Popper.tsx @@ -12,7 +12,8 @@ import { useMergeRefs, FloatingPortal, FloatingArrow, - hide + hide, + useTransitionStyles } from '@floating-ui/react'; import type { Placement } from '@floating-ui/react'; import { arrow } from '@floating-ui/dom'; @@ -31,6 +32,8 @@ interface PopperOptions { openOnClick?: boolean; mainAxisOffset?: number; crossAxisOffset?: number; + disableCloseOnOutsideClick?: boolean; + transform?: boolean; } interface PopperContentProps { @@ -62,7 +65,9 @@ export function usePopper({ hiddenPadding = 16, openOnClick = true, mainAxisOffset = 9, - crossAxisOffset = 9 + crossAxisOffset = 9, + disableCloseOnOutsideClick = false, + transform = true }: PopperOptions = {}) { const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen); @@ -74,6 +79,7 @@ export function usePopper({ open, onOpenChange: setOpen, whileElementsMounted: autoUpdate, + transform, middleware: [ offset({ mainAxis: mainAxisOffset, @@ -98,7 +104,10 @@ export function usePopper({ const click = useClick(context, { enabled: controlledOpen == null }); - const dismiss = useDismiss(context); + const dismiss = useDismiss(context, { + outsidePress: !disableCloseOnOutsideClick, + escapeKey: !disableCloseOnOutsideClick + }); const role = useRole(context, { role: 'popper' }); const defaultInteractions = [dismiss, role]; @@ -182,11 +191,14 @@ export const PopperTrigger = React.forwardRef( export const PopperContent = React.forwardRef( function PopperContent( - { style, headerText, iconRequired = true, noHeader = false, onClose, ...props }, + { style, headerText, iconRequired = true, noHeader = false, onClose, className, ...props }, propRef ) { const context = usePopperContext(); const ref = useMergeRefs([context.refs.setFloating, propRef]); + const transition = useTransitionStyles(context?.context, { + duration: 500 + }); const referenceHidden = context?.middlewareData?.hide?.escaped; @@ -204,41 +216,45 @@ export const PopperContent = React.forwardRef -
- {!noHeader ? ( -
-
{headerText}
+ {transition?.isMounted && ( +
+ {!noHeader ? (
{ - if (onClose) onClose(); - context.setOpen(false); + style={{ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: '12px' }} > - {iconRequired && } +
{headerText}
+
{ + if (onClose) onClose(); + context?.setOpen(false); + }} + > + {iconRequired && } +
-
- ) : null} - {typeof props.children === 'function' - ? props?.children({ closePopper: closePopper, openPopper: openPopper }) - : props.children} - -
+ ) : null} + {typeof props.children === 'function' + ? props?.children({ closePopper: closePopper, openPopper: openPopper }) + : props.children} + +
+ )} ); } diff --git a/src/pages/CaseDetails/components/CustomerSummary/index.module.scss b/src/pages/CaseDetails/components/CustomerSummary/index.module.scss index 8e5b971d..7c0c8a2c 100644 --- a/src/pages/CaseDetails/components/CustomerSummary/index.module.scss +++ b/src/pages/CaseDetails/components/CustomerSummary/index.module.scss @@ -274,3 +274,22 @@ margin-top: 0; margin-bottom: 0; } + +:global { + .penaltyPopperWrapper { + display: flex; + flex-direction: column; + background-color: var(--green-dark); + padding: 4px 8px; + border-radius: 4px; + color: var(--text-primary); + font-family: Inter; + font-size: 13px; + font-style: normal; + font-weight: 500; + line-height: 20px; + letter-spacing: -0.13px; + box-shadow: 0px 6px 10px 0px rgba(0, 0, 0, 0.08), 0px 1px 18px 0px rgba(0, 0, 0, 0.06), + 0px 3px 5px 0px rgba(0, 0, 0, 0.1); + } +} diff --git a/src/pages/CaseDetails/components/EmiSchedule/EmiBreakup.tsx b/src/pages/CaseDetails/components/EmiSchedule/EmiBreakup.tsx index 6b3a7f7c..298230a7 100644 --- a/src/pages/CaseDetails/components/EmiSchedule/EmiBreakup.tsx +++ b/src/pages/CaseDetails/components/EmiSchedule/EmiBreakup.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Typography } from '@navi/web-ui/lib/primitives'; import { formatAmount } from '@cp/src/utils/commonUtils'; -import HourGlassIcon from '@cp/src/assets/icons/HourGlassIcon'; +import HourGlassIcon from '@cp/assets/icons/HourglassIconV2'; import cx from 'classnames'; import dayjs from 'dayjs'; import { EmiFeeActions, IEmiBreakup, PenaltyWaivedLabelMap } from './interfaces'; diff --git a/src/pages/Dashboard/AmountDueCard.tsx b/src/pages/Dashboard/AmountDueCard.tsx index 941fb4c0..9cadef0c 100644 --- a/src/pages/Dashboard/AmountDueCard.tsx +++ b/src/pages/Dashboard/AmountDueCard.tsx @@ -1,26 +1,38 @@ -import React from 'react'; +import React, { MutableRefObject } from 'react'; import GridContainer from '@navi/web-ui/lib/layouts/Grid/GridContainer/GridContainer'; import GridRow from '@navi/web-ui/lib/layouts/Grid/GridRow/GridRow'; -import { Chip, Typography } from '@navi/web-ui/lib/primitives'; +import { Checkbox, Tag, Typography } from '@navi/web-ui/lib/primitives'; import styles from './AmountdueCard.module.scss'; import { formatAmount } from 'src/utils/commonUtils'; import cx from 'classnames'; import EnachCard from './dc-97/EnachCard'; import { HumanReminderType, + IHumanReminderDues, MandateStatus, RepaymentFailureDetails, - UpcomingDueDetails + UpcomingDueDetails, + WAIVER_REQUEST_TYPE } from 'src/reducers/commonSlice'; import { COLOR_CODING, ColorCoding, DAYS_PAST_DUE } from './dc-97/dc97Constant'; -import { calculateDateDifferenceInDays, DateFormat, getFormatDate } from '../../utils/DateHelper'; +import { calculateDateDifferenceInDays } from '../../utils/DateHelper'; +import useUpdateEffect from '@cp/hooks/useUpdateEffect'; +import { + Popper, + PopperContent, + PopperContextType, + PopperTrigger +} from '@cp/components/Popper/Popper'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@cp/components/TooltipV2/TooltipV2'; +import InfoIconOutlined from '@cp/assets/images/icons/InfoIconOutlined'; +import HourglassIconV2 from '@cp/assets/icons/HourglassIconV2'; interface AmountDueCardProps { totalAmount: number; emiDueDate: string; emiAmount: number; emiDaysRemaining: number; - emiPenaltyCharge: string; + emiPenaltyCharge: string | number; emiIncreasePenaltyChargeDate: string; emiIncreasePenalChargeAmount: number; emiAmountWithIncrementalPenaltyCharge: number; @@ -28,6 +40,14 @@ interface AmountDueCardProps { repaymentFailureDetails: RepaymentFailureDetails; reminderType: HumanReminderType; upcomingDueDetails?: UpcomingDueDetails; + totalUnpaidDueAmount: number; + totalEmiAmount: number; + penaltyApplied: number; + nextIncrementAmount: number; + paidAmount: number; + heldPenaltyCharge: number; + holdEligibleDues: IHumanReminderDues; + nextDayDues: IHumanReminderDues; } const MANDATE_NOT_SETUP = 'Mandate not set up'; @@ -65,19 +85,41 @@ const AmountDueCard = (props: AmountDueCardProps) => { repaymentFailureDetails, reminderType, upcomingDueDetails, - emiAmountWithIncrementalPenaltyCharge + emiAmountWithIncrementalPenaltyCharge, + totalUnpaidDueAmount, + totalEmiAmount, + penaltyApplied, + nextIncrementAmount, + paidAmount, + heldPenaltyCharge, + holdEligibleDues, + nextDayDues } = props; const upcomingDueDateDiff = calculateDateDifferenceInDays( new Date(), new Date(upcomingDueDetails?.emiDueDate) ); const upcomingDueDays = upcomingDueDateDiff > 1 ? DAYS_PAST_DUE.DUE_DAYS : DAYS_PAST_DUE.DUE_DAY; + const [slotValue, setSlotValue] = React.useState(''); + const [showHoldEligibility, setShowHoldEligibility] = React.useState(false); const isMandateStatusSuccess = mandateStatus === MandateStatus.SUCCESS; const hideEnachCard = isMandateStatusSuccess && !repaymentFailureDetails?.reason; const enachCardText = repaymentFailureDetails?.reason ? repaymentFailureDetails?.reason : MANDATE_NOT_SETUP; + const popperTriggerRef = React.useRef>(null); + + useUpdateEffect(() => { + if (showHoldEligibility) { + setSlotValue(formatAmount(holdEligibleDues?.totalUnpaidDueAmount?.amount || 0)); + popperTriggerRef.current?.context?.setOpen(true); + } else { + popperTriggerRef.current?.context?.setOpen(false); + setSlotValue(formatAmount(totalUnpaidDueAmount)); + } + }, [showHoldEligibility]); + return ( { )} {emiDaysRemaining && reminderType === HumanReminderType.POST_DUE ? ( - since Due Date + since Due Date ({emiDueDate}) ) : null} - Total Amount Due + Total Overdue - {reminderType === HumanReminderType.POST_DUE - ? formatAmount(totalAmount) - : reminderType === HumanReminderType.PRE_DUE - ? formatAmount(upcomingDueDetails?.upcomingPayableDueAmount.amount) - : '--'} + {totalUnpaidDueAmount ? formatAmount(totalUnpaidDueAmount) : '--'} @@ -141,26 +179,135 @@ const AmountDueCard = (props: AmountDueCardProps) => { <> - EMI Due Date + EMI Amount - {emiDueDate} + {formatAmount(totalEmiAmount)} - EMI Penalty Charge + EMI Penalty - - {emiPenaltyCharge} + + {typeof emiPenaltyCharge === 'number' + ? formatAmount(emiPenaltyCharge) + : emiPenaltyCharge} - + {(holdEligibleDues || nextDayDues) && ( + + + Penalty Held + {nextDayDues ? ( +
+ } + label={`Till 11:59 PM today`} + size="sm" + variant="transparent" + /> + + + + + +
+ + Total Overdue (Tomorrow) + + + {formatAmount(nextDayDues?.totalUnpaidDueAmount?.amount || 0)} + +
+
+ + EMI Penalty (Tomorrow) + + + {formatAmount(nextDayDues?.penaltyApplied?.amount || 0)} + +
+
+
+
+ ) : ( + <> + // @TODO : to be added later + /* { + setShowHoldEligibility(prev => !prev); + }} + title="Show Hold Eligibility" + checkboxTitleClass={cx('text-[var(--blue-base)] font-medium font-[14px]')} + />*/ + )} +
+ + + + {showHoldEligibility + ? `${ + holdEligibleDues?.heldPenaltyCharge?.amount > 0 ? '-' : '' + } ${formatAmount(holdEligibleDues?.heldPenaltyCharge?.amount)}` + : `${heldPenaltyCharge > 0 ? '-' : ''} ${formatAmount(heldPenaltyCharge)}`} + + + {holdEligibleDues?.waiverRequestType === WAIVER_REQUEST_TYPE.WAIVER + ? `Eligible for 100% waiver to close EMI` + : 'Eligible for 50% of EMI Penalty'} + + + +
+ )} + + - EMI Amount + Paid Amount - - {formatAmount(emiAmount)} + + - {formatAmount(paidAmount)} { - - Upcoming EMI penalty charge + + Upcoming Overdue + - - - - - {formatAmount(emiIncreasePenalChargeAmount)} - - - - - - - Upcoming Total Amount Due - - @@ -201,25 +337,73 @@ const AmountDueCard = (props: AmountDueCardProps) => { + + + Upcoming EMI Penalty + + + {formatAmount(nextIncrementAmount)} + + ) : reminderType === HumanReminderType.PRE_DUE ? ( <> - Upcoming EMI Due Date + EMI Amount - {upcomingDueDetails?.emiDueDate - ? getFormatDate(new Date(upcomingDueDetails.emiDueDate), DateFormat.DD_MMM_YYYY) - : '--'} + {formatAmount(totalEmiAmount)} - Upcoming EMI Amount + EMI Penalty - {formatAmount(upcomingDueDetails?.upcomingDueAmount.amount)} + {formatAmount(penaltyApplied)} + + + + + Paid Amount + + + - {formatAmount(paidAmount)} + + + + {' '} + + + + Upcoming Overdue + {emiIncreasePenaltyChargeDate && ( + + )} + + + {formatAmount(emiAmountWithIncrementalPenaltyCharge)} + + + + + Upcoming EMI Penalty + + + {formatAmount(nextIncrementAmount)} diff --git a/src/pages/Dashboard/AmountdueCard.module.scss b/src/pages/Dashboard/AmountdueCard.module.scss index 98a213ae..4cc3849a 100644 --- a/src/pages/Dashboard/AmountdueCard.module.scss +++ b/src/pages/Dashboard/AmountdueCard.module.scss @@ -159,3 +159,17 @@ .fw700 { font-weight: 700 !important; } + +.tooltipWrapper { + padding: 4px 4px 4px 8px; + box-sizing: border-box; + width: fit-content; + z-index: var(--z-index-tooltip); + font-weight: 400; + font-size: 12px; + line-height: 14px; + word-wrap: break-word; + color: var(--text-primary); + border-radius: 4px; + background: var(--grayscale-warm-4); +} diff --git a/src/pages/Dashboard/HrCustomerDetails.tsx b/src/pages/Dashboard/HrCustomerDetails.tsx index 2de1e953..671033f2 100644 --- a/src/pages/Dashboard/HrCustomerDetails.tsx +++ b/src/pages/Dashboard/HrCustomerDetails.tsx @@ -23,7 +23,7 @@ interface HrCustomerDetailsProps { disConnectHandler: () => void; } -const emiPenaltyChargeOnHold = 'on hold'; +const emiPenaltyChargeOnHold = 'On hold'; const HrCustomerDetails = (props: HrCustomerDetailsProps) => { const { resetCallData, disConnectHandler } = props; @@ -80,9 +80,10 @@ const HrCustomerDetails = (props: HrCustomerDetailsProps) => { const genderAndAge = `${customerDetails?.gender || '--'}, ${customerDetails?.age || '--'}`; const lastUpdatedDue = loanDetails?.unpaidDues?.length ? loanDetails?.unpaidDues[0] : null; const emiPenaltyChargeStatus = - lastUpdatedDue?.penaltyCharges?.amount === 0 && lastUpdatedDue?.heldPenaltyCharge?.amount > 0 + lastUpdatedDue?.unpaidPenaltyCharges?.amount === 0 && + lastUpdatedDue?.heldPenaltyCharge?.amount > 0 ? emiPenaltyChargeOnHold - : formatAmount(lastUpdatedDue?.penaltyCharges?.amount); + : lastUpdatedDue?.penaltyApplied?.amount; const showFeedbackOptions = loanDetails?.loanAccountNumber && customerDetails?.customerReferenceId; @@ -143,7 +144,7 @@ const HrCustomerDetails = (props: HrCustomerDetailsProps) => { } emiAmount={lastUpdatedDue?.emiAmount?.amount || 0} emiDaysRemaining={loanDetails?.currentDpd || 0} - emiPenaltyCharge={emiPenaltyChargeStatus || '--'} + emiPenaltyCharge={emiPenaltyChargeStatus || 0} emiIncreasePenaltyChargeDate={ lastUpdatedDue?.nextIncrementDate ? getFormatDate(new Date(lastUpdatedDue?.nextIncrementDate), DateFormat.DD_MMM_YYYY) @@ -157,6 +158,14 @@ const HrCustomerDetails = (props: HrCustomerDetailsProps) => { repaymentFailureDetails={loanDetails?.repaymentFailureDetails} upcomingDueDetails={loanDetails?.upcomingDueDetails} reminderType={reminderType} + totalUnpaidDueAmount={lastUpdatedDue?.totalUnpaidDueAmount?.amount || 0} + totalEmiAmount={lastUpdatedDue?.totalEmiAmount?.amount || 0} + penaltyApplied={lastUpdatedDue?.penaltyApplied?.amount || 0} + nextIncrementAmount={lastUpdatedDue?.nextIncrementAmount?.amount || 0} + paidAmount={lastUpdatedDue?.paidAmount?.amount || 0} + heldPenaltyCharge={lastUpdatedDue?.heldPenaltyCharge?.amount || 0} + holdEligibleDues={loanDetails?.holdEligibilityDues?.[0]} + nextDayDues={loanDetails?.nextDayDues?.[0]} />