feat: wip receipt mail preview

This commit is contained in:
Ahmed Bouhuolia
2024-11-18 15:52:13 +02:00
parent 7df316aa56
commit 7df6aa4110
18 changed files with 225 additions and 291 deletions

View File

@@ -1,4 +1,4 @@
import { Group, Stack } from '@/components';
import { Stack } from '@/components';
import { Classes } from '@blueprintjs/core';
import { InvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
import { InvoiceSendMailForm } from './InvoiceSendMailForm';

View File

@@ -2,8 +2,6 @@
import React from 'react';
import { useFormikContext } from 'formik';
import ReceiptNumberDialog from '@/containers/Dialogs/ReceiptNumberDialog';
import ReceiptFormMailDeliverDialog from './Dialogs/ReceiptFormMailDeliverDialog';
import { DialogsName } from '@/constants/dialogs';
/**
* Receipt form dialogs.
@@ -29,9 +27,6 @@ export default function ReceiptFormDialogs() {
dialogName={'receipt-number-form'}
onConfirm={handleReceiptNumberFormConfirm}
/>
<ReceiptFormMailDeliverDialog
dialogName={DialogsName.ReceiptFormMailDeliver}
/>
</>
);
}

View File

@@ -1,34 +0,0 @@
// @ts-nocheck
import React from 'react';
import { Dialog, DialogSuspense } from '@/components';
import withDialogRedux from '@/components/DialogReduxConnect';
import { compose } from '@/utils';
const ReceiptMailDialogBody = React.lazy(
() => import('./ReceiptMailDialogBody'),
);
/**
* Receipt mail dialog.
*/
function ReceiptMailDialog({
dialogName,
payload: { receiptId = null },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={'Receipt Mail'}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
style={{ width: 600 }}
>
<DialogSuspense>
<ReceiptMailDialogBody receiptId={receiptId} />
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(ReceiptMailDialog);

View File

@@ -1,33 +0,0 @@
// @ts-nocheck
import * as R from 'ramda';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import ReceiptMailDialogContent, {
ReceiptMailDialogContentProps,
} from './ReceiptMailDialogContent';
import { DialogsName } from '@/constants/dialogs';
interface ReceiptMailDialogBodyProps extends ReceiptMailDialogContentProps {}
function ReceiptMailDialogBodyRoot({
receiptId,
// #withDialogActions
closeDialog,
}: ReceiptMailDialogBodyProps) {
const handleCancelClick = () => {
closeDialog(DialogsName.ReceiptMail);
};
const handleSubmitClick = () => {
closeDialog(DialogsName.ReceiptMail);
};
return (
<ReceiptMailDialogContent
receiptId={receiptId}
onFormSubmit={handleSubmitClick}
onCancelClick={handleCancelClick}
/>
);
}
export default R.compose(withDialogActions)(ReceiptMailDialogBodyRoot);

View File

@@ -1,49 +0,0 @@
// @ts-nocheck
import React, { createContext } from 'react';
import { useSaleReceiptDefaultOptions } from '@/hooks/query';
import { DialogContent } from '@/components';
interface ReceiptMailDialogBootValues {
receiptId: number;
mailOptions: any;
redirectToReceiptsList: boolean;
}
const ReceiptMailDialogBootContext =
createContext<ReceiptMailDialogBootValues>();
interface ReceiptMailDialogBootProps {
receiptId: number;
children: React.ReactNode;
redirectToReceiptsList?: boolean;
}
/**
* Receipt mail dialog boot provider.
*/
function ReceiptMailDialogBoot({
receiptId,
redirectToReceiptsList = false,
...props
}: ReceiptMailDialogBootProps) {
const { data: mailOptions, isLoading: isMailOptionsLoading } =
useSaleReceiptDefaultOptions(receiptId);
const provider = {
saleReceiptId: receiptId,
mailOptions,
isMailOptionsLoading,
redirectToReceiptsList,
};
return (
<DialogContent isLoading={isMailOptionsLoading}>
<ReceiptMailDialogBootContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useReceiptMailDialogBoot = () =>
React.useContext<ReceiptMailDialogBootValues>(ReceiptMailDialogBootContext);
export { ReceiptMailDialogBoot, useReceiptMailDialogBoot };

View File

@@ -1,23 +0,0 @@
import React from 'react';
import { ReceiptMailDialogBoot } from './ReceiptMailDialogBoot';
import { ReceiptMailDialogForm } from './ReceiptMailDialogForm';
export interface ReceiptMailDialogContentProps {
receiptId: number;
onFormSubmit?: () => void;
onCancelClick?: () => void;
}
export default function ReceiptMailDialogContent({
receiptId,
onFormSubmit,
onCancelClick
}: ReceiptMailDialogContentProps) {
return (
<ReceiptMailDialogBoot receiptId={receiptId}>
<ReceiptMailDialogForm
onFormSubmit={onFormSubmit}
onCancelClick={onCancelClick}
/>
</ReceiptMailDialogBoot>
);
}

View File

@@ -1,79 +0,0 @@
// @ts-nocheck
import { Formik, FormikBag } from 'formik';
import * as R from 'ramda';
import { Intent } from '@blueprintjs/core';
import { useReceiptMailDialogBoot } from './ReceiptMailDialogBoot';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { DialogsName } from '@/constants/dialogs';
import { useSendSaleReceiptMail } from '@/hooks/query';
import { ReceiptMailDialogFormContent } from './ReceiptMailDialogFormContent';
import {
initialMailNotificationValues,
MailNotificationFormValues,
transformMailFormToInitialValues,
transformMailFormToRequest,
} from '@/containers/SendMailNotification/utils';
import { AppToaster } from '@/components';
const initialFormValues = {
...initialMailNotificationValues,
attachReceipt: true,
};
interface ReceiptMailFormValues extends MailNotificationFormValues {
attachReceipt: boolean;
}
interface ReceiptMailDialogFormProps {
onFormSubmit?: () => void;
onCancelClick?: () => void;
}
export function ReceiptMailDialogForm({
// #props
onFormSubmit,
onCancelClick,
}: ReceiptMailDialogFormProps) {
const { mailOptions, saleReceiptId } = useReceiptMailDialogBoot();
const { mutateAsync: sendReceiptMail } = useSendSaleReceiptMail();
// Transformes mail options to initial form values.
const initialValues = transformMailFormToInitialValues(
mailOptions,
initialFormValues,
);
// Handle the form submitting.
const handleSubmit = (
values: ReceiptMailFormValues,
{ setSubmitting }: FormikBag<ReceiptMailFormValues>,
) => {
const reqValues = transformMailFormToRequest(values);
setSubmitting(true);
sendReceiptMail([saleReceiptId, 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} onSubmit={handleSubmit}>
<ReceiptMailDialogFormContent onClose={handleClose} />
</Formik>
);
}

View File

@@ -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 { useReceiptMailDialogBoot } from './ReceiptMailDialogBoot';
import { saveInvoke } from '@/utils';
interface SendMailNotificationFormProps {
onClose?: () => void;
}
export function ReceiptMailDialogFormContent({
onClose,
}: SendMailNotificationFormProps) {
const { mailOptions } = useReceiptMailDialogBoot();
const { isSubmitting } = useFormikContext();
const handleClose = () => {
saveInvoke(onClose);
};
return (
<Form>
<div className={Classes.DIALOG_BODY}>
<MailNotificationForm
fromAddresses={mailOptions.from_options}
toAddresses={mailOptions.to_options}
/>
<AttachFormGroup name={'attachReceipt:'} inline>
<FSwitch name={'attachReceipt:'} label={'Attach Receipt'} />
</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;
`;

View File

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

View File

@@ -0,0 +1,55 @@
import React, { createContext, useContext } from 'react';
import { Spinner } from '@blueprintjs/core';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useSaleReceiptMailState } from '@/hooks/query';
interface ReceiptSendMailBootValues {
receiptId: number;
receiptMailState: any;
isReceiptMailState: boolean;
}
interface ReceiptSendMailBootProps {
children: React.ReactNode;
}
const ReceiptSendMailContentBootContext =
createContext<ReceiptSendMailBootValues>({} as ReceiptSendMailBootValues);
export const ReceiptSendMailBoot = ({
children,
}: ReceiptSendMailBootProps) => {
const {
payload: { receiptId },
} = useDrawerContext();
// Receipt mail options.
const { data: receiptMailState, isLoading: isReceiptMailState } =
useSaleReceiptMailState(receiptId);
const isLoading = isReceiptMailState;
if (isLoading) {
return <Spinner size={20} />;
}
const value = {
receiptId,
// # Receipt mail options
isReceiptMailState,
receiptMailState,
};
return (
<ReceiptSendMailContentBootContext.Provider value={value}>
{children}
</ReceiptSendMailContentBootContext.Provider>
);
};
ReceiptSendMailBoot.displayName = 'ReceiptSendMailBoot';
export const useReceiptSendMailBoot = () => {
return useContext<ReceiptSendMailBootValues>(
ReceiptSendMailContentBootContext,
);
};

View File

@@ -0,0 +1,24 @@
import { Stack } from '@/components';
import { Classes } from '@blueprintjs/core';
import { SendMailViewHeader } from '../../Estimates/SendMailViewDrawer/SendMailViewHeader';
import { SendMailViewLayout } from '../../Estimates/SendMailViewDrawer/SendMailViewLayout';
import { ReceiptSendMailBoot } from './ReceiptSendMailBoot';
import { ReceiptSendMailForm } from './ReceiptSendMailForm';
import { ReceiptSendMailFormFields } from './ReceiptSendMailFormFields';
import { ReceiptSendMailPreviewTabs } from './ReceiptSendMailPreviewTabs';
export function InvoiceSendMailContent() {
return (
<Stack className={Classes.DRAWER_BODY}>
<ReceiptSendMailBoot>
<ReceiptSendMailForm>
<SendMailViewLayout
header={<SendMailViewHeader label={'Send Invoice Mail'} />}
fields={<ReceiptSendMailFormFields />}
preview={<ReceiptSendMailPreviewTabs />}
/>
</ReceiptSendMailForm>
</ReceiptSendMailBoot>
</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 ReceiptSendMailContent = React.lazy(() =>
import('./ReceiptSendMailContent').then((module) => ({
default: module.ReceiptSendMailContent,
})),
);
interface ReceiptSendMailDrawerProps {
name: string;
isOpen?: boolean;
payload?: any;
}
function ReceiptSendMailDrawerRoot({
name,
// #withDrawer
isOpen,
payload,
}: ReceiptSendMailDrawerProps) {
return (
<Drawer
isOpen={isOpen}
name={name}
payload={payload}
size={'calc(100% - 10px)'}
>
<DrawerSuspense>
<ReceiptSendMailContent />
</DrawerSuspense>
</Drawer>
);
}
export const ReceiptSendMailDrawer = R.compose(withDrawers())(
ReceiptSendMailDrawerRoot,
);

View File

@@ -0,0 +1,11 @@
import * as Yup from 'yup';
export const ReceiptSendMailFormSchema = 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')),
});

View File

@@ -0,0 +1,78 @@
import { Form, Formik, FormikHelpers } from 'formik';
import { css } from '@emotion/css';
import { Intent } from '@blueprintjs/core';
import { ReceiptSendMailFormValues } from './_types';
import { ReceiptSendMailFormSchema } from './ReceiptSendMailForm.schema';
import { useSendSaleReceiptMail } from '@/hooks/query';
import { AppToaster } from '@/components';
import { useReceiptSendMailBoot } from './ReceiptSendMailBoot';
import { useDrawerActions } from '@/hooks/state';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { transformToForm } from '@/utils';
const initialValues: ReceiptSendMailFormValues = {
subject: '',
message: '',
to: [],
cc: [],
bcc: [],
attachPdf: true,
};
interface ReceiptSendMailFormProps {
children: React.ReactNode;
}
export function ReceiptSendMailForm({ children }: ReceiptSendMailFormProps) {
const { mutateAsync: sendReceiptMail } = useSendSaleReceiptMail();
const { receiptId, receiptMailState } = useReceiptSendMailBoot();
const { name } = useDrawerContext();
const { closeDrawer } = useDrawerActions();
const _initialValues: ReceiptSendMailFormValues = {
...initialValues,
...transformToForm(receiptMailState, initialValues),
};
const handleSubmit = (
values: ReceiptSendMailFormValues,
{ setSubmitting }: FormikHelpers<ReceiptSendMailFormValues>,
) => {
setSubmitting(true);
sendReceiptMail({ id: receiptId, values: { ...values } })
.then(() => {
AppToaster.show({
message: 'The receipt mail has been sent to the customer.',
intent: Intent.SUCCESS,
});
setSubmitting(false);
closeDrawer(name);
})
.catch((error) => {
setSubmitting(false);
AppToaster.show({
message: 'Something went wrong!',
intent: Intent.SUCCESS,
});
});
};
return (
<Formik
initialValues={_initialValues}
validationSchema={ReceiptSendMailFormSchema}
onSubmit={handleSubmit}
>
<Form
className={css`
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
`}
>
{children}
</Form>
</Formik>
);
}

View File

@@ -0,0 +1,6 @@
export function ReceiptSendMailFormFields() {
return null;
}

View File

@@ -0,0 +1,6 @@
export function ReceiptSendMailPreviewTabs() {
return null;
}

View File

@@ -0,0 +1 @@
export interface ReceiptSendMailFormValues {}

View File

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