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,21 +1,18 @@
import React, { useMemo, useCallback, useState } from 'react';
import React, { useCallback, useState } from 'react';
import Icon from 'components/Icon';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
MenuItem,
Menu,
Popover,
PopoverInteractionKind,
Position,
Intent,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { useRouteMatch, useHistory } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T } from 'react-intl';
import { connect } from 'react-redux';
import FilterDropdown from 'components/FilterDropdown';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
@@ -23,8 +20,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import { If, DashboardActionViewsList } from 'components';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withExpenses from 'containers/Expenses/withExpenses';
import { useExpensesListContext } from './ExpensesListProvider';
import withExpensesActions from 'containers/Expenses/withExpensesActions';
import { compose } from 'utils';
@@ -33,53 +29,24 @@ import { compose } from 'utils';
* Expenses actions bar.
*/
function ExpensesActionsBar({
// #withResourceDetail
resourceFields,
//#withExpenses
expensesViews,
//#withExpensesActions
addExpensesTableQueries,
changeExpensesView,
onFilterChanged,
selectedRows,
onBulkDelete,
}) {
const [filterCount, setFilterCount] = useState(0);
const history = useHistory();
const { expensesViews } = useExpensesListContext();
const onClickNewExpense = useCallback(() => {
history.push('/expenses/new');
}, [history]);
const filterDropdown = FilterDropdown({
initialCondition: {
fieldKey: 'reference_no',
compatator: 'contains',
value: '',
},
fields: resourceFields,
onFilterChange: (filterConditions) => {
addExpensesTableQueries({
filter_roles: filterConditions || '',
});
onFilterChanged && onFilterChanged(filterConditions);
},
});
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows,
]);
// Handle delete button click.
const handleBulkDelete = useCallback(() => {
onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
}, [onBulkDelete, selectedRows]);
const handleBulkDelete = () => {
};
const handleTabChange = (viewId) => {
changeExpensesView(viewId.id || -1);
addExpensesTableQueries({
custom_view_id: viewId.id || null,
});
@@ -93,6 +60,7 @@ function ExpensesActionsBar({
onChange={handleTabChange}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="plus" />}
@@ -101,7 +69,7 @@ function ExpensesActionsBar({
/>
<Popover
minimal={true}
content={filterDropdown}
content={''}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
@@ -114,7 +82,7 @@ function ExpensesActionsBar({
/>
</Popover>
<If condition={hasSelectedRows}>
<If condition={false}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
@@ -144,20 +112,7 @@ function ExpensesActionsBar({
);
}
const mapStateToProps = (state, props) => ({
resourceName: 'expenses',
});
const withExpensesActionsBar = connect(mapStateToProps);
export default compose(
withExpensesActionsBar,
withDialogActions,
withResourceDetail(({ resourceFields }) => ({
resourceFields,
})),
withExpenses(({ expensesViews }) => ({
expensesViews,
})),
withExpensesActions,
)(ExpensesActionsBar);

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import {
Intent,
Button,
@@ -11,17 +11,17 @@ import {
Position,
Tag,
} from '@blueprintjs/core';
import { useParams } from 'react-router-dom';
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import Icon from 'components/Icon';
import { compose, saveInvoke } from 'utils';
import { useIsValuePassed } from 'hooks';
import { If, Money, Choose, LoadingIndicator } from 'components';
import { useExpensesListContext } from './ExpensesListProvider';
import { If, Money, Choose } from 'components';
import { CLASSES } from 'common/classes';
import DataTable from 'components/DataTable';
@@ -29,54 +29,26 @@ import ExpensesEmptyStatus from './ExpensesEmptyStatus';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetails from 'containers/Views/withViewDetails';
import withExpenses from 'containers/Expenses/withExpenses';
import withExpensesActions from 'containers/Expenses/withExpensesActions';
import withCurrentView from 'containers/Views/withCurrentView';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
/**
* Expenses datatable.
*/
function ExpensesDataTable({
// #withExpenes
expensesCurrentPage,
expensesLoading,
expensesPagination,
expensesTableQuery,
expensesCurrentViewId,
// #withExpensesActions
addExpensesTableQueries,
// #withDashboardActions
changeCurrentView,
changePageSubtitle,
setTopbarEditView,
// #withView
viewMeta,
// #ownProps
onEditExpense,
onDeleteExpense,
onPublishExpense,
onSelectedRowsChange,
}) {
const { custom_view_id: customViewId } = useParams();
const isLoadedBefore = useIsValuePassed(expensesLoading, false);
const { formatMessage } = useIntl();
useEffect(() => {
if (customViewId) {
changeCurrentView(customViewId);
setTopbarEditView(customViewId);
}
changePageSubtitle(customViewId && viewMeta ? viewMeta.name : '');
}, [
customViewId,
changeCurrentView,
changePageSubtitle,
setTopbarEditView,
viewMeta,
]);
const { expenses, isExpensesLoading } = useExpensesListContext();
// Handle fetch data of manual jouranls datatable.
const handleFetchData = useCallback(
@@ -187,7 +159,9 @@ function ExpensesDataTable({
{
id: 'total_amount',
Header: formatMessage({ id: 'full_amount' }),
accessor: (r) => <Money amount={r.total_amount} currency={r.currency_code} />,
accessor: (r) => (
<Money amount={r.total_amount} currency={r.currency_code} />
),
className: 'total_amount',
width: 150,
},
@@ -270,64 +244,42 @@ function ExpensesDataTable({
[onSelectedRowsChange],
);
const showEmptyStatus = [
expensesCurrentViewId === -1,
expensesCurrentPage.length === 0,
].every((condition) => condition === true);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={expensesLoading && !isLoadedBefore}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<ExpensesEmptyStatus />
</Choose.When>
<Choose>
<Choose.When condition={false}>
<ExpensesEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
columns={columns}
data={expensesCurrentPage}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
onFetchData={handleFetchData}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={expensesPagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
initialPageSize={expensesTableQuery.page_size}
initialPageIndex={expensesTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<Choose.Otherwise>
<DataTable
columns={columns}
data={expenses}
loading={isExpensesLoading}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
onFetchData={handleFetchData}
rowContextMenu={onRowContextMenu}
TableLoadingRenderer={TableSkeletonRows}
pagination={true}
// pagesCount={expensesPagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
// initialPageSize={expensesTableQuery.page_size}
// initialPageIndex={expensesTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</div>
);
}
export default compose(
withRouter,
withCurrentView,
withDialogActions,
withDashboardActions,
withExpensesActions,
withExpenses(
({
expensesCurrentPage,
expensesLoading,
expensesPagination,
expensesTableQuery,
expensesCurrentViewId,
}) => ({
expensesCurrentPage,
expensesLoading,
expensesPagination,
expensesTableQuery,
expensesCurrentViewId,
}),
),
withViewDetails(),
)(ExpensesDataTable);

View File

@@ -29,10 +29,7 @@ export default function ExpenseFloatingFooter({
const { submitForm, resetForm } = useFormikContext();
const handleSubmitPublishBtnClick = (event) => {
saveInvoke(onSubmitClick, event, {
redirect: true,
publish: true,
});
saveInvoke(onSubmitClick, event, { redirect: true, publish: true});
};
const handleSubmitPublishAndNewBtnClick = (event) => {
@@ -46,17 +43,11 @@ export default function ExpenseFloatingFooter({
const handleSubmitPublishContinueEditingBtnClick = (event) => {
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
publish: true,
});
saveInvoke(onSubmitClick, event, { redirect: false, publish: true });
};
const handleSubmitDraftBtnClick = (event) => {
saveInvoke(onSubmitClick, event, {
redirect: true,
publish: false,
});
saveInvoke(onSubmitClick, event, { redirect: true, publish: false });
};
const handleSubmitDraftAndNewBtnClick = (event) => {
@@ -70,10 +61,7 @@ export default function ExpenseFloatingFooter({
const handleSubmitDraftContinueEditingBtnClick = (event) => {
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
publish: false,
});
saveInvoke(onSubmitClick, event, { redirect: false, publish: false });
};
const handleCancelBtnClick = (event) => {
@@ -81,9 +69,9 @@ export default function ExpenseFloatingFooter({
};
const handleClearBtnClick = (event) => {
// saveInvoke(onClearClick, event);
resetForm();
};
return (
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
{/* ----------- Save And Publish ----------- */}
@@ -92,7 +80,6 @@ export default function ExpenseFloatingFooter({
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitPublishBtnClick}
text={<T id={'save_publish'} />}
/>
@@ -125,7 +112,6 @@ export default function ExpenseFloatingFooter({
<Button
disabled={isSubmitting}
className={'ml1'}
type="submit"
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
@@ -159,7 +145,6 @@ export default function ExpenseFloatingFooter({
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitPublishBtnClick}
text={<T id={'save'} />}
/>

View File

@@ -1,7 +1,7 @@
import React, { useMemo, useEffect,useState,useCallback } from 'react';
import React, { useMemo, useEffect, useState, useCallback } from 'react';
import { Intent } from '@blueprintjs/core';
import { useIntl } from 'react-intl';
import { defaultTo, pick } from 'lodash';
import { defaultTo, pick, sumBy } from 'lodash';
import { Formik, Form } from 'formik';
import moment from 'moment';
import classNames from 'classnames';
@@ -13,9 +13,8 @@ import ExpenseFormBody from './ExpenseFormBody';
import ExpenseFloatingFooter from './ExpenseFloatingActions';
import ExpenseFormFooter from './ExpenseFormFooter';
import withExpensesActions from 'containers/Expenses/withExpensesActions';
import withExpenseDetail from 'containers/Expenses/withExpenseDetail';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withMediaActions from 'containers/Media/withMediaActions';
import withSettings from 'containers/Settings/withSettings';
@@ -45,7 +44,7 @@ const defaultInitialValues = {
description: '',
reference_no: '',
currency_code: '',
publish:'',
publish: '',
categories: [...repeatValue(defaultCategory, MIN_LINES_NUMBER)],
};
@@ -57,36 +56,26 @@ function ExpenseForm({
requestSubmitMedia,
requestDeleteMedia,
// #withExpensesActions
requestSubmitExpense,
requestEditExpense,
requestFetchExpensesTable,
// #withDashboard
changePageTitle,
changePageSubtitle,
// #withExpenseDetail
expense,
// #withSettings
baseCurrency,
preferredPaymentAccount,
// #ownProps
expenseId,
onFormSubmit,
onCancelForm,
}) {
const {
editExpenseMutate,
createExpenseMutate,
expense,
expenseId,
} = useExpenseFormContext();
const isNewMode = !expenseId;
const [submitPayload, setSubmitPayload] = useState({});
const { formatMessage } = useIntl();
const history = useHistory();
const validationSchema = isNewMode
? CreateExpenseFormSchema
: EditExpenseFormSchema;
useEffect(() => {
if (isNewMode) {
changePageTitle(formatMessage({ id: 'new_expense' }));
@@ -104,10 +93,6 @@ function ExpenseForm({
...expense.categories.map((category) => ({
...pick(category, Object.keys(defaultCategory)),
})),
// ...repeatValue(
// defaultCategory,
// Math.max(MIN_LINES_NUMBER - expense.categories.length, 0),
// ),
],
}
: {
@@ -120,11 +105,10 @@ function ExpenseForm({
[expense, baseCurrency, preferredPaymentAccount],
);
// Handle form submit.
const handleSubmit = (values, { setSubmitting, setErrors, resetForm }) => {
setSubmitting(true);
const totalAmount = values.categories.reduce((total, item) => {
return total + item.amount;
}, 0);
const totalAmount = sumBy(values.categories, 'amount');
if (totalAmount <= 0) {
AppToaster.show({
@@ -135,7 +119,6 @@ function ExpenseForm({
});
return;
}
const categories = values.categories.filter(
(category) =>
category.amount && category.index && category.expense_account_id,
@@ -175,9 +158,9 @@ function ExpenseForm({
setSubmitting(false);
};
if (isNewMode) {
requestSubmitExpense(form).then(handleSuccess).catch(handleError);
createExpenseMutate(form).then(handleSuccess).catch(handleError);
} else {
requestEditExpense(expense.id, form)
editExpenseMutate(expense.id, form)
.then(handleSuccess)
.catch(handleError);
}
@@ -202,7 +185,9 @@ function ExpenseForm({
)}
>
<Formik
validationSchema={validationSchema}
validationSchema={isNewMode
? CreateExpenseFormSchema
: EditExpenseFormSchema}
initialValues={initialValues}
onSubmit={handleSubmit}
>
@@ -226,11 +211,8 @@ function ExpenseForm({
}
export default compose(
withExpensesActions,
withAccountsActions,
withDashboardActions,
withMediaActions,
withExpenseDetail(),
withSettings(({ organizationSettings, expenseSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
preferredPaymentAccount: parseInt(

View File

@@ -6,7 +6,6 @@ import { omit } from 'lodash';
import { DataTableEditable, Icon } from 'components';
import { Hint } from 'components';
import {
compose,
formattedAmount,
transformUpdatedRows,
saveInvoke,
@@ -16,7 +15,7 @@ import {
MoneyFieldCell,
InputGroupCell,
} from 'components/DataTableCells';
import withAccounts from 'containers/Accounts/withAccounts';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
const ExpenseCategoryHeaderCell = () => {
return (
@@ -88,10 +87,7 @@ const TotalAmountCellRenderer = (chainedComponent, type) => (props) => {
return chainedComponent(props);
};
function ExpenseTable({
// #withAccounts
accountsList,
export default function ExpenseTable({
// #ownPorps
onClickRemoveRow,
onClickAddNewRow,
@@ -104,6 +100,8 @@ function ExpenseTable({
const [rows, setRows] = useState([]);
const { formatMessage } = useIntl();
const { accounts } = useExpenseFormContext();
useEffect(() => {
setRows([...entries.map((e) => ({ ...e, rowType: 'editor' }))]);
}, [entries]);
@@ -230,7 +228,7 @@ function ExpenseTable({
rowClassNames={rowClassNames}
sticky={true}
payload={{
accounts: accountsList,
accounts: accounts,
errors: error,
updateData: handleUpdateData,
removeRow: handleRemoveRow,
@@ -258,10 +256,4 @@ function ExpenseTable({
totalRow={true}
/>
);
}
export default compose(
withAccounts(({ accountsList }) => ({
accountsList,
})),
)(ExpenseTable);
}

View File

@@ -20,21 +20,16 @@ import {
FieldRequiredHint,
Hint,
} from 'components';
import withCurrencies from 'containers/Currencies/withCurrencies';
import withAccounts from 'containers/Accounts/withAccounts';
import withCustomers from 'containers/Customers/withCustomers';
function ExpenseFormHeader({
//withCurrencies
currenciesList,
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
import { useExpenseFormContext } from './ExpenseFormPageProvider';
// #withAccounts
accountsList,
accountsTypes,
/**
* Expense form header.
*/
export default function ExpenseFormHeader({}) {
const { currencies, accounts, customers } = useExpenseFormContext();
// #withCustomers
customers,
}) {
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
<FastField name={'payment_date'}>
@@ -74,13 +69,13 @@ function ExpenseFormHeader({
inline={true}
>
<AccountsSelectList
accounts={accountsList}
accounts={accounts}
onAccountSelected={(account) => {
form.setFieldValue('payment_account_id', account.id);
}}
defaultSelectText={<T id={'select_payment_account'} />}
selectedAccountId={value}
filterByTypes={['current_asset']}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
/>
</FormGroup>
)}
@@ -100,7 +95,7 @@ function ExpenseFormHeader({
inline={true}
>
<CurrencySelectList
currenciesList={currenciesList}
currenciesList={currencies}
selectedCurrencyCode={value}
onCurrencySelected={(currencyItem) => {
form.setFieldValue('currency_code', currencyItem.currency_code);
@@ -149,16 +144,3 @@ function ExpenseFormHeader({
</div>
);
}
export default compose(
withAccounts(({ accountsList, accountsTypes }) => ({
accountsList,
accountsTypes,
})),
withCurrencies(({ currenciesList }) => ({
currenciesList,
})),
withCustomers(({ customers }) => ({
customers,
})),
)(ExpenseFormHeader);

View File

@@ -1,14 +1,9 @@
import React, { useCallback, useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query';
import ExpenseForm from './ExpenseForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { ExpenseFormPageProvider } from './ExpenseFormPageProvider';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withExpensesActions from 'containers/Expenses/withExpensesActions';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
@@ -19,19 +14,6 @@ import 'style/pages/Expense/PageForm.scss';
* Expense page form.
*/
function ExpenseFormPage({
// #withwithAccountsActions
requestFetchAccounts,
requestFetchAccountTypes,
// #withExpensesActions
requestFetchExpense,
// #wihtCurrenciesActions
requestFetchCurrencies,
// #withCustomersActions
requestFetchCustomers,
// #withDashboardActions
setSidebarShrink,
resetSidebarPreviousExpand,
@@ -54,25 +36,6 @@ function ExpenseFormPage({
};
}, [resetSidebarPreviousExpand, setSidebarShrink, setDashboardBackLink]);
const fetchAccounts = useQuery('accounts-list', (key) =>
requestFetchAccounts(),
);
const fetchExpense = useQuery(
['expense', id],
(key, _id) => requestFetchExpense(_id),
{ enabled: !!id },
);
const fetchCurrencies = useQuery('currencies', () =>
requestFetchCurrencies(),
);
// Handle fetch customers data table or list
const fetchCustomers = useQuery('customers-table', () =>
requestFetchCustomers({}),
);
const handleFormSubmit = useCallback(
(payload) => {
payload.redirect && history.push('/expenses-list');
@@ -85,28 +48,16 @@ function ExpenseFormPage({
}, [history]);
return (
<DashboardInsider
loading={
fetchExpense.isFetching ||
fetchAccounts.isFetching ||
fetchCurrencies.isFetching ||
fetchCustomers.isFetching
}
name={'expense-form'}
>
<ExpenseFormPageProvider expenseId={id}>
<ExpenseForm
onFormSubmit={handleFormSubmit}
expenseId={id}
onCancelForm={handleCancel}
/>
</DashboardInsider>
</ExpenseFormPageProvider>
);
}
export default compose(
withAccountsActions,
withCurrenciesActions,
withExpensesActions,
withCustomersActions,
withDashboardActions,
)(ExpenseFormPage);

View File

@@ -0,0 +1,71 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useCurrencies,
useCustomers,
useExpense,
useAccounts,
useCreateExpense,
useEditExpense,
} from 'hooks/query';
const ExpenseFormPageContext = createContext();
/**
* Accounts chart data provider.
*/
function ExpenseFormPageProvider({ expenseId, ...props }) {
const { data: currencies, isFetching: isCurrenciesLoading } = useCurrencies();
// Fetches customers list.
const {
data: { customers },
isFetching: isFieldsLoading,
} = useCustomers();
// Fetch the expense details.
const { data: expense, isFetching: isExpenseLoading } = useExpense(expenseId);
// Fetch accounts list.
const { data: accounts, isFetching: isAccountsLoading } = useAccounts();
// Create and edit expense mutate.
const { mutateAsync: createExpenseMutate } = useCreateExpense();
const { mutateAsync: editExpenseMutate } = useEditExpense();
// Provider payload.
const provider = {
expenseId,
currencies,
customers,
expense,
accounts,
isCurrenciesLoading,
isExpenseLoading,
isFieldsLoading,
isAccountsLoading,
createExpenseMutate,
editExpenseMutate,
};
return (
<DashboardInsider
loading={
isCurrenciesLoading ||
isExpenseLoading ||
isFieldsLoading ||
isAccountsLoading
}
name={'expense-form'}
>
<ExpenseFormPageContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useExpenseFormContext = () => React.useContext(ExpenseFormPageContext);
export { ExpenseFormPageProvider, useExpenseFormContext };

View File

@@ -1,16 +1,13 @@
import React, { useEffect, useRef } from 'react';
import { useHistory } from 'react-router';
import React from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { useParams, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { useParams } from 'react-router-dom';
import { pick } from 'lodash';
import { DashboardViewsTabs } from 'components';
import withExpenses from './withExpenses';
import { useExpensesListContext } from './ExpensesListProvider';
import withExpensesActions from './withExpensesActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetails from 'containers/Views/withViewDetails';
import { compose } from 'utils';
@@ -18,38 +15,19 @@ import { compose } from 'utils';
* Expesne views tabs.
*/
function ExpenseViewTabs({
// #withExpenses
expensesViews,
// #withViewDetails
viewItem,
// #withExpensesActions
addExpensesTableQueries,
changeExpensesView,
// #withDashboardActions
setTopbarEditView,
changePageSubtitle,
// props
customViewChanged,
onViewChanged,
addExpensesTableQueries,
}) {
const history = useHistory();
const { expensesViews } = useExpensesListContext();
const { custom_view_id: customViewId = null } = useParams();
useEffect(() => {
setTopbarEditView(customViewId);
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
}, [customViewId]);
const handleTabChange = (viewId) => {
changeExpensesView(viewId || -1);
addExpensesTableQueries({
custom_view_id: viewId || null,
});
};
const tabs = expensesViews.map((view) => ({
...pick(view, ['name', 'id']),
}));
@@ -72,19 +50,7 @@ function ExpenseViewTabs({
);
}
const mapStateToProps = (state, ownProps) => ({
viewId: ownProps.match.params.custom_view_id,
});
const withExpensesViewTabs = connect(mapStateToProps);
export default compose(
withRouter,
withExpensesViewTabs,
withExpensesActions,
withDashboardActions,
withViewDetails(),
withExpenses(({ expensesViews }) => ({
expensesViews,
})),
)(ExpenseViewTabs);

View File

@@ -0,0 +1,17 @@
import React from 'react';
import ExpenseDeleteAlert from 'alerts/expenses/ExpenseDeleteAlert';
import ExpensePublishAlert from 'alerts/expenses/ExpensePublishAlert';
/**
* Accounts alert.
*/
export default function ExpensesAlerts({
}) {
return (
<div class="expenses-alerts">
<ExpenseDeleteAlert name={'expense-delete'} />
<ExpensePublishAlert name={'expense-publish'} />
</div>
)
}

View File

@@ -1,263 +1,45 @@
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { Route, Switch, useHistory, useParams } from 'react-router-dom';
import { useQuery, queryCache } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core';
import AppToaster from 'components/AppToaster';
import { FormattedMessage as T, useIntl } from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import React, { useEffect } from 'react';
import { useIntl } from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import ExpenseViewTabs from 'containers/Expenses/ExpenseViewTabs';
import ExpenseDataTable from 'containers/Expenses/ExpenseDataTable';
import ExpenseActionsBar from 'containers/Expenses/ExpenseActionsBar';
import ExpensesViewPage from './ExpensesViewPage';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withExpenses from 'containers/Expenses/withExpenses';
import withExpensesActions from 'containers/Expenses/withExpensesActions';
import withViewsActions from 'containers/Views/withViewsActions';
import { compose } from 'utils';
import { ExpensesListProvider } from './ExpensesListProvider';
/**
* Expenses list.
*/
function ExpensesList({
// #withDashboardActions
changePageTitle,
// #withViewsActions
requestFetchResourceViews,
requestFetchResourceFields,
// #withExpenses
expensesTableQuery,
//#withExpensesActions
requestFetchExpensesTable,
requestDeleteExpense,
requestPublishExpense,
requestDeleteBulkExpenses,
addExpensesTableQueries,
requestFetchExpense,
}) {
const history = useHistory();
const { id } = useParams();
const { formatMessage } = useIntl();
const [deleteExpense, setDeleteExpense] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
const [bulkDelete, setBulkDelete] = useState(false);
const [publishExpense, setPublishExpense] = useState(false);
const fetchResourceViews = useQuery(
['resource-views', 'expenses'],
(key, resourceName) => requestFetchResourceViews(resourceName),
);
const fetchResourceFields = useQuery(
['resource-fields', 'expenses'],
(key, resourceName) => requestFetchResourceFields(resourceName),
);
const fetchExpenses = useQuery(['expenses-table', expensesTableQuery], () =>
requestFetchExpensesTable(),
);
useEffect(() => {
changePageTitle(formatMessage({ id: 'expenses_list' }));
}, [changePageTitle, formatMessage]);
// Handle delete expense click.
const handleDeleteExpense = useCallback(
(expnese) => {
setDeleteExpense(expnese);
},
[setDeleteExpense],
);
// Handle cancel expense journal.
const handleCancelExpenseDelete = useCallback(() => {
setDeleteExpense(false);
}, [setDeleteExpense]);
// Handle confirm delete expense.
const handleConfirmExpenseDelete = useCallback(() => {
requestDeleteExpense(deleteExpense.id).then(() => {
AppToaster.show({
message: formatMessage(
{ id: 'the_expense_has_been_deleted_successfully' },
{ number: deleteExpense.payment_account_id },
),
intent: Intent.SUCCESS,
});
setDeleteExpense(false);
});
}, [deleteExpense, requestDeleteExpense, formatMessage]);
// Calculates the selected rows count.
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
selectedRows,
]);
const handleBulkDelete = useCallback(
(accountsIds) => {
setBulkDelete(accountsIds);
},
[setBulkDelete],
);
// Handle confirm journals bulk delete.
const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkExpenses(bulkDelete)
.then(() => {
AppToaster.show({
message: formatMessage(
{ id: 'the_expenses_have_been_deleted_successfully' },
{ count: selectedRowsCount },
),
intent: Intent.SUCCESS,
});
setBulkDelete(false);
})
.catch((error) => {
setBulkDelete(false);
});
}, [requestDeleteBulkExpenses, bulkDelete, formatMessage, selectedRowsCount]);
// Handle cancel bulk delete alert.
const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false);
}, []);
const handleEidtExpense = useCallback(
(expense) => {
history.push(`/expenses/${expense.id}/edit`);
},
[history],
);
// Handle filter change to re-fetch data-table.
const handleFilterChanged = useCallback(() => {}, []);
// const fetchExpenses = useQuery(['expenses-table', expensesTableQuery], () =>
// requestFetchExpensesTable(),
// );
const handlePublishExpense = useCallback((expense) => {
setPublishExpense(expense);
}, []);
const handleCancelPublishExpense = useCallback(() => {
setPublishExpense(false);
});
// Handle publish expense confirm.
const handleConfirmPublishExpense = useCallback(() => {
requestPublishExpense(publishExpense.id)
.then(() => {
setPublishExpense(false);
AppToaster.show({
message: formatMessage({
id: 'the_expense_has_been_published',
}),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('expenses-table');
})
.catch((error) => {
setPublishExpense(false);
});
}, [publishExpense, requestPublishExpense, formatMessage]);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback(
(accounts) => {
setSelectedRows(accounts);
},
[setSelectedRows],
);
return (
<DashboardInsider
loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
name={'expenses'}
>
<ExpenseActionsBar
onBulkDelete={handleBulkDelete}
selectedRows={selectedRows}
onFilterChanged={handleFilterChanged}
/>
<ExpensesListProvider query={expensesTableQuery}>
<ExpenseActionsBar />
<DashboardPageContent>
<Switch>
<Route
exact={true}
// path={[
// '/expenses/:custom_view_id/custom_view',
// 'expenses',
// ]}
>
<ExpenseViewTabs />
<ExpenseDataTable
onDeleteExpense={handleDeleteExpense}
onEditExpense={handleEidtExpense}
onPublishExpense={handlePublishExpense}
onSelectedRowsChange={handleSelectedRowsChange}
/>
</Route>
</Switch>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={deleteExpense}
onCancel={handleCancelExpenseDelete}
onConfirm={handleConfirmExpenseDelete}
>
<p>
<T id={'once_delete_this_expense_you_will_able_to_restore_it'} />
</p>
</Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={
<T id={'delete_count'} values={{ count: selectedRowsCount }} />
}
icon="trash"
intent={Intent.DANGER}
isOpen={bulkDelete}
onCancel={handleCancelBulkDelete}
onConfirm={handleConfirmBulkDelete}
>
<p>
<T
id={'once_delete_these_expenses_you_will_not_able_restore_them'}
/>
</p>
</Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'publish'} />}
intent={Intent.WARNING}
isOpen={publishExpense}
onCancel={handleCancelPublishExpense}
onConfirm={handleConfirmPublishExpense}
>
<p>
<T id={'are_sure_to_publish_this_expense'} />
</p>
</Alert>
<ExpensesViewPage />
</DashboardPageContent>
</DashboardInsider>
</ExpensesListProvider>
);
}
export default compose(
withExpensesActions,
withDashboardActions,
withViewsActions,
withResourceActions,
withExpenses(({ expensesTableQuery }) => ({ expensesTableQuery })),
)(ExpensesList);

View File

@@ -0,0 +1,44 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useExpenses, useResourceViews } from 'hooks/query';
const ExpensesListContext = createContext();
/**
* Accounts chart data provider.
*/
function ExpensesListProvider({ query, ...props }) {
// Fetch accounts resource views and fields.
const { data: expensesViews, isFetching: isViewsLoading } = useResourceViews(
'expenses',
);
const {
data: { expenses, pagination },
isFetching: isExpensesLoading,
} = useExpenses();
// Provider payload.
const provider = {
expensesViews,
expenses,
pagination,
isViewsLoading,
isExpensesLoading
};
return (
<DashboardInsider
loading={isViewsLoading}
name={'expenses'}
>
<ExpensesListContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useExpensesListContext = () =>
React.useContext(ExpensesListContext);
export { ExpensesListProvider, useExpensesListContext };

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import ExpenseViewTabs from 'containers/Expenses/ExpenseViewTabs';
import ExpenseDataTable from './ExpenseDataTable';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Expenses inner page.
*/
function ExpensesViewPage() {
return (
<Switch>
<Route
exact={true}
path={['/expenses/:custom_view_id/custom_view', '/expenses']}
>
<ExpenseViewTabs />
<ExpenseDataTable />
{/* // onDeleteExpense={handleDeleteExpense}
// onEditExpense={handleEidtExpense}
// onPublishExpense={handlePublishExpense}
// onSelectedRowsChange={handleSelectedRowsChange}
// /> */}
</Route>
</Switch>
);
}
export default compose(
withAlertsActions,
withDialogActions,
)(ExpensesViewPage);