diff --git a/packages/server/src/api/controllers/Banking/BankAccountsController.ts b/packages/server/src/api/controllers/Banking/BankAccountsController.ts index 7f134fd75..e02aad096 100644 --- a/packages/server/src/api/controllers/Banking/BankAccountsController.ts +++ b/packages/server/src/api/controllers/Banking/BankAccountsController.ts @@ -29,7 +29,7 @@ export class BankAccountsController extends BaseController { [ query('account_id').optional().isNumeric().toInt(), query('page').optional().isNumeric().toInt(), - query('page_size').optional().isNumeric().toInt(), + query('page_size').optional().isNumeric().toInt(), ], this.validationResult, this.getBankAccountsPendingTransactions.bind(this) @@ -94,11 +94,13 @@ export class BankAccountsController extends BaseController { next: NextFunction ) { const { tenantId } = req; + const query = this.matchedQueryData(req); try { const data = await this.getPendingTransactionsService.getPendingTransactions( - tenantId + tenantId, + query ); return res.status(200).send(data); } catch (error) { diff --git a/packages/server/src/services/Cashflow/GetPendingBankAccountTransaction.ts b/packages/server/src/services/Cashflow/GetPendingBankAccountTransaction.ts index 34bd5ad31..11ad32576 100644 --- a/packages/server/src/services/Cashflow/GetPendingBankAccountTransaction.ts +++ b/packages/server/src/services/Cashflow/GetPendingBankAccountTransaction.ts @@ -37,7 +37,7 @@ export class GetPendingBankAccountTransactions { }) .pagination(_filter.page - 1, _filter.pageSize); - const data = this.transformer.transform( + const data = await this.transformer.transform( tenantId, results, new GetPendingBankAccountTransactionTransformer() diff --git a/packages/webapp/src/constants/query-keys/banking.ts b/packages/webapp/src/constants/query-keys/banking.ts index 1a69341b0..26ec3135b 100644 --- a/packages/webapp/src/constants/query-keys/banking.ts +++ b/packages/webapp/src/constants/query-keys/banking.ts @@ -7,5 +7,7 @@ export const BANK_QUERY_KEY = { 'RECOGNIZED_BANK_TRANSACTIONS_INFINITY', BANK_ACCOUNT_SUMMARY_META: 'BANK_ACCOUNT_SUMMARY_META', 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', }; diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/AllTransactionsUncategorized.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/AllTransactionsUncategorized.tsx index 54996f1a3..5e77c38f9 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/AllTransactionsUncategorized.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/AllTransactionsUncategorized.tsx @@ -54,6 +54,12 @@ const AccountUncategorizedTransactions = lazy(() => ).then((module) => ({ default: module.AccountUncategorizedTransactionsAll })), ); +const PendingTransactions = lazy(() => + import('./PendingTransactions/PendingTransactions').then((module) => ({ + default: module.PendingTransactions, + })), +); + /** * Switches between the account transactions tables. * @returns {React.ReactNode} @@ -71,7 +77,7 @@ function AccountTransactionsSwitcher() { default: return ; case 'pending': - return null; + return ; } } diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactions.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactions.tsx new file mode 100644 index 000000000..34e813cc2 --- /dev/null +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactions.tsx @@ -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 ( + + + + + + ); +} diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactionsTable.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactionsTable.tsx index e69de29bb..ae3ecf944 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactionsTable.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactionsTable.tsx @@ -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 ( + + ); +} + +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; + } + } + } +`; diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactionsTableBoot.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactionsTableBoot.tsx index e69de29bb..9cd5eba44 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactionsTableBoot.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/PendingTransactionsTableBoot.tsx @@ -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 ( + + {children} + + + ); +} + +const usePendingTransactionsContext = () => + React.useContext(PendingTransactionsContext); + +export { PendingTransactionsBoot, usePendingTransactionsContext }; diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/_hooks.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/_hooks.tsx new file mode 100644 index 000000000..2ea2e20f3 --- /dev/null +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/PendingTransactions/_hooks.tsx @@ -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, + }, + ], + [], + ); +} diff --git a/packages/webapp/src/hooks/query/bank-rules.ts b/packages/webapp/src/hooks/query/bank-rules.ts index 71697ec7e..1f72e7137 100644 --- a/packages/webapp/src/hooks/query/bank-rules.ts +++ b/packages/webapp/src/hooks/query/bank-rules.ts @@ -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, + }, + ); +}