diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0e0d0a0..9f9a76ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to Bigcapital server-side will be in this file. +## [1.5.8] - 13-01-2022 + +### Added +- Add payment receive PDF print. +- Add credit note PDF print. + +### Fixed +- fix: Payment receive initial loading state depends on request loading state instead fetching. +- fix: Balance sheet report alert positioning. +- fix: Separate customer and vendor inactivate and activate alerts. +- fix: Hide convert to invoice button if the invoice is already converted. +- fix: Customer and vendor balance summary percentage of column option. +- fix: Remove duplicated details in sales and purchases details drawers. + ## [1.5.3] - 03-01-2020 ### Fixed diff --git a/package.json b/package.json index 2fda86adc..56532c6c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bigcapital-client", - "version": "1.5.3", + "version": "1.5.8", "private": true, "dependencies": { "@babel/core": "7.8.4", diff --git a/src/components/CommercialDoc/index.js b/src/components/CommercialDoc/index.js index c8e2c593b..d1480c3f5 100644 --- a/src/components/CommercialDoc/index.js +++ b/src/components/CommercialDoc/index.js @@ -21,5 +21,5 @@ export const CommercialDocEntriesTable = styled(DataTable)` `; export const CommercialDocFooter = styled.div` - margin-top: 25px; + margin-top: 28px; `; diff --git a/src/components/DialogsContainer.js b/src/components/DialogsContainer.js index d9d3609c5..cd8eb745e 100644 --- a/src/components/DialogsContainer.js +++ b/src/components/DialogsContainer.js @@ -32,6 +32,8 @@ import ReconcileVendorCreditDialog from '../containers/Dialogs/ReconcileVendorCr import LockingTransactionsDialog from '../containers/Dialogs/LockingTransactionsDialog'; import UnlockingTransactionsDialog from '../containers/Dialogs/UnlockingTransactionsDialog'; import UnlockingPartialTransactionsDialog from '../containers/Dialogs/UnlockingPartialTransactionsDialog'; +import CreditNotePdfPreviewDialog from '../containers/Dialogs/CreditNotePdfPreviewDialog'; +import PaymentReceivePdfPreviewDialog from '../containers/Dialogs/PaymentReceivePdfPreviewDialog'; /** * Dialogs container. @@ -74,6 +76,8 @@ export default function DialogsContainer() { + + ); } diff --git a/src/containers/Alerts/Customers/CustomerActivateAlert.js b/src/containers/Alerts/Customers/CustomerActivateAlert.js new file mode 100644 index 000000000..ef86134a3 --- /dev/null +++ b/src/containers/Alerts/Customers/CustomerActivateAlert.js @@ -0,0 +1,67 @@ +import React from 'react'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; +import { Intent, Alert } from '@blueprintjs/core'; +import { AppToaster } from 'components'; + +import { useActivateContact } from 'hooks/query'; + +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; +import withAlertActions from 'containers/Alert/withAlertActions'; + +import { compose } from 'utils'; + +/** + * Customer activate alert. + */ +function CustomerActivateAlert({ + name, + + // #withAlertStoreConnect + isOpen, + payload: { customerId, service }, + + // #withAlertActions + closeAlert, +}) { + const { mutateAsync: activateContact, isLoading } = useActivateContact(); + + // Handle activate constomer alert cancel. + const handleCancelActivateCustomer = () => { + closeAlert(name); + }; + + // Handle confirm customer activated. + const handleConfirmCustomerActivate = () => { + activateContact(customerId) + .then(() => { + AppToaster.show({ + message: intl.get('customer.alert.activated_message'), + intent: Intent.SUCCESS, + }); + }) + .catch((error) => {}) + .finally(() => { + closeAlert(name); + }); + }; + + return ( + } + confirmButtonText={} + intent={Intent.WARNING} + isOpen={isOpen} + onCancel={handleCancelActivateCustomer} + loading={isLoading} + onConfirm={handleConfirmCustomerActivate} + > +

{intl.get('customer.alert.are_you_sure_want_to_activate_this_customer')}

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(CustomerActivateAlert); diff --git a/src/containers/Alerts/Customers/CustomerInactivateAlert.js b/src/containers/Alerts/Customers/CustomerInactivateAlert.js new file mode 100644 index 000000000..d86675c91 --- /dev/null +++ b/src/containers/Alerts/Customers/CustomerInactivateAlert.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; +import { Intent, Alert } from '@blueprintjs/core'; +import { AppToaster } from 'components'; + +import { useInactivateContact } from 'hooks/query'; + +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; +import withAlertActions from 'containers/Alert/withAlertActions'; + +import { compose } from 'utils'; + +/** + * customer inactivate alert. + */ +function CustomerInactivateAlert({ + name, + // #withAlertStoreConnect + isOpen, + payload: { customerId, service }, + + // #withAlertActions + closeAlert, +}) { + const { mutateAsync: inactivateContact, isLoading } = useInactivateContact(); + + // Handle cancel inactivate alert. + const handleCancelInactivateCustomer = () => { + closeAlert(name); + }; + + // Handle confirm contact Inactive. + const handleConfirmCustomerInactive = () => { + inactivateContact(customerId) + .then(() => { + AppToaster.show({ + message: intl.get('the_contact_has_been_inactivated_successfully'), + intent: Intent.SUCCESS, + }); + }) + .catch((error) => {}) + .finally(() => { + closeAlert(name); + }); + }; + + return ( + } + confirmButtonText={} + intent={Intent.WARNING} + isOpen={isOpen} + onCancel={handleCancelInactivateCustomer} + onConfirm={handleConfirmCustomerInactive} + loading={isLoading} + > +

+ {intl.get( + 'customer.alert.are_you_sure_want_to_inactivate_this_customer', + )} +

+
+ ); +} +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(CustomerInactivateAlert); diff --git a/src/containers/Alerts/Vendors/VendorActivateAlert.js b/src/containers/Alerts/Vendors/VendorActivateAlert.js new file mode 100644 index 000000000..8e48ab34f --- /dev/null +++ b/src/containers/Alerts/Vendors/VendorActivateAlert.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; +import { Intent, Alert } from '@blueprintjs/core'; +import { AppToaster } from 'components'; + +import { useActivateContact } from 'hooks/query'; + +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; +import withAlertActions from 'containers/Alert/withAlertActions'; + +import { compose } from 'utils'; + +/** + * Vendor activate alert. + */ +function VendorActivateAlert({ + name, + + // #withAlertStoreConnect + isOpen, + payload: { vendorId }, + + // #withAlertActions + closeAlert, +}) { + const { mutateAsync: activateContact, isLoading } = useActivateContact(); + + // Handle activate vendor alert cancel. + const handleCancelActivateVendor = () => { + closeAlert(name); + }; + + // Handle confirm vendor activated. + const handleConfirmVendorActivate = () => { + activateContact(vendorId) + .then(() => { + AppToaster.show({ + message: intl.get('vendor.alert.activated_message'), + intent: Intent.SUCCESS, + }); + }) + .catch((error) => {}) + .finally(() => { + closeAlert(name); + }); + }; + + return ( + } + confirmButtonText={} + intent={Intent.WARNING} + isOpen={isOpen} + onCancel={handleCancelActivateVendor} + loading={isLoading} + onConfirm={handleConfirmVendorActivate} + > +

+ {intl.get('vendor.alert.are_you_sure_want_to_activate_this_vendor')} +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(VendorActivateAlert); diff --git a/src/containers/Alerts/Vendors/VendorInactivateAlert.js b/src/containers/Alerts/Vendors/VendorInactivateAlert.js new file mode 100644 index 000000000..a5d48358d --- /dev/null +++ b/src/containers/Alerts/Vendors/VendorInactivateAlert.js @@ -0,0 +1,68 @@ +import React from 'react'; +import { FormattedMessage as T } from 'components'; +import intl from 'react-intl-universal'; +import { Intent, Alert } from '@blueprintjs/core'; +import { AppToaster } from 'components'; + +import { useInactivateContact } from 'hooks/query'; + +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; +import withAlertActions from 'containers/Alert/withAlertActions'; + +import { compose } from 'utils'; + +/** + * Vendor inactivate alert. + */ +function VendorInactivateAlert({ + name, + // #withAlertStoreConnect + isOpen, + payload: { vendorId }, + + // #withAlertActions + closeAlert, +}) { + const { mutateAsync: inactivateContact, isLoading } = useInactivateContact(); + + // Handle cancel inactivate alert. + const handleCancelInactivateVendor = () => { + closeAlert(name); + }; + + // Handle confirm contact Inactive. + const handleConfirmVendorInactive = () => { + inactivateContact(vendorId) + .then(() => { + AppToaster.show({ + message: intl.get('vendor.alert.inactivated_message'), + intent: Intent.SUCCESS, + }); + }) + .catch((error) => {}) + .finally(() => { + closeAlert(name); + }); + }; + + return ( + } + confirmButtonText={} + intent={Intent.WARNING} + isOpen={isOpen} + onCancel={handleCancelInactivateVendor} + onConfirm={handleConfirmVendorInactive} + loading={isLoading} + > +

+ {intl.get('vendor.alert.are_you_sure_want_to_inactivate_this_vendor')} +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(VendorInactivateAlert); diff --git a/src/containers/Customers/CustomersAlerts.js b/src/containers/Customers/CustomersAlerts.js index a7bab20eb..7cd902d21 100644 --- a/src/containers/Customers/CustomersAlerts.js +++ b/src/containers/Customers/CustomersAlerts.js @@ -3,11 +3,11 @@ import React from 'react'; const CustomerDeleteAlert = React.lazy(() => import('../Alerts/Customers/CustomerDeleteAlert'), ); -const ContactActivateAlert = React.lazy(() => - import('../Alerts/Contacts/ContactActivateAlert'), +const CustomerActivateAlert = React.lazy(() => + import('../Alerts/Customers/CustomerActivateAlert'), ); -const ContactInactivateAlert = React.lazy(() => - import('../Alerts/Contacts/ContactInactivateAlert'), +const CustomerInactivateAlert = React.lazy(() => + import('../Alerts/Customers/CustomerInactivateAlert'), ); /** @@ -15,6 +15,6 @@ const ContactInactivateAlert = React.lazy(() => */ export default [ { name: 'customer-delete', component: CustomerDeleteAlert }, - { name: 'contact-activate', component: ContactActivateAlert }, - { name: 'contact-inactivate', component: ContactInactivateAlert }, + { name: 'customer-activate', component: CustomerActivateAlert }, + { name: 'customer-inactivate', component: CustomerInactivateAlert }, ]; diff --git a/src/containers/Customers/CustomersLanding/CustomersTable.js b/src/containers/Customers/CustomersLanding/CustomersTable.js index 7619936ac..9618ba4cf 100644 --- a/src/containers/Customers/CustomersLanding/CustomersTable.js +++ b/src/containers/Customers/CustomersLanding/CustomersTable.js @@ -89,15 +89,17 @@ function CustomersTable({ // Handle cancel/confirm inactive. const handleInactiveCustomer = ({ id, contact_service }) => { - openAlert('contact-inactivate', { - contactId: id, - service: contact_service, + openAlert('customer-inactivate', { + customerId: id, }); }; // Handle cancel/confirm activate. const handleActivateCustomer = ({ id, contact_service }) => { - openAlert('contact-activate', { contactId: id, service: contact_service }); + openAlert('customer-activate', { + customerId: id, + service: contact_service, + }); }; // Handle view detail contact. diff --git a/src/containers/Dialogs/CreditNotePdfPreviewDialog/CreditNotePdfPreviewDialogContent.js b/src/containers/Dialogs/CreditNotePdfPreviewDialog/CreditNotePdfPreviewDialogContent.js new file mode 100644 index 000000000..3c13480c9 --- /dev/null +++ b/src/containers/Dialogs/CreditNotePdfPreviewDialog/CreditNotePdfPreviewDialogContent.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { AnchorButton } from '@blueprintjs/core'; + +import { DialogContent, PdfDocumentPreview, T } from 'components'; +import { usePdfCreditNote } from 'hooks/query'; + +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { compose } from 'utils'; + +function CreditNotePdfPreviewDialogContent({ + subscriptionForm: { creditNoteId }, +}) { + const { isLoading, pdfUrl } = usePdfCreditNote(creditNoteId); + + return ( + +
+ + + + + + + +
+ + +
+ ); +} + +export default compose(withDialogActions)(CreditNotePdfPreviewDialogContent); diff --git a/src/containers/Dialogs/CreditNotePdfPreviewDialog/index.js b/src/containers/Dialogs/CreditNotePdfPreviewDialog/index.js new file mode 100644 index 000000000..061cc6b85 --- /dev/null +++ b/src/containers/Dialogs/CreditNotePdfPreviewDialog/index.js @@ -0,0 +1,42 @@ +import React from 'react'; +import classNames from 'classnames'; + +import { T, Dialog, DialogSuspense } from 'components'; + +import withDialogRedux from 'components/DialogReduxConnect'; + +import { CLASSES } from 'common/classes'; +import { compose } from 'utils'; + +const PdfPreviewDialogContent = React.lazy(() => + import('./CreditNotePdfPreviewDialogContent'), +); + +/** + * Credit note PDF previwe dialog. + */ +function CreditNotePdfPreviewDialog({ + dialogName, + payload = { creditNoteId: null }, + isOpen, +}) { + return ( + } + className={classNames(CLASSES.DIALOG_PDF_PREVIEW)} + autoFocus={true} + canEscapeKeyClose={true} + isOpen={isOpen} + style={{ width: '1000px' }} + > + + + + + ); +} +export default compose(withDialogRedux())(CreditNotePdfPreviewDialog); diff --git a/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/PaymentReceivePdfPreviewContent.js b/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/PaymentReceivePdfPreviewContent.js new file mode 100644 index 000000000..c03dc9488 --- /dev/null +++ b/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/PaymentReceivePdfPreviewContent.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { AnchorButton } from '@blueprintjs/core'; + +import { DialogContent, PdfDocumentPreview, T } from 'components'; +import { usePdfPaymentReceive } from 'hooks/query'; + +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { compose } from 'utils'; + +function PaymentReceivePdfPreviewDialogContent({ + subscriptionForm: { paymentReceiveId }, +}) { + const { isLoading, pdfUrl } = usePdfPaymentReceive(paymentReceiveId); + + return ( + +
+ + + + + + + +
+ + +
+ ); +} + +export default compose(withDialogActions)( + PaymentReceivePdfPreviewDialogContent, +); diff --git a/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/index.js b/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/index.js new file mode 100644 index 000000000..f96b22147 --- /dev/null +++ b/src/containers/Dialogs/PaymentReceivePdfPreviewDialog/index.js @@ -0,0 +1,44 @@ +import React from 'react'; +import classNames from 'classnames'; + +import { T, Dialog, DialogSuspense } from 'components'; + +import withDialogRedux from 'components/DialogReduxConnect'; + +import { CLASSES } from 'common/classes'; +import { compose } from 'utils'; + +// Lazy loading the content. +const PdfPreviewDialogContent = React.lazy(() => + import('./PaymentReceivePdfPreviewContent'), +); + +/** + * Payment receive PDF preview dialog. + */ +function PaymentReceivePdfPreviewDialog({ + dialogName, + payload = { paymentReceiveId: null }, + isOpen, +}) { + return ( + } + className={classNames(CLASSES.DIALOG_PDF_PREVIEW)} + autoFocus={true} + canEscapeKeyClose={true} + isOpen={isOpen} + style={{ width: '1000px' }} + > + + + + + ); +} + +export default compose(withDialogRedux())(PaymentReceivePdfPreviewDialog); diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js index 5d7810b34..2ab7d47a6 100644 --- a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js +++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js @@ -65,6 +65,11 @@ function CreditNoteDetailActionsBar({ openDialog('reconcile-credit-note', { creditNoteId }); }; + // Handle print credit note. + const handlePrintCreditNote = () => { + openDialog('credit-note-pdf-preview', { creditNoteId }); + }; + return ( @@ -88,6 +93,14 @@ function CreditNoteDetailActionsBar({ + +