diff --git a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/Filters.js b/packages/core/admin/admin/src/content-manager/components/AttributeFilter/Filters.js
index 6b00c4c71e..ee2755fe0c 100644
--- a/packages/core/admin/admin/src/content-manager/components/AttributeFilter/Filters.js
+++ b/packages/core/admin/admin/src/content-manager/components/AttributeFilter/Filters.js
@@ -1,6 +1,6 @@
import React, { useRef, useState } from 'react';
-import { Button } from '@strapi/design-system';
+import { Box, Button } from '@strapi/design-system';
import { FilterListURLQuery, FilterPopoverURLQuery, useTracking } from '@strapi/helper-plugin';
import { Filter } from '@strapi/icons';
import PropTypes from 'prop-types';
@@ -21,23 +21,25 @@ const Filters = ({ displayedFilters }) => {
return (
<>
- }
- onClick={handleToggle}
- size="S"
- >
- {formatMessage({ id: 'app.utils.filters', defaultMessage: 'Filters' })}
-
- {isVisible && (
-
- )}
+
+ }
+ onClick={handleToggle}
+ size="S"
+ >
+ {formatMessage({ id: 'app.utils.filters', defaultMessage: 'Filters' })}
+
+ {isVisible && (
+
+ )}
+
>
);
diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/components/BulkActionButtons/SelectedEntriesModal/tests/index.test.js b/packages/core/admin/admin/src/content-manager/pages/ListView/components/BulkActionButtons/SelectedEntriesModal/tests/index.test.js
index 84bbf87d14..fdf186fa87 100644
--- a/packages/core/admin/admin/src/content-manager/pages/ListView/components/BulkActionButtons/SelectedEntriesModal/tests/index.test.js
+++ b/packages/core/admin/admin/src/content-manager/pages/ListView/components/BulkActionButtons/SelectedEntriesModal/tests/index.test.js
@@ -233,9 +233,8 @@ describe('Bulk publish selected entries modal', () => {
await user.click(publishDialogButton);
- expect(publishDialog).not.toBeInTheDocument();
-
- await waitFor(async () => {
+ await waitFor(() => {
+ expect(publishDialog).not.toBeInTheDocument();
expect(screen.queryByRole('gridcell', { name: 'Entry 1' })).not.toBeInTheDocument();
expect(screen.queryByRole('gridcell', { name: 'Entry 2' })).not.toBeInTheDocument();
expect(screen.queryByRole('gridcell', { name: 'Entry 3' })).not.toBeInTheDocument();
diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js b/packages/core/admin/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js
index f68acfa6e9..70d54cb3dd 100644
--- a/packages/core/admin/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js
+++ b/packages/core/admin/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js
@@ -1,93 +1,81 @@
import React from 'react';
-import { Flex, BaseCheckbox, TextButton, Typography } from '@strapi/design-system';
-import { useCollator, useTracking } from '@strapi/helper-plugin';
+import { Select, Option, Box } from '@strapi/design-system';
+import { useTracking } from '@strapi/helper-plugin';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { useDispatch, useSelector } from 'react-redux';
-import styled from 'styled-components';
-import { checkIfAttributeIsDisplayable } from '../../../../utils';
-import { onChangeListHeaders, onResetListHeaders } from '../../actions';
+import { getTrad, checkIfAttributeIsDisplayable } from '../../../../utils';
+import { onChangeListHeaders } from '../../actions';
import { selectDisplayedHeaders } from '../../selectors';
-const ChackboxWrapper = styled(Flex)`
- :hover {
- background-color: ${(props) => props.theme.colors.primary100};
- }
-`;
-
export const FieldPicker = ({ layout }) => {
const dispatch = useDispatch();
const displayedHeaders = useSelector(selectDisplayedHeaders);
const { trackUsage } = useTracking();
- const { formatMessage, locale } = useIntl();
- const formatter = useCollator(locale, {
- sensitivity: 'base',
+ const { formatMessage } = useIntl();
+
+ const allAllowedHeaders = getAllAllowedHeaders(layout.contentType.attributes).map((attrName) => {
+ const metadatas = layout.contentType.metadatas[attrName].list;
+
+ return {
+ name: attrName,
+ intlLabel: { id: metadatas.label, defaultMessage: metadatas.label },
+ };
});
- const columns = Object.keys(layout.contentType.attributes)
- .filter((name) => checkIfAttributeIsDisplayable(layout.contentType.attributes[name]))
- .map((name) => ({
- name,
- label: layout.contentType.metadatas[name].list.label,
- }))
- .sort((a, b) => formatter.compare(a.label, b.label));
+ const values = displayedHeaders.map(({ name }) => name);
- const displayedHeaderKeys = displayedHeaders.map(({ name }) => name);
-
- const handleChange = (name) => {
+ const handleChange = (updatedValues) => {
trackUsage('didChangeDisplayedFields');
- dispatch(onChangeListHeaders({ name, value: displayedHeaderKeys.includes(name) }));
- };
- const handleReset = () => {
- dispatch(onResetListHeaders());
+ // removing a header
+ if (updatedValues.length < values.length) {
+ const removedHeader = values.filter((value) => {
+ return updatedValues.indexOf(value) === -1;
+ });
+
+ dispatch(onChangeListHeaders({ name: removedHeader[0], value: true }));
+ } else {
+ const addedHeader = updatedValues.filter((value) => {
+ return values.indexOf(value) === -1;
+ });
+
+ dispatch(onChangeListHeaders({ name: addedHeader[0], value: false }));
+ }
};
return (
-
-
-
- {formatMessage({
- id: 'containers.ListPage.displayedFields',
- defaultMessage: 'Displayed fields',
- })}
-
-
-
- {formatMessage({
- id: 'app.components.Button.reset',
- defaultMessage: 'Reset',
- })}
-
-
-
-
- {columns.map((header) => {
- const isActive = displayedHeaderKeys.includes(header.name);
-
+
+
-
+
+
);
};
@@ -104,3 +92,17 @@ FieldPicker.propTypes = {
}).isRequired,
}).isRequired,
};
+
+const getAllAllowedHeaders = (attributes) => {
+ const allowedAttributes = Object.keys(attributes).reduce((acc, current) => {
+ const attribute = attributes[current];
+
+ if (checkIfAttributeIsDisplayable(attribute)) {
+ acc.push(current);
+ }
+
+ return acc;
+ }, []);
+
+ return allowedAttributes.sort();
+};
diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/components/FieldPicker/tests/index.test.js b/packages/core/admin/admin/src/content-manager/pages/ListView/components/FieldPicker/tests/index.test.js
deleted file mode 100644
index 6671dc408d..0000000000
--- a/packages/core/admin/admin/src/content-manager/pages/ListView/components/FieldPicker/tests/index.test.js
+++ /dev/null
@@ -1,195 +0,0 @@
-import React from 'react';
-
-import { lightTheme, ThemeProvider } from '@strapi/design-system';
-import { render as renderRTL, fireEvent } from '@testing-library/react';
-import { IntlProvider } from 'react-intl';
-import { Provider } from 'react-redux';
-import { combineReducers, createStore } from 'redux';
-
-import reducers from '../../../../../../reducers';
-import { FieldPicker } from '../index';
-
-const layout = {
- contentType: {
- attributes: {
- id: { type: 'integer' },
- name: { type: 'string' },
- createdAt: { type: 'datetime' },
- updatedAt: { type: 'datetime' },
- },
- metadatas: {
- id: {
- list: { label: 'id', searchable: true, sortable: true },
- },
- name: {
- list: { label: 'name', searchable: true, sortable: true },
- },
- createdAt: {
- list: { label: 'createdAt', searchable: true, sortable: true },
- },
- updatedAt: {
- list: { label: 'updatedAt', searchable: true, sortable: true },
- },
- },
- layouts: {
- list: [],
- },
- options: {},
- settings: {},
- },
-};
-
-const render = () => ({
- ...renderRTL(, {
- wrapper({ children }) {
- const rootReducer = combineReducers(reducers);
-
- const store = createStore(rootReducer, {
- 'content-manager_listView': {
- contentType: {
- attributes: {
- id: { type: 'integer' },
- name: { type: 'string' },
- createdAt: { type: 'datetime' },
- updatedAt: { type: 'datetime' },
- },
- metadatas: {
- id: {
- edit: {},
- list: { label: 'id', searchable: true, sortable: true },
- },
- name: {
- edit: {
- label: 'name',
- description: '',
- placeholder: '',
- visible: true,
- editable: true,
- },
- list: { label: 'name', searchable: true, sortable: true },
- },
- createdAt: {
- edit: {
- label: 'createdAt',
- description: '',
- placeholder: '',
- visible: false,
- editable: true,
- },
- list: { label: 'createdAt', searchable: true, sortable: true },
- },
- updatedAt: {
- edit: {
- label: 'updatedAt',
- description: '',
- placeholder: '',
- visible: false,
- editable: true,
- },
- list: { label: 'updatedAt', searchable: true, sortable: true },
- },
- },
- },
- displayedHeaders: [
- {
- key: '__id_key__',
- name: 'id',
- fieldSchema: { type: 'integer' },
- metadatas: { label: 'id', searchable: true, sortable: true },
- },
- ],
- initialDisplayedHeaders: [
- {
- key: '__id_key__',
- name: 'id',
- fieldSchema: { type: 'integer' },
- metadatas: { label: 'id', searchable: true, sortable: true },
- },
- ],
- },
- });
-
- return (
-
-
- {children}
-
-
- );
- },
- }),
-});
-
-describe('FieldPicker', () => {
- it('should contains all the headers', () => {
- const { getAllByRole, getByRole } = render();
-
- const checkboxes = getAllByRole('checkbox');
- const { attributes } = layout.contentType;
- const attributesKeys = Object.keys(attributes);
-
- expect(checkboxes.length).toBe(attributesKeys.length);
-
- // eslint-disable-next-line no-restricted-syntax
- for (let attributeKey of attributesKeys) {
- // for each attribute make sure you have a checkbox
- const checkbox = getByRole('checkbox', {
- name: attributeKey,
- });
-
- expect(checkbox).toBeInTheDocument();
- }
- });
-
- it('should contains the initially selected headers', () => {
- const { getByRole } = render();
-
- const checkboxSelected = getByRole('checkbox', {
- name: 'id',
- });
- const checkboxNotSelected = getByRole('checkbox', {
- name: 'name',
- });
-
- expect(checkboxSelected).toHaveAttribute('checked');
- expect(checkboxNotSelected).not.toHaveAttribute('checked');
- });
-
- it('should select an header', async () => {
- const { getByRole } = render();
-
- // User can toggle selected headers
- const checkboxIdHeader = getByRole('checkbox', { name: 'id' });
- const checkboxNameHeader = getByRole('checkbox', { name: 'name' });
- expect(checkboxIdHeader).toBeChecked();
-
- // User can unselect headers
- fireEvent.click(checkboxIdHeader);
-
- expect(checkboxIdHeader).not.toBeChecked();
-
- // User can select headers
- expect(checkboxNameHeader).not.toBeChecked();
-
- fireEvent.click(checkboxNameHeader);
-
- expect(checkboxNameHeader).toBeChecked();
- });
-
- it('should show inside the Popover the reset button and when clicked select the initial headers selected', async () => {
- const { getByRole } = render();
-
- // select a new header
- const checkboxNameHeader = getByRole('checkbox', { name: 'name' });
- fireEvent.click(checkboxNameHeader);
-
- expect(checkboxNameHeader).toBeChecked();
-
- const resetBtn = getByRole('button', {
- name: 'Reset',
- });
- fireEvent.click(resetBtn);
-
- expect(checkboxNameHeader).not.toBeChecked();
- });
-});
diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js b/packages/core/admin/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js
deleted file mode 100644
index 00b9ea12c5..0000000000
--- a/packages/core/admin/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import React from 'react';
-
-import { Flex, IconButton, Popover } from '@strapi/design-system';
-import { CheckPermissions, LinkButton } from '@strapi/helper-plugin';
-import { Cog, Layer } from '@strapi/icons';
-import PropTypes from 'prop-types';
-import { useIntl } from 'react-intl';
-import { useSelector } from 'react-redux';
-
-import { selectAdminPermissions } from '../../../../../pages/App/selectors';
-import { FieldPicker } from '../FieldPicker';
-
-export const ViewSettingsMenu = ({ slug, layout }) => {
- const [isVisible, setIsVisible] = React.useState(false);
- const cogButtonRef = React.useRef();
- const permissions = useSelector(selectAdminPermissions);
- const { formatMessage } = useIntl();
-
- const handleToggle = () => {
- setIsVisible((prev) => !prev);
- };
-
- return (
- <>
- }
- label={formatMessage({
- id: 'components.ViewSettings.tooltip',
- defaultMessage: 'View Settings',
- })}
- ref={cogButtonRef}
- onClick={handleToggle}
- />
- {isVisible && (
-
-
-
- }
- to={`${slug}/configurations/list`}
- variant="secondary"
- >
- {formatMessage({
- id: 'app.links.configure-view',
- defaultMessage: 'Configure the view',
- })}
-
-
-
-
-
-
- )}
- >
- );
-};
-
-ViewSettingsMenu.propTypes = {
- slug: PropTypes.string.isRequired,
- layout: PropTypes.shape({
- contentType: PropTypes.shape({
- attributes: PropTypes.object.isRequired,
- metadatas: PropTypes.object.isRequired,
- layouts: PropTypes.shape({
- list: PropTypes.array.isRequired,
- }).isRequired,
- options: PropTypes.object.isRequired,
- settings: PropTypes.object.isRequired,
- }).isRequired,
- }).isRequired,
-};
diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/tests/index.test.js b/packages/core/admin/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/tests/index.test.js
deleted file mode 100644
index e83cba708e..0000000000
--- a/packages/core/admin/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/tests/index.test.js
+++ /dev/null
@@ -1,156 +0,0 @@
-import React from 'react';
-
-import { lightTheme, ThemeProvider } from '@strapi/design-system';
-import { fireEvent, render as renderRTL, waitFor } from '@testing-library/react';
-import { createMemoryHistory } from 'history';
-import { IntlProvider } from 'react-intl';
-import { Provider } from 'react-redux';
-import { Router } from 'react-router-dom';
-import { combineReducers, createStore } from 'redux';
-
-import reducers from '../../../../../../reducers';
-import { ViewSettingsMenu } from '../index';
-
-const layout = {
- contentType: {
- attributes: {
- id: { type: 'integer' },
- name: { type: 'string' },
- createdAt: { type: 'datetime' },
- updatedAt: { type: 'datetime' },
- },
- metadatas: {
- id: {
- list: { label: 'id', searchable: true, sortable: true },
- },
- name: {
- list: { label: 'name', searchable: true, sortable: true },
- },
- createdAt: {
- list: { label: 'createdAt', searchable: true, sortable: true },
- },
- updatedAt: {
- list: { label: 'updatedAt', searchable: true, sortable: true },
- },
- },
- layouts: {
- list: [],
- },
- options: {},
- settings: {},
- },
-};
-
-jest.mock('@strapi/helper-plugin', () => ({
- ...jest.requireActual('@strapi/helper-plugin'),
- // eslint-disable-next-line react/prop-types
- CheckPermissions: ({ children }) =>
{children}
,
-}));
-
-const history = createMemoryHistory();
-
-const render = () => ({
- ...renderRTL(, {
- wrapper({ children }) {
- const rootReducer = combineReducers(reducers);
-
- const store = createStore(rootReducer, {
- 'content-manager_listView': {
- displayedHeaders: [],
- },
- admin_app: {
- permissions: {
- contentManager: {},
- },
- },
- });
-
- return (
-
-
-
- {children}
-
-
-
- );
- },
- }),
-});
-
-describe('Content Manager | List view | ViewSettingsMenu', () => {
- it('should show the Cog Button', () => {
- const { getByRole } = render();
-
- const cogBtn = getByRole('button', {
- name: 'View Settings',
- });
-
- expect(cogBtn).toBeInTheDocument();
- });
-
- it('should open the Popover when you click on the Cog Button', () => {
- const { getByRole } = render();
-
- const cogBtn = getByRole('button', {
- name: 'View Settings',
- });
-
- fireEvent.click(cogBtn);
-
- const configureViewLink = getByRole('link', {
- name: 'Configure the view',
- });
-
- expect(configureViewLink).toBeInTheDocument();
- });
-
- it('should show inside the Popover the Configure the view link button', async () => {
- const { getByRole } = render();
-
- const cogBtn = getByRole('button', {
- name: 'View Settings',
- });
-
- fireEvent.click(cogBtn);
-
- const configureViewLink = getByRole('link', {
- name: 'Configure the view',
- });
-
- expect(configureViewLink).toBeInTheDocument();
-
- fireEvent.click(configureViewLink);
- await waitFor(() => {
- expect(history.location.pathname).toBe('/api::temp.temp/configurations/list');
- });
- });
-
- it('should show inside the Popover the title Dysplayed fields title', async () => {
- const { getByText, getByRole } = render();
-
- const cogBtn = getByRole('button', {
- name: 'View Settings',
- });
-
- fireEvent.click(cogBtn);
-
- expect(getByText('Displayed fields')).toBeInTheDocument();
- });
-
- it('should show inside the Popover the reset button', () => {
- const { getByRole } = render();
-
- const cogBtn = getByRole('button', {
- name: 'View Settings',
- });
-
- fireEvent.click(cogBtn);
-
- const resetBtn = getByRole('button', {
- name: 'Reset',
- });
-
- expect(resetBtn).toBeInTheDocument();
- });
-});
diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/index.js b/packages/core/admin/admin/src/content-manager/pages/ListView/index.js
index ced9878e09..70d3ce2be7 100644
--- a/packages/core/admin/admin/src/content-manager/pages/ListView/index.js
+++ b/packages/core/admin/admin/src/content-manager/pages/ListView/index.js
@@ -1,7 +1,9 @@
import * as React from 'react';
import {
+ IconButton,
Main,
+ Box,
ActionLayout,
Button,
ContentLayout,
@@ -16,6 +18,7 @@ import {
} from '@strapi/design-system';
import {
NoPermissions,
+ CheckPermissions,
SearchURLQuery,
useFetchClient,
useFocusWhenNavigate,
@@ -30,7 +33,7 @@ import {
PaginationURLQuery,
PageSizeURLQuery,
} from '@strapi/helper-plugin';
-import { ArrowLeft, Plus } from '@strapi/icons';
+import { ArrowLeft, Cog, Plus } from '@strapi/icons';
import axios, { AxiosError } from 'axios';
import isEqual from 'lodash/isEqual';
import PropTypes from 'prop-types';
@@ -40,9 +43,11 @@ import { useMutation } from 'react-query';
import { connect, useSelector } from 'react-redux';
import { useHistory, useLocation, Link as ReactRouterLink } from 'react-router-dom';
import { bindActionCreators, compose } from 'redux';
+import styled from 'styled-components';
import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
import { useEnterprise } from '../../../hooks/useEnterprise';
+import { selectAdminPermissions } from '../../../pages/App/selectors';
import { InjectionZone } from '../../../shared/components';
import AttributeFilter from '../../components/AttributeFilter';
import { getTrad } from '../../utils';
@@ -51,10 +56,18 @@ import { getData, getDataSucceeded, onChangeListHeaders, onResetListHeaders } fr
import { Body } from './components/Body';
import BulkActionButtons from './components/BulkActionButtons';
import CellContent from './components/CellContent';
-import { ViewSettingsMenu } from './components/ViewSettingsMenu';
+import { FieldPicker } from './components/FieldPicker';
import makeSelectListView, { selectDisplayedHeaders } from './selectors';
import { buildValidGetParams } from './utils';
+const ConfigureLayoutBox = styled(Box)`
+ svg {
+ path {
+ fill: ${({ theme }) => theme.colors.neutral900};
+ }
+ }
+`;
+
const REVIEW_WORKFLOW_COLUMNS_CE = null;
const REVIEW_WORKFLOW_COLUMNS_CELL_CE = () => null;
@@ -87,6 +100,7 @@ function ListView({
const fetchPermissionsRef = React.useRef(refetchPermissions);
const { notifyStatus } = useNotifyAT();
const { formatAPIError } = useAPIErrorHandler(getTrad);
+ const permissions = useSelector(selectAdminPermissions);
useFocusWhenNavigate();
@@ -468,7 +482,25 @@ function ListView({
endActions={
<>
-
+
+
+
+ {
+ trackUsage('willEditListLayout');
+ }}
+ forwardedAs={ReactRouterLink}
+ to={{ pathname: `${slug}/configurations/list`, search: pluginsQueryParams }}
+ icon={}
+ label={formatMessage({
+ id: 'app.links.configure-view',
+ defaultMessage: 'Configure the view',
+ })}
+ />
+
+
>
}
startActions={
diff --git a/packages/core/admin/admin/src/translations/en.json b/packages/core/admin/admin/src/translations/en.json
index 6b1b8b91a5..3edb2e26db 100644
--- a/packages/core/admin/admin/src/translations/en.json
+++ b/packages/core/admin/admin/src/translations/en.json
@@ -612,7 +612,7 @@
"components.PageFooter.select": "Entries per page",
"components.ProductionBlocker.description": "For safety purposes we have to disable this plugin in other environments.",
"components.ProductionBlocker.header": "This plugin is only available in development.",
- "components.ViewSettings.tooltip": "View settings",
+ "components.Search.placeholder": "Search...",
"components.TableHeader.sort": "Sort on {label}",
"components.Wysiwyg.ToggleMode.markdown-mode": "Markdown mode",
"components.Wysiwyg.ToggleMode.preview-mode": "Preview mode",