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

@@ -10,72 +10,64 @@ import {
MenuItem,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes';
import classNames from 'classnames';
import { useFormikContext } from 'formik';
import { saveInvoke } from 'utils';
import { If, Icon } from 'components';
import { useBillFormContext } from './BillFormProvider';
/**
* Bill floating actions bar.
*/
export default function BillFloatingActions({
isSubmitting,
onSubmitClick,
onCancelClick,
bill,
}) {
const { resetForm, submitForm } = useFormikContext();
export default function BillFloatingActions() {
const history = useHistory();
// Formik context.
const { resetForm, submitForm, isSubmitting } = useFormikContext();
// Bill form context.
const { bill, setSubmitPayload } = useBillFormContext();
// Handle submit as open button click.
const handleSubmitOpenBtnClick = (event) => {
saveInvoke(onSubmitClick, event, {
redirect: true,
status: true,
});
setSubmitPayload({ redirect: true, status: true });
submitForm();
};
// Handle submit, open and anothe new button click.
const handleSubmitOpenAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: true, resetForm: true });
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
status: true,
resetForm: true,
});
};
// Handle submit as open & continue editing button click.
const handleSubmitOpenContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: true });
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
status: true,
});
};
// Handle submit as draft button click.
const handleSubmitDraftBtnClick = (event) => {
saveInvoke(onSubmitClick, event, {
redirect: true,
status: false,
});
setSubmitPayload({ redirect: true, status: false });
submitForm();
};
// handle submit as draft & new button click.
const handleSubmitDraftAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: false, resetForm: true });
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
status: false,
resetForm: true,
});
};
// Handle submit as draft & continue editing button click.
const handleSubmitDraftContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: false, });
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
status: false,
});
};
// Handle cancel button click.
const handleCancelBtnClick = (event) => {
saveInvoke(onCancelClick, event);
history.goBack();
};
const handleClearBtnClick = (event) => {
@@ -89,6 +81,7 @@ export default function BillFloatingActions({
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitOpenBtnClick}

View File

@@ -1,11 +1,11 @@
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import React, { useMemo, useCallback, useEffect } from 'react';
import { Formik, Form } from 'formik';
import moment from 'moment';
import { Intent } from '@blueprintjs/core';
import classNames from 'classnames';
import { useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { pick, sumBy, omit } from 'lodash';
import { pick, sumBy, isEmpty, omit } from 'lodash';
import { CLASSES } from 'common/classes';
import { EditBillFormSchema, CreateBillFormSchema } from './BillForm.schema';
@@ -13,17 +13,14 @@ import BillFormHeader from './BillFormHeader';
import BillFloatingActions from './BillFloatingActions';
import BillFormFooter from './BillFormFooter';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withMediaActions from 'containers/Media/withMediaActions';
import withBillActions from './withBillActions';
import withBillDetail from './withBillDetail';
import { AppToaster } from 'components';
import { ERROR } from 'common/errors';
import { compose, repeatValue, defaultToTransform, orderingLinesIndexes } from 'utils';
import { compose, repeatValue, orderingLinesIndexes } from 'utils';
import BillFormBody from './BillFormBody';
import { useBillFormContext } from './BillFormProvider';
const MIN_LINES_NUMBER = 5;
@@ -43,7 +40,7 @@ const defaultInitialValues = {
due_date: moment(new Date()).format('YYYY-MM-DD'),
reference_no: '',
note: '',
open:'',
open: '',
entries: [...repeatValue(defaultBill, MIN_LINES_NUMBER)],
};
@@ -51,31 +48,21 @@ const defaultInitialValues = {
* Bill form.
*/
function BillForm({
//#WithMedia
requestSubmitMedia,
requestDeleteMedia,
//#withBillActions
requestSubmitBill,
requestEditBill,
setBillNumberChanged,
//#withDashboard
changePageTitle,
changePageSubtitle,
//#withBillDetail
bill,
//#Own Props
billId,
onFormSubmit,
onCancelForm,
}) {
const { formatMessage } = useIntl();
const history = useHistory();
const [submitPayload, setSubmitPayload] = useState({});
const {
bill,
billId,
submitPayload,
createBillMutate,
editBillMutate,
} = useBillFormContext();
const isNewMode = !billId;
useEffect(() => {
@@ -89,7 +76,7 @@ function BillForm({
// Initial values in create and edit mode.
const initialValues = useMemo(
() => ({
...(bill
...(!isEmpty(bill)
? {
...pick(bill, Object.keys(defaultInitialValues)),
entries: [
@@ -139,10 +126,9 @@ function BillForm({
setSubmitting(false);
return;
}
const form = {
...values,
open:submitPayload.status,
open: submitPayload.status,
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
};
// Handle the request success.
@@ -151,8 +137,8 @@ function BillForm({
message: formatMessage(
{
id: isNewMode
? 'the_bill_has_been_created_successfully' :
'the_bill_has_been_edited_successfully',
? 'the_bill_has_been_created_successfully'
: 'the_bill_has_been_edited_successfully',
},
{ number: values.bill_number },
),
@@ -175,9 +161,9 @@ function BillForm({
setSubmitting(false);
};
if (bill && bill.id) {
requestEditBill(bill.id, form).then(onSuccess).catch(onError);
editBillMutate(bill.id, form).then(onSuccess).catch(onError);
} else {
requestSubmitBill(form).then(onSuccess).catch(onError);
createBillMutate(form).then(onSuccess).catch(onError);
}
};
@@ -189,60 +175,28 @@ function BillForm({
[changePageSubtitle],
);
// Clear page subtitle once unmount bill form page.
useEffect(
() => () => {
changePageSubtitle('');
},
[changePageSubtitle],
);
const handleSubmitClick = useCallback(
(event, payload) => {
setSubmitPayload({ ...payload });
},
[setSubmitPayload],
);
const handleCancelClick = useCallback(() => {
history.goBack();
}, [history]);
return (
<div className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_STRIP_STYLE,
CLASSES.PAGE_FORM_BILL,
)}>
<div
className={classNames(
CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_STRIP_STYLE,
CLASSES.PAGE_FORM_BILL,
)}
>
<Formik
validationSchema={isNewMode ? CreateBillFormSchema : EditBillFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
{({ isSubmitting}) => (
<Form>
<BillFormHeader onBillNumberChanged={handleBillNumberChanged} />
<BillFormBody defaultBill={defaultBill} />
<BillFormFooter
oninitialFiles={[]}
// onDropFiles={handleDeleteFile}
/>
<BillFloatingActions
isSubmitting={isSubmitting}
bill={bill}
onSubmitClick={handleSubmitClick}
onCancelClick={handleCancelClick}
/>
</Form>
)}
<Form>
<BillFormHeader onBillNumberChanged={handleBillNumberChanged} />
<BillFormBody defaultBill={defaultBill} />
<BillFormFooter />
<BillFloatingActions />
</Form>
</Formik>
</div>
);
}
export default compose(
withBillActions,
withBillDetail(),
withDashboardActions,
withMediaActions,
)(BillForm);
export default compose(withDashboardActions)(BillForm);

View File

@@ -2,11 +2,15 @@ import React from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import EditableItemsEntriesTable from 'containers/Entries/EditableItemsEntriesTable';
import { useBillFormContext } from './BillFormProvider';
export default function BillFormBody({ defaultBill }) {
const { items } = useBillFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<EditableItemsEntriesTable
items={items}
defaultEntry={defaultBill}
filterPurchasableItems={true}
/>

View File

@@ -9,10 +9,7 @@ import Dragzone from 'components/Dragzone';
import { inputIntent } from 'utils';
// Bill form floating actions.
export default function BillFormFooter({
oninitialFiles,
onDropFiles,
}) {
export default function BillFormFooter() {
return (
<div class={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Row>
@@ -32,9 +29,9 @@ export default function BillFormFooter({
<Col md={4}>
<Dragzone
initialFiles={oninitialFiles}
onDrop={onDropFiles}
onDeleteFile={onDropFiles}
initialFiles={[]}
// onDrop={onDropFiles}
// onDeleteFile={onDropFiles}
hint={'Attachments: Maxiumum size: 20MB'}
/>
</Col>

View File

@@ -8,10 +8,8 @@ import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { ContactSelecetList, FieldRequiredHint, Icon } from 'components';
import withVendors from 'containers/Vendors/withVendors';
import withAccounts from 'containers/Accounts/withAccounts';
import { useBillFormContext } from './BillFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import {
momentFormatter,
compose,
@@ -26,10 +24,10 @@ import {
*/
function BillFormHeader({
onBillNumberChanged,
//#withVendors
vendorItems,
}) {
// Bill form context.
const { vendors } = useBillFormContext();
const handleBillNumberBlur = (event) => {
saveInvoke(onBillNumberChanged, event.currentTarget.value);
};
@@ -48,7 +46,7 @@ function BillFormHeader({
helperText={<ErrorMessage name={'vendor_id'} />}
>
<ContactSelecetList
contactsList={vendorItems}
contactsList={vendors}
selectedContactId={value}
defaultSelectText={<T id={'select_vender_account'} />}
onContactSelected={(contact) => {
@@ -151,11 +149,5 @@ function BillFormHeader({
}
export default compose(
withVendors(({ vendorItems }) => ({
vendorItems,
})),
withAccounts(({ accountsList }) => ({
accountsList,
})),
withDialogActions,
)(BillFormHeader);

View File

@@ -1,15 +1,9 @@
import React, { useCallback, useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query';
import BillForm from './BillForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withVendorActions from 'containers/Vendors/withVendorActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withItemsActions from 'containers/Items/withItemsActions';
import withBillActions from './withBillActions';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import { BillFormProvider } from './BillFormProvider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
@@ -17,27 +11,11 @@ import { compose } from 'utils';
import 'style/pages/Bills/PageForm.scss';
function BillFormPage({
// #withwithAccountsActions
requestFetchAccounts,
// #withVendorActions
requestFetchVendorsTable,
// #withItemsActions
requestFetchItems,
// #withBilleActions
requestFetchBill,
// #withSettingsActions
requestFetchOptions,
// #withDashboardActions
setSidebarShrink,
resetSidebarPreviousExpand,
setDashboardBackLink
}) {
const history = useHistory();
const { id } = useParams();
useEffect(() => {
@@ -54,62 +32,14 @@ function BillFormPage({
};
}, [resetSidebarPreviousExpand, setSidebarShrink, setDashboardBackLink]);
// Handle fetch accounts
const fetchAccounts = useQuery('accounts-list', (key) =>
requestFetchAccounts(),
);
// Handle fetch customers data table
const fetchVendors = useQuery('vendors-list', () =>
requestFetchVendorsTable({}),
);
// Handle fetch Items data table or list
const fetchItems = useQuery('items-list', () => requestFetchItems({}));
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
const handleFormSubmit = useCallback(
(payload) => {
payload.redirect && history.push('/bills');
},
[history],
);
const handleCancel = useCallback(() => {
history.goBack();
}, [history]);
const fetchBill = useQuery(
['bill', id],
(key, _id) => requestFetchBill(_id),
{ enabled: !!id },
);
return (
<DashboardInsider
loading={
fetchVendors.isFetching ||
fetchItems.isFetching ||
fetchAccounts.isFetching ||
fetchBill.isFetching
}
name={'bill-form'}
>
<BillForm
onFormSubmit={handleFormSubmit}
billId={id}
onCancelForm={handleCancel}
/>
</DashboardInsider>
<BillFormProvider billId={id}>
<BillForm />
</BillFormProvider>
);
}
export default compose(
withBillActions,
withVendorActions,
withItemsActions,
withAccountsActions,
withSettingsActions,
withDashboardActions
)(BillFormPage);

View File

@@ -0,0 +1,81 @@
import React, { createContext, useState } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useAccounts,
useVendors,
useItems,
useBill,
useSettings,
useCreateBill,
useEditBill
} from 'hooks/query';
const BillFormContext = createContext();
/**
* Bill form provider.
*/
function BillFormProvider({ billId, ...props }) {
// Handle fetch accounts.
const { data: accounts, isFetching: isAccountsLoading } = useAccounts();
// Handle fetch vendors data table
const {
data: { vendors },
isFetching: isVendorsLoading,
} = useVendors();
// Handle fetch Items data table or list
const {
data: { items },
isFetching: isItemsLoading,
} = useItems();
// Handle fetch bill details.
const { data: bill, isFetching: isBillLoading } = useBill(billId, {
enabled: !!billId,
});
// Handle fetching bill settings.
const { isFetching: isSettingLoading } = useSettings();
// Form submit payload.
const [submitPayload, setSubmitPayload] = useState({});
// Create and edit bills mutations.
const { mutateAsync: createBillMutate } = useCreateBill();
const { mutateAsync: editBillMutate } = useEditBill();
const provider = {
accounts,
vendors,
items,
bill,
submitPayload,
isSettingLoading,
isBillLoading,
isAccountsLoading,
isItemsLoading,
isVendorsLoading,
createBillMutate,
editBillMutate,
setSubmitPayload
};
return (
<DashboardInsider
loading={
isVendorsLoading || isItemsLoading || isAccountsLoading || isBillLoading
}
name={'bill-form'}
>
<BillFormContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useBillFormContext = () => React.useContext(BillFormContext);
export { BillFormProvider, useBillFormContext };

View File

@@ -1,59 +1,32 @@
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 { pick, debounce } from 'lodash';
import { useParams } from 'react-router-dom';
import { pick } from 'lodash';
import { DashboardViewsTabs } from 'components';
import { useUpdateEffect } from 'hooks';
import withBills from './withBills';
import { useBillsListContext } from './BillsListProvider';
import withBillActions from './withBillActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetails from 'containers/Views/withViewDetails';
import { compose } from 'utils';
/**
* Bills view tabs.
*/
function BillViewTabs({
//#withBills
billsViews,
// #withViewDetails
viewItem,
//#withBillActions
changeBillView,
addBillsTableQueries,
// #withDashboardActions
setTopbarEditView,
changePageSubtitle,
// #ownProps
customViewChanged,
onViewChanged,
}) {
const history = useHistory();
const { custom_view_id: customViewId = null } = useParams();
useEffect(() => {
setTopbarEditView(customViewId);
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
}, [customViewId]);
// Handle click a new view tab.
const handleClickNewView = () => {
setTopbarEditView(null);
history.push('/custom_views/invoices/new');
};
// Bills list context.
const { billsViews } = useBillsListContext();
const handleTabsChange = (viewId) => {
changeBillView(viewId || -1);
addBillsTableQueries({
custom_view_id: customViewId || null,
});
setTopbarEditView(viewId);
};
const tabs = billsViews.map((view) => ({
@@ -67,7 +40,6 @@ function BillViewTabs({
initialViewId={customViewId}
resourceName={'bills'}
tabs={tabs}
onNewViewTabClick={handleClickNewView}
onChange={handleTabsChange}
/>
</NavbarGroup>
@@ -75,19 +47,4 @@ function BillViewTabs({
);
}
const mapStateToProps = (state, ownProps) => ({
viewId: ownProps.match.params.custom_view_id,
});
const withBillsViewTabs = connect(mapStateToProps);
export default compose(
withRouter,
withBillsViewTabs,
withBillActions,
withDashboardActions,
withViewDetails(),
withBills(({ billsViews }) => ({
billsViews,
})),
)(BillViewTabs);
export default compose(withBillActions)(BillViewTabs);

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useState, useMemo } from 'react';
import React, { useState } from 'react';
import Icon from 'components/Icon';
import {
Button,
@@ -15,66 +15,40 @@ import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { connect } from 'react-redux';
import FilterDropdown from 'components/FilterDropdown';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { If, DashboardActionViewsList } from 'components';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withBillActions from './withBillActions';
import withBills from './withBills';
import { useBillsListContext } from './BillsListProvider';
import { compose } from 'utils';
/**
* Bills actions bar.
*/
function BillActionsBar({
// #withResourceDetail
resourceFields,
//#withBills
billsViews,
//#withBillActions
addBillsTableQueries,
changeBillView,
// #own Porps
onFilterChanged,
selectedRows = [],
}) {
const history = useHistory();
const [filterCount, setFilterCount] = useState(0);
const { formatMessage } = useIntl();
// Bills list context.
const { billsViews } = useBillsListContext();
const [filterCount] = useState(0);
const handleClickNewBill = useCallback(() => {
// Handle click a new bill.
const handleClickNewBill = () => {
history.push('/bills/new');
}, [history]);
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows,
]);
};
// Handle tab change.
const handleTabChange = (viewId) => {
changeBillView(viewId.id || -1);
addBillsTableQueries({
custom_view_id: viewId.id || null,
});
};
// const FilterDropdown = FilterDropdown({
// initialCondition: {
// fieldKey: '',
// compatator: '',
// value: '',
// },
// fields: resourceFields,
// onFilterChange: (filterConditions) => {
// addBillsTableQueries({
// filter_roles: filterConditions || '',
// });
// onFilterChanged && onFilterChanged(filterConditions);
// },
// });
return (
<DashboardActionsBar>
<NavbarGroup>
@@ -99,7 +73,7 @@ function BillActionsBar({
<Button
className={classNames(Classes.MINIMAL)}
text={
filterCount <= 0 ? (
true ? (
<T id={'filter'} />
) : (
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
@@ -108,7 +82,7 @@ function BillActionsBar({
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} />}
@@ -137,18 +111,6 @@ function BillActionsBar({
);
}
const mapStateToProps = (state, props) => ({
resourceName: 'bills',
});
const withBillActionsBar = connect(mapStateToProps);
export default compose(
withBillActionsBar,
withResourceDetail(({ resourceFields }) => ({
resourceFields,
})),
withBills(({ billsViews }) => ({
billsViews,
})),
withBillActions,
)(BillActionsBar);

View File

@@ -0,0 +1,12 @@
import React from 'react';
import BillOpenAlert from 'containers/Alerts/Bills/BillOpenAlert';
import BillDeleteAlert from 'containers/Alerts/Bills/BillDeleteAlert';
export default function BillsAlerts() {
return (
<div class="bills-alerts">
<BillDeleteAlert name={'bill-delete'} />
<BillOpenAlert name={'bill-open'} />
</div>
);
}

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import {
Intent,
Button,
@@ -10,7 +10,6 @@ import {
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';
@@ -65,7 +64,6 @@ function BillsDataTable({
onSelectedRowsChange,
}) {
const { formatMessage } = useIntl();
const isLoadedBefore = useIsValuePassed(billsLoading, false);
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
@@ -243,31 +241,29 @@ function BillsDataTable({
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={billsLoading && !isLoadedBefore} mount={false}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<BillsEmptyStatus />
</Choose.When>
<Choose>
<Choose.When condition={showEmptyStatus}>
<BillsEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
columns={columns}
data={billsCurrentPage}
onFetchData={handleFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={billsPageination.pagesCount}
initialPageSize={billsPageination.pageSize}
initialPageIndex={billsPageination.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<Choose.Otherwise>
<DataTable
columns={columns}
data={billsCurrentPage}
onFetchData={handleFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={billsPageination.pagesCount}
initialPageSize={billsPageination.pageSize}
initialPageIndex={billsPageination.page - 1}
/>
</Choose.Otherwise>
</Choose>
</div>
);
}

View File

@@ -1,23 +1,16 @@
import React, { useEffect, useCallback, useMemo, useState } from 'react';
import { Route, Switch, useHistory } from 'react-router-dom';
import { useQuery, queryCache } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core';
import React, { useEffect } from 'react';
import AppToaster from 'components/AppToaster';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { BillsListProvider } from './BillsListProvider';
import BillsDataTable from './BillsDataTable';
import BillActionsBar from './BillActionsBar';
import BillViewTabs from './BillViewTabs';
import BillsActionsBar from './BillsActionsBar';
import BillsAlerts from './BillsAlerts';
import BillsViewPage from './BillsViewPage';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withBills from './withBills';
import withBillActions from './withBillActions';
import withViewsActions from 'containers/Views/withViewsActions';
import { compose } from 'utils';
@@ -28,170 +21,29 @@ function BillsList({
// #withDashboardActions
changePageTitle,
// #withViewsActions
requestFetchResourceViews,
requestFetchResourceFields,
//#withBills
// #withBills
billsTableQuery,
//#withBillActions
requestFetchBillsTable,
requestDeleteBill,
requestOpenBill,
addBillsTableQueries,
}) {
const history = useHistory();
const { formatMessage } = useIntl();
const [deleteBill, setDeleteBill] = useState(false);
const [openBill, setOpenBill] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
useEffect(() => {
changePageTitle(formatMessage({ id: 'bills_list' }));
}, [changePageTitle, formatMessage]);
const fetchResourceViews = useQuery(
['resource-views', 'bills'],
(key, resourceName) => requestFetchResourceViews(resourceName),
);
const fetchResourceFields = useQuery(
['resource-fields', 'bills'],
(key, resourceName) => requestFetchResourceFields(resourceName),
);
const fetchBills = useQuery(['bills-table', billsTableQuery], (key, query) =>
requestFetchBillsTable({ ...query }),
);
//handle dalete Bill
const handleDeleteBill = useCallback(
(bill) => {
setDeleteBill(bill);
},
[setDeleteBill],
);
// handle cancel Bill
const handleCancelBillDelete = useCallback(() => {
setDeleteBill(false);
}, [setDeleteBill]);
// handleConfirm delete invoice
const handleConfirmBillDelete = useCallback(() => {
requestDeleteBill(deleteBill.id).then(() => {
AppToaster.show({
message: formatMessage({
id: 'the_bill_has_been_deleted_successfully',
}),
intent: Intent.SUCCESS,
});
setDeleteBill(false);
});
}, [deleteBill, requestDeleteBill, formatMessage]);
// Handle cancel/confirm bill open.
const handleOpenBill = useCallback((bill) => {
setOpenBill(bill);
}, []);
// Handle cancel open bill alert.
const handleCancelOpenBill = useCallback(() => {
setOpenBill(false);
}, []);
// Handle confirm bill open.
const handleConfirmBillOpen = useCallback(() => {
requestOpenBill(openBill.id)
.then(() => {
setOpenBill(false);
AppToaster.show({
message: formatMessage({
id: 'the_bill_has_been_opened_successfully',
}),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('bills-table');
})
.catch((error) => {});
}, [openBill, requestOpenBill, formatMessage]);
const handleEditBill = useCallback((bill) => {
history.push(`/bills/${bill.id}/edit`);
});
// Handle selected rows change.
const handleSelectedRowsChange = useCallback(
(_invoices) => {
setSelectedRows(_invoices);
},
[setSelectedRows],
);
// Handle filter change to re-fetch data-table.
const handleFilterChanged = useCallback((filterConditions) => {}, []);
return (
<DashboardInsider
loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
name={'bills'}
>
<BillActionsBar
// onBulkDelete={}
selectedRows={selectedRows}
onFilterChanged={handleFilterChanged}
/>
<BillsListProvider query={billsTableQuery}>
<BillsActionsBar />
<DashboardPageContent>
<Switch>
<Route
exact={true}
path={['/bills/:custom_view_id/custom_view', '/bills']}
>
<BillViewTabs />
<BillsDataTable
onDeleteBill={handleDeleteBill}
onEditBill={handleEditBill}
onOpenBill={handleOpenBill}
onSelectedRowsChange={handleSelectedRowsChange}
/>
</Route>
</Switch>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon={'trash'}
intent={Intent.DANGER}
isOpen={deleteBill}
onCancel={handleCancelBillDelete}
onConfirm={handleConfirmBillDelete}
>
<p>
<T id={'once_delete_this_bill_you_will_able_to_restore_it'} />
</p>
</Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'open'} />}
intent={Intent.WARNING}
isOpen={openBill}
onCancel={handleCancelOpenBill}
onConfirm={handleConfirmBillOpen}
>
<p>
<T id={'are_sure_to_open_this_bill'} />
</p>
</Alert>
<BillsViewPage />
<BillsAlerts />
</DashboardPageContent>
</DashboardInsider>
</BillsListProvider>
);
}
export default compose(
withResourceActions,
withBillActions,
withDashboardActions,
withViewsActions,
withBills(({ billsTableQuery }) => ({
billsTableQuery,
})),

View File

@@ -0,0 +1,52 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useResourceFields, useBills } from 'hooks/query';
const BillsListContext = createContext();
/**
* Accounts chart data provider.
*/
function BillsListProvider({ query, ...props }) {
// Fetch accounts resource views and fields.
const { data: billsViews, isFetching: isViewsLoading } = useResourceViews(
'bills',
);
// Fetch the accounts resource fields.
const {
data: billsFields,
isFetching: isFieldsLoading,
} = useResourceFields('bills');
// Fetch accounts list according to the given custom view id.
const {
data: { bills, pagination },
isFetching: isBillsLoading,
} = useBills(query);
// Provider payload.
const provider = {
bills,
pagination,
billsFields,
billsViews,
isBillsLoading,
isFieldsLoading,
isViewsLoading,
};
return (
<DashboardInsider
loading={isViewsLoading || isFieldsLoading}
name={'bills'}
>
<BillsListContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useBillsListContext = () => React.useContext(BillsListContext);
export { BillsListProvider, useBillsListContext };

View File

@@ -0,0 +1,49 @@
import React, { useCallback } from 'react';
import { Switch, Route, useHistory } from 'react-router-dom';
import BillViewTabs from './BillViewTabs';
import BillsDataTable from './BillsDataTable';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
function BillsViewPage() {
const history = useHistory();
const handleEditBill = useCallback((bill) => {
history.push(`/bills/${bill.id}/edit`);
});
const handleDeleteBill = () => {
};
const handleOpenBill = () => {
};
const handleSelectedRowsChange = () => {
}
return (
<Switch>
<Route
exact={true}
path={['/bills/:custom_view_id/custom_view', '/bills']}
>
<BillViewTabs />
{/* <BillsDataTable
onDeleteBill={handleDeleteBill}
onEditBill={handleEditBill}
onOpenBill={handleOpenBill}
onSelectedRowsChange={handleSelectedRowsChange}
/> */}
</Route>
</Switch>
);
}
export default compose(withAlertsActions, withDialogActions)(BillsViewPage);

View File

@@ -1,10 +1,8 @@
import React, { useCallback, useState, useMemo } from 'react';
import React from 'react';
import Icon from 'components/Icon';
import {
Button,
Classes,
Menu,
MenuItem,
Popover,
NavbarDivider,
NavbarGroup,
@@ -14,69 +12,41 @@ import {
} from '@blueprintjs/core';
import classNames from 'classnames';
import { useRouteMatch, useHistory } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { connect } from 'react-redux';
import FilterDropdown from 'components/FilterDropdown';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { If, DashboardActionViewsList } from 'components';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withPaymentMade from './withPaymentMade';
import withPaymentMadeActions from './withPaymentMadeActions';
import { usePaymentMadesListContext } from './PaymentMadesListProvider';
import { compose } from 'utils';
/**
* Payment made actions bar.
*/
function PaymentMadeActionsBar({
//#withResourceDetail
resourceFields,
//#withPaymentMades
paymentMadeViews,
//#withPaymentMadesActions
addPaymentMadesTableQueries,
// #own Porps
onFilterChanged,
selectedRows = [],
}) {
const history = useHistory();
const { path } = useRouteMatch();
const [filterCount, setFilterCount] = useState(0);
const { formatMessage } = useIntl();
const handleClickNewPaymentMade = useCallback(() => {
// Payment receives list context.
const { paymentMadesViews } = usePaymentMadesListContext();
// Handle new payment made button click.
const handleClickNewPaymentMade = () => {
history.push('/payment-mades/new');
}, [history]);
// const filterDropdown = FilterDropdown({
// initialCondition: {
// fieldKey: '',
// compatator: 'contains',
// value: '',
// },
// fields: resourceFields,
// onFilterChange: (filterConditions) => {
// addPaymentMadesTableQueries({
// filter_roles: filterConditions || '',
// });
// onFilterChanged && onFilterChanged(filterConditions);
// },
// });
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows,
]);
};
return (
<DashboardActionsBar>
<NavbarGroup>
<DashboardActionViewsList
resourceName={'bill_payments'}
views={paymentMadeViews}
views={paymentMadesViews}
/>
<NavbarDivider />
<Button
@@ -94,16 +64,16 @@ function PaymentMadeActionsBar({
<Button
className={classNames(Classes.MINIMAL)}
text={
filterCount <= 0 ? (
true ? (
<T id={'filter'} />
) : (
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
`${0} ${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} />}
@@ -132,19 +102,4 @@ function PaymentMadeActionsBar({
);
}
const mapStateToProps = (state, props) => ({
resourceName: 'bill_payments',
});
const withPaymentMadeActionsBar = connect(mapStateToProps);
export default compose(
withPaymentMadeActionsBar,
withResourceDetail(({ resourceFields }) => ({
resourceFields,
})),
withPaymentMade(({ paymentMadeViews }) => ({
paymentMadeViews,
})),
withPaymentMadeActions,
)(PaymentMadeActionsBar);
export default compose(withPaymentMadeActions)(PaymentMadeActionsBar);

View File

@@ -1,164 +1,47 @@
import React, { useEffect, useCallback, useMemo, useState } from 'react';
import { Route, Switch, useHistory } from 'react-router-dom';
import { useQuery, queryCache } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core';
import AppToaster from 'components/AppToaster';
import React, { useEffect } from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import PaymentMadeDataTable from './PaymentMadeDataTable';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import PaymentMadeActionsBar from './PaymentMadeActionsBar';
import PaymentMadeViewTabs from './PaymentMadeViewTabs';
import PaymentMadesAlerts from './PaymentMadesAlerts';
import { PaymentMadesListProvider } from './PaymentMadesListProvider';
import PaymentMadesView from './PaymentMadesView';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withPaymentMades from './withPaymentMade';
import withPaymentMadeActions from './withPaymentMadeActions';
import withViewsActions from 'containers/Views/withViewsActions';
import { compose } from 'utils';
/**
* Payment mades list.
*/
function PaymentMadeList({
//#withDashboardActions
// #withDashboardActions
changePageTitle,
//#withViewsActions
requestFetchResourceViews,
requestFetchResourceFields,
//#withPaymentMades
// #withPaymentMades
paymentMadeTableQuery,
//#withPaymentMadesActions
requestFetchPaymentMadesTable,
requestDeletePaymentMade,
addPaymentMadesTableQueries,
}) {
const history = useHistory();
const { formatMessage } = useIntl();
const [deletePaymentMade, setDeletePaymentMade] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
useEffect(() => {
changePageTitle(formatMessage({ id: 'payment_made_list' }));
}, [changePageTitle, formatMessage]);
const fetchResourceViews = useQuery(
['resource-views', 'bill_payments'],
(key, resourceName) => requestFetchResourceViews(resourceName),
);
const fetchResourceFields = useQuery(
['resource-fields', 'bill_payments'],
(key, resourceName) => requestFetchResourceFields(resourceName),
);
const fetchPaymentMades = useQuery(
['paymantMades-table', paymentMadeTableQuery],
() => requestFetchPaymentMadesTable(),
);
//handle dalete Payment Made
const handleDeletePaymentMade = useCallback(
(paymentMade) => {
setDeletePaymentMade(paymentMade);
},
[setDeletePaymentMade],
);
// handle cancel Payment Made
const handleCancelPaymentMadeDelete = useCallback(() => {
setDeletePaymentMade(false);
}, [setDeletePaymentMade]);
// handleConfirm delete payment made
const handleConfirmPaymentMadeDelete = useCallback(() => {
requestDeletePaymentMade(deletePaymentMade.id).then(() => {
AppToaster.show({
message: formatMessage({
id: 'the_payment_made_has_been_deleted_successfully',
}),
intent: Intent.SUCCESS,
});
setDeletePaymentMade(false);
});
}, [deletePaymentMade, requestDeletePaymentMade, formatMessage]);
const handleEditPaymentMade = useCallback((payment) => {
history.push(`/payment-mades/${payment.id}/edit`);
});
// Calculates the selected rows count.
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
selectedRows,
]);
// Handle filter change to re-fetch data-table.
const handleFilterChanged = useCallback(() => {}, [fetchPaymentMades]);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback(
(payment) => {
setSelectedRows(payment);
},
[setSelectedRows],
);
return (
<DashboardInsider
// loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
name={'payment_made'}
>
<PaymentMadeActionsBar
// onBulkDelete={}
selectedRows={selectedRows}
onFilterChanged={handleFilterChanged}
/>
<DashboardPageContent>
<Switch>
<Route
exact={true}
path={[
'/payment-mades/:custom_view_id/custom_view',
'/payment-mades',
]}
>
<PaymentMadeViewTabs />
<PaymentMadeDataTable
onDeletePaymentMade={handleDeletePaymentMade}
onEditPaymentMade={handleEditPaymentMade}
onSelectedRowsChange={handleSelectedRowsChange}
/>
</Route>
</Switch>
<PaymentMadesListProvider query={paymentMadeTableQuery}>
<PaymentMadeActionsBar />
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon={'trash'}
intent={Intent.DANGER}
isOpen={deletePaymentMade}
onCancel={handleCancelPaymentMadeDelete}
onConfirm={handleConfirmPaymentMadeDelete}
>
<p>
<T
id={'once_delete_this_payment_made_you_will_able_to_restore_it'}
/>
</p>
</Alert>
<DashboardPageContent>
<PaymentMadesView />
<PaymentMadesAlerts />
</DashboardPageContent>
</DashboardInsider>
</PaymentMadesListProvider>
);
}
export default compose(
withResourceActions,
withPaymentMadeActions,
withDashboardActions,
withViewsActions,
withPaymentMades(({ paymentMadeTableQuery }) => ({
paymentMadeTableQuery,
})),

View File

@@ -1,78 +1,38 @@
import React, { useEffect, useRef } from 'react';
import React from 'react';
import { useHistory } from 'react-router';
import { FormattedMessage as T } from 'react-intl';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { useParams, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { pick, debounce } from 'lodash';
import { useParams } from 'react-router-dom';
import { pick } from 'lodash';
import { DashboardViewsTabs } from 'components';
import { useUpdateEffect } from 'hooks';
import withPaymentMade from './withPaymentMade';
import { usePaymentMadesListContext } from './PaymentMadesListProvider';
import withPaymentMadeActions from './withPaymentMadeActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetails from 'containers/Views/withViewDetails';
import { compose } from 'utils';
function PaymentMadeViewTabs({
//#withPaymentMades
paymentMadeViews,
//#withPaymentMadesActions
changePaymentMadeView,
addPaymentMadesTableQueries,
// #withViewDetails
viewItem,
// #withDashboardActions
setTopbarEditView,
changePageSubtitle,
//#Own Props
onViewChanged,
}) {
const history = useHistory();
const { custom_view_id: customViewId = null } = useParams();
useEffect(() => {
changePaymentMadeView(customViewId || -1);
setTopbarEditView(customViewId);
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
addPaymentMadesTableQueries({
custom_view_id: customViewId,
});
return () => {
setTopbarEditView(null);
changePageSubtitle('');
changePaymentMadeView(null);
};
}, [customViewId, addPaymentMadesTableQueries, changePaymentMadeView]);
useUpdateEffect(() => {
onViewChanged && onViewChanged(customViewId);
}, [customViewId]);
const debounceChangeHistory = useRef(
debounce((toUrl) => {
history.push(toUrl);
}, 250),
);
// Payment receives list context.
const { paymentMadesViews } = usePaymentMadesListContext();
const handleTabsChange = (viewId) => {
const toPath = viewId ? `${viewId}/custom_view` : '';
debounceChangeHistory.current(`/payment-mades/${toPath}`);
setTopbarEditView(viewId);
addPaymentMadesTableQueries({
custom_view_id: viewId || null,
});
};
const tabs = paymentMadeViews.map((view) => ({
const tabs = paymentMadesViews.map((view) => ({
...pick(view, ['name', 'id']),
}));
const handleClickNewView = () => {
setTopbarEditView(null);
history.push('/custom_views/payment-mades/new');
};
@@ -91,19 +51,4 @@ function PaymentMadeViewTabs({
);
}
const mapStateToProps = (state, ownProps) => ({
viewId: ownProps.match.params.custom_view_id,
});
const withPaymentMadesViewTabs = connect(mapStateToProps);
export default compose(
withRouter,
withPaymentMadesViewTabs,
withPaymentMadeActions,
withDashboardActions,
withViewDetails(),
withPaymentMade(({ paymentMadeViews }) => ({
paymentMadeViews,
})),
)(PaymentMadeViewTabs);
export default compose(withPaymentMadeActions)(PaymentMadeViewTabs);

View File

@@ -0,0 +1,10 @@
import React from 'react';
import PaymentMadeDeleteAlert from 'containers/Alerts/PaymentMades/PaymentMadeDeleteAlert';
export default function PaymentMadesAlerts() {
return (
<div>
<PaymentMadeDeleteAlert dialogName={'payment-made-delete'} />
</div>
);
}

View File

@@ -0,0 +1,58 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useResourceViews,
useResourceFields,
usePaymentReceives,
} from 'hooks/query';
const PaymentMadesListContext = createContext();
/**
* Accounts chart data provider.
*/
function PaymentMadesListProvider({ accountsTableQuery, ...props }) {
// Fetch accounts resource views and fields.
const {
data: paymentMadesViews,
isFetching: isViewsLoading,
} = useResourceViews('bill_payments');
// Fetch the accounts resource fields.
const {
data: paymentMadesFields,
isFetching: isFieldsLoading,
} = useResourceFields('bill_payments');
// Fetch accounts list according to the given custom view id.
const {
data: { paymentMades, pagination },
isFetching: isInvoicesLoading,
} = usePaymentReceives(accountsTableQuery);
// Provider payload.
const provider = {
paymentMades,
pagination,
paymentMadesFields,
paymentMadesViews,
isInvoicesLoading,
isFieldsLoading,
isViewsLoading,
};
return (
<DashboardInsider
loading={isViewsLoading || isFieldsLoading}
name={'payment-mades-list'}
>
<PaymentMadesListContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const usePaymentMadesListContext = () =>
React.useContext(PaymentMadesListContext);
export { PaymentMadesListProvider, usePaymentMadesListContext };

View File

@@ -0,0 +1,38 @@
import React, { useEffect } from 'react';
import { Switch, Route } from 'react-router-dom';
import PaymentMadeViewTabs from './PaymentMadeViewTabs';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Payment mades view page.
*/
function PaymentMadesViewPage({
// #withAlertActions
openAlert,
}) {
return (
<Switch>
<Route
exact={true}
path={['/payment-mades/:custom_view_id/custom_view', '/payment-mades']}
>
<PaymentMadeViewTabs />
{/* <PaymentMadeDataTable
onDeletePaymentMade={handleDeletePaymentMade}
onEditPaymentMade={handleEditPaymentMade}
onSelectedRowsChange={handleSelectedRowsChange}
/> */}
</Route>
</Switch>
);
}
export default compose(
withAlertsActions,
withDialogActions,
)(PaymentMadesViewPage);

View File

@@ -0,0 +1,52 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useResourceFields, usePaymentMades } from 'hooks/query';
const PaymentMadesContext = createContext();
/**
* Accounts chart data provider.
*/
function PaymentMadesProvider({ query, ...props }) {
// Fetch accounts resource views and fields.
const { data: paymentsViews, isFetching: isViewsLoading } = useResourceViews(
'bill_payments',
);
// Fetch the accounts resource fields.
const {
data: paymentsFields,
isFetching: isFieldsLoading,
} = useResourceFields('bill_payments');
// Fetch accounts list according to the given custom view id.
const {
data: { paymentMades, pagination },
isFetching: isPaymentsLoading,
} = usePaymentMades(query);
// Provider payload.
const provider = {
paymentMades,
pagination,
paymentsFields,
paymentsViews,
isPaymentsLoading,
isFieldsLoading,
isViewsLoading,
};
return (
<DashboardInsider
loading={isViewsLoading || isFieldsLoading}
name={'payment_made'}
>
<PaymentMadesContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const usePaymentMadesContext = () => React.useContext(PaymentMadesContext);
export { PaymentMadesProvider, usePaymentMadesContext };