refactoring: migrating to react-query to manage service-side state.

This commit is contained in:
a.bouhuolia
2021-02-07 08:10:21 +02:00
parent e093be0663
commit adac2386bb
284 changed files with 8255 additions and 6610 deletions

View File

@@ -2,9 +2,7 @@ import React from 'react';
import { RawIntlProvider } from 'react-intl';
import { Router, Switch, Route } from 'react-router';
import { createBrowserHistory } from 'history';
import { ReactQueryConfigProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query-devtools';
import { QueryClientProvider, QueryClient } from 'react-query';
import 'style/App.scss';
import PrivateRoute from 'components/Guards/PrivateRoute';
@@ -17,14 +15,18 @@ function App({ locale }) {
const history = createBrowserHistory();
const queryConfig = {
queries: {
refetchOnWindowFocus: false,
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
};
const queryClient = new QueryClient(queryConfig);
return (
<RawIntlProvider value={intl}>
<div className="App">
<ReactQueryConfigProvider config={queryConfig}>
<QueryClientProvider client={queryClient}>
<div className="App">
<Router history={history}>
<Switch>
<Route path={'/auth'}>
@@ -38,9 +40,8 @@ function App({ locale }) {
</Router>
<GlobalErrors />
<ReactQueryDevtools />
</ReactQueryConfigProvider>
</div>
</div>
</QueryClientProvider>
</RawIntlProvider>
);
}

View File

@@ -7,7 +7,7 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
export default function CategoriesSelectList({
categoriesList,
categories,
selecetedCategoryId,
defaultSelectText = <T id={'select_category'} />,
onCategorySelected,
@@ -41,7 +41,7 @@ export default function CategoriesSelectList({
return (
<ListSelect
items={categoriesList}
items={categories}
selectedItemProp={'id'}
selectedItem={selecetedCategoryId}
textProp={'name'}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2016 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// import classNames from 'classnames';
import * as React from "react";
import * as ReactDOM from "react-dom";
// import { polyfill } from "react-lifecycles-compat";
import {
Popover,
Classes,
Position,
} from '@blueprintjs/core';
// import { IOverlayLifecycleProps } from "../overlay/overlay";
// import { Popover } from "../popover/popover";
// import { PopperModifiers } from "../popover/popoverSharedProps";
export interface IOffset {
left: number;
top: number;
}
interface IContextMenuState {
isOpen: boolean;
isDarkTheme: boolean;
menu?: JSX.Element;
offset?: IOffset;
onClose?: () => void;
}
const POPPER_MODIFIERS = {
preventOverflow: { boundariesElement: "viewport" },
};
const TRANSITION_DURATION = 100;
// type IContextMenuProps = IOverlayLifecycleProps;
/* istanbul ignore next */
export default class ContextMenu extends React.PureComponent {
public state: IContextMenuState = {
isDarkTheme: false,
isOpen: false,
};
public render() {
// prevent right-clicking in a context menu
const content = <div onContextMenu={this.cancelContextMenu}>{this.state.menu}</div>;
const popoverClassName = {};
// HACKHACK: workaround until we have access to Popper#scheduleUpdate().
// https://github.com/palantir/blueprint/issues/692
// Generate key based on offset so a new Popover instance is created
// when offset changes, to force recomputing position.
const key = this.state.offset === undefined ? "" : `${this.state.offset.left}x${this.state.offset.top}`;
// wrap the popover in a positioned div to make sure it is properly
// offset on the screen.
return (
<div className={Classes.CONTEXT_MENU_POPOVER_TARGET} style={this.state.offset}>
<Popover
{...this.props}
backdropProps={{ onContextMenu: this.handleBackdropContextMenu }}
content={content}
enforceFocus={false}
key={key}
hasBackdrop={true}
isOpen={this.state.isOpen}
minimal={true}
// modifiers={POPPER_MODIFIERS}
onInteraction={this.handlePopoverInteraction}
position={Position.RIGHT_TOP}
// popoverClassName={popoverClassName}
target={<div />}
transitionDuration={TRANSITION_DURATION}
/>
</div>
);
}
public show(menu: JSX.Element, offset: IOffset, onClose?: () => void, isDarkTheme = false) {
this.setState({ isOpen: true, menu, offset, onClose, isDarkTheme });
}
public hide() {
this.state.onClose?.();
this.setState({ isOpen: false, onClose: undefined });
}
private cancelContextMenu = (e: React.SyntheticEvent<HTMLDivElement>) => e.preventDefault();
private handleBackdropContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
// React function to remove from the event pool, useful when using a event within a callback
e.persist();
e.preventDefault();
// wait for backdrop to disappear so we can find the "real" element at event coordinates.
// timeout duration is equivalent to transition duration so we know it's animated out.
setTimeout(() => {
// retrigger context menu event at the element beneath the backdrop.
// if it has a `contextmenu` event handler then it'll be invoked.
// if it doesn't, no native menu will show (at least on OSX) :(
const newTarget = document.elementFromPoint(e.clientX, e.clientY);
const { view, ...newEventInit } = e;
newTarget?.dispatchEvent(new MouseEvent("contextmenu", newEventInit));
}, TRANSITION_DURATION);
};
private handlePopoverInteraction = (nextOpenState: boolean) => {
if (!nextOpenState) {
// delay the actual hiding till the event queue clears
// to avoid flicker of opening twice
this.hide();
}
};
}

View File

@@ -5,9 +5,11 @@ import { Button, Tabs, Tab, Tooltip, Position } from '@blueprintjs/core';
import { debounce } from 'lodash';
import { useHistory } from 'react-router';
import { If, Icon } from 'components';
import { saveInvoke } from 'utils';
export default function DashboardViewsTabs({
initialViewId = 0,
viewId,
tabs,
defaultTabText = <T id={'all'} />,
allTab = true,
@@ -26,16 +28,16 @@ export default function DashboardViewsTabs({
};
const handleTabClick = (viewId) => {
onTabClick && onTabClick(viewId);
saveInvoke(onTabClick, viewId);
};
const mappedTabs = useMemo(
() => tabs.map((tab) => ({ ...tab, onTabClick: handleTabClick })),
[tabs],
[tabs, handleTabClick],
);
const handleViewLinkClick = () => {
onNewViewTabClick && onNewViewTabClick();
saveInvoke(onNewViewTabClick);
};
const debounceChangeHistory = useRef(
@@ -49,7 +51,7 @@ export default function DashboardViewsTabs({
debounceChangeHistory.current(`/${resourceName}/${toPath}`);
setCurrentView(viewId);
onChange && onChange(viewId);
saveInvoke(onChange, viewId);
};
return (

View File

@@ -26,15 +26,14 @@ function DashboardPrivatePages({
// #withSubscriptionsActions
requestFetchSubscriptions,
}) {
// Fetch all user's organizatins.
// Fetches all user's organizatins.
const fetchOrganizations = useQuery(
['organizations'], () => requestAllOrganizations(),
);
// Fetchs organization subscriptions.
// Fetches organization subscriptions.
const fetchSuscriptions = useQuery(
['susbcriptions'], () => requestFetchSubscriptions(),
{ enabled: fetchOrganizations.data },
)
return (

View File

@@ -83,6 +83,7 @@ export default function DataTable(props) {
minWidth: selectionColumnWidth,
width: selectionColumnWidth,
maxWidth: selectionColumnWidth,
skeletonWidthMin: 100,
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox
Header: TableIndeterminateCheckboxHeader,
@@ -198,4 +199,7 @@ DataTable.defaultProps = {
TableTBodyRenderer: TableTBody,
TablePaginationRenderer: TablePagination,
TableNoResultsRowRenderer: TableNoResultsRow,
noResults: 'There is no results in the table.',
payload: {},
};

View File

@@ -27,11 +27,13 @@ function TableHeaderCell({ column, index }) {
</span>
</If>
<div {...column.getSortByToggleProps({
className: classNames('cell-inner', {
'text-overview': column.textOverview,
})
})}>
<div
{...column.getSortByToggleProps({
className: classNames('cell-inner', {
'text-overview': column.textOverview,
}),
})}
>
{column.render('Header')}
<If condition={column.isSorted}>
@@ -74,9 +76,13 @@ function TableHeaderGroup({ headerGroup }) {
*/
export default function TableHeader() {
const {
table: { headerGroups },
table: { headerGroups, page },
props: { TableHeaderSkeletonRenderer, headerLoading },
} = useContext(TableContext);
if (headerLoading && TableHeaderSkeletonRenderer) {
return <TableHeaderSkeletonRenderer />;
}
return (
<ScrollSyncPane>
<div className="thead">

View File

@@ -0,0 +1,42 @@
import React, { useContext } from 'react';
import TableContext from './TableContext';
import { Skeleton } from 'components';
function TableHeaderCell({ column }) {
const { skeletonWidthMax = 100, skeletonWidthMin = 40 } = column;
return (
<div
{...column.getHeaderProps({
className: 'th',
})}
>
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
</div>
);
}
/**
* Table skeleton rows.
*/
export default function TableSkeletonHeader({}) {
const {
table: { headerGroups },
} = useContext(TableContext);
return (
<div class="thead">
{headerGroups.map((headerGroup) => (
<div
{...headerGroup.getHeaderGroupProps({
className: 'tr',
})}
>
{headerGroup.headers.map((column) => (
<TableHeaderCell column={column} />
))}
</div>
))}
</div>
);
}

View File

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

View File

@@ -0,0 +1,44 @@
import React, { useContext } from 'react';
import TableContext from './TableContext';
import { Skeleton } from 'components';
/**
* Table header cell.
*/
function TableHeaderCell({ column }) {
const { skeletonWidthMax = 100, skeletonWidthMin = 40 } = column;
return (
<div
{...column.getHeaderProps({
className: 'td',
})}
>
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
</div>
);
}
/**
* Table skeleton rows.
*/
export default function TableSkeletonRows({}) {
const {
table: { headerGroups },
} = useContext(TableContext);
const skeletonRows = 10;
return Array.from({ length: skeletonRows }).map(() => {
return headerGroups.map((headerGroup) => (
<div
{...headerGroup.getHeaderGroupProps({
className: 'tr',
})}
>
{headerGroup.headers.map((column) => (
<TableHeaderCell column={column} />
))}
</div>
));
});
}

View File

@@ -11,9 +11,11 @@ import EstimateNumberDialog from 'containers/Dialogs/EstimateNumberDialog';
import ReceiptNumberDialog from 'containers/Dialogs/ReceiptNumberDialog';
import InvoiceNumberDialog from 'containers/Dialogs/InvoiceNumberDialog';
import InventoryAdjustmentDialog from 'containers/Dialogs/InventoryAdjustmentFormDialog';
import PaymentViaVoucherDialog from 'containers/Dialogs/PaymentViaVoucherDialog';
/**
* Dialogs container.
*/
export default function DialogsContainer() {
return (
<div>
@@ -27,7 +29,7 @@ export default function DialogsContainer() {
<InviteUserDialog dialogName={'invite-user'} />
<ExchangeRateFormDialog dialogName={'exchangeRate-form'} />
<ItemCategoryDialog dialogName={'item-category-form'} />
<InventoryAdjustmentDialog dialogName={'inventory-adjustment-form'} />
<InventoryAdjustmentDialog dialogName={'inventory-adjustment'} />
<PaymentViaVoucherDialog dialogName={'payment-via-voucher'} />
</div>
);

View File

@@ -0,0 +1,19 @@
import React, { useMemo } from 'react';
import 'style/components/Skeleton.scss';
import { randomNumber } from 'utils';
/**
* Skeleton component.
*/
export default function Skeleton({
Tag = 'span',
minWidth = 40,
maxWidth = 100,
}) {
const randomWidth = useMemo(() => randomNumber(minWidth, maxWidth), [
minWidth,
maxWidth,
]);
return <Tag className={'skeleton'} style={{ width: `${randomWidth}%` }} />;
}

View File

@@ -44,7 +44,7 @@ import InputPrependText from './Forms/InputPrependText';
import PageFormBigNumber from './PageFormBigNumber';
import AccountsMultiSelect from './AccountsMultiSelect';
import CustomersMultiSelect from './CustomersMultiSelect';
import Skeleton from './Skeleton'
import TableFastCell from './Datatable/TableFastCell';
@@ -97,6 +97,6 @@ export {
AccountsMultiSelect,
DataTableEditable,
CustomersMultiSelect,
TableFastCell,
Skeleton,
};