Skip to content

Instantly share code, notes, and snippets.

@gregberge
Last active June 19, 2024 07:03
Show Gist options
  • Save gregberge/4e70a18d082104620de242668ce2efc8 to your computer and use it in GitHub Desktop.
Save gregberge/4e70a18d082104620de242668ce2efc8 to your computer and use it in GitHub Desktop.
import React from 'react'
import {
List,
Datagrid,
Filter,
Edit,
Create,
SimpleForm,
TextField,
EditButton,
DisabledInput,
TextInput,
BooleanInput,
LongTextInput,
NumberInput,
ReferenceInput,
SelectInput,
DateInput,
} from 'react-admin'
import ReferenceManyToManyInput from './inputs/ReferenceManyToManyInput'
import ReferenceManyInput from './inputs/ReferenceManyInput'
import { Poster } from './pictures'
export const ExperienceIcon = null
const ExperienceFilter = props => (
<Filter {...props}>
<BooleanInput label="published" source="published" defaultValue />
</Filter>
)
export const ExperienceList = props => (
<List {...props} filters={<ExperienceFilter />}>
<Datagrid>
<TextField source="id" />
<TextField source="name" />
<EditButton basePath="/experiences" />
</Datagrid>
</List>
)
const ExperienceTitle = ({ record: { name } }) => `Experience "${name}"`
const commonInputs = (
<React.Fragment>
<TextInput source="name" placeholder="Petit-déjeuner pour deux" />
<LongTextInput source="description" />
<NumberInput source="commission" />
<NumberInput source="price" placeholder="200" />
<NumberInput source="discountPrice" placeholder="160" />
<TextField source="priceDescription" placeholder="pour deux" />
<BooleanInput source="included" defaultValue={false} />
<NumberInput source="minimalQuantity" placeholder="1" />
<NumberInput source="maximalQuantity" placeholder="10" />
<TextInput source="openingHours" placeholder="10:00" />
<ReferenceInput
label="Picture"
source="coverPictureId"
reference="pictures"
allowEmpty
>
<SelectInput optionText="cloudinaryId" />
</ReferenceInput>
<ReferenceInput
label="Place"
source="placeId"
reference="places"
allowEmpty
>
<SelectInput optionText="name" />
</ReferenceInput>
<ReferenceManyInput label="Openings" source="openings">
<DateInput source="date" label="Date" />
<NumberInput source="price" label="Price" />
<NumberInput source="discountPrice" label="Discount Price" />
<NumberInput source="stock" label="stock" placeholder="5" />
</ReferenceManyInput>
<ReferenceManyToManyInput
label="Categories"
source="experienceCategories"
sourceReferenceId="experienceCategoryId"
reference="experienceCategories"
optionText="name"
>
<TextField label="Name" source="$reference.name" />
</ReferenceManyToManyInput>
<ReferenceManyToManyInput
label="Pictures"
source="experiencePictures"
sourceReferenceId="pictureId"
reference="pictures"
optionText="cloudinaryId"
>
<Poster source="$reference.cloudinaryId" />
<NumberInput label="Priority" source="priority" defaultValue={10} />
<BooleanInput label="Published" source="published" defaultValue />
</ReferenceManyToManyInput>
<BooleanInput source="published" defaultValue />
</React.Fragment>
)
export const ExperienceCreate = props => (
<Create title="Create an Experience" {...props}>
<SimpleForm>{commonInputs.props.children}</SimpleForm>
</Create>
)
export const ExperienceEdit = props => (
<Edit title={<ExperienceTitle />} {...props}>
<SimpleForm>
<DisabledInput source="id" />
{commonInputs.props.children}
</SimpleForm>
</Edit>
)
/* eslint-disable react/no-multi-comp, react/no-unused-state */
import React from 'react'
import { connect } from 'react-redux'
import * as ReactAdmin from 'react-admin'
import { createSelector } from 'reselect'
import { Field, arrayRemove, arrayPush } from 'redux-form'
import { CircularProgress } from 'material-ui/Progress'
import Table, {
TableBody,
TableCell,
TableHead,
TableRow,
} from 'material-ui/Table'
import { MenuItem } from 'material-ui/Menu'
import Paper from 'material-ui/Paper'
import Button from 'material-ui/Button'
import Select from 'material-ui/Select'
import Typography from 'material-ui/Typography'
import DeleteIcon from '@material-ui/icons/Delete'
const referenceSource = (resource, source) => `${resource}@${source}`
class IDComponent extends React.Component {
componentDidMount() {
this.props.input.onChange(this.props.defaultValue)
}
render() {
return this.props.defaultValue
}
}
const getDefaultValues = children =>
React.Children.toArray(children)
.filter(child => !child.props.source.startsWith('$reference'))
.reduce(
(values, child) => ({
...values,
[child.props.source]: child.props.defaultValue || null,
}),
{},
)
class ReferenceManyToManyInputComponent extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
if (!nextProps.data || nextProps.record === prevState.record)
return prevState
if (!nextProps.record[nextProps.source] && nextProps.record.id) {
throw new Error(
`Unable to found relation "${nextProps.source}" on "${
nextProps.resource
}", please fill it in "resourceRelations.js"`,
)
}
const links = nextProps.record[nextProps.source] || []
return {
record: nextProps.record,
pending: false,
references: links.map(link =>
nextProps.data.find(
({ id }) => String(link[nextProps.sourceReferenceId]) === String(id),
),
),
}
}
state = {
record: null,
pending: true,
references: [],
}
handleAdd = event => {
if (!event.target.value) return
this.setState((previousState, nextProps) => {
const reference = nextProps.data.find(
({ id }) => String(event.target.value) === String(id),
)
nextProps.onAdd({
[this.props.sourceReferenceId]: reference.id,
...getDefaultValues(this.props.children),
})
return { references: [...previousState.references, reference] }
})
}
handleRemove = referenceId => {
this.setState((previousState, nextProps) => {
const index = previousState.references.findIndex(
({ id }) => id === referenceId,
)
if (index === -1) return previousState
const references = [...previousState.references]
references.splice(index, 1)
nextProps.onRemove(index)
return { references }
})
}
componentDidMount() {
this.props.onFetchData()
}
render() {
const data = this.props.data || []
const availableData = data.filter(item =>
this.state.references.every(reference => reference.id !== item.id),
)
return (
<Paper style={{ margin: '30px 0 20px' }}>
<Typography variant="headline" style={{ padding: '10px' }}>
{this.props.label}
</Typography>
{this.state.pending ? (
<CircularProgress />
) : (
<React.Fragment>
<Table>
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
{React.Children.map(this.props.children, child => (
<TableCell>{child.props.label || '-'}</TableCell>
))}
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{this.state.references.map((reference, index) => (
<TableRow key={reference.id}>
<TableCell>
<Field
defaultValue={reference.id}
name={`${this.props.source}[${index}].${
this.props.sourceReferenceId
}`}
component={IDComponent}
/>
</TableCell>
{React.Children.map(this.props.children, child => (
<TableCell>
{React.cloneElement(
child,
child.props.source.startsWith('$reference')
? {
source: child.props.source.replace(
'$reference.',
'',
),
record: reference,
resource: this.props.resource,
basePath: this.props.basePath,
}
: {
source: `${this.props.source}[${index}].${
child.props.source
}`,
record: this.props.record,
resource: this.props.resource,
basePath: this.props.basePath,
defaultValue: undefined,
},
)}
</TableCell>
))}
<TableCell style={{ textAlign: 'right' }}>
<Button
onClick={() => this.handleRemove(reference.id)}
variant="fab"
color="primary"
mini
>
<DeleteIcon />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Select value="" onChange={this.handleAdd}>
{availableData &&
availableData.map(item => (
<MenuItem key={item.id} value={item.id}>
{item[this.props.optionText]}
</MenuItem>
))}
</Select>
</React.Fragment>
)}
</Paper>
)
}
}
const ReferenceManyToManyInput = connect(
createSelector(
[ReactAdmin.getReferenceResource, ReactAdmin.getPossibleReferenceValues],
(referenceState, possibleValues, inputIds) => ({
data: ReactAdmin.getPossibleReferences(
referenceState,
possibleValues,
inputIds,
),
}),
),
(dispatch, ownProps) => ({
onFetchData() {
dispatch(
ReactAdmin.crudGetMatching(
ownProps.reference,
ownProps.referenceSource(ownProps.resource, ownProps.source),
{ page: 1, perPage: 2000 },
{ field: 'id', order: 'DESC' },
{},
),
)
},
onRemove(index) {
dispatch(arrayRemove('record-form', ownProps.source, index))
},
onAdd(value) {
dispatch(arrayPush('record-form', ownProps.source, value))
},
}),
)(ReferenceManyToManyInputComponent)
ReferenceManyToManyInput.defaultProps = {
referenceSource,
}
export default ReferenceManyToManyInput
@s4kh
Copy link

s4kh commented Apr 5, 2021

hi there, does this still work with the current version of react-admin tried, and seems lots of improvements needed? Any other suggestions people?

@MartinCura
Copy link

I made custom components too, if it's of any help to anyone.

https://gist.github.com/MartinCura/a5a76241f528b9f718ab872623df1e97

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment