mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
WIP Make journal entries.
This commit is contained in:
@@ -8,30 +8,9 @@ import { FormattedList } from 'react-intl';
|
||||
export default function MakeJournalEntriesFooter({
|
||||
formik,
|
||||
}) {
|
||||
const creditSum = useMemo(() => {
|
||||
return formik.values.entries.reduce((sum, entry) => {
|
||||
return entry.credit + sum;
|
||||
}, 0);
|
||||
}, [formik.values.entries]);
|
||||
|
||||
const debitSum = useMemo(() => {
|
||||
return formik.values.entries.reduce((sum, entry) => {
|
||||
return entry.debit + sum;
|
||||
}, 0);
|
||||
}, [formik.values.entries]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Total</strong></td>
|
||||
<td>{ creditSum }</td>
|
||||
<td>{ debitSum }</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="form__floating-footer">
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
@@ -42,9 +21,21 @@ export default function MakeJournalEntriesFooter({
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
type="submit"
|
||||
className={'ml2'}>
|
||||
className={'ml1'}>
|
||||
Save & New
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className={'button-secondary ml1'}>
|
||||
Save as Draft
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className={'button-secondary ml1'}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -76,13 +76,14 @@ function MakeJournalEntriesForm({
|
||||
},
|
||||
});
|
||||
|
||||
console.log(formik.errors);
|
||||
return (
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<MakeJournalEntriesHeader formik={formik} />
|
||||
<MakeJournalEntriesTable formik={formik} />
|
||||
<MakeJournalEntriesFooter formik={formik} />
|
||||
</form>
|
||||
<div class="make-journal-entries">
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<MakeJournalEntriesHeader formik={formik} />
|
||||
<MakeJournalEntriesTable formik={formik} defaultRow={defaultEntry} />
|
||||
<MakeJournalEntriesFooter formik={formik} />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, {useMemo, useCallback} from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import {
|
||||
InputGroup,
|
||||
@@ -12,6 +12,9 @@ import {useIntl} from 'react-intl';
|
||||
import {Row, Col} from 'react-grid-system';
|
||||
import moment from 'moment';
|
||||
import {momentFormatter} from 'utils';
|
||||
import Icon from 'components/Icon';
|
||||
import CurrenciesSelectList from 'components/CurrenciesSelectList';
|
||||
|
||||
|
||||
export default function MakeJournalEntriesHeader({
|
||||
formik
|
||||
@@ -22,25 +25,29 @@ export default function MakeJournalEntriesHeader({
|
||||
const formatted = moment(date).format('YYYY-MM-DD');
|
||||
formik.setFieldValue('date', formatted);
|
||||
};
|
||||
|
||||
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
|
||||
|
||||
return (
|
||||
<div class="make-journal-entries__header">
|
||||
<Row>
|
||||
<Col sm={4}>
|
||||
<Col sm={3}>
|
||||
<FormGroup
|
||||
label={intl.formatMessage({'id': 'reference'})}
|
||||
className={'form-group--reference'}
|
||||
intent={formik.errors.name && Intent.DANGER}
|
||||
helperText={formik.errors.name && formik.errors.label}
|
||||
label={'Journal number'}
|
||||
labelInfo={infoIcon}
|
||||
className={'form-group--journal-number'}
|
||||
intent={formik.errors.journal_number && Intent.DANGER}
|
||||
helperText={formik.errors.journal_number && formik.errors.journal_number}
|
||||
fill={true}>
|
||||
|
||||
<InputGroup
|
||||
intent={formik.errors.name && Intent.DANGER}
|
||||
intent={formik.errors.journal_number && Intent.DANGER}
|
||||
fill={true}
|
||||
{...formik.getFieldProps('reference')} />
|
||||
{...formik.getFieldProps('journal_number')} />
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col sm={3}>
|
||||
<Col sm={2}>
|
||||
<FormGroup
|
||||
label={intl.formatMessage({'id': 'date'})}
|
||||
intent={formik.errors.date && Intent.DANGER}
|
||||
@@ -70,6 +77,28 @@ export default function MakeJournalEntriesHeader({
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col sm={3}>
|
||||
<FormGroup
|
||||
label={'Reference'}
|
||||
labelInfo={infoIcon}
|
||||
className={'form-group--reference'}
|
||||
intent={formik.errors.reference && Intent.DANGER}
|
||||
helperText={formik.errors.reference && formik.errors.reference}
|
||||
fill={true}>
|
||||
|
||||
<InputGroup
|
||||
intent={formik.errors.reference && Intent.DANGER}
|
||||
fill={true}
|
||||
{...formik.getFieldProps('reference')} />
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col sm={4}>
|
||||
<CurrenciesSelectList />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,55 +1,88 @@
|
||||
import React, {useState, useMemo} from 'react';
|
||||
import React, {useState, useMemo, useCallback} from 'react';
|
||||
import DataTable from 'components/DataTable';
|
||||
import {
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
MenuItem,
|
||||
Button,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import {Select} from '@blueprintjs/select';
|
||||
import Icon from 'components/Icon';
|
||||
import AccountsConnect from 'connectors/Accounts.connector.js';
|
||||
import {compose} from 'utils';
|
||||
import {compose, formattedAmount} from 'utils';
|
||||
import {
|
||||
AccountsListFieldCell,
|
||||
MoneyFieldCell,
|
||||
InputGroupCell,
|
||||
} from 'comp}onents/DataTableCells';
|
||||
|
||||
// Actions cell renderer.
|
||||
const ActionsCellRenderer = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value: initialValue },
|
||||
data,
|
||||
payload,
|
||||
}) => {
|
||||
if (data.length <= (index + 2)) {
|
||||
return '';
|
||||
}
|
||||
const onClickRemoveRole = () => {
|
||||
payload.removeRow(index);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={<Icon icon="times-circle" iconSize={14} />}
|
||||
iconSize={14}
|
||||
className="ml2"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole} />
|
||||
);
|
||||
};
|
||||
|
||||
// Total text cell renderer.
|
||||
const TotalAccountCellRenderer = (chainedComponent) => (props) => {
|
||||
if (props.data.length === (props.row.index + 2)) {
|
||||
return (<span>{ 'Total USD' }</span>);
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
|
||||
// Total credit/debit cell renderer.
|
||||
const TotalCreditDebitCellRenderer = (chainedComponent, type) => (props) => {
|
||||
if (props.data.length === (props.row.index + 2)) {
|
||||
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 chainedComponent(props);
|
||||
};
|
||||
|
||||
const NoteCellRenderer = (chainedComponent) => (props) => {
|
||||
if (props.data.length === (props.row.index + 2)) {
|
||||
return '';
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
|
||||
function MakeJournalEntriesTable({
|
||||
formik,
|
||||
accounts,
|
||||
onClickRemoveRow,
|
||||
onClickAddNewRow,
|
||||
defaultRow,
|
||||
}) {
|
||||
const [selectAccountsState, setSelectedAccount] = useState(false);
|
||||
|
||||
// Account item of select accounts field.
|
||||
const accountItem = (item, { handleClick, modifiers, query }) => {
|
||||
return (<MenuItem text={item.name} label={item.code} key={item.id} onClick={handleClick} />)
|
||||
};
|
||||
|
||||
// Filters accounts options by account name or code.
|
||||
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle account field change.
|
||||
const onChangeAccount = (index) => (account) => {
|
||||
setSelectedAccount({
|
||||
...selectAccountsState,
|
||||
[index + 1]: account,
|
||||
});
|
||||
formik.setFieldValue(`entries[${index}].account_id`, account.id);
|
||||
};
|
||||
|
||||
const [data, setData] = useState([
|
||||
...formik.values.entries,
|
||||
const [rows, setRow] = useState([
|
||||
...formik.values.entries.map((e) => ({ ...e, rowType: 'editor'})),
|
||||
]);
|
||||
|
||||
const updateData = (rowIndex, columnId, value) => {
|
||||
setData((old) =>
|
||||
// Handles update datatable data.
|
||||
const handleUpdateData = useCallback((rowIndex, columnId, value) => {
|
||||
setRow((old) =>
|
||||
old.map((row, index) => {
|
||||
if (index === rowIndex) {
|
||||
return {
|
||||
@@ -60,51 +93,18 @@ function MakeJournalEntriesTable({
|
||||
return row
|
||||
})
|
||||
)
|
||||
}
|
||||
}, []);
|
||||
|
||||
const CellInputGroupRenderer = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value: initialValue },
|
||||
}) => {
|
||||
const [state, setState] = useState(initialValue);
|
||||
// Handles click remove datatable row.
|
||||
const handleRemoveRow = useCallback((rowIndex) => {
|
||||
const removeIndex = parseInt(rowIndex, 10);
|
||||
setRow([
|
||||
...rows.filter((row, index) => index !== removeIndex),
|
||||
]);
|
||||
onClickRemoveRow && onClickRemoveRow(removeIndex);
|
||||
}, [rows, onClickRemoveRow]);
|
||||
|
||||
return (
|
||||
<InputGroup
|
||||
fill={true}
|
||||
value={state}
|
||||
onChange={(event) => {
|
||||
setState(event.target.value);
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
formik.setFieldValue(`entries[${index}].[${id}]`, event.target.value);
|
||||
updateData(index, id, state);
|
||||
}}
|
||||
/>);
|
||||
}
|
||||
|
||||
|
||||
const ActionsCellRenderer = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value: initialValue },
|
||||
}) => {
|
||||
|
||||
const onClickRemoveRole = () => {
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={<Icon icon="mines" />}
|
||||
iconSize={14}
|
||||
className="ml2"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole()} />
|
||||
);
|
||||
}
|
||||
|
||||
// Memorized data table columns.
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
Header: '#',
|
||||
@@ -112,89 +112,93 @@ function MakeJournalEntriesTable({
|
||||
Cell: ({ row: {index} }) => (
|
||||
<span>{ index + 1 }</span>
|
||||
),
|
||||
className: "actions",
|
||||
className: "index",
|
||||
width: 40,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: 'Account',
|
||||
accessor: 'account',
|
||||
Cell: ({ row: { index } }) => (
|
||||
<FormGroup
|
||||
className="{'form-group--account'}"
|
||||
inline={true}>
|
||||
<Select
|
||||
items={accounts}
|
||||
noResults={<MenuItem disabled={true} text="No results." />}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccountsPredicater}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onChangeAccount(index)}>
|
||||
|
||||
<Button
|
||||
rightIcon="caret-down"
|
||||
text={(selectAccountsState[(index + 1)])
|
||||
? selectAccountsState[(index + 1)].name : "Select Account"}
|
||||
/>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
),
|
||||
Cell: TotalAccountCellRenderer(AccountsListFieldCell),
|
||||
className: "account",
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
Header: 'Credit (USD)',
|
||||
accessor: 'credit',
|
||||
Cell: TotalCreditDebitCellRenderer(MoneyFieldCell, 'credit'),
|
||||
className: "credit",
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
Header: 'Debit (USD)',
|
||||
accessor: 'debit',
|
||||
Cell: TotalCreditDebitCellRenderer(MoneyFieldCell, 'debit'),
|
||||
className: "debit",
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
Header: 'Note',
|
||||
accessor: 'note',
|
||||
Cell: CellInputGroupRenderer,
|
||||
Cell: NoteCellRenderer(InputGroupCell),
|
||||
disableSortBy: true,
|
||||
className: "note",
|
||||
},
|
||||
{
|
||||
Header: 'Credit',
|
||||
accessor: 'credit',
|
||||
Cell: CellInputGroupRenderer,
|
||||
className: "credit",
|
||||
},
|
||||
{
|
||||
Header: 'Debit',
|
||||
accessor: 'debit',
|
||||
Cell: CellInputGroupRenderer,
|
||||
className: "debit",
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
accessor: 'action',
|
||||
Cell: ActionsCellRenderer,
|
||||
className: "actions",
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 45,
|
||||
}
|
||||
]);
|
||||
], []);
|
||||
|
||||
const onClickNewRow = () => {
|
||||
setData([
|
||||
...data,
|
||||
{
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
account_id: null,
|
||||
note: '',
|
||||
}
|
||||
// Handles click new line.
|
||||
const onClickNewRow = useCallback(() => {
|
||||
setRow([
|
||||
...rows,
|
||||
{ ...defaultRow, rowType: 'editor' },
|
||||
]);
|
||||
}
|
||||
onClickAddNewRow && onClickAddNewRow();
|
||||
}, [defaultRow, rows, onClickAddNewRow]);
|
||||
|
||||
const rowClassNames = useCallback((row) => ({
|
||||
'row--total': rows.length === (row.index + 2),
|
||||
}), [rows]);
|
||||
return (
|
||||
<div class="make-journal-entries__table">
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data} />
|
||||
data={rows}
|
||||
rowClassNames={rowClassNames}
|
||||
payload={{
|
||||
accounts,
|
||||
updateData: handleUpdateData,
|
||||
removeRow: handleRemoveRow,
|
||||
}}/>
|
||||
|
||||
<div class="mt1">
|
||||
<Button
|
||||
minimal={true}
|
||||
intent={Intent.PRIMARY}
|
||||
small={true}
|
||||
className={'button--secondary button--new-line'}
|
||||
onClick={onClickNewRow}>
|
||||
+ New Entry
|
||||
New lines
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
minimal={true}
|
||||
intent={Intent.PRIMARY}
|
||||
small={true}
|
||||
className={'button--secondary button--clear-lines ml1'}
|
||||
onClick={onClickNewRow}>
|
||||
- Clear all entries
|
||||
Clear all lines
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function BalanceSheetHeader({
|
||||
formik.setFieldValue('display_columns_by', item.by);
|
||||
}, []);
|
||||
|
||||
// handle submit filter submit button.
|
||||
// Handle submit filter submit button.
|
||||
const handleSubmitClick = useCallback(() => {
|
||||
formik.submitForm();
|
||||
}, [formik]);
|
||||
|
||||
Reference in New Issue
Block a user