Merge pull request #8727 from strapi/relational-fields/display-in-list

Display relational fields in the list view
This commit is contained in:
cyril lopez 2020-11-26 09:46:38 +01:00 committed by GitHub
commit 3c9686f986
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 232 additions and 98 deletions

View File

@ -23,12 +23,12 @@
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"@babel/runtime": "^7.9.2",
"@buffetjs/core": "3.3.1",
"@buffetjs/custom": "3.3.1",
"@buffetjs/hooks": "3.3.1",
"@buffetjs/icons": "3.3.1",
"@buffetjs/styles": "3.3.1",
"@buffetjs/utils": "3.3.1",
"@buffetjs/core": "3.3.1-next.2",
"@buffetjs/custom": "3.3.1-next.2",
"@buffetjs/hooks": "3.3.1-next.2",
"@buffetjs/icons": "3.3.1-next.2",
"@buffetjs/styles": "3.3.1-next.2",
"@buffetjs/utils": "3.3.1-next.2",
"@casl/ability": "^4.1.5",
"@fortawesome/fontawesome-free": "^5.11.2",
"@fortawesome/fontawesome-svg-core": "^1.2.32",

View File

@ -50,12 +50,12 @@
"rollup-plugin-terser": "^4.0.4"
},
"dependencies": {
"@buffetjs/core": "3.3.1",
"@buffetjs/custom": "3.3.1",
"@buffetjs/hooks": "3.3.1",
"@buffetjs/icons": "3.3.1",
"@buffetjs/styles": "3.3.1",
"@buffetjs/utils": "3.3.1",
"@buffetjs/core": "3.3.1-next.2",
"@buffetjs/custom": "3.3.1-next.2",
"@buffetjs/hooks": "3.3.1-next.2",
"@buffetjs/icons": "3.3.1-next.2",
"@buffetjs/styles": "3.3.1-next.2",
"@buffetjs/utils": "3.3.1-next.2",
"bootstrap": "^4.5.3",
"classnames": "^2.2.5",
"immutable": "^3.8.2",

View File

@ -6,11 +6,11 @@ import { useGlobalContext } from 'strapi-helper-plugin';
import { IconLinks } from '@buffetjs/core';
import { Duplicate } from '@buffetjs/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import useListView from '../../hooks/useListView';
import { useListView } from '../../hooks';
import dateFormats from '../../utils/dateFormats';
import CustomInputCheckbox from '../CustomInputCheckbox';
import MediaPreviewList from '../MediaPreviewList';
import { ActionContainer, Truncate, Truncated } from './styledComponents';
import { ActionContainer } from './styledComponents';
import RowCell from './RowCell';
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
@ -62,6 +62,9 @@ const getDisplayedValue = (type, value, name) => {
return date.format(dateFormats.time);
}
case 'relation': {
return value;
}
default:
return '-';
}
@ -84,12 +87,16 @@ function Row({ canCreate, canDelete, canUpdate, isBulkable, row, headers, goTo }
icon: canCreate ? <Duplicate fill="black" /> : null,
onClick: e => {
e.stopPropagation();
goTo(`create/clone/${row.id}`);
},
},
{
icon: canUpdate ? <FontAwesomeIcon icon="pencil-alt" /> : null,
onClick: e => {
e.stopPropagation();
emitEventRef.current('willDeleteEntryFromList');
goTo(row.id);
},
},
{
icon: canDelete ? <FontAwesomeIcon icon="trash-alt" /> : null,
@ -113,21 +120,22 @@ function Row({ canCreate, canDelete, canUpdate, isBulkable, row, headers, goTo }
/>
</td>
)}
{headers.map(({ key, name, fieldSchema: { type }, cellFormatter }) => {
const isMedia = type === 'media';
return (
{headers.map(
({ key, name, fieldSchema: { type, relationType }, cellFormatter, metadatas }) => (
<td key={key}>
{isMedia && <MediaPreviewList files={memoizedDisplayedValue(name, type)} />}
{cellFormatter && cellFormatter(row)}
{!isMedia && !cellFormatter && (
<Truncate>
<Truncated>{memoizedDisplayedValue(name, type)}</Truncated>
</Truncate>
{cellFormatter ? (
cellFormatter(row)
) : (
<RowCell
type={type}
metadatas={metadatas}
relationType={relationType}
value={memoizedDisplayedValue(name, type)}
/>
)}
</td>
);
})}
)
)}
<ActionContainer>
<IconLinks links={links} />
</ActionContainer>

View File

@ -0,0 +1,36 @@
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import MediaPreviewList from '../MediaPreviewList';
import RelationPreviewList from '../RelationPreviewList';
import { Truncate, Truncated } from './styledComponents';
const RowCell = ({ metadatas, type, value, relationType }) => {
if (type === 'media') {
return <MediaPreviewList files={value} />;
}
if (type === 'relation') {
return <RelationPreviewList relationType={relationType} metadatas={metadatas} value={value} />;
}
return (
<Truncate>
<Truncated title={value}>{value}</Truncated>
</Truncate>
);
};
RowCell.defaultProps = {
type: null,
value: null,
relationType: null,
};
RowCell.propTypes = {
metadatas: PropTypes.object.isRequired,
relationType: PropTypes.string,
type: PropTypes.string,
value: PropTypes.any,
};
export default memo(RowCell);

View File

@ -55,13 +55,20 @@ const CustomTable = ({
const colSpanLength = isBulkable && canDelete ? headers.length + 2 : headers.length + 1;
const handleGoTo = id => {
const handleRowGoTo = id => {
emitEvent('willEditEntryFromList');
push({
pathname: `${pathname}/${id}`,
state: { from: pathname },
});
};
const handleEditGoTo = id => {
emitEvent('willEditEntryFromButton');
push({
pathname: `${pathname}/${id}`,
state: { from: pathname },
});
};
const values = { contentType: upperFirst(label), search: _q };
let tableEmptyMsgId = filters.length > 0 ? 'withFilters' : 'withoutFilter';
@ -89,7 +96,7 @@ const CustomTable = ({
e.preventDefault();
e.stopPropagation();
handleGoTo(row.id);
handleRowGoTo(row.id);
}}
>
<Row
@ -99,7 +106,7 @@ const CustomTable = ({
isBulkable={isBulkable && canDelete}
headers={headers}
row={row}
goTo={handleGoTo}
goTo={handleEditGoTo}
/>
</TableRow>
);

View File

@ -0,0 +1,7 @@
import styled from 'styled-components';
const CountWrapper = styled.div`
padding-top: 0.3rem;
`;
export default CountWrapper;

View File

@ -0,0 +1,55 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Flex, Padded, Count } from '@buffetjs/core';
import { useIntl } from 'react-intl';
import { getTrad } from '../../utils';
import { Truncate, Truncated } from '../CustomTable/styledComponents';
import CountWrapper from './CountWrapper';
const RelationPreviewList = ({ metadatas: { mainField }, relationType, value }) => {
const { formatMessage } = useIntl();
const isSingle = ['oneWay', 'oneToOne', 'manyToOne'].includes(relationType);
if (isSingle) {
return (
<Truncate>
<Truncated>{value ? value[mainField] : '-'}</Truncated>
</Truncate>
);
}
const size = value ? value.length : 0;
return (
<Truncate>
<Flex>
<CountWrapper>
<Count count={size} />
</CountWrapper>
<Padded left size="xs" />
<Truncated>
{formatMessage({
id: getTrad(
size > 1 ? 'containers.ListPage.items.plural' : 'containers.ListPage.items.singular'
),
})}
</Truncated>
</Flex>
</Truncate>
);
};
RelationPreviewList.defaultProps = {
value: null,
};
RelationPreviewList.propTypes = {
metadatas: PropTypes.shape({
mainField: PropTypes.string.isRequired,
}).isRequired,
relationType: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
};
export default RelationPreviewList;

View File

@ -193,6 +193,7 @@ function ListView({
});
onDeleteSeveralDataSucceeded();
emitEventRef.current('didBulkDeleteEntries');
} catch (err) {
strapi.notification.error(`${pluginId}.error.record.delete`);
}
@ -346,6 +347,11 @@ function ListView({
};
}, [total, headerAction, label, canRead, formatMessage]);
const handleToggleModalDeleteAll = e => {
emitEventRef.current('willBulkDeleteEntries');
toggleModalDeleteAll(e);
};
return (
<>
<ListViewProvider
@ -360,7 +366,7 @@ function ListView({
onChangeBulkSelectall={onChangeBulkSelectall}
onClickDelete={handleClickDelete}
slug={slug}
toggleModalDeleteAll={toggleModalDeleteAll}
toggleModalDeleteAll={handleToggleModalDeleteAll}
setQuery={setQuery}
>
<FilterPicker

View File

@ -88,10 +88,17 @@ const listViewReducer = (state = initialState, action) =>
if (!value) {
const { metadatas, attributes } = state.contentType;
let metas = metadatas[name].list;
if (attributes[name].type === 'relation') {
const mainField = metadatas[name].edit.mainField;
metas = { ...metas, mainField };
}
drafState.displayedHeaders.push({
name,
fieldSchema: attributes[name],
metadatas: metadatas[name].list,
metadatas: metas,
key: `__${name}_key__`,
});
} else {

View File

@ -82,7 +82,16 @@ const formatLayoutWithMetas = (obj, ctUid, models) => {
const formatListLayoutWithMetas = obj => {
const formatted = obj.layouts.list.reduce((acc, current) => {
const fieldSchema = get(obj, ['attributes', current], {});
const metadatas = get(obj, ['metadatas', current, 'list'], {});
let metadatas = get(obj, ['metadatas', current, 'list'], {});
const type = fieldSchema.type;
if (type === 'relation') {
metadatas = {
...metadatas,
mainField: get(obj, ['metadatas', current, 'edit', 'mainField'], 'id'),
};
}
acc.push({ key: `__${current}_key__`, name: current, fieldSchema, metadatas });

View File

@ -77,6 +77,8 @@
"containers.List.pluginHeaderDescription.singular": "{label} entry found",
"containers.List.published": "Published",
"containers.ListPage.displayedFields": "Displayed Fields",
"containers.ListPage.items.plural": "items",
"containers.ListPage.items.singular": "item",
"containers.ListPage.table-headers.published_at": "State",
"containers.ListSettingsView.modal-form.edit-label": "Edit the label",
"containers.SettingPage.add.field": "Insert another field",

View File

@ -9,12 +9,12 @@
"required": true
},
"dependencies": {
"@buffetjs/core": "3.3.1",
"@buffetjs/custom": "3.3.1",
"@buffetjs/hooks": "3.3.1",
"@buffetjs/icons": "3.3.1",
"@buffetjs/styles": "3.3.1",
"@buffetjs/utils": "3.3.1",
"@buffetjs/core": "3.3.1-next.2",
"@buffetjs/custom": "3.3.1-next.2",
"@buffetjs/hooks": "3.3.1-next.2",
"@buffetjs/icons": "3.3.1-next.2",
"@buffetjs/styles": "3.3.1-next.2",
"@buffetjs/utils": "3.3.1-next.2",
"@sindresorhus/slugify": "1.1.0",
"classnames": "^2.2.6",
"codemirror": "^5.46.0",

View File

@ -8,12 +8,12 @@
"description": "content-type-builder.plugin.description"
},
"dependencies": {
"@buffetjs/core": "3.3.1",
"@buffetjs/custom": "3.3.1",
"@buffetjs/hooks": "3.3.1",
"@buffetjs/icons": "3.3.1",
"@buffetjs/styles": "3.3.1",
"@buffetjs/utils": "3.3.1",
"@buffetjs/core": "3.3.1-next.2",
"@buffetjs/custom": "3.3.1-next.2",
"@buffetjs/hooks": "3.3.1-next.2",
"@buffetjs/icons": "3.3.1-next.2",
"@buffetjs/styles": "3.3.1-next.2",
"@buffetjs/utils": "3.3.1-next.2",
"@sindresorhus/slugify": "1.1.0",
"fs-extra": "^9.0.1",
"immutable": "^3.8.2",

View File

@ -11,12 +11,12 @@
"test": "echo \"no tests yet\""
},
"dependencies": {
"@buffetjs/core": "3.3.1",
"@buffetjs/custom": "3.3.1",
"@buffetjs/hooks": "3.3.1",
"@buffetjs/icons": "3.3.1",
"@buffetjs/styles": "3.3.1",
"@buffetjs/utils": "3.3.1",
"@buffetjs/core": "3.3.1-next.2",
"@buffetjs/custom": "3.3.1-next.2",
"@buffetjs/hooks": "3.3.1-next.2",
"@buffetjs/icons": "3.3.1-next.2",
"@buffetjs/styles": "3.3.1-next.2",
"@buffetjs/utils": "3.3.1-next.2",
"cheerio": "^1.0.0-rc.3",
"fs-extra": "^9.0.1",
"immutable": "^3.8.2",

View File

@ -2,9 +2,6 @@ import styled from 'styled-components';
import Flex from '../../Flex';
// TODO : Design System
// Wait for the product designer to see
// if we need to add this new color in the theme or use an existing one.
const Wrapper = styled(Flex)`
width: 1.4rem;
height: 1.4rem;

View File

@ -12,12 +12,12 @@
"test": "echo \"no tests yet\""
},
"dependencies": {
"@buffetjs/core": "3.3.1",
"@buffetjs/custom": "3.3.1",
"@buffetjs/hooks": "3.3.1",
"@buffetjs/icons": "3.3.1",
"@buffetjs/styles": "3.3.1",
"@buffetjs/utils": "3.3.1",
"@buffetjs/core": "3.3.1-next.2",
"@buffetjs/custom": "3.3.1-next.2",
"@buffetjs/hooks": "3.3.1-next.2",
"@buffetjs/icons": "3.3.1-next.2",
"@buffetjs/styles": "3.3.1-next.2",
"@buffetjs/utils": "3.3.1-next.2",
"byte-size": "^6.2.0",
"cropperjs": "^1.5.6",
"immer": "^7.0.14",

View File

@ -12,12 +12,12 @@
"test": "echo \"no tests yet\""
},
"dependencies": {
"@buffetjs/core": "3.3.1",
"@buffetjs/custom": "3.3.1",
"@buffetjs/hooks": "3.3.1",
"@buffetjs/icons": "3.3.1",
"@buffetjs/styles": "3.3.1",
"@buffetjs/utils": "3.3.1",
"@buffetjs/core": "3.3.1-next.2",
"@buffetjs/custom": "3.3.1-next.2",
"@buffetjs/hooks": "3.3.1-next.2",
"@buffetjs/icons": "3.3.1-next.2",
"@buffetjs/styles": "3.3.1-next.2",
"@buffetjs/utils": "3.3.1-next.2",
"@purest/providers": "^1.0.1",
"bcryptjs": "^2.4.3",
"grant-koa": "5.2.0",

View File

@ -1036,15 +1036,15 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@buffetjs/core@3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@buffetjs/core/-/core-3.3.1.tgz#56e4a398a854989c5072be7ae6c1e18ceb5861ed"
integrity sha512-WnbZB4yG0JskFmRu9EutzV3pvzaI/Qm23a74f+teuLuODLwK5OpClje76ZL1zw3ezxkGQ6O9dQ/A4J6hYcOimw==
"@buffetjs/core@3.3.1-next.2":
version "3.3.1-next.2"
resolved "https://registry.yarnpkg.com/@buffetjs/core/-/core-3.3.1-next.2.tgz#a10eb10bcc8c729c41f2aec268d4aa42b1ba3442"
integrity sha512-6BCuHqW74X8MW11l3USh7iQHmytRUOsPpiO4vcbqTOzNXvJftu3yaXS+fvXrRES5N48sY4TZ2HpLl2fSW541Hg==
dependencies:
"@buffetjs/hooks" "3.3.1"
"@buffetjs/icons" "3.3.1"
"@buffetjs/styles" "3.3.1"
"@buffetjs/utils" "3.3.1"
"@buffetjs/hooks" "3.3.1-next.2"
"@buffetjs/icons" "3.3.1-next.2"
"@buffetjs/styles" "3.3.1-next.2"
"@buffetjs/utils" "3.3.1-next.2"
"@fortawesome/fontawesome-svg-core" "^1.2.25"
"@fortawesome/free-regular-svg-icons" "^5.11.2"
"@fortawesome/free-solid-svg-icons" "^5.11.2"
@ -1060,35 +1060,35 @@
react-with-direction "^1.3.1"
reactstrap "^8.5.1"
"@buffetjs/custom@3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@buffetjs/custom/-/custom-3.3.1.tgz#6f01fa91302cdb31199fc3489096b3c7d3e988ff"
integrity sha512-akv22y1q3yzKiO0KtSMTXzF9UqBgWkZSaVgnWjtJ3xjB3ZfYlntwRvw1hJ791G0/Be2wypRUH2qIR/9kHsTIQw==
"@buffetjs/custom@3.3.1-next.2":
version "3.3.1-next.2"
resolved "https://registry.yarnpkg.com/@buffetjs/custom/-/custom-3.3.1-next.2.tgz#9c8291ee24222e305c16824d14d78a36e68e38a6"
integrity sha512-qiqzVM6A7Cofy0W+OU5rcmz4hkcP/V2i8QQQiPsaRcOnmhtfriCwi5eGiae45f7RL77faMRwsZYxefrmTTr2Hg==
dependencies:
"@buffetjs/core" "3.3.1"
"@buffetjs/styles" "3.3.1"
"@buffetjs/utils" "3.3.1"
"@buffetjs/core" "3.3.1-next.2"
"@buffetjs/styles" "3.3.1-next.2"
"@buffetjs/utils" "3.3.1-next.2"
lodash "4.17.19"
moment "^2.24.0"
prop-types "^15.5.10"
react-moment-proptypes "^1.7.0"
"@buffetjs/hooks@3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@buffetjs/hooks/-/hooks-3.3.1.tgz#1888c5c7bebb1be0c2e8b7d88e8149610f7cf129"
integrity sha512-aFTscNP3qKmAZNW7vqHeTIHwka6HhFQ/Z/0wJyfD8E52sMnPU0yJpXZgcDNyUjWisvYfZjJLqPowWIYuu4WFwA==
"@buffetjs/hooks@3.3.1-next.2":
version "3.3.1-next.2"
resolved "https://registry.yarnpkg.com/@buffetjs/hooks/-/hooks-3.3.1-next.2.tgz#32db4c2d4c61ed2de3a01569a6cb204d67ef2766"
integrity sha512-C5vNPTmDlriJwXARYuR9UuvCMq9/2f7y9kNzKmDLeCjEtpmvVP6gzvwQnmG+SjvN7eyDyFKOx0HBAu+dj7dfbg==
"@buffetjs/icons@3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@buffetjs/icons/-/icons-3.3.1.tgz#e066944b1b76409d07062e17629ff0a270ef3dd0"
integrity sha512-lkHUemQdB22rNaZbZBQZ1tTxJARD7JJa2LGm3GRQ4nfjP2MH2kf2xgm3UP/kCK+TOO8RRW+Cnn0UvVKhgzwQXw==
"@buffetjs/icons@3.3.1-next.2":
version "3.3.1-next.2"
resolved "https://registry.yarnpkg.com/@buffetjs/icons/-/icons-3.3.1-next.2.tgz#f7d0c51a7463db8571b70ba96121944866fa3137"
integrity sha512-lcmi3l7pazrykcf8akpLxAha+Ic7uEpmsLowwR7mUX51u0akVqrSzG6FShk/AaN+UNsGo6Gs3UlBldakrQO9QA==
dependencies:
prop-types "^15.5.10"
"@buffetjs/styles@3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@buffetjs/styles/-/styles-3.3.1.tgz#eaef219649357b958192d79d0d74e1b1debbbd97"
integrity sha512-CZiszfXpND3e1o50bG4rwDzU8uakHEjUjHgtJDeI3ZRlsWnFFjnmE8nHB43IPEaPmiIfmokAqNzNUzJ+NMI6Xw==
"@buffetjs/styles@3.3.1-next.2":
version "3.3.1-next.2"
resolved "https://registry.yarnpkg.com/@buffetjs/styles/-/styles-3.3.1-next.2.tgz#7165ae1f2f2c66d56b3fbc228e4a5517a974e878"
integrity sha512-udG1L1BIQcGCc8bmW0Q2gvtEg281HcA2WYr9Tr/n5UE6z8jk828eqvJQpjtfMxNquo9yRBR/zz4z52G7mYt2Dg==
dependencies:
"@fortawesome/fontawesome-free" "^5.12.0"
"@fortawesome/fontawesome-svg-core" "^1.2.22"
@ -1098,10 +1098,10 @@
prop-types "^15.7.2"
react-dates "^21.1.0"
"@buffetjs/utils@3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@buffetjs/utils/-/utils-3.3.1.tgz#903e29c8e69448418ec7bc940cf1c8eb104061c6"
integrity sha512-0EKrGc/yWNrI9w93HH0pM+EYTEsq98pf+vZOU8R2bWHXHeEodYC73Jr1wKwo7Z9ux/30Bmz5utgcHunnoj72SQ==
"@buffetjs/utils@3.3.1-next.2":
version "3.3.1-next.2"
resolved "https://registry.yarnpkg.com/@buffetjs/utils/-/utils-3.3.1-next.2.tgz#da72b8d4e47df60b5debc5e066380745d5908ff0"
integrity sha512-9+lWadHP75sTO0GLwgdrK6AEDA8a4MIQnTqAsIDnIbSutdUZSC8ieJdHX+YzN5T4DuMXrMW//AO264h46zXyvQ==
dependencies:
lodash "4.17.19"
yup "^0.27.0"