How do I map an array of key.value to Chip? I have a list of a {k,v} map - react-admin

Input json for Person - has a xrefAccounts{key,value} map
In the column "SubSys", I want to display "LIBRARY" and "SPORTS" as tags.
The Person json object has all the values required, but I can't figure out how to map it in DataGrid component.
I'm using react-admin 3.10
[{
...
"xrefAccounts": {
"LIBRARY": {
"id": "1",
"xrefSystemId": "LIBRARY"
},
"SPORTS": {
"id": "1",
"xrefSystemId": "SPORTS"
}
}
},
export const PersonList = props => (
<List filters={<PersonFilter />} {...props} >
<Datagrid rowClick="edit">
...
<ReferenceArrayField label="SubSys" reference="id" source="xrefAccounts.value">
{/* Find how to put multiple xref in one column SubSys */}
<SingleFieldList>
<ChipField source="value.xrefSystemId" />
</SingleFieldList>
</ReferenceArrayField>
<EditButton />
</Datagrid>
</List>
);
I want to display SubSys ["Library" "Sports"] similar to Tags["Sport" "Code" ]in this image

It doesn't work because you're using ArrayField on data which is not an array - it is an object!
So the best thing would be actually to fix your API response structure.
But ... if you can't do so - as I look at your attempt maybe I can suggest the following workaround via custom field component:
<XrefAccountsField source="xrefAccounts" />
inside
const XrefAccountsField = ({source, record = {}}) => {
const accountsObject = record[source];
// Convert the record to an array
const accounts = {
accountsArr: accountsObject ? Object.keys(accountsObject).map((key) => accountsObject[key]) : []
};
return (
<ArrayField source="accountsArr" record={accounts}>
<SingleFieldList>
<ChipField source="xrefSystemId" />
</SingleFieldList>
</ArrayField>
)
}

Related

Is there any method to implement search input in React Native FlatList?

I want to implement a search bar for flat list data, the problem is that the search function is working but it is not coming back to its original state data after a search is done or search text is null.
const [data,setdata] = useState([{
id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
fullName: "Bianca Bradley",
timeStamp: "12:47 PM",
recentText: "Contacts",
avatarUrl: "https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500"
}, {
id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
fullName: "Sujitha Mathur",
timeStamp: "11:11 PM",
recentText: "Contacts",
avatarUrl: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTyEaZqT3fHeNrPGcnjLLX1v_W4mvBlgpwxnA&usqp=CAU"
},
]
const handleSearch = text => {
const formattedQuery = text.toLowerCase();
const ndata = data.filter((item) => {
return item.fullName.toLowerCase().match(formattedQuery)
})
setdata(ndata);
setQuery(text);
console.log("Working",ndata)
}
return (
<>
<Center>
<View style={[styles.searchboxview]}>
<Input type="text" value={query} onChangeText={(text)=>handleSearch(text)} style={[styles.searchbox]} textAlign="center" placeholder="Search User Name" w="85%" maxWidth="340px" />
</View>
</Center>
<FlatList data={data} style={[styles.list]} renderItem={({
item
}) keyExtractor={item => item.id} />
</>
)
}
The problem which i can predict is that setdata(ndata) sets to new search data and it doesnot returns back to original data list. What will be the solution for it?
You can add a condition, if the text in textinput is empty then we will set the main data as data which is passed to the flatlist

How to create a custom record action button inside a List component with React-Admin?

I'm a totally newbie with React and React-Admin. IMHO, I'm trying to achieve something simple that many people must have already done but I cannot find any kind of tutorial anywhere.
I'd like to add another button to the list of action buttons (show/edit) within each row in a <List> component. This button would archive the record.
My last try looks like the code below.
import React from 'react';
import {
Datagrid,
EmailField,
List,
TextField,
ShowButton,
EditButton,
DeleteButton,
CloneButton,
} from 'react-admin';
import { makeStyles } from '#material-ui/core/styles';
import ArchiveIcon from '#material-ui/icons/Archive';
const useRowActionToolbarStyles = makeStyles({
toolbar: {
alignItems: 'center',
float: 'right',
width: '160px',
marginTop: -1,
marginBottom: -1,
},
icon_action_button: {
minWidth: '40px;'
},
});
const ArchiveButton = props => {
const transform = data => ({
...data,
archived: true
});
return <CloneButton {...props} transform={transform} />;
}
const RowActionToolbar = (props) => {
const classes = useRowActionToolbarStyles();
return (
<div className={classes.toolbar}>
<ShowButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<EditButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<ArchiveButton {...props} basePath={props.basePath} label="" icon={<ArchiveIcon/>} record={props.record} className={classes.icon_action_button} />
<DeleteButton basePath={props.basePath} label="" record={props.record} className={classes.icon_action_button}/>
</div>
);
};
export const UserList = props => {
return (
<List
{...props}
sort={{ field: 'first_name', order: 'ASC' }}
>
<Datagrid>
<TextField source="first_name"/>
<TextField source="last_name"/>
<EmailField source="email"/>
<RowActionToolbar/>
</Datagrid>
</List>
)
};
Obviously, this code does not work because the <CloneButton> component get rid of the id the record. Moreover, except if I did something wrong - which is totally possible -, it makes a GET request to a create endpoint.
I'm using different routes in my dataProvider (The back end is using Django and Django rest framework). I want to send a PATCH to the detail endpoint, like the <Edit> component does.
I also tried with a <SaveButton>, but it fails too.
Uncaught TypeError: Cannot read property 'save' of undefined
at useSaveContext (SaveContext.js:23)
I guess the <SaveButton> must be within a <SimpleForm>?
I'd like the save behaviour of the <DeleteButton>, i.e. update the record from the list, display the notification that the record has been archived (with the Undo link), send the request to the back end, refresh the list.
Any guidance, directions would be very appreciated.
I don't know that this is a full answer, but felt like more than a comment...
You are trying to archive the existing record, not create a whole new record, right? CloneButton is supposed to be used to create a new record with a new ID (which is why your ID is going away), so you don't want to us it here. note that I've never used CloneButton. it is not fully documented so I could be wrong about its use.
I am thinking that you should use the useRecordContext hook within your Archive button to pull in all of the record's data, including the id; read this little section: https://marmelab.com/react-admin/Architecture.html#context-pull-dont-push
And I don't think transform is what you're looking for here. You will need to use one of the dataProvider hooks, i'm assuming useUpdate: https://marmelab.com/react-admin/Actions.html#useupdate
//first create component
const MyButton = (props: any) => {
const [sendEmailLoading, setSendEmailLoading] =
React.useState<boolean>(false);
const record = useRecordContext(props);
const sendEmail = (id: Identifier) => {
setSendEmailLoading(true)
dataProvider.sendEmail(
"notifications", { id: id })
.then(({ data }: any) => {
if (data && data.status == "success")
notify('Email send success', { type: 'success' });
setSendEmailLoading(false);
refresh();
});
};
return (
<ButtonMUI color='primary' size="small" onClick={() => {
sendEmail(record.id) }}>
{
!record.publish &&(
!sendEmailLoading ? (
translate('resources.notifications.buttons.send')
) : (
<CircularProgress size={25} thickness={2} />
)
)
}
</ButtonMUI>
)
}
//and second add to datagrid list
<Datagrid>
<NumberField source="id" />
<TextFieldRA source="subject" />
<DateField source="date" />
<BooleanField source="publish" />
{/* <EditButton /> */}
<ShowButton />
<MyButton />
</Datagrid>

`ArrayInput` but only for a single item

The following snippet inside an Edit context creates an Array as listed below.
import { ArrayInput, SimpleFormIterator, DateInput, TextInput } from 'react-admin';
<ArrayInput source="backlinks">
<SimpleFormIterator>
<DateInput source="date" />
<TextInput source="url" />
</SimpleFormIterator>
</ArrayInput>
JSON:
{
"id": 123,
"backlinks": [
{
"date": "2012-08-10T00:00:00.000Z",
"url": "http://example.com/foo/bar.html",
},
{
"date": "2012-08-14T00:00:00.000Z",
"url": "https://blog.johndoe.com/2012/08/12/foobar.html",
}
]
}
Is there a way to achieve the same for a single item? Something like a reference field but without the feature to select an existing item. Rather the user should need to create one like in the example above.
You don't need a special input component, react-admin inputs let you edit nested objects by default by specifing the path in the source prop
<DateInput source="backlink.date" />
<TextInput source="backlink.url" />
Hope this solves the problem.

React-admin: Can I truncate a <TextField> within a <List>?

Is there a way to truncate text returned by the <TextField> and show ...?
See title column within image below (I want to truncate the title after 20 chars).
Is there a specific prop? Unfortunately, I didn't find any clue within the react-admin doc.
Thank you in advance
You can actually manipulate any field outside a <List>,and then get the <Datagrid> to render that field, as long as it's iterative.
const CustomTitleField = ({ record }) => {
// "record" is a prop received from the Datagrid
let str = record.title;
return record ? (
{/* If length is greater than 20 characters, slice and add ellipsis.*/}
<span>{str.length > 20 ? str.slice(0, 20) + "..." : str}</span>
) : null;
};
// Then, within your list, do this...
export const CommentList => (
<List {...props}>
<Datagrid>
<CustomTitleField /> // this should render with your truncate logic
// ...
</Datagrid>
</List>
);
Let me know how it goes, after try this out!!
You can use the cellClassName to customize the cell style inside a Datagrid.
https://marmelab.com/react-admin/Fields.html#styling-fields
I had a similar question and here is what works for me.
const useStyles = makeStyles((theme) => ({
tableHeader: {
fontWeight: "bold",
textTransform: "capitalize",
},
tableCell: {
maxWidth: 300,
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}
}));
const MyList = ({ ...props }) => {
const classes = useStyles();
return (
<List
{...props}
>
<Datagrid>
<TextField
headerClassName={classes.tableHeader}
cellClassName={classes.tableCell}
source="detail"
label="Detail"
/>
</Datagrid>
</List>
);
};

How can I get an array of strings as the submitted value for react-final-form using react-select?

I'm trying to get a react-select's multi-select component to submit the selected values as an array of strings but it's coming out as an array of {label, value} objects.
The structure of the objects I will be passing into react-select via react-final-form's Field component is like this:
[
{ val: "first-value", display: "First Value" },
{ val: "second-value", display: "Second Value" },
{val: "third-value", display: "Third Value"
];
Passing the prop getOptionLabel={option => option.display} is able to change the option labels to the display key given by the objects.
However doing the same thing for getOptionValue still gives me the entire {val, display} object when submitted. e.g. selecting the first item and submitting the form would get me [{val: "first-value", display: "First Value" }] when I only want ["first-value"]
I'm not too sure if it's something I need to solve on react-final-form or on react-select
import React from "react";
import { Field } from "react-final-form";
import { Form } from "react-final-form";
import Select from "react-select";
const data = [{ val: "1", display: "123" }, { val: "2", display: "321" }];
const Test2: React.FC<any> = () => {
return (
<Form
onSubmit={values => console.log(values)}
showErrorModal={showErrorModal}
setShowErrorModal={setShowErrorModal}
>
{() => (
<>
<Field
name="test"
component={MultiSelect}
label={"Multi Select test"}
placeholder="Please Select..."
options={data}
getOptionLabel={option => `${option.display}`}
getOptionValue={option => `${option.val}`}
/>
<SubmitButton btnTxt="next" />
</>
)}
</Form>
);
};
export default Test2;
When submitting, the message I get on the console is
[{"val": "1", "display": "123"}, {"val": "2", "display": "321"}]
whereas the result I'm hoping for is
["1","2"]
getOptionLabel & getOptionValue are only for viewing purpose.
When we get the selected value, it will give you the complete object as we provided array of object to options.
So the result of,
onSubmit={values => console.log(values)}
is correct, which is [{"val": "1", "display": "123"}, {"val": "2", "display": "321"}]
You need to further work on this result to get your desired result as,
onSubmit={values => console.log(values.map(data=>data.val))}