refactoring: WIP payment receive and made form.

This commit is contained in:
a.bouhuolia
2021-02-16 14:03:43 +02:00
parent f6456db592
commit a75177b9d1
47 changed files with 1331 additions and 1723 deletions

View File

@@ -0,0 +1,70 @@
import React, { createContext, useContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useResourceViews,
useResourceFields,
usePaymentReceives,
} from 'hooks/query';
import { isTableEmptyStatus } from 'utils';
const PaymentReceivesListContext = createContext();
/**
* Payment receives list data provider.
*/
function PaymentReceivesListProvider({ query, ...props }) {
// Fetch payment receives resource views and fields.
const {
data: paymentReceivesViews,
isFetching: isViewsLoading,
} = useResourceViews('payment_receives');
// Fetch the payment receives resource fields.
const {
data: paymentReceivesFields,
isFetching: isFieldsLoading,
} = useResourceFields('payment_receives');
// Fetch payment receives list according to the given custom view id.
const {
data: { paymentReceives, pagination, filterMeta },
isLoading: isPaymentReceivesLoading,
isFetching: isPaymentReceivesFetching,
} = usePaymentReceives(query);
// Detarmines the datatable empty status.
const isEmptyStatus =
isTableEmptyStatus({
data: paymentReceives,
pagination,
filterMeta,
}) && !isPaymentReceivesLoading;
// Provider payload.
const state = {
paymentReceives,
pagination,
paymentReceivesFields,
paymentReceivesViews,
isPaymentReceivesLoading,
isPaymentReceivesFetching,
isFieldsLoading,
isViewsLoading,
isEmptyStatus,
};
return (
<DashboardInsider
loading={isViewsLoading || isFieldsLoading}
name={'payment-receives'}
>
<PaymentReceivesListContext.Provider value={state} {...props} />
</DashboardInsider>
);
}
const usePaymentReceivesListContext = () =>
useContext(PaymentReceivesListContext);
export { PaymentReceivesListProvider, usePaymentReceivesListContext };

View File

@@ -0,0 +1,112 @@
import React from 'react';
import Icon from 'components/Icon';
import {
Button,
Classes,
Popover,
NavbarDivider,
NavbarGroup,
PopoverInteractionKind,
Position,
Intent,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T } from 'react-intl';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { If, DashboardActionViewsList } from 'components';
import withPaymentReceivesActions from './withPaymentReceivesActions';
import withPaymentReceives from './withPaymentReceives';
import { compose } from 'utils';
import { usePaymentReceivesListContext } from './PaymentReceiptsListProvider';
/**
* Payment receives actions bar.
*/
function PaymentReceiveActionsBar({
// #withPaymentReceivesActions
setPaymentReceivesTableState,
}) {
// History context.
const history = useHistory();
// Payment receives list context.
const { paymentReceivesViews } = usePaymentReceivesListContext();
// Handle new payment button click.
const handleClickNewPaymentReceive = () => {
history.push('/payment-receives/new');
};
// Handle tab changing.
const handleTabChange = (viewId) => {
setPaymentReceivesTableState({ customViewId: viewId.id || null });
};
return (
<DashboardActionsBar>
<NavbarGroup>
<DashboardActionViewsList
resourceName={'payment_receives'}
views={paymentReceivesViews}
onChange={handleTabChange}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'plus'} />}
text={<T id={'new_payment_receive'} />}
onClick={handleClickNewPaymentReceive}
/>
<Popover
minimal={true}
// content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL)}
text={<T id={'filter'} />}
icon={<Icon icon={'filter-16'} iconSize={16} />}
/>
</Popover>
<If condition={false}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
// onClick={handleBulkDelete}
/>
</If>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'print-16'} iconSize={'16'} />}
text={<T id={'print'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'file-import-16'} />}
text={<T id={'import'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
text={<T id={'export'} />}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withPaymentReceivesActions,
withPaymentReceives(({ paymentReceivesTableState }) => ({
paymentReceivesTableState,
})),
)(PaymentReceiveActionsBar);

View File

@@ -0,0 +1,64 @@
import React from 'react';
import { useHistory } from 'react-router';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import { pick } from 'lodash';
import { DashboardViewsTabs } from 'components';
import withPaymentReceives from './withPaymentReceives';
import withPaymentReceivesActions from './withPaymentReceivesActions';
import { usePaymentReceivesListContext } from './PaymentReceiptsListProvider';
import { compose } from 'utils';
/**
* Payment receive view tabs.
*/
function PaymentReceiveViewTabs({
// #withPaymentReceivesActions
addPaymentReceivesTableQueries,
// #withPaymentReceives
paymentReceivesTableState,
}) {
const history = useHistory();
const { paymentReceivesViews, ...res } = usePaymentReceivesListContext();
const tabs = paymentReceivesViews.map((view) => ({
...pick(view, ['name', 'id']),
}));
// Handles click a new view tab.
const handleClickNewView = () => {
history.push('/custom_views/payment-receives/new');
};
// Handles the active tab chaing.
const handleTabsChange = (customView) => {
addPaymentReceivesTableQueries({
customViewId: customView || null,
});
};
return (
<Navbar className={'navbar--dashboard-views'}>
<NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs
customViewId={paymentReceivesTableState.customViewId}
tabs={tabs}
defaultTabText={<T id={'all_payments'} />}
onNewViewTabClick={handleClickNewView}
onChange={handleTabsChange}
/>
</NavbarGroup>
</Navbar>
);
}
export default compose(
withPaymentReceivesActions,
withPaymentReceives(({ paymentReceivesTableState }) => ({
paymentReceivesTableState,
})),
)(PaymentReceiveViewTabs);

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { EmptyStatus } from 'components';
export default function PaymentReceivesEmptyStatus() {
const history = useHistory();
return (
<EmptyStatus
title={"The organization does't receive money, yet!"}
description={
<p>
It is a long established fact that a reader will be distracted by the
readable content of a page when looking at its layout.
</p>
}
action={
<>
<Button
intent={Intent.PRIMARY}
large={true}
onClick={() => {
history.push('/payment-receives/new');
}}
>
New payment receive
</Button>
<Button intent={Intent.NONE} large={true}>
Learn more
</Button>
</>
}
/>
);
}

View File

@@ -0,0 +1,57 @@
import React, { useEffect } from 'react';
import { useIntl } from 'react-intl';
import { DashboardContentTable, DashboardPageContent } from 'components';
import PaymentReceiveActionsBar from './PaymentReceiveActionsBar';
import PaymentReceiveAlerts from '../PaymentReceiveAlerts';
import { PaymentReceivesListProvider } from './PaymentReceiptsListProvider';
import PaymentReceiveViewTabs from './PaymentReceiveViewTabs';
import PaymentReceivesTable from './PaymentReceivesTable';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withPaymentReceives from './withPaymentReceives';
import { compose, transformTableStateToQuery } from 'utils';
/**
* Payment receives list.
*/
function PaymentReceiveList({
// #withDashboardActions
changePageTitle,
// #withPaymentReceives
paymentReceivesTableState,
}) {
const { formatMessage } = useIntl();
// Changes the dashboard page title once the page mount.
useEffect(() => {
changePageTitle(formatMessage({ id: 'payment_Receives_list' }));
}, [changePageTitle, formatMessage]);
return (
<PaymentReceivesListProvider
query={transformTableStateToQuery(paymentReceivesTableState)}
>
<PaymentReceiveActionsBar />
<DashboardPageContent>
<PaymentReceiveViewTabs />
<DashboardContentTable>
<PaymentReceivesTable />
</DashboardContentTable>
</DashboardPageContent>
<PaymentReceiveAlerts />
</PaymentReceivesListProvider>
);
}
export default compose(
withDashboardActions,
withPaymentReceives(({ paymentReceivesTableState }) => ({
paymentReceivesTableState,
})),
)(PaymentReceiveList);

View File

@@ -0,0 +1,60 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useResourceViews,
useResourceFields,
usePaymentReceives,
} from 'hooks/query';
const PaymentReceivesListContext = createContext();
/**
* Payment receives data provider.
*/
function PaymentReceivesListProvider({ query, ...props }) {
// Fetch accounts resource views and fields.
const {
data: paymentReceivesViews,
isFetching: isViewsLoading,
} = useResourceViews('payment_receives');
// Fetch the accounts resource fields.
const {
data: paymentReceivesFields,
isFetching: isFieldsLoading,
} = useResourceFields('payment_receives');
// Fetch accounts list according to the given custom view id.
const {
data: { paymentReceives, pagination, filterMeta },
isLoading: isPaymentReceivesLoading,
isFetching: isPaymentReceivesFetching,
} = usePaymentReceives(query);
// Provider payload.
const provider = {
paymentReceives,
paymentReceivesViews,
paymentReceivesFields,
pagination,
isViewsLoading,
isFieldsLoading,
isPaymentReceivesLoading,
isPaymentReceivesFetching
};
return (
<DashboardInsider
loading={isViewsLoading || isFieldsLoading}
name={'payment_receives'}
>
<PaymentReceivesListContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const usePaymentReceivesListContext = () =>
React.useContext(PaymentReceivesListContext);
export { PaymentReceivesListProvider, usePaymentReceivesListContext };

View File

@@ -0,0 +1,104 @@
import React, { useCallback } from 'react';
import classNames from 'classnames';
import { compose } from 'utils';
import { useHistory } from 'react-router-dom';
import { CLASSES } from 'common/classes';
import PaymentReceivesEmptyStatus from './PaymentReceivesEmptyStatus';
import { DataTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withPaymentReceivesActions from './withPaymentReceivesActions';
import withSettings from 'containers/Settings/withSettings';
import { usePaymentReceivesColumns, ActionsMenu } from './components';
import { usePaymentReceivesListContext } from './PaymentReceiptsListProvider';
/**
* Payment receives datatable.
*/
function PaymentReceivesDataTable({
// #withPaymentReceivesActions
setPaymentReceivesTableState,
// #withAlertsActions
openAlert,
}) {
const history = useHistory();
// Payment receives list context.
const {
paymentReceives,
pagination,
isPaymentReceivesLoading,
isPaymentReceivesFetching,
isEmptyStatus,
} = usePaymentReceivesListContext();
// Payment receives columns.
const columns = usePaymentReceivesColumns();
// Handles edit payment receive.
const handleEditPaymentReceive = ({ id }) => {
history.push(`/payment-receives/${id}/edit`);
};
// Handles delete payment receive.
const handleDeletePaymentReceive = ({ id }) => {
openAlert('payment-receive-delete', { paymentReceiveId: id });
};
// Handle datatable fetch once the table's state changing.
const handleDataTableFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
setPaymentReceivesTableState({
pageIndex,
pageSize,
sortBy,
});
},
[setPaymentReceivesTableState],
);
// Display empty status instead of the table.
if (isEmptyStatus) {
return <PaymentReceivesEmptyStatus />;
}
return (
<DataTable
columns={columns}
data={paymentReceives}
loading={isPaymentReceivesLoading}
headerLoading={isPaymentReceivesLoading}
progressBarLoading={isPaymentReceivesFetching}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
autoResetSortBy={false}
autoResetPage={false}
pagination={true}
pagesCount={pagination.pagesCount}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeletePaymentReceive,
onEdit: handleEditPaymentReceive,
}}
/>
);
}
export default compose(
withPaymentReceivesActions,
withAlertsActions,
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
)(PaymentReceivesDataTable);

View File

@@ -0,0 +1,137 @@
import React from 'react';
import {
Intent,
Button,
Popover,
Menu,
MenuItem,
MenuDivider,
Position,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import { Money, Icon } from 'components';
import { safeCallback } from 'utils';
/**
* Table actions menu.
*/
export function ActionsMenu({
row: { original: paymentReceive },
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_receive' })}
onClick={safeCallback(onEdit, paymentReceive)}
/>
<MenuItem
text={formatMessage({ id: 'delete_payment_receive' })}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, paymentReceive)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
);
}
/**
* Amount accessor.
*/
export function AmountAccessor(row) {
return <Money amount={row.amount} currency={'USD'} />;
}
/**
* Payment date accessor.
*/
export function PaymentDateAccessor(row) {
return moment(row.payment_date).format('YYYY MMM DD');
}
/**
* 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 receives columns.
*/
export function usePaymentReceivesColumns() {
const { formatMessage } = useIntl();
return React.useMemo(
() => [
{
id: 'payment_date',
Header: formatMessage({ id: 'payment_date' }),
accessor: PaymentDateAccessor,
width: 140,
className: 'payment_date',
},
{
id: 'customer_id',
Header: formatMessage({ id: 'customer_name' }),
accessor: 'customer.display_name',
width: 140,
className: 'customer_id',
},
{
id: 'payment_receive_no',
Header: formatMessage({ id: 'payment_receive_no' }),
accessor: (row) =>
row.payment_receive_no ? `#${row.payment_receive_no}` : null,
width: 140,
className: 'payment_receive_no',
},
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: AmountAccessor,
width: 140,
className: 'amount',
},
{
id: 'reference_no',
Header: formatMessage({ id: 'reference_no' }),
accessor: 'reference_no',
width: 140,
className: 'reference_no',
},
{
id: 'deposit_account_id',
Header: formatMessage({ id: 'deposit_account' }),
accessor: 'deposit_account.name',
width: 140,
className: 'deposit_account_id',
},
{
id: 'actions',
Header: '',
Cell: ActionsCell,
className: 'actions',
width: 50,
disableResizing: true,
},
],
[formatMessage],
);
}

View File

@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import {
getPaymentReceiveByIdFactory,
getPaymentReceiveEntriesFactory,
} from 'store/PaymentReceive/paymentReceive.selector';
export default () => {
const getPaymentReceiveById = getPaymentReceiveByIdFactory();
const getPaymentReceiveEntries = getPaymentReceiveEntriesFactory();
const mapStateToProps = (state, props) => ({
paymentReceive: getPaymentReceiveById(state, props),
paymentReceiveEntries: getPaymentReceiveEntries(state, props),
});
return connect(mapStateToProps);
};

View File

@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import {
getPaymentReceiveTableStateFactory
} from 'store/PaymentReceives/paymentReceives.selector';
export default (mapState) => {
const getPaymentReceiveTableState = getPaymentReceiveTableStateFactory();
const mapStateToProps = (state, props) => {
const mapped = {
paymentReceivesTableState: getPaymentReceiveTableState(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import { setPaymentReceivesTableState } from 'store/PaymentReceives/paymentReceives.actions';
const mapDispatchToProps = (dispatch) => ({
setPaymentReceivesTableState: (state) =>
dispatch(setPaymentReceivesTableState(state)),
});
export default connect(null, mapDispatchToProps);