mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
feat: fix a bunch of bugs.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
@@ -29,6 +29,8 @@ function AccountsActionsBar({
|
||||
getResourceFields,
|
||||
addAccountsTableQueries,
|
||||
onFilterChanged,
|
||||
onBulkDelete,
|
||||
onBulkArchive,
|
||||
}) {
|
||||
const {path} = useRouteMatch();
|
||||
const onClickNewAccount = () => { openDialog('account-form', {}); };
|
||||
@@ -51,6 +53,15 @@ function AccountsActionsBar({
|
||||
onFilterChanged && onFilterChanged(filterConditions);
|
||||
},
|
||||
});
|
||||
|
||||
const handleBulkArchive = useCallback(() => {
|
||||
onBulkArchive && onBulkArchive(selectedRows.map(r => r.id));
|
||||
}, [onBulkArchive, selectedRows]);
|
||||
|
||||
const handleBulkDelete = useCallback(() => {
|
||||
onBulkDelete && onBulkDelete(selectedRows.map(r => r.id));
|
||||
}, [onBulkDelete, selectedRows]);
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -77,6 +88,7 @@ function AccountsActionsBar({
|
||||
onClick={onClickNewAccount}
|
||||
/>
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={filterDropdown}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}>
|
||||
@@ -92,6 +104,7 @@ function AccountsActionsBar({
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='archive' iconSize={15} />}
|
||||
text='Archive'
|
||||
onClick={handleBulkArchive}
|
||||
/>
|
||||
)}
|
||||
{hasSelectedRows && (
|
||||
@@ -100,6 +113,7 @@ function AccountsActionsBar({
|
||||
icon={<Icon icon='trash' iconSize={15} />}
|
||||
text='Delete'
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleBulkDelete}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
|
||||
@@ -19,6 +19,7 @@ import ViewConnect from 'connectors/View.connector';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import DataTable from 'components/DataTable';
|
||||
import Money from 'components/Money';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
function AccountsDataTable({
|
||||
accounts,
|
||||
@@ -31,9 +32,16 @@ function AccountsDataTable({
|
||||
setTopbarEditView,
|
||||
accountsLoading,
|
||||
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(() => {
|
||||
const viewMeta = getViewItem(customViewId);
|
||||
@@ -52,12 +60,20 @@ function AccountsDataTable({
|
||||
openDialog('account-form', { action: 'edit', id: account.id });
|
||||
}, [openDialog]);
|
||||
|
||||
const actionMenuList = useCallback(account => (
|
||||
const handleNewParentAccount = useCallback((account) => {
|
||||
openDialog('account-form', { action: 'new_child', id: account.id });
|
||||
}, [openDialog]);
|
||||
|
||||
const actionMenuList = useCallback((account) => (
|
||||
<Menu>
|
||||
<MenuItem text='View Details' />
|
||||
<MenuDivider />
|
||||
<MenuItem text='Edit Account' onClick={handleEditAccount(account)} />
|
||||
<MenuItem text='New Account' />
|
||||
<MenuItem
|
||||
text='Edit Account'
|
||||
onClick={handleEditAccount(account)} />
|
||||
<MenuItem
|
||||
text='New Account'
|
||||
onClick={() => handleNewParentAccount(account)} />
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text='Inactivate Account'
|
||||
@@ -152,17 +168,22 @@ function AccountsDataTable({
|
||||
onFetchData && onFetchData(...params);
|
||||
}, []);
|
||||
|
||||
const handleSelectedRowsChange = useCallback((selectedRows) => {
|
||||
onSelectedRowsChange && onSelectedRowsChange(selectedRows.map(s => s.original));
|
||||
}, [onSelectedRowsChange]);
|
||||
|
||||
return (
|
||||
<LoadingIndicator loading={accountsLoading} spinnerSize={30}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={accounts}
|
||||
onFetchData={handleDatatableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={selectionColumn}
|
||||
expandable={true}
|
||||
treeGraph={true} />
|
||||
</LoadingIndicator>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={accounts}
|
||||
onFetchData={handleDatatableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={selectionColumn}
|
||||
expandable={true}
|
||||
treeGraph={true}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
loading={accountsLoading && !initialMount}
|
||||
spinnerProps={{size: 30}} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import PrivateRoute from 'components/PrivateRoute';
|
||||
import Authentication from 'components/Authentication';
|
||||
import Dashboard from 'components/Dashboard/Dashboard';
|
||||
import { isAuthenticated } from 'store/authentication/authentication.reducer'
|
||||
import Progress from 'components/NProgress/Progress';
|
||||
import messages from 'lang/en';
|
||||
import 'style/App.scss';
|
||||
|
||||
@@ -22,6 +23,8 @@ function App(props) {
|
||||
<PrivateRoute isAuthenticated={props.isAuthorized} component={Dashboard} />
|
||||
</Router>
|
||||
</div>
|
||||
|
||||
<Progress isAnimating={props.isLoading} />
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
@@ -37,5 +40,6 @@ App.propTypes = {
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isAuthorized: isAuthenticated(state),
|
||||
isLoading: !!state.dashboard.requestsLoading,
|
||||
});
|
||||
export default connect(mapStateToProps)(App);
|
||||
@@ -6,7 +6,7 @@ export default function DashboardInsider({
|
||||
loading,
|
||||
children,
|
||||
name,
|
||||
mount = true,
|
||||
mount = false,
|
||||
}) {
|
||||
return (
|
||||
<div className={classnames({
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React, {useEffect, useMemo, useCallback} from 'react';
|
||||
import React, {useEffect, useRef, useCallback} from 'react';
|
||||
import {
|
||||
useTable,
|
||||
useExpanded,
|
||||
useRowSelect,
|
||||
usePagination,
|
||||
useResizeColumns,
|
||||
useAsyncDebounce,
|
||||
useSortBy,
|
||||
useFlexLayout
|
||||
} from 'react-table'
|
||||
import {Checkbox} from '@blueprintjs/core';
|
||||
} from 'react-table';
|
||||
import { Checkbox, Spinner } from '@blueprintjs/core';
|
||||
import classnames from 'classnames';
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { ConditionalWrapper } from 'utils';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
const IndeterminateCheckbox = React.forwardRef(
|
||||
({ indeterminate, ...rest }, ref) => {
|
||||
@@ -40,6 +40,8 @@ export default function DataTable({
|
||||
payload,
|
||||
expandable = false,
|
||||
expandToggleColumn = 2,
|
||||
noInitialFetch = false,
|
||||
spinnerProps = { size: 40 },
|
||||
}) {
|
||||
const {
|
||||
getTableProps,
|
||||
@@ -62,7 +64,7 @@ export default function DataTable({
|
||||
isAllRowsExpanded,
|
||||
|
||||
// Get the state from the instance
|
||||
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
||||
state: { pageIndex, pageSize, sortBy, selectedRowIds, selectedRows },
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
@@ -115,12 +117,22 @@ export default function DataTable({
|
||||
])
|
||||
}
|
||||
);
|
||||
|
||||
const isInitialMount = useRef(noInitialFetch);
|
||||
|
||||
// When these table states change, fetch new data!
|
||||
useEffect(() => {
|
||||
onFetchData && onFetchData({ pageIndex, pageSize, sortBy })
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
} else {
|
||||
onFetchData && onFetchData({ pageIndex, pageSize, sortBy })
|
||||
}
|
||||
}, [pageIndex, pageSize, sortBy]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
onSelectedRowsChange && onSelectedRowsChange(selectedFlatRows);
|
||||
}, [selectedRowIds, onSelectedRowsChange]);
|
||||
|
||||
// Renders table cell.
|
||||
const RenderCell = useCallback(({ row, cell, index }) => (
|
||||
<ConditionalWrapper
|
||||
@@ -188,13 +200,18 @@ export default function DataTable({
|
||||
{RenderVirtualizedRows}
|
||||
</FixedSizeList>
|
||||
) : RenderPage();
|
||||
}, [fixedSizeHeight, rows, fixedItemSize, virtualizedRows, RenderVirtualizedRows, RenderPage]);
|
||||
}, [fixedSizeHeight, rows, fixedItemSize, virtualizedRows,
|
||||
RenderVirtualizedRows, RenderPage]);
|
||||
|
||||
return (
|
||||
<div className={classnames(
|
||||
'bigcapital-datatable',
|
||||
className,
|
||||
{'has-sticky-header': stickyHeader, 'is-expandable': expandable})}>
|
||||
{
|
||||
'has-sticky-header': stickyHeader,
|
||||
'is-expandable': expandable,
|
||||
'has-virtualized-rows': virtualizedRows,
|
||||
})}>
|
||||
<div {...getTableProps()} className="table">
|
||||
<div className="thead">
|
||||
{headerGroups.map(headerGroup => (
|
||||
@@ -240,13 +257,16 @@ export default function DataTable({
|
||||
))}
|
||||
</div>
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
{ RenderTBody() }
|
||||
|
||||
{ (page.length === 0) && (
|
||||
{ !loading && RenderTBody() }
|
||||
|
||||
{ !loading && (page.length === 0) && (
|
||||
<div className={'tr no-results'}>
|
||||
<div class="td">{ noResults }</div>
|
||||
</div>
|
||||
)}
|
||||
{ loading && (
|
||||
<div class="loading"><Spinner size={spinnerProps.size} /></div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useMemo} from 'react';
|
||||
import React, {useEffect, useMemo, useCallback, useRef} from 'react';
|
||||
import {
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
@@ -10,67 +10,94 @@ import {
|
||||
import { useFormik } from 'formik';
|
||||
import { isEqual } from 'lodash';
|
||||
import { usePrevious } from 'react-use';
|
||||
import { debounce } from 'lodash';
|
||||
import Icon from 'components/Icon';
|
||||
import { checkRequiredProperties } from 'utils';
|
||||
|
||||
export default function FilterDropdown({
|
||||
fields,
|
||||
onFilterChange,
|
||||
}) {
|
||||
const conditionalsItems = [
|
||||
const conditionalsItems = useMemo(() => [
|
||||
{ value: 'and', label: 'AND' },
|
||||
{ value: 'or', label: 'OR' },
|
||||
];
|
||||
const resourceFields = [
|
||||
], []);
|
||||
|
||||
const resourceFields = useMemo(() => [
|
||||
...fields.map((field) => ({ value: field.key, label: field.label_name, })),
|
||||
];
|
||||
const compatatorsItems = [
|
||||
], [fields]);
|
||||
|
||||
const compatatorsItems = useMemo(() => [
|
||||
{value: '', label: 'Select a compatator'},
|
||||
{value: 'equals', label: 'Equals'},
|
||||
{value: 'not_equal', label: 'Not Equal'},
|
||||
{value: 'contain', label: 'Contain'},
|
||||
{value: 'not_contain', label: 'Not Contain'},
|
||||
];
|
||||
const defaultFilterCondition = {
|
||||
], []);
|
||||
|
||||
const defaultFilterCondition = useMemo(() => ({
|
||||
condition: 'and',
|
||||
field_key: fields.length > 0 ? fields[0].key : '',
|
||||
compatator: 'equals',
|
||||
value: '',
|
||||
};
|
||||
const formik = useFormik({
|
||||
}), [fields]);
|
||||
|
||||
const {
|
||||
setFieldValue,
|
||||
getFieldProps,
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
} = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: {
|
||||
conditions: [ defaultFilterCondition ],
|
||||
},
|
||||
});
|
||||
|
||||
const onClickNewFilter = () => {
|
||||
formik.setFieldValue('conditions', [
|
||||
...formik.values.conditions, defaultFilterCondition,
|
||||
const onClickNewFilter = useCallback(() => {
|
||||
setFieldValue('conditions', [
|
||||
...values.conditions, defaultFilterCondition,
|
||||
]);
|
||||
};
|
||||
}, [values, defaultFilterCondition, setFieldValue]);
|
||||
|
||||
const filteredFilterConditions = useMemo(() => {
|
||||
return formik.values.conditions.filter(condition => !!condition.value);
|
||||
}, [formik.values.conditions]);
|
||||
const requiredProps = ['field_key', 'condition', 'compatator', 'value'];
|
||||
|
||||
return values.conditions
|
||||
.filter((condition) =>
|
||||
!checkRequiredProperties(condition, requiredProps));
|
||||
}, [values.conditions]);
|
||||
|
||||
const prevConditions = usePrevious(filteredFilterConditions);
|
||||
|
||||
const onClickRemoveCondition = (index) => () => {
|
||||
if (formik.values.conditions.length === 1) { return; }
|
||||
|
||||
const conditions = [ ...formik.values.conditions ];
|
||||
conditions.splice(index, 1);
|
||||
formik.setFieldValue('conditions', [ ...conditions ]);
|
||||
}
|
||||
|
||||
const onFilterChangeThrottled = useRef(debounce((conditions) => {
|
||||
onFilterChange && onFilterChange(conditions);
|
||||
}, 1000));
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEqual(filteredFilterConditions, prevConditions)) {
|
||||
onFilterChange(filteredFilterConditions);
|
||||
if (!isEqual(prevConditions, filteredFilterConditions) && prevConditions) {
|
||||
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 (
|
||||
<div class="filter-dropdown">
|
||||
<div class="filter-dropdown__body">
|
||||
{formik.values.conditions.map((condition, index) => (
|
||||
{values.conditions.map((condition, index) => (
|
||||
<div class="filter-dropdown__condition">
|
||||
<FormGroup
|
||||
className={'form-group--condition'}>
|
||||
@@ -78,7 +105,7 @@ export default function FilterDropdown({
|
||||
options={conditionalsItems}
|
||||
className={Classes.FILL}
|
||||
disabled={index > 1}
|
||||
{...formik.getFieldProps(`conditions[${index}].condition`)} />
|
||||
{...getFieldProps(`conditions[${index}].condition`)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
@@ -87,7 +114,7 @@ export default function FilterDropdown({
|
||||
options={resourceFields}
|
||||
value={1}
|
||||
className={Classes.FILL}
|
||||
{...formik.getFieldProps(`conditions[${index}].field_key`)} />
|
||||
{...getFieldProps(`conditions[${index}].field_key`)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
@@ -95,14 +122,14 @@ export default function FilterDropdown({
|
||||
<HTMLSelect
|
||||
options={compatatorsItems}
|
||||
className={Classes.FILL}
|
||||
{...formik.getFieldProps(`conditions[${index}].compatator`)} />
|
||||
{...getFieldProps(`conditions[${index}].compatator`)} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
className={'form-group--value'}>
|
||||
<InputGroup
|
||||
placeholder="Value"
|
||||
{...formik.getFieldProps(`conditions[${index}].value`)} />
|
||||
{...getFieldProps(`conditions[${index}].value`)} />
|
||||
</FormGroup>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -39,6 +39,8 @@ export default class Icon extends React.Component{
|
||||
color,
|
||||
htmlTitle,
|
||||
iconSize = Icon.SIZE_STANDARD,
|
||||
height,
|
||||
width,
|
||||
intent,
|
||||
title = icon,
|
||||
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 viewBox = iconPath.viewBox;
|
||||
|
||||
const computedHeight = height || iconSize;
|
||||
const computedWidth = width || iconSize;
|
||||
|
||||
return React.createElement(
|
||||
tagName,
|
||||
{
|
||||
@@ -64,7 +69,7 @@ export default class Icon extends React.Component{
|
||||
className: classes,
|
||||
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>}
|
||||
{paths}
|
||||
</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 appMeta from 'config/app';
|
||||
import Icon from 'components/Icon';
|
||||
|
||||
export default function() {
|
||||
return (
|
||||
<div className="sidebar__head">
|
||||
<div className="sidebar__head-logo">
|
||||
|
||||
</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>
|
||||
<Icon icon={'bigcapital'} width={140} height={28} className="bigcapital--alt" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
fetchAccountsTable,
|
||||
submitAccount,
|
||||
fetchAccount,
|
||||
deleteBulkAccounts,
|
||||
} from 'store/accounts/accounts.actions';
|
||||
import {
|
||||
getAccountsItems,
|
||||
@@ -26,6 +27,7 @@ const mapStateToProps = (state, props) => ({
|
||||
|
||||
tableQuery: state.accounts.tableQuery,
|
||||
accountsLoading: state.accounts.loading,
|
||||
accountErrors: state.accounts.errors,
|
||||
|
||||
getAccountById: (id) => getItemById(state.accounts.items, id),
|
||||
});
|
||||
@@ -38,6 +40,7 @@ const mapActionsToProps = (dispatch) => ({
|
||||
requestInactiveAccount: (id) => dispatch(inactiveAccount({ id })),
|
||||
requestFetchAccount: (id) => dispatch(fetchAccount({ id })),
|
||||
requestFetchAccountsTable: (query = {}) => dispatch(fetchAccountsTable({ query: { ...query } })),
|
||||
requestDeleteBulkAccounts: (ids) => dispatch(deleteBulkAccounts({ ids })),
|
||||
|
||||
changeCurrentView: (id) => dispatch({
|
||||
type: t.ACCOUNTS_SET_CURRENT_VIEW,
|
||||
@@ -50,7 +53,7 @@ const mapActionsToProps = (dispatch) => ({
|
||||
type: 'ACCOUNTS_TABLE_QUERIES_ADD', queries,
|
||||
}),
|
||||
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({
|
||||
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);
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import RegisterFromConnect from 'connectors/RegisterForm.connect';
|
||||
import ErrorMessage from 'components/ErrorMessage';
|
||||
import AppToaster from 'components/AppToaster';
|
||||
import { compose, regExpCollection } from 'utils';
|
||||
import { compose } from 'utils';
|
||||
|
||||
function Register({
|
||||
requestSubmitRegister,
|
||||
@@ -29,7 +29,6 @@ function Register({
|
||||
last_name: Yup.string().required(),
|
||||
email: Yup.string().email().required(),
|
||||
phone_number: Yup.string()
|
||||
.matches(regExpCollection.phoneNumber)
|
||||
.required(intl.formatMessage({ id: 'required' })),
|
||||
password: Yup.string()
|
||||
.min(4, 'Password has to be longer than 8 characters!')
|
||||
|
||||
@@ -2,8 +2,6 @@ import React, { useEffect, useState, useCallback } from 'react';
|
||||
import {
|
||||
Route,
|
||||
Switch,
|
||||
useParams,
|
||||
useRouteMatch
|
||||
} from 'react-router-dom';
|
||||
import useAsync from 'hooks/async';
|
||||
import { Alert, Intent } from '@blueprintjs/core';
|
||||
@@ -21,56 +19,71 @@ import { compose } from 'utils';
|
||||
|
||||
function AccountsChart({
|
||||
changePageTitle,
|
||||
requestFetchAccounts,
|
||||
requestDeleteAccount,
|
||||
requestInactiveAccount,
|
||||
fetchResourceViews,
|
||||
fetchResourceFields,
|
||||
getResourceFields,
|
||||
requestFetchAccountsTable,
|
||||
addAccountsTableQueries
|
||||
addAccountsTableQueries,
|
||||
requestDeleteBulkAccounts,
|
||||
setDashboardRequestLoading,
|
||||
setDashboardRequestCompleted,
|
||||
}) {
|
||||
const [state, setState] = useState({
|
||||
deleteAlertActive: false,
|
||||
restoreAlertActive: false,
|
||||
inactiveAlertActive: false,
|
||||
targetAccount: {},
|
||||
});
|
||||
const [deleteAccount, setDeleteAccount] = 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 () => {
|
||||
setDashboardRequestLoading();
|
||||
|
||||
await Promise.all([
|
||||
fetchResourceViews('accounts'),
|
||||
fetchResourceFields('accounts'),
|
||||
]);
|
||||
setDashboardRequestCompleted();
|
||||
});
|
||||
|
||||
// Fetch accounts list according to the given custom view id.
|
||||
const fetchAccountsHook = useAsync(async () => {
|
||||
setDashboardRequestLoading();
|
||||
|
||||
await Promise.all([
|
||||
requestFetchAccountsTable(),
|
||||
]);
|
||||
setDashboardRequestCompleted();
|
||||
}, false);
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle('Chart of Accounts');
|
||||
}, []);
|
||||
|
||||
}, [changePageTitle]);
|
||||
|
||||
// Handle click and cancel/confirm account delete
|
||||
const handleDeleteAccount = (account) => { setDeleteAccount(account); };
|
||||
|
||||
// handle cancel delete account alert.
|
||||
const handleCancelAccountDelete = () => { setDeleteAccount(false); };
|
||||
const handleCancelAccountDelete = useCallback(() => { setDeleteAccount(false); }, []);
|
||||
|
||||
// Handle confirm account delete
|
||||
const handleConfirmAccountDelete = useCallback(() => {
|
||||
requestDeleteAccount(deleteAccount.id).then(() => {
|
||||
setDeleteAccount(false);
|
||||
fetchAccountsHook.execute();
|
||||
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'
|
||||
});
|
||||
}
|
||||
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
||||
AppToaster.show({
|
||||
message: 'cannot_delete_account_has_associated_transactions'
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [deleteAccount]);
|
||||
}, [deleteAccount, requestDeleteAccount]);
|
||||
|
||||
// Handle cancel/confirm account inactive.
|
||||
const handleInactiveAccount = useCallback((account) => {
|
||||
@@ -91,13 +104,7 @@ function AccountsChart({
|
||||
});
|
||||
}, [inactiveAccount]);
|
||||
|
||||
/**
|
||||
* Handle cancel/confirm account restore.
|
||||
*/
|
||||
const handleCancelAccountRestore = () => {
|
||||
|
||||
};
|
||||
|
||||
|
||||
const handleEditAccount = (account) => {
|
||||
|
||||
};
|
||||
@@ -106,22 +113,43 @@ function AccountsChart({
|
||||
|
||||
};
|
||||
|
||||
const handleConfirmAccountRestore = (account) => {
|
||||
const handleBulkDelete = useCallback((accountsIds) => {
|
||||
setBulkDelete(accountsIds);
|
||||
}, [setBulkDelete]);
|
||||
|
||||
};
|
||||
const handleDeleteBulkAccounts = (accounts) => {
|
||||
const handleConfirmBulkDelete = useCallback(() => {
|
||||
requestDeleteBulkAccounts(bulkDelete).then(() => {
|
||||
setBulkDelete(false);
|
||||
AppToaster.show({ message: 'the_accounts_have_been_deleted' });
|
||||
}).catch((error) => {
|
||||
setBulkDelete(false);
|
||||
});
|
||||
}, [requestDeleteBulkAccounts, bulkDelete]);
|
||||
|
||||
};
|
||||
const handleCancelBulkDelete = useCallback(() => {
|
||||
setBulkDelete(false);
|
||||
}, []);
|
||||
|
||||
const handleSelectedRowsChange = (accounts) => {
|
||||
console.log(accounts);
|
||||
};
|
||||
const handleBulkArchive = useCallback((accounts) => {
|
||||
|
||||
}, []);
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = useCallback((accounts) => {
|
||||
setSelectedRows(accounts);
|
||||
}, [setSelectedRows]);
|
||||
|
||||
// Refetches accounts data table when current custom view changed.
|
||||
const handleFilterChanged = useCallback(() => {
|
||||
fetchAccountsHook.execute();
|
||||
}, [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 }) => {
|
||||
addAccountsTableQueries({
|
||||
...(sortBy.length > 0) ? {
|
||||
@@ -135,26 +163,29 @@ function AccountsChart({
|
||||
return (
|
||||
<DashboardInsider loading={fetchHook.pending} name={'accounts-chart'}>
|
||||
<DashboardActionsBar
|
||||
onFilterChanged={handleFilterChanged} />
|
||||
selectedRows={selectedRows}
|
||||
onFilterChanged={handleFilterChanged}
|
||||
onBulkDelete={handleBulkDelete}
|
||||
onBulkArchive={handleBulkArchive} />
|
||||
|
||||
<DashboardPageContent>
|
||||
<Switch>
|
||||
<Route
|
||||
exact={true}
|
||||
path={[
|
||||
'/dashboard/accounts/:custom_view_id/custom_view',
|
||||
'/dashboard/accounts'
|
||||
'/dashboard/accounts',
|
||||
]}>
|
||||
<AccountsViewsTabs
|
||||
onViewChanged={handleViewChanged}
|
||||
onDeleteBulkAccounts={handleDeleteBulkAccounts} />
|
||||
onViewChanged={handleViewChanged} />
|
||||
|
||||
<AccountsDataTable
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
onDeleteAccount={handleDeleteAccount}
|
||||
onInactiveAccount={handleInactiveAccount}
|
||||
onRestoreAccount={handleRestoreAccount}
|
||||
onEditAccount={handleEditAccount}
|
||||
onFetchData={handleFetchData} />
|
||||
onFetchData={handleFetchData}
|
||||
onSelectedRowsChange={handleSelectedRowsChange} />
|
||||
</Route>
|
||||
</Switch>
|
||||
|
||||
@@ -185,6 +216,20 @@ function AccountsChart({
|
||||
but it will become private to you.
|
||||
</p>
|
||||
</Alert>
|
||||
|
||||
<Alert
|
||||
cancelButtonText="Cancel"
|
||||
confirmButtonText="Delete"
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
isOpen={bulkDelete}
|
||||
onCancel={handleCancelBulkDelete}
|
||||
onConfirm={handleConfirmBulkDelete}>
|
||||
<p>
|
||||
Are you sure you want to move <b>filename</b> to Trash? You will be able to restore it later,
|
||||
but it will become private to you.
|
||||
</p>
|
||||
</Alert>
|
||||
</DashboardPageContent>
|
||||
</DashboardInsider>
|
||||
);
|
||||
|
||||
@@ -38,7 +38,7 @@ function AccountFormDialog({
|
||||
closeDialog,
|
||||
requestSubmitAccount,
|
||||
requestEditAccount,
|
||||
getAccountById
|
||||
getAccountById,
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
const accountFormValidationSchema = Yup.object().shape({
|
||||
@@ -57,12 +57,23 @@ function AccountFormDialog({
|
||||
}), []);
|
||||
|
||||
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(() =>
|
||||
payload.action === 'edit' ? getAccountById(payload.id) : null,
|
||||
[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
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
@@ -71,21 +82,22 @@ function AccountFormDialog({
|
||||
? editAccount : initialValues,
|
||||
},
|
||||
validationSchema: accountFormValidationSchema,
|
||||
onSubmit: (values, { setSubmitting }) => {
|
||||
onSubmit: (values, { setSubmitting, setErrors }) => {
|
||||
const exclude = ['subaccount'];
|
||||
|
||||
if (payload.action === 'edit') {
|
||||
requestEditAccount({
|
||||
payload: payload.id,
|
||||
form: { ...omit(values, exclude) }
|
||||
form: { ...omit(values, [...exclude, 'account_type_id']) }
|
||||
}).then(response => {
|
||||
closeDialog(name);
|
||||
AppToaster.show({
|
||||
message: 'the_account_has_been_edited'
|
||||
});
|
||||
setSubmitting(false);
|
||||
}).catch(() => {
|
||||
}).catch((errors) => {
|
||||
setSubmitting(false);
|
||||
setErrors(transformApiErrors(errors));
|
||||
});
|
||||
} else {
|
||||
requestSubmitAccount({ form: { ...omit(values, exclude) } }).then(response => {
|
||||
@@ -94,8 +106,9 @@ function AccountFormDialog({
|
||||
message: 'the_account_has_been_submit'
|
||||
});
|
||||
setSubmitting(false);
|
||||
}).catch(() => {
|
||||
}).catch((errors) => {
|
||||
setSubmitting(false);
|
||||
setErrors(transformApiErrors(errors));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -187,9 +200,7 @@ function AccountFormDialog({
|
||||
return (<span>{'Sub account?'} <Icon icon="info-circle" iconSize={12} /></span>);
|
||||
}, []);
|
||||
|
||||
const requiredSpan = useMemo(() => (
|
||||
<span class="required">*</span>
|
||||
), []);
|
||||
const requiredSpan = useMemo(() => (<span class="required">*</span>), []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -232,6 +243,7 @@ function AccountFormDialog({
|
||||
rightIcon='caret-down'
|
||||
text={selectedAccountType ?
|
||||
selectedAccountType.name : 'Select account type'}
|
||||
disabled={payload.action === 'edit'}
|
||||
/>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { omit } from 'lodash';
|
||||
import ApiService from 'services/ApiService';
|
||||
import t from 'store/types';
|
||||
|
||||
@@ -41,14 +42,20 @@ export const fetchAccountsList = ({ query } = {}) => {
|
||||
export const fetchAccountsTable = ({ query } = {}) => {
|
||||
return (dispatch, getState) =>
|
||||
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({
|
||||
type: t.ACCOUNTS_TABLE_LOADING,
|
||||
loading: true,
|
||||
});
|
||||
ApiService.get('accounts', { params: { ...pageQuery, ...query } })
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
dispatch({
|
||||
type: t.ACCOUNTS_PAGE_SET,
|
||||
accounts: response.data.accounts,
|
||||
@@ -64,7 +71,7 @@ export const fetchAccountsTable = ({ query } = {}) => {
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
@@ -89,9 +96,12 @@ export const fetchAccountsDataTable = ({ query }) => {
|
||||
export const submitAccount = ({ form }) => {
|
||||
return dispatch =>
|
||||
new Promise((resolve, reject) => {
|
||||
|
||||
ApiService.post('accounts', form)
|
||||
.then(response => {
|
||||
dispatch({ type: t.CLEAR_ACCOUNT_FORM_ERRORS });
|
||||
dispatch({
|
||||
type: t.ACCOUNT_ERRORS_CLEAR,
|
||||
});
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -99,11 +109,16 @@ export const submitAccount = ({ form }) => {
|
||||
const { data } = response;
|
||||
const { errors } = data;
|
||||
|
||||
dispatch({ type: t.CLEAR_ACCOUNT_FORM_ERRORS });
|
||||
dispatch({
|
||||
type: t.ACCOUNT_ERRORS_CLEAR,
|
||||
});
|
||||
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) {
|
||||
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) => {
|
||||
dispatch({ type: t.ACCOUNT_DELETE, id });
|
||||
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 }) => {
|
||||
return dispatch =>
|
||||
|
||||
@@ -7,11 +7,11 @@ const initialState = {
|
||||
views: {},
|
||||
accountsTypes: [],
|
||||
accountsById: {},
|
||||
accountFormErrors: [],
|
||||
datatableQuery: {},
|
||||
tableQuery: {},
|
||||
currentViewId: -1,
|
||||
selectedRows: [],
|
||||
loading: false,
|
||||
errors: [],
|
||||
};
|
||||
|
||||
const accountsReducer = createReducer(initialState, {
|
||||
@@ -34,8 +34,7 @@ const accountsReducer = createReducer(initialState, {
|
||||
state.views[viewId] = {
|
||||
...view,
|
||||
ids: action.accounts.map(i => i.id),
|
||||
};
|
||||
state.accounts = action.accounts;
|
||||
};
|
||||
},
|
||||
|
||||
[t.ACCOUNT_TYPES_LIST_SET]: (state, action) => {
|
||||
@@ -53,7 +52,8 @@ const accountsReducer = createReducer(initialState, {
|
||||
},
|
||||
|
||||
[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) => {
|
||||
@@ -63,6 +63,27 @@ const accountsReducer = createReducer(initialState, {
|
||||
[t.ACCOUNTS_TABLE_LOADING]: (state, action) => {
|
||||
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);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { pickItemsFromIds } from 'store/selectors';
|
||||
|
||||
export const getAccountsItems = (state, viewId) => {
|
||||
|
||||
const accountsView = state.accounts.views[viewId || -1];
|
||||
const accountsItems = state.accounts.items;
|
||||
|
||||
|
||||
@@ -17,4 +17,8 @@ export default {
|
||||
ACCOUNTS_TABLE_QUERIES_SET: 'ACCOUNTS_TABLE_QUERIES_SET',
|
||||
|
||||
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: '',
|
||||
dialogs: {},
|
||||
topbarEditViewId: 1,
|
||||
requestsLoading: 0,
|
||||
};
|
||||
|
||||
export default createReducer(initialState, {
|
||||
@@ -42,6 +43,15 @@ export default createReducer(initialState, {
|
||||
|
||||
[t.SET_TOPBAR_EDIT_VIEW]: (state, action) => {
|
||||
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',
|
||||
ALTER_DASHBOARD_PAGE_SUBTITLE: 'ALTER_DASHBOARD_PAGE_SUBTITLE',
|
||||
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) => {
|
||||
return items[id] || null;
|
||||
};
|
||||
|
||||
export const pickItemsFromIds = (items, ids) => {
|
||||
return Object.values(pick(items, ids));
|
||||
return at(items, ids).filter(i => i);
|
||||
}
|
||||
|
||||
export const getCurrentPageResults = (items, pages, pageNumber) => {
|
||||
|
||||
@@ -60,4 +60,16 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
.path-13{
|
||||
fill: #2d95fd;
|
||||
}
|
||||
}
|
||||
|
||||
.bigcapital--alt{
|
||||
|
||||
|
||||
svg{
|
||||
path,
|
||||
.path-13,
|
||||
.path-1{
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,9 +111,6 @@
|
||||
}
|
||||
|
||||
.tbody{
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
|
||||
.tr .td{
|
||||
border-bottom: 1px solid #E8E8E8;
|
||||
align-items: center;
|
||||
@@ -145,6 +142,10 @@
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
> .loading{
|
||||
padding-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.tr .th,
|
||||
@@ -175,8 +176,6 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +198,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.has-virtualized-rows{
|
||||
.tbody{
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&--financial-report{
|
||||
|
||||
.table {
|
||||
|
||||
@@ -210,13 +210,13 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__page-content{
|
||||
// padding: 22px;
|
||||
|
||||
|
||||
.bigcapital-datatable{
|
||||
|
||||
.table{
|
||||
@@ -229,6 +229,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard__loading-indicator{
|
||||
padding-top: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
&__preferences-topbar{
|
||||
|
||||
@@ -18,22 +18,14 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62);
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
&__head{
|
||||
padding: 16px 10px;
|
||||
padding: 16px 12px;
|
||||
|
||||
&-company-meta{
|
||||
&-logo{
|
||||
margin-top: 4px;
|
||||
|
||||
.company-name{
|
||||
font-size: 16px;
|
||||
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;
|
||||
svg{
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,14 @@
|
||||
margin-bottom: 0;
|
||||
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,4 +144,11 @@ export function formattedAmount(cents, currency) {
|
||||
}
|
||||
|
||||
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);
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user