feat: payment received mail receipt preview

This commit is contained in:
Ahmed Bouhuolia
2024-11-20 13:03:17 +02:00
parent 6103f1e4c7
commit 63a95df534
18 changed files with 622 additions and 9 deletions

View File

@@ -0,0 +1,61 @@
import React, { createContext, useContext } from 'react';
import { Spinner } from '@blueprintjs/core';
import {
PaymentReceivedStateResponse,
usePaymentReceivedState,
} from '@/hooks/query';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
interface PaymentReceivedSendMailBootValues {
paymentReceivedId: number;
paymentReceivedMailState: PaymentReceivedStateResponse | undefined;
isPaymentReceivedStateLoading: boolean;
}
interface InvoiceSendMailBootProps {
children: React.ReactNode;
}
const PaymentReceivedSendMailBootContext =
createContext<PaymentReceivedSendMailBootValues>(
{} as PaymentReceivedSendMailBootValues,
);
export const PaymentReceivedSendMailBoot = ({
children,
}: InvoiceSendMailBootProps) => {
const {
payload: { paymentReceivedId },
} = useDrawerContext();
const {
data: paymentReceivedMailState,
isLoading: isPaymentReceivedStateLoading,
} = usePaymentReceivedState(paymentReceivedId);
const isLoading = isPaymentReceivedStateLoading;
if (isLoading) {
return <Spinner size={20} />;
}
const value = {
paymentReceivedId,
// # mail options
isPaymentReceivedStateLoading,
paymentReceivedMailState,
};
return (
<PaymentReceivedSendMailBootContext.Provider value={value}>
{children}
</PaymentReceivedSendMailBootContext.Provider>
);
};
PaymentReceivedSendMailBoot.displayName = 'PaymentReceivedSendMailBoot';
export const usePaymentReceivedSendMailBoot = () => {
return useContext<PaymentReceivedSendMailBootValues>(
PaymentReceivedSendMailBootContext,
);
};

View File

@@ -0,0 +1,25 @@
import { Stack } from '@/components';
import { Classes } from '@blueprintjs/core';
import { PaymentReceivedSendMailBoot } from './PaymentReceivedMailBoot';
import { PaymentReceivedSendMailForm } from './PaymentReceivedMailForm';
import { PaymentReceivedSendMailPreview } from './PaymentReceivedMailPreviewTabs';
// import { InvoiceSendMailFields } from './InvoiceSendMailFields';
import { SendMailViewHeader } from '../../Estimates/SendMailViewDrawer/SendMailViewHeader';
import { SendMailViewLayout } from '../../Estimates/SendMailViewDrawer/SendMailViewLayout';
import { PaymentReceivedSendMailFields } from './PaymentReceivedMailFields';
export function PaymentReceivedSendMailContent() {
return (
<Stack className={Classes.DRAWER_BODY}>
<PaymentReceivedSendMailBoot>
<PaymentReceivedSendMailForm>
<SendMailViewLayout
header={<SendMailViewHeader label={'Send Payment Mail'} />}
fields={<PaymentReceivedSendMailFields />}
preview={<PaymentReceivedSendMailPreview />}
/>
</PaymentReceivedSendMailForm>
</PaymentReceivedSendMailBoot>
</Stack>
);
}

View File

@@ -0,0 +1,42 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
const PaymentReceivedMailContent = React.lazy(() =>
import('./PaymentReceivedMailContent').then((module) => ({
default: module.PaymentReceivedSendMailContent,
})),
);
interface PaymentReceivedSendMailDrawerProps {
name: string;
isOpen?: boolean;
payload?: any;
}
function PaymentReceivedSendMailDrawerRoot({
name,
// #withDrawer
isOpen,
payload,
}: PaymentReceivedSendMailDrawerProps) {
return (
<Drawer
isOpen={isOpen}
name={name}
payload={payload}
size={'calc(100% - 10px)'}
>
<DrawerSuspense>
<PaymentReceivedMailContent />
</DrawerSuspense>
</Drawer>
);
}
export const PaymentReceivedSendMailDrawer = R.compose(withDrawers())(
PaymentReceivedSendMailDrawerRoot,
);

View File

@@ -0,0 +1,77 @@
// @ts-nocheck
import { Button, Intent } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FCheckbox, FFormGroup, FInputGroup, Group, Stack } from '@/components';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useDrawerActions } from '@/hooks/state';
import { SendMailViewToAddressField } from '../../Estimates/SendMailViewDrawer/SendMailViewToAddressField';
import { SendMailViewMessageField } from '../../Estimates/SendMailViewDrawer/SendMailViewMessageField';
const items = [];
const argsOptions = [];
export function PaymentReceivedSendMailFields() {
// const items = useInvoiceMailItems();
// const argsOptions = useSendInvoiceFormatArgsOptions();
return (
<Stack>
<Stack spacing={0} overflow="auto" flex="1" p={'30px'}>
<SendMailViewToAddressField
toMultiSelectProps={{ items }}
ccMultiSelectProps={{ items }}
bccMultiSelectProps={{ items }}
/>
<FFormGroup label={'Submit'} name={'subject'}>
<FInputGroup name={'subject'} large fastField />
</FFormGroup>
<SendMailViewMessageField argsOptions={argsOptions} />
<Group>
<FCheckbox name={'attachPdf'} label={'Attach PDF'} />
</Group>
</Stack>
<PaymentReceivedSendMailFooter />
</Stack>
);
}
function PaymentReceivedSendMailFooter() {
const { isSubmitting } = useFormikContext();
const { name } = useDrawerContext();
const { closeDrawer } = useDrawerActions();
const handleClose = () => {
closeDrawer(name);
};
return (
<Group
py={'12px'}
px={'16px'}
borderTop="1px solid #d8d8d9"
position={'apart'}
>
<Group spacing={10} ml={'auto'}>
<Button
disabled={isSubmitting}
onClick={handleClose}
style={{ minWidth: '65px' }}
>
Close
</Button>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
style={{ minWidth: '85px' }}
type="submit"
>
Send Mail
</Button>
</Group>
</Group>
);
}

View File

@@ -0,0 +1,81 @@
import { Form, Formik, FormikHelpers } from 'formik';
import { css } from '@emotion/css';
import { Intent } from '@blueprintjs/core';
import { PaymentReceivedSendMailFormSchema } from './_types';
import { AppToaster } from '@/components';
import { useSendSaleInvoiceMail } from '@/hooks/query';
import { usePaymentReceivedSendMailBoot } from './PaymentReceivedMailBoot';
import { useDrawerActions } from '@/hooks/state';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { transformToForm } from '@/utils';
import { PaymentReceivedSendMailFormValues } from './_types';
const initialValues: PaymentReceivedSendMailFormValues = {
subject: '',
message: '',
to: [],
cc: [],
bcc: [],
attachPdf: true,
};
interface PaymentReceivedSendMailFormProps {
children: React.ReactNode;
}
export function PaymentReceivedSendMailForm({
children,
}: PaymentReceivedSendMailFormProps) {
const { mutateAsync: sendInvoiceMail } = useSendSaleInvoiceMail();
const { paymentReceivedId, paymentReceivedMailState } =
usePaymentReceivedSendMailBoot();
const { name } = useDrawerContext();
const { closeDrawer } = useDrawerActions();
const _initialValues: PaymentReceivedSendMailFormValues = {
...initialValues,
...transformToForm(paymentReceivedMailState, initialValues),
};
const handleSubmit = (
values: PaymentReceivedSendMailFormValues,
{ setSubmitting }: FormikHelpers<PaymentReceivedSendMailFormValues>,
) => {
setSubmitting(true);
sendInvoiceMail({ id: paymentReceivedId, values: { ...values } })
.then(() => {
AppToaster.show({
message: 'The invoice mail has been sent to the customer.',
intent: Intent.SUCCESS,
});
setSubmitting(false);
closeDrawer(name);
})
.catch(() => {
setSubmitting(false);
AppToaster.show({
message: 'Something went wrong!',
intent: Intent.SUCCESS,
});
});
};
return (
<Formik
initialValues={_initialValues}
validationSchema={PaymentReceivedSendMailFormSchema}
onSubmit={handleSubmit}
>
<Form
className={css`
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
`}
>
{children}
</Form>
</Formik>
);
}

View File

@@ -0,0 +1,13 @@
import { SendViewPreviewHeader } from "../../Estimates/SendMailViewDrawer/SendMailViewPreviewHeader";
export function PaymentReceivedMailPreviewHeader() {
return (
<SendViewPreviewHeader
companyName="A"
customerName="A"
subject={'adsfsdf'}
from={['a.bouhuolia@gmail.com']}
to={['a.bouhuolia@gmail.com']}
/>
);
}

View File

@@ -0,0 +1,32 @@
import { Spinner } from '@blueprintjs/core';
import { Stack } from '@/components';
import { useGetPaymentReceiveHtml } from '@/hooks/query';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { SendMailViewPreviewPdfIframe } from '../../Estimates/SendMailViewDrawer/SendMailViewPreviewPdfIframe';
import { PaymentReceivedMailPreviewHeader } from './PaymentReceivedMailPreviewHeader';
export function PaymentReceivedSendMailPreviewPdf() {
return (
<Stack>
<PaymentReceivedMailPreviewHeader />
<Stack px={4} py={6}>
<PaymentReceivedSendPdfPreviewIframe />
</Stack>
</Stack>
);
}
function PaymentReceivedSendPdfPreviewIframe() {
const { payload } = useDrawerContext();
const { data, isLoading } = useGetPaymentReceiveHtml(
payload?.paymentReceivedId,
);
if (isLoading && data) {
return <Spinner size={20} />;
}
const iframeSrcDoc = data?.htmlContent;
return <SendMailViewPreviewPdfIframe srcDoc={iframeSrcDoc} />;
}

View File

@@ -0,0 +1,41 @@
import { css } from '@emotion/css';
import { PaymentReceivedMailReceipt } from './PaymentReceivedMailReceipt';
import { PaymentReceivedMailPreviewHeader } from './PaymentReceivedMailPreviewHeader';
import { Stack } from '@/components';
const defaultPaymentReceiptMailProps = {
companyName: 'Company Name',
companyLogoUri: 'https://via.placeholder.com/150',
primaryColor: 'rgb(0, 82, 204)',
paymentDate: '2021-01-01',
paymentDateLabel: 'Payment Date',
total: '100.00',
totalLabel: 'Total',
paymentNumber: '123456',
paymentNumberLabel: 'Payment #',
message: 'Thank you for your payment!',
subtotal: '100.00',
subtotalLabel: 'Subtotal',
items: [{ label: 'Invoice 1', total: '100.00' }],
};
export function PaymentReceivedMailPreviewReceipt() {
return (
<Stack>
<PaymentReceivedMailPreviewHeader />
<Stack px={4} py={6}>
<PaymentReceivedMailReceipt
{...defaultPaymentReceiptMailProps}
className={css`
margin: 0 auto;
border-radius: 5px !important;
transform: scale(0.9);
transform-origin: top;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.05) !important;
`}
/>
</Stack>
</Stack>
);
}

View File

@@ -0,0 +1,39 @@
import { lazy, Suspense } from 'react';
import { Tab } from '@blueprintjs/core';
import { SendMailViewPreviewTabs } from '../../Estimates/SendMailViewDrawer/SendMailViewPreviewTabs';
const PaymentReceivedMailPreviewReceipt = lazy(() =>
import('./PaymentReceivedMailPreviewReceipt').then((module) => ({
default: module.PaymentReceivedMailPreviewReceipt,
})),
);
const PaymentReceivedSendMailPreviewPdf = lazy(() =>
import('./PaymentReceivedMailPreviewPdf').then((module) => ({
default: module.PaymentReceivedSendMailPreviewPdf,
})),
);
export function PaymentReceivedSendMailPreview() {
return (
<SendMailViewPreviewTabs>
<Tab
id={'payment-page'}
title={'Payment page'}
panel={
<Suspense>
<PaymentReceivedMailPreviewReceipt />
</Suspense>
}
/>
<Tab
id="pdf-document"
title={'PDF document'}
panel={
<Suspense>
<PaymentReceivedSendMailPreviewPdf />
</Suspense>
}
/>
</SendMailViewPreviewTabs>
);
}

View File

@@ -0,0 +1,145 @@
import { x } from '@xstyled/emotion';
import { Group, Stack } from '@/components';
import {
SendMailReceipt,
SendMailReceiptProps,
} from '../../Estimates/SendMailViewDrawer/SendMailViewReceiptPreview';
export interface PaymentReceivedMailReceiptProps extends SendMailReceiptProps {
// # Company
companyName: string;
companyLogoUri?: string;
// # Colors
primaryColor?: string;
// # Payment date
paymentDate: string;
paymentDateLabel?: string;
// # Total
total: string;
totalLabel?: string;
// # Subtotal
subtotal: string;
subtotalLabel?: string;
// # Invoice number
paymentNumber: string;
paymentNumberLabel?: string;
// # Mail message
message: string;
// # Paid Invoices
items?: Array<{ label: string; total: string }>;
}
export function PaymentReceivedMailReceipt({
// # Company
companyName,
companyLogoUri,
// # Colors
primaryColor = 'rgb(0, 82, 204)',
// # Payment date
paymentDate,
paymentDateLabel = 'Payment Date',
// # Total
total,
totalLabel = 'Total',
// # Payment number
paymentNumber,
paymentNumberLabel = 'Payment #',
// # Mail message
message,
// # Subtotal
subtotal,
subtotalLabel = 'Subtotal',
// # Paid Invoices
items = [],
...restProps
}: PaymentReceivedMailReceiptProps) {
return (
<SendMailReceipt {...restProps}>
<Stack spacing={16} textAlign={'center'}>
{companyLogoUri && <SendMailReceipt.CompanyLogo src={companyLogoUri} />}
<Stack spacing={8}>
<x.h1 m={0} fontSize={'18px'} fontWeight={500} color="#404854">
{companyName}
</x.h1>
<x.h3 color="#383E47" fontWeight={500}>
{total}
</x.h3>
<x.span fontSize={'13px'} color="#404854">
{paymentNumberLabel} {paymentNumber}
</x.span>
<x.span fontSize={'13px'} color="#404854">
{paymentDateLabel} {paymentDate}
</x.span>
</Stack>
</Stack>
<x.p m={0} whiteSpace={'pre-line'} color="#252A31">
{message}
</x.p>
<Stack spacing={0}>
{items?.map((item, key) => (
<Group
key={key}
h={'40px'}
position={'apart'}
borderBottomStyle="solid"
borderBottomWidth={'1px'}
borderBottomColor={'#D9D9D9'}
borderTopStyle="solid"
borderTopColor={'#D9D9D9'}
borderTopWidth={'1px'}
>
<x.span>{item.label}</x.span>
<x.span>{item.total}</x.span>
</Group>
))}
<Group
h={'40px'}
position={'apart'}
borderBottomStyle="solid"
borderBottomWidth={'1px'}
borderBottomColor={'#000'}
>
<x.span fontWeight={500}>{subtotalLabel}</x.span>
<x.span fontWeight={600} fontSize={15}>
{subtotal}
</x.span>
</Group>
<Group
h={'40px'}
position={'apart'}
borderBottomStyle="solid"
borderBottomWidth={'1px'}
borderColor={'#000'}
>
<x.span fontWeight={500}>{totalLabel}</x.span>
<x.span fontWeight={600} fontSize={15}>
{total}
</x.span>
</Group>
</Stack>
</SendMailReceipt>
);
}

View File

@@ -0,0 +1,20 @@
import * as Yup from 'yup';
export const PaymentReceivedSendMailFormSchema = Yup.object().shape({
subject: Yup.string().required('Subject is required'),
message: Yup.string().required('Message is required'),
to: Yup.array()
.of(Yup.string().email('Invalid email address'))
.required('To address is required'),
cc: Yup.array().of(Yup.string().email('Invalid email address')),
bcc: Yup.array().of(Yup.string().email('Invalid email address')),
});
export interface PaymentReceivedSendMailFormValues {
subject: string;
message: string;
to: string[];
cc: string[];
bcc: string[];
attachPdf: boolean;
}

View File

@@ -0,0 +1 @@
export * from './PaymentReceivedMailDrawer';

View File

@@ -80,7 +80,7 @@ function PaymentsReceivedDataTable({
// Handle mail send payment receive.
const handleSendMailPayment = ({ id }) => {
openDialog(DialogsName.PaymentMail, { paymentReceiveId: id });
openDrawer(DRAWERS.PAYMENT_RECEIVED_SEND_MAIL, { paymentReceivedId: id });
};
// Handle cell click.