- 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:
Ahmed Bouhuolia
2020-07-01 12:51:12 +02:00
parent 111aa83908
commit 4718f63c94
94 changed files with 1706 additions and 1001 deletions

View File

@@ -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);
});
}

View File

@@ -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>
);
}
}

View File

@@ -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);

View File

@@ -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',
})),
],
}}

View File

@@ -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} />}

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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,