refactoring: migrating to react-query to manage service-side state.

This commit is contained in:
a.bouhuolia
2021-02-07 08:10:21 +02:00
parent e093be0663
commit adac2386bb
284 changed files with 8255 additions and 6610 deletions

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { Menu, MenuItem, MenuDivider, Intent } from '@blueprintjs/core';
import { Icon, If } from 'components';
import { useIntl } from 'react-intl';
import withDialogs from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
import { formatMessage } from 'services/intl';
/**
* Account actions menu list.
*/
export default function AccountActionsMenuList({
account,
// #withAlert
openAlert,
openDialog,
}) {
const { formatMessage } = useIntl();
return (
);
}
// export default compose(withDialogs, withAlertsActions)(AccountActionsMenuList);

View File

@@ -1,4 +1,4 @@
import React, { memo, useState } from 'react';
import React from 'react';
import Icon from 'components/Icon';
import {
Button,
@@ -11,16 +11,14 @@ import {
Intent,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { FormattedMessage as T } from 'react-intl';
import { If, DashboardActionViewsList } from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import FilterDropdown from 'components/FilterDropdown';
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
import withAccounts from 'containers/Accounts/withAccounts';
import withAlertActions from 'containers/Alert/withAlertActions';
@@ -30,61 +28,39 @@ import { compose } from 'utils';
* Accounts actions bar.
*/
function AccountsActionsBar({
// #withDialogActions
openDialog,
accountsViews,
// #withResourceDetail
resourceFields,
// #withAccountsTableActions
addAccountsTableQueries,
setAccountsBulkAction,
// #withAccounts
accountsTableQuery,
accountsSelectedRows,
// #withAlertActions
openAlert,
// #ownProps
onFilterChanged,
}) {
const [filterCount, setFilterCount] = useState(
accountsTableQuery?.filter_roles?.length || 0,
);
const { resourceViews } = useAccountsChartContext();
const onClickNewAccount = () => {
openDialog('account-form', {});
};
// Filter dropdown.
const filterDropdown = FilterDropdown({
fields: resourceFields,
initialConditions: accountsTableQuery.filter_roles,
initialCondition: {
fieldKey: 'name',
comparator: 'contains',
value: '',
},
onFilterChange: (filterConditions) => {
setFilterCount(filterConditions.length || 0);
addAccountsTableQueries({
filter_roles: filterConditions || '',
});
onFilterChanged && onFilterChanged(filterConditions);
},
});
// handle bulk accounts delete.
const handleBulkDelete = () => {
openAlert('accounts-bulk-delete', { accountsIds: accountsSelectedRows });
};
// Handle bulk accounts activate.
const handelBulkActivate = () => {
openAlert('accounts-bulk-activate', { accountsIds: accountsSelectedRows });
};
// Handle bulk accounts inactivate.
const handelBulkInactive = () => {
openAlert('accounts-bulk-inactivate', { accountsIds: accountsSelectedRows });
openAlert('accounts-bulk-inactivate', {
accountsIds: accountsSelectedRows,
});
};
return (
@@ -92,7 +68,7 @@ function AccountsActionsBar({
<NavbarGroup>
<DashboardActionViewsList
resourceName={'accounts'}
views={accountsViews}
views={resourceViews}
/>
<NavbarDivider />
@@ -104,23 +80,20 @@ function AccountsActionsBar({
/>
<Popover
minimal={true}
content={filterDropdown}
content={''}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
canOutsideClickClose={true}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter', {
'has-active-filters': filterCount > 0,
'has-active-filters': false,
})}
text={
(filterCount <= 0) ? (
true ? (
<T id={'filter'} />
) : (
<T
id={'count_filters_applied'}
values={{ count: filterCount }}
/>
<T id={'count_filters_applied'} values={{ count: 0 }} />
)
}
icon={<Icon icon="filter-16" iconSize={16} />}
@@ -169,30 +142,10 @@ function AccountsActionsBar({
);
}
// Momerize the component.
const AccountsActionsBarMemo = memo(AccountsActionsBar);
const mapStateToProps = (state, props) => ({
resourceName: 'accounts',
});
const withAccountsActionsBar = connect(mapStateToProps);
const comp = compose(
withAccountsActionsBar,
export default compose(
withDialogActions,
withAccounts(
({ accountsSelectedRows, accountsViews, accountsTableQuery }) => ({
accountsViews,
accountsTableQuery,
accountsSelectedRows,
}),
),
withResourceDetail(({ resourceFields }) => ({
resourceFields,
withAccounts(({ accountsSelectedRows }) => ({
accountsSelectedRows,
})),
withAccountsTableActions,
withAlertActions
)(AccountsActionsBarMemo);
export default comp;
withAlertActions,
)(AccountsActionsBar);

View File

@@ -1,23 +1,15 @@
import React, { useEffect, useState, useCallback } from 'react';
import { Route, Switch } from 'react-router-dom';
import { useQuery } from 'react-query';
import {
useIntl,
} from 'react-intl';
import React, { useEffect } from 'react';
import { useIntl } from 'react-intl';
import 'style/pages/Accounts/List.scss';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { AccountsChartProvider } from 'containers/Accounts/AccountsChartProvider';
import AccountsViewPage from 'containers/Accounts/AccountsViewPage';
import AccountsActionsBar from 'containers/Accounts/AccountsActionsBar';
import AccountsAlerts from './AccountsAlerts';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
import withViewsActions from 'containers/Views/withViewsActions';
import withAccounts from 'containers/Accounts/withAccounts';
import { compose } from 'utils';
@@ -29,83 +21,29 @@ function AccountsChart({
// #withDashboardActions
changePageTitle,
// #withViewsActions
requestFetchResourceViews,
// #withResourceActions
requestFetchResourceFields,
// #withAccountsTableActions
requestFetchAccountsTable,
addAccountsTableQueries,
// #withAccounts
accountsTableQuery,
accountsTableQuery
}) {
const { formatMessage } = useIntl();
// Fetch accounts resource views and fields.
const fetchResourceViews = useQuery(
['resource-views', 'accounts'],
(key, resourceName) => requestFetchResourceViews(resourceName),
);
// Fetch the accounts resource fields.
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],
(key, q) => requestFetchAccountsTable(),
);
useEffect(() => {
changePageTitle(formatMessage({ id: 'chart_of_accounts' }));
}, [changePageTitle, formatMessage]);
// Refetches accounts data table when current custom view changed.
const handleFilterChanged = useCallback(() => {
}, []);
// Handle fetch data of accounts datatable.
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
addAccountsTableQueries({
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
});
},
[addAccountsTableQueries],
);
return (
<DashboardInsider
loading={fetchResourceFields.isFetching || fetchResourceViews.isFetching}
name={'accounts-chart'}
>
<AccountsActionsBar
onFilterChanged={handleFilterChanged}
/>
<AccountsChartProvider query={accountsTableQuery}>
<AccountsActionsBar />
<DashboardPageContent>
<AccountsViewPage />
</DashboardPageContent>
<AccountsAlerts />
</DashboardInsider>
</AccountsChartProvider>
);
}
export default compose(
withAccountsActions,
withAccountsTableActions,
withViewsActions,
withResourceActions,
withDashboardActions,
withAccounts(({ accountsTableQuery }) => ({
accountsTableQuery,

View File

@@ -0,0 +1,51 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useResourceFields, useAccounts } from 'hooks/query';
const AccountsChartContext = createContext();
/**
* Accounts chart data provider.
*/
function AccountsChartProvider({ query, ...props }) {
// Fetch accounts resource views and fields.
const { data: resourceViews, isFetching: isViewsLoading } = useResourceViews(
'accounts',
);
// Fetch the accounts resource fields.
const {
data: resourceFields,
isFetching: isFieldsLoading,
} = useResourceFields('accounts');
// Fetch accounts list according to the given custom view id.
const { data: accounts, isFetching: isAccountsLoading } = useAccounts(
query,
{ keepPreviousData: true }
);
// Provider payload.
const provider = {
accounts,
resourceFields,
resourceViews,
isAccountsLoading,
isFieldsLoading,
isViewsLoading,
};
return (
<DashboardInsider
loading={isViewsLoading || isFieldsLoading}
name={'accounts-chart'}
>
<AccountsChartContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useAccountsChartContext = () => React.useContext(AccountsChartContext);
export { AccountsChartProvider, useAccountsChartContext };

View File

@@ -1,98 +1,53 @@
import React, { useCallback, useState, useMemo, useEffect } from 'react';
import {
Button,
Popover,
Position,
} from '@blueprintjs/core';
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import React, { useCallback, useMemo } from 'react';
import { Button, Popover, Position } from '@blueprintjs/core';
import { useIntl } from 'react-intl';
import classNames from 'classnames';
import { Icon, DataTable, If } from 'components';
import { saveInvoke, compose } from 'utils';
import { useUpdateEffect } from 'hooks';
import { Icon, DataTable } from 'components';
import { saveInvoke } from 'utils';
import { CLASSES } from 'common/classes';
import { NormalCell, BalanceCell, AccountActionsMenuList } from './components';
import { NormalCell, BalanceCell, AccountActionsMenu } from './components';
import { TableFastCell } from 'components';
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withAccounts from 'containers/Accounts/withAccounts';
import withCurrentView from 'containers/Views/withCurrentView';
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
/**
* Accounts data-table.
*/
function AccountsDataTable({
// #withDashboardActions
accountsTable,
accountsLoading,
// #
currentViewId,
export default function AccountsDataTable({
// #ownProps
accounts,
loading,
onFetchData,
onSelectedRowsChange,
onDeleteAccount,
onInactivateAccount,
onActivateAccount,
onEditAccount,
onNewChildAccount
// onDeleteAccount,
// onInactivateAccount,
// onActivateAccount,
// onEditAccount,
// onNewChildAccount,
}) {
const [isMounted, setIsMounted] = useState(false);
const { formatMessage } = useIntl();
useEffect(() => {
setIsMounted(false);
}, [currentViewId]);
useUpdateEffect(() => {
if (!accountsLoading) {
setIsMounted(true);
}
}, [accountsLoading, setIsMounted]);
const ActionsCell = useMemo(() =>
({ row }) => (
const ActionsCell = useMemo(
() => ({ row }) => (
<Popover
content={<AccountActionsMenuList
account={row.original}
onDeleteAccount={onDeleteAccount}
onInactivateAccount={onInactivateAccount}
onActivateAccount={onActivateAccount}
onEditAccount={onEditAccount}
/>}
content={<AccountActionsMenu row={row} />}
position={Position.RIGHT_TOP}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
), [
onDeleteAccount,
onInactivateAccount,
onActivateAccount,
onEditAccount
]);
),
[],
);
const RowContextMenu = useMemo(() => ({ row }) => (
<AccountActionsMenuList
account={row.original}
onDeleteAccount={onDeleteAccount}
onInactivateAccount={onInactivateAccount}
onActivateAccount={onActivateAccount}
onEditAccount={onEditAccount}
onNewChildAccount={onNewChildAccount}
/>
), [
onDeleteAccount,
onInactivateAccount,
onActivateAccount,
onEditAccount,
onNewChildAccount
]);
const RowContextMenu = useMemo(
() => ({ row }) => <AccountActionsMenu row={row} />,
[],
);
const columns = useMemo(
() => [
@@ -144,6 +99,7 @@ function AccountsDataTable({
Cell: ActionsCell,
className: 'actions',
width: 50,
skeletonWidthMin: 100,
},
],
[ActionsCell, formatMessage],
@@ -172,13 +128,14 @@ function AccountsDataTable({
<DataTable
noInitialFetch={true}
columns={columns}
data={accountsTable}
data={accounts}
onFetchData={handleDatatableFetchData}
selectionColumn={true}
expandable={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
loading={accountsLoading && !isMounted}
loading={loading}
headerLoading={loading}
rowContextMenu={RowContextMenu}
rowClassNames={rowClassNames}
autoResetExpanded={false}
@@ -189,6 +146,8 @@ function AccountsDataTable({
selectionColumnWidth={50}
TableCellRenderer={TableFastCell}
TableRowsRenderer={TableVirtualizedListRows}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
// #TableVirtualizedListRows props.
vListrowHeight={42}
vListOverscanRowCount={10}
@@ -196,14 +155,3 @@ function AccountsDataTable({
</div>
);
}
export default compose(
withRouter,
withCurrentView,
withDashboardActions,
withAccountsActions,
withAccounts(({ accountsLoading, accountsTable }) => ({
accountsLoading,
accountsTable,
})),
)(AccountsDataTable);

View File

@@ -1,7 +1,8 @@
import React, { memo } from 'react';
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
import AccountsDataTable from 'containers/Accounts/AccountsDataTable';
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
@@ -13,45 +14,50 @@ import { compose } from 'utils';
* Accounts view page.
*/
function AccountsViewPage({
// #withAlertActions
openAlert,
// #withDialog.
openDialog,
// #withAccountsTableActions
setSelectedRowsAccounts
setSelectedRowsAccounts,
}) {
const { isAccountsLoading, accounts } = useAccountsChartContext();
// Handle delete action account.
const handleDeleteAccount = (account) => {
openAlert('account-delete', { accountId: account.id })
// openAlert('account-delete', { accountId: account.id });
};
// Handle activate action account.
const handleActivateAccount = (account) => {
openAlert('account-activate', { accountId: account.id });
// openAlert('account-activate', { accountId: account.id });
};
// Handle inactivate action account.
const handleInactivateAccount = (account) => {
openAlert('account-inactivate', { accountId: account.id });
// openAlert('account-inactivate', { accountId: account.id });
};
// Handle select accounts datatable rows.
const handleSelectedRowsChange = (selectedRows) => {
const selectedRowsIds = selectedRows.map(r => r.id);
setSelectedRowsAccounts(selectedRowsIds);
// const handleSelectedRowsChange = (selectedRows) => {
// const selectedRowsIds = selectedRows.map((r) => r.id);
// setSelectedRowsAccounts(selectedRowsIds);
// };
// Handle edit account action.
const handleEditAccount = (account) => {
// openDialog('account-form', { action: 'edit', id: account.id });
};
const handleEditAccount = (account) => {
openDialog('account-form', { action: 'edit', id: account.id });
}
// Handle new child button click.
const handleNewChildAccount = (account) => {
openDialog('account-form', {
action: 'new_child',
parentAccountId: account.id,
accountType: account.account_type,
});
// openDialog('account-form', {
// action: 'new_child',
// parentAccountId: account.id,
// accountType: account.account_type,
// });
};
return (
@@ -63,22 +69,22 @@ function AccountsViewPage({
<AccountsViewsTabs />
<AccountsDataTable
onDeleteAccount={handleDeleteAccount}
onInactivateAccount={handleInactivateAccount}
onActivateAccount={handleActivateAccount}
onSelectedRowsChange={handleSelectedRowsChange}
onEditAccount={handleEditAccount}
onNewChildAccount={handleNewChildAccount}
loading={isAccountsLoading}
accounts={accounts}
// onDeleteAccount={handleDeleteAccount}
// onInactivateAccount={handleInactivateAccount}
// onActivateAccount={handleActivateAccount}
// onSelectedRowsChange={handleSelectedRowsChange}
// onEditAccount={handleEditAccount}
// onNewChildAccount={handleNewChildAccount}
/>
</Route>
</Switch>
);
}
const AccountsViewPageMemo = memo(AccountsViewPage);
export default compose(
withAlertsActions,
withAccountsTableActions,
withDialogActions
)(AccountsViewPageMemo);
withDialogActions,
)(AccountsViewPage);

View File

@@ -1,15 +1,12 @@
import React, { useEffect, useMemo, memo, useCallback } from 'react';
import { useHistory } from 'react-router';
import { connect } from 'react-redux';
import React, { useMemo, useCallback } from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { useParams, withRouter } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { pick } from 'lodash';
import { DashboardViewsTabs } from 'components';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withAccounts from 'containers/Accounts/withAccounts';
import { useAccountsChartContext } from 'containers/Accounts/AccountsChartProvider';
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
import withViewDetail from 'containers/Views/withViewDetails';
import { compose } from 'utils';
@@ -17,41 +14,28 @@ import { compose } from 'utils';
* Accounts views tabs.
*/
function AccountsViewsTabs({
// #withViewDetail
viewId,
viewItem,
// #withAccounts
accountsViews,
// #withAccountsTableActions
changeAccountsCurrentView,
// #withDashboardActions
setTopbarEditView,
changePageSubtitle,
addAccountsTableQuery,
}) {
const history = useHistory();
const { resourceViews } = useAccountsChartContext();
const { custom_view_id: customViewId = null } = useParams();
useEffect(() => {
setTopbarEditView(customViewId);
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
}, [customViewId, viewItem, changePageSubtitle, setTopbarEditView]);
const handleTabChange = useCallback(
(viewId) => {
addAccountsTableQuery({
custom_view_id: viewId || null,
});
},
[addAccountsTableQuery],
);
// Handle click a new view tab.
const handleClickNewView = useCallback(() => {
setTopbarEditView(null);
history.push('/custom_views/accounts/new');
}, [setTopbarEditView]);
const handleTabChange = useCallback((viewId) => {
changeAccountsCurrentView(viewId || -1);
}, [changeAccountsCurrentView]);
const tabs = useMemo(() => accountsViews.map((view) => ({
...pick(view, ['name', 'id']),
})), [accountsViews]);;
const tabs = useMemo(
() =>
resourceViews.map((view) => ({
...pick(view, ['name', 'id']),
})),
[resourceViews],
);
return (
<Navbar className="navbar--dashboard-views">
@@ -68,21 +52,6 @@ function AccountsViewsTabs({
);
}
const AccountsViewsTabsMemo = memo(AccountsViewsTabs);
const mapStateToProps = (state, ownProps) => ({
viewId: -1,
});
const withAccountsViewsTabs = connect(mapStateToProps);
export default compose(
withRouter,
withAccountsViewsTabs,
withDashboardActions,
withAccounts(({ accountsViews }) => ({
accountsViews,
})),
withAccountsTableActions,
withViewDetail(),
)(AccountsViewsTabsMemo);
)(AccountsViewsTabs);

View File

@@ -13,17 +13,8 @@ import classNames from 'classnames';
import { Icon, Money, If } from 'components';
import { saveInvoke } from 'utils';
import { formatMessage } from 'services/intl';
import { POPOVER_CONTENT_SIZING } from '@blueprintjs/core/lib/esm/common/classes';
export function AccountActionsMenuList({
account,
onNewChildAccount,
onEditAccount,
onActivateAccount,
onInactivateAccount,
onDeleteAccount,
}) {
export function AccountActionsMenu({ row: { original } }) {
return (
<Menu>
<MenuItem
@@ -34,38 +25,39 @@ export function AccountActionsMenuList({
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_account' })}
onClick={() => saveInvoke(onEditAccount, account)}
// onClick={handleEditAccount}
/>
<MenuItem
icon={<Icon icon="plus" />}
text={formatMessage({ id: 'new_child_account' })}
onClick={() => saveInvoke(onNewChildAccount, account)}
// onClick={handleNewChildAccount}
/>
<MenuDivider />
<If condition={account.active}>
<If condition={original.active}>
<MenuItem
text={formatMessage({ id: 'inactivate_account' })}
icon={<Icon icon="pause-16" iconSize={16} />}
onClick={() => saveInvoke(onInactivateAccount, account)}
// onClick={handleInactivateAccount}
/>
</If>
<If condition={!account.active}>
<If condition={!original.active}>
<MenuItem
text={formatMessage({ id: 'activate_account' })}
icon={<Icon icon="play-16" iconSize={16} />}
onClick={() => saveInvoke(onActivateAccount, account)}
// onClick={handleActivateAccount}
/>
</If>
<MenuItem
text={formatMessage({ id: 'delete_account' })}
icon={<Icon icon="trash-16" iconSize={16} />}
intent={Intent.DANGER}
onClick={() => saveInvoke(onDeleteAccount, account)}
// onClick={handleDeleteA ccount}
/>
</Menu>
);
}
export function NormalCell({ cell: { value } }) {
const { formatMessage } = useIntl();
const arrowDirection = value === 'credit' ? 'down' : 'up';

View File

@@ -16,10 +16,10 @@ const mapActionsToProps = (dispatch) => ({
key,
value,
}),
addAccountsTableQueries: (queries) =>
addAccountsTableQuery: (queries) =>
dispatch({
type: t.ACCOUNTS_TABLE_QUERIES_ADD,
queries,
payload: { queries },
}),
setSelectedRowsAccounts: (selectedRows) =>
dispatch({