Files
frontend-app-authoring/src/taxonomy/taxonomy-menu/TaxonomyMenu.jsx
Yusuf Musleh d0b3328f26 feat: Import new taxonomy dialog flow (#1017)
This PR updates the existing import tags wizard to also handle  importing new taxonomies.
2024-05-24 19:19:26 +05:30

192 lines
5.7 KiB
JavaScript

// @ts-check
import React, { useCallback, useContext } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
useToggle,
Button,
Dropdown,
Icon,
IconButton,
} from '@openedx/paragon';
import { MoreVert } from '@openedx/paragon/icons';
import { pickBy } from 'lodash';
import PropTypes from 'prop-types';
import { useNavigate } from 'react-router-dom';
import ExportModal from '../export-modal';
import { useDeleteTaxonomy } from '../data/apiHooks';
import { TaxonomyContext } from '../common/context';
import DeleteDialog from '../delete-dialog';
import { ImportTagsWizard } from '../import-tags';
import { ManageOrgsModal } from '../manage-orgs';
import messages from './messages';
/** @typedef {import('../data/types.mjs').TaxonomyData} TaxonomyData */
// Note: to make mocking easier for tests, the types below only specify the subset of TaxonomyData that we actually use.
/**
* A menu that provides actions for editing a specific taxonomy.
* @type {React.FC<{
* taxonomy: Pick<TaxonomyData, 'id'|'name'|'tagsCount'|'systemDefined'|'canChangeTaxonomy'|'canDeleteTaxonomy'>,
* iconMenu?: boolean
* }>}
*/
const TaxonomyMenu = ({
taxonomy, iconMenu,
}) => {
const intl = useIntl();
const navigate = useNavigate();
const deleteTaxonomy = useDeleteTaxonomy();
const { setToastMessage } = useContext(TaxonomyContext);
const onDeleteTaxonomy = useCallback(() => {
deleteTaxonomy({ pk: taxonomy.id }, {
onSuccess: () => {
if (setToastMessage) {
setToastMessage(intl.formatMessage(messages.taxonomyDeleteToast, { name: taxonomy.name }));
}
navigate('/taxonomies');
},
onError: () => {
// TODO: display the error to the user
},
});
}, [setToastMessage, taxonomy]);
const [isDeleteDialogOpen, deleteDialogOpen, deleteDialogClose] = useToggle(false);
const [isExportModalOpen, exportModalOpen, exportModalClose] = useToggle(false);
const [isImportModalOpen, importModalOpen, importModalClose] = useToggle(false);
const [isManageOrgsModalOpen, manageOrgsModalOpen, manageOrgsModalClose] = useToggle(false);
/**
* @typedef {Object} MenuItem
* @property {string} title - The title of the menu item
* @property {() => void} action - The action to perform when the menu item is clicked
* @property {boolean} [show] - Whether or not to show the menu item
*
* @constant
* @type {Record<string, MenuItem>}
*/
let menuItems = {
import: {
title: intl.formatMessage(messages.importMenu),
action: importModalOpen,
show: taxonomy.canChangeTaxonomy && !taxonomy.systemDefined,
},
export: {
title: intl.formatMessage(messages.exportMenu),
action: exportModalOpen,
show: true, // if we can view the taxonomy, we can export it
},
delete: {
title: intl.formatMessage(messages.deleteMenu),
action: deleteDialogOpen,
show: taxonomy.canDeleteTaxonomy && !taxonomy.systemDefined,
},
manageOrgs: {
title: intl.formatMessage(messages.manageOrgsMenu),
action: manageOrgsModalOpen,
show: taxonomy.canChangeTaxonomy,
},
};
// Remove hidden menu items
menuItems = pickBy(menuItems, (value) => value.show);
const renderModals = () => (
<>
{isDeleteDialogOpen && (
<DeleteDialog
isOpen={isDeleteDialogOpen}
onClose={deleteDialogClose}
onDelete={onDeleteTaxonomy}
taxonomyName={taxonomy.name}
tagsCount={taxonomy.tagsCount}
/>
)}
{isExportModalOpen && (
<ExportModal
isOpen={isExportModalOpen}
onClose={exportModalClose}
taxonomyId={taxonomy.id}
/>
)}
{isImportModalOpen && (
<ImportTagsWizard
taxonomy={taxonomy}
isOpen={isImportModalOpen}
onClose={importModalClose}
reimport
/>
)}
{isManageOrgsModalOpen && (
<ManageOrgsModal
isOpen={isManageOrgsModalOpen}
onClose={manageOrgsModalClose}
taxonomyId={taxonomy.id}
/>
)}
</>
);
const toggleProps = iconMenu ? {
as: IconButton,
src: MoreVert,
iconAs: Icon,
} : {
as: Button,
};
return (
<Dropdown id={`taxonomy-menu-${taxonomy.id}`} onToggle={(_isOpen, ev) => ev.preventDefault()}>
<Dropdown.Toggle
id={`taxonomy-menu-toggle-${taxonomy.id}`}
{...toggleProps}
variant="primary"
alt={intl.formatMessage(messages.actionsButtonAlt, { name: taxonomy.name })}
data-testid="taxonomy-menu-button"
disabled={Object.keys(menuItems).length === 0}
>
{intl.formatMessage(messages.actionsButtonLabel)}
</Dropdown.Toggle>
<Dropdown.Menu data-testid="taxonomy-menu">
{Object.keys(menuItems).map((key) => (
<Dropdown.Item
key={key}
data-testid={`taxonomy-menu-${key}`}
as="button" // Prevents <a> cannot appear as a descendant of <a> warning
onClick={
(e) => {
e.preventDefault();
menuItems[key].action();
}
}
>
{menuItems[key].title}
</Dropdown.Item>
))}
</Dropdown.Menu>
{renderModals()}
</Dropdown>
);
};
TaxonomyMenu.propTypes = {
taxonomy: PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
tagsCount: PropTypes.number.isRequired,
systemDefined: PropTypes.bool.isRequired,
canChangeTaxonomy: PropTypes.bool.isRequired,
canDeleteTaxonomy: PropTypes.bool.isRequired,
}).isRequired,
iconMenu: PropTypes.bool,
};
TaxonomyMenu.defaultProps = {
iconMenu: false,
};
export default TaxonomyMenu;