mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
refactoring: invoice form.
refactoring: receipt form. refactoring: bill form. refactoring: estimate form.
This commit is contained in:
@@ -1,276 +0,0 @@
|
||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { omit } from 'lodash';
|
||||
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { Hint, Icon } from 'components';
|
||||
import DataTable from 'components/DataTable';
|
||||
import {
|
||||
InputGroupCell,
|
||||
MoneyFieldCell,
|
||||
ItemsListCell,
|
||||
PercentFieldCell,
|
||||
DivFieldCell,
|
||||
} from 'components/DataTableCells';
|
||||
|
||||
import withItems from 'containers/Items/withItems';
|
||||
import { compose, formattedAmount } from 'utils';
|
||||
|
||||
const ActionsCellRenderer = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value },
|
||||
data,
|
||||
payload,
|
||||
}) => {
|
||||
if (data.length <= index + 1) {
|
||||
return '';
|
||||
}
|
||||
const onRemoveRole = () => {
|
||||
payload.removeRow(index);
|
||||
};
|
||||
return (
|
||||
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
|
||||
<Button
|
||||
icon={<Icon icon={'times-circle'} iconSize={14} />}
|
||||
iconSize={14}
|
||||
className="m12"
|
||||
intent={Intent.DANGER}
|
||||
onClick={onRemoveRole}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const TotalEstimateCellRederer = (content, type) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
const total = props.data.reduce((total, entry) => {
|
||||
const amount = parseInt(entry[type], 10);
|
||||
const computed = amount ? total + amount : total;
|
||||
|
||||
return computed;
|
||||
}, 0);
|
||||
return <span>{formattedAmount(total, 'USD')}</span>;
|
||||
}
|
||||
return content(props);
|
||||
};
|
||||
|
||||
const calculateDiscount = (discount, quantity, rate) =>
|
||||
quantity * rate - (quantity * rate * discount) / 100;
|
||||
|
||||
const CellRenderer = (content, type) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
return '';
|
||||
}
|
||||
return content(props);
|
||||
};
|
||||
|
||||
const ItemHeaderCell = () => {
|
||||
return (
|
||||
<>
|
||||
<T id={'product_and_service'} />
|
||||
<Hint />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function EntriesItemsTable({
|
||||
//#withitems
|
||||
itemsCurrentPage,
|
||||
|
||||
//#ownProps
|
||||
onClickRemoveRow,
|
||||
onClickAddNewRow,
|
||||
onClickClearAllLines,
|
||||
entries,
|
||||
|
||||
errors,
|
||||
setFieldValue,
|
||||
values,
|
||||
}) {
|
||||
const [rows, setRows] = useState([]);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
setRows([...entries.map((e) => ({ ...e }))]);
|
||||
}, [entries]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: '#',
|
||||
accessor: 'index',
|
||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
||||
width: 40,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
className: 'index',
|
||||
},
|
||||
{
|
||||
Header: ItemHeaderCell,
|
||||
id: 'item_id',
|
||||
accessor: 'item_id',
|
||||
Cell: ItemsListCell,
|
||||
// ItemsListCell
|
||||
disableSortBy: true,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'description' }),
|
||||
accessor: 'description',
|
||||
Cell: InputGroupCell,
|
||||
disableSortBy: true,
|
||||
className: 'description',
|
||||
width: 100,
|
||||
},
|
||||
|
||||
{
|
||||
Header: formatMessage({ id: 'quantity' }),
|
||||
accessor: 'quantity',
|
||||
Cell: CellRenderer(InputGroupCell, 'quantity'),
|
||||
disableSortBy: true,
|
||||
width: 80,
|
||||
className: 'quantity',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'rate' }),
|
||||
accessor: 'rate',
|
||||
Cell: TotalEstimateCellRederer(MoneyFieldCell, 'rate'),
|
||||
disableSortBy: true,
|
||||
width: 80,
|
||||
className: 'rate',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'discount' }),
|
||||
accessor: 'discount',
|
||||
Cell: CellRenderer(PercentFieldCell, InputGroupCell),
|
||||
disableSortBy: true,
|
||||
width: 80,
|
||||
className: 'discount',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'total' }),
|
||||
accessor: (row) =>
|
||||
calculateDiscount(row.discount, row.quantity, row.rate),
|
||||
Cell: TotalEstimateCellRederer(DivFieldCell, 'total'),
|
||||
disableSortBy: true,
|
||||
width: 120,
|
||||
className: 'total',
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
accessor: 'action',
|
||||
Cell: ActionsCellRenderer,
|
||||
className: 'actions',
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 45,
|
||||
},
|
||||
],
|
||||
[formatMessage],
|
||||
);
|
||||
|
||||
const handleUpdateData = useCallback(
|
||||
(rowIndex, columnId, value) => {
|
||||
const newRow = rows.map((row, index) => {
|
||||
if (index === rowIndex) {
|
||||
const newRow = { ...rows[rowIndex], [columnId]: value };
|
||||
return {
|
||||
...newRow,
|
||||
total: calculateDiscount(
|
||||
newRow.discount,
|
||||
newRow.quantity,
|
||||
newRow.rate,
|
||||
),
|
||||
};
|
||||
}
|
||||
return row;
|
||||
});
|
||||
setFieldValue(
|
||||
'entries',
|
||||
newRow.map((row) => ({
|
||||
...omit(row, ['total']),
|
||||
})),
|
||||
);
|
||||
},
|
||||
[rows, setFieldValue],
|
||||
);
|
||||
|
||||
const handleRemoveRow = useCallback(
|
||||
(rowIndex) => {
|
||||
if (rows.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const removeIndex = parseInt(rowIndex, 10);
|
||||
const newRows = rows.filter((row, index) => index !== removeIndex);
|
||||
setFieldValue(
|
||||
'entries',
|
||||
newRows.map((row, index) => ({
|
||||
...omit(row),
|
||||
index: index + 1,
|
||||
})),
|
||||
);
|
||||
onClickRemoveRow && onClickRemoveRow(removeIndex);
|
||||
},
|
||||
[rows, setFieldValue, onClickRemoveRow],
|
||||
);
|
||||
|
||||
const onClickNewRow = () => {
|
||||
onClickAddNewRow && onClickAddNewRow();
|
||||
};
|
||||
|
||||
const handleClickClearAllLines = () => {
|
||||
onClickClearAllLines && onClickClearAllLines();
|
||||
};
|
||||
|
||||
const rowClassNames = useCallback(
|
||||
(row) => ({
|
||||
'row--total': rows.length === row.index + 1,
|
||||
}),
|
||||
[rows],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(
|
||||
CLASSES.DATATABLE_EDITOR,
|
||||
CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES,
|
||||
)}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={rows}
|
||||
rowClassNames={rowClassNames}
|
||||
payload={{
|
||||
items: itemsCurrentPage,
|
||||
errors: errors.entries || [],
|
||||
updateData: handleUpdateData,
|
||||
removeRow: handleRemoveRow,
|
||||
}}
|
||||
/>
|
||||
<div className={classNames(CLASSES.DATATABLE_EDITOR_ACTIONS, 'mt1')}>
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--new-line'}
|
||||
onClick={onClickNewRow}
|
||||
>
|
||||
<T id={'new_lines'} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--clear-lines ml1'}
|
||||
onClick={handleClickClearAllLines}
|
||||
>
|
||||
<T id={'clear_all_lines'} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withItems(({ itemsCurrentPage }) => ({
|
||||
itemsCurrentPage,
|
||||
})),
|
||||
)(EntriesItemsTable);
|
||||
@@ -27,7 +27,8 @@ export default function EstimateFloatingActions() {
|
||||
|
||||
// Handle submit & deliver button click.
|
||||
const handleSubmitDeliverBtnClick = (event) => {
|
||||
setSubmitPayload({ redirect: true, deliver: true, });
|
||||
setSubmitPayload({ redirect: true, deliver: true });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle submit, deliver & new button click.
|
||||
@@ -77,7 +78,6 @@ export default function EstimateFloatingActions() {
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
type="submit"
|
||||
onClick={handleSubmitDeliverBtnClick}
|
||||
text={<T id={'save_and_deliver'} />}
|
||||
/>
|
||||
@@ -105,12 +105,12 @@ export default function EstimateFloatingActions() {
|
||||
/>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
|
||||
{/* ----------- Save As Draft ----------- */}
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
className={'ml1'}
|
||||
type="submit"
|
||||
onClick={handleSubmitDraftBtnClick}
|
||||
text={<T id={'save_as_draft'} />}
|
||||
/>
|
||||
@@ -138,13 +138,13 @@ export default function EstimateFloatingActions() {
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
</If>
|
||||
|
||||
{/* ----------- Save and New ----------- */}
|
||||
<If condition={estimate && estimate?.is_delivered}>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
type="submit"
|
||||
onClick={handleSubmitDeliverBtnClick}
|
||||
text={<T id={'save'} />}
|
||||
/>
|
||||
@@ -177,6 +177,7 @@ export default function EstimateFloatingActions() {
|
||||
onClick={handleClearBtnClick}
|
||||
text={estimate ? <T id={'reset'} /> : <T id={'clear'} />}
|
||||
/>
|
||||
|
||||
{/* ----------- Cancel ----------- */}
|
||||
<Button
|
||||
className={'ml1'}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Formik, Form } from 'formik';
|
||||
import moment from 'moment';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { pick, sumBy } from 'lodash';
|
||||
import { omit, sumBy, isEmpty } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
@@ -14,57 +13,31 @@ import {
|
||||
} from './EstimateForm.schema';
|
||||
|
||||
import EstimateFormHeader from './EstimateFormHeader';
|
||||
import EstimateFormBody from './EstimateFormBody';
|
||||
import EstimateItemsEntriesField from './EstimateItemsEntriesField';
|
||||
import EstimateFloatingActions from './EstimateFloatingActions';
|
||||
import EstimateFormFooter from './EstimateFormFooter';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withMediaActions from 'containers/Media/withMediaActions';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
|
||||
import { AppToaster } from 'components';
|
||||
import { ERROR } from 'common/errors';
|
||||
import {
|
||||
compose,
|
||||
repeatValue,
|
||||
orderingLinesIndexes,
|
||||
} from 'utils';
|
||||
import { useEstimateFormContext } from './EstimateFormProvider';
|
||||
|
||||
const MIN_LINES_NUMBER = 4;
|
||||
|
||||
const defaultEstimate = {
|
||||
index: 0,
|
||||
item_id: '',
|
||||
rate: '',
|
||||
discount: 0,
|
||||
quantity: 1,
|
||||
description: '',
|
||||
};
|
||||
|
||||
const defaultInitialValues = {
|
||||
customer_id: '',
|
||||
estimate_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
expiration_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
estimate_number: '',
|
||||
delivered: '',
|
||||
reference: '',
|
||||
note: '',
|
||||
terms_conditions: '',
|
||||
entries: [...repeatValue(defaultEstimate, MIN_LINES_NUMBER)],
|
||||
};
|
||||
import { transformToEditForm, defaultEstimate } from './utils';
|
||||
|
||||
/**
|
||||
* Estimate form.
|
||||
*/
|
||||
const EstimateForm = ({
|
||||
function EstimateForm({
|
||||
// #withSettings
|
||||
estimateNextNumber,
|
||||
estimateNumberPrefix,
|
||||
}) => {
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
const history = useHistory();
|
||||
|
||||
const {
|
||||
estimate,
|
||||
isNewMode,
|
||||
@@ -80,23 +53,14 @@ const EstimateForm = ({
|
||||
// Initial values in create and edit mode.
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
...(estimate
|
||||
...(!isEmpty(estimate)
|
||||
? {
|
||||
...pick(estimate, Object.keys(defaultInitialValues)),
|
||||
entries: [
|
||||
...estimate.entries.map((estimate) => ({
|
||||
...pick(estimate, Object.keys(defaultEstimate)),
|
||||
})),
|
||||
...repeatValue(
|
||||
defaultEstimate,
|
||||
Math.max(MIN_LINES_NUMBER - estimate.entries.length, 0),
|
||||
),
|
||||
],
|
||||
...transformToEditForm(estimate)
|
||||
}
|
||||
: {
|
||||
...defaultInitialValues,
|
||||
...defaultEstimate,
|
||||
estimate_number: estimateNumber,
|
||||
entries: orderingLinesIndexes(defaultInitialValues.entries),
|
||||
entries: orderingLinesIndexes(defaultEstimate.entries),
|
||||
}),
|
||||
}),
|
||||
[estimate, estimateNumber],
|
||||
@@ -138,10 +102,7 @@ const EstimateForm = ({
|
||||
const form = {
|
||||
...values,
|
||||
delivered: submitPayload.deliver,
|
||||
// Exclude all entries properties that out of request schema.
|
||||
entries: entries.map((entry) => ({
|
||||
...pick(entry, Object.keys(defaultEstimate)),
|
||||
})),
|
||||
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
|
||||
};
|
||||
const onSuccess = (response) => {
|
||||
AppToaster.show({
|
||||
@@ -172,7 +133,7 @@ const EstimateForm = ({
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
if (estimate && estimate.id) {
|
||||
if (!isNewMode) {
|
||||
editEstimateMutate([estimate.id, form]).then(onSuccess).catch(onError);
|
||||
} else {
|
||||
createEstimateMutate(form).then(onSuccess).catch(onError);
|
||||
@@ -196,18 +157,19 @@ const EstimateForm = ({
|
||||
>
|
||||
<Form>
|
||||
<EstimateFormHeader />
|
||||
<EstimateFormBody defaultEstimate={defaultEstimate} />
|
||||
|
||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||
<EstimateItemsEntriesField />
|
||||
</div>
|
||||
<EstimateFormFooter />
|
||||
<EstimateFloatingActions />
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withMediaActions,
|
||||
withSettings(({ estimatesSettings }) => ({
|
||||
estimateNextNumber: estimatesSettings?.nextNumber,
|
||||
estimateNumberPrefix: estimatesSettings?.numberPrefix,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
// import EditableItemsEntriesTable from 'containers/Entries/EditableItemsEntriesTable';
|
||||
|
||||
export default function EstimateFormBody({ defaultEstimate }) {
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||
{/* <EditableItemsEntriesTable
|
||||
defaultEntry={defaultEstimate}
|
||||
filterSellableItems={true}
|
||||
/> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
InputPrependButton,
|
||||
} from 'components';
|
||||
|
||||
import withCustomers from 'containers/Customers/withCustomers';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
import { inputIntent, handleDateChange } from 'utils';
|
||||
@@ -169,8 +168,5 @@ function EstimateFormHeader({
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withCustomers(({ customers }) => ({
|
||||
customers,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(EstimateFormHeader);
|
||||
|
||||
@@ -1,49 +1,20 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import 'style/pages/SaleEstimate/PageForm.scss';
|
||||
|
||||
import EstimateForm from './EstimateForm';
|
||||
|
||||
import { EstimateFormProvider } from './EstimateFormProvider';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Estimate form page.
|
||||
*/
|
||||
function EstimateFormPage({
|
||||
// #withDashboardActions
|
||||
setSidebarShrink,
|
||||
resetSidebarPreviousExpand,
|
||||
setDashboardBackLink,
|
||||
}) {
|
||||
|
||||
export default function EstimateFormPage() {
|
||||
const { id } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
// Shrink the sidebar by foce.
|
||||
setSidebarShrink();
|
||||
// Show the back link on dashboard topbar.
|
||||
setDashboardBackLink(true);
|
||||
|
||||
return () => {
|
||||
// Reset the sidebar to the previous status.
|
||||
resetSidebarPreviousExpand();
|
||||
// Hide the back link on dashboard topbar.
|
||||
setDashboardBackLink(false);
|
||||
};
|
||||
}, [resetSidebarPreviousExpand, setSidebarShrink, setDashboardBackLink]);
|
||||
|
||||
|
||||
return (
|
||||
<EstimateFormProvider estimateId={id}>
|
||||
<EstimateForm />
|
||||
</EstimateFormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
|
||||
withDashboardActions,
|
||||
)(EstimateFormPage);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useSettings,
|
||||
useCreateEstimate,
|
||||
useEditEstimate
|
||||
} from 'query/hooks';
|
||||
} from 'hooks/query';
|
||||
|
||||
const EstimateFormContext = createContext();
|
||||
|
||||
@@ -16,7 +16,7 @@ const EstimateFormContext = createContext();
|
||||
*/
|
||||
function EstimateFormProvider({ estimateId, ...props }) {
|
||||
const { data: estimate, isFetching: isEstimateFetching } = useEstimate(
|
||||
estimateId,
|
||||
estimateId, { enabled: !!estimateId }
|
||||
);
|
||||
|
||||
// Handle fetch Items data table or list
|
||||
@@ -32,16 +32,16 @@ function EstimateFormProvider({ estimateId, ...props }) {
|
||||
} = useCustomers();
|
||||
|
||||
// Handle fetch settings.
|
||||
const {
|
||||
data: { settings },
|
||||
} = useSettings();
|
||||
useSettings();
|
||||
|
||||
// Form submit payload.
|
||||
const [submitPayload, setSubmitPayload] = React.useState({});
|
||||
|
||||
const isNewMode = !estimateId;
|
||||
|
||||
|
||||
// Create and edit estimate form.
|
||||
const { mutateAsync: createEstimateMutate } = useCreateEstimate();
|
||||
const { mutateAsync: editEstimateMutate } = useEditEstimate();
|
||||
|
||||
const isNewMode = !estimateId;
|
||||
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { FastField } from 'formik';
|
||||
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
|
||||
import { useEstimateFormContext } from './EstimateFormProvider';
|
||||
|
||||
/**
|
||||
* Estimate form items entries editor.
|
||||
*/
|
||||
export default function EstimateFormItemsEntriesField() {
|
||||
const { items } = useEstimateFormContext();
|
||||
|
||||
return (
|
||||
<FastField name={'entries'}>
|
||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||
<ItemsEntriesTable
|
||||
entries={value}
|
||||
onUpdateData={(entries) => {
|
||||
form.setFieldValue('entries', entries);
|
||||
}}
|
||||
items={items}
|
||||
errors={error}
|
||||
linesNumber={4}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useFormikContext } from 'formik';
|
||||
|
||||
import withEstimates from './withEstimates';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withEstimateActions from './withEstimateActions';
|
||||
import { compose } from 'utils';
|
||||
|
||||
function EstimateNumberWatcher({
|
||||
estimateNumberChanged,
|
||||
|
||||
// #WithEstimateActions
|
||||
setEstimateNumberChanged,
|
||||
|
||||
// #withDashboardActions
|
||||
changePageSubtitle,
|
||||
|
||||
// #ownProps
|
||||
estimateNumber,
|
||||
}) {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (estimateNumberChanged) {
|
||||
setFieldValue('estimate_number', estimateNumber);
|
||||
changePageSubtitle(`No. ${estimateNumber}`);
|
||||
setEstimateNumberChanged(false);
|
||||
}
|
||||
}, [
|
||||
estimateNumber,
|
||||
estimateNumberChanged,
|
||||
setEstimateNumberChanged,
|
||||
setFieldValue,
|
||||
changePageSubtitle,
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
export default compose(
|
||||
withEstimates(({ estimateNumberChanged }) => ({
|
||||
estimateNumberChanged,
|
||||
})),
|
||||
withEstimateActions,
|
||||
withDashboardActions
|
||||
)(EstimateNumberWatcher)
|
||||
38
client/src/containers/Sales/Estimates/EstimateForm/utils.js
Normal file
38
client/src/containers/Sales/Estimates/EstimateForm/utils.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import moment from 'moment';
|
||||
import { repeatValue, transformToForm } from 'utils';
|
||||
|
||||
export const MIN_LINES_NUMBER = 4;
|
||||
|
||||
export const defaultEstimateEntry = {
|
||||
index: 0,
|
||||
item_id: '',
|
||||
rate: '',
|
||||
discount: 0,
|
||||
quantity: 1,
|
||||
description: '',
|
||||
};
|
||||
|
||||
export const defaultEstimate = {
|
||||
customer_id: '',
|
||||
estimate_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
expiration_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
estimate_number: '',
|
||||
delivered: '',
|
||||
reference: '',
|
||||
note: '',
|
||||
terms_conditions: '',
|
||||
entries: [...repeatValue(defaultEstimateEntry, MIN_LINES_NUMBER)],
|
||||
};
|
||||
|
||||
export const transformToEditForm = (estimate) => ({
|
||||
...transformToForm(estimate, defaultEstimate),
|
||||
entries: [
|
||||
...estimate.entries.map((estimate) => ({
|
||||
...transformToForm(estimate, defaultEstimateEntry),
|
||||
})),
|
||||
...repeatValue(
|
||||
defaultEstimate,
|
||||
Math.max(MIN_LINES_NUMBER - estimate.entries.length, 0),
|
||||
),
|
||||
],
|
||||
});
|
||||
@@ -67,6 +67,7 @@ export function ActionsMenu({
|
||||
/>
|
||||
<If condition={!original.is_delivered}>
|
||||
<MenuItem
|
||||
icon={<Icon icon={'check'} iconSize={18} />}
|
||||
text={formatMessage({ id: 'mark_as_delivered' })}
|
||||
onClick={safeCallback(onDeliver, original)}
|
||||
/>
|
||||
@@ -80,16 +81,19 @@ export function ActionsMenu({
|
||||
</Choose.When>
|
||||
<Choose.When condition={original.is_delivered && original.is_rejected}>
|
||||
<MenuItem
|
||||
icon={<Icon icon={'check'} iconSize={18} />}
|
||||
text={formatMessage({ id: 'mark_as_approved' })}
|
||||
onClick={safeCallback(onApprove, original)}
|
||||
/>
|
||||
</Choose.When>
|
||||
<Choose.When condition={original.is_delivered}>
|
||||
<MenuItem
|
||||
icon={<Icon icon={'check'} iconSize={18} />}
|
||||
text={formatMessage({ id: 'mark_as_approved' })}
|
||||
onClick={safeCallback(onApprove, original)}
|
||||
/>
|
||||
<MenuItem
|
||||
icon={<Icon icon={'close-black'} />}
|
||||
text={formatMessage({ id: 'mark_as_rejected' })}
|
||||
onClick={safeCallback(onReject, original)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user