From 31c08d657865f9c02ff0d5d8591d01de7de70960 Mon Sep 17 00:00:00 2001 From: Cyril Nxumalo Date: Tue, 27 Feb 2024 07:19:26 +0200 Subject: [PATCH] Display warning when plan is about to expire --- .../EnterpriseApp/data/constants.js | 1 + .../BudgetDetailPageHeader.jsx | 40 ++++++------ .../BudgetOverview.jsx | 63 +++++++++++++++++++ .../learner-credit-management/data/utils.js | 63 +++++++++++++++++++ 4 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 src/components/learner-credit-management/BudgetOverview.jsx diff --git a/src/components/EnterpriseApp/data/constants.js b/src/components/EnterpriseApp/data/constants.js index c19fcc0a62..a17c95b6eb 100644 --- a/src/components/EnterpriseApp/data/constants.js +++ b/src/components/EnterpriseApp/data/constants.js @@ -17,6 +17,7 @@ export const ROUTE_NAMES = { export const BUDGET_STATUSES = { active: 'Active', expired: 'Expired', + expiring: 'Expiring', scheduled: 'Scheduled', retired: 'Retired', }; diff --git a/src/components/learner-credit-management/BudgetDetailPageHeader.jsx b/src/components/learner-credit-management/BudgetDetailPageHeader.jsx index 7c359ecc17..6437390b29 100644 --- a/src/components/learner-credit-management/BudgetDetailPageHeader.jsx +++ b/src/components/learner-credit-management/BudgetDetailPageHeader.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { - Stack, Card, Badge, Skeleton, + Stack, Badge, Skeleton, } from '@edx/paragon'; import { connect } from 'react-redux'; @@ -12,11 +12,12 @@ import { useEnterpriseOffer, formatDate, useSubsidySummaryAnalyticsApi, + isPlanApproachingExpiry, } from './data'; import BudgetDetailPageBreadcrumbs from './BudgetDetailPageBreadcrumbs'; -import BudgetDetailPageOverviewAvailability from './BudgetDetailPageOverviewAvailability'; -import BudgetDetailPageOverviewUtilization from './BudgetDetailPageOverviewUtilization'; +import BudgetOverview from './BudgetOverview'; +import LearnerCreditExpiryCard from './LearnerCreditExpiryCard'; import { BUDGET_TYPES } from '../EnterpriseApp/data/constants'; const BudgetStatusBadge = ({ @@ -61,7 +62,9 @@ const BudgetDetailPageHeader = ({ enterpriseUUID, enterpriseFeatures }) => { enterpriseOfferMetadata, isTopDownAssignmentEnabled: enterpriseFeatures.topDownAssignmentRealTimeLcm, }); - + + const expiry = isPlanApproachingExpiry(date); + if (!subsidyAccessPolicy && (isLoadingSubsidySummary || isLoadingEnterpriseOffer)) { return (
@@ -74,23 +77,18 @@ const BudgetDetailPageHeader = ({ enterpriseUUID, enterpriseFeatures }) => { return ( - - -

{budgetDisplayName}

- - - -
-
+ {expiry.isExpiring && } +
); }; diff --git a/src/components/learner-credit-management/BudgetOverview.jsx b/src/components/learner-credit-management/BudgetOverview.jsx new file mode 100644 index 0000000000..beba35263e --- /dev/null +++ b/src/components/learner-credit-management/BudgetOverview.jsx @@ -0,0 +1,63 @@ +// BudgetOverview.js +import React from 'react'; +import PropTypes from 'prop-types'; +import { Badge, Card, Stack } from '@edx/paragon'; + +import BudgetDetailPageOverviewAvailability from './BudgetDetailPageOverviewAvailability'; +import BudgetDetailPageOverviewUtilization from './BudgetDetailPageOverviewUtilization'; +import { formatDate } from './data'; + +const BudgetStatusBadge = ({ + badgeVariant, status, term, date, +}) => ( + + {status} + {(term && date) && ( + {term} {formatDate(date)} + )} + +); + +const BudgetOverview = ({ + budgetDisplayName, + badgeVariant, + status, + term, + date, + budgetId, + budgetTotalSummary, + budgetAggregates, + isAssignable, +}) => ( + + +

{budgetDisplayName}

+ + + +
+
+); + +BudgetOverview.propTypes = { + budgetDisplayName: PropTypes.string.isRequired, + badgeVariant: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + term: PropTypes.string.isRequired, + date: PropTypes.string.isRequired, + budgetId: PropTypes.string.isRequired, + budgetTotalSummary: PropTypes.object.isRequired, + budgetAggregates: PropTypes.object.isRequired, + isAssignable: PropTypes.bool.isRequired, +}; + +export default BudgetOverview; diff --git a/src/components/learner-credit-management/data/utils.js b/src/components/learner-credit-management/data/utils.js index a01631bcac..dd14c3386b 100644 --- a/src/components/learner-credit-management/data/utils.js +++ b/src/components/learner-credit-management/data/utils.js @@ -136,6 +136,60 @@ export const getProgressBarVariant = ({ percentUtilized, remainingFunds }) => { // Utility function to check if the ID is a UUID export const isUUID = (id) => /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(id); +// Utility function to check if plan is about to expire +export const isPlanApproachingExpiry = (endDateStr, currentDate = new Date()) => { + const endDate = new Date(endDateStr); + const currentDateObj = new Date(currentDate); + const oneDay = 24 * 60 * 60 * 1000; + const oneHour = 60 * 60 * 1000; + const oneMinute = 60 * 1000; + + const timeUntilExpiry = endDate - currentDateObj; + const daysUntilExpiry = Math.floor(timeUntilExpiry / oneDay); + const hoursUntilExpiry = Math.floor((timeUntilExpiry % oneDay) / oneHour); + const minutesUntilExpiry = Math.floor((timeUntilExpiry % oneHour) / oneMinute); + + if (daysUntilExpiry > 120) { + return { + isExpiring: false, + warning: {}, + }; + } + + let warning = {}; + + if (daysUntilExpiry <= 120 && daysUntilExpiry > 60) { + warning = { + title: 'Your Learner Credit plan is ending soon', + message: `Your Learner Credit plan expires ${endDate.toLocaleDateString()}. Contact your representative today to renew your plan and keep people learning.`, + dismissible: false, + }; + } else if (daysUntilExpiry <= 60 && daysUntilExpiry > 30) { + warning = { + title: `Your Learner Credit plan expires ${endDate.toLocaleDateString()}`, + message: 'When your Learner Credit plan expires, you will no longer have access to administrative functions and the remaining balance of your budget(s) will be unusable. Contact a representative today to renew your plan.', + dismissible: true, + }; + } else if (daysUntilExpiry <= 30 && daysUntilExpiry > 10) { + warning = { + title: 'Your Learner Credit plan expires in less than 30 days', + message: 'When your plan expires you will lose access to administrative functions and the remaining balance of your plan’s budget(s) will be unusable. Contact your representative today to renew your plan.', + dismissible: false, + }; + } else if (daysUntilExpiry <= 10) { + warning = { + title: `Reminder: Your Learner Credit plan expires ${endDate.toLocaleDateString()}`, + message: `Your Learner Credit plan expires in ${daysUntilExpiry} days, ${hoursUntilExpiry} hours, and ${minutesUntilExpiry} minutes. Contact your representative today to renew your plan.`, + dismissible: false, + }; + } + + return { + isExpiring: true, + warning, + }; +}; + // Utility function to check the budget status export const getBudgetStatus = ({ startDateStr, @@ -167,6 +221,15 @@ export const getBudgetStatus = ({ }; } + if (isPlanApproachingExpiry(endDateStr).isExpiring) { + return { + status: BUDGET_STATUSES.expiring, + badgeVariant: 'warning', + term: 'Expiring', + date: endDateStr, + }; + } + // Check if budget is current (today's date between start/end dates) if (currentDate >= startDate && currentDate <= endDate) { return {