Last active
March 19, 2025 07:56
-
-
Save gregberge/4e70a18d082104620de242668ce2efc8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 |
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
hi there, does this still work with the current version of react-admin tried, and seems lots of improvements needed? Any other suggestions people?