Skip to content

Commit

Permalink
Merge pull request #352 from openedx/knguyen2/ENT-7504
Browse files Browse the repository at this point in the history
feat: Implements Detailed Plan View with Edit Mode Button
  • Loading branch information
katrinan029 authored Aug 28, 2023
2 parents a3222cf + 7cbc867 commit 21c4aa3
Show file tree
Hide file tree
Showing 38 changed files with 1,155 additions and 2 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@fortawesome/react-fontawesome": "^0.1.14",
"babel-polyfill": "6.26.0",
"classnames": "2.2.6",
"dayjs": "^1.11.9",
"lodash.debounce": "4.0.8",
"lodash.snakecase": "4.1.1",
"moment": "2.29.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import PropTypes from 'prop-types';
import PROVISIONING_PAGE_TEXT from '../data/constants';

const AccountTypeDetail = ({ isMultipleFunds }) => {
const { FORM: { ACCOUNT_CREATION } } = PROVISIONING_PAGE_TEXT;

return (
<article className="mt-4.5">
<div className="mb-1">
<h3>{ACCOUNT_CREATION.TITLE}</h3>
<p className="ml-3 mb-1">
{ACCOUNT_CREATION.SUB_TITLE}
</p>
<p className="ml-3 text-gray-500">
{isMultipleFunds ? ACCOUNT_CREATION.OPTIONS.multiple : ACCOUNT_CREATION.OPTIONS.single}
</p>
</div>
</article>
);
};

AccountTypeDetail.propTypes = {
isMultipleFunds: PropTypes.bool.isRequired,
};

export default AccountTypeDetail;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import PropTypes from 'prop-types';
import PROVISIONING_PAGE_TEXT from '../data/constants';

const CustomerDetail = ({ enterpriseCustomer, financialIdentifier, uuid }) => {
const { FORM: { CUSTOMER } } = PROVISIONING_PAGE_TEXT;

return (
<article className="mt-4.5">
<div className="mb-1">
<h3>{CUSTOMER.TITLE}</h3>
</div>
<div className="mb-1 ml-3 mt-3">
<h4>{CUSTOMER.ENTERPRISE_UUID.TITLE}</h4>
<p className="small">
{enterpriseCustomer} / {uuid}
</p>
</div>
<div className="mb-1 ml-3 mt-3">
<h4>{CUSTOMER.FINANCIAL_IDENTIFIER.TITLE}</h4>
<p className="small">
{financialIdentifier}
</p>
</div>
</article>
);
};

CustomerDetail.propTypes = {
enterpriseCustomer: PropTypes.string.isRequired,
financialIdentifier: PropTypes.string.isRequired,
uuid: PropTypes.string.isRequired,
};

export default CustomerDetail;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Button } from '@edx/paragon';

const EditButton = () => (
<Button variant="primary">
Edit plan
</Button>
);

export default EditButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import PropTypes from 'prop-types';
import { Icon } from '@edx/paragon';
import { Check } from '@edx/paragon/icons';
import PROVISIONING_PAGE_TEXT from '../data/constants';

const InternalOnlyDetail = ({ isInternalOnly }) => {
const { FORM: { INTERNAL_ONLY } } = PROVISIONING_PAGE_TEXT;

return (
isInternalOnly ? (
<article className="mt-4.5">
<div className="mb-1">
<h3>{INTERNAL_ONLY.TITLE}</h3>
</div>
<div className="d-flex">
<Icon src={Check} className="mr-2" />
<p className="small">{INTERNAL_ONLY.CHECKBOX.label}</p>
</div>
</article>
) : null
);
};

InternalOnlyDetail.propTypes = {
isInternalOnly: PropTypes.bool.isRequired,
};

export default InternalOnlyDetail;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import PropTypes from 'prop-types';
import CustomCatalogDetail from './CustomCatalogDetail';
import PROVISIONING_PAGE_TEXT from '../../data/constants';

const { FORM: { CATALOG } } = PROVISIONING_PAGE_TEXT;

// finds the catalog in associatedCatalog string,
// e.g. "9c1fd12b-2365-4100-8e3e-01d2ff1414e0 - Executive Education budget"
// returns "Executive Education"
function getCatalogType(associatedCatalog) {
let catalogType = null;
if (associatedCatalog) {
if (associatedCatalog.includes(CATALOG.OPTIONS.openCourses)) {
catalogType = CATALOG.OPTIONS.openCourses;
} else if (associatedCatalog.includes(CATALOG.OPTIONS.executiveEducation)) {
catalogType = CATALOG.OPTIONS.executiveEducation;
} else if (associatedCatalog.includes(CATALOG.OPTIONS.everything)) {
catalogType = CATALOG.OPTIONS.everything;
} else {
catalogType = CATALOG.OPTIONS.custom;
}
}
return catalogType;
}

const AssociatedCatalogDetail = ({ associatedCatalog }) => {
const catalogType = getCatalogType(associatedCatalog);

return (
<div className="mb-1 mt-3">
<h3>{CATALOG.TITLE}</h3>
<div className="mb-1 ml-3 mt-3">
<h4>{CATALOG.SUB_TITLE}</h4>
<p className="small">
{catalogType}
</p>
{(catalogType === CATALOG.OPTIONS.custom) && <CustomCatalogDetail catalogTitle={associatedCatalog} />}
</div>
</div>
);
};

AssociatedCatalogDetail.propTypes = {
associatedCatalog: PropTypes.string.isRequired,
};

export default AssociatedCatalogDetail;
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router';
import { logError } from '@edx/frontend-platform/logging';
import LmsApiService from '../../../../data/services/EnterpriseApiService';
import PROVISIONING_PAGE_TEXT from '../../data/constants';
import ROUTES from '../../../../data/constants/routes';

const { FORM: { CUSTOM_CATALOG } } = PROVISIONING_PAGE_TEXT;

function getCustomCatalogTitle(catalogTitle) {
let customCatalogTitle;
const separator = ' - ';
if (catalogTitle.includes(separator)) {
[, customCatalogTitle] = catalogTitle.split(separator);
}
return customCatalogTitle;
}

async function getCatalogQueries() {
const { data } = await LmsApiService.fetchEnterpriseCatalogQueries();
return data;
}

const CustomCatalogDetail = ({ catalogTitle }) => {
const history = useHistory();
const { SUB_DIRECTORY: { ERROR } } = ROUTES.CONFIGURATION.SUB_DIRECTORY.PROVISIONING;

const [catalogQueryContentFilter, setCatalogQueryContentFilter] = useState(null);
const [isLoading, setIsLoading] = useState(true);

const redirectOnError = (statusCode, message) => {
history.push(ERROR, {
errorMessage: `Error ${statusCode}: ${message}`,
});
};

const formatCatalogTitle = getCustomCatalogTitle(catalogTitle);

useEffect(() => {
const fetchCatalogQueryContentFilter = async () => {
try {
const catalogQueries = await getCatalogQueries();
const findCatalogQuery = catalogQueries.results.filter(
catalogQuery => formatCatalogTitle === catalogQuery.title,
);
setCatalogQueryContentFilter(JSON.stringify(findCatalogQuery[0].content_filter, null, 2));
setIsLoading(false);
} catch (error) {
const { customAttributes } = error;
logError(error);
redirectOnError(customAttributes?.httpErrorStatus, error);
}
};
fetchCatalogQueryContentFilter();
}, [catalogTitle]);

return (
!isLoading ? (
<div className="mb-1 mt-4.5">
<h4>{CUSTOM_CATALOG.HEADER.DEFINE.TITLE}</h4>
<div className="mb-1 ml-3 mt-3">
<h5>{CUSTOM_CATALOG.OPTIONS.enterpriseCatalogQuery.title}</h5>
<p className="small">
{catalogTitle}
</p>
<h5>{CUSTOM_CATALOG.OPTIONS.catalogTitle}</h5>
<p className="small">
{getCustomCatalogTitle(catalogTitle)}
</p>
<h5>{CUSTOM_CATALOG.OPTIONS.contentFilter}</h5>
<pre data-testid="content-filter" className="x-small text-gray-500">
{catalogQueryContentFilter}
</pre>
</div>
</div>
) : null
);
};

CustomCatalogDetail.propTypes = {
catalogTitle: PropTypes.string.isRequired,
};

export default CustomCatalogDetail;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Stack } from '@edx/paragon';
import AssociatedCatalogDetail from './AssociatedCatalogDetail';
import PolicyDescription from './PolicyDescription';
import PolicyDetail from './PolicyDetail';
import PolicyDetailHeader from './PolicyDetailHeader';
import PolicyLimitsDetail from './PolicyLimitsDetail';

function getBudgetDisplayName(subsidyTitle, catalogTitle) {
let budgetDisplayName;
if (subsidyTitle && catalogTitle) {
const associatedCatalog = catalogTitle.split(' - ')[1];
budgetDisplayName = `${subsidyTitle} - ${associatedCatalog}`;
}
return budgetDisplayName;
}

const PolicyContainer = ({ data }) => {
const { subsidy, policies, catalogs } = data;
const renderPolicy = policies.map((policy) => catalogs.map(catalog => {
if (catalog.uuid === policy.catalog_uuid) {
return (
<Stack key={policy.catalog_uuid} gap={48}>
<PolicyDetailHeader policiesLength={policies.length} accountType={catalog.title} />
<PolicyDetail
displayName={getBudgetDisplayName(subsidy.title, catalog.title)}
spendLimit={policy.spend_limit}
/>
<PolicyDescription description={policy.description} />
<AssociatedCatalogDetail associatedCatalog={catalog.title} />
<PolicyLimitsDetail perLearnerLimit={policy.per_learner_spend_limit} />
</Stack>
);
}
return null;
}));
return renderPolicy;
};

export default PolicyContainer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import PropTypes from 'prop-types';
import PROVISIONING_PAGE_TEXT from '../../data/constants';

const PolicyDescription = ({ description }) => {
const { FORM: { ACCOUNT_DESCRIPTION } } = PROVISIONING_PAGE_TEXT;

return (
<div className="mb-1 mt-3">
<h3>{ACCOUNT_DESCRIPTION.TITLE}</h3>
<p className="small">
{description}
</p>
</div>
);
};

PolicyDescription.propTypes = {
description: PropTypes.string.isRequired,
};

export default PolicyDescription;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import PropTypes from 'prop-types';
import PROVISIONING_PAGE_TEXT from '../../data/constants';
import { formatCurrency } from '../../data/utils';

const PolicyDetail = ({ displayName, spendLimit }) => {
const { FORM: { ACCOUNT_TYPE, ACCOUNT_DETAIL } } = PROVISIONING_PAGE_TEXT;

return (
<div className="mb-1 mt-3">
<h3>{ACCOUNT_TYPE.DEFAULT}</h3>
<h3>{ACCOUNT_DETAIL.TITLE}</h3>
<div className="mb-1 ml-3 mt-3">
<h4>{ACCOUNT_DETAIL.OPTIONS.displayName}</h4>
<p className="small">
{displayName}
</p>
<h4>{ACCOUNT_DETAIL.OPTIONS.totalAccountValue.title}</h4>
<p className="small">
{formatCurrency(spendLimit)}
</p>
</div>
</div>
);
};

PolicyDetail.propTypes = {
displayName: PropTypes.string.isRequired,
spendLimit: PropTypes.number.isRequired,
};

export default PolicyDetail;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import PropTypes from 'prop-types';
import PROVISIONING_PAGE_TEXT from '../../data/constants';

const PolicyDetailHeader = ({ accountType, policiesLength }) => {
const { FORM: { ACCOUNT_TYPE, CATALOG } } = PROVISIONING_PAGE_TEXT;
const renderAccountType = () => {
let account;
if (policiesLength > 1 && accountType.includes(CATALOG.OPTIONS.executiveEducation)) {
account = ACCOUNT_TYPE.OPTIONS.executiveEducation;
} else if (policiesLength > 1 && accountType.includes(CATALOG.OPTIONS.openCourses)) {
account = ACCOUNT_TYPE.OPTIONS.openCourses;
} else {
account = ACCOUNT_TYPE.OPTIONS.default;
}
return account;
};

return (
<article className="mt-4.5">
<div className="mb-1">
<h2>{renderAccountType()}</h2>
<hr />
</div>
</article>
);
};

PolicyDetailHeader.propTypes = {
accountType: PropTypes.string.isRequired,
policiesLength: PropTypes.number.isRequired,
};

export default PolicyDetailHeader;
Loading

0 comments on commit 21c4aa3

Please sign in to comment.