diff --git a/packages/core/admin/admin/src/content-manager/components/DynamicTable/BulkActionsBar/index.js b/packages/core/admin/admin/src/content-manager/components/DynamicTable/BulkActionsBar/index.js
new file mode 100644
index 0000000000..b163a2e598
--- /dev/null
+++ b/packages/core/admin/admin/src/content-manager/components/DynamicTable/BulkActionsBar/index.js
@@ -0,0 +1,85 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { Button } from '@strapi/design-system';
+import { useIntl } from 'react-intl';
+import { useTracking } from '@strapi/helper-plugin';
+import ConfirmDialogDeleteAll from '../ConfirmDialogDeleteAll';
+
+const BulkActionsBar = ({
+ showPublish,
+ showDelete,
+ onConfirmDeleteAll,
+ selectedEntries,
+ clearSelectedEntries,
+}) => {
+ const { formatMessage } = useIntl();
+ const { trackUsage } = useTracking();
+
+ const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false);
+ const [showConfirmDeleteAll, setShowConfirmDeleteAll] = useState(false);
+
+ const handleToggleShowDeleteAllModal = () => {
+ if (!showConfirmDeleteAll) {
+ trackUsage('willBulkDeleteEntries');
+ }
+
+ setShowConfirmDeleteAll((prev) => !prev);
+ };
+
+ const handleConfirmDeleteAll = async () => {
+ try {
+ setIsConfirmButtonLoading(true);
+ await onConfirmDeleteAll(selectedEntries);
+ handleToggleShowDeleteAllModal();
+ clearSelectedEntries();
+ setIsConfirmButtonLoading(false);
+ } catch (err) {
+ setIsConfirmButtonLoading(false);
+ handleToggleShowDeleteAllModal();
+ }
+ };
+
+ return (
+ <>
+ {showPublish && (
+ <>
+
+
+ >
+ )}
+ {showDelete && (
+ <>
+
+
+ >
+ )}
+ >
+ );
+};
+
+BulkActionsBar.defaultProps = {
+ showPublish: false,
+ showDelete: false,
+ onConfirmDeleteAll() {},
+};
+
+BulkActionsBar.propTypes = {
+ showPublish: PropTypes.bool,
+ showDelete: PropTypes.bool,
+ onConfirmDeleteAll: PropTypes.func,
+ selectedEntries: PropTypes.array.isRequired,
+ clearSelectedEntries: PropTypes.func.isRequired,
+};
+
+export default BulkActionsBar;
diff --git a/packages/core/admin/admin/src/content-manager/components/DynamicTable/BulkActionsBar/tests/index.test.js b/packages/core/admin/admin/src/content-manager/components/DynamicTable/BulkActionsBar/tests/index.test.js
new file mode 100644
index 0000000000..90202ed36b
--- /dev/null
+++ b/packages/core/admin/admin/src/content-manager/components/DynamicTable/BulkActionsBar/tests/index.test.js
@@ -0,0 +1,87 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { ThemeProvider, lightTheme } from '@strapi/design-system';
+import { IntlProvider } from 'react-intl';
+import BulkActionsBar from '../index';
+import { act } from 'react-dom/test-utils';
+
+jest.mock('@strapi/helper-plugin', () => ({
+ ...jest.requireActual('@strapi/helper-plugin'),
+ useTracking: () => ({
+ trackUsage: jest.fn(),
+ }),
+}));
+
+jest.mock('../../../../../shared/hooks', () => ({
+ ...jest.requireActual('../../../../../shared/hooks'),
+ useInjectionZone: () => [],
+}));
+
+describe('BulkActionsBar', () => {
+ const requiredProps = {
+ selectedEntries: [],
+ clearSelectedEntries: jest.fn(),
+ };
+
+ const TestComponent = (props) => (
+
+
+
+
+
+ );
+
+ const setup = (props) => render();
+
+ it('should render publish buttons if showPublish is true', () => {
+ setup({ showPublish: true });
+
+ expect(screen.getByRole('button', { name: /\bPublish\b/ })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /\bUnpublish\b/ })).toBeInTheDocument();
+ });
+
+ it('should not render publish buttons if showPublish is false', () => {
+ setup({ showPublish: false });
+
+ expect(screen.queryByRole('button', { name: /\bPublish\b/ })).not.toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: /\bUnpublish\b/ })).not.toBeInTheDocument();
+ });
+
+ it('should render delete button if showDelete is true', () => {
+ setup({ showDelete: true });
+
+ expect(screen.getByRole('button', { name: /\bDelete\b/ })).toBeInTheDocument();
+ });
+
+ it('should not render delete button if showDelete is false', () => {
+ setup({ showDelete: false });
+
+ expect(screen.queryByRole('button', { name: /\bDelete\b/ })).not.toBeInTheDocument();
+ });
+
+ it('should show delete modal if delete button is clicked', () => {
+ setup({ showDelete: true });
+
+ act(() => {
+ fireEvent.click(screen.getByRole('button', { name: /\bDelete\b/ }));
+ });
+
+ expect(screen.getByText('Confirmation')).toBeInTheDocument();
+ });
+
+ it('should call confirm delete all if confirmation button is clicked', async () => {
+ const mockConfirmDeleteAll = jest.fn();
+
+ setup({
+ showDelete: true,
+ onConfirmDeleteAll: mockConfirmDeleteAll,
+ });
+
+ await act(async () => {
+ await fireEvent.click(screen.getByRole('button', { name: /\bDelete\b/ }));
+ fireEvent.click(screen.getByRole('button', { name: /confirm/i }));
+ });
+
+ expect(mockConfirmDeleteAll).toHaveBeenCalledWith([]);
+ });
+});
diff --git a/packages/core/admin/admin/src/content-manager/components/DynamicTable/index.js b/packages/core/admin/admin/src/content-manager/components/DynamicTable/index.js
index 721dbd8fb5..15f439be18 100644
--- a/packages/core/admin/admin/src/content-manager/components/DynamicTable/index.js
+++ b/packages/core/admin/admin/src/content-manager/components/DynamicTable/index.js
@@ -9,13 +9,14 @@ import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
import { selectDisplayedHeaders } from '../../pages/ListView/selectors';
import { getTrad } from '../../utils';
import TableRows from './TableRows';
-import ConfirmDialogDeleteAll from './ConfirmDialogDeleteAll';
import ConfirmDialogDelete from './ConfirmDialogDelete';
import { PublicationState } from './CellContent/PublicationState/PublicationState';
+import BulkActionsBar from './BulkActionsBar';
const DynamicTable = ({
canCreate,
canDelete,
+ canPublish,
contentTypeName,
action,
isBulkable,
@@ -89,17 +90,25 @@ const DynamicTable = ({
return (
(
+
+ )}
>
{
- const [entriesToDelete, setEntriesToDelete] = useState([]);
+ const [selectedEntries, setSelectedEntries] = useState([]);
const [showConfirmDeleteAll, setShowConfirmDeleteAll] = useState(false);
const [showConfirmDelete, setShowConfirmDelete] = useState(false);
const [isConfirmButtonLoading, setIsConfirmButtonLoading] = useState(false);
@@ -44,7 +45,7 @@ const Table = ({
const ROW_COUNT = rows.length + 1;
const COL_COUNT = headers.length + (withBulkActions ? 1 : 0) + (withMainAction ? 1 : 0);
const hasFilters = query?.filters !== undefined;
- const areAllEntriesSelected = entriesToDelete.length === rows.length && rows.length > 0;
+ const areAllEntriesSelected = selectedEntries.length === rows.length && rows.length > 0;
const content = hasFilters
? {
@@ -57,9 +58,9 @@ const Table = ({
const handleConfirmDeleteAll = async () => {
try {
setIsConfirmButtonLoading(true);
- await onConfirmDeleteAll(entriesToDelete);
+ await onConfirmDeleteAll(selectedEntries);
handleToggleConfirmDeleteAll();
- setEntriesToDelete([]);
+ setSelectedEntries([]);
setIsConfirmButtonLoading(false);
} catch (err) {
setIsConfirmButtonLoading(false);
@@ -71,7 +72,7 @@ const Table = ({
try {
setIsConfirmButtonLoading(true);
// await onConfirmDeleteAll(entriesToDelete);
- await onConfirmDelete(entriesToDelete[0]);
+ await onConfirmDelete(selectedEntries[0]);
handleToggleConfirmDelete();
setIsConfirmButtonLoading(false);
} catch (err) {
@@ -82,9 +83,9 @@ const Table = ({
const handleSelectAll = () => {
if (!areAllEntriesSelected) {
- setEntriesToDelete(rows.map((row) => row.id));
+ setSelectedEntries(rows.map((row) => row.id));
} else {
- setEntriesToDelete([]);
+ setSelectedEntries([]);
}
};
@@ -98,19 +99,19 @@ const Table = ({
const handleToggleConfirmDelete = () => {
if (showConfirmDelete) {
- setEntriesToDelete([]);
+ setSelectedEntries([]);
}
setShowConfirmDelete((prev) => !prev);
};
const handleClickDelete = (id) => {
- setEntriesToDelete([id]);
+ setSelectedEntries([id]);
handleToggleConfirmDelete();
};
const handleSelectRow = ({ name, value }) => {
- setEntriesToDelete((prev) => {
+ setSelectedEntries((prev) => {
if (value) {
return prev.concat(name);
}
@@ -119,6 +120,10 @@ const Table = ({
});
};
+ const clearSelectedEntries = () => {
+ setSelectedEntries([]);
+ };
+
const ConfirmDeleteAllComponent = components?.ConfirmDialogDeleteAll
? components.ConfirmDialogDeleteAll
: ConfirmDialog;
@@ -129,7 +134,7 @@ const Table = ({
return (
<>
- {entriesToDelete.length > 0 && (
+ {selectedEntries.length > 0 && (
@@ -140,17 +145,21 @@ const Table = ({
id: 'content-manager.components.TableDelete.label',
defaultMessage: '{number, plural, one {# entry} other {# entries}} selected',
},
- { number: entriesToDelete.length }
+ { number: selectedEntries.length }
)}
- }
- size="L"
- variant="danger-light"
- >
- {formatMessage({ id: 'global.delete', defaultMessage: 'Delete' })}
-
+ {renderBulkActionsBar ? (
+ renderBulkActionsBar({ selectedEntries, clearSelectedEntries })
+ ) : (
+ }
+ size="L"
+ variant="danger-light"
+ >
+ {formatMessage({ id: 'global.delete', defaultMessage: 'Delete' })}
+
+ )}
@@ -159,7 +168,7 @@ const Table = ({
cloneElement(child, {
- entriesToDelete,
+ entriesToDelete: selectedEntries,
onClickDelete: handleClickDelete,
onSelectRow: handleSelectRow,
headers,
@@ -219,6 +228,7 @@ Table.defaultProps = {
rows: [],
withBulkActions: false,
withMainAction: false,
+ renderBulkActionsBar: undefined,
};
Table.propTypes = {
@@ -248,6 +258,7 @@ Table.propTypes = {
rows: PropTypes.array,
withBulkActions: PropTypes.bool,
withMainAction: PropTypes.bool,
+ renderBulkActionsBar: PropTypes.func,
};
export default Table;