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

View File

@@ -59,20 +59,22 @@ function AccountsChart({
const [selectedRows, setSelectedRows] = useState([]);
const [bulkActivate, setBulkActivate] = useState(false);
const [bulkInactiveAccounts, setBulkInactiveAccounts] = useState(false);
const [tableLoading, setTableLoading] = useState(false);
// Fetch accounts resource views and fields.
const fetchHook = useQuery('resource-accounts', () => {
return Promise.all([
requestFetchResourceViews('accounts'),
requestFetchResourceFields('accounts'),
]);
});
const fetchResourceViews = useQuery(
['resource-views', 'accounts'],
(key, resourceName) => requestFetchResourceViews(resourceName),
);
const fetchResourceFields = useQuery(
['resource-fields', 'accounts'],
(key, resourceName) => requestFetchResourceFields(resourceName),
);
// Fetch accounts list according to the given custom view id.
const fetchAccountsHook = useQuery(
['accounts-table', accountsTableQuery],
() => requestFetchAccountsTable(),
(key, q) => requestFetchAccountsTable(),
);
useEffect(() => {
@@ -89,6 +91,7 @@ function AccountsChart({
setDeleteAccount(false);
}, []);
// Handle delete errors in bulk and singular.
const handleDeleteErrors = (errors) => {
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
AppToaster.show({
@@ -221,17 +224,6 @@ function AccountsChart({
fetchAccountsHook.refetch();
}, [fetchAccountsHook]);
// Refetch accounts data table when current custom view changed.
const handleViewChanged = useCallback(async () => {
setTableLoading(true);
}, [fetchAccountsHook]);
useEffect(() => {
if (tableLoading && !fetchAccountsHook.isFetching) {
setTableLoading(false);
}
}, [tableLoading, fetchAccountsHook.isFetching]);
// Handle fetch data of accounts datatable.
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
@@ -243,7 +235,6 @@ function AccountsChart({
}
: {}),
});
fetchAccountsHook.refetch();
},
[fetchAccountsHook, addAccountsTableQueries],
);
@@ -314,7 +305,10 @@ function AccountsChart({
}, [requestBulkInactiveAccounts, bulkInactiveAccounts]);
return (
<DashboardInsider loading={fetchHook.isFetching} name={'accounts-chart'}>
<DashboardInsider
loading={fetchResourceFields.isFetching || fetchResourceViews.isFetching}
name={'accounts-chart'}
>
<DashboardActionsBar
selectedRows={selectedRows}
onFilterChanged={handleFilterChanged}
@@ -323,17 +317,15 @@ function AccountsChart({
onBulkActivate={handleBulkActivate}
onBulkInactive={handleBulkInactive}
/>
<DashboardPageContent>
<Switch>
<Route
exact={true}
path={['/accounts/:custom_view_id/custom_view', '/accounts']}
>
<AccountsViewsTabs onViewChanged={handleViewChanged} />
<AccountsViewsTabs />
<AccountsDataTable
loading={fetchAccountsHook.isFetching}
onDeleteAccount={handleDeleteAccount}
onInactiveAccount={handleInactiveAccount}
onActivateAccount={handleActivateAccount}
@@ -341,7 +333,6 @@ function AccountsChart({
onEditAccount={handleEditAccount}
onFetchData={handleFetchData}
onSelectedRowsChange={handleSelectedRowsChange}
/>
</Route>
</Switch>

View File

@@ -12,13 +12,13 @@ import {
} from '@blueprintjs/core';
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import Icon from 'components/Icon';
import {
Icon,
DataTable,
Money,
If,
} from 'components';
import { compose } from 'utils';
import LoadingIndicator from 'components/LoadingIndicator';
import DataTable from 'components/DataTable';
import Money from 'components/Money';
import { useUpdateEffect } from 'hooks';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
@@ -27,7 +27,53 @@ import withAccounts from 'containers/Accounts/withAccounts';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withCurrentView from 'containers/Views/withCurrentView';
import { If } from 'components';
function NormalCell({ cell }) {
const { formatMessage } = useIntl();
const account = cell.row.original;
const normal = account?.type?.normal || '';
const arrowDirection = normal === 'credit' ? 'down' : 'up';
return (
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={formatMessage({ id: normal })}
position={Position.RIGHT}
hoverOpenDelay={100}
>
<Icon icon={`arrow-${arrowDirection}`} />
</Tooltip>
);
}
function BalanceCell({ cell }) {
const account = cell.row.original;
const { balance = null } = account;
return balance ? (
<span>
<Money amount={balance.amount} currency={balance.currency_code} />
</span>
) : (
<span class="placeholder">--</span>
);
}
function AccountNameAccessor(row) {
return row.description ? (
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={row.description}
position={Position.RIGHT_TOP}
hoverOpenDelay={500}
>
{row.name}
</Tooltip>
) : (
row.name
);
}
function AccountsDataTable({
// #withDashboardActions
@@ -40,7 +86,6 @@ function AccountsDataTable({
currentViewId,
// own properties
loading,
onFetchData,
onSelectedRowsChange,
onDeleteAccount,
@@ -125,20 +170,7 @@ function AccountsDataTable({
{
id: 'name',
Header: formatMessage({ id: 'account_name' }),
accessor: (row) => {
return row.description ? (
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={row.description}
position={Position.RIGHT_TOP}
hoverOpenDelay={500}
>
{row.name}
</Tooltip>
) : (
row.name
);
},
accessor: AccountNameAccessor,
className: 'account_name',
width: 220,
},
@@ -159,40 +191,22 @@ function AccountsDataTable({
{
id: 'normal',
Header: formatMessage({ id: 'normal' }),
Cell: ({ cell }) => {
const account = cell.row.original;
const normal = account.type ? account.type.normal : '';
const arrowDirection = normal === 'credit' ? 'down' : 'up';
return (
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={formatMessage({ id: normal })}
position={Position.RIGHT}
hoverOpenDelay={500}
>
<Icon icon={`arrow-${arrowDirection}`} />
</Tooltip>
);
},
Cell: NormalCell,
accessor: 'type.normal',
className: 'normal',
width: 115,
},
{
id: 'currency',
Header: formatMessage({ id: 'currency' }),
accessor: (row) => 'USD',
width: 100,
},
{
id: 'balance',
Header: formatMessage({ id: 'balance' }),
Cell: ({ cell }) => {
const account = cell.row.original;
const { balance = null } = account;
return balance ? (
<span>
<Money amount={balance.amount} currency={balance.currency_code} />
</span>
) : (
<span class="placeholder">--</span>
);
},
accessor: 'balance',
Cell: BalanceCell,
width: 150,
},
{
@@ -235,23 +249,19 @@ function AccountsDataTable({
);
return (
<LoadingIndicator loading={loading && !isMounted} mount={false}>
<DataTable
noInitialFetch={true}
columns={columns}
data={accounts}
onFetchData={handleDatatableFetchData}
manualSortBy={true}
selectionColumn={selectionColumn}
expandable={true}
treeGraph={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
loading={accountsLoading && !isMounted}
spinnerProps={{ size: 30 }}
rowContextMenu={rowContextMenu}
/>
</LoadingIndicator>
<DataTable
noInitialFetch={true}
columns={columns}
data={accounts}
onFetchData={handleDatatableFetchData}
manualSortBy={true}
selectionColumn={selectionColumn}
expandable={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
loading={accountsLoading && !isMounted}
rowContextMenu={rowContextMenu}
/>
);
}

View File

@@ -5,17 +5,12 @@ import {
Alignment,
Navbar,
NavbarGroup,
Tabs,
Tab,
Button,
} from '@blueprintjs/core';
import { useParams, withRouter } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { pick, debounce } from 'lodash';
import Icon from 'components/Icon';
import { FormattedMessage as T } from 'react-intl';
import { useUpdateEffect } from 'hooks';
import { DashboardViewsTabs } from 'components';
import { DashboardViewsTabs, Icon } from 'components';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withAccounts from 'containers/Accounts/withAccounts';
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
@@ -49,7 +44,7 @@ function AccountsViewsTabs({
useEffect(() => {
changeAccountsCurrentView(customViewId || -1);
setTopbarEditView(customViewId);
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
changePageSubtitle((customViewId && viewItem) ? viewItem.name : '');
addAccountsTableQueries({
custom_view_id: customViewId,
@@ -92,6 +87,7 @@ function AccountsViewsTabs({
<Navbar className="navbar--dashboard-views">
<NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs
initialViewId={customViewId}
baseUrl={'/accounts'}
tabs={tabs}
onNewViewTabClick={handleClickNewView}
@@ -116,5 +112,5 @@ export default compose(
accountsViews,
})),
withAccountsTableActions,
withViewDetail,
withViewDetail(),
)(AccountsViewsTabs);

View File

@@ -1,10 +1,8 @@
import { connect } from 'react-redux';
import {
getItemById
} from 'store/selectors';
import { getAccountById } from 'store/accounts/accounts.selectors';
const mapStateToProps = (state, props) => ({
account: getItemById(state.accounts.items, props.accountId),
account: getAccountById(state, props),
});
export default connect(mapStateToProps);

View File

@@ -1,32 +1,24 @@
import { connect } from 'react-redux';
import { compose } from 'utils';
import withDialogActions from 'containers/Dialog/withDialogActions';
import DialogReduxConnect from 'components/DialogReduxConnect';
import { getDialogPayload } from 'store/dashboard/dashboard.reducer';
import withDialogRedux from 'components/DialogReduxConnect';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withAccountDetail from 'containers/Accounts/withAccountDetail';
import withAccounts from 'containers/Accounts/withAccounts';
export const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'account-form');
return {
name: 'account-form',
payload: { action: 'new', id: null, ...dialogPayload },
accountId: dialogPayload?.id || null,
};
};
export const mapStateToProps = (state, props) => ({
dialogName: 'account-form',
});
const AccountFormDialogConnect = connect(mapStateToProps);
export default compose(
AccountFormDialogConnect,
withDialogRedux(null, 'account-form'),
withAccountsActions,
withAccountDetail,
withAccounts(({ accountsTypes, accounts }) => ({
accountsTypes,
accounts,
})),
DialogReduxConnect,
withDialogActions,
);

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import {
Button,
Classes,
@@ -13,23 +13,25 @@ import {
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { omit, pick } from 'lodash';
import { useQuery, queryCache } from 'react-query';
import Dialog from 'components/Dialog';
import AppToaster from 'components/AppToaster';
import classNames from 'classnames';
import {
ListSelect,
ErrorMessage,
Dialog,
AppToaster,
FieldRequiredHint,
Hint,
} from 'components';
import AccountFormDialogContainer from 'containers/Dialogs/AccountFormDialog.container';
import classNames from 'classnames';
import Icon from 'components/Icon';
import ErrorMessage from 'components/ErrorMessage';
import { ListSelect } from 'components';
/**
* Account form dialog.
*/
function AccountFormDialog({
name,
payload,
dialogName,
payload = { action: 'new', id: null },
isOpen,
// #withAccounts
@@ -115,7 +117,7 @@ function AccountFormDialog({
},
})
.then((response) => {
closeDialog(name);
closeDialog(dialogName);
queryCache.refetchQueries('accounts-table', { force: true });
AppToaster.show({
@@ -137,7 +139,7 @@ function AccountFormDialog({
} else {
requestSubmitAccount({ form: { ...omit(values, exclude) } })
.then((response) => {
closeDialog(name);
closeDialog(dialogName);
queryCache.refetchQueries('accounts-table', { force: true });
AppToaster.show({
@@ -190,7 +192,12 @@ function AccountFormDialog({
// Account item of select accounts field.
const accountItem = (item, { handleClick, modifiers, query }) => {
return (
<MenuItem text={item.name} label={item.code} key={item.id} onClick={handleClick} />
<MenuItem
text={item.name}
label={item.code}
key={item.id}
onClick={handleClick}
/>
);
};
@@ -213,14 +220,14 @@ function AccountFormDialog({
// Handles dialog close.
const handleClose = useCallback(() => {
closeDialog(name);
}, [closeDialog, name]);
closeDialog(dialogName);
}, [closeDialog, dialogName]);
// Fetches accounts list.
const fetchAccountsList = useQuery(
'accounts-list',
() => requestFetchAccounts(),
{ manual: true },
{ enabled: true },
);
// Fetches accounts types.
@@ -229,14 +236,14 @@ function AccountFormDialog({
async () => {
await requestFetchAccountTypes();
},
{ manual: true },
{ enabled: true },
);
// Fetch the given account id on edit mode.
const fetchAccount = useQuery(
payload.action === 'edit' && ['account', payload.id],
['account', payload.id],
(key, id) => requestFetchAccount(id),
{ manual: true },
{ enabled: false },
);
const isFetching =
@@ -248,8 +255,11 @@ function AccountFormDialog({
const onDialogOpening = useCallback(() => {
fetchAccountsList.refetch();
fetchAccountsTypes.refetch();
fetchAccount.refetch();
}, [fetchAccount, fetchAccountsList, fetchAccountsTypes]);
if (payload.action === 'edit' && payload.id) {
fetchAccount.refetch();
}
}, [payload, fetchAccount, fetchAccountsList, fetchAccountsTypes]);
const onChangeAccountType = useCallback(
(accountType) => {
@@ -270,12 +280,11 @@ function AccountFormDialog({
resetForm();
}, [resetForm]);
const infoIcon = useMemo(() => <Icon icon="info-circle" iconSize={12} />, []);
const subAccountLabel = useMemo(() => {
return (
<span>
<T id={'sub_account'} /> <Icon icon="info-circle" iconSize={12} />
<T id={'sub_account'} />
<Hint />
</span>
);
}, []);
@@ -284,7 +293,7 @@ function AccountFormDialog({
return (
<Dialog
name={name}
name={dialogName}
title={
payload.action === 'edit' ? (
<T id={'edit_account'} />
@@ -308,7 +317,7 @@ function AccountFormDialog({
<div className={Classes.DIALOG_BODY}>
<FormGroup
label={<T id={'account_type'} />}
labelInfo={requiredSpan}
labelInfo={<FieldRequiredHint />}
className={classNames(
'form-group--account-type',
'form-group--select-list',
@@ -339,7 +348,7 @@ function AccountFormDialog({
<FormGroup
label={<T id={'account_name'} />}
labelInfo={requiredSpan}
labelInfo={<FieldRequiredHint />}
className={'form-group--account-name'}
intent={errors.name && touched.name && Intent.DANGER}
helperText={<ErrorMessage name="name" {...{ errors, touched }} />}
@@ -358,7 +367,7 @@ function AccountFormDialog({
intent={errors.code && touched.code && Intent.DANGER}
helperText={<ErrorMessage name="code" {...{ errors, touched }} />}
inline={true}
labelInfo={infoIcon}
labelInfo={<Hint content={<T id='account_code_hint' />} />}
>
<InputGroup
medium={true}

View File

@@ -15,7 +15,7 @@ import { pick } from 'lodash';
import AppToaster from 'components/AppToaster';
import Dialog from 'components/Dialog';
import DialogReduxConnect from 'components/DialogReduxConnect';
import withDialogRedux from 'components/DialogReduxConnect';
import ErrorMessage from 'components/ErrorMessage';
import classNames from 'classnames';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -221,22 +221,16 @@ function CurrencyDialog({
);
}
const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'currency-form');
return {
name: 'currency-form',
payload: { action: 'new', currencyCode: null, ...dialogPayload },
currencyCode: dialogPayload?.currencyCode || null,
};
};
const mapStateToProps = (state, props) => ({
dialogName: 'currency-form',
});
const withCurrencyFormDialog = connect(mapStateToProps);
export default compose(
withCurrencyFormDialog,
withDialogActions,
DialogReduxConnect,
withCurrenciesActions,
withDialogRedux(null, 'currency-form'),
withCurrency,
withDialogActions,
withCurrenciesActions,
)(CurrencyDialog);

View File

@@ -1,35 +1,29 @@
import { connect } from 'react-redux';
import { compose } from 'utils';
import { getDialogPayload } from 'store/dashboard/dashboard.reducer';
import withDialogActions from 'containers/Dialog/withDialogActions';
import DialogReduxConnect from 'components/DialogReduxConnect';
import withDialogRedux from 'components/DialogReduxConnect';
import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions';
import withExchangeRates from 'containers/ExchangeRates/withExchangeRates';
import withCurrencies from 'containers/Currencies/withCurrencies';
const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'exchangeRate-form');
return {
name: 'exchangeRate-form',
payload: { action: 'new', id: null, ...dialogPayload },
};
};
const mapStateToProps = (state, props) => ({
dialogName: 'exchangeRate-form',
});
const withExchangeRateDialog = connect(mapStateToProps);
export default compose(
withExchangeRateDialog,
withDialogRedux(null, 'exchangeRate-form'),
withCurrencies(({ currenciesList }) => ({
currenciesList,
})),
withExchangeRatesActions,
withExchangeRates(({ exchangeRatesList }) => ({
exchangeRatesList,
})),
DialogReduxConnect,
withExchangeRatesActions,
withDialogActions,
);

View File

@@ -16,18 +16,21 @@ import { useQuery, queryCache } from 'react-query';
import moment from 'moment';
import { DateInput } from '@blueprintjs/datetime';
import { momentFormatter } from 'utils';
import AppToaster from 'components/AppToaster';
import Dialog from 'components/Dialog';
import ErrorMessage from 'components/ErrorMessage';
import {
AppToaster,
Dialog,
ErrorMessage,
ListSelect,
} from 'components';
import classNames from 'classnames';
import { ListSelect } from 'components';
import withExchangeRatesDialog from './ExchangeRateDialog.container';
/**
* Exchange rate dialog.
*/
function ExchangeRateDialog({
name,
payload,
dialogName,
payload = {},
isOpen,
// #withDialog
@@ -91,7 +94,7 @@ function ExchangeRateDialog({
if (payload.action === 'edit') {
requestEditExchangeRate(payload.id, values)
.then((response) => {
closeDialog(name);
closeDialog(dialogName);
AppToaster.show({
message: formatMessage({
id: 'the_exchange_rate_has_been_successfully_edited',
@@ -107,7 +110,7 @@ function ExchangeRateDialog({
} else {
requestSubmitExchangeRate(values)
.then((response) => {
closeDialog(name);
closeDialog(dialogName);
AppToaster.show({
message: formatMessage({
id: 'the_exchange_rate_has_been_successfully_created',
@@ -136,13 +139,13 @@ function ExchangeRateDialog({
const requiredSpan = useMemo(() => <span class="required">*</span>, []);
const handleClose = useCallback(() => {
closeDialog(name);
}, [name, closeDialog]);
closeDialog(dialogName);
}, [dialogName, closeDialog]);
const onDialogClosed = useCallback(() => {
resetForm();
closeDialog(name);
}, [closeDialog, name, resetForm]);
closeDialog(dialogName);
}, [closeDialog, dialogName, resetForm]);
const onDialogOpening = useCallback(() => {
fetchExchangeRatesDialog.refetch();
@@ -197,7 +200,7 @@ function ExchangeRateDialog({
return (
<Dialog
name={name}
name={dialogName}
title={
payload.action === 'edit' ? (
<T id={'edit_exchange_rate'} />

View File

@@ -24,9 +24,8 @@ import { ListSelect } from 'components';
import Dialog from 'components/Dialog';
import withDialogActions from 'containers/Dialog/withDialogActions';
import DialogReduxConnect from 'components/DialogReduxConnect';
import withDialogRedux from 'components/DialogReduxConnect';
import { getDialogPayload } from 'store/dashboard/dashboard.reducer';
import withItemCategoryDetail from 'containers/Items/withItemCategoryDetail';
import withItemCategories from 'containers/Items/withItemCategories';
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
@@ -34,7 +33,7 @@ import withItemCategoriesActions from 'containers/Items/withItemCategoriesAction
import Icon from 'components/Icon';
function ItemCategoryDialog({
name,
dialogName,
payload,
isOpen,
@@ -99,7 +98,7 @@ function ItemCategoryDialog({
if (payload.action === 'edit') {
requestEditItemCategory(payload.id, values)
.then((response) => {
closeDialog(name);
closeDialog(dialogName);
AppToaster.show({
message: formatMessage({
id: 'the_item_category_has_been_successfully_edited',
@@ -117,7 +116,7 @@ function ItemCategoryDialog({
} else {
requestSubmitItemCategory(values)
.then((response) => {
closeDialog(name);
closeDialog(dialogName);
AppToaster.show({
message: formatMessage({
id: 'the_item_category_has_been_successfully_created',
@@ -165,8 +164,8 @@ function ItemCategoryDialog({
// Handle the dialog closing.
const handleClose = useCallback(() => {
closeDialog(name);
}, [name, closeDialog]);
closeDialog(dialogName);
}, [dialogName, closeDialog]);
// Handle the dialog opening.
const onDialogOpening = useCallback(() => {
@@ -183,15 +182,15 @@ function ItemCategoryDialog({
const onDialogClosed = useCallback(() => {
resetForm();
closeDialog(name);
}, [resetForm, closeDialog, name]);
closeDialog(dialogName);
}, [resetForm, closeDialog, dialogName]);
const requiredSpan = useMemo(() => <span class="required">*</span>, []);
const infoIcon = useMemo(() => <Icon icon="info-circle" iconSize={12} />, []);
return (
<Dialog
name={name}
name={dialogName}
title={
payload.action === 'edit' ? (
<T id={'edit_category'} />
@@ -303,23 +302,17 @@ function ItemCategoryDialog({
);
}
const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'item-category-form');
return {
name: 'item-category-form',
payload: { action: 'new', id: null, ...dialogPayload },
itemCategoryId: dialogPayload?.id || null,
};
};
const mapStateToProps = (state, props) => ({
itemCategoryId: props?.dialogPayload?.id || null,
});
const withItemCategoryDialog = connect(mapStateToProps);
export default compose(
withDialogRedux(null, 'item-category-form'),
withItemCategoryDialog,
withDialogActions,
DialogReduxConnect,
withItemCategoryDetail,
withItemCategoryDetail(),
withItemCategories(({ categoriesList }) => ({
categoriesList,
})),

View File

@@ -1,9 +1,10 @@
import { connect } from 'react-redux';
import { getExchangeRatesList } from 'store/ExchangeRate/exchange.selector';
export default (mapState) => {
const mapStateToProps = (state, props) => {
const mapped = {
exchangeRatesList: Object.values(state.exchangeRates.exchangeRates),
exchangeRatesList: getExchangeRatesList(state, props),
exchangeRatesLoading: state.exchangeRates.loading,
};
return mapState ? mapState(mapped, state, props) : mapped;

View File

@@ -15,6 +15,7 @@ import {
import classNames from 'classnames';
import { useRouteMatch, useHistory } from 'react-router-dom';
import { FormattedMessage as T } from 'react-intl';
import { connect } from 'react-redux';
import FilterDropdown from 'components/FilterDropdown';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
@@ -28,12 +29,13 @@ import withExpensesActions from 'containers/Expenses/withExpensesActions';
import { compose } from 'utils';
function ExpenseActionsBar({
function ExpensesActionsBar({
// #withResourceDetail
resourceFields,
//#withExpenses
expensesViews,
//#withExpensesActions
addExpensesTableQueries,
@@ -44,17 +46,20 @@ function ExpenseActionsBar({
const { path } = useRouteMatch();
const history = useHistory();
const viewsMenuItems = expensesViews.map((view) => {
return (
<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />
);
});
const viewsMenuItems = expensesViews.map((view) => (
<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />
));
const onClickNewExpense = useCallback(() => {
history.push('/expenses/new');
}, [history]);
const filterDropdown = FilterDropdown({
initialCondition: {
fieldKey: 'reference_no',
compatator: 'contains',
value: '',
},
fields: resourceFields,
onFilterChange: (filterConditions) => {
addExpensesTableQueries({
@@ -97,6 +102,7 @@ function ExpenseActionsBar({
onClick={onClickNewExpense}
/>
<Popover
minimal={true}
content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
@@ -118,6 +124,11 @@ function ExpenseActionsBar({
/>
</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} />}
@@ -133,7 +144,14 @@ function ExpenseActionsBar({
);
}
const mapStateToProps = (state, props) => ({
resourceName: 'expenses',
});
const withExpensesActionsBar = connect(mapStateToProps);
export default compose(
withExpensesActionsBar,
withDialogActions,
withResourceDetail(({ resourceFields }) => ({
resourceFields,
@@ -142,4 +160,4 @@ export default compose(
expensesViews,
})),
withExpensesActions,
)(ExpenseActionsBar);
)(ExpensesActionsBar);

View File

@@ -29,10 +29,11 @@ import withViewDetails from 'containers/Views/withViewDetails';
import withExpenses from 'containers/Expenses/withExpenses';
import withExpensesActions from 'containers/Expenses/withExpensesActions';
function ExpenseDataTable({
function ExpensesDataTable({
//#withExpenes
expenses,
expensesCurrentPage,
expensesLoading,
expensesPagination,
// #withDashboardActions
changeCurrentView,
@@ -101,7 +102,7 @@ function ExpenseDataTable({
<MenuItem
text={formatMessage({ id: 'view_details' })} />
<MenuDivider />
<If condition={expenses.published}>
<If condition={expense.published}>
<MenuItem
text={formatMessage({ id: 'publish_expense' })}
onClick={handlePublishExpense(expense)}
@@ -142,7 +143,7 @@ function ExpenseDataTable({
{
id: 'payment_date',
Header: formatMessage({ id: 'payment_date' }),
accessor: () => moment().format('YYYY-MM-DD'),
accessor: () => moment().format('YYYY MMM DD'),
width: 150,
className: 'payment_date',
},
@@ -250,15 +251,19 @@ function ExpenseDataTable({
<LoadingIndicator loading={loading} mount={false}>
<DataTable
columns={columns}
data={expenses}
data={expensesCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
loading={expensesLoading && !initialMount}
loading={expensesLoading}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={expensesPagination.pagesCount}
initialPageSize={expensesPagination.pageSize}
initialPageIndex={expensesPagination.page - 1}
/>
</LoadingIndicator>
</div>
@@ -269,9 +274,10 @@ export default compose(
withDialogActions,
withDashboardActions,
withExpensesActions,
withExpenses(({ expenses, expensesLoading }) => ({
expenses,
withExpenses(({ expensesCurrentPage, expensesLoading, expensesPagination }) => ({
expensesCurrentPage,
expensesLoading,
expensesPagination,
})),
withViewDetails,
)(ExpenseDataTable);
)(ExpensesDataTable);

View File

@@ -99,7 +99,6 @@ function ExpenseForm({
description: Yup.string()
.trim()
.label(formatMessage({ id: 'description' })),
publish: Yup.boolean().label(formatMessage({ id: 'publish' })),
categories: Yup.array().of(
Yup.object().shape({
@@ -258,8 +257,6 @@ function ExpenseForm({
},
});
console.log(formik.values, 'VALUES');
const handleSubmitClick = useCallback(
(payload) => {
setPayload(payload);
@@ -285,8 +282,6 @@ function ExpenseForm({
},
[setDeletedFiles, deletedFiles],
);
// @todo @mohamed
const fetchHook = useQuery('expense-form', () => requestFetchExpensesTable());
return (
<div className={'dashboard__insider--expense-form'}>
@@ -334,5 +329,5 @@ export default compose(
withAccountsActions,
withDashboardActions,
withMediaActions,
withExpneseDetail,
withExpneseDetail(),
)(ExpenseForm);

View File

@@ -14,9 +14,13 @@ import moment from 'moment';
import { momentFormatter, compose } from 'utils';
import classNames from 'classnames';
import Icon from 'components/Icon';
import ErrorMessage from 'components/ErrorMessage';
import { ListSelect } from 'components';
import {
ListSelect,
ErrorMessage,
Icon,
FieldRequiredHint,
Hint,
} from 'components';
import withCurrencies from 'containers/Currencies/withCurrencies';
import withAccounts from 'containers/Accounts/withAccounts';
@@ -35,11 +39,6 @@ function ExpenseFormHeader({
[setFieldValue],
);
// @todo @mohamed reusable components.
const infoIcon = useMemo(() => <Icon icon="info-circle" iconSize={12} />, []);
const requiredSpan = useMemo(() => <span className="required">*</span>, []);
const currencyCodeRenderer = useCallback((item, { handleClick }) => {
return (
<MenuItem key={item.id} text={item.currency_code} onClick={handleClick} />
@@ -121,7 +120,7 @@ function ExpenseFormHeader({
<FormGroup
label={<T id={'beneficiary'} />}
className={classNames('form-group--select-list', Classes.FILL)}
labelInfo={infoIcon}
labelInfo={<Hint />}
intent={errors.beneficiary && touched.beneficiary && Intent.DANGER}
helperText={
<ErrorMessage name={'beneficiary'} {...{ errors, touched }} />
@@ -150,7 +149,7 @@ function ExpenseFormHeader({
'form-group--select-list',
Classes.FILL,
)}
labelInfo={requiredSpan}
labelInfo={<FieldRequiredHint />}
intent={
errors.payment_account_id &&
touched.payment_account_id &&
@@ -183,7 +182,7 @@ function ExpenseFormHeader({
<Col width={300}>
<FormGroup
label={<T id={'payment_date'} />}
labelInfo={infoIcon}
labelInfo={<Hint />}
className={classNames('form-group--select-list', Classes.FILL)}
intent={
errors.payment_date && touched.payment_date && Intent.DANGER
@@ -234,10 +233,7 @@ function ExpenseFormHeader({
<Col width={200}>
<FormGroup
label={<T id={'ref_no'} />}
className={classNames(
'form-group--ref_no',
Classes.FILL,
)}
className={classNames('form-group--ref_no', Classes.FILL)}
intent={
errors.reference_no && touched.reference_no && Intent.DANGER
}

View File

@@ -1,113 +1,94 @@
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 } from 'components';
import withExpenses from './withExpenses';
import withExpensesActions from './withExpensesActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetails from 'containers/Views/withViewDetails';
import { compose } from 'utils';
function ExpenseViewTabs({
//#withExpenses
// #withExpenses
expensesViews,
//#withExpensesActions
// #withViewDetails
viewItem,
// #withExpensesActions
addExpensesTableQueries,
changeExpensesView,
// #withDashboardActions
setTopbarEditView,
// #ownProps
customViewChanged,
onViewChanged,
changePageSubtitle,
}) {
const history = useHistory();
const { custom_view_id: customViewId } = useParams();
useEffect(() => {
changeExpensesView(customViewId || -1);
setTopbarEditView(customViewId);
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
addExpensesTableQueries({
custom_view_id: customViewId,
});
return () => {
setTopbarEditView(null);
changePageSubtitle('');
changeExpensesView(null);
};
}, [customViewId, addExpensesTableQueries, changeExpensesView]);
const debounceChangeHistory = useRef(
debounce((toUrl) => {
history.push(toUrl);
}, 250),
);
const handleTabsChange = (viewId) => {
const toPath = viewId ? `${viewId}/custom_view` : '';
debounceChangeHistory.current(`/expenses/${toPath}`);
setTopbarEditView(viewId);
};
const tabs = expensesViews.map((view) => ({
...pick(view, ['name', 'id']),
}));
// Handle click a new view tab.
const handleClickNewView = () => {
setTopbarEditView(null);
history.push('/custom_views/expenses/new');
};
const handleViewLinkClick = () => {
setTopbarEditView(customViewId);
};
useUpdateEffect(() => {
customViewChanged && customViewChanged(customViewId);
addExpensesTableQueries({
custom_view_id: customViewId || null,
});
onViewChanged && onViewChanged(customViewId);
}, [customViewId]);
useEffect(() => {
addExpensesTableQueries({
custom_view_id: customViewId,
});
}, [customViewId, addExpensesTableQueries]);
const tabs = expensesViews.map((view) => {
const baseUrl = '/expenses/new';
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'}>
<NavbarGroup align={Alignment.LEFT}>
<Tabs
id="navbar"
large={true}
selectedTabId={`custom_view_${customViewId}`}
className="tabs--dashboard-views"
>
<Tab
id="all"
title={
<Link to={``}>
<T id={'all'} />
</Link>
}
/>
{tabs}
<Button
className="button--new-view"
icon={<Icon icon="plus" />}
onClick={handleClickNewView}
minimal={true}
/>
</Tabs>
<DashboardViewsTabs
initialViewId={customViewId}
baseUrl={'/expenses'}
tabs={tabs}
onNewViewTabClick={handleClickNewView}
onChange={handleTabsChange}
/>
</NavbarGroup>
</Navbar>
);
}
const mapStateToProps = (state, ownProps) => ({
// Mapping view id from matched route params.
viewId: ownProps.match.params.custom_view_id,
});
@@ -115,6 +96,7 @@ const withExpensesViewTabs = connect(mapStateToProps);
export default compose(
withRouter,
withViewDetails(),
withExpensesViewTabs,
withExpenses(({ expensesViews }) => ({
expensesViews,

View File

@@ -12,18 +12,21 @@ import ExpenseDataTable from 'containers/Expenses/ExpenseDataTable';
import ExpenseActionsBar from 'containers/Expenses/ExpenseActionsBar';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withExpenses from 'containers/Expenses/withExpenses';
import withExpensesActions from 'containers/Expenses/withExpensesActions';
import withViewsActions from 'containers/Views/withViewsActions';
import { compose } from 'utils';
function ExpensesList({
// #withDashboardActions
changePageTitle,
// #withViewsActions
requestFetchResourceViews,
requestFetchResourceFields,
// #withExpenses
expensesTableQuery,
@@ -44,13 +47,18 @@ function ExpensesList({
const [selectedRows, setSelectedRows] = useState([]);
const [bulkDelete, setBulkDelete] = useState(false);
const fetchViews = useQuery('expenses-resource-views', () => {
return requestFetchResourceViews('expenses');
});
const fetchResourceViews = useQuery(
['resource-views', 'expenses'],
(key, resourceName) => requestFetchResourceViews(resourceName),
);
const fetchExpenses = useQuery(
['expenses-table', expensesTableQuery],
() => requestFetchExpensesTable(),
const fetchResourceFields = useQuery(
['resource-fields', 'expenses'],
(key, resourceName) => requestFetchResourceFields(resourceName),
);
const fetchExpenses = useQuery(['expenses-table', expensesTableQuery], () =>
requestFetchExpensesTable(),
);
useEffect(() => {
@@ -132,6 +140,8 @@ function ExpensesList({
// Handle fetch data of manual jouranls datatable.
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
const page = pageIndex + 1;
addExpensesTableQueries({
...(sortBy.length > 0
? {
@@ -139,6 +149,8 @@ function ExpensesList({
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
page_size: pageSize,
page,
});
},
[addExpensesTableQueries],
@@ -166,7 +178,7 @@ function ExpensesList({
return (
<DashboardInsider
loading={fetchViews.isFetching}
loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
name={'expenses'}
>
<ExpenseActionsBar
@@ -187,6 +199,7 @@ function ExpensesList({
<ExpenseViewTabs />
<ExpenseDataTable
loading={fetchExpenses.isFetching}
onDeleteExpense={handleDeleteExpense}
onFetchData={handleFetchData}
onEditExpense={handleEidtExpense}
@@ -237,4 +250,5 @@ export default compose(
withExpensesActions,
withExpenses(({ expensesTableQuery }) => ({ expensesTableQuery })),
withViewsActions,
withResourceActions
)(ExpensesList);

View File

@@ -1,8 +1,11 @@
import { connect } from 'react-redux';
import { getExpenseById } from 'store/expenses/expenses.reducer';
import { getExpenseByIdFactory } from 'store/expenses/expenses.selectors';
const mapStateToProps = (state, props) => ({
expenseDetail: getExpenseById(state, props.expenseId),
});
export default () => {
const getExpenseById = getExpenseByIdFactory();
export default connect(mapStateToProps);
const mapStateToProps = (state, props) => ({
expenseDetail: getExpenseById(state, props),
});
return connect(mapStateToProps);
};

View File

@@ -1,14 +1,26 @@
import { connect } from 'react-redux';
import { getResourceViews } from 'store/customViews/customViews.selectors';
import { getExpensesItems } from 'store/expenses/expenses.selectors';
import {
getExpensesCurrentPageFactory,
getExpenseByIdFactory,
getExpensesTableQuery,
getExpensesPaginationMetaFactory,
} from 'store/expenses/expenses.selectors';
export default (mapState) => {
const getExpensesItems = getExpensesCurrentPageFactory();
const getExpensesPaginationMeta = getExpensesPaginationMetaFactory();
const mapStateToProps = (state, props) => {
const query = getExpensesTableQuery(state, props);
const mapped = {
expenses: getExpensesItems(state, state.expenses.currentViewId),
expensesCurrentPage: getExpensesItems(state, props, query),
expensesViews: getResourceViews(state, props, 'expenses'),
expensesItems: state.expenses.items,
expensesTableQuery: state.expenses.tableQuery,
expensesTableQuery: query,
expensesPagination: getExpensesPaginationMeta(state, props),
expensesLoading: state.expenses.loading,
};
return mapState ? mapState(mapped, state, props) : mapped;

View File

@@ -20,7 +20,7 @@ export const mapDispatchToProps = (dispatch) => ({
requestPublishExpense: (id) => dispatch(publishExpense({ id })),
requestDeleteBulkExpenses: (ids) => dispatch(deleteBulkExpenses({ ids })),
changeCurrentView: (id) =>
changeExpensesView: (id) =>
dispatch({
type: t.EXPENSES_SET_CURRENT_VIEW,
currentViewId: parseInt(id, 10),

View File

@@ -29,11 +29,6 @@ function BalanceSheetActionsBar({
toggleBalanceSheetFilter,
refreshBalanceSheet
}) {
const filterDropdown = FilterDropdown({
fields: [],
onFilterChange: (filterConditions) => {},
});
const handleFilterToggleClick = () => {
toggleBalanceSheetFilter();
};
@@ -81,7 +76,7 @@ function BalanceSheetActionsBar({
</If>
<Popover
content={filterDropdown}
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>

View File

@@ -8,33 +8,27 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
function FinancialReportsItem({
title,
desc,
link
}) {
function FinancialReportsItem({ title, desc, link }) {
return (
<div class="financial-reports__item">
<Link class="title" to={link}>{ title }</Link>
<p class="desc">{ desc }</p>
<Link class="title" to={link}>
{title}
</Link>
<p class="desc">{desc}</p>
</div>
);
}
function FinancialReportsSection({
sectionTitle,
reports
}) {
function FinancialReportsSection({ sectionTitle, reports }) {
return (
<div class="financial-reports__section">
<div class="section-title">{ sectionTitle }</div>
<div class="section-title">{sectionTitle}</div>
<div class="financial-reports__list">
<For render={FinancialReportsItem} of={reports} />
</div>
</div>
)
);
}
function FinancialReports({
@@ -45,7 +39,7 @@ function FinancialReports({
useEffect(() => {
changePageTitle(formatMessage({ id: 'all_financial_reports' }));
}, [changePageTitle, formatMessage]);
}, [changePageTitle, formatMessage]);
return (
<div class="financial-reports">
@@ -54,6 +48,4 @@ function FinancialReports({
);
}
export default compose(
withDashboardActions
)(FinancialReports);
export default compose(withDashboardActions)(FinancialReports);

View File

@@ -4,8 +4,7 @@ import { momentFormatter } from 'utils';
import { DateInput } from '@blueprintjs/datetime';
import { useIntl } from 'react-intl';
import { HTMLSelect, FormGroup, Intent, Position } from '@blueprintjs/core';
import Icon from 'components/Icon';
import { FieldHint } from 'components';
import { Hint } from 'components';
import { parseDateRangeQuery } from 'utils';
export default function FinancialStatementDateRange({ formik }) {
@@ -48,14 +47,12 @@ export default function FinancialStatementDateRange({ formik }) {
[formik],
);
const infoIcon = useMemo(() => <Icon icon="info-circle" iconSize={12} />, []);
return (
<>
<Col width={260}>
<FormGroup
label={intl.formatMessage({ id: 'report_date_range' })}
labelInfo={infoIcon}
labelInfo={<Hint />}
minimal={true}
fill={true}
>
@@ -71,7 +68,7 @@ export default function FinancialStatementDateRange({ formik }) {
<Col width={260}>
<FormGroup
label={intl.formatMessage({ id: 'from_date' })}
labelInfo={infoIcon}
labelInfo={<Hint />}
fill={true}
intent={formik.errors.from_date && Intent.DANGER}
>
@@ -89,7 +86,7 @@ export default function FinancialStatementDateRange({ formik }) {
<Col width={260}>
<FormGroup
label={intl.formatMessage({ id: 'to_date' })}
labelInfo={<FieldHint />}
labelInfo={<Hint />}
fill={true}
intent={formik.errors.to_date && Intent.DANGER}
>

View File

@@ -31,13 +31,6 @@ function GeneralLedgerActionsBar({
toggleGeneralLedgerSheetFilter,
refreshGeneralLedgerSheet
}) {
const filterDropdown = FilterDropdown({
fields: [],
onFilterChange: (filterConditions) => {
},
});
const handleFilterClick = () => {
toggleGeneralLedgerSheetFilter();
};
@@ -86,7 +79,6 @@ function GeneralLedgerActionsBar({
</If>
<Popover
content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}>

View File

@@ -29,11 +29,6 @@ function JournalActionsBar({
toggleJournalSheetFilter,
refreshJournalSheet,
}) {
const filterDropdown = FilterDropdown({
fields: [],
onFilterChange: (filterConditions) => {},
});
const handleFilterToggleClick = () => {
toggleJournalSheetFilter();
};
@@ -54,11 +49,10 @@ function JournalActionsBar({
<Button
className={classNames(Classes.MINIMAL, 'button--gray-highlight')}
text={'Re-calc Report'}
text={<T id={'recalc_report'} />}
onClick={handleRecalcReport}
icon={<Icon icon="refresh-16" iconSize={16} />}
/>
<If condition={journalSheetFilter}>
<Button
className={Classes.MINIMAL}
@@ -78,7 +72,6 @@ function JournalActionsBar({
</If>
<Popover
content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>

View File

@@ -12,7 +12,6 @@ import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames';
import DashboardActionsBar from "components/Dashboard/DashboardActionsBar";
import FilterDropdown from 'components/FilterDropdown';
import Icon from 'components/Icon';
import { If } from 'components';
@@ -30,11 +29,6 @@ function ReceivableAgingSummaryActionsBar({
toggleFilterReceivableAgingSummary,
refreshReceivableAgingSummary,
}) {
const filterDropdown = FilterDropdown({
fields: [],
onFilterChange: (filterConditions) => {},
});
const handleFilterToggleClick = () => {
toggleFilterReceivableAgingSummary();
};
@@ -62,7 +56,6 @@ function ReceivableAgingSummaryActionsBar({
icon={<Icon icon="refresh-16" iconSize={16} />}
onClick={handleRecalcReport}
/>
<If condition={receivableAgingFilter}>
<Button
className={Classes.MINIMAL}
@@ -82,7 +75,6 @@ function ReceivableAgingSummaryActionsBar({
</If>
<Popover
content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>

View File

@@ -1,9 +1,15 @@
import { connect } from 'react-redux';
import {
getItemsCategoriesListFactory
} from 'store/itemCategories/ItemsCategories.selectors';
export default (mapState) => {
const getItemsCategoriesList = getItemsCategoriesListFactory();
const mapStateToProps = (state, props) => {
const mapped = {
categoriesList: Object.values(state.itemCategories.categories),
categoriesList: getItemsCategoriesList(state, props),
categoriesTableLoading: state.itemCategories.loading,
};
return mapState ? mapState(mapped, state, props) : mapState;

View File

@@ -1,12 +1,16 @@
import { connect } from 'react-redux';
import {
getCategoryId,
} from 'store/itemCategories/itemsCategory.reducer';
getItemCategoryByIdFactory,
} from 'store/itemCategories/ItemsCategories.selectors';
export const mapStateToProps = (state, props) => {
return {
itemCategory: getCategoryId(state, props.itemCategoryId),
};
export default () => {
const getCategoryId = getItemCategoryByIdFactory();
const mapStateToProps = (state, props) => {
return {
itemCategory: getCategoryId(state, props),
};
};
return connect(mapStateToProps);
};
export default connect(mapStateToProps);

View File

@@ -1,15 +1,16 @@
import {connect} from 'react-redux';
import {
getViewItem,
getViewMeta,
getViewItemFactory,
getViewMetaFactory,
} from 'store/customViews/customViews.selectors';
export default () => {
const getViewItem = getViewItemFactory();
const getViewMeta = getViewMetaFactory();
export const mapStateToProps = (state, props) => {
return {
const mapStateToProps = (state, props) => ({
viewMeta: getViewMeta(state, props),
viewItem: getViewItem(state, props),
};
};
export default connect(mapStateToProps);
});
return connect(mapStateToProps);
};