mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
Merge branch 'feature/react-query' of https://github.com/abouolia/Bigcapital into feature/react-query
This commit is contained in:
@@ -21,9 +21,6 @@ function EstimateApproveAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { estimateId },
|
payload: { estimateId },
|
||||||
|
|
||||||
// #withEstimateActions
|
|
||||||
requestApproveEstimate,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
@@ -59,7 +56,6 @@ function EstimateApproveAlert({
|
|||||||
<Alert
|
<Alert
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
confirmButtonText={<T id={'approve'} />}
|
confirmButtonText={<T id={'approve'} />}
|
||||||
icon="trash"
|
|
||||||
intent={Intent.WARNING}
|
intent={Intent.WARNING}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ function EstimateDeleteAlert({
|
|||||||
const { mutateAsync: deleteEstimateMutate, isLoading } = useDeleteEstimate();
|
const { mutateAsync: deleteEstimateMutate, isLoading } = useDeleteEstimate();
|
||||||
|
|
||||||
// handle cancel delete alert.
|
// handle cancel delete alert.
|
||||||
const handleCancelEstimateDelete = () => {
|
const handleAlertCancel = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// handle confirm delete estimate
|
// handle confirm delete estimate
|
||||||
const handleConfirmEstimateDelete = useCallback(() => {
|
const handleAlertConfirm = () => {
|
||||||
deleteEstimateMutate(estimateId)
|
deleteEstimateMutate(estimateId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -50,7 +50,7 @@ function EstimateDeleteAlert({
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
}, [deleteEstimateMutate, name, closeAlert, formatMessage, estimateId]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -60,8 +60,8 @@ function EstimateDeleteAlert({
|
|||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onCancel={handleCancelEstimateDelete}
|
onCancel={handleAlertCancel}
|
||||||
onConfirm={handleConfirmEstimateDelete}
|
onConfirm={handleAlertConfirm}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
<FormattedHTMLMessage
|
<FormattedHTMLMessage
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { queryCache } from 'react-query';
|
|
||||||
|
|
||||||
import { useDeliverEstimate } from 'hooks/query';
|
import { useDeliverEstimate } from 'hooks/query';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
@@ -28,12 +27,12 @@ function EstimateDeliveredAlert({
|
|||||||
const { mutateAsync: deliverEstimateMutate, isLoading } = useDeliverEstimate();
|
const { mutateAsync: deliverEstimateMutate, isLoading } = useDeliverEstimate();
|
||||||
|
|
||||||
// Handle cancel delivered estimate alert.
|
// Handle cancel delivered estimate alert.
|
||||||
const handleCancelDeliveredEstimate = () => {
|
const handleAlertCancel = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle confirm estimate delivered.
|
// Handle confirm estimate delivered.
|
||||||
const handleConfirmEstimateDelivered = useCallback(() => {
|
const handleAlertConfirm = () => {
|
||||||
deliverEstimateMutate(estimateId)
|
deliverEstimateMutate(estimateId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -41,14 +40,13 @@ function EstimateDeliveredAlert({
|
|||||||
id: 'the_estimate_has_been_delivered_successfully',
|
id: 'the_estimate_has_been_delivered_successfully',
|
||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
})
|
||||||
queryCache.invalidateQueries('estimates-table');
|
|
||||||
})
|
})
|
||||||
.catch((error) => {})
|
.catch((error) => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
}, [estimateId, deliverEstimateMutate, formatMessage]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -56,8 +54,8 @@ function EstimateDeliveredAlert({
|
|||||||
confirmButtonText={<T id={'deliver'} />}
|
confirmButtonText={<T id={'deliver'} />}
|
||||||
intent={Intent.WARNING}
|
intent={Intent.WARNING}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onCancel={handleCancelDeliveredEstimate}
|
onCancel={handleAlertCancel}
|
||||||
onConfirm={handleConfirmEstimateDelivered}
|
onConfirm={handleAlertConfirm}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ export default function BillFloatingActions() {
|
|||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
type="submit"
|
|
||||||
onClick={handleSubmitOpenBtnClick}
|
onClick={handleSubmitOpenBtnClick}
|
||||||
text={<T id={'save_open'} />}
|
text={<T id={'save_open'} />}
|
||||||
/>
|
/>
|
||||||
@@ -116,7 +115,6 @@ export default function BillFloatingActions() {
|
|||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className={'ml1'}
|
className={'ml1'}
|
||||||
type="submit"
|
|
||||||
onClick={handleSubmitDraftBtnClick}
|
onClick={handleSubmitDraftBtnClick}
|
||||||
text={<T id={'save_as_draft'} />}
|
text={<T id={'save_as_draft'} />}
|
||||||
/>
|
/>
|
||||||
@@ -150,7 +148,6 @@ export default function BillFloatingActions() {
|
|||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
type="submit"
|
|
||||||
onClick={handleSubmitOpenBtnClick}
|
onClick={handleSubmitOpenBtnClick}
|
||||||
text={<T id={'save'} />}
|
text={<T id={'save'} />}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,85 +1,51 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import moment from 'moment';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { pick, sumBy, isEmpty, omit } from 'lodash';
|
import { sumBy, isEmpty, omit } from 'lodash';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
import { EditBillFormSchema, CreateBillFormSchema } from './BillForm.schema';
|
import { EditBillFormSchema, CreateBillFormSchema } from './BillForm.schema';
|
||||||
import BillFormHeader from './BillFormHeader';
|
import BillFormHeader from './BillFormHeader';
|
||||||
import BillFloatingActions from './BillFloatingActions';
|
import BillFloatingActions from './BillFloatingActions';
|
||||||
import BillFormFooter from './BillFormFooter';
|
import BillFormFooter from './BillFormFooter';
|
||||||
|
import BillItemsEntriesEditor from './BillItemsEntriesEditor';
|
||||||
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import { ERROR } from 'common/errors';
|
import { ERROR } from 'common/errors';
|
||||||
import { repeatValue, orderingLinesIndexes } from 'utils';
|
|
||||||
import BillFormBody from './BillFormBody';
|
|
||||||
import { useBillFormContext } from './BillFormProvider';
|
import { useBillFormContext } from './BillFormProvider';
|
||||||
|
import { orderingLinesIndexes, safeSumBy } from 'utils';
|
||||||
const MIN_LINES_NUMBER = 5;
|
import { defaultBill, transformToEditForm } from './utils';
|
||||||
|
|
||||||
const defaultBill = {
|
|
||||||
index: 0,
|
|
||||||
item_id: '',
|
|
||||||
rate: '',
|
|
||||||
discount: 0,
|
|
||||||
quantity: 1,
|
|
||||||
description: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultInitialValues = {
|
|
||||||
vendor_id: '',
|
|
||||||
bill_number: '',
|
|
||||||
bill_date: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
due_date: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
reference_no: '',
|
|
||||||
note: '',
|
|
||||||
open: '',
|
|
||||||
entries: [...repeatValue(defaultBill, MIN_LINES_NUMBER)],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bill form.
|
* Bill form.
|
||||||
*/
|
*/
|
||||||
export default function BillForm({
|
export default function BillForm() {
|
||||||
|
|
||||||
}) {
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
// Bill form context.
|
||||||
const {
|
const {
|
||||||
bill,
|
bill,
|
||||||
billId,
|
isNewMode,
|
||||||
submitPayload,
|
submitPayload,
|
||||||
createBillMutate,
|
createBillMutate,
|
||||||
editBillMutate,
|
editBillMutate,
|
||||||
} = useBillFormContext();
|
} = useBillFormContext();
|
||||||
|
|
||||||
const isNewMode = !billId;
|
|
||||||
|
|
||||||
// Initial values in create and edit mode.
|
// Initial values in create and edit mode.
|
||||||
const initialValues = useMemo(
|
const initialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...(!isEmpty(bill)
|
...(!isEmpty(bill)
|
||||||
? {
|
? {
|
||||||
...pick(bill, Object.keys(defaultInitialValues)),
|
...transformToEditForm(bill),
|
||||||
entries: [
|
|
||||||
...bill.entries.map((bill) => ({
|
|
||||||
...pick(bill, Object.keys(defaultBill)),
|
|
||||||
})),
|
|
||||||
...repeatValue(
|
|
||||||
defaultBill,
|
|
||||||
Math.max(MIN_LINES_NUMBER - bill.entries.length, 0),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...defaultInitialValues,
|
...defaultBill,
|
||||||
entries: orderingLinesIndexes(defaultInitialValues.entries),
|
entries: orderingLinesIndexes(defaultBill.entries),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[bill],
|
[bill],
|
||||||
@@ -102,7 +68,7 @@ export default function BillForm({
|
|||||||
const entries = values.entries.filter(
|
const entries = values.entries.filter(
|
||||||
(item) => item.item_id && item.quantity,
|
(item) => item.item_id && item.quantity,
|
||||||
);
|
);
|
||||||
const totalQuantity = sumBy(entries, (entry) => parseInt(entry.quantity));
|
const totalQuantity = safeSumBy(entries, (entry) => entry.quantity);
|
||||||
|
|
||||||
if (totalQuantity === 0) {
|
if (totalQuantity === 0) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -146,10 +112,10 @@ export default function BillForm({
|
|||||||
handleErrors(errors, { setErrors });
|
handleErrors(errors, { setErrors });
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
if (bill && bill.id) {
|
if (isNewMode) {
|
||||||
editBillMutate(bill.id, form).then(onSuccess).catch(onError);
|
|
||||||
} else {
|
|
||||||
createBillMutate(form).then(onSuccess).catch(onError);
|
createBillMutate(form).then(onSuccess).catch(onError);
|
||||||
|
} else {
|
||||||
|
editBillMutate([bill.id, form]).then(onSuccess).catch(onError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -168,7 +134,7 @@ export default function BillForm({
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<BillFormHeader />
|
<BillFormHeader />
|
||||||
<BillFormBody defaultBill={defaultBill} />
|
<BillItemsEntriesEditor />
|
||||||
<BillFormFooter />
|
<BillFormFooter />
|
||||||
<BillFloatingActions />
|
<BillFloatingActions />
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { CLASSES } from 'common/classes';
|
|
||||||
import { useBillFormContext } from './BillFormProvider';
|
|
||||||
|
|
||||||
export default function BillFormBody({ defaultBill }) {
|
|
||||||
const { items } = useBillFormContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -16,22 +16,15 @@ import {
|
|||||||
tansformDateValue,
|
tansformDateValue,
|
||||||
handleDateChange,
|
handleDateChange,
|
||||||
inputIntent,
|
inputIntent,
|
||||||
saveInvoke,
|
|
||||||
} from 'utils';
|
} from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill form header.
|
* Fill form header.
|
||||||
*/
|
*/
|
||||||
function BillFormHeader({
|
function BillFormHeader() {
|
||||||
onBillNumberChanged,
|
|
||||||
}) {
|
|
||||||
// Bill form context.
|
// Bill form context.
|
||||||
const { vendors } = useBillFormContext();
|
const { vendors } = useBillFormContext();
|
||||||
|
|
||||||
const handleBillNumberBlur = (event) => {
|
|
||||||
saveInvoke(onBillNumberChanged, event.currentTarget.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
|
||||||
{/* ------- Vendor name ------ */}
|
{/* ------- Vendor name ------ */}
|
||||||
@@ -121,11 +114,7 @@ function BillFormHeader({
|
|||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name="bill_number" />}
|
helperText={<ErrorMessage name="bill_number" />}
|
||||||
>
|
>
|
||||||
<InputGroup
|
<InputGroup minimal={true} {...field} />
|
||||||
minimal={true}
|
|
||||||
{...field}
|
|
||||||
onBlur={handleBillNumberBlur}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</FastField>
|
||||||
@@ -148,6 +137,4 @@ function BillFormHeader({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(withDialogActions)(BillFormHeader);
|
||||||
withDialogActions,
|
|
||||||
)(BillFormHeader);
|
|
||||||
|
|||||||
@@ -46,12 +46,15 @@ function BillFormProvider({ billId, ...props }) {
|
|||||||
const { mutateAsync: createBillMutate } = useCreateBill();
|
const { mutateAsync: createBillMutate } = useCreateBill();
|
||||||
const { mutateAsync: editBillMutate } = useEditBill();
|
const { mutateAsync: editBillMutate } = useEditBill();
|
||||||
|
|
||||||
|
const isNewMode = !billId;
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
accounts,
|
accounts,
|
||||||
vendors,
|
vendors,
|
||||||
items,
|
items,
|
||||||
bill,
|
bill,
|
||||||
submitPayload,
|
submitPayload,
|
||||||
|
isNewMode,
|
||||||
|
|
||||||
isSettingLoading,
|
isSettingLoading,
|
||||||
isBillLoading,
|
isBillLoading,
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { FastField } from 'formik';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { useBillFormContext } from './BillFormProvider';
|
||||||
|
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
|
||||||
|
|
||||||
|
export default function BillFormBody({ defaultBill }) {
|
||||||
|
const { items } = useBillFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
39
client/src/containers/Purchases/Bills/BillForm/utils.js
Normal file
39
client/src/containers/Purchases/Bills/BillForm/utils.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import { transformToForm, repeatValue } from 'utils';
|
||||||
|
|
||||||
|
export const MIN_LINES_NUMBER = 4;
|
||||||
|
|
||||||
|
export const defaultBillEntry = {
|
||||||
|
index: 0,
|
||||||
|
item_id: '',
|
||||||
|
rate: '',
|
||||||
|
discount: 0,
|
||||||
|
quantity: 1,
|
||||||
|
description: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultBill = {
|
||||||
|
vendor_id: '',
|
||||||
|
bill_number: '',
|
||||||
|
bill_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
due_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
reference_no: '',
|
||||||
|
note: '',
|
||||||
|
open: '',
|
||||||
|
entries: [...repeatValue(defaultBillEntry, MIN_LINES_NUMBER)],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformToEditForm = (bill) => {
|
||||||
|
return {
|
||||||
|
...transformToForm(bill, defaultBill),
|
||||||
|
entries: [
|
||||||
|
...bill.entries.map((bill) => ({
|
||||||
|
...transformToForm(bill, defaultBill.entries[0]),
|
||||||
|
})),
|
||||||
|
...repeatValue(
|
||||||
|
defaultBill,
|
||||||
|
Math.max(MIN_LINES_NUMBER - bill.entries.length, 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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.
|
// Handle submit & deliver button click.
|
||||||
const handleSubmitDeliverBtnClick = (event) => {
|
const handleSubmitDeliverBtnClick = (event) => {
|
||||||
setSubmitPayload({ redirect: true, deliver: true, });
|
setSubmitPayload({ redirect: true, deliver: true });
|
||||||
|
submitForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle submit, deliver & new button click.
|
// Handle submit, deliver & new button click.
|
||||||
@@ -77,7 +78,6 @@ export default function EstimateFloatingActions() {
|
|||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
type="submit"
|
|
||||||
onClick={handleSubmitDeliverBtnClick}
|
onClick={handleSubmitDeliverBtnClick}
|
||||||
text={<T id={'save_and_deliver'} />}
|
text={<T id={'save_and_deliver'} />}
|
||||||
/>
|
/>
|
||||||
@@ -105,12 +105,12 @@ export default function EstimateFloatingActions() {
|
|||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
{/* ----------- Save As Draft ----------- */}
|
{/* ----------- Save As Draft ----------- */}
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className={'ml1'}
|
className={'ml1'}
|
||||||
type="submit"
|
|
||||||
onClick={handleSubmitDraftBtnClick}
|
onClick={handleSubmitDraftBtnClick}
|
||||||
text={<T id={'save_as_draft'} />}
|
text={<T id={'save_as_draft'} />}
|
||||||
/>
|
/>
|
||||||
@@ -138,13 +138,13 @@ export default function EstimateFloatingActions() {
|
|||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
{/* ----------- Save and New ----------- */}
|
{/* ----------- Save and New ----------- */}
|
||||||
<If condition={estimate && estimate?.is_delivered}>
|
<If condition={estimate && estimate?.is_delivered}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
type="submit"
|
|
||||||
onClick={handleSubmitDeliverBtnClick}
|
onClick={handleSubmitDeliverBtnClick}
|
||||||
text={<T id={'save'} />}
|
text={<T id={'save'} />}
|
||||||
/>
|
/>
|
||||||
@@ -177,6 +177,7 @@ export default function EstimateFloatingActions() {
|
|||||||
onClick={handleClearBtnClick}
|
onClick={handleClearBtnClick}
|
||||||
text={estimate ? <T id={'reset'} /> : <T id={'clear'} />}
|
text={estimate ? <T id={'reset'} /> : <T id={'clear'} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* ----------- Cancel ----------- */}
|
{/* ----------- Cancel ----------- */}
|
||||||
<Button
|
<Button
|
||||||
className={'ml1'}
|
className={'ml1'}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import moment from 'moment';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { pick, sumBy } from 'lodash';
|
import { omit, sumBy, isEmpty } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -14,57 +13,31 @@ import {
|
|||||||
} from './EstimateForm.schema';
|
} from './EstimateForm.schema';
|
||||||
|
|
||||||
import EstimateFormHeader from './EstimateFormHeader';
|
import EstimateFormHeader from './EstimateFormHeader';
|
||||||
import EstimateFormBody from './EstimateFormBody';
|
import EstimateItemsEntriesField from './EstimateItemsEntriesField';
|
||||||
import EstimateFloatingActions from './EstimateFloatingActions';
|
import EstimateFloatingActions from './EstimateFloatingActions';
|
||||||
import EstimateFormFooter from './EstimateFormFooter';
|
import EstimateFormFooter from './EstimateFormFooter';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
|
||||||
import withMediaActions from 'containers/Media/withMediaActions';
|
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import { ERROR } from 'common/errors';
|
import { ERROR } from 'common/errors';
|
||||||
import {
|
import {
|
||||||
compose,
|
compose,
|
||||||
repeatValue,
|
|
||||||
orderingLinesIndexes,
|
orderingLinesIndexes,
|
||||||
} from 'utils';
|
} from 'utils';
|
||||||
import { useEstimateFormContext } from './EstimateFormProvider';
|
import { useEstimateFormContext } from './EstimateFormProvider';
|
||||||
|
import { transformToEditForm, defaultEstimate } from './utils';
|
||||||
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)],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimate form.
|
* Estimate form.
|
||||||
*/
|
*/
|
||||||
const EstimateForm = ({
|
function EstimateForm({
|
||||||
// #withSettings
|
// #withSettings
|
||||||
estimateNextNumber,
|
estimateNextNumber,
|
||||||
estimateNumberPrefix,
|
estimateNumberPrefix,
|
||||||
}) => {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
estimate,
|
estimate,
|
||||||
isNewMode,
|
isNewMode,
|
||||||
@@ -80,23 +53,14 @@ const EstimateForm = ({
|
|||||||
// Initial values in create and edit mode.
|
// Initial values in create and edit mode.
|
||||||
const initialValues = useMemo(
|
const initialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...(estimate
|
...(!isEmpty(estimate)
|
||||||
? {
|
? {
|
||||||
...pick(estimate, Object.keys(defaultInitialValues)),
|
...transformToEditForm(estimate)
|
||||||
entries: [
|
|
||||||
...estimate.entries.map((estimate) => ({
|
|
||||||
...pick(estimate, Object.keys(defaultEstimate)),
|
|
||||||
})),
|
|
||||||
...repeatValue(
|
|
||||||
defaultEstimate,
|
|
||||||
Math.max(MIN_LINES_NUMBER - estimate.entries.length, 0),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...defaultInitialValues,
|
...defaultEstimate,
|
||||||
estimate_number: estimateNumber,
|
estimate_number: estimateNumber,
|
||||||
entries: orderingLinesIndexes(defaultInitialValues.entries),
|
entries: orderingLinesIndexes(defaultEstimate.entries),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[estimate, estimateNumber],
|
[estimate, estimateNumber],
|
||||||
@@ -138,10 +102,7 @@ const EstimateForm = ({
|
|||||||
const form = {
|
const form = {
|
||||||
...values,
|
...values,
|
||||||
delivered: submitPayload.deliver,
|
delivered: submitPayload.deliver,
|
||||||
// Exclude all entries properties that out of request schema.
|
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
|
||||||
entries: entries.map((entry) => ({
|
|
||||||
...pick(entry, Object.keys(defaultEstimate)),
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
const onSuccess = (response) => {
|
const onSuccess = (response) => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
@@ -172,7 +133,7 @@ const EstimateForm = ({
|
|||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (estimate && estimate.id) {
|
if (!isNewMode) {
|
||||||
editEstimateMutate([estimate.id, form]).then(onSuccess).catch(onError);
|
editEstimateMutate([estimate.id, form]).then(onSuccess).catch(onError);
|
||||||
} else {
|
} else {
|
||||||
createEstimateMutate(form).then(onSuccess).catch(onError);
|
createEstimateMutate(form).then(onSuccess).catch(onError);
|
||||||
@@ -196,18 +157,19 @@ const EstimateForm = ({
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<EstimateFormHeader />
|
<EstimateFormHeader />
|
||||||
<EstimateFormBody defaultEstimate={defaultEstimate} />
|
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||||
|
<EstimateItemsEntriesField />
|
||||||
|
</div>
|
||||||
<EstimateFormFooter />
|
<EstimateFormFooter />
|
||||||
<EstimateFloatingActions />
|
<EstimateFloatingActions />
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withDashboardActions,
|
|
||||||
withMediaActions,
|
|
||||||
withSettings(({ estimatesSettings }) => ({
|
withSettings(({ estimatesSettings }) => ({
|
||||||
estimateNextNumber: estimatesSettings?.nextNumber,
|
estimateNextNumber: estimatesSettings?.nextNumber,
|
||||||
estimateNumberPrefix: estimatesSettings?.numberPrefix,
|
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,
|
InputPrependButton,
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
|
||||||
import withCustomers from 'containers/Customers/withCustomers';
|
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
import { inputIntent, handleDateChange } from 'utils';
|
import { inputIntent, handleDateChange } from 'utils';
|
||||||
@@ -169,8 +168,5 @@ function EstimateFormHeader({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withCustomers(({ customers }) => ({
|
|
||||||
customers,
|
|
||||||
})),
|
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
)(EstimateFormHeader);
|
)(EstimateFormHeader);
|
||||||
|
|||||||
@@ -1,49 +1,20 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import 'style/pages/SaleEstimate/PageForm.scss';
|
import 'style/pages/SaleEstimate/PageForm.scss';
|
||||||
|
|
||||||
import EstimateForm from './EstimateForm';
|
import EstimateForm from './EstimateForm';
|
||||||
|
|
||||||
import { EstimateFormProvider } from './EstimateFormProvider';
|
import { EstimateFormProvider } from './EstimateFormProvider';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
|
||||||
import { compose } from 'utils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimate form page.
|
* Estimate form page.
|
||||||
*/
|
*/
|
||||||
function EstimateFormPage({
|
export default function EstimateFormPage() {
|
||||||
// #withDashboardActions
|
|
||||||
setSidebarShrink,
|
|
||||||
resetSidebarPreviousExpand,
|
|
||||||
setDashboardBackLink,
|
|
||||||
}) {
|
|
||||||
|
|
||||||
const { id } = useParams();
|
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 (
|
return (
|
||||||
<EstimateFormProvider estimateId={id}>
|
<EstimateFormProvider estimateId={id}>
|
||||||
<EstimateForm />
|
<EstimateForm />
|
||||||
</EstimateFormProvider>
|
</EstimateFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
|
|
||||||
withDashboardActions,
|
|
||||||
)(EstimateFormPage);
|
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
useSettings,
|
useSettings,
|
||||||
useCreateEstimate,
|
useCreateEstimate,
|
||||||
useEditEstimate
|
useEditEstimate
|
||||||
} from 'query/hooks';
|
} from 'hooks/query';
|
||||||
|
|
||||||
const EstimateFormContext = createContext();
|
const EstimateFormContext = createContext();
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ const EstimateFormContext = createContext();
|
|||||||
*/
|
*/
|
||||||
function EstimateFormProvider({ estimateId, ...props }) {
|
function EstimateFormProvider({ estimateId, ...props }) {
|
||||||
const { data: estimate, isFetching: isEstimateFetching } = useEstimate(
|
const { data: estimate, isFetching: isEstimateFetching } = useEstimate(
|
||||||
estimateId,
|
estimateId, { enabled: !!estimateId }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle fetch Items data table or list
|
// Handle fetch Items data table or list
|
||||||
@@ -32,16 +32,16 @@ function EstimateFormProvider({ estimateId, ...props }) {
|
|||||||
} = useCustomers();
|
} = useCustomers();
|
||||||
|
|
||||||
// Handle fetch settings.
|
// Handle fetch settings.
|
||||||
const {
|
useSettings();
|
||||||
data: { settings },
|
|
||||||
} = useSettings();
|
|
||||||
|
|
||||||
|
// Form submit payload.
|
||||||
const [submitPayload, setSubmitPayload] = React.useState({});
|
const [submitPayload, setSubmitPayload] = React.useState({});
|
||||||
|
|
||||||
const isNewMode = !estimateId;
|
// Create and edit estimate form.
|
||||||
|
|
||||||
const { mutateAsync: createEstimateMutate } = useCreateEstimate();
|
const { mutateAsync: createEstimateMutate } = useCreateEstimate();
|
||||||
const { mutateAsync: editEstimateMutate } = useEditEstimate();
|
const { mutateAsync: editEstimateMutate } = useEditEstimate();
|
||||||
|
|
||||||
|
const isNewMode = !estimateId;
|
||||||
|
|
||||||
// Provider payload.
|
// Provider payload.
|
||||||
const provider = {
|
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}>
|
<If condition={!original.is_delivered}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
icon={<Icon icon={'check'} iconSize={18} />}
|
||||||
text={formatMessage({ id: 'mark_as_delivered' })}
|
text={formatMessage({ id: 'mark_as_delivered' })}
|
||||||
onClick={safeCallback(onDeliver, original)}
|
onClick={safeCallback(onDeliver, original)}
|
||||||
/>
|
/>
|
||||||
@@ -80,16 +81,19 @@ export function ActionsMenu({
|
|||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.When condition={original.is_delivered && original.is_rejected}>
|
<Choose.When condition={original.is_delivered && original.is_rejected}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
icon={<Icon icon={'check'} iconSize={18} />}
|
||||||
text={formatMessage({ id: 'mark_as_approved' })}
|
text={formatMessage({ id: 'mark_as_approved' })}
|
||||||
onClick={safeCallback(onApprove, original)}
|
onClick={safeCallback(onApprove, original)}
|
||||||
/>
|
/>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.When condition={original.is_delivered}>
|
<Choose.When condition={original.is_delivered}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
icon={<Icon icon={'check'} iconSize={18} />}
|
||||||
text={formatMessage({ id: 'mark_as_approved' })}
|
text={formatMessage({ id: 'mark_as_approved' })}
|
||||||
onClick={safeCallback(onApprove, original)}
|
onClick={safeCallback(onApprove, original)}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
icon={<Icon icon={'close-black'} />}
|
||||||
text={formatMessage({ id: 'mark_as_rejected' })}
|
text={formatMessage({ id: 'mark_as_rejected' })}
|
||||||
onClick={safeCallback(onReject, original)}
|
onClick={safeCallback(onReject, original)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -22,17 +22,9 @@ import withSettings from 'containers/Settings/withSettings';
|
|||||||
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import { ERROR } from 'common/errors';
|
import { ERROR } from 'common/errors';
|
||||||
import {
|
import { compose, orderingLinesIndexes, transactionNumber } from 'utils';
|
||||||
compose,
|
|
||||||
orderingLinesIndexes,
|
|
||||||
transactionNumber,
|
|
||||||
} from 'utils';
|
|
||||||
import { useInvoiceFormContext } from './InvoiceFormProvider';
|
import { useInvoiceFormContext } from './InvoiceFormProvider';
|
||||||
import { transformToEditForm } from './utils';
|
import { transformToEditForm, defaultInvoice } from './utils';
|
||||||
import {
|
|
||||||
MIN_LINES_NUMBER,
|
|
||||||
defaultInitialValues
|
|
||||||
} from './constants';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoice form.
|
* Invoice form.
|
||||||
@@ -64,11 +56,11 @@ function InvoiceForm({
|
|||||||
const initialValues = useMemo(
|
const initialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...(!isEmpty(invoice)
|
...(!isEmpty(invoice)
|
||||||
? transformToEditForm(invoice, defaultInitialValues, MIN_LINES_NUMBER)
|
? transformToEditForm(invoice)
|
||||||
: {
|
: {
|
||||||
...defaultInitialValues,
|
...defaultInvoice,
|
||||||
invoice_no: invoiceNumber,
|
invoice_no: invoiceNumber,
|
||||||
entries: orderingLinesIndexes(defaultInitialValues.entries),
|
entries: orderingLinesIndexes(defaultInvoice.entries),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[invoice, invoiceNumber],
|
[invoice, invoiceNumber],
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { moment } from 'moment';
|
|
||||||
import { repeatValue } from 'utils';
|
|
||||||
|
|
||||||
export const MIN_LINES_NUMBER = 4;
|
|
||||||
|
|
||||||
export const defaultInvoice = {
|
|
||||||
index: 0,
|
|
||||||
item_id: '',
|
|
||||||
rate: '',
|
|
||||||
discount: 0,
|
|
||||||
quantity: 1,
|
|
||||||
description: '',
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultInitialValues = {
|
|
||||||
customer_id: '',
|
|
||||||
invoice_date: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
due_date: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
delivered: '',
|
|
||||||
invoice_no: '',
|
|
||||||
reference_no: '',
|
|
||||||
invoice_message: '',
|
|
||||||
terms_conditions: '',
|
|
||||||
entries: [...repeatValue(defaultInvoice, MIN_LINES_NUMBER)],
|
|
||||||
};
|
|
||||||
@@ -1,16 +1,43 @@
|
|||||||
|
import moment from 'moment';
|
||||||
import { transformToForm, repeatValue } from 'utils';
|
import { transformToForm, repeatValue } from 'utils';
|
||||||
|
|
||||||
|
export const MIN_LINES_NUMBER = 4;
|
||||||
|
|
||||||
export function transformToEditForm(invoice, defaultInvoice, linesNumber) {
|
export const defaultInvoiceEntry = {
|
||||||
|
index: 0,
|
||||||
|
item_id: '',
|
||||||
|
rate: '',
|
||||||
|
discount: 0,
|
||||||
|
quantity: 1,
|
||||||
|
description: '',
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultInvoice = {
|
||||||
|
customer_id: '',
|
||||||
|
invoice_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
due_date: moment().format('YYYY-MM-DD'),
|
||||||
|
delivered: '',
|
||||||
|
invoice_no: '',
|
||||||
|
reference_no: '',
|
||||||
|
invoice_message: '',
|
||||||
|
terms_conditions: '',
|
||||||
|
entries: [...repeatValue(defaultInvoiceEntry, MIN_LINES_NUMBER)],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform invoice to initial values in edit mode.
|
||||||
|
*/
|
||||||
|
export function transformToEditForm(invoice) {
|
||||||
return {
|
return {
|
||||||
...transformToForm(invoice, defaultInvoice),
|
...transformToForm(invoice, defaultInvoice),
|
||||||
entries: [
|
entries: [
|
||||||
...invoice.entries.map((invoice) => ({
|
...invoice.entries.map((invoice) => ({
|
||||||
...transformToForm(invoice, defaultInvoice.entries[0]),
|
...transformToForm(invoice, defaultInvoiceEntry),
|
||||||
})),
|
})),
|
||||||
...repeatValue(
|
...repeatValue(
|
||||||
defaultInvoice,
|
defaultInvoiceEntry,
|
||||||
Math.max(linesNumber - invoice.entries.length, 0),
|
Math.max(MIN_LINES_NUMBER - invoice.entries.length, 0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import moment from 'moment';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { pick, sumBy, isEmpty } from 'lodash';
|
import { omit, sumBy, isEmpty } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ import 'style/pages/SaleReceipt/PageForm.scss';
|
|||||||
import { useReceiptFormContext } from './ReceiptFormProvider';
|
import { useReceiptFormContext } from './ReceiptFormProvider';
|
||||||
|
|
||||||
import ReceiptFromHeader from './ReceiptFormHeader';
|
import ReceiptFromHeader from './ReceiptFormHeader';
|
||||||
import ReceiptFormBody from './ReceiptFormBody';
|
import ReceiptItemsEntriesEditor from './ReceiptItemsEntriesEditor';
|
||||||
import ReceiptFormFloatingActions from './ReceiptFormFloatingActions';
|
import ReceiptFormFloatingActions from './ReceiptFormFloatingActions';
|
||||||
import ReceiptFormFooter from './ReceiptFormFooter';
|
import ReceiptFormFooter from './ReceiptFormFooter';
|
||||||
|
|
||||||
@@ -29,33 +28,11 @@ import withSettings from 'containers/Settings/withSettings';
|
|||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import {
|
import {
|
||||||
compose,
|
compose,
|
||||||
repeatValue,
|
|
||||||
orderingLinesIndexes,
|
orderingLinesIndexes,
|
||||||
transactionNumber,
|
transactionNumber,
|
||||||
} from 'utils';
|
} from 'utils';
|
||||||
|
import { transformToEditForm, defaultReceipt } from './utils'
|
||||||
|
|
||||||
const MIN_LINES_NUMBER = 4;
|
|
||||||
|
|
||||||
const defaultReceipt = {
|
|
||||||
index: 0,
|
|
||||||
item_id: '',
|
|
||||||
rate: '',
|
|
||||||
discount: 0,
|
|
||||||
quantity: '',
|
|
||||||
description: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultInitialValues = {
|
|
||||||
customer_id: '',
|
|
||||||
deposit_account_id: '',
|
|
||||||
receipt_number: '',
|
|
||||||
receipt_date: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
reference_no: '',
|
|
||||||
receipt_message: '',
|
|
||||||
statement: '',
|
|
||||||
closed: '',
|
|
||||||
entries: [...repeatValue(defaultReceipt, MIN_LINES_NUMBER)],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receipt form.
|
* Receipt form.
|
||||||
@@ -75,11 +52,10 @@ function ReceiptForm({
|
|||||||
receipt,
|
receipt,
|
||||||
editReceiptMutate,
|
editReceiptMutate,
|
||||||
createReceiptMutate,
|
createReceiptMutate,
|
||||||
submitPayload
|
submitPayload,
|
||||||
|
isNewMode
|
||||||
} = useReceiptFormContext();
|
} = useReceiptFormContext();
|
||||||
|
|
||||||
const isNewMode = !receiptId;
|
|
||||||
|
|
||||||
// The next receipt number.
|
// The next receipt number.
|
||||||
const receiptNumber = transactionNumber(
|
const receiptNumber = transactionNumber(
|
||||||
receiptNumberPrefix,
|
receiptNumberPrefix,
|
||||||
@@ -89,23 +65,12 @@ function ReceiptForm({
|
|||||||
const initialValues = useMemo(
|
const initialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...(!isEmpty(receipt)
|
...(!isEmpty(receipt)
|
||||||
? {
|
? transformToEditForm(receipt)
|
||||||
...pick(receipt, Object.keys(defaultInitialValues)),
|
|
||||||
entries: [
|
|
||||||
...receipt.entries.map((receipt) => ({
|
|
||||||
...pick(receipt, Object.keys(defaultReceipt)),
|
|
||||||
})),
|
|
||||||
...repeatValue(
|
|
||||||
defaultReceipt,
|
|
||||||
Math.max(MIN_LINES_NUMBER - receipt.entries.length, 0),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
: {
|
: {
|
||||||
...defaultInitialValues,
|
...defaultReceipt,
|
||||||
receipt_number: receiptNumber,
|
receipt_number: receiptNumber,
|
||||||
deposit_account_id: parseInt(preferredDepositAccount),
|
deposit_account_id: parseInt(preferredDepositAccount),
|
||||||
entries: orderingLinesIndexes(defaultInitialValues.entries),
|
entries: orderingLinesIndexes(defaultReceipt.entries),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[receipt, preferredDepositAccount, receiptNumber],
|
[receipt, preferredDepositAccount, receiptNumber],
|
||||||
@@ -145,10 +110,7 @@ function ReceiptForm({
|
|||||||
const form = {
|
const form = {
|
||||||
...values,
|
...values,
|
||||||
closed: submitPayload.status,
|
closed: submitPayload.status,
|
||||||
entries: entries.map((entry) => ({
|
entries: entries.map((entry) => ({ ...omit(entry, ['total']), })),
|
||||||
// Exclude all properties that out of request entries schema.
|
|
||||||
...pick(entry, Object.keys(defaultReceipt)),
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
// Handle the request success.
|
// Handle the request success.
|
||||||
const onSuccess = (response) => {
|
const onSuccess = (response) => {
|
||||||
@@ -179,8 +141,8 @@ function ReceiptForm({
|
|||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (receipt && receipt.id) {
|
if (!isNewMode) {
|
||||||
editReceiptMutate(receipt.id, form).then(onSuccess).catch(onError);
|
editReceiptMutate([receipt.id, form]).then(onSuccess).catch(onError);
|
||||||
} else {
|
} else {
|
||||||
createReceiptMutate(form).then(onSuccess).catch(onError);
|
createReceiptMutate(form).then(onSuccess).catch(onError);
|
||||||
}
|
}
|
||||||
@@ -203,7 +165,7 @@ function ReceiptForm({
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<ReceiptFromHeader />
|
<ReceiptFromHeader />
|
||||||
<ReceiptFormBody defaultReceipt={defaultReceipt} />
|
<ReceiptItemsEntriesEditor />
|
||||||
<ReceiptFormFooter />
|
<ReceiptFormFooter />
|
||||||
<ReceiptFormFloatingActions />
|
<ReceiptFormFloatingActions />
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { CLASSES } from 'common/classes';
|
|
||||||
|
|
||||||
// import EditableItemsEntriesTable from 'containers/Entries/EditableItemsEntriesTable';
|
|
||||||
import { useReceiptFormContext } from './ReceiptFormProvider';
|
|
||||||
|
|
||||||
export default function ExpenseFormBody({ defaultReceipt }) {
|
|
||||||
const { items } = useReceiptFormContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
|
||||||
{/* <EditableItemsEntriesTable
|
|
||||||
items={items}
|
|
||||||
defaultEntry={defaultReceipt}
|
|
||||||
filterSellableItems={true}
|
|
||||||
/> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -46,6 +46,8 @@ function ReceiptFormProvider({ receiptId, ...props }) {
|
|||||||
|
|
||||||
const [submitPayload, setSubmitPayload] = useState({});
|
const [submitPayload, setSubmitPayload] = useState({});
|
||||||
|
|
||||||
|
const isNewMode = !receiptId;
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
receiptId,
|
receiptId,
|
||||||
receipt,
|
receipt,
|
||||||
@@ -54,6 +56,7 @@ function ReceiptFormProvider({ receiptId, ...props }) {
|
|||||||
items,
|
items,
|
||||||
submitPayload,
|
submitPayload,
|
||||||
|
|
||||||
|
isNewMode,
|
||||||
isReceiptLoading,
|
isReceiptLoading,
|
||||||
isAccountsLoading,
|
isAccountsLoading,
|
||||||
isCustomersLoading,
|
isCustomersLoading,
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { FastField } from 'formik';
|
||||||
|
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { useReceiptFormContext } from './ReceiptFormProvider';
|
||||||
|
|
||||||
|
export default function ReceiptItemsEntriesEditor({ defaultReceipt }) {
|
||||||
|
const { items } = useReceiptFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useFormikContext } from 'formik';
|
|
||||||
|
|
||||||
import withDashboardActions from "containers/Dashboard/withDashboardActions";
|
|
||||||
import withReceipts from './withReceipts';
|
|
||||||
import withReceiptActions from './withReceiptActions';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
|
||||||
|
|
||||||
function ReceiptNumberWatcher({
|
|
||||||
receiptNumber,
|
|
||||||
|
|
||||||
// #withDashboardActions
|
|
||||||
changePageSubtitle,
|
|
||||||
|
|
||||||
// #withReceipts
|
|
||||||
receiptNumberChanged,
|
|
||||||
|
|
||||||
//#withReceiptActions
|
|
||||||
setReceiptNumberChanged,
|
|
||||||
}) {
|
|
||||||
const { setFieldValue } = useFormikContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (receiptNumberChanged) {
|
|
||||||
setFieldValue('receipt_number', receiptNumber);
|
|
||||||
changePageSubtitle(`No. ${receiptNumber}`);
|
|
||||||
setReceiptNumberChanged(false);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
receiptNumber,
|
|
||||||
receiptNumberChanged,
|
|
||||||
setReceiptNumberChanged,
|
|
||||||
setFieldValue,
|
|
||||||
changePageSubtitle,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withReceipts(({ receiptNumberChanged }) => ({ receiptNumberChanged })),
|
|
||||||
withReceiptActions,
|
|
||||||
withDashboardActions
|
|
||||||
)(ReceiptNumberWatcher);
|
|
||||||
41
client/src/containers/Sales/Receipts/ReceiptForm/utils.js
Normal file
41
client/src/containers/Sales/Receipts/ReceiptForm/utils.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import { repeatValue, transformToForm } from 'utils';
|
||||||
|
|
||||||
|
export const MIN_LINES_NUMBER = 4;
|
||||||
|
|
||||||
|
export const defaultReceiptEntry = {
|
||||||
|
index: 0,
|
||||||
|
item_id: '',
|
||||||
|
rate: '',
|
||||||
|
discount: 0,
|
||||||
|
quantity: '',
|
||||||
|
description: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultReceipt = {
|
||||||
|
customer_id: '',
|
||||||
|
deposit_account_id: '',
|
||||||
|
receipt_number: '',
|
||||||
|
receipt_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
reference_no: '',
|
||||||
|
receipt_message: '',
|
||||||
|
statement: '',
|
||||||
|
closed: '',
|
||||||
|
entries: [...repeatValue(defaultReceiptEntry, MIN_LINES_NUMBER)],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform to form in edit mode.
|
||||||
|
*/
|
||||||
|
export const transformToEditForm = (receipt) => ({
|
||||||
|
...transformToForm(receipt, defaultReceipt),
|
||||||
|
entries: [
|
||||||
|
...receipt.entries.map((entry) => ({
|
||||||
|
...transformToForm(entry, defaultReceiptEntry),
|
||||||
|
})),
|
||||||
|
...repeatValue(
|
||||||
|
defaultReceiptEntry,
|
||||||
|
Math.max(MIN_LINES_NUMBER - receipt.entries.length, 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -25,7 +25,7 @@ export function useEditBill(props) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
(id, values) => ApiService.post(`purchases/bills/${id}`, values),
|
([id, values]) => ApiService.post(`purchases/bills/${id}`, values),
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries('BILLS');
|
queryClient.invalidateQueries('BILLS');
|
||||||
@@ -97,7 +97,10 @@ export function useBill(id, props) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return defaultTo(states.data, {});
|
return {
|
||||||
|
...states,
|
||||||
|
data: defaultTo(states.data, {}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export function useEstimate(id, props) {
|
|||||||
['SALE_ESTIMATE', id],
|
['SALE_ESTIMATE', id],
|
||||||
() => ApiService.get(`sales/estimates/${id}`),
|
() => ApiService.get(`sales/estimates/${id}`),
|
||||||
{
|
{
|
||||||
select: (res) => ({
|
select: (res) => res.data.estimate,
|
||||||
estimate: res.data.sale_estimate,
|
|
||||||
}),
|
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ export function useEditInvoice(props) {
|
|||||||
return useMutation(
|
return useMutation(
|
||||||
([id, values]) => ApiService.post(`sales/invoices/${id}`, values),
|
([id, values]) => ApiService.post(`sales/invoices/${id}`, values),
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: (res, id) => {
|
||||||
queryClient.invalidateQueries('SALE_INVOICES');
|
queryClient.invalidateQueries('SALE_INVOICES');
|
||||||
|
queryClient.invalidateQueries(['SALE_INVOICE', id]);
|
||||||
},
|
},
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
@@ -41,8 +42,9 @@ export function useDeleteInvoice(props) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation((id) => ApiService.delete(`sales/invoices/${id}`), {
|
return useMutation((id) => ApiService.delete(`sales/invoices/${id}`), {
|
||||||
onSuccess: () => {
|
onSuccess: (res, id) => {
|
||||||
queryClient.invalidateQueries('SALE_INVOICES');
|
queryClient.invalidateQueries('SALE_INVOICES');
|
||||||
|
queryClient.invalidateQueries(['SALE_INVOICE', id]);
|
||||||
},
|
},
|
||||||
...props,
|
...props,
|
||||||
});
|
});
|
||||||
@@ -86,10 +88,11 @@ export function useDeliverInvoice(props) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
(id) => ApiService.delete(`sales/invoices/${id}/deliver`),
|
(id) => ApiService.post(`sales/invoices/${id}/deliver`),
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: (res, id) => {
|
||||||
queryClient.invalidateQueries('SALE_INVOICES');
|
queryClient.invalidateQueries('SALE_INVOICES');
|
||||||
|
queryClient.invalidateQueries(['SALE_INVOICE', id]);
|
||||||
},
|
},
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function useEditReceipt(props) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
(id, values) => ApiService.post(`sales/receipts/${id}`, values),
|
([id, values]) => ApiService.post(`sales/receipts/${id}`, values),
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries('SALE_RECEIPTS');
|
queryClient.invalidateQueries('SALE_RECEIPTS');
|
||||||
|
|||||||
@@ -259,21 +259,23 @@ export default [
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Estimates
|
// Estimates
|
||||||
// {
|
{
|
||||||
// path: `/estimates/:id/edit`,
|
path: `/estimates/:id/edit`,
|
||||||
// component: LazyLoader({
|
component: lazy(() => import('containers/Sales/Estimates/EstimateForm/EstimateFormPage')),
|
||||||
// loader: () => import('containers/Sales/Estimate/EstimateFormPage'),
|
breadcrumb: 'Edit',
|
||||||
// }),
|
pageTitle: formatMessage({ id: 'edit_estimate' }),
|
||||||
// breadcrumb: 'Edit',
|
backLink: true,
|
||||||
// },
|
sidebarShrink: true,
|
||||||
// {
|
},
|
||||||
// path: `/estimates/new`,
|
{
|
||||||
// component: LazyLoader({
|
path: `/estimates/new`,
|
||||||
// loader: () => import('containers/Sales/Estimate/EstimateFormPage'),
|
component: lazy(() => import('containers/Sales/Estimates/EstimateForm/EstimateFormPage')),
|
||||||
// }),
|
breadcrumb: 'New Estimate',
|
||||||
// breadcrumb: 'New Estimate',
|
hotkey: 'ctrl+shift+e',
|
||||||
// hotkey: 'ctrl+shift+e',
|
pageTitle: formatMessage({ id: 'new_estimate' }),
|
||||||
// },
|
backLink: true,
|
||||||
|
sidebarShrink: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `/estimates`,
|
path: `/estimates`,
|
||||||
component: lazy(() =>
|
component: lazy(() =>
|
||||||
@@ -323,6 +325,9 @@ export default [
|
|||||||
import('containers/Sales/Receipts/ReceiptForm/ReceiptFormPage'),
|
import('containers/Sales/Receipts/ReceiptForm/ReceiptFormPage'),
|
||||||
),
|
),
|
||||||
breadcrumb: 'Edit',
|
breadcrumb: 'Edit',
|
||||||
|
pageTitle: formatMessage({ id: 'edit_receipt' }),
|
||||||
|
backLink: true,
|
||||||
|
sidebarShrink: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/receipts/new`,
|
path: `/receipts/new`,
|
||||||
@@ -331,6 +336,9 @@ export default [
|
|||||||
),
|
),
|
||||||
breadcrumb: 'New Receipt',
|
breadcrumb: 'New Receipt',
|
||||||
hotkey: 'ctrl+shift+r',
|
hotkey: 'ctrl+shift+r',
|
||||||
|
pageTitle: formatMessage({ id: 'new_receipt' }),
|
||||||
|
backLink: true,
|
||||||
|
sidebarShrink: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/receipts`,
|
path: `/receipts`,
|
||||||
@@ -376,7 +384,9 @@ export default [
|
|||||||
import('containers/Purchases/Bills/BillForm/BillFormPage'),
|
import('containers/Purchases/Bills/BillForm/BillFormPage'),
|
||||||
),
|
),
|
||||||
breadcrumb: 'Edit',
|
breadcrumb: 'Edit',
|
||||||
pageTitle: formatMessage({ id: 'edit_bill' })
|
pageTitle: formatMessage({ id: 'edit_bill' }),
|
||||||
|
sidebarShrink: true,
|
||||||
|
backLink: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/bills/new`,
|
path: `/bills/new`,
|
||||||
@@ -385,7 +395,9 @@ export default [
|
|||||||
),
|
),
|
||||||
breadcrumb: 'New Bill',
|
breadcrumb: 'New Bill',
|
||||||
hotkey: 'ctrl+shift+b',
|
hotkey: 'ctrl+shift+b',
|
||||||
pageTitle: formatMessage({ id: 'new_bill' })
|
pageTitle: formatMessage({ id: 'new_bill' }),
|
||||||
|
sidebarShrink: true,
|
||||||
|
backLink: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `/bills`,
|
path: `/bills`,
|
||||||
|
|||||||
@@ -373,5 +373,17 @@ export default {
|
|||||||
'M10.6,10.9V5.4H9v5.5H6.7L9.8,14l3.1-3.1ZM5.1,0,2,3.1H4.3V8.6H5.9V3.1H8.2Z',
|
'M10.6,10.9V5.4H9v5.5H6.7L9.8,14l3.1-3.1ZM5.1,0,2,3.1H4.3V8.6H5.9V3.1H8.2Z',
|
||||||
],
|
],
|
||||||
viewBox: '0 0 14 14'
|
viewBox: '0 0 14 14'
|
||||||
}
|
},
|
||||||
|
"check": {
|
||||||
|
path: [
|
||||||
|
'M17 4c-.28 0-.53.11-.71.29L7 13.59 3.71 10.3A.965.965 0 003 10a1.003 1.003 0 00-.71 1.71l4 4c.18.18.43.29.71.29s.53-.11.71-.29l10-10A1.003 1.003 0 0017 4z'
|
||||||
|
],
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
},
|
||||||
|
"close-black": {
|
||||||
|
path: [
|
||||||
|
'M11.41 10l4.29-4.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L10 8.59l-4.29-4.3a1.003 1.003 0 00-1.42 1.42L8.59 10 4.3 14.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4.29-4.3 4.29 4.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L11.41 10z',
|
||||||
|
],
|
||||||
|
viewBox: '0 0 20 20',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,190 +2,45 @@
|
|||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.estimate-form {
|
.page-form--estimate {
|
||||||
padding-bottom: 30px;
|
$self: '.page-form';
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.bp3-form-group {
|
#{$self}__header {
|
||||||
width: 100%;
|
display: flex;
|
||||||
margin: 25px 20px 15px;
|
|
||||||
}
|
&-fields {
|
||||||
.bp3-label {
|
flex: 1 0 0;
|
||||||
margin: 0 20px 0;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #444;
|
|
||||||
width: 130px;
|
|
||||||
}
|
|
||||||
.bp3-form-content {
|
|
||||||
width: 35%;
|
|
||||||
.bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal) {
|
|
||||||
width: 120%;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
&__table {
|
|
||||||
padding: 15px 15px 0;
|
|
||||||
|
|
||||||
.bp3-form-group {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.table {
|
|
||||||
border: 1px dotted rgb(195, 195, 195);
|
|
||||||
border-bottom: transparent;
|
|
||||||
border-left: transparent;
|
|
||||||
|
|
||||||
.th,
|
|
||||||
.td {
|
|
||||||
border-left: 1px dotted rgb(195, 195, 195);
|
|
||||||
|
|
||||||
&.index {
|
|
||||||
> span,
|
|
||||||
> div {
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.thead {
|
|
||||||
.tr .th {
|
|
||||||
padding: 10px 10px;
|
|
||||||
background-color: #f2f5fa;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tbody {
|
|
||||||
.tr .td {
|
|
||||||
padding: 7px;
|
|
||||||
border-bottom: 1px dotted rgb(195, 195, 195);
|
|
||||||
min-height: 46px;
|
|
||||||
|
|
||||||
&.index {
|
|
||||||
background-color: #f2f5fa;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
margin-top: auto;
|
|
||||||
margin-bottom: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tr {
|
|
||||||
.bp3-form-group .bp3-input,
|
|
||||||
.form-group--select-list .bp3-button {
|
|
||||||
border-radius: 3px;
|
|
||||||
padding-left: 8px;
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-form-group:not(.bp3-intent-danger) .bp3-input,
|
|
||||||
.form-group--select-list:not(.bp3-intent-danger) .bp3-button {
|
|
||||||
border-color: #e5e5e5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
.td {
|
|
||||||
border-bottom: transparent;
|
|
||||||
|
|
||||||
.bp3-button,
|
|
||||||
.bp3-input-group {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.td.actions {
|
|
||||||
.bp3-button {
|
|
||||||
background-color: transparent;
|
|
||||||
color: #e68f8e;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #c23030;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.row--total {
|
|
||||||
.td.amount {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.th {
|
|
||||||
color: #444;
|
|
||||||
font-weight: 600;
|
|
||||||
border-bottom: 1px dotted #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td {
|
|
||||||
border-bottom: 1px dotted #999;
|
|
||||||
|
|
||||||
&.description {
|
|
||||||
.bp3-form-group {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions.td {
|
|
||||||
.bp3-button {
|
|
||||||
background: transparent;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__floating-footer {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
background: #fff;
|
|
||||||
padding: 18px 18px;
|
|
||||||
border-top: 1px solid #ececec;
|
|
||||||
|
|
||||||
.has-mini-sidebar & {
|
|
||||||
left: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bp3-button {
|
|
||||||
&.button--clear-lines {
|
|
||||||
background-color: #fcefef;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button--clear-lines,
|
|
||||||
.button--new-line {
|
|
||||||
padding-left: 14px;
|
|
||||||
padding-right: 14px;
|
|
||||||
}
|
|
||||||
.dropzone-container {
|
|
||||||
margin-top: 0;
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
.dropzone {
|
|
||||||
width: 300px;
|
|
||||||
height: 75px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group--description {
|
|
||||||
.bp3-label {
|
.bp3-label {
|
||||||
font-weight: 500;
|
min-width: 140px;
|
||||||
font-size: 13px;
|
|
||||||
color: #444;
|
|
||||||
}
|
}
|
||||||
.bp3-form-content {
|
.bp3-form-content {
|
||||||
// width: 280px;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-form-group {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
|
||||||
|
&.bp3-inline {
|
||||||
|
max-width: 440px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.col--invoice-date {
|
||||||
|
max-width: 435px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$self}__footer {
|
||||||
|
.form-group--customer_note,
|
||||||
|
.form-group--terms_conditions {
|
||||||
|
max-width: 450px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 450px;
|
width: 100%;
|
||||||
min-height: 75px;
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user