feat: payment received mail preview

This commit is contained in:
Ahmed Bouhuolia
2024-11-21 14:32:28 +02:00
parent c5c85bdfbe
commit b6f3c0145f
27 changed files with 422 additions and 366 deletions

View File

@@ -1,43 +0,0 @@
// @ts-nocheck
import React from 'react';
import { Dialog, DialogSuspense } from '@/components';
import withDialogRedux from '@/components/DialogReduxConnect';
import { compose } from '@/utils';
const PaymentMailDialogContent = React.lazy(
() => import('./PaymentMailDialogContent'),
);
/**
* Payment mail dialog.
*/
function PaymentMailDialog({
dialogName,
payload: {
paymentReceiveId = null,
// Redirects to the payments list on mail submitting.
redirectToPaymentsList = false,
},
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={'Payment Mail'}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
style={{ width: 600 }}
>
<DialogSuspense>
<PaymentMailDialogContent
dialogName={dialogName}
paymentReceiveId={paymentReceiveId}
redirectToPaymentsList={redirectToPaymentsList}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(PaymentMailDialog);

View File

@@ -1,48 +0,0 @@
// @ts-nocheck
import React, { createContext } from 'react';
import { usePaymentReceiveDefaultOptions } from '@/hooks/query';
import { DialogContent } from '@/components';
interface PaymentMailDialogBootValues {
paymentReceiveId: number;
mailOptions: any;
}
const PaymentMailDialogBootContext =
createContext<PaymentMailDialogBootValues>();
interface PaymentMailDialogBootProps {
paymentReceiveId: number;
redirectToPaymentsList: boolean;
children: React.ReactNode;
}
/**
* Payment mail dialog boot provider.
*/
function PaymentMailDialogBoot({
paymentReceiveId,
redirectToPaymentsList,
...props
}: PaymentMailDialogBootProps) {
const { data: mailOptions, isLoading: isMailOptionsLoading } =
usePaymentReceiveDefaultOptions(paymentReceiveId);
const provider = {
mailOptions,
isMailOptionsLoading,
paymentReceiveId,
redirectToPaymentsList
};
return (
<DialogContent isLoading={isMailOptionsLoading}>
<PaymentMailDialogBootContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const usePaymentMailDialogBoot = () =>
React.useContext<PaymentMailDialogBootValues>(PaymentMailDialogBootContext);
export { PaymentMailDialogBoot, usePaymentMailDialogBoot };

View File

@@ -1,22 +0,0 @@
import { PaymentMailDialogBoot } from './PaymentMailDialogBoot';
import { PaymentMailDialogForm } from './PaymentMailDialogForm';
interface PaymentMailDialogContentProps {
dialogName: string;
paymentReceiveId: number;
redirectToPaymentsList: boolean;
}
export default function PaymentMailDialogContent({
dialogName,
paymentReceiveId,
redirectToPaymentsList,
}: PaymentMailDialogContentProps) {
return (
<PaymentMailDialogBoot
paymentReceiveId={paymentReceiveId}
redirectToPaymentsList={redirectToPaymentsList}
>
<PaymentMailDialogForm />
</PaymentMailDialogBoot>
);
}

View File

@@ -1,87 +0,0 @@
// @ts-nocheck
import { Formik, FormikBag } from 'formik';
import * as R from 'ramda';
import { Intent } from '@blueprintjs/core';
import { usePaymentMailDialogBoot } from './PaymentMailDialogBoot';
import { DialogsName } from '@/constants/dialogs';
import { useSendPaymentReceiveMail } from '@/hooks/query';
import { PaymentMailDialogFormContent } from './PaymentMailDialogFormContent';
import {
MailNotificationFormValues,
initialMailNotificationValues,
transformMailFormToRequest,
transformMailFormToInitialValues,
} from '@/containers/SendMailNotification/utils';
import { AppToaster } from '@/components';
import { useHistory } from 'react-router-dom';
import withDialogActions from '@/containers/Dialog/withDialogActions';
const initialFormValues = {
...initialMailNotificationValues,
attachPayment: true,
};
interface PaymentMailFormValue extends MailNotificationFormValues {
attachPayment: boolean;
}
export function PaymentMailDialogFormRoot({
// #withDialogActions
closeDialog,
}) {
const { mailOptions, paymentReceiveId, redirectToPaymentsList } =
usePaymentMailDialogBoot();
const { mutateAsync: sendPaymentMail } = useSendPaymentReceiveMail();
const history = useHistory();
const initialValues = transformMailFormToInitialValues(
mailOptions,
initialFormValues,
);
// Handles the form submitting.
const handleSubmit = (
values: PaymentMailFormValue,
{ setSubmitting }: FormikBag<PaymentMailFormValue>,
) => {
const reqValues = transformMailFormToRequest(values);
setSubmitting(true);
sendPaymentMail([paymentReceiveId, reqValues])
.then(() => {
AppToaster.show({
message: 'The mail notification has been sent successfully.',
intent: Intent.SUCCESS,
});
setSubmitting(false);
closeDialog(DialogsName.PaymentMail);
// Redirects to payments list if the option is enabled.
if (redirectToPaymentsList) {
history.push('/payments-received');
}
})
.catch(() => {
AppToaster.show({
message: 'Something went wrong.',
intent: Intent.DANGER,
});
setSubmitting(false);
closeDialog(DialogsName.PaymentMail);
});
};
const handleClose = () => {
closeDialog(DialogsName.PaymentMail);
};
return (
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
<PaymentMailDialogFormContent onClose={handleClose} />
</Formik>
);
}
export const PaymentMailDialogForm = R.compose(withDialogActions)(
PaymentMailDialogFormRoot,
);

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 { saveInvoke } from '@/utils';
import { usePaymentMailDialogBoot } from './PaymentMailDialogBoot';
interface PaymentMailDialogFormContentProps {
onClose?: () => void;
}
export function PaymentMailDialogFormContent({
onClose,
}: PaymentMailDialogFormContentProps) {
const { mailOptions } = usePaymentMailDialogBoot();
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={'attachPayment'} inline>
<FSwitch name={'attachPayment'} label={'Attach Payment'} />
</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 './PaymentMailDialog';

View File

@@ -1,15 +1,15 @@
import React, { createContext, useContext } from 'react';
import { Spinner } from '@blueprintjs/core';
import {
PaymentReceivedStateResponse,
usePaymentReceivedState,
GetPaymentReceivedMailStateResponse,
usePaymentReceivedMailState,
} from '@/hooks/query';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
interface PaymentReceivedSendMailBootValues {
paymentReceivedId: number;
paymentReceivedMailState: PaymentReceivedStateResponse | undefined;
paymentReceivedMailState: GetPaymentReceivedMailStateResponse | undefined;
isPaymentReceivedStateLoading: boolean;
}
interface InvoiceSendMailBootProps {
@@ -31,7 +31,7 @@ export const PaymentReceivedSendMailBoot = ({
const {
data: paymentReceivedMailState,
isLoading: isPaymentReceivedStateLoading,
} = usePaymentReceivedState(paymentReceivedId);
} = usePaymentReceivedMailState(paymentReceivedId);
const isLoading = isPaymentReceivedStateLoading;

View File

@@ -3,7 +3,6 @@ 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';

View File

@@ -6,16 +6,15 @@ 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 = [];
import { usePaymentReceivedFormatArgsOptions, } from './_hooks';
import { useSendMailItems } from '../../Estimates/SendMailViewDrawer/hooks';
export function PaymentReceivedSendMailFields() {
// const items = useInvoiceMailItems();
// const argsOptions = useSendInvoiceFormatArgsOptions();
const argsOptions = usePaymentReceivedFormatArgsOptions();
const items = useSendMailItems();
return (
<Stack>
<Stack flex={1}>
<Stack spacing={0} overflow="auto" flex="1" p={'30px'}>
<SendMailViewToAddressField
toMultiSelectProps={{ items }}

View File

@@ -16,6 +16,7 @@ const initialValues: PaymentReceivedSendMailFormValues = {
to: [],
cc: [],
bcc: [],
from: [],
attachPdf: true,
};

View File

@@ -1,13 +1,23 @@
import { SendViewPreviewHeader } from "../../Estimates/SendMailViewDrawer/SendMailViewPreviewHeader";
import { useFormikContext } from 'formik';
import { SendViewPreviewHeader } from '../../Estimates/SendMailViewDrawer/SendMailViewPreviewHeader';
import { usePaymentReceivedMailSubject } from './_hooks';
import { PaymentReceivedSendMailFormValues } from './_types';
import { usePaymentReceivedSendMailBoot } from './PaymentReceivedMailBoot';
export function PaymentReceivedMailPreviewHeader() {
const subject = usePaymentReceivedMailSubject();
const { paymentReceivedMailState } = usePaymentReceivedSendMailBoot();
const {
values: { from, to },
} = useFormikContext<PaymentReceivedSendMailFormValues>();
return (
<SendViewPreviewHeader
companyName="A"
customerName="A"
subject={'adsfsdf'}
from={['a.bouhuolia@gmail.com']}
to={['a.bouhuolia@gmail.com']}
companyName={paymentReceivedMailState?.companyName || ''}
customerName={paymentReceivedMailState?.customerName || ''}
subject={subject}
from={from}
to={to}
/>
);
}

View File

@@ -7,7 +7,7 @@ import { PaymentReceivedMailPreviewHeader } from './PaymentReceivedMailPreviewHe
export function PaymentReceivedSendMailPreviewPdf() {
return (
<Stack>
<Stack flex={1}>
<PaymentReceivedMailPreviewHeader />
<Stack px={4} py={6}>

View File

@@ -21,7 +21,7 @@ const defaultPaymentReceiptMailProps = {
export function PaymentReceivedMailPreviewReceipt() {
return (
<Stack>
<Stack flex={1}>
<PaymentReceivedMailPreviewHeader />
<Stack px={4} py={6}>

View File

@@ -0,0 +1,55 @@
import { useMemo } from 'react';
import { useFormikContext } from 'formik';
import { SelectOptionProps } from '@blueprintjs-formik/select';
import { usePaymentReceivedSendMailBoot } from './PaymentReceivedMailBoot';
import { PaymentReceivedSendMailFormValues } from './_types';
import {
formatMailMessage,
transformEmailArgs,
transformFormatArgsToOptions,
} from '../../Estimates/SendMailViewDrawer/hooks';
/**
* Retrieves the mail format arguments of payment received mail.
* @returns {Record<string, string>}
*/
export const usePaymentReceivedMailFormatArgs = (): Record<string, string> => {
const { paymentReceivedMailState } = usePaymentReceivedSendMailBoot();
return useMemo(() => {
return transformEmailArgs(paymentReceivedMailState?.formatArgs || {});
}, [paymentReceivedMailState]);
};
/**
* Retrieves the formatted receipt subject.
* @returns {string}
*/
export const usePaymentReceivedMailSubject = (): string => {
const { values } = useFormikContext<PaymentReceivedSendMailFormValues>();
const formatArgs = usePaymentReceivedMailFormatArgs();
return formatMailMessage(values?.subject, formatArgs);
};
/**
* Retrieves the payment received format options.
* @returns {Array<SelectOptionProps>}
*/
export const usePaymentReceivedFormatArgsOptions =
(): Array<SelectOptionProps> => {
const formatArgs = usePaymentReceivedMailFormatArgs();
return transformFormatArgsToOptions(formatArgs);
};
/**
* Retrieves the formatted estimate message.
* @returns {string}
*/
export const useSendPaymentReceivedtMailMessage = (): string => {
const { values } = useFormikContext<PaymentReceivedSendMailFormValues>();
const formatArgs = usePaymentReceivedMailFormatArgs();
return formatMailMessage(values?.message, formatArgs);
};

View File

@@ -1,4 +1,5 @@
import * as Yup from 'yup';
import { SendMailViewFormValues } from '../../Estimates/SendMailViewDrawer/_types';
export const PaymentReceivedSendMailFormSchema = Yup.object().shape({
subject: Yup.string().required('Subject is required'),
@@ -10,11 +11,7 @@ export const PaymentReceivedSendMailFormSchema = Yup.object().shape({
bcc: Yup.array().of(Yup.string().email('Invalid email address')),
});
export interface PaymentReceivedSendMailFormValues {
subject: string;
message: string;
to: string[];
cc: string[];
bcc: string[];
export interface PaymentReceivedSendMailFormValues
extends SendMailViewFormValues {
attachPdf: boolean;
}

View File

@@ -2,12 +2,12 @@
import React, { createContext, useContext } from 'react';
import { Spinner } from '@blueprintjs/core';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useSaleInvoiceMailState } from '@/hooks/query';
import { GetSaleReceiptMailStateResponse, useSaleInvoiceMailState, useSaleReceiptMailState } from '@/hooks/query';
interface ReceiptSendMailBootValues {
receiptId: number;
receiptMailState: any;
receiptMailState: GetSaleReceiptMailStateResponse | null;
isReceiptMailState: boolean;
}
interface ReceiptSendMailBootProps {
@@ -24,13 +24,13 @@ export const ReceiptSendMailBoot = ({ children }: ReceiptSendMailBootProps) => {
// Receipt mail options.
const { data: receiptMailState, isLoading: isReceiptMailState } =
useSaleInvoiceMailState(receiptId);
useSaleReceiptMailState(receiptId);
const isLoading = isReceiptMailState;
// if (isLoading) {
// return <Spinner size={20} />;
// }
if (isLoading) {
return <Spinner size={20} />;
}
const value = {
receiptId,

View File

@@ -1,17 +1,19 @@
import { Button, Intent } from "@blueprintjs/core";
import { useFormikContext } from "formik";
import { FCheckbox, FFormGroup, FInputGroup, Group, Stack } from "@/components";
import { SendMailViewToAddressField } from "../../Estimates/SendMailViewDrawer/SendMailViewToAddressField";
import { SendMailViewMessageField } from "../../Estimates/SendMailViewDrawer/SendMailViewMessageField";
import { Button, Intent } from "@blueprintjs/core";
import { useDrawerActions } from "@/hooks/state";
import { useDrawerContext } from "@/components/Drawer/DrawerProvider";
import { useFormikContext } from "formik";
const items: Array<any> = [];
const argsOptions: Array<any> = [];
import { useSendReceiptFormatArgsOptions } from "./_hooks";
import { useSendMailItems } from "../../Estimates/SendMailViewDrawer/hooks";
export function ReceiptSendMailFormFields() {
const argsOptions = useSendReceiptFormatArgsOptions();
const items = useSendMailItems();
return (
<Stack>
<Stack flex={1}>
<Stack spacing={0} overflow="auto" flex="1" p={'30px'}>
<SendMailViewToAddressField
toMultiSelectProps={{ items }}

View File

@@ -1,13 +1,23 @@
import { useFormikContext } from 'formik';
import { SendViewPreviewHeader } from '../../Estimates/SendMailViewDrawer/SendMailViewPreviewHeader';
import { useSendReceiptMailSubject } from './_hooks';
import { useReceiptSendMailBoot } from './ReceiptSendMailBoot';
import { ReceiptSendMailFormValues } from './_types';
export function ReceiptSendMailPreviewHeader() {
const subject = useSendReceiptMailSubject();
const { receiptMailState } = useReceiptSendMailBoot();
const {
values: { to, from },
} = useFormikContext<ReceiptSendMailFormValues>();
return (
<SendViewPreviewHeader
companyName="A"
customerName="A"
subject={'adsfsdf'}
from={['a.bouhuolia@gmail.com']}
to={['a.bouhuolia@gmail.com']}
companyName={receiptMailState?.companyName || ''}
customerName={receiptMailState?.customerName || ''}
subject={subject}
from={to}
to={from}
/>
);
}

View File

@@ -0,0 +1,54 @@
import { useMemo } from 'react';
import { useFormikContext } from 'formik';
import { SelectOptionProps } from '@blueprintjs-formik/select';
import { useReceiptSendMailBoot } from './ReceiptSendMailBoot';
import { ReceiptSendMailFormValues } from './_types';
import {
formatMailMessage,
transformEmailArgs,
transformFormatArgsToOptions,
} from '../../Estimates/SendMailViewDrawer/hooks';
/**
* Retrieves the mail format arguments of receipt mail.
* @returns {Record<string, string>}
*/
export const useSendReceiptMailFormatArgs = (): Record<string, string> => {
const { receiptMailState } = useReceiptSendMailBoot();
return useMemo(() => {
return transformEmailArgs(receiptMailState?.formatArgs || {});
}, [receiptMailState]);
};
/**
* Retrieves the formatted receipt subject.
* @returns {string}
*/
export const useSendReceiptMailSubject = (): string => {
const { values } = useFormikContext<ReceiptSendMailFormValues>();
const formatArgs = useSendReceiptMailFormatArgs();
return formatMailMessage(values?.subject, formatArgs);
};
/**
* Retrieves the estimate format options.
* @returns {Array<SelectOptionProps>}
*/
export const useSendReceiptFormatArgsOptions = (): Array<SelectOptionProps> => {
const formatArgs = useSendReceiptMailFormatArgs();
return transformFormatArgsToOptions(formatArgs);
};
/**
* Retrieves the formatted estimate message.
* @returns {string}
*/
export const useSendReceiptMailMessage = (): string => {
const { values } = useFormikContext<ReceiptSendMailFormValues>();
const formatArgs = useSendReceiptMailFormatArgs();
return formatMailMessage(values?.message, formatArgs);
};

View File

@@ -1 +1,3 @@
export interface ReceiptSendMailFormValues {}
import { SendMailViewFormValues } from "../../Estimates/SendMailViewDrawer/_types";
export interface ReceiptSendMailFormValues extends SendMailViewFormValues {}

View File

@@ -261,17 +261,32 @@ export function useSendPaymentReceiveMail(props) {
);
}
export function usePaymentReceiveDefaultOptions(paymentReceiveId, props) {
return useRequestQuery(
export interface GetPaymentReceivedMailStateResponse {
companyName: string;
customerName: string;
entries: Array<{ paymentAmountFormatted: string }>;
from: Array<string>;
fromOptions: Array<{ mail: string; label: string; primary: boolean }>;
paymentAmountFormatted: string;
paymentDate: string;
paymentDateFormatted: string;
to: Array<string>;
toOptions: Array<{ mail: string; label: string; primary: boolean }>;
totalFormatted: string;
}
export function usePaymentReceivedMailState(
paymentReceiveId: number,
props?: UseQueryOptions<GetPaymentReceivedMailStateResponse, Error>,
): UseQueryResult<GetPaymentReceivedMailStateResponse, Error> {
const apiRequest = useApiRequest();
return useQuery<GetPaymentReceivedMailStateResponse, Error>(
[t.PAYMENT_RECEIVE_MAIL_OPTIONS, paymentReceiveId],
{
method: 'get',
url: `sales/payment_receives/${paymentReceiveId}/mail`,
},
{
select: (res) => res.data.data,
...props,
},
() =>
apiRequest
.get(`sales/payment_receives/${paymentReceiveId}/mail`)
.then((res) => transformToCamelCase(res.data?.data)),
);
}

View File

@@ -237,17 +237,29 @@ export function useSendSaleReceiptMail(props) {
);
}
export function useSaleReceiptDefaultOptions(invoiceId, props) {
return useRequestQuery(
[t.SALE_RECEIPT_MAIL_OPTIONS, invoiceId],
{
method: 'get',
url: `sales/receipts/${invoiceId}/mail`,
},
{
select: (res) => res.data.data,
...props,
},
export interface GetSaleReceiptMailStateResponse {
attachReceipt: boolean;
formatArgs: Record<string, any>;
from: string[];
fromOptions: Array<{ mail: string; label: string; primary: boolean; }>
message: string;
subject: string;
to: string[];
toOptions: Array<{ mail: string; label: string; primary: boolean; }>;
}
export function useSaleReceiptMailState(
receiptId: number,
props?: UseQueryOptions<GetSaleReceiptMailStateResponse, Error>,
): UseQueryResult<GetSaleReceiptMailStateResponse, Error> {
const apiRequest = useApiRequest();
return useQuery<GetSaleReceiptMailStateResponse, Error>(
[t.SALE_RECEIPT_MAIL_OPTIONS, receiptId],
() =>
apiRequest
.get(`sales/receipts/${receiptId}/mail`)
.then((res) => transformToCamelCase(res.data.data)),
);
}