diff --git a/client/src/components/CurrencySelectList.js b/client/src/components/CurrencySelectList.js new file mode 100644 index 000000000..dbcf8a43f --- /dev/null +++ b/client/src/components/CurrencySelectList.js @@ -0,0 +1,60 @@ +import React, { useCallback, useState, useEffect, useMemo } from 'react'; +import { FormattedMessage as T } from 'react-intl'; +import { ListSelect } from 'components'; +import { MenuItem } from '@blueprintjs/core'; + +export default function CurrencySelectList({ + currenciesList, + selectedCurrencyCode, + defaultSelectText = , + onCurrencySelected, + ...restProps +}) { + const [selectedCurrency, setSelectedCurrency] = useState(null); + + // Filters currencies list. + const filterCurrencies = (query, currency, _index, exactMatch) => { + const normalizedTitle = currency.currency_code.toLowerCase(); + const normalizedQuery = query.toLowerCase(); + + if (exactMatch) { + return normalizedTitle === normalizedQuery; + } else { + return ( + `${currency.currency_code} ${normalizedTitle}`.indexOf( + normalizedQuery, + ) >= 0 + ); + } + }; + + const onCurrencySelect = useCallback((currency) => { + setSelectedCurrency({ ...currency }); + onCurrencySelected && onCurrencySelected(currency); + }); + + const currencyCodeRenderer = useCallback((CurrencyCode, { handleClick }) => { + return ( + + ); + }, []); + + return ( + + ); +} diff --git a/client/src/components/index.js b/client/src/components/index.js index 31a83a3c1..00f0948bb 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -31,6 +31,8 @@ import Col from './Grid/Col'; import CloudLoadingIndicator from './CloudLoadingIndicator'; import MoneyExchangeRate from './MoneyExchangeRate'; import ContactSelecetList from './ContactSelecetList'; +import CurrencySelectList from './CurrencySelectList' + const Hint = FieldHint; @@ -69,4 +71,5 @@ export { CloudLoadingIndicator, MoneyExchangeRate, ContactSelecetList , + CurrencySelectList }; diff --git a/client/src/containers/Dialogs/AccountFormDialogContent.js b/client/src/containers/Dialogs/AccountFormDialog/AccountFormDialogContent.js similarity index 100% rename from client/src/containers/Dialogs/AccountFormDialogContent.js rename to client/src/containers/Dialogs/AccountFormDialog/AccountFormDialogContent.js diff --git a/client/src/containers/Dialogs/AccountFormDialog.js b/client/src/containers/Dialogs/AccountFormDialog/index.js similarity index 100% rename from client/src/containers/Dialogs/AccountFormDialog.js rename to client/src/containers/Dialogs/AccountFormDialog/index.js diff --git a/client/src/containers/Dialogs/CurencyFormDialogContent.js b/client/src/containers/Dialogs/CurrencyFormDialog/CurencyFormDialogContent.js similarity index 100% rename from client/src/containers/Dialogs/CurencyFormDialogContent.js rename to client/src/containers/Dialogs/CurrencyFormDialog/CurencyFormDialogContent.js diff --git a/client/src/containers/Dialogs/CurrencyFormDialog.js b/client/src/containers/Dialogs/CurrencyFormDialog/index.js similarity index 96% rename from client/src/containers/Dialogs/CurrencyFormDialog.js rename to client/src/containers/Dialogs/CurrencyFormDialog/index.js index 48c6757cb..19a1a82fc 100644 --- a/client/src/containers/Dialogs/CurrencyFormDialog.js +++ b/client/src/containers/Dialogs/CurrencyFormDialog/index.js @@ -7,6 +7,10 @@ import { compose } from 'utils'; const CurrencyFormDialogContent = lazy(() => import('./CurencyFormDialogContent'), ); + +/** + * Currency form dialog. + */ function CurrencyFormDialog({ dialogName, payload = { action: '', id: null }, diff --git a/client/src/containers/Dialogs/ExchangeRateDialog.container.js b/client/src/containers/Dialogs/ExchangeRateDialog.container.js deleted file mode 100644 index d107d266b..000000000 --- a/client/src/containers/Dialogs/ExchangeRateDialog.container.js +++ /dev/null @@ -1,33 +0,0 @@ -// import { connect } from 'react-redux'; -// import { compose } from 'utils'; - -// import withDialogActions from 'containers/Dialog/withDialogActions'; -// import withDialogRedux from 'components/DialogReduxConnect'; -// import withExchangeRateDetail from 'containers/ExchangeRates/withExchangeRateDetail'; -// import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions'; -// import withExchangeRates from 'containers/ExchangeRates/withExchangeRates'; -// import withCurrencies from 'containers/Currencies/withCurrencies'; - -// const mapStateToProps = (state, props) => ({ -// dialogName: 'exchangeRate-form', -// exchangeRateId: -// props.payload.action === 'edit' && props.payload.id -// ? props.payload.id -// : null, -// }); - -// const withExchangeRateDialog = connect(mapStateToProps); - -// export default compose( -// withDialogRedux(null, 'exchangeRate-form'), -// withExchangeRateDialog, -// withCurrencies(({ currenciesList }) => ({ -// currenciesList, -// })), -// withExchangeRatesActions, -// withExchangeRateDetail, -// withExchangeRates(({ exchangeRatesList }) => ({ -// exchangeRatesList, -// })), -// withDialogActions, -// ); diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialogContent.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js similarity index 85% rename from client/src/containers/Dialogs/ExchangeRateFormDialogContent.js rename to client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js index e640ecbd0..26aac7e70 100644 --- a/client/src/containers/Dialogs/ExchangeRateFormDialogContent.js +++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js @@ -23,6 +23,7 @@ import { ListSelect, DialogContent, FieldRequiredHint, + CurrencySelectList, } from 'components'; import classNames from 'classnames'; import withExchangeRateDetail from 'containers/ExchangeRates/withExchangeRateDetail'; @@ -173,33 +174,6 @@ function ExchangeRateFormDialogContent({ }, [setFieldValue, selectedItems], ); - - const filterCurrencyCode = (query, currency, _index, exactMatch) => { - const normalizedTitle = currency.currency_code.toLowerCase(); - const normalizedQuery = query.toLowerCase(); - - if (exactMatch) { - return normalizedTitle === normalizedQuery; - } else { - return ( - `${currency.currency_code} ${normalizedTitle}`.indexOf( - normalizedQuery, - ) >= 0 - ); - } - }; - - const currencyCodeRenderer = useCallback((CurrencyCode, { handleClick }) => { - return ( - - ); - }, []); - return ( @@ -232,17 +206,10 @@ function ExchangeRateFormDialogContent({ } > - } - itemRenderer={currencyCodeRenderer} - itemPredicate={filterCurrencyCode} - popoverProps={{ minimal: true }} - onItemSelect={onItemsSelect('currency_code')} - selectedItem={values.currency_code} - selectedItemProp={'currency_code'} - defaultText={} - labelProp={'currency_code'} + diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/index.js similarity index 100% rename from client/src/containers/Dialogs/ExchangeRateFormDialog.js rename to client/src/containers/Dialogs/ExchangeRateFormDialog/index.js diff --git a/client/src/containers/Dialogs/UserFormDialogContent.js b/client/src/containers/Dialogs/UserFormDialog/UserFormDialogContent.js similarity index 100% rename from client/src/containers/Dialogs/UserFormDialogContent.js rename to client/src/containers/Dialogs/UserFormDialog/UserFormDialogContent.js diff --git a/client/src/containers/Dialogs/UserFormDialog.js b/client/src/containers/Dialogs/UserFormDialog/index.js similarity index 100% rename from client/src/containers/Dialogs/UserFormDialog.js rename to client/src/containers/Dialogs/UserFormDialog/index.js diff --git a/client/src/containers/Expenses/ExpenseFormHeader.js b/client/src/containers/Expenses/ExpenseFormHeader.js index 1f128b644..e9b525dee 100644 --- a/client/src/containers/Expenses/ExpenseFormHeader.js +++ b/client/src/containers/Expenses/ExpenseFormHeader.js @@ -14,7 +14,7 @@ import moment from 'moment'; import { momentFormatter, compose, tansformDateValue } from 'utils'; import classNames from 'classnames'; import { - ListSelect, + CurrencySelectList, ContactSelecetList, ErrorMessage, AccountsSelectList, @@ -42,28 +42,6 @@ function ExpenseFormHeader({ [setFieldValue], ); - const currencyCodeRenderer = useCallback((item, { handleClick }) => { - return ( - - ); - }, []); - - // Filters Currency code. - const filterCurrencyCode = (query, currency, _index, exactMatch) => { - const normalizedTitle = currency.currency_code.toLowerCase(); - const normalizedQuery = query.toLowerCase(); - - if (exactMatch) { - return normalizedTitle === normalizedQuery; - } else { - return ( - `${currency.currency_code} ${normalizedTitle}`.indexOf( - normalizedQuery, - ) >= 0 - ); - } - }; - // Handles change account. const onChangeAccount = useCallback( (account) => { @@ -91,17 +69,6 @@ function ExpenseFormHeader({ [accountsList], ); - const CustomerRenderer = useCallback( - (cutomer, { handleClick }) => ( - - ), - [], - ); - // handle change customer const onChangeCustomer = useCallback( (filedName) => { @@ -205,17 +172,10 @@ function ExpenseFormHeader({ } > - } - itemRenderer={currencyCodeRenderer} - itemPredicate={filterCurrencyCode} - popoverProps={{ minimal: true }} - onItemSelect={onItemsSelect('currency_code')} - selectedItem={values.currency_code} - selectedItemProp={'currency_code'} - defaultText={} - labelProp={'currency_code'} + diff --git a/client/src/containers/Purchases/Bill/BillForm.js b/client/src/containers/Purchases/Bill/BillForm.js index 4c1b838ba..bf8d7087c 100644 --- a/client/src/containers/Purchases/Bill/BillForm.js +++ b/client/src/containers/Purchases/Bill/BillForm.js @@ -11,7 +11,7 @@ import moment from 'moment'; import { Intent } from '@blueprintjs/core'; import classNames from 'classnames'; import { FormattedMessage as T, useIntl } from 'react-intl'; -import { pick } from 'lodash'; +import { pick, sumBy } from 'lodash'; import { CLASSES } from 'common/classes'; import BillFormHeader from './BillFormHeader'; @@ -215,11 +215,25 @@ function BillForm({ ...initialValues, }, onSubmit: (values, { setSubmitting, setErrors, resetForm }) => { - setSubmitting(true); + const entries = values.entries.filter( + (item) => item.item_id && item.quantity, + ); + const totalQuantity = sumBy(entries, (entry) => parseInt(entry.quantity)); + + if (totalQuantity === 0) { + AppToaster.show({ + message: formatMessage({ + id: 'quantity_cannot_be_zero_or_empty', + }), + intent: Intent.DANGER, + }); + setSubmitting(false); + return; + } const form = { ...values, - entries: values.entries.filter((item) => item.item_id && item.quantity), + entries, }; const requestForm = { ...form }; if (bill && bill.id) { @@ -311,16 +325,20 @@ function BillForm({ }, []); // Clear page subtitle once unmount bill form page. - useEffect(() => () => { - changePageSubtitle(''); - }, [changePageSubtitle]); + useEffect( + () => () => { + changePageSubtitle(''); + }, + [changePageSubtitle], + ); return ( + onBillNumberChanged={handleBillNumberChanged} + /> item.item_id && item.quantity, ); + + const totalQuantity = sumBy(entries, (entry) => parseInt(entry.quantity)); + + if (totalQuantity === 0) { + AppToaster.show({ + message: formatMessage({ + id: 'quantity_cannot_be_zero_or_empty', + }), + intent: Intent.DANGER, + }); + setSubmitting(false); + return; + } const form = { ...values, entries, @@ -277,7 +290,6 @@ const EstimateForm = ({ } }, }); - useEffect(() => { formik.setFieldValue('estimate_number', estimateNumber); }, [estimateNumber]); diff --git a/client/src/containers/Sales/Invoice/InvoiceForm.js b/client/src/containers/Sales/Invoice/InvoiceForm.js index be611ec40..bee436764 100644 --- a/client/src/containers/Sales/Invoice/InvoiceForm.js +++ b/client/src/containers/Sales/Invoice/InvoiceForm.js @@ -10,7 +10,7 @@ import { useFormik } from 'formik'; import moment from 'moment'; import { Intent, FormGroup, TextArea } from '@blueprintjs/core'; import { FormattedMessage as T, useIntl } from 'react-intl'; -import { pick } from 'lodash'; +import { pick, sumBy } from 'lodash'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; @@ -97,8 +97,7 @@ function InvoiceForm({ due_date: Yup.date() .required() .label(formatMessage({ id: 'due_date_' })), - invoice_no: Yup.string() - .label(formatMessage({ id: 'invoice_no_' })), + invoice_no: Yup.string().label(formatMessage({ id: 'invoice_no_' })), reference_no: Yup.string().min(1).max(255), status: Yup.string().required(), invoice_message: Yup.string() @@ -127,7 +126,7 @@ function InvoiceForm({ is: (quantity, rate) => quantity || rate, then: Yup.number().required(), }), - discount: Yup.number().nullable().min(0).max(100), + discount: Yup.number().nullable().min(0).max(100), description: Yup.string().nullable(), }), ), @@ -232,6 +231,18 @@ function InvoiceForm({ const entries = values.entries.filter( (item) => item.item_id && item.quantity, ); + const totalQuantity = sumBy(entries, (entry) => parseInt(entry.quantity)); + + if (totalQuantity === 0) { + AppToaster.show({ + message: formatMessage({ + id: 'quantity_cannot_be_zero_or_empty', + }), + intent: Intent.DANGER, + }); + setSubmitting(false); + return; + } const form = { ...values, entries, @@ -279,6 +290,7 @@ function InvoiceForm({ } }, }); + useEffect(() => { formik.setFieldValue('invoice_no', invoiceNumber); }, [invoiceNumber]); diff --git a/client/src/containers/Sales/Receipt/ReceiptForm.js b/client/src/containers/Sales/Receipt/ReceiptForm.js index eeb3afc7d..93e9a2414 100644 --- a/client/src/containers/Sales/Receipt/ReceiptForm.js +++ b/client/src/containers/Sales/Receipt/ReceiptForm.js @@ -11,7 +11,7 @@ import { useFormik } from 'formik'; import moment from 'moment'; import { Intent, FormGroup, TextArea } from '@blueprintjs/core'; import { FormattedMessage as T, useIntl } from 'react-intl'; -import { pick } from 'lodash'; +import { pick,sumBy } from 'lodash'; import classNames from 'classnames'; import { CLASSES } from 'common/classes'; @@ -229,6 +229,19 @@ function ReceiptForm({ const entries = values.entries.filter( (item) => item.item_id && item.quantity, ); + + const totalQuantity = sumBy(entries, (entry) => parseInt(entry.quantity)); + + if (totalQuantity === 0) { + AppToaster.show({ + message: formatMessage({ + id: 'quantity_cannot_be_zero_or_empty', + }), + intent: Intent.DANGER, + }); + setSubmitting(false); + return; + } const form = { ...values, entries, diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index 2793350a2..d9ba48e51 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -789,4 +789,6 @@ export default { sale_invoice_number_is_exists: 'Sale invoice number is exists', bill_number_exists:'Bill number exists', ok: 'Ok!', + quantity_cannot_be_zero_or_empty: 'Quantity cannot be zero or empty.', + };