feat: send invoice receipt preview

This commit is contained in:
Ahmed Bouhuolia
2024-10-31 12:40:48 +02:00
parent 470bfd32f7
commit dbbaa387bd
27 changed files with 383 additions and 476 deletions

View File

@@ -1,33 +1,38 @@
import { Box, Group, Stack } from '@/components';
import { InvoiceMailReceiptPreview } from '../InvoiceCustomize/InvoiceMailReceiptPreview';
import { css } from '@emotion/css';
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
import { useMemo } from 'react';
import { x } from '@xstyled/emotion';
import { css } from '@emotion/css';
import { Box, } from '@/components';
import { InvoiceMailReceiptPreview } from '../InvoiceCustomize/InvoiceMailReceiptPreview';
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
import { InvoiceSendMailPreviewWithHeader } from './InvoiceSendMailHeaderPreview';
import { useSendInvoiceMailMessage } from './_hooks';
export function InvoiceMailReceiptPreviewConneceted() {
const { invoice } = useInvoiceSendMailBoot();
const mailMessage = useSendInvoiceMailMessage();
const { invoiceMailState } = useInvoiceSendMailBoot();
const items = useMemo(
() =>
invoice.entries.map((entry: any) => ({
invoiceMailState?.entries?.map((entry: any) => ({
quantity: entry.quantity,
total: entry.rate_formatted,
label: entry.item.name,
total: entry.totalFormatted,
label: entry.name,
})),
[invoice.entries],
[invoiceMailState?.entries],
);
return (
<InvoiceSendMailPreviewWithHeader>
<Box px={4} pt={8} pb={16}>
<InvoiceMailReceiptPreview
total={invoice.total_formatted}
dueDate={invoice.due_date_formatted}
invoiceNumber={invoice.invoice_no}
companyName={invoiceMailState?.companyName}
// companyLogoUri={invoiceMailState?.companyLogoUri}
primaryColor={invoiceMailState?.primaryColor}
total={invoiceMailState?.totalFormatted}
dueDate={invoiceMailState?.dueDateFormatted}
dueAmount={invoiceMailState?.dueAmountFormatted}
invoiceNumber={invoiceMailState?.invoiceNo}
items={items}
message={mailMessage}
className={css`
@@ -37,4 +42,4 @@ export function InvoiceMailReceiptPreviewConneceted() {
</Box>
</InvoiceSendMailPreviewWithHeader>
);
}
}

View File

@@ -3,18 +3,15 @@ import React, { createContext, useContext } from 'react';
import { Spinner } from '@blueprintjs/core';
import {
GetSaleInvoiceDefaultOptionsResponse,
useInvoice,
useSaleInvoiceDefaultOptions,
useSaleInvoiceMailState,
} from '@/hooks/query';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
interface InvoiceSendMailBootValues {
invoice: any;
invoiceId: number;
isInvoiceLoading: boolean;
invoiceMailOptions: GetSaleInvoiceDefaultOptionsResponse | undefined;
isInvoiceMailOptionsLoading: boolean;
invoiceMailState: GetSaleInvoiceDefaultOptionsResponse | undefined;
isInvoiceMailState: boolean;
}
interface InvoiceSendMailBootProps {
children: React.ReactNode;
@@ -28,25 +25,21 @@ export const InvoiceSendMailBoot = ({ children }: InvoiceSendMailBootProps) => {
payload: { invoiceId },
} = useDrawerContext();
// Invoice details.
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
enabled: !!invoiceId,
});
// Invoice mail options.
const { data: invoiceMailOptions, isLoading: isInvoiceMailOptionsLoading } =
useSaleInvoiceDefaultOptions(invoiceId);
const { data: invoiceMailState, isLoading: isInvoiceMailState } =
useSaleInvoiceMailState(invoiceId);
const isLoading = isInvoiceLoading || isInvoiceMailOptionsLoading;
const isLoading = isInvoiceMailState;
if (isLoading) {
return <Spinner size={20} />;
}
const value = {
invoice,
isInvoiceLoading,
invoiceId,
invoiceMailOptions,
isInvoiceMailOptionsLoading,
// # Invoice mail options
isInvoiceMailState,
invoiceMailState,
};
return (

View File

@@ -25,13 +25,14 @@ interface InvoiceSendMailFormProps {
export function InvoiceSendMailForm({ children }: InvoiceSendMailFormProps) {
const { mutateAsync: sendInvoiceMail } = useSendSaleInvoiceMail();
const { invoiceId, invoiceMailOptions } = useInvoiceSendMailBoot();
const { invoiceId, invoiceMailState } = useInvoiceSendMailBoot();
const { name } = useDrawerContext();
const { closeDrawer } = useDrawerActions();
const _initialValues: InvoiceSendMailFormValues = {
...initialValues,
...transformToForm(invoiceMailOptions, initialValues),
...transformToForm(invoiceMailState, initialValues),
};
const handleSubmit = (
values: InvoiceSendMailFormValues,

View File

@@ -7,6 +7,7 @@ import { useDrawerActions } from '@/hooks/state';
interface ElementCustomizeHeaderProps {
label?: string;
children?: React.ReactNode;
closeButton?: boolean;
}
export function InvoiceSendMailHeader({

View File

@@ -1,10 +1,13 @@
import React, { useMemo } from 'react';
import { x } from '@xstyled/emotion';
import { Box, Group, Stack } from '@/components';
import React from 'react';
import { useSendInvoiceMailSubject } from './_hooks';
import { useSendInvoiceMailForm, useSendInvoiceMailSubject } from './_hooks';
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
export function InvoiceSendMailHeaderPreview() {
const mailSubject = useSendInvoiceMailSubject();
const { invoiceMailState } = useInvoiceSendMailBoot();
const toAddresses = useMailHeaderToAddresses();
return (
<Stack
@@ -43,12 +46,12 @@ export function InvoiceSendMailHeaderPreview() {
<Group spacing={2}>
<Box fontWeight={600}>Ahmed </Box>
<Box color={'#738091'}>
&lt;messaging-service@post.xero.com&gt;
&lt;messaging-service@post.bigcapital.app&gt;
</Box>
</Group>
<Box fontSize={'sm'} color={'#738091'}>
Reply to: Ahmed &lt;a.m.bouhuolia@gmail.com&gt;
Reply to: {invoiceMailState?.companyName} {toAddresses};
</Box>
</Stack>
</Group>
@@ -65,8 +68,15 @@ export function InvoiceSendMailPreviewWithHeader({
return (
<Box>
<InvoiceSendMailHeaderPreview />
<Box>{children}</Box>
</Box>
);
}
export const useMailHeaderToAddresses = () => {
const {
values: { to },
} = useSendInvoiceMailForm();
return useMemo(() => to?.map((email) => '<' + email + '>').join(' '), [to]);
};

View File

@@ -1,8 +1,18 @@
import { Tab, Tabs } from '@blueprintjs/core';
import { lazy, Suspense } from 'react';
import { css } from '@emotion/css';
import { Tab, Tabs } from '@blueprintjs/core';
import { Stack } from '@/components';
import { InvoiceMailReceiptPreviewConneceted } from './InvoiceMailReceiptPreviewConnected.';
import { InvoiceSendPdfPreviewConnected } from './InvoiceSendPdfPreviewConnected';
const InvoiceMailReceiptPreviewConneceted = lazy(() =>
import('./InvoiceMailReceiptPreviewConnected.').then((module) => ({
default: module.InvoiceMailReceiptPreviewConneceted,
})),
);
const InvoiceSendPdfPreviewConnected = lazy(() =>
import('./InvoiceSendPdfPreviewConnected').then((module) => ({
default: module.InvoiceSendPdfPreviewConnected,
})),
);
export function InvoiceSendMailPreview() {
return (
@@ -39,12 +49,20 @@ export function InvoiceSendMailPreview() {
<Tab
id={'payment-page'}
title={'Payment page'}
panel={<InvoiceMailReceiptPreviewConneceted />}
panel={
<Suspense>
<InvoiceMailReceiptPreviewConneceted />
</Suspense>
}
/>
<Tab
id="pdf-document"
title={'PDF document'}
panel={<InvoiceSendPdfPreviewConnected />}
panel={
<Suspense>
<InvoiceSendPdfPreviewConnected />
</Suspense>
}
/>
</Tabs>
</Stack>

View File

@@ -1,25 +1,13 @@
import { css } from '@emotion/css';
import { Box } from '@/components';
import { InvoicePaperTemplate } from '../InvoiceCustomize/InvoicePaperTemplate';
import { css } from '@emotion/css';
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
import { InvoiceSendMailPreviewWithHeader } from './InvoiceSendMailHeaderPreview';
export function InvoiceSendPdfPreviewConnected() {
const { invoice } = useInvoiceSendMailBoot();
return (
<InvoiceSendMailPreviewWithHeader>
<Box px={4} py={6}>
<InvoicePaperTemplate
dueDate={invoice.due_date_formatted}
dateIssue={invoice.invoice_date_formatted}
invoiceNumber={invoice.invoice_no}
total={invoice.total_formatted}
subtotal={invoice.subtotal}
discount={''}
paymentMade={''}
balanceDue={invoice.due_amount_Formatted}
statement={invoice.statement}
className={css`
margin: 0 auto;
`}

View File

@@ -5,6 +5,10 @@ import { chain, defaultTo, mapKeys, snakeCase, startCase } from 'lodash';
import { InvoiceSendMailFormValues } from './_types';
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
export const useSendInvoiceMailForm = () => {
return useFormikContext<InvoiceSendMailFormValues>();
};
export const useInvoiceMailItems = () => {
const { values } = useFormikContext<InvoiceSendMailFormValues>();
const cc = values?.cc || [];
@@ -21,13 +25,13 @@ export const useInvoiceMailItems = () => {
};
export const useSendInvoiceMailFormatArgs = (): Record<string, string> => {
const { invoiceMailOptions } = useInvoiceSendMailBoot();
const { invoiceMailState } = useInvoiceSendMailBoot();
return useMemo(() => {
return mapKeys(invoiceMailOptions?.formatArgs, (_, key) =>
return mapKeys(invoiceMailState?.formatArgs, (_, key) =>
startCase(snakeCase(key).replace('_', ' ')),
);
}, [invoiceMailOptions]);
}, [invoiceMailState]);
};
export const useSendInvoiceMailSubject = (): string => {