diff --git a/client/src/components/DataTableCells/DivFieldCell.js b/client/src/components/DataTableCells/DivFieldCell.js new file mode 100644 index 000000000..a9274ceae --- /dev/null +++ b/client/src/components/DataTableCells/DivFieldCell.js @@ -0,0 +1,11 @@ +import React, { useState, useEffect } from 'react'; + +export default function DivFieldCell({ cell: { value: initialValue } }) { + const [value, setValue] = useState(initialValue); + + useEffect(() => { + setValue(initialValue); + }, [initialValue]); + + return
${value}
; +} diff --git a/client/src/components/DataTableCells/EstimatesListFieldCell.js b/client/src/components/DataTableCells/EstimatesListFieldCell.js new file mode 100644 index 000000000..1923028c0 --- /dev/null +++ b/client/src/components/DataTableCells/EstimatesListFieldCell.js @@ -0,0 +1,38 @@ +import React, { useCallback, useMemo } from 'react'; +import EstimateListField from 'components/EstimateListField'; +import classNames from 'classnames'; +import { FormGroup, Classes, Intent } from '@blueprintjs/core'; + +function EstimatesListFieldCell({ + column: { id }, + row: { index }, + cell: { value: initialValue }, + payload: { products, updateData, errors }, +}) { + const handleProductSelected = useCallback( + (item) => { + updateData(index, id, item.id); + }, + [updateData, index, id], + ); + + const error = errors?.[index]?.[id]; + + return ( + + + + ); +} + +export default EstimatesListFieldCell; diff --git a/client/src/components/DataTableCells/InputGroupCell.js b/client/src/components/DataTableCells/InputGroupCell.js index 07872a00f..3521fcbba 100644 --- a/client/src/components/DataTableCells/InputGroupCell.js +++ b/client/src/components/DataTableCells/InputGroupCell.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import classNames from 'classnames'; -import { Classes, InputGroup, FormGroup } from '@blueprintjs/core'; +import { Classes, InputGroup, FormGroup, Intent } from '@blueprintjs/core'; const InputEditableCell = ({ row: { index }, @@ -20,8 +20,17 @@ const InputEditableCell = ({ setValue(initialValue); }, [initialValue]); + const error = payload.errors?.[index]?.[id]; + return ( - + { + updateData(index, id, _item.id); + }, + [updateData, index, id], + ); + + const error = errors?.[index]?.[id]; + + return ( + + + + ); +} + +export default PaymentReceiveListFieldCell; diff --git a/client/src/components/DataTableCells/PercentFieldCell.js b/client/src/components/DataTableCells/PercentFieldCell.js new file mode 100644 index 000000000..e6b2a0092 --- /dev/null +++ b/client/src/components/DataTableCells/PercentFieldCell.js @@ -0,0 +1,43 @@ +import React, { useCallback, useState, useEffect } from 'react'; +import { FormGroup, Intent } from '@blueprintjs/core'; +import MoneyInputGroup from 'components/MoneyInputGroup'; + +const PercentFieldCell = ({ + cell: { value: initialValue }, + row: { index }, + column: { id }, + payload: { errors, updateData }, +}) => { + const [value, setValue] = useState(initialValue); + + const onBlur = (e) => { + updateData(index, id, parseInt(e.target.value, 10)); + }; + + const onChange = useCallback((e) => { + setValue(e.target.value); + }, []); + + + useEffect(() => { + setValue(initialValue); + }, [initialValue]); + + const error = errors?.[index]?.[id]; + + return ( + + + + ); +}; + +export default PercentFieldCell; diff --git a/client/src/components/DataTableCells/index.js b/client/src/components/DataTableCells/index.js index 60232831e..54be925fe 100644 --- a/client/src/components/DataTableCells/index.js +++ b/client/src/components/DataTableCells/index.js @@ -2,10 +2,15 @@ import AccountsListFieldCell from './AccountsListFieldCell'; import MoneyFieldCell from './MoneyFieldCell'; import InputGroupCell from './InputGroupCell'; import ContactsListFieldCell from './ContactsListFieldCell'; - +import EstimatesListFieldCell from './EstimatesListFieldCell'; +import PercentFieldCell from './PercentFieldCell'; +import DivFieldCell from './DivFieldCell'; export { AccountsListFieldCell, MoneyFieldCell, InputGroupCell, ContactsListFieldCell, -} \ No newline at end of file + EstimatesListFieldCell, + PercentFieldCell, + DivFieldCell, +}; diff --git a/client/src/components/EstimateListField.js b/client/src/components/EstimateListField.js new file mode 100644 index 000000000..a7b94a662 --- /dev/null +++ b/client/src/components/EstimateListField.js @@ -0,0 +1,73 @@ +import React, { useCallback, useMemo, useEffect, useState } from 'react'; +import { MenuItem } from '@blueprintjs/core'; +import ListSelect from 'components/ListSelect'; +import { FormattedMessage as T } from 'react-intl'; + +function EstimateListField({ + products, + initialProductId, + selectedProductId, + defautlSelectText = , + onProductSelected, +}) { + const initialProduct = useMemo( + () => products.find((a) => a.id === initialProductId), + [initialProductId], + ); + + const [selectedProduct, setSelectedProduct] = useState( + initialProduct || null, + ); + + useEffect(() => { + if (typeof selectedProductId !== 'undefined') { + const product = selectedProductId + ? products.find((a) => a.id === selectedProductId) + : null; + setSelectedProduct(product); + } + }, [selectedProductId, products, setSelectedProduct]); + + const onProductSelect = useCallback( + (product) => { + setSelectedProduct({ ...product }); + onProductSelected && onProductSelected(product); + }, + [onProductSelected], + ); + + const productRenderer = useCallback( + (item, { handleClick }) => ( + + ), + [], + ); + + const filterProduct = useCallback((query, product, _index, exactMatch) => { + const normalizedTitle = product.name.toLowerCase(); + const normalizedQuery = query.toLowerCase(); + + if (exactMatch) { + return normalizedTitle === normalizedQuery; + } else { + return normalizedTitle.indexOf(normalizedQuery) >= 0; + } + }, []); + + return ( + } + itemRenderer={productRenderer} + itemPredicate={filterProduct} + popoverProps={{ minimal: true }} + onItemSelect={onProductSelect} + selectedItem={`${selectedProductId}`} + selectedItemProp={'id'} + labelProp={'name'} + defaultText={selectedProduct ? selectedProduct.name : defautlSelectText} + /> + ); +} + +export default EstimateListField; diff --git a/client/src/components/PaymentReceiveListField.js b/client/src/components/PaymentReceiveListField.js new file mode 100644 index 000000000..4c9f828ba --- /dev/null +++ b/client/src/components/PaymentReceiveListField.js @@ -0,0 +1,38 @@ +import React, { useCallback } from 'react'; +import { MenuItem } from '@blueprintjs/core'; +import ListSelect from 'components/ListSelect'; +import { FormattedMessage as T } from 'react-intl'; + +function PaymentReceiveListField({ + invoices, + selectedInvoiceId, + onInvoiceSelected, + defaultSelectText = , +}) { + const onInvoiceSelect = useCallback((_invoice) => { + onInvoiceSelected && onInvoiceSelected(_invoice); + }); + + const handleInvoiceRenderer = useCallback( + (item, { handleClick }) => ( + + ), + [], + ); + + return ( + } + itemRenderer={handleInvoiceRenderer} + popoverProps={{ minimal: true }} + onItemSelect={onInvoiceSelect} + selectedItem={`${selectedInvoiceId}`} + selectedItemProp={'id'} + labelProp={'name'} + defaultText={defaultSelectText} + /> + ); +} + +export default PaymentReceiveListField; diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js index 1f904e8cd..8945fa9dd 100644 --- a/client/src/config/sidebarMenu.js +++ b/client/src/config/sidebarMenu.js @@ -37,16 +37,55 @@ export default [ }, { text: , - children: [], + children: [ + { + text: , + href: '/estimates/new', + }, + // { + // text: , + // href: '/estimates', + // }, + { + text: , + href: '/invoices/new', + }, + // { + // text: , + // href: '/invoices', + // }, + { + text: , + href: '/payment-receive/new', + }, + { + divider: true, + text: , + href: '/invoices', + }, + { + text: , + href: '/receipts/new', + }, + // { + // text: , + // href: '/receipts', + // }, + ], }, { text: , children: [ { - icon: 'cut', - text: 'cut', - label: '⌘C', - disabled: false, + text: , + href: '/bills/new', + }, + // { + // text: , + // href: '/bills', + // }, + { + text: , }, ], }, @@ -99,11 +138,11 @@ export default [ text: , children: [ { - text: , + text: , href: '/expenses-list', }, { - text: , + text: , href: '/expenses/new', }, ], @@ -140,12 +179,12 @@ export default [ }, { text: 'Receivable Aging Summary', - href: '/financial-reports/receivable-aging-summary' + href: '/financial-reports/receivable-aging-summary', }, { text: 'Payable Aging Summary', - href: '/financial-reports/payable-aging-summary' - } + href: '/financial-reports/payable-aging-summary', + }, ], }, { diff --git a/client/src/containers/Purchases/Bill/BillActionsBar.js b/client/src/containers/Purchases/Bill/BillActionsBar.js new file mode 100644 index 000000000..dad1c4f2b --- /dev/null +++ b/client/src/containers/Purchases/Bill/BillActionsBar.js @@ -0,0 +1,148 @@ +import React, { useCallback, useState, useMemo } from 'react'; +import Icon from 'components/Icon'; +import { + Button, + Classes, + Menu, + MenuItem, + Popover, + NavbarDivider, + NavbarGroup, + PopoverInteractionKind, + Position, + Intent, +} from '@blueprintjs/core'; + +import classNames from 'classnames'; +import { useRouteMatch, useHistory } from 'react-router-dom'; +import { FormattedMessage as T, useIntl } from 'react-intl'; + +import { connect } from 'react-redux'; +import FilterDropdown from 'components/FilterDropdown'; +import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; + +import { If, DashboardActionViewsList } from 'components'; + +import withResourceDetail from 'containers/Resources/withResourceDetails'; +import withBillActions from './withBillActions'; +import withBills from './withBills'; + +import { compose } from 'utils'; + +function BillActionsBar({ + // #withResourceDetail + resourceFields, + + //#withBills + billsViews, + + //#withBillActions + addBillsTableQueries, + + // #own Porps + onFilterChanged, + selectedRows = [], +}) { + const history = useHistory(); + const { path } = useRouteMatch(); + const [filterCount, setFilterCount] = useState(0); + const { formatMessage } = useIntl(); + + const handleClickNewBill = useCallback(() => { + history.push('/bills/new'); + }, [history]); + + // const FilterDropdown = FilterDropdown({ + // initialCondition: { + // fieldKey: '', + // compatator: '', + // value: '', + // }, + // fields: resourceFields, + // onFilterChange: (filterConditions) => { + // addBillsTableQueries({ + // filter_roles: filterConditions || '', + // }); + // onFilterChanged && onFilterChanged(filterConditions); + // }, + // }); + + const hasSelectedRows = useMemo(() => selectedRows.length > 0, [ + selectedRows, + ]); + + return ( + + + + +