` component into a `
// in src/posts.tsx
import {
List,
- Datagrid,
- TextField,
+ DataTable,
ReferenceField,
EditButton,
Edit,
diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
index 10c40540cba..ec2d2b6a7b0 100644
--- a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
+++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx
@@ -1,52 +1,44 @@
import * as React from 'react';
import expect from 'expect';
-import { render, screen, waitFor } from '@testing-library/react';
-import { CoreAdminContext } from 'ra-core';
+import { render, screen } from '@testing-library/react';
-import { ShowGuesser } from './ShowGuesser';
-import { ThemeProvider } from '../theme/ThemeProvider';
+import { ShowGuesser } from './ShowGuesser.stories';
describe('', () => {
it('should log the guessed Show view based on the fetched record', async () => {
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
- const dataProvider = {
- getOne: () =>
- Promise.resolve({
- data: {
- id: 123,
- author: 'john doe',
- post_id: 6,
- score: 3,
- body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.",
- created_at: new Date('2012-08-02'),
- tags_ids: [1, 2],
- },
- }),
- };
- render(
-
-
-
-
-
- );
- await waitFor(() => {
- screen.getByText('john doe');
- });
+ render();
+ await screen.findByText('john doe');
expect(logSpy).toHaveBeenCalledWith(`Guessed Show:
-import { DateField, NumberField, ReferenceArrayField, ReferenceField, Show, SimpleShowLayout, TextField } from 'react-admin';
+import { ArrayField, BooleanField, DataTable, DateField, EmailField, NumberField, ReferenceArrayField, ReferenceField, RichTextField, Show, SimpleShowLayout, TextField, UrlField } from 'react-admin';
-export const CommentShow = () => (
+export const BookShow = () => (
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);`);
diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx
new file mode 100644
index 00000000000..80d9bb66be0
--- /dev/null
+++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react';
+import { Admin } from 'react-admin';
+import { Resource, TestMemoryRouter } from 'ra-core';
+import fakeRestProvider from 'ra-data-fakerest';
+
+import { ShowGuesser as RAShowGuesser } from './ShowGuesser';
+
+export default { title: 'ra-ui-materialui/detail/ShowGuesser' };
+
+const data = {
+ books: [
+ {
+ id: 123,
+ authors: [
+ { id: 1, name: 'john doe', dob: '1990-01-01' },
+ { id: 2, name: 'jane doe', dob: '1992-01-01' },
+ ],
+ post_id: 6,
+ score: 3,
+ body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.",
+ description: `War and Peace is a novel by the Russian author Leo Tolstoy,
+published serially, then in its entirety in 1869.
+It is regarded as one of Tolstoy's finest literary achievements and remains a classic of world literature.
`,
+ created_at: new Date('2012-08-02'),
+ tags_ids: [1, 2],
+ url: 'https://www.myshop.com/tags/top-seller',
+ email: 'doe@production.com',
+ isAlreadyPublished: true,
+ },
+ ],
+ tags: [
+ { id: 1, name: 'top seller' },
+ { id: 2, name: 'new' },
+ ],
+ posts: [
+ { id: 6, title: 'War and Peace', body: 'A great novel by Leo Tolstoy' },
+ ],
+};
+
+const ShowGuesserWithProdLogs = () => ;
+
+export const ShowGuesser = () => (
+
+
+
+
+
+);
diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx
index e21d81be0cc..d3ecc4fc15a 100644
--- a/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx
+++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx
@@ -72,10 +72,16 @@ const ShowViewGuesser = (
)
.sort();
+ const importsToLog = components.includes('DataTable')
+ ? components.filter(
+ component => !component.startsWith('DataTable.')
+ )
+ : components;
+
console.log(
`Guessed Show:
-import { ${components.join(', ')} } from 'react-admin';
+import { ${importsToLog.join(', ')} } from 'react-admin';
export const ${capitalize(singularize(resource))}Show = () => (
diff --git a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
index 7e39fdc5996..ce894912d73 100644
--- a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { ReactNode } from 'react';
-import { Datagrid } from '../list/datagrid/Datagrid';
+import type { InferredElement, InferredTypeMap, InputProps } from 'ra-core';
import {
ArrayField,
BooleanField,
@@ -17,8 +17,7 @@ import {
ChipField,
} from '../field';
import { SimpleShowLayout, SimpleShowLayoutProps } from './SimpleShowLayout';
-import { InferredElement, InferredTypeMap, InputProps } from 'ra-core';
-import { SingleFieldList } from '../list';
+import { DataTable, SingleFieldList } from '../list';
export const showFieldTypes: InferredTypeMap = {
show: {
@@ -30,18 +29,31 @@ ${children.map(child => ` ${child.getRepresentation()}`).join('\n')}
`,
},
array: {
- component: ({
- children,
- ...props
- }: { children: ReactNode } & InputProps) => (
+ component: ({ children, ...props }: { children } & InputProps) => (
- {children}
+
+ {children && children.length > 0
+ ? children.map((child, index) => (
+
+ {child}
+
+ ))
+ : children}
+
),
representation: (props: InputProps, children: InferredElement[]) =>
- `${children
- .map(child => child.getRepresentation())
- .join('\n')}`,
+ `
+
+ ${children
+ .map(
+ child => `
+ ${child.getRepresentation()}
+ `
+ )
+ .join('\n ')}
+
+ `,
},
boolean: {
component: BooleanField,
diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
index 1f898e5ea73..0c8f0fcd04c 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx
@@ -1,57 +1,81 @@
import * as React from 'react';
import expect from 'expect';
-import { render, screen, waitFor } from '@testing-library/react';
-import { CoreAdminContext, testDataProvider } from 'ra-core';
-
-import { ListGuesser } from './ListGuesser';
-import { ThemeProvider } from '../theme/ThemeProvider';
+import { fireEvent, render, screen } from '@testing-library/react';
+import { ManyResources } from './ListGuesser.stories';
describe('', () => {
- it('should log the guessed List view based on the fetched records', async () => {
+ it('should log the guessed List views based on the fetched records', async () => {
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
- const dataProvider = testDataProvider({
- getList: () =>
- Promise.resolve({
- data: [
- {
- id: 123,
- author: 'john doe',
- post_id: 6,
- score: 3,
- body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.",
- created_at: new Date('2012-08-02'),
- tags_ids: [1, 2],
- },
- ],
- total: 1,
- }),
- getMany: () => Promise.resolve({ data: [], total: 0 }),
- });
- render(
-
-
-
-
-
- );
- await waitFor(() => {
- screen.getByText('john doe');
- });
+ render();
+ await screen.findAllByText('top seller', undefined, { timeout: 2000 });
+ expect(logSpy).toHaveBeenCalledWith(`Guessed List:
+
+import { DataTable, DateField, EmailField, List, ReferenceArrayField, ReferenceField } from 'react-admin';
+
+export const ProductList = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);`);
+ logSpy.mockClear();
+
+ fireEvent.click(screen.getByText('Categories'));
+ await screen.findByText('Jeans');
+ expect(logSpy).toHaveBeenCalledWith(`Guessed List:
+
+import { ArrayField, BooleanField, ChipField, DataTable, List, SingleFieldList } from 'react-admin';
+
+export const CategoryList = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);`);
+
+ logSpy.mockClear();
+ fireEvent.click(screen.getByText('Tags'));
+ await screen.findByText('top seller');
expect(logSpy).toHaveBeenCalledWith(`Guessed List:
-import { Datagrid, DateField, List, NumberField, ReferenceArrayField, ReferenceField, TextField } from 'react-admin';
+import { DataTable, List, UrlField } from 'react-admin';
-export const CommentList = () => (
+export const TagList = () => (
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);`);
});
diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx
index 61c93ec4168..165844e50e2 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx
@@ -21,6 +21,8 @@ const data = {
price: 45.99,
category_id: 1,
tags_ids: [1],
+ last_update: new Date('2023-10-01').toISOString(),
+ email: 'office.jeans@myshop.com',
},
{
id: 2,
@@ -28,6 +30,8 @@ const data = {
price: 69.99,
category_id: 1,
tags_ids: [2, 3],
+ last_update: new Date('2023-11-01').toISOString(),
+ email: 'black.elegance.jeans@myshop.com',
},
{
id: 3,
@@ -35,6 +39,8 @@ const data = {
price: 55.99,
category_id: 1,
tags_ids: [2, 4],
+ last_update: new Date('2023-12-01').toISOString(),
+ email: 'slim.fit.jeans@myshop.com',
},
{
id: 4,
@@ -42,6 +48,8 @@ const data = {
price: 15.99,
category_id: 2,
tags_ids: [1, 4, 3],
+ last_update: new Date('2023-10-15').toISOString(),
+ email: 'basic.t.shirt@myshop.com',
},
{
id: 5,
@@ -49,30 +57,93 @@ const data = {
price: 19.99,
category_id: 6,
tags_ids: [1, 4, 3],
+ last_update: new Date('2023-10-15').toISOString(),
+ email: 'basic.cap@myshop.com',
},
],
categories: [
- { id: 1, name: 'Jeans' },
- { id: 2, name: 'T-Shirts' },
- { id: 3, name: 'Jackets' },
- { id: 4, name: 'Shoes' },
- { id: 5, name: 'Accessories' },
- { id: 6, name: 'Hats' },
- { id: 7, name: 'Socks' },
- { id: 8, name: 'Shirts' },
- { id: 9, name: 'Sweaters' },
- { id: 10, name: 'Trousers' },
- { id: 11, name: 'Coats' },
- { id: 12, name: 'Dresses' },
- { id: 13, name: 'Skirts' },
- { id: 14, name: 'Swimwear' },
- { id: 15, name: 'Bags' },
+ {
+ id: 1,
+ name: 'Jeans',
+ alternativeName: [{ name: 'denims' }, { name: 'pants' }],
+ isVeganProduction: true,
+ },
+ {
+ id: 2,
+ name: 'T-Shirts',
+ alternativeName: [{ name: 'polo' }, { name: 'tee shirt' }],
+ isVeganProduction: false,
+ },
+ {
+ id: 3,
+ name: 'Jackets',
+ alternativeName: [{ name: 'coat' }, { name: 'blazers' }],
+ isVeganProduction: false,
+ },
+ {
+ id: 4,
+ name: 'Shoes',
+ alternativeName: [{ name: 'sneakers' }, { name: 'moccasins' }],
+ isVeganProduction: false,
+ },
+ {
+ id: 5,
+ name: 'Accessories',
+ alternativeName: [{ name: 'jewelry' }, { name: 'belts' }],
+ isVeganProduction: true,
+ },
+ {
+ id: 6,
+ name: 'Hats',
+ alternativeName: [{ name: 'caps' }, { name: 'headwear' }],
+ isVeganProduction: true,
+ },
+ {
+ id: 7,
+ name: 'Socks',
+ alternativeName: [{ name: 'stockings' }, { name: 'hosiery' }],
+ isVeganProduction: false,
+ },
+ {
+ id: 8,
+ name: 'Bags',
+ alternativeName: [{ name: 'handbags' }, { name: 'purses' }],
+ isVeganProduction: false,
+ },
+ {
+ id: 9,
+ name: 'Dresses',
+ alternativeName: [{ name: 'robes' }, { name: 'gowns' }],
+ isVeganProduction: false,
+ },
+ {
+ id: 10,
+ name: 'Skirts',
+ alternativeName: [{ name: 'tutus' }, { name: 'kilts' }],
+ isVeganProduction: false,
+ },
],
tags: [
- { id: 1, name: 'top seller' },
- { id: 2, name: 'new' },
- { id: 3, name: 'sale' },
- { id: 4, name: 'promotion' },
+ {
+ id: 1,
+ name: 'top seller',
+ url: 'https://www.myshop.com/tags/top-seller',
+ },
+ {
+ id: 2,
+ name: 'new',
+ url: 'https://www.myshop.com/tags/new',
+ },
+ {
+ id: 3,
+ name: 'sale',
+ url: 'https://www.myshop.com/tags/sale',
+ },
+ {
+ id: 4,
+ name: 'promotion',
+ url: 'https://www.myshop.com/tags/promotion',
+ },
],
};
@@ -119,6 +190,8 @@ const delayedDataProvider = fakeRestProvider(
300
);
+const ListGuesserWithProdLogs = () => ;
+
export const ManyResources = () => (
(
diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
index 595194e7c4f..14a0b34b9cf 100644
--- a/packages/ra-ui-materialui/src/list/ListGuesser.tsx
+++ b/packages/ra-ui-materialui/src/list/ListGuesser.tsx
@@ -18,11 +18,11 @@ import { listFieldTypes } from './listFieldTypes';
import { capitalize, singularize } from 'inflection';
/**
- * List component rendering a based on the result of the
+ * List component rendering a based on the result of the
* dataProvider.getList() call.
*
* The result (choice and type of columns) isn't configurable, but the
- * outputs the it has guessed to the console so that
+ * outputs the it has guessed to the console so that
* developers can start from there.
*
* To be used as the list prop of a .
@@ -132,10 +132,16 @@ const ListViewGuesser = (
.sort();
if (enableLog) {
+ const importsToLog = components.includes('DataTable')
+ ? components.filter(
+ component => !component.startsWith('DataTable.')
+ )
+ : components;
+
console.log(
`Guessed List:
-import { ${components.join(', ')} } from 'react-admin';
+import { ${importsToLog.join(', ')} } from 'react-admin';
export const ${capitalize(singularize(resource))}List = () => (
diff --git a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
index 69b1592236c..dcb4745ff10 100644
--- a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
+++ b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { Datagrid } from './datagrid';
+import { DataTable } from './datatable';
import { SingleFieldList } from './SingleFieldList';
import {
ArrayField,
@@ -7,80 +7,117 @@ import {
ChipField,
DateField,
EmailField,
- NumberField,
ReferenceField,
ReferenceArrayField,
- TextField,
UrlField,
ArrayFieldProps,
+ TextField,
} from '../field';
export const listFieldTypes = {
table: {
component: props => {
- return ;
+ return ;
},
- representation: (_props, children) => `
+ representation: (_props, children) => `
${children.map(child => ` ${child.getRepresentation()}`).join('\n')}
- `,
+
`,
},
array: {
component: ({ children, ...props }: ArrayFieldProps) => {
const childrenArray = React.Children.toArray(children);
return (
-
-
- 0 &&
- React.isValidElement(childrenArray[0]) &&
- childrenArray[0].props.source
- }
- />
-
-
+
+
+
+ 0 &&
+ React.isValidElement(childrenArray[0]) &&
+ childrenArray[0].props.source
+ }
+ />
+
+
+
);
},
representation: (props, children) =>
- ``,
+ `
+
+
+
+
+
+ `,
},
boolean: {
- component: BooleanField,
- representation: props => ``,
+ component: props => (
+
+
+
+ ),
+ representation: props =>
+ `
+
+ `,
},
date: {
- component: DateField,
- representation: props => ``,
+ component: props => (
+
+
+
+ ),
+ representation: props =>
+ `
+
+ `,
},
email: {
- component: EmailField,
- representation: props => ``,
+ component: props => (
+
+
+
+ ),
+ representation: props =>
+ `
+
+ `,
},
id: {
- component: TextField,
- representation: props => ``,
+ component: props => ,
+ representation: props => ``,
},
number: {
- component: NumberField,
- representation: props => ``,
+ component: DataTable.NumberCol,
+ representation: props =>
+ ``,
},
reference: {
- component: ReferenceField,
+ component: props => (
+
+
+
+ ),
representation: props =>
- ``,
+ `
+
+ `,
},
referenceChild: {
component: () => ,
representation: () => ``,
},
referenceArray: {
- component: ReferenceArrayField,
+ component: props => (
+
+
+
+ ),
representation: props =>
- ``,
+ `
+
+ `,
},
referenceArrayChild: {
component: () => (
@@ -89,15 +126,24 @@ ${children.map(child => ` ${child.getRepresentation()}`).join('\n')}
),
representation: () =>
- ``,
+ `
+
+ `,
},
richText: undefined, // never display a rich text field in a datagrid
string: {
- component: TextField,
- representation: props => ``,
+ component: DataTable.Col,
+ representation: props => ``,
},
url: {
- component: UrlField,
- representation: props => ``,
+ component: props => (
+
+
+
+ ),
+ representation: props =>
+ `
+
+ `,
},
};