mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
refactoring: account form.
refactoring: expense form. refactoring: manual journal form. refactoring: invoice form.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { DataTable, Choose } from 'components';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { DataTable } from 'components';
|
||||
|
||||
import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus';
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
@@ -35,20 +35,22 @@ function ManualJournalsDataTable({
|
||||
pagination,
|
||||
isManualJournalsLoading,
|
||||
isManualJournalsFetching,
|
||||
isEmptyStatus
|
||||
isEmptyStatus,
|
||||
} = useManualJournalsContext();
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
// Manual journals columns.
|
||||
const columns = useManualJournalsColumns();
|
||||
|
||||
// Handles the journal publish action.
|
||||
const handlePublishJournal = ({ id }) => {
|
||||
openAlert('journal-publish', { manualJournalId: id })
|
||||
openAlert('journal-publish', { manualJournalId: id });
|
||||
};
|
||||
|
||||
// Handle the journal edit action.
|
||||
const handleEditJournal = ({ id }) => {
|
||||
|
||||
history.push(`/manual-journals/${id}/edit`);
|
||||
};
|
||||
|
||||
// Handle the journal delete action.
|
||||
@@ -68,51 +70,41 @@ function ManualJournalsDataTable({
|
||||
[setManualJournalsTableState],
|
||||
);
|
||||
|
||||
// Display manual journal empty status instead of the table.
|
||||
if (isEmptyStatus) {
|
||||
return <ManualJournalsEmptyStatus />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
||||
<Choose>
|
||||
<Choose.When condition={isEmptyStatus}>
|
||||
<ManualJournalsEmptyStatus />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<DataTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={manualJournals}
|
||||
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
expandable={true}
|
||||
sticky={true}
|
||||
|
||||
loading={isManualJournalsLoading}
|
||||
headerLoading={isManualJournalsLoading}
|
||||
progressBarLoading={isManualJournalsFetching}
|
||||
|
||||
pagesCount={pagination.pagesCount}
|
||||
pagination={true}
|
||||
|
||||
autoResetSortBy={false}
|
||||
autoResetPage={false}
|
||||
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
ContextMenu={ActionsMenu}
|
||||
|
||||
onFetchData={handleFetchData}
|
||||
payload={{
|
||||
onDelete: handleDeleteJournal,
|
||||
onPublish: handlePublishJournal
|
||||
}}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</div>
|
||||
<DataTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={manualJournals}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
expandable={true}
|
||||
sticky={true}
|
||||
loading={isManualJournalsLoading}
|
||||
headerLoading={isManualJournalsLoading}
|
||||
progressBarLoading={isManualJournalsFetching}
|
||||
pagesCount={pagination.pagesCount}
|
||||
pagination={true}
|
||||
autoResetSortBy={false}
|
||||
autoResetPage={false}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
ContextMenu={ActionsMenu}
|
||||
onFetchData={handleFetchData}
|
||||
payload={{
|
||||
onDelete: handleDeleteJournal,
|
||||
onPublish: handlePublishJournal,
|
||||
onEdit: handleEditJournal,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withManualJournalsActions,
|
||||
withAlertsActions
|
||||
withAlertsActions,
|
||||
)(ManualJournalsDataTable);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import { DashboardContentTable, DashboardPageContent } from 'components';
|
||||
|
||||
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
||||
import ManualJournalsAlerts from './ManualJournalsAlerts';
|
||||
@@ -41,7 +41,11 @@ function ManualJournalsTable({
|
||||
|
||||
<DashboardPageContent>
|
||||
<ManualJournalsViewTabs />
|
||||
<ManualJournalsDataTable />
|
||||
|
||||
<DashboardContentTable>
|
||||
<ManualJournalsDataTable />
|
||||
</DashboardContentTable>
|
||||
|
||||
<ManualJournalsAlerts />
|
||||
</DashboardPageContent>
|
||||
</ManualJournalsListProvider>
|
||||
|
||||
@@ -3,12 +3,12 @@ import { FastField } from 'formik';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import MakeJournalEntriesTable from './MakeJournalEntriesTable';
|
||||
import { orderingLinesIndexes, repeatValue } from 'utils';
|
||||
import { defaultManualJournal, MIN_LINES_NUMBER } from './utils';
|
||||
|
||||
export default function MakeJournalEntriesField({
|
||||
defaultRow,
|
||||
linesNumber = 4,
|
||||
}) {
|
||||
/**
|
||||
* Make journal entries field.
|
||||
*/
|
||||
export default function MakeJournalEntriesField() {
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||
<FastField name={'entries'}>
|
||||
@@ -18,16 +18,9 @@ export default function MakeJournalEntriesField({
|
||||
form.setFieldValue('entries', entries);
|
||||
}}
|
||||
entries={value}
|
||||
defaultEntry={defaultManualJournal}
|
||||
initialLinesNumber={MIN_LINES_NUMBER}
|
||||
error={error}
|
||||
onClickAddNewRow={() => {
|
||||
form.setFieldValue('entries', [...value, defaultRow]);
|
||||
}}
|
||||
onClickClearAllLines={() => {
|
||||
form.setFieldValue(
|
||||
'entries',
|
||||
orderingLinesIndexes([...repeatValue(defaultRow, linesNumber)])
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
@@ -10,79 +10,70 @@ import {
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import classNames from 'classnames';
|
||||
import { saveInvoke } from 'utils';
|
||||
import { If, Icon } from 'components';
|
||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* Make Journal floating actions bar.
|
||||
*/
|
||||
export default function MakeJournalEntriesFooter({
|
||||
isSubmitting,
|
||||
onSubmitClick,
|
||||
onCancelClick,
|
||||
manualJournalId,
|
||||
onSubmitForm,
|
||||
onResetForm,
|
||||
manualJournalPublished,
|
||||
}) {
|
||||
export default function MakeJournalEntriesFooter() {
|
||||
const history = useHistory();
|
||||
|
||||
// Formik context.
|
||||
const { isSubmitting, submitForm } = useFormikContext();
|
||||
|
||||
// Make journal form context.
|
||||
const {
|
||||
manualJournalId,
|
||||
setSubmitPayload,
|
||||
manualJournalPublished = false,
|
||||
} = useMakeJournalFormContext();
|
||||
|
||||
// Handle `submit & publish` button click.
|
||||
const handleSubmitPublishBtnClick = (event) => {
|
||||
saveInvoke(onSubmitClick, event, {
|
||||
redirect: true,
|
||||
publish: true,
|
||||
});
|
||||
setSubmitPayload({ redirect: true, publish: true });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle `submit, publish & new` button click.
|
||||
const handleSubmitPublishAndNewBtnClick = (event) => {
|
||||
onSubmitForm();
|
||||
saveInvoke(onSubmitClick, event, {
|
||||
redirect: false,
|
||||
publish: true,
|
||||
resetForm: true,
|
||||
});
|
||||
setSubmitPayload({ redirect: false, publish: true, resetForm: true });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle `submit, publish & continue editing` button click.
|
||||
const handleSubmitPublishContinueEditingBtnClick = (event) => {
|
||||
onSubmitForm();
|
||||
saveInvoke(onSubmitClick, event, {
|
||||
redirect: false,
|
||||
publish: true,
|
||||
});
|
||||
setSubmitPayload({ redirect: false, publish: true });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle `submit as draft` button click.
|
||||
const handleSubmitDraftBtnClick = (event) => {
|
||||
saveInvoke(onSubmitClick, event, {
|
||||
redirect: true,
|
||||
publish: false,
|
||||
});
|
||||
setSubmitPayload({ redirect: true, publish: false });
|
||||
};
|
||||
|
||||
// Handle `submit as draft & new` button click.
|
||||
const handleSubmitDraftAndNewBtnClick = (event) => {
|
||||
onSubmitForm();
|
||||
saveInvoke(onSubmitClick, event, {
|
||||
redirect: false,
|
||||
publish: false,
|
||||
resetForm: true,
|
||||
});
|
||||
setSubmitPayload({ redirect: false, publish: false, resetForm: true });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handles submit as draft & continue editing button click.
|
||||
const handleSubmitDraftContinueEditingBtnClick = (event) => {
|
||||
onSubmitForm();
|
||||
saveInvoke(onSubmitClick, event, {
|
||||
redirect: false,
|
||||
publish: false,
|
||||
});
|
||||
setSubmitPayload({ redirect: false, publish: false });
|
||||
submitForm();
|
||||
};
|
||||
|
||||
// Handle cancel button action click.
|
||||
const handleCancelBtnClick = (event) => {
|
||||
saveInvoke(onCancelClick, event);
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
const handleClearBtnClick = (event) => {
|
||||
// saveInvoke(onClearClick, event);
|
||||
onResetForm();
|
||||
};
|
||||
const handleClearBtnClick = (event) => {};
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Formik, Form } from 'formik';
|
||||
import moment from 'moment';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { pick, defaultTo } from 'lodash';
|
||||
import { defaultTo, isEmpty } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
@@ -15,56 +14,30 @@ import {
|
||||
import MakeJournalEntriesHeader from './MakeJournalEntriesHeader';
|
||||
import MakeJournalFormFloatingActions from './MakeJournalFormFloatingActions';
|
||||
import MakeJournalEntriesField from './MakeJournalEntriesField';
|
||||
import MakeJournalNumberWatcher from './MakeJournalNumberWatcher';
|
||||
import MakeJournalFormFooter from './MakeJournalFormFooter';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
|
||||
import AppToaster from 'components/AppToaster';
|
||||
import withMediaActions from 'containers/Media/withMediaActions';
|
||||
import { compose, orderingLinesIndexes, transactionNumber } from 'utils';
|
||||
import {
|
||||
compose,
|
||||
repeatValue,
|
||||
orderingLinesIndexes,
|
||||
defaultToTransform,
|
||||
transactionNumber,
|
||||
} from 'utils';
|
||||
import { transformErrors } from './utils';
|
||||
transformErrors,
|
||||
transformToEditForm,
|
||||
defaultManualJournal,
|
||||
} from './utils';
|
||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
|
||||
const defaultEntry = {
|
||||
index: 0,
|
||||
account_id: '',
|
||||
credit: '',
|
||||
debit: '',
|
||||
contact_id: '',
|
||||
note: '',
|
||||
};
|
||||
const defaultInitialValues = {
|
||||
journal_number: '',
|
||||
journal_type: 'Journal',
|
||||
date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
description: '',
|
||||
reference: '',
|
||||
currency_code: '',
|
||||
publish: '',
|
||||
entries: [...repeatValue(defaultEntry, 4)],
|
||||
};
|
||||
|
||||
/**
|
||||
* Journal entries form.
|
||||
*/
|
||||
function MakeJournalEntriesForm({
|
||||
// #withDashboard
|
||||
changePageTitle,
|
||||
changePageSubtitle,
|
||||
|
||||
// #withSettings
|
||||
journalNextNumber,
|
||||
journalNumberPrefix,
|
||||
baseCurrency,
|
||||
}) {
|
||||
// Journal form context.
|
||||
const {
|
||||
createJournalMutate,
|
||||
editJournalMutate,
|
||||
@@ -81,58 +54,23 @@ function MakeJournalEntriesForm({
|
||||
journalNumberPrefix,
|
||||
journalNextNumber,
|
||||
);
|
||||
// Changes the page title based on the form in new and edit mode.
|
||||
useEffect(() => {
|
||||
const transactionNumber = manualJournal
|
||||
? manualJournal.journal_number
|
||||
: journalNumber;
|
||||
|
||||
if (isNewMode) {
|
||||
changePageTitle(formatMessage({ id: 'new_journal' }));
|
||||
} else {
|
||||
changePageTitle(formatMessage({ id: 'edit_journal' }));
|
||||
}
|
||||
changePageSubtitle(
|
||||
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
|
||||
);
|
||||
}, [
|
||||
changePageTitle,
|
||||
changePageSubtitle,
|
||||
journalNumber,
|
||||
manualJournal,
|
||||
formatMessage,
|
||||
isNewMode,
|
||||
]);
|
||||
|
||||
// Form initial values.
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
...(manualJournal
|
||||
...(!isEmpty(manualJournal)
|
||||
? {
|
||||
...pick(manualJournal, Object.keys(defaultInitialValues)),
|
||||
entries: manualJournal.entries.map((entry) => ({
|
||||
...pick(entry, Object.keys(defaultEntry)),
|
||||
})),
|
||||
...transformToEditForm(manualJournal),
|
||||
}
|
||||
: {
|
||||
...defaultInitialValues,
|
||||
...defaultManualJournal,
|
||||
journal_number: defaultTo(journalNumber, ''),
|
||||
currency_code: defaultTo(baseCurrency, ''),
|
||||
entries: orderingLinesIndexes(defaultInitialValues.entries),
|
||||
entries: orderingLinesIndexes(defaultManualJournal.entries),
|
||||
}),
|
||||
}),
|
||||
[manualJournal, baseCurrency, journalNumber],
|
||||
);
|
||||
|
||||
// Handle journal number field change.
|
||||
const handleJournalNumberChanged = useCallback(
|
||||
(journalNumber) => {
|
||||
changePageSubtitle(
|
||||
defaultToTransform(journalNumber, `No. ${journalNumber}`, ''),
|
||||
);
|
||||
},
|
||||
[changePageSubtitle],
|
||||
);
|
||||
|
||||
// Handle the form submiting.
|
||||
const handleSubmit = (values, { setErrors, setSubmitting, resetForm }) => {
|
||||
setSubmitting(true);
|
||||
@@ -170,8 +108,12 @@ function MakeJournalEntriesForm({
|
||||
const form = { ...values, publish: submitPayload.publish, entries };
|
||||
|
||||
// Handle the request error.
|
||||
const handleError = (error) => {
|
||||
transformErrors(error, { setErrors });
|
||||
const handleError = ({
|
||||
response: {
|
||||
data: { errors },
|
||||
},
|
||||
}) => {
|
||||
transformErrors(errors, { setErrors });
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
@@ -201,7 +143,7 @@ function MakeJournalEntriesForm({
|
||||
if (isNewMode) {
|
||||
createJournalMutate(form).then(handleSuccess).catch(handleError);
|
||||
} else {
|
||||
editJournalMutate(manualJournal.id, form)
|
||||
editJournalMutate([manualJournal.id, form])
|
||||
.then(handleSuccess)
|
||||
.catch(handleError);
|
||||
}
|
||||
@@ -221,11 +163,8 @@ function MakeJournalEntriesForm({
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Form>
|
||||
<MakeJournalEntriesHeader
|
||||
onJournalNumberChanged={handleJournalNumberChanged}
|
||||
/>
|
||||
<MakeJournalNumberWatcher journalNumber={journalNumber} />
|
||||
<MakeJournalEntriesField defaultRow={defaultEntry} />
|
||||
<MakeJournalEntriesHeader />
|
||||
<MakeJournalEntriesField />
|
||||
<MakeJournalFormFooter />
|
||||
<MakeJournalFormFloatingActions />
|
||||
</Form>
|
||||
@@ -235,7 +174,6 @@ function MakeJournalEntriesForm({
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withMediaActions,
|
||||
withSettings(({ manualJournalsSettings, organizationSettings }) => ({
|
||||
journalNextNumber: parseInt(manualJournalsSettings?.nextNumber, 10),
|
||||
|
||||
@@ -1,62 +1,20 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import MakeJournalEntriesForm from './MakeJournalEntriesForm';
|
||||
import { MakeJournalProvider } from './MakeJournalProvider';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
import 'style/pages/ManualJournal/MakeJournal.scss';
|
||||
|
||||
/**
|
||||
* Make journal entries page.
|
||||
*/
|
||||
function MakeJournalEntriesPage({
|
||||
// #withDashboardActions
|
||||
setSidebarShrink,
|
||||
resetSidebarPreviousExpand,
|
||||
setDashboardBackLink,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
export default function MakeJournalEntriesPage() {
|
||||
const { id: journalId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
// Shrink the sidebar by foce.
|
||||
setSidebarShrink();
|
||||
// Show the back link on dashboard topbar.
|
||||
setDashboardBackLink('/manual-journals');
|
||||
|
||||
return () => {
|
||||
// Reset the sidebar to the previous status.
|
||||
resetSidebarPreviousExpand();
|
||||
// Hide the back link on dashboard topbar.
|
||||
setDashboardBackLink(false);
|
||||
};
|
||||
}, [resetSidebarPreviousExpand, setDashboardBackLink, setSidebarShrink]);
|
||||
|
||||
const handleFormSubmit = useCallback(
|
||||
(payload) => {
|
||||
payload.redirect && history.push('/manual-journals');
|
||||
},
|
||||
[history],
|
||||
);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
history.goBack();
|
||||
}, [history]);
|
||||
|
||||
|
||||
return (
|
||||
<MakeJournalProvider journalId={journalId}>
|
||||
<MakeJournalEntriesForm
|
||||
onFormSubmit={handleFormSubmit}
|
||||
onCancelForm={handleCancel}
|
||||
/>
|
||||
<MakeJournalEntriesForm />
|
||||
</MakeJournalProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
)(MakeJournalEntriesPage);
|
||||
|
||||
@@ -1,205 +1,111 @@
|
||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { omit } from 'lodash';
|
||||
import { saveInvoke } from 'utils';
|
||||
import {
|
||||
AccountsListFieldCell,
|
||||
MoneyFieldCell,
|
||||
InputGroupCell,
|
||||
ContactsListFieldCell,
|
||||
} from 'components/DataTableCells';
|
||||
import {
|
||||
ContactHeaderCell,
|
||||
ActionsCellRenderer,
|
||||
TotalAccountCellRenderer,
|
||||
TotalCreditDebitCellRenderer,
|
||||
NoteCellRenderer,
|
||||
} from './components';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { saveInvoke, removeRowsByIndex } from 'utils';
|
||||
import { DataTableEditable } from 'components';
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { updateDataReducer } from './utils';
|
||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
import { useJournalTableEntriesColumns } from './components';
|
||||
|
||||
import JournalDeleteEntriesAlert from 'containers/Alerts/ManualJournals/JournalDeleteEntriesAlert';
|
||||
import { compose } from 'redux';
|
||||
import { repeatValue } from 'utils';
|
||||
|
||||
/**
|
||||
* Make journal entries table component.
|
||||
*/
|
||||
export default function MakeJournalEntriesTable({
|
||||
function MakeJournalEntriesTable({
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #ownPorps
|
||||
onClickRemoveRow,
|
||||
onClickAddNewRow,
|
||||
onClickClearAllLines,
|
||||
onChange,
|
||||
entries,
|
||||
defaultEntry,
|
||||
error,
|
||||
initialLinesNumber = 4,
|
||||
minLinesNumber = 4,
|
||||
}) {
|
||||
const [rows, setRows] = useState([]);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const { accounts, customers } = useMakeJournalFormContext();
|
||||
|
||||
useEffect(() => {
|
||||
setRows([...entries.map((e) => ({ ...e, rowType: 'editor' }))]);
|
||||
}, [entries, setRows]);
|
||||
|
||||
// Final table rows editor rows and total and final blank row.
|
||||
const tableRows = useMemo(() => [...rows, { rowType: 'total' }], [rows]);
|
||||
|
||||
// Memorized data table columns.
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: '#',
|
||||
accessor: 'index',
|
||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
||||
className: 'index',
|
||||
width: 40,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
sticky: 'left',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'account' }),
|
||||
id: 'account_id',
|
||||
accessor: 'account_id',
|
||||
Cell: TotalAccountCellRenderer(AccountsListFieldCell),
|
||||
className: 'account',
|
||||
disableSortBy: true,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'credit_currency' }, { currency: 'USD' }),
|
||||
accessor: 'credit',
|
||||
Cell: TotalCreditDebitCellRenderer(MoneyFieldCell, 'credit'),
|
||||
className: 'credit',
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'debit_currency' }, { currency: 'USD' }),
|
||||
accessor: 'debit',
|
||||
Cell: TotalCreditDebitCellRenderer(MoneyFieldCell, 'debit'),
|
||||
className: 'debit',
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
Header: ContactHeaderCell,
|
||||
id: 'contact_id',
|
||||
accessor: 'contact_id',
|
||||
Cell: NoteCellRenderer(ContactsListFieldCell),
|
||||
className: 'contact',
|
||||
disableSortBy: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'note' }),
|
||||
accessor: 'note',
|
||||
Cell: NoteCellRenderer(InputGroupCell),
|
||||
disableSortBy: true,
|
||||
className: 'note',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
accessor: 'action',
|
||||
Cell: ActionsCellRenderer,
|
||||
className: 'actions',
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 45,
|
||||
},
|
||||
],
|
||||
[formatMessage],
|
||||
);
|
||||
const columns = useJournalTableEntriesColumns();
|
||||
|
||||
// Handles click new line.
|
||||
const onClickNewRow = () => {
|
||||
saveInvoke(onClickAddNewRow);
|
||||
const newRows = [...entries, defaultEntry];
|
||||
saveInvoke(onChange, newRows);
|
||||
};
|
||||
|
||||
// Handles update datatable data.
|
||||
const handleUpdateData = (rowIndex, columnId, value) => {
|
||||
const newRows = updateDataReducer(rows, rowIndex, columnId, value);
|
||||
|
||||
saveInvoke(
|
||||
onChange,
|
||||
newRows
|
||||
.filter((row) => row.rowType === 'editor')
|
||||
.map((row) => ({
|
||||
...omit(row, ['rowType']),
|
||||
})),
|
||||
);
|
||||
const newRows = updateDataReducer(entries, rowIndex, columnId, value);
|
||||
saveInvoke(onChange, newRows);
|
||||
};
|
||||
|
||||
// Handle remove datatable row.
|
||||
const handleRemoveRow = (rowIndex) => {
|
||||
// Can't continue if there is just one row line or less.
|
||||
if (rows.length <= 2) {
|
||||
return;
|
||||
}
|
||||
const removeIndex = parseInt(rowIndex, 10);
|
||||
const newRows = rows.filter((row, index) => index !== removeIndex);
|
||||
|
||||
saveInvoke(
|
||||
onChange,
|
||||
newRows
|
||||
.filter((row) => row.rowType === 'editor')
|
||||
.map((row) => ({ ...omit(row, ['rowType']) })),
|
||||
);
|
||||
saveInvoke(onClickRemoveRow, removeIndex);
|
||||
const newRows = removeRowsByIndex(entries, rowIndex);
|
||||
saveInvoke(onChange, newRows);
|
||||
};
|
||||
|
||||
// Rows class names callback.
|
||||
const rowClassNames = useCallback(
|
||||
(row) => ({
|
||||
'row--total': rows.length === row.index + 2,
|
||||
}),
|
||||
[rows],
|
||||
);
|
||||
|
||||
// Handle clear all lines action.
|
||||
const handleClickClearAllLines = () => {
|
||||
saveInvoke(onClickClearAllLines);
|
||||
openAlert('make-journal-delete-all-entries');
|
||||
};
|
||||
|
||||
// Handle clear all lines alaert confirm.
|
||||
const handleCofirmClearEntriesAlert = () => {
|
||||
const newRows = repeatValue(defaultEntry, initialLinesNumber);
|
||||
saveInvoke(onChange, newRows);
|
||||
};
|
||||
|
||||
return (
|
||||
<DataTableEditable
|
||||
columns={columns}
|
||||
data={tableRows}
|
||||
rowClassNames={rowClassNames}
|
||||
sticky={true}
|
||||
totalRow={true}
|
||||
payload={{
|
||||
accounts,
|
||||
errors: error,
|
||||
updateData: handleUpdateData,
|
||||
removeRow: handleRemoveRow,
|
||||
contacts: [
|
||||
...customers.map((customer) => ({
|
||||
<>
|
||||
<DataTableEditable
|
||||
columns={columns}
|
||||
data={entries}
|
||||
sticky={true}
|
||||
totalRow={true}
|
||||
payload={{
|
||||
accounts,
|
||||
errors: error,
|
||||
updateData: handleUpdateData,
|
||||
removeRow: handleRemoveRow,
|
||||
contacts: customers.map((customer) => ({
|
||||
...customer,
|
||||
contact_type: 'customer',
|
||||
})),
|
||||
],
|
||||
autoFocus: ['account_id', 0],
|
||||
}}
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--new-line'}
|
||||
onClick={onClickNewRow}
|
||||
>
|
||||
<T id={'new_lines'} />
|
||||
</Button>
|
||||
autoFocus: ['account_id', 0],
|
||||
}}
|
||||
actions={
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
small={true}
|
||||
className={'button--secondary button--clear-lines ml1'}
|
||||
onClick={handleClickClearAllLines}
|
||||
>
|
||||
<T id={'clear_all_lines'} />
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<JournalDeleteEntriesAlert
|
||||
name={'make-journal-delete-all-entries'}
|
||||
onConfirm={handleCofirmClearEntriesAlert}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withAlertActions)(MakeJournalEntriesTable);
|
||||
|
||||
@@ -80,8 +80,8 @@ export default function MakeJournalFloatingAction() {
|
||||
<If condition={!manualJournal || !manualJournal?.is_published}>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
onClick={handleSubmitPublishBtnClick}
|
||||
text={<T id={'save_publish'} />}
|
||||
@@ -146,6 +146,7 @@ export default function MakeJournalFloatingAction() {
|
||||
<If condition={manualJournal && manualJournal?.is_published}>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
intent={Intent.PRIMARY}
|
||||
onClick={handleSubmitPublishBtnClick}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { useFormikContext } from 'formik';
|
||||
|
||||
import withManualJournalsActions from './withManualJournalsActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withManualJournals from './withManualJournals';
|
||||
|
||||
import { defaultToTransform } from 'utils';
|
||||
|
||||
/**
|
||||
* Journal number chaلing watcher.
|
||||
*/
|
||||
function MakeJournalNumberChangingWatcher({
|
||||
// #withDashboardActions
|
||||
changePageSubtitle,
|
||||
|
||||
// #withManualJournals
|
||||
journalNumberChanged,
|
||||
|
||||
// #withManualJournalsActions
|
||||
setJournalNumberChanged,
|
||||
|
||||
// #ownProps
|
||||
journalNumber,
|
||||
}) {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
// Observes journal number settings changes.
|
||||
useEffect(() => {
|
||||
if (journalNumberChanged) {
|
||||
setFieldValue('journal_number', journalNumber);
|
||||
changePageSubtitle(
|
||||
defaultToTransform(journalNumber, `No. ${journalNumber}`, ''),
|
||||
);
|
||||
setJournalNumberChanged(false);
|
||||
}
|
||||
}, [
|
||||
journalNumber,
|
||||
journalNumberChanged,
|
||||
setJournalNumberChanged,
|
||||
setFieldValue,
|
||||
changePageSubtitle,
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withManualJournals(({ journalNumberChanged }) => ({
|
||||
journalNumberChanged,
|
||||
})),
|
||||
withManualJournalsActions,
|
||||
withDashboardActions,
|
||||
)(MakeJournalNumberChangingWatcher);
|
||||
@@ -42,6 +42,7 @@ function MakeJournalProvider({ journalId, ...props }) {
|
||||
// Loading the journal settings.
|
||||
const { isFetching: isSettingsLoading } = useSettings();
|
||||
|
||||
// Submit form payload.
|
||||
const [submitPayload, setSubmitPayload] = useState({});
|
||||
|
||||
const provider = {
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Position } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { Money, Hint } from 'components';
|
||||
import { Intent, Position, Button, Tooltip } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { Icon, Money, Hint } from 'components';
|
||||
import {
|
||||
AccountsListFieldCell,
|
||||
MoneyFieldCell,
|
||||
InputGroupCell,
|
||||
ContactsListFieldCell,
|
||||
} from 'components/DataTableCells';
|
||||
import { safeSumBy } from 'utils';
|
||||
|
||||
/**
|
||||
* Contact header cell.
|
||||
@@ -18,74 +25,138 @@ export function ContactHeaderCell() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total text cell renderer.
|
||||
/**
|
||||
* Account footer cell.
|
||||
*/
|
||||
export const TotalAccountCellRenderer = (chainedComponent) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
return <span>{'Total USD'}</span>;
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
function AccountFooterCell() {
|
||||
return <span>{'Total USD'}</span>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total credit/debit cell renderer.
|
||||
* Total credit table footer cell.
|
||||
*/
|
||||
export const TotalCreditDebitCellRenderer = (chainedComponent, 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;
|
||||
function TotalCreditFooterCell({ rows }) {
|
||||
const credit = safeSumBy(rows, 'original.credit');
|
||||
|
||||
return computed;
|
||||
}, 0);
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Money amount={total} currency={'USD'} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
|
||||
export const NoteCellRenderer = (chainedComponent) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
return '';
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
return (
|
||||
<span>
|
||||
<Money amount={credit} currency={'USD'} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total debit table footer cell.
|
||||
*/
|
||||
function TotalDebitFooterCell({ rows }) {
|
||||
const debit = safeSumBy(rows, 'original.debit');
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Money amount={debit} currency={'USD'} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Actions cell renderer.
|
||||
*/
|
||||
export const ActionsCellRenderer = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value: initialValue },
|
||||
data,
|
||||
payload,
|
||||
}) => {
|
||||
if (data.length <= index + 1) {
|
||||
return '';
|
||||
}
|
||||
const onClickRemoveRole = () => {
|
||||
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="ml2"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value: initialValue },
|
||||
data,
|
||||
payload,
|
||||
}) => {
|
||||
const onClickRemoveRole = () => {
|
||||
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="ml2"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve columns of make journal entries table.
|
||||
*/
|
||||
export const useJournalTableEntriesColumns = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: '#',
|
||||
accessor: 'index',
|
||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
||||
className: 'index',
|
||||
width: 40,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
sticky: 'left',
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'account' }),
|
||||
id: 'account_id',
|
||||
accessor: 'account_id',
|
||||
Cell: AccountsListFieldCell,
|
||||
Footer: AccountFooterCell,
|
||||
className: 'account',
|
||||
disableSortBy: true,
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'credit_currency' }, { currency: 'USD' }),
|
||||
accessor: 'credit',
|
||||
Cell: MoneyFieldCell,
|
||||
Footer: TotalCreditFooterCell,
|
||||
className: 'credit',
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'debit_currency' }, { currency: 'USD' }),
|
||||
accessor: 'debit',
|
||||
Cell: MoneyFieldCell,
|
||||
Footer: TotalDebitFooterCell,
|
||||
className: 'debit',
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
Header: ContactHeaderCell,
|
||||
id: 'contact_id',
|
||||
accessor: 'contact_id',
|
||||
Cell: ContactsListFieldCell,
|
||||
className: 'contact',
|
||||
disableSortBy: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'note' }),
|
||||
accessor: 'note',
|
||||
Cell: InputGroupCell,
|
||||
disableSortBy: true,
|
||||
className: 'note',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
accessor: 'action',
|
||||
Cell: ActionsCellRenderer,
|
||||
className: 'actions',
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 45,
|
||||
},
|
||||
],
|
||||
[formatMessage],
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { sumBy, setWith, toSafeInteger, get } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import { transformUpdatedRows } from 'utils';
|
||||
import { transformUpdatedRows, repeatValue, transformToForm } from 'utils';
|
||||
import { AppToaster } from 'components';
|
||||
import { formatMessage } from 'services/intl';
|
||||
|
||||
@@ -17,6 +18,44 @@ const ERROR = {
|
||||
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
|
||||
};
|
||||
|
||||
export const MIN_LINES_NUMBER = 4;
|
||||
|
||||
export const defaultEntry = {
|
||||
index: 0,
|
||||
account_id: '',
|
||||
credit: '',
|
||||
debit: '',
|
||||
contact_id: '',
|
||||
note: '',
|
||||
};
|
||||
|
||||
export const defaultManualJournal = {
|
||||
journal_number: '',
|
||||
journal_type: 'Journal',
|
||||
date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
description: '',
|
||||
reference: '',
|
||||
currency_code: '',
|
||||
publish: '',
|
||||
entries: [...repeatValue(defaultEntry, 4)],
|
||||
};
|
||||
|
||||
// Transform to edit form.
|
||||
export function transformToEditForm(manualJournal) {
|
||||
return {
|
||||
...transformToForm(manualJournal, defaultManualJournal),
|
||||
entries: [
|
||||
...manualJournal.entries.map((entry) => ({
|
||||
...transformToForm(entry, defaultManualJournal.entries[0]),
|
||||
})),
|
||||
...repeatValue(
|
||||
defaultEntry,
|
||||
Math.max(MIN_LINES_NUMBER - manualJournal.entries.length, 0),
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Entries adjustment.
|
||||
*/
|
||||
@@ -30,6 +69,9 @@ function adjustmentEntries(entries) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const updateDataReducer = (rows, rowIndex, columnId, value) => {
|
||||
let newRows = transformUpdatedRows(rows, rowIndex, columnId, value);
|
||||
|
||||
@@ -59,7 +101,9 @@ export const updateDataReducer = (rows, rowIndex, columnId, value) => {
|
||||
return newRows;
|
||||
};
|
||||
|
||||
// Transform API errors in toasts messages.
|
||||
/**
|
||||
* Transform API errors in toasts messages.
|
||||
*/
|
||||
export const transformErrors = (resErrors, { setErrors, errors }) => {
|
||||
const getError = (errorType) => resErrors.find((e) => e.type === errorType);
|
||||
const toastMessages = [];
|
||||
|
||||
Reference in New Issue
Block a user