diff --git a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.tsx b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.tsx index 65821afd8..3ba5b0d02 100644 --- a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.tsx +++ b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.tsx @@ -84,20 +84,18 @@ function EstimateDetailActionsBar({ + } + text={'Send Mail'} + onClick={handleMailEstimate} + /> } text={} onClick={handlePrintEstimate} /> - - - - diff --git a/packages/webapp/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.tsx b/packages/webapp/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.tsx index 36ca2bcd1..4a2482165 100644 --- a/packages/webapp/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.tsx +++ b/packages/webapp/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.tsx @@ -123,18 +123,18 @@ function InvoiceDetailActionsBar({ } onClick={handleMailInvoice} className={Classes.MINIMAL} /> - - } text={} onClick={handlePrintInvoice} /> + + } + onClick={handleMailPaymentReceive} + /> } text={} onClick={handlePrintPaymentReceive} /> - - - + { openDialog(DialogsName.ReceiptMail, { receiptId }); - } + }; return ( @@ -78,6 +78,12 @@ function ReceiptDetailActionBar({ + } + onClick={handleReceiptMail} + /> } @@ -85,13 +91,6 @@ function ReceiptDetailActionBar({ onClick={safeCallback(onPrintReceipt)} /> - - - ); } + export default compose(withDialogRedux())(EstimateMailDialog); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBoot.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBoot.tsx index d47fb3c99..d68afb4c5 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBoot.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBoot.tsx @@ -26,6 +26,7 @@ function EstimateMailDialogBoot({ useSaleEstimateDefaultOptions(estimateId); const provider = { + saleEstimateId: estimateId, mailOptions, isMailOptionsLoading, }; diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx index 1e8f01801..448b604c9 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx @@ -1,8 +1,13 @@ +// @ts-nocheck import { Formik } from 'formik'; +import * as R from 'ramda'; +import { castArray } from 'lodash'; import { useEstimateMailDialogBoot } from './EstimateMailDialogBoot'; import { transformToForm } from '@/utils'; import { SendMailNotificationForm } from '@/containers/SendMailNotification'; -import { castArray } from 'lodash'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { DialogsName } from '@/constants/dialogs'; +import { useSendSaleEstimateMail } from '@/hooks/query'; const initialFormValues = { from: [], @@ -11,8 +16,20 @@ const initialFormValues = { message: '', }; -export function EstimateMailDialogForm() { - const { mailOptions } = useEstimateMailDialogBoot(); +interface EstimateMailFormValues { + from: string[]; + to: string[]; + subject: string; + message: string; + attachEstimate: boolean; +} + +function EstimateMailDialogFormRoot( + // #withDialogClose + closeDialog, +) { + const { mutateAsync: sendEstimateMail } = useSendSaleEstimateMail(); + const { mailOptions, saleEstimateId } = useEstimateMailDialogBoot(); const initialValues = { ...initialFormValues, @@ -20,12 +37,29 @@ export function EstimateMailDialogForm() { from: mailOptions.from ? castArray(mailOptions.from) : [], to: mailOptions.to ? castArray(mailOptions.to) : [], }; + // Handle the form submitting. + const handleSubmit = (values: EstimateMailFormValues, { setSubmitting }) => { + setSubmitting(true); + sendEstimateMail([saleEstimateId, values]) + .then(() => { + setSubmitting(false); + }) + .catch((error) => { + setSubmitting(false); + }); + }; - const handleSubmit = () => {}; + const handleClose = () => { + closeDialog(DialogsName.EstimateMail); + }; return ( - + ); } + +export const EstimateMailDialogForm = R.compose(withDialogActions)( + EstimateMailDialogFormRoot, +); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesDataTable.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesDataTable.tsx index 402771604..dec1d548c 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesDataTable.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesDataTable.tsx @@ -22,6 +22,7 @@ import { useEstimatesListContext } from './EstimatesListProvider'; import { useMemorizedColumnsWidths } from '@/hooks'; import { compose } from '@/utils'; import { DRAWERS } from '@/constants/drawers'; +import { DialogsName } from '@/constants/dialogs'; /** * Estimates datatable. @@ -100,6 +101,11 @@ function EstimatesDataTable({ openDrawer(DRAWERS.ESTIMATE_DETAILS, { estimateId: cell.row.original.id }); }; + // Handle mail send estimate. + const handleMailSendEstimate = ({ id }) => { + openDialog(DialogsName.EstimateMail, { estimateId: id }); + } + // Local storage memorizing columns widths. const [initialColumnsWidths, , handleColumnResizing] = useMemorizedColumnsWidths(TABLES.ESTIMATES); @@ -153,6 +159,7 @@ function EstimatesDataTable({ onConvert: handleConvertToInvoice, onViewDetails: handleViewDetailEstimate, onPrint: handlePrintEstimate, + onSendMail: handleMailSendEstimate, }} /> diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/components.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/components.tsx index a596a419a..5edfd9051 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/components.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/components.tsx @@ -64,6 +64,7 @@ export function ActionsMenu({ onConvert, onViewDetails, onPrint, + onSendMail }, }) { return ( @@ -129,6 +130,11 @@ export function ActionsMenu({ + } + text={'Send Mail'} + onClick={safeCallback(onSendMail, original)} + /> } text={intl.get('print')} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx index dab608e3f..63430ce10 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx @@ -23,6 +23,7 @@ function InvoiceMailDialog({ isOpen={isOpen} canEscapeJeyClose={true} autoFocus={true} + style={{ width: 600 }} > {}; + // Handle the form submitting. + const handleSubmit = (values: InvoiceMailFormValues, { setSubmitting }) => { + setSubmitting(true); + sendInvoiceMail([saleInvoiceId, values]) + .then(() => { + setSubmitting(false); + }) + .catch(() => { + setSubmitting(false); + }); + }; + // Handle the close button click. + const handleClose = () => { + closeDialog(DialogsName.InvoiceMail); + }; return ( - + ); } + +export const InvoiceMailDialogForm = R.compose(withDialogActions)( + InvoiceMailDialogFormRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesDataTable.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesDataTable.tsx index c2f5a78df..75ba9b7f6 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesDataTable.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesDataTable.tsx @@ -26,6 +26,7 @@ import { useInvoicesListContext } from './InvoicesListProvider'; import { compose } from '@/utils'; import { DRAWERS } from '@/constants/drawers'; +import { DialogsName } from '@/constants/dialogs'; /** * Invoices datatable. @@ -98,6 +99,11 @@ function InvoicesDataTable({ openDialog('invoice-pdf-preview', { invoiceId: id }); }; + // Handle send mail invoice. + const handleSendMailInvoice = ({ id }) => { + openDialog(DialogsName.InvoiceMail, { invoiceId: id }); + }; + // Handle cell click. const handleCellClick = (cell, event) => { openDrawer(DRAWERS.INVOICE_DETAILS, { invoiceId: cell.row.original.id }); @@ -157,6 +163,7 @@ function InvoicesDataTable({ onViewDetails: handleViewDetailInvoice, onPrint: handlePrintInvoice, onConvert: handleConvertToCreitNote, + onSendMail: handleSendMailInvoice }} /> diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/components.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/components.tsx index c5ebc3aee..2739ad9e5 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/components.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/components.tsx @@ -128,6 +128,7 @@ export function ActionsMenu({ onQuick, onViewDetails, onPrint, + onSendMail }, row: { original }, }) { @@ -150,7 +151,6 @@ export function ActionsMenu({ text={intl.get('invoice.convert_to_credit_note')} onClick={safeCallback(onConvert, original)} /> - } @@ -169,6 +169,11 @@ export function ActionsMenu({ + } + text={'Send Mail'} + onClick={safeCallback(onSendMail, original)} + /> } text={intl.get('print')} diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog.tsx index 7e4e3e6ac..6da51d03e 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog.tsx @@ -23,6 +23,7 @@ function PaymentMailDialog({ isOpen={isOpen} canEscapeJeyClose={true} autoFocus={true} + style={{ width: 600 }} > {}; + // Handles the form submitting. + const handleSubmit = ( + values: PaymentMailFormValue, + { setSubmitting }: FormikBag, + ) => { + setSubmitting(true); + sendPaymentMail([paymentId, values]) + .then(() => { + setSubmitting(false); + }) + .catch((error) => { + setSubmitting(false); + }); + }; + + const handleClose = () => { + closeDialog(DialogsName.PaymentMail); + }; return ( - + ); } + +export const PaymentMailDialogForm = R.compose(withDialogActions)( + PaymentMailDialogFormRoot, +); diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesTable.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesTable.tsx index 15a8346df..82e7f7cb5 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesTable.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesTable.tsx @@ -17,12 +17,14 @@ import withPaymentReceives from './withPaymentReceives'; import withPaymentReceivesActions from './withPaymentReceivesActions'; import withAlertsActions from '@/containers/Alert/withAlertActions'; import withDrawerActions from '@/containers/Drawer/withDrawerActions'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; import withSettings from '@/containers/Settings/withSettings'; import { usePaymentReceivesColumns, ActionsMenu } from './components'; import { usePaymentReceivesListContext } from './PaymentReceiptsListProvider'; import { useMemorizedColumnsWidths } from '@/hooks'; import { DRAWERS } from '@/constants/drawers'; +import { DialogsName } from '@/constants/dialogs'; /** * Payment receives datatable. @@ -31,15 +33,15 @@ function PaymentReceivesDataTable({ // #withPaymentReceivesActions setPaymentReceivesTableState, - // #withPaymentReceives - paymentReceivesTableState, - // #withAlertsActions openAlert, // #withDrawerActions openDrawer, + // #withDialogActions + openDialog, + // #withSettings paymentReceivesTableSize, }) { @@ -73,6 +75,11 @@ function PaymentReceivesDataTable({ openDrawer(DRAWERS.PAYMENT_RECEIVE_DETAILS, { paymentReceiveId: id }); }; + // Handle mail send payment receive. + const handleSendMailPayment = ({ id }) => { + openDialog(DialogsName.PaymentMail, { paymentReceiveId: id }); + }; + // Handle cell click. const handleCellClick = (cell, event) => { openDrawer(DRAWERS.PAYMENT_RECEIVE_DETAILS, { @@ -129,6 +136,7 @@ function PaymentReceivesDataTable({ onDelete: handleDeletePaymentReceive, onEdit: handleEditPaymentReceive, onViewDetails: handleViewDetailPaymentReceive, + onSendMail: handleSendMailPayment, }} /> @@ -139,6 +147,7 @@ export default compose( withPaymentReceivesActions, withAlertsActions, withDrawerActions, + withDialogActions, withPaymentReceives(({ paymentReceivesTableState }) => ({ paymentReceivesTableState, })), diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/components.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/components.tsx index 4af4f14ba..fd6aec581 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/components.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/components.tsx @@ -15,14 +15,17 @@ import { import { FormatDateCell, Money, Icon, Can } from '@/components'; import { safeCallback } from '@/utils'; import { CLASSES } from '@/constants/classes'; -import { PaymentReceiveAction, AbilitySubject } from '@/constants/abilityOption'; +import { + PaymentReceiveAction, + AbilitySubject, +} from '@/constants/abilityOption'; /** * Table actions menu. */ export function ActionsMenu({ row: { original: paymentReceive }, - payload: { onEdit, onDelete, onViewDetails }, + payload: { onEdit, onDelete, onViewDetails, onSendMail }, }) { return ( @@ -31,6 +34,11 @@ export function ActionsMenu({ text={intl.get('view_details')} onClick={safeCallback(onViewDetails, paymentReceive)} /> + } + text={'Send Mail'} + onClick={safeCallback(onSendMail, paymentReceive)} + /> {}; + const handleSubmit = ( + values: ReceiptMailFormValues, + { setSubmitting }: FormikBag, + ) => { + setSubmitting(true); + sendReceiptMail([saleReceiptId, values]) + .then(() => { + setSubmitting(false); + }) + .catch((error) => { + setSubmitting(false); + }); + }; + + const handleClose = () => { + closeDialog(DialogsName.ReceiptMail); + }; return ( - + ); } + +export const ReceiptMailDialogForm = R.compose(withDialogActions)( + ReceiptMailDialogFormRoot, +); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx index 828ff5a70..7517cf104 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx @@ -140,7 +140,6 @@ function ReceiptActionsBar({ icon={} text={} /> - { + openDialog(DialogsName.ReceiptMail, { receiptId: id }); + }; + // Local storage memorizing columns widths. const [initialColumnsWidths, , handleColumnResizing] = useMemorizedColumnsWidths(TABLES.RECEIPTS); @@ -141,6 +147,7 @@ function ReceiptsDataTable({ onClose: handleCloseReceipt, onViewDetails: handleViewDetailReceipt, onPrint: handlePrintInvoice, + onSendMail: handleSendMailReceipt, }} /> diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/components.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/components.tsx index 4d843937e..e76b2d9c6 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/components.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/components.tsx @@ -24,7 +24,7 @@ import { SaleReceiptAction, AbilitySubject } from '@/constants/abilityOption'; * @returns {React.JSX} */ export function ActionsMenu({ - payload: { onEdit, onDelete, onClose, onDrawer, onViewDetails, onPrint }, + payload: { onEdit, onDelete, onClose, onSendMail, onViewDetails, onPrint }, row: { original: receipt }, }) { return ( @@ -51,6 +51,11 @@ export function ActionsMenu({ + } + text={'Send Mail'} + onClick={safeCallback(onSendMail, receipt)} + /> } text={intl.get('print')} diff --git a/packages/webapp/src/containers/SendMailNotification/SendMailNotificationForm.tsx b/packages/webapp/src/containers/SendMailNotification/SendMailNotificationForm.tsx index 00d4b0faf..a23b108af 100644 --- a/packages/webapp/src/containers/SendMailNotification/SendMailNotificationForm.tsx +++ b/packages/webapp/src/containers/SendMailNotification/SendMailNotificationForm.tsx @@ -1,36 +1,118 @@ // @ts-nocheck -import { Form } from 'formik'; +import { Form, useFormikContext } from 'formik'; import { FFormGroup, FInputGroup, FMultiSelect } from '@/components'; +import styled from 'styled-components'; +import { Button, Classes, Intent } from '@blueprintjs/core'; +import { saveInvoke } from '@/utils'; + +interface SendMailNotificationFormProps { + onClose?: () => void; +} + +export function SendMailNotificationForm({ + onClose, +}: SendMailNotificationFormProps) { + const { isSubmitting } = useFormikContext(); + + const handleClose = () => { + saveInvoke(onClose); + }; -export function SendMailNotificationForm() { return ( - - - + + + + + - - - + + + - - - + + + + + + + + + + Close + + + + Send + + + ); } + +const HeaderBox = styled('div')` + border-top-right-radius: 5px; + border-top-left-radius: 5px; + border: 1px solid #dddfe9; + padding: 15px; + + .bp4-form-group { + margin: 0; + padding-top: 12px; + padding-bottom: 12px; + + &:not(:last-of-type) { + border-bottom: 1px solid #dddfe9; + } + &:first-of-type { + padding-top: 0; + } + &:last-of-type { + padding-bottom: 0; + } + } + + .bp4-form-content { + flex: 1 0; + } + + .bp4-label { + min-width: 65px; + color: #738091; + } + + .bp4-input { + } +`; diff --git a/packages/webapp/src/static/json/icons.tsx b/packages/webapp/src/static/json/icons.tsx index b13e9ffa5..e60213bb8 100644 --- a/packages/webapp/src/static/json/icons.tsx +++ b/packages/webapp/src/static/json/icons.tsx @@ -561,8 +561,14 @@ export default { }, 'content-copy': { path: [ - 'M15 0H5c-.55 0-1 .45-1 1v2h2V2h8v7h-1v2h2c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-4 4H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zm-1 10H2V6h8v8z' + 'M15 0H5c-.55 0-1 .45-1 1v2h2V2h8v7h-1v2h2c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-4 4H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zm-1 10H2V6h8v8z', ], - viewBox: '0 0 16 16' - } + viewBox: '0 0 16 16', + }, + envelope: { + path: [ + 'M0 4.01v11.91l6.27-6.27L0 4.01zm18.91-1.03H1.09L10 10.97l8.91-7.99zm-5.18 6.66L20 15.92V4.01l-6.27 5.63zm-3.23 2.9c-.13.12-.31.19-.5.19s-.37-.07-.5-.19l-2.11-1.89-6.33 6.33h17.88l-6.33-6.33-2.11 1.89z', + ], + viewBox: '0 0 20 20', + }, };