mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
feat: fix a bunch of bugs.
This commit is contained in:
@@ -8,10 +8,11 @@
|
|||||||
"@blueprintjs/datetime": "^3.15.2",
|
"@blueprintjs/datetime": "^3.15.2",
|
||||||
"@blueprintjs/select": "^3.11.2",
|
"@blueprintjs/select": "^3.11.2",
|
||||||
"@blueprintjs/table": "^3.8.3",
|
"@blueprintjs/table": "^3.8.3",
|
||||||
"@blueprintjs/timezone": "^3.6.1",
|
"@blueprintjs/timezone": "^3.6.2",
|
||||||
"@reduxjs/toolkit": "^1.2.5",
|
"@reduxjs/toolkit": "^1.2.5",
|
||||||
"@svgr/webpack": "4.3.3",
|
"@svgr/webpack": "4.3.3",
|
||||||
"@syncfusion/ej2-react-grids": "^17.4.50",
|
"@syncfusion/ej2-react-grids": "^17.4.50",
|
||||||
|
"@tanem/react-nprogress": "^3.0.24",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.4.0",
|
"@testing-library/react": "^9.4.0",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^7.2.1",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState, useCallback } from 'react';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -29,6 +29,8 @@ function AccountsActionsBar({
|
|||||||
getResourceFields,
|
getResourceFields,
|
||||||
addAccountsTableQueries,
|
addAccountsTableQueries,
|
||||||
onFilterChanged,
|
onFilterChanged,
|
||||||
|
onBulkDelete,
|
||||||
|
onBulkArchive,
|
||||||
}) {
|
}) {
|
||||||
const {path} = useRouteMatch();
|
const {path} = useRouteMatch();
|
||||||
const onClickNewAccount = () => { openDialog('account-form', {}); };
|
const onClickNewAccount = () => { openDialog('account-form', {}); };
|
||||||
@@ -51,6 +53,15 @@ function AccountsActionsBar({
|
|||||||
onFilterChanged && onFilterChanged(filterConditions);
|
onFilterChanged && onFilterChanged(filterConditions);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleBulkArchive = useCallback(() => {
|
||||||
|
onBulkArchive && onBulkArchive(selectedRows.map(r => r.id));
|
||||||
|
}, [onBulkArchive, selectedRows]);
|
||||||
|
|
||||||
|
const handleBulkDelete = useCallback(() => {
|
||||||
|
onBulkDelete && onBulkDelete(selectedRows.map(r => r.id));
|
||||||
|
}, [onBulkDelete, selectedRows]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -77,6 +88,7 @@ function AccountsActionsBar({
|
|||||||
onClick={onClickNewAccount}
|
onClick={onClickNewAccount}
|
||||||
/>
|
/>
|
||||||
<Popover
|
<Popover
|
||||||
|
minimal={true}
|
||||||
content={filterDropdown}
|
content={filterDropdown}
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
position={Position.BOTTOM_LEFT}>
|
position={Position.BOTTOM_LEFT}>
|
||||||
@@ -92,6 +104,7 @@ function AccountsActionsBar({
|
|||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon='archive' iconSize={15} />}
|
icon={<Icon icon='archive' iconSize={15} />}
|
||||||
text='Archive'
|
text='Archive'
|
||||||
|
onClick={handleBulkArchive}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasSelectedRows && (
|
{hasSelectedRows && (
|
||||||
@@ -100,6 +113,7 @@ function AccountsActionsBar({
|
|||||||
icon={<Icon icon='trash' iconSize={15} />}
|
icon={<Icon icon='trash' iconSize={15} />}
|
||||||
text='Delete'
|
text='Delete'
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
|
onClick={handleBulkDelete}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import ViewConnect from 'connectors/View.connector';
|
|||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
import Money from 'components/Money';
|
import Money from 'components/Money';
|
||||||
|
import { useUpdateEffect } from 'hooks';
|
||||||
|
|
||||||
function AccountsDataTable({
|
function AccountsDataTable({
|
||||||
accounts,
|
accounts,
|
||||||
@@ -31,9 +32,16 @@ function AccountsDataTable({
|
|||||||
setTopbarEditView,
|
setTopbarEditView,
|
||||||
accountsLoading,
|
accountsLoading,
|
||||||
onFetchData,
|
onFetchData,
|
||||||
setSelectedRowsAccounts,
|
onSelectedRowsChange
|
||||||
}) {
|
}) {
|
||||||
const {custom_view_id: customViewId} = useParams();
|
const {custom_view_id: customViewId} = useParams();
|
||||||
|
const [initialMount, setInitialMount] = useState(false);
|
||||||
|
|
||||||
|
useUpdateEffect(() => {
|
||||||
|
if (!accountsLoading) {
|
||||||
|
setInitialMount(true);
|
||||||
|
}
|
||||||
|
}, [accountsLoading, setInitialMount]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const viewMeta = getViewItem(customViewId);
|
const viewMeta = getViewItem(customViewId);
|
||||||
@@ -52,12 +60,20 @@ function AccountsDataTable({
|
|||||||
openDialog('account-form', { action: 'edit', id: account.id });
|
openDialog('account-form', { action: 'edit', id: account.id });
|
||||||
}, [openDialog]);
|
}, [openDialog]);
|
||||||
|
|
||||||
const actionMenuList = useCallback(account => (
|
const handleNewParentAccount = useCallback((account) => {
|
||||||
|
openDialog('account-form', { action: 'new_child', id: account.id });
|
||||||
|
}, [openDialog]);
|
||||||
|
|
||||||
|
const actionMenuList = useCallback((account) => (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem text='View Details' />
|
<MenuItem text='View Details' />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem text='Edit Account' onClick={handleEditAccount(account)} />
|
<MenuItem
|
||||||
<MenuItem text='New Account' />
|
text='Edit Account'
|
||||||
|
onClick={handleEditAccount(account)} />
|
||||||
|
<MenuItem
|
||||||
|
text='New Account'
|
||||||
|
onClick={() => handleNewParentAccount(account)} />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text='Inactivate Account'
|
text='Inactivate Account'
|
||||||
@@ -152,8 +168,11 @@ function AccountsDataTable({
|
|||||||
onFetchData && onFetchData(...params);
|
onFetchData && onFetchData(...params);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleSelectedRowsChange = useCallback((selectedRows) => {
|
||||||
|
onSelectedRowsChange && onSelectedRowsChange(selectedRows.map(s => s.original));
|
||||||
|
}, [onSelectedRowsChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingIndicator loading={accountsLoading} spinnerSize={30}>
|
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={accounts}
|
data={accounts}
|
||||||
@@ -161,8 +180,10 @@ function AccountsDataTable({
|
|||||||
manualSortBy={true}
|
manualSortBy={true}
|
||||||
selectionColumn={selectionColumn}
|
selectionColumn={selectionColumn}
|
||||||
expandable={true}
|
expandable={true}
|
||||||
treeGraph={true} />
|
treeGraph={true}
|
||||||
</LoadingIndicator>
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
|
loading={accountsLoading && !initialMount}
|
||||||
|
spinnerProps={{size: 30}} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import PrivateRoute from 'components/PrivateRoute';
|
|||||||
import Authentication from 'components/Authentication';
|
import Authentication from 'components/Authentication';
|
||||||
import Dashboard from 'components/Dashboard/Dashboard';
|
import Dashboard from 'components/Dashboard/Dashboard';
|
||||||
import { isAuthenticated } from 'store/authentication/authentication.reducer'
|
import { isAuthenticated } from 'store/authentication/authentication.reducer'
|
||||||
|
import Progress from 'components/NProgress/Progress';
|
||||||
import messages from 'lang/en';
|
import messages from 'lang/en';
|
||||||
import 'style/App.scss';
|
import 'style/App.scss';
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@ function App(props) {
|
|||||||
<PrivateRoute isAuthenticated={props.isAuthorized} component={Dashboard} />
|
<PrivateRoute isAuthenticated={props.isAuthorized} component={Dashboard} />
|
||||||
</Router>
|
</Router>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Progress isAnimating={props.isLoading} />
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -37,5 +40,6 @@ App.propTypes = {
|
|||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
isAuthorized: isAuthenticated(state),
|
isAuthorized: isAuthenticated(state),
|
||||||
|
isLoading: !!state.dashboard.requestsLoading,
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps)(App);
|
export default connect(mapStateToProps)(App);
|
||||||
@@ -6,7 +6,7 @@ export default function DashboardInsider({
|
|||||||
loading,
|
loading,
|
||||||
children,
|
children,
|
||||||
name,
|
name,
|
||||||
mount = true,
|
mount = false,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={classnames({
|
<div className={classnames({
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import React, {useEffect, useMemo, useCallback} from 'react';
|
import React, {useEffect, useRef, useCallback} from 'react';
|
||||||
import {
|
import {
|
||||||
useTable,
|
useTable,
|
||||||
useExpanded,
|
useExpanded,
|
||||||
useRowSelect,
|
useRowSelect,
|
||||||
usePagination,
|
usePagination,
|
||||||
useResizeColumns,
|
useResizeColumns,
|
||||||
useAsyncDebounce,
|
|
||||||
useSortBy,
|
useSortBy,
|
||||||
useFlexLayout
|
useFlexLayout
|
||||||
} from 'react-table'
|
} from 'react-table';
|
||||||
import {Checkbox} from '@blueprintjs/core';
|
import { Checkbox, Spinner } from '@blueprintjs/core';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { FixedSizeList } from 'react-window'
|
import { FixedSizeList } from 'react-window'
|
||||||
import { ConditionalWrapper } from 'utils';
|
import { ConditionalWrapper } from 'utils';
|
||||||
|
import { useUpdateEffect } from 'hooks';
|
||||||
|
|
||||||
const IndeterminateCheckbox = React.forwardRef(
|
const IndeterminateCheckbox = React.forwardRef(
|
||||||
({ indeterminate, ...rest }, ref) => {
|
({ indeterminate, ...rest }, ref) => {
|
||||||
@@ -40,6 +40,8 @@ export default function DataTable({
|
|||||||
payload,
|
payload,
|
||||||
expandable = false,
|
expandable = false,
|
||||||
expandToggleColumn = 2,
|
expandToggleColumn = 2,
|
||||||
|
noInitialFetch = false,
|
||||||
|
spinnerProps = { size: 40 },
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
getTableProps,
|
getTableProps,
|
||||||
@@ -62,7 +64,7 @@ export default function DataTable({
|
|||||||
isAllRowsExpanded,
|
isAllRowsExpanded,
|
||||||
|
|
||||||
// Get the state from the instance
|
// Get the state from the instance
|
||||||
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
state: { pageIndex, pageSize, sortBy, selectedRowIds, selectedRows },
|
||||||
} = useTable(
|
} = useTable(
|
||||||
{
|
{
|
||||||
columns,
|
columns,
|
||||||
@@ -116,11 +118,21 @@ export default function DataTable({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isInitialMount = useRef(noInitialFetch);
|
||||||
|
|
||||||
// When these table states change, fetch new data!
|
// When these table states change, fetch new data!
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isInitialMount.current) {
|
||||||
|
isInitialMount.current = false;
|
||||||
|
} else {
|
||||||
onFetchData && onFetchData({ pageIndex, pageSize, sortBy })
|
onFetchData && onFetchData({ pageIndex, pageSize, sortBy })
|
||||||
|
}
|
||||||
}, [pageIndex, pageSize, sortBy]);
|
}, [pageIndex, pageSize, sortBy]);
|
||||||
|
|
||||||
|
useUpdateEffect(() => {
|
||||||
|
onSelectedRowsChange && onSelectedRowsChange(selectedFlatRows);
|
||||||
|
}, [selectedRowIds, onSelectedRowsChange]);
|
||||||
|
|
||||||
// Renders table cell.
|
// Renders table cell.
|
||||||
const RenderCell = useCallback(({ row, cell, index }) => (
|
const RenderCell = useCallback(({ row, cell, index }) => (
|
||||||
<ConditionalWrapper
|
<ConditionalWrapper
|
||||||
@@ -188,13 +200,18 @@ export default function DataTable({
|
|||||||
{RenderVirtualizedRows}
|
{RenderVirtualizedRows}
|
||||||
</FixedSizeList>
|
</FixedSizeList>
|
||||||
) : RenderPage();
|
) : RenderPage();
|
||||||
}, [fixedSizeHeight, rows, fixedItemSize, virtualizedRows, RenderVirtualizedRows, RenderPage]);
|
}, [fixedSizeHeight, rows, fixedItemSize, virtualizedRows,
|
||||||
|
RenderVirtualizedRows, RenderPage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames(
|
<div className={classnames(
|
||||||
'bigcapital-datatable',
|
'bigcapital-datatable',
|
||||||
className,
|
className,
|
||||||
{'has-sticky-header': stickyHeader, 'is-expandable': expandable})}>
|
{
|
||||||
|
'has-sticky-header': stickyHeader,
|
||||||
|
'is-expandable': expandable,
|
||||||
|
'has-virtualized-rows': virtualizedRows,
|
||||||
|
})}>
|
||||||
<div {...getTableProps()} className="table">
|
<div {...getTableProps()} className="table">
|
||||||
<div className="thead">
|
<div className="thead">
|
||||||
{headerGroups.map(headerGroup => (
|
{headerGroups.map(headerGroup => (
|
||||||
@@ -240,13 +257,16 @@ export default function DataTable({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div {...getTableBodyProps()} className="tbody">
|
<div {...getTableBodyProps()} className="tbody">
|
||||||
{ RenderTBody() }
|
{ !loading && RenderTBody() }
|
||||||
|
|
||||||
{ (page.length === 0) && (
|
{ !loading && (page.length === 0) && (
|
||||||
<div className={'tr no-results'}>
|
<div className={'tr no-results'}>
|
||||||
<div class="td">{ noResults }</div>
|
<div class="td">{ noResults }</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{ loading && (
|
||||||
|
<div class="loading"><Spinner size={spinnerProps.size} /></div>
|
||||||
|
) }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {useEffect, useMemo} from 'react';
|
import React, {useEffect, useMemo, useCallback, useRef} from 'react';
|
||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
@@ -10,67 +10,94 @@ import {
|
|||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { usePrevious } from 'react-use';
|
import { usePrevious } from 'react-use';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
|
import { checkRequiredProperties } from 'utils';
|
||||||
|
|
||||||
export default function FilterDropdown({
|
export default function FilterDropdown({
|
||||||
fields,
|
fields,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
}) {
|
}) {
|
||||||
const conditionalsItems = [
|
const conditionalsItems = useMemo(() => [
|
||||||
{ value: 'and', label: 'AND' },
|
{ value: 'and', label: 'AND' },
|
||||||
{ value: 'or', label: 'OR' },
|
{ value: 'or', label: 'OR' },
|
||||||
];
|
], []);
|
||||||
const resourceFields = [
|
|
||||||
|
const resourceFields = useMemo(() => [
|
||||||
...fields.map((field) => ({ value: field.key, label: field.label_name, })),
|
...fields.map((field) => ({ value: field.key, label: field.label_name, })),
|
||||||
];
|
], [fields]);
|
||||||
const compatatorsItems = [
|
|
||||||
|
const compatatorsItems = useMemo(() => [
|
||||||
{value: '', label: 'Select a compatator'},
|
{value: '', label: 'Select a compatator'},
|
||||||
{value: 'equals', label: 'Equals'},
|
{value: 'equals', label: 'Equals'},
|
||||||
{value: 'not_equal', label: 'Not Equal'},
|
{value: 'not_equal', label: 'Not Equal'},
|
||||||
{value: 'contain', label: 'Contain'},
|
{value: 'contain', label: 'Contain'},
|
||||||
{value: 'not_contain', label: 'Not Contain'},
|
{value: 'not_contain', label: 'Not Contain'},
|
||||||
];
|
], []);
|
||||||
const defaultFilterCondition = {
|
|
||||||
|
const defaultFilterCondition = useMemo(() => ({
|
||||||
condition: 'and',
|
condition: 'and',
|
||||||
field_key: fields.length > 0 ? fields[0].key : '',
|
field_key: fields.length > 0 ? fields[0].key : '',
|
||||||
compatator: 'equals',
|
compatator: 'equals',
|
||||||
value: '',
|
value: '',
|
||||||
};
|
}), [fields]);
|
||||||
const formik = useFormik({
|
|
||||||
|
const {
|
||||||
|
setFieldValue,
|
||||||
|
getFieldProps,
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
} = useFormik({
|
||||||
|
enableReinitialize: true,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
conditions: [ defaultFilterCondition ],
|
conditions: [ defaultFilterCondition ],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onClickNewFilter = () => {
|
const onClickNewFilter = useCallback(() => {
|
||||||
formik.setFieldValue('conditions', [
|
setFieldValue('conditions', [
|
||||||
...formik.values.conditions, defaultFilterCondition,
|
...values.conditions, defaultFilterCondition,
|
||||||
]);
|
]);
|
||||||
};
|
}, [values, defaultFilterCondition, setFieldValue]);
|
||||||
|
|
||||||
const filteredFilterConditions = useMemo(() => {
|
const filteredFilterConditions = useMemo(() => {
|
||||||
return formik.values.conditions.filter(condition => !!condition.value);
|
const requiredProps = ['field_key', 'condition', 'compatator', 'value'];
|
||||||
}, [formik.values.conditions]);
|
|
||||||
|
return values.conditions
|
||||||
|
.filter((condition) =>
|
||||||
|
!checkRequiredProperties(condition, requiredProps));
|
||||||
|
}, [values.conditions]);
|
||||||
|
|
||||||
const prevConditions = usePrevious(filteredFilterConditions);
|
const prevConditions = usePrevious(filteredFilterConditions);
|
||||||
|
|
||||||
const onClickRemoveCondition = (index) => () => {
|
const onFilterChangeThrottled = useRef(debounce((conditions) => {
|
||||||
if (formik.values.conditions.length === 1) { return; }
|
onFilterChange && onFilterChange(conditions);
|
||||||
|
}, 1000));
|
||||||
const conditions = [ ...formik.values.conditions ];
|
|
||||||
conditions.splice(index, 1);
|
|
||||||
formik.setFieldValue('conditions', [ ...conditions ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEqual(filteredFilterConditions, prevConditions)) {
|
if (!isEqual(prevConditions, filteredFilterConditions) && prevConditions) {
|
||||||
onFilterChange(filteredFilterConditions);
|
onFilterChangeThrottled.current(filteredFilterConditions);
|
||||||
}
|
}
|
||||||
}, [filteredFilterConditions])
|
}, [filteredFilterConditions]);
|
||||||
|
|
||||||
|
// Handle click remove condition.
|
||||||
|
const onClickRemoveCondition = (index) => () => {
|
||||||
|
if (values.conditions.length === 1) {
|
||||||
|
setFieldValue('conditions', [
|
||||||
|
defaultFilterCondition,
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const conditions = [ ...values.conditions ];
|
||||||
|
conditions.splice(index, 1);
|
||||||
|
setFieldValue('conditions', [ ...conditions ]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="filter-dropdown">
|
<div class="filter-dropdown">
|
||||||
<div class="filter-dropdown__body">
|
<div class="filter-dropdown__body">
|
||||||
{formik.values.conditions.map((condition, index) => (
|
{values.conditions.map((condition, index) => (
|
||||||
<div class="filter-dropdown__condition">
|
<div class="filter-dropdown__condition">
|
||||||
<FormGroup
|
<FormGroup
|
||||||
className={'form-group--condition'}>
|
className={'form-group--condition'}>
|
||||||
@@ -78,7 +105,7 @@ export default function FilterDropdown({
|
|||||||
options={conditionalsItems}
|
options={conditionalsItems}
|
||||||
className={Classes.FILL}
|
className={Classes.FILL}
|
||||||
disabled={index > 1}
|
disabled={index > 1}
|
||||||
{...formik.getFieldProps(`conditions[${index}].condition`)} />
|
{...getFieldProps(`conditions[${index}].condition`)} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
@@ -87,7 +114,7 @@ export default function FilterDropdown({
|
|||||||
options={resourceFields}
|
options={resourceFields}
|
||||||
value={1}
|
value={1}
|
||||||
className={Classes.FILL}
|
className={Classes.FILL}
|
||||||
{...formik.getFieldProps(`conditions[${index}].field_key`)} />
|
{...getFieldProps(`conditions[${index}].field_key`)} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
@@ -95,14 +122,14 @@ export default function FilterDropdown({
|
|||||||
<HTMLSelect
|
<HTMLSelect
|
||||||
options={compatatorsItems}
|
options={compatatorsItems}
|
||||||
className={Classes.FILL}
|
className={Classes.FILL}
|
||||||
{...formik.getFieldProps(`conditions[${index}].compatator`)} />
|
{...getFieldProps(`conditions[${index}].compatator`)} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
className={'form-group--value'}>
|
className={'form-group--value'}>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
{...formik.getFieldProps(`conditions[${index}].value`)} />
|
{...getFieldProps(`conditions[${index}].value`)} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ export default class Icon extends React.Component{
|
|||||||
color,
|
color,
|
||||||
htmlTitle,
|
htmlTitle,
|
||||||
iconSize = Icon.SIZE_STANDARD,
|
iconSize = Icon.SIZE_STANDARD,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
intent,
|
intent,
|
||||||
title = icon,
|
title = icon,
|
||||||
tagName = "span",
|
tagName = "span",
|
||||||
@@ -57,6 +59,9 @@ export default class Icon extends React.Component{
|
|||||||
const classes = classNames(Classes.ICON, Classes.iconClass(icon), Classes.intentClass(intent), className);
|
const classes = classNames(Classes.ICON, Classes.iconClass(icon), Classes.intentClass(intent), className);
|
||||||
const viewBox = iconPath.viewBox;
|
const viewBox = iconPath.viewBox;
|
||||||
|
|
||||||
|
const computedHeight = height || iconSize;
|
||||||
|
const computedWidth = width || iconSize;
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
tagName,
|
tagName,
|
||||||
{
|
{
|
||||||
@@ -64,7 +69,7 @@ export default class Icon extends React.Component{
|
|||||||
className: classes,
|
className: classes,
|
||||||
title: htmlTitle,
|
title: htmlTitle,
|
||||||
},
|
},
|
||||||
<svg fill={color} data-icon={icon} width={iconSize} height={iconSize} viewBox={viewBox}>
|
<svg fill={color} data-icon={icon} width={computedWidth} height={computedHeight} viewBox={viewBox}>
|
||||||
{title && <desc>{title}</desc>}
|
{title && <desc>{title}</desc>}
|
||||||
{paths}
|
{paths}
|
||||||
</svg>,
|
</svg>,
|
||||||
|
|||||||
38
client/src/components/NProgress/Bar.js
Normal file
38
client/src/components/NProgress/Bar.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
const Bar = ({ progress, animationDuration }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: '#79b8ff',
|
||||||
|
height: 3,
|
||||||
|
left: 0,
|
||||||
|
marginLeft: `${(-1 + progress) * 100}%`,
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
transition: `margin-left ${animationDuration}ms linear`,
|
||||||
|
width: '100%',
|
||||||
|
zIndex: 1031,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
boxShadow: '0 0 10px #79b8ff, 0 0 5px #79b8ff',
|
||||||
|
display: 'block',
|
||||||
|
height: '100%',
|
||||||
|
opacity: 1,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
transform: 'rotate(3deg) translate(0px, -4px)',
|
||||||
|
width: 100,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
Bar.propTypes = {
|
||||||
|
animationDuration: PropTypes.number.isRequired,
|
||||||
|
progress: PropTypes.number.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Bar;
|
||||||
22
client/src/components/NProgress/Container.js
Normal file
22
client/src/components/NProgress/Container.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
const Container = ({ children, isFinished, animationDuration }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
opacity: isFinished ? 0 : 1,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
transition: `opacity ${animationDuration}ms linear`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
Container.propTypes = {
|
||||||
|
animationDuration: PropTypes.number.isRequired,
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
isFinished: PropTypes.bool.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Container;
|
||||||
27
client/src/components/NProgress/Progress.js
Normal file
27
client/src/components/NProgress/Progress.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { useNProgress } from '@tanem/react-nprogress'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import Bar from './Bar'
|
||||||
|
import Container from './Container'
|
||||||
|
import Spinner from './Spinner'
|
||||||
|
|
||||||
|
const Progress = ({
|
||||||
|
isAnimating,
|
||||||
|
minimum = 0.2
|
||||||
|
}) => {
|
||||||
|
const { animationDuration, isFinished, progress } = useNProgress({
|
||||||
|
isAnimating, minimum,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container isFinished={isFinished} animationDuration={animationDuration}>
|
||||||
|
<Bar progress={progress} animationDuration={animationDuration} />
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Progress.propTypes = {
|
||||||
|
isAnimating: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Progress;
|
||||||
29
client/src/components/NProgress/Spinner.js
Normal file
29
client/src/components/NProgress/Spinner.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
const Spinner = () => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
position: 'fixed',
|
||||||
|
right: 15,
|
||||||
|
top: 15,
|
||||||
|
zIndex: 1031,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
animation: '400ms linear infinite spinner',
|
||||||
|
borderBottom: '2px solid transparent',
|
||||||
|
borderLeft: '2px solid #29d',
|
||||||
|
borderRadius: '50%',
|
||||||
|
borderRight: '2px solid transparent',
|
||||||
|
borderTop: '2px solid #29d',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
height: 18,
|
||||||
|
width: 18,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Spinner;
|
||||||
@@ -1,23 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import appMeta from 'config/app';
|
import appMeta from 'config/app';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
return (
|
return (
|
||||||
<div className="sidebar__head">
|
<div className="sidebar__head">
|
||||||
<div className="sidebar__head-logo">
|
<div className="sidebar__head-logo">
|
||||||
|
<Icon icon={'bigcapital'} width={140} height={28} className="bigcapital--alt" />
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="sidebar__head-company-meta">
|
|
||||||
<div className="company-name">
|
|
||||||
{ appMeta.app_name }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="company-meta">
|
|
||||||
<span class="version">
|
|
||||||
{ appMeta.app_version }
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
fetchAccountsTable,
|
fetchAccountsTable,
|
||||||
submitAccount,
|
submitAccount,
|
||||||
fetchAccount,
|
fetchAccount,
|
||||||
|
deleteBulkAccounts,
|
||||||
} from 'store/accounts/accounts.actions';
|
} from 'store/accounts/accounts.actions';
|
||||||
import {
|
import {
|
||||||
getAccountsItems,
|
getAccountsItems,
|
||||||
@@ -26,6 +27,7 @@ const mapStateToProps = (state, props) => ({
|
|||||||
|
|
||||||
tableQuery: state.accounts.tableQuery,
|
tableQuery: state.accounts.tableQuery,
|
||||||
accountsLoading: state.accounts.loading,
|
accountsLoading: state.accounts.loading,
|
||||||
|
accountErrors: state.accounts.errors,
|
||||||
|
|
||||||
getAccountById: (id) => getItemById(state.accounts.items, id),
|
getAccountById: (id) => getItemById(state.accounts.items, id),
|
||||||
});
|
});
|
||||||
@@ -38,6 +40,7 @@ const mapActionsToProps = (dispatch) => ({
|
|||||||
requestInactiveAccount: (id) => dispatch(inactiveAccount({ id })),
|
requestInactiveAccount: (id) => dispatch(inactiveAccount({ id })),
|
||||||
requestFetchAccount: (id) => dispatch(fetchAccount({ id })),
|
requestFetchAccount: (id) => dispatch(fetchAccount({ id })),
|
||||||
requestFetchAccountsTable: (query = {}) => dispatch(fetchAccountsTable({ query: { ...query } })),
|
requestFetchAccountsTable: (query = {}) => dispatch(fetchAccountsTable({ query: { ...query } })),
|
||||||
|
requestDeleteBulkAccounts: (ids) => dispatch(deleteBulkAccounts({ ids })),
|
||||||
|
|
||||||
changeCurrentView: (id) => dispatch({
|
changeCurrentView: (id) => dispatch({
|
||||||
type: t.ACCOUNTS_SET_CURRENT_VIEW,
|
type: t.ACCOUNTS_SET_CURRENT_VIEW,
|
||||||
@@ -50,7 +53,7 @@ const mapActionsToProps = (dispatch) => ({
|
|||||||
type: 'ACCOUNTS_TABLE_QUERIES_ADD', queries,
|
type: 'ACCOUNTS_TABLE_QUERIES_ADD', queries,
|
||||||
}),
|
}),
|
||||||
setSelectedRowsAccounts: (ids) => dispatch({
|
setSelectedRowsAccounts: (ids) => dispatch({
|
||||||
type: t.ACCOUNTS_SELECTED_ROWS_SET, ids,
|
type: t.ACCOUNTS_SELECTED_ROWS_SET, payload: { ids },
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ const mapActionsToProps = (dispatch) => ({
|
|||||||
setTopbarEditView: (id) => dispatch({
|
setTopbarEditView: (id) => dispatch({
|
||||||
type: t.SET_TOPBAR_EDIT_VIEW, id,
|
type: t.SET_TOPBAR_EDIT_VIEW, id,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
setDashboardRequestLoading: () => dispatch({
|
||||||
|
type: t.SET_DASHBOARD_REQUEST_LOADING,
|
||||||
|
}),
|
||||||
|
|
||||||
|
setDashboardRequestCompleted: () => dispatch({
|
||||||
|
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapActionsToProps);
|
export default connect(mapStateToProps, mapActionsToProps);
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
import RegisterFromConnect from 'connectors/RegisterForm.connect';
|
import RegisterFromConnect from 'connectors/RegisterForm.connect';
|
||||||
import ErrorMessage from 'components/ErrorMessage';
|
import ErrorMessage from 'components/ErrorMessage';
|
||||||
import AppToaster from 'components/AppToaster';
|
import AppToaster from 'components/AppToaster';
|
||||||
import { compose, regExpCollection } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
function Register({
|
function Register({
|
||||||
requestSubmitRegister,
|
requestSubmitRegister,
|
||||||
@@ -29,7 +29,6 @@ function Register({
|
|||||||
last_name: Yup.string().required(),
|
last_name: Yup.string().required(),
|
||||||
email: Yup.string().email().required(),
|
email: Yup.string().email().required(),
|
||||||
phone_number: Yup.string()
|
phone_number: Yup.string()
|
||||||
.matches(regExpCollection.phoneNumber)
|
|
||||||
.required(intl.formatMessage({ id: 'required' })),
|
.required(intl.formatMessage({ id: 'required' })),
|
||||||
password: Yup.string()
|
password: Yup.string()
|
||||||
.min(4, 'Password has to be longer than 8 characters!')
|
.min(4, 'Password has to be longer than 8 characters!')
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import React, { useEffect, useState, useCallback } from 'react';
|
|||||||
import {
|
import {
|
||||||
Route,
|
Route,
|
||||||
Switch,
|
Switch,
|
||||||
useParams,
|
|
||||||
useRouteMatch
|
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import useAsync from 'hooks/async';
|
import useAsync from 'hooks/async';
|
||||||
import { Alert, Intent } from '@blueprintjs/core';
|
import { Alert, Intent } from '@blueprintjs/core';
|
||||||
@@ -21,56 +19,71 @@ import { compose } from 'utils';
|
|||||||
|
|
||||||
function AccountsChart({
|
function AccountsChart({
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
requestFetchAccounts,
|
|
||||||
requestDeleteAccount,
|
requestDeleteAccount,
|
||||||
requestInactiveAccount,
|
requestInactiveAccount,
|
||||||
fetchResourceViews,
|
fetchResourceViews,
|
||||||
fetchResourceFields,
|
fetchResourceFields,
|
||||||
getResourceFields,
|
|
||||||
requestFetchAccountsTable,
|
requestFetchAccountsTable,
|
||||||
addAccountsTableQueries
|
addAccountsTableQueries,
|
||||||
|
requestDeleteBulkAccounts,
|
||||||
|
setDashboardRequestLoading,
|
||||||
|
setDashboardRequestCompleted,
|
||||||
}) {
|
}) {
|
||||||
const [state, setState] = useState({
|
|
||||||
deleteAlertActive: false,
|
|
||||||
restoreAlertActive: false,
|
|
||||||
inactiveAlertActive: false,
|
|
||||||
targetAccount: {},
|
|
||||||
});
|
|
||||||
const [deleteAccount, setDeleteAccount] = useState(false);
|
const [deleteAccount, setDeleteAccount] = useState(false);
|
||||||
const [inactiveAccount, setInactiveAccount] = useState(false);
|
const [inactiveAccount, setInactiveAccount] = useState(false);
|
||||||
|
const [bulkDelete, setBulkDelete] = useState(false);
|
||||||
|
const [selectedRows, setSelectedRows] = useState([]);
|
||||||
|
|
||||||
|
// Fetch accounts resource views and fields.
|
||||||
const fetchHook = useAsync(async () => {
|
const fetchHook = useAsync(async () => {
|
||||||
|
setDashboardRequestLoading();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchResourceViews('accounts'),
|
fetchResourceViews('accounts'),
|
||||||
fetchResourceFields('accounts'),
|
fetchResourceFields('accounts'),
|
||||||
]);
|
]);
|
||||||
|
setDashboardRequestCompleted();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch accounts list according to the given custom view id.
|
// Fetch accounts list according to the given custom view id.
|
||||||
const fetchAccountsHook = useAsync(async () => {
|
const fetchAccountsHook = useAsync(async () => {
|
||||||
|
setDashboardRequestLoading();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
requestFetchAccountsTable(),
|
requestFetchAccountsTable(),
|
||||||
]);
|
]);
|
||||||
|
setDashboardRequestCompleted();
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle('Chart of Accounts');
|
changePageTitle('Chart of Accounts');
|
||||||
}, []);
|
}, [changePageTitle]);
|
||||||
|
|
||||||
// Handle click and cancel/confirm account delete
|
// Handle click and cancel/confirm account delete
|
||||||
const handleDeleteAccount = (account) => { setDeleteAccount(account); };
|
const handleDeleteAccount = (account) => { setDeleteAccount(account); };
|
||||||
|
|
||||||
// handle cancel delete account alert.
|
// handle cancel delete account alert.
|
||||||
const handleCancelAccountDelete = () => { setDeleteAccount(false); };
|
const handleCancelAccountDelete = useCallback(() => { setDeleteAccount(false); }, []);
|
||||||
|
|
||||||
// Handle confirm account delete
|
// Handle confirm account delete
|
||||||
const handleConfirmAccountDelete = useCallback(() => {
|
const handleConfirmAccountDelete = useCallback(() => {
|
||||||
requestDeleteAccount(deleteAccount.id).then(() => {
|
requestDeleteAccount(deleteAccount.id).then(() => {
|
||||||
setDeleteAccount(false);
|
setDeleteAccount(false);
|
||||||
fetchAccountsHook.execute();
|
|
||||||
AppToaster.show({ message: 'the_account_has_been_deleted' });
|
AppToaster.show({ message: 'the_account_has_been_deleted' });
|
||||||
|
}).catch(errors => {
|
||||||
|
setDeleteAccount(false);
|
||||||
|
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'cannot_delete_predefined_account'
|
||||||
});
|
});
|
||||||
}, [deleteAccount]);
|
}
|
||||||
|
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'cannot_delete_account_has_associated_transactions'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [deleteAccount, requestDeleteAccount]);
|
||||||
|
|
||||||
// Handle cancel/confirm account inactive.
|
// Handle cancel/confirm account inactive.
|
||||||
const handleInactiveAccount = useCallback((account) => {
|
const handleInactiveAccount = useCallback((account) => {
|
||||||
@@ -91,12 +104,6 @@ function AccountsChart({
|
|||||||
});
|
});
|
||||||
}, [inactiveAccount]);
|
}, [inactiveAccount]);
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle cancel/confirm account restore.
|
|
||||||
*/
|
|
||||||
const handleCancelAccountRestore = () => {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditAccount = (account) => {
|
const handleEditAccount = (account) => {
|
||||||
|
|
||||||
@@ -106,22 +113,43 @@ function AccountsChart({
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmAccountRestore = (account) => {
|
const handleBulkDelete = useCallback((accountsIds) => {
|
||||||
|
setBulkDelete(accountsIds);
|
||||||
|
}, [setBulkDelete]);
|
||||||
|
|
||||||
};
|
const handleConfirmBulkDelete = useCallback(() => {
|
||||||
const handleDeleteBulkAccounts = (accounts) => {
|
requestDeleteBulkAccounts(bulkDelete).then(() => {
|
||||||
|
setBulkDelete(false);
|
||||||
|
AppToaster.show({ message: 'the_accounts_have_been_deleted' });
|
||||||
|
}).catch((error) => {
|
||||||
|
setBulkDelete(false);
|
||||||
|
});
|
||||||
|
}, [requestDeleteBulkAccounts, bulkDelete]);
|
||||||
|
|
||||||
};
|
const handleCancelBulkDelete = useCallback(() => {
|
||||||
|
setBulkDelete(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleSelectedRowsChange = (accounts) => {
|
const handleBulkArchive = useCallback((accounts) => {
|
||||||
console.log(accounts);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handle selected rows change.
|
||||||
|
const handleSelectedRowsChange = useCallback((accounts) => {
|
||||||
|
setSelectedRows(accounts);
|
||||||
|
}, [setSelectedRows]);
|
||||||
|
|
||||||
|
// Refetches accounts data table when current custom view changed.
|
||||||
const handleFilterChanged = useCallback(() => {
|
const handleFilterChanged = useCallback(() => {
|
||||||
fetchAccountsHook.execute();
|
fetchAccountsHook.execute();
|
||||||
}, [fetchAccountsHook]);
|
}, [fetchAccountsHook]);
|
||||||
|
|
||||||
const handleViewChanged = useCallback(() => { fetchAccountsHook.execute(); }, []);
|
// Refetch accounts data table when current custom view changed.
|
||||||
|
const handleViewChanged = useCallback(() => {
|
||||||
|
fetchAccountsHook.execute();
|
||||||
|
}, [fetchAccountsHook]);
|
||||||
|
|
||||||
|
// Handle fetch data of accounts datatable.
|
||||||
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
|
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
|
||||||
addAccountsTableQueries({
|
addAccountsTableQueries({
|
||||||
...(sortBy.length > 0) ? {
|
...(sortBy.length > 0) ? {
|
||||||
@@ -135,26 +163,29 @@ function AccountsChart({
|
|||||||
return (
|
return (
|
||||||
<DashboardInsider loading={fetchHook.pending} name={'accounts-chart'}>
|
<DashboardInsider loading={fetchHook.pending} name={'accounts-chart'}>
|
||||||
<DashboardActionsBar
|
<DashboardActionsBar
|
||||||
onFilterChanged={handleFilterChanged} />
|
selectedRows={selectedRows}
|
||||||
|
onFilterChanged={handleFilterChanged}
|
||||||
|
onBulkDelete={handleBulkDelete}
|
||||||
|
onBulkArchive={handleBulkArchive} />
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact={true}
|
exact={true}
|
||||||
path={[
|
path={[
|
||||||
'/dashboard/accounts/:custom_view_id/custom_view',
|
'/dashboard/accounts/:custom_view_id/custom_view',
|
||||||
'/dashboard/accounts'
|
'/dashboard/accounts',
|
||||||
]}>
|
]}>
|
||||||
<AccountsViewsTabs
|
<AccountsViewsTabs
|
||||||
onViewChanged={handleViewChanged}
|
onViewChanged={handleViewChanged} />
|
||||||
onDeleteBulkAccounts={handleDeleteBulkAccounts} />
|
|
||||||
|
|
||||||
<AccountsDataTable
|
<AccountsDataTable
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
|
||||||
onDeleteAccount={handleDeleteAccount}
|
onDeleteAccount={handleDeleteAccount}
|
||||||
onInactiveAccount={handleInactiveAccount}
|
onInactiveAccount={handleInactiveAccount}
|
||||||
onRestoreAccount={handleRestoreAccount}
|
onRestoreAccount={handleRestoreAccount}
|
||||||
onEditAccount={handleEditAccount}
|
onEditAccount={handleEditAccount}
|
||||||
onFetchData={handleFetchData} />
|
onFetchData={handleFetchData}
|
||||||
|
onSelectedRowsChange={handleSelectedRowsChange} />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
||||||
@@ -185,6 +216,20 @@ function AccountsChart({
|
|||||||
but it will become private to you.
|
but it will become private to you.
|
||||||
</p>
|
</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
cancelButtonText="Cancel"
|
||||||
|
confirmButtonText="Delete"
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={bulkDelete}
|
||||||
|
onCancel={handleCancelBulkDelete}
|
||||||
|
onConfirm={handleConfirmBulkDelete}>
|
||||||
|
<p>
|
||||||
|
Are you sure you want to move <b>filename</b> to Trash? You will be able to restore it later,
|
||||||
|
but it will become private to you.
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function AccountFormDialog({
|
|||||||
closeDialog,
|
closeDialog,
|
||||||
requestSubmitAccount,
|
requestSubmitAccount,
|
||||||
requestEditAccount,
|
requestEditAccount,
|
||||||
getAccountById
|
getAccountById,
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const accountFormValidationSchema = Yup.object().shape({
|
const accountFormValidationSchema = Yup.object().shape({
|
||||||
@@ -57,12 +57,23 @@ function AccountFormDialog({
|
|||||||
}), []);
|
}), []);
|
||||||
|
|
||||||
const [selectedAccountType, setSelectedAccountType] = useState(null);
|
const [selectedAccountType, setSelectedAccountType] = useState(null);
|
||||||
const [selectedSubaccount, setSelectedSubaccount] = useState(null);
|
const [selectedSubaccount, setSelectedSubaccount] = useState(
|
||||||
|
payload.action === 'new_child' ?
|
||||||
|
accounts.find(a => a.id === payload.id) : null,
|
||||||
|
);
|
||||||
|
|
||||||
const editAccount = useMemo(() =>
|
const editAccount = useMemo(() =>
|
||||||
payload.action === 'edit' ? getAccountById(payload.id) : null,
|
payload.action === 'edit' ? getAccountById(payload.id) : null,
|
||||||
[payload, getAccountById]);
|
[payload, getAccountById]);
|
||||||
|
|
||||||
|
const transformApiErrors = (errors) => {
|
||||||
|
const fields = {};
|
||||||
|
if (errors.find(e => e.type === 'NOT_UNIQUE_CODE')) {
|
||||||
|
fields.code = 'Account code is not unqiue.'
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
};
|
||||||
|
|
||||||
// Formik
|
// Formik
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
@@ -71,21 +82,22 @@ function AccountFormDialog({
|
|||||||
? editAccount : initialValues,
|
? editAccount : initialValues,
|
||||||
},
|
},
|
||||||
validationSchema: accountFormValidationSchema,
|
validationSchema: accountFormValidationSchema,
|
||||||
onSubmit: (values, { setSubmitting }) => {
|
onSubmit: (values, { setSubmitting, setErrors }) => {
|
||||||
const exclude = ['subaccount'];
|
const exclude = ['subaccount'];
|
||||||
|
|
||||||
if (payload.action === 'edit') {
|
if (payload.action === 'edit') {
|
||||||
requestEditAccount({
|
requestEditAccount({
|
||||||
payload: payload.id,
|
payload: payload.id,
|
||||||
form: { ...omit(values, exclude) }
|
form: { ...omit(values, [...exclude, 'account_type_id']) }
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
closeDialog(name);
|
closeDialog(name);
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'the_account_has_been_edited'
|
message: 'the_account_has_been_edited'
|
||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}).catch(() => {
|
}).catch((errors) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
setErrors(transformApiErrors(errors));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
requestSubmitAccount({ form: { ...omit(values, exclude) } }).then(response => {
|
requestSubmitAccount({ form: { ...omit(values, exclude) } }).then(response => {
|
||||||
@@ -94,8 +106,9 @@ function AccountFormDialog({
|
|||||||
message: 'the_account_has_been_submit'
|
message: 'the_account_has_been_submit'
|
||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}).catch(() => {
|
}).catch((errors) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
setErrors(transformApiErrors(errors));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,9 +200,7 @@ function AccountFormDialog({
|
|||||||
return (<span>{'Sub account?'} <Icon icon="info-circle" iconSize={12} /></span>);
|
return (<span>{'Sub account?'} <Icon icon="info-circle" iconSize={12} /></span>);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const requiredSpan = useMemo(() => (
|
const requiredSpan = useMemo(() => (<span class="required">*</span>), []);
|
||||||
<span class="required">*</span>
|
|
||||||
), []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -232,6 +243,7 @@ function AccountFormDialog({
|
|||||||
rightIcon='caret-down'
|
rightIcon='caret-down'
|
||||||
text={selectedAccountType ?
|
text={selectedAccountType ?
|
||||||
selectedAccountType.name : 'Select account type'}
|
selectedAccountType.name : 'Select account type'}
|
||||||
|
disabled={payload.action === 'edit'}
|
||||||
/>
|
/>
|
||||||
</Select>
|
</Select>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { omit } from 'lodash';
|
||||||
import ApiService from 'services/ApiService';
|
import ApiService from 'services/ApiService';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
|
||||||
@@ -41,14 +42,20 @@ export const fetchAccountsList = ({ query } = {}) => {
|
|||||||
export const fetchAccountsTable = ({ query } = {}) => {
|
export const fetchAccountsTable = ({ query } = {}) => {
|
||||||
return (dispatch, getState) =>
|
return (dispatch, getState) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
const pageQuery = getState().accounts.tableQuery;
|
let pageQuery = getState().accounts.tableQuery;
|
||||||
|
|
||||||
|
if (pageQuery.filter_roles) {
|
||||||
|
pageQuery = {
|
||||||
|
...omit(pageQuery, ['filter_roles']),
|
||||||
|
stringified_filter_roles: JSON.stringify(pageQuery.filter_roles) || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.ACCOUNTS_TABLE_LOADING,
|
type: t.ACCOUNTS_TABLE_LOADING,
|
||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
ApiService.get('accounts', { params: { ...pageQuery, ...query } })
|
ApiService.get('accounts', { params: { ...pageQuery, ...query } })
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.ACCOUNTS_PAGE_SET,
|
type: t.ACCOUNTS_PAGE_SET,
|
||||||
accounts: response.data.accounts,
|
accounts: response.data.accounts,
|
||||||
@@ -64,7 +71,7 @@ export const fetchAccountsTable = ({ query } = {}) => {
|
|||||||
});
|
});
|
||||||
resolve(response);
|
resolve(response);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -89,9 +96,12 @@ export const fetchAccountsDataTable = ({ query }) => {
|
|||||||
export const submitAccount = ({ form }) => {
|
export const submitAccount = ({ form }) => {
|
||||||
return dispatch =>
|
return dispatch =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
|
|
||||||
ApiService.post('accounts', form)
|
ApiService.post('accounts', form)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
dispatch({ type: t.CLEAR_ACCOUNT_FORM_ERRORS });
|
dispatch({
|
||||||
|
type: t.ACCOUNT_ERRORS_CLEAR,
|
||||||
|
});
|
||||||
resolve(response);
|
resolve(response);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -99,11 +109,16 @@ export const submitAccount = ({ form }) => {
|
|||||||
const { data } = response;
|
const { data } = response;
|
||||||
const { errors } = data;
|
const { errors } = data;
|
||||||
|
|
||||||
dispatch({ type: t.CLEAR_ACCOUNT_FORM_ERRORS });
|
dispatch({
|
||||||
|
type: t.ACCOUNT_ERRORS_CLEAR,
|
||||||
|
});
|
||||||
if (errors) {
|
if (errors) {
|
||||||
dispatch({ type: t.ACCOUNT_FORM_ERRORS, errors });
|
dispatch({
|
||||||
|
type: t.ACCOUNT_ERRORS_SET,
|
||||||
|
payload: { errors },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
reject(error);
|
reject(errors);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -125,7 +140,7 @@ export const editAccount = ({ id, form }) => {
|
|||||||
if (errors) {
|
if (errors) {
|
||||||
dispatch({ type: t.ACCOUNT_FORM_ERRORS, errors });
|
dispatch({ type: t.ACCOUNT_FORM_ERRORS, errors });
|
||||||
}
|
}
|
||||||
reject(error);
|
reject(errors);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -143,11 +158,25 @@ export const deleteAccount = ({ id }) => {
|
|||||||
ApiService.delete(`accounts/${id}`).then((response) => {
|
ApiService.delete(`accounts/${id}`).then((response) => {
|
||||||
dispatch({ type: t.ACCOUNT_DELETE, id });
|
dispatch({ type: t.ACCOUNT_DELETE, id });
|
||||||
resolve(response);
|
resolve(response);
|
||||||
}).catch(error => { reject(error); });
|
}).catch((error) => {
|
||||||
|
reject(error.response.data.errors || []);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteBulkAccounts = ({ ids }) => {};
|
export const deleteBulkAccounts = ({ ids }) => {
|
||||||
|
return dispatch => new Promise((resolve, reject) => {
|
||||||
|
ApiService.delete(`accounts`, { params: { ids }}).then((response) => {
|
||||||
|
dispatch({
|
||||||
|
type: t.ACCOUNTS_BULK_DELETE,
|
||||||
|
payload: { ids }
|
||||||
|
});
|
||||||
|
resolve(response);
|
||||||
|
}).catch((error) => {
|
||||||
|
reject(error.response.data.errors || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const fetchAccount = ({ id }) => {
|
export const fetchAccount = ({ id }) => {
|
||||||
return dispatch =>
|
return dispatch =>
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ const initialState = {
|
|||||||
views: {},
|
views: {},
|
||||||
accountsTypes: [],
|
accountsTypes: [],
|
||||||
accountsById: {},
|
accountsById: {},
|
||||||
accountFormErrors: [],
|
tableQuery: {},
|
||||||
datatableQuery: {},
|
|
||||||
currentViewId: -1,
|
currentViewId: -1,
|
||||||
selectedRows: [],
|
selectedRows: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
|
errors: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const accountsReducer = createReducer(initialState, {
|
const accountsReducer = createReducer(initialState, {
|
||||||
@@ -35,7 +35,6 @@ const accountsReducer = createReducer(initialState, {
|
|||||||
...view,
|
...view,
|
||||||
ids: action.accounts.map(i => i.id),
|
ids: action.accounts.map(i => i.id),
|
||||||
};
|
};
|
||||||
state.accounts = action.accounts;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.ACCOUNT_TYPES_LIST_SET]: (state, action) => {
|
[t.ACCOUNT_TYPES_LIST_SET]: (state, action) => {
|
||||||
@@ -53,7 +52,8 @@ const accountsReducer = createReducer(initialState, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
[t.ACCOUNTS_SELECTED_ROWS_SET]: (state, action) => {
|
[t.ACCOUNTS_SELECTED_ROWS_SET]: (state, action) => {
|
||||||
state.selectedRows.push(...action.ids);
|
const { ids } = action.payload;
|
||||||
|
state.selectedRows = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.ACCOUNTS_SET_CURRENT_VIEW]: (state, action) => {
|
[t.ACCOUNTS_SET_CURRENT_VIEW]: (state, action) => {
|
||||||
@@ -63,6 +63,27 @@ const accountsReducer = createReducer(initialState, {
|
|||||||
[t.ACCOUNTS_TABLE_LOADING]: (state, action) => {
|
[t.ACCOUNTS_TABLE_LOADING]: (state, action) => {
|
||||||
state.loading = action.loading;
|
state.loading = action.loading;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[t.ACCOUNT_ERRORS_SET]: (state, action) => {
|
||||||
|
const { errors } = action.payload;
|
||||||
|
state.errors = errors;
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.ACCOUNT_ERRORS_CLEAR]: (state, action) => {
|
||||||
|
state.errors = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.ACCOUNTS_BULK_DELETE]: (state, action) => {
|
||||||
|
const { ids } = action.payload;
|
||||||
|
const items = { ...state.items };
|
||||||
|
|
||||||
|
ids.forEach((id) => {
|
||||||
|
if (typeof items[id] !== 'undefined') {
|
||||||
|
delete items[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
state.items = items;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default createTableQueryReducers('accounts', accountsReducer);
|
export default createTableQueryReducers('accounts', accountsReducer);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { pickItemsFromIds } from 'store/selectors';
|
import { pickItemsFromIds } from 'store/selectors';
|
||||||
|
|
||||||
export const getAccountsItems = (state, viewId) => {
|
export const getAccountsItems = (state, viewId) => {
|
||||||
|
|
||||||
const accountsView = state.accounts.views[viewId || -1];
|
const accountsView = state.accounts.views[viewId || -1];
|
||||||
const accountsItems = state.accounts.items;
|
const accountsItems = state.accounts.items;
|
||||||
|
|
||||||
|
|||||||
@@ -17,4 +17,8 @@ export default {
|
|||||||
ACCOUNTS_TABLE_QUERIES_SET: 'ACCOUNTS_TABLE_QUERIES_SET',
|
ACCOUNTS_TABLE_QUERIES_SET: 'ACCOUNTS_TABLE_QUERIES_SET',
|
||||||
|
|
||||||
ACCOUNTS_TABLE_LOADING: 'ACCOUNTS_TABLE_LOADING',
|
ACCOUNTS_TABLE_LOADING: 'ACCOUNTS_TABLE_LOADING',
|
||||||
|
|
||||||
|
ACCOUNT_ERRORS_SET: 'ACCOUNT_ERRORS_SET',
|
||||||
|
ACCOUNT_ERRORS_CLEAR: 'ACCOUNT_ERRORS_CLEAR',
|
||||||
|
ACCOUNTS_BULK_DELETE: 'ACCOUNTS_BULK_DELETE'
|
||||||
};
|
};
|
||||||
@@ -7,6 +7,7 @@ const initialState = {
|
|||||||
preferencesPageTitle: '',
|
preferencesPageTitle: '',
|
||||||
dialogs: {},
|
dialogs: {},
|
||||||
topbarEditViewId: 1,
|
topbarEditViewId: 1,
|
||||||
|
requestsLoading: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createReducer(initialState, {
|
export default createReducer(initialState, {
|
||||||
@@ -42,6 +43,15 @@ export default createReducer(initialState, {
|
|||||||
|
|
||||||
[t.SET_TOPBAR_EDIT_VIEW]: (state, action) => {
|
[t.SET_TOPBAR_EDIT_VIEW]: (state, action) => {
|
||||||
state.topbarEditViewId = action.id;
|
state.topbarEditViewId = action.id;
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.SET_DASHBOARD_REQUEST_LOADING]: (state, action) => {
|
||||||
|
state.requestsLoading = state.requestsLoading + 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.SET_DASHBOARD_REQUEST_COMPLETED]: (state, action) => {
|
||||||
|
const requestsLoading = state.requestsLoading - 1;
|
||||||
|
state.requestsLoading = Math.max(requestsLoading, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ export default {
|
|||||||
CHANGE_PREFERENCES_PAGE_TITLE: 'CHANGE_PREFERENCES_PAGE_TITLE',
|
CHANGE_PREFERENCES_PAGE_TITLE: 'CHANGE_PREFERENCES_PAGE_TITLE',
|
||||||
ALTER_DASHBOARD_PAGE_SUBTITLE: 'ALTER_DASHBOARD_PAGE_SUBTITLE',
|
ALTER_DASHBOARD_PAGE_SUBTITLE: 'ALTER_DASHBOARD_PAGE_SUBTITLE',
|
||||||
SET_TOPBAR_EDIT_VIEW: 'SET_TOPBAR_EDIT_VIEW',
|
SET_TOPBAR_EDIT_VIEW: 'SET_TOPBAR_EDIT_VIEW',
|
||||||
|
SET_DASHBOARD_REQUEST_LOADING: 'SET_DASHBOARD_REQUEST_LOADING',
|
||||||
|
SET_DASHBOARD_REQUEST_COMPLETED: 'SET_DASHBOARD_REQUEST_COMPLETED',
|
||||||
};
|
};
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import {pick} from 'lodash';
|
import {pick, at} from 'lodash';
|
||||||
|
|
||||||
export const getItemById = (items, id) => {
|
export const getItemById = (items, id) => {
|
||||||
return items[id] || null;
|
return items[id] || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pickItemsFromIds = (items, ids) => {
|
export const pickItemsFromIds = (items, ids) => {
|
||||||
return Object.values(pick(items, ids));
|
return at(items, ids).filter(i => i);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCurrentPageResults = (items, pages, pageNumber) => {
|
export const getCurrentPageResults = (items, pages, pageNumber) => {
|
||||||
|
|||||||
@@ -61,3 +61,15 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
|||||||
fill: #2d95fd;
|
fill: #2d95fd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bigcapital--alt{
|
||||||
|
|
||||||
|
|
||||||
|
svg{
|
||||||
|
path,
|
||||||
|
.path-13,
|
||||||
|
.path-1{
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -111,9 +111,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tbody{
|
.tbody{
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
.tr .td{
|
.tr .td{
|
||||||
border-bottom: 1px solid #E8E8E8;
|
border-bottom: 1px solid #E8E8E8;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -145,6 +142,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .loading{
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tr .th,
|
.tr .th,
|
||||||
@@ -175,8 +176,6 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +198,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.has-virtualized-rows{
|
||||||
|
.tbody{
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&--financial-report{
|
&--financial-report{
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
|
|||||||
@@ -210,13 +210,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__page-content{
|
&__page-content{
|
||||||
// padding: 22px;
|
// padding: 22px;
|
||||||
|
|
||||||
|
|
||||||
.bigcapital-datatable{
|
.bigcapital-datatable{
|
||||||
|
|
||||||
.table{
|
.table{
|
||||||
@@ -229,6 +229,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard__loading-indicator{
|
||||||
|
padding-top: 150px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__preferences-topbar{
|
&__preferences-topbar{
|
||||||
|
|||||||
@@ -18,22 +18,14 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
&__head{
|
&__head{
|
||||||
padding: 16px 10px;
|
padding: 16px 12px;
|
||||||
|
|
||||||
&-company-meta{
|
&-logo{
|
||||||
|
margin-top: 4px;
|
||||||
|
|
||||||
.company-name{
|
svg{
|
||||||
font-size: 16px;
|
opacity: 0.35;
|
||||||
font-weight: 200;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: rgba(255, 255, 255, 0.75);
|
|
||||||
}
|
|
||||||
.company-meta{
|
|
||||||
color: rgba(255, 255, 255, 0.4);
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,14 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
&:not(:last-of-type) {
|
&:not(:last-of-type) {
|
||||||
padding-right: 15px;
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
.bp3-html-select select,
|
||||||
|
.bp3-select select{
|
||||||
|
padding: 0 20px 0 6px;
|
||||||
|
}
|
||||||
|
.bp3-input{
|
||||||
|
padding: 0 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -145,3 +145,10 @@ export function formattedAmount(cents, currency) {
|
|||||||
|
|
||||||
export const ConditionalWrapper = ({ condition, wrapper, children }) =>
|
export const ConditionalWrapper = ({ condition, wrapper, children }) =>
|
||||||
condition ? wrapper(children) : children;
|
condition ? wrapper(children) : children;
|
||||||
|
|
||||||
|
export const checkRequiredProperties = (obj, properties) => {
|
||||||
|
return properties.some((prop) => {
|
||||||
|
const value = obj[prop];
|
||||||
|
return (value === '' || value === null || value === undefined);
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -33,6 +33,11 @@ export default {
|
|||||||
'code': {
|
'code': {
|
||||||
column: 'code',
|
column: 'code',
|
||||||
},
|
},
|
||||||
|
'root_type': {
|
||||||
|
column: 'account_type_id',
|
||||||
|
relation: 'account_types.id',
|
||||||
|
relationColumn: 'account_types.root_type',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'items': {
|
'items': {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ exports.up = function (knex) {
|
|||||||
table.text('description');
|
table.text('description');
|
||||||
table.boolean('active').defaultTo(true);
|
table.boolean('active').defaultTo(true);
|
||||||
table.integer('index').unsigned();
|
table.integer('index').unsigned();
|
||||||
|
table.boolean('predefined').defaultTo(false);
|
||||||
table.timestamps();
|
table.timestamps();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return knex.seed.run({
|
return knex.seed.run({
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ exports.up = (knex) => {
|
|||||||
table.increments();
|
table.increments();
|
||||||
table.string('name');
|
table.string('name');
|
||||||
table.string('normal');
|
table.string('normal');
|
||||||
|
table.string('root_type');
|
||||||
table.boolean('balance_sheet');
|
table.boolean('balance_sheet');
|
||||||
table.boolean('income_sheet');
|
table.boolean('income_sheet');
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ exports.seed = (knex) => {
|
|||||||
id: 1,
|
id: 1,
|
||||||
name: 'Fixed Asset',
|
name: 'Fixed Asset',
|
||||||
normal: 'debit',
|
normal: 'debit',
|
||||||
|
root_type: 'asset',
|
||||||
balance_sheet: true,
|
balance_sheet: true,
|
||||||
income_sheet: false,
|
income_sheet: false,
|
||||||
},
|
},
|
||||||
@@ -16,6 +17,7 @@ exports.seed = (knex) => {
|
|||||||
id: 2,
|
id: 2,
|
||||||
name: 'Current Asset',
|
name: 'Current Asset',
|
||||||
normal: 'debit',
|
normal: 'debit',
|
||||||
|
root_type: 'asset',
|
||||||
balance_sheet: true,
|
balance_sheet: true,
|
||||||
income_sheet: false,
|
income_sheet: false,
|
||||||
},
|
},
|
||||||
@@ -23,6 +25,7 @@ exports.seed = (knex) => {
|
|||||||
id: 3,
|
id: 3,
|
||||||
name: 'Long Term Liability',
|
name: 'Long Term Liability',
|
||||||
normal: 'credit',
|
normal: 'credit',
|
||||||
|
root_type: 'liability',
|
||||||
balance_sheet: false,
|
balance_sheet: false,
|
||||||
income_sheet: true,
|
income_sheet: true,
|
||||||
},
|
},
|
||||||
@@ -30,6 +33,7 @@ exports.seed = (knex) => {
|
|||||||
id: 4,
|
id: 4,
|
||||||
name: 'Current Liability',
|
name: 'Current Liability',
|
||||||
normal: 'credit',
|
normal: 'credit',
|
||||||
|
root_type: 'liability',
|
||||||
balance_sheet: false,
|
balance_sheet: false,
|
||||||
income_sheet: true,
|
income_sheet: true,
|
||||||
},
|
},
|
||||||
@@ -37,6 +41,7 @@ exports.seed = (knex) => {
|
|||||||
id: 5,
|
id: 5,
|
||||||
name: 'Equity',
|
name: 'Equity',
|
||||||
normal: 'credit',
|
normal: 'credit',
|
||||||
|
root_type: 'equity',
|
||||||
balance_sheet: false,
|
balance_sheet: false,
|
||||||
income_sheet: true,
|
income_sheet: true,
|
||||||
},
|
},
|
||||||
@@ -44,6 +49,7 @@ exports.seed = (knex) => {
|
|||||||
id: 6,
|
id: 6,
|
||||||
name: 'Expense',
|
name: 'Expense',
|
||||||
normal: 'debit',
|
normal: 'debit',
|
||||||
|
root_type: 'expense',
|
||||||
balance_sheet: false,
|
balance_sheet: false,
|
||||||
income_sheet: true,
|
income_sheet: true,
|
||||||
},
|
},
|
||||||
@@ -51,6 +57,7 @@ exports.seed = (knex) => {
|
|||||||
id: 7,
|
id: 7,
|
||||||
name: 'Income',
|
name: 'Income',
|
||||||
normal: 'credit',
|
normal: 'credit',
|
||||||
|
root_type: 'income',
|
||||||
balance_sheet: false,
|
balance_sheet: false,
|
||||||
income_sheet: true,
|
income_sheet: true,
|
||||||
},
|
},
|
||||||
@@ -58,6 +65,7 @@ exports.seed = (knex) => {
|
|||||||
id: 8,
|
id: 8,
|
||||||
name: 'Accounts Receivable',
|
name: 'Accounts Receivable',
|
||||||
normal: 'debit',
|
normal: 'debit',
|
||||||
|
root_type: 'asset',
|
||||||
balance_sheet: true,
|
balance_sheet: true,
|
||||||
income_sheet: false,
|
income_sheet: false,
|
||||||
},
|
},
|
||||||
@@ -65,6 +73,7 @@ exports.seed = (knex) => {
|
|||||||
id: 9,
|
id: 9,
|
||||||
name: 'Accounts Payable',
|
name: 'Accounts Payable',
|
||||||
normal: 'credit',
|
normal: 'credit',
|
||||||
|
root_type: 'liability',
|
||||||
balance_sheet: true,
|
balance_sheet: true,
|
||||||
income_sheet: false,
|
income_sheet: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,20 +10,22 @@ exports.seed = (knex) => {
|
|||||||
name: 'Petty Cash',
|
name: 'Petty Cash',
|
||||||
account_type_id: 2,
|
account_type_id: 2,
|
||||||
parent_account_id: null,
|
parent_account_id: null,
|
||||||
code: '10000',
|
code: '1000',
|
||||||
description: '',
|
description: '',
|
||||||
active: 1,
|
active: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Bank',
|
name: 'Bank',
|
||||||
account_type_id: 2,
|
account_type_id: 2,
|
||||||
parent_account_id: null,
|
parent_account_id: null,
|
||||||
code: '20000',
|
code: '2000',
|
||||||
description: '',
|
description: '',
|
||||||
active: 1,
|
active: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
@@ -34,6 +36,7 @@ exports.seed = (knex) => {
|
|||||||
description: '',
|
description: '',
|
||||||
active: 1,
|
active: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
@@ -44,6 +47,7 @@ exports.seed = (knex) => {
|
|||||||
description: '',
|
description: '',
|
||||||
active: 1,
|
active: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
@@ -54,6 +58,7 @@ exports.seed = (knex) => {
|
|||||||
description: '',
|
description: '',
|
||||||
active: 1,
|
active: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
@@ -64,6 +69,7 @@ exports.seed = (knex) => {
|
|||||||
description: '',
|
description: '',
|
||||||
active: 1,
|
active: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 7,
|
id: 7,
|
||||||
@@ -74,6 +80,7 @@ exports.seed = (knex) => {
|
|||||||
description: '',
|
description: '',
|
||||||
active: 1,
|
active: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 8,
|
id: 8,
|
||||||
@@ -84,6 +91,7 @@ exports.seed = (knex) => {
|
|||||||
description: '',
|
description: '',
|
||||||
active: 1,
|
active: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 9,
|
id: 9,
|
||||||
@@ -94,6 +102,7 @@ exports.seed = (knex) => {
|
|||||||
description: '',
|
description: '',
|
||||||
active: 1,
|
active: 1,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
predefined: 1,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,15 @@ exports.seed = function(knex) {
|
|||||||
{ id: 2, label_name: 'Code', key: 'code', data_type: '', active: 1, predefined: 1 },
|
{ id: 2, label_name: 'Code', key: 'code', data_type: '', active: 1, predefined: 1 },
|
||||||
{ id: 3, label_name: 'Account Type', key: 'account_type_id', data_type: '', active: 1, predefined: 1 },
|
{ id: 3, label_name: 'Account Type', key: 'account_type_id', data_type: '', active: 1, predefined: 1 },
|
||||||
{ id: 4, label_name: 'Description', key: 'description', data_type: '', active: 1, predefined: 1 },
|
{ id: 4, label_name: 'Description', key: 'description', data_type: '', active: 1, predefined: 1 },
|
||||||
|
{ id: 5, label_name: 'Account Normal', key: 'normal', data_type: 'string', active: 1, predefined: 1 },
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
label_name: 'Root Account Type',
|
||||||
|
key: 'root_account_type',
|
||||||
|
data_type: 'string',
|
||||||
|
active: 1,
|
||||||
|
predefined: 1,
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,10 +40,19 @@ exports.seed = (knex) => {
|
|||||||
predefined: 1,
|
predefined: 1,
|
||||||
columnable: true,
|
columnable: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
resource_id: 1,
|
||||||
|
label_name: 'Root type',
|
||||||
|
data_type: 'textbox',
|
||||||
|
key: 'root_type',
|
||||||
|
predefined: 1,
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
|
||||||
// Expenses
|
// Expenses
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 7,
|
||||||
resource_id: 3,
|
resource_id: 3,
|
||||||
label_name: 'Date',
|
label_name: 'Date',
|
||||||
data_type: 'date',
|
data_type: 'date',
|
||||||
@@ -51,7 +60,7 @@ exports.seed = (knex) => {
|
|||||||
columnable: true,
|
columnable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 7,
|
id: 8,
|
||||||
resource_id: 3,
|
resource_id: 3,
|
||||||
label_name: 'Expense Account',
|
label_name: 'Expense Account',
|
||||||
data_type: 'options',
|
data_type: 'options',
|
||||||
@@ -59,7 +68,7 @@ exports.seed = (knex) => {
|
|||||||
columnable: true,
|
columnable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 8,
|
id: 9,
|
||||||
resource_id: 3,
|
resource_id: 3,
|
||||||
label_name: 'Payment Account',
|
label_name: 'Payment Account',
|
||||||
data_type: 'options',
|
data_type: 'options',
|
||||||
@@ -67,7 +76,7 @@ exports.seed = (knex) => {
|
|||||||
columnable: true,
|
columnable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 9,
|
id: 10,
|
||||||
resource_id: 3,
|
resource_id: 3,
|
||||||
label_name: 'Amount',
|
label_name: 'Amount',
|
||||||
data_type: 'number',
|
data_type: 'number',
|
||||||
@@ -77,7 +86,7 @@ exports.seed = (knex) => {
|
|||||||
|
|
||||||
// Items
|
// Items
|
||||||
{
|
{
|
||||||
id: 10,
|
id: 11,
|
||||||
resource_id: 2,
|
resource_id: 2,
|
||||||
label_name: 'Name',
|
label_name: 'Name',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
@@ -86,7 +95,7 @@ exports.seed = (knex) => {
|
|||||||
columnable: true,
|
columnable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 11,
|
id: 12,
|
||||||
resource_id: 2,
|
resource_id: 2,
|
||||||
label_name: 'Type',
|
label_name: 'Type',
|
||||||
key: 'type',
|
key: 'type',
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ exports.seed = (knex) => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
// Inserts seed entries
|
// Inserts seed entries
|
||||||
return knex('view_roles').insert([
|
return knex('view_roles').insert([
|
||||||
{id: 1, field_id: 3, comparator: 'equals', value: '', view_id: 1},
|
{ id: 1, field_id: 6, comparator: 'equals', value: 'asset', view_id: 1 },
|
||||||
{id: 2, field_id: 3, comparator: 'equals', value: '', view_id: 2},
|
{ id: 2, field_id: 6, comparator: 'equals', value: 'liability', view_id: 2 },
|
||||||
{id: 3, field_id: 3, comparator: 'equals', value: '', view_id: 3},
|
{ id: 3, field_id: 6, comparator: 'equals', value: 'equity', view_id: 3 },
|
||||||
{id: 4, field_id: 3, comparator: 'equals', value: '', view_id: 4},
|
{ id: 4, field_id: 6, comparator: 'equals', value: 'income', view_id: 4 },
|
||||||
{id: 5, field_id: 3, comparator: 'equals', value: '', view_id: 5},
|
{ id: 5, field_id: 6, comparator: 'equals', value: 'expense', view_id: 5 },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -212,6 +212,11 @@ export default {
|
|||||||
if (!account) {
|
if (!account) {
|
||||||
return res.boom.notFound();
|
return res.boom.notFound();
|
||||||
}
|
}
|
||||||
|
if (account.predefined) {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'ACCOUNT.PREDEFINED' , code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
const accountTransactions = await AccountTransaction.query()
|
const accountTransactions = await AccountTransaction.query()
|
||||||
.where('account_id', account.id);
|
.where('account_id', account.id);
|
||||||
|
|
||||||
@@ -289,7 +294,6 @@ export default {
|
|||||||
const dynamicFilter = new DynamicFilter(Account.tableName);
|
const dynamicFilter = new DynamicFilter(Account.tableName);
|
||||||
|
|
||||||
if (filter.column_sort_by) {
|
if (filter.column_sort_by) {
|
||||||
console.log(filter);
|
|
||||||
if (resourceFieldsKeys.indexOf(filter.column_sort_by) === -1) {
|
if (resourceFieldsKeys.indexOf(filter.column_sort_by) === -1) {
|
||||||
errorReasons.push({ type: 'COLUMN.SORT.ORDER.NOT.FOUND', code: 300 });
|
errorReasons.push({ type: 'COLUMN.SORT.ORDER.NOT.FOUND', code: 300 });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user