@strapi/admin 4.12.2 → 4.13.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumns.js +2 -0
  2. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +8 -1
  3. package/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js +42 -0
  4. package/admin/src/content-manager/components/{AttributeFilter/Filters.js → Filter/Filter.js} +5 -7
  5. package/admin/src/content-manager/components/Filter/index.js +1 -0
  6. package/admin/src/content-manager/hooks/useAllowedAttributes.js +47 -0
  7. package/admin/src/content-manager/hooks/useSyncRbac/index.js +10 -2
  8. package/admin/src/content-manager/pages/EditView/Information/index.js +9 -8
  9. package/admin/src/content-manager/pages/EditViewLayoutManager/index.js +2 -2
  10. package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +40 -7
  11. package/admin/src/content-manager/pages/ListSettingsView/index.js +6 -2
  12. package/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js +67 -69
  13. package/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js +74 -0
  14. package/admin/src/content-manager/pages/ListView/index.js +254 -68
  15. package/admin/src/content-manager/pages/ListViewLayoutManager/index.js +2 -2
  16. package/admin/src/content-manager/utils/getDisplayName.js +33 -0
  17. package/admin/src/content-manager/utils/index.js +1 -0
  18. package/admin/src/translations/en.json +3 -1
  19. package/admin/src/translations/zh-Hans.json +918 -902
  20. package/build/1227.32fe57ce.chunk.js +1 -0
  21. package/build/4174.fa8f9954.chunk.js +1 -0
  22. package/build/4546.ff09eeda.chunk.js +1 -0
  23. package/build/4724.baf7c5b1.chunk.js +6 -0
  24. package/build/6158.c3c13c20.chunk.js +1 -0
  25. package/build/78.dcc6df5c.chunk.js +1 -0
  26. package/build/{9806.3392505e.chunk.js → 9806.5d5a0e8d.chunk.js} +16 -16
  27. package/build/{Admin-authenticatedApp.3c585a0d.chunk.js → Admin-authenticatedApp.53a24d28.chunk.js} +2 -2
  28. package/build/audit-logs-settings-page.0f73ccf8.chunk.js +1 -0
  29. package/build/content-manager.7f96a2f1.chunk.js +1097 -0
  30. package/build/{content-type-builder.40534de5.chunk.js → content-type-builder.cd999f6e.chunk.js} +2 -2
  31. package/build/{en-json.08c05fcf.chunk.js → en-json.4f06fe03.chunk.js} +1 -1
  32. package/build/i18n-translation-ru-json.a3dbc125.chunk.js +1 -0
  33. package/build/index.html +1 -1
  34. package/build/main.40b94779.js +2859 -0
  35. package/build/{runtime~main.2902859a.js → runtime~main.b16af570.js} +2 -2
  36. package/build/users-permissions-translation-zh-Hans-json.8d82c809.chunk.js +1 -0
  37. package/build/{users-roles-settings-page.3f9f063e.chunk.js → users-roles-settings-page.9d9a1eff.chunk.js} +1 -1
  38. package/build/zh-Hans-json.97efd015.chunk.js +1 -0
  39. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/AssigneeFilter.js +42 -0
  40. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/StageFilter.js +70 -0
  41. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js +71 -0
  42. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +9 -217
  43. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/AssigneeSelect.js +149 -0
  44. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/index.js +1 -0
  45. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +241 -0
  46. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/index.js +1 -0
  47. package/ee/admin/content-manager/pages/EditView/InformationBox/constants.js +2 -0
  48. package/ee/admin/content-manager/pages/ListSettingsView/constants.js +7 -0
  49. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/ReviewWorkflowsAssigneeEE.js +51 -0
  50. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +44 -17
  51. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
  52. package/ee/server/constants/workflows.js +1 -0
  53. package/ee/server/controllers/index.js +1 -0
  54. package/ee/server/controllers/workflows/assignees/index.js +44 -0
  55. package/ee/server/routes/review-workflows.js +17 -0
  56. package/ee/server/services/index.js +1 -0
  57. package/ee/server/services/review-workflows/assignees.js +54 -0
  58. package/ee/server/services/review-workflows/metrics/index.js +5 -0
  59. package/ee/server/services/review-workflows/review-workflows.js +20 -11
  60. package/ee/server/validation/review-workflows.js +8 -0
  61. package/package.json +10 -10
  62. package/server/services/permission/permissions-manager/sanitize.js +12 -0
  63. package/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js +0 -42
  64. package/admin/src/content-manager/components/AttributeFilter/index.js +0 -40
  65. package/build/3984.dda474f7.chunk.js +0 -1
  66. package/build/4546.cfafae68.chunk.js +0 -1
  67. package/build/5483.6dd2e776.chunk.js +0 -6
  68. package/build/6158.c974fd83.chunk.js +0 -1
  69. package/build/audit-logs-settings-page.4b422831.chunk.js +0 -1
  70. package/build/content-manager.2af15f57.chunk.js +0 -1099
  71. package/build/i18n-translation-ru-json.401bc498.chunk.js +0 -1
  72. package/build/main.f13fc96c.js +0 -2856
  73. package/build/users-permissions-translation-zh-Hans-json.6ab714ee.chunk.js +0 -1
  74. package/build/zh-Hans-json.937b395b.chunk.js +0 -1
@@ -0,0 +1,2 @@
1
+ // Overwritten in EE
2
+ export default () => [];
@@ -239,7 +239,14 @@ const reducer = (state, action) =>
239
239
  (value) => {
240
240
  return value.type === 'relation';
241
241
  },
242
- (_, { path }) => {
242
+ (value, { path }) => {
243
+ const relationFieldName = path[path.length - 1];
244
+
245
+ // When editing, we don't want to fetch the relations with creator fields because we already have it
246
+ if (value && (relationFieldName === 'createdBy' || relationFieldName === 'updatedBy')) {
247
+ return value;
248
+ }
249
+
243
250
  if (state.modifiedData?.id === data.id && get(state.modifiedData, path)) {
244
251
  return get(state.modifiedData, path);
245
252
  }
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+
3
+ import { Combobox, ComboboxOption } from '@strapi/design-system';
4
+ import PropTypes from 'prop-types';
5
+ import { useIntl } from 'react-intl';
6
+
7
+ import { useAdminUsers } from '../../../../hooks/useAdminUsers';
8
+ import { getDisplayName } from '../../../utils';
9
+
10
+ export const AdminUsersFilter = ({ value, onChange }) => {
11
+ const { formatMessage } = useIntl();
12
+ const { users, isLoading } = useAdminUsers();
13
+
14
+ return (
15
+ <Combobox
16
+ value={value}
17
+ aria-label={formatMessage({
18
+ id: 'content-manager.components.Filters.usersSelect.label',
19
+ defaultMessage: 'Search and select an user to filter',
20
+ })}
21
+ onChange={onChange}
22
+ loading={isLoading}
23
+ >
24
+ {users.map((user) => {
25
+ return (
26
+ <ComboboxOption key={user.id} value={user.id.toString()}>
27
+ {getDisplayName(user, formatMessage)}
28
+ </ComboboxOption>
29
+ );
30
+ })}
31
+ </Combobox>
32
+ );
33
+ };
34
+
35
+ AdminUsersFilter.propTypes = {
36
+ onChange: PropTypes.func.isRequired,
37
+ value: PropTypes.string,
38
+ };
39
+
40
+ AdminUsersFilter.defaultProps = {
41
+ value: '',
42
+ };
@@ -1,12 +1,12 @@
1
1
  import React, { useRef, useState } from 'react';
2
2
 
3
- import { Box, Button } from '@strapi/design-system';
3
+ import { Button, Box } from '@strapi/design-system';
4
4
  import { FilterListURLQuery, FilterPopoverURLQuery, useTracking } from '@strapi/helper-plugin';
5
- import { Filter } from '@strapi/icons';
5
+ import { Filter as FilterIcon } from '@strapi/icons';
6
6
  import PropTypes from 'prop-types';
7
7
  import { useIntl } from 'react-intl';
8
8
 
9
- const Filters = ({ displayedFilters }) => {
9
+ export const Filter = ({ displayedFilters }) => {
10
10
  const [isVisible, setIsVisible] = useState(false);
11
11
  const { formatMessage } = useIntl();
12
12
  const buttonRef = useRef();
@@ -25,7 +25,7 @@ const Filters = ({ displayedFilters }) => {
25
25
  <Button
26
26
  variant="tertiary"
27
27
  ref={buttonRef}
28
- startIcon={<Filter />}
28
+ startIcon={<FilterIcon />}
29
29
  onClick={handleToggle}
30
30
  size="S"
31
31
  >
@@ -45,7 +45,7 @@ const Filters = ({ displayedFilters }) => {
45
45
  );
46
46
  };
47
47
 
48
- Filters.propTypes = {
48
+ Filter.propTypes = {
49
49
  displayedFilters: PropTypes.arrayOf(
50
50
  PropTypes.shape({
51
51
  name: PropTypes.string.isRequired,
@@ -54,5 +54,3 @@ Filters.propTypes = {
54
54
  })
55
55
  ).isRequired,
56
56
  };
57
-
58
- export default Filters;
@@ -0,0 +1 @@
1
+ export * from './Filter';
@@ -0,0 +1,47 @@
1
+ import { useRBACProvider, findMatchingPermissions } from '@strapi/helper-plugin';
2
+
3
+ const NOT_ALLOWED_FILTERS = ['json', 'component', 'media', 'richtext', 'dynamiczone', 'password'];
4
+ const TIMESTAMPS = ['createdAt', 'updatedAt'];
5
+ const CREATOR_ATTRIBUTES = ['createdBy', 'updatedBy'];
6
+
7
+ export const useAllowedAttributes = (contentType, slug) => {
8
+ const { allPermissions } = useRBACProvider();
9
+
10
+ const readPermissionsForSlug = findMatchingPermissions(allPermissions, [
11
+ {
12
+ action: 'plugin::content-manager.explorer.read',
13
+ subject: slug,
14
+ },
15
+ ]);
16
+
17
+ const canReadAdminUsers =
18
+ findMatchingPermissions(allPermissions, [
19
+ {
20
+ action: 'admin::users.read',
21
+ subject: null,
22
+ },
23
+ ]).length > 0;
24
+
25
+ const attributesWithReadPermissions = readPermissionsForSlug?.[0]?.properties?.fields ?? [];
26
+
27
+ const allowedAttributes = attributesWithReadPermissions.filter((attr) => {
28
+ const current = contentType?.attributes?.[attr] ?? {};
29
+
30
+ if (!current.type) {
31
+ return false;
32
+ }
33
+
34
+ if (NOT_ALLOWED_FILTERS.includes(current.type)) {
35
+ return false;
36
+ }
37
+
38
+ return true;
39
+ });
40
+
41
+ return [
42
+ 'id',
43
+ ...allowedAttributes,
44
+ ...TIMESTAMPS,
45
+ ...(canReadAdminUsers ? CREATOR_ATTRIBUTES : []),
46
+ ];
47
+ };
@@ -6,9 +6,10 @@ import { resetPermissions, setPermissions } from './actions';
6
6
  import { selectCollectionTypePermissions, selectPermissions } from './selectors';
7
7
 
8
8
  const useSyncRbac = (query, collectionTypeUID, containerName = 'listView') => {
9
+ const dispatch = useDispatch();
10
+
9
11
  const collectionTypesRelatedPermissions = useSelector(selectCollectionTypePermissions);
10
12
  const permissions = useSelector(selectPermissions);
11
- const dispatch = useDispatch();
12
13
 
13
14
  const relatedPermissions = collectionTypesRelatedPermissions[collectionTypeUID];
14
15
 
@@ -24,7 +25,14 @@ const useSyncRbac = (query, collectionTypeUID, containerName = 'listView') => {
24
25
  return () => {};
25
26
  }, [relatedPermissions, dispatch, query, containerName]);
26
27
 
27
- return permissions;
28
+ // Check if the permissions are related to the current collectionTypeUID
29
+ const isPermissionMismatch =
30
+ permissions?.some((permission) => permission.subject !== collectionTypeUID) ?? true;
31
+
32
+ return {
33
+ isValid: permissions && !isPermissionMismatch,
34
+ permissions,
35
+ };
28
36
  };
29
37
 
30
38
  export default useSyncRbac;
@@ -5,8 +5,7 @@ import { useCMEditViewDataManager } from '@strapi/helper-plugin';
5
5
  import PropTypes from 'prop-types';
6
6
  import { useIntl } from 'react-intl';
7
7
 
8
- import { getFullName } from '../../../../utils';
9
- import { getTrad } from '../../../utils';
8
+ import { getTrad, getDisplayName } from '../../../utils';
10
9
 
11
10
  import getUnits from './utils/getUnits';
12
11
 
@@ -42,9 +41,13 @@ const KeyValuePair = ({ label, value }) => {
42
41
  );
43
42
  };
44
43
 
44
+ KeyValuePair.defaultProps = {
45
+ value: '-',
46
+ };
47
+
45
48
  KeyValuePair.propTypes = {
46
49
  label: PropTypes.string.isRequired,
47
- value: PropTypes.string.isRequired,
50
+ value: PropTypes.string,
48
51
  };
49
52
 
50
53
  const Body = () => {
@@ -53,18 +56,16 @@ const Body = () => {
53
56
  const currentTime = useRef(Date.now());
54
57
 
55
58
  const getFieldInfo = (atField, byField) => {
56
- const { firstname, lastname, username } = initialData[byField] ?? {};
59
+ const user = initialData[byField] ?? {};
57
60
 
58
- const userFirstname = firstname ?? '';
59
- const userLastname = lastname ?? '';
60
- const user = username ?? getFullName(userFirstname, userLastname);
61
+ const displayName = getDisplayName(user, formatMessage);
61
62
  const timestamp = initialData[atField] ? new Date(initialData[atField]).getTime() : Date.now();
62
63
  const elapsed = timestamp - currentTime.current;
63
64
  const { unit, value } = getUnits(-elapsed);
64
65
 
65
66
  return {
66
67
  at: formatRelativeTime(value, unit, { numeric: 'auto' }),
67
- by: isCreatingEntry ? '-' : user,
68
+ by: isCreatingEntry ? '-' : displayName,
68
69
  };
69
70
  };
70
71
 
@@ -16,7 +16,7 @@ const EditViewLayoutManager = ({ layout, ...rest }) => {
16
16
  const dispatch = useDispatch();
17
17
  const [{ query }] = useQueryParams();
18
18
  const { runHookWaterfall } = useStrapiApp();
19
- const permissions = useSyncRbac(query, rest.slug, 'editView');
19
+ const { permissions, isValid: isValidPermissions } = useSyncRbac(query, rest.slug, 'editView');
20
20
 
21
21
  useEffect(() => {
22
22
  // Allow the plugins to extend the edit view layout
@@ -29,7 +29,7 @@ const EditViewLayoutManager = ({ layout, ...rest }) => {
29
29
  };
30
30
  }, [layout, dispatch, query, runHookWaterfall]);
31
31
 
32
- if (!currentLayout || !permissions) {
32
+ if (!currentLayout || !isValidPermissions) {
33
33
  return <LoadingIndicatorPage />;
34
34
  }
35
35
 
@@ -10,14 +10,41 @@ import {
10
10
  ToggleInput,
11
11
  Typography,
12
12
  } from '@strapi/design-system';
13
+ import { useCollator } from '@strapi/helper-plugin';
13
14
  import PropTypes from 'prop-types';
14
15
  import { useIntl } from 'react-intl';
15
16
 
17
+ import { useEnterprise } from '../../../../hooks/useEnterprise';
16
18
  import { getTrad } from '../../../utils';
17
19
 
18
- export const Settings = ({ modifiedData, onChange, sortOptions }) => {
19
- const { formatMessage } = useIntl();
20
- const { settings, metadatas } = modifiedData;
20
+ export const Settings = ({
21
+ contentTypeOptions,
22
+ modifiedData,
23
+ onChange,
24
+ sortOptions: sortOptionsCE,
25
+ }) => {
26
+ const { formatMessage, locale } = useIntl();
27
+ const formatter = useCollator(locale, {
28
+ sensitivity: 'base',
29
+ });
30
+ const sortOptions = useEnterprise(
31
+ sortOptionsCE,
32
+ async () =>
33
+ (await import('../../../../../../ee/admin/content-manager/pages/ListSettingsView/constants'))
34
+ .REVIEW_WORKFLOW_STAGE_SORT_OPTION_NAME,
35
+ {
36
+ combine(ceOptions, eeOption) {
37
+ return [...ceOptions, { ...eeOption, label: formatMessage(eeOption.label) }];
38
+ },
39
+
40
+ defaultValue: sortOptionsCE,
41
+
42
+ enabled: !!contentTypeOptions?.reviewWorkflows,
43
+ }
44
+ );
45
+
46
+ const sortOptionsSorted = sortOptions.sort((a, b) => formatter.compare(a.label, b.label));
47
+ const { settings } = modifiedData;
21
48
 
22
49
  return (
23
50
  <Flex direction="column" alignItems="stretch" gap={4}>
@@ -129,9 +156,9 @@ export const Settings = ({ modifiedData, onChange, sortOptions }) => {
129
156
  name="settings.defaultSortBy"
130
157
  value={modifiedData.settings.defaultSortBy || ''}
131
158
  >
132
- {sortOptions.map((sortBy) => (
133
- <Option key={sortBy} value={sortBy}>
134
- {metadatas[sortBy].list.label || sortBy}
159
+ {sortOptionsSorted.map(({ value, label }) => (
160
+ <Option key={value} value={value}>
161
+ {label}
135
162
  </Option>
136
163
  ))}
137
164
  </Select>
@@ -164,7 +191,13 @@ Settings.defaultProps = {
164
191
  };
165
192
 
166
193
  Settings.propTypes = {
194
+ contentTypeOptions: PropTypes.object.isRequired,
167
195
  modifiedData: PropTypes.object,
168
196
  onChange: PropTypes.func.isRequired,
169
- sortOptions: PropTypes.array,
197
+ sortOptions: PropTypes.arrayOf(
198
+ PropTypes.shape({
199
+ value: PropTypes.string,
200
+ label: PropTypes.string,
201
+ }).isRequired
202
+ ),
170
203
  };
@@ -55,7 +55,7 @@ const ListSettingsView = ({ layout, slug }) => {
55
55
 
56
56
  const isModalFormOpen = Object.keys(fieldForm).length !== 0;
57
57
 
58
- const { attributes } = layout;
58
+ const { attributes, options } = layout;
59
59
  const displayedFields = modifiedData.layouts.list;
60
60
 
61
61
  const goBackUrl = () => {
@@ -179,7 +179,10 @@ const ListSettingsView = ({ layout, slug }) => {
179
179
 
180
180
  const sortOptions = Object.entries(attributes)
181
181
  .filter(([, attribute]) => !EXCLUDED_SORT_ATTRIBUTE_TYPES.includes(attribute.type))
182
- .map(([name]) => name);
182
+ .map(([name]) => ({
183
+ value: name,
184
+ label: layout.metadatas[name].list.label,
185
+ }));
183
186
 
184
187
  const move = (originalIndex, atIndex) => {
185
188
  dispatch({
@@ -235,6 +238,7 @@ const ListSettingsView = ({ layout, slug }) => {
235
238
  paddingRight={7}
236
239
  >
237
240
  <Settings
241
+ contentTypeOptions={options}
238
242
  modifiedData={modifiedData}
239
243
  onChange={handleChange}
240
244
  sortOptions={sortOptions}
@@ -1,81 +1,93 @@
1
1
  import React from 'react';
2
2
 
3
- import { Select, Option, Box } from '@strapi/design-system';
4
- import { useTracking } from '@strapi/helper-plugin';
3
+ import { Flex, BaseCheckbox, TextButton, Typography } from '@strapi/design-system';
4
+ import { useCollator, useTracking } from '@strapi/helper-plugin';
5
5
  import PropTypes from 'prop-types';
6
6
  import { useIntl } from 'react-intl';
7
7
  import { useDispatch, useSelector } from 'react-redux';
8
+ import styled from 'styled-components';
8
9
 
9
- import { getTrad, checkIfAttributeIsDisplayable } from '../../../../utils';
10
- import { onChangeListHeaders } from '../../actions';
10
+ import { checkIfAttributeIsDisplayable } from '../../../../utils';
11
+ import { onChangeListHeaders, onResetListHeaders } from '../../actions';
11
12
  import { selectDisplayedHeaders } from '../../selectors';
12
13
 
14
+ const ChackboxWrapper = styled(Flex)`
15
+ :hover {
16
+ background-color: ${(props) => props.theme.colors.primary100};
17
+ }
18
+ `;
19
+
13
20
  export const FieldPicker = ({ layout }) => {
14
21
  const dispatch = useDispatch();
15
22
  const displayedHeaders = useSelector(selectDisplayedHeaders);
16
23
  const { trackUsage } = useTracking();
17
- const { formatMessage } = useIntl();
18
-
19
- const allAllowedHeaders = getAllAllowedHeaders(layout.contentType.attributes).map((attrName) => {
20
- const metadatas = layout.contentType.metadatas[attrName].list;
21
-
22
- return {
23
- name: attrName,
24
- intlLabel: { id: metadatas.label, defaultMessage: metadatas.label },
25
- };
24
+ const { formatMessage, locale } = useIntl();
25
+ const formatter = useCollator(locale, {
26
+ sensitivity: 'base',
26
27
  });
27
28
 
28
- const values = displayedHeaders.map(({ name }) => name);
29
+ const columns = Object.keys(layout.contentType.attributes)
30
+ .filter((name) => checkIfAttributeIsDisplayable(layout.contentType.attributes[name]))
31
+ .map((name) => ({
32
+ name,
33
+ label: layout.contentType.metadatas[name].list.label,
34
+ }))
35
+ .sort((a, b) => formatter.compare(a.label, b.label));
29
36
 
30
- const handleChange = (updatedValues) => {
31
- trackUsage('didChangeDisplayedFields');
37
+ const displayedHeaderKeys = displayedHeaders.map(({ name }) => name);
32
38
 
33
- // removing a header
34
- if (updatedValues.length < values.length) {
35
- const removedHeader = values.filter((value) => {
36
- return updatedValues.indexOf(value) === -1;
37
- });
38
-
39
- dispatch(onChangeListHeaders({ name: removedHeader[0], value: true }));
40
- } else {
41
- const addedHeader = updatedValues.filter((value) => {
42
- return values.indexOf(value) === -1;
43
- });
39
+ const handleChange = (name) => {
40
+ trackUsage('didChangeDisplayedFields');
41
+ dispatch(onChangeListHeaders({ name, value: displayedHeaderKeys.includes(name) }));
42
+ };
44
43
 
45
- dispatch(onChangeListHeaders({ name: addedHeader[0], value: false }));
46
- }
44
+ const handleReset = () => {
45
+ dispatch(onResetListHeaders());
47
46
  };
48
47
 
49
48
  return (
50
- <Box paddingTop={1} paddingBottom={1}>
51
- <Select
52
- aria-label="change displayed fields"
53
- value={values}
54
- onChange={handleChange}
55
- customizeContent={(values) =>
56
- formatMessage(
57
- {
58
- id: getTrad('select.currently.selected'),
59
- defaultMessage: '{count} currently selected',
60
- },
61
- { count: values.length }
62
- )
63
- }
64
- multi
65
- size="S"
66
- >
67
- {allAllowedHeaders.map((header) => {
49
+ <Flex as="fieldset" direction="column" alignItems="stretch" gap={3}>
50
+ <Flex justifyContent="space-between">
51
+ <Typography as="legend" variant="pi" fontWeight="bold">
52
+ {formatMessage({
53
+ id: 'containers.ListPage.displayedFields',
54
+ defaultMessage: 'Displayed fields',
55
+ })}
56
+ </Typography>
57
+
58
+ <TextButton onClick={handleReset}>
59
+ {formatMessage({
60
+ id: 'app.components.Button.reset',
61
+ defaultMessage: 'Reset',
62
+ })}
63
+ </TextButton>
64
+ </Flex>
65
+
66
+ <Flex direction="column" alignItems="stretch">
67
+ {columns.map((header) => {
68
+ const isActive = displayedHeaderKeys.includes(header.name);
69
+
68
70
  return (
69
- <Option key={header.name} value={header.name}>
70
- {formatMessage({
71
- id: header.intlLabel.id || header.name,
72
- defaultMessage: header.intlLabel.defaultMessage || header.name,
73
- })}
74
- </Option>
71
+ <ChackboxWrapper
72
+ wrap="wrap"
73
+ gap={2}
74
+ as="label"
75
+ background={isActive ? 'primary100' : 'transparent'}
76
+ hasRadius
77
+ padding={2}
78
+ key={header.name}
79
+ >
80
+ <BaseCheckbox
81
+ onChange={() => handleChange(header.name)}
82
+ value={isActive}
83
+ name={header.name}
84
+ />
85
+ <Typography fontSize={1}>{header.label}</Typography>
86
+ </ChackboxWrapper>
75
87
  );
76
88
  })}
77
- </Select>
78
- </Box>
89
+ </Flex>
90
+ </Flex>
79
91
  );
80
92
  };
81
93
 
@@ -92,17 +104,3 @@ FieldPicker.propTypes = {
92
104
  }).isRequired,
93
105
  }).isRequired,
94
106
  };
95
-
96
- const getAllAllowedHeaders = (attributes) => {
97
- const allowedAttributes = Object.keys(attributes).reduce((acc, current) => {
98
- const attribute = attributes[current];
99
-
100
- if (checkIfAttributeIsDisplayable(attribute)) {
101
- acc.push(current);
102
- }
103
-
104
- return acc;
105
- }, []);
106
-
107
- return allowedAttributes.sort();
108
- };
@@ -0,0 +1,74 @@
1
+ import React from 'react';
2
+
3
+ import { Flex, IconButton, Popover } from '@strapi/design-system';
4
+ import { CheckPermissions, LinkButton } from '@strapi/helper-plugin';
5
+ import { Cog, Layer } from '@strapi/icons';
6
+ import PropTypes from 'prop-types';
7
+ import { useIntl } from 'react-intl';
8
+ import { useSelector } from 'react-redux';
9
+
10
+ import { selectAdminPermissions } from '../../../../../pages/App/selectors';
11
+ import { FieldPicker } from '../FieldPicker';
12
+
13
+ export const ViewSettingsMenu = ({ slug, layout }) => {
14
+ const [isVisible, setIsVisible] = React.useState(false);
15
+ const cogButtonRef = React.useRef();
16
+ const permissions = useSelector(selectAdminPermissions);
17
+ const { formatMessage } = useIntl();
18
+
19
+ const handleToggle = () => {
20
+ setIsVisible((prev) => !prev);
21
+ };
22
+
23
+ return (
24
+ <>
25
+ <IconButton
26
+ icon={<Cog />}
27
+ label={formatMessage({
28
+ id: 'components.ViewSettings.tooltip',
29
+ defaultMessage: 'View Settings',
30
+ })}
31
+ ref={cogButtonRef}
32
+ onClick={handleToggle}
33
+ />
34
+ {isVisible && (
35
+ <Popover placement="bottom-end" source={cogButtonRef} onDismiss={handleToggle} padding={2}>
36
+ <Flex alignItems="stretch" direction="column" gap={3}>
37
+ <CheckPermissions
38
+ permissions={permissions.contentManager.collectionTypesConfigurations}
39
+ >
40
+ <LinkButton
41
+ size="S"
42
+ startIcon={<Layer />}
43
+ to={`${slug}/configurations/list`}
44
+ variant="secondary"
45
+ >
46
+ {formatMessage({
47
+ id: 'app.links.configure-view',
48
+ defaultMessage: 'Configure the view',
49
+ })}
50
+ </LinkButton>
51
+ </CheckPermissions>
52
+
53
+ <FieldPicker layout={layout} />
54
+ </Flex>
55
+ </Popover>
56
+ )}
57
+ </>
58
+ );
59
+ };
60
+
61
+ ViewSettingsMenu.propTypes = {
62
+ slug: PropTypes.string.isRequired,
63
+ layout: PropTypes.shape({
64
+ contentType: PropTypes.shape({
65
+ attributes: PropTypes.object.isRequired,
66
+ metadatas: PropTypes.object.isRequired,
67
+ layouts: PropTypes.shape({
68
+ list: PropTypes.array.isRequired,
69
+ }).isRequired,
70
+ options: PropTypes.object.isRequired,
71
+ settings: PropTypes.object.isRequired,
72
+ }).isRequired,
73
+ }).isRequired,
74
+ };