feat(FinancialReports): add loading progress bar.

fix(preformance): Optimize preformance of virtualized list.
fix(preformance): Optimize financial reports preformance.
This commit is contained in:
a.bouhuolia
2021-03-16 17:27:27 +02:00
parent f1cf02c9df
commit 42230fe64b
73 changed files with 969 additions and 320 deletions

View File

@@ -19,7 +19,7 @@ function App({ locale }) {
const queryConfig = {
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
refetchOnWindowFocus: true,
staleTime: 30000,
},
},

View File

@@ -4,8 +4,6 @@ import { useQuery } from 'react-query';
import 'style/pages/Dashboard/Dashboard.scss';
import DashboardLoadingIndicator from './DashboardLoadingIndicator';
import Sidebar from 'components/Sidebar/Sidebar';
import DashboardContent from 'components/Dashboard/DashboardContent';
import DialogsContainer from 'components/DialogsContainer';
@@ -13,22 +11,15 @@ import PreferencesPage from 'components/Preferences/PreferencesPage';
import Search from 'containers/GeneralSearch/Search';
import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane';
import GlobalHotkeys from './GlobalHotkeys';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import DashboardProvider from './DashboardProvider';
import DrawersContainer from 'components/DrawersContainer';
import { compose } from 'utils';
/**
* Dashboard page.
*/
function Dashboard({
// #withSettings
requestFetchOptions,
}) {
const fetchOptions = useQuery(['options'], () => requestFetchOptions());
export default function Dashboard() {
return (
<DashboardLoadingIndicator isLoading={fetchOptions.isFetching}>
<DashboardProvider>
<Switch>
<Route path="/preferences">
<DashboardSplitPane>
@@ -49,8 +40,6 @@ function Dashboard({
<DialogsContainer />
<GlobalHotkeys />
<DrawersContainer />
</DashboardLoadingIndicator>
</DashboardProvider>
);
}
export default compose(withSettingsActions)(Dashboard);
}

View File

@@ -10,7 +10,7 @@ export default function DashboardLoadingIndicator({
className,
children,
}) {
return (
return (
<Choose>
<Choose.When condition={isLoading}>
<BigcapitalLoading />

View File

@@ -0,0 +1,16 @@
import React from 'react';
import DashboardLoadingIndicator from './DashboardLoadingIndicator';
import { useSettings } from 'hooks/query';
/**
* Dashboard provider.
*/
export default function DashboardProvider({ children }) {
const { isLoading } = useSettings();
return (
<DashboardLoadingIndicator isLoading={isLoading}>
{ children }
</DashboardLoadingIndicator>
)
}

View File

@@ -1,46 +1,21 @@
import React from 'react';
import { Switch, Route } from 'react-router';
import { useQuery } from 'react-query';
import Dashboard from 'components/Dashboard/Dashboard';
import SetupWizardPage from 'containers/Setup/WizardSetupPage';
import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator';
import withOrganizationActions from 'containers/Organization/withOrganizationActions';
import withSubscriptionsActions from 'containers/Subscriptions/withSubscriptionsActions';
import EnsureOrganizationIsReady from 'components/Guards/EnsureOrganizationIsReady';
import EnsureOrganizationIsNotReady from 'components/Guards/EnsureOrganizationIsNotReady';
import { compose } from 'utils';
import { PrivatePagesProvider } from './PrivatePagesProvider';
import 'style/pages/Dashboard/Dashboard.scss';
/**
* Dashboard inner private pages.
*/
function DashboardPrivatePages({
// #withOrganizationActions
requestAllOrganizations,
// #withSubscriptionsActions
requestFetchSubscriptions,
}) {
// Fetches all user's organizatins.
const fetchOrganizations = useQuery(
['organizations'], () => requestAllOrganizations(),
);
// Fetches organization subscriptions.
const fetchSuscriptions = useQuery(
['susbcriptions'], () => requestFetchSubscriptions(),
)
export default function DashboardPrivatePages() {
return (
<DashboardLoadingIndicator isLoading={
fetchOrganizations.isFetching ||
fetchSuscriptions.isFetching
}>
<PrivatePagesProvider>
<Switch>
<Route path={'/setup'}>
<EnsureOrganizationIsNotReady>
@@ -54,11 +29,6 @@ function DashboardPrivatePages({
</EnsureOrganizationIsReady>
</Route>
</Switch>
</DashboardLoadingIndicator>
</PrivatePagesProvider>
);
}
export default compose(
withOrganizationActions,
withSubscriptionsActions,
)(DashboardPrivatePages);
}

View File

@@ -0,0 +1,17 @@
import React from 'react';
import DashboardLoadingIndicator from 'components/Dashboard/DashboardLoadingIndicator';
import { useCurrentOrganization } from '../../hooks/query/organization';
/**
* Private pages provider.
*/
export function PrivatePagesProvider({ children }) {
// Fetches the current user's organization.
const { isLoading } = useCurrentOrganization();
return (
<DashboardLoadingIndicator isLoading={isLoading}>
{children}
</DashboardLoadingIndicator>
)
}

View File

@@ -62,7 +62,7 @@ export default function TableCell({
'cell-inner',
)}
style={{
'padding-left':
paddingLeft:
isExpandColumn && expandable
? `${depth * expandColumnSpace}rem`
: '',

View File

@@ -66,7 +66,7 @@ function TableHeaderGroup({ headerGroup }) {
return (
<div {...headerGroup.getHeaderGroupProps()} className="tr">
{headerGroup.headers.map((column, index) => (
<TableHeaderCell column={column} index={index} />
<TableHeaderCell key={index} column={column} index={index} />
))}
</div>
);
@@ -87,8 +87,8 @@ export default function TableHeader() {
return (
<ScrollSyncPane>
<div className="thead">
{headerGroups.map((headerGroup) => (
<TableHeaderGroup headerGroup={headerGroup} />
{headerGroups.map((headerGroup, index) => (
<TableHeaderGroup key={index} headerGroup={headerGroup} />
))}
<If condition={progressBarLoading}>
<MaterialProgressBar />

View File

@@ -1,23 +1,18 @@
import React, { useContext } from 'react';
import React, { useCallback, useContext } from 'react';
import { ContextMenu } from 'components';
import classNames from 'classnames';
import useContextMenu from 'react-use-context-menu';
import TableContext from './TableContext';
import { saveInvoke } from 'utils';
import { ContextMenu } from 'components';
import { saveInvoke, ConditionalWrapper } from 'utils';
/**
* Table row.
* Table row context wrapper.
*/
export default function TableRow({ row, className, style }) {
function TableRowContextMenu({ children, row }) {
// Table context.
const {
props: {
TableCellRenderer,
rowContextMenu,
rowClassNames,
ContextMenu: ContextMenuContent,
},
props: { ContextMenu: ContextMenuContent },
table,
} = useContext(TableContext);
@@ -32,33 +27,64 @@ export default function TableRow({ row, className, style }) {
collect: () => 'Title',
});
const handleClose = useCallback(() => {
setVisible(false);
}, [setVisible]);
return (
<div
{...row.getRowProps({
className: classNames(
'tr',
{
'is-expanded': row.isExpanded && row.canExpand,
},
saveInvoke(rowClassNames, row),
className,
),
style,
})}
{...bindTrigger}
>
{row.cells.map((cell, index) => (
<TableCellRenderer cell={cell} row={row} index={index + 1} />
))}
<div class="tr-context" {...bindTrigger}>
{children}
<ContextMenu
bindMenu={bindMenu}
isOpen={isVisible}
coords={coords}
onClosed={() => setVisible(false)}
onClosed={handleClose}
>
<ContextMenuContent {...table} row={row} />
</ContextMenu>
</div>
);
}
/**
* Table row.
*/
export default function TableRow({ row, className, style }) {
const {
props: {
TableCellRenderer,
rowClassNames,
ContextMenu: ContextMenuContent,
},
} = useContext(TableContext);
return (
<div
{...row.getRowProps({
className: classNames(
'tr',
{ 'is-expanded': row.isExpanded && row.canExpand },
saveInvoke(rowClassNames, row),
className,
),
style,
})}
>
<ConditionalWrapper
condition={ContextMenuContent}
wrapper={TableRowContextMenu}
row={row}
>
{row.cells.map((cell, index) => (
<TableCellRenderer
key={index}
cell={cell}
row={row}
index={index + 1}
/>
))}
</ConditionalWrapper>
</div>
);
}

View File

@@ -10,8 +10,8 @@ export default function TableRows() {
props: { TableRowRenderer, TableCellRenderer },
} = useContext(TableContext);
return page.map((row) => {
return page.map((row, index) => {
prepareRow(row);
return <TableRowRenderer row={row} TableCellRenderer={TableCellRenderer} />;
return <TableRowRenderer key={index} row={row} TableCellRenderer={TableCellRenderer} />;
});
}

View File

@@ -3,11 +3,13 @@ import { WindowScroller, AutoSizer, List } from 'react-virtualized';
import { CLASSES } from 'common/classes';
import TableContext from './TableContext';
/**
* Table virtualized list row.
*/
function TableVirtualizedListRow({
index,
isScrolling,
isVisible,
key,
style,
}) {
const {
@@ -18,7 +20,7 @@ function TableVirtualizedListRow({
const row = page[index];
prepareRow(row);
return <TableRowRenderer row={row} style={style} />;
return (<TableRowRenderer row={row} style={style} />);
}
/**
@@ -27,39 +29,38 @@ function TableVirtualizedListRow({
export default function TableVirtualizedListRows() {
const {
table: { page },
props: { vListrowHeight, vListOverscanRowCount }
props: { vListrowHeight, vListOverscanRowCount },
} = useContext(TableContext);
// Dashboard content pane.
const dashboardContentPane = document.querySelector(
const dashboardContentPane = React.useMemo(()=> document.querySelector(
`.${CLASSES.DASHBOARD_CONTENT_PANE}`,
);
), []);
const rowRenderer = React.useCallback(({ key, ...args }) => (
<TableVirtualizedListRow {...args} key={key} />
), []);
return (
<WindowScroller scrollElement={dashboardContentPane}>
{({ height, isScrolling, registerChild, onChildScroll, scrollTop }) => (
<div className={'WindowScrollerWrapper'}>
<AutoSizer disableHeight>
{({ width }) => (
<div ref={registerChild}>
<List
autoHeight={true}
className={'List'}
height={height}
isScrolling={isScrolling}
onScroll={onChildScroll}
overscanRowCount={vListOverscanRowCount}
rowCount={page.length}
rowHeight={vListrowHeight}
rowRenderer={({ ...args }) => {
return <TableVirtualizedListRow {...args} />;
}}
scrollTop={scrollTop}
width={width}
/>
</div>
)}
</AutoSizer>
</div>
{({ height, isScrolling, onChildScroll, scrollTop }) => (
<AutoSizer disableHeight>
{({ width }) => (
<List
autoHeight={true}
className={'List'}
height={height}
isScrolling={isScrolling}
onScroll={onChildScroll}
overscanRowCount={vListOverscanRowCount}
rowCount={page.length}
rowHeight={vListrowHeight}
rowRenderer={rowRenderer}
scrollTop={scrollTop}
width={width}
/>
)}
</AutoSizer>
)}
</WindowScroller>
);

View File

@@ -7,7 +7,6 @@ import 'style/pages/FinancialStatements/FinancialSheet.scss';
import { If, LoadingIndicator, MODIFIER } from 'components';
export default function FinancialSheet({
companyName,
sheetType,
@@ -57,49 +56,47 @@ export default function FinancialSheet({
'is-full-width': fullWidth,
})}
>
<LoadingIndicator loading={loading} spinnerSize={34} />
<div
className={classnames('financial-sheet__inner', {
'is-loading': loading,
})}
>
<If condition={!!companyName}>
<h1 class="financial-sheet__title">{companyName}</h1>
</If>
<If condition={!!sheetType}>
<h6 class="financial-sheet__sheet-type">{sheetType}</h6>
</If>
<div class="financial-sheet__date">
<If condition={asDate}>
<T id={'as'} /> {formattedAsDate}
{loading ? (
<LoadingIndicator loading={loading} spinnerSize={34} />
) : (
<div className={classnames('financial-sheet__inner')}>
<If condition={!!companyName}>
<h1 class="financial-sheet__title">{companyName}</h1>
</If>
<If condition={fromDate && toDate}>
<T id={'from'} /> {formattedFromDate} | <T id={'to'} />{' '}
{formattedToDate}
<If condition={!!sheetType}>
<h6 class="financial-sheet__sheet-type">{sheetType}</h6>
</If>
<div class="financial-sheet__date">
<If condition={asDate}>
<T id={'as'} /> {formattedAsDate}
</If>
<If condition={fromDate && toDate}>
<T id={'from'} /> {formattedFromDate} | <T id={'to'} />{' '}
{formattedToDate}
</If>
</div>
<div class="financial-sheet__table">{children}</div>
<div class="financial-sheet__accounting-basis">{accountingBasis}</div>
<div class="financial-sheet__footer">
<If condition={basisLabel}>
<span class="financial-sheet__basis">
<T id={'accounting_basis'} /> {basisLabel}
</span>
</If>
<If condition={currentDate}>
<span class="financial-sheet__current-date">
{moment().format('YYYY MMM DD HH:MM')}
</span>
</If>
</div>
</div>
<div class="financial-sheet__table">{children}</div>
<div class="financial-sheet__accounting-basis">{accountingBasis}</div>
<div class="financial-sheet__footer">
<If condition={basisLabel}>
<span class="financial-sheet__basis">
<T id={'accounting_basis'} /> {basisLabel}
</span>
</If>
<If condition={currentDate}>
<span class="financial-sheet__current-date">
{moment().format('YYYY MMM DD HH:MM')}
</span>
</If>
</div>
</div>
)}
</div>
);
}

View File

@@ -5,6 +5,9 @@ import { compose } from 'utils';
import withAuthentication from 'containers/Authentication/withAuthentication';
import withOrganization from 'containers/Organization/withOrganization';
/**
* Ensures organization is not ready.
*/
function EnsureOrganizationIsNotReady({
children,

View File

@@ -11,11 +11,7 @@ export default function PrivateRoute({ component: Component, ...rest }) {
{isAuthenticated ? (
<Component />
) : (
<Redirect
to={{
pathname: '/auth/login',
}}
/>
<Redirect to={{ pathname: '/auth/login' }} />
)}
</BodyClassName>
);

View File

@@ -5,7 +5,7 @@ const If = props => props.condition
? (props.render ? props.render() : props.children) : null;
If.propTypes = {
condition: PropTypes.bool.isRequired,
// condition: PropTypes.bool.isRequired,
children: PropTypes.node,
render: PropTypes.func
};

View File

@@ -53,6 +53,7 @@ import Drawer from './Drawer/Drawer';
import DrawerSuspense from './Drawer/DrawerSuspense';
import Postbox from './Postbox';
import AccountsSuggestField from './AccountsSuggestField';
import MaterialProgressBar from './MaterialProgressBar';
const Hint = FieldHint;
@@ -112,5 +113,6 @@ export {
Drawer,
DrawerSuspense,
Postbox,
AccountsSuggestField
AccountsSuggestField,
MaterialProgressBar
};