mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
feat: wip preview invoice payment mail
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import { Box } from '@/components';
|
||||
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 { InvoiceSendMailPreviewWithHeader } from './InvoiceSendMailHeaderPreview';
|
||||
import { useSendInvoiceMailMessage } from './_hooks';
|
||||
|
||||
export function InvoiceMailReceiptPreviewConneceted() {
|
||||
const { invoice } = useInvoiceSendMailBoot();
|
||||
const mailMessage = useSendInvoiceMailMessage();
|
||||
|
||||
const items = useMemo(
|
||||
() =>
|
||||
@@ -18,16 +22,19 @@ export function InvoiceMailReceiptPreviewConneceted() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Box px={4} pt={8} pb={16}>
|
||||
<InvoiceMailReceiptPreview
|
||||
total={invoice.total_formatted}
|
||||
dueDate={invoice.due_date_formatted}
|
||||
invoiceNumber={invoice.invoice_no}
|
||||
items={items}
|
||||
className={css`
|
||||
margin: 0 auto;
|
||||
`}
|
||||
/>
|
||||
</Box>
|
||||
<InvoiceSendMailPreviewWithHeader>
|
||||
<Box px={4} pt={8} pb={16}>
|
||||
<InvoiceMailReceiptPreview
|
||||
total={invoice.total_formatted}
|
||||
dueDate={invoice.due_date_formatted}
|
||||
invoiceNumber={invoice.invoice_no}
|
||||
items={items}
|
||||
message={mailMessage}
|
||||
className={css`
|
||||
margin: 0 auto;
|
||||
`}
|
||||
/>
|
||||
</Box>
|
||||
</InvoiceSendMailPreviewWithHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
// @ts-nocheck
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { Spinner } from '@blueprintjs/core';
|
||||
import { useInvoice } from '@/hooks/query';
|
||||
import {
|
||||
GetSaleInvoiceDefaultOptionsResponse,
|
||||
useInvoice,
|
||||
useSaleInvoiceDefaultOptions,
|
||||
} from '@/hooks/query';
|
||||
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||
|
||||
interface InvoiceSendMailBootValues {
|
||||
invoice: any;
|
||||
invoiceId: number;
|
||||
isInvoiceLoading: boolean;
|
||||
|
||||
invoiceMailOptions: GetSaleInvoiceDefaultOptionsResponse | undefined;
|
||||
isInvoiceMailOptionsLoading: boolean;
|
||||
}
|
||||
interface InvoiceSendMailBootProps {
|
||||
children: React.ReactNode;
|
||||
@@ -21,10 +28,15 @@ export const InvoiceSendMailBoot = ({ children }: InvoiceSendMailBootProps) => {
|
||||
payload: { invoiceId },
|
||||
} = useDrawerContext();
|
||||
|
||||
// Invoice details.
|
||||
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
|
||||
enabled: !!invoiceId,
|
||||
});
|
||||
const isLoading = isInvoiceLoading;
|
||||
// Invoice mail options.
|
||||
const { data: invoiceMailOptions, isLoading: isInvoiceMailOptionsLoading } =
|
||||
useSaleInvoiceDefaultOptions(invoiceId);
|
||||
|
||||
const isLoading = isInvoiceLoading || isInvoiceMailOptionsLoading;
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner size={20} />;
|
||||
@@ -33,6 +45,8 @@ export const InvoiceSendMailBoot = ({ children }: InvoiceSendMailBootProps) => {
|
||||
invoice,
|
||||
isInvoiceLoading,
|
||||
invoiceId,
|
||||
invoiceMailOptions,
|
||||
isInvoiceMailOptionsLoading,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { useState } from 'react';
|
||||
import { Button, Intent, MenuItem } from '@blueprintjs/core';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Button, Intent, MenuItem, Position } from '@blueprintjs/core';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { css } from '@emotion/css';
|
||||
import { x } from '@xstyled/emotion';
|
||||
@@ -9,8 +9,10 @@ import {
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FMultiSelect,
|
||||
FSelect,
|
||||
FTextArea,
|
||||
Group,
|
||||
Icon,
|
||||
Stack,
|
||||
} from '@/components';
|
||||
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||
@@ -60,6 +62,7 @@ const fieldsWrapStyle = css`
|
||||
export function InvoiceSendMailFields() {
|
||||
const [showCCField, setShowCCField] = useState<boolean>(false);
|
||||
const [showBccField, setShowBccField] = useState<boolean>(false);
|
||||
const textareaRef = useRef(null);
|
||||
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
const items = useInvoiceMailItems();
|
||||
@@ -117,6 +120,30 @@ export function InvoiceSendMailFields() {
|
||||
</Group>
|
||||
);
|
||||
|
||||
const handleTextareaChange = () => {
|
||||
const textarea = textareaRef.current;
|
||||
if (!textarea) return;
|
||||
|
||||
const { selectionStart, selectionEnd } = textarea;
|
||||
const insertText = '{Variable}';
|
||||
|
||||
// Insert the text at the cursor position
|
||||
const message =
|
||||
text.substring(0, selectionStart) +
|
||||
insertText +
|
||||
text.substring(selectionEnd);
|
||||
|
||||
setFieldValue('message', message);
|
||||
|
||||
// Move the cursor to the end of the inserted text
|
||||
setTimeout(() => {
|
||||
textarea.selectionStart = textarea.selectionEnd =
|
||||
selectionStart + insertText.length;
|
||||
|
||||
textarea.focus();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
bg="white"
|
||||
@@ -144,6 +171,7 @@ export function InvoiceSendMailFields() {
|
||||
resetOnQuery
|
||||
resetOnSelect
|
||||
fill
|
||||
fastField
|
||||
/>
|
||||
{showCCField && (
|
||||
<FMultiSelect
|
||||
@@ -161,6 +189,7 @@ export function InvoiceSendMailFields() {
|
||||
resetOnQuery
|
||||
resetOnSelect
|
||||
fill
|
||||
fastField
|
||||
/>
|
||||
)}
|
||||
{showBccField && (
|
||||
@@ -179,25 +208,62 @@ export function InvoiceSendMailFields() {
|
||||
resetOnQuery
|
||||
resetOnSelect
|
||||
fill
|
||||
fastField
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup label={'Submit'} name={'subject'}>
|
||||
<FInputGroup name={'subject'} large />
|
||||
<FInputGroup name={'subject'} large fastField />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup label={'Message'} name={'message'}>
|
||||
<FTextArea
|
||||
name={'message'}
|
||||
large
|
||||
fill
|
||||
className={css`
|
||||
resize: vertical;
|
||||
min-height: 200px;
|
||||
`}
|
||||
/>
|
||||
<Stack spacing={0}>
|
||||
<Group
|
||||
border={'1px solid #ced4da'}
|
||||
borderBottom={0}
|
||||
borderRadius={'3px 3px 0 0'}
|
||||
>
|
||||
<FSelect
|
||||
selectedItem={'customerName'}
|
||||
name={'item'}
|
||||
items={[{ value: 'customerName', text: 'Customer Name' }]}
|
||||
onItemChange={handleTextareaChange}
|
||||
popoverProps={{
|
||||
fill: false,
|
||||
position: Position.BOTTOM_LEFT,
|
||||
minimal: true,
|
||||
}}
|
||||
input={({ activeItem, text, label, value }) => (
|
||||
<Button
|
||||
minimal
|
||||
rightIcon={
|
||||
<Icon icon={'caret-down-16'} color={'#8F99A8'} />
|
||||
}
|
||||
>
|
||||
{text}
|
||||
</Button>
|
||||
)}
|
||||
fill={false}
|
||||
fastField
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<FTextArea
|
||||
ref={textareaRef}
|
||||
name={'message'}
|
||||
large
|
||||
fill
|
||||
fastField
|
||||
className={css`
|
||||
resize: vertical;
|
||||
min-height: 200px;
|
||||
border-top-right-radius: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
`}
|
||||
/>
|
||||
</Stack>
|
||||
</FFormGroup>
|
||||
</Stack>
|
||||
|
||||
@@ -243,4 +309,3 @@ function InvoiceSendMailFooter() {
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as Yup from 'yup';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
import { css } from '@emotion/css';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
@@ -9,22 +8,14 @@ import { AppToaster } from '@/components';
|
||||
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
|
||||
import { useDrawerActions } from '@/hooks/state';
|
||||
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||
import { transformToForm } from '@/utils';
|
||||
|
||||
const initialValues = {
|
||||
subject: 'invoice INV-0002 for AED 0.00',
|
||||
message: `Hi Ahmed,
|
||||
|
||||
Here’s invoice INV-0002 for AED 0.00
|
||||
|
||||
The amount outstanding of AED $100,00 is due on 2 October 2024
|
||||
|
||||
View your bill online From your online you can print a PDF or pay your outstanding bills,
|
||||
|
||||
If you have any questions, please let us know,
|
||||
|
||||
Thanks,
|
||||
Mohamed`,
|
||||
to: ['a.bouhuolia@gmail.com'],
|
||||
subject: '',
|
||||
message: '',
|
||||
to: [],
|
||||
cc: [],
|
||||
bcc: [],
|
||||
};
|
||||
|
||||
interface InvoiceSendMailFormProps {
|
||||
@@ -33,10 +24,14 @@ interface InvoiceSendMailFormProps {
|
||||
|
||||
export function InvoiceSendMailForm({ children }: InvoiceSendMailFormProps) {
|
||||
const { mutateAsync: sendInvoiceMail } = useSendSaleInvoiceMail();
|
||||
const { invoiceId } = useInvoiceSendMailBoot();
|
||||
const { invoiceId, invoiceMailOptions } = useInvoiceSendMailBoot();
|
||||
const { name } = useDrawerContext();
|
||||
const { closeDrawer } = useDrawerActions();
|
||||
|
||||
const _initialValues = {
|
||||
...initialValues,
|
||||
...transformToForm(invoiceMailOptions, initialValues),
|
||||
};
|
||||
const handleSubmit = (
|
||||
values: InvoiceSendMailFormValues,
|
||||
{ setSubmitting }: FormikHelpers<InvoiceSendMailFormValues>,
|
||||
@@ -62,7 +57,7 @@ export function InvoiceSendMailForm({ children }: InvoiceSendMailFormProps) {
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
initialValues={_initialValues}
|
||||
validationSchema={InvoiceSendMailFormSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { x } from '@xstyled/emotion';
|
||||
import { Box, Group, Stack } from '@/components';
|
||||
import React from 'react';
|
||||
import { useSendInvoiceMailSubject } from './_hooks';
|
||||
|
||||
export function InvoiceSendMailHeaderPreview() {
|
||||
const mailSubject = useSendInvoiceMailSubject();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
bg={'white'}
|
||||
borderBottom={'1px solid #dcdcdd'}
|
||||
padding={'22px 30px'}
|
||||
spacing={8}
|
||||
position={'sticky'}
|
||||
top={0}
|
||||
zIndex={1}
|
||||
>
|
||||
<Box>
|
||||
<x.h2 fontWeight={600} fontSize={16}>
|
||||
{mailSubject}
|
||||
</x.h2>
|
||||
</Box>
|
||||
|
||||
<Group display="flex" gap={2}>
|
||||
<Group display="flex" alignItems="center" gap={15}>
|
||||
<x.abbr
|
||||
role="presentation"
|
||||
className={css`
|
||||
background-color: #daa3e4;
|
||||
color: #3f1946;
|
||||
fill: #daa3e4;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
border-radius: 40px;
|
||||
font-size: 14px;
|
||||
`}
|
||||
>
|
||||
A
|
||||
</x.abbr>
|
||||
|
||||
<Stack spacing={2}>
|
||||
<Group spacing={2}>
|
||||
<Box fontWeight={600}>Ahmed </Box>
|
||||
<Box color={'#738091'}>
|
||||
<messaging-service@post.xero.com>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
<Box fontSize={'sm'} color={'#738091'}>
|
||||
Reply to: Ahmed <a.m.bouhuolia@gmail.com>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Group>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export function InvoiceSendMailPreviewWithHeader({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Box>
|
||||
<InvoiceSendMailHeaderPreview />
|
||||
|
||||
<Box>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +1,30 @@
|
||||
import { Box } from "@/components";
|
||||
import { InvoicePaperTemplate } from "../InvoiceCustomize/InvoicePaperTemplate";
|
||||
import { css } from "@emotion/css";
|
||||
import { useInvoiceSendMailBoot } from "./InvoiceSendMailContentBoot";
|
||||
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 (
|
||||
<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;
|
||||
`}
|
||||
/>
|
||||
</Box>
|
||||
<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;
|
||||
`}
|
||||
/>
|
||||
</Box>
|
||||
</InvoiceSendMailPreviewWithHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useFormikContext } from 'formik';
|
||||
import { chain } from 'lodash';
|
||||
import { camelCase, chain, defaultTo, mapKeys, upperFirst } from 'lodash';
|
||||
import { InvoiceSendMailFormValues } from './_types';
|
||||
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useInvoiceMailItems = () => {
|
||||
const { values } = useFormikContext<InvoiceSendMailFormValues>();
|
||||
@@ -16,3 +18,42 @@ export const useInvoiceMailItems = () => {
|
||||
}))
|
||||
.value();
|
||||
};
|
||||
|
||||
export const useSendInvoiceMailFormatArgs = () => {
|
||||
const { invoiceMailOptions } = useInvoiceSendMailBoot();
|
||||
|
||||
return useMemo(() => {
|
||||
return mapKeys(invoiceMailOptions?.formatArgs, (_, key) =>
|
||||
upperFirst(camelCase(key)),
|
||||
);
|
||||
}, [invoiceMailOptions]);
|
||||
};
|
||||
|
||||
export const useSendInvoiceMailSubject = () => {
|
||||
const { values } = useFormikContext<InvoiceSendMailFormValues>();
|
||||
const formatArgs = useSendInvoiceMailFormatArgs();
|
||||
|
||||
return formatSmsMessage(values?.subject, formatArgs);
|
||||
};
|
||||
|
||||
export const useSendInvoiceMailMessage = () => {
|
||||
const { values } = useFormikContext<InvoiceSendMailFormValues>();
|
||||
const formatArgs = useSendInvoiceMailFormatArgs();
|
||||
|
||||
return formatSmsMessage(values?.message, formatArgs);
|
||||
};
|
||||
|
||||
export const formatSmsMessage = (
|
||||
message: string,
|
||||
args: Record<string, any>,
|
||||
) => {
|
||||
let formattedMessage = message;
|
||||
|
||||
Object.keys(args).forEach((key) => {
|
||||
const variable = `{${key}}`;
|
||||
const value = defaultTo(args[key], '');
|
||||
|
||||
formattedMessage = formattedMessage.replace(variable, value);
|
||||
});
|
||||
return formattedMessage;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,6 @@ export interface InvoiceSendMailFormValues {
|
||||
subject: string;
|
||||
message: string;
|
||||
to: string[];
|
||||
cc?: string[];
|
||||
bcc?: string[];
|
||||
cc: string[];
|
||||
bcc: string[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user