downloading a pdf with data fetched from api in react-admin dashboard - reactjs

i create an app based on react-admin
and a fake server using json-server
my file structure is the following :
client ( here goes the react app )
node_modules
db.json
package-lock.json
pachake.json
range.js
then i created products and orders lists and show page :
the link for the show page of an order is ( for example order 3 ) :
http://localhost:3000/#/orders/3/show
and it shows the orders datils
what i wanna do is to download a pdf file contains this details , i tried the following code as a starting point :
import * as React from "react";
import {useQuery, Show, SimpleShowLayout, TextField } from 'react-admin';
import { PDFDownloadLink, Document, Page , View , Text } from '#react-pdf/renderer'
const MyDoc = ({}) => {
return (
<Document>
<Page size="A4">
<View>
<Text>
details must go here
</Text>
</View>
</Page>
</Document>
)};
const OrderShow = (props) => (
<Show {...props}>
<SimpleShowLayout>
<TextField source='order_number' />
<TextField source='reference_number' />
<TextField source='status' />
<TextField source='payment' />
<TextField source='shipment' />
<TextField source='created_at' />
<div>
<PDFDownloadLink document={<MyDoc /> } fileName="somename.pdf">
{({ loading }) => (loading ? 'Loading document...' : 'Download now!')}
</PDFDownloadLink>
</div>
</SimpleShowLayout>
</Show>
);
export default OrderShow
with this i got the following output :
when i click on Download now! a pdf with the content ' details must go here ' but instead of that i want the data about the current order

Try having a look at useShowController: https://marmelab.com/react-admin/Show.html#useshowcontroller
By using that, I think you will be able to access the data from the currently shown record, and build the PDF from there.

i did it this way and it works fine :
import React, {Component, PropTypes} from 'react';
// download html2canvas and jsPDF and save the files in app/ext, or somewhere else
// the built versions are directly consumable
// import {html2canvas, jsPDF} from 'app/ext';
export default class Export extends Component {
constructor(props) {
super(props);
}
printDocument() {
const input = document.getElementById('divToPrint');
html2canvas(input)
.then((canvas) => {
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF();
pdf.addImage(imgData, 'JPEG', 0, 0);
// pdf.output('dataurlnewwindow');
pdf.save("download.pdf");
})
;
}
render() {
return (<div>
<div className="mb5">
<button onClick={this.printDocument}>Print</button>
</div>
<div id="divToPrint" className="mt4" {...css({
backgroundColor: '#f5f5f5',
width: '210mm',
minHeight: '297mm',
marginLeft: 'auto',
marginRight: 'auto'
})}>
<div>Note: Here the dimensions of div are same as A4</div>
<div>You Can add any component here</div>
</div>
</div>);
}
}

Related

Can't generate pdf download link with react-pdf

I'm new with react. I want to create a website that can generate pdf file to download. I use React with Vite and Tailwind. I found this in internet: https://react-pdf.org/advanced#on-the-fly-rendering.
I try it in my code but nothing shows up.
import {Document, Page, PDFDownloadLink, StyleSheet, Text, View} from "#react-pdf/renderer";
import React from "react";
// Create styles
const styles = StyleSheet.create({
page: {
flexDirection: "row",
backgroundColor: "#E4E4E4",
},
section: {
margin: 10,
padding: 10,
flexGrow: 1,
},
});
// Create Document Component
const MyDoc = () => (
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>Section #1</Text>
</View>
<View style={styles.section}>
<Text>Section #2</Text>
</View>
</Page>
</Document>
);
const Dashboard = () => (
<div>
<PDFDownloadLink
className=" bg-slate-600"
document={<MyDoc />}
fileName="somename.pdf"
>
{({ blob, url, loading, error }) =>
<button className=" bg-slate-500">Loading document...</button>
) : (
<button className=" bg-slate-500">Download now!</button>
)
}
</PDFDownloadLink>
</div>
);
export default Dashboard;
And i call this from my App.jsx file
import Dashboard from "./Dashboard";
function App() {
return (
<div className=" bg-slate-500">
<Dashboard />
</div>
);
}
export default App;
This is how it's look when run
This is when i inspect
I run it from brave browser, but it still the same when i run it from google browser.
Anyone know why this is happen? Any help is appreciated
So after a lot of searching, i found that my problem is caused by vite. This post pretty much solve it

react typescript stitches css prop problem, not work

i'm currently working on a project using stitches with cra but i've stuck to a problem with css props.
here's my code
Texts/index.tsx
import React from 'react';
import { TextStyle } from './textStyle';
const Texts = ({ text, css }: PropsType) => {
console.log(css);
return (
<>
<TextStyle css={{ ...css }} >
<>{text}</>
</TextStyle>
</>
);
};
export default Texts;
and this index.tsx is exported to another components
Container/index.tsx
import { styled, css } from '../../../stitches.config';
// atoms
import Texts from 'src/components/atoms/texts';
const PageContainer = () => {
return (
<Container>
<Contents >
<div>
<Texts
css={{ color: 'red' }}
/>
<Texts
css={{ paddingTop: '20px' }}
/>
</div>
</Contents>
</Container>
);
};
export default PageContainer;
as you can see with the above code, contains css as its child but css is never rendered at all
can anyone help me with this issue?
FYI, console.log(css); returned undefined to me.
Thank you in advance!

Create a PDF document after click a button in React

I want to generate a PDF document that is generated after user click "Create PDF document" for my current React page. The document I want to generate will have the following:
Some, but not all component in the current page
Selectable
Only download the document when clicking and nowhere else
I have spent 3 hours researching on this trivial task, but somehow all library I have looking up for only allow their pre-defined component, or not selectable, or both. I know that this task is very trivial, but how exactly I could do that?
The best way to do this is, having a separate component that only contains what data need to be downloaded. And you can pass all necessary data using props.
I recommend using this library React-PDF.
App.js
import { PDFDownloadLink } from '#react-pdf/renderer';
import Document from './Document.js'
export default function App() {
const data = {/* Pass your data here */}
return (
<div className="App">
<PDFDownloadLink document={<MyDocument data={data}/>} fileName="somename.pdf">
{({ blob, url, loading, error }) =>
loading ? 'Loading document...' : 'Download now!'
}
</PDFDownloadLink>
</div>
);
}
Document.js
import React from 'react';
import { Document, Page, Text, View, StyleSheet } from '#react-pdf/renderer';
// Create styles
const styles = StyleSheet.create({
page: {
flexDirection: 'row',
backgroundColor: '#E4E4E4'
},
section: {
margin: 10,
padding: 10,
flexGrow: 1
}
});
// Create Document Component
const MyDocument = ({ data }) => ( //
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>{data.something}</Text>
</View>
<View style={styles.section}>
<Text>{data.something}</Text>
</View>
</Page>
</Document>
);
In the main component, you will have a Download now! button. Your PDF will only contain data that you pass through props

Not able to upload Image with ImageField in EDIT mode for react-admin

Using React Admin I am creating a dashboard for one of my clients and I have a requirement where I have to add the products of the client, out of the many fields there is one Image field too where I have to upload an image which serves in the API and the product is created with CREATE of react-admin.
// create product
import React, { useState} from "react";
import {
SimpleForm,
Create,
ImageField,
ImageInput,
} from "react-admin";
import Grid from "#material-ui/core/Grid";
import { ThemeProvider } from "#material-ui/styles";
import customTheme from "../../customTheme";
const CreateProduct = props => {
const classes = useStyles();
return (
<ThemeProvider theme={customTheme}>
<Create resource="products" basePath="/products">
<SimpleForm>
<Grid
container
spacing={2}
justify="space-between"
>
<Grid item xs={10}>
<ImageInput
source="data.pictures"
label="Images"
accept="image/png, image/jpg, image/jpeg"
maxSize={5000000}
placeholder={
<p>
Upload Image
<span >
*File size should not exceed 5MB
</span>
</p>
}
>
<ImageField source="src" title="images" />
</ImageInput>
</Grid>
</Grid>
</SimpleForm>
</Create>
</ThemeProvider>
);
};
export default CreateProduct;
Once a product is created I need to EDIT that product too, and with the same respect, I need to update the Image too.
//Edit Product
import React, { useState} from "react";
import {
SimpleForm,
Create,
ImageField,
ImageInput,
} from "react-admin";
import Grid from "#material-ui/core/Grid";
import { ThemeProvider } from "#material-ui/styles";
import customTheme from "../../customTheme";
const PreviewImage = ({ record }) => (
<img width={30} src={record} alt="Image Preview" />
);
const EditProduct = props => {
const classes = useStyles();
return (
<ThemeProvider theme={customTheme}>
<Edit {...props}>
<SimpleForm>
<Grid
container
spacing={2}
justify="space-between"
>
<Grid item xs={10}>
<ImageInput
source="data.pictures"
label="Images"
accept="image/png, image/jpg, image/jpeg"
maxSize={5000000}
placeholder={
<p>
Upload Image
<span >
*File size should not exceed 5MB
</span>
</p>
}
>
//<ImageField source="src" title="images" />
<PreviewImage />
</ImageInput>
</Grid>
</Grid>
</SimpleForm>
</Edit>
</ThemeProvider>
);
};
export default EditProduct;
The issue with EditProduct is I am not able to fetch the image from the record which is a URL with the help of ImageField used inside ImageInput and in order to achieve that I've created a separate component PreviewImage which fetched the image from the record and render it in img tag, but I would like to upload the new image too to the edit product form.
And I'm not able to achieve that with current documentation present in react-admin.
If anyone is aware of how I could achieve this EDIT functionality through react-admin, please post your solutions.
it works for me
const PreviewImage = ({ record, source }) => {
if (typeof (record) == "string") {
record = {
[source]: record
}
}
return <ImageField record={record} source={source} />
}
....
<ImageInput source="preview">
<PreviewImage source="src" />
</ImageInput>

Custom layout in SimpleForm component on react-admin

I want to create a custom two-column-grid layout on my react-admin project on Edit and Show pages. I want to display selectboxes and the imageupload area on the left column, and the text inputs on the right column by using only one <SimpleForm>.
Simply like this
If I use a div or a <Card> component under <SimpleForm> and <EditController> components, I receive an error.
Warning: React does not recognize the `basePath` prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom
attribute, spell it as lowercase `basepath` instead. If you
accidentally passed it from a parent component, remove it from the DOM
element.
Is there any way to create a layout without this error?
I solved it with creating another component with using divs, <Grid/> etc, and used that component in <SimpleForm> component.
import {withStyles} from '#material-ui/core/styles';
import React from 'react';
import {
EditController,
SimpleForm,
TextInput,
SelectInput,
Title,
} from 'react-admin';
import Grid from '#material-ui/core/Grid';
import Card from '#material-ui/core/Card';
import Poster from "../customField/Poster";
import {EditToolbar} from '../toolbar/CustomToolbar'
import {EditActions} from '../toolbar/CustomActions'
const editStyles = {
root: {display: 'flex', alignItems: 'flex-start', width: '100%'},
form: {flexGrow: 9},
};
class CardEdit extends React.Component {
constructor(props) {
super(props);
this.state = {
refresh: false
};
}
render() {
const FormDiv = withStyles(editStyles)(({children, classes, ...props}) => {
return (
<div className={classes.root}>
<div className={classes.form}>
<Grid container spacing={24}>
<Grid item xs={6}>
<TextInput source="name" fullWidth />
</Grid>
<Grid item xs={6}>
<TextInput source="card_id" fullWidth />
</Grid>
</Grid>
</div>
</div>
)
}
)
return (
<EditController {...this.props}>
{({resource, record, redirect, save, basePath, version}) => {
return (
<div>
<Title defaultTitle="sample"/>
<Card>
<div style={{ margin: '20px 20px 0 0' }}>
<EditActions
basePath={basePath}
resource={resource}
data={record}
hasShow
hasList
/>
</div>
{record && (
<SimpleForm
basePath={basePath}
redirect={redirect}
resource={resource}
record={record}
save={save}
version={version}
toolbar={<EditToolbar/>}
>
<FormDiv record={record} />
</SimpleForm>
)}
</Card>
</div>
)
}}
</EditController>
)
}
}
export default withStyles(editStyles)(CardEdit);
Actually, this could be done a little bit easier in case you don't need any custom styles and what not.
In order to get rid of the basePath error, just sanitize the props passed to the Material UI Grid Component:
const SanitizedGrid = ({basePath, ...props}) => {
return (
<Grid {...props} />
);
};
Then use it in place of a normal Grid:
export default props => (
<SimpleForm {...props}>
<SanitizedGrid container spacing={16}>
<Grid item xs>
<TextInput source="name" />
</Grid>
</SanitizedGrid>
</SimpleForm>
);
As another way, I've just worked out (thanks to Alexander's answer) a nice generic way to add any custom HTML content to a react-admin form:
import React, { Fragment } from 'react';
import { SimpleForm } from 'react-admin';
const CustomContent = ({ basePath, record, resource, children }) => (
<Fragment>
{children}
</Fragment>
);
export const MyForm = (props) => (
<SimpleForm>
<CustomContent>
<h3>Custom Content</h3>
<p>I can now add standard HTML to my react admin forms!</p>
</customContent>
</SimpleForm>
);
You get the basePath prop (which you probably don't want), but the record and resource props might be useful to your custom content (if you switch the code to use a render prop)

Resources