mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
Merge branch 'master' of https://github.com/abouolia/Bigcapital
This commit is contained in:
@@ -1,23 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button } from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
|
||||||
import { saveInvoke, removeRowsByIndex } from 'utils';
|
|
||||||
import { DataTableEditable } from 'components';
|
import { DataTableEditable } from 'components';
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
import {
|
||||||
|
compose,
|
||||||
import { updateDataReducer } from './utils';
|
saveInvoke,
|
||||||
|
updateMinEntriesLines,
|
||||||
|
updateRemoveLineByIndex,
|
||||||
|
updateAutoAddNewLine,
|
||||||
|
updateTableRow,
|
||||||
|
} from 'utils';
|
||||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||||
import { useJournalTableEntriesColumns } from './components';
|
import { useJournalTableEntriesColumns } from './components';
|
||||||
|
import { updateAdjustEntries } from './utils';
|
||||||
import { compose } from 'redux';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make journal entries table component.
|
* Make journal entries table component.
|
||||||
*/
|
*/
|
||||||
function MakeJournalEntriesTable({
|
export default function MakeJournalEntriesTable({
|
||||||
// #withAlertsActions
|
|
||||||
openAlert,
|
|
||||||
|
|
||||||
// #ownPorps
|
// #ownPorps
|
||||||
onChange,
|
onChange,
|
||||||
entries,
|
entries,
|
||||||
@@ -30,22 +28,34 @@ function MakeJournalEntriesTable({
|
|||||||
|
|
||||||
// Memorized data table columns.
|
// Memorized data table columns.
|
||||||
const columns = useJournalTableEntriesColumns();
|
const columns = useJournalTableEntriesColumns();
|
||||||
|
|
||||||
// Handles update datatable data.
|
// Handles update datatable data.
|
||||||
const handleUpdateData = (rowIndex, columnId, value) => {
|
const handleUpdateData = (rowIndex, columnId, value) => {
|
||||||
const newRows = updateDataReducer(entries, rowIndex, columnId, value);
|
const newRows = compose(
|
||||||
|
// Auto-adding new lines.
|
||||||
|
updateAutoAddNewLine(defaultEntry, ['account_id', 'credit', 'debit']),
|
||||||
|
// Update items entries total.
|
||||||
|
updateAdjustEntries(rowIndex, columnId, value),
|
||||||
|
// Update entry of the given row index and column id.
|
||||||
|
updateTableRow(rowIndex, columnId, value),
|
||||||
|
)(entries);
|
||||||
|
|
||||||
saveInvoke(onChange, newRows);
|
saveInvoke(onChange, newRows);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle remove datatable row.
|
// Handle remove datatable row.
|
||||||
const handleRemoveRow = (rowIndex) => {
|
const handleRemoveRow = (rowIndex) => {
|
||||||
const newRows = removeRowsByIndex(entries, rowIndex);
|
const newRows = compose(
|
||||||
|
// Ensure minimum lines count.
|
||||||
|
updateMinEntriesLines(minLinesNumber, defaultEntry),
|
||||||
|
// Remove the line by the given index.
|
||||||
|
updateRemoveLineByIndex(rowIndex),
|
||||||
|
)(entries);
|
||||||
|
|
||||||
saveInvoke(onChange, newRows);
|
saveInvoke(onChange, newRows);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
|
return (
|
||||||
<DataTableEditable
|
<DataTableEditable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={entries}
|
data={entries}
|
||||||
@@ -63,8 +73,6 @@ function MakeJournalEntriesTable({
|
|||||||
})),
|
})),
|
||||||
autoFocus: ['account_id', 0],
|
autoFocus: ['account_id', 0],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withAlertActions)(MakeJournalEntriesTable);
|
|
||||||
@@ -3,7 +3,7 @@ import { Intent } from '@blueprintjs/core';
|
|||||||
import { sumBy, setWith, toSafeInteger, get } from 'lodash';
|
import { sumBy, setWith, toSafeInteger, get } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { transformUpdatedRows, repeatValue, transformToForm } from 'utils';
|
import { updateTableRow, repeatValue, transformToForm } from 'utils';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import { formatMessage } from 'services/intl';
|
import { formatMessage } from 'services/intl';
|
||||||
|
|
||||||
@@ -70,10 +70,14 @@ function adjustmentEntries(entries) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Adjustment credit/debit entries.
|
||||||
|
* @param {number} rowIndex
|
||||||
|
* @param {number} columnId
|
||||||
|
* @param {string} value
|
||||||
|
* @return {array}
|
||||||
*/
|
*/
|
||||||
export const updateDataReducer = (rows, rowIndex, columnId, value) => {
|
export const updateAdjustEntries = (rowIndex, columnId, value) => (rows) => {
|
||||||
let newRows = transformUpdatedRows(rows, rowIndex, columnId, value);
|
let newRows = [...rows];
|
||||||
|
|
||||||
const oldCredit = get(rows, `[${rowIndex}].credit`);
|
const oldCredit = get(rows, `[${rowIndex}].credit`);
|
||||||
const oldDebit = get(rows, `[${rowIndex}].debit`);
|
const oldDebit = get(rows, `[${rowIndex}].debit`);
|
||||||
@@ -82,20 +86,10 @@ export const updateDataReducer = (rows, rowIndex, columnId, value) => {
|
|||||||
const adjustment = adjustmentEntries(rows);
|
const adjustment = adjustmentEntries(rows);
|
||||||
|
|
||||||
if (adjustment.credit) {
|
if (adjustment.credit) {
|
||||||
newRows = transformUpdatedRows(
|
newRows = updateTableRow(rowIndex, 'credit', adjustment.credit)(newRows);
|
||||||
newRows,
|
|
||||||
rowIndex,
|
|
||||||
'credit',
|
|
||||||
adjustment.credit,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (adjustment.debit) {
|
if (adjustment.debit) {
|
||||||
newRows = transformUpdatedRows(
|
newRows = updateTableRow(rowIndex, 'debit', adjustment.debit)(newRows);
|
||||||
newRows,
|
|
||||||
rowIndex,
|
|
||||||
'debit',
|
|
||||||
adjustment.debit,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newRows;
|
return newRows;
|
||||||
|
|||||||
@@ -17,6 +17,17 @@ export default function InviteUserFormContent() {
|
|||||||
// Formik context.
|
// Formik context.
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
const [passwordType, setPasswordType] = React.useState('password');
|
||||||
|
|
||||||
|
// Handle password revealer changing.
|
||||||
|
const handlePasswordRevealerChange = React.useCallback(
|
||||||
|
(shown) => {
|
||||||
|
const type = shown ? 'text' : 'password';
|
||||||
|
setPasswordType(type);
|
||||||
|
},
|
||||||
|
[setPasswordType],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<Row>
|
<Row>
|
||||||
@@ -74,14 +85,14 @@ export default function InviteUserFormContent() {
|
|||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'password'} />}
|
label={<T id={'password'} />}
|
||||||
labelInfo={<PasswordRevealer />}
|
labelInfo={<PasswordRevealer onChange={handlePasswordRevealerChange} />}
|
||||||
className={'form-group--password has-password-revealer'}
|
className={'form-group--password has-password-revealer'}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name={'password'} />}
|
helperText={<ErrorMessage name={'password'} />}
|
||||||
>
|
>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
lang={true}
|
lang={true}
|
||||||
// type={shown ? 'text' : 'password'}
|
type={passwordType}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
FormGroup,
|
FormGroup,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Form, ErrorMessage, FastField } from 'formik';
|
import { Form, ErrorMessage, Field } from 'formik';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { inputIntent } from 'utils';
|
import { inputIntent } from 'utils';
|
||||||
import { PasswordRevealer } from './components';
|
import { PasswordRevealer } from './components';
|
||||||
@@ -14,12 +14,21 @@ import { PasswordRevealer } from './components';
|
|||||||
/**
|
/**
|
||||||
* Login form.
|
* Login form.
|
||||||
*/
|
*/
|
||||||
export default function LoginForm({
|
export default function LoginForm({ isSubmitting }) {
|
||||||
isSubmitting
|
const [passwordType, setPasswordType] = React.useState('password');
|
||||||
}) {
|
|
||||||
|
// Handle password revealer changing.
|
||||||
|
const handlePasswordRevealerChange = React.useCallback(
|
||||||
|
(shown) => {
|
||||||
|
const type = shown ? 'text' : 'password';
|
||||||
|
setPasswordType(type);
|
||||||
|
},
|
||||||
|
[setPasswordType],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form className={'authentication-page__form'}>
|
<Form className={'authentication-page__form'}>
|
||||||
<FastField name={'crediential'}>
|
<Field name={'crediential'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'email_or_phone_number'} />}
|
label={<T id={'email_or_phone_number'} />}
|
||||||
@@ -34,13 +43,15 @@ export default function LoginForm({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</Field>
|
||||||
|
|
||||||
<FastField name={'password'}>
|
<Field name={'password'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'password'} />}
|
label={<T id={'password'} />}
|
||||||
labelInfo={<PasswordRevealer />}
|
labelInfo={
|
||||||
|
<PasswordRevealer onChange={handlePasswordRevealerChange} />
|
||||||
|
}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name={'password'} />}
|
helperText={<ErrorMessage name={'password'} />}
|
||||||
className={'form-group--password has-password-revealer'}
|
className={'form-group--password has-password-revealer'}
|
||||||
@@ -48,12 +59,12 @@ export default function LoginForm({
|
|||||||
<InputGroup
|
<InputGroup
|
||||||
large={true}
|
large={true}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
// type={shown ? 'text' : 'password'}
|
type={passwordType}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</Field>
|
||||||
|
|
||||||
<div className={'login-form__checkbox-section'}>
|
<div className={'login-form__checkbox-section'}>
|
||||||
<Checkbox large={true} className={'checkbox--remember-me'}>
|
<Checkbox large={true} className={'checkbox--remember-me'}>
|
||||||
|
|||||||
@@ -4,26 +4,35 @@ import {
|
|||||||
InputGroup,
|
InputGroup,
|
||||||
Intent,
|
Intent,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Spinner
|
Spinner,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { ErrorMessage, FastField, Form } from 'formik';
|
import { ErrorMessage, Field, Form } from 'formik';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Row, Col, If } from 'components';
|
import { Row, Col, If } from 'components';
|
||||||
|
import { PasswordRevealer } from './components';
|
||||||
import { inputIntent } from 'utils';
|
import { inputIntent } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register form.
|
* Register form.
|
||||||
*/
|
*/
|
||||||
export default function RegisterForm({
|
export default function RegisterForm({ isSubmitting }) {
|
||||||
isSubmitting,
|
const [passwordType, setPasswordType] = React.useState('password');
|
||||||
}) {
|
|
||||||
|
// Handle password revealer changing.
|
||||||
|
const handlePasswordRevealerChange = React.useCallback(
|
||||||
|
(shown) => {
|
||||||
|
const type = shown ? 'text' : 'password';
|
||||||
|
setPasswordType(type);
|
||||||
|
},
|
||||||
|
[setPasswordType],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form className={'authentication-page__form'}>
|
<Form className={'authentication-page__form'}>
|
||||||
<Row className={'name-section'}>
|
<Row className={'name-section'}>
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
<FastField name={'first_name'}>
|
<Field name={'first_name'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'first_name'} />}
|
label={<T id={'first_name'} />}
|
||||||
@@ -37,11 +46,11 @@ export default function RegisterForm({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</Field>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col md={6}>
|
<Col md={6}>
|
||||||
<FastField name={'last_name'}>
|
<Field name={'last_name'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'last_name'} />}
|
label={<T id={'last_name'} />}
|
||||||
@@ -55,11 +64,11 @@ export default function RegisterForm({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</Field>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<FastField name={'phone_number'}>
|
<Field name={'phone_number'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'phone_number'} />}
|
label={<T id={'phone_number'} />}
|
||||||
@@ -70,8 +79,9 @@ export default function RegisterForm({
|
|||||||
<InputGroup intent={inputIntent({ error, touched })} {...field} />
|
<InputGroup intent={inputIntent({ error, touched })} {...field} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</Field>
|
||||||
<FastField name={'email'}>
|
|
||||||
|
<Field name={'email'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'email'} />}
|
label={<T id={'email'} />}
|
||||||
@@ -82,28 +92,28 @@ export default function RegisterForm({
|
|||||||
<InputGroup intent={inputIntent({ error, touched })} {...field} />
|
<InputGroup intent={inputIntent({ error, touched })} {...field} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</Field>
|
||||||
|
|
||||||
<FastField name={'password'}>
|
<Field name={'password'}>
|
||||||
{({ form, field, meta: { error, touched } }) => (
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'password'} />}
|
label={<T id={'password'} />}
|
||||||
// labelInfo={passwordRevealerTmp}
|
labelInfo={
|
||||||
intent={inputIntent({ error, touched })}
|
<PasswordRevealer onChange={handlePasswordRevealerChange} />
|
||||||
helperText={
|
|
||||||
<ErrorMessage name={'password'} />
|
|
||||||
}
|
}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'password'} />}
|
||||||
className={'form-group--password has-password-revealer'}
|
className={'form-group--password has-password-revealer'}
|
||||||
>
|
>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
lang={true}
|
lang={true}
|
||||||
// type={shown ? 'text' : 'password'}
|
type={passwordType}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</Field>
|
||||||
|
|
||||||
<div className={'register-form__agreement-section'}>
|
<div className={'register-form__agreement-section'}>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -2,10 +2,18 @@ import React from 'react';
|
|||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import ContentLoader from 'react-content-loader';
|
import ContentLoader from 'react-content-loader';
|
||||||
import { If, Icon } from 'components';
|
import { If, Icon } from 'components';
|
||||||
|
import { saveInvoke } from 'utils';
|
||||||
|
|
||||||
|
export function PasswordRevealer({ defaultShown = false, onChange }) {
|
||||||
|
const [shown, setShown] = React.useState(defaultShown);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
setShown(!shown);
|
||||||
|
saveInvoke(onChange, !shown);
|
||||||
|
};
|
||||||
|
|
||||||
export function PasswordRevealer({ shown, onClick }) {
|
|
||||||
return (
|
return (
|
||||||
<span class="password-revealer" onClick={onClick}>
|
<span class="password-revealer" onClick={handleClick}>
|
||||||
<If condition={shown}>
|
<If condition={shown}>
|
||||||
<Icon icon="eye-slash" />{' '}
|
<Icon icon="eye-slash" />{' '}
|
||||||
<span class="text">
|
<span class="text">
|
||||||
|
|||||||
@@ -1,40 +1,25 @@
|
|||||||
import React, { useEffect, useCallback } from 'react';
|
import React, { useEffect, useCallback } from 'react';
|
||||||
import { Button } from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useItem } from 'hooks/query';
|
import { useItem } from 'hooks/query';
|
||||||
|
|
||||||
import ItemsEntriesDeleteAlert from 'containers/Alerts/ItemsEntries/ItemsEntriesDeleteAlert';
|
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { DataTableEditable } from 'components';
|
import { DataTableEditable } from 'components';
|
||||||
|
|
||||||
import { useEditableItemsEntriesColumns } from './components';
|
import { useEditableItemsEntriesColumns } from './components';
|
||||||
import {
|
import {
|
||||||
saveInvoke,
|
saveInvoke,
|
||||||
updateTableRow,
|
|
||||||
repeatValue,
|
|
||||||
removeRowsByIndex,
|
|
||||||
compose,
|
compose,
|
||||||
|
updateTableRow,
|
||||||
|
updateMinEntriesLines,
|
||||||
|
updateAutoAddNewLine,
|
||||||
|
updateRemoveLineByIndex,
|
||||||
} from 'utils';
|
} from 'utils';
|
||||||
import { updateItemsEntriesTotal, ITEM_TYPE } from './utils';
|
import { updateItemsEntriesTotal, ITEM_TYPE } from './utils';
|
||||||
import { last } from 'lodash';
|
|
||||||
|
|
||||||
const updateAutoAddNewLine = (defaultEntry) => (entries) => {
|
|
||||||
const newEntries = [...entries];
|
|
||||||
const lastEntry = last(newEntries);
|
|
||||||
|
|
||||||
return (lastEntry.item_id) ? [...entries, defaultEntry] : [...entries];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Items entries table.
|
* Items entries table.
|
||||||
*/
|
*/
|
||||||
function ItemsEntriesTable({
|
function ItemsEntriesTable({
|
||||||
// #withAlertActions
|
|
||||||
openAlert,
|
|
||||||
|
|
||||||
// #ownProps
|
// #ownProps
|
||||||
items,
|
items,
|
||||||
entries,
|
entries,
|
||||||
@@ -116,7 +101,7 @@ function ItemsEntriesTable({
|
|||||||
setRowItem({ rowIndex, columnId, itemId: value });
|
setRowItem({ rowIndex, columnId, itemId: value });
|
||||||
}
|
}
|
||||||
const newRows = compose(
|
const newRows = compose(
|
||||||
updateAutoAddNewLine(defaultEntry),
|
updateAutoAddNewLine(defaultEntry, ['item_id']),
|
||||||
updateItemsEntriesTotal,
|
updateItemsEntriesTotal,
|
||||||
updateTableRow(rowIndex, columnId, value),
|
updateTableRow(rowIndex, columnId, value),
|
||||||
)(rows);
|
)(rows);
|
||||||
@@ -129,55 +114,35 @@ function ItemsEntriesTable({
|
|||||||
|
|
||||||
// Handle table rows removing by index.
|
// Handle table rows removing by index.
|
||||||
const handleRemoveRow = (rowIndex) => {
|
const handleRemoveRow = (rowIndex) => {
|
||||||
const newRows = removeRowsByIndex(rows, rowIndex);
|
const newRows = compose(
|
||||||
setRows(newRows);
|
// Ensure minimum lines count.
|
||||||
saveInvoke(onUpdateData, newRows);
|
updateMinEntriesLines(4, defaultEntry),
|
||||||
};
|
// Remove the line by the given index.
|
||||||
|
updateRemoveLineByIndex(rowIndex),
|
||||||
|
)(rows);
|
||||||
|
|
||||||
// Handle table rows adding a new row.
|
|
||||||
const onClickNewRow = (event) => {
|
|
||||||
const newRows = [...rows, defaultEntry];
|
|
||||||
setRows(newRows);
|
|
||||||
saveInvoke(onUpdateData, newRows);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle table clearing all rows.
|
|
||||||
const handleClickClearAllLines = (event) => {
|
|
||||||
openAlert('items-entries-clear-lines');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle alert confirm of clear all lines.
|
|
||||||
const handleClearLinesAlertConfirm = () => {
|
|
||||||
const newRows = repeatValue(defaultEntry, linesNumber);
|
|
||||||
setRows(newRows);
|
setRows(newRows);
|
||||||
saveInvoke(onUpdateData, newRows);
|
saveInvoke(onUpdateData, newRows);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DataTableEditable
|
||||||
<DataTableEditable
|
className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)}
|
||||||
className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)}
|
columns={columns}
|
||||||
columns={columns}
|
data={rows}
|
||||||
data={rows}
|
sticky={true}
|
||||||
sticky={true}
|
progressBarLoading={isItemFetching}
|
||||||
progressBarLoading={isItemFetching}
|
cellsLoading={isItemFetching}
|
||||||
cellsLoading={isItemFetching}
|
cellsLoadingCoords={cellsLoading}
|
||||||
cellsLoadingCoords={cellsLoading}
|
footer={true}
|
||||||
footer={true}
|
payload={{
|
||||||
payload={{
|
items,
|
||||||
items,
|
errors: errors || [],
|
||||||
errors: errors || [],
|
updateData: handleUpdateData,
|
||||||
updateData: handleUpdateData,
|
removeRow: handleRemoveRow,
|
||||||
removeRow: handleRemoveRow,
|
autoFocus: ['item_id', 0],
|
||||||
autoFocus: ['item_id', 0],
|
}}
|
||||||
}}
|
/>
|
||||||
|
|
||||||
/>
|
|
||||||
<ItemsEntriesDeleteAlert
|
|
||||||
name={'items-entries-clear-lines'}
|
|
||||||
onConfirm={handleClearLinesAlertConfirm}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +151,7 @@ ItemsEntriesTable.defaultProps = {
|
|||||||
index: 0,
|
index: 0,
|
||||||
item_id: '',
|
item_id: '',
|
||||||
description: '',
|
description: '',
|
||||||
quantity: 1,
|
quantity: '',
|
||||||
rate: '',
|
rate: '',
|
||||||
discount: '',
|
discount: '',
|
||||||
},
|
},
|
||||||
@@ -194,4 +159,4 @@ ItemsEntriesTable.defaultProps = {
|
|||||||
linesNumber: 4,
|
linesNumber: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default compose(withAlertActions)(ItemsEntriesTable);
|
export default ItemsEntriesTable;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { repeat } from 'lodash';
|
||||||
import { toSafeNumber } from 'utils';
|
import { toSafeNumber } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve item entry total from the given rate, quantity and discount.
|
* Retrieve item entry total from the given rate, quantity and discount.
|
||||||
* @param {number} rate
|
* @param {number} rate
|
||||||
* @param {number} quantity
|
* @param {number} quantity
|
||||||
* @param {number} discount
|
* @param {number} discount
|
||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
export const calcItemEntryTotal = (discount, quantity, rate) => {
|
export const calcItemEntryTotal = (discount, quantity, rate) => {
|
||||||
@@ -21,9 +22,9 @@ export const calcItemEntryTotal = (discount, quantity, rate) => {
|
|||||||
export function updateItemsEntriesTotal(rows) {
|
export function updateItemsEntriesTotal(rows) {
|
||||||
return rows.map((row) => ({
|
return rows.map((row) => ({
|
||||||
...row,
|
...row,
|
||||||
total: calcItemEntryTotal(row.discount, row.quantity, row.rate)
|
total: calcItemEntryTotal(row.discount, row.quantity, row.rate),
|
||||||
}));
|
}));
|
||||||
};
|
}
|
||||||
|
|
||||||
export const ITEM_TYPE = {
|
export const ITEM_TYPE = {
|
||||||
SELLABLE: 'SELLABLE',
|
SELLABLE: 'SELLABLE',
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ import classNames from 'classnames';
|
|||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import ExpenseFormEntriesField from './ExpenseFormEntriesField';
|
import ExpenseFormEntriesField from './ExpenseFormEntriesField';
|
||||||
|
|
||||||
export default function ExpenseFormBody({
|
export default function ExpenseFormBody() {
|
||||||
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||||
<ExpenseFormEntriesField />
|
<ExpenseFormEntriesField />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FastField } from 'formik';
|
import { FastField } from 'formik';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ExpenseFormEntriesTable from './ExpenseFormEntriesTable';
|
import ExpenseFormEntriesTable from './ExpenseFormEntriesTable';
|
||||||
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
import { defaultExpenseEntry } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expense form entries field.
|
* Expense form entries field.
|
||||||
@@ -9,8 +9,6 @@ import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
|||||||
export default function ExpenseFormEntriesField({
|
export default function ExpenseFormEntriesField({
|
||||||
linesNumber = 4,
|
linesNumber = 4,
|
||||||
}) {
|
}) {
|
||||||
const { defaultCategoryEntry } = useExpenseFormContext();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FastField name={'categories'}>
|
<FastField name={'categories'}>
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
@@ -20,7 +18,7 @@ export default function ExpenseFormEntriesField({
|
|||||||
onChange={(entries) => {
|
onChange={(entries) => {
|
||||||
form.setFieldValue('categories', entries);
|
form.setFieldValue('categories', entries);
|
||||||
}}
|
}}
|
||||||
defaultEntry={defaultCategoryEntry}
|
defaultEntry={defaultExpenseEntry}
|
||||||
linesNumber={linesNumber}
|
linesNumber={linesNumber}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Button } from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
|
||||||
|
|
||||||
import { DataTableEditable } from 'components';
|
import { DataTableEditable } from 'components';
|
||||||
import ExpenseDeleteEntriesAlert from 'containers/Alerts/Expenses/ExpenseDeleteEntriesAlert';
|
|
||||||
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
import { useExpenseFormContext } from './ExpenseFormPageProvider';
|
||||||
import { useExpenseFormTableColumns } from './components';
|
import { useExpenseFormTableColumns } from './components';
|
||||||
|
import {
|
||||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
saveInvoke,
|
||||||
|
compose,
|
||||||
import { transformUpdatedRows, compose, saveInvoke, repeatValue } from 'utils';
|
updateTableRow,
|
||||||
|
updateMinEntriesLines,
|
||||||
|
updateAutoAddNewLine,
|
||||||
|
updateRemoveLineByIndex,
|
||||||
|
} from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expenses form entries.
|
* Expenses form entries.
|
||||||
*/
|
*/
|
||||||
function ExpenseFormEntriesTable({
|
export default function ExpenseFormEntriesTable({
|
||||||
// #withAlertActions
|
|
||||||
openAlert,
|
|
||||||
|
|
||||||
// #ownPorps
|
// #ownPorps
|
||||||
entries,
|
entries,
|
||||||
defaultEntry,
|
defaultEntry,
|
||||||
@@ -32,29 +30,30 @@ function ExpenseFormEntriesTable({
|
|||||||
|
|
||||||
// Handles update datatable data.
|
// Handles update datatable data.
|
||||||
const handleUpdateData = useCallback(
|
const handleUpdateData = useCallback(
|
||||||
(rowIndex, columnIdOrObj, value) => {
|
(rowIndex, columnId, value) => {
|
||||||
const newRows = transformUpdatedRows(
|
const newRows = compose(
|
||||||
entries,
|
updateAutoAddNewLine(defaultEntry, ['expense_account_id']),
|
||||||
rowIndex,
|
updateTableRow(rowIndex, columnId, value),
|
||||||
columnIdOrObj,
|
)(entries);
|
||||||
value,
|
|
||||||
);
|
|
||||||
saveInvoke(onChange, newRows);
|
saveInvoke(onChange, newRows);
|
||||||
},
|
},
|
||||||
[entries, onChange],
|
[entries, defaultEntry, onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handles click remove datatable row.
|
// Handles click remove datatable row.
|
||||||
const handleRemoveRow = useCallback(
|
const handleRemoveRow = useCallback(
|
||||||
(rowIndex) => {
|
(rowIndex) => {
|
||||||
// Can't continue if there is just one row line or less.
|
const newRows = compose(
|
||||||
if (entries.length <= 1) {
|
// Ensure minimum lines count.
|
||||||
return;
|
updateMinEntriesLines(4, defaultEntry),
|
||||||
}
|
// Remove the line by the given index.
|
||||||
const newRows = entries.filter((row, index) => index !== rowIndex);
|
updateRemoveLineByIndex(rowIndex),
|
||||||
|
)(entries);
|
||||||
|
|
||||||
saveInvoke(onChange, newRows);
|
saveInvoke(onChange, newRows);
|
||||||
},
|
},
|
||||||
[entries, onChange],
|
[entries, defaultEntry, onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -72,6 +71,4 @@ function ExpenseFormEntriesTable({
|
|||||||
footer={true}
|
footer={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withAlertActions)(ExpenseFormEntriesTable);
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import { FastField } from 'formik';
|
import { FastField } from 'formik';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
|
|||||||
@@ -93,17 +93,6 @@ export const compose = (...funcs) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
export const updateTableRow = (rowIndex, columnId, value) => (old) => {
|
|
||||||
return old.map((row, index) => {
|
|
||||||
if (index === rowIndex) {
|
|
||||||
return {
|
|
||||||
...old[rowIndex],
|
|
||||||
[columnId]: value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return row
|
|
||||||
})
|
|
||||||
}
|
|
||||||
export const getObjectDiff = (a, b) => {
|
export const getObjectDiff = (a, b) => {
|
||||||
return _.reduce(
|
return _.reduce(
|
||||||
a,
|
a,
|
||||||
@@ -600,4 +589,41 @@ export const amountPaymentEntries = (amount, entries) => {
|
|||||||
payment_amount: diff,
|
payment_amount: diff,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateAutoAddNewLine = (defaultEntry, props) => (entries) => {
|
||||||
|
const newEntries = [...entries];
|
||||||
|
const lastEntry = _.last(newEntries);
|
||||||
|
const newLine = props.filter((entryKey) => !isBlank(lastEntry[entryKey]));
|
||||||
|
|
||||||
|
return newLine.length > 0 ? [...entries, defaultEntry] : [...entries];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure min entries lines.
|
||||||
|
* @param {number} min
|
||||||
|
* @param {any} defaultEntry
|
||||||
|
*/
|
||||||
|
export const updateMinEntriesLines = (min, defaultEntry) => (lines) => {
|
||||||
|
if (lines.length < min) {
|
||||||
|
const diffLines = Math.max(min - lines.length, 0);
|
||||||
|
return [...lines, ...repeatValue(defaultEntry, diffLines)];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateRemoveLineByIndex = (rowIndex) => (entries) => {
|
||||||
|
const removeIndex = parseInt(rowIndex, 10);
|
||||||
|
return entries.filter((row, index) => index !== removeIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateTableRow = (rowIndex, columnId, value) => (old) => {
|
||||||
|
return old.map((row, index) => {
|
||||||
|
if (index === rowIndex) {
|
||||||
|
return {
|
||||||
|
...old[rowIndex],
|
||||||
|
[columnId]: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
})
|
||||||
};
|
};
|
||||||
@@ -172,26 +172,6 @@ export default class SalesReceiptService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve estimate number to object model.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {ISaleReceiptDTO} saleReceiptDTO - Sale receipt DTO.
|
|
||||||
* @param {ISaleReceipt} oldSaleReceipt - Old receipt model object.
|
|
||||||
*/
|
|
||||||
transformReceiptNumberToModel(
|
|
||||||
tenantId: number,
|
|
||||||
saleReceiptDTO: ISaleReceiptDTO,
|
|
||||||
oldSaleReceipt?: ISaleReceipt
|
|
||||||
): string {
|
|
||||||
// Retreive the next invoice number.
|
|
||||||
const autoNextNumber = this.getNextReceiptNumber(tenantId);
|
|
||||||
|
|
||||||
if (saleReceiptDTO.receiptNumber) {
|
|
||||||
return saleReceiptDTO.receiptNumber;
|
|
||||||
}
|
|
||||||
return oldSaleReceipt ? oldSaleReceipt.receiptNumber : autoNextNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform create DTO object to model object.
|
* Transform create DTO object to model object.
|
||||||
* @param {ISaleReceiptDTO} saleReceiptDTO -
|
* @param {ISaleReceiptDTO} saleReceiptDTO -
|
||||||
@@ -233,7 +213,7 @@ export default class SalesReceiptService {
|
|||||||
receiptNumber,
|
receiptNumber,
|
||||||
// Avoid rewrite the deliver date in edit mode when already published.
|
// Avoid rewrite the deliver date in edit mode when already published.
|
||||||
...(saleReceiptDTO.closed &&
|
...(saleReceiptDTO.closed &&
|
||||||
!oldSaleReceipt.closedAt && {
|
!oldSaleReceipt?.closedAt && {
|
||||||
closedAt: moment().toMySqlDateTime(),
|
closedAt: moment().toMySqlDateTime(),
|
||||||
}),
|
}),
|
||||||
entries: saleReceiptDTO.entries.map((entry) => ({
|
entries: saleReceiptDTO.entries.map((entry) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user