mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat: send invoice receipt preview
This commit is contained in:
@@ -45,7 +45,6 @@ import ProjectBillableEntriesFormDialog from '@/containers/Projects/containers/P
|
||||
import TaxRateFormDialog from '@/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialog';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import InvoiceExchangeRateChangeDialog from '@/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceExchangeRateChangeDialog';
|
||||
import InvoiceMailDialog from '@/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog';
|
||||
import EstimateMailDialog from '@/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog';
|
||||
import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog';
|
||||
import PaymentMailDialog from '@/containers/Sales/PaymentsReceived/PaymentMailDialog/PaymentMailDialog';
|
||||
@@ -144,7 +143,6 @@ export default function DialogsContainer() {
|
||||
<InvoiceExchangeRateChangeDialog
|
||||
dialogName={DialogsName.InvoiceExchangeRateChangeNotice}
|
||||
/>
|
||||
<InvoiceMailDialog dialogName={DialogsName.InvoiceMail} />
|
||||
<EstimateMailDialog dialogName={DialogsName.EstimateMail} />
|
||||
<ReceiptMailDialog dialogName={DialogsName.ReceiptMail} />
|
||||
<PaymentMailDialog dialogName={DialogsName.PaymentMail} />
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Dialog, DialogSuspense } from '@/components';
|
||||
import withDialogRedux from '@/components/DialogReduxConnect';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
const InvoiceFormMailDeliverDialogContent = React.lazy(
|
||||
() => import('./InvoiceFormMailDeliverDialogContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Invoice mail dialog.
|
||||
*/
|
||||
function InvoiceFormMailDeliverDialog({
|
||||
dialogName,
|
||||
payload: { invoiceId = null },
|
||||
isOpen,
|
||||
}) {
|
||||
return (
|
||||
<Dialog
|
||||
name={dialogName}
|
||||
title={'Invoice Mail'}
|
||||
isOpen={isOpen}
|
||||
canEscapeJeyClose={false}
|
||||
isCloseButtonShown={false}
|
||||
autoFocus={true}
|
||||
style={{ width: 600 }}
|
||||
>
|
||||
<DialogSuspense>
|
||||
<InvoiceFormMailDeliverDialogContent
|
||||
dialogName={dialogName}
|
||||
invoiceId={invoiceId}
|
||||
/>
|
||||
</DialogSuspense>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDialogRedux())(InvoiceFormMailDeliverDialog);
|
||||
@@ -1,40 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import * as R from 'ramda';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import InvoiceMailDialogContent from '../../../InvoiceMailDialog/InvoiceMailDialogContent';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
interface InvoiceFormDeliverDialogContent {
|
||||
invoiceId: number;
|
||||
}
|
||||
|
||||
function InvoiceFormDeliverDialogContentRoot({
|
||||
invoiceId,
|
||||
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}: InvoiceFormDeliverDialogContent) {
|
||||
const history = useHistory();
|
||||
|
||||
const handleSubmit = () => {
|
||||
history.push('/invoices');
|
||||
closeDialog(DialogsName.InvoiceFormMailDeliver);
|
||||
};
|
||||
const handleCancel = () => {
|
||||
history.push('/invoices');
|
||||
closeDialog(DialogsName.InvoiceFormMailDeliver);
|
||||
};
|
||||
|
||||
return (
|
||||
<InvoiceMailDialogContent
|
||||
invoiceId={invoiceId}
|
||||
onFormSubmit={handleSubmit}
|
||||
onCancelClick={handleCancel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDialogActions)(
|
||||
InvoiceFormDeliverDialogContentRoot,
|
||||
);
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useFormikContext } from 'formik';
|
||||
import InvoiceNumberDialog from '@/containers/Dialogs/InvoiceNumberDialog';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import InvoiceFormMailDeliverDialog from './Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialog';
|
||||
|
||||
/**
|
||||
* Invoice form dialogs.
|
||||
@@ -28,9 +27,6 @@ export default function InvoiceFormDialogs() {
|
||||
dialogName={DialogsName.InvoiceNumberSettings}
|
||||
onConfirm={handleInvoiceNumberFormConfirm}
|
||||
/>
|
||||
<InvoiceFormMailDeliverDialog
|
||||
dialogName={DialogsName.InvoiceFormMailDeliver}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Dialog, DialogSuspense } from '@/components';
|
||||
import withDialogRedux from '@/components/DialogReduxConnect';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
const InvoiceMailDialogBody = React.lazy(
|
||||
() => import('./InvoiceMailDialogBody'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Invoice mail dialog.
|
||||
*/
|
||||
function InvoiceMailDialog({
|
||||
dialogName,
|
||||
payload: { invoiceId = null },
|
||||
isOpen,
|
||||
}) {
|
||||
return (
|
||||
<Dialog
|
||||
name={dialogName}
|
||||
title={'Invoice Mail'}
|
||||
isOpen={isOpen}
|
||||
canEscapeJeyClose={false}
|
||||
isCloseButtonShown={false}
|
||||
autoFocus={true}
|
||||
style={{ width: 600 }}
|
||||
>
|
||||
<DialogSuspense>
|
||||
<InvoiceMailDialogBody invoiceId={invoiceId} />
|
||||
</DialogSuspense>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
export default compose(withDialogRedux())(InvoiceMailDialog);
|
||||
@@ -1,36 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import * as R from 'ramda';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import InvoiceMailDialogContent, {
|
||||
InvoiceMailDialogContentProps,
|
||||
} from './InvoiceMailDialogContent';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
export interface InvoiceMailDialogBodyProps
|
||||
extends InvoiceMailDialogContentProps {}
|
||||
|
||||
function InvoiceMailDialogBodyRoot({
|
||||
invoiceId,
|
||||
onCancelClick,
|
||||
onFormSubmit,
|
||||
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}: InvoiceMailDialogBodyProps) {
|
||||
const handleCancelClick = () => {
|
||||
closeDialog(DialogsName.InvoiceMail);
|
||||
};
|
||||
const handleSubmitClick = () => {
|
||||
closeDialog(DialogsName.InvoiceMail);
|
||||
};
|
||||
|
||||
return (
|
||||
<InvoiceMailDialogContent
|
||||
invoiceId={invoiceId}
|
||||
onCancelClick={handleCancelClick}
|
||||
onFormSubmit={handleSubmitClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDialogActions)(InvoiceMailDialogBodyRoot);
|
||||
@@ -1,48 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React, { createContext } from 'react';
|
||||
import { useSaleInvoiceDefaultOptions } from '@/hooks/query';
|
||||
import { DialogContent } from '@/components';
|
||||
|
||||
interface InvoiceMailDialogBootValues {
|
||||
invoiceId: number;
|
||||
mailOptions: any;
|
||||
redirectToInvoicesList: boolean;
|
||||
}
|
||||
|
||||
const InvoiceMailDialagBoot = createContext<InvoiceMailDialogBootValues>();
|
||||
|
||||
interface InvoiceMailDialogBootProps {
|
||||
invoiceId: number;
|
||||
redirectToInvoicesList?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice mail dialog boot provider.
|
||||
*/
|
||||
function InvoiceMailDialogBoot({
|
||||
invoiceId,
|
||||
redirectToInvoicesList,
|
||||
...props
|
||||
}: InvoiceMailDialogBootProps) {
|
||||
const { data: mailOptions, isLoading: isMailOptionsLoading } =
|
||||
useSaleInvoiceDefaultOptions(invoiceId);
|
||||
|
||||
const provider = {
|
||||
saleInvoiceId: invoiceId,
|
||||
mailOptions,
|
||||
isMailOptionsLoading,
|
||||
redirectToInvoicesList,
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent isLoading={isMailOptionsLoading}>
|
||||
<InvoiceMailDialagBoot.Provider value={provider} {...props} />
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
const useInvoiceMailDialogBoot = () =>
|
||||
React.useContext<InvoiceMailDialogBootValues>(InvoiceMailDialagBoot);
|
||||
|
||||
export { InvoiceMailDialogBoot, useInvoiceMailDialogBoot };
|
||||
@@ -1,22 +0,0 @@
|
||||
import { InvoiceMailDialogBoot } from './InvoiceMailDialogBoot';
|
||||
import { InvoiceMailDialogForm } from './InvoiceMailDialogForm';
|
||||
|
||||
export interface InvoiceMailDialogContentProps {
|
||||
invoiceId: number;
|
||||
onFormSubmit?: () => void;
|
||||
onCancelClick?: () => void;
|
||||
}
|
||||
export default function InvoiceMailDialogContent({
|
||||
invoiceId,
|
||||
onFormSubmit,
|
||||
onCancelClick,
|
||||
}: InvoiceMailDialogContentProps) {
|
||||
return (
|
||||
<InvoiceMailDialogBoot invoiceId={invoiceId}>
|
||||
<InvoiceMailDialogForm
|
||||
onFormSubmit={onFormSubmit}
|
||||
onCancelClick={onCancelClick}
|
||||
/>
|
||||
</InvoiceMailDialogBoot>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const InvoiceMailFormSchema = Yup.object().shape({
|
||||
from: Yup.array().required().min(1).max(5).label('From address'),
|
||||
to: Yup.array().required().min(1).max(5).label('To address'),
|
||||
subject: Yup.string().required().label('Mail subject'),
|
||||
body: Yup.string().required().label('Mail body'),
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { Formik } from 'formik';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { useInvoiceMailDialogBoot } from './InvoiceMailDialogBoot';
|
||||
import { AppToaster } from '@/components';
|
||||
import { useSendSaleInvoiceMail } from '@/hooks/query';
|
||||
import { InvoiceMailDialogFormContent } from './InvoiceMailDialogFormContent';
|
||||
import { InvoiceMailFormSchema } from './InvoiceMailDialogForm.schema';
|
||||
import {
|
||||
MailNotificationFormValues,
|
||||
initialMailNotificationValues,
|
||||
transformMailFormToRequest,
|
||||
transformMailFormToInitialValues,
|
||||
} from '@/containers/SendMailNotification/utils';
|
||||
|
||||
const initialFormValues = {
|
||||
...initialMailNotificationValues,
|
||||
attachInvoice: true,
|
||||
};
|
||||
|
||||
interface InvoiceMailFormValues extends MailNotificationFormValues {
|
||||
attachInvoice: boolean;
|
||||
}
|
||||
|
||||
export function InvoiceMailDialogForm({ onFormSubmit, onCancelClick }) {
|
||||
const { mailOptions, saleInvoiceId } = useInvoiceMailDialogBoot();
|
||||
const { mutateAsync: sendInvoiceMail } = useSendSaleInvoiceMail();
|
||||
|
||||
const initialValues = transformMailFormToInitialValues(
|
||||
mailOptions,
|
||||
initialFormValues,
|
||||
);
|
||||
// Handle the form submitting.
|
||||
const handleSubmit = (values: InvoiceMailFormValues, { setSubmitting }) => {
|
||||
const reqValues = transformMailFormToRequest(values);
|
||||
|
||||
setSubmitting(true);
|
||||
sendInvoiceMail([saleInvoiceId, reqValues])
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The mail notification has been sent successfully.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
setSubmitting(false);
|
||||
onFormSubmit && onFormSubmit(values);
|
||||
})
|
||||
.catch(() => {
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong.',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
// Handle the close button click.
|
||||
const handleClose = () => {
|
||||
onCancelClick && onCancelClick();
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={InvoiceMailFormSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<InvoiceMailDialogFormContent onClose={handleClose} />
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { Form, useFormikContext } from 'formik';
|
||||
import { Button, Classes, Intent } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
import { FFormGroup, FSwitch } from '@/components';
|
||||
import { MailNotificationForm } from '@/containers/SendMailNotification';
|
||||
import { saveInvoke } from '@/utils';
|
||||
import { useInvoiceMailDialogBoot } from './InvoiceMailDialogBoot';
|
||||
|
||||
interface SendMailNotificationFormProps {
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export function InvoiceMailDialogFormContent({
|
||||
onClose,
|
||||
}: SendMailNotificationFormProps) {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
const { mailOptions } = useInvoiceMailDialogBoot();
|
||||
|
||||
const handleClose = () => {
|
||||
saveInvoke(onClose);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<MailNotificationForm
|
||||
fromAddresses={mailOptions.from_addresses}
|
||||
toAddresses={mailOptions.to_addresses}
|
||||
/>
|
||||
<AttachFormGroup name={'attachInvoice'} inline>
|
||||
<FSwitch name={'attachInvoice'} label={'Attach Invoice'} />
|
||||
</AttachFormGroup>
|
||||
</div>
|
||||
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
onClick={handleClose}
|
||||
style={{ minWidth: '65px' }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
loading={isSubmitting}
|
||||
style={{ minWidth: '75px' }}
|
||||
type="submit"
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
const AttachFormGroup = styled(FFormGroup)`
|
||||
background: #f8f9fb;
|
||||
margin-top: 0.6rem;
|
||||
padding: 4px 14px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #dcdcdd;
|
||||
`;
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './InvoiceMailDialog';
|
||||
export * from './InvoiceMailDialogContent';
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useDrawerActions } from '@/hooks/state';
|
||||
interface ElementCustomizeHeaderProps {
|
||||
label?: string;
|
||||
children?: React.ReactNode;
|
||||
closeButton?: boolean;
|
||||
}
|
||||
|
||||
export function InvoiceSendMailHeader({
|
||||
|
||||
@@ -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'}>
|
||||
<messaging-service@post.xero.com>
|
||||
<messaging-service@post.bigcapital.app>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
<Box fontSize={'sm'} color={'#738091'}>
|
||||
Reply to: Ahmed <a.m.bouhuolia@gmail.com>
|
||||
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]);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
`}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -320,6 +320,8 @@ export function useInvoicePaymentTransactions(invoiceId, props) {
|
||||
);
|
||||
}
|
||||
|
||||
// # Send sale invoice mail.
|
||||
// ------------------------------
|
||||
export interface SendSaleInvoiceMailValues {
|
||||
id: number;
|
||||
values: {
|
||||
@@ -366,16 +368,49 @@ export function useSendSaleInvoiceMail(
|
||||
);
|
||||
}
|
||||
|
||||
// # Get sale invoice default options.
|
||||
// --------------------------------------
|
||||
export interface GetSaleInvoiceDefaultOptionsResponse {
|
||||
to: Array<string>;
|
||||
from: Array<String>;
|
||||
subject: string;
|
||||
message: string;
|
||||
attachInvoice: boolean;
|
||||
companyName: string;
|
||||
|
||||
dueDate: string;
|
||||
dueDateFormatted: string;
|
||||
|
||||
dueAmount: number;
|
||||
dueAmountFormatted: string;
|
||||
|
||||
entries: Array<{
|
||||
quantity: number;
|
||||
quantityFormatted: string;
|
||||
rate: number;
|
||||
rateFormatted: string;
|
||||
total: number;
|
||||
totalFormatted: string;
|
||||
}>;
|
||||
formatArgs: Record<string, string>;
|
||||
|
||||
from: string[];
|
||||
to: string[];
|
||||
|
||||
invoiceDate: string;
|
||||
invoiceDateFormatted: string;
|
||||
|
||||
invoiceNo: string;
|
||||
|
||||
message: string;
|
||||
subject: string;
|
||||
|
||||
subtotal: number;
|
||||
subtotalFormatted: string;
|
||||
|
||||
total: number;
|
||||
totalFormatted: string;
|
||||
|
||||
attachInvoice: boolean;
|
||||
primaryColor: string;
|
||||
}
|
||||
|
||||
export function useSaleInvoiceDefaultOptions(
|
||||
export function useSaleInvoiceMailState(
|
||||
invoiceId: number,
|
||||
options?: UseQueryOptions<GetSaleInvoiceDefaultOptionsResponse>,
|
||||
): UseQueryResult<GetSaleInvoiceDefaultOptionsResponse> {
|
||||
@@ -385,12 +420,14 @@ export function useSaleInvoiceDefaultOptions(
|
||||
[t.SALE_INVOICE_DEFAULT_OPTIONS, invoiceId],
|
||||
() =>
|
||||
apiRequest
|
||||
.get(`/sales/invoices/${invoiceId}/mail`)
|
||||
.get(`/sales/invoices/${invoiceId}/mail/state`)
|
||||
.then((res) => transformToCamelCase(res.data?.data)),
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
// # Get sale invoice state.
|
||||
// -------------------------------------
|
||||
export interface GetSaleInvoiceStateResponse {
|
||||
defaultTemplateId: number;
|
||||
}
|
||||
@@ -409,3 +446,68 @@ export function useGetSaleInvoiceState(
|
||||
{ ...options },
|
||||
);
|
||||
}
|
||||
|
||||
// # Get sale invoice branding template.
|
||||
// --------------------------------------
|
||||
export interface GetSaleInvoiceBrandingTemplateResponse {
|
||||
id: number;
|
||||
default: number;
|
||||
predefined: number;
|
||||
resource: string;
|
||||
resourceFormatted: string;
|
||||
templateName: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
createdAtFormatted: string;
|
||||
attributes: {
|
||||
billedToLabel?: string;
|
||||
companyLogoKey?: string | null;
|
||||
companyLogoUri?: string;
|
||||
dateIssueLabel?: string;
|
||||
discountLabel?: string;
|
||||
dueAmountLabel?: string;
|
||||
dueDateLabel?: string;
|
||||
invoiceNumberLabel?: string;
|
||||
itemDescriptionLabel?: string;
|
||||
itemNameLabel?: string;
|
||||
itemRateLabel?: string;
|
||||
itemTotalLabel?: string;
|
||||
paymentMadeLabel?: string;
|
||||
primaryColor?: string;
|
||||
secondaryColor?: string;
|
||||
showCompanyAddress?: boolean;
|
||||
showCompanyLogo?: boolean;
|
||||
showCustomerAddress?: boolean;
|
||||
showDateIssue?: boolean;
|
||||
showDiscount?: boolean;
|
||||
showDueAmount?: boolean;
|
||||
showDueDate?: boolean;
|
||||
showInvoiceNumber?: boolean;
|
||||
showPaymentMade?: boolean;
|
||||
showStatement?: boolean;
|
||||
showSubtotal?: boolean;
|
||||
showTaxes?: boolean;
|
||||
showTermsConditions?: boolean;
|
||||
showTotal?: boolean;
|
||||
statementLabel?: string;
|
||||
subtotalLabel?: string;
|
||||
termsConditionsLabel?: string;
|
||||
totalLabel?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function useGetSaleInvoiceBrandingTemplate(
|
||||
invoiceId: number,
|
||||
options?: UseQueryOptions<GetSaleInvoiceBrandingTemplateResponse, Error>,
|
||||
): UseQueryResult<GetSaleInvoiceBrandingTemplateResponse, Error> {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useQuery<GetSaleInvoiceBrandingTemplateResponse, Error>(
|
||||
['SALE_INVOICE_BRANDING_TEMPLATE', invoiceId],
|
||||
() =>
|
||||
apiRequest
|
||||
.get(`/sales/invoices/${invoiceId}/template`)
|
||||
.then((res) => transformToCamelCase(res.data?.data)),
|
||||
{ ...options },
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user