diff --git a/client/src/components/DialogsContainer.js b/client/src/components/DialogsContainer.js index 12e26077c..41789dd99 100644 --- a/client/src/components/DialogsContainer.js +++ b/client/src/components/DialogsContainer.js @@ -10,6 +10,7 @@ import InventoryAdjustmentDialog from 'containers/Dialogs/InventoryAdjustmentFor import PaymentViaVoucherDialog from 'containers/Dialogs/PaymentViaVoucherDialog'; import KeyboardShortcutsDialog from 'containers/Dialogs/keyboardShortcutsDialog'; import ContactDuplicateDialog from 'containers/Dialogs/ContactDuplicateDialog'; +import QuickPaymentReceiveFormDialog from 'containers/Dialogs/QuickPaymentReceiveFormDialog'; /** * Dialogs container. */ @@ -25,6 +26,7 @@ export default function DialogsContainer() { + ); } diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceive.schema.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceive.schema.js new file mode 100644 index 000000000..e3b5ed2f6 --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceive.schema.js @@ -0,0 +1,35 @@ +import * as Yup from 'yup'; +import { formatMessage } from 'services/intl'; +import { DATATYPES_LENGTH } from 'common/dataTypes'; + +const Schema = Yup.object().shape({ + customer_id: Yup.string() + .label(formatMessage({ id: 'customer_name_' })) + .required(), + payment_receive_no: Yup.string() + .required() + .nullable() + .max(DATATYPES_LENGTH.STRING) + .label(formatMessage({ id: 'payment_receive_no_' })), + payment_date: Yup.date() + .required() + .label(formatMessage({ id: 'payment_date_' })), + deposit_account_id: Yup.number() + .required() + .label(formatMessage({ id: 'deposit_account_' })), + reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(), + // statement: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT), + entries: Yup.array().of( + Yup.object().shape({ + payment_amount: Yup.number().nullable(), + invoice_id: Yup.number() + .nullable() + .when(['payment_amount'], { + is: (payment_amount) => payment_amount, + then: Yup.number().required(), + }), + }), + ), +}); + +export const CreateQuickPaymentReceiveFormSchema = Schema; diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFloatingActions.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFloatingActions.js new file mode 100644 index 000000000..84aac1b82 --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFloatingActions.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { Intent, Button, Classes } from '@blueprintjs/core'; +import { useFormikContext } from 'formik'; +import { FormattedMessage as T } from 'react-intl'; + +import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider'; +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { compose } from 'utils'; + +function QuickPaymentReceiveFloatingActions({ + // #withDialogActions + closeDialog, +}) { + // Formik context. + const { isSubmitting } = useFormikContext(); + + // quick payment receive dialog context. + const { dialogName } = useQuickPaymentReceiveContext(); + + // Handle close button click. + const handleCancelBtnClick = () => { + closeDialog(dialogName); + }; + return ( +
+
+ + +
+
+ ); +} +export default compose(withDialogActions)(QuickPaymentReceiveFloatingActions); diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.js new file mode 100644 index 000000000..66729ae9c --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.js @@ -0,0 +1,86 @@ +import React from 'react'; +import { Formik } from 'formik'; +import { Intent } from '@blueprintjs/core'; +import { FormattedMessage as T, useIntl } from 'react-intl'; +import { pick } from 'lodash'; + +import { AppToaster } from 'components'; +import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider'; +import { CreateQuickPaymentReceiveFormSchema } from './QuickPaymentReceive.schema'; +import QuickPaymentReceiveFormContent from './QuickPaymentReceiveFormContent'; + +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { defaultInitialValues, transformErrors } from './utils'; +import { compose } from 'utils'; + +/** + * Quick payment receive form. + */ +function QuickPaymentReceiveForm({ + // #withDialogActions + closeDialog, +}) { + const { formatMessage } = useIntl(); + const { + dialogName, + invoice, + createPaymentReceiveMutate, + } = useQuickPaymentReceiveContext(); + + // Initial form values + const initialValues = { + ...defaultInitialValues, + ...invoice, + }; + + // Handles the form submit. + const handleFormSubmit = (values, { setSubmitting, setFieldError }) => { + const entries = [values] + .filter((entry) => entry.id && entry.payment_amount) + .map((entry) => ({ + invoice_id: entry.id, + ...pick(entry, ['payment_amount']), + })); + + const form = { + ...values, + customer_id: values.customer.id, + entries, + }; + + // Handle request response success. + const onSaved = (response) => { + AppToaster.show({ + message: formatMessage({ + id: 'the_payment_receive_transaction_has_been_created', + }), + intent: Intent.SUCCESS, + }); + closeDialog(dialogName); + }; + // Handle request response errors. + const onError = ({ + response: { + data: { errors }, + }, + }) => { + if (errors) { + transformErrors(errors, { setFieldError }); + } + setSubmitting(false); + }; + + createPaymentReceiveMutate(form).then(onSaved).catch(onError); + }; + + return ( + + ); +} + +export default compose(withDialogActions)(QuickPaymentReceiveForm); diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormContent.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormContent.js new file mode 100644 index 000000000..a7644d2e5 --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormContent.js @@ -0,0 +1,16 @@ +import React from 'react'; +import { Form } from 'formik'; +import QuickPaymentReceiveFormFields from './QuickPaymentReceiveFormFields'; +import QuickPaymentReceiveFloatingActions from './QuickPaymentReceiveFloatingActions'; + +/** + * Quick payment receive form content. + */ +export default function QuickPaymentReceiveFormContent() { + return ( +
+ + + + ); +} diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormDialogContent.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormDialogContent.js new file mode 100644 index 000000000..94bfc506f --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormDialogContent.js @@ -0,0 +1,24 @@ +import React from 'react'; + +import 'style/pages/PaymentReceive/QuickPaymentReceiveDialog.scss'; + +import { QuickPaymentReceiveFormProvider } from './QuickPaymentReceiveFormProvider'; +import QuickPaymentReceiveForm from './QuickPaymentReceiveForm'; + +/** + * Quick payment receive form dialog content. + */ +export default function QuickPaymentReceiveFormDialogContent({ + // #ownProps + dialogName, + invoice, +}) { + return ( + + + + ); +} diff --git a/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.js b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.js new file mode 100644 index 000000000..e407c23ef --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.js @@ -0,0 +1,200 @@ +import React from 'react'; +import { FastField, ErrorMessage } from 'formik'; +import { FormattedMessage as T } from 'react-intl'; +import { useAutofocus } from 'hooks'; +import { + Classes, + FormGroup, + InputGroup, + TextArea, + Position, + ControlGroup, +} from '@blueprintjs/core'; +import classNames from 'classnames'; +import { CLASSES } from 'common/classes'; +import { DateInput } from '@blueprintjs/datetime'; +import { FieldRequiredHint, Col, Row } from 'components'; +import { + AccountsSelectList, + InputPrependText, + MoneyInputGroup, + Icon, +} from 'components'; +import { + inputIntent, + momentFormatter, + tansformDateValue, + handleDateChange, +} from 'utils'; +import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider'; + +/** + * Quick payment receive form fields. + */ +export default function QuickPaymentReceiveFormFields({}) { + const { accounts } = useQuickPaymentReceiveContext(); + + const paymentReceiveFieldRef = useAutofocus(); + + return ( +
+ + + {/* ------------- Customer name ------------- */} + + {({ from, field, meta: { error, touched } }) => ( + } + className={classNames('form-group--select-list', CLASSES.FILL)} + labelInfo={} + intent={inputIntent({ error, touched })} + helperText={} + > + + + )} + + + + {/* ------------ Payment receive no. ------------ */} + + {({ form, field, meta: { error, touched } }) => ( + } + labelInfo={} + className={('form-group--payment_receive_no', CLASSES.FILL)} + intent={inputIntent({ error, touched })} + helperText={} + > + + + )} + + + + {/*------------ Amount Received -----------*/} + + {({ + form: { values, setFieldValue }, + field: { value }, + meta: { error, touched }, + }) => ( + } + labelInfo={} + className={classNames('form-group--payment_amount', CLASSES.FILL)} + intent={inputIntent({ error, touched })} + helperText={} + > + + + + { + setFieldValue('payment_amount', amount); + }} + intent={inputIntent({ error, touched })} + inputRef={(ref) => (paymentReceiveFieldRef.current = ref)} + /> + + + )} + + + + {/* ------------- Payment date ------------- */} + + {({ form, field: { value }, meta: { error, touched } }) => ( + } + labelInfo={} + className={classNames('form-group--select-list', CLASSES.FILL)} + intent={inputIntent({ error, touched })} + helperText={} + > + { + form.setFieldValue('payment_date', formattedDate); + })} + popoverProps={{ position: Position.BOTTOM, minimal: true }} + inputProps={{ + leftIcon: , + }} + /> + + )} + + + + {/* ------------ Deposit account ------------ */} + + {({ form, field: { value }, meta: { error, touched } }) => ( + } + className={classNames( + 'form-group--deposit_account_id', + 'form-group--select-list', + CLASSES.FILL, + )} + labelInfo={} + intent={inputIntent({ error, touched })} + helperText={} + > + } + onAccountSelected={(account) => { + form.setFieldValue('deposit_account_id', account.id); + }} + defaultSelectText={} + selectedAccountId={value} + /> + + )} + + + + {/* ------------ Reference No. ------------ */} + + {({ form, field, meta: { error, touched } }) => ( + } + className={classNames('form-group--reference', CLASSES.FILL)} + intent={inputIntent({ error, touched })} + helperText={} + > + + + )} + + {/* --------- Statement --------- */} + + {({ form, field, meta: { error, touched } }) => ( + } + className={'form-group--statement'} + > +