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

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from 'react';
import React from 'react';
import {
NavbarGroup,
NavbarDivider,
@@ -11,42 +11,34 @@ import {
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { useHistory } from 'react-router-dom';
import Icon from 'components/Icon';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import Icon from 'components/Icon';
import { If, DashboardActionViewsList } from 'components';
import withVendors from './withVendors';
import { useVendorsListContext } from './VendorsListProvider';
import { useHistory } from 'react-router-dom';
import withVendorActions from './withVendorActions';
import { compose } from 'utils';
/**
* Vendors actions bar.
*/
function VendorActionsBar({
// #withVendors
vendorViews,
// #withVendorActions
addVendorsTableQueries,
changeVendorView,
// #ownProps
selectedRows = [],
}) {
const [filterCount, setFilterCount] = useState(0);
const history = useHistory();
const { formatMessage } = useIntl();
const { vendorsViews } = useVendorsListContext();
const onClickNewVendor = useCallback(() => {
const onClickNewVendor = () => {
history.push('/vendors/new');
}, [history]);
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows,
]);
};
const handleTabChange = (viewId) => {
changeVendorView(viewId.id || -1);
addVendorsTableQueries({
custom_view_id: viewId.id || null,
});
@@ -57,7 +49,7 @@ function VendorActionsBar({
<NavbarGroup>
<DashboardActionViewsList
resourceName={'vendors'}
views={vendorViews}
views={vendorsViews}
onChange={handleTabChange}
/>
<NavbarDivider />
@@ -76,16 +68,16 @@ function VendorActionsBar({
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={
filterCount <= 0 ? (
true ? (
<T id={'filter'} />
) : (
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
`${9} ${formatMessage({ id: 'filters_applied' })}`
)
}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<If condition={hasSelectedRows}>
<If condition={false}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
@@ -107,13 +99,7 @@ function VendorActionsBar({
</DashboardActionsBar>
);
}
const mapStateToProps = (state, props) => ({
resourceName: 'vendors',
});
const withVendorsActionsBar = connect(mapStateToProps);
export default compose(
withVendorsActionsBar,
withVendorActions,
withVendors(({ vendorViews }) => ({ vendorViews })),
)(VendorActionsBar);

View File

@@ -12,26 +12,15 @@ import {
Col,
} from 'components';
import { FormattedMessage as T } from 'react-intl';
import withCurrencies from 'containers/Currencies/withCurrencies';
import {
compose,
momentFormatter,
tansformDateValue,
inputIntent,
} from 'utils';
import { momentFormatter, tansformDateValue, inputIntent } from 'utils';
import { useVendorFormContext } from './VendorFormProvider';
/**
* Vendor Finaniceal Panel Tab.
*/
function VendorFinanicalPanelTab({
// #withCurrencies
currenciesList,
export default function VendorFinanicalPanelTab() {
const { vendorId, currencies } = useVendorFormContext();
// #OwnProps
vendorId,
}) {
return (
<div className={'tab-panel--financial'}>
<Row>
@@ -104,7 +93,7 @@ function VendorFinanicalPanelTab({
inline={true}
>
<CurrencySelectList
currenciesList={currenciesList}
currenciesList={currencies}
selectedCurrencyCode={value}
onCurrencySelected={(currency) => {
form.setFieldValue('currency_code', currency.currency_code);
@@ -119,7 +108,3 @@ function VendorFinanicalPanelTab({
</div>
);
}
export default compose(
withCurrencies(({ currenciesList }) => ({ currenciesList })),
)(VendorFinanicalPanelTab);

View File

@@ -13,41 +13,43 @@ import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { useFormikContext } from 'formik';
import { saveInvoke } from 'utils';
import { useHistory } from 'react-router-dom';
import { Icon } from 'components';
import { useVendorFormContext } from './VendorFormProvider';
/**
* Vendor floating actions bar.
*/
export default function VendorFloatingActions({
onSubmitClick,
onSubmitAndNewClick,
onCancelClick,
isSubmitting,
vendor,
}) {
const { resetForm, submitForm } = useFormikContext();
export default function VendorFloatingActions() {
// Formik context.
const { resetForm, isSubmitting, submitForm } = useFormikContext();
// Vendor form context.
const { vendor, setSubmitPayload } = useVendorFormContext();
// History.
const history = useHistory();
// Handle the submit button.
const handleSubmitBtnClick = (event) => {
saveInvoke(onSubmitClick, event, {
noRedirect: false,
});
};
const handleCancelBtnClick = (event) => {
saveInvoke(onCancelClick, event);
};
const handleClearBtnClick = (event) => {
// saveInvoke(onClearClick, event);
resetForm();
setSubmitPayload({ noRedirect: false, });
submitForm();
};
// Handle the submit & new button click.
const handleSubmitAndNewClick = (event) => {
submitForm();
saveInvoke(onSubmitClick, event, {
noRedirect: true,
});
setSubmitPayload({ noRedirect: true, });
};
// Handle cancel button click.
const handleCancelBtnClick = (event) => {
history.goBack();
};
// Handle clear button click.
const handleClearBtnClick = (event) => {
resetForm();
};
return (

View File

@@ -1,5 +1,4 @@
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import * as Yup from 'yup';
import React, { useMemo, useEffect } from 'react';
import { Formik, Form } from 'formik';
import moment from 'moment';
import { Intent } from '@blueprintjs/core';
@@ -20,11 +19,10 @@ import VendorTabs from './VendorsTabs';
import VendorFloatingActions from './VendorFloatingActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withVendorDetail from './withVendorDetail';
import withVendorActions from './withVendorActions';
import withSettings from 'containers/Settings/withSettings';
import { compose, transformToForm } from 'utils';
import { useVendorFormContext } from './VendorFormProvider';
const defaultInitialValues = {
salutation: '',
@@ -68,20 +66,19 @@ function VendorForm({
// #withDashboardActions
changePageTitle,
// #withVendorDetailsActions
vendor,
// #withVendorActions
requestSubmitVendor,
requestEditVendor,
// #withSettings
baseCurrency,
// #OwnProps
vendorId,
}) {
const {
vendorId,
vendor,
createVendorMutate,
editVendorMutate,
setSubmitPayload,
submitPayload,
} = useVendorFormContext();
const isNewMode = !vendorId;
const [submitPayload, setSubmitPayload] = useState({});
const history = useHistory();
const { formatMessage } = useIntl();
@@ -95,15 +92,16 @@ function VendorForm({
currency_code: baseCurrency,
...transformToForm(vendor, defaultInitialValues),
}),
[defaultInitialValues],
[vendor, baseCurrency],
);
useEffect(() => {
!isNewMode
? changePageTitle(formatMessage({ id: 'edit_vendor' }))
: changePageTitle(formatMessage({ id: 'new_vendor' }));
}, [changePageTitle, isNewMode, formatMessage]);
//Handles the form submit.
// Handles the form submit.
const handleFormSubmit = (
values,
{ setSubmitting: resetForm, setErrors },
@@ -132,23 +130,12 @@ function VendorForm({
};
if (vendor && vendor.id) {
requestEditVendor(vendor.id, requestForm).then(onSuccess).catch(onError);
editVendorMutate(vendor.id, requestForm).then(onSuccess).catch(onError);
} else {
requestSubmitVendor(requestForm).then(onSuccess).catch(onError);
createVendorMutate(requestForm).then(onSuccess).catch(onError);
}
};
const handleCancelClick = useCallback(() => {
history.goBack();
}, [history]);
const handleSubmitClick = useCallback(
(event, payload) => {
setSubmitPayload({ ...payload });
},
[setSubmitPayload],
);
return (
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_CUSTOMER)}>
<Formik
@@ -158,38 +145,29 @@ function VendorForm({
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
{({ isSubmitting }) => (
<Form>
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
<VendorFormPrimarySection />
</div>
<Form>
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
<VendorFormPrimarySection />
</div>
<div className={'page-form__after-priamry-section'}>
<VendorFormAfterPrimarySection />
</div>
<div className={'page-form__after-priamry-section'}>
<VendorFormAfterPrimarySection />
</div>
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
<VendorTabs vendor={vendorId} />
</div>
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
<VendorTabs vendor={vendorId} />
</div>
<VendorFloatingActions
isSubmitting={isSubmitting}
vendor={vendorId}
onSubmitClick={handleSubmitClick}
onCancelClick={handleCancelClick}
/>
</Form>
)}
<VendorFloatingActions />
</Form>
</Formik>
</div>
);
}
export default compose(
withVendorDetail(),
withDashboardActions,
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
withVendorActions,
)(VendorForm);

View File

@@ -2,8 +2,6 @@ import React from 'react';
import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core';
import { FastField, ErrorMessage } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { inputIntent } from 'utils';
/**

View File

@@ -1,68 +1,25 @@
import React, { useCallback } from 'react';
import React from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query';
import { DashboardCard } from 'components';
import VendorFrom from './VendorForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withVendorActions from './withVendorActions';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
import { VendorFormProvider } from './VendorFormProvider';
import { compose } from 'utils';
function VendorFormPage({
// #withVendorActions
requestFetchVendorsTable,
requestFetchVendor,
// #withCurrenciesActions
requestFetchCurrencies,
}) {
/**
* Vendor form page.
*/
function VendorFormPage() {
const { id } = useParams();
const history = useHistory();
// Handle fetch Currencies data table
const fetchCurrencies = useQuery('currencies', () =>
requestFetchCurrencies(),
);
// Handle fetch vendors data table
const fetchVendors = useQuery('vendor-list', () =>
requestFetchVendorsTable({}),
);
// Handle fetch vendor details.
const fetchVendor = useQuery(
['vendor', id],
(_id, vendorId) => requestFetchVendor(vendorId),
{ enabled: id && id },
);
const handleFormSubmit = useCallback(() => {}, []);
const handleCancel = useCallback(() => {
history.goBack();
}, [history]);
return (
<DashboardInsider
loading={
fetchCurrencies.isFetching ||
fetchVendors.isFetching ||
fetchVendor.isFetching
}
name={'vendor-form'}
>
<VendorFormProvider vendorId={id}>
<DashboardCard page>
<VendorFrom
onFormSubmit={handleFormSubmit}
vendorId={id}
onCancelForm={handleCancel}
/>
<VendorFrom />
</DashboardCard>
</DashboardInsider>
</VendorFormProvider>
);
}
export default compose(withCurrenciesActions, withVendorActions)(VendorFormPage);
export default VendorFormPage;

View File

@@ -0,0 +1,61 @@
import React, { useState, createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useVendor,
useVendors,
useCurrencies,
useCreateVendor,
useEditVendor,
} from 'hooks/query';
const VendorFormContext = createContext();
/**
* Vendor form provider.
*/
function VendorFormProvider({ vendorId, ...props }) {
// Handle fetch Currencies data table
const { data: currencies, isFetching: isCurrenciesLoading } = useCurrencies();
// Handle fetch vendors data table
const {
data: { vendors },
isFetching: isVendorsLoading,
} = useVendors();
// Handle fetch vendor details.
const { data: vendor, isFetching: isVendorLoading } = useVendor(vendorId, {
enabled: !!vendorId,
});
// Form submit payload.
const [submitPayload, setSubmitPayload] = useState({});
// Create and edit vendor mutations.
const { mutateAsync: createVendorMutate } = useCreateVendor();
const { mutateAsync: editVendorMutate } = useEditVendor();
const provider = {
currencies,
vendors,
vendor,
submitPayload,
createVendorMutate,
editVendorMutate,
setSubmitPayload,
};
return (
<DashboardInsider
loading={isVendorLoading || isVendorsLoading || isCurrenciesLoading}
name={'vendor-form'}
>
<VendorFormContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useVendorFormContext = () => React.useContext(VendorFormContext);
export { VendorFormProvider, useVendorFormContext };

View File

@@ -1,54 +1,34 @@
import React, { useEffect, useMemo } from 'react';
import React, { useMemo } from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { compose } from 'redux';
import { useParams, withRouter, useHistory } from 'react-router-dom';
import { connect } from 'react-redux';
import { useParams } from 'react-router-dom';
import { useVendorsListContext } from './VendorsListProvider';
import { DashboardViewsTabs } from 'components';
import withVendors from './withVendors';
import withVendorActions from './withVendorActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetail from 'containers/Views/withViewDetails';
import { pick } from 'lodash';
/**
* Vendors views tabs.
*/
function VendorViewsTabs({
// #withViewDetail
viewId,
viewItem,
// #withVendors
vendorViews,
// #withVendorActions
addVendorsTableQueries,
changeVendorView,
// #withDashboardActions
setTopbarEditView,
changePageSubtitle,
}) {
const { custom_view_id: customViewId = null } = useParams();
useEffect(() => {
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
setTopbarEditView(customViewId);
}, [customViewId]);
const { vendorsViews } = useVendorsListContext();
const tabs = useMemo(() =>
vendorViews.map(
vendorsViews.map(
(view) => ({
...pick(view, ['name', 'id']),
}),
[vendorViews],
),
[vendorsViews],
);
const handleTabsChange = (viewId) => {
changeVendorView(viewId || -1);
addVendorsTableQueries({
custom_view_id: viewId || null,
});
@@ -68,18 +48,6 @@ function VendorViewsTabs({
);
}
const mapStateToProps = (state, ownProps) => ({
viewId: ownProps.match.params.custom_view_id,
});
const withVendorsViewsTabs = connect(mapStateToProps);
export default compose(
withRouter,
withDashboardActions,
withVendorsViewsTabs,
withVendorActions,
withViewDetail(),
withVendors(({ vendorViews }) => ({ vendorViews })),
)(VendorViewsTabs);

View File

@@ -1,79 +1,47 @@
import React, { useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { FormattedMessage as T, useIntl } from 'react-intl';
import React, { useEffect } from 'react';
import { useIntl } from 'react-intl';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { VendorsListProvider } from './VendorsListProvider';
import VendorActionsBar from 'containers/Vendors/VendorActionsBar';
import VendorsViewPage from 'containers/Vendors/VendorsViewPage';
import VendorsAlerts from 'containers/Vendors/VendorsAlerts';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withViewsActions from 'containers/Views/withViewsActions';
import withVendors from 'containers/Vendors/withVendors';
import withVendorActions from 'containers/Vendors/withVendorActions';
import { compose } from 'utils';
/**
* Vendors list page.
*/
function VendorsList({
// #withDashboardActions
changePageTitle,
// #withResourceActions
requestFetchResourceViews,
// #withVendors
vendorTableQuery,
// #withVendorActions
requestFetchVendorsTable,
}) {
const [tableLoading, setTableLoading] = useState(false);
const { formatMessage } = useIntl();
useEffect(() => {
changePageTitle(formatMessage({ id: 'vendors_list' }));
}, [changePageTitle, formatMessage]);
// Fetch vendors resource views and fields.
const fetchResourceViews = useQuery(
['resource-views', 'vendors'],
(key, resourceName) => requestFetchResourceViews(resourceName),
);
// Handle fetch vendors data table
const fetchVendors = useQuery(
['vendors-table', vendorTableQuery],
(key, query) => requestFetchVendorsTable({ ...query }),
);
useEffect(() => {
if (tableLoading && !fetchVendors.isFetching) {
setTableLoading(false);
}
}, [tableLoading, fetchVendors.isFetching]);
return (
<DashboardInsider
loading={fetchResourceViews.isFetching}
name={'customers-list'}
>
<VendorsListProvider query={vendorTableQuery}>
<VendorActionsBar />
<DashboardPageContent>
<VendorsViewPage />
<VendorsAlerts />
<VendorsViewPage />
{/* <VendorsAlerts /> */}
</DashboardPageContent>
</DashboardInsider>
</VendorsListProvider>
);
}
export default compose(
withResourceActions,
withVendorActions,
withDashboardActions,
withViewsActions,
withVendors(({ vendorTableQuery }) => ({ vendorTableQuery })),
)(VendorsList);

View File

@@ -0,0 +1,39 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useVendors } from 'hooks/query';
const VendorsListContext = createContext();
function VendorsListProvider({ query, ...props }) {
// Fetch vendors list with pagination meta.
const {
data: { vendors, pagination },
isFetching: isVendorsLoading,
} = useVendors(query);
// Fetch customers resource views and fields.
const {
data: vendorsViews,
isFetching: isVendorsViewsLoading,
} = useResourceViews('vendors');
const provider = {
vendors,
pagination,
vendorsViews,
isVendorsLoading,
isVendorsViewsLoading,
};
return (
<DashboardInsider loading={isVendorsViewsLoading} name={'vendors-list'}>
<VendorsListContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useVendorsListContext = () => React.useContext(VendorsListContext);
export { VendorsListProvider, useVendorsListContext };

View File

@@ -1,4 +1,4 @@
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import {
Button,
Popover,
@@ -8,15 +8,15 @@ import {
Position,
Intent,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import classNames from 'classnames';
import { useIsValuePassed } from 'hooks';
import VendorsEmptyStatus from './VendorsEmptyStatus';
import { DataTable, LoadingIndicator, Icon, Money, Choose } from 'components';
import { CLASSES } from 'common/classes';
import withVendors from './withVendors';
import VendorsEmptyStatus from './VendorsEmptyStatus';
import { DataTable, Icon, Money, Choose } from 'components';
import { useVendorsListContext } from './VendorsListProvider';
import withVendorsActions from './withVendorActions';
import { compose, firstLettersArgs, saveInvoke } from 'utils';
@@ -26,25 +26,16 @@ const AvatarCell = (row) => {
};
function VendorsTable({
// #withVendors
vendorsCurrentPage,
vendorsLoading,
vendorsPageination,
vendorTableQuery,
vendorItems,
vendorsCurrentViewId,
// #withVendorsActions
addVendorsTableQueries,
// #ownProps
loading,
onEditVendor,
onDeleteVendor,
onSelectedRowsChange,
}) {
const { formatMessage } = useIntl();
const isLoadedBefore = useIsValuePassed(loading, false);
const { vendors } = useVendorsListContext();
// Vendor actions list.
const renderContextMenu = useMemo(
@@ -186,64 +177,38 @@ function VendorsTable({
onEditVendor,
onDeleteVendor,
});
const showEmptyStatus = [
vendorsCurrentViewId === -1,
vendorItems.length === 0,
].every((condition) => condition === true);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator
loading={vendorsLoading && !isLoadedBefore}
mount={false}
>
<Choose>
<Choose.When condition={showEmptyStatus}>
<VendorsEmptyStatus />
</Choose.When>
<Choose>
<Choose.When condition={false}>
<VendorsEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
noInitialFetch={true}
columns={columns}
data={vendorItems}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
spinnerProps={{ size: 30 }}
rowContextMenu={rowContextMenu}
pagination={true}
manualSortBy={true}
pagesCount={vendorsPageination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
initialPageSize={vendorTableQuery.page_size}
initialPageIndex={vendorTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<Choose.Otherwise>
<DataTable
noInitialFetch={true}
columns={columns}
data={vendors}
onFetchData={handleFetchData}
selectionColumn={true}
expandable={false}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
spinnerProps={{ size: 30 }}
rowContextMenu={rowContextMenu}
pagination={true}
manualSortBy={true}
// pagesCount={vendorsPageination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
// initialPageSize={vendorTableQuery.page_size}
// initialPageIndex={vendorTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</div>
);
}
export default compose(
withVendors(
({
vendorItems,
vendorsLoading,
vendorTableQuery,
vendorsPageination,
vendorsCurrentViewId,
}) => ({
vendorItems,
vendorsLoading,
vendorsPageination,
vendorTableQuery,
vendorsCurrentViewId,
}),
),
withVendorsActions,
)(VendorsTable);
export default compose(withVendorsActions)(VendorsTable);

View File

@@ -8,7 +8,7 @@ import VendorAttahmentTab from './VendorAttahmentTab';
import CustomerAddressTabs from 'containers/Customers/CustomerAddressTabs';
import CustomerNotePanel from 'containers/Customers/CustomerNotePanel';
export default function VendorTabs({ vendor }) {
export default function VendorTabs() {
const { formatMessage } = useIntl();
return (
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
@@ -21,7 +21,7 @@ export default function VendorTabs({ vendor }) {
<Tab
id={'financial'}
title={formatMessage({ id: 'financial_details' })}
panel={<VendorFinanicalPanelTab vendorId={vendor} />}
panel={<VendorFinanicalPanelTab />}
/>
<Tab
id={'address'}