From 282da55d08aace313af4063e59780df3a00be2bd Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 Jul 2020 21:22:27 +0200 Subject: [PATCH] - feat: Filter expense and payment accounts on expense form. - feat: Make journal errors with receivable and payable accounts. - fix: Handle database big numbers. - fix: Indexing lines when add a new line on make journal form. - fix: Abstruct accounts type component. --- client/src/components/AccountsSelectList.js | 3 +- client/src/components/AccountsTypesSelect.js | 46 ++ client/src/components/ContactsListField.js | 10 +- .../DataTableCells/AccountsListFieldCell.js | 45 +- .../DataTableCells/ContactsListFieldCell.js | 6 +- .../DataTableCells/InputGroupCell.js | 40 +- .../DataTableCells/MoneyFieldCell.js | 37 +- client/src/components/ListSelect.js | 2 +- client/src/components/index.js | 2 + .../Accounting/MakeJournalEntriesForm.js | 106 +++-- .../Accounting/MakeJournalEntriesTable.js | 5 +- .../Accounting/ManualJournalsDataTable.js | 4 +- .../src/containers/Accounts/AccountsChart.js | 2 +- .../containers/Dialogs/AccountFormDialog.js | 191 +++----- .../containers/Expenses/ExpenseDataTable.js | 2 +- .../src/containers/Expenses/ExpenseFooter.js | 2 +- client/src/containers/Expenses/ExpenseForm.js | 60 ++- .../containers/Expenses/ExpenseFormHeader.js | 38 +- .../src/containers/Expenses/ExpenseTable.js | 29 +- .../src/containers/Expenses/ExpensesList.js | 3 +- .../containers/Expenses/withExpenseDetail.js | 2 +- client/src/lang/en/index.js | 14 +- client/src/services/yup.js | 23 + client/src/store/accounts/accounts.actions.js | 4 +- client/src/store/accounts/accounts.reducer.js | 6 +- client/src/store/expenses/expenses.reducer.js | 2 +- client/src/style/objects/form.scss | 3 +- client/src/style/pages/accounts-chart.scss | 22 +- client/src/style/pages/dashboard.scss | 6 +- client/src/style/pages/expense-form.scss | 21 +- .../src/style/pages/make-journal-entries.scss | 15 +- .../20190822214303_create_items_table.js | 4 +- ...2647_create_accounts_transactions_table.js | 4 +- .../20200105014405_create_expenses_table.js | 4 +- ...23827_create_currency_adjustments_table.js | 2 +- ...0105195823_create_manual_journals_table.js | 2 +- ...e_expense_transactions_categories_table.js | 2 +- server/src/http/controllers/Accounting.js | 442 +++++++++++------- server/src/http/controllers/Accounts.js | 258 +++++----- server/src/http/controllers/Expenses.js | 309 +++++++----- 40 files changed, 1031 insertions(+), 747 deletions(-) create mode 100644 client/src/components/AccountsTypesSelect.js create mode 100644 client/src/services/yup.js diff --git a/client/src/components/AccountsSelectList.js b/client/src/components/AccountsSelectList.js index 57a78c856..dec6a7061 100644 --- a/client/src/components/AccountsSelectList.js +++ b/client/src/components/AccountsSelectList.js @@ -5,11 +5,10 @@ import { FormattedMessage as T } from 'react-intl'; export default function AccountsSelectList({ accounts, - onAccountSelected, - error = [], initialAccountId, selectedAccountId, defaultSelectText = 'Select account', + onAccountSelected, }) { // Find initial account object to set it as default account in initial render. const initialAccount = useMemo( diff --git a/client/src/components/AccountsTypesSelect.js b/client/src/components/AccountsTypesSelect.js new file mode 100644 index 000000000..1dd4b6fd3 --- /dev/null +++ b/client/src/components/AccountsTypesSelect.js @@ -0,0 +1,46 @@ +import React, { useCallback } from 'react'; +import { + ListSelect, +} from 'components'; + +export default function AccountsTypesSelect({ + accountsTypes, + selectedTypeId, + defaultSelectText = 'Select account type', + onTypeSelected, + ...restProps +}) { + // Filters accounts types items. + const filterAccountTypeItems = (query, accountType, _index, exactMatch) => { + const normalizedTitle = accountType.name.toLowerCase(); + const normalizedQuery = query.toLowerCase(); + + if (exactMatch) { + return normalizedTitle === normalizedQuery; + } else { + return normalizedTitle.indexOf(normalizedQuery) >= 0; + } + }; + + // Handle item selected. + const handleItemSelected = (accountType) => { + onTypeSelected && onTypeSelected(accountType); + }; + + const items = accountsTypes.map((type) => ({ + id: type.id, name: type.name, + })); + + return ( + + ); +} \ No newline at end of file diff --git a/client/src/components/ContactsListField.js b/client/src/components/ContactsListField.js index b99c17c4c..8db5fa0de 100644 --- a/client/src/components/ContactsListField.js +++ b/client/src/components/ContactsListField.js @@ -17,17 +17,9 @@ export default function ContactsListField({ initialContact || null ); - const contactTypeLabel = (contactType) => { - switch(contactType) { - case 'customer': - return 'Customer'; - case 'vendor': - return 'Vendor'; - } - }; // Contact item of select accounts field. const contactItem = useCallback((item, { handleClick, modifiers, query }) => ( - + ), []); const onContactSelect = useCallback((contact) => { diff --git a/client/src/components/DataTableCells/AccountsListFieldCell.js b/client/src/components/DataTableCells/AccountsListFieldCell.js index c4c5e33a5..30c1f5d6a 100644 --- a/client/src/components/DataTableCells/AccountsListFieldCell.js +++ b/client/src/components/DataTableCells/AccountsListFieldCell.js @@ -1,44 +1,43 @@ -import React, {useCallback, useMemo} from 'react'; +import React, { useCallback, useMemo } from 'react'; import AccountsSelectList from 'components/AccountsSelectList'; import classNames from 'classnames'; -import { - FormGroup, - Classes, - Intent, -} from '@blueprintjs/core'; +import { FormGroup, Classes, Intent } from '@blueprintjs/core'; // Account cell renderer. const AccountCellRenderer = ({ - column: { id }, + column: { id, accountsDataProp }, row: { index, original }, cell: { value: initialValue }, - payload: { accounts, updateData, errors }, + payload: { accounts: defaultAccounts, updateData, errors, ...restProps }, }) => { - const handleAccountSelected = useCallback((account) => { - updateData(index, id, account.id); - }, [updateData, index, id]); - - const { account_id = false } = (errors[index] || {}); - - // const initialAccount = useMemo(() => - // accounts.find(a => a.id === initialValue), - // [accounts, initialValue]); + const handleAccountSelected = useCallback( + (account) => { + updateData(index, id, account.id); + }, + [updateData, index, id], + ); + const error = errors?.[index]?.[id]; + const accounts = useMemo( + () => restProps[accountsDataProp] || defaultAccounts, + [restProps, defaultAccounts, accountsDataProp], + ); return ( + Classes.FILL, + )} + > + selectedAccountId={initialValue} + /> ); }; -export default AccountCellRenderer; \ No newline at end of file +export default AccountCellRenderer; diff --git a/client/src/components/DataTableCells/ContactsListFieldCell.js b/client/src/components/DataTableCells/ContactsListFieldCell.js index 23d881f0b..e04e35ec3 100644 --- a/client/src/components/DataTableCells/ContactsListFieldCell.js +++ b/client/src/components/DataTableCells/ContactsListFieldCell.js @@ -1,5 +1,5 @@ import React, { useCallback, useMemo } from 'react'; -import { FormGroup, Classes } from "@blueprintjs/core"; +import { FormGroup, Intent, Classes } from "@blueprintjs/core"; import classNames from 'classnames'; import ContactsListField from 'components/ContactsListField'; @@ -20,8 +20,11 @@ export default function ContactsListCellRenderer({ return contacts.find(c => c.id === initialValue); }, [contacts, initialValue]); + const error = errors?.[index]?.[id]; + return ( ) diff --git a/client/src/components/DataTableCells/InputGroupCell.js b/client/src/components/DataTableCells/InputGroupCell.js index 86503eabb..07872a00f 100644 --- a/client/src/components/DataTableCells/InputGroupCell.js +++ b/client/src/components/DataTableCells/InputGroupCell.js @@ -1,31 +1,35 @@ -import React, {useState, useEffect} from 'react'; -import { - InputGroup -} from '@blueprintjs/core'; +import React, { useState, useEffect } from 'react'; +import classNames from 'classnames'; +import { Classes, InputGroup, FormGroup } from '@blueprintjs/core'; const InputEditableCell = ({ row: { index }, - column: { id, }, + column: { id }, cell: { value: initialValue }, payload, }) => { - const [value, setValue] = useState(initialValue) + const [value, setValue] = useState(initialValue); - const onChange = e => { - setValue(e.target.value) - } + const onChange = (e) => { + setValue(e.target.value); + }; const onBlur = () => { - payload.updateData(index, id, value) - } + payload.updateData(index, id, value); + }; useEffect(() => { - setValue(initialValue) - }, [initialValue]) + setValue(initialValue); + }, [initialValue]); - return (); + return ( + + + + ); }; export default InputEditableCell; diff --git a/client/src/components/DataTableCells/MoneyFieldCell.js b/client/src/components/DataTableCells/MoneyFieldCell.js index ab47e808a..cb0fbdf4e 100644 --- a/client/src/components/DataTableCells/MoneyFieldCell.js +++ b/client/src/components/DataTableCells/MoneyFieldCell.js @@ -1,4 +1,5 @@ import React, { useCallback, useState, useEffect } from 'react'; +import { FormGroup, Intent } from '@blueprintjs/core'; import MoneyInputGroup from 'components/MoneyInputGroup'; // Input form cell renderer. @@ -6,7 +7,7 @@ const MoneyFieldCellRenderer = ({ row: { index }, column: { id }, cell: { value: initialValue }, - payload + payload: { errors, updateData }, }) => { const [value, setValue] = useState(initialValue); @@ -15,26 +16,36 @@ const MoneyFieldCellRenderer = ({ }, []); function isNumeric(data) { - return !isNaN(parseFloat(data)) && isFinite(data) && data.constructor !== Array; + return ( + !isNaN(parseFloat(data)) && isFinite(data) && data.constructor !== Array + ); } const onBlur = () => { const updateValue = isNumeric(value) ? parseFloat(value) : value; - payload.updateData(index, id, updateValue); + updateData(index, id, updateValue); }; useEffect(() => { setValue(initialValue); - }, [initialValue]) + }, [initialValue]); - return () + const error = errors?.[index]?.[id]; + + return ( + + + + ); }; -export default MoneyFieldCellRenderer; \ No newline at end of file +export default MoneyFieldCellRenderer; diff --git a/client/src/components/ListSelect.js b/client/src/components/ListSelect.js index 8d94ac3bc..88b7440e2 100644 --- a/client/src/components/ListSelect.js +++ b/client/src/components/ListSelect.js @@ -33,7 +33,7 @@ export default function ListSelect ({ ('loading') : ; const itemRenderer = (item, { handleClick, modifiers, query }) => { - return (); + return (); }; return ( diff --git a/client/src/components/index.js b/client/src/components/index.js index 7db273f77..49025b12a 100644 --- a/client/src/components/index.js +++ b/client/src/components/index.js @@ -19,6 +19,7 @@ import Dialog from './Dialog'; import AppToaster from './AppToaster'; import DataTable from './DataTable'; import AccountsSelectList from './AccountsSelectList'; +import AccountsTypesSelect from './AccountsTypesSelect'; const Hint = FieldHint; @@ -45,4 +46,5 @@ export { AppToaster, DataTable, AccountsSelectList, + AccountsTypesSelect, }; \ No newline at end of file diff --git a/client/src/containers/Accounting/MakeJournalEntriesForm.js b/client/src/containers/Accounting/MakeJournalEntriesForm.js index 3afe8950e..f586d6056 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesForm.js +++ b/client/src/containers/Accounting/MakeJournalEntriesForm.js @@ -10,7 +10,7 @@ import { useFormik } from 'formik'; import moment from 'moment'; import { Intent } from '@blueprintjs/core'; import { useIntl } from 'react-intl'; -import { pick } from 'lodash'; +import { pick, setWith } from 'lodash'; import MakeJournalEntriesHeader from './MakeJournalEntriesHeader'; import MakeJournalEntriesFooter from './MakeJournalEntriesFooter'; @@ -89,19 +89,23 @@ function MakeJournalEntriesForm({ const validationSchema = Yup.object().shape({ journal_number: Yup.string() .required() + .min(1) + .max(255) .label(formatMessage({ id: 'journal_number_' })), journal_type: Yup.string() .required() + .min(1) + .max(255) .label(formatMessage({ id: 'journal_type' })), date: Yup.date() .required() .label(formatMessage({ id: 'date' })), - reference: Yup.string(), - description: Yup.string(), + reference: Yup.string().min(1).max(255), + description: Yup.string().min(1).max(1024), entries: Yup.array().of( Yup.object().shape({ - credit: Yup.number().nullable(), - debit: Yup.number().nullable(), + credit: Yup.number().decimalScale(13).nullable(), + debit: Yup.number().decimalScale(13).nullable(), account_id: Yup.number() .nullable() .when(['credit', 'debit'], { @@ -109,7 +113,7 @@ function MakeJournalEntriesForm({ then: Yup.number().required(), }), contact_id: Yup.number().nullable(), - note: Yup.string().nullable(), + note: Yup.string().max(255).nullable(), }), ), }); @@ -180,48 +184,66 @@ function MakeJournalEntriesForm({ }, [manualJournal]); // Transform API errors in toasts messages. - const transformErrors = (errors, { setErrors }) => { - const hasError = (errorType) => errors.some((e) => e.type === errorType); + const transformErrors = (resErrors, { setErrors, errors }) => { + const getError = (errorType) => resErrors.find((e) => e.type === errorType); + const toastMessages = []; + let error; + let newErrors = { ...errors, entries: [] }; - if (hasError(ERROR.CUSTOMERS_NOT_WITH_RECEVIABLE_ACC)) { - AppToaster.show({ - message: formatMessage({ - id: 'customers_should_assign_with_receivable_account_only', - }), - intent: Intent.DANGER, + const setEntriesErrors = (indexes, prop, message) => + indexes.forEach((i) => { + const index = Math.max(i - 1, 0); + newErrors = setWith(newErrors, `entries.[${index}].${prop}`, message); }); - } - if (hasError(ERROR.PAYABLE_ENTRIES_HAS_NO_VENDORS)) { - AppToaster.show({ - message: formatMessage({ - id: 'vendors_should_assign_with_payable_account_only', + + if ((error = getError(ERROR.PAYABLE_ENTRIES_HAS_NO_VENDORS))) { + toastMessages.push( + formatMessage({ + id: 'vendors_should_selected_with_payable_account_only', }), - intent: Intent.DANGER, - }); + ); + setEntriesErrors(error.indexes, 'contact_id', 'error'); } - if (hasError(ERROR.RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS)) { - AppToaster.show({ - message: formatMessage({ - id: 'entries_with_receivable_account_no_assigned_with_customers', + if ((error = getError(ERROR.RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS))) { + toastMessages.push( + formatMessage({ + id: 'should_select_customers_with_entries_have_receivable_account', }), - intent: Intent.DANGER, - }); + ); + setEntriesErrors(error.indexes, 'contact_id', 'error'); } - if (hasError(ERROR.PAYABLE_ENTRIES_HAS_NO_VENDORS)) { - AppToaster.show({ - message: formatMessage({ - id: 'entries_with_payable_account_no_assigned_with_vendors', + if ((error = getError(ERROR.CUSTOMERS_NOT_WITH_RECEVIABLE_ACC))) { + toastMessages.push( + formatMessage({ + id: 'customers_should_selected_with_receivable_account_only', }), - intent: Intent.DANGER, - }); + ); + setEntriesErrors(error.indexes, 'account_id', 'error'); } - if (hasError(ERROR.JOURNAL_NUMBER_ALREADY_EXISTS)) { - setErrors({ - journal_number: formatMessage({ + if ((error = getError(ERROR.VENDORS_NOT_WITH_PAYABLE_ACCOUNT))) { + toastMessages.push( + formatMessage({ + id: 'vendors_should_selected_with_payable_account_only', + }), + ); + setEntriesErrors(error.indexes, 'account_id', 'error'); + } + if ((error = getError(ERROR.JOURNAL_NUMBER_ALREADY_EXISTS))) { + newErrors = setWith( + newErrors, + 'journal_number', + formatMessage({ id: 'journal_number_is_already_used', }), - }); + ); } + setErrors({ ...newErrors }); + AppToaster.show({ + message: toastMessages.map((message) => { + return
- {message}
; + }), + intent: Intent.DANGER, + }); }; const formik = useFormik({ @@ -255,7 +277,7 @@ function MakeJournalEntriesForm({ } else if (totalCredit === 0 || totalDebit === 0) { AppToaster.show({ message: formatMessage({ - id: 'should_total_of_credit_and_debit_be_bigger_then_zero', + id: 'amount_cannot_be_zero_or_empty', }), intent: Intent.DANGER, }); @@ -353,7 +375,10 @@ function MakeJournalEntriesForm({ // Handle click on add a new line/row. const handleClickAddNewRow = () => { - formik.setFieldValue('entries', [...formik.values.entries, defaultEntry]); + formik.setFieldValue( + 'entries', + reorderingEntriesIndex([...formik.values.entries, defaultEntry]), + ); }; // Handle click `Clear all lines` button. @@ -370,13 +395,12 @@ function MakeJournalEntriesForm({ - { - setRows([...values.entries.map((e) => ({ ...e, rowType: 'editor' }))]); + setRows([...values.map((e) => ({ ...e, rowType: 'editor' }))]); }, [values, setRows]); // Final table rows editor rows and total and final blank row. @@ -217,6 +217,9 @@ function MakeJournalEntriesTable({ const handleRemoveRow = useCallback( (rowIndex) => { + // Can't continue if there is just one row line or less. + if (rows.length <= 2) { return; } + const removeIndex = parseInt(rowIndex, 10); const newRows = rows.filter((row, index) => index !== removeIndex); diff --git a/client/src/containers/Accounting/ManualJournalsDataTable.js b/client/src/containers/Accounting/ManualJournalsDataTable.js index aba441aa7..f4959fc4b 100644 --- a/client/src/containers/Accounting/ManualJournalsDataTable.js +++ b/client/src/containers/Accounting/ManualJournalsDataTable.js @@ -28,7 +28,7 @@ import withManualJournalsActions from 'containers/Accounting/withManualJournalsA function StatusAccessor(row) { return ( - + @@ -178,7 +178,7 @@ function ManualJournalsDataTable({ { id: 'status', Header: formatMessage({ id: 'status' }), - accessor: StatusAccessor, + accessor: row => StatusAccessor(row), width: 95, className: 'status', }, diff --git a/client/src/containers/Accounts/AccountsChart.js b/client/src/containers/Accounts/AccountsChart.js index fd813a124..2c225618f 100644 --- a/client/src/containers/Accounts/AccountsChart.js +++ b/client/src/containers/Accounts/AccountsChart.js @@ -306,7 +306,7 @@ function AccountsChart({ setBulkInactiveAccounts(false); AppToaster.show({ message: formatMessage({ - id: 'the_accounts_has_been_successfully_inactivated', + id: 'the_accounts_have_been_successfully_inactivated', }), intent: Intent.SUCCESS, }); diff --git a/client/src/containers/Dialogs/AccountFormDialog.js b/client/src/containers/Dialogs/AccountFormDialog.js index 670349962..6d43a91f3 100644 --- a/client/src/containers/Dialogs/AccountFormDialog.js +++ b/client/src/containers/Dialogs/AccountFormDialog.js @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useEffect } from 'react'; import { Button, Classes, @@ -6,24 +6,24 @@ import { InputGroup, Intent, TextArea, - MenuItem, Checkbox, Position, } from '@blueprintjs/core'; -import * as Yup from 'yup'; import { useFormik } from 'formik'; import { FormattedMessage as T, useIntl } from 'react-intl'; -import { If } from 'components'; -import { omit, pick } from 'lodash'; +import { pick } from 'lodash'; import { useQuery, queryCache } from 'react-query'; import classNames from 'classnames'; +import Yup from 'services/yup'; import { - ListSelect, + If, ErrorMessage, Dialog, AppToaster, FieldRequiredHint, Hint, + AccountsSelectList, + AccountsTypesSelect, } from 'components'; import AccountFormDialogContainer from 'containers/Dialogs/AccountFormDialog.container'; @@ -53,33 +53,33 @@ function AccountFormDialog({ closeDialog, }) { const { formatMessage } = useIntl(); - const accountFormValidationSchema = Yup.object().shape({ + const validationSchema = Yup.object().shape({ name: Yup.string() .required() + .min(3) + .max(255) .label(formatMessage({ id: 'account_name_' })), - code: Yup.number(), - account_type_id: Yup.string() - .nullable() + code: Yup.string().digits().min(3).max(6), + account_type_id: Yup.number() .required() .label(formatMessage({ id: 'account_type_id' })), - description: Yup.string().nullable().trim(), - parent_account_id: Yup.string().nullable(), + description: Yup.string().min(3).max(512).nullable().trim(), + parent_account_id: Yup.number().nullable(), }); const initialValues = useMemo( () => ({ account_type_id: null, name: '', - description: '', code: '', - type: '', + description: '', + parent_account_id: null, }), [], ); - const transformApiErrors = (errors) => { const fields = {}; if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) { - fields.code = 'Account code is not unqiue.'; + fields.code = formatMessage({ id: 'account_code_is_not_unique' }); } return fields; }; @@ -98,20 +98,33 @@ function AccountFormDialog({ enableReinitialize: true, initialValues: { ...initialValues, - ...(payload.action === 'edit' && pick(account, Object.keys(initialValues))), + ...(payload.action === 'edit' && + pick(account, Object.keys(initialValues))), }, - validationSchema: accountFormValidationSchema, + validationSchema, onSubmit: (values, { setSubmitting, setErrors }) => { - const exclude = ['subaccount']; + const form = pick(values, Object.keys(initialValues)); + const toastAccountName = values.code ? `${values.code} - ${values.name}` : values.name; + const afterSubmit = () => { + closeDialog(dialogName); + queryCache.invalidateQueries('accounts-table'); + queryCache.invalidateQueries('accounts-list'); + }; + + const afterErrors = (errors) => { + const errorsTransformed = transformApiErrors(errors); + setErrors({ ...errorsTransformed }); + setSubmitting(false); + }; + if (payload.action === 'edit') { - requestEditAccount(payload.id, values) + requestEditAccount(payload.id, form) .then((response) => { - closeDialog(dialogName); - queryCache.invalidateQueries('accounts-table'); + afterSubmit(response); AppToaster.show({ message: formatMessage( @@ -124,19 +137,11 @@ function AccountFormDialog({ intent: Intent.SUCCESS, }); }) - .catch((errors) => { - const errorsTransformed = transformApiErrors(errors); - setErrors({ ...errorsTransformed }); - setSubmitting(false); - }); + .catch(afterErrors); } else { - requestSubmitAccount({ - payload: payload.parent_account_id, - form: { ...omit(values, exclude) }, - }) + requestSubmitAccount({ form }) .then((response) => { - closeDialog(dialogName); - queryCache.invalidateQueries('accounts-table', { force: true }); + afterSubmit(response); AppToaster.show({ message: formatMessage( @@ -150,70 +155,28 @@ function AccountFormDialog({ position: Position.BOTTOM, }); }) - .catch((errors) => { - const errorsTransformed = transformApiErrors(errors); - setErrors({ ...errorsTransformed }); - setSubmitting(false); - }); + .catch(afterErrors); } }, }); + useEffect(() => { + if (values.parent_account_id) { + setFieldValue('subaccount', true); + } + }, [values.parent_account_id]); + // Filtered accounts based on the given account type. const filteredAccounts = useMemo( () => accounts.filter( - (account) => account.account_type_id === values.account_type_id, + (account) => + account.account_type_id === values.account_type_id || + !values.account_type_id, ), [accounts, values.account_type_id], ); - // Filters accounts types items. - const filterAccountTypeItems = (query, accountType, _index, exactMatch) => { - const normalizedTitle = accountType.name.toLowerCase(); - const normalizedQuery = query.toLowerCase(); - - if (exactMatch) { - return normalizedTitle === normalizedQuery; - } else { - return normalizedTitle.indexOf(normalizedQuery) >= 0; - } - }; - - // Account type item of select filed. - const accountTypeItem = (item, { handleClick, modifiers, query }) => { - return ; - }; - - // Account item of select accounts field. - const accountItem = (item, { handleClick, modifiers, query }) => { - return ( - - ); - }; - - // Filters accounts items. - const filterAccountsPredicater = useCallback( - (query, account, _index, exactMatch) => { - const normalizedTitle = account.name.toLowerCase(); - const normalizedQuery = query.toLowerCase(); - - if (exactMatch) { - return normalizedTitle === normalizedQuery; - } else { - return ( - `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0 - ); - } - }, - [], - ); - // Handles dialog close. const handleClose = useCallback(() => { closeDialog(dialogName); @@ -256,12 +219,12 @@ function AccountFormDialog({ fetchAccount.refetch(); } if (payload.action === 'new_child') { - setFieldValue('subaccount', true); setFieldValue('parent_account_id', payload.parentAccountId); setFieldValue('account_type_id', payload.accountTypeId); } - }, [fetchAccount, fetchAccountsList, fetchAccountsTypes]); + }, [payload, fetchAccount, fetchAccountsList, fetchAccountsTypes]); + // Handle account type change. const onChangeAccountType = useCallback( (accountType) => { setFieldValue('account_type_id', accountType.id); @@ -277,21 +240,11 @@ function AccountFormDialog({ [setFieldValue], ); + // Handle dialog on closed. const onDialogClosed = useCallback(() => { resetForm(); }, [resetForm]); - const subAccountLabel = useMemo(() => { - return ( - - - - - ); - }, []); - - const requiredSpan = useMemo(() => *, []); - return ( - } - itemRenderer={accountTypeItem} - itemPredicate={filterAccountTypeItems} - popoverProps={{ minimal: true }} - onItemSelect={onChangeAccountType} - selectedItem={values.account_type_id} - selectedItemProp={'id'} - defaultText={} - labelProp={'name'} + } + onTypeSelected={onChangeAccountType} buttonProps={{ disabled: payload.action === 'edit' }} + popoverProps={{ minimal: true }} /> @@ -384,7 +332,12 @@ function AccountFormDialog({ > + + + + } {...getFieldProps('subaccount')} checked={values.subaccount} /> @@ -400,17 +353,11 @@ function AccountFormDialog({ )} inline={true} > - } - itemRenderer={accountItem} - itemPredicate={filterAccountsPredicater} - popoverProps={{ minimal: true }} - onItemSelect={onChangeSubaccount} - selectedItem={values.parent_account_id} - selectedItemProp={'id'} - defaultText={} - labelProp={'name'} + } + selectedAccountId={values.parent_account_id} /> @@ -424,7 +371,7 @@ function AccountFormDialog({ >