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

@@ -0,0 +1,116 @@
import React, { useState } 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, useIntl } from 'react-intl';
import { If, DashboardActionViewsList } from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import withReceiptsActions from './withReceiptsActions';
import { useReceiptsListContext } from './ReceiptsListProvider';
import { compose } from 'utils';
/**
* Receipts actions bar.
*/
function ReceiptActionsBar({
// #withReceiptsActions
setReceiptsTableState,
}) {
const history = useHistory();
const { formatMessage } = useIntl();
const [filterCount, setFilterCount] = useState(0);
// Sale receipts list context.
const { receiptsViews } = useReceiptsListContext();
// Handle new receipt button click.
const onClickNewReceipt = () => {
history.push('/receipts/new');
};
// Handle the active tab change.
const handleTabChange = (viewId) => {
setReceiptsTableState({
csutomViewId: viewId.id || null,
});
};
return (
<DashboardActionsBar>
<NavbarGroup>
<DashboardActionViewsList
resourceName={'receipts'}
views={receiptsViews}
onChange={handleTabChange}
/>
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'plus'} />}
text={<T id={'new_receipt'} />}
onClick={onClickNewReceipt}
/>
<Popover
minimal={true}
// content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={
filterCount <= 0 ? (
<T id={'filter'} />
) : (
`${filterCount} ${formatMessage({ id: 'filters_applied' })}`
)
}
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}
/>
</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(withReceiptsActions)(ReceiptActionsBar);

View File

@@ -0,0 +1,53 @@
import React from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { pick } from 'lodash';
import { DashboardViewsTabs } from 'components';
import withReceiptActions from './withReceiptsActions';
import withReceipts from './withReceipts';
import { compose } from 'utils';
import { useReceiptsListContext } from './ReceiptsListProvider';
/**
* Receipts views tabs.
*/
function ReceiptViewTabs({
// #withReceiptActions
setReceiptsTableState,
// #withReceipts
receiptTableState
}) {
// Receipts list context.
const { receiptsViews } = useReceiptsListContext();
const tabs = receiptsViews.map((view) => ({
...pick(view, ['name', 'id']),
}));
// Handles the active tab chaning.
const handleTabsChange = (customView) => {
setReceiptsTableState({
customViewId: customView.id || null,
});
};
return (
<Navbar className={'navbar--dashboard-views'}>
<NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs
initialViewId={receiptTableState.customViewId}
tabs={tabs}
resourceName={'receipts'}
onChange={handleTabsChange}
/>
</NavbarGroup>
</Navbar>
);
}
export default compose(
withReceiptActions,
withReceipts(({ receiptTableState }) => ({ receiptTableState })),
)(ReceiptViewTabs);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { EmptyStatus } from 'components';
export default function ReceiptsEmptyStatus() {
const history = useHistory();
return (
<EmptyStatus
title={'Manage the organizations services and products.'}
description={
<p>
Here a list of your organization products and services, to be used when you create invoices or bills to your customers or vendors.
</p>
}
action={
<>
<Button
intent={Intent.PRIMARY}
large={true}
onClick={() => {
history.push('/receipts/new');
}}
>
New receipt
</Button>
<Button intent={Intent.NONE} large={true}>
Learn more
</Button>
</>
}
/>
);
}

View File

@@ -0,0 +1,56 @@
import React, { useEffect } from 'react';
import { useIntl } from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import ReceiptActionsBar from './ReceiptActionsBar';
import ReceiptViewTabs from './ReceiptViewTabs';
import ReceiptsAlerts from '../ReceiptsAlerts';
import ReceiptsTable from './ReceiptsTable';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withReceipts from './withReceipts';
import { ReceiptsListProvider } from './ReceiptsListProvider';
import { transformTableStateToQuery, compose } from 'utils';
/**
* Receipts list page.
*/
function ReceiptsList({
// #withDashboardActions
changePageTitle,
// #withReceipts
receiptTableState,
}) {
const { formatMessage } = useIntl();
// Changes the dashboard page title once the page mount.
useEffect(() => {
changePageTitle(formatMessage({ id: 'receipts_list' }));
}, [changePageTitle, formatMessage]);
return (
<ReceiptsListProvider query={transformTableStateToQuery(receiptTableState)}>
<DashboardPageContent>
<ReceiptActionsBar />
<DashboardPageContent>
<ReceiptViewTabs />
<ReceiptsTable />
</DashboardPageContent>
<ReceiptsAlerts />
</DashboardPageContent>
</ReceiptsListProvider>
);
}
export default compose(
withDashboardActions,
withReceipts(({ receiptTableState }) => ({
receiptTableState,
})),
)(ReceiptsList);

View File

@@ -0,0 +1,59 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useResourceFields, useReceipts } from 'hooks/query';
import { isTableEmptyStatus } from 'utils';
const ReceiptsListContext = createContext();
// Receipts list provider.
function ReceiptsListProvider({ query, ...props }) {
// Fetch receipts resource views and fields.
const { data: receiptsViews, isFetching: isViewsLoading } = useResourceViews(
'sale_receipt',
);
// Fetches the sale receipts resource fields.
// const {
// data: receiptsFields,
// isFetching: isFieldsLoading,
// } = useResourceFields('sale_receipt');
const {
data: { receipts, pagination, filterMeta },
isLoading: isReceiptsLoading,
isFetching: isReceiptsFetching,
} = useReceipts(query, { keepPreviousData: true });
// Detarmines the datatable empty status.
const isEmptyStatus =
isTableEmptyStatus({
data: receipts,
pagination,
filterMeta,
}) && !isReceiptsLoading;
const provider = {
receipts,
pagination,
// receiptsFields,
receiptsViews,
isViewsLoading,
// isFieldsLoading,
isReceiptsLoading,
isReceiptsFetching,
isEmptyStatus
};
return (
<DashboardInsider
loading={isViewsLoading}
name={'sales_receipts'}
>
<ReceiptsListContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useReceiptsListContext = () => React.useContext(ReceiptsListContext);
export { ReceiptsListProvider, useReceiptsListContext };

View File

@@ -0,0 +1,126 @@
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';
import ReceiptsEmptyStatus from './ReceiptsEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withReceipts from './withReceipts';
import withReceiptsActions from './withReceiptsActions';
import withSettings from 'containers/Settings/withSettings';
import { useReceiptsListContext } from './ReceiptsListProvider';
import { useReceiptsTableColumns, ActionsMenu } from './components';
/**
* Sale receipts datatable.
*/
function ReceiptsDataTable({
// #withReceiptsActions
setReceiptsTableState,
// #withSettings
baseCurrency,
// #withAlertsActions
openAlert
}) {
const history = useHistory();
// Receipts list context.
const {
receipts,
pagination,
isReceiptsFetching,
isReceiptsLoading,
isEmptyStatus
} = useReceiptsListContext();
// Receipts table columns.
const columns = useReceiptsTableColumns();
// Handle receipt edit action.
const handleEditReceipt = ({ id }) => {
history.push(`/receipts/${id}/edit`);
};
// Handles receipt delete action.
const handleDeleteReceipt = (receipt) => {
openAlert('receipt-delete', { receiptId: receipt.id });
};
// Handles receipt close action.
const handleCloseReceipt = (receipt) => {
openAlert('receipt-close', { receiptId: receipt.id });
}
// Handles the datable fetch data once the state changing.
const handleDataTableFetchData = useCallback(
({ sortBy, pageIndex, pageSize }) => {
setReceiptsTableState({
pageIndex,
pageSize,
sortBy,
});
},
[setReceiptsTableState],
);
if (isEmptyStatus) {
return <ReceiptsEmptyStatus />
}
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<DataTable
columns={columns}
data={receipts}
loading={isReceiptsLoading}
headerLoading={isReceiptsLoading}
progressBarLoading={isReceiptsFetching}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
pagination={true}
pagesCount={pagination.pagesCount}
manualPagination={true}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
payload={{
onEdit: handleEditReceipt,
onDelete: handleDeleteReceipt,
onClose: handleCloseReceipt,
baseCurrency
}}
/>
</div>
);
}
export default compose(
withAlertsActions,
withReceiptsActions,
withReceipts(({ receiptTableState }) => ({
receiptTableState,
})),
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
)(ReceiptsDataTable);

View File

@@ -0,0 +1,155 @@
import React from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import {
Position,
Menu,
MenuItem,
MenuDivider,
Intent,
Popover,
Tag,
Button,
} from '@blueprintjs/core';
import { safeCallback } from 'utils';
import { Choose, Money, Icon, If } from 'components';
import moment from 'moment';
export function ActionsMenu({
payload: { onEdit, onDelete, onClose },
row: { original: receipt },
}) {
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_receipt' })}
onClick={safeCallback(onEdit, receipt)}
/>
<If condition={!receipt.is_closed}>
<MenuItem
text={formatMessage({ id: 'mark_as_closed' })}
onClick={safeCallback(onClose, receipt)}
/>
</If>
<MenuItem
text={formatMessage({ id: 'delete_receipt' })}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, receipt)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
);
}
/**
* 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>
);
}
/**
* Status accessor.
*/
export function StatusAccessor(receipt) {
return (
<Choose>
<Choose.When condition={receipt.is_closed}>
<Tag minimal={true} intent={Intent.SUCCESS}>
<T id={'closed'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true} intent={Intent.WARNING}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
);
}
/**
* Retrieve receipts table columns.
*/
export function useReceiptsTableColumns() {
const { formatMessage } = useIntl();
return React.useMemo(
() => [
{
id: 'receipt_date',
Header: formatMessage({ id: 'receipt_date' }),
accessor: (r) => moment(r.receipt_date).format('YYYY MMM DD'),
width: 140,
className: 'receipt_date',
},
{
id: 'customer_id',
Header: formatMessage({ id: 'customer_name' }),
accessor: 'customer.display_name',
width: 140,
className: 'customer_id',
},
{
id: 'receipt_number',
Header: formatMessage({ id: 'receipt_number' }),
accessor: (row) =>
row.receipt_number ? `#${row.receipt_number}` : null,
width: 140,
className: 'receipt_number',
},
{
id: 'deposit_account_id',
Header: formatMessage({ id: 'deposit_account' }),
accessor: 'deposit_account.name',
width: 140,
className: 'deposit_account',
},
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: (r) => <Money amount={r.amount} currency={'USD'} />,
width: 140,
className: 'amount',
},
{
id: 'status',
Header: formatMessage({ id: 'status' }),
accessor: StatusAccessor,
width: 140,
className: 'amount',
},
{
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,16 @@
import { connect } from 'react-redux';
import {
getReceiptsTableStateFactory,
} from 'store/receipts/receipts.selector';
export default (mapState) => {
const getReceiptsTableState = getReceiptsTableStateFactory();
const mapStateToProps = (state, props) => {
const mapped = {
receiptTableState: getReceiptsTableState(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

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