mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
- feat: Update react-query package to V 2.1.1.
- feat: Favicon setup. - feat: Fix accounts inactivate/activate 1 account. - feat: Seed accounts, expenses and manual journals resource fields. - feat: Validate make journal receivable/payable without contact. - feat: Validate make journal contact without receivable or payable. - feat: More components abstractions. - feat: Use reselect.js to memorize components properties. - fix: Journal type of manual journal. - fix: Sidebar style optimization. - fix: Data-table check-box style optimization. - fix: Data-table spinner style dimensions. - fix: Submit journal with contact_id and contact_type.
This commit is contained in:
@@ -82,6 +82,9 @@ function MakeJournalEntriesForm({
|
||||
journal_number: Yup.string()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'journal_number_' })),
|
||||
journal_type: Yup.string()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'journal_type' })),
|
||||
date: Yup.date()
|
||||
.required()
|
||||
.label(formatMessage({ id: 'date' })),
|
||||
@@ -112,6 +115,12 @@ function MakeJournalEntriesForm({
|
||||
|
||||
const [payload, setPayload] = useState({});
|
||||
|
||||
const reorderingEntriesIndex = (entries) =>
|
||||
entries.map((entry, index) => ({
|
||||
...entry,
|
||||
index: index + 1,
|
||||
}));
|
||||
|
||||
const defaultEntry = useMemo(
|
||||
() => ({
|
||||
account_id: null,
|
||||
@@ -126,6 +135,7 @@ function MakeJournalEntriesForm({
|
||||
const defaultInitialValues = useMemo(
|
||||
() => ({
|
||||
journal_number: '',
|
||||
journal_type: 'Journal',
|
||||
date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
description: '',
|
||||
reference: '',
|
||||
@@ -145,6 +155,7 @@ function MakeJournalEntriesForm({
|
||||
}
|
||||
: {
|
||||
...defaultInitialValues,
|
||||
entries: reorderingEntriesIndex(defaultInitialValues.entries),
|
||||
}),
|
||||
}),
|
||||
[manualJournal, defaultInitialValues, defaultEntry],
|
||||
@@ -160,6 +171,45 @@ function MakeJournalEntriesForm({
|
||||
: [];
|
||||
}, [manualJournal]);
|
||||
|
||||
const transformErrors = (errors, { setErrors }) => {
|
||||
const hasError = (errorType) => errors.some((e) => e.type === errorType);
|
||||
|
||||
if (hasError('CUSTOMERS.NOT.WITH.RECEIVABLE.ACCOUNT')) {
|
||||
AppToaster.show({
|
||||
message:
|
||||
'customers_should_assign_with_receivable_account_only',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
if (hasError('VENDORS.NOT.WITH.PAYABLE.ACCOUNT')) {
|
||||
AppToaster.show({
|
||||
message: 'vendors_should_assign_with_payable_account_only',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
if (hasError('RECEIVABLE.ENTRIES.HAS.NO.CUSTOMERS')) {
|
||||
AppToaster.show({
|
||||
message:
|
||||
'entries_with_receivable_account_no_assigned_with_customers',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
if (hasError('PAYABLE.ENTRIES.HAS.NO.VENDORS')) {
|
||||
AppToaster.show({
|
||||
message:
|
||||
'entries_with_payable_account_no_assigned_with_vendors',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
if (hasError('JOURNAL.NUMBER.ALREADY.EXISTS')) {
|
||||
setErrors({
|
||||
journal_number: formatMessage({
|
||||
id: 'journal_number_is_already_used',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
validationSchema,
|
||||
@@ -181,7 +231,19 @@ function MakeJournalEntriesForm({
|
||||
// Validate the total credit should be eqials total debit.
|
||||
if (totalCredit !== totalDebit) {
|
||||
AppToaster.show({
|
||||
message: formatMessage({ id: 'credit_and_debit_not_equal' }),
|
||||
message: formatMessage({
|
||||
id: 'should_total_of_credit_and_debit_be_equal',
|
||||
}),
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
setSubmitting(false);
|
||||
return;
|
||||
} else if (totalCredit === 0 || totalDebit === 0) {
|
||||
AppToaster.show({
|
||||
message: formatMessage({
|
||||
id: 'should_total_of_credit_and_debit_be_bigger_then_zero',
|
||||
}),
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
setSubmitting(false);
|
||||
return;
|
||||
@@ -209,15 +271,7 @@ function MakeJournalEntriesForm({
|
||||
resolve(response);
|
||||
})
|
||||
.catch((errors) => {
|
||||
if (
|
||||
errors.find((e) => e.type === 'JOURNAL.NUMBER.ALREADY.EXISTS')
|
||||
) {
|
||||
setErrors({
|
||||
journal_number: formatMessage({
|
||||
id: 'journal_number_is_already_used',
|
||||
}),
|
||||
});
|
||||
}
|
||||
transformErrors(errors, { setErrors });
|
||||
setSubmitting(false);
|
||||
});
|
||||
} else {
|
||||
@@ -237,15 +291,7 @@ function MakeJournalEntriesForm({
|
||||
resolve(response);
|
||||
})
|
||||
.catch((errors) => {
|
||||
if (
|
||||
errors.find((e) => e.type === 'JOURNAL.NUMBER.ALREADY.EXISTS')
|
||||
) {
|
||||
setErrors({
|
||||
journal_number: formatMessage({
|
||||
id: 'journal_number_is_already_used',
|
||||
}),
|
||||
});
|
||||
}
|
||||
transformErrors(errors, { setErrors });
|
||||
setSubmitting(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,78 +1,102 @@
|
||||
import React, {useMemo, useCallback} from 'react';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import {
|
||||
InputGroup,
|
||||
FormGroup,
|
||||
Intent,
|
||||
Position,
|
||||
Classes,
|
||||
} from '@blueprintjs/core';
|
||||
import {DateInput} from '@blueprintjs/datetime';
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import {Row, Col} from 'react-grid-system';
|
||||
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';
|
||||
import ErrorMessage from 'components/ErrorMessage';
|
||||
import classNames from 'classnames';
|
||||
import { momentFormatter } from 'utils';
|
||||
import {
|
||||
CurrenciesSelectList,
|
||||
ErrorMessage,
|
||||
Hint,
|
||||
FieldHint,
|
||||
FieldRequiredHint,
|
||||
} from 'components';
|
||||
|
||||
export default function MakeJournalEntriesHeader({
|
||||
formik: { errors, touched, setFieldValue, getFieldProps }
|
||||
formik: { errors, touched, values, setFieldValue, getFieldProps },
|
||||
}) {
|
||||
|
||||
const handleDateChange = useCallback((date) => {
|
||||
const formatted = moment(date).format('YYYY-MM-DD');
|
||||
setFieldValue('date', formatted);
|
||||
}, [setFieldValue]);
|
||||
|
||||
const infoIcon = useMemo(() =>
|
||||
(<Icon icon="info-circle" iconSize={12} />), []);
|
||||
const handleDateChange = useCallback(
|
||||
(date) => {
|
||||
const formatted = moment(date).format('YYYY-MM-DD');
|
||||
setFieldValue('date', formatted);
|
||||
},
|
||||
[setFieldValue],
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="make-journal-entries__header">
|
||||
<Row>
|
||||
<Col width={260}>
|
||||
<FormGroup
|
||||
label={<T id={'journal_number'}/>}
|
||||
labelInfo={infoIcon}
|
||||
label={<T id={'journal_number'} />}
|
||||
labelInfo={
|
||||
<>
|
||||
<FieldRequiredHint />
|
||||
<FieldHint />
|
||||
</>
|
||||
}
|
||||
className={'form-group--journal-number'}
|
||||
intent={(errors.journal_number && touched.journal_number) && Intent.DANGER}
|
||||
helperText={<ErrorMessage name="journal_number" {...{errors, touched}} />}
|
||||
fill={true}>
|
||||
|
||||
intent={
|
||||
errors.journal_number && touched.journal_number && Intent.DANGER
|
||||
}
|
||||
helperText={
|
||||
<ErrorMessage name="journal_number" {...{ errors, touched }} />
|
||||
}
|
||||
fill={true}
|
||||
>
|
||||
<InputGroup
|
||||
intent={(errors.journal_number && touched.journal_number) && Intent.DANGER}
|
||||
intent={
|
||||
errors.journal_number && touched.journal_number && Intent.DANGER
|
||||
}
|
||||
fill={true}
|
||||
{...getFieldProps('journal_number')} />
|
||||
{...getFieldProps('journal_number')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col width={220}>
|
||||
<FormGroup
|
||||
label={<T id={'date'}/>}
|
||||
intent={(errors.date && touched.date) && Intent.DANGER}
|
||||
helperText={<ErrorMessage name="date" {...{errors, touched}} />}
|
||||
minimal={true}>
|
||||
|
||||
label={<T id={'date'} />}
|
||||
labelInfo={<FieldRequiredHint />}
|
||||
intent={errors.date && touched.date && Intent.DANGER}
|
||||
helperText={<ErrorMessage name="date" {...{ errors, touched }} />}
|
||||
minimal={true}
|
||||
>
|
||||
<DateInput
|
||||
{...momentFormatter('YYYY/MM/DD')}
|
||||
defaultValue={new Date()}
|
||||
onChange={handleDateChange}
|
||||
popoverProps={{ position: Position.BOTTOM }} />
|
||||
popoverProps={{
|
||||
position: Position.BOTTOM,
|
||||
minimal: true,
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col width={400}>
|
||||
<FormGroup
|
||||
label={<T id={'description'}/>}
|
||||
label={<T id={'description'} />}
|
||||
className={'form-group--description'}
|
||||
intent={(errors.name && touched.name) && Intent.DANGER}
|
||||
helperText={<ErrorMessage name="description" {...{errors, touched}} />}
|
||||
fill={true}>
|
||||
|
||||
intent={errors.name && touched.name && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name="description" {...{ errors, touched }} />
|
||||
}
|
||||
fill={true}
|
||||
>
|
||||
<InputGroup
|
||||
intent={(errors.name && touched.name) && Intent.DANGER}
|
||||
intent={errors.name && touched.name && Intent.DANGER}
|
||||
fill={true}
|
||||
{...getFieldProps('description')} />
|
||||
{...getFieldProps('description')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -80,24 +104,51 @@ export default function MakeJournalEntriesHeader({
|
||||
<Row>
|
||||
<Col width={260}>
|
||||
<FormGroup
|
||||
label={<T id={'reference'}/>}
|
||||
labelInfo={infoIcon}
|
||||
label={<T id={'reference'} />}
|
||||
labelInfo={
|
||||
<Hint
|
||||
content={<T id={'journal_reference_hint'} />}
|
||||
position={Position.RIGHT}
|
||||
/>
|
||||
}
|
||||
className={'form-group--reference'}
|
||||
intent={(errors.reference && touched.reference) && Intent.DANGER}
|
||||
helperText={<ErrorMessage name="reference" {...{errors, touched}} />}
|
||||
fill={true}>
|
||||
|
||||
intent={errors.reference && touched.reference && Intent.DANGER}
|
||||
helperText={
|
||||
<ErrorMessage name="reference" {...{ errors, touched }} />
|
||||
}
|
||||
fill={true}
|
||||
>
|
||||
<InputGroup
|
||||
intent={(errors.reference && touched.reference) && Intent.DANGER}
|
||||
intent={errors.reference && touched.reference && Intent.DANGER}
|
||||
fill={true}
|
||||
{...getFieldProps('reference')} />
|
||||
{...getFieldProps('reference')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col width={220}>
|
||||
<CurrenciesSelectList />
|
||||
<FormGroup
|
||||
label={<T id={'journal_type'} />}
|
||||
className={classNames(
|
||||
'form-group--account-type',
|
||||
'form-group--select-list',
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<InputGroup
|
||||
intent={
|
||||
errors.journal_type && touched.journal_type && Intent.DANGER
|
||||
}
|
||||
fill={true}
|
||||
{...getFieldProps('journal_type')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col width={230}>
|
||||
<CurrenciesSelectList className={Classes.FILL} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ import withCustomersActions from 'containers/Customers/withCustomersActions';
|
||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
||||
|
||||
import {compose} from 'utils';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function MakeJournalEntriesPage({
|
||||
// #withCustomersActions
|
||||
@@ -25,20 +24,26 @@ function MakeJournalEntriesPage({
|
||||
const history = useHistory();
|
||||
const { id } = useParams();
|
||||
|
||||
const fetchAccounts = useQuery('accounts-list',
|
||||
(key) => requestFetchAccounts());
|
||||
const fetchAccounts = useQuery('accounts-list', (key) =>
|
||||
requestFetchAccounts(),
|
||||
);
|
||||
|
||||
const fetchCustomers = useQuery('customers-list',
|
||||
(key) => requestFetchCustomers());
|
||||
const fetchCustomers = useQuery('customers-list', (key) =>
|
||||
requestFetchCustomers(),
|
||||
);
|
||||
|
||||
const fetchJournal = useQuery(
|
||||
id && ['manual-journal', id],
|
||||
(key, journalId) => requestFetchManualJournal(journalId));
|
||||
['manual-journal', id],
|
||||
(key, journalId) => requestFetchManualJournal(journalId),
|
||||
{ enabled: id && id },
|
||||
);
|
||||
|
||||
const handleFormSubmit = useCallback((payload) => {
|
||||
payload.redirect &&
|
||||
history.push('/manual-journals');
|
||||
}, [history]);
|
||||
const handleFormSubmit = useCallback(
|
||||
(payload) => {
|
||||
payload.redirect && history.push('/manual-journals');
|
||||
},
|
||||
[history],
|
||||
);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
history.push('/manual-journals');
|
||||
@@ -51,11 +56,13 @@ function MakeJournalEntriesPage({
|
||||
fetchAccounts.isFetching ||
|
||||
fetchCustomers.isFetching
|
||||
}
|
||||
name={'make-journal-page'}>
|
||||
name={'make-journal-page'}
|
||||
>
|
||||
<MakeJournalEntriesForm
|
||||
onFormSubmit={handleFormSubmit}
|
||||
manualJournalId={id}
|
||||
onCancelForm={handleCancel} />
|
||||
onCancelForm={handleCancel}
|
||||
/>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
@@ -64,4 +71,4 @@ export default compose(
|
||||
withAccountsActions,
|
||||
withCustomersActions,
|
||||
withManualJournalsActions,
|
||||
)(MakeJournalEntriesPage);
|
||||
)(MakeJournalEntriesPage);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { Button, Intent } from '@blueprintjs/core';
|
||||
import { Button, Tooltip, Position, Intent } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
@@ -31,14 +31,16 @@ const ActionsCellRenderer = ({
|
||||
payload.removeRow(index);
|
||||
};
|
||||
return (
|
||||
<Button
|
||||
icon={<Icon icon="times-circle" iconSize={14} />}
|
||||
iconSize={14}
|
||||
className="ml2"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -101,10 +103,18 @@ function MakeJournalEntriesTable({
|
||||
|
||||
// Handles update datatable data.
|
||||
const handleUpdateData = useCallback(
|
||||
(rowIndex, columnId, value) => {
|
||||
(rowIndex, columnIdOrBulk, value) => {
|
||||
const columnId = typeof columnIdOrBulk !== 'object'
|
||||
? columnIdOrBulk : null;
|
||||
|
||||
const updateTable = typeof columnIdOrBulk === 'object'
|
||||
? columnIdOrBulk : null;
|
||||
|
||||
const newData = updateTable ? updateTable : { [columnId]: value };
|
||||
|
||||
const newRows = rows.map((row, index) => {
|
||||
if (index === rowIndex) {
|
||||
return { ...rows[rowIndex], [columnId]: value };
|
||||
return { ...rows[rowIndex], ...newData };
|
||||
}
|
||||
return { ...row };
|
||||
});
|
||||
@@ -179,14 +189,22 @@ function MakeJournalEntriesTable({
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
Header: (<><T id={'contact'} /><Hint /></>),
|
||||
Header: (
|
||||
<>
|
||||
<T id={'contact'} />
|
||||
<Hint
|
||||
content={<T id={'contact_column_hint'} />}
|
||||
position={Position.LEFT_BOTTOM}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
id: 'contact_id',
|
||||
accessor: 'contact_id',
|
||||
Cell: NoteCellRenderer(ContactsListFieldCell),
|
||||
className: 'contact',
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
width: 180,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'note' }),
|
||||
@@ -235,7 +253,8 @@ function MakeJournalEntriesTable({
|
||||
removeRow: handleRemoveRow,
|
||||
contacts: [
|
||||
...customers.map((customer) => ({
|
||||
...customer, contact_type: 'customer',
|
||||
...customer,
|
||||
contact_type: 'customer',
|
||||
})),
|
||||
],
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
@@ -45,6 +45,7 @@ function ManualJournalActionsBar({
|
||||
onBulkDelete,
|
||||
}) {
|
||||
const { path } = useRouteMatch();
|
||||
const [filterCount, setFilterCount] = useState(0);
|
||||
const history = useHistory();
|
||||
|
||||
const viewsMenuItems = manualJournalsViews.map((view) => {
|
||||
@@ -65,6 +66,7 @@ function ManualJournalActionsBar({
|
||||
value: '',
|
||||
},
|
||||
onFilterChange: (filterConditions) => {
|
||||
setFilterCount(filterConditions.length || 0);
|
||||
addManualJournalsTableQueries({
|
||||
filter_roles: filterConditions || '',
|
||||
});
|
||||
@@ -112,7 +114,9 @@ function ManualJournalActionsBar({
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||
className={classNames(Classes.MINIMAL, 'button--filter', {
|
||||
'has-active-filters': filterCount > 0,
|
||||
})}
|
||||
text="Filter"
|
||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||
/>
|
||||
@@ -128,6 +132,11 @@ function ManualJournalActionsBar({
|
||||
/>
|
||||
</If>
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="print-16" iconSize={16} />}
|
||||
text={<T id={'print'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||
|
||||
@@ -15,68 +15,79 @@ import { withRouter, useParams } from 'react-router-dom';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import moment from 'moment';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import { DataTable, If, Money, Choose, Icon } from 'components';
|
||||
import { compose } from 'utils';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import { If, Money } from 'components';
|
||||
import DataTable from 'components/DataTable';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withViewDetails from 'containers/Views/withViewDetails';
|
||||
import withManualJournals from 'containers/Accounting/withManualJournals';
|
||||
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
||||
|
||||
function ManualJournalsDataTable({
|
||||
loading,
|
||||
/**
|
||||
* Status column accessor.
|
||||
*/
|
||||
function StatusAccessor(row) {
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={row.status}>
|
||||
<Tag minimal={true}>
|
||||
<T id={'published'} />
|
||||
</Tag>
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<Tag minimal={true} intent={Intent.WARNING}>
|
||||
<T id={'draft'} />
|
||||
</Tag>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note column accessor.
|
||||
*/
|
||||
function NoteAccessor(row) {
|
||||
return (
|
||||
<If condition={row.description}>
|
||||
<Tooltip
|
||||
className={Classes.TOOLTIP_INDICATOR}
|
||||
content={row.description}
|
||||
position={Position.LEFT_TOP}
|
||||
hoverOpenDelay={50}
|
||||
>
|
||||
<Icon icon={'file-alt'} iconSize={16} />
|
||||
</Tooltip>
|
||||
</If>
|
||||
);
|
||||
}
|
||||
|
||||
function ManualJournalsDataTable({
|
||||
// #withManualJournals
|
||||
manualJournalsCurrentPage,
|
||||
manualJournalsLoading,
|
||||
manualJournalsPagination,
|
||||
manualJournalsTableQuery,
|
||||
|
||||
// #withDashboardActions
|
||||
changeCurrentView,
|
||||
changePageSubtitle,
|
||||
setTopbarEditView,
|
||||
|
||||
// #withViewDetails
|
||||
viewId,
|
||||
viewMeta,
|
||||
|
||||
onFetchData,
|
||||
onEditJournal,
|
||||
onDeleteJournal,
|
||||
onPublishJournal,
|
||||
onSelectedRowsChange,
|
||||
}) {
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
const [initialMount, setInitialMount] = useState(false);
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (!manualJournalsLoading) {
|
||||
setInitialMount(true);
|
||||
}
|
||||
}, [manualJournalsLoading, setInitialMount]);
|
||||
useEffect(() => {
|
||||
setIsMounted(false);
|
||||
}, [customViewId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (customViewId) {
|
||||
changeCurrentView(customViewId);
|
||||
setTopbarEditView(customViewId);
|
||||
if (!manualJournalsLoading) {
|
||||
setIsMounted(true);
|
||||
}
|
||||
changePageSubtitle(customViewId && viewMeta ? viewMeta.name : '');
|
||||
}, [
|
||||
customViewId,
|
||||
changeCurrentView,
|
||||
changePageSubtitle,
|
||||
setTopbarEditView,
|
||||
viewMeta,
|
||||
]);
|
||||
}, [manualJournalsLoading, setIsMounted]);
|
||||
|
||||
const handlePublishJournal = useCallback(
|
||||
(journal) => () => {
|
||||
@@ -106,7 +117,7 @@ function ManualJournalsDataTable({
|
||||
<MenuDivider />
|
||||
{!journal.status && (
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'publish_journal' })}
|
||||
text={formatMessage({ id: 'publish_journal' })}
|
||||
onClick={handlePublishJournal(journal)}
|
||||
/>
|
||||
)}
|
||||
@@ -121,84 +132,69 @@ function ManualJournalsDataTable({
|
||||
/>
|
||||
</Menu>
|
||||
),
|
||||
[handleEditJournal, handleDeleteJournal, handlePublishJournal, formatMessage],
|
||||
[
|
||||
handleEditJournal,
|
||||
handleDeleteJournal,
|
||||
handlePublishJournal,
|
||||
formatMessage,
|
||||
],
|
||||
);
|
||||
|
||||
const onRowContextMenu = useCallback((cell) => {
|
||||
return actionMenuList(cell.row.original);
|
||||
}, [actionMenuList]);
|
||||
const onRowContextMenu = useCallback(
|
||||
(cell) => actionMenuList(cell.row.original),
|
||||
[actionMenuList],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'date',
|
||||
Header: formatMessage({ id: 'date' }),
|
||||
accessor: (r) => moment().format('YYYY-MM-DD'),
|
||||
disableResizing: true,
|
||||
width: 150,
|
||||
accessor: (r) => moment(r.date).format('YYYY MMM DD'),
|
||||
width: 115,
|
||||
className: 'date',
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
Header: formatMessage({ id: 'amount' }),
|
||||
accessor: (r) => <Money amount={r.amount} currency={'USD'} />,
|
||||
disableResizing: true,
|
||||
className: 'amount',
|
||||
width: 115,
|
||||
},
|
||||
{
|
||||
id: 'journal_number',
|
||||
Header: formatMessage({ id: 'journal_no' }),
|
||||
accessor: 'journal_number',
|
||||
disableResizing: true,
|
||||
accessor: (row) => `#${row.journal_number}`,
|
||||
className: 'journal_number',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
id: 'journal_type',
|
||||
Header: formatMessage({ id: 'journal_type' }),
|
||||
accessor: 'journal_type',
|
||||
width: 110,
|
||||
className: 'journal_type',
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
Header: formatMessage({ id: 'status' }),
|
||||
accessor: (r) => {
|
||||
return r.status ? (
|
||||
<Tag minimal={true}><T id={'published'} /></Tag>
|
||||
) : (
|
||||
<Tag minimal={true} intent={Intent.WARNING}><T id={'draft'} /></Tag>
|
||||
);
|
||||
},
|
||||
disableResizing: true,
|
||||
width: 100,
|
||||
accessor: StatusAccessor,
|
||||
width: 95,
|
||||
className: 'status',
|
||||
},
|
||||
{
|
||||
id: 'note',
|
||||
Header: formatMessage({ id: 'note' }),
|
||||
accessor: (row) => (
|
||||
<If condition={row.description}>
|
||||
<Tooltip
|
||||
className={Classes.TOOLTIP_INDICATOR}
|
||||
content={row.description}
|
||||
position={Position.TOP}
|
||||
hoverOpenDelay={250}
|
||||
>
|
||||
<Icon icon={'file-alt'} iconSize={16} />
|
||||
</Tooltip>
|
||||
</If>
|
||||
),
|
||||
disableResizing: true,
|
||||
accessor: NoteAccessor,
|
||||
disableSorting: true,
|
||||
width: 100,
|
||||
width: 85,
|
||||
className: 'note',
|
||||
},
|
||||
{
|
||||
id: 'transaction_type',
|
||||
Header: formatMessage({ id: 'transaction_type' }),
|
||||
accessor: 'transaction_type',
|
||||
width: 100,
|
||||
className: 'transaction_type',
|
||||
},
|
||||
{
|
||||
id: 'created_at',
|
||||
Header: formatMessage({ id: 'created_at' }),
|
||||
accessor: (r) => moment().format('YYYY-MM-DD'),
|
||||
disableResizing: true,
|
||||
width: 150,
|
||||
accessor: (r) => moment(r.created_at).format('YYYY MMM DD'),
|
||||
width: 125,
|
||||
className: 'created_at',
|
||||
},
|
||||
{
|
||||
@@ -236,33 +232,28 @@ function ManualJournalsDataTable({
|
||||
);
|
||||
|
||||
return (
|
||||
<LoadingIndicator loading={loading} mount={false}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={manualJournalsCurrentPage}
|
||||
onFetchData={handleDataTableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
loading={manualJournalsLoading && !manualJournalsCurrentPage.length > 0}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
pagination={true}
|
||||
|
||||
rowContextMenu={onRowContextMenu}
|
||||
|
||||
pagesCount={manualJournalsPagination.pagesCount}
|
||||
initialPageSize={manualJournalsTableQuery.page_size}
|
||||
initialPageIndex={manualJournalsTableQuery.page - 1}
|
||||
/>
|
||||
</LoadingIndicator>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={manualJournalsCurrentPage}
|
||||
onFetchData={handleDataTableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
loading={manualJournalsLoading && !isMounted}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
pagination={true}
|
||||
rowContextMenu={onRowContextMenu}
|
||||
pagesCount={manualJournalsPagination.pagesCount}
|
||||
initialPageSize={manualJournalsTableQuery.page_size}
|
||||
initialPageIndex={manualJournalsTableQuery.page - 1}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
withDialogActions,
|
||||
withDashboardActions,
|
||||
withManualJournalsActions,
|
||||
withManualJournals(
|
||||
({
|
||||
@@ -277,5 +268,4 @@ export default compose(
|
||||
manualJournalsTableQuery,
|
||||
}),
|
||||
),
|
||||
withViewDetails,
|
||||
)(ManualJournalsDataTable);
|
||||
|
||||
@@ -43,8 +43,6 @@ function ManualJournalsTable({
|
||||
requestPublishManualJournal,
|
||||
requestDeleteBulkManualJournals,
|
||||
addManualJournalsTableQueries,
|
||||
|
||||
addQuery,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -54,18 +52,19 @@ function ManualJournalsTable({
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const fetchViews = useQuery('manual-journals-resource-views', () => {
|
||||
return requestFetchResourceViews('manual_journals');
|
||||
});
|
||||
const fetchResourceViews = useQuery(
|
||||
['resource-views', 'manual-journals'],
|
||||
() => requestFetchResourceViews('manual_journals'),
|
||||
);
|
||||
|
||||
const fetchResourceFields = useQuery(
|
||||
'manual-journals-resource-fields',
|
||||
['resource-fields', 'manual-journals'],
|
||||
() => requestFetchResourceFields('manual_journals'),
|
||||
);
|
||||
|
||||
const fetchManualJournals = useQuery(
|
||||
['manual-journals-table', manualJournalsTableQuery],
|
||||
(key, q) => requestFetchManualJournalsTable(q),
|
||||
(key, q) => requestFetchManualJournalsTable(),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -198,7 +197,7 @@ function ManualJournalsTable({
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
loading={fetchViews.isFetching || fetchResourceFields.isFetching}
|
||||
loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
|
||||
name={'manual-journals'}
|
||||
>
|
||||
<ManualJournalsActionsBar
|
||||
|
||||
@@ -1,115 +1,98 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import {
|
||||
Alignment,
|
||||
Navbar,
|
||||
NavbarGroup,
|
||||
Tabs,
|
||||
Tab,
|
||||
Button,
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams, withRouter } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { pick, debounce } from 'lodash';
|
||||
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
import Icon from 'components/Icon';
|
||||
import { DashboardViewsTabs, Icon } from 'components';
|
||||
|
||||
import withManualJournals from './withManualJournals';
|
||||
import withManualJournalsActions from './withManualJournalsActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withViewDetail from 'containers/Views/withViewDetails';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function ManualJournalsViewTabs({
|
||||
// #withViewDetail
|
||||
viewId,
|
||||
viewItem,
|
||||
|
||||
// #withManualJournals
|
||||
manualJournalsViews,
|
||||
|
||||
|
||||
// #withManualJournalsActions
|
||||
addManualJournalsTableQueries,
|
||||
changeManualJournalCurrentView,
|
||||
|
||||
// #withDashboardActions
|
||||
setTopbarEditView,
|
||||
changePageSubtitle,
|
||||
|
||||
// #ownProps
|
||||
customViewChanged,
|
||||
onViewChanged,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
changeManualJournalCurrentView(customViewId || -1);
|
||||
setTopbarEditView(customViewId);
|
||||
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
||||
|
||||
addManualJournalsTableQueries({
|
||||
custom_view_id: customViewId,
|
||||
});
|
||||
}, [customViewId, addManualJournalsTableQueries]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
onViewChanged && onViewChanged(customViewId);
|
||||
}, [customViewId]);
|
||||
|
||||
const tabs = manualJournalsViews.map((view) => ({
|
||||
...pick(view, ['name', 'id']),
|
||||
}));
|
||||
|
||||
const debounceChangeHistory = useRef(
|
||||
debounce((toUrl) => {
|
||||
history.push(toUrl);
|
||||
}, 250),
|
||||
);
|
||||
|
||||
const handleClickNewView = () => {
|
||||
setTopbarEditView(null);
|
||||
history.push('/custom_views/manual_journals/new');
|
||||
};
|
||||
const handleViewLinkClick = () => {
|
||||
setTopbarEditView(customViewId);
|
||||
|
||||
const handleTabChange = (viewId) => {
|
||||
const toPath = viewId ? `${viewId}/custom_view` : '';
|
||||
debounceChangeHistory.current(`/manual-journals/${toPath}`);
|
||||
setTopbarEditView(viewId);
|
||||
};
|
||||
|
||||
useUpdateEffect(() => {
|
||||
customViewChanged && customViewChanged(customViewId);
|
||||
|
||||
addManualJournalsTableQueries({
|
||||
custom_view_id: customViewId || null,
|
||||
});
|
||||
onViewChanged && onViewChanged(customViewId);
|
||||
}, [customViewId]);
|
||||
|
||||
useEffect(() => {
|
||||
addManualJournalsTableQueries({
|
||||
custom_view_id: customViewId,
|
||||
});
|
||||
}, [customViewId,addManualJournalsTableQueries]);
|
||||
|
||||
const tabs = manualJournalsViews.map((view) => {
|
||||
const baseUrl = '/manual-journals';
|
||||
const link = (
|
||||
<Link
|
||||
to={`${baseUrl}/${view.id}/custom_view`}
|
||||
onClick={handleViewLinkClick}
|
||||
>
|
||||
{view.name}
|
||||
</Link>
|
||||
);
|
||||
return <Tab id={`custom_view_${view.id}`} title={link} />;
|
||||
});
|
||||
|
||||
return (
|
||||
<Navbar className='navbar--dashboard-views'>
|
||||
<Navbar className="navbar--dashboard-views">
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<Tabs
|
||||
id='navbar'
|
||||
large={true}
|
||||
selectedTabId={`custom_view_${customViewId}`}
|
||||
className='tabs--dashboard-views'
|
||||
>
|
||||
<Tab
|
||||
id='all'
|
||||
title={
|
||||
<Link to={`/dashboard/accounting/manual-journals`}>
|
||||
<T id={'all'} />
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
|
||||
{ tabs }
|
||||
|
||||
<Button
|
||||
className='button--new-view'
|
||||
icon={<Icon icon='plus' />}
|
||||
onClick={handleClickNewView}
|
||||
minimal={true}
|
||||
/>
|
||||
</Tabs>
|
||||
<DashboardViewsTabs
|
||||
initialViewId={customViewId}
|
||||
baseUrl={'/manual-journals'}
|
||||
tabs={tabs}
|
||||
onChange={handleTabChange}
|
||||
onNewViewTabClick={handleClickNewView}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
// Mapping view id from matched route params.
|
||||
viewId: ownProps.match.params.custom_view_id,
|
||||
viewId: parseInt(ownProps.match.params.custom_view_id, 10),
|
||||
});
|
||||
|
||||
const withManualJournalsViewTabs = connect(mapStateToProps);
|
||||
@@ -121,5 +104,6 @@ export default compose(
|
||||
manualJournalsViews,
|
||||
})),
|
||||
withManualJournalsActions,
|
||||
withDashboardActions
|
||||
withDashboardActions,
|
||||
withViewDetail(),
|
||||
)(ManualJournalsViewTabs);
|
||||
|
||||
@@ -1,37 +1,23 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { pick, mapValues } from 'lodash';
|
||||
import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
import { getManualJournalsItems } from 'store/manualJournals/manualJournals.selectors';
|
||||
import {
|
||||
getManualJournalsItems,
|
||||
getManualJournalsPagination,
|
||||
getManualJournalsTableQuery,
|
||||
} from 'store/manualJournals/manualJournals.selectors';
|
||||
|
||||
const queryParamsKeys = ['page_size', 'page'];
|
||||
|
||||
export default (mapState) => {
|
||||
const mapStateToProps = (state, props) => {
|
||||
const queryParams = props.location
|
||||
? new URLSearchParams(props.location.search)
|
||||
: null;
|
||||
|
||||
const manualJournalsTableQuery = {
|
||||
...state.manualJournals.tableQuery,
|
||||
...(queryParams
|
||||
? mapValues(
|
||||
pick(Object.fromEntries(queryParams), queryParamsKeys),
|
||||
(v) => parseInt(v, 10),
|
||||
)
|
||||
: {}),
|
||||
};
|
||||
const query = getManualJournalsTableQuery(state, props);
|
||||
|
||||
const mapped = {
|
||||
manualJournalsCurrentPage: getManualJournalsItems(
|
||||
state,
|
||||
state.manualJournals.currentViewId,
|
||||
manualJournalsTableQuery.page,
|
||||
),
|
||||
manualJournalsTableQuery,
|
||||
manualJournalsCurrentPage: getManualJournalsItems(state, props, query),
|
||||
manualJournalsTableQuery: query,
|
||||
manualJournalsViews: getResourceViews(state, props, 'manual_journals'),
|
||||
manualJournalsItems: state.manualJournals.items,
|
||||
|
||||
manualJournalsPagination: state.manualJournals.paginationMeta,
|
||||
manualJournalsPagination: getManualJournalsPagination(state, props, query),
|
||||
manualJournalsLoading: state.manualJournals.loading,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
|
||||
@@ -14,9 +14,11 @@ const mapActionsToProps = (dispatch) => ({
|
||||
requestFetchManualJournal: (id) => dispatch(fetchManualJournal({ id })),
|
||||
requestPublishManualJournal: (id) => dispatch(publishManualJournal({ id })),
|
||||
requestDeleteBulkManualJournals: (ids) => dispatch(deleteBulkManualJournals({ ids })),
|
||||
changeCurrentView: (id) => dispatch({
|
||||
changeManualJournalCurrentView: (id) => dispatch({
|
||||
type: t.MANUAL_JOURNALS_SET_CURRENT_VIEW,
|
||||
currentViewId: parseInt(id, 10),
|
||||
payload: {
|
||||
currentViewId: parseInt(id, 10),
|
||||
}
|
||||
}),
|
||||
addManualJournalsTableQueries: (queries) => dispatch({
|
||||
type: t.MANUAL_JOURNALS_TABLE_QUERIES_ADD,
|
||||
|
||||
Reference in New Issue
Block a user