mirror of
https://github.com/strapi/strapi.git
synced 2025-08-11 18:27:22 +00:00
Revert "List view: new cog button icon with the view settings in the list view page (#17551)" (#17601)
This reverts commit e786f44833aad311e0ef669a7e98aa21ced2bd0a.
This commit is contained in:
parent
a92367510f
commit
3e0f6d3514
@ -1,6 +1,6 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
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 { FilterListURLQuery, FilterPopoverURLQuery, useTracking } from '@strapi/helper-plugin';
|
||||||
import { Filter } from '@strapi/icons';
|
import { Filter } from '@strapi/icons';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
@ -21,23 +21,25 @@ const Filters = ({ displayedFilters }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Box paddingTop={1} paddingBottom={1}>
|
||||||
variant="tertiary"
|
<Button
|
||||||
ref={buttonRef}
|
variant="tertiary"
|
||||||
startIcon={<Filter />}
|
ref={buttonRef}
|
||||||
onClick={handleToggle}
|
startIcon={<Filter />}
|
||||||
size="S"
|
onClick={handleToggle}
|
||||||
>
|
size="S"
|
||||||
{formatMessage({ id: 'app.utils.filters', defaultMessage: 'Filters' })}
|
>
|
||||||
</Button>
|
{formatMessage({ id: 'app.utils.filters', defaultMessage: 'Filters' })}
|
||||||
{isVisible && (
|
</Button>
|
||||||
<FilterPopoverURLQuery
|
{isVisible && (
|
||||||
displayedFilters={displayedFilters}
|
<FilterPopoverURLQuery
|
||||||
isVisible={isVisible}
|
displayedFilters={displayedFilters}
|
||||||
onToggle={handleToggle}
|
isVisible={isVisible}
|
||||||
source={buttonRef}
|
onToggle={handleToggle}
|
||||||
/>
|
source={buttonRef}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
<FilterListURLQuery filtersSchema={displayedFilters} />
|
<FilterListURLQuery filtersSchema={displayedFilters} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -233,9 +233,8 @@ describe('Bulk publish selected entries modal', () => {
|
|||||||
|
|
||||||
await user.click(publishDialogButton);
|
await user.click(publishDialogButton);
|
||||||
|
|
||||||
expect(publishDialog).not.toBeInTheDocument();
|
await waitFor(() => {
|
||||||
|
expect(publishDialog).not.toBeInTheDocument();
|
||||||
await waitFor(async () => {
|
|
||||||
expect(screen.queryByRole('gridcell', { name: 'Entry 1' })).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 2' })).not.toBeInTheDocument();
|
||||||
expect(screen.queryByRole('gridcell', { name: 'Entry 3' })).not.toBeInTheDocument();
|
expect(screen.queryByRole('gridcell', { name: 'Entry 3' })).not.toBeInTheDocument();
|
||||||
|
@ -1,93 +1,81 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Flex, BaseCheckbox, TextButton, Typography } from '@strapi/design-system';
|
import { Select, Option, Box } from '@strapi/design-system';
|
||||||
import { useCollator, useTracking } from '@strapi/helper-plugin';
|
import { useTracking } from '@strapi/helper-plugin';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { checkIfAttributeIsDisplayable } from '../../../../utils';
|
import { getTrad, checkIfAttributeIsDisplayable } from '../../../../utils';
|
||||||
import { onChangeListHeaders, onResetListHeaders } from '../../actions';
|
import { onChangeListHeaders } from '../../actions';
|
||||||
import { selectDisplayedHeaders } from '../../selectors';
|
import { selectDisplayedHeaders } from '../../selectors';
|
||||||
|
|
||||||
const ChackboxWrapper = styled(Flex)`
|
|
||||||
:hover {
|
|
||||||
background-color: ${(props) => props.theme.colors.primary100};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const FieldPicker = ({ layout }) => {
|
export const FieldPicker = ({ layout }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const displayedHeaders = useSelector(selectDisplayedHeaders);
|
const displayedHeaders = useSelector(selectDisplayedHeaders);
|
||||||
const { trackUsage } = useTracking();
|
const { trackUsage } = useTracking();
|
||||||
const { formatMessage, locale } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const formatter = useCollator(locale, {
|
|
||||||
sensitivity: 'base',
|
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)
|
const values = displayedHeaders.map(({ name }) => name);
|
||||||
.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 displayedHeaderKeys = displayedHeaders.map(({ name }) => name);
|
const handleChange = (updatedValues) => {
|
||||||
|
|
||||||
const handleChange = (name) => {
|
|
||||||
trackUsage('didChangeDisplayedFields');
|
trackUsage('didChangeDisplayedFields');
|
||||||
dispatch(onChangeListHeaders({ name, value: displayedHeaderKeys.includes(name) }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReset = () => {
|
// removing a header
|
||||||
dispatch(onResetListHeaders());
|
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 (
|
return (
|
||||||
<Flex as="fieldset" direction="column" alignItems="stretch" gap={3}>
|
<Box paddingTop={1} paddingBottom={1}>
|
||||||
<Flex justifyContent="space-between">
|
<Select
|
||||||
<Typography as="legend" variant="pi" fontWeight="bold">
|
aria-label="change displayed fields"
|
||||||
{formatMessage({
|
value={values}
|
||||||
id: 'containers.ListPage.displayedFields',
|
onChange={handleChange}
|
||||||
defaultMessage: 'Displayed fields',
|
customizeContent={(values) =>
|
||||||
})}
|
formatMessage(
|
||||||
</Typography>
|
{
|
||||||
|
id: getTrad('select.currently.selected'),
|
||||||
<TextButton onClick={handleReset}>
|
defaultMessage: '{count} currently selected',
|
||||||
{formatMessage({
|
},
|
||||||
id: 'app.components.Button.reset',
|
{ count: values.length }
|
||||||
defaultMessage: 'Reset',
|
)
|
||||||
})}
|
}
|
||||||
</TextButton>
|
multi
|
||||||
</Flex>
|
size="S"
|
||||||
|
>
|
||||||
<Flex direction="column" alignItems="stretch">
|
{allAllowedHeaders.map((header) => {
|
||||||
{columns.map((header) => {
|
|
||||||
const isActive = displayedHeaderKeys.includes(header.name);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChackboxWrapper
|
<Option key={header.name} value={header.name}>
|
||||||
wrap="wrap"
|
{formatMessage({
|
||||||
gap={2}
|
id: header.intlLabel.id || header.name,
|
||||||
as="label"
|
defaultMessage: header.intlLabel.defaultMessage || header.name,
|
||||||
background={isActive ? 'primary100' : 'transparent'}
|
})}
|
||||||
hasRadius
|
</Option>
|
||||||
padding={2}
|
|
||||||
key={header.name}
|
|
||||||
>
|
|
||||||
<BaseCheckbox
|
|
||||||
onChange={() => handleChange(header.name)}
|
|
||||||
value={isActive}
|
|
||||||
name={header.name}
|
|
||||||
/>
|
|
||||||
<Typography fontSize={1}>{header.label}</Typography>
|
|
||||||
</ChackboxWrapper>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Flex>
|
</Select>
|
||||||
</Flex>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -104,3 +92,17 @@ FieldPicker.propTypes = {
|
|||||||
}).isRequired,
|
}).isRequired,
|
||||||
}).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();
|
||||||
|
};
|
||||||
|
@ -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(<FieldPicker layout={layout} />, {
|
|
||||||
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 (
|
|
||||||
<IntlProvider messages={{}} textComponent="span" locale="en">
|
|
||||||
<ThemeProvider theme={lightTheme}>
|
|
||||||
<Provider store={store}>{children}</Provider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</IntlProvider>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<IconButton
|
|
||||||
icon={<Cog />}
|
|
||||||
label={formatMessage({
|
|
||||||
id: 'components.ViewSettings.tooltip',
|
|
||||||
defaultMessage: 'View Settings',
|
|
||||||
})}
|
|
||||||
ref={cogButtonRef}
|
|
||||||
onClick={handleToggle}
|
|
||||||
/>
|
|
||||||
{isVisible && (
|
|
||||||
<Popover placement="bottom-end" source={cogButtonRef} onDismiss={handleToggle} padding={2}>
|
|
||||||
<Flex alignItems="stretch" direction="column" gap={3}>
|
|
||||||
<CheckPermissions
|
|
||||||
permissions={permissions.contentManager.collectionTypesConfigurations}
|
|
||||||
>
|
|
||||||
<LinkButton
|
|
||||||
size="S"
|
|
||||||
startIcon={<Layer />}
|
|
||||||
to={`${slug}/configurations/list`}
|
|
||||||
variant="secondary"
|
|
||||||
>
|
|
||||||
{formatMessage({
|
|
||||||
id: 'app.links.configure-view',
|
|
||||||
defaultMessage: 'Configure the view',
|
|
||||||
})}
|
|
||||||
</LinkButton>
|
|
||||||
</CheckPermissions>
|
|
||||||
|
|
||||||
<FieldPicker layout={layout} />
|
|
||||||
</Flex>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
@ -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 }) => <div>{children}</div>,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const history = createMemoryHistory();
|
|
||||||
|
|
||||||
const render = () => ({
|
|
||||||
...renderRTL(<ViewSettingsMenu layout={layout} slug="api::temp.temp" />, {
|
|
||||||
wrapper({ children }) {
|
|
||||||
const rootReducer = combineReducers(reducers);
|
|
||||||
|
|
||||||
const store = createStore(rootReducer, {
|
|
||||||
'content-manager_listView': {
|
|
||||||
displayedHeaders: [],
|
|
||||||
},
|
|
||||||
admin_app: {
|
|
||||||
permissions: {
|
|
||||||
contentManager: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Router history={history}>
|
|
||||||
<IntlProvider messages={{}} textComponent="span" locale="en">
|
|
||||||
<ThemeProvider theme={lightTheme}>
|
|
||||||
<Provider store={store}>{children}</Provider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</IntlProvider>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,7 +1,9 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
IconButton,
|
||||||
Main,
|
Main,
|
||||||
|
Box,
|
||||||
ActionLayout,
|
ActionLayout,
|
||||||
Button,
|
Button,
|
||||||
ContentLayout,
|
ContentLayout,
|
||||||
@ -16,6 +18,7 @@ import {
|
|||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
import {
|
import {
|
||||||
NoPermissions,
|
NoPermissions,
|
||||||
|
CheckPermissions,
|
||||||
SearchURLQuery,
|
SearchURLQuery,
|
||||||
useFetchClient,
|
useFetchClient,
|
||||||
useFocusWhenNavigate,
|
useFocusWhenNavigate,
|
||||||
@ -30,7 +33,7 @@ import {
|
|||||||
PaginationURLQuery,
|
PaginationURLQuery,
|
||||||
PageSizeURLQuery,
|
PageSizeURLQuery,
|
||||||
} from '@strapi/helper-plugin';
|
} from '@strapi/helper-plugin';
|
||||||
import { ArrowLeft, Plus } from '@strapi/icons';
|
import { ArrowLeft, Cog, Plus } from '@strapi/icons';
|
||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
@ -40,9 +43,11 @@ import { useMutation } from 'react-query';
|
|||||||
import { connect, useSelector } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
import { useHistory, useLocation, Link as ReactRouterLink } from 'react-router-dom';
|
import { useHistory, useLocation, Link as ReactRouterLink } from 'react-router-dom';
|
||||||
import { bindActionCreators, compose } from 'redux';
|
import { bindActionCreators, compose } from 'redux';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
|
import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
|
||||||
import { useEnterprise } from '../../../hooks/useEnterprise';
|
import { useEnterprise } from '../../../hooks/useEnterprise';
|
||||||
|
import { selectAdminPermissions } from '../../../pages/App/selectors';
|
||||||
import { InjectionZone } from '../../../shared/components';
|
import { InjectionZone } from '../../../shared/components';
|
||||||
import AttributeFilter from '../../components/AttributeFilter';
|
import AttributeFilter from '../../components/AttributeFilter';
|
||||||
import { getTrad } from '../../utils';
|
import { getTrad } from '../../utils';
|
||||||
@ -51,10 +56,18 @@ import { getData, getDataSucceeded, onChangeListHeaders, onResetListHeaders } fr
|
|||||||
import { Body } from './components/Body';
|
import { Body } from './components/Body';
|
||||||
import BulkActionButtons from './components/BulkActionButtons';
|
import BulkActionButtons from './components/BulkActionButtons';
|
||||||
import CellContent from './components/CellContent';
|
import CellContent from './components/CellContent';
|
||||||
import { ViewSettingsMenu } from './components/ViewSettingsMenu';
|
import { FieldPicker } from './components/FieldPicker';
|
||||||
import makeSelectListView, { selectDisplayedHeaders } from './selectors';
|
import makeSelectListView, { selectDisplayedHeaders } from './selectors';
|
||||||
import { buildValidGetParams } from './utils';
|
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_CE = null;
|
||||||
const REVIEW_WORKFLOW_COLUMNS_CELL_CE = () => null;
|
const REVIEW_WORKFLOW_COLUMNS_CELL_CE = () => null;
|
||||||
|
|
||||||
@ -87,6 +100,7 @@ function ListView({
|
|||||||
const fetchPermissionsRef = React.useRef(refetchPermissions);
|
const fetchPermissionsRef = React.useRef(refetchPermissions);
|
||||||
const { notifyStatus } = useNotifyAT();
|
const { notifyStatus } = useNotifyAT();
|
||||||
const { formatAPIError } = useAPIErrorHandler(getTrad);
|
const { formatAPIError } = useAPIErrorHandler(getTrad);
|
||||||
|
const permissions = useSelector(selectAdminPermissions);
|
||||||
|
|
||||||
useFocusWhenNavigate();
|
useFocusWhenNavigate();
|
||||||
|
|
||||||
@ -468,7 +482,25 @@ function ListView({
|
|||||||
endActions={
|
endActions={
|
||||||
<>
|
<>
|
||||||
<InjectionZone area="contentManager.listView.actions" />
|
<InjectionZone area="contentManager.listView.actions" />
|
||||||
<ViewSettingsMenu slug={slug} layout={layout} />
|
<FieldPicker layout={layout} />
|
||||||
|
<CheckPermissions
|
||||||
|
permissions={permissions.contentManager.collectionTypesConfigurations}
|
||||||
|
>
|
||||||
|
<ConfigureLayoutBox paddingTop={1} paddingBottom={1}>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
trackUsage('willEditListLayout');
|
||||||
|
}}
|
||||||
|
forwardedAs={ReactRouterLink}
|
||||||
|
to={{ pathname: `${slug}/configurations/list`, search: pluginsQueryParams }}
|
||||||
|
icon={<Cog />}
|
||||||
|
label={formatMessage({
|
||||||
|
id: 'app.links.configure-view',
|
||||||
|
defaultMessage: 'Configure the view',
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</ConfigureLayoutBox>
|
||||||
|
</CheckPermissions>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
startActions={
|
startActions={
|
||||||
|
@ -612,7 +612,7 @@
|
|||||||
"components.PageFooter.select": "Entries per page",
|
"components.PageFooter.select": "Entries per page",
|
||||||
"components.ProductionBlocker.description": "For safety purposes we have to disable this plugin in other environments.",
|
"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.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.TableHeader.sort": "Sort on {label}",
|
||||||
"components.Wysiwyg.ToggleMode.markdown-mode": "Markdown mode",
|
"components.Wysiwyg.ToggleMode.markdown-mode": "Markdown mode",
|
||||||
"components.Wysiwyg.ToggleMode.preview-mode": "Preview mode",
|
"components.Wysiwyg.ToggleMode.preview-mode": "Preview mode",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user