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 ( - + { + 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..ea37b794f --- /dev/null +++ b/client/src/components/EstimateListField.js @@ -0,0 +1,41 @@ +import React, { useCallback, useMemo, useEffect, useState } from 'react'; +import { MenuItem, Button } from '@blueprintjs/core'; +import ListSelect from 'components/ListSelect'; +import { FormattedMessage as T } from 'react-intl'; + +function EstimateListField({ + products, + selectedProductId, + onProductSelected, + defautlSelectText = , +}) { + const onProductSelect = useCallback( + (product) => { + onProductSelected && onProductSelected(product); + }, + [onProductSelected], + ); + + const productRenderer = useCallback( + (item, { handleClick, modifiers, query }) => ( + + ), + [], + ); + + return ( + } + itemRenderer={productRenderer} + popoverProps={{ minimal: true }} + onItemSelect={onProductSelect} + selectedItem={`${selectedProductId}`} + selectedItemProp={'id'} + labelProp={'name'} + defaultText={defautlSelectText} + /> + ); +} + +export default EstimateListField; diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js index 1f904e8cd..7f6e7bf23 100644 --- a/client/src/config/sidebarMenu.js +++ b/client/src/config/sidebarMenu.js @@ -37,7 +37,20 @@ export default [ }, { text: , - children: [], + children: [ + { + text: , + href: '/estimates/new', + }, + { + text: , + href: '/invoices/new', + }, + { + text: , + href: '/receipts/new', + }, + ], }, { text: , @@ -99,11 +112,11 @@ export default [ text: , children: [ { - text: , + text: , href: '/expenses-list', }, { - text: , + text: , href: '/expenses/new', }, ], @@ -140,12 +153,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/Sales/Estimate/EntriesItemsTable.js b/client/src/containers/Sales/Estimate/EntriesItemsTable.js new file mode 100644 index 000000000..eaa0a370e --- /dev/null +++ b/client/src/containers/Sales/Estimate/EntriesItemsTable.js @@ -0,0 +1,259 @@ +import React, { useState, useMemo, useEffect, useCallback } from 'react'; +import { Button, Intent, Position, Tooltip } from '@blueprintjs/core'; +import { FormattedMessage as T, useIntl } from 'react-intl'; +import DataTable from 'components/DataTable'; +import Icon from 'components/Icon'; + +import { compose, formattedAmount, transformUpdatedRows } from 'utils'; +import { + InputGroupCell, + MoneyFieldCell, + EstimatesListFieldCell, + PercentFieldCell, + DivFieldCell, +} from 'components/DataTableCells'; + +import withItems from 'containers/Items/withItems'; +import { omit } from 'lodash'; + +const ActionsCellRenderer = ({ + row: { index }, + column: { id }, + cell: { value }, + data, + payload, +}) => { + if (data.length <= index + 1) { + return ''; + } + const onRemoveRole = () => { + payload.removeRow(index); + }; + return ( + } position={Position.LEFT}> + + + + + + ); +} + +export default compose( + withItems(({ itemsCurrentPage }) => ({ + itemsCurrentPage, + })), +)(EstimateTable); diff --git a/client/src/containers/Sales/Estimate/EstimateActionsBar.js b/client/src/containers/Sales/Estimate/EstimateActionsBar.js new file mode 100644 index 000000000..dca9ab169 --- /dev/null +++ b/client/src/containers/Sales/Estimate/EstimateActionsBar.js @@ -0,0 +1,140 @@ +import React, { useMemo, useCallback } 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 } from 'react-intl'; + +import { If } from 'components'; +import FilterDropdown from 'components/FilterDropdown'; +import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; + +import withResourceDetail from 'containers/Resources/withResourceDetails'; +import withDialogActions from 'containers/Dialog/withDialogActions'; +import withEstimateActions from './withEstimateActions'; + +import withEstimates from './withEstimates'; + +import { compose } from 'utils'; +import { connect } from 'react-redux'; + +function EstimateActionsBar({ + // #withResourceDetail + resourceFields, + + //#withEstimates + estimateViews, + + // #withEstimateActions + addEstimatesTableQueries, + + // #own Porps + onFilterChanged, + selectedRows, +}) { + const { path } = useRouteMatch(); + const history = useHistory(); + + const onClickNewEstimate = useCallback(() => { + // history.push('/estimates/new'); + }, [history]); + + const filterDropdown = FilterDropdown({ + initialCondition: { + fieldKey: '', + compatator: '', + value: '', + }, + fields: resourceFields, + onFilterChange: (filterConditions) => { + addEstimatesTableQueries({ + filter_roles: filterConditions || '', + }); + onFilterChanged && onFilterChange(filterConditions); + }, + }); + + const hasSelectedRows = useMemo(() => selectedRows.length > 0, [ + selectedRows, + ]); + + return ( + + + +