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 {