refactoring: sales tables.

refacoring: purchases tables.
This commit is contained in:
a.bouhuolia
2021-02-11 20:45:06 +02:00
parent 3901c336df
commit d48532a7e6
210 changed files with 2799 additions and 5392 deletions

View File

@@ -8,6 +8,7 @@ import withAlertActions from 'containers/Alert/withAlertActions';
import { useActivateAccount } from 'hooks/query';
import { compose } from 'utils';
/**
* Account activate alert.
*/

View File

@@ -1,9 +1,5 @@
import React, { useState } from 'react';
import {
FormattedMessage as T,
FormattedHTMLMessage,
useIntl,
} from 'react-intl';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { Intent, Alert } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { AppToaster } from 'components';

View File

@@ -12,6 +12,9 @@ import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Account bulk delete alert.
*/
function AccountBulkDeleteAlert({
// #ownProps
name,

View File

@@ -13,7 +13,6 @@ import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { useDeleteAccount } from 'hooks/query';
import { compose } from 'utils';
/**
@@ -48,12 +47,7 @@ function AccountDeleteAlert({
});
closeAlert(name);
})
.catch((error) => {
const {
response: {
data: { errors },
},
} = error;
.catch(({ response: { data: { errors } } }) => {
handleDeleteErrors(errors);
closeAlert(name);
});

View File

@@ -10,7 +10,6 @@ import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { useDeleteBill } from 'hooks/query';
import { compose } from 'utils';
/**
@@ -29,12 +28,12 @@ function BillDeleteAlert({
const { formatMessage } = useIntl();
const { isLoading, mutateAsync: deleteBillMutate } = useDeleteBill();
// handle cancel Bill
// Handle cancel Bill
const handleCancel = () => {
closeAlert(name);
};
// handleConfirm delete invoice
// Handle confirm delete invoice
const handleConfirmBillDelete = () => {
deleteBillMutate(billId).then(() => {
AppToaster.show({

View File

@@ -40,8 +40,9 @@ function BillOpenAlert({
}),
intent: Intent.SUCCESS,
});
closeAlert(name);
})
.finally((error) => {
.catch((error) => {
closeAlert(name);
});
};

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback } from 'react';
import {
FormattedMessage as T,
FormattedHTMLMessage,

View File

@@ -1,14 +1,12 @@
import React, { useCallback } from 'react';
import React from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { Intent, Alert } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { AppToaster } from 'components';
import { useRejectEstimate } from 'hooks/query';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import withEstimateActions from 'containers/Sales/Estimate/withEstimateActions';
import { compose } from 'utils';
@@ -22,9 +20,6 @@ function EstimateRejectAlert({
isOpen,
payload: { estimateId },
// #withEstimateActions
requestRejectEstimate,
// #withAlertActions
closeAlert,
}) {
@@ -40,8 +35,8 @@ function EstimateRejectAlert({
};
// Handle confirm estimate reject.
const handleConfirmEstimateReject = useCallback(() => {
requestRejectEstimate(estimateId)
const handleConfirmEstimateReject = () => {
rejectEstimateMutate(estimateId)
.then(() => {
AppToaster.show({
message: formatMessage({
@@ -49,13 +44,12 @@ function EstimateRejectAlert({
}),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('estimates-table');
})
.catch((error) => {})
.finally(() => {
closeAlert(name);
});
}, [estimateId, rejectEstimateMutate, formatMessage]);
};
return (
<Alert
@@ -77,5 +71,4 @@ function EstimateRejectAlert({
export default compose(
withAlertStoreConnect(),
withAlertActions,
withEstimateActions,
)(EstimateRejectAlert);

View File

@@ -1,20 +1,17 @@
import React, { useCallback } from 'react';
import React from 'react';
import {
FormattedMessage as T,
FormattedHTMLMessage,
useIntl,
} from 'react-intl';
import { Intent, Alert } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { AppToaster } from 'components';
import { useDeleteInvoice } from 'hooks/query';
import { handleDeleteErrors } from 'containers/Sales/Invoice/components';
import { handleDeleteErrors } from 'containers/Sales/Invoices/InvoicesLanding/components';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import withInvoiceActions from 'containers/Sales/Invoice/withInvoiceActions';
import { compose } from 'utils';
@@ -32,12 +29,9 @@ function InvoiceDeleteAlert({
closeAlert,
}) {
const { formatMessage } = useIntl();
const {
mutateAsync: deleteInvoiceMutate,
isLoading
} = useDeleteInvoice();
const { mutateAsync: deleteInvoiceMutate, isLoading } = useDeleteInvoice();
// handle cancel delete invoice alert.
// handle cancel delete invoice alert.
const handleCancelDeleteAlert = () => {
closeAlert(name);
};
@@ -84,5 +78,4 @@ function InvoiceDeleteAlert({
export default compose(
withAlertStoreConnect(),
withAlertActions,
withInvoiceActions,
)(InvoiceDeleteAlert);

View File

@@ -7,7 +7,6 @@ import { AppToaster } from 'components';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import withInvoiceActions from 'containers/Sales/Invoice/withInvoiceActions';
import { compose } from 'utils';
@@ -72,5 +71,4 @@ function InvoiceDeliverAlert({
export default compose(
withAlertStoreConnect(),
withAlertActions,
withInvoiceActions,
)(InvoiceDeliverAlert);

View File

@@ -6,7 +6,6 @@ import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from 'containers/Settings/withSettings';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withEstimateActions from 'containers/Sales/Estimate/withEstimateActions';
import { compose, optionsMapToArray } from 'utils';
@@ -18,15 +17,13 @@ function EstimateNumberDialogContent({
// #withSettings
nextNumber,
numberPrefix,
// #withSettingsActions
requestFetchOptions,
requestSubmitOptions,
// #withDialogActions
closeDialog,
// #withEstimateActions
setEstimateNumberChanged,
}) {
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
@@ -41,7 +38,7 @@ function EstimateNumberDialogContent({
setTimeout(() => {
queryCache.invalidateQueries('settings');
setEstimateNumberChanged(true);
// setEstimateNumberChanged(true);
}, 250);
})
.catch(() => {
@@ -72,5 +69,4 @@ export default compose(
nextNumber: estimatesSettings?.nextNumber,
numberPrefix: estimatesSettings?.numberPrefix,
})),
withEstimateActions,
)(EstimateNumberDialogContent);

View File

@@ -7,7 +7,7 @@ import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from 'containers/Settings/withSettings';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withInvoicesActions from 'containers/Sales/Invoice/withInvoiceActions';
// import withInvoicesActions from 'containers/Sales/Invoice/withInvoiceActions';
import { compose, optionsMapToArray } from 'utils';
@@ -28,7 +28,7 @@ function InvoiceNumberDialogContent({
closeDialog,
// #withInvoicesActions
setInvoiceNumberChanged,
// setInvoiceNumberChanged,
}) {
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
@@ -44,7 +44,7 @@ function InvoiceNumberDialogContent({
setTimeout(() => {
queryCache.invalidateQueries('settings');
setInvoiceNumberChanged(true);
// setInvoiceNumberChanged(true);
}, 250);
})
.catch(() => {
@@ -75,5 +75,5 @@ export default compose(
nextNumber: invoiceSettings?.nextNumber,
numberPrefix: invoiceSettings?.numberPrefix,
})),
withInvoicesActions,
// withInvoicesActions,
)(InvoiceNumberDialogContent);

View File

@@ -1,13 +1,13 @@
import React, { useCallback } from 'react';
import { DialogContent } from 'components';
import { useQuery, queryCache } from 'react-query';
import { useQuery } from 'react-query';
import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withSettings from 'containers/Settings/withSettings';
import withPaymentReceivesActions from 'containers/Sales/PaymentReceive/withPaymentReceivesActions';
// import withPaymentReceivesActions from 'containers/Sales/PaymentReceive/withPaymentReceivesActions';
import { compose, optionsMapToArray } from 'utils';
@@ -28,7 +28,7 @@ function PaymentNumberDialogContent({
closeDialog,
// #withPaymentReceivesActions
setPaymentReceiveNumberChanged,
// setPaymentReceiveNumberChanged,
}) {
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
@@ -43,8 +43,7 @@ function PaymentNumberDialogContent({
closeDialog('payment-receive-number-form');
setTimeout(() => {
queryCache.invalidateQueries('settings');
setPaymentReceiveNumberChanged(true);
// setPaymentReceiveNumberChanged(true);
}, 250);
})
.catch(() => {
@@ -76,5 +75,5 @@ export default compose(
nextNumber: paymentReceiveSettings?.nextNumber,
numberPrefix: paymentReceiveSettings?.numberPrefix,
})),
withPaymentReceivesActions,
// withPaymentReceivesActions,
)(PaymentNumberDialogContent);

View File

@@ -1,13 +1,12 @@
import React, { useCallback } from 'react';
import { DialogContent } from 'components';
import { useQuery, queryCache } from 'react-query';
import { useQuery } from 'react-query';
import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from 'containers/Settings/withSettings';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withReceiptActions from 'containers/Sales/Receipt/withReceiptActions';
import { compose, optionsMapToArray } from 'utils';
@@ -43,7 +42,6 @@ function ReceiptNumberDialogContent({
closeDialog('receipt-number-form');
setTimeout(() => {
queryCache.invalidateQueries('settings');
setReceiptNumberChanged(true);
}, 250);
})
@@ -75,5 +73,4 @@ export default compose(
nextNumber: receiptSettings?.nextNumber,
numberPrefix: receiptSettings?.numberPrefix,
})),
withReceiptActions,
)(ReceiptNumberDialogContent);

View File

@@ -1,6 +1,8 @@
import React, { useEffect } from 'react';
import { useIntl } from 'react-intl';
import 'style/pages/Expense/List.scss';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import ExpenseActionsBar from './ExpenseActionsBar';

View File

@@ -1,296 +0,0 @@
import React, { useCallback, useMemo } from 'react';
import {
Intent,
Button,
Popover,
Menu,
MenuItem,
MenuDivider,
Position,
Tag,
} from '@blueprintjs/core';
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, isBlank } from 'utils';
import { CLASSES } from 'common/classes';
import { useIsValuePassed } from 'hooks';
import { LoadingIndicator, Money, Choose, If } from 'components';
import DataTable from 'components/DataTable';
import BillsEmptyStatus from './BillsEmptyStatus';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetails from 'containers/Views/withViewDetails';
import withBills from './withBills';
import withBillActions from './withBillActions';
import withCurrentView from 'containers/Views/withCurrentView';
import withSettings from 'containers/Settings/withSettings';
// Bills transactions datatable.
function BillsDataTable({
// #withBills
billsCurrentPage,
billsLoading,
billsPageination,
billsCurrentViewId,
// #withDashboardActions
changeCurrentView,
changePageSubtitle,
setTopbarEditView,
// #withBillsActions
addBillsTableQueries,
// #withView
viewMeta,
// #withSettings
baseCurrency,
// #ownProps
loading,
onFetchData,
onEditBill,
onDeleteBill,
onOpenBill,
onSelectedRowsChange,
}) {
const { formatMessage } = useIntl();
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
const page = pageIndex + 1;
addBillsTableQueries({
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
page_size: pageSize,
page,
});
},
[addBillsTableQueries],
);
const handleEditBill = useCallback(
(_bill) => () => {
saveInvoke(onEditBill, _bill);
},
[onEditBill],
);
const handleDeleteBill = useCallback(
(_bill) => () => {
saveInvoke(onDeleteBill, _bill);
},
[onDeleteBill],
);
const actionMenuList = useCallback(
(bill) => (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_bill' })}
onClick={handleEditBill(bill)}
/>
<If condition={!bill.is_open}>
<MenuItem
text={formatMessage({ id: 'mark_as_opened' })}
onClick={() => onOpenBill(bill)}
/>
</If>
<MenuItem
text={formatMessage({ id: 'delete_bill' })}
intent={Intent.DANGER}
onClick={handleDeleteBill(bill)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
),
[handleDeleteBill, handleEditBill, formatMessage],
);
const onRowContextMenu = useCallback(
(cell) => {
return actionMenuList(cell.row.original);
},
[actionMenuList],
);
const columns = useMemo(
() => [
{
id: 'bill_date',
Header: formatMessage({ id: 'bill_date' }),
accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'),
width: 140,
className: 'bill_date',
},
{
id: 'vendor_id',
Header: formatMessage({ id: 'vendor_name' }),
accessor: 'vendor.display_name',
width: 140,
className: 'vendor_id',
},
{
id: 'bill_number',
Header: formatMessage({ id: 'bill_number' }),
accessor: (row) => (row.bill_number ? `#${row.bill_number}` : null),
width: 140,
className: 'bill_number',
},
{
id: 'due_date',
Header: formatMessage({ id: 'due_date' }),
accessor: (r) => moment(r.due_date).format('YYYY MMM DD'),
width: 140,
className: 'due_date',
},
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: (row) =>
!isBlank(row.amount) ? (
<Money amount={row.amount} currency={baseCurrency} />
) : (
''
),
width: 140,
className: 'amount',
},
{
id: 'status',
Header: formatMessage({ id: 'status' }),
accessor: (row) => (
<Choose>
<Choose.When condition={row.is_open}>
<Tag minimal={true} intent={Intent.SUCCESS}>
<T id={'opened'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
),
width: 140,
className: 'status',
},
{
id: 'reference_no',
Header: formatMessage({ id: 'reference_no' }),
accessor: 'reference_no',
width: 140,
className: 'reference_no',
},
{
id: 'actions',
Header: '',
Cell: ({ cell }) => (
<Popover
content={actionMenuList(cell.row.original)}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
),
className: 'actions',
width: 50,
disableResizing: true,
},
],
[actionMenuList, formatMessage],
);
const handleSelectedRowsChange = useCallback(
(selectedRows) => {
saveInvoke(
onSelectedRowsChange,
selectedRows.map((s) => s.original),
);
},
[onSelectedRowsChange],
);
const showEmptyStatus = [
billsCurrentViewId === -1,
billsCurrentPage.length === 0,
].every((condition) => condition === true);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<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>
</div>
);
}
export default compose(
withRouter,
withCurrentView,
withDialogActions,
withDashboardActions,
withBillActions,
withBills(
({
billsCurrentPage,
billsLoading,
billsPageination,
billsTableQuery,
billsCurrentViewId,
}) => ({
billsCurrentPage,
billsLoading,
billsPageination,
billsTableQuery,
billsCurrentViewId,
}),
),
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
withViewDetails(),
)(BillsDataTable);

View File

@@ -1,49 +0,0 @@
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,40 +0,0 @@
import { connect } from 'react-redux';
import {
submitBill,
deleteBill,
editBill,
fetchBillsTable,
fetchBill,
fetchDueBills,
openBill,
} from 'store/Bills/bills.actions';
import t from 'store/types';
const mapDispatchToProps = (dispatch) => ({
requestSubmitBill: (form) => dispatch(submitBill({ form })),
requestFetchBill: (id) => dispatch(fetchBill({ id })),
requestEditBill: (id, form) => dispatch(editBill(id, form)),
requestDeleteBill: (id) => dispatch(deleteBill({ id })),
requestFetchBillsTable: (query = {}) =>
dispatch(fetchBillsTable({ query: { ...query } })),
requestFetchDueBills: (vendorId) => dispatch(fetchDueBills({ vendorId })),
requestOpenBill: (id) => dispatch(openBill({ id })),
changeBillView: (id) =>
dispatch({
type: t.BILLS_SET_CURRENT_VIEW,
currentViewId: parseInt(id, 10),
}),
addBillsTableQueries: (queries) =>
dispatch({
type: t.BILLS_TABLE_QUERIES_ADD,
payload: { queries }
}),
setBillNumberChanged: (isChanged) =>
dispatch({
type: t.BILL_NUMBER_CHANGED,
payload: { isChanged },
}),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,11 +0,0 @@
import { connect } from 'react-redux';
import { getBillByIdFactory } from 'store/Bills/bills.selectors';
export default () => {
const getBillById = getBillByIdFactory();
const mapStateToProps = (state, props) => ({
bill: getBillById(state, props),
});
return connect(mapStateToProps);
};

View File

@@ -1,40 +0,0 @@
import { connect } from 'react-redux';
import { getResourceViews } from 'store/customViews/customViews.selectors';
import {
getBillCurrentPageFactory,
getBillPaginationMetaFactory,
getBillTableQueryFactory,
getVendorPayableBillsFactory,
getVendorPayableBillsEntriesFactory,
getBillsCurrentViewIdFactory,
} from 'store/Bills/bills.selectors';
export default (mapState) => {
const getBillsItems = getBillCurrentPageFactory();
const getBillsPaginationMeta = getBillPaginationMetaFactory();
const getBillTableQuery = getBillTableQueryFactory();
const getVendorPayableBills = getVendorPayableBillsFactory();
const getVendorPayableBillsEntries = getVendorPayableBillsEntriesFactory();
const getBillsCurrentViewId = getBillsCurrentViewIdFactory();
const mapStateToProps = (state, props) => {
const tableQuery = getBillTableQuery(state, props);
const mapped = {
billsCurrentPage: getBillsItems(state, props, tableQuery),
billsCurrentViewId: getBillsCurrentViewId(state),
billsViews: getResourceViews(state, props, 'bills'),
billsItems: state.bills.items,
billsTableQuery: tableQuery,
billsPageination: getBillsPaginationMeta(state, props, tableQuery),
billsLoading: state.bills.loading,
nextBillNumberChanged: state.bills.nextBillNumberChanged,
vendorPayableBills: getVendorPayableBills(state, props),
vendorPayableBillsEntries: getVendorPayableBillsEntries(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -18,7 +18,7 @@ import { FormattedMessage as T, useIntl } from 'react-intl';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { If, DashboardActionViewsList } from 'components';
import withBillActions from './withBillActions';
import withBillsActions from './withBillsActions';
import { useBillsListContext } from './BillsListProvider';
import { compose } from 'utils';
@@ -27,28 +27,31 @@ import { compose } from 'utils';
* Bills actions bar.
*/
function BillActionsBar({
//#withBillActions
addBillsTableQueries,
// #withBillActions
setBillsTableState,
}) {
const history = useHistory();
// React intl.
const { formatMessage } = useIntl();
// Bills list context.
const { billsViews } = useBillsListContext();
const [filterCount] = useState(0);
// Handle click a new bill.
const handleClickNewBill = () => {
history.push('/bills/new');
};
// Handle tab change.
const handleTabChange = (viewId) => {
addBillsTableQueries({
custom_view_id: viewId.id || null,
const handleTabChange = (customView) => {
setBillsTableState({
customViewId: customView.id || null,
});
};
return (
<DashboardActionsBar>
<NavbarGroup>
@@ -111,6 +114,4 @@ function BillActionsBar({
);
}
export default compose(
withBillActions,
)(BillActionsBar);
export default compose(withBillsActions)(BillActionsBar);

View File

@@ -4,15 +4,16 @@ import { useIntl } from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import { BillsListProvider } from './BillsListProvider';
import BillsDataTable from './BillsDataTable';
import BillsActionsBar from './BillsActionsBar';
import BillsAlerts from './BillsAlerts';
import BillsViewPage from './BillsViewPage';
import BillsViewsTabs from './BillsViewsTabs';
import BillsTable from './BillsTable';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withBills from './withBills';
import { compose } from 'utils';
import { transformTableStateToQuery, compose } from 'utils';
/**
* Bills list.
@@ -22,7 +23,7 @@ function BillsList({
changePageTitle,
// #withBills
billsTableQuery,
billsTableState,
}) {
const { formatMessage } = useIntl();
@@ -31,20 +32,20 @@ function BillsList({
}, [changePageTitle, formatMessage]);
return (
<BillsListProvider query={billsTableQuery}>
<BillsListProvider query={transformTableStateToQuery(billsTableState)}>
<BillsActionsBar />
<DashboardPageContent>
<BillsViewPage />
<BillsAlerts />
<BillsViewsTabs />
<BillsTable />
</DashboardPageContent>
<BillsAlerts />
</BillsListProvider>
);
}
export default compose(
withDashboardActions,
withBills(({ billsTableQuery }) => ({
billsTableQuery,
})),
withBills(({ billsTableState }) => ({ billsTableState })),
)(BillsList);

View File

@@ -1,6 +1,7 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useResourceFields, useBills } from 'hooks/query';
import { isTableEmptyStatus } from 'utils';
const BillsListContext = createContext();
@@ -21,9 +22,15 @@ function BillsListProvider({ query, ...props }) {
// Fetch accounts list according to the given custom view id.
const {
data: { bills, pagination },
isFetching: isBillsLoading,
} = useBills(query);
data: { bills, pagination, filterMeta },
isLoading: isBillsLoading,
isFetching: isBillsFetching,
} = useBills(query, { keepPreviousData: true });
// Detarmines the datatable empty status.
const isEmptyStatus = isTableEmptyStatus({
data: bills, pagination, filterMeta,
}) && !isBillsFetching;
// Provider payload.
const provider = {
@@ -33,8 +40,10 @@ function BillsListProvider({ query, ...props }) {
billsViews,
isBillsLoading,
isBillsFetching,
isFieldsLoading,
isViewsLoading,
isEmptyStatus
};
return (

View File

@@ -0,0 +1,108 @@
import React, { useCallback } from 'react';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { compose } from 'utils';
import { CLASSES } from 'common/classes';
import DataTable from 'components/DataTable';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import BillsEmptyStatus from './BillsEmptyStatus';
import withBillActions from './withBillsActions';
import withSettings from 'containers/Settings/withSettings';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { useBillsTableColumns, ActionsMenu } from './components';
import { useBillsListContext } from './BillsListProvider';
/**
* Bills transactions datatable.
*/
function BillsDataTable({
// #withBillsActions
setBillsTableState,
// #withAlerts
openAlert
}) {
// Bills list context.
const {
bills,
pagination,
isBillsLoading,
isBillsFetching,
isEmptyStatus,
} = useBillsListContext();
const history = useHistory();
// Bills table columns.
const columns = useBillsTableColumns();
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
setBillsTableState({
pageIndex,
pageSize,
sortBy,
});
},
[setBillsTableState],
);
// Handle bill edit action.
const handleEditBill = (bill) => {
history.push(`/bills/${bill.id}/edit`);
};
// Handle bill delete action.
const handleDeleteBill = (bill) => {
openAlert('bill-delete', { billId: bill.id });
};
// Handle bill open action.
const handleOpenBill = (bill) => {
openAlert('bill-open', { billId: bill.id });
};
if (isEmptyStatus) {
return <BillsEmptyStatus />;
}
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<DataTable
columns={columns}
data={bills}
loading={isBillsLoading}
headerLoading={isBillsLoading}
progressBarLoading={isBillsFetching}
onFetchData={handleFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
pagination={true}
pagesCount={pagination.pagesCount}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteBill,
onEdit: handleEditBill,
onOpen: handleOpenBill
}}
/>
</div>
);
}
export default compose(
withBillActions,
withAlertsActions,
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
)(BillsDataTable);

View File

@@ -7,7 +7,7 @@ import { pick } from 'lodash';
import { DashboardViewsTabs } from 'components';
import { useBillsListContext } from './BillsListProvider';
import withBillActions from './withBillActions';
import withBillActions from './withBillsActions';
import { compose } from 'utils';
@@ -16,16 +16,17 @@ import { compose } from 'utils';
*/
function BillViewTabs({
//#withBillActions
addBillsTableQueries,
setBillsTableState,
}) {
const { custom_view_id: customViewId = null } = useParams();
// Bills list context.
const { billsViews } = useBillsListContext();
const handleTabsChange = (viewId) => {
addBillsTableQueries({
custom_view_id: customViewId || null,
// Handle tab chaging.
const handleTabsChange = (customView) => {
setBillsTableState({
customViewId: customView || null,
});
};

View File

@@ -0,0 +1,167 @@
import React from 'react';
import {
Intent,
Menu,
MenuItem,
MenuDivider,
Popover,
Button,
Position,
Tag,
} from '@blueprintjs/core';
import { useIntl, FormattedMessage as T } from 'react-intl';
import { Icon, If, Choose, Money } from 'components';
import { safeCallback, isBlank } from 'utils';
import moment from 'moment';
/**
* Actions menu.
*/
export function ActionsMenu({
payload: { onEdit, onOpen, onDelete },
row: { original },
}) {
const { formatMessage } = useIntl();
return (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_bill' })}
onClick={safeCallback(onEdit, original)}
/>
<If condition={!original.is_open}>
<MenuItem
text={formatMessage({ id: 'mark_as_opened' })}
onClick={safeCallback(onOpen, original)}
/>
</If>
<MenuItem
text={formatMessage({ id: 'delete_bill' })}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
);
}
/**
* Amount accessor.
*/
export function AmountAccessor(bill) {
return !isBlank(bill.amount) ? (
<Money amount={bill.amount} currency={'USD'} />
) : (
''
);
}
/**
* Status accessor.
*/
export function StatusAccessor(bill) {
return (
<Choose>
<Choose.When condition={bill.is_open}>
<Tag minimal={true} intent={Intent.SUCCESS}>
<T id={'opened'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
);
}
export function ActionsCell(props) {
return (
<Popover
content={<ActionsMenu {...props} />}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
);
}
/**
* Retrieve bills table columns.
*/
export function useBillsTableColumns() {
const { formatMessage } = useIntl();
return React.useMemo(
() => [
{
id: 'bill_date',
Header: formatMessage({ id: 'bill_date' }),
accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'),
width: 140,
className: 'bill_date',
},
{
id: 'vendor_id',
Header: formatMessage({ id: 'vendor_name' }),
accessor: 'vendor.display_name',
width: 140,
className: 'vendor_id',
},
{
id: 'bill_number',
Header: formatMessage({ id: 'bill_number' }),
accessor: (row) => (row.bill_number ? `#${row.bill_number}` : null),
width: 140,
className: 'bill_number',
},
{
id: 'due_date',
Header: formatMessage({ id: 'due_date' }),
accessor: (r) => moment(r.due_date).format('YYYY MMM DD'),
width: 140,
className: 'due_date',
},
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: AmountAccessor,
width: 140,
className: 'amount',
},
{
id: 'status',
Header: formatMessage({ id: 'status' }),
accessor: StatusAccessor,
width: 140,
className: 'status',
},
{
id: 'reference_no',
Header: formatMessage({ id: 'reference_no' }),
accessor: 'reference_no',
width: 140,
className: 'reference_no',
},
{
id: 'actions',
Header: '',
Cell: ActionsCell,
className: 'actions',
width: 50,
disableResizing: true,
},
],
[formatMessage],
);
}

View File

@@ -0,0 +1,14 @@
import { connect } from 'react-redux';
import { getBillsTableStateFactory } from 'store/Bills/bills.selectors';
export default (mapState) => {
const getBillsTableState = getBillsTableStateFactory();
const mapStateToProps = (state, props) => {
const mapped = {
billsTableState: getBillsTableState(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -0,0 +1,8 @@
import { connect } from 'react-redux';
import { setBillsTableState } from 'store/Bills/bills.actions';
const mapDispatchToProps = (dispatch) => ({
setBillsTableState: (queries) => dispatch(setBillsTableState(queries)),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,249 +0,0 @@
import React, { useCallback, useMemo } from 'react';
import {
Intent,
Button,
Popover,
Menu,
MenuItem,
MenuDivider,
Position,
} from '@blueprintjs/core';
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import { compose, saveInvoke } from 'utils';
import { useIsValuePassed } from 'hooks';
import { CLASSES } from 'common/classes';
import { DataTable, Money, Icon, Choose, LoadingIndicator } from 'components';
import PaymentMadesEmptyStatus from './PaymentMadesEmptyStatus';
import withPaymentMade from './withPaymentMade';
import withPaymentMadeActions from './withPaymentMadeActions';
import withCurrentView from 'containers/Views/withCurrentView';
import withSettings from 'containers/Settings/withSettings';
/**
* Payment made datatable transactions.
*/
function PaymentMadeDataTable({
// #withPaymentMades
paymentMadeCurrentPage,
paymentMadePageination,
paymentMadesLoading,
paymentMadeTableQuery,
paymentMadesCurrentViewId,
// #withPaymentMadeActions
addPaymentMadesTableQueries,
// #withSettings
baseCurrency,
// #ownProps
onEditPaymentMade,
onDeletePaymentMade,
onSelectedRowsChange,
}) {
const isLoaded = useIsValuePassed(paymentMadesLoading, false);
const { formatMessage } = useIntl();
const handleEditPaymentMade = useCallback(
(paymentMade) => () => {
saveInvoke(onEditPaymentMade, paymentMade);
},
[onEditPaymentMade],
);
const handleDeletePaymentMade = useCallback(
(paymentMade) => () => {
saveInvoke(onDeletePaymentMade, paymentMade);
},
[onDeletePaymentMade],
);
const actionMenuList = useCallback(
(paymentMade) => (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_payment_made' })}
onClick={handleEditPaymentMade(paymentMade)}
/>
<MenuItem
text={formatMessage({ id: 'delete_payment_made' })}
intent={Intent.DANGER}
onClick={handleDeletePaymentMade(paymentMade)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
),
[handleDeletePaymentMade, handleEditPaymentMade, formatMessage],
);
const onRowContextMenu = useCallback(
(cell) => {
return actionMenuList(cell.row.original);
},
[actionMenuList],
);
const columns = useMemo(
() => [
{
id: 'payment_date',
Header: formatMessage({ id: 'payment_date' }),
accessor: (r) => moment(r.payment_date).format('YYYY MMM DD'),
width: 140,
className: 'payment_date',
},
{
id: 'vendor_id',
Header: formatMessage({ id: 'vendor_name' }),
accessor: 'vendor.display_name',
width: 140,
className: 'vendor_id',
},
{
id: 'payment_number',
Header: formatMessage({ id: 'payment_number' }),
accessor: (row) =>
row.payment_number ? `#${row.payment_number}` : null,
width: 140,
className: 'payment_number',
},
{
id: 'payment_account_id',
Header: formatMessage({ id: 'payment_account' }),
accessor: 'payment_account.name',
width: 140,
className: 'payment_account_id',
},
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: (r) => <Money amount={r.amount} currency={baseCurrency} />,
width: 140,
className: 'amount',
},
{
id: 'reference',
Header: formatMessage({ id: 'reference' }),
accessor: 'reference',
width: 140,
className: 'reference',
},
{
id: 'actions',
Header: '',
Cell: ({ cell }) => (
<Popover
content={actionMenuList(cell.row.original)}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
),
className: 'actions',
width: 50,
disableResizing: true,
},
],
[actionMenuList, formatMessage],
);
const handleDataTableFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
addPaymentMadesTableQueries({
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
page_size: pageSize,
page: pageIndex + 1,
});
},
[addPaymentMadesTableQueries],
);
const handleSelectedRowsChange = useCallback(
(selectedRows) => {
saveInvoke(
onSelectedRowsChange,
selectedRows.map((s) => s.original),
);
},
[onSelectedRowsChange],
);
const showEmptyStatuts = [
paymentMadeCurrentPage.length === 0,
paymentMadesCurrentViewId === -1,
].every((condition) => condition === true);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={paymentMadesLoading && !isLoaded}>
<Choose>
<Choose.When condition={showEmptyStatuts}>
<PaymentMadesEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
columns={columns}
data={paymentMadeCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={paymentMadePageination.pagesCount}
initialPageSize={paymentMadeTableQuery.page_size}
initialPageIndex={paymentMadeTableQuery.page - 1}
autoResetSortBy={false}
autoResetPage={false}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
}
export default compose(
withRouter,
withCurrentView,
withPaymentMadeActions,
withPaymentMade(
({
paymentMadeCurrentPage,
paymentMadesLoading,
paymentMadePageination,
paymentMadeTableQuery,
paymentMadesCurrentViewId,
}) => ({
paymentMadeCurrentPage,
paymentMadesLoading,
paymentMadePageination,
paymentMadeTableQuery,
paymentMadesCurrentViewId,
}),
),
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
)(PaymentMadeDataTable);

View File

@@ -4,7 +4,7 @@ import PaymentMadeDeleteAlert from 'containers/Alerts/PaymentMades/PaymentMadeDe
export default function PaymentMadesAlerts() {
return (
<div>
<PaymentMadeDeleteAlert dialogName={'payment-made-delete'} />
<PaymentMadeDeleteAlert name={'payment-made-delete'} />
</div>
);
}

View File

@@ -27,8 +27,8 @@ import { compose } from 'utils';
* Payment made actions bar.
*/
function PaymentMadeActionsBar({
//#withPaymentMadesActions
addPaymentMadesTableQueries,
// #withPaymentMadesActions
setPaymentMadesTableState,
}) {
const history = useHistory();
const { formatMessage } = useIntl();
@@ -41,12 +41,18 @@ function PaymentMadeActionsBar({
history.push('/payment-mades/new');
};
// Handle tab changing.
const handleTabChange = (customView) => {
setPaymentMadesTableState({ customViewId: customView.id || null });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<DashboardActionViewsList
resourceName={'bill_payments'}
views={paymentMadesViews}
onChange={handleTabChange}
/>
<NavbarDivider />
<Button

View File

@@ -1,16 +1,17 @@
import React, { useEffect } from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import PaymentMadeActionsBar from './PaymentMadeActionsBar';
import PaymentMadesAlerts from './PaymentMadesAlerts';
import PaymentMadesAlerts from '../PaymentMadesAlerts';
import PaymentMadesTable from './PaymentMadesTable';
import { PaymentMadesListProvider } from './PaymentMadesListProvider';
import PaymentMadesView from './PaymentMadesView';
import PaymentMadeViewTabs from './PaymentMadeViewTabs';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withPaymentMades from './withPaymentMade';
import { compose } from 'utils';
import { compose, transformTableStateToQuery } from 'utils';
/**
* Payment mades list.
@@ -20,7 +21,7 @@ function PaymentMadeList({
changePageTitle,
// #withPaymentMades
paymentMadeTableQuery,
paymentMadesTableState,
}) {
const { formatMessage } = useIntl();
@@ -29,20 +30,24 @@ function PaymentMadeList({
}, [changePageTitle, formatMessage]);
return (
<PaymentMadesListProvider query={paymentMadeTableQuery}>
<PaymentMadesListProvider
query={transformTableStateToQuery(paymentMadesTableState)}
>
<PaymentMadeActionsBar />
<DashboardPageContent>
<PaymentMadesView />
<PaymentMadesAlerts />
<PaymentMadeViewTabs />
<PaymentMadesTable />
</DashboardPageContent>
<PaymentMadesAlerts />
</PaymentMadesListProvider>
);
}
export default compose(
withDashboardActions,
withPaymentMades(({ paymentMadeTableQuery }) => ({
paymentMadeTableQuery,
withPaymentMades(({ paymentMadesTableState }) => ({
paymentMadesTableState,
})),
)(PaymentMadeList);

View File

@@ -2,7 +2,6 @@ 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 } from 'react-router-dom';
import { pick } from 'lodash';
import { DashboardViewsTabs } from 'components';
@@ -11,20 +10,27 @@ import { usePaymentMadesListContext } from './PaymentMadesListProvider';
import withPaymentMadeActions from './withPaymentMadeActions';
import { compose } from 'utils';
import withPaymentMade from './withPaymentMade';
/**
* Payment made views tabs.
*/
function PaymentMadeViewTabs({
//#withPaymentMadesActions
addPaymentMadesTableQueries,
// #withPaymentMadesActions
setPaymentMadesTableState,
// #withPaymentMade
paymentMadesTableState,
}) {
const history = useHistory();
const { custom_view_id: customViewId = null } = useParams();
// Payment receives list context.
const { paymentMadesViews } = usePaymentMadesListContext();
const handleTabsChange = (viewId) => {
addPaymentMadesTableQueries({
custom_view_id: viewId || null,
// Handle the active tab changning.
const handleTabsChange = (customView) => {
setPaymentMadesTableState({
customViewId: customView.id || null,
});
};
@@ -40,7 +46,7 @@ function PaymentMadeViewTabs({
<Navbar className={'navbar--dashboard-views'}>
<NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs
initialViewId={customViewId}
customViewId={paymentMadesTableState.customViewId}
defaultTabText={<T id={'all_payments'} />}
tabs={tabs}
onNewViewTabClick={handleClickNewView}
@@ -51,4 +57,7 @@ function PaymentMadeViewTabs({
);
}
export default compose(withPaymentMadeActions)(PaymentMadeViewTabs);
export default compose(
withPaymentMadeActions,
withPaymentMade(({ paymentMadesTableState }) => ({ paymentMadesTableState }))
)(PaymentMadeViewTabs);

View File

@@ -3,8 +3,9 @@ import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useResourceViews,
useResourceFields,
usePaymentReceives,
usePaymentMades,
} from 'hooks/query';
import { isTableEmptyStatus } from 'utils';
const PaymentMadesListContext = createContext();
@@ -26,20 +27,32 @@ function PaymentMadesListProvider({ accountsTableQuery, ...props }) {
// Fetch accounts list according to the given custom view id.
const {
data: { paymentMades, pagination },
isFetching: isInvoicesLoading,
} = usePaymentReceives(accountsTableQuery);
data: { paymentMades, pagination, filterMeta },
isLoading: isPaymentsLoading,
isFetching: isPaymentsFetching,
} = usePaymentMades(accountsTableQuery, { keepPreviousData: true });
// Detarmines the datatable empty status.
const isEmptyStatus =
isTableEmptyStatus({
data: paymentMades,
pagination,
filterMeta,
}) && !isPaymentsLoading;
// Provider payload.
const provider = {
paymentMades,
pagination,
filterMeta,
paymentMadesFields,
paymentMadesViews,
isInvoicesLoading,
isPaymentsLoading,
isPaymentsFetching,
isFieldsLoading,
isViewsLoading,
isEmptyStatus
};
return (

View File

@@ -0,0 +1,95 @@
import React, { useCallback } from 'react';
import classNames from 'classnames';
import { compose } from 'utils';
import { CLASSES } from 'common/classes';
import { DataTable } from 'components';
import PaymentMadesEmptyStatus from './PaymentMadesEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import withPaymentMadeActions from './withPaymentMadeActions';
import withSettings from 'containers/Settings/withSettings';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { usePaymentMadesTableColumns, ActionsMenu } from './components';
import { usePaymentMadesListContext } from './PaymentMadesListProvider';
/**
* Payment made datatable transactions.
*/
function PaymentMadesTable({
// #withPaymentMadeActions
addPaymentMadesTableQueries,
// #withAlerts
openAlert
}) {
// Payment mades table columns.
const columns = usePaymentMadesTableColumns();
// Payment mades list context.
const {
paymentMades,
pagination,
isEmptyStatus,
isPaymentsLoading,
isPaymentsFetching,
} = usePaymentMadesListContext();
// Handles the edit payment made action.
const handleEditPaymentMade = (paymentMade) => {};
// Handles the delete payment made action.
const handleDeletePaymentMade = (paymentMade) => {
openAlert('payment-made-delete', { paymentMadeId: paymentMade.id })
};
// Handle datatable fetch data once the table state change.
const handleDataTableFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
addPaymentMadesTableQueries({ pageIndex, pageSize, sortBy });
},
[addPaymentMadesTableQueries],
);
if (isEmptyStatus) {
return <PaymentMadesEmptyStatus />;
}
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<DataTable
columns={columns}
data={paymentMades}
onFetchData={handleDataTableFetchData}
loading={isPaymentsLoading}
headerLoading={isPaymentsLoading}
progressBarLoading={isPaymentsFetching}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
pagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditPaymentMade,
onDelete: handleDeletePaymentMade,
}}
/>
</div>
);
}
export default compose(
withPaymentMadeActions,
withAlertsActions,
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
)(PaymentMadesTable);

View File

@@ -21,7 +21,7 @@ function PaymentMadesViewPage({
exact={true}
path={['/payment-mades/:custom_view_id/custom_view', '/payment-mades']}
>
<PaymentMadeViewTabs />
{/* <PaymentMadeDataTable
onDeletePaymentMade={handleDeletePaymentMade}
onEditPaymentMade={handleEditPaymentMade}

View File

@@ -0,0 +1,133 @@
import React from 'react';
import moment from 'moment';
import {
Intent,
Button,
Popover,
Menu,
MenuItem,
MenuDivider,
Position,
} from '@blueprintjs/core';
import { useIntl } from 'react-intl';
import { Icon, Money } from 'components';
import { safeCallback } from 'utils';
export function DateCell({ value }) {
return moment(value).format('YYYY MMM DD');
}
export function AmountAccessor(row) {
return <Money amount={row.amount} currency={'USD'} />
}
/**
* Actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onDelete },
}) {
const { formatMessage } = useIntl();
return (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_payment_made' })}
onClick={safeCallback(onEdit, original)}
/>
<MenuItem
text={formatMessage({ id: 'delete_payment_made' })}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
);
}
/**
* Payment mades table actions cell.
*/
export function ActionsCell(props) {
return (
<Popover
content={<ActionsMenu {...props} />}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
);
}
/**
* Retrieve payment mades table columns.
*/
export function usePaymentMadesTableColumns() {
const { formatMessage } = useIntl();
return React.useMemo(
() => [
{
id: 'payment_date',
Header: formatMessage({ id: 'payment_date' }),
Cell: DateCell,
accessor: 'payment_date',
width: 140,
className: 'payment_date',
},
{
id: 'vendor_id',
Header: formatMessage({ id: 'vendor_name' }),
accessor: 'vendor.display_name',
width: 140,
className: 'vendor_id',
},
{
id: 'payment_number',
Header: formatMessage({ id: 'payment_number' }),
accessor: (row) =>
row.payment_number ? `#${row.payment_number}` : null,
width: 140,
className: 'payment_number',
},
{
id: 'payment_account_id',
Header: formatMessage({ id: 'payment_account' }),
accessor: 'payment_account.name',
width: 140,
className: 'payment_account_id',
},
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: AmountAccessor,
width: 140,
className: 'amount',
},
{
id: 'reference',
Header: formatMessage({ id: 'reference' }),
accessor: 'reference',
width: 140,
className: 'reference',
},
{
id: 'actions',
Header: '',
Cell: ActionsCell,
className: 'actions',
width: 50,
disableResizing: true,
},
],
[formatMessage],
);
}

View File

@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import {
getPaymentMadesTableStateFactory
} from 'store/PaymentMades/paymentMades.selector';
export default (mapState) => {
const getPaymentMadesTableState = getPaymentMadesTableStateFactory();
const mapStateToProps = (state, props) => {
const mapped = {
paymentMadesTableState: getPaymentMadesTableState(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -0,0 +1,8 @@
import { connect } from 'react-redux';
import { setPaymentMadesTableState } from 'store/PaymentMades/paymentMades.actions';
const mapDispatchToProps = (dispatch) => ({
setPaymentMadesTableState: (state) =>
dispatch(setPaymentMadesTableState(state)),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,39 +0,0 @@
import { connect } from 'react-redux';
import { getResourceViews } from 'store/customViews/customViews.selectors';
import {
getPaymentMadeCurrentPageFactory,
getPaymentMadePaginationMetaFactory,
getPaymentMadeTableQuery,
getPaymentMadeEntriesFactory,
getPaymentMadesCurrentViewIdFactory
} from 'store/PaymentMades/paymentMade.selector';
export default (mapState) => {
const getPyamentMadesItems = getPaymentMadeCurrentPageFactory();
const getPyamentMadesPaginationMeta = getPaymentMadePaginationMetaFactory();
const getPaymentMadeEntries = getPaymentMadeEntriesFactory();
const getPaymentMadesCurrentViewId = getPaymentMadesCurrentViewIdFactory();
const mapStateToProps = (state, props) => {
const query = getPaymentMadeTableQuery(state, props);
const mapped = {
paymentMadeCurrentPage: getPyamentMadesItems(state, props, query),
paymentMadeViews: getResourceViews(state, props, 'bill_payments'),
paymentMadeItems: state.paymentMades.items,
paymentMadeTableQuery: query,
paymentMadePageination: getPyamentMadesPaginationMeta(
state,
props,
query,
),
paymentMadesLoading: state.paymentMades.loading,
nextPaymentNumberChanged: state.paymentMades.nextPaymentNumberChanged,
paymentMadeEntries: getPaymentMadeEntries(state, props),
paymentMadesCurrentViewId: getPaymentMadesCurrentViewId(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -1,39 +0,0 @@
import { connect } from 'react-redux';
import t from 'store/types';
import {
submitPaymentMade,
editPaymentMade,
deletePaymentMade,
fetchPaymentMadesTable,
fetchPaymentMade,
fetchPaymentMadeBills,
} from 'store/PaymentMades/paymentMade.actions';
const mapDispatchToProps = (dispatch) => ({
requestSubmitPaymentMade: (form) => dispatch(submitPaymentMade({ form })),
requestFetchPaymentMade: (id) => dispatch(fetchPaymentMade({ id })),
requestEditPaymentMade: (id, form) => dispatch(editPaymentMade(id, form)),
requestDeletePaymentMade: (id) => dispatch(deletePaymentMade({ id })),
requestFetchPaymentMadesTable: (query = {}) =>
dispatch(fetchPaymentMadesTable({ query: { ...query } })),
requestFetchPaymentMadeBills: (paymentMadeId) => dispatch(fetchPaymentMadeBills({ paymentMadeId })),
changePaymentMadeView: (id) =>
dispatch({
type: t.PAYMENT_MADE_SET_CURRENT_VIEW,
currentViewId: parseInt(id, 10),
}),
addPaymentMadesTableQueries: (queries) =>
dispatch({
type: t.PAYMENT_MADES_TABLE_QUERIES_ADD,
payload: { queries },
}),
setPaymentNumberChange: (isChanged) =>
dispatch({
type: t.PAYMENT_MADES_NUMBER_CHANGED,
payload: { isChanged },
}),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,84 +0,0 @@
import React, { useCallback } from 'react';
import { Switch, Route, useHistory } from 'react-router-dom';
import EstimateViewTabs from './EstimateViewTabs';
import EstimatesDataTable from './EstimatesDataTable';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Estimates list view page.
*/
function EstimatesViewPage({
// #withAlertActions
openAlert,
}) {
const history = useHistory();
// handle delete estimate click
const handleDeleteEstimate = useCallback(
({ id }) => {
openAlert('estimate-delete', { estimateId: id });
},
[openAlert],
);
// Handle cancel/confirm estimate deliver.
const handleDeliverEstimate = useCallback(
({ id }) => {
openAlert('estimate-deliver', { estimateId: id });
},
[openAlert],
);
// Handle cancel/confirm estimate approve.
const handleApproveEstimate = useCallback(
({ id }) => {
openAlert('estimate-Approve', { estimateId: id });
},
[openAlert],
);
// Handle cancel/confirm estimate reject.
const handleRejectEstimate = useCallback(
({ id }) => {
openAlert('estimate-reject', { estimateId: id });
},
[openAlert],
);
const handleEditEstimate = useCallback(
(estimate) => {
history.push(`/estimates/${estimate.id}/edit`);
},
[history],
);
return (
<Switch>
<Route
exact={true}
path={['/estimates/:custom_view_id/custom_view', '/estimates']}
>
<EstimateViewTabs />
{/* <EstimatesDataTable
onDeleteEstimate={handleDeleteEstimate}
onEditEstimate={handleEditEstimate}
onDeliverEstimate={handleDeliverEstimate}
onApproveEstimate={handleApproveEstimate}
onRejectEstimate={handleRejectEstimate}
onSelectedRowsChange={handleSelectedRowsChange}
/> */}
</Route>
</Switch>
);
}
export default compose(
withAlertsActions,
withDialogActions,
)(EstimatesViewPage)

View File

@@ -1,292 +0,0 @@
import React, { useCallback, useMemo } from 'react';
import {
Intent,
Button,
Popover,
Menu,
MenuItem,
MenuDivider,
Position,
Tag,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import { CLASSES } from 'common/classes';
import { compose, saveInvoke } from 'utils';
import { useIsValuePassed } from 'hooks';
import LoadingIndicator from 'components/LoadingIndicator';
import { DataTable, Money, Choose, Icon, If } from 'components';
import EstimatesEmptyStatus from './EstimatesEmptyStatus';
import { statusAccessor } from './components';
import withEstimates from './withEstimates';
import withEstimateActions from './withEstimateActions';
import withSettings from 'containers/Settings/withSettings';
// Estimates transactions datatable.
function EstimatesDataTable({
// #withEstimates
estimatesCurrentPage,
estimatesLoading,
estimatesPageination,
estimatesTableQuery,
estimatesCurrentViewId,
// #withEstimatesActions
addEstimatesTableQueries,
// #withSettings
baseCurrency,
// #ownProps
onEditEstimate,
onDeleteEstimate,
onDeliverEstimate,
onApproveEstimate,
onRejectEstimate,
onSelectedRowsChange,
}) {
const { formatMessage } = useIntl();
const isLoaded = useIsValuePassed(estimatesLoading, false);
const handleEditEstimate = useCallback(
(estimate) => () => {
saveInvoke(onEditEstimate, estimate);
},
[onEditEstimate],
);
const handleDeleteEstimate = useCallback(
(estimate) => () => {
saveInvoke(onDeleteEstimate, estimate);
},
[onDeleteEstimate],
);
const actionMenuList = useCallback(
(estimate) => (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_estimate' })}
onClick={handleEditEstimate(estimate)}
/>
<If condition={!estimate.is_delivered}>
<MenuItem
text={formatMessage({ id: 'mark_as_delivered' })}
onClick={() => onDeliverEstimate(estimate)}
/>
</If>
<Choose>
<Choose.When
condition={estimate.is_delivered && estimate.is_approved}
>
<MenuItem
text={formatMessage({ id: 'mark_as_rejected' })}
onClick={() => onRejectEstimate(estimate)}
/>
</Choose.When>
<Choose.When
condition={estimate.is_delivered && estimate.is_rejected}
>
<MenuItem
text={formatMessage({ id: 'mark_as_approved' })}
onClick={() => onApproveEstimate(estimate)}
/>
</Choose.When>
<Choose.When condition={estimate.is_delivered}>
<MenuItem
text={formatMessage({ id: 'mark_as_approved' })}
onClick={() => onApproveEstimate(estimate)}
/>
<MenuItem
text={formatMessage({ id: 'mark_as_rejected' })}
onClick={() => onRejectEstimate(estimate)}
/>
</Choose.When>
</Choose>
<MenuItem
text={formatMessage({ id: 'delete_estimate' })}
intent={Intent.DANGER}
onClick={handleDeleteEstimate(estimate)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
),
[handleDeleteEstimate, handleEditEstimate, formatMessage],
);
const onRowContextMenu = useCallback(
(cell) => {
return actionMenuList(cell.row.original);
},
[actionMenuList],
);
const columns = useMemo(
() => [
{
id: 'estimate_date',
Header: formatMessage({ id: 'estimate_date' }),
accessor: (r) => moment(r.estimate_date).format('YYYY MMM DD'),
width: 140,
className: 'estimate_date',
},
{
id: 'customer_id',
Header: formatMessage({ id: 'customer_name' }),
accessor: 'customer.display_name',
width: 140,
className: 'customer_id',
},
{
id: 'expiration_date',
Header: formatMessage({ id: 'expiration_date' }),
accessor: (r) => moment(r.expiration_date).format('YYYY MMM DD'),
width: 140,
className: 'expiration_date',
},
{
id: 'estimate_number',
Header: formatMessage({ id: 'estimate_number' }),
accessor: (row) =>
row.estimate_number ? `#${row.estimate_number}` : null,
width: 140,
className: 'estimate_number',
},
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: (r) => <Money amount={r.amount} currency={baseCurrency} />,
width: 140,
className: 'amount',
},
{
id: 'status',
Header: formatMessage({ id: 'status' }),
accessor: (row) => statusAccessor(row),
width: 140,
className: 'status',
},
{
id: 'reference',
Header: formatMessage({ id: 'reference_no' }),
accessor: 'reference',
width: 140,
className: 'reference',
},
{
id: 'actions',
Header: '',
Cell: ({ cell }) => (
<Popover
content={actionMenuList(cell.row.original)}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
),
className: 'actions',
width: 50,
disableResizing: true,
},
],
[actionMenuList, formatMessage],
);
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
const page = pageIndex + 1;
addEstimatesTableQueries({
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
page_size: pageSize,
page,
});
},
[addEstimatesTableQueries],
);
const handleSelectedRowsChange = useCallback(
(selectedRows) => {
saveInvoke(
onSelectedRowsChange,
selectedRows.map((s) => s.original),
);
},
[onSelectedRowsChange],
);
const showEmptyStatus = [
estimatesCurrentPage.length === 0,
estimatesCurrentViewId === -1,
].every((d) => d === true);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={estimatesLoading && !isLoaded} mount={false}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<EstimatesEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
columns={columns}
data={estimatesCurrentPage}
onFetchData={handleFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={estimatesPageination.pagesCount}
initialPageSize={estimatesTableQuery.page_size}
initialPageIndex={estimatesTableQuery.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
}
export default compose(
withEstimateActions,
withEstimates(
({
estimatesCurrentPage,
estimatesLoading,
estimatesPageination,
estimatesTableQuery,
estimatesCurrentViewId,
}) => ({
estimatesCurrentPage,
estimatesLoading,
estimatesPageination,
estimatesTableQuery,
estimatesCurrentViewId,
}),
),
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
)(EstimatesDataTable);

View File

@@ -1,31 +0,0 @@
import React from 'react';
import { Intent, Tag } from '@blueprintjs/core';
import { Choose, If } from 'components';
import { FormattedMessage as T, useIntl } from 'react-intl';
export const statusAccessor = (row) => (
<Choose>
<Choose.When condition={row.is_delivered && row.is_approved}>
<Tag minimal={true} intent={Intent.SUCCESS}>
<T id={'approved'} />
</Tag>
</Choose.When>
<Choose.When condition={row.is_delivered && row.is_rejected}>
<Tag minimal={true} intent={Intent.DANGER}>
<T id={'rejected'} />
</Tag>
</Choose.When>
<Choose.When
condition={row.is_delivered && !row.is_rejected && !row.is_approved}
>
<Tag minimal={true} intent={Intent.SUCCESS}>
<T id={'delivered'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
);

View File

@@ -1,48 +0,0 @@
import { connect } from 'react-redux';
import {
submitEstimate,
editEstimate,
deleteEstimate,
fetchEstimate,
fetchEstimatesTable,
deliverEstimate,
approveEstimate,
rejectEstimate,
} from 'store/Estimate/estimates.actions';
import t from 'store/types';
const mapDispatchToProps = (dispatch) => ({
requestSubmitEstimate: (form) => dispatch(submitEstimate({ form })),
requestFetchEstimate: (id) => dispatch(fetchEstimate({ id })),
requestEditEstimate: (id, form) => dispatch(editEstimate(id, form)),
requestFetchEstimatesTable: (query = {}) =>
dispatch(fetchEstimatesTable({ query: { ...query } })),
requestDeleteEstimate: (id) => dispatch(deleteEstimate({ id })),
requestDeliveredEstimate: (id) => dispatch(deliverEstimate({ id })),
requestApproveEstimate: (id) => dispatch(approveEstimate({ id })),
requestRejectEstimate: (id) => dispatch(rejectEstimate({ id })),
changeEstimateView: (id) =>
dispatch({
type: t.ESTIMATES_SET_CURRENT_VIEW,
currentViewId: parseInt(id, 10),
}),
addEstimatesTableQueries: (queries) =>
dispatch({
type: t.ESTIMATES_TABLE_QUERIES_ADD,
payload: { queries },
}),
setEstimateNumberChanged: (isChanged) =>
dispatch({
type: t.ESTIMATE_NUMBER_CHANGED,
payload: { isChanged },
}),
setSelectedRowsEstimates: (selectedRows) =>
dispatch({
type: t.ESTIMATES_SELECTED_ROWS_SET,
payload: { selectedRows },
}),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,36 +0,0 @@
import { connect } from 'react-redux';
import { getResourceViews } from 'store/customViews/customViews.selectors';
import {
getEstimateCurrentPageFactory,
getEstimatesTableQueryFactory,
getEstimatesPaginationMetaFactory,
getEstimatesCurrentViewIdFactory,
} from 'store/Estimate/estimates.selectors';
export default (mapState) => {
const getEstimatesItems = getEstimateCurrentPageFactory();
const getEstimatesPaginationMeta = getEstimatesPaginationMetaFactory();
const getEstimatesTableQuery = getEstimatesTableQueryFactory();
const getEstimatesCurrentViewId = getEstimatesCurrentViewIdFactory();
const mapStateToProps = (state, props) => {
const query = getEstimatesTableQuery(state, props);
const mapped = {
estimatesCurrentPage: getEstimatesItems(state, props, query),
estimatesCurrentViewId: getEstimatesCurrentViewId(state, props),
estimateViews: getResourceViews(state, props, 'sale_estimate'),
estimateItems: state.salesEstimates.items,
estimateSelectedRows: state.salesEstimates.selectedRows,
estimatesTableQuery: query,
estimatesPageination: getEstimatesPaginationMeta(state, props, query),
estimatesLoading: state.salesEstimates.loading,
estimateNumberChanged: state.salesEstimates.journalNumberChanged,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -17,7 +17,7 @@ import { FormattedMessage as T, useIntl } from 'react-intl';
import { If, DashboardActionViewsList } from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import withEstimateActions from './withEstimateActions';
import withEstimatesActions from './withEstimatesActions';
import { useEstimatesListContext } from './EstimatesListProvider';
import { compose } from 'utils';
@@ -27,7 +27,7 @@ import { compose } from 'utils';
*/
function EstimateActionsBar({
// #withEstimateActions
addEstimatesTableQueries,
setEstimatesTableState,
}) {
const history = useHistory();
const { formatMessage } = useIntl();
@@ -42,9 +42,9 @@ function EstimateActionsBar({
history.push('/estimates/new');
};
const handleTabChange = (viewId) => {
addEstimatesTableQueries({
custom_view_id: viewId.id || null,
const handleTabChange = (customView) => {
setEstimatesTableState({
customViewId: customView.id || null,
});
};
@@ -111,5 +111,5 @@ function EstimateActionsBar({
}
export default compose(
withEstimateActions,
withEstimatesActions,
)(EstimateActionsBar);

View File

@@ -0,0 +1,127 @@
import React, { useCallback } from 'react';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes';
import { compose } from 'utils';
import { DataTable } from 'components';
import EstimatesEmptyStatus from './EstimatesEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import withEstimatesActions from './withEstimatesActions';
import withSettings from 'containers/Settings/withSettings';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { useEstimatesListContext } from './EstimatesListProvider';
import { ActionsMenu, useEstiamtesTableColumns } from './components';
/**
* Estimates datatable.
*/
function EstimatesDataTable({
// #withEstimatesActions
setEstimatesTableState,
// #withAlertsActions
openAlert,
}) {
const history = useHistory();
// Estimates list context.
const {
estimates,
pagination,
isEmptyStatus,
isEstimatesLoading,
isEstimatesFetching,
} = useEstimatesListContext();
// Estimates table columns.
const columns = useEstiamtesTableColumns();
// Handle estimate edit action.
const handleEditEstimate = (estimate) => {
history.push(`/estimates/${estimate.id}/edit`);
};
// Handle estimate delete action.
const handleDeleteEstimate = ({ id }) => {
openAlert('estimate-delete', { estimateId: id });
};
// Handle cancel/confirm estimate deliver.
const handleDeliverEstimate = ({ id }) => {
openAlert('estimate-deliver', { estimateId: id });
};
// Handle cancel/confirm estimate approve.
const handleApproveEstimate = ({ id }) => {
openAlert('estimate-Approve', { estimateId: id });
};
// Handle cancel/confirm estimate reject.
const handleRejectEstimate = ({ id }) => {
openAlert('estimate-reject', { estimateId: id });
};
// Handles fetch data.
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
setEstimatesTableState({
pageIndex,
pageSize,
sortBy,
});
},
[setEstimatesTableState],
);
if (isEmptyStatus) {
return <EstimatesEmptyStatus />;
}
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<DataTable
columns={columns}
data={estimates}
loading={isEstimatesLoading}
headerLoading={isEstimatesLoading}
progressBarLoading={isEstimatesFetching}
onFetchData={handleFetchData}
noInitialFetch={true}
manualSortBy={true}
selectionColumn={true}
sticky={true}
pagination={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onApprove: handleApproveEstimate,
onEdit: handleEditEstimate,
onReject: handleRejectEstimate,
onDeliver: handleDeliverEstimate,
onDelete: handleDeleteEstimate,
}}
/>
</div>
);
}
export default compose(
withEstimatesActions,
withAlertsActions,
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
)(EstimatesDataTable);

View File

@@ -2,15 +2,16 @@ import React, { useEffect } from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import EstimateActionsBar from './EstimateActionsBar';
import EstimatesAlerts from './EstimatesAlerts';
import EstiamtesViewPage from './EstiamtesViewPage';
import EstimatesActionsBar from './EstimatesActionsBar';
import EstimatesAlerts from '../EstimatesAlerts';
import EstimatesViewTabs from './EstimatesViewTabs';
import EstimatesDataTable from './EstimatesDataTable';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withEstimates from './withEstimates';
import { EstimatesListProvider } from './EstimatesListProvider';
import { compose } from 'utils';
import { compose, transformTableStateToQuery } from 'utils';
/**
* Sale estimates list page.
@@ -20,7 +21,7 @@ function EstimatesList({
changePageTitle,
// #withEstimate
estimatesTableQuery,
estimatesTableState,
}) {
const { formatMessage } = useIntl();
@@ -29,20 +30,22 @@ function EstimatesList({
}, [changePageTitle, formatMessage]);
return (
<EstimatesListProvider query={estimatesTableQuery}>
<EstimateActionsBar />
<EstimatesListProvider
query={transformTableStateToQuery(estimatesTableState)}
>
<EstimatesActionsBar />
<DashboardPageContent>
<EstiamtesViewPage />
<EstimatesAlerts />
<EstimatesViewTabs />
<EstimatesDataTable />
</DashboardPageContent>
<EstimatesAlerts />
</EstimatesListProvider>
);
}
export default compose(
withDashboardActions,
withEstimates(({ estimatesTableQuery }) => ({
estimatesTableQuery,
})),
withEstimates(({ estimatesTableState }) => ({ estimatesTableState })),
)(EstimatesList);

View File

@@ -1,6 +1,7 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useResourceFields, useEstimates } from 'hooks/query';
import { isTableEmptyStatus } from 'utils';
const EstimatesListContext = createContext();
@@ -8,12 +9,12 @@ const EstimatesListContext = createContext();
* Sale estimates data provider.
*/
function EstimatesListProvider({ query, ...props }) {
// Fetch estimates resource views and fields.
// Fetches estimates resource views and fields.
const { data: estimatesViews, isFetching: isViewsLoading } = useResourceViews(
'sale_estimates',
);
// Fetch the estimates resource fields.
// Fetches the estimates resource fields.
const {
data: estimatesFields,
isFetching: isFieldsLoading,
@@ -21,9 +22,18 @@ function EstimatesListProvider({ query, ...props }) {
// Fetch estimates list according to the given custom view id.
const {
data: { estimates, pagination },
isFetching: isEstimatesLoading,
} = useEstimates(query);
data: { estimates, pagination, filterMeta },
isLoading: isEstimatesLoading,
isFetching: isEstimatesFetching,
} = useEstimates(query, { keepPreviousData: true });
// Detarmines the datatable empty status.
const isEmptyStatus =
isTableEmptyStatus({
data: estimates,
pagination,
filterMeta,
}) && !isEstimatesFetching;
// Provider payload.
const provider = {
@@ -33,8 +43,11 @@ function EstimatesListProvider({ query, ...props }) {
estimatesViews,
isEstimatesLoading,
isEstimatesFetching,
isFieldsLoading,
isViewsLoading,
isEmptyStatus,
};
return (

View File

@@ -1,13 +1,11 @@
import React from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { useParams } from 'react-router-dom';
import { pick } from 'lodash';
import { DashboardViewsTabs } from 'components';
import withEstimateActions from './withEstimateActions';
import withEstimatesActions from './withEstimatesActions';
import withEstimates from './withEstimates';
import { useEstimatesListContext } from './EstimatesListProvider';
import { compose } from 'utils';
@@ -16,11 +14,12 @@ import { compose } from 'utils';
* Estimates views tabs.
*/
function EstimateViewTabs({
//#withEstimatesActions
addEstimatesTableQueries,
}) {
const { custom_view_id: customViewId = null } = useParams();
// #withEstimatesActions
setEstimatesTableState,
// #withEstimates
estimatesTableState
}) {
// Estimates list context.
const { estimatesViews } = useEstimatesListContext();
@@ -29,8 +28,8 @@ function EstimateViewTabs({
}));
const handleTabsChange = (viewId) => {
addEstimatesTableQueries({
custom_view_id: viewId || null,
setEstimatesTableState({
customViewId: viewId || null,
});
};
@@ -38,7 +37,7 @@ function EstimateViewTabs({
<Navbar className={'navbar--dashboard-views'}>
<NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs
initialViewId={customViewId}
customViewId={estimatesTableState.customViewId}
resourceName={'estimates'}
tabs={tabs}
onChange={handleTabsChange}
@@ -49,5 +48,6 @@ function EstimateViewTabs({
}
export default compose(
withEstimateActions,
withEstimatesActions,
withEstimates(({ estimatesTableState }) => ({ estimatesTableState })),
)(EstimateViewTabs);

View File

@@ -0,0 +1,195 @@
import React from 'react';
import {
Intent,
Tag,
Button,
Popover,
Menu,
MenuItem,
MenuDivider,
Position,
} from '@blueprintjs/core';
import { Money, Choose, Icon, If } from 'components';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { safeCallback } from 'utils';
import moment from 'moment';
/**
* Status accessor.
*/
export const statusAccessor = (row) => (
<Choose>
<Choose.When condition={row.is_delivered && row.is_approved}>
<Tag minimal={true} intent={Intent.SUCCESS}>
<T id={'approved'} />
</Tag>
</Choose.When>
<Choose.When condition={row.is_delivered && row.is_rejected}>
<Tag minimal={true} intent={Intent.DANGER}>
<T id={'rejected'} />
</Tag>
</Choose.When>
<Choose.When
condition={row.is_delivered && !row.is_rejected && !row.is_approved}
>
<Tag minimal={true} intent={Intent.SUCCESS}>
<T id={'delivered'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
);
/**
* Actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onDeliver, onReject, onApprove, onDelete },
}) {
const { formatMessage } = useIntl();
return (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_estimate' })}
onClick={safeCallback(onEdit, original)}
/>
<If condition={!original.is_delivered}>
<MenuItem
text={formatMessage({ id: 'mark_as_delivered' })}
onClick={safeCallback(onDeliver, original)}
/>
</If>
<Choose>
<Choose.When condition={original.is_delivered && original.is_approved}>
<MenuItem
text={formatMessage({ id: 'mark_as_rejected' })}
onClick={safeCallback(onReject, original)}
/>
</Choose.When>
<Choose.When condition={original.is_delivered && original.is_rejected}>
<MenuItem
text={formatMessage({ id: 'mark_as_approved' })}
onClick={safeCallback(onApprove, original)}
/>
</Choose.When>
<Choose.When condition={original.is_delivered}>
<MenuItem
text={formatMessage({ id: 'mark_as_approved' })}
onClick={safeCallback(onApprove, original)}
/>
<MenuItem
text={formatMessage({ id: 'mark_as_rejected' })}
onClick={safeCallback(onReject, original)}
/>
</Choose.When>
</Choose>
<MenuItem
text={formatMessage({ id: 'delete_estimate' })}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
);
}
function DateCell({ value }) {
return moment(value).format('YYYY MMM DD');
}
function AmountAccessor(row) {
return <Money amount={row.amount} currency={'USD'} />;
}
function ActionsCell(props) {
return (
<Popover
content={<ActionsMenu {...props} />}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
);
}
export function useEstiamtesTableColumns() {
const { formatMessage } = useIntl();
return React.useMemo(
() => [
{
id: 'estimate_date',
Header: formatMessage({ id: 'estimate_date' }),
accessor: 'estimate_date',
Cell: DateCell,
width: 140,
className: 'estimate_date',
},
{
id: 'customer_id',
Header: formatMessage({ id: 'customer_name' }),
accessor: 'customer.display_name',
width: 140,
className: 'customer_id',
},
{
id: 'expiration_date',
Header: formatMessage({ id: 'expiration_date' }),
accessor: 'expiration_date',
Cell: DateCell,
width: 140,
className: 'expiration_date',
},
{
id: 'estimate_number',
Header: formatMessage({ id: 'estimate_number' }),
accessor: (row) =>
row.estimate_number ? `#${row.estimate_number}` : null,
width: 140,
className: 'estimate_number',
},
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: AmountAccessor,
width: 140,
className: 'amount',
},
{
id: 'status',
Header: formatMessage({ id: 'status' }),
accessor: (row) => statusAccessor(row),
width: 140,
className: 'status',
},
{
id: 'reference',
Header: formatMessage({ id: 'reference_no' }),
accessor: 'reference',
width: 140,
className: 'reference',
},
{
id: 'actions',
Header: '',
Cell: ActionsCell,
className: 'actions',
width: 50,
disableResizing: true,
},
],
[formatMessage],
);
}

View File

@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import {
getEstimatesTableStateFactory,
} from 'store/Estimate/estimates.selectors';
export default (mapState) => {
const getEstimatesTableState = getEstimatesTableStateFactory();
const mapStateToProps = (state, props) => {
const mapped = {
estimatesTableState: getEstimatesTableState(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import {
setEstimatesTableState,
} from 'store/Estimate/estimates.actions';
const mapDispatchToProps = (dispatch) => ({
setEstimatesTableState: (state) => dispatch(setEstimatesTableState(state)),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,273 +0,0 @@
import React, { useCallback, useMemo } from 'react';
import {
Intent,
Button,
Popover,
Menu,
MenuItem,
MenuDivider,
Position,
} from '@blueprintjs/core';
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { compose, saveInvoke } from 'utils';
import { useIsValuePassed } from 'hooks';
import {
LoadingIndicator,
Choose,
If,
DataTable,
Money,
Icon,
} from 'components';
import InvoicesEmptyStatus from './InvoicesEmptyStatus';
import { statusAccessor } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetails from 'containers/Views/withViewDetails';
import withInvoices from './withInvoices';
import withInvoiceActions from './withInvoiceActions';
import withCurrentView from 'containers/Views/withCurrentView';
import withSettings from 'containers/Settings/withSettings';
// Invoices datatable.
function InvoicesDataTable({
// #withInvoices
invoicesCurrentPage,
invoicesLoading,
invoicesPageination,
invoicesCurrentViewId,
// #withInvoicesActions
addInvoiceTableQueries,
// #withSettings
baseCurrency,
// #OwnProps
onEditInvoice,
onDeleteInvoice,
onDeliverInvoice,
onSelectedRowsChange,
}) {
const { formatMessage } = useIntl();
const isLoadedBefore = useIsValuePassed(invoicesLoading, false);
const handleEditInvoice = useCallback(
(_invoice) => () => {
saveInvoke(onEditInvoice, _invoice);
},
[onEditInvoice],
);
const handleDeleteInvoice = useCallback(
(_invoice) => () => {
saveInvoke(onDeleteInvoice, _invoice);
},
[onDeleteInvoice],
);
const actionMenuList = useCallback(
(invoice) => (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_invoice' })}
onClick={handleEditInvoice(invoice)}
/>
<If condition={!invoice.is_delivered}>
<MenuItem
text={formatMessage({ id: 'mark_as_delivered' })}
onClick={() => onDeliverInvoice(invoice)}
/>
</If>
<MenuItem
text={formatMessage({ id: 'delete_invoice' })}
intent={Intent.DANGER}
onClick={handleDeleteInvoice(invoice)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
),
[handleDeleteInvoice, handleEditInvoice, formatMessage],
);
const onRowContextMenu = useCallback(
(cell) => {
return actionMenuList(cell.row.original);
},
[actionMenuList],
);
const columns = useMemo(
() => [
{
id: 'invoice_date',
Header: formatMessage({ id: 'invoice_date' }),
accessor: (r) => moment(r.invoice_date).format('YYYY MMM DD'),
width: 110,
className: 'invoice_date',
},
{
id: 'customer_id',
Header: formatMessage({ id: 'customer_name' }),
accessor: 'customer.display_name',
width: 180,
className: 'customer_id',
},
{
id: 'invoice_no',
Header: formatMessage({ id: 'invoice_no__' }),
accessor: (row) => (row.invoice_no ? `#${row.invoice_no}` : null),
width: 100,
className: 'invoice_no',
},
{
id: 'balance',
Header: formatMessage({ id: 'balance' }),
accessor: (r) => <Money amount={r.balance} currency={baseCurrency} />,
width: 110,
className: 'balance',
},
{
id: 'status',
Header: formatMessage({ id: 'status' }),
accessor: (row) => statusAccessor(row),
width: 160,
className: 'status',
},
{
id: 'due_date',
Header: formatMessage({ id: 'due_date' }),
accessor: (r) => moment(r.due_date).format('YYYY MMM DD'),
width: 110,
className: 'due_date',
},
{
id: 'reference_no',
Header: formatMessage({ id: 'reference_no' }),
accessor: 'reference_no',
width: 90,
className: 'reference_no',
},
{
id: 'actions',
Header: '',
Cell: ({ cell }) => (
<Popover
content={actionMenuList(cell.row.original)}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
),
className: 'actions',
width: 50,
disableResizing: true,
},
],
[actionMenuList, formatMessage],
);
const handleDataTableFetchData = useCallback(
({ pageSize, pageIndex, sortBy }) => {
addInvoiceTableQueries({
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
page_size: pageSize,
page: pageIndex + 1,
});
},
[addInvoiceTableQueries],
);
const handleSelectedRowsChange = useCallback(
(selectedRows) => {
saveInvoke(
onSelectedRowsChange,
selectedRows.map((s) => s.original),
);
},
[onSelectedRowsChange],
);
const showEmptyStatus = [
invoicesCurrentPage.length === 0,
invoicesCurrentViewId === -1,
].every((d) => d === true);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={invoicesLoading && !isLoadedBefore}>
<Choose>
<Choose.When condition={showEmptyStatus}>
<InvoicesEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
columns={columns}
data={invoicesCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
autoResetSortBy={false}
autoResetPage={false}
pagesCount={invoicesPageination.pagesCount}
initialPageSize={invoicesPageination.pageSize}
initialPageIndex={invoicesPageination.page - 1}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
</div>
);
}
export default compose(
withRouter,
withCurrentView,
withDialogActions,
withDashboardActions,
withInvoiceActions,
withInvoices(
({
invoicesCurrentPage,
invoicesLoading,
invoicesPageination,
invoicesTableQuery,
invoicesCurrentViewId,
}) => ({
invoicesCurrentPage,
invoicesLoading,
invoicesPageination,
invoicesTableQuery,
invoicesCurrentViewId,
}),
),
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
withViewDetails(),
)(InvoicesDataTable);

View File

@@ -1,73 +0,0 @@
import React, { useCallback } from 'react';
import { Switch, Route, useHistory } from 'react-router-dom';
import InvoicesDataTable from './InvoicesDataTable';
import InvoiceViewTabs from './InvoiceViewTabs';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Invoices list view page.
*/
function InvoicesViewPage({
// #withAlertActions
openAlert,
}) {
const history = useHistory();
// Handle delete sale invoice.
const handleDeleteInvoice = useCallback(
({ id }) => {
openAlert('invoice-delete', { invoiceId: id });
},
[openAlert],
);
// Handle cancel/confirm invoice deliver.
const handleDeliverInvoice = useCallback(
({ id }) => {
openAlert('invoice-deliver', { invoiceId: id });
},
[openAlert],
);
// Handle edit sale invoice.
const handleEditInvoice = useCallback(
(invoice) => {
history.push(`/invoices/${invoice.id}/edit`);
},
[history],
);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback(
(invoices) => {
},
[],
);
return (
<Switch>
<Route
exact={true}
path={['/invoices/:custom_view_id/custom_view', '/invoices']}
>
<InvoiceViewTabs />
{/* <InvoicesDataTable
onDeleteInvoice={handleDeleteInvoice}
onEditInvoice={handleEditInvoice}
onDeliverInvoice={handleDeliverInvoice}
onSelectedRowsChange={handleSelectedRowsChange}
/> */}
</Route>
</Switch>
);
}
export default compose(
withAlertsActions,
withDialogActions,
)(InvoicesViewPage)

View File

@@ -1,79 +0,0 @@
import React from 'react';
import { Intent, Tag, ProgressBar } from '@blueprintjs/core';
import { Choose, If, Icon } from 'components';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { AppToaster } from 'components';
import { formatMessage } from 'services/intl';
const calculateStatus = (paymentAmount, balanceAmount) =>
paymentAmount / balanceAmount;
export const statusAccessor = (row) => {
return (
<div className={'status-accessor'}>
<Choose>
<Choose.When condition={row.is_fully_paid && row.is_delivered}>
<span className={'fully-paid-icon'}>
<Icon icon="small-tick" iconSize={18} />
</span>
<span class="fully-paid-text">
<T id={'paid'} />
</span>
</Choose.When>
<Choose.When condition={row.is_delivered}>
<Choose>
<Choose.When condition={row.is_overdue}>
<span className={'overdue-status'}>
<T id={'overdue_by'} values={{ overdue: row.overdue_days }} />
</span>
</Choose.When>
<Choose.Otherwise>
<span className={'due-status'}>
<T id={'due_in'} values={{ due: row.remaining_days }} />
</span>
</Choose.Otherwise>
</Choose>
<If condition={row.is_partially_paid}>
<span class="partial-paid">
<T
id={'day_partially_paid'}
values={{
due: row.due_amount,
currencySign: '$',
}}
/>
</span>
<ProgressBar
animate={false}
stripes={false}
intent={Intent.PRIMARY}
value={calculateStatus(row.payment_amount, row.balance)}
/>
</If>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
</div>
);
};
export const handleDeleteErrors = (errors) => {
if (
errors.find(
(error) => error.type === 'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES',
)
) {
AppToaster.show({
message: formatMessage({
id: 'the_invoice_cannot_be_deleted',
}),
intent: Intent.DANGER,
});
}
};

View File

@@ -1,40 +0,0 @@
import { connect } from 'react-redux';
import {
submitInvoice,
editInvoice,
deleteInvoice,
fetchInvoice,
fetchInvoicesTable,
fetchDueInvoices,
deliverInvoice,
} from 'store/Invoice/invoices.actions';
import t from 'store/types';
const mapDipatchToProps = (dispatch) => ({
requestSubmitInvoice: (form) => dispatch(submitInvoice({ form })),
requsetFetchInvoice: (id) => dispatch(fetchInvoice({ id })),
requestEditInvoice: (id, form) => dispatch(editInvoice(id, form)),
requestFetchInvoiceTable: (query = {}) =>
dispatch(fetchInvoicesTable({ query: { ...query } })),
requestDeleteInvoice: (id) => dispatch(deleteInvoice({ id })),
requestFetchDueInvoices: (customerId) =>
dispatch(fetchDueInvoices({ customerId })),
requestDeliverInvoice: (id) => dispatch(deliverInvoice({ id })),
changeInvoiceView: (id) =>
dispatch({
type: t.INVOICES_SET_CURRENT_VIEW,
currentViewId: parseInt(id, 10),
}),
addInvoiceTableQueries: (queries) =>
dispatch({
type: t.INVOICES_TABLE_QUERIES_ADD,
payload: { queries },
}),
setInvoiceNumberChanged: (isChanged) =>
dispatch({
type: t.INVOICE_NUMBER_CHANGED,
payload: { isChanged },
}),
});
export default connect(null, mapDipatchToProps);

View File

@@ -1,11 +0,0 @@
import { connect } from 'react-redux';
import { getInvoiecsByIdFactory } from 'store/Invoice/invoices.selector';
export default () => {
const getInvoiceById = getInvoiecsByIdFactory();
const mapStateToProps = (state, props) => ({
invoice: getInvoiceById(state, props),
});
return connect(mapStateToProps);
};

View File

@@ -1,42 +0,0 @@
import { connect } from 'react-redux';
import { getResourceViews } from 'store/customViews/customViews.selectors';
import {
getInvoiceCurrentPageFactory,
getInvoicePaginationMetaFactory,
getInvoiceTableQueryFactory,
getCustomerReceivableInvoicesEntriesFactory,
getInvoicesCurrentViewIdFactory,
} from 'store/Invoice/invoices.selector';
export default (mapState) => {
const getInvoicesItems = getInvoiceCurrentPageFactory();
const getInvoicesPaginationMeta = getInvoicePaginationMetaFactory();
const getInvoiceTableQuery = getInvoiceTableQueryFactory();
const getCustomerReceivableInvoicesEntries = getCustomerReceivableInvoicesEntriesFactory();
const getInvoicesCurrentViewId = getInvoicesCurrentViewIdFactory();
const mapStateToProps = (state, props) => {
const query = getInvoiceTableQuery(state, props);
const mapped = {
invoicesCurrentPage: getInvoicesItems(state, props, query),
invoicesCurrentViewId: getInvoicesCurrentViewId(state, props),
invoicesViews: getResourceViews(state, props, 'sale_invoice'),
invoicesItems: state.salesInvoices.items,
invoicesTableQuery: query,
invoicesPageination: getInvoicesPaginationMeta(state, props, query),
invoicesLoading: state.salesInvoices.loading,
customerInvoiceEntries: getCustomerReceivableInvoicesEntries(
state,
props,
),
invoiceNumberChanged: state.salesInvoices.journalNumberChanged,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

Some files were not shown because too many files have changed in this diff Show More