mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-24 16:49:48 +00:00
feat: optimize accounts performance.
feat: optimize alerts architecture. feat: optimize datatable architecture. feat: optimize datatable style.
This commit is contained in:
@@ -80,7 +80,7 @@
|
|||||||
"react-query": "^2.4.6",
|
"react-query": "^2.4.6",
|
||||||
"react-redux": "^7.1.3",
|
"react-redux": "^7.1.3",
|
||||||
"react-router-breadcrumbs-hoc": "^3.2.10",
|
"react-router-breadcrumbs-hoc": "^3.2.10",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scroll-sync": "^0.7.1",
|
"react-scroll-sync": "^0.7.1",
|
||||||
"react-scrollbars-custom": "^4.0.21",
|
"react-scrollbars-custom": "^4.0.21",
|
||||||
"react-sortablejs": "^2.0.11",
|
"react-sortablejs": "^2.0.11",
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
"react-table-sticky": "^1.1.2",
|
"react-table-sticky": "^1.1.2",
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"react-use": "^13.26.1",
|
"react-use": "^13.26.1",
|
||||||
"react-window": "^1.8.5",
|
"react-virtualized": "^9.22.3",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const CLASSES = {
|
|||||||
|
|
||||||
DASHBOARD_CONTENT: 'dashboard-content',
|
DASHBOARD_CONTENT: 'dashboard-content',
|
||||||
DASHBOARD_CONTENT_PREFERENCES: 'dashboard-content--preferences',
|
DASHBOARD_CONTENT_PREFERENCES: 'dashboard-content--preferences',
|
||||||
|
DASHBOARD_CONTENT_PANE: 'Pane2',
|
||||||
|
|
||||||
PAGE_FORM: 'page-form',
|
PAGE_FORM: 'page-form',
|
||||||
PAGE_FORM_HEADER: 'page-form__header',
|
PAGE_FORM_HEADER: 'page-form__header',
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function AccountsSelectList({
|
|||||||
onAccountSelected,
|
onAccountSelected,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
popoverFill = false,
|
popoverFill = false,
|
||||||
filterByRootTypes = [],
|
filterByParentTypes = [],
|
||||||
filterByTypes = [],
|
filterByTypes = [],
|
||||||
filterByNormal,
|
filterByNormal,
|
||||||
buttonProps = {}
|
buttonProps = {}
|
||||||
@@ -23,23 +23,23 @@ export default function AccountsSelectList({
|
|||||||
const filteredAccounts = useMemo(() => {
|
const filteredAccounts = useMemo(() => {
|
||||||
let filteredAccounts = [...accounts];
|
let filteredAccounts = [...accounts];
|
||||||
|
|
||||||
if (!isEmpty(filterByRootTypes)) {
|
if (!isEmpty(filterByParentTypes)) {
|
||||||
filteredAccounts = filteredAccounts.filter(
|
filteredAccounts = filteredAccounts.filter(
|
||||||
(account) => filterByRootTypes.indexOf(account.type.root_type) !== -1,
|
(account) => filterByParentTypes.indexOf(account.account_parent_type) !== -1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!isEmpty(filterByTypes)) {
|
if (!isEmpty(filterByTypes)) {
|
||||||
filteredAccounts = filteredAccounts.filter(
|
filteredAccounts = filteredAccounts.filter(
|
||||||
(account) => filterByTypes.indexOf(account.type.key) !== -1,
|
(account) => filterByTypes.indexOf(account.account_type) !== -1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!isEmpty(filterByNormal)) {
|
if (!isEmpty(filterByNormal)) {
|
||||||
filteredAccounts = filteredAccounts.filter(
|
filteredAccounts = filteredAccounts.filter(
|
||||||
(account) => filterByTypes.indexOf(account.type.normal) === filterByNormal,
|
(account) => filterByTypes.indexOf(account.account_normal) === filterByNormal,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return filteredAccounts;
|
return filteredAccounts;
|
||||||
}, [accounts, filterByRootTypes, filterByTypes, filterByNormal]);
|
}, [accounts, filterByParentTypes, filterByTypes, filterByNormal]);
|
||||||
|
|
||||||
// Find initial account object to set it as default account in initial render.
|
// Find initial account object to set it as default account in initial render.
|
||||||
const initialAccount = useMemo(
|
const initialAccount = useMemo(
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export default function AccountsSuggestField({
|
|||||||
inputProps={{ placeholder: defaultSelectText }}
|
inputProps={{ placeholder: defaultSelectText }}
|
||||||
resetOnClose={true}
|
resetOnClose={true}
|
||||||
fill={true}
|
fill={true}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||||
inputValueRenderer={handleInputValueRenderer}
|
inputValueRenderer={handleInputValueRenderer}
|
||||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Position, Toaster, Intent } from "@blueprintjs/core";
|
import { Position, Toaster, Intent } from "@blueprintjs/core";
|
||||||
|
|
||||||
const AppToaster = Toaster.create({
|
const AppToaster = Toaster.create({
|
||||||
position: Position.TOP,
|
position: Position.RIGHT_BOTTOM,
|
||||||
intent: Intent.WARNING,
|
intent: Intent.WARNING,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -94,8 +94,7 @@ export default function ContactsSuggestField({
|
|||||||
selectedItem={selecetedContact}
|
selectedItem={selecetedContact}
|
||||||
inputProps={{ placeholder: defaultTextSelect }}
|
inputProps={{ placeholder: defaultTextSelect }}
|
||||||
resetOnClose={true}
|
resetOnClose={true}
|
||||||
// fill={true}
|
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
inputValueRenderer={handleInputValueRenderer}
|
inputValueRenderer={handleInputValueRenderer}
|
||||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function DashboardSplitPane({
|
|||||||
sidebarExpended,
|
sidebarExpended,
|
||||||
children
|
children
|
||||||
}) {
|
}) {
|
||||||
const initialSize = 190;
|
const initialSize = 200;
|
||||||
|
|
||||||
const [defaultSize, setDefaultSize] = useState(
|
const [defaultSize, setDefaultSize] = useState(
|
||||||
parseInt(localStorage.getItem('dashboard-size'), 10) || initialSize,
|
parseInt(localStorage.getItem('dashboard-size'), 10) || initialSize,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
MenuDivider,
|
MenuDivider,
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
Popover,
|
||||||
|
Position,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ function DashboardTopbarUser({ requestLogout, user }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover content={userAvatarDropMenu}>
|
<Popover content={userAvatarDropMenu} position={Position.BOTTOM}>
|
||||||
<Button>
|
<Button>
|
||||||
<div className="user-text">
|
<div className="user-text">
|
||||||
{firstLettersArgs(user.first_name, user.last_name)}
|
{firstLettersArgs(user.first_name, user.last_name)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useCallback, useMemo } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
useTable,
|
useTable,
|
||||||
useExpanded,
|
useExpanded,
|
||||||
@@ -9,30 +9,36 @@ import {
|
|||||||
useFlexLayout,
|
useFlexLayout,
|
||||||
useAsyncDebounce,
|
useAsyncDebounce,
|
||||||
} from 'react-table';
|
} from 'react-table';
|
||||||
import { Checkbox, Spinner, ContextMenu } from '@blueprintjs/core';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import { FixedSizeList } from 'react-window';
|
|
||||||
import { useSticky } from 'react-table-sticky';
|
import { useSticky } from 'react-table-sticky';
|
||||||
import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync';
|
|
||||||
|
|
||||||
import { useUpdateEffect } from 'hooks';
|
import { useUpdateEffect } from 'hooks';
|
||||||
import { If, Pagination, Choose } from 'components';
|
import { saveInvoke } from 'utils';
|
||||||
|
|
||||||
import { ConditionalWrapper, saveInvoke } from 'utils';
|
|
||||||
|
|
||||||
import 'style/components/DataTable/DataTable.scss';
|
import 'style/components/DataTable/DataTable.scss';
|
||||||
|
|
||||||
const IndeterminateCheckbox = React.forwardRef(
|
import TableNoResultsRow from './Datatable/TableNoResultsRow';
|
||||||
({ indeterminate, ...rest }, ref) => {
|
import TableLoadingRow from './Datatable/TableLoading';
|
||||||
return <Checkbox indeterminate={indeterminate} {...rest} />;
|
import TableHeader from './Datatable/TableHeader';
|
||||||
},
|
import TablePage from './Datatable/TablePage';
|
||||||
);
|
import TableRow from './Datatable/TableRow';
|
||||||
|
import TableRows from './Datatable/TableRows';
|
||||||
|
import TableCell from './Datatable/TableCell';
|
||||||
|
import TableTBody from './Datatable/TableTBody';
|
||||||
|
import TableContext from './Datatable/TableContext';
|
||||||
|
import TablePagination from './Datatable/TablePagination';
|
||||||
|
import TableWrapper from './Datatable/TableWrapper';
|
||||||
|
|
||||||
export default function DataTable({
|
import TableIndeterminateCheckboxRow from './Datatable/TableIndeterminateCheckboxRow';
|
||||||
|
import TableIndeterminateCheckboxHeader from './Datatable/TableIndeterminateCheckboxHeader';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Datatable component.
|
||||||
|
*/
|
||||||
|
export default function DataTable(props) {
|
||||||
|
const {
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
|
|
||||||
loading,
|
|
||||||
onFetchData,
|
onFetchData,
|
||||||
|
|
||||||
onSelectedRowsChange,
|
onSelectedRowsChange,
|
||||||
@@ -40,73 +46,64 @@ export default function DataTable({
|
|||||||
manualPagination = true,
|
manualPagination = true,
|
||||||
selectionColumn = false,
|
selectionColumn = false,
|
||||||
expandSubRows = true,
|
expandSubRows = true,
|
||||||
className,
|
|
||||||
noResults = 'This report does not contain any data.',
|
|
||||||
expanded = {},
|
expanded = {},
|
||||||
rowClassNames,
|
rowClassNames,
|
||||||
sticky = false,
|
|
||||||
virtualizedRows = false,
|
|
||||||
fixedSizeHeight = 100,
|
|
||||||
fixedItemSize = 30,
|
|
||||||
payload,
|
payload,
|
||||||
expandable = false,
|
expandable = false,
|
||||||
expandToggleColumn = 2,
|
expandToggleColumn = 2,
|
||||||
noInitialFetch = false,
|
noInitialFetch = false,
|
||||||
spinnerProps = { size: 30 },
|
|
||||||
|
|
||||||
pagination = false,
|
|
||||||
pagesCount: controlledPageCount,
|
pagesCount: controlledPageCount,
|
||||||
|
|
||||||
// Pagination props.
|
// Pagination props.
|
||||||
initialPageIndex = 0,
|
initialPageIndex = 0,
|
||||||
initialPageSize = 10,
|
initialPageSize = 10,
|
||||||
rowContextMenu,
|
rowContextMenu,
|
||||||
|
|
||||||
expandColumnSpace = 1.5,
|
expandColumnSpace = 1.5,
|
||||||
|
|
||||||
updateDebounceTime = 200,
|
updateDebounceTime = 200,
|
||||||
selectionColumnWidth = 42,
|
selectionColumnWidth = 42,
|
||||||
|
|
||||||
// Read this document to know why! https://bit.ly/2Uw9SEc
|
autoResetPage,
|
||||||
autoResetPage = true,
|
autoResetExpanded,
|
||||||
autoResetExpanded = true,
|
autoResetGroupBy,
|
||||||
autoResetGroupBy = true,
|
autoResetSelectedRows,
|
||||||
autoResetSelectedRows = true,
|
autoResetSortBy,
|
||||||
autoResetSortBy = true,
|
autoResetFilters,
|
||||||
autoResetFilters = true,
|
autoResetRowState,
|
||||||
autoResetRowState = true,
|
|
||||||
}) {
|
|
||||||
const {
|
|
||||||
getTableProps,
|
|
||||||
getTableBodyProps,
|
|
||||||
headerGroups,
|
|
||||||
prepareRow,
|
|
||||||
page,
|
|
||||||
rows,
|
|
||||||
selectedFlatRows,
|
|
||||||
getToggleAllRowsExpandedProps,
|
|
||||||
isAllRowsExpanded,
|
|
||||||
totalColumnsWidth,
|
|
||||||
|
|
||||||
// page,
|
// Components
|
||||||
pageCount,
|
TableHeaderRenderer,
|
||||||
canPreviousPage,
|
TablePageRenderer,
|
||||||
canNextPage,
|
TableWrapperRenderer,
|
||||||
gotoPage,
|
TableTBodyRenderer,
|
||||||
previousPage,
|
TablePaginationRenderer,
|
||||||
nextPage,
|
} = props;
|
||||||
setPageSize,
|
|
||||||
|
|
||||||
// Get the state from the instance
|
const selectionColumnObj = {
|
||||||
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
id: 'selection',
|
||||||
} = useTable(
|
disableResizing: true,
|
||||||
|
minWidth: selectionColumnWidth,
|
||||||
|
width: selectionColumnWidth,
|
||||||
|
maxWidth: selectionColumnWidth,
|
||||||
|
// The header can use the table's getToggleAllRowsSelectedProps method
|
||||||
|
// to render a checkbox
|
||||||
|
Header: TableIndeterminateCheckboxHeader,
|
||||||
|
// The cell can use the individual row's getToggleRowSelectedProps method
|
||||||
|
// to the render a checkbox
|
||||||
|
Cell: TableIndeterminateCheckboxRow,
|
||||||
|
className: 'selection',
|
||||||
|
...(typeof selectionColumn === 'object' ? selectionColumn : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const table = useTable(
|
||||||
{
|
{
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
initialState: {
|
initialState: {
|
||||||
pageIndex: initialPageIndex,
|
pageIndex: initialPageIndex,
|
||||||
pageSize: initialPageSize,
|
pageSize: initialPageSize,
|
||||||
expanded
|
expanded,
|
||||||
},
|
},
|
||||||
manualPagination,
|
manualPagination,
|
||||||
pageCount: controlledPageCount,
|
pageCount: controlledPageCount,
|
||||||
@@ -133,49 +130,23 @@ export default function DataTable({
|
|||||||
(hooks) => {
|
(hooks) => {
|
||||||
hooks.visibleColumns.push((columns) => [
|
hooks.visibleColumns.push((columns) => [
|
||||||
// Let's make a column for selection
|
// Let's make a column for selection
|
||||||
...(selectionColumn
|
...(selectionColumn ? [selectionColumnObj] : []),
|
||||||
? [
|
|
||||||
{
|
|
||||||
id: 'selection',
|
|
||||||
disableResizing: true,
|
|
||||||
minWidth: selectionColumnWidth,
|
|
||||||
width: selectionColumnWidth,
|
|
||||||
maxWidth: selectionColumnWidth,
|
|
||||||
// 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>
|
|
||||||
),
|
|
||||||
className: 'selection',
|
|
||||||
...(typeof selectionColumn === 'object' ? selectionColumn : {}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...columns,
|
...columns,
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
selectedFlatRows,
|
||||||
|
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
||||||
|
} = table;
|
||||||
|
|
||||||
const isInitialMount = useRef(noInitialFetch);
|
const isInitialMount = useRef(noInitialFetch);
|
||||||
const onFetchDataDebounced = useAsyncDebounce(
|
|
||||||
(...args) => {
|
const onFetchDataDebounced = useAsyncDebounce((...args) => {
|
||||||
saveInvoke(onFetchData, ...args);
|
saveInvoke(onFetchData, ...args);
|
||||||
},
|
}, updateDebounceTime);
|
||||||
updateDebounceTime,
|
|
||||||
);
|
|
||||||
// When these table states change, fetch new data!
|
// When these table states change, fetch new data!
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isInitialMount.current) {
|
if (isInitialMount.current) {
|
||||||
@@ -189,285 +160,42 @@ export default function DataTable({
|
|||||||
saveInvoke(onSelectedRowsChange, selectedFlatRows);
|
saveInvoke(onSelectedRowsChange, selectedFlatRows);
|
||||||
}, [selectedRowIds, onSelectedRowsChange]);
|
}, [selectedRowIds, onSelectedRowsChange]);
|
||||||
|
|
||||||
// Renders table cell.
|
|
||||||
const RenderCell = useCallback(
|
|
||||||
({ row, cell, column, index }) => (
|
|
||||||
<ConditionalWrapper
|
|
||||||
condition={expandToggleColumn === index && expandable}
|
|
||||||
wrapper={(children) => (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
'padding-left': `${row.depth * expandColumnSpace}rem`,
|
|
||||||
}}
|
|
||||||
className={'expend-padding'}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter
|
|
||||||
// to build the toggle for expanding a row
|
|
||||||
}
|
|
||||||
<If
|
|
||||||
condition={
|
|
||||||
row.canExpand && expandable && index === expandToggleColumn
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
{...row.getToggleRowExpandedProps({ className: 'expand-toggle' })}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={classnames({
|
|
||||||
'arrow-down': row.isExpanded,
|
|
||||||
'arrow-right': !row.isExpanded,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<ConditionalWrapper
|
|
||||||
condition={cell.column.textOverview}
|
|
||||||
wrapper={(children) => (
|
|
||||||
<span class="text-overview">{ children }</span>
|
|
||||||
)}>
|
|
||||||
{cell.render('Cell')}
|
|
||||||
</ConditionalWrapper>
|
|
||||||
</ConditionalWrapper>
|
|
||||||
),
|
|
||||||
[expandable, expandToggleColumn, expandColumnSpace],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle rendering row context menu.
|
|
||||||
const handleRowContextMenu = useMemo(
|
|
||||||
() => (cell, row) => (e) => {
|
|
||||||
if (typeof rowContextMenu === 'function') {
|
|
||||||
e.preventDefault();
|
|
||||||
const tr = e.currentTarget.closest('.tr');
|
|
||||||
tr.classList.add('is-context-menu-active');
|
|
||||||
|
|
||||||
const DropdownEl = rowContextMenu(cell, row);
|
|
||||||
|
|
||||||
ContextMenu.show(
|
|
||||||
DropdownEl,
|
|
||||||
{ left: e.clientX, top: e.clientY },
|
|
||||||
() => {
|
|
||||||
tr.classList.remove('is-context-menu-active');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[rowContextMenu],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Renders table row.
|
|
||||||
const RenderRow = useCallback(
|
|
||||||
({ style = {}, row }) => {
|
|
||||||
prepareRow(row);
|
|
||||||
const rowClasses = rowClassNames && rowClassNames(row);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<TableContext.Provider value={{ table, props }}>
|
||||||
{...row.getRowProps({
|
<TableWrapperRenderer>
|
||||||
className: classnames(
|
<TableHeaderRenderer />
|
||||||
'tr',
|
|
||||||
{
|
|
||||||
'is-expanded': row.isExpanded && row.canExpand,
|
|
||||||
},
|
|
||||||
rowClasses,
|
|
||||||
),
|
|
||||||
style,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{row.cells.map((cell, i) => {
|
|
||||||
const index = i + 1;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
{...cell.getCellProps({
|
|
||||||
className: classnames(
|
|
||||||
cell.column.className,
|
|
||||||
'td',
|
|
||||||
{
|
|
||||||
'is-text-overview': cell.column.textOverview,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
})}
|
|
||||||
onContextMenu={handleRowContextMenu(cell, row)}
|
|
||||||
>
|
|
||||||
{RenderCell({ cell, row, index })}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[prepareRow, rowClassNames, RenderCell, handleRowContextMenu],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Renders virtualize circle table rows.
|
<TableTBodyRenderer>
|
||||||
const RenderVirtualizedRows = useCallback(
|
<TablePageRenderer />
|
||||||
({ index, style }) => {
|
</TableTBodyRenderer>
|
||||||
const row = rows[index];
|
</TableWrapperRenderer>
|
||||||
return RenderRow({ row, style });
|
|
||||||
},
|
|
||||||
[RenderRow, rows],
|
|
||||||
);
|
|
||||||
// Renders page with multi-rows.
|
|
||||||
const RenderPage = useCallback(
|
|
||||||
({ style, index } = {}) => {
|
|
||||||
return page.map((row, index) => RenderRow({ row }));
|
|
||||||
},
|
|
||||||
[RenderRow, page],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Renders fixed size tbody.
|
<TablePaginationRenderer />
|
||||||
const RenderTBody = useCallback(() => {
|
</TableContext.Provider>
|
||||||
return virtualizedRows ? (
|
|
||||||
<FixedSizeList
|
|
||||||
height={fixedSizeHeight}
|
|
||||||
itemCount={rows.length}
|
|
||||||
itemSize={fixedItemSize}
|
|
||||||
>
|
|
||||||
{RenderVirtualizedRows}
|
|
||||||
</FixedSizeList>
|
|
||||||
) : (
|
|
||||||
RenderPage()
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
fixedSizeHeight,
|
|
||||||
rows,
|
|
||||||
fixedItemSize,
|
|
||||||
virtualizedRows,
|
|
||||||
RenderVirtualizedRows,
|
|
||||||
RenderPage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handlePageChange = useCallback(
|
|
||||||
(currentPage) => {
|
|
||||||
gotoPage(currentPage - 1);
|
|
||||||
},
|
|
||||||
[gotoPage],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePageSizeChange = useCallback(
|
|
||||||
(pageSize, currentPage) => {
|
|
||||||
gotoPage(0);
|
|
||||||
setPageSize(pageSize);
|
|
||||||
},
|
|
||||||
[gotoPage, setPageSize],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classnames('bigcapital-datatable', className, {
|
|
||||||
'has-sticky': sticky,
|
|
||||||
'has-pagination': pagination,
|
|
||||||
'is-expandable': expandable,
|
|
||||||
'is-loading': loading,
|
|
||||||
'has-virtualized-rows': virtualizedRows,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<ScrollSync>
|
|
||||||
<div
|
|
||||||
{...getTableProps({ style: { minWidth: 'none' } })}
|
|
||||||
className="table"
|
|
||||||
>
|
|
||||||
<ScrollSyncPane>
|
|
||||||
<div className="thead">
|
|
||||||
{headerGroups.map((headerGroup) => (
|
|
||||||
<div {...headerGroup.getHeaderGroupProps()} className="tr">
|
|
||||||
{headerGroup.headers.map((column, index) => (
|
|
||||||
<div
|
|
||||||
{...column.getHeaderProps({
|
|
||||||
className: classnames(column.className || '', 'th'),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<If
|
|
||||||
condition={
|
|
||||||
expandable && index + 1 === expandToggleColumn
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
{...getToggleAllRowsExpandedProps()}
|
|
||||||
className="expand-toggle"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={classnames({
|
|
||||||
'arrow-down': isAllRowsExpanded,
|
|
||||||
'arrow-right': !isAllRowsExpanded,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<div {...column.getSortByToggleProps()}>
|
|
||||||
{column.render('Header')}
|
|
||||||
|
|
||||||
<If condition={column.isSorted}>
|
|
||||||
<span
|
|
||||||
className={classnames(
|
|
||||||
{
|
|
||||||
'sort-icon--desc': column.isSortedDesc,
|
|
||||||
'sort-icon--asc': !column.isSortedDesc,
|
|
||||||
},
|
|
||||||
'sort-icon',
|
|
||||||
)}
|
|
||||||
></span>
|
|
||||||
</If>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{column.canResize && (
|
|
||||||
<div
|
|
||||||
{...column.getResizerProps()}
|
|
||||||
className={`resizer ${
|
|
||||||
column.isResizing ? 'isResizing' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div class="inner-resizer" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ScrollSyncPane>
|
|
||||||
|
|
||||||
<ScrollSyncPane>
|
|
||||||
<div {...getTableBodyProps()} className="tbody">
|
|
||||||
<div class="tbody-inner" style={{ minWidth: totalColumnsWidth }}>
|
|
||||||
<Choose>
|
|
||||||
<Choose.When condition={loading}>
|
|
||||||
<div class="loading">
|
|
||||||
<Spinner {...spinnerProps} />
|
|
||||||
</div>
|
|
||||||
</Choose.When>
|
|
||||||
|
|
||||||
<Choose.Otherwise>
|
|
||||||
{RenderTBody()}
|
|
||||||
|
|
||||||
<If condition={page.length === 0}>
|
|
||||||
<div className={'tr no-results'}>
|
|
||||||
<div class="td">{noResults}</div>
|
|
||||||
</div>
|
|
||||||
</If>
|
|
||||||
</Choose.Otherwise>
|
|
||||||
</Choose>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ScrollSyncPane>
|
|
||||||
</div>
|
|
||||||
</ScrollSync>
|
|
||||||
|
|
||||||
<If condition={pagination && !loading}>
|
|
||||||
<Pagination
|
|
||||||
initialPage={pageIndex + 1}
|
|
||||||
total={pageSize * pageCount}
|
|
||||||
size={pageSize}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
onPageSizeChange={handlePageSizeChange}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DataTable.defaultProps = {
|
||||||
|
pagination: false,
|
||||||
|
spinnerProps: { size: 30 },
|
||||||
|
|
||||||
|
autoResetPage: true,
|
||||||
|
autoResetExpanded: true,
|
||||||
|
autoResetGroupBy: true,
|
||||||
|
autoResetSelectedRows: true,
|
||||||
|
autoResetSortBy: true,
|
||||||
|
autoResetFilters: true,
|
||||||
|
autoResetRowState: true,
|
||||||
|
|
||||||
|
TableHeaderRenderer: TableHeader,
|
||||||
|
TableLoadingRenderer: TableLoadingRow,
|
||||||
|
TablePageRenderer: TablePage,
|
||||||
|
TableRowsRenderer: TableRows,
|
||||||
|
TableRowRenderer: TableRow,
|
||||||
|
TableCellRenderer: TableCell,
|
||||||
|
TableWrapperRenderer: TableWrapper,
|
||||||
|
TableTBodyRenderer: TableTBody,
|
||||||
|
TablePaginationRenderer: TablePagination,
|
||||||
|
TableNoResultsRowRenderer: TableNoResultsRow,
|
||||||
|
};
|
||||||
11
client/src/components/Datatable/TableBody.js
Normal file
11
client/src/components/Datatable/TableBody.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export default function TableBody({}) {
|
||||||
|
return (
|
||||||
|
<ScrollSyncPane>
|
||||||
|
<div {...getTableBodyProps()} className="tbody">
|
||||||
|
<div class="tbody-inner" style={{ minWidth: totalColumnsWidth }}></div>
|
||||||
|
</div>
|
||||||
|
</ScrollSyncPane>
|
||||||
|
);
|
||||||
|
}
|
||||||
52
client/src/components/Datatable/TableCell.js
Normal file
52
client/src/components/Datatable/TableCell.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { If } from 'components';
|
||||||
|
import { ConditionalWrapper } from 'utils';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tabl cell.
|
||||||
|
*/
|
||||||
|
export default function TableCell({ cell, row, index }) {
|
||||||
|
const {
|
||||||
|
props: { expandToggleColumn, expandable }
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...cell.getCellProps({
|
||||||
|
className: classNames(cell.column.className, 'td', {
|
||||||
|
'is-text-overview': cell.column.textOverview,
|
||||||
|
}),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter
|
||||||
|
// to build the toggle for expanding a row
|
||||||
|
}
|
||||||
|
<If
|
||||||
|
condition={
|
||||||
|
cell.row.canExpand && expandable && index === expandToggleColumn
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
{...row.getToggleRowExpandedProps({ className: 'expand-toggle' })}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={classNames({
|
||||||
|
'arrow-down': row.isExpanded,
|
||||||
|
'arrow-right': !row.isExpanded,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</If>
|
||||||
|
|
||||||
|
<ConditionalWrapper
|
||||||
|
condition={cell.column.textOverview}
|
||||||
|
wrapper={(children) => <span class="text-overview">{children}</span>}
|
||||||
|
>
|
||||||
|
{cell.render('Cell')}
|
||||||
|
</ConditionalWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
client/src/components/Datatable/TableContext.js
Normal file
3
client/src/components/Datatable/TableContext.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export default createContext();
|
||||||
18
client/src/components/Datatable/TableFastCell.js
Normal file
18
client/src/components/Datatable/TableFastCell.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import TableCell from './TableCell';
|
||||||
|
|
||||||
|
function TableFastCell({ cell, row, index }) {
|
||||||
|
return <TableCell cell={cell} row={row} index={index} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(TableFastCell, (prevProps, nextProps) => {
|
||||||
|
if (
|
||||||
|
prevProps.cell.value === nextProps.cell.value &&
|
||||||
|
prevProps.cell.maxWidth === nextProps.cell.maxWidth &&
|
||||||
|
prevProps.cell.width === nextProps.cell.width
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
82
client/src/components/Datatable/TableHeader.js
Normal file
82
client/src/components/Datatable/TableHeader.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { ScrollSyncPane } from 'react-scroll-sync';
|
||||||
|
import { If } from 'components';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
|
||||||
|
function TableHeaderGroup({ headerGroup }) {
|
||||||
|
const {
|
||||||
|
table: { getToggleAllRowsExpandedProps, isAllRowsExpanded },
|
||||||
|
props: { expandable, expandToggleColumn },
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...headerGroup.getHeaderGroupProps()} className="tr">
|
||||||
|
{headerGroup.headers.map((column, index) => (
|
||||||
|
<div
|
||||||
|
{...column.getHeaderProps({
|
||||||
|
className: classNames(column.className || '', 'th'),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<If condition={expandable && index + 1 === expandToggleColumn}>
|
||||||
|
<span
|
||||||
|
{...getToggleAllRowsExpandedProps()}
|
||||||
|
className="expand-toggle"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={classNames({
|
||||||
|
'arrow-down': isAllRowsExpanded,
|
||||||
|
'arrow-right': !isAllRowsExpanded,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</If>
|
||||||
|
|
||||||
|
<div {...column.getSortByToggleProps()}>
|
||||||
|
{column.render('Header')}
|
||||||
|
|
||||||
|
<If condition={column.isSorted}>
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
{
|
||||||
|
'sort-icon--desc': column.isSortedDesc,
|
||||||
|
'sort-icon--asc': !column.isSortedDesc,
|
||||||
|
},
|
||||||
|
'sort-icon',
|
||||||
|
)}
|
||||||
|
></span>
|
||||||
|
</If>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{column.canResize && (
|
||||||
|
<div
|
||||||
|
{...column.getResizerProps()}
|
||||||
|
className={`resizer ${column.isResizing ? 'isResizing' : ''}`}
|
||||||
|
>
|
||||||
|
<div class="inner-resizer" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table header.
|
||||||
|
*/
|
||||||
|
export default function TableHeader() {
|
||||||
|
const {
|
||||||
|
table: { headerGroups },
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollSyncPane>
|
||||||
|
<div className="thead">
|
||||||
|
{headerGroups.map((headerGroup) => (
|
||||||
|
<TableHeaderGroup headerGroup={headerGroup} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ScrollSyncPane>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Checkbox } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
export default function TableIndeterminateCheckboxHeader({
|
||||||
|
getToggleAllRowsSelectedProps,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Checkbox {...getToggleAllRowsSelectedProps()} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Checkbox } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
export default function TableIndeterminateCheckboxRow({ row }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Checkbox {...row.getToggleRowSelectedProps()} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
15
client/src/components/Datatable/TableLoading.js
Normal file
15
client/src/components/Datatable/TableLoading.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Spinner } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table loading component.
|
||||||
|
*/
|
||||||
|
export default function TableLoading({
|
||||||
|
spinnerProps
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div class="loading">
|
||||||
|
<Spinner {...spinnerProps} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
client/src/components/Datatable/TableNoResultsRow.js
Normal file
17
client/src/components/Datatable/TableNoResultsRow.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table no-results row text.
|
||||||
|
*/
|
||||||
|
export default function TableNoResultsRow() {
|
||||||
|
const {
|
||||||
|
props: { noResults }
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'tr no-results'}>
|
||||||
|
<div class="td">{ noResults }</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
26
client/src/components/Datatable/TablePage.js
Normal file
26
client/src/components/Datatable/TablePage.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table page.
|
||||||
|
*/
|
||||||
|
export default function TablePage() {
|
||||||
|
const {
|
||||||
|
table: { page },
|
||||||
|
props: {
|
||||||
|
spinnerProps,
|
||||||
|
loading,
|
||||||
|
TableRowsRenderer,
|
||||||
|
TableLoadingRenderer,
|
||||||
|
TableNoResultsRow,
|
||||||
|
},
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <TableLoadingRenderer spinnerProps={spinnerProps} />;
|
||||||
|
}
|
||||||
|
if (page.length === 0) {
|
||||||
|
return <TableNoResultsRow />;
|
||||||
|
}
|
||||||
|
return (<TableRowsRenderer />);
|
||||||
|
}
|
||||||
45
client/src/components/Datatable/TablePagination.js
Normal file
45
client/src/components/Datatable/TablePagination.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import React, { useCallback, useContext } from 'react';
|
||||||
|
import { If, Pagination } from 'components';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table pagination.
|
||||||
|
*/
|
||||||
|
export default function TablePagination({}) {
|
||||||
|
const {
|
||||||
|
table: {
|
||||||
|
gotoPage,
|
||||||
|
setPageSize,
|
||||||
|
pageCount,
|
||||||
|
state: { pageIndex, pageSize },
|
||||||
|
},
|
||||||
|
props: { pagination, loading },
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
const handlePageChange = useCallback(
|
||||||
|
(currentPage) => {
|
||||||
|
gotoPage(currentPage - 1);
|
||||||
|
},
|
||||||
|
[gotoPage],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePageSizeChange = useCallback(
|
||||||
|
(pageSize, currentPage) => {
|
||||||
|
gotoPage(0);
|
||||||
|
setPageSize(pageSize);
|
||||||
|
},
|
||||||
|
[gotoPage, setPageSize],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<If condition={pagination && !loading}>
|
||||||
|
<Pagination
|
||||||
|
initialPage={pageIndex + 1}
|
||||||
|
total={pageSize * pageCount}
|
||||||
|
size={pageSize}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
onPageSizeChange={handlePageSizeChange}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
client/src/components/Datatable/TableRow.js
Normal file
51
client/src/components/Datatable/TableRow.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { ContextMenu } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
import { saveInvoke } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table row.
|
||||||
|
*/
|
||||||
|
export default function TableRow({ row, className, style }) {
|
||||||
|
const {
|
||||||
|
props: { TableCellRenderer, rowContextMenu, rowClassNames },
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
// Handle rendering row context menu.
|
||||||
|
const handleRowContextMenu = (row) => (e) => {
|
||||||
|
if (typeof rowContextMenu === 'function') {
|
||||||
|
e.preventDefault();
|
||||||
|
const tr = e.currentTarget.closest('.tr');
|
||||||
|
tr.classList.add('is-context-menu-active');
|
||||||
|
|
||||||
|
const DropdownEl = rowContextMenu({ row });
|
||||||
|
|
||||||
|
ContextMenu.show(DropdownEl, { left: e.clientX, top: e.clientY }, () => {
|
||||||
|
tr.classList.remove('is-context-menu-active');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...row.getRowProps({
|
||||||
|
className: classNames(
|
||||||
|
'tr',
|
||||||
|
{
|
||||||
|
'is-expanded': row.isExpanded && row.canExpand,
|
||||||
|
},
|
||||||
|
saveInvoke(rowClassNames, row),
|
||||||
|
className,
|
||||||
|
),
|
||||||
|
style,
|
||||||
|
onContextMenu: handleRowContextMenu(row)
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{row.cells.map((cell, index) => (
|
||||||
|
<TableCellRenderer cell={cell} row={row} index={index + 1} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
client/src/components/Datatable/TableRows.js
Normal file
17
client/src/components/Datatable/TableRows.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React, { useContext } from "react";
|
||||||
|
import TableContext from "./TableContext";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table rows.
|
||||||
|
*/
|
||||||
|
export default function TableRows() {
|
||||||
|
const {
|
||||||
|
table: { prepareRow, page },
|
||||||
|
props: { TableRowRenderer, TableCellRenderer },
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
return page.map((row) => {
|
||||||
|
prepareRow(row);
|
||||||
|
return <TableRowRenderer row={row} TableCellRenderer={TableCellRenderer} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
21
client/src/components/Datatable/TableTBody.js
Normal file
21
client/src/components/Datatable/TableTBody.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { ScrollSyncPane } from 'react-scroll-sync';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
|
||||||
|
export default function TableTBody({
|
||||||
|
children
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
table: { getTableBodyProps }
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollSyncPane>
|
||||||
|
<div {...getTableBodyProps()} className="tbody">
|
||||||
|
<div class="tbody-inner">
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollSyncPane>
|
||||||
|
);
|
||||||
|
}
|
||||||
66
client/src/components/Datatable/TableVirtualizedRows.js
Normal file
66
client/src/components/Datatable/TableVirtualizedRows.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { WindowScroller, AutoSizer, List } from 'react-virtualized';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
|
||||||
|
function TableVirtualizedListRow({
|
||||||
|
index,
|
||||||
|
isScrolling,
|
||||||
|
isVisible,
|
||||||
|
key,
|
||||||
|
style,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
table: { page, prepareRow },
|
||||||
|
props: { TableRowRenderer },
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
const row = page[index];
|
||||||
|
prepareRow(row);
|
||||||
|
|
||||||
|
return <TableRowRenderer row={row} style={style} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table virtualized list rows.
|
||||||
|
*/
|
||||||
|
export default function TableVirtualizedListRows() {
|
||||||
|
const {
|
||||||
|
table: { page },
|
||||||
|
props: { vListrowHeight, vListOverscanRowCount }
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
// Dashboard content pane.
|
||||||
|
const dashboardContentPane = document.querySelector(
|
||||||
|
`.${CLASSES.DASHBOARD_CONTENT_PANE}`,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<WindowScroller scrollElement={dashboardContentPane}>
|
||||||
|
{({ height, isScrolling, registerChild, onChildScroll, scrollTop }) => (
|
||||||
|
<div className={'WindowScrollerWrapper'}>
|
||||||
|
<AutoSizer disableHeight>
|
||||||
|
{({ width }) => (
|
||||||
|
<div ref={registerChild}>
|
||||||
|
<List
|
||||||
|
autoHeight={true}
|
||||||
|
className={'List'}
|
||||||
|
height={height}
|
||||||
|
isScrolling={isScrolling}
|
||||||
|
onScroll={onChildScroll}
|
||||||
|
overscanRowCount={vListOverscanRowCount}
|
||||||
|
rowCount={page.length}
|
||||||
|
rowHeight={vListrowHeight}
|
||||||
|
rowRenderer={({ ...args }) => {
|
||||||
|
return <TableVirtualizedListRow {...args} />;
|
||||||
|
}}
|
||||||
|
scrollTop={scrollTop}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AutoSizer>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</WindowScroller>
|
||||||
|
);
|
||||||
|
}
|
||||||
35
client/src/components/Datatable/TableWrapper.js
Normal file
35
client/src/components/Datatable/TableWrapper.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { ScrollSync } from 'react-scroll-sync';
|
||||||
|
import TableContext from './TableContext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table wrapper.
|
||||||
|
*/
|
||||||
|
export default function TableWrapper({ children }) {
|
||||||
|
const {
|
||||||
|
table: { getTableProps },
|
||||||
|
props: { sticky, pagination, loading, expandable, virtualizedRows, className },
|
||||||
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('bigcapital-datatable', className, {
|
||||||
|
'has-sticky': sticky,
|
||||||
|
'has-pagination': pagination,
|
||||||
|
'is-expandable': expandable,
|
||||||
|
'is-loading': loading,
|
||||||
|
'has-virtualized-rows': virtualizedRows,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<ScrollSync>
|
||||||
|
<div
|
||||||
|
{...getTableProps({ style: { minWidth: 'none' } })}
|
||||||
|
className="table"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</ScrollSync>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -100,7 +100,7 @@ export default function ItemsSuggestField({
|
|||||||
inputProps={{ placeholder: defautlSelectText }}
|
inputProps={{ placeholder: defautlSelectText }}
|
||||||
resetOnClose={true}
|
resetOnClose={true}
|
||||||
fill={true}
|
fill={true}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ import PageFormBigNumber from './PageFormBigNumber';
|
|||||||
import AccountsMultiSelect from './AccountsMultiSelect';
|
import AccountsMultiSelect from './AccountsMultiSelect';
|
||||||
import CustomersMultiSelect from './CustomersMultiSelect';
|
import CustomersMultiSelect from './CustomersMultiSelect';
|
||||||
|
|
||||||
|
|
||||||
|
import TableFastCell from './Datatable/TableFastCell';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -93,5 +96,7 @@ export {
|
|||||||
PageFormBigNumber,
|
PageFormBigNumber,
|
||||||
AccountsMultiSelect,
|
AccountsMultiSelect,
|
||||||
DataTableEditable,
|
DataTableEditable,
|
||||||
CustomersMultiSelect
|
CustomersMultiSelect,
|
||||||
|
|
||||||
|
TableFastCell,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ export default [
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/homepage',
|
href: '/homepage',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
spacer: 1,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: 'Sales & inventory',
|
text: 'Sales & inventory',
|
||||||
label: true,
|
label: true,
|
||||||
@@ -91,10 +88,7 @@ export default [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
divider: true,
|
text: <T id={'accounting'} />,
|
||||||
},
|
|
||||||
{
|
|
||||||
text: <T id={'financial'} />,
|
|
||||||
label: true,
|
label: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -137,7 +137,8 @@ function ManualJournalsDataTable({
|
|||||||
accessor: (r) => (
|
accessor: (r) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={<AmountPopoverContent journalEntries={r.entries} />}
|
content={<AmountPopoverContent journalEntries={r.entries} />}
|
||||||
position={Position.RIGHT_BOTTOM}
|
position={Position.RIGHT_TOP}
|
||||||
|
boundary={'viewport'}
|
||||||
>
|
>
|
||||||
<Money amount={r.amount} currency={'USD'} />
|
<Money amount={r.amount} currency={'USD'} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useState, useCallback } from 'react';
|
import React, { memo, useState } from 'react';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -22,9 +22,13 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
|
|||||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||||
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts actions bar.
|
||||||
|
*/
|
||||||
function AccountsActionsBar({
|
function AccountsActionsBar({
|
||||||
openDialog,
|
openDialog,
|
||||||
accountsViews,
|
accountsViews,
|
||||||
@@ -32,18 +36,18 @@ function AccountsActionsBar({
|
|||||||
// #withResourceDetail
|
// #withResourceDetail
|
||||||
resourceFields,
|
resourceFields,
|
||||||
|
|
||||||
// #withAccountsActions
|
// #withAccountsTableActions
|
||||||
addAccountsTableQueries,
|
addAccountsTableQueries,
|
||||||
|
setAccountsBulkAction,
|
||||||
|
|
||||||
// #withAccounts
|
// #withAccounts
|
||||||
accountsTableQuery,
|
accountsTableQuery,
|
||||||
|
accountsSelectedRows,
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
openAlert,
|
||||||
|
|
||||||
selectedRows = [],
|
|
||||||
onFilterChanged,
|
onFilterChanged,
|
||||||
onBulkDelete,
|
|
||||||
onBulkArchive,
|
|
||||||
onBulkActivate,
|
|
||||||
onBulkInactive,
|
|
||||||
}) {
|
}) {
|
||||||
const [filterCount, setFilterCount] = useState(
|
const [filterCount, setFilterCount] = useState(
|
||||||
accountsTableQuery?.filter_roles?.length || 0,
|
accountsTableQuery?.filter_roles?.length || 0,
|
||||||
@@ -53,10 +57,7 @@ function AccountsActionsBar({
|
|||||||
openDialog('account-form', {});
|
openDialog('account-form', {});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
|
// Filter dropdown.
|
||||||
selectedRows,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const filterDropdown = FilterDropdown({
|
const filterDropdown = FilterDropdown({
|
||||||
fields: resourceFields,
|
fields: resourceFields,
|
||||||
initialConditions: accountsTableQuery.filter_roles,
|
initialConditions: accountsTableQuery.filter_roles,
|
||||||
@@ -74,17 +75,17 @@ function AccountsActionsBar({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleBulkDelete = useCallback(() => {
|
const handleBulkDelete = () => {
|
||||||
onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
|
openAlert('accounts-bulk-delete', { accountsIds: accountsSelectedRows });
|
||||||
}, [onBulkDelete, selectedRows]);
|
};
|
||||||
|
|
||||||
const handelBulkActivate = useCallback(() => {
|
const handelBulkActivate = () => {
|
||||||
onBulkActivate && onBulkActivate(selectedRows.map((r) => r.id));
|
openAlert('accounts-bulk-activate', { accountsIds: accountsSelectedRows });
|
||||||
}, [onBulkActivate, selectedRows]);
|
};
|
||||||
|
|
||||||
const handelBulkInactive = useCallback(() => {
|
const handelBulkInactive = () => {
|
||||||
onBulkInactive && onBulkInactive(selectedRows.map((r) => r.id));
|
openAlert('accounts-bulk-inactivate', { accountsIds: accountsSelectedRows });
|
||||||
}, [onBulkInactive, selectedRows]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
@@ -113,7 +114,7 @@ function AccountsActionsBar({
|
|||||||
'has-active-filters': filterCount > 0,
|
'has-active-filters': filterCount > 0,
|
||||||
})}
|
})}
|
||||||
text={
|
text={
|
||||||
filterCount <= 0 ? (
|
(filterCount <= 0) ? (
|
||||||
<T id={'filter'} />
|
<T id={'filter'} />
|
||||||
) : (
|
) : (
|
||||||
<T
|
<T
|
||||||
@@ -126,7 +127,7 @@ function AccountsActionsBar({
|
|||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<If condition={hasSelectedRows}>
|
<If condition={accountsSelectedRows.length}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon="play-16" iconSize={16} />}
|
icon={<Icon icon="play-16" iconSize={16} />}
|
||||||
@@ -168,21 +169,30 @@ function AccountsActionsBar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Momerize the component.
|
||||||
|
const AccountsActionsBarMemo = memo(AccountsActionsBar);
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
resourceName: 'accounts',
|
resourceName: 'accounts',
|
||||||
});
|
});
|
||||||
|
|
||||||
const withAccountsActionsBar = connect(mapStateToProps);
|
const withAccountsActionsBar = connect(mapStateToProps);
|
||||||
|
|
||||||
export default compose(
|
const comp = compose(
|
||||||
withAccountsActionsBar,
|
withAccountsActionsBar,
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
withAccounts(({ accountsViews, accountsTableQuery }) => ({
|
withAccounts(
|
||||||
|
({ accountsSelectedRows, accountsViews, accountsTableQuery }) => ({
|
||||||
accountsViews,
|
accountsViews,
|
||||||
accountsTableQuery,
|
accountsTableQuery,
|
||||||
})),
|
accountsSelectedRows,
|
||||||
|
}),
|
||||||
|
),
|
||||||
withResourceDetail(({ resourceFields }) => ({
|
withResourceDetail(({ resourceFields }) => ({
|
||||||
resourceFields,
|
resourceFields,
|
||||||
})),
|
})),
|
||||||
withAccountsTableActions,
|
withAccountsTableActions,
|
||||||
)(AccountsActionsBar);
|
withAlertActions
|
||||||
|
)(AccountsActionsBarMemo);
|
||||||
|
|
||||||
|
export default comp;
|
||||||
|
|||||||
26
client/src/containers/Accounts/AccountsAlerts.js
Normal file
26
client/src/containers/Accounts/AccountsAlerts.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import AccountDeleteAlert from 'containers/Alerts/AccountDeleteAlert';
|
||||||
|
import AccountInactivateAlert from 'containers/Alerts/AccountInactivateAlert';
|
||||||
|
import AccountActivateAlert from 'containers/Alerts/AccountActivateAlert';
|
||||||
|
import AccountBulkDeleteAlert from 'containers/Alerts/AccountBulkDeleteAlert';
|
||||||
|
import AccountBulkInactivateAlert from 'containers/Alerts/AccountBulkInactivateAlert';
|
||||||
|
import AccountBulkActivateAlert from 'containers/Alerts/AccountBulkActivateAlert';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts alert.
|
||||||
|
*/
|
||||||
|
export default function AccountsAlerts({
|
||||||
|
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div class="accounts-alerts">
|
||||||
|
<AccountDeleteAlert name={'account-delete'} />
|
||||||
|
<AccountInactivateAlert name={'account-inactivate'} />
|
||||||
|
<AccountActivateAlert name={'account-activate'} />
|
||||||
|
|
||||||
|
<AccountBulkDeleteAlert name={'accounts-bulk-delete'} />
|
||||||
|
<AccountBulkInactivateAlert name={'accounts-bulk-inactivate'} />
|
||||||
|
<AccountBulkActivateAlert name={'accounts-bulk-activate'} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,20 +1,17 @@
|
|||||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { Route, Switch } from 'react-router-dom';
|
||||||
import { Alert, Intent } from '@blueprintjs/core';
|
import { useQuery } from 'react-query';
|
||||||
import { useQuery, queryCache } from 'react-query';
|
|
||||||
import {
|
import {
|
||||||
FormattedMessage as T,
|
|
||||||
FormattedHTMLMessage,
|
|
||||||
useIntl,
|
useIntl,
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
|
|
||||||
import AppToaster from 'components/AppToaster';
|
import 'style/pages/Accounts/List.scss';
|
||||||
|
|
||||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
|
import AccountsViewPage from 'containers/Accounts/AccountsViewPage';
|
||||||
import AccountsDataTable from 'containers/Accounts/AccountsDataTable';
|
import AccountsActionsBar from 'containers/Accounts/AccountsActionsBar';
|
||||||
import DashboardActionsBar from 'containers/Accounts/AccountsActionsBar';
|
import AccountsAlerts from './AccountsAlerts';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
import withResourceActions from 'containers/Resources/withResourcesActions';
|
||||||
@@ -25,8 +22,6 @@ import withAccounts from 'containers/Accounts/withAccounts';
|
|||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
import 'style/pages/Accounts/List.scss';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts chart list.
|
* Accounts chart list.
|
||||||
*/
|
*/
|
||||||
@@ -34,11 +29,6 @@ function AccountsChart({
|
|||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
|
|
||||||
// #withAccountsActions
|
|
||||||
requestDeleteAccount,
|
|
||||||
requestInactiveAccount,
|
|
||||||
requestActivateAccount,
|
|
||||||
|
|
||||||
// #withViewsActions
|
// #withViewsActions
|
||||||
requestFetchResourceViews,
|
requestFetchResourceViews,
|
||||||
|
|
||||||
@@ -47,35 +37,23 @@ function AccountsChart({
|
|||||||
|
|
||||||
// #withAccountsTableActions
|
// #withAccountsTableActions
|
||||||
requestFetchAccountsTable,
|
requestFetchAccountsTable,
|
||||||
requestDeleteBulkAccounts,
|
|
||||||
addAccountsTableQueries,
|
addAccountsTableQueries,
|
||||||
requestBulkActivateAccounts,
|
|
||||||
requestBulkInactiveAccounts,
|
|
||||||
|
|
||||||
// #withAccounts
|
// #withAccounts
|
||||||
accountsTableQuery,
|
accountsTableQuery,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const [deleteAccount, setDeleteAccount] = useState(false);
|
|
||||||
const [inactiveAccount, setInactiveAccount] = useState(false);
|
|
||||||
const [activateAccount, setActivateAccount] = useState(false);
|
|
||||||
const [bulkDelete, setBulkDelete] = useState(false);
|
|
||||||
const [selectedRows, setSelectedRows] = useState([]);
|
|
||||||
const [bulkActivate, setBulkActivate] = useState(false);
|
|
||||||
const [bulkInactiveAccounts, setBulkInactiveAccounts] = useState(false);
|
|
||||||
|
|
||||||
// Fetch accounts resource views and fields.
|
// Fetch accounts resource views and fields.
|
||||||
const fetchResourceViews = useQuery(
|
const fetchResourceViews = useQuery(
|
||||||
['resource-views', 'accounts'],
|
['resource-views', 'accounts'],
|
||||||
(key, resourceName) => requestFetchResourceViews(resourceName),
|
(key, resourceName) => requestFetchResourceViews(resourceName),
|
||||||
);
|
);
|
||||||
|
// Fetch the accounts resource fields.
|
||||||
const fetchResourceFields = useQuery(
|
const fetchResourceFields = useQuery(
|
||||||
['resource-fields', 'accounts'],
|
['resource-fields', 'accounts'],
|
||||||
(key, resourceName) => requestFetchResourceFields(resourceName),
|
(key, resourceName) => requestFetchResourceFields(resourceName),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
// Fetch accounts list according to the given custom view id.
|
||||||
const fetchAccountsHook = useQuery(
|
const fetchAccountsHook = useQuery(
|
||||||
['accounts-table', accountsTableQuery],
|
['accounts-table', accountsTableQuery],
|
||||||
@@ -86,162 +64,10 @@ function AccountsChart({
|
|||||||
changePageTitle(formatMessage({ id: 'chart_of_accounts' }));
|
changePageTitle(formatMessage({ id: 'chart_of_accounts' }));
|
||||||
}, [changePageTitle, formatMessage]);
|
}, [changePageTitle, formatMessage]);
|
||||||
|
|
||||||
// Handle click and cancel/confirm account delete
|
|
||||||
const handleDeleteAccount = (account) => {
|
|
||||||
setDeleteAccount(account);
|
|
||||||
};
|
|
||||||
|
|
||||||
// handle cancel delete account alert.
|
|
||||||
const handleCancelAccountDelete = useCallback(() => {
|
|
||||||
setDeleteAccount(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle delete errors in bulk and singular.
|
|
||||||
const handleDeleteErrors = (errors) => {
|
|
||||||
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'you_could_not_delete_predefined_accounts',
|
|
||||||
}),
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'cannot_delete_account_has_associated_transactions',
|
|
||||||
}),
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle confirm account delete
|
|
||||||
const handleConfirmAccountDelete = useCallback(() => {
|
|
||||||
requestDeleteAccount(deleteAccount.id)
|
|
||||||
.then(() => {
|
|
||||||
setDeleteAccount(false);
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_account_has_been_successfully_deleted',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
})
|
|
||||||
.catch((errors) => {
|
|
||||||
setDeleteAccount(false);
|
|
||||||
handleDeleteErrors(errors);
|
|
||||||
});
|
|
||||||
}, [deleteAccount, requestDeleteAccount, formatMessage]);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_account_has_been_successfully_inactivated',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setInactiveAccount(false);
|
|
||||||
});
|
|
||||||
}, [inactiveAccount, requestInactiveAccount, formatMessage]);
|
|
||||||
|
|
||||||
// Handle activate account click.
|
|
||||||
const handleActivateAccount = useCallback((account) => {
|
|
||||||
setActivateAccount(account);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle activate account alert cancel.
|
|
||||||
const handleCancelActivateAccount = useCallback(() => {
|
|
||||||
setActivateAccount(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle activate account confirm.
|
|
||||||
const handleConfirmAccountActivate = useCallback(() => {
|
|
||||||
requestActivateAccount(activateAccount.id)
|
|
||||||
.then(() => {
|
|
||||||
setActivateAccount(false);
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_account_has_been_successfully_activated',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setActivateAccount(false);
|
|
||||||
});
|
|
||||||
}, [activateAccount, requestActivateAccount, formatMessage]);
|
|
||||||
|
|
||||||
const handleRestoreAccount = (account) => {};
|
|
||||||
|
|
||||||
// Handle accounts bulk delete button click.,
|
|
||||||
const handleBulkDelete = useCallback(
|
|
||||||
(accountsIds) => {
|
|
||||||
setBulkDelete(accountsIds);
|
|
||||||
},
|
|
||||||
[setBulkDelete],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle confirm accounts bulk delete.
|
|
||||||
const handleConfirmBulkDelete = useCallback(() => {
|
|
||||||
requestDeleteBulkAccounts(bulkDelete)
|
|
||||||
.then(() => {
|
|
||||||
setBulkDelete(false);
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_accounts_has_been_successfully_deleted',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
})
|
|
||||||
.catch((errors) => {
|
|
||||||
setBulkDelete(false);
|
|
||||||
handleDeleteErrors(errors);
|
|
||||||
});
|
|
||||||
}, [requestDeleteBulkAccounts, bulkDelete, formatMessage]);
|
|
||||||
|
|
||||||
// Handle cancel accounts bulk delete.
|
|
||||||
const handleCancelBulkDelete = useCallback(() => {
|
|
||||||
setBulkDelete(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleBulkArchive = useCallback((accounts) => {}, []);
|
|
||||||
|
|
||||||
const handleEditAccount = useCallback(() => {}, []);
|
|
||||||
|
|
||||||
// Handle selected rows change.
|
|
||||||
const handleSelectedRowsChange = useCallback(
|
|
||||||
(accounts) => {
|
|
||||||
setSelectedRows(accounts);
|
|
||||||
},
|
|
||||||
[setSelectedRows],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Refetches accounts data table when current custom view changed.
|
// Refetches accounts data table when current custom view changed.
|
||||||
const handleFilterChanged = useCallback(() => {
|
const handleFilterChanged = useCallback(() => {
|
||||||
fetchAccountsHook.refetch();
|
|
||||||
}, [fetchAccountsHook]);
|
}, []);
|
||||||
|
|
||||||
// Handle fetch data of accounts datatable.
|
// Handle fetch data of accounts datatable.
|
||||||
const handleFetchData = useCallback(
|
const handleFetchData = useCallback(
|
||||||
@@ -255,198 +81,22 @@ function AccountsChart({
|
|||||||
: {}),
|
: {}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[fetchAccountsHook, addAccountsTableQueries],
|
[addAccountsTableQueries],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calculates the data table selected rows count.
|
|
||||||
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
|
|
||||||
selectedRows,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Handle bulk Activate accounts button click.,
|
|
||||||
const handleBulkActivate = useCallback(
|
|
||||||
(bulkActivateIds) => {
|
|
||||||
setBulkActivate(bulkActivateIds);
|
|
||||||
},
|
|
||||||
[setBulkActivate],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle cancel Bulk Activate accounts bulk delete.
|
|
||||||
const handleCancelBulkActivate = useCallback(() => {
|
|
||||||
setBulkActivate(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle Bulk activate account confirm.
|
|
||||||
const handleConfirmBulkActivate = useCallback(() => {
|
|
||||||
requestBulkActivateAccounts(bulkActivate)
|
|
||||||
.then(() => {
|
|
||||||
setBulkActivate(false);
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_accounts_has_been_successfully_activated',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
})
|
|
||||||
.catch((errors) => {
|
|
||||||
setBulkActivate(false);
|
|
||||||
});
|
|
||||||
}, [requestBulkActivateAccounts, bulkActivate, formatMessage]);
|
|
||||||
|
|
||||||
// Handle bulk Inactive accounts button click.,
|
|
||||||
const handleBulkInactive = useCallback(
|
|
||||||
(bulkInactiveIds) => {
|
|
||||||
setBulkInactiveAccounts(bulkInactiveIds);
|
|
||||||
},
|
|
||||||
[setBulkInactiveAccounts],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle cancel Bulk Inactive accounts bulk delete.
|
|
||||||
const handleCancelBulkInactive = useCallback(() => {
|
|
||||||
setBulkInactiveAccounts(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle Bulk Inactive accounts confirm.
|
|
||||||
const handleConfirmBulkInactive = useCallback(() => {
|
|
||||||
requestBulkInactiveAccounts(bulkInactiveAccounts)
|
|
||||||
.then(() => {
|
|
||||||
setBulkInactiveAccounts(false);
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_accounts_have_been_successfully_inactivated',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
queryCache.invalidateQueries('accounts-table');
|
|
||||||
})
|
|
||||||
.catch((errors) => {
|
|
||||||
setBulkInactiveAccounts(false);
|
|
||||||
});
|
|
||||||
}, [requestBulkInactiveAccounts, bulkInactiveAccounts]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={fetchResourceFields.isFetching || fetchResourceViews.isFetching}
|
loading={fetchResourceFields.isFetching || fetchResourceViews.isFetching}
|
||||||
name={'accounts-chart'}
|
name={'accounts-chart'}
|
||||||
>
|
>
|
||||||
<DashboardActionsBar
|
<AccountsActionsBar
|
||||||
selectedRows={selectedRows}
|
|
||||||
onFilterChanged={handleFilterChanged}
|
onFilterChanged={handleFilterChanged}
|
||||||
onBulkDelete={handleBulkDelete}
|
|
||||||
onBulkArchive={handleBulkArchive}
|
|
||||||
onBulkActivate={handleBulkActivate}
|
|
||||||
onBulkInactive={handleBulkInactive}
|
|
||||||
/>
|
/>
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<Switch>
|
<AccountsViewPage />
|
||||||
<Route
|
|
||||||
exact={true}
|
|
||||||
path={['/accounts/:custom_view_id/custom_view', '/accounts']}
|
|
||||||
>
|
|
||||||
<AccountsViewsTabs />
|
|
||||||
|
|
||||||
<AccountsDataTable
|
|
||||||
onDeleteAccount={handleDeleteAccount}
|
|
||||||
onInactiveAccount={handleInactiveAccount}
|
|
||||||
onActivateAccount={handleActivateAccount}
|
|
||||||
onRestoreAccount={handleRestoreAccount}
|
|
||||||
onEditAccount={handleEditAccount}
|
|
||||||
onFetchData={handleFetchData}
|
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
|
|
||||||
<Alert
|
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
|
||||||
confirmButtonText={<T id={'delete'} />}
|
|
||||||
icon="trash"
|
|
||||||
intent={Intent.DANGER}
|
|
||||||
isOpen={deleteAccount}
|
|
||||||
onCancel={handleCancelAccountDelete}
|
|
||||||
onConfirm={handleConfirmAccountDelete}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<FormattedHTMLMessage
|
|
||||||
id={'once_delete_this_account_you_will_able_to_restore_it'}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Alert
|
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
|
||||||
confirmButtonText={<T id={'inactivate'} />}
|
|
||||||
intent={Intent.WARNING}
|
|
||||||
isOpen={inactiveAccount}
|
|
||||||
onCancel={handleCancelInactiveAccount}
|
|
||||||
onConfirm={handleConfirmAccountActive}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<T id={'are_sure_to_inactive_this_account'} />
|
|
||||||
</p>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Alert
|
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
|
||||||
confirmButtonText={<T id={'activate'} />}
|
|
||||||
intent={Intent.WARNING}
|
|
||||||
isOpen={activateAccount}
|
|
||||||
onCancel={handleCancelActivateAccount}
|
|
||||||
onConfirm={handleConfirmAccountActivate}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<T id={'are_sure_to_activate_this_account'} />
|
|
||||||
</p>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Alert
|
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
|
||||||
confirmButtonText={`${formatMessage({
|
|
||||||
id: 'delete',
|
|
||||||
})} (${selectedRowsCount})`}
|
|
||||||
icon="trash"
|
|
||||||
intent={Intent.DANGER}
|
|
||||||
isOpen={bulkDelete}
|
|
||||||
onCancel={handleCancelBulkDelete}
|
|
||||||
onConfirm={handleConfirmBulkDelete}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<T
|
|
||||||
id={'once_delete_these_accounts_you_will_not_able_restore_them'}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Alert
|
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
|
||||||
confirmButtonText={`${formatMessage({
|
|
||||||
id: 'activate',
|
|
||||||
})} (${selectedRowsCount})`}
|
|
||||||
intent={Intent.WARNING}
|
|
||||||
isOpen={bulkActivate}
|
|
||||||
onCancel={handleCancelBulkActivate}
|
|
||||||
onConfirm={handleConfirmBulkActivate}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<T id={'are_sure_to_activate_this_accounts'} />
|
|
||||||
</p>
|
|
||||||
</Alert>
|
|
||||||
<Alert
|
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
|
||||||
confirmButtonText={`${formatMessage({
|
|
||||||
id: 'inactivate',
|
|
||||||
})} (${selectedRowsCount})`}
|
|
||||||
intent={Intent.WARNING}
|
|
||||||
isOpen={bulkInactiveAccounts}
|
|
||||||
onCancel={handleCancelBulkInactive}
|
|
||||||
onConfirm={handleConfirmBulkInactive}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
<T id={'are_sure_to_inactive_this_accounts'} />
|
|
||||||
</p>
|
|
||||||
</Alert>
|
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
|
|
||||||
|
<AccountsAlerts />
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,47 +2,45 @@ import React, { useCallback, useState, useMemo, useEffect } from 'react';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Popover,
|
Popover,
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
MenuDivider,
|
|
||||||
Position,
|
Position,
|
||||||
Intent,
|
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Icon, DataTable, If } from 'components';
|
import { Icon, DataTable, If } from 'components';
|
||||||
import { compose } from 'utils';
|
import { saveInvoke, compose } from 'utils';
|
||||||
import { useUpdateEffect } from 'hooks';
|
import { useUpdateEffect } from 'hooks';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import {
|
|
||||||
NormalCell,
|
import { NormalCell, BalanceCell, AccountActionsMenuList } from './components';
|
||||||
BalanceCell,
|
import { TableFastCell } from 'components';
|
||||||
} from './components';
|
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
|
||||||
import withCurrentView from 'containers/Views/withCurrentView';
|
import withCurrentView from 'containers/Views/withCurrentView';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts data-table.
|
||||||
|
*/
|
||||||
function AccountsDataTable({
|
function AccountsDataTable({
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
accountsTable,
|
accountsTable,
|
||||||
accountsLoading,
|
accountsLoading,
|
||||||
|
|
||||||
// #withDialog.
|
// #
|
||||||
openDialog,
|
|
||||||
|
|
||||||
currentViewId,
|
currentViewId,
|
||||||
|
|
||||||
// own properties
|
// #ownProps
|
||||||
onFetchData,
|
onFetchData,
|
||||||
onSelectedRowsChange,
|
onSelectedRowsChange,
|
||||||
onDeleteAccount,
|
onDeleteAccount,
|
||||||
onInactiveAccount,
|
onInactivateAccount,
|
||||||
onActivateAccount,
|
onActivateAccount,
|
||||||
|
onEditAccount,
|
||||||
|
onNewChildAccount
|
||||||
}) {
|
}) {
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
@@ -57,77 +55,44 @@ function AccountsDataTable({
|
|||||||
}
|
}
|
||||||
}, [accountsLoading, setIsMounted]);
|
}, [accountsLoading, setIsMounted]);
|
||||||
|
|
||||||
const handleEditAccount = useCallback(
|
|
||||||
(account) => () => {
|
|
||||||
openDialog('account-form', { action: 'edit', id: account.id });
|
|
||||||
},
|
|
||||||
[openDialog],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNewParentAccount = useCallback(
|
const ActionsCell = useMemo(() =>
|
||||||
(account) => {
|
({ row }) => (
|
||||||
openDialog('account-form', {
|
<Popover
|
||||||
action: 'new_child',
|
content={<AccountActionsMenuList
|
||||||
parentAccountId: account.id,
|
account={row.original}
|
||||||
accountTypeId: account.account_type_id,
|
onDeleteAccount={onDeleteAccount}
|
||||||
});
|
onInactivateAccount={onInactivateAccount}
|
||||||
},
|
onActivateAccount={onActivateAccount}
|
||||||
[openDialog],
|
onEditAccount={onEditAccount}
|
||||||
);
|
/>}
|
||||||
|
position={Position.RIGHT_TOP}
|
||||||
const actionMenuList = useCallback(
|
>
|
||||||
(account) => (
|
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||||
<Menu>
|
</Popover>
|
||||||
<MenuItem
|
), [
|
||||||
icon={<Icon icon="reader-18" />}
|
|
||||||
text={formatMessage({ id: 'view_details' })}
|
|
||||||
/>
|
|
||||||
<MenuDivider />
|
|
||||||
<MenuItem
|
|
||||||
icon={<Icon icon="pen-18" />}
|
|
||||||
text={formatMessage({ id: 'edit_account' })}
|
|
||||||
onClick={handleEditAccount(account)}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
icon={<Icon icon="plus" />}
|
|
||||||
text={formatMessage({ id: 'new_child_account' })}
|
|
||||||
onClick={() => handleNewParentAccount(account)}
|
|
||||||
/>
|
|
||||||
<MenuDivider />
|
|
||||||
<If condition={account.active}>
|
|
||||||
<MenuItem
|
|
||||||
text={formatMessage({ id: 'inactivate_account' })}
|
|
||||||
icon={<Icon icon="pause-16" iconSize={16} />}
|
|
||||||
onClick={() => onInactiveAccount(account)}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
<If condition={!account.active}>
|
|
||||||
<MenuItem
|
|
||||||
text={formatMessage({ id: 'activate_account' })}
|
|
||||||
icon={<Icon icon="play-16" iconSize={16} />}
|
|
||||||
onClick={() => onActivateAccount(account)}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
<MenuItem
|
|
||||||
text={formatMessage({ id: 'delete_account' })}
|
|
||||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
|
||||||
intent={Intent.DANGER}
|
|
||||||
onClick={() => onDeleteAccount(account)}
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
),
|
|
||||||
[
|
|
||||||
handleEditAccount,
|
|
||||||
onDeleteAccount,
|
onDeleteAccount,
|
||||||
onInactiveAccount,
|
onInactivateAccount,
|
||||||
handleNewParentAccount,
|
onActivateAccount,
|
||||||
formatMessage,
|
onEditAccount
|
||||||
],
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
const rowContextMenu = (cell) => {
|
const RowContextMenu = useMemo(() => ({ row }) => (
|
||||||
return actionMenuList(cell.row.original);
|
<AccountActionsMenuList
|
||||||
};
|
account={row.original}
|
||||||
|
onDeleteAccount={onDeleteAccount}
|
||||||
|
onInactivateAccount={onInactivateAccount}
|
||||||
|
onActivateAccount={onActivateAccount}
|
||||||
|
onEditAccount={onEditAccount}
|
||||||
|
onNewChildAccount={onNewChildAccount}
|
||||||
|
/>
|
||||||
|
), [
|
||||||
|
onDeleteAccount,
|
||||||
|
onInactivateAccount,
|
||||||
|
onActivateAccount,
|
||||||
|
onEditAccount,
|
||||||
|
onNewChildAccount
|
||||||
|
]);
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -143,7 +108,7 @@ function AccountsDataTable({
|
|||||||
Header: formatMessage({ id: 'code' }),
|
Header: formatMessage({ id: 'code' }),
|
||||||
accessor: 'code',
|
accessor: 'code',
|
||||||
className: 'code',
|
className: 'code',
|
||||||
width: 70,
|
width: 80,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'type',
|
id: 'type',
|
||||||
@@ -158,7 +123,7 @@ function AccountsDataTable({
|
|||||||
Cell: NormalCell,
|
Cell: NormalCell,
|
||||||
accessor: 'account_normal',
|
accessor: 'account_normal',
|
||||||
className: 'normal',
|
className: 'normal',
|
||||||
width: 65,
|
width: 80,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'currency',
|
id: 'currency',
|
||||||
@@ -176,40 +141,31 @@ function AccountsDataTable({
|
|||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
Header: '',
|
Header: '',
|
||||||
// Cell: ({ cell }) => (
|
Cell: ActionsCell,
|
||||||
// <Popover
|
|
||||||
// content={actionMenuList(cell.row.original)}
|
|
||||||
// position={Position.RIGHT_TOP}
|
|
||||||
// >
|
|
||||||
// <Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
|
||||||
// </Popover>
|
|
||||||
// ),
|
|
||||||
className: 'actions',
|
className: 'actions',
|
||||||
width: 50,
|
width: 50,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[actionMenuList, formatMessage],
|
[ActionsCell, formatMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDatatableFetchData = useCallback(
|
||||||
|
(...params) => {
|
||||||
const handleDatatableFetchData = useCallback((...params) => {
|
saveInvoke(onFetchData, params);
|
||||||
onFetchData && onFetchData(...params);
|
},
|
||||||
}, []);
|
[onFetchData],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSelectedRowsChange = useCallback(
|
const handleSelectedRowsChange = useCallback(
|
||||||
(selectedRows) => {
|
(selectedRows) => {
|
||||||
onSelectedRowsChange &&
|
saveInvoke(onSelectedRowsChange, selectedRows);
|
||||||
onSelectedRowsChange(selectedRows.map((s) => s.original));
|
|
||||||
},
|
},
|
||||||
[onSelectedRowsChange],
|
[onSelectedRowsChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const rowClassNames = (row) => {
|
const rowClassNames = (row) => ({
|
||||||
return {
|
inactive: !row.original.active,
|
||||||
'inactive': !row.original.active,
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
||||||
@@ -223,14 +179,19 @@ function AccountsDataTable({
|
|||||||
sticky={true}
|
sticky={true}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
loading={accountsLoading && !isMounted}
|
loading={accountsLoading && !isMounted}
|
||||||
rowContextMenu={rowContextMenu}
|
rowContextMenu={RowContextMenu}
|
||||||
rowClassNames={rowClassNames}
|
rowClassNames={rowClassNames}
|
||||||
expandColumnSpace={1}
|
|
||||||
autoResetExpanded={false}
|
autoResetExpanded={false}
|
||||||
autoResetSortBy={false}
|
autoResetSortBy={false}
|
||||||
|
autoResetSelectedRows={false}
|
||||||
|
expandColumnSpace={1}
|
||||||
|
expandToggleColumn={2}
|
||||||
selectionColumnWidth={50}
|
selectionColumnWidth={50}
|
||||||
virtualizedRows={true}
|
TableCellRenderer={TableFastCell}
|
||||||
fixedSizeHeight={1000}
|
TableRowsRenderer={TableVirtualizedListRows}
|
||||||
|
// #TableVirtualizedListRows props.
|
||||||
|
vListrowHeight={42}
|
||||||
|
vListOverscanRowCount={10}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -239,7 +200,6 @@ function AccountsDataTable({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withRouter,
|
withRouter,
|
||||||
withCurrentView,
|
withCurrentView,
|
||||||
withDialogActions,
|
|
||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
withAccountsActions,
|
withAccountsActions,
|
||||||
withAccounts(({ accountsLoading, accountsTable }) => ({
|
withAccounts(({ accountsLoading, accountsTable }) => ({
|
||||||
|
|||||||
84
client/src/containers/Accounts/AccountsViewPage.js
Normal file
84
client/src/containers/Accounts/AccountsViewPage.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import { Switch, Route } from 'react-router-dom';
|
||||||
|
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
|
||||||
|
import AccountsDataTable from 'containers/Accounts/AccountsDataTable';
|
||||||
|
|
||||||
|
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
|
||||||
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts view page.
|
||||||
|
*/
|
||||||
|
function AccountsViewPage({
|
||||||
|
openAlert,
|
||||||
|
|
||||||
|
// #withDialog.
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withAccountsTableActions
|
||||||
|
setSelectedRowsAccounts
|
||||||
|
}) {
|
||||||
|
// Handle delete action account.
|
||||||
|
const handleDeleteAccount = (account) => {
|
||||||
|
openAlert('account-delete', { accountId: account.id })
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle activate action account.
|
||||||
|
const handleActivateAccount = (account) => {
|
||||||
|
openAlert('account-activate', { accountId: account.id });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle inactivate action account.
|
||||||
|
const handleInactivateAccount = (account) => {
|
||||||
|
openAlert('account-inactivate', { accountId: account.id });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle select accounts datatable rows.
|
||||||
|
const handleSelectedRowsChange = (selectedRows) => {
|
||||||
|
const selectedRowsIds = selectedRows.map(r => r.id);
|
||||||
|
setSelectedRowsAccounts(selectedRowsIds);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditAccount = (account) => {
|
||||||
|
openDialog('account-form', { action: 'edit', id: account.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNewChildAccount = (account) => {
|
||||||
|
openDialog('account-form', {
|
||||||
|
action: 'new_child',
|
||||||
|
parentAccountId: account.id,
|
||||||
|
accountType: account.account_type,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
exact={true}
|
||||||
|
path={['/accounts/:custom_view_id/custom_view', '/accounts']}
|
||||||
|
>
|
||||||
|
<AccountsViewsTabs />
|
||||||
|
|
||||||
|
<AccountsDataTable
|
||||||
|
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);
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useMemo, memo, useCallback } from 'react';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
||||||
import { useParams, withRouter } from 'react-router-dom';
|
import { useParams, withRouter } from 'react-router-dom';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
import { useUpdateEffect } from 'hooks';
|
|
||||||
import { DashboardViewsTabs } from 'components';
|
import { DashboardViewsTabs } from 'components';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
@@ -14,6 +13,9 @@ import withViewDetail from 'containers/Views/withViewDetails';
|
|||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts views tabs.
|
||||||
|
*/
|
||||||
function AccountsViewsTabs({
|
function AccountsViewsTabs({
|
||||||
// #withViewDetail
|
// #withViewDetail
|
||||||
viewId,
|
viewId,
|
||||||
@@ -23,16 +25,11 @@ function AccountsViewsTabs({
|
|||||||
accountsViews,
|
accountsViews,
|
||||||
|
|
||||||
// #withAccountsTableActions
|
// #withAccountsTableActions
|
||||||
addAccountsTableQueries,
|
|
||||||
changeAccountsCurrentView,
|
changeAccountsCurrentView,
|
||||||
|
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
setTopbarEditView,
|
setTopbarEditView,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
|
|
||||||
// props
|
|
||||||
customViewChanged,
|
|
||||||
onViewChanged,
|
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { custom_view_id: customViewId = null } = useParams();
|
const { custom_view_id: customViewId = null } = useParams();
|
||||||
@@ -40,28 +37,27 @@ function AccountsViewsTabs({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTopbarEditView(customViewId);
|
setTopbarEditView(customViewId);
|
||||||
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
||||||
}, [customViewId]);
|
}, [customViewId, viewItem, changePageSubtitle, setTopbarEditView]);
|
||||||
|
|
||||||
// Handle click a new view tab.
|
// Handle click a new view tab.
|
||||||
const handleClickNewView = () => {
|
const handleClickNewView = useCallback(() => {
|
||||||
setTopbarEditView(null);
|
setTopbarEditView(null);
|
||||||
history.push('/custom_views/accounts/new');
|
history.push('/custom_views/accounts/new');
|
||||||
};
|
}, [setTopbarEditView]);
|
||||||
|
|
||||||
const handleTabChange = (viewId) => {
|
const handleTabChange = useCallback((viewId) => {
|
||||||
changeAccountsCurrentView(viewId || -1);
|
changeAccountsCurrentView(viewId || -1);
|
||||||
// addAccountsTableQueries({
|
}, [changeAccountsCurrentView]);
|
||||||
// custom_view_id: viewId || null,
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabs = accountsViews.map((view) => ({
|
const tabs = useMemo(() => accountsViews.map((view) => ({
|
||||||
...pick(view, ['name', 'id']),
|
...pick(view, ['name', 'id']),
|
||||||
}));
|
})), [accountsViews]);;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar className="navbar--dashboard-views">
|
<Navbar className="navbar--dashboard-views">
|
||||||
<NavbarGroup align={Alignment.LEFT}>
|
<NavbarGroup align={Alignment.LEFT}>
|
||||||
<DashboardViewsTabs
|
<DashboardViewsTabs
|
||||||
|
defaultTabText={'All Accounts'}
|
||||||
initialViewId={customViewId}
|
initialViewId={customViewId}
|
||||||
resourceName={'accounts'}
|
resourceName={'accounts'}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
@@ -72,8 +68,10 @@ function AccountsViewsTabs({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AccountsViewsTabsMemo = memo(AccountsViewsTabs);
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
viewId: ownProps.match.params.custom_view_id,
|
viewId: -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const withAccountsViewsTabs = connect(mapStateToProps);
|
const withAccountsViewsTabs = connect(mapStateToProps);
|
||||||
@@ -87,4 +85,4 @@ export default compose(
|
|||||||
})),
|
})),
|
||||||
withAccountsTableActions,
|
withAccountsTableActions,
|
||||||
withViewDetail(),
|
withViewDetail(),
|
||||||
)(AccountsViewsTabs);
|
)(AccountsViewsTabsMemo);
|
||||||
|
|||||||
@@ -3,15 +3,76 @@ import {
|
|||||||
Position,
|
Position,
|
||||||
Classes,
|
Classes,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
MenuItem,
|
||||||
|
Menu,
|
||||||
|
MenuDivider,
|
||||||
|
Intent
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Icon, Money, If, Choose } from 'components';
|
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,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="reader-18" />}
|
||||||
|
text={formatMessage({ id: 'view_details' })}
|
||||||
|
/>
|
||||||
|
<MenuDivider />
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="pen-18" />}
|
||||||
|
text={formatMessage({ id: 'edit_account' })}
|
||||||
|
onClick={() => saveInvoke(onEditAccount, account)}
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="plus" />}
|
||||||
|
text={formatMessage({ id: 'new_child_account' })}
|
||||||
|
onClick={() => saveInvoke(onNewChildAccount, account)}
|
||||||
|
/>
|
||||||
|
<MenuDivider />
|
||||||
|
<If condition={account.active}>
|
||||||
|
<MenuItem
|
||||||
|
text={formatMessage({ id: 'inactivate_account' })}
|
||||||
|
icon={<Icon icon="pause-16" iconSize={16} />}
|
||||||
|
onClick={() => saveInvoke(onInactivateAccount, account)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
<If condition={!account.active}>
|
||||||
|
<MenuItem
|
||||||
|
text={formatMessage({ id: 'activate_account' })}
|
||||||
|
icon={<Icon icon="play-16" iconSize={16} />}
|
||||||
|
onClick={() => saveInvoke(onActivateAccount, account)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
<MenuItem
|
||||||
|
text={formatMessage({ id: 'delete_account' })}
|
||||||
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={() => saveInvoke(onDeleteAccount, account)}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function NormalCell({ cell: { value } }) {
|
export function NormalCell({ cell: { value } }) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const arrowDirection = value === 'credit' ? 'down' : 'up';
|
const arrowDirection = value === 'credit' ? 'down' : 'up';
|
||||||
|
|
||||||
|
// if (value !== 'credit' || value !== 'debit') {
|
||||||
|
// return '';
|
||||||
|
// }
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className={Classes.TOOLTIP_INDICATOR}
|
className={Classes.TOOLTIP_INDICATOR}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { If } from 'components';
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { If, AppToaster } from 'components';
|
||||||
|
import { formatMessage } from 'services/intl';
|
||||||
|
|
||||||
export const accountNameAccessor = (account) => {
|
export const accountNameAccessor = (account) => {
|
||||||
return (
|
return (
|
||||||
@@ -11,3 +13,23 @@ export const accountNameAccessor = (account) => {
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle delete errors in bulk and singular.
|
||||||
|
export const handleDeleteErrors = (errors) => {
|
||||||
|
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'you_could_not_delete_predefined_accounts',
|
||||||
|
}),
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'cannot_delete_account_has_associated_transactions',
|
||||||
|
}),
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -18,6 +18,7 @@ export default (mapState) => {
|
|||||||
accountsTableQuery: state.accounts.tableQuery,
|
accountsTableQuery: state.accounts.tableQuery,
|
||||||
accountsLoading: state.accounts.loading,
|
accountsLoading: state.accounts.loading,
|
||||||
accountErrors: state.accounts.errors,
|
accountErrors: state.accounts.errors,
|
||||||
|
accountsSelectedRows: state.accounts.selectedRows,
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
import { fetchAccountsTable } from 'store/accounts/accounts.actions';
|
import { fetchAccountsTable, setBulkAction } from 'store/accounts/accounts.actions';
|
||||||
|
|
||||||
const mapActionsToProps = (dispatch) => ({
|
const mapActionsToProps = (dispatch) => ({
|
||||||
requestFetchAccountsTable: (query = {}) =>
|
requestFetchAccountsTable: (query = {}) =>
|
||||||
@@ -21,11 +21,12 @@ const mapActionsToProps = (dispatch) => ({
|
|||||||
type: t.ACCOUNTS_TABLE_QUERIES_ADD,
|
type: t.ACCOUNTS_TABLE_QUERIES_ADD,
|
||||||
queries,
|
queries,
|
||||||
}),
|
}),
|
||||||
setSelectedRowsAccounts: (ids) =>
|
setSelectedRowsAccounts: (selectedRows) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.ACCOUNTS_SELECTED_ROWS_SET,
|
type: t.ACCOUNTS_SELECTED_ROWS_SET,
|
||||||
payload: { ids },
|
payload: { selectedRows },
|
||||||
}),
|
}),
|
||||||
|
setAccountsBulkAction: (actionName) => setBulkAction(actionName),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapActionsToProps);
|
export default connect(null, mapActionsToProps);
|
||||||
|
|||||||
13
client/src/containers/Alert/withAlertActions.js
Normal file
13
client/src/containers/Alert/withAlertActions.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import t from 'store/types';
|
||||||
|
|
||||||
|
export const mapStateToProps = (state, props) => {
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
|
openAlert: (name, payload) => dispatch({ type: t.OPEN_ALERT, name, payload }),
|
||||||
|
closeAlert: (name, payload) => dispatch({ type: t.CLOSE_ALERT, name, payload }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps);
|
||||||
19
client/src/containers/Alert/withAlertStoreConnect.js
Normal file
19
client/src/containers/Alert/withAlertStoreConnect.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
isAlertOpenFactory,
|
||||||
|
getAlertPayloadFactory,
|
||||||
|
} from 'store/dashboard/dashboard.selectors';
|
||||||
|
|
||||||
|
export default (mapState) => {
|
||||||
|
const isAlertOpen = isAlertOpenFactory();
|
||||||
|
const getAlertPayload = getAlertPayloadFactory();
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => {
|
||||||
|
const mapped = {
|
||||||
|
isOpen: isAlertOpen(state, props),
|
||||||
|
payload: getAlertPayload(state, props),
|
||||||
|
};
|
||||||
|
return mapState ? mapState(mapped) : mapped;
|
||||||
|
};
|
||||||
|
return connect(mapStateToProps);
|
||||||
|
}
|
||||||
74
client/src/containers/Alerts/AccountActivateAlert.js
Normal file
74
client/src/containers/Alerts/AccountActivateAlert.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
useIntl,
|
||||||
|
} from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account activate alert.
|
||||||
|
*/
|
||||||
|
function AccountActivateAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { accountId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
|
||||||
|
requestActivateAccount
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
// Handle alert cancel.
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert('account-activate');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle activate account confirm.
|
||||||
|
const handleConfirmAccountActivate = () => {
|
||||||
|
requestActivateAccount(accountId)
|
||||||
|
.then(() => {
|
||||||
|
closeAlert('account-activate');
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_account_has_been_successfully_activated',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('accounts-table');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
closeAlert('account-activate');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'activate'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmAccountActivate}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'are_sure_to_activate_this_account'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
withAccountsActions
|
||||||
|
)(AccountActivateAlert);
|
||||||
76
client/src/containers/Alerts/AccountBulkActivateAlert.js
Normal file
76
client/src/containers/Alerts/AccountBulkActivateAlert.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
FormattedHTMLMessage,
|
||||||
|
useIntl
|
||||||
|
} from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function AccountBulkActivateAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { accountsIds },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
|
||||||
|
requestBulkActivateAccounts
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const selectedRowsCount = 0;
|
||||||
|
|
||||||
|
// Handle alert cancel.
|
||||||
|
const handleClose = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Bulk activate account confirm.
|
||||||
|
const handleConfirmBulkActivate = () => {
|
||||||
|
requestBulkActivateAccounts(accountsIds)
|
||||||
|
.then(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_accounts_has_been_successfully_activated',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('accounts-table');
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={`${formatMessage({
|
||||||
|
id: 'activate',
|
||||||
|
})} (${selectedRowsCount})`}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleClose}
|
||||||
|
onConfirm={handleConfirmBulkActivate}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'are_sure_to_activate_this_accounts'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
withAccountsActions
|
||||||
|
)(AccountBulkActivateAlert);
|
||||||
80
client/src/containers/Alerts/AccountBulkDeleteAlert.js
Normal file
80
client/src/containers/Alerts/AccountBulkDeleteAlert.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
useIntl
|
||||||
|
} from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import { handleDeleteErrors } from 'containers/Accounts/utils';
|
||||||
|
|
||||||
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function AccountBulkDeleteAlert({
|
||||||
|
// #ownProps
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { accountsIds },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
|
||||||
|
// #withAccountsActions
|
||||||
|
requestDeleteBulkAccounts
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const selectedRowsCount = 0;
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
// Handle confirm accounts bulk delete.
|
||||||
|
const handleConfirmBulkDelete = () => {
|
||||||
|
requestDeleteBulkAccounts(accountsIds)
|
||||||
|
.then(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_accounts_has_been_successfully_deleted',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('accounts-table');
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
closeAlert(name);
|
||||||
|
handleDeleteErrors(errors);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={`${formatMessage({
|
||||||
|
id: 'delete',
|
||||||
|
})} (${selectedRowsCount})`}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_accounts_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
withAccountsActions
|
||||||
|
)(AccountBulkDeleteAlert);
|
||||||
71
client/src/containers/Alerts/AccountBulkInactivateAlert.js
Normal file
71
client/src/containers/Alerts/AccountBulkInactivateAlert.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function AccountBulkInactivateAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { accountsIds },
|
||||||
|
|
||||||
|
// #withAccountsActions
|
||||||
|
requestBulkInactiveAccounts,
|
||||||
|
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const selectedRowsCount = 0;
|
||||||
|
|
||||||
|
// Handle alert cancel.
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
// Handle Bulk Inactive accounts confirm.
|
||||||
|
const handleConfirmBulkInactive = () => {
|
||||||
|
requestBulkInactiveAccounts(accountsIds)
|
||||||
|
.then(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_accounts_have_been_successfully_inactivated',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('accounts-table');
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={`${formatMessage({
|
||||||
|
id: 'inactivate',
|
||||||
|
})} (${selectedRowsCount})`}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmBulkInactive}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'are_sure_to_inactive_this_accounts'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
withAccountsActions,
|
||||||
|
)(AccountBulkInactivateAlert);
|
||||||
84
client/src/containers/Alerts/AccountDeleteAlert.js
Normal file
84
client/src/containers/Alerts/AccountDeleteAlert.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
FormattedHTMLMessage,
|
||||||
|
useIntl
|
||||||
|
} from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import { handleDeleteErrors } from 'containers/Accounts/utils';
|
||||||
|
|
||||||
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account delete alerts.
|
||||||
|
*/
|
||||||
|
function AccountDeleteAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { accountId },
|
||||||
|
|
||||||
|
// #withAccountsActions
|
||||||
|
requestDeleteAccount,
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
// handle cancel delete account alert.
|
||||||
|
const handleCancelAccountDelete = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle confirm account delete.
|
||||||
|
const handleConfirmAccountDelete = () => {
|
||||||
|
requestDeleteAccount(accountId)
|
||||||
|
.then(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_account_has_been_successfully_deleted',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('accounts-table');
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
handleDeleteErrors(errors);
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'delete'} />}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelAccountDelete}
|
||||||
|
onConfirm={handleConfirmAccountDelete}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
id={'once_delete_this_account_you_will_able_to_restore_it'}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
withAccountsActions
|
||||||
|
)(AccountDeleteAlert);
|
||||||
71
client/src/containers/Alerts/AccountInactivateAlert.js
Normal file
71
client/src/containers/Alerts/AccountInactivateAlert.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
useIntl,
|
||||||
|
} from 'react-intl';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function AccountInactivateAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { accountId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
|
||||||
|
// #withAccountsActions
|
||||||
|
requestInactiveAccount,
|
||||||
|
|
||||||
|
}) {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
const handleCancelInactiveAccount = () => {
|
||||||
|
closeAlert('account-inactivate');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmAccountActive = () => {
|
||||||
|
requestInactiveAccount(accountId)
|
||||||
|
.then(() => {
|
||||||
|
closeAlert('account-inactivate');
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'the_account_has_been_successfully_inactivated',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('accounts-table');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
closeAlert('account-inactivate');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'inactivate'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelInactiveAccount}
|
||||||
|
onConfirm={handleConfirmAccountActive}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'are_sure_to_inactive_this_account'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
withAccountsActions
|
||||||
|
)(AccountInactivateAlert);
|
||||||
5
client/src/containers/Alerts/index.js
Normal file
5
client/src/containers/Alerts/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import AccountDeleteAlert from './AccountDeleteAlert';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
AccountDeleteAlert,
|
||||||
|
};
|
||||||
@@ -22,7 +22,7 @@ import { transformApiErrors, transformAccountToForm } from './utils';
|
|||||||
import 'style/pages/Accounts/AccountFormDialog.scss';
|
import 'style/pages/Accounts/AccountFormDialog.scss';
|
||||||
|
|
||||||
const defaultInitialValues = {
|
const defaultInitialValues = {
|
||||||
account_type_id: '',
|
account_type: '',
|
||||||
parent_account_id: '',
|
parent_account_id: '',
|
||||||
name: '',
|
name: '',
|
||||||
code: '',
|
code: '',
|
||||||
@@ -51,7 +51,7 @@ function AccountFormDialogContent({
|
|||||||
accountId,
|
accountId,
|
||||||
action,
|
action,
|
||||||
parentAccountId,
|
parentAccountId,
|
||||||
accountTypeId,
|
accountType,
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const isNewMode = !accountId;
|
const isNewMode = !accountId;
|
||||||
@@ -72,7 +72,10 @@ function AccountFormDialogContent({
|
|||||||
const handleSuccess = () => {
|
const handleSuccess = () => {
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
queryCache.invalidateQueries('accounts-table');
|
queryCache.invalidateQueries('accounts-table');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
queryCache.invalidateQueries('accounts-list');
|
queryCache.invalidateQueries('accounts-list');
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage(
|
message: formatMessage(
|
||||||
@@ -116,7 +119,7 @@ function AccountFormDialogContent({
|
|||||||
transformAccountToForm(account, {
|
transformAccountToForm(account, {
|
||||||
action,
|
action,
|
||||||
parentAccountId,
|
parentAccountId,
|
||||||
accountTypeId,
|
accountType,
|
||||||
}),
|
}),
|
||||||
defaultInitialValues,
|
defaultInitialValues,
|
||||||
),
|
),
|
||||||
@@ -158,7 +161,7 @@ function AccountFormDialogContent({
|
|||||||
>
|
>
|
||||||
<AccountFormDialogFields
|
<AccountFormDialogFields
|
||||||
dialogName={dialogName}
|
dialogName={dialogName}
|
||||||
isNewMode={isNewMode}
|
action={action}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
/>
|
/>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { useAutofocus } from 'hooks';
|
|||||||
function AccountFormDialogFields({
|
function AccountFormDialogFields({
|
||||||
// #ownPropscl
|
// #ownPropscl
|
||||||
onClose,
|
onClose,
|
||||||
isNewMode,
|
action,
|
||||||
|
|
||||||
// #withAccounts
|
// #withAccounts
|
||||||
accounts,
|
accounts,
|
||||||
@@ -42,7 +42,7 @@ function AccountFormDialogFields({
|
|||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
<FastField name={'account_type'}>
|
<Field name={'account_type'}>
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'account_type'} />}
|
label={<T id={'account_type'} />}
|
||||||
@@ -59,13 +59,13 @@ function AccountFormDialogFields({
|
|||||||
onTypeSelected={(accountType) => {
|
onTypeSelected={(accountType) => {
|
||||||
form.setFieldValue('account_type', accountType.key);
|
form.setFieldValue('account_type', accountType.key);
|
||||||
}}
|
}}
|
||||||
disabled={!isNewMode}
|
disabled={action === 'edit' || action === 'new_child'}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</FastField>
|
</Field>
|
||||||
|
|
||||||
<FastField name={'name'}>
|
<FastField name={'name'}>
|
||||||
{({ field, meta: { error, touched } }) => (
|
{({ field, meta: { error, touched } }) => (
|
||||||
@@ -126,7 +126,11 @@ function AccountFormDialogFields({
|
|||||||
|
|
||||||
<If condition={values.subaccount}>
|
<If condition={values.subaccount}>
|
||||||
<FastField name={'parent_account_id'}>
|
<FastField name={'parent_account_id'}>
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'parent_account'} />}
|
label={<T id={'parent_account'} />}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -139,11 +143,12 @@ function AccountFormDialogFields({
|
|||||||
<AccountsSelectList
|
<AccountsSelectList
|
||||||
accounts={accounts}
|
accounts={accounts}
|
||||||
onAccountSelected={(account) => {
|
onAccountSelected={(account) => {
|
||||||
form.setFieldValue('parent_account_id', account.id);
|
setFieldValue('parent_account_id', account.id);
|
||||||
}}
|
}}
|
||||||
defaultSelectText={<T id={'select_parent_account'} />}
|
defaultSelectText={<T id={'select_parent_account'} />}
|
||||||
selectedAccountId={value}
|
selectedAccountId={value}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
|
filterByTypes={values.account_type}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
@@ -177,7 +182,7 @@ function AccountFormDialogFields({
|
|||||||
style={{ minWidth: '75px' }}
|
style={{ minWidth: '75px' }}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{!isNewMode ? <T id={'edit'} /> : <T id={'submit'} />}
|
{action === 'edit' ? <T id={'edit'} /> : <T id={'submit'} />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ function AccountFormDialog({
|
|||||||
accountId={payload.id}
|
accountId={payload.id}
|
||||||
action={payload.action}
|
action={payload.action}
|
||||||
parentAccountId={payload.parentAccountId}
|
parentAccountId={payload.parentAccountId}
|
||||||
accountTypeId={payload.accountTypeId}
|
accountType={payload.accountType}
|
||||||
/>
|
/>
|
||||||
</DialogSuspense>
|
</DialogSuspense>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ export const transformApiErrors = (errors) => {
|
|||||||
export const transformAccountToForm = (account, {
|
export const transformAccountToForm = (account, {
|
||||||
action,
|
action,
|
||||||
parentAccountId,
|
parentAccountId,
|
||||||
accountTypeId
|
accountType
|
||||||
}) => {
|
}) => {
|
||||||
return {
|
return {
|
||||||
parent_account_id: action === 'new_child' ? parentAccountId : '',
|
parent_account_id: action === 'new_child' ? parentAccountId : '',
|
||||||
account_type_id: action === 'new_child'? accountTypeId : '',
|
account_type: action === 'new_child'? accountType : '',
|
||||||
subaccount: action === 'new_child' ? true : false,
|
subaccount: action === 'new_child' ? true : false,
|
||||||
...account,
|
...account,
|
||||||
}
|
}
|
||||||
|
|||||||
6
client/src/containers/Router/withRoute.js
Normal file
6
client/src/containers/Router/withRoute.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { connect } from "react-redux";
|
||||||
|
import { withRouter } from "react-router-dom"
|
||||||
|
|
||||||
|
export default (mapState) => {
|
||||||
|
return () => withRouter ;
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { batch } from 'react-redux'
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import ApiService from 'services/ApiService';
|
import ApiService from 'services/ApiService';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
@@ -26,6 +27,7 @@ export const fetchAccountsList = () => {
|
|||||||
|
|
||||||
ApiService.get('accounts', { params: query })
|
ApiService.get('accounts', { params: query })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
batch(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.ACCOUNTS_ITEMS_SET,
|
type: t.ACCOUNTS_ITEMS_SET,
|
||||||
accounts: response.data.accounts,
|
accounts: response.data.accounts,
|
||||||
@@ -36,6 +38,7 @@ export const fetchAccountsList = () => {
|
|||||||
accounts: response.data.accounts,
|
accounts: response.data.accounts,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
resolve(response);
|
resolve(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -62,6 +65,7 @@ export const fetchAccountsTable = ({ query } = {}) => {
|
|||||||
});
|
});
|
||||||
ApiService.get('accounts', { params: { ...pageQuery, ...query } })
|
ApiService.get('accounts', { params: { ...pageQuery, ...query } })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
batch(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.ACCOUNTS_PAGE_SET,
|
type: t.ACCOUNTS_PAGE_SET,
|
||||||
accounts: response.data.accounts,
|
accounts: response.data.accounts,
|
||||||
@@ -75,6 +79,7 @@ export const fetchAccountsTable = ({ query } = {}) => {
|
|||||||
type: t.ACCOUNTS_TABLE_LOADING,
|
type: t.ACCOUNTS_TABLE_LOADING,
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
resolve(response);
|
resolve(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -243,3 +248,11 @@ export const fetchAccount = ({ id }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const setBulkAction = ({ action }) => {
|
||||||
|
return (dispatch) => dispatch({
|
||||||
|
type: t.ACCOUNTS_BULK_ACTION,
|
||||||
|
payload: { action }
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -75,11 +75,6 @@ const accountsReducer = createReducer(initialState, {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.ACCOUNTS_SELECTED_ROWS_SET]: (state, action) => {
|
|
||||||
const { ids } = action.payload;
|
|
||||||
state.selectedRows = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
[t.ACCOUNTS_SET_CURRENT_VIEW]: (state, action) => {
|
[t.ACCOUNTS_SET_CURRENT_VIEW]: (state, action) => {
|
||||||
state.currentViewId = action.currentViewId;
|
state.currentViewId = action.currentViewId;
|
||||||
},
|
},
|
||||||
@@ -108,6 +103,11 @@ const accountsReducer = createReducer(initialState, {
|
|||||||
});
|
});
|
||||||
state.items = items;
|
state.items = items;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[t.ACCOUNTS_SELECTED_ROWS_SET]: (state, action) => {
|
||||||
|
const { selectedRows } = action.payload;
|
||||||
|
state.selectedRows = selectedRows;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default createTableQueryReducers('accounts', accountsReducer);
|
export default createTableQueryReducers('accounts', accountsReducer);
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect';
|
||||||
import { repeat } from 'lodash';
|
import { repeat, isEqual } from 'lodash';
|
||||||
import {
|
import {
|
||||||
pickItemsFromIds,
|
pickItemsFromIds,
|
||||||
getItemById,
|
getItemById,
|
||||||
paginationLocationQuery,
|
|
||||||
} from 'store/selectors';
|
} from 'store/selectors';
|
||||||
import { flatToNestedArray, treeToList } from 'utils';
|
import { flatToNestedArray, treeToList } from 'utils';
|
||||||
|
|
||||||
|
const createDeepEqualSelector = createSelectorCreator(
|
||||||
|
defaultMemoize,
|
||||||
|
isEqual
|
||||||
|
);
|
||||||
const accountsViewsSelector = (state) => state.accounts.views;
|
const accountsViewsSelector = (state) => state.accounts.views;
|
||||||
const accountsDataSelector = (state) => state.accounts.items;
|
const accountsDataSelector = (state) => state.accounts.items;
|
||||||
const accountsCurrentViewSelector = (state) => state.accounts.currentViewId;
|
const accountsCurrentViewSelector = (state) => state.accounts.currentViewId;
|
||||||
@@ -23,7 +26,7 @@ export const getAccountsTableQuery = createSelector(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getAccountsItems = createSelector(
|
export const getAccountsItems = createDeepEqualSelector(
|
||||||
accountsViewsSelector,
|
accountsViewsSelector,
|
||||||
accountsDataSelector,
|
accountsDataSelector,
|
||||||
accountsCurrentViewSelector,
|
accountsCurrentViewSelector,
|
||||||
@@ -42,7 +45,7 @@ export const getAccountsItems = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const getAccountsListFactory = () =>
|
export const getAccountsListFactory = () =>
|
||||||
createSelector(
|
createDeepEqualSelector(
|
||||||
accountsListSelector,
|
accountsListSelector,
|
||||||
accountsDataSelector,
|
accountsDataSelector,
|
||||||
(accountsTree, accountsItems) => {
|
(accountsTree, accountsItems) => {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export default {
|
|||||||
ACCOUNT_SET: 'ACCOUNT_SET',
|
ACCOUNT_SET: 'ACCOUNT_SET',
|
||||||
ACCOUNT_DELETE: 'ACCOUNT_DELETE',
|
ACCOUNT_DELETE: 'ACCOUNT_DELETE',
|
||||||
ACCOUNT_FORM_ERRORS: 'ACCOUNT_FORM_ERRORS',
|
ACCOUNT_FORM_ERRORS: 'ACCOUNT_FORM_ERRORS',
|
||||||
|
ACCOUNTS_BULK_ACTION: 'ACCOUNTS_BULK_ACTION',
|
||||||
|
|
||||||
CLEAR_ACCOUNT_FORM_ERRORS: 'CLEAR_ACCOUNT_FORM_ERRORS',
|
CLEAR_ACCOUNT_FORM_ERRORS: 'CLEAR_ACCOUNT_FORM_ERRORS',
|
||||||
|
|
||||||
ACCOUNTS_SELECTED_ROWS_SET: 'ACCOUNTS_SELECTED_ROWS_SET',
|
ACCOUNTS_SELECTED_ROWS_SET: 'ACCOUNTS_SELECTED_ROWS_SET',
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { pickItemsFromIds } from 'store/selectors';
|
|||||||
import { getResourceColumn } from 'store/resources/resources.reducer';
|
import { getResourceColumn } from 'store/resources/resources.reducer';
|
||||||
|
|
||||||
const resourceViewsIdsSelector = (state, props, resourceName) =>
|
const resourceViewsIdsSelector = (state, props, resourceName) =>
|
||||||
state.views.resourceViews[resourceName] || [];
|
state.views.resourceViews[resourceName];
|
||||||
|
|
||||||
const viewsSelector = (state) => state.views.views;
|
const viewsSelector = (state) => state.views.views;
|
||||||
const viewByIdSelector = (state, props) => state.views.views[props.viewId] || {};
|
const viewByIdSelector = (state, props) => state.views.views[props.viewId];
|
||||||
|
|
||||||
const viewColumnsSelector = (state, props) => {
|
const viewColumnsSelector = (state, props) => {
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,5 +13,21 @@ export function closeDialog(name, payload) {
|
|||||||
type: t.CLOSE_DIALOG,
|
type: t.CLOSE_DIALOG,
|
||||||
name: name,
|
name: name,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openAlert(name, payload) {
|
||||||
|
return {
|
||||||
|
type: t.OPEN_ALERT,
|
||||||
|
name,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeAlert(name, payload) {
|
||||||
|
return {
|
||||||
|
type: t.CLOSE_ALERT,
|
||||||
|
name,
|
||||||
|
payload,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ const initialState = {
|
|||||||
sidebarExpended: true,
|
sidebarExpended: true,
|
||||||
previousSidebarExpended: null,
|
previousSidebarExpended: null,
|
||||||
dialogs: {},
|
dialogs: {},
|
||||||
|
alerts: {},
|
||||||
topbarEditViewId: null,
|
topbarEditViewId: null,
|
||||||
requestsLoading: 0,
|
requestsLoading: 0,
|
||||||
backLink: false,
|
backLink: false,
|
||||||
@@ -47,6 +48,20 @@ const reducerInstance = createReducer(initialState, {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[t.OPEN_ALERT]: (state, action) => {
|
||||||
|
state.alerts[action.name] = {
|
||||||
|
isOpen: true,
|
||||||
|
payload: action.payload || {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.CLOSE_ALERT]: (state, action) => {
|
||||||
|
state.alerts[action.name] = {
|
||||||
|
...state.alerts[action.name],
|
||||||
|
isOpen: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
[t.CLOSE_ALL_DIALOGS]: (state, action) => {
|
[t.CLOSE_ALL_DIALOGS]: (state, action) => {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,3 +15,19 @@ export const getDialogPayloadFactory = () => createSelector(
|
|||||||
return { ...dialog?.payload };
|
return { ...dialog?.payload };
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const alertByNameSelector = (state, props) => state.dashboard.alerts?.[props.name];
|
||||||
|
|
||||||
|
export const isAlertOpenFactory = () => createSelector(
|
||||||
|
alertByNameSelector,
|
||||||
|
(alert) => {
|
||||||
|
return alert && alert.isOpen;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getAlertPayloadFactory = () => createSelector(
|
||||||
|
alertByNameSelector,
|
||||||
|
(alert) => {
|
||||||
|
return { ...alert?.payload };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OPEN_DIALOG: 'OPEN_DIALOG',
|
OPEN_DIALOG: 'OPEN_DIALOG',
|
||||||
CLOSE_DIALOG: 'CLOSE_DIALOG',
|
CLOSE_DIALOG: 'CLOSE_DIALOG',
|
||||||
|
OPEN_ALERT: 'OPEN_ALERT',
|
||||||
|
CLOSE_ALERT: 'CLOSE_ALERT',
|
||||||
CLOSE_ALL_DIALOGS: 'CLOSE_ALL_DIALOGS',
|
CLOSE_ALL_DIALOGS: 'CLOSE_ALL_DIALOGS',
|
||||||
|
CLOSE_ALL_ALERTS: 'CLOSE_ALL_ALERTS',
|
||||||
CHANGE_DASHBOARD_PAGE_TITLE: 'CHANGE_DASHBOARD_PAGE_TITLE',
|
CHANGE_DASHBOARD_PAGE_TITLE: 'CHANGE_DASHBOARD_PAGE_TITLE',
|
||||||
CHANGE_DASHBOARD_PAGE_HINT: 'CHANGE_DASHBOARD_PAGE_HINT',
|
CHANGE_DASHBOARD_PAGE_HINT: 'CHANGE_DASHBOARD_PAGE_HINT',
|
||||||
CHANGE_PREFERENCES_PAGE_TITLE: 'CHANGE_PREFERENCES_PAGE_TITLE',
|
CHANGE_PREFERENCES_PAGE_TITLE: 'CHANGE_PREFERENCES_PAGE_TITLE',
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
.tr .td {
|
.tr .td {
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 1px solid #e8e8e8;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #141720;
|
color: #101219;
|
||||||
|
|
||||||
.placeholder {
|
.placeholder {
|
||||||
color: #a0a0a0;
|
color: #a0a0a0;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
color: $sidebar-menu-label-color;
|
color: $sidebar-menu-label-color;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 8px 18px;
|
padding: 10px 18px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
@@ -60,7 +60,6 @@
|
|||||||
.bp3-navbar-divider {
|
.bp3-navbar-divider {
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group-quick-new-downDrop {
|
.form-group-quick-new-downDrop {
|
||||||
.bp3-popover-target .bp3-button {
|
.bp3-popover-target .bp3-button {
|
||||||
color: #1552c8;
|
color: #1552c8;
|
||||||
@@ -146,7 +145,7 @@
|
|||||||
}
|
}
|
||||||
.#{$ns}-button {
|
.#{$ns}-button {
|
||||||
color: #32304a;
|
color: #32304a;
|
||||||
padding: 8px 10px;
|
padding: 8px 12px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(167, 182, 194, 0.12);
|
background: rgba(167, 182, 194, 0.12);
|
||||||
@@ -323,7 +322,7 @@
|
|||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
margin: 20px;
|
margin: 22px 32px;
|
||||||
border: 1px solid #d2dce2;
|
border: 1px solid #d2dce2;
|
||||||
|
|
||||||
.bigcapital-datatable {
|
.bigcapital-datatable {
|
||||||
@@ -425,15 +424,15 @@
|
|||||||
|
|
||||||
&[aria-selected='true'] {
|
&[aria-selected='true'] {
|
||||||
color: #0052cc;
|
color: #0052cc;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}-tab-indicator-wrapper {
|
.#{$ns}-tab-indicator-wrapper {
|
||||||
.#{$ns}-tab-indicator {
|
.#{$ns}-tab-indicator {
|
||||||
height: 3px;
|
height: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button--new-view {
|
.button--new-view {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
|||||||
$sidebar-menu-label-color: rgba(255, 255, 255, 0.45);
|
$sidebar-menu-label-color: rgba(255, 255, 255, 0.45);
|
||||||
$sidebar-submenu-item-color: rgba(255, 255, 255, 0.6);
|
$sidebar-submenu-item-color: rgba(255, 255, 255, 0.6);
|
||||||
$sidebar-submenu-item-hover-color: rgba(255, 255, 255, 0.85);
|
$sidebar-submenu-item-hover-color: rgba(255, 255, 255, 0.85);
|
||||||
$sidebar-logo-opacity: 0.55;
|
$sidebar-logo-opacity: 0.5;
|
||||||
$sidebar-submenu-item-bg-color: #01287d;
|
$sidebar-submenu-item-bg-color: #01287d;
|
||||||
|
|
||||||
$form-check-input-checked-color: #fff;
|
$form-check-input-checked-color: #fff;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default (req: Request, res: Response, next: Function) => {
|
|||||||
}
|
}
|
||||||
if (!req.tenant.initializedAt) {
|
if (!req.tenant.initializedAt) {
|
||||||
Logger.info('[ensure_tenant_initialized_middleware] tenant database not initalized.');
|
Logger.info('[ensure_tenant_initialized_middleware] tenant database not initalized.');
|
||||||
|
|
||||||
return res.boom.badRequest(
|
return res.boom.badRequest(
|
||||||
'Tenant database is not migrated with application schema yut.',
|
'Tenant database is not migrated with application schema yut.',
|
||||||
{ errors: [{ type: 'TENANT.DATABASE.NOT.INITALIZED' }] },
|
{ errors: [{ type: 'TENANT.DATABASE.NOT.INITALIZED' }] },
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default class Account extends TenantModel {
|
|||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return [
|
return [
|
||||||
'accountTypeLabel',
|
'accountTypeLabel',
|
||||||
'accountParentTypeLabel',
|
'accountParentType',
|
||||||
'accountNormal',
|
'accountNormal',
|
||||||
'isBalanceSheetAccount',
|
'isBalanceSheetAccount',
|
||||||
'isPLSheet'
|
'isPLSheet'
|
||||||
|
|||||||
@@ -160,8 +160,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
const manualAccountsIds = manualJournalDTO.entries.map((e) => e.accountId);
|
const manualAccountsIds = manualJournalDTO.entries.map((e) => e.accountId);
|
||||||
|
|
||||||
const accounts = await Account.query()
|
const accounts = await Account.query()
|
||||||
.whereIn('id', manualAccountsIds)
|
.whereIn('id', manualAccountsIds);
|
||||||
.withGraphFetched('type');
|
|
||||||
|
|
||||||
const storedAccountsIds = accounts.map((account) => account.id);
|
const storedAccountsIds = accounts.map((account) => account.id);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user