mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
WIP Version 0.0.1
This commit is contained in:
157
client/src/containers/Accounts/AccountsActionsBar.js
Normal file
157
client/src/containers/Accounts/AccountsActionsBar.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
MenuItem,
|
||||
Menu,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { If } from 'components';
|
||||
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import DialogConnect from 'connectors/Dialog.connector';
|
||||
|
||||
import FilterDropdown from 'components/FilterDropdown';
|
||||
|
||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
|
||||
import {compose} from 'utils';
|
||||
|
||||
|
||||
function AccountsActionsBar({
|
||||
openDialog,
|
||||
accountsViews,
|
||||
|
||||
resourceFields,
|
||||
addAccountsTableQueries,
|
||||
|
||||
selectedRows = [],
|
||||
onFilterChanged,
|
||||
onBulkDelete,
|
||||
onBulkArchive,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const [filterCount, setFilterCount] = useState(0);
|
||||
|
||||
const onClickNewAccount = () => { openDialog('account-form', {}); };
|
||||
const onClickViewItem = (view) => {
|
||||
history.push(view
|
||||
? `/dashboard/accounts/${view.id}/custom_view` : '/dashboard/accounts');
|
||||
};
|
||||
|
||||
const viewsMenuItems = accountsViews.map((view) => {
|
||||
return (<MenuItem onClick={() => onClickViewItem(view)} text={view.name} />);
|
||||
});
|
||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
|
||||
|
||||
const filterDropdown = FilterDropdown({
|
||||
fields: resourceFields,
|
||||
onFilterChange: (filterConditions) => {
|
||||
setFilterCount(filterConditions.length || 0);
|
||||
addAccountsTableQueries({
|
||||
filter_roles: filterConditions || '',
|
||||
});
|
||||
onFilterChanged && onFilterChanged(filterConditions);
|
||||
},
|
||||
});
|
||||
|
||||
const handleBulkArchive = useCallback(() => {
|
||||
onBulkArchive && onBulkArchive(selectedRows.map(r => r.id));
|
||||
}, [onBulkArchive, selectedRows]);
|
||||
|
||||
const handleBulkDelete = useCallback(() => {
|
||||
onBulkDelete && onBulkDelete(selectedRows.map(r => r.id));
|
||||
}, [onBulkDelete, selectedRows]);
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Popover
|
||||
content={<Menu>{viewsMenuItems}</Menu>}
|
||||
minimal={true}
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||
icon={<Icon icon='table' />}
|
||||
text='Table Views'
|
||||
rightIcon={'caret-down'}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
<NavbarDivider />
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='plus' />}
|
||||
text='New Account'
|
||||
onClick={onClickNewAccount}
|
||||
/>
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={filterDropdown}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}>
|
||||
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||
text={filterCount <= 0 ? 'Filter' : `${filterCount} filters applied`}
|
||||
icon={ <Icon icon="filter" /> }/>
|
||||
</Popover>
|
||||
|
||||
<If condition={hasSelectedRows}>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='archive' iconSize={15} />}
|
||||
text='Archive'
|
||||
onClick={handleBulkArchive}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='trash' iconSize={15} />}
|
||||
text='Delete'
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleBulkDelete}
|
||||
/>
|
||||
</If>
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='file-import' />}
|
||||
text='Import'
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='file-export' />}
|
||||
text='Export'
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
resourceName: 'accounts',
|
||||
});
|
||||
|
||||
const withAccountsActionsBar = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withAccountsActionsBar,
|
||||
DialogConnect,
|
||||
withAccounts,
|
||||
withResourceDetail,
|
||||
withAccountsTableActions,
|
||||
)(AccountsActionsBar);
|
||||
267
client/src/containers/Accounts/AccountsChart.js
Normal file
267
client/src/containers/Accounts/AccountsChart.js
Normal file
@@ -0,0 +1,267 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Route,
|
||||
Switch,
|
||||
} from 'react-router-dom';
|
||||
import { Alert, Intent } from '@blueprintjs/core';
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
import AppToaster from 'components/AppToaster';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
|
||||
import AccountsDataTable from 'containers/Accounts/AccountsDataTable';
|
||||
import DashboardActionsBar from 'containers/Accounts/AccountsActionsBar';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboard';
|
||||
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';
|
||||
|
||||
|
||||
function AccountsChart({
|
||||
|
||||
// #withDashboard
|
||||
changePageTitle,
|
||||
|
||||
// #withAccountsActions
|
||||
requestDeleteAccount,
|
||||
requestInactiveAccount,
|
||||
|
||||
// #withViewsActions
|
||||
requestFetchResourceViews,
|
||||
|
||||
// #withResourceActions
|
||||
requestFetchResourceFields,
|
||||
|
||||
// #withAccountsTableActions
|
||||
requestFetchAccountsTable,
|
||||
requestDeleteBulkAccounts,
|
||||
addAccountsTableQueries,
|
||||
|
||||
// #withAccounts
|
||||
accountsTableQuery,
|
||||
}) {
|
||||
const [deleteAccount, setDeleteAccount] = useState(false);
|
||||
const [inactiveAccount, setInactiveAccount] = useState(false);
|
||||
const [bulkDelete, setBulkDelete] = useState(false);
|
||||
const [selectedRows, setSelectedRows] = useState([]);
|
||||
|
||||
const [tableLoading, setTableLoading] = useState(false);
|
||||
|
||||
// Fetch accounts resource views and fields.
|
||||
const fetchHook = useQuery('resource-accounts', () => {
|
||||
return Promise.all([
|
||||
requestFetchResourceViews('accounts'),
|
||||
requestFetchResourceFields('accounts'),
|
||||
]);
|
||||
});
|
||||
|
||||
// Fetch accounts list according to the given custom view id.
|
||||
const fetchAccountsHook = useQuery(['accounts-table', accountsTableQuery],
|
||||
() => requestFetchAccountsTable());
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle('Chart of Accounts');
|
||||
}, [changePageTitle]);
|
||||
|
||||
// Handle click and cancel/confirm account delete
|
||||
const handleDeleteAccount = (account) => { setDeleteAccount(account); };
|
||||
|
||||
// handle cancel delete account alert.
|
||||
const handleCancelAccountDelete = useCallback(() => { setDeleteAccount(false); }, []);
|
||||
|
||||
// Handle confirm account delete
|
||||
const handleConfirmAccountDelete = useCallback(() => {
|
||||
requestDeleteAccount(deleteAccount.id).then(() => {
|
||||
setDeleteAccount(false);
|
||||
AppToaster.show({ message: 'the_account_has_been_deleted' });
|
||||
}).catch(errors => {
|
||||
setDeleteAccount(false);
|
||||
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
|
||||
AppToaster.show({
|
||||
message: 'cannot_delete_predefined_account',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
||||
AppToaster.show({
|
||||
message: 'cannot_delete_account_has_associated_transactions'
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [deleteAccount, requestDeleteAccount]);
|
||||
|
||||
// Handle cancel/confirm account inactive.
|
||||
const handleInactiveAccount = useCallback((account) => {
|
||||
setInactiveAccount(account);
|
||||
}, []);
|
||||
|
||||
// Handle cancel inactive account alert.
|
||||
const handleCancelInactiveAccount = useCallback(() => {
|
||||
setInactiveAccount(false);
|
||||
}, []);
|
||||
|
||||
// Handle confirm account activation.
|
||||
const handleConfirmAccountActive = useCallback(() => {
|
||||
requestInactiveAccount(inactiveAccount.id).then(() => {
|
||||
setInactiveAccount(false);
|
||||
requestFetchAccountsTable();
|
||||
AppToaster.show({ message: 'the_account_has_been_inactivated' });
|
||||
});
|
||||
}, [inactiveAccount, requestFetchAccountsTable, requestInactiveAccount]);
|
||||
|
||||
|
||||
const handleEditAccount = (account) => {
|
||||
|
||||
};
|
||||
|
||||
const handleRestoreAccount = (account) => {
|
||||
|
||||
};
|
||||
|
||||
const handleBulkDelete = useCallback((accountsIds) => {
|
||||
setBulkDelete(accountsIds);
|
||||
}, [setBulkDelete]);
|
||||
|
||||
const handleConfirmBulkDelete = useCallback(() => {
|
||||
requestDeleteBulkAccounts(bulkDelete).then(() => {
|
||||
setBulkDelete(false);
|
||||
AppToaster.show({ message: 'the_accounts_have_been_deleted' });
|
||||
}).catch((error) => {
|
||||
setBulkDelete(false);
|
||||
});
|
||||
}, [requestDeleteBulkAccounts, bulkDelete]);
|
||||
|
||||
const handleCancelBulkDelete = useCallback(() => {
|
||||
setBulkDelete(false);
|
||||
}, []);
|
||||
|
||||
const handleBulkArchive = useCallback((accounts) => {
|
||||
|
||||
}, []);
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = useCallback((accounts) => {
|
||||
setSelectedRows(accounts);
|
||||
}, [setSelectedRows]);
|
||||
|
||||
// Refetches accounts data table when current custom view changed.
|
||||
const handleFilterChanged = useCallback(() => {
|
||||
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 }) => {
|
||||
addAccountsTableQueries({
|
||||
...(sortBy.length > 0) ? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
} : {},
|
||||
});
|
||||
fetchAccountsHook.refetch();
|
||||
}, [fetchAccountsHook, addAccountsTableQueries]);
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
loading={fetchHook.isFetching}
|
||||
name={'accounts-chart'}>
|
||||
<DashboardActionsBar
|
||||
selectedRows={selectedRows}
|
||||
onFilterChanged={handleFilterChanged}
|
||||
onBulkDelete={handleBulkDelete}
|
||||
onBulkArchive={handleBulkArchive} />
|
||||
|
||||
<DashboardPageContent>
|
||||
<Switch>
|
||||
<Route
|
||||
exact={true}
|
||||
path={[
|
||||
'/dashboard/accounts/:custom_view_id/custom_view',
|
||||
'/dashboard/accounts',
|
||||
]}>
|
||||
<AccountsViewsTabs
|
||||
onViewChanged={handleViewChanged} />
|
||||
|
||||
<AccountsDataTable
|
||||
onDeleteAccount={handleDeleteAccount}
|
||||
onInactiveAccount={handleInactiveAccount}
|
||||
onRestoreAccount={handleRestoreAccount}
|
||||
onEditAccount={handleEditAccount}
|
||||
onFetchData={handleFetchData}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
loading={tableLoading} />
|
||||
</Route>
|
||||
</Switch>
|
||||
|
||||
<Alert
|
||||
cancelButtonText="Cancel"
|
||||
confirmButtonText="Move to Trash"
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
isOpen={deleteAccount}
|
||||
onCancel={handleCancelAccountDelete}
|
||||
onConfirm={handleConfirmAccountDelete}>
|
||||
<p>
|
||||
Are you sure you want to move <b>filename</b> to Trash? You will be able to restore it later,
|
||||
but it will become private to you.
|
||||
</p>
|
||||
</Alert>
|
||||
|
||||
<Alert
|
||||
cancelButtonText="Cancel"
|
||||
confirmButtonText="Inactivate"
|
||||
icon="trash"
|
||||
intent={Intent.WARNING}
|
||||
isOpen={inactiveAccount}
|
||||
onCancel={handleCancelInactiveAccount}
|
||||
onConfirm={handleConfirmAccountActive}>
|
||||
<p>
|
||||
Are you sure you want to move <b>filename</b> to Trash? You will be able to restore it later,
|
||||
but it will become private to you.
|
||||
</p>
|
||||
</Alert>
|
||||
|
||||
<Alert
|
||||
cancelButtonText="Cancel"
|
||||
confirmButtonText="Delete"
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
isOpen={bulkDelete}
|
||||
onCancel={handleCancelBulkDelete}
|
||||
onConfirm={handleConfirmBulkDelete}>
|
||||
<p>
|
||||
Are you sure you want to move <b>filename</b> to Trash? You will be able to restore it later,
|
||||
but it will become private to you.
|
||||
</p>
|
||||
</Alert>
|
||||
</DashboardPageContent>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAccountsActions,
|
||||
withAccountsTableActions,
|
||||
withViewsActions,
|
||||
withResourceActions,
|
||||
withDashboardActions,
|
||||
|
||||
withAccounts,
|
||||
)(AccountsChart);
|
||||
191
client/src/containers/Accounts/AccountsDataTable.js
Normal file
191
client/src/containers/Accounts/AccountsDataTable.js
Normal file
@@ -0,0 +1,191 @@
|
||||
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
Classes,
|
||||
Tooltip,
|
||||
} from '@blueprintjs/core';
|
||||
import Icon from 'components/Icon';
|
||||
import { compose } from 'utils';
|
||||
import DialogConnect from 'connectors/Dialog.connector';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import DataTable from 'components/DataTable';
|
||||
import Money from 'components/Money';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboard';
|
||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
|
||||
import {If} from 'components';
|
||||
|
||||
function AccountsDataTable({
|
||||
// # withAccounts
|
||||
accounts,
|
||||
accountsLoading,
|
||||
|
||||
// # withDialog.
|
||||
openDialog,
|
||||
|
||||
// own properties
|
||||
loading,
|
||||
onFetchData,
|
||||
onSelectedRowsChange,
|
||||
onDeleteAccount,
|
||||
onInactiveAccount,
|
||||
}) {
|
||||
const [initialMount, setInitialMount] = useState(false);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (!accountsLoading) {
|
||||
setInitialMount(true);
|
||||
}
|
||||
}, [accountsLoading, setInitialMount]);
|
||||
|
||||
const handleEditAccount = useCallback((account) => () => {
|
||||
openDialog('account-form', { action: 'edit', id: account.id });
|
||||
}, [openDialog]);
|
||||
|
||||
const handleNewParentAccount = useCallback((account) => {
|
||||
openDialog('account-form', { action: 'new_child', id: account.id });
|
||||
}, [openDialog]);
|
||||
|
||||
const actionMenuList = useCallback((account) => (
|
||||
<Menu>
|
||||
<MenuItem text='View Details' />
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text='Edit Account'
|
||||
onClick={handleEditAccount(account)} />
|
||||
<MenuItem
|
||||
text='New Account'
|
||||
onClick={() => handleNewParentAccount(account)} />
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text='Inactivate Account'
|
||||
onClick={() => onInactiveAccount(account)} />
|
||||
<MenuItem
|
||||
text='Delete Account'
|
||||
onClick={() => onDeleteAccount(account)} />
|
||||
</Menu>
|
||||
), [handleEditAccount, onDeleteAccount, onInactiveAccount]);
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
id: 'name',
|
||||
Header: '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;
|
||||
},
|
||||
className: 'account_name',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
id: 'code',
|
||||
Header: 'Code',
|
||||
accessor: 'code',
|
||||
className: 'code',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
Header: 'Type',
|
||||
accessor: 'type.name',
|
||||
className: 'type',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
id: 'normal',
|
||||
Header: 'Normal',
|
||||
Cell: ({ cell }) => {
|
||||
const account = cell.row.original;
|
||||
const type = account.type ? account.type.normal : '';
|
||||
const arrowDirection = type === 'credit' ? 'down' : 'up';
|
||||
|
||||
return (<Icon icon={`arrow-${arrowDirection}`} />);
|
||||
},
|
||||
className: 'normal',
|
||||
width: 75,
|
||||
},
|
||||
{
|
||||
id: 'balance',
|
||||
Header: '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>);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_TOP}>
|
||||
<Button icon={<Icon icon='ellipsis-h' />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
}
|
||||
], [actionMenuList]);
|
||||
|
||||
const selectionColumn = useMemo(() => ({
|
||||
minWidth: 50,
|
||||
width: 50,
|
||||
maxWidth: 50,
|
||||
}), [])
|
||||
|
||||
const handleDatatableFetchData = useCallback((...params) => {
|
||||
onFetchData && onFetchData(...params);
|
||||
}, []);
|
||||
|
||||
const handleSelectedRowsChange = useCallback((selectedRows) => {
|
||||
onSelectedRowsChange && onSelectedRowsChange(selectedRows.map(s => s.original));
|
||||
}, [onSelectedRowsChange]);
|
||||
|
||||
return (
|
||||
<LoadingIndicator loading={loading} mount={false}>
|
||||
<If condition={loading}>
|
||||
asdasdsadsa
|
||||
</If>
|
||||
<DataTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={accounts}
|
||||
onFetchData={handleDatatableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={selectionColumn}
|
||||
expandable={true}
|
||||
treeGraph={true}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
loading={accountsLoading && !initialMount}
|
||||
spinnerProps={{size: 30}} />
|
||||
</LoadingIndicator>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
DialogConnect,
|
||||
withDashboardActions,
|
||||
withAccountsActions,
|
||||
withAccounts,
|
||||
)(AccountsDataTable);
|
||||
132
client/src/containers/Accounts/AccountsViewsTabs.js
Normal file
132
client/src/containers/Accounts/AccountsViewsTabs.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import React, {useEffect, useCallback} from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
Alignment,
|
||||
Navbar,
|
||||
NavbarGroup,
|
||||
Tabs,
|
||||
Tab,
|
||||
Button
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams, withRouter } from 'react-router-dom';
|
||||
import Icon from 'components/Icon';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { compose } from 'utils';
|
||||
import {useUpdateEffect} from 'hooks';
|
||||
|
||||
import withDashboard from 'containers/Dashboard/withDashboard';
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
||||
import withViewDetail from 'containers/Views/withViewDetails';
|
||||
|
||||
function AccountsViewsTabs({
|
||||
// #withViewDetail
|
||||
viewId,
|
||||
viewItem,
|
||||
|
||||
// #withAccounts
|
||||
accountsViews,
|
||||
|
||||
// #withAccountsTableActions
|
||||
addAccountsTableQueries,
|
||||
changeAccountsCurrentView,
|
||||
|
||||
// #withDashboard
|
||||
setTopbarEditView,
|
||||
changePageSubtitle,
|
||||
|
||||
// props
|
||||
customViewChanged,
|
||||
onViewChanged,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { custom_view_id: customViewId = null } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
changeAccountsCurrentView(customViewId || -1);
|
||||
setTopbarEditView(customViewId);
|
||||
changePageSubtitle((customViewId && viewItem) ? viewItem.name : '');
|
||||
|
||||
addAccountsTableQueries({
|
||||
custom_view_id: customViewId,
|
||||
});
|
||||
|
||||
return () => {
|
||||
setTopbarEditView(null);
|
||||
changePageSubtitle('');
|
||||
changeAccountsCurrentView(null)
|
||||
};
|
||||
}, [customViewId]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
onViewChanged && onViewChanged(customViewId);
|
||||
}, [customViewId]);
|
||||
|
||||
|
||||
// Handle click a new view tab.
|
||||
const handleClickNewView = () => {
|
||||
setTopbarEditView(null);
|
||||
history.push('/dashboard/custom_views/accounts/new');
|
||||
};
|
||||
|
||||
// Handle view tab link click.
|
||||
const handleViewLinkClick = () => {
|
||||
setTopbarEditView(customViewId);
|
||||
};
|
||||
|
||||
const tabs = accountsViews.map((view) => {
|
||||
const baseUrl = '/dashboard/accounts';
|
||||
|
||||
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={customViewId ? `custom_view_${customViewId}` : 'all'}
|
||||
className='tabs--dashboard-views'
|
||||
>
|
||||
<Tab
|
||||
id={'all'}
|
||||
title={<Link to={`/dashboard/accounts`}>All</Link>}
|
||||
onClick={handleViewLinkClick}
|
||||
/>
|
||||
{ tabs }
|
||||
<Button
|
||||
className='button--new-view'
|
||||
icon={<Icon icon='plus' />}
|
||||
onClick={handleClickNewView}
|
||||
minimal={true}
|
||||
/>
|
||||
</Tabs>
|
||||
</NavbarGroup>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
|
||||
// Mapping view id from matched route params.
|
||||
viewId: ownProps.match.params.custom_view_id,
|
||||
});
|
||||
|
||||
const withAccountsViewsTabs = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
withAccountsViewsTabs,
|
||||
withDashboard,
|
||||
withAccounts,
|
||||
withAccountsTableActions,
|
||||
withViewDetail
|
||||
)(AccountsViewsTabs);
|
||||
10
client/src/containers/Accounts/withAccountDetail.js
Normal file
10
client/src/containers/Accounts/withAccountDetail.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getItemById
|
||||
} from 'store/selectors';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
account: getItemById(state.accounts.items, props.accountId),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps);
|
||||
19
client/src/containers/Accounts/withAccounts.js
Normal file
19
client/src/containers/Accounts/withAccounts.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getAccountsItems,
|
||||
} from 'store/accounts/accounts.selectors';
|
||||
import {
|
||||
getResourceViews,
|
||||
} from 'store/customViews/customViews.selectors';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
accountsViews: getResourceViews(state, 'accounts'),
|
||||
accounts: getAccountsItems(state, state.accounts.currentViewId),
|
||||
accountsTypes: state.accounts.accountsTypes,
|
||||
|
||||
accountsTableQuery: state.accounts.tableQuery,
|
||||
accountsLoading: state.accounts.loading,
|
||||
accountErrors: state.accounts.errors,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps);
|
||||
22
client/src/containers/Accounts/withAccountsActions.js
Normal file
22
client/src/containers/Accounts/withAccountsActions.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
fetchAccountTypes,
|
||||
fetchAccountsList,
|
||||
deleteAccount,
|
||||
inactiveAccount,
|
||||
submitAccount,
|
||||
fetchAccount,
|
||||
deleteBulkAccounts,
|
||||
} from 'store/accounts/accounts.actions';
|
||||
|
||||
const mapActionsToProps = (dispatch) => ({
|
||||
requestFetchAccounts: (query) => dispatch(fetchAccountsList({ query })),
|
||||
requestFetchAccountTypes: () => dispatch(fetchAccountTypes()),
|
||||
requestSubmitAccount: ({ form }) => dispatch(submitAccount({ form })),
|
||||
requestDeleteAccount: (id) => dispatch(deleteAccount({ id })),
|
||||
requestInactiveAccount: (id) => dispatch(inactiveAccount({ id })),
|
||||
requestFetchAccount: (id) => dispatch(fetchAccount({ id })),
|
||||
requestDeleteBulkAccounts: (ids) => dispatch(deleteBulkAccounts({ ids })),
|
||||
});
|
||||
|
||||
export default connect(null, mapActionsToProps);
|
||||
24
client/src/containers/Accounts/withAccountsTableActions.js
Normal file
24
client/src/containers/Accounts/withAccountsTableActions.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { connect } from 'react-redux';
|
||||
import t from 'store/types';
|
||||
import {
|
||||
fetchAccountsTable,
|
||||
} from 'store/accounts/accounts.actions';
|
||||
|
||||
const mapActionsToProps = (dispatch) => ({
|
||||
requestFetchAccountsTable: (query = {}) => dispatch(fetchAccountsTable({ query: { ...query } })),
|
||||
changeAccountsCurrentView: (id) => dispatch({
|
||||
type: t.ACCOUNTS_SET_CURRENT_VIEW,
|
||||
currentViewId: parseInt(id, 10),
|
||||
}),
|
||||
setAccountsTableQuery: (key, value) => dispatch({
|
||||
type: 'ACCOUNTS_TABLE_QUERY_SET', key, value,
|
||||
}),
|
||||
addAccountsTableQueries: (queries) => dispatch({
|
||||
type: 'ACCOUNTS_TABLE_QUERIES_ADD', queries,
|
||||
}),
|
||||
setSelectedRowsAccounts: (ids) => dispatch({
|
||||
type: t.ACCOUNTS_SELECTED_ROWS_SET, payload: { ids },
|
||||
}),
|
||||
});
|
||||
|
||||
export default connect(null, mapActionsToProps);
|
||||
Reference in New Issue
Block a user