mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 22:30:31 +00:00
feat: Accounts datatable.
This commit is contained in:
@@ -25,9 +25,10 @@ import ResourceConnect from 'connectors/Resource.connector';
|
|||||||
function AccountsActionsBar({
|
function AccountsActionsBar({
|
||||||
openDialog,
|
openDialog,
|
||||||
views,
|
views,
|
||||||
bulkActions,
|
selectedRows = [],
|
||||||
getResourceFields,
|
getResourceFields,
|
||||||
onFilterChange,
|
addAccountsTableQueries,
|
||||||
|
onFilterChanged,
|
||||||
}) {
|
}) {
|
||||||
const {path} = useRouteMatch();
|
const {path} = useRouteMatch();
|
||||||
const onClickNewAccount = () => { openDialog('account-form', {}); };
|
const onClickNewAccount = () => { openDialog('account-form', {}); };
|
||||||
@@ -37,13 +38,16 @@ function AccountsActionsBar({
|
|||||||
const viewsMenuItems = views.map((view) => {
|
const viewsMenuItems = views.map((view) => {
|
||||||
return (<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />);
|
return (<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />);
|
||||||
});
|
});
|
||||||
const hasBulkActionsSelected = useMemo(() => {
|
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
|
||||||
return Object.keys(bulkActions).length > 0;
|
|
||||||
}, [bulkActions]);
|
|
||||||
|
|
||||||
const filterDropdown = FilterDropdown({
|
const filterDropdown = FilterDropdown({
|
||||||
fields: accountsFields,
|
fields: accountsFields,
|
||||||
onFilterChange,
|
onFilterChange: (filterConditions) => {
|
||||||
|
addAccountsTableQueries({
|
||||||
|
filter_roles: filterConditions || '',
|
||||||
|
});
|
||||||
|
onFilterChanged && onFilterChanged(filterConditions);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
@@ -82,7 +86,7 @@ function AccountsActionsBar({
|
|||||||
|
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
{hasBulkActionsSelected && (
|
{hasSelectedRows && (
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='archive' iconSize={15} />}
|
icon={<Icon icon='archive' iconSize={15} />}
|
||||||
@@ -90,7 +94,7 @@ function AccountsActionsBar({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasBulkActionsSelected && (
|
{hasSelectedRows && (
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='trash' iconSize={15} />}
|
icon={<Icon icon='trash' iconSize={15} />}
|
||||||
@@ -115,7 +119,7 @@ function AccountsActionsBar({
|
|||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
bulkActions: state.accounts.bulkActions
|
// selectedRows: state.accounts.selectedRows
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||||
GridComponent,
|
|
||||||
ColumnsDirective,
|
|
||||||
ColumnDirective,
|
|
||||||
Inject,
|
|
||||||
Sort
|
|
||||||
} from '@syncfusion/ej2-react-grids';
|
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
Popover,
|
||||||
@@ -13,50 +6,34 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
MenuDivider,
|
MenuDivider,
|
||||||
Position,
|
Position,
|
||||||
Checkbox
|
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import useAsync from 'hooks/async';
|
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import { handleBooleanChange, compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
import AccountsConnect from 'connectors/Accounts.connector';
|
import AccountsConnect from 'connectors/Accounts.connector';
|
||||||
import DialogConnect from 'connectors/Dialog.connector';
|
import DialogConnect from 'connectors/Dialog.connector';
|
||||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||||
import ViewConnect from 'connectors/View.connector';
|
import ViewConnect from 'connectors/View.connector';
|
||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
|
import DataTable from 'components/DataTable';
|
||||||
|
|
||||||
function AccountsDataTable({
|
function AccountsDataTable({
|
||||||
filterConditions,
|
|
||||||
accounts,
|
accounts,
|
||||||
onDeleteAccount,
|
onDeleteAccount,
|
||||||
onInactiveAccount,
|
onInactiveAccount,
|
||||||
openDialog,
|
openDialog,
|
||||||
addBulkActionAccount,
|
|
||||||
removeBulkActionAccount,
|
|
||||||
fetchAccounts,
|
|
||||||
changeCurrentView,
|
changeCurrentView,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
getViewItem,
|
getViewItem,
|
||||||
setTopbarEditView
|
setTopbarEditView,
|
||||||
|
accountsLoading,
|
||||||
|
onFetchData,
|
||||||
|
setSelectedRowsAccounts,
|
||||||
}) {
|
}) {
|
||||||
const { custom_view_id: customViewId } = useParams();
|
const { custom_view_id: customViewId } = useParams();
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
|
||||||
const fetchHook = useAsync(async () => {
|
|
||||||
await Promise.all([
|
|
||||||
fetchAccounts({
|
|
||||||
custom_view_id: customViewId,
|
|
||||||
stringified_filter_roles: JSON.stringify(filterConditions) || '',
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => { fetchHook.execute(); }, [filterConditions]);
|
|
||||||
|
|
||||||
// Refetch accounts list after custom view id change.
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const viewMeta = getViewItem(customViewId);
|
const viewMeta = getViewItem(customViewId);
|
||||||
fetchHook.execute();
|
|
||||||
|
|
||||||
if (customViewId) {
|
if (customViewId) {
|
||||||
changeCurrentView(customViewId);
|
changeCurrentView(customViewId);
|
||||||
@@ -65,10 +42,8 @@ function AccountsDataTable({
|
|||||||
changePageSubtitle((customViewId && viewMeta) ? viewMeta.name : '');
|
changePageSubtitle((customViewId && viewMeta) ? viewMeta.name : '');
|
||||||
}, [customViewId]);
|
}, [customViewId]);
|
||||||
|
|
||||||
useEffect(() => () => {
|
// Clear page subtitle when unmount the page.
|
||||||
// Clear page subtitle when unmount the page.
|
useEffect(() => () => { changePageSubtitle(''); }, []);
|
||||||
changePageSubtitle('');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleEditAccount = account => () => {
|
const handleEditAccount = account => () => {
|
||||||
openDialog('account-form', { action: 'edit', id: account.id });
|
openDialog('account-form', { action: 'edit', id: account.id });
|
||||||
@@ -88,95 +63,76 @@ function AccountsDataTable({
|
|||||||
onClick={() => onDeleteAccount(account)} />
|
onClick={() => onDeleteAccount(account)} />
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
const columns = useMemo(() => [
|
||||||
|
{
|
||||||
|
id: 'name',
|
||||||
|
Header: 'Account Name',
|
||||||
|
accessor: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'code',
|
||||||
|
Header: 'Code',
|
||||||
|
accessor: 'code'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'type',
|
||||||
|
Header: 'Type',
|
||||||
|
accessor: 'type.name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'normal',
|
||||||
|
Header: 'Normal',
|
||||||
|
Cell: ({ cell }) => {
|
||||||
|
const account = cell.row.original;
|
||||||
|
const type = account.type ? account.type.normal : '';
|
||||||
|
const arrowDirection = type === 'credit' ? 'down' : 'up';
|
||||||
|
|
||||||
const handleClickCheckboxBulk = account =>
|
return (<Icon icon={`arrow-${arrowDirection}`} />);
|
||||||
handleBooleanChange(value => {
|
|
||||||
if (value) {
|
|
||||||
addBulkActionAccount(account.id);
|
|
||||||
} else {
|
|
||||||
removeBulkActionAccount(account.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
field: '',
|
|
||||||
headerText: '',
|
|
||||||
template: account => (
|
|
||||||
<Checkbox onChange={handleClickCheckboxBulk(account)} />
|
|
||||||
),
|
|
||||||
customAttributes: { class: 'checkbox-row' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'name',
|
|
||||||
headerText: 'Account Name',
|
|
||||||
customAttributes: { class: 'account-name' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'code',
|
|
||||||
headerText: 'Code'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'type.name',
|
|
||||||
headerText: 'Type'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headerText: 'Normal',
|
|
||||||
template: column => {
|
|
||||||
const type = column.type ? column.type.normal : '';
|
|
||||||
return type === 'credit' ? (
|
|
||||||
<Icon icon={'arrow-down'} />
|
|
||||||
) : (
|
|
||||||
<Icon icon={'arrow-up'} />
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
customAttributes: { class: 'account-normal' }
|
className: 'normal',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'balance',
|
id: 'balance',
|
||||||
headerText: 'Balance',
|
Header: 'Balance',
|
||||||
template: (column, data) => {
|
Cell: ({ cell }) => {
|
||||||
return <span>$10,000</span>;
|
const account = cell.row.original;
|
||||||
}
|
const {balance} = account;
|
||||||
|
|
||||||
|
return ('undefined' !== typeof balance) ?
|
||||||
|
(<span>{ balance.amount }</span>) :
|
||||||
|
(<span>--</span>);
|
||||||
|
},
|
||||||
|
|
||||||
|
// canResize: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerText: '',
|
id: 'actions',
|
||||||
template: account => (
|
Header: '',
|
||||||
|
Cell: ({ cell }) => (
|
||||||
<Popover
|
<Popover
|
||||||
content={actionMenuList(account)}
|
content={actionMenuList(cell.row.original)}
|
||||||
position={Position.RIGHT_BOTTOM}
|
position={Position.RIGHT_BOTTOM}>
|
||||||
>
|
|
||||||
<Button icon={<Icon icon='ellipsis-h' />} />
|
<Button icon={<Icon icon='ellipsis-h' />} />
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
),
|
||||||
|
className: 'actions',
|
||||||
|
width: 50,
|
||||||
|
// canResize: false
|
||||||
}
|
}
|
||||||
];
|
], []);
|
||||||
|
|
||||||
|
const handleDatatableFetchData = useCallback(() => {
|
||||||
|
onFetchData && onFetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const dataStateChange = state => {};
|
|
||||||
return (
|
return (
|
||||||
<LoadingIndicator loading={fetchHook.pending} spinnerSize={30}>
|
<LoadingIndicator loading={accountsLoading} spinnerSize={30}>
|
||||||
<GridComponent
|
<DataTable
|
||||||
allowSorting={true}
|
columns={columns}
|
||||||
allowGrouping={true}
|
data={accounts}
|
||||||
dataSource={{ result: accounts, count: 12 }}
|
onFetchData={handleDatatableFetchData}
|
||||||
dataStateChange={dataStateChange}
|
manualSortBy={true} />
|
||||||
>
|
</LoadingIndicator>
|
||||||
<ColumnsDirective>
|
|
||||||
{columns.map(column => {
|
|
||||||
return (
|
|
||||||
<ColumnDirective
|
|
||||||
field={column.field}
|
|
||||||
headerText={column.headerText}
|
|
||||||
template={column.template}
|
|
||||||
allowSorting={true}
|
|
||||||
customAttributes={column.customAttributes}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ColumnsDirective>
|
|
||||||
<Inject services={[Sort]} />
|
|
||||||
</GridComponent>
|
|
||||||
</LoadingIndicator>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, {useEffect} from 'react';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
@@ -15,10 +15,14 @@ import { Link } from 'react-router-dom';
|
|||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
import AccountsConnect from 'connectors/Accounts.connector';
|
import AccountsConnect from 'connectors/Accounts.connector';
|
||||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||||
|
import {useUpdateEffect} from 'hooks';
|
||||||
|
|
||||||
function AccountsViewsTabs({
|
function AccountsViewsTabs({
|
||||||
views,
|
views,
|
||||||
setTopbarEditView,
|
setTopbarEditView,
|
||||||
|
customViewChanged,
|
||||||
|
addAccountsTableQueries,
|
||||||
|
onViewChanged,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { custom_view_id: customViewId } = useParams();
|
const { custom_view_id: customViewId } = useParams();
|
||||||
@@ -30,6 +34,22 @@ function AccountsViewsTabs({
|
|||||||
const handleViewLinkClick = () => {
|
const handleViewLinkClick = () => {
|
||||||
setTopbarEditView(customViewId);
|
setTopbarEditView(customViewId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useUpdateEffect(() => {
|
||||||
|
customViewChanged && customViewChanged(customViewId);
|
||||||
|
|
||||||
|
addAccountsTableQueries({
|
||||||
|
custom_view_id: customViewId || null,
|
||||||
|
});
|
||||||
|
onViewChanged && onViewChanged(customViewId);
|
||||||
|
}, [customViewId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addAccountsTableQueries({
|
||||||
|
custom_view_id: customViewId,
|
||||||
|
})
|
||||||
|
}, [customViewId]);
|
||||||
|
|
||||||
const tabs = views.map(view => {
|
const tabs = views.map(view => {
|
||||||
const baseUrl = '/dashboard/accounts';
|
const baseUrl = '/dashboard/accounts';
|
||||||
const link = (
|
const link = (
|
||||||
@@ -38,7 +58,9 @@ function AccountsViewsTabs({
|
|||||||
onClick={handleViewLinkClick}
|
onClick={handleViewLinkClick}
|
||||||
>{view.name}</Link>
|
>{view.name}</Link>
|
||||||
);
|
);
|
||||||
return <Tab id={`custom_view_${view.id}`} title={link} />;
|
return <Tab
|
||||||
|
id={`custom_view_${view.id}`}
|
||||||
|
title={link} />;
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Navbar className='navbar--dashboard-views'>
|
<Navbar className='navbar--dashboard-views'>
|
||||||
@@ -49,7 +71,9 @@ function AccountsViewsTabs({
|
|||||||
selectedTabId={`custom_view_${customViewId}`}
|
selectedTabId={`custom_view_${customViewId}`}
|
||||||
className='tabs--dashboard-views'
|
className='tabs--dashboard-views'
|
||||||
>
|
>
|
||||||
<Tab id='all' title={<Link to={`/dashboard/accounts`}>All</Link>} />
|
<Tab
|
||||||
|
id='all'
|
||||||
|
title={<Link to={`/dashboard/accounts`}>All</Link>} />
|
||||||
|
|
||||||
{tabs}
|
{tabs}
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,10 +1,41 @@
|
|||||||
import React from 'react';
|
import React, {useEffect} from 'react';
|
||||||
import { useTable, useExpanded, usePagination } from 'react-table'
|
import {
|
||||||
|
useTable,
|
||||||
|
useExpanded,
|
||||||
|
useRowSelect,
|
||||||
|
usePagination,
|
||||||
|
useResizeColumns,
|
||||||
|
useAsyncDebounce,
|
||||||
|
useSortBy,
|
||||||
|
useFlexLayout
|
||||||
|
} from 'react-table'
|
||||||
|
import {Checkbox} from '@blueprintjs/core';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
// import { FixedSizeList } from 'react-window'
|
||||||
|
|
||||||
|
const IndeterminateCheckbox = React.forwardRef(
|
||||||
|
({ indeterminate, ...rest }, ref) => {
|
||||||
|
const defaultRef = React.useRef()
|
||||||
|
const resolvedRef = ref || defaultRef
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
resolvedRef.current.indeterminate = indeterminate
|
||||||
|
}, [resolvedRef, indeterminate])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Checkbox ref={resolvedRef} {...rest} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default function DataTable({
|
export default function DataTable({
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
loading,
|
loading,
|
||||||
|
onFetchData,
|
||||||
|
onSelectedRowsChange,
|
||||||
|
manualSortBy = 'false'
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
@@ -20,8 +51,10 @@ export default function DataTable({
|
|||||||
nextPage,
|
nextPage,
|
||||||
previousPage,
|
previousPage,
|
||||||
setPageSize,
|
setPageSize,
|
||||||
|
|
||||||
|
selectedFlatRows,
|
||||||
// Get the state from the instance
|
// Get the state from the instance
|
||||||
state: { pageIndex, pageSize },
|
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
||||||
} = useTable(
|
} = useTable(
|
||||||
{
|
{
|
||||||
columns,
|
columns,
|
||||||
@@ -33,59 +66,102 @@ export default function DataTable({
|
|||||||
// pageCount.
|
// pageCount.
|
||||||
// pageCount: controlledPageCount,
|
// pageCount: controlledPageCount,
|
||||||
getSubRows: row => row.children,
|
getSubRows: row => row.children,
|
||||||
|
manualSortBy
|
||||||
},
|
},
|
||||||
|
useSortBy,
|
||||||
useExpanded,
|
useExpanded,
|
||||||
|
useRowSelect,
|
||||||
usePagination,
|
usePagination,
|
||||||
|
useResizeColumns,
|
||||||
|
useFlexLayout,
|
||||||
|
hooks => {
|
||||||
|
hooks.visibleColumns.push(columns => [
|
||||||
|
// Let's make a column for selection
|
||||||
|
{
|
||||||
|
id: 'selection',
|
||||||
|
disableResizing: true,
|
||||||
|
minWidth: 35,
|
||||||
|
width: 35,
|
||||||
|
maxWidth: 35,
|
||||||
|
// The header can use the table's getToggleAllRowsSelectedProps method
|
||||||
|
// to render a checkbox
|
||||||
|
Header: ({ getToggleAllRowsSelectedProps }) => (
|
||||||
|
<div>
|
||||||
|
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
// The cell can use the individual row's getToggleRowSelectedProps method
|
||||||
|
// to the render a checkbox
|
||||||
|
Cell: ({ row }) => (
|
||||||
|
<div>
|
||||||
|
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
...columns,
|
||||||
|
])
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Debounce our onFetchData call for 100ms
|
||||||
|
const onFetchDataDebounced = useAsyncDebounce(onFetchData, 100);
|
||||||
|
const onSelectRowsDebounced = useAsyncDebounce(onSelectedRowsChange, 250);
|
||||||
|
|
||||||
|
// When these table states change, fetch new data!
|
||||||
|
useEffect(() => {
|
||||||
|
onFetchDataDebounced({ pageIndex, pageSize, sortBy })
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'bigcapital-datatable'}>
|
<div className={'bigcapital-datatable'}>
|
||||||
<table {...getTableProps()}>
|
<div {...getTableProps()} className="table">
|
||||||
<thead>
|
<div className="thead">
|
||||||
{headerGroups.map(headerGroup => (
|
{headerGroups.map(headerGroup => (
|
||||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
<div {...headerGroup.getHeaderGroupProps()} className="tr">
|
||||||
{headerGroup.headers.map(column => (
|
{headerGroup.headers.map(column => (
|
||||||
<th {...column.getHeaderProps({
|
<div {...column.getHeaderProps({
|
||||||
className: column.className || '',
|
className: classnames(column.className || '', 'th'),
|
||||||
})}>
|
})}>
|
||||||
{column.render('Header')}
|
<div {...column.getSortByToggleProps()}>
|
||||||
<span>
|
{column.render('Header')}
|
||||||
{column.isSorted
|
<span>
|
||||||
? column.isSortedDesc
|
{column.isSorted
|
||||||
? ' 🔽'
|
? column.isSortedDesc
|
||||||
: ' 🔼'
|
? (<Icon icon="sort-down" />)
|
||||||
: ''}
|
: (<Icon icon="sort-up" />)
|
||||||
</span>
|
: ''}
|
||||||
</th>
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{column.canResize && (
|
||||||
|
<div
|
||||||
|
{...column.getResizerProps()}
|
||||||
|
className={`resizer ${
|
||||||
|
column.isResizing ? 'isResizing' : ''
|
||||||
|
}`}>
|
||||||
|
<div class="inner-resizer" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</div>
|
||||||
))}
|
))}
|
||||||
</thead>
|
</div>
|
||||||
<tbody {...getTableBodyProps()}>
|
<div {...getTableBodyProps()} className="tbody">
|
||||||
{page.map((row, i) => {
|
{page.map((row, i) => {
|
||||||
prepareRow(row)
|
prepareRow(row)
|
||||||
return (
|
return (
|
||||||
<tr {...row.getRowProps()}>
|
<div {...row.getRowProps()} className="tr">
|
||||||
{row.cells.map((cell) => {
|
{row.cells.map((cell) => {
|
||||||
return <td {...cell.getCellProps({
|
return <div {...cell.getCellProps({
|
||||||
className: cell.column.className || '',
|
className: classnames(cell.column.className || '', 'td'),
|
||||||
})}>{ cell.render('Cell') }</td>
|
})}>{ cell.render('Cell') }</div>
|
||||||
})}
|
})}
|
||||||
</tr>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<tr>
|
</div>
|
||||||
{loading ? (
|
</div>
|
||||||
// Use our custom loading state to show a loading indicator
|
|
||||||
<td colSpan="10000">Loading...</td>
|
|
||||||
) : (
|
|
||||||
<td colSpan="10000">
|
|
||||||
{/* Showing {page.length} of ~{controlledPageCount * pageSize}{' '} results */}
|
|
||||||
</td>
|
|
||||||
)}
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ export default function DialogsContainer() {
|
|||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<AccountFormDialog />
|
<AccountFormDialog />
|
||||||
<UserFormDialog />
|
{/* <UserFormDialog /> */}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, {useState, useEffect, useMemo} from 'react';
|
||||||
import { Spinner } from '@blueprintjs/core';
|
import { Spinner } from '@blueprintjs/core';
|
||||||
|
|
||||||
export default function LoadingIndicator({
|
export default function LoadingIndicator({
|
||||||
@@ -6,15 +6,33 @@ export default function LoadingIndicator({
|
|||||||
spinnerSize = 40,
|
spinnerSize = 40,
|
||||||
children
|
children
|
||||||
}) {
|
}) {
|
||||||
|
const [rendered, setRendered] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) { setRendered(true); }
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
|
const componentStyle = useMemo(() => {
|
||||||
|
return {display: !loading ? 'block' : 'none'};
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
|
const loadingComponent = useMemo(() => (
|
||||||
|
<div class='dashboard__loading-indicator'>
|
||||||
|
<Spinner size={spinnerSize} value={null} />
|
||||||
|
</div>
|
||||||
|
), [spinnerSize]);
|
||||||
|
|
||||||
|
const renderComponent = useMemo(() => (
|
||||||
|
<div style={componentStyle}>{ children }</div>
|
||||||
|
), [children, componentStyle]);
|
||||||
|
|
||||||
|
const maybeRenderComponent = rendered && renderComponent;
|
||||||
|
const maybeRenderLoadingSpinner = loading && loadingComponent;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{loading ? (
|
{ maybeRenderLoadingSpinner }
|
||||||
<div class='dashboard__loading-indicator'>
|
{ maybeRenderComponent }
|
||||||
<Spinner size={spinnerSize} value={null} />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
children
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {useParams} from 'react-router-dom';
|
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
import {
|
import {
|
||||||
fetchAccountTypes,
|
fetchAccountTypes,
|
||||||
fetchAccountsList,
|
fetchAccountsList,
|
||||||
deleteAccount,
|
deleteAccount,
|
||||||
inactiveAccount,
|
inactiveAccount,
|
||||||
|
fetchAccountsTable,
|
||||||
} from 'store/accounts/accounts.actions';
|
} from 'store/accounts/accounts.actions';
|
||||||
import {
|
import {
|
||||||
getAccountsItems,
|
getAccountsItems,
|
||||||
@@ -17,23 +17,32 @@ import {
|
|||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
views: getResourceViews(state, 'accounts'),
|
views: getResourceViews(state, 'accounts'),
|
||||||
accounts: getAccountsItems(state, state.accounts.currentViewId),
|
accounts: getAccountsItems(state, state.accounts.currentViewId),
|
||||||
|
tableQuery: state.accounts.tableQuery,
|
||||||
|
accountsLoading: state.accounts.loading,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapActionsToProps = (dispatch) => ({
|
const mapActionsToProps = (dispatch) => ({
|
||||||
fetchAccounts: (query) => dispatch(fetchAccountsList({ query })),
|
fetchAccounts: (query) => dispatch(fetchAccountsList({ query })),
|
||||||
fetchAccountTypes: () => dispatch(fetchAccountTypes()),
|
fetchAccountTypes: () => dispatch(fetchAccountTypes()),
|
||||||
deleteAccount: (id) => dispatch(deleteAccount({ id })),
|
requestDeleteAccount: (id) => dispatch(deleteAccount({ id })),
|
||||||
inactiveAccount: (id) => dispatch(inactiveAccount({ id })),
|
requestInactiveAccount: (id) => dispatch(inactiveAccount({ id })),
|
||||||
addBulkActionAccount: (id) => dispatch({
|
|
||||||
type: t.ACCOUNT_BULK_ACTION_ADD, account_id: id
|
|
||||||
}),
|
|
||||||
removeBulkActionAccount: (id) => dispatch({
|
|
||||||
type: t.ACCOUNT_BULK_ACTION_REMOVE, account_id: id,
|
|
||||||
}),
|
|
||||||
changeCurrentView: (id) => dispatch({
|
changeCurrentView: (id) => dispatch({
|
||||||
type: t.ACCOUNTS_SET_CURRENT_VIEW,
|
type: t.ACCOUNTS_SET_CURRENT_VIEW,
|
||||||
currentViewId: parseInt(id, 10),
|
currentViewId: parseInt(id, 10),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
setAccountsTableQuery: (key, value) => dispatch({
|
||||||
|
type: 'ACCOUNTS_TABLE_QUERY_SET', key, value,
|
||||||
|
}),
|
||||||
|
addAccountsTableQueries: (queries) => dispatch({
|
||||||
|
type: 'ACCOUNTS_TABLE_QUERIES_ADD', queries,
|
||||||
|
}),
|
||||||
|
|
||||||
|
fetchAccountsTable: (query = {}) => dispatch(fetchAccountsTable({ query: { ...query } })),
|
||||||
|
|
||||||
|
setSelectedRowsAccounts: (ids) => dispatch({
|
||||||
|
type: t.ACCOUNTS_SELECTED_ROWS_SET, ids,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapActionsToProps);
|
export default connect(mapStateToProps, mapActionsToProps);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Route,
|
Route,
|
||||||
Switch,
|
Switch,
|
||||||
@@ -22,11 +22,12 @@ import { compose } from 'utils';
|
|||||||
function AccountsChart({
|
function AccountsChart({
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
fetchAccounts,
|
fetchAccounts,
|
||||||
deleteAccount,
|
requestDeleteAccount,
|
||||||
inactiveAccount,
|
requestInactiveAccount,
|
||||||
fetchResourceViews,
|
fetchResourceViews,
|
||||||
fetchResourceFields,
|
fetchResourceFields,
|
||||||
getResourceFields,
|
getResourceFields,
|
||||||
|
fetchAccountsTable,
|
||||||
}) {
|
}) {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
deleteAlertActive: false,
|
deleteAlertActive: false,
|
||||||
@@ -34,8 +35,8 @@ function AccountsChart({
|
|||||||
inactiveAlertActive: false,
|
inactiveAlertActive: false,
|
||||||
targetAccount: {},
|
targetAccount: {},
|
||||||
});
|
});
|
||||||
|
const [deleteAccount, setDeleteAccount] = useState(false);
|
||||||
const [filterConditions, setFilterConditions] = useState([]);
|
const [inactiveAccount, setInactiveAccount] = useState(false);
|
||||||
|
|
||||||
const fetchHook = useAsync(async () => {
|
const fetchHook = useAsync(async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -44,57 +45,56 @@ function AccountsChart({
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fetch accounts list according to the given custom view id.
|
||||||
|
const fetchAccountsHook = useAsync(async () => {
|
||||||
|
await Promise.all([
|
||||||
|
fetchAccountsTable(),
|
||||||
|
]);
|
||||||
|
}, false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle('Chart of Accounts');
|
changePageTitle('Chart of Accounts');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
// Handle click and cancel/confirm account delete
|
||||||
* Handle click and cancel/confirm account delete
|
const handleDeleteAccount = (account) => { setDeleteAccount(account); };
|
||||||
*/
|
|
||||||
const handleDeleteAccount = (account) => {
|
// handle cancel delete account alert.
|
||||||
setState({
|
const handleCancelAccountDelete = () => { setDeleteAccount(false); };
|
||||||
deleteAlertActive: true,
|
|
||||||
deleteAccount: account,
|
// Handle confirm account delete
|
||||||
});
|
const handleConfirmAccountDelete = useCallback(() => {
|
||||||
};
|
requestDeleteAccount(deleteAccount.id).then(() => {
|
||||||
|
setDeleteAccount(false);
|
||||||
const handleCancelAccountDelete = () => {
|
fetchAccountsHook.execute();
|
||||||
setState({ deleteAlertActive: false });
|
|
||||||
};
|
|
||||||
const handleConfirmAccountDelete = () => {
|
|
||||||
const { targetAccount: account } = state;
|
|
||||||
deleteAccount(account.id).then(() => {
|
|
||||||
setState({ deleteAlertActive: false });
|
|
||||||
fetchAccounts();
|
|
||||||
AppToaster.show({ message: 'the_account_has_been_deleted' });
|
AppToaster.show({ message: 'the_account_has_been_deleted' });
|
||||||
});
|
});
|
||||||
};
|
}, [deleteAccount]);
|
||||||
|
|
||||||
/**
|
// Handle cancel/confirm account inactive.
|
||||||
* Handle cancel/confirm account inactive.
|
const handleInactiveAccount = useCallback((account) => {
|
||||||
*/
|
setInactiveAccount(account);
|
||||||
const handleInactiveAccount = (account) => {
|
}, []);
|
||||||
setState({ inactiveAlertActive: true, targetAccount: account });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancelInactiveAccount = () => {
|
// Handle cancel inactive account alert.
|
||||||
setState({ inactiveAlertActive: false });
|
const handleCancelInactiveAccount = useCallback(() => {
|
||||||
};
|
setInactiveAccount(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleConfirmAccountActive = () => {
|
// Handle confirm account activation.
|
||||||
const { targetAccount: account } = state;
|
const handleConfirmAccountActive = useCallback(() => {
|
||||||
inactiveAccount(account.id).then(() => {
|
requestInactiveAccount(inactiveAccount.id).then(() => {
|
||||||
setState({ inactiveAlertActive: true });
|
setInactiveAccount(false);
|
||||||
fetchAccounts();
|
fetchAccountsTable();
|
||||||
AppToaster.show({ message: 'the_account_has_been_inactivated' });
|
AppToaster.show({ message: 'the_account_has_been_inactivated' });
|
||||||
});
|
});
|
||||||
};
|
}, [inactiveAccount]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle cancel/confirm account restore.
|
* Handle cancel/confirm account restore.
|
||||||
*/
|
*/
|
||||||
const handleCancelAccountRestore = () => {
|
const handleCancelAccountRestore = () => {
|
||||||
setState({ restoreAlertActive: false });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditAccount = (account) => {
|
const handleEditAccount = (account) => {
|
||||||
@@ -108,16 +108,22 @@ function AccountsChart({
|
|||||||
const handleConfirmAccountRestore = (account) => {
|
const handleConfirmAccountRestore = (account) => {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteBulkAccounts = (accounts) => {
|
const handleDeleteBulkAccounts = (accounts) => {
|
||||||
|
|
||||||
};
|
};
|
||||||
const handleFilterChange = (conditions) => { setFilterConditions(conditions); };
|
|
||||||
|
const handleSelectedRowsChange = (accounts) => {
|
||||||
|
console.log(accounts);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterChanged = useCallback(() => { fetchAccountsHook.execute(); }, []);
|
||||||
|
const handleViewChanged = useCallback(() => { fetchAccountsHook.execute(); }, []);
|
||||||
|
const handleFetchData = useCallback(() => { fetchAccountsHook.execute(); }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider loading={fetchHook.pending} name={'accounts-chart'}>
|
<DashboardInsider loading={fetchHook.pending} name={'accounts-chart'}>
|
||||||
<DashboardActionsBar
|
<DashboardActionsBar
|
||||||
onFilterChange={handleFilterChange} />
|
onFilterChanged={handleFilterChanged} />
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
@@ -126,14 +132,17 @@ function AccountsChart({
|
|||||||
'/dashboard/accounts/:custom_view_id/custom_view',
|
'/dashboard/accounts/:custom_view_id/custom_view',
|
||||||
'/dashboard/accounts'
|
'/dashboard/accounts'
|
||||||
]}>
|
]}>
|
||||||
<AccountsViewsTabs onDeleteBulkAccounts={handleDeleteBulkAccounts} />
|
<AccountsViewsTabs
|
||||||
|
onViewChanged={handleViewChanged}
|
||||||
|
onDeleteBulkAccounts={handleDeleteBulkAccounts} />
|
||||||
|
|
||||||
<AccountsDataTable
|
<AccountsDataTable
|
||||||
filterConditions={filterConditions}
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
onDeleteAccount={handleDeleteAccount}
|
onDeleteAccount={handleDeleteAccount}
|
||||||
onInactiveAccount={handleInactiveAccount}
|
onInactiveAccount={handleInactiveAccount}
|
||||||
onRestoreAccount={handleRestoreAccount}
|
onRestoreAccount={handleRestoreAccount}
|
||||||
onEditAccount={handleEditAccount} />
|
onEditAccount={handleEditAccount}
|
||||||
|
onFetchData={handleFetchData} />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
||||||
@@ -142,7 +151,7 @@ function AccountsChart({
|
|||||||
confirmButtonText="Move to Trash"
|
confirmButtonText="Move to Trash"
|
||||||
icon="trash"
|
icon="trash"
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
isOpen={state.deleteAlertActive}
|
isOpen={deleteAccount}
|
||||||
onCancel={handleCancelAccountDelete}
|
onCancel={handleCancelAccountDelete}
|
||||||
onConfirm={handleConfirmAccountDelete}>
|
onConfirm={handleConfirmAccountDelete}>
|
||||||
<p>
|
<p>
|
||||||
@@ -156,7 +165,7 @@ function AccountsChart({
|
|||||||
confirmButtonText="Inactivate"
|
confirmButtonText="Inactivate"
|
||||||
icon="trash"
|
icon="trash"
|
||||||
intent={Intent.WARNING}
|
intent={Intent.WARNING}
|
||||||
isOpen={state.inactiveAlertActive}
|
isOpen={inactiveAccount}
|
||||||
onCancel={handleCancelInactiveAccount}
|
onCancel={handleCancelInactiveAccount}
|
||||||
onConfirm={handleConfirmAccountActive}>
|
onConfirm={handleConfirmAccountActive}>
|
||||||
<p>
|
<p>
|
||||||
@@ -164,20 +173,6 @@ function AccountsChart({
|
|||||||
but it will become private to you.
|
but it will become private to you.
|
||||||
</p>
|
</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Alert
|
|
||||||
cancelButtonText="Cancel"
|
|
||||||
confirmButtonText="Move to Trash"
|
|
||||||
icon="trash"
|
|
||||||
intent={Intent.DANGER}
|
|
||||||
isOpen={state.restoreAlertActive}
|
|
||||||
onCancel={handleCancelAccountRestore}
|
|
||||||
onConfirm={handleConfirmAccountRestore}>
|
|
||||||
<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>
|
</DashboardPageContent>
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import AppToaster from 'components/AppToaster';
|
|||||||
import DialogConnect from 'connectors/Dialog.connector';
|
import DialogConnect from 'connectors/Dialog.connector';
|
||||||
import DialogReduxConnect from 'components/DialogReduxConnect';
|
import DialogReduxConnect from 'components/DialogReduxConnect';
|
||||||
import AccountFormDialogConnect from 'connectors/AccountFormDialog.connector';
|
import AccountFormDialogConnect from 'connectors/AccountFormDialog.connector';
|
||||||
|
import AccountsConnect from 'connectors/Accounts.connector';
|
||||||
|
|
||||||
function AccountFormDialog({
|
function AccountFormDialog({
|
||||||
name,
|
name,
|
||||||
@@ -33,7 +34,8 @@ function AccountFormDialog({
|
|||||||
closeDialog,
|
closeDialog,
|
||||||
submitAccount,
|
submitAccount,
|
||||||
fetchAccount,
|
fetchAccount,
|
||||||
editAccount
|
editAccount,
|
||||||
|
fetchAccountsTable,
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const accountFormValidationSchema = Yup.object().shape({
|
const accountFormValidationSchema = Yup.object().shape({
|
||||||
@@ -64,6 +66,7 @@ function AccountFormDialog({
|
|||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'the_account_has_been_edited'
|
message: 'the_account_has_been_edited'
|
||||||
});
|
});
|
||||||
|
refetchAccounts.execute();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
submitAccount({ form: { ...omit(values, exclude) } }).then(response => {
|
submitAccount({ form: { ...omit(values, exclude) } }).then(response => {
|
||||||
@@ -71,6 +74,7 @@ function AccountFormDialog({
|
|||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'the_account_has_been_submit'
|
message: 'the_account_has_been_submit'
|
||||||
});
|
});
|
||||||
|
refetchAccounts.execute();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,15 +135,19 @@ function AccountFormDialog({
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchAccounts(),
|
fetchAccounts(),
|
||||||
fetchAccountTypes(),
|
fetchAccountTypes(),
|
||||||
|
|
||||||
// Fetch the target in case edit mode.
|
// Fetch the target in case edit mode.
|
||||||
...(payload.action === 'edit' ? [fetchAccount(payload.id)] : [])
|
...(payload.action === 'edit' ? [fetchAccount(payload.id)] : [])
|
||||||
]);
|
]);
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
const onDialogOpening = async () => {
|
const refetchAccounts = useAsync(async () => {
|
||||||
fetchHook.execute();
|
await Promise.all([
|
||||||
};
|
fetchAccountsTable(),
|
||||||
|
]);
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// Fetch requests on dialog opening.
|
||||||
|
const onDialogOpening = async () => { fetchHook.execute(); };
|
||||||
|
|
||||||
const onChangeAccountType = accountType => {
|
const onChangeAccountType = accountType => {
|
||||||
setState({ ...state, selectedAccountType: accountType.name });
|
setState({ ...state, selectedAccountType: accountType.name });
|
||||||
@@ -294,6 +302,7 @@ function AccountFormDialog({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
AccountFormDialogConnect,
|
AccountFormDialogConnect,
|
||||||
|
AccountsConnect,
|
||||||
DialogReduxConnect,
|
DialogReduxConnect,
|
||||||
DialogConnect
|
DialogConnect
|
||||||
)(AccountFormDialog);
|
)(AccountFormDialog);
|
||||||
|
|||||||
@@ -1,6 +1,27 @@
|
|||||||
|
import {useRef, useEffect} from 'react';
|
||||||
import useAsync from './async';
|
import useAsync from './async';
|
||||||
|
|
||||||
// import use from 'async';
|
// import use from 'async';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom useEffect hook that only triggers on updates, not on initial mount
|
||||||
|
* Idea stolen from: https://stackoverflow.com/a/55075818/1526448
|
||||||
|
* @param {Function} effect
|
||||||
|
* @param {Array<any>} dependencies
|
||||||
|
*/
|
||||||
|
export function useUpdateEffect(effect, dependencies = []) {
|
||||||
|
const isInitialMount = useRef(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInitialMount.current) {
|
||||||
|
isInitialMount.current = false;
|
||||||
|
} else {
|
||||||
|
effect();
|
||||||
|
}
|
||||||
|
}, dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
useAsync,
|
useAsync,
|
||||||
|
useUpdateEffect,
|
||||||
}
|
}
|
||||||
@@ -78,5 +78,13 @@ export default {
|
|||||||
"pen": {
|
"pen": {
|
||||||
path: ['M493.26 56.26l-37.51-37.51C443.25 6.25 426.87 0 410.49 0s-32.76 6.25-45.25 18.74l-74.49 74.49L256 127.98 12.85 371.12.15 485.34C-1.45 499.72 9.88 512 23.95 512c.89 0 1.79-.05 2.69-.15l114.14-12.61L384.02 256l34.74-34.74 74.49-74.49c25-25 25-65.52.01-90.51zM118.75 453.39l-67.58 7.46 7.53-67.69 231.24-231.24 31.02-31.02 60.14 60.14-31.02 31.02-231.33 231.33zm340.56-340.57l-44.28 44.28-60.13-60.14 44.28-44.28c4.08-4.08 8.84-4.69 11.31-4.69s7.24.61 11.31 4.69l37.51 37.51c6.24 6.25 6.24 16.4 0 22.63z'],
|
path: ['M493.26 56.26l-37.51-37.51C443.25 6.25 426.87 0 410.49 0s-32.76 6.25-45.25 18.74l-74.49 74.49L256 127.98 12.85 371.12.15 485.34C-1.45 499.72 9.88 512 23.95 512c.89 0 1.79-.05 2.69-.15l114.14-12.61L384.02 256l34.74-34.74 74.49-74.49c25-25 25-65.52.01-90.51zM118.75 453.39l-67.58 7.46 7.53-67.69 231.24-231.24 31.02-31.02 60.14 60.14-31.02 31.02-231.33 231.33zm340.56-340.57l-44.28 44.28-60.13-60.14 44.28-44.28c4.08-4.08 8.84-4.69 11.31-4.69s7.24.61 11.31 4.69l37.51 37.51c6.24 6.25 6.24 16.4 0 22.63z'],
|
||||||
viewBox: '0 0 512 512',
|
viewBox: '0 0 512 512',
|
||||||
|
},
|
||||||
|
"sort-up": {
|
||||||
|
path: ['M279 224H41c-21.4 0-32.1-25.9-17-41L143 64c9.4-9.4 24.6-9.4 33.9 0l119 119c15.2 15.1 4.5 41-16.9 41z'],
|
||||||
|
viewBox: '0 0 320 512'
|
||||||
|
},
|
||||||
|
"sort-down": {
|
||||||
|
path: ['M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z'],
|
||||||
|
viewBox: '0 0 320 512'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,9 +12,7 @@ export const fetchAccountTypes = () => {
|
|||||||
});
|
});
|
||||||
resolve(response);
|
resolve(response);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => { reject(error); });
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,6 +38,38 @@ export const fetchAccountsList = ({ query } = {}) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchAccountsTable = ({ query } = {}) => {
|
||||||
|
return (dispatch, getState) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const pageQuery = getState().accounts.tableQuery;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: t.ACCOUNTS_TABLE_LOADING,
|
||||||
|
loading: true,
|
||||||
|
});
|
||||||
|
ApiService.get('accounts', { params: { ...pageQuery, ...query } })
|
||||||
|
.then(response => {
|
||||||
|
dispatch({
|
||||||
|
type: t.ACCOUNTS_PAGE_SET,
|
||||||
|
accounts: response.data.accounts,
|
||||||
|
customViewId: response.data.customViewId
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: t.ACCOUNTS_ITEMS_SET,
|
||||||
|
accounts: response.data.accounts
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: t.ACCOUNTS_TABLE_LOADING,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
resolve(response);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchAccountsDataTable = ({ query }) => {
|
export const fetchAccountsDataTable = ({ query }) => {
|
||||||
return dispatch =>
|
return dispatch =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
@@ -109,7 +139,12 @@ export const inactiveAccount = ({ id }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const deleteAccount = ({ id }) => {
|
export const deleteAccount = ({ id }) => {
|
||||||
return dispatch => ApiService.delete(`accounts/${id}`);
|
return dispatch => new Promise((resolve, reject) => {
|
||||||
|
ApiService.delete(`accounts/${id}`).then((response) => {
|
||||||
|
dispatch({ type: t.ACCOUNT_DELETE, id });
|
||||||
|
resolve(response);
|
||||||
|
}).catch(error => { reject(error); });
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteBulkAccounts = ({ ids }) => {};
|
export const deleteBulkAccounts = ({ ids }) => {};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer, combineReducers } from '@reduxjs/toolkit';
|
||||||
|
import { createTableQueryReducers } from 'store/queryReducers';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
items: {},
|
items: {},
|
||||||
@@ -9,10 +10,11 @@ const initialState = {
|
|||||||
accountFormErrors: [],
|
accountFormErrors: [],
|
||||||
datatableQuery: {},
|
datatableQuery: {},
|
||||||
currentViewId: -1,
|
currentViewId: -1,
|
||||||
bulkActions: {},
|
selectedRows: [],
|
||||||
|
loading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createReducer(initialState, {
|
const accountsReducer = createReducer(initialState, {
|
||||||
[t.ACCOUNTS_ITEMS_SET]: (state, action) => {
|
[t.ACCOUNTS_ITEMS_SET]: (state, action) => {
|
||||||
const _items = {};
|
const _items = {};
|
||||||
|
|
||||||
@@ -44,19 +46,32 @@ export default createReducer(initialState, {
|
|||||||
state.accountsById[action.account.id] = action.account;
|
state.accountsById[action.account.id] = action.account;
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.ACCOUNT_BULK_ACTION_ADD]: (state, action) => {
|
[t.ACCOUNT_DELETE]: (state, action) => {
|
||||||
state.bulkActions[action.account_id] = true;
|
if (typeof state.items[action.id] !== 'undefined'){
|
||||||
|
delete state.items[action.id];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.ACCOUNT_BULK_ACTION_REMOVE]: (state, action) => {
|
[t.ACCOUNTS_SELECTED_ROWS_SET]: (state, action) => {
|
||||||
delete state.bulkActions[action.account_id];
|
state.selectedRows.push(...action.ids);
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.ACCOUNTS_SET_CURRENT_VIEW]: (state, action) => {
|
[t.ACCOUNTS_SET_CURRENT_VIEW]: (state, action) => {
|
||||||
state.currentViewId = action.currentViewId;
|
state.currentViewId = action.currentViewId;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
[t.ACCOUNTS_TABLE_LOADING]: (state, action) => {
|
||||||
|
state.loading = action.loading;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default createTableQueryReducers('accounts', accountsReducer);
|
||||||
|
|
||||||
export const getAccountById = (state, id) => {
|
export const getAccountById = (state, id) => {
|
||||||
return state.accounts.accountsById[id];
|
return state.accounts.accountsById[id];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// export default {
|
||||||
|
// // ...accountsReducer,
|
||||||
|
// // testReducer,
|
||||||
|
// }
|
||||||
@@ -5,11 +5,16 @@ export default {
|
|||||||
ACCOUNTS_PAGE_SET: 'ACCOUNTS_PAGE_SET',
|
ACCOUNTS_PAGE_SET: 'ACCOUNTS_PAGE_SET',
|
||||||
ACCOUNTS_ITEMS_SET: 'ACCOUNTS_ITEMS_SET',
|
ACCOUNTS_ITEMS_SET: 'ACCOUNTS_ITEMS_SET',
|
||||||
ACCOUNT_SET: 'ACCOUNT_SET',
|
ACCOUNT_SET: 'ACCOUNT_SET',
|
||||||
|
ACCOUNT_DELETE: 'ACCOUNT_DELETE',
|
||||||
ACCOUNT_FORM_ERRORS: 'ACCOUNT_FORM_ERRORS',
|
ACCOUNT_FORM_ERRORS: 'ACCOUNT_FORM_ERRORS',
|
||||||
CLEAR_ACCOUNT_FORM_ERRORS: 'CLEAR_ACCOUNT_FORM_ERRORS',
|
CLEAR_ACCOUNT_FORM_ERRORS: 'CLEAR_ACCOUNT_FORM_ERRORS',
|
||||||
|
|
||||||
ACCOUNT_BULK_ACTION_ADD: 'ACCOUNT_BULK_ACTION_ADD',
|
ACCOUNTS_SELECTED_ROWS_SET: 'ACCOUNTS_SELECTED_ROWS_SET',
|
||||||
ACCOUNT_BULK_ACTION_REMOVE: 'ACCOUNT_BULK_ACTION_REMOVE',
|
|
||||||
|
|
||||||
ACCOUNTS_SET_CURRENT_VIEW: 'ACCOUNTS_SET_CURRENT_VIEW',
|
ACCOUNTS_SET_CURRENT_VIEW: 'ACCOUNTS_SET_CURRENT_VIEW',
|
||||||
|
|
||||||
|
ACCOUNTS_TABLE_QUERY_SET: 'ACCOUNTS_TABLE_QUERY_SET',
|
||||||
|
ACCOUNTS_TABLE_QUERIES_SET: 'ACCOUNTS_TABLE_QUERIES_SET',
|
||||||
|
|
||||||
|
ACCOUNTS_TABLE_LOADING: 'ACCOUNTS_TABLE_LOADING',
|
||||||
};
|
};
|
||||||
28
client/src/store/queryReducers.js
Normal file
28
client/src/store/queryReducers.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export const createTableQueryReducers =
|
||||||
|
(resourceName = '', reducer) =>
|
||||||
|
(state, action) => {
|
||||||
|
const RESOURCE_NAME = resourceName.toUpperCase();
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case `${RESOURCE_NAME}_TABLE_QUERY_SET`:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
tableQuery: {
|
||||||
|
...state.tableQuery,
|
||||||
|
[state.key]: state.value,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case `${RESOURCE_NAME}_TABLE_QUERIES_ADD`:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
tableQuery: {
|
||||||
|
...state.tableQuery,
|
||||||
|
...action.queries
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return reducer(state, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,49 +1,107 @@
|
|||||||
|
|
||||||
.bigcapital-datatable{
|
.bigcapital-datatable{
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
// max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
table {
|
.table {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
width: 100%;
|
min-width: 100%;
|
||||||
|
display: block;
|
||||||
|
// width: 100%;
|
||||||
|
|
||||||
thead{
|
.thead{
|
||||||
th{
|
overflow-y: auto;
|
||||||
height: 48px;
|
overflow-x: hidden;
|
||||||
padding: 0.5rem 1.5rem;
|
|
||||||
|
.th{
|
||||||
|
|
||||||
|
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
background: #F8FAFA;
|
background: #F8FAFA;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: #666;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
border-bottom: 1px solid rgb(224, 224, 224);
|
border-bottom: 1px solid rgb(224, 224, 224);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
.tr {
|
||||||
:last-child {
|
display: flex;
|
||||||
td {
|
flex: 1 0 auto;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
.td {
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
th,
|
.th,
|
||||||
td {
|
.td {
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
|
||||||
:last-child {
|
:last-child {
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bp3-control{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizer {
|
||||||
|
display: inline-block;
|
||||||
|
background: transparent;
|
||||||
|
width: 10px;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: translateX(50%);
|
||||||
|
z-index: 1;
|
||||||
|
touch-action:none;
|
||||||
|
|
||||||
|
&.isResizing {
|
||||||
|
// background: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-resizer{
|
||||||
|
height: 100%;
|
||||||
|
width: 1px;
|
||||||
|
background: #ececec;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.isResizing .inner-resizer{
|
||||||
|
background: #1183DA;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody{
|
.tbody{
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
.tr .td{
|
||||||
|
border-bottom: 1px solid #E0E2E2;
|
||||||
|
}
|
||||||
|
|
||||||
.#{$ns}-button--action{
|
.td.actions .#{$ns}-button{
|
||||||
background: #E6EFFB;
|
background: #E6EFFB;
|
||||||
border: 0;
|
border: 0;
|
||||||
box-shadow: 0 0 0;
|
box-shadow: 0 0 0;
|
||||||
padding: 5px 15px;
|
padding: 5px 15px;
|
||||||
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,12 @@
|
|||||||
|
|
||||||
.dashboard__insider--accounts-chart{
|
.dashboard__insider--accounts-chart{
|
||||||
|
|
||||||
.e-grid{
|
.bigcapital-datatable{
|
||||||
.e-gridheader{
|
.normal{
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-normal{
|
|
||||||
|
|
||||||
.#{$ns}-icon{
|
.#{$ns}-icon{
|
||||||
color: #666;
|
color: #666;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-row{
|
|
||||||
width: 0;
|
|
||||||
|
|
||||||
.#{$ns}-control{
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,8 +196,7 @@
|
|||||||
width: calc(100% - 220px);
|
width: calc(100% - 220px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
margin-left: 220px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__insider{
|
&__insider{
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
|||||||
background: $sidebar-background;
|
background: $sidebar-background;
|
||||||
color: $sidebar-text-color;
|
color: $sidebar-text-color;
|
||||||
width: $sidebar-width;
|
width: $sidebar-width;
|
||||||
|
position: fixed;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
&__inner{
|
&__inner{
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|||||||
Reference in New Issue
Block a user