Skip to content

Commit

Permalink
feat: Modal for export taxonomy
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisChV committed Oct 18, 2023
1 parent 0bdfbb1 commit 5698718
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 53 deletions.
2 changes: 1 addition & 1 deletion src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@
@import "course-updates/CourseUpdates";
@import "export-page/CourseExportPage";
@import "import-page/CourseImportPage";
@import "taxonomy/TaxonomyCard.scss";
@import "taxonomy/taxonomy-card/TaxonomyCard.scss";
2 changes: 1 addition & 1 deletion src/taxonomy/TaxonomyListPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import Header from '../header';
import SubHeader from '../generic/sub-header/SubHeader';
import messages from './messages';
import TaxonomyCard from './TaxonomyCard';
import TaxonomyCard from './taxonomy-card/TaxonomyCard';
import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from './api/hooks/selectors';

const TaxonomyListPage = ({ intl }) => {
Expand Down
24 changes: 24 additions & 0 deletions src/taxonomy/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,30 @@ const messages = defineMessages({
id: 'course-authoring.taxonomy-list.menu.alt',
defaultMessage: '{name} menu',
},
exportModalTitle: {
id: 'course-authoring.taxonomy-list.modal.export.title',
defaultMessage: 'Select format to export',
},
exportModalBodyDescription: {
id: 'course-authoring.taxonomy-list.modal.export.body',
defaultMessage: 'Select the file format in which you would like the taxonomy to be exported:',
},
exportModalSubmitButtonLabel: {
id: 'course-authoring.taxonomy-list.modal.export.submit.label',
defaultMessage: 'Export',
},
taxonomyCSVFormat: {
id: 'course-authoring.taxonomy-list.csv-format',
defaultMessage: 'CSV file',
},
taxonomyJSONFormat: {
id: 'course-authoring.taxonomy-list.json-format',
defaultMessage: 'JSON file',
},
taxonomyModalsCancelLabel: {
id: 'course-authoring.taxonomy-list.modal.cancel',
defaultMessage: 'Cancel',
},
});

export default messages;
79 changes: 79 additions & 0 deletions src/taxonomy/modals/ExportModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useState } from 'react';
import {
ActionRow,
Button,
Form,
ModalDialog,
} from '@edx/paragon';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from '../messages';

const ExportModal = ({
taxonomyId,
isOpen,
onClose,
intl,
}) => {
const [modalSize, setModalSize] = useState('csv');

return (
<ModalDialog
isOpen={isOpen}
onClose={onClose}
size="lg"
hasCloseButton
isFullscreenOnMobile
>
<ModalDialog.Header>
<ModalDialog.Title>
{intl.formatMessage(messages.exportModalTitle)}
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body className="pb-5 mt-2">
<Form.Group>
<Form.Label>
{intl.formatMessage(messages.exportModalBodyDescription)}
</Form.Label>
<Form.RadioSet
name="export-format"
value={modalSize}
onChange={(e) => setModalSize(e.target.value)}
>
<Form.Radio
key={`export-csv-format-${taxonomyId}`}
value="csv"
>
{intl.formatMessage(messages.taxonomyCSVFormat)}
</Form.Radio>
<Form.Radio
key={`export-json-format-${taxonomyId}`}
value="json"
>
{intl.formatMessage(messages.taxonomyJSONFormat)}
</Form.Radio>
</Form.RadioSet>
</Form.Group>
</ModalDialog.Body>
<ModalDialog.Footer>
<ActionRow>
<ModalDialog.CloseButton variant="tertiary">
{intl.formatMessage(messages.taxonomyModalsCancelLabel)}
</ModalDialog.CloseButton>
<Button variant="primary">
{intl.formatMessage(messages.exportModalSubmitButtonLabel)}
</Button>
</ActionRow>
</ModalDialog.Footer>
</ModalDialog>
);
};

ExportModal.propTypes = {
taxonomyId: PropTypes.number.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
intl: intlShape.isRequired,
};

export default injectIntl(ExportModal);
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
import React, { useState } from 'react';
import {
Badge,
IconButton,
Card,
OverlayTrigger,
Popover,
ModalPopup,
Menu,
Icon,
MenuItem,
} from '@edx/paragon';
import { MoreVert } from '@edx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import messages from './messages';
import messages from '../messages';
import TaxonomyCardMenu from './TaxonomyCardMenu';
import ExportModal from '../modals/ExportModal';

const TaxonomyCard = ({ className, original, intl }) => {
const {
id, name, description, systemDefined, orgsCount,
} = original;

const [menuIsOpen, setMenuIsOpen] = useState(false);
const [menuTarget, setMenuTarget] = useState(null);
const [isExportModalOpen, setIsExportModalOpen] = useState(false);

const orgsCountEnabled = () => orgsCount !== undefined && orgsCount !== 0;

const onClickMenuItem = (menuName) => {
switch (menuName) {
case 'export':
setIsExportModalOpen(true);
break;
default:
break;
}
};

const getSystemBadgeToolTip = () => (
<Popover id={`system-defined-tooltip-${id}`}>
<Popover.Title as="h5">
Expand Down Expand Up @@ -61,52 +66,42 @@ const TaxonomyCard = ({ className, original, intl }) => {
return undefined;
};

const onClickExport = () => {
setMenuIsOpen(false);
};

const getHeaderActions = () => (
<TaxonomyCardMenu id={id} name={name} onClickMenuItem={onClickMenuItem} />
);

const renderModals = () => (
// eslint-disable-next-line react/jsx-no-useless-fragment
<>
<IconButton
variant="primary"
onClick={() => setMenuIsOpen(true)}
ref={setMenuTarget}
src={MoreVert}
iconAs={Icon}
alt={intl.formatMessage(messages.taxonomyMenuAlt, { name })}
data-testid={`taxonomy-card-menu-button-${id}`}
/>
<ModalPopup
positionRef={menuTarget}
isOpen={menuIsOpen}
onClose={() => setMenuIsOpen(false)}
>
<Menu data-testid={`taxonomy-card-menu-${id}`}>
<MenuItem className="taxonomy-menu-item" onClick={onClickExport}>
{intl.formatMessage(messages.taxonomyCardExportMenu)}
</MenuItem>
</Menu>
</ModalPopup>
{isExportModalOpen && (
<ExportModal
isOpen={isExportModalOpen}
onClose={() => setIsExportModalOpen(false)}
/>
)}
</>
);

return (
<Card className={classNames('taxonomy-card', className)} data-testid={`taxonomy-card-${id}`}>
<Card.Header
title={name}
subtitle={getHeaderSubtitle()}
actions={getHeaderActions()}
/>
<Card.Body className={classNames('taxonomy-card-body', {
'taxonomy-card-body-overflow-m': !systemDefined && !orgsCountEnabled(),
'taxonomy-card-body-overflow-sm': systemDefined || orgsCountEnabled(),
})}
>
<Card.Section>
{description}
</Card.Section>
</Card.Body>
</Card>
<>
<Card className={classNames('taxonomy-card', className)} data-testid={`taxonomy-card-${id}`}>
<Card.Header
title={name}
subtitle={getHeaderSubtitle()}
actions={getHeaderActions()}
/>
<Card.Body className={classNames('taxonomy-card-body', {
'taxonomy-card-body-overflow-m': !systemDefined && !orgsCountEnabled(),
'taxonomy-card-body-overflow-sm': systemDefined || orgsCountEnabled(),
})}
>
<Card.Section>
{description}
</Card.Section>
</Card.Body>
</Card>
{renderModals()}
</>
);
};

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AppProvider } from '@edx/frontend-platform/react';
import { render, fireEvent } from '@testing-library/react';
import PropTypes from 'prop-types';

import initializeStore from '../store';
import initializeStore from '../../store';

import TaxonomyCard from './TaxonomyCard';

Expand Down Expand Up @@ -92,7 +92,7 @@ describe('<TaxonomyCard />', async () => {
// Click on the menu button to open
fireEvent.click(getByTestId('taxonomy-card-menu-button-1'));

// Menu open
// Menu opened
expect(getByTestId('taxonomy-card-menu-1')).toBeInTheDocument();

// Click on any element to close the menu
Expand All @@ -101,4 +101,24 @@ describe('<TaxonomyCard />', async () => {
// Menu closed
expect(() => getByTestId('taxonomy-card-menu-1')).toThrow();
});

test('should open export modal on export menu click', () => {
const { getByTestId, getByText } = render(<TaxonomyCardComponent original={data} />);

// Modal closed
expect(() => getByText('Select format to export')).toThrow();

// Click on export menu
fireEvent.click(getByTestId('taxonomy-card-menu-button-1'));
fireEvent.click(getByText('Export'));

// Modal opened
expect(getByText('Select format to export')).toBeInTheDocument();

// Click on cancel button
fireEvent.click(getByText('Cancel'));

// Modal closed
expect(() => getByText('Select format to export')).toThrow();
});
});
58 changes: 58 additions & 0 deletions src/taxonomy/taxonomy-card/TaxonomyCardMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react';
import {
IconButton,
ModalPopup,
Menu,
Icon,
MenuItem,
} from '@edx/paragon';
import { MoreVert } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from '../messages';

const TaxonomyCardMenu = ({
id, name, onClickMenuItem, intl,
}) => {
const [menuIsOpen, setMenuIsOpen] = useState(false);
const [menuTarget, setMenuTarget] = useState(null);

const onClickItem = (menuName) => {
setMenuIsOpen(false);
onClickMenuItem(menuName);
};

return (
<>
<IconButton
variant="primary"
onClick={() => setMenuIsOpen(true)}
ref={setMenuTarget}
src={MoreVert}
iconAs={Icon}
alt={intl.formatMessage(messages.taxonomyMenuAlt, { name })}
data-testid={`taxonomy-card-menu-button-${id}`}
/>
<ModalPopup
positionRef={menuTarget}
isOpen={menuIsOpen}
onClose={() => setMenuIsOpen(false)}
>
<Menu data-testid={`taxonomy-card-menu-${id}`}>
<MenuItem className="taxonomy-menu-item" onClick={() => onClickItem('export')}>
{intl.formatMessage(messages.taxonomyCardExportMenu)}
</MenuItem>
</Menu>
</ModalPopup>
</>
);
};

TaxonomyCardMenu.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
onClickMenuItem: PropTypes.func.isRequired,
intl: intlShape.isRequired,
};

export default injectIntl(TaxonomyCardMenu);

0 comments on commit 5698718

Please sign in to comment.