diff --git a/client/src/components/AccountsSuggestField.js b/client/src/components/AccountsSuggestField.js new file mode 100644 index 000000000..c0757d8b0 --- /dev/null +++ b/client/src/components/AccountsSuggestField.js @@ -0,0 +1,130 @@ +import React, { useState, useCallback, useEffect, useMemo } from 'react'; +import { MenuItem } from '@blueprintjs/core'; +import { Suggest } from '@blueprintjs/select'; +import { isEmpty } from 'lodash'; + +import classNames from 'classnames'; +import { CLASSES } from 'common/classes'; + +import { FormattedMessage as T } from 'react-intl'; + +export default function AccountsSuggestField({ + accounts, + initialAccountId, + selectedAccountId, + defaultSelectText = 'Select account', + onAccountSelected, + + filterByRootTypes = [], + filterByTypes = [], + filterByNormal, + popoverFill = false, + + ...suggestProps +}) { + // Filters accounts based on filter props. + const filteredAccounts = useMemo(() => { + let filteredAccounts = [...accounts]; + + if (!isEmpty(filterByRootTypes)) { + filteredAccounts = filteredAccounts.filter( + (account) => filterByRootTypes.indexOf(account.type.root_type) !== -1, + ); + } + if (!isEmpty(filterByTypes)) { + filteredAccounts = filteredAccounts.filter( + (account) => filterByTypes.indexOf(account.type.key) !== -1, + ); + } + if (!isEmpty(filterByNormal)) { + filteredAccounts = filteredAccounts.filter( + (account) => + filterByTypes.indexOf(account.type.normal) === filterByNormal, + ); + } + return filteredAccounts; + }, [accounts, filterByRootTypes, filterByTypes, filterByNormal]); + + // Find initial account object to set it as default account in initial render. + const initialAccount = useMemo( + () => filteredAccounts.find((a) => a.id === initialAccountId), + [initialAccountId, filteredAccounts], + ); + + const [selectedAccount, setSelectedAccount] = useState( + initialAccount || null, + ); + + useEffect(() => { + if (typeof selectedAccountId !== 'undefined') { + const account = selectedAccountId + ? filteredAccounts.find((a) => a.id === selectedAccountId) + : null; + setSelectedAccount(account); + } + }, [selectedAccountId, filteredAccounts, setSelectedAccount]); + + // 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 + ); + } + }, + [], + ); + + // Account item of select accounts field. + const accountItem = useCallback((item, { handleClick, modifiers, query }) => { + return ( + + ); + }, []); + + const handleInputValueRenderer = (inputValue) => { + if (inputValue) { + return inputValue.name.toString(); + } + return ''; + }; + + const onAccountSelect = useCallback( + (account) => { + setSelectedAccount({ ...account }); + onAccountSelected && onAccountSelected(account); + }, + [setSelectedAccount, onAccountSelected], + ); + + return ( + } />} + itemRenderer={accountItem} + itemPredicate={filterAccountsPredicater} + onItemSelect={onAccountSelect} + selectedItem={selectedAccount} + inputProps={{ placeholder: defaultSelectText }} + resetOnClose={true} + fill={true} + popoverProps={{ minimal: true }} + inputValueRenderer={handleInputValueRenderer} + className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, { + [CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill, + })} + {...suggestProps} + /> + ); +} diff --git a/client/src/components/ContactsSuggestField.js b/client/src/components/ContactsSuggestField.js new file mode 100644 index 000000000..4269d21ff --- /dev/null +++ b/client/src/components/ContactsSuggestField.js @@ -0,0 +1,106 @@ +import React, { useCallback, useState, useEffect, useMemo } from 'react'; +import { MenuItem } from '@blueprintjs/core'; +import { Suggest } from '@blueprintjs/select'; + +import { FormattedMessage as T } from 'react-intl'; +import classNames from 'classnames'; +import { CLASSES } from 'common/classes'; + +export default function ContactsSuggestField({ + contactsList, + initialContactId, + selectedContactId, + defaultTextSelect = 'Select contact', + onContactSelected, + + selectedContactType = [], + popoverFill = false, + + ...suggestProps +}) { + // filteredContacts + const contacts = useMemo( + () => + contactsList.map((contact) => ({ + ...contact, + _id: `${contact.id}_${contact.contact_type}`, + })), + [contactsList], + ); + + const initialContact = useMemo( + () => contacts.find((a) => a.id === initialContactId), + [initialContactId, contacts], + ); + + const [selecetedContact, setSelectedContact] = useState( + initialContact || null, + ); + + useEffect(() => { + if (typeof selectedContactId !== 'undefined') { + const contact = selectedContactId + ? contacts.find((a) => a.id === selectedContactId) + : null; + setSelectedContact(contact); + } + }, [selectedContactId, contacts, setSelectedContact]); + + const contactRenderer = useCallback( + (contact, { handleClick }) => ( + + ), + [], + ); + + const onContactSelect = useCallback( + (contact) => { + setSelectedContact({ ...contact }); + onContactSelected && onContactSelected(contact); + }, + [setSelectedContact, onContactSelected], + ); + + const handleInputValueRenderer = (inputValue) => { + if (inputValue) { + return inputValue.display_name.toString(); + } + }; + + const filterContacts = (query, contact, index, exactMatch) => { + const normalizedTitle = contact.display_name.toLowerCase(); + const normalizedQuery = query.toLowerCase(); + if (exactMatch) { + return normalizedTitle === normalizedQuery; + } else { + return ( + `${contact.display_name} ${normalizedTitle}`.indexOf(normalizedQuery) >= + 0 + ); + } + }; + + return ( + } />} + itemRenderer={contactRenderer} + itemPredicate={filterContacts} + onItemSelect={onContactSelect} + selectedItem={selecetedContact} + inputProps={{ placeholder: defaultTextSelect }} + resetOnClose={true} + // fill={true} + popoverProps={{ minimal: true }} + inputValueRenderer={handleInputValueRenderer} + className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, { + [CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill, + })} + {...suggestProps} + /> + ); +} diff --git a/client/src/components/DataTableCells/AccountsListFieldCell.js b/client/src/components/DataTableCells/AccountsListFieldCell.js index 4757d6e01..c5c2a6dcc 100644 --- a/client/src/components/DataTableCells/AccountsListFieldCell.js +++ b/client/src/components/DataTableCells/AccountsListFieldCell.js @@ -1,5 +1,6 @@ import React, { useCallback, useMemo } from 'react'; -import AccountsSelectList from 'components/AccountsSelectList'; +import AccountsSuggestField from 'components/AccountsSuggestField'; +// import AccountsSelectList from 'components/AccountsSelectList'; import classNames from 'classnames'; import { FormGroup, Classes, Intent } from '@blueprintjs/core'; @@ -42,7 +43,7 @@ const AccountCellRenderer = ({ Classes.FILL, )} > - - - { + let filteredItems = [...items]; + + if (sellable) { + filteredItems = filteredItems.filter((item) => item.sellable); + } + if (purchasable) { + filteredItems = filteredItems.filter((item) => item.purchasable); + } + return filteredItems; + }, [items, sellable, purchasable]); + + // Find initial item object. + const initialItem = useMemo( + () => filteredItems.some((a) => a.id === initialItemId), + [initialItemId], + ); + + const [selectedItem, setSelectedItem] = useState(initialItem || null); + + const onItemSelect = useCallback( + (item) => { + setSelectedItem({ ...item }); + onItemSelected && onItemSelected(item); + }, + [setSelectedItem, onItemSelected], + ); + + const itemRenderer = useCallback((item, { modifiers, handleClick }) => ( + + )); + + useEffect(() => { + if (typeof selectedItemId !== 'undefined') { + const item = selectedItemId + ? filteredItems.find((a) => a.id === selectedItemId) + : null; + setSelectedItem(item); + } + }, [selectedItemId, filteredItems, setSelectedItem]); + + const handleInputValueRenderer = (inputValue) => { + if (inputValue) { + return inputValue.name.toString(); + } + return ''; + }; + + // Filters items. + const filterItemsPredicater = useCallback( + (query, item, _index, exactMatch) => { + const normalizedTitle = item.name.toLowerCase(); + const normalizedQuery = query.toLowerCase(); + + if (exactMatch) { + return normalizedTitle === normalizedQuery; + } else { + return normalizedTitle.indexOf(normalizedQuery) >= 0; + } + }, + [], + ); + return ( + } />} + itemRenderer={itemRenderer} + itemPredicate={filterItemsPredicater} + inputValueRenderer={handleInputValueRenderer} + selectedItem={selectedItem} + onItemSelect={onItemSelect} + inputProps={{ placeholder: defautlSelectText }} + resetOnClose={true} + fill={true} + popoverProps={{ minimal: true }} + className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, { + [CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill, + })} + {...suggestProps} + /> + ); +} diff --git a/client/src/containers/Sales/PaymentReceive/PaymentReceiveList.js b/client/src/containers/Sales/PaymentReceive/PaymentReceiveList.js index 8b3d36a88..8161a7db0 100644 --- a/client/src/containers/Sales/PaymentReceive/PaymentReceiveList.js +++ b/client/src/containers/Sales/PaymentReceive/PaymentReceiveList.js @@ -87,7 +87,7 @@ function PaymentReceiveList({ }, [deletePaymentReceive, requestDeletePaymentReceive, formatMessage]); const handleEditPaymentReceive = useCallback((payment) => { - history.push(`/payment-receive/${payment.id}/edit`); + history.push(`/payment-receives/${payment.id}/edit`); }); // Calculates the selected rows count. diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index 170c86a2e..a7ecf2ddc 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -935,4 +935,5 @@ export default { opening_average_cost: 'Opening average cost', opening_cost_: 'Opening cost ', opening_date_: 'Opening date ', + no_results:'No results.' };