diff --git a/client/src/components/DialogsContainer.js b/client/src/components/DialogsContainer.js index 41789dd99..1dfdd0998 100644 --- a/client/src/components/DialogsContainer.js +++ b/client/src/components/DialogsContainer.js @@ -11,6 +11,8 @@ import PaymentViaVoucherDialog from 'containers/Dialogs/PaymentViaVoucherDialog' import KeyboardShortcutsDialog from 'containers/Dialogs/keyboardShortcutsDialog'; import ContactDuplicateDialog from 'containers/Dialogs/ContactDuplicateDialog'; import QuickPaymentReceiveFormDialog from 'containers/Dialogs/QuickPaymentReceiveFormDialog'; +import QuickPaymentMadeFormDialog from 'containers/Dialogs/QuickPaymentMadeFormDialog'; + /** * Dialogs container. */ @@ -26,7 +28,8 @@ export default function DialogsContainer() { - + + ); } diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMade.schema.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMade.schema.js new file mode 100644 index 000000000..7139ab69c --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMade.schema.js @@ -0,0 +1,34 @@ +import * as Yup from 'yup'; +import { formatMessage } from 'services/intl'; +import { DATATYPES_LENGTH } from 'common/dataTypes'; + +const Schema = Yup.object().shape({ + vendor_id: Yup.string() + .label(formatMessage({ id: 'vendor_name_' })) + .required(), + payment_date: Yup.date() + .required() + .label(formatMessage({ id: 'payment_date_' })), + payment_number: Yup.string() + .nullable() + .max(DATATYPES_LENGTH.STRING) + .label(formatMessage({ id: 'payment_no_' })), + payment_account_id: Yup.number() + .required() + .label(formatMessage({ id: 'payment_account_' })), + reference: 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(), + bill_id: Yup.number() + .nullable() + .when(['payment_amount'], { + is: (payment_amount) => payment_amount, + then: Yup.number().required(), + }), + }), + ), +}); + +export const CreateQuickPaymentMadeFormSchema = Schema; diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFloatingActions.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFloatingActions.js new file mode 100644 index 000000000..ea2413200 --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFloatingActions.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { Intent, Button, Classes } from '@blueprintjs/core'; +import { useFormikContext } from 'formik'; +import { FormattedMessage as T } from 'react-intl'; + +import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider'; +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { compose } from 'utils'; + +function QuickPaymentMadeFloatingActions({ + // #withDialogActions + closeDialog, +}) { + // Formik context. + const { isSubmitting } = useFormikContext(); + + const { dialogName } = useQuickPaymentMadeContext(); + + // Handle close button click. + const handleCancelBtnClick = () => { + closeDialog(dialogName); + }; + + return ( +
+
+ + +
+
+ ); +} + +export default compose(withDialogActions)(QuickPaymentMadeFloatingActions); diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.js new file mode 100644 index 000000000..b2bd5198e --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.js @@ -0,0 +1,85 @@ +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 { CreateQuickPaymentMadeFormSchema } from './QuickPaymentMade.schema'; +import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider'; +import QuickPaymentMadeFormContent from './QuickPaymentMadeFormContent'; + +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { defaultPaymentMade, transformErrors } from './utils'; +import { compose } from 'utils'; + +/** + * Quick payment made form. + */ +function QuickPaymentMadeForm({ + // #withDialogActions + closeDialog, +}) { + const { formatMessage } = useIntl(); + const { + bill, + dialogName, + createPaymentMadeMutate, + } = useQuickPaymentMadeContext(); + + // Initial form values + const initialValues = { + ...defaultPaymentMade, + ...bill, + }; + + // Handles the form submit. + const handleFormSubmit = (values, { setSubmitting, setFieldError }) => { + const entries = [values] + .filter((entry) => entry.id && entry.payment_amount) + .map((entry) => ({ + bill_id: entry.id, + ...pick(entry, ['payment_amount']), + })); + + const form = { + ...values, + vendor_id: values?.vendor?.id, + entries, + }; + + // Handle request response success. + const onSuccess = () => { + AppToaster.show({ + message: formatMessage({ + id: 'the_payment_made_has_been_created_successfully', + }), + intent: Intent.SUCCESS, + }); + closeDialog(dialogName); + }; + // Handle request response errors. + const onError = ({ + response: { + data: { errors }, + }, + }) => { + if (errors) { + transformErrors(errors, { setFieldError }); + } + setSubmitting(false); + }; + createPaymentMadeMutate(form).then(onSuccess).catch(onError); + }; + + return ( + + ); +} + +export default compose(withDialogActions)(QuickPaymentMadeForm); diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormContent.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormContent.js new file mode 100644 index 000000000..f2bbd8615 --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormContent.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { Form } from 'formik'; +import QuickPaymentMadeFormFields from './QuickPaymentMadeFormFields'; +import QuickPaymentMadeFloatingActions from './QuickPaymentMadeFloatingActions'; +/** + * Quick payment made form content. + */ +export default function QuickPaymentMadeFormContent() { + return ( +
+ + + + ); +} diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormDialogContent.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormDialogContent.js new file mode 100644 index 000000000..bfc35be0c --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormDialogContent.js @@ -0,0 +1,21 @@ +import React from 'react'; + +import 'style/pages/PaymentReceive/QuickPaymentReceiveDialog.scss'; + +import { QuickPaymentMadeFormProvider } from './QuickPaymentMadeFormProvider'; +import QuickPaymentMadeForm from './QuickPaymentMadeForm'; + +/** + * Quick payment made form dialog content. + */ +export default function QuickPaymentMadeFormDialogContent({ + // #ownProps + dialogName, + bill, +}) { + return ( + + + + ); +} diff --git a/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.js b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.js new file mode 100644 index 000000000..361923570 --- /dev/null +++ b/client/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.js @@ -0,0 +1,197 @@ +import React from 'react'; +import { FastField, ErrorMessage } from 'formik'; +import { FormattedMessage as T } from 'react-intl'; +import { + Classes, + FormGroup, + InputGroup, + TextArea, + Position, + ControlGroup, +} from '@blueprintjs/core'; +import { useAutofocus } from 'hooks'; +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 { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider'; + +/** + * Quick payment made form fields. + */ +export default function QuickPaymentMadeFormFields() { + const { accounts } = useQuickPaymentMadeContext(); + const paymentMadeFieldRef = useAutofocus(); + + return ( +
+ + + {/* ------------- Vendor name ------------- */} + + {({ from, field, meta: { error, touched } }) => ( + } + className={classNames('form-group--select-list', CLASSES.FILL)} + labelInfo={} + intent={inputIntent({ error, touched })} + helperText={} + > + + + )} + + + + {/* ------------ Payment number. ------------ */} + + {({ form, field, meta: { error, touched } }) => ( + } + className={('form-group--payment_number', 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) => (paymentMadeFieldRef.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: , + }} + /> + + )} + + + + {/* ------------ payment account ------------ */} + + {({ form, field: { value }, meta: { error, touched } }) => ( + } + className={classNames( + 'form-group--payment_account_id', + 'form-group--select-list', + CLASSES.FILL, + )} + labelInfo={} + intent={inputIntent({ error, touched })} + helperText={} + > + } + onAccountSelected={(account) => { + form.setFieldValue('payment_account_id', account.id); + }} + 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'} + > +