Skip to content

Commit

Permalink
Merge branch 'master' of github.com:edx/frontend-app-admin-portal int…
Browse files Browse the repository at this point in the history
…o hamza/ENT-8510
  • Loading branch information
hamzawaleed01 committed Mar 11, 2024
2 parents 5de18f9 + 135cb6e commit ee7f5ff
Show file tree
Hide file tree
Showing 13 changed files with 325 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Button, Card } from '@edx/paragon';
import {
formatDate, formatPrice, useBudgetId, usePathToCatalogTab, useSubsidyAccessPolicy,
} from './data';
import nameYourLearner from './assets/nameYourLearners.svg';
import nameYourLearner from './assets/reading.svg';

const AssignMoreCoursesEmptyStateMinimal = () => {
const { subsidyAccessPolicyId } = useBudgetId();
Expand Down
60 changes: 35 additions & 25 deletions src/components/learner-credit-management/BudgetCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,46 +17,55 @@ import { BUDGET_TYPES } from '../EnterpriseApp/data/constants';
*
* @returns Budget card component(s).
*/
const BudgetCard = ({
budget,
enterpriseUUID,
enterpriseSlug,
}) => {
const BudgetCard = ({ original }) => {
const {
aggregates,
end,
enterpriseSlug,
enterpriseUUID,
id,
isAssignable,
isRetired,
name,
source,
start,
} = original;

const {
isLoading: isLoadingSubsidySummaryAnalyticsApi,
subsidySummary: subsidySummaryAnalyticsApi,
} = useSubsidySummaryAnalyticsApi(enterpriseUUID, budget.id, budget.source);
} = useSubsidySummaryAnalyticsApi(enterpriseUUID, id, source);

// Subsidy Access Policies will always have a single budget, so we can render a single card
// without relying on `useSubsidySummaryAnalyticsApi`.
if (budget.source === BUDGET_TYPES.policy) {
if (source === BUDGET_TYPES.policy) {
return (
<SubBudgetCard
id={budget.id}
start={budget.start}
end={budget.end}
available={budget.aggregates.available}
spent={budget.aggregates.spent}
pending={budget.aggregates.pending}
displayName={budget.name}
id={id}
start={start}
end={end}
available={aggregates.available}
spent={aggregates.spent}
pending={aggregates.pending}
displayName={name}
enterpriseSlug={enterpriseSlug}
isAssignable={budget.isAssignable}
isRetired={budget.isRetired}
isAssignable={isAssignable}
isRetired={isRetired}
/>
);
}

// Enterprise Offers (ecommerce) will always have a single budget, so we can render a single card.
if (budget.source === BUDGET_TYPES.ecommerce) {
if (source === BUDGET_TYPES.ecommerce) {
return (
<SubBudgetCard
isLoading={isLoadingSubsidySummaryAnalyticsApi}
id={subsidySummaryAnalyticsApi?.offerId}
start={budget.start}
end={budget.end}
start={start}
end={end}
available={subsidySummaryAnalyticsApi?.remainingFunds}
spent={subsidySummaryAnalyticsApi?.redeemedFunds}
displayName={budget.name}
displayName={name}
enterpriseSlug={enterpriseSlug}
/>
);
Expand All @@ -74,8 +83,8 @@ const BudgetCard = ({
isLoading={isLoadingSubsidySummaryAnalyticsApi}
key={subBudget.subsidyAccessPolicyUuid}
id={subBudget.subsidyAccessPolicyUuid}
start={budget.start}
end={budget.end}
start={start}
end={end}
available={subBudget.remainingFunds}
spent={subBudget.redeemedFunds}
displayName={subBudget.subsidyAccessPolicyDisplayName}
Expand All @@ -85,7 +94,7 @@ const BudgetCard = ({
};

BudgetCard.propTypes = {
budget: PropTypes.shape({
original: PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
name: PropTypes.string.isRequired,
start: PropTypes.string.isRequired,
Expand All @@ -98,9 +107,10 @@ BudgetCard.propTypes = {
}),
isAssignable: PropTypes.bool,
isRetired: PropTypes.bool,
enterpriseUUID: PropTypes.string.isRequired,
enterpriseSlug: PropTypes.string.isRequired,
status: PropTypes.string,
}).isRequired,
enterpriseUUID: PropTypes.string.isRequired,
enterpriseSlug: PropTypes.string.isRequired,
};

export default BudgetCard;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { Stack, Skeleton } from '@edx/paragon';
import BudgetDetailRedemptions from './BudgetDetailRedemptions';
import BudgetDetailAssignments from './BudgetDetailAssignments';
import { useBudgetDetailActivityOverview, useBudgetId, useSubsidyAccessPolicy } from './data';
import NoBudgetActivityEmptyState from './NoBudgetActivityEmptyState';
import NoAssignableBudgetActivity from './empty-state/NoAssignableBudgetActivity';
import NoBnEBudgetActivity from './empty-state/NoBnEBudgetActivity';

const BudgetDetailActivityTabContents = ({ enterpriseUUID, enterpriseFeatures }) => {
const isTopDownAssignmentEnabled = enterpriseFeatures.topDownAssignmentRealTimeLcm;
Expand All @@ -32,19 +33,22 @@ const BudgetDetailActivityTabContents = ({ enterpriseUUID, enterpriseFeatures })
);
}

const hasSpentTransactions = budgetActivityOverview.spentTransactions?.count > 0;
const hasContentAssignments = budgetActivityOverview.contentAssignments?.count > 0;

if (!isTopDownAssignmentEnabled || !subsidyAccessPolicy?.isAssignable) {
return <BudgetDetailRedemptions />;
return (
<>
{!hasSpentTransactions && (<NoBnEBudgetActivity />)}
<BudgetDetailRedemptions />
</>
);
}

const hasContentAssignments = !!budgetActivityOverview.contentAssignments?.count > 0;
const hasSpentTransactions = !!budgetActivityOverview.spentTransactions?.count > 0;

// If there is no activity whatsoever (no assignments, no spent transactions), show the
// full empty state.
if (!hasContentAssignments && !hasSpentTransactions) {
return (
<NoBudgetActivityEmptyState />
);
return <NoAssignableBudgetActivity />;
}

// Otherwise, render the contents of the "Activity" tab.
Expand Down
90 changes: 69 additions & 21 deletions src/components/learner-credit-management/MultipleBudgetsPicker.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import React from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import {
Stack,
DataTable,
CardView,
TextFilter,
CheckboxFilter,
Row,
Col,
} from '@edx/paragon';
import groupBy from 'lodash/groupBy';

import BudgetCard from './BudgetCard';
import { orderBudgets } from './data/utils';
import { getBudgetStatus, orderBudgets } from './data/utils';

const MultipleBudgetsPicker = ({
budgets,
Expand All @@ -16,27 +20,71 @@ const MultipleBudgetsPicker = ({
enableLearnerPortal,
}) => {
const orderedBudgets = orderBudgets(budgets);

const rows = useMemo(
() => orderedBudgets.map(budget => {
const budgetLabel = getBudgetStatus({
startDateStr: budget.start,
endDateStr: budget.end,
isBudgetRetired: budget.isRetired,
});

return ({
...budget,
status: budgetLabel.status,
enterpriseUUID,
enterpriseSlug,
enableLearnerPortal,
});
}),
[orderedBudgets, enterpriseUUID, enterpriseSlug, enableLearnerPortal],
);

const budgetLabels = orderedBudgets.map(budget => (
getBudgetStatus({
startDateStr: budget.start,
endDateStr: budget.end,
isBudgetRetired: budget.isRetired,
})
));
const budgetLabelsByStatus = groupBy(budgetLabels, 'status');
const reducedChoices = Object.keys(budgetLabelsByStatus).map(budgetLabel => ({
name: budgetLabel,
number: budgetLabelsByStatus[budgetLabel].length,
value: budgetLabel,
}));

return (
<Stack gap={4}>
<Row>
<>
<Row className="mb-4">
<Col lg="12"><h2>Budgets</h2></Col>
</Row>
<Row>
<Col lg="12">
<Stack gap={4}>
{orderedBudgets.map(budget => (
<BudgetCard
key={budget.id}
budget={budget}
enterpriseUUID={enterpriseUUID}
enterpriseSlug={enterpriseSlug}
enableLearnerPortal={enableLearnerPortal}
/>
))}
</Stack>
</Col>
</Row>
</Stack>
<DataTable
defaultColumnValues={{ Filter: TextFilter }}
isFilterable
itemCount={orderedBudgets.length || 0}
data={rows}
columns={[
{
Header: 'budget name',
accessor: 'name',
},
{
Header: 'Status',
accessor: 'status',
Filter: CheckboxFilter,
filterChoices: reducedChoices,
},
]}
>
<DataTable.TableControlBar />
<CardView
CardComponent={BudgetCard}
columnSizes={{ xs: 12 }}
/>
<DataTable.TableFooter />
</DataTable>
</>
);
};

Expand Down
7 changes: 3 additions & 4 deletions src/components/learner-credit-management/SubBudgetCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import classNames from 'classnames';
import {
Card,
Button,
Row,
Col,
Badge,
Stack,
Expand Down Expand Up @@ -123,8 +122,8 @@ const BaseSubBudgetCard = ({
title={<h4>Balance</h4>}
muted
>
<Row className="d-flex flex-row justify-content-start w-md-75">
<Col xs="6" md="auto" className="mb-3 mb-md-0">
<Col className="d-flex justify-content-start w-md-75">
<Col xs="6" md="auto" className="mb-3 mb-md-0 ml-n4.5">
<div className="small font-weight-bold">Available</div>
<span className="small">
{isFetchingBudgets ? <Skeleton /> : formatPrice(available)}
Expand All @@ -144,7 +143,7 @@ const BaseSubBudgetCard = ({
{isFetchingBudgets ? <Skeleton /> : formatPrice(spent)}
</span>
</Col>
</Row>
</Col>
</Card.Section>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';

import { useIsLargeOrGreater, usePathToCatalogTab } from './data';
import nameYourLearners from './assets/nameYourLearners.svg';
import findTheRightCourse from './assets/findTheRightCourse.svg';
import confirmSpend from './assets/confirmSpend.svg';
import EVENT_NAMES from '../../eventTracking';
import { useIsLargeOrGreater, usePathToCatalogTab } from '../data';
import findTheRightCourse from '../assets/phoneScroll.svg';
import nameYourLearners from '../assets/reading.svg';
import confirmSpend from '../assets/wallet.svg';
import EVENT_NAMES from '../../../eventTracking';

const FindTheRightCourseIllustration = (props) => (
<img data-testid="find-the-right-course-illustration" src={findTheRightCourse} alt="" {...props} />
Expand Down
Loading

0 comments on commit ee7f5ff

Please sign in to comment.