mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 22:30:31 +00:00
feat: pending transactions table
This commit is contained in:
@@ -29,7 +29,7 @@ export class BankAccountsController extends BaseController {
|
|||||||
[
|
[
|
||||||
query('account_id').optional().isNumeric().toInt(),
|
query('account_id').optional().isNumeric().toInt(),
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.getBankAccountsPendingTransactions.bind(this)
|
this.getBankAccountsPendingTransactions.bind(this)
|
||||||
@@ -94,11 +94,13 @@ export class BankAccountsController extends BaseController {
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
const query = this.matchedQueryData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data =
|
const data =
|
||||||
await this.getPendingTransactionsService.getPendingTransactions(
|
await this.getPendingTransactionsService.getPendingTransactions(
|
||||||
tenantId
|
tenantId,
|
||||||
|
query
|
||||||
);
|
);
|
||||||
return res.status(200).send(data);
|
return res.status(200).send(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export class GetPendingBankAccountTransactions {
|
|||||||
})
|
})
|
||||||
.pagination(_filter.page - 1, _filter.pageSize);
|
.pagination(_filter.page - 1, _filter.pageSize);
|
||||||
|
|
||||||
const data = this.transformer.transform(
|
const data = await this.transformer.transform(
|
||||||
tenantId,
|
tenantId,
|
||||||
results,
|
results,
|
||||||
new GetPendingBankAccountTransactionTransformer()
|
new GetPendingBankAccountTransactionTransformer()
|
||||||
|
|||||||
@@ -7,5 +7,7 @@ export const BANK_QUERY_KEY = {
|
|||||||
'RECOGNIZED_BANK_TRANSACTIONS_INFINITY',
|
'RECOGNIZED_BANK_TRANSACTIONS_INFINITY',
|
||||||
BANK_ACCOUNT_SUMMARY_META: 'BANK_ACCOUNT_SUMMARY_META',
|
BANK_ACCOUNT_SUMMARY_META: 'BANK_ACCOUNT_SUMMARY_META',
|
||||||
AUTOFILL_CATEGORIZE_BANK_TRANSACTION: 'AUTOFILL_CATEGORIZE_BANK_TRANSACTION',
|
AUTOFILL_CATEGORIZE_BANK_TRANSACTION: 'AUTOFILL_CATEGORIZE_BANK_TRANSACTION',
|
||||||
PENDING_BANK_ACCOUNT_TRANSACTIONS: 'PENDING_BANK_ACCOUNT_TRANSACTIONS'
|
PENDING_BANK_ACCOUNT_TRANSACTIONS: 'PENDING_BANK_ACCOUNT_TRANSACTIONS',
|
||||||
|
PENDING_BANK_ACCOUNT_TRANSACTIONS_INFINITY:
|
||||||
|
'PENDING_BANK_ACCOUNT_TRANSACTIONS_INFINITY',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ const AccountUncategorizedTransactions = lazy(() =>
|
|||||||
).then((module) => ({ default: module.AccountUncategorizedTransactionsAll })),
|
).then((module) => ({ default: module.AccountUncategorizedTransactionsAll })),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const PendingTransactions = lazy(() =>
|
||||||
|
import('./PendingTransactions/PendingTransactions').then((module) => ({
|
||||||
|
default: module.PendingTransactions,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switches between the account transactions tables.
|
* Switches between the account transactions tables.
|
||||||
* @returns {React.ReactNode}
|
* @returns {React.ReactNode}
|
||||||
@@ -71,7 +77,7 @@ function AccountTransactionsSwitcher() {
|
|||||||
default:
|
default:
|
||||||
return <AccountUncategorizedTransactions />;
|
return <AccountUncategorizedTransactions />;
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return null;
|
return <PendingTransactions />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { AccountTransactionsCard } from '../UncategorizedTransactions/AccountTransactionsCard';
|
||||||
|
import { PendingTransactionsBoot } from './PendingTransactionsTableBoot';
|
||||||
|
import { PendingTransactionsDataTable } from './PendingTransactionsTable';
|
||||||
|
|
||||||
|
export function PendingTransactions() {
|
||||||
|
return (
|
||||||
|
<PendingTransactionsBoot>
|
||||||
|
<AccountTransactionsCard>
|
||||||
|
<PendingTransactionsDataTable />
|
||||||
|
</AccountTransactionsCard>
|
||||||
|
</PendingTransactionsBoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import clsx from 'classnames';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
DataTable,
|
||||||
|
TableFastCell,
|
||||||
|
TableSkeletonRows,
|
||||||
|
TableSkeletonHeader,
|
||||||
|
TableVirtualizedListRows,
|
||||||
|
} from '@/components';
|
||||||
|
import withSettings from '@/containers/Settings/withSettings';
|
||||||
|
import { withBankingActions } from '../../withBankingActions';
|
||||||
|
|
||||||
|
import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
|
||||||
|
import { usePendingTransactionsContext } from './PendingTransactionsTableBoot';
|
||||||
|
import { usePendingTransactionsTableColumns } from './_hooks';
|
||||||
|
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account transactions data table.
|
||||||
|
*/
|
||||||
|
function PendingTransactionsDataTableRoot({
|
||||||
|
// #withSettings
|
||||||
|
cashflowTansactionsTableSize,
|
||||||
|
}) {
|
||||||
|
// Retrieve table columns.
|
||||||
|
const columns = usePendingTransactionsTableColumns();
|
||||||
|
const { scrollableRef } = useAccountTransactionsContext();
|
||||||
|
|
||||||
|
// Retrieve list context.
|
||||||
|
const { pendingTransactions, isPendingTransactionsLoading } =
|
||||||
|
usePendingTransactionsContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CashflowTransactionsTable
|
||||||
|
noInitialFetch={true}
|
||||||
|
columns={columns}
|
||||||
|
data={pendingTransactions || []}
|
||||||
|
sticky={true}
|
||||||
|
loading={isPendingTransactionsLoading}
|
||||||
|
headerLoading={isPendingTransactionsLoading}
|
||||||
|
TableCellRenderer={TableFastCell}
|
||||||
|
TableLoadingRenderer={TableSkeletonRows}
|
||||||
|
TableRowsRenderer={TableVirtualizedListRows}
|
||||||
|
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||||
|
// #TableVirtualizedListRows props.
|
||||||
|
vListrowHeight={cashflowTansactionsTableSize === 'small' ? 32 : 40}
|
||||||
|
vListOverscanRowCount={0}
|
||||||
|
noResults={'There is no pending transactions in the current account.'}
|
||||||
|
windowScrollerProps={{ scrollElement: scrollableRef }}
|
||||||
|
className={clsx('table-constrant')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PendingTransactionsDataTable = compose(
|
||||||
|
withSettings(({ cashflowTransactionsSettings }) => ({
|
||||||
|
cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize,
|
||||||
|
})),
|
||||||
|
withBankingActions,
|
||||||
|
)(PendingTransactionsDataTableRoot);
|
||||||
|
|
||||||
|
const DashboardConstrantTable = styled(DataTable)`
|
||||||
|
.table {
|
||||||
|
.thead {
|
||||||
|
.th {
|
||||||
|
background: #fff;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-size: 13px;i
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody {
|
||||||
|
.tr:last-child .td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CashflowTransactionsTable = styled(DashboardConstrantTable)`
|
||||||
|
.table .tbody {
|
||||||
|
.tbody-inner .tr.no-results {
|
||||||
|
.td {
|
||||||
|
padding: 2rem 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #888;
|
||||||
|
font-weight: 400;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody-inner {
|
||||||
|
.tr .td:not(:first-child) {
|
||||||
|
border-left: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td-description {
|
||||||
|
color: #5f6b7c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { flatten, map } from 'lodash';
|
||||||
|
import { IntersectionObserver } from '@/components';
|
||||||
|
import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
|
||||||
|
import { usePendingBankTransactionsInfinity } from '@/hooks/query/bank-rules';
|
||||||
|
|
||||||
|
const PendingTransactionsContext = React.createContext();
|
||||||
|
|
||||||
|
function flattenInfinityPagesData(data) {
|
||||||
|
return flatten(map(data.pages, (page) => page.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account pending transctions provider.
|
||||||
|
*/
|
||||||
|
function PendingTransactionsBoot({ children }) {
|
||||||
|
const { accountId } = useAccountTransactionsContext();
|
||||||
|
|
||||||
|
// Fetches the pending transactions.
|
||||||
|
const {
|
||||||
|
data: pendingTransactionsPage,
|
||||||
|
isFetching: isPendingTransactionFetching,
|
||||||
|
isLoading: isPendingTransactionsLoading,
|
||||||
|
isSuccess: isPendingTransactionsSuccess,
|
||||||
|
isFetchingNextPage: isPendingTransactionFetchNextPage,
|
||||||
|
fetchNextPage: fetchNextPendingTransactionsPage,
|
||||||
|
hasNextPage: hasPendingTransactionsNextPage,
|
||||||
|
} = usePendingBankTransactionsInfinity({
|
||||||
|
account_id: accountId,
|
||||||
|
page_size: 50,
|
||||||
|
});
|
||||||
|
// Memorized the cashflow account transactions.
|
||||||
|
const pendingTransactions = React.useMemo(
|
||||||
|
() =>
|
||||||
|
isPendingTransactionsSuccess
|
||||||
|
? flattenInfinityPagesData(pendingTransactionsPage)
|
||||||
|
: [],
|
||||||
|
[pendingTransactionsPage, isPendingTransactionsSuccess],
|
||||||
|
);
|
||||||
|
// Handle the observer ineraction.
|
||||||
|
const handleObserverInteract = React.useCallback(() => {
|
||||||
|
if (!isPendingTransactionFetching && hasPendingTransactionsNextPage) {
|
||||||
|
fetchNextPendingTransactionsPage();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
isPendingTransactionFetching,
|
||||||
|
hasPendingTransactionsNextPage,
|
||||||
|
fetchNextPendingTransactionsPage,
|
||||||
|
]);
|
||||||
|
// Provider payload.
|
||||||
|
const provider = {
|
||||||
|
pendingTransactions,
|
||||||
|
isPendingTransactionFetching,
|
||||||
|
isPendingTransactionsLoading,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PendingTransactionsContext.Provider value={provider}>
|
||||||
|
{children}
|
||||||
|
<IntersectionObserver
|
||||||
|
onIntersect={handleObserverInteract}
|
||||||
|
enabled={!isPendingTransactionFetchNextPage}
|
||||||
|
/>
|
||||||
|
</PendingTransactionsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const usePendingTransactionsContext = () =>
|
||||||
|
React.useContext(PendingTransactionsContext);
|
||||||
|
|
||||||
|
export { PendingTransactionsBoot, usePendingTransactionsContext };
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve account pending transctions table columns.
|
||||||
|
*/
|
||||||
|
export function usePendingTransactionsTableColumns() {
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: 'date',
|
||||||
|
Header: intl.get('date'),
|
||||||
|
accessor: 'formatted_date',
|
||||||
|
width: 40,
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'description',
|
||||||
|
Header: 'Description',
|
||||||
|
accessor: 'description',
|
||||||
|
width: 160,
|
||||||
|
textOverview: true,
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'payee',
|
||||||
|
Header: 'Payee',
|
||||||
|
accessor: 'payee',
|
||||||
|
width: 60,
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'reference_number',
|
||||||
|
Header: 'Ref.#',
|
||||||
|
accessor: 'reference_no',
|
||||||
|
width: 50,
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'deposit',
|
||||||
|
Header: intl.get('cash_flow.label.deposit'),
|
||||||
|
accessor: 'formatted_deposit_amount',
|
||||||
|
width: 40,
|
||||||
|
className: 'deposit',
|
||||||
|
textOverview: true,
|
||||||
|
align: 'right',
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'withdrawal',
|
||||||
|
Header: intl.get('cash_flow.label.withdrawal'),
|
||||||
|
accessor: 'formatted_withdrawal_amount',
|
||||||
|
className: 'withdrawal',
|
||||||
|
width: 40,
|
||||||
|
textOverview: true,
|
||||||
|
align: 'right',
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -686,3 +686,34 @@ export function useExcludedBankTransactionsInfinity(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function usePendingBankTransactionsInfinity(
|
||||||
|
query,
|
||||||
|
infinityProps,
|
||||||
|
axios,
|
||||||
|
) {
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useInfiniteQuery(
|
||||||
|
[BANK_QUERY_KEY.PENDING_BANK_ACCOUNT_TRANSACTIONS_INFINITY, query],
|
||||||
|
async ({ pageParam = 1 }) => {
|
||||||
|
const response = await apiRequest.http({
|
||||||
|
...axios,
|
||||||
|
method: 'get',
|
||||||
|
url: `/api/banking/bank_accounts/pending_transactions`,
|
||||||
|
params: { page: pageParam, ...query },
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getPreviousPageParam: (firstPage) => firstPage.pagination.page - 1,
|
||||||
|
getNextPageParam: (lastPage) => {
|
||||||
|
const { pagination } = lastPage;
|
||||||
|
return pagination.total > pagination.page_size * pagination.page
|
||||||
|
? lastPage.pagination.page + 1
|
||||||
|
: undefined;
|
||||||
|
},
|
||||||
|
...infinityProps,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user