diff --git a/examples/demo/package.json b/examples/demo/package.json index 73314703b88..42984701dba 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -42,6 +42,7 @@ "not op_mini all" ], "devDependencies": { + "@types/fetch-mock": "^7.3.2", "@types/classnames": "^2.2.9", "@types/jest": "^24.0.23", "@types/node": "^12.12.14", diff --git a/examples/demo/src/App.tsx b/examples/demo/src/App.tsx index 37b3d714409..fb5259e515f 100644 --- a/examples/demo/src/App.tsx +++ b/examples/demo/src/App.tsx @@ -38,10 +38,10 @@ const App = () => { const fetchDataProvider = async () => { restoreFetch = await fakeServerFactory( - process.env.REACT_APP_DATA_PROVIDER + process.env.REACT_APP_DATA_PROVIDER || '' ); const dataProviderInstance = await dataProviderFactory( - process.env.REACT_APP_DATA_PROVIDER + process.env.REACT_APP_DATA_PROVIDER || '' ); setDataProvider( // GOTCHA: dataProviderInstance can be a function diff --git a/examples/demo/src/data-generator-retail.d.ts b/examples/demo/src/data-generator-retail.d.ts new file mode 100644 index 00000000000..47eadcd8dd6 --- /dev/null +++ b/examples/demo/src/data-generator-retail.d.ts @@ -0,0 +1 @@ +declare module 'data-generator-retail'; diff --git a/examples/demo/src/dataProvider/graphql.js b/examples/demo/src/dataProvider/graphql.ts similarity index 58% rename from examples/demo/src/dataProvider/graphql.js rename to examples/demo/src/dataProvider/graphql.ts index 483a3561620..baf3fe635ec 100644 --- a/examples/demo/src/dataProvider/graphql.js +++ b/examples/demo/src/dataProvider/graphql.ts @@ -1,9 +1,16 @@ +import { ApolloQueryResult } from 'apollo-client'; import buildApolloClient, { buildQuery as buildQueryFactory, } from 'ra-data-graphql-simple'; -import { DELETE } from 'ra-core'; +import { DELETE, LegacyDataProvider } from 'ra-core'; import gql from 'graphql-tag'; -const getGqlResource = resource => { +import { + IntrospectionField, + IntrospectionSchema, + IntrospectionType, +} from 'graphql'; + +const getGqlResource = (resource: string) => { switch (resource) { case 'customers': return 'Customer'; @@ -28,7 +35,20 @@ const getGqlResource = resource => { } }; -const customBuildQuery = introspectionResults => { +type IntrospectionResource = IntrospectionType & { + [key: string]: IntrospectionField; +}; + +interface IntrospectionResults { + types: IntrospectionType[]; + queries: IntrospectionField[]; + resources: IntrospectionResource[]; + schema: IntrospectionSchema; +} + +const customBuildQuery = ( + introspectionResults: IntrospectionResults +): LegacyDataProvider => { const buildQuery = buildQueryFactory(introspectionResults); return (type, resource, params) => { @@ -38,7 +58,7 @@ const customBuildQuery = introspectionResults => { remove${resource}(id: $id) }`, variables: { id: params.id }, - parseResponse: ({ data }) => { + parseResponse: ({ data }: ApolloQueryResult) => { if (data[`remove${resource}`]) { return { data: { id: params.id } }; } @@ -59,11 +79,17 @@ export default () => { }, introspection: { operationNames: { - [DELETE]: resource => `remove${resource.name}`, + [DELETE]: (resource: IntrospectionType) => + `remove${resource.name}`, }, }, buildQuery: customBuildQuery, - }).then(dataProvider => (type, resource, params) => - dataProvider(type, getGqlResource(resource), params) + }).then( + (dataProvider: LegacyDataProvider) => ( + ...rest: Parameters + ) => { + const [type, resource, params] = rest; + return dataProvider(type, getGqlResource(resource), params); + } ); }; diff --git a/examples/demo/src/dataProvider/index.js b/examples/demo/src/dataProvider/index.ts similarity index 86% rename from examples/demo/src/dataProvider/index.js rename to examples/demo/src/dataProvider/index.ts index dd45b680bb9..b4046fa916f 100644 --- a/examples/demo/src/dataProvider/index.js +++ b/examples/demo/src/dataProvider/index.ts @@ -1,4 +1,4 @@ -export default type => { +export default (type: string) => { switch (type) { case 'graphql': return import('./graphql').then(factory => factory.default()); diff --git a/examples/demo/src/dataProvider/rest.js b/examples/demo/src/dataProvider/rest.ts similarity index 71% rename from examples/demo/src/dataProvider/rest.js rename to examples/demo/src/dataProvider/rest.ts index bf024cf45a8..2f816a84004 100644 --- a/examples/demo/src/dataProvider/rest.js +++ b/examples/demo/src/dataProvider/rest.ts @@ -6,10 +6,13 @@ const delayedDataProvider = new Proxy(restProvider, { get: (target, name, self) => name === 'then' // as we await for the dataProvider, JS calls then on it. We must trap that call or else the dataProvider will be called with the then method ? self - : (resource, params) => + : (resource: string, params: any) => new Promise(resolve => setTimeout( - () => resolve(restProvider[name](resource, params)), + () => + resolve( + restProvider[name as string](resource, params) + ), 500 ) ), diff --git a/examples/demo/src/fakeServer/graphql.js b/examples/demo/src/fakeServer/graphql.ts similarity index 100% rename from examples/demo/src/fakeServer/graphql.js rename to examples/demo/src/fakeServer/graphql.ts diff --git a/examples/demo/src/fakeServer/index.js b/examples/demo/src/fakeServer/index.ts similarity index 86% rename from examples/demo/src/fakeServer/index.js rename to examples/demo/src/fakeServer/index.ts index 1481189600a..79e5427ed5b 100644 --- a/examples/demo/src/fakeServer/index.js +++ b/examples/demo/src/fakeServer/index.ts @@ -1,4 +1,4 @@ -export default type => { +export default (type: string) => { switch (type) { case 'graphql': return import('./graphql').then(factory => factory.default()); diff --git a/examples/demo/src/fakeServer/rest.js b/examples/demo/src/fakeServer/rest.ts similarity index 100% rename from examples/demo/src/fakeServer/rest.js rename to examples/demo/src/fakeServer/rest.ts diff --git a/examples/demo/src/fakerest.d.ts b/examples/demo/src/fakerest.d.ts new file mode 100644 index 00000000000..b88450de3f9 --- /dev/null +++ b/examples/demo/src/fakerest.d.ts @@ -0,0 +1 @@ +declare module 'fakerest'; diff --git a/examples/demo/src/json-graphql-server.d.ts b/examples/demo/src/json-graphql-server.d.ts new file mode 100644 index 00000000000..4280bbded89 --- /dev/null +++ b/examples/demo/src/json-graphql-server.d.ts @@ -0,0 +1 @@ +declare module 'json-graphql-server'; diff --git a/examples/demo/src/orders/MobileGrid.js b/examples/demo/src/orders/MobileGrid.tsx similarity index 91% rename from examples/demo/src/orders/MobileGrid.js rename to examples/demo/src/orders/MobileGrid.tsx index 5cadfd3f975..978a5745523 100644 --- a/examples/demo/src/orders/MobileGrid.js +++ b/examples/demo/src/orders/MobileGrid.tsx @@ -1,5 +1,5 @@ // in src/comments.js -import React from 'react'; +import React, { FC } from 'react'; import Card from '@material-ui/core/Card'; import CardHeader from '@material-ui/core/CardHeader'; import CardContent from '@material-ui/core/CardContent'; @@ -14,6 +14,7 @@ import { } from 'react-admin'; import CustomerReferenceField from '../visitors/CustomerReferenceField'; +import { RecordMap, Identifier, Record } from 'ra-core'; const useListStyles = makeStyles(theme => ({ card: { @@ -24,22 +25,33 @@ const useListStyles = makeStyles(theme => ({ }, cardTitleContent: { display: 'flex', - flexDirection: 'rows', + flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, cardContent: theme.typography.body1, cardContentRow: { display: 'flex', - flexDirection: 'rows', + flexDirection: 'row', alignItems: 'center', margin: '0.5rem 0', }, })); -const MobileGrid = ({ ids, data, basePath }) => { +interface MobileGridProps { + ids?: Identifier[]; + data?: RecordMap; + basePath?: string; +} + +const MobileGrid: FC = ({ ids, data, basePath }) => { const translate = useTranslate(); const classes = useListStyles(); + + if (!ids || !data || !basePath) { + return null; + } + return (
{ids.map(id => ( @@ -88,7 +100,6 @@ const MobileGrid = ({ ids, data, basePath }) => { record={data[id]} source="total" options={{ style: 'currency', currency: 'USD' }} - className={classes.total} /> diff --git a/examples/demo/src/orders/NbItemsField.js b/examples/demo/src/orders/NbItemsField.js deleted file mode 100644 index 18900334b11..00000000000 --- a/examples/demo/src/orders/NbItemsField.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { FunctionField } from 'react-admin'; - -const render = record => record.basket.length; - -const NbItemsField = props => ; - -NbItemsField.defaultProps = { - label: 'Nb Items', - textAlign: 'right', -}; - -export default NbItemsField; diff --git a/examples/demo/src/orders/NbItemsField.tsx b/examples/demo/src/orders/NbItemsField.tsx new file mode 100644 index 00000000000..0620301b346 --- /dev/null +++ b/examples/demo/src/orders/NbItemsField.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; +import { FunctionField } from 'react-admin'; +import { Order, FieldProps } from '../types'; + +const render = (record: Order) => record.basket.length; + +interface NbItemsFieldProps extends FieldProps { + textAlign?: string; +} + +const NbItemsField: FC = props => ( + +); + +NbItemsField.defaultProps = { + label: 'Nb Items', + textAlign: 'right', +}; + +export default NbItemsField; diff --git a/examples/demo/src/orders/OrderEdit.js b/examples/demo/src/orders/OrderEdit.tsx similarity index 82% rename from examples/demo/src/orders/OrderEdit.js rename to examples/demo/src/orders/OrderEdit.tsx index 1c3ad566f6d..926d8343d95 100644 --- a/examples/demo/src/orders/OrderEdit.js +++ b/examples/demo/src/orders/OrderEdit.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { FC } from 'react'; import { AutocompleteInput, BooleanInput, @@ -10,25 +10,30 @@ import { useTranslate, } from 'react-admin'; import { makeStyles } from '@material-ui/core/styles'; +import { Order, Customer, EditComponentProps } from '../types'; import Basket from './Basket'; -const OrderTitle = ({ record }) => { +interface OrderTitleProps { + record?: Order; +} + +const OrderTitle: FC = ({ record }) => { const translate = useTranslate(); - return ( + return record ? ( {translate('resources.commands.title', { reference: record.reference, })} - ); + ) : null; }; const useEditStyles = makeStyles({ root: { alignItems: 'flex-start' }, }); -const OrderEdit = props => { +const OrderEdit: FC = props => { const classes = useEditStyles(); return ( { + optionText={(choice: Customer) => `${choice.first_name} ${choice.last_name}` } /> diff --git a/examples/demo/src/orders/OrderList.js b/examples/demo/src/orders/OrderList.js deleted file mode 100644 index 057f922361b..00000000000 --- a/examples/demo/src/orders/OrderList.js +++ /dev/null @@ -1,184 +0,0 @@ -import React, { Fragment } from 'react'; -import { - AutocompleteInput, - BooleanField, - Datagrid, - DateField, - DateInput, - EditButton, - Filter, - List, - NullableBooleanInput, - NumberField, - ReferenceInput, - SearchInput, - TextField, - TextInput, -} from 'react-admin'; -import { - makeStyles, - useMediaQuery, - Divider, - Tabs, - Tab, -} from '@material-ui/core'; - -import NbItemsField from './NbItemsField'; -import CustomerReferenceField from '../visitors/CustomerReferenceField'; -import MobileGrid from './MobileGrid'; - -const OrderFilter = props => ( - - - - - choice.first_name && choice.last_name - ? `${choice.first_name} ${choice.last_name}` - : '' - } - /> - - - - - - -); - -const useDatagridStyles = makeStyles({ - total: { fontWeight: 'bold' }, -}); - -class TabbedDatagrid extends React.Component { - tabs = [ - { id: 'ordered', name: 'ordered' }, - { id: 'delivered', name: 'delivered' }, - { id: 'cancelled', name: 'cancelled' }, - ]; - - state = { ordered: [], delivered: [], cancelled: [] }; - - static getDerivedStateFromProps(props, state) { - if (props.ids !== state[props.filterValues.status]) { - return { ...state, [props.filterValues.status]: props.ids }; - } - return null; - } - - handleChange = (event, value) => { - const { filterValues, setFilters } = this.props; - setFilters({ ...filterValues, status: value }); - }; - - render() { - const { isXSmall, classes, filterValues, ...props } = this.props; - - return ( - - - {this.tabs.map(choice => ( - - ))} - - - {isXSmall ? ( - - ) : ( -
- {filterValues.status === 'ordered' && ( - - - - - - - - )} - {filterValues.status === 'delivered' && ( - - - - - - - - - - )} - {filterValues.status === 'cancelled' && ( - - - - - - - - - - )} -
- )} -
- ); - } -} - -const StyledTabbedDatagrid = props => { - const classes = useDatagridStyles(); - const isXSmall = useMediaQuery(theme => theme.breakpoints.down('xs')); - return ; -}; - -const OrderList = ({ classes, ...props }) => ( - } - > - - -); - -export default OrderList; diff --git a/examples/demo/src/orders/OrderList.tsx b/examples/demo/src/orders/OrderList.tsx new file mode 100644 index 00000000000..e463fd6457e --- /dev/null +++ b/examples/demo/src/orders/OrderList.tsx @@ -0,0 +1,227 @@ +import React, { FC, Fragment, useCallback, useEffect, useState } from 'react'; +import { + AutocompleteInput, + BooleanField, + Datagrid, + DateField, + DateInput, + EditButton, + Filter, + List, + NullableBooleanInput, + NumberField, + ReferenceInput, + SearchInput, + TextField, + TextInput, +} from 'react-admin'; +import { + makeStyles, + useMediaQuery, + Divider, + Tabs, + Tab, + Theme, +} from '@material-ui/core'; + +import NbItemsField from './NbItemsField'; +import CustomerReferenceField from '../visitors/CustomerReferenceField'; +import MobileGrid from './MobileGrid'; +import { + Customer, + FilterProps, + OrderStatus, + DatagridProps, + Order, + ListComponentProps, +} from '../types'; +import { Identifier } from 'ra-core'; + +interface FilterParams { + q?: string; + customer_id?: string; + date_gte?: string; + date_lte?: string; + total_gte?: string; + returned?: boolean; + status?: OrderStatus; +} + +const OrderFilter: FC> = props => ( + + + + + choice.first_name && choice.last_name + ? `${choice.first_name} ${choice.last_name}` + : '' + } + /> + + + + + + +); + +const useDatagridStyles = makeStyles({ + total: { fontWeight: 'bold' }, +}); + +const tabs = [ + { id: 'ordered', name: 'ordered' }, + { id: 'delivered', name: 'delivered' }, + { id: 'cancelled', name: 'cancelled' }, +]; + +interface TabbedDatagridProps extends DatagridProps {} + +const TabbedDatagrid: FC = ({ + ids, + filterValues, + setFilters, + displayedFilters, + ...rest +}) => { + const classes = useDatagridStyles(); + const isXSmall = useMediaQuery(theme => + theme.breakpoints.down('xs') + ); + const [ordered, setOrdered] = useState([]); + const [delivered, setDelivered] = useState([]); + const [cancelled, setCancelled] = useState([]); + + useEffect(() => { + if (ids && ids !== filterValues.status) { + switch (filterValues.status) { + case 'ordered': + setOrdered(ids); + break; + case 'delivered': + setDelivered(ids); + break; + case 'cancelled': + setCancelled(ids); + break; + } + } + }, [ids, filterValues.status]); + + const handleChange = useCallback( + (event: React.ChangeEvent<{}>, value: any) => { + setFilters && + setFilters( + { ...filterValues, status: value }, + displayedFilters + ); + }, + [displayedFilters, filterValues, setFilters] + ); + + const selectedIds = + filterValues.status === 'ordered' + ? ordered + : filterValues.status === 'delivered' + ? delivered + : cancelled; + + return ( + + + {tabs.map(choice => ( + + ))} + + + {isXSmall ? ( + + ) : ( +
+ {filterValues.status === 'ordered' && ( + + + + + + + + )} + {filterValues.status === 'delivered' && ( + + + + + + + + + + )} + {filterValues.status === 'cancelled' && ( + + + + + + + + + + )} +
+ )} +
+ ); +}; + +const OrderList: FC = props => ( + } + > + + +); + +export default OrderList; diff --git a/examples/demo/src/orders/index.js b/examples/demo/src/orders/index.ts similarity index 100% rename from examples/demo/src/orders/index.js rename to examples/demo/src/orders/index.ts diff --git a/examples/demo/src/ra-data-graphql-simple.d.ts b/examples/demo/src/ra-data-graphql-simple.d.ts new file mode 100644 index 00000000000..adaa2fcbc9b --- /dev/null +++ b/examples/demo/src/ra-data-graphql-simple.d.ts @@ -0,0 +1 @@ +declare module 'ra-data-graphql-simple'; diff --git a/examples/demo/src/reviews/StarRatingField.tsx b/examples/demo/src/reviews/StarRatingField.tsx index 8e59119e67f..6203738ad9d 100644 --- a/examples/demo/src/reviews/StarRatingField.tsx +++ b/examples/demo/src/reviews/StarRatingField.tsx @@ -20,7 +20,7 @@ const useStyles = makeStyles({ }); interface OwnProps { - size: 'large' | 'small'; + size?: 'large' | 'small'; } const StarRatingField: FC = ({ diff --git a/examples/demo/src/routes.js b/examples/demo/src/routes.tsx similarity index 100% rename from examples/demo/src/routes.js rename to examples/demo/src/routes.tsx diff --git a/examples/demo/src/themeReducer.js b/examples/demo/src/themeReducer.js deleted file mode 100644 index 7c825c5f91f..00000000000 --- a/examples/demo/src/themeReducer.js +++ /dev/null @@ -1,8 +0,0 @@ -import { CHANGE_THEME } from './configuration/actions'; - -export default (previousState = 'light', { type, payload }) => { - if (type === CHANGE_THEME) { - return payload; - } - return previousState; -}; diff --git a/examples/demo/src/themeReducer.ts b/examples/demo/src/themeReducer.ts new file mode 100644 index 00000000000..6259ac65241 --- /dev/null +++ b/examples/demo/src/themeReducer.ts @@ -0,0 +1,20 @@ +import { Reducer } from 'redux'; +import { CHANGE_THEME, changeTheme } from './configuration/actions'; +import { ThemeName } from './types'; + +type State = ThemeName; +type Action = + | ReturnType + | { type: 'OTHER_ACTION'; payload?: any }; + +const themeReducer: Reducer = ( + previousState = 'light', + action +) => { + if (action.type === CHANGE_THEME) { + return action.payload; + } + return previousState; +}; + +export default themeReducer; diff --git a/examples/demo/src/types.ts b/examples/demo/src/types.ts index ffdd27b048f..ae18e61dc89 100644 --- a/examples/demo/src/types.ts +++ b/examples/demo/src/types.ts @@ -1,4 +1,15 @@ -import { ReduxState, Record, Identifier } from 'ra-core'; +import { + useListController, + ReduxState, + Record, + Identifier, + usePermissions, +} from 'ra-core'; +import { RouteComponentProps } from 'react-router-dom'; +import { StaticContext } from 'react-router'; +import * as H from 'history'; +import { ClassNameMap } from '@material-ui/core/styles/withStyles'; +import { ListControllerProps } from 'ra-core/esm/controller/useListController'; export type ThemeName = 'light' | 'dark'; @@ -40,7 +51,11 @@ export interface Customer extends Record { total_spent: number; } +export type OrderStatus = 'ordered' | 'delivered' | 'cancelled'; + export interface Order extends Record { + date: string; + status: OrderStatus; basket: BasketItem[]; } @@ -57,8 +72,71 @@ export interface FieldProps { label?: string; record?: T; source?: string; + basePath?: string; } export interface Review extends Record { + date: string; customer_id: string; } + +export interface ResourceMatch { + id: string; + [k: string]: string; +} + +type FilterClassKey = 'button' | 'form'; + +export interface FilterProps { + classes?: ClassNameMap; + context?: 'form' | 'button'; + displayedFilters?: { [K in keyof Params]?: boolean }; + filterValues?: Params; + hideFilter?: ReturnType['hideFilter']; + setFilters?: ReturnType['setFilters']; + showFilter?: ReturnType['showFilter']; + resource?: string; +} + +export interface DatagridProps + extends Partial> { + hasBulkActions?: boolean; +} + +export interface ResourceComponentProps< + Params extends { [K in keyof Params]?: string } = {}, + C extends StaticContext = StaticContext, + S = H.LocationState +> extends RouteComponentProps { + resource: string; + options: object; + hasList: boolean; + hasEdit: boolean; + hasShow: boolean; + hasCreate: boolean; + permissions: ReturnType['permissions']; +} + +export interface ListComponentProps extends ResourceComponentProps {} + +export interface EditComponentProps< + Params extends ResourceMatch = { id: string }, + C extends StaticContext = StaticContext, + S = H.LocationState +> extends ResourceComponentProps { + id: string; +} + +export interface ShowComponentProps< + Params extends ResourceMatch = { id: string }, + C extends StaticContext = StaticContext, + S = H.LocationState +> extends ResourceComponentProps { + id: string; +} + +declare global { + interface Window { + restServer: any; + } +} diff --git a/examples/demo/src/visitors/Aside.js b/examples/demo/src/visitors/Aside.tsx similarity index 88% rename from examples/demo/src/visitors/Aside.js rename to examples/demo/src/visitors/Aside.tsx index b6db569eb31..f20f1a46550 100644 --- a/examples/demo/src/visitors/Aside.js +++ b/examples/demo/src/visitors/Aside.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { FC } from 'react'; import { NumberField, TextField, @@ -27,6 +27,8 @@ import { makeStyles } from '@material-ui/core/styles'; import ProductReferenceField from '../products/ProductReferenceField'; import StarRatingField from '../reviews/StarRatingField'; +import { Record, RecordMap, Identifier } from 'ra-core'; +import { Order as OrderRecord, Review as ReviewRecord } from '../types'; const useAsideStyles = makeStyles(theme => ({ root: { @@ -37,7 +39,12 @@ const useAsideStyles = makeStyles(theme => ({ }, })); -const Aside = ({ record, basePath }) => { +interface AsideProps { + record?: Record; + basePath?: string; +} + +const Aside: FC = ({ record, basePath }) => { const classes = useAsideStyles(); return (
@@ -51,7 +58,11 @@ Aside.propTypes = { basePath: PropTypes.string, }; -const EventList = ({ record, basePath }) => { +interface EventListProps { + record?: Record; + basePath?: string; +} +const EventList: FC = ({ record, basePath }) => { const translate = useTranslate(); const { data: orders, ids: orderIds } = useGetList( 'commands', @@ -170,13 +181,13 @@ const EventList = ({ record, basePath }) => { {events.map(event => event.type === 'order' ? ( ) : ( @@ -186,19 +197,32 @@ const EventList = ({ record, basePath }) => { ); }; -const mixOrdersAndReviews = (orders, orderIds, reviews, reviewIds) => { - const eventsFromOrders = orderIds.map(id => ({ +interface AsideEvent { + type: string; + date: string; + data: OrderRecord | ReviewRecord; +} + +const mixOrdersAndReviews = ( + orders: RecordMap, + orderIds: Identifier[], + reviews: RecordMap, + reviewIds: Identifier[] +) => { + const eventsFromOrders = orderIds.map(id => ({ type: 'order', date: orders[id].date, data: orders[id], })); - const eventsFromReviews = reviewIds.map(id => ({ + const eventsFromReviews = reviewIds.map(id => ({ type: 'review', date: reviews[id].date, data: reviews[id], })); const events = eventsFromOrders.concat(eventsFromReviews); - events.sort((e1, e2) => new Date(e1.date) - new Date(e2.date)); + events.sort( + (e1, e2) => new Date(e1.date).getTime() - new Date(e2.date).getTime() + ); return events; }; @@ -217,10 +241,15 @@ const useEventStyles = makeStyles({ }, }); -const Order = ({ record, basePath }) => { +interface OrderProps { + record?: OrderRecord; + basePath?: string; +} + +const Order: FC = ({ record, basePath }) => { const translate = useTranslate(); const classes = useEventStyles(); - return ( + return record ? ( { aria-label={translate('resources.commands.name', { smart_count: 1, })} - className={classes.avatar} > @@ -267,13 +295,18 @@ const Order = ({ record, basePath }) => { } /> - ); + ) : null; }; -const Review = ({ record, basePath }) => { +interface ReviewProps { + record?: ReviewRecord; + basePath?: string; +} + +const Review: FC = ({ record, basePath }) => { const translate = useTranslate(); const classes = useEventStyles(); - return ( + return record ? ( { aria-label={translate('resources.reviews.name', { smart_count: 1, })} - className={classes.avatar} > @@ -309,10 +341,15 @@ const Review = ({ record, basePath }) => { } /> - ); + ) : null; }; -const EditButton = ({ record, basePath }) => { +interface EditButtonProps { + record?: Record; + basePath?: string; +} + +const EditButton: FC = ({ record, basePath }) => { const translate = useTranslate(); return ( diff --git a/yarn.lock b/yarn.lock index f0e9053fc75..5b411aef5ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1535,6 +1535,11 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/fetch-mock@^7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.3.2.tgz#58805ba36a9357be92cc8c008dbfda937e9f7d8f" + integrity sha512-NCEfv49jmDsBAixjMjEHKVgmVQlJ+uK56FOc+2roYPExnXCZDpi6mJOHQ3v23BiO84hBDStND9R2itJr7PNoow== + "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"