Building an Customizable Invoice Generator App with Refine, Strapi & Ant Design | Part II
Melih Ekinci
Posted on March 3, 2022
Looking for an invoice generator that is easy to use and lets you customize your invoices? With refine simple and intuitive interface, you can create your own invoices in few hours. Plus, we offer a wide range of templates and customization options so you can get the exact look you want. Learn more about our invoice generator here!
Introduction
We are almost ready to launch our refine Invoice Generator. In this Part II article, we'll customize it just a little more and then take an in-depth look at what you can do with the finished refine product!
In this part, we will create a missions part for the services your company provides. Then we will create our invoice page using these missions and the clients and contacts we created in Part I. In addition, you will not only be able to create your invoices, but you will also be able to view and download these invoices as PDF.
This article is written as a continuation of our Develop your Own Customizable Invoice Generator with Refine article. If you have not read Part I, we recommend that you read Part I before reading this article.
Let's see together how easily and in a short time we can develop our project with its refine features.
Create New Strapi Collections
In our Part I article, we created our company, contact and client collections. In this section, let's create the Missions
and Invoice
Strapi collections for the missions and invoices pages.
Mission Collection:
- Mission(Mission Title): Text
- Mission_description: Text
- Day: Number
- Daily_rate: Number
Invoice Collection:
- Name: Text
- Date: Date
- Company : Relation with Company
- Discount : Number
- Tax: Number
- Custom_id: Text
- Contact: Relation with Contact
- Missions: Relation with Mission
We created our missions and invoice collections fields. Our goal here is to define the products or services you offer specifically to your company and to create invoices based on them. By determining how many working days a product or service will last and its price on a daily basis, the total will be automatically reflected on your invoice. Now let's create our refine Missions page using this collection. And let's understand better by creating an example missions with refine.
Refine Missions Page
Let's use the refine-antd package's useTable hook to create our page, and let's define the fields in our Table Component.
src/pages/MissionList.tsx:
import {
List,
Table,
useTable,
TagField,
useModalForm,
EditButton,
} from "@pankod/refine-antd";
import { IMission } from "interfaces";
export const MissionList: React.FC = () => {
const { tableProps } = useTable<IMission>();
return (
<List>
<Table {...tableProps}>
<Table.Column dataIndex="id" title="ID" />
<Table.Column dataIndex="mission" title="Mission" />
<Table.Column
dataIndex="mission_description"
title="Mission Description"
/>
<Table.Column dataIndex="day" title="Day(s)" />
<Table.Column
dataIndex="daily_rate"
title="Daily Rate"
render={(value) => <TagField value={value} color="red" />}
/>
<Table.Column<IMission>
title="Total"
render={(_, record) => {
return (
<TagField
value={`${record.daily_rate * record.day} $`}
color="green"
/>
);
}}
/>
<Table.Column<IMission>
title="Actions"
dataIndex="actions"
key="actions"
render={(_value, record) => (
<EditButton
hideText
size="small"
recordItemId={record.id}
onClick={() => editShow(record.id)}
/>
)}
/>
</Table>
</List>
);
};
We defined the fields we created on the strapi side with the refine Table and created our table. Let's take a look at how our table looks like.
As you can see, we were able to create and display our table very simply thanks to the refine. Let's learn how to create a Mission from our refine interface now.
Refine Missions Create Page
Let's create a modal component for our Mission Create
page. Let's connect our fields using Modal and Form from the refine-antd
package.
src/components/mission/CreateMission.tsx:
import {
Modal,
Form,
Input,
ModalProps,
FormProps,
InputNumber,
} from "@pankod/refine-antd";
type CreateMissionProps = {
modalProps: ModalProps;
formProps: FormProps;
};
export const CreateMission: React.FC<CreateMissionProps> = ({
modalProps,
formProps,
}) => {
return (
<Modal {...modalProps} title="Create Contact">
<Form {...formProps} layout="vertical">
<Form.Item
label="Title"
name="mission"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item label="Description" name="mission_description">
<Input />
</Form.Item>
<Form.Item label="Day(s)" name="day">
<InputNumber defaultValue={1} />
</Form.Item>
<Form.Item label="Daily Rate" name="daily_rate">
<InputNumber defaultValue={1} />
</Form.Item>
</Form>
</Modal>
);
};
Let's define the CreateMission
component we created above in our MissionList
and fill its props with refine useModalForm.
src/pages/MissionList.tsx:
import {
List,
Table,
useTable,
TagField,
useModalForm,
} from "@pankod/refine-antd";
import { IMission } from "interfaces";
import { CreateMission, EditMission } from "components/mission";
export const MissionList: React.FC = () => {
const { tableProps } = useTable<IMission>();
const { formProps, modalProps, show } = useModalForm({
resource: "missions",
action: "create",
});
return (
<>
<List
createButtonProps={{
onClick: () => {
show();
},
}}
>
<Table {...tableProps}>
<Table.Column dataIndex="id" title="ID" />
<Table.Column dataIndex="mission" title="Mission" />
<Table.Column
dataIndex="mission_description"
title="Mission Description"
/>
<Table.Column dataIndex="day" title="Day(s)" />
<Table.Column
dataIndex="daily_rate"
title="Daily Rate"
render={(value) => (
<TagField value={value} color="red" />
)}
/>
<Table.Column<IMission>
title="Total"
render={(_, record) => {
return (
<TagField
value={`${
record.daily_rate * record.day
} $`}
color="green"
/>
);
}}
/>
</Table>
</List>
<CreateMission modalProps={modalProps} formProps={formProps} />
</>
);
};
Missions Page is now ready, you can create and manage your business's products or services here with refine.
Our next step is to create invoices according to these tasks and clients. Let's create, organize and display invoices with refine.
Refine Invoices Page
Let's put the Invoice Collections
fields that we created with Strapi into our Table by fetch the refine-antd useTable. Our Invoice collection has a relation with the client, company and missions collections.
Thanks to the refine-strapi-v4 dataProvider, we can use the data of collections that are related to each other.
In order to use the fields of the collections that are related to each other, we must populate the collections in metaData
.
Populate the contacts, companies and missions related with our Invoice collection in metaData.
src/pages/InvoiceList.tsx:
import {
List,
Table,
useTable,
DateField,
TagField,
EmailField,
Space,
DeleteButton,
EditButton,
Icons,
Button,
Modal,
} from "@pankod/refine-antd";
import { IInvoice } from "interfaces";
import { PdfLayout } from "components/pdf";
const { FilePdfOutlined } = Icons;
export const InvoiceList: React.FC = () => {
const { tableProps } = useTable<IInvoice>({
metaData: {
populate: {
contact: { populate: ["client"] },
company: { populate: ["logo"] },
missions: "*",
},
},
});
return (
<>
<List>
<Table {...tableProps}>
<Table.Column dataIndex="id" title="ID" />
<Table.Column<IInvoice>
dataIndex="name"
title="Invoice Name"
render={(_, record) => {
return `Invoice_#${record.id}${record.name}`;
}}
/>
<Table.Column<IInvoice>
dataIndex="date"
title="Invoice Date"
render={(value) => (
<DateField format="LL" value={value} />
)}
/>
<Table.Column
dataIndex={["company", "name"]}
title="Your Company"
/>
<Table.Column
dataIndex={"missions"}
title="Missions"
render={(value) => {
return value.map((item: any) => {
return (
<TagField
color="blue"
value={item.mission}
/>
);
});
}}
/>
<Table.Column
dataIndex="discount"
title="Discount(%)"
render={(value) => (
<TagField color="blue" value={value} />
)}
/>
<Table.Column
dataIndex="tax"
title="Tax(%)"
render={(value) => (
<TagField color="cyan" value={value} />
)}
/>
<Table.Column
dataIndex="custom_id"
title="Custom Invoice ID"
/>
<Table.Column
dataIndex={["contact", "email"]}
title="Contact"
render={(value) => <EmailField value={value} />}
/>
<Table.Column<IInvoice>
title="Actions"
dataIndex="actions"
render={(_, record) => {
return (
<Space>
<EditButton
hideText
size="small"
recordItemId={record.id}
/>
<DeleteButton
hideText
size="small"
recordItemId={record.id}
/>
</Space>
);
}}
/>
</Table>
</List>
</>
);
};
As you can see, we were able to list invoices with refine. Using the Invoice collection and the fields associated with it, we can create a fully featured Invoice.
Our invoice contains all the information. With Refine Invoice Generator
you can define the company issuing the invoice, discount percentage, tax percentage, customId and similar information in a single invoice.
Let's understand better by creating an invoice example from our refine UI.
Refine Create Invoice Page
Here, we first fetch the company, contacts and missions using the refine's useSelect hook, and by giving it to the Select component, we create selectable components to detail our invoice.
Then, we fill our refine Create and Form components with the fields of the collection in the strap to perform a creation process as we did in our previous examples.
src/pages/invoice/CreateInvoice:
import { IResourceComponentsProps } from "@pankod/refine-core";
import {
Create,
Form,
Input,
Select,
useForm,
useSelect,
DatePicker,
} from "@pankod/refine-antd";
import { ICompany, IContact, IMission, IInvoice } from "interfaces";
export const CreateInvoice: React.FC<IResourceComponentsProps> = () => {
const { formProps, saveButtonProps } = useForm<IInvoice>();
const { selectProps: companySelectProps } = useSelect<ICompany>({
resource: "companies",
optionLabel: "name",
});
const { selectProps: contactSelectProps } = useSelect<IContact>({
resource: "contacts",
optionLabel: "first_name",
});
const { selectProps: missionSelectProps } = useSelect<IMission>({
resource: "missions",
optionLabel: "mission",
});
return (
<Create saveButtonProps={saveButtonProps}>
<Form {...formProps} layout="vertical">
<Form.Item label="Invoice Name" name="name">
<Input />
</Form.Item>
<Form.Item
label="Select Your Company"
name="company"
rules={[
{
required: true,
},
]}
>
<Select {...companySelectProps} />
</Form.Item>
<Form.Item
label="Mission"
name="missions"
rules={[
{
required: true,
},
]}
>
<Select {...missionSelectProps} mode="multiple" />
</Form.Item>
<Form.Item label="Discount(%)" name="discount">
<Input />
</Form.Item>
<Form.Item label="Tax(%)" name="tax">
<Input />
</Form.Item>
<Form.Item label="Custom ID" name="custom_id">
<Input />
</Form.Item>
<Form.Item
label="Contact"
name="contact"
rules={[
{
required: true,
},
]}
>
<Select {...contactSelectProps} />
</Form.Item>
<Form.Item label="Invoice Date" name="date">
<DatePicker style={{ width: "50%" }} />
</Form.Item>
</Form>
</Create>
);
};
Our invoice generator is almost ready! As you can see, we can now create a fully featured invoice with refine and display it in our table. As the last step, let's view and download the invoices we created as PDF.
View and Download Invoice as PDF
In this example, we will use the KendoReact PDF package to view as PDF. Let's start our process by installing our package.
Let's start our process by installing our package.
Installation
npm i @progress/kendo-react-pdf
Usage
To begin, let's create a pdf layout and add props to receive the data in our Invoice List.
src/components/pdf/PdfLayout.tsx:
import { useRef } from "react";
import "./pdf.css";
import { PDFExport } from "@progress/kendo-react-pdf";
import { IInvoice } from "interfaces";
type PdfProps = {
record: IInvoice | undefined;
};
export const PdfLayout: React.FC<PdfProps> = ({ record }) => {
return <></>;
};
Let's create a button to display our PDF arrangement in the Invoice List, as well as a modal component for this field to appear.
src/pages/invoice/InvoiceList.tsx:
import { useState } from "react";
import { useModal } from "@pankod/refine-core";
import {
List,
Table,
useTable,
DateField,
TagField,
EmailField,
Space,
DeleteButton,
EditButton,
Icons,
Button,
Modal,
} from "@pankod/refine-antd";
import { IInvoice } from "interfaces";
import { PdfLayout } from "components/pdf";
const { FilePdfOutlined } = Icons;
export const InvoiceList: React.FC = () => {
const [record, setRecord] = useState<IInvoice>();
const { tableProps } = useTable<IInvoice>({
metaData: {
populate: {
contact: { populate: ["client"] },
company: { populate: ["logo"] },
missions: "*",
},
},
});
const { show, visible, close } = useModal();
return (
<>
<List>
<Table {...tableProps}>
...
<Table.Column<IInvoice>
title="Actions"
dataIndex="actions"
render={(_, record) => {
return (
<Space>
<EditButton
hideText
size="small"
recordItemId={record.id}
/>
<DeleteButton
hideText
size="small"
recordItemId={record.id}
/>
<Button
size="small"
icon={<FilePdfOutlined />}
onClick={() => {
setRecord(record);
show();
}}
/>
</Space>
);
}}
/>
</Table>
</List>
<Modal visible={visible} onCancel={close} width={700} footer={null}>
<PdfLayout record={record} />
</Modal>
</>
);
};
We`ve created a button in our Invoice List and when this button is pressed, we show the Modal component that contains the PdfLayout we created. Finally, as you can see, we passed the record data in the Invoice List as PdfLayout props.
Now that we have the data of the Invoices we can edit the PdfLayout.
Check out PdfLayout design and code
Conclusion
In this post, we've created a fully customizable and completely functional Invoice Generator app. If you want to construct an application similar to this, you may add any feature with refine and personalize your invoice generator to your liking. We developed in very short amount of time, thanks to refine's features and the possibilities it provides for customization.
You can develop any web application or admin panel you want in a very short time with refine.
With refine's headless and SSR-Next.js features, it is possible and very easy to develop both B2B and B2C applications using a single framework.
Live CodeSandbox Example
PDF download may not work in codeSandbox mode. With this link, you can open the example in the browser and try it.
-
Username
: demo -
Password
: demodemo
Posted on March 3, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
March 3, 2022