- 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

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