mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
Merge branch 'develop' into filter-uncategorized-bank-transactions
This commit is contained in:
@@ -32,7 +32,7 @@ import {
|
||||
getAddMoneyOutOptions,
|
||||
getAddMoneyInOptions,
|
||||
} from '@/constants/cashflowOptions';
|
||||
import { useRefreshCashflowTransactionsInfinity } from '@/hooks/query';
|
||||
import { useRefreshCashflowTransactions } from '@/hooks/query';
|
||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
@@ -64,6 +64,7 @@ function AccountTransactionsActionsBar({
|
||||
uncategorizedTransationsIdsSelected,
|
||||
excludedTransactionsIdsSelected,
|
||||
openMatchingTransactionAside,
|
||||
categorizedTransactionsSelected,
|
||||
|
||||
// #withBankingActions
|
||||
enableMultipleCategorization,
|
||||
@@ -75,7 +76,7 @@ function AccountTransactionsActionsBar({
|
||||
const { accountId, currentAccount } = useAccountTransactionsContext();
|
||||
|
||||
// Refresh cashflow infinity transactions hook.
|
||||
const { refresh } = useRefreshCashflowTransactionsInfinity();
|
||||
const { refresh } = useRefreshCashflowTransactions();
|
||||
|
||||
const { mutateAsync: updateBankAccount } = useUpdateBankAccount();
|
||||
|
||||
@@ -140,7 +141,7 @@ function AccountTransactionsActionsBar({
|
||||
};
|
||||
// Handle the refresh button click.
|
||||
const handleRefreshBtnClick = () => {
|
||||
refresh();
|
||||
refresh(accountId);
|
||||
};
|
||||
|
||||
const {
|
||||
@@ -194,7 +195,7 @@ function AccountTransactionsActionsBar({
|
||||
// Handle multi select transactions for categorization or matching.
|
||||
const handleMultipleCategorizingSwitch = (event) => {
|
||||
enableMultipleCategorization(event.currentTarget.checked);
|
||||
}
|
||||
};
|
||||
// Handle resume bank feeds syncing.
|
||||
const handleResumeFeedsSyncing = () => {
|
||||
openAlert('resume-feeds-syncing-bank-accounnt', {
|
||||
@@ -207,6 +208,18 @@ function AccountTransactionsActionsBar({
|
||||
bankAccountId: accountId,
|
||||
});
|
||||
};
|
||||
// Handles uncategorize the categorized transactions in bulk.
|
||||
const handleUncategorizeCategorizedBulkBtnClick = () => {
|
||||
openAlert('uncategorize-transactions-bulk', {
|
||||
uncategorizeTransactionsIds: categorizedTransactionsSelected,
|
||||
});
|
||||
};
|
||||
// Handles the delete account button click.
|
||||
const handleDeleteAccountClick = () => {
|
||||
openAlert('account-delete', {
|
||||
accountId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -297,6 +310,14 @@ function AccountTransactionsActionsBar({
|
||||
disabled={isUnexcludingLoading}
|
||||
/>
|
||||
)}
|
||||
{!isEmpty(categorizedTransactionsSelected) && (
|
||||
<Button
|
||||
text={'Uncategorize'}
|
||||
onClick={handleUncategorizeCategorizedBulkBtnClick}
|
||||
intent={Intent.DANGER}
|
||||
minimal
|
||||
/>
|
||||
)}
|
||||
</NavbarGroup>
|
||||
|
||||
<NavbarGroup align={Alignment.RIGHT}>
|
||||
@@ -348,9 +369,19 @@ function AccountTransactionsActionsBar({
|
||||
|
||||
<MenuItem onClick={handleBankRulesClick} text={'Bank rules'} />
|
||||
|
||||
<MenuDivider />
|
||||
<If condition={isSyncingOwner && isFeedsActive}>
|
||||
<MenuItem onClick={handleDisconnectClick} text={'Disconnect'} />
|
||||
<MenuItem
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDisconnectClick}
|
||||
text={'Disconnect'}
|
||||
/>
|
||||
</If>
|
||||
<MenuItem
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteAccountClick}
|
||||
text={'Delete'}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
@@ -379,10 +410,12 @@ export default compose(
|
||||
uncategorizedTransationsIdsSelected,
|
||||
excludedTransactionsIdsSelected,
|
||||
openMatchingTransactionAside,
|
||||
categorizedTransactionsSelected,
|
||||
}) => ({
|
||||
uncategorizedTransationsIdsSelected,
|
||||
excludedTransactionsIdsSelected,
|
||||
openMatchingTransactionAside,
|
||||
categorizedTransactionsSelected,
|
||||
}),
|
||||
),
|
||||
withBankingActions,
|
||||
|
||||
@@ -17,15 +17,16 @@ import { TABLES } from '@/constants/tables';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withAlertsActions from '@/containers/Alert/withAlertActions';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
import { withBankingActions } from '../withBankingActions';
|
||||
|
||||
import { useMemorizedColumnsWidths } from '@/hooks';
|
||||
import { useAccountTransactionsColumns, ActionsMenu } from './components';
|
||||
import { useAccountTransactionsAllContext } from './AccountTransactionsAllBoot';
|
||||
import { useUnmatchMatchedUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
import { useUncategorizeTransaction } from '@/hooks/query';
|
||||
import { handleCashFlowTransactionType } from './utils';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { useUncategorizeTransaction } from '@/hooks/query';
|
||||
|
||||
/**
|
||||
* Account transactions data table.
|
||||
@@ -39,13 +40,19 @@ function AccountTransactionsDataTable({
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
|
||||
// #withBankingActions
|
||||
setCategorizedTransactionsSelected,
|
||||
}) {
|
||||
// Retrieve table columns.
|
||||
const columns = useAccountTransactionsColumns();
|
||||
|
||||
// Retrieve list context.
|
||||
const { cashflowTransactions, isCashFlowTransactionsLoading } =
|
||||
useAccountTransactionsAllContext();
|
||||
const {
|
||||
cashflowTransactions,
|
||||
isCashFlowTransactionsLoading,
|
||||
isCashFlowTransactionsFetching,
|
||||
} = useAccountTransactionsAllContext();
|
||||
|
||||
const { mutateAsync: uncategorizeTransaction } = useUncategorizeTransaction();
|
||||
const { mutateAsync: unmatchTransaction } =
|
||||
@@ -97,6 +104,15 @@ function AccountTransactionsDataTable({
|
||||
});
|
||||
};
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = (selected) => {
|
||||
const selectedIds = selected
|
||||
?.filter((row) => row.original.uncategorized_transaction_id)
|
||||
?.map((row) => row.original.uncategorized_transaction_id);
|
||||
|
||||
setCategorizedTransactionsSelected(selectedIds);
|
||||
};
|
||||
|
||||
return (
|
||||
<CashflowTransactionsTable
|
||||
noInitialFetch={true}
|
||||
@@ -105,6 +121,7 @@ function AccountTransactionsDataTable({
|
||||
sticky={true}
|
||||
loading={isCashFlowTransactionsLoading}
|
||||
headerLoading={isCashFlowTransactionsLoading}
|
||||
progressBarLoading={isCashFlowTransactionsFetching}
|
||||
expandColumnSpace={1}
|
||||
expandToggleColumn={2}
|
||||
selectionColumnWidth={45}
|
||||
@@ -119,6 +136,8 @@ function AccountTransactionsDataTable({
|
||||
vListOverscanRowCount={0}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
selectionColumn={true}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
noResults={<T id={'cash_flow.account_transactions.no_results'} />}
|
||||
className="table-constrant"
|
||||
payload={{
|
||||
@@ -136,6 +155,7 @@ export default compose(
|
||||
})),
|
||||
withAlertsActions,
|
||||
withDrawerActions,
|
||||
withBankingActions,
|
||||
)(AccountTransactionsDataTable);
|
||||
|
||||
const DashboardConstrantTable = styled(DataTable)`
|
||||
|
||||
@@ -20,7 +20,8 @@ export function AccountTransactionsFilterTabs() {
|
||||
const hasUncategorizedTransx = useMemo(
|
||||
() =>
|
||||
bankAccountMetaSummary?.totalUncategorizedTransactions > 0 ||
|
||||
bankAccountMetaSummary?.totalExcludedTransactions > 0,
|
||||
bankAccountMetaSummary?.totalExcludedTransactions > 0 ||
|
||||
bankAccountMetaSummary?.totalPendingTransactions > 0,
|
||||
[bankAccountMetaSummary],
|
||||
);
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
useAccountTransactionsContext,
|
||||
} from './AccountTransactionsProvider';
|
||||
import { AccountTransactionsDetailsBar } from './AccountTransactionsDetailsBar';
|
||||
import { AccountTransactionsProgressBar } from './components';
|
||||
import { AccountTransactionsFilterTabs } from './AccountTransactionsFilterTabs';
|
||||
import { AppContentShell } from '@/components/AppShell';
|
||||
import { CategorizeTransactionAside } from '../CategorizeTransactionAside/CategorizeTransactionAside';
|
||||
import { AccountTransactionsLoadingBar } from './components';
|
||||
import { withBanking } from '../withBanking';
|
||||
|
||||
/**
|
||||
@@ -42,8 +42,8 @@ function AccountTransactionsMain() {
|
||||
return (
|
||||
<AppContentShell.Main ref={(e) => setScrollableRef(e)}>
|
||||
<AccountTransactionsActionsBar />
|
||||
<AccountTransactionsLoadingBar />
|
||||
<AccountTransactionsDetailsBar />
|
||||
<AccountTransactionsProgressBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<AccountTransactionsFilterTabs />
|
||||
|
||||
@@ -39,6 +39,7 @@ function AccountTransactionsProvider({ query, ...props }) {
|
||||
const {
|
||||
data: bankAccountMetaSummary,
|
||||
isLoading: isBankAccountMetaSummaryLoading,
|
||||
isFetching: isBankAccountMetaSummaryFetching,
|
||||
} = useGetBankAccountSummaryMeta(accountId);
|
||||
|
||||
const [scrollableRef, setScrollableRef] = useState();
|
||||
@@ -52,15 +53,18 @@ function AccountTransactionsProvider({ query, ...props }) {
|
||||
|
||||
isCashFlowAccountsFetching,
|
||||
isCashFlowAccountsLoading,
|
||||
|
||||
isCurrentAccountFetching,
|
||||
isCurrentAccountLoading,
|
||||
|
||||
isBankAccountMetaSummaryLoading,
|
||||
isBankAccountMetaSummaryFetching,
|
||||
|
||||
filterTab,
|
||||
setFilterTab,
|
||||
|
||||
scrollableRef,
|
||||
setScrollableRef
|
||||
setScrollableRef,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
// @ts-nocheck
|
||||
import { useMemo } from 'react';
|
||||
import * as R from 'ramda';
|
||||
import * as Yup from 'yup';
|
||||
import { useMemo } from 'react';
|
||||
import { Form, Formik } from 'formik';
|
||||
import { useAppQueryString } from '@/hooks';
|
||||
import { Box, FDateInput, FFormGroup, Group, Icon, Stack } from '@/components';
|
||||
import { FDateInput, FFormGroup, Group, Icon, Stack } from '@/components';
|
||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||
import { TagsControl } from '@/components/TagsControl';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
FormGroup,
|
||||
Intent,
|
||||
Popover,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import { Form, Formik } from 'formik';
|
||||
import { Button, FormGroup, Intent, Position } from '@blueprintjs/core';
|
||||
|
||||
export function AccountTransactionsUncategorizeFilter() {
|
||||
const { bankAccountMetaSummary } = useAccountTransactionsContext();
|
||||
@@ -25,31 +17,49 @@ export function AccountTransactionsUncategorizeFilter() {
|
||||
bankAccountMetaSummary?.totalUncategorizedTransactions;
|
||||
const totalRecognized = bankAccountMetaSummary?.totalRecognizedTransactions;
|
||||
|
||||
const totalPending = bankAccountMetaSummary?.totalPendingTransactions;
|
||||
|
||||
const handleTabsChange = (value) => {
|
||||
setLocationQuery({ uncategorizedFilter: value });
|
||||
};
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
R.when(
|
||||
() => totalPending > 0,
|
||||
R.append({
|
||||
value: 'pending',
|
||||
label: (
|
||||
<>
|
||||
Pending <strong>({totalPending})</strong>
|
||||
</>
|
||||
),
|
||||
}),
|
||||
)([
|
||||
{
|
||||
value: 'all',
|
||||
label: (
|
||||
<>
|
||||
All <strong>({totalUncategorized})</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'recognized',
|
||||
label: (
|
||||
<>
|
||||
Recognized <strong>({totalRecognized})</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]),
|
||||
[totalPending, totalRecognized, totalUncategorized],
|
||||
);
|
||||
|
||||
return (
|
||||
<Group position={'apart'}>
|
||||
<TagsControl
|
||||
options={[
|
||||
{
|
||||
value: 'all',
|
||||
label: (
|
||||
<>
|
||||
All <strong>({totalUncategorized})</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'recognized',
|
||||
label: (
|
||||
<>
|
||||
Recognized <strong>({totalRecognized})</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
options={options}
|
||||
value={locationQuery?.uncategorizedFilter || 'all'}
|
||||
onValueChange={handleTabsChange}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
@@ -70,6 +76,8 @@ function AccountTransactionsSwitcher() {
|
||||
case 'all':
|
||||
default:
|
||||
return <AccountUncategorizedTransactions />;
|
||||
case 'pending':
|
||||
return <PendingTransactions />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,11 @@ function ExcludedTransactionsTableRoot({
|
||||
// #withBankingActions
|
||||
setExcludedTransactionsSelected,
|
||||
}: ExcludeTransactionsTableProps) {
|
||||
const { excludedBankTransactions } = useExcludedTransactionsBoot();
|
||||
const {
|
||||
excludedBankTransactions,
|
||||
isExcludedTransactionsLoading,
|
||||
isExcludedTransactionsFetching,
|
||||
} = useExcludedTransactionsBoot();
|
||||
const { mutateAsync: unexcludeBankTransaction } =
|
||||
useUnexcludeUncategorizedTransaction();
|
||||
|
||||
@@ -79,8 +83,9 @@ function ExcludedTransactionsTableRoot({
|
||||
columns={columns}
|
||||
data={excludedBankTransactions}
|
||||
sticky={true}
|
||||
loading={false}
|
||||
headerLoading={false}
|
||||
loading={isExcludedTransactionsLoading}
|
||||
headerLoading={isExcludedTransactionsLoading}
|
||||
progressBarLoading={isExcludedTransactionsFetching}
|
||||
expandColumnSpace={1}
|
||||
expandToggleColumn={2}
|
||||
selectionColumnWidth={45}
|
||||
|
||||
@@ -37,12 +37,13 @@ export function useExcludedTransactionsColumns() {
|
||||
() => [
|
||||
{
|
||||
Header: 'Date',
|
||||
accessor: 'formatted_date',
|
||||
accessor: 'formatted_date',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
Header: 'Description',
|
||||
accessor: descriptionAccessor,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
Header: 'Payee',
|
||||
|
||||
@@ -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,111 @@
|
||||
// @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,
|
||||
isPendingTransactionFetching,
|
||||
} = usePendingTransactionsContext();
|
||||
|
||||
return (
|
||||
<CashflowTransactionsTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={pendingTransactions || []}
|
||||
sticky={true}
|
||||
loading={isPendingTransactionsLoading}
|
||||
headerLoading={isPendingTransactionsLoading}
|
||||
progressBarLoading={isPendingTransactionFetching}
|
||||
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,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
@@ -52,8 +52,11 @@ function AccountTransactionsDataTable({
|
||||
const { scrollableRef } = useAccountTransactionsContext();
|
||||
|
||||
// Retrieve list context.
|
||||
const { uncategorizedTransactions, isUncategorizedTransactionsLoading } =
|
||||
useAccountUncategorizedTransactionsContext();
|
||||
const {
|
||||
uncategorizedTransactions,
|
||||
isUncategorizedTransactionsLoading,
|
||||
isUncategorizedTransactionFetching,
|
||||
} = useAccountUncategorizedTransactionsContext();
|
||||
|
||||
const { mutateAsync: excludeTransaction } =
|
||||
useExcludeUncategorizedTransaction();
|
||||
@@ -105,6 +108,7 @@ function AccountTransactionsDataTable({
|
||||
selectionColumn={true}
|
||||
loading={isUncategorizedTransactionsLoading}
|
||||
headerLoading={isUncategorizedTransactionsLoading}
|
||||
progressBarLoading={isUncategorizedTransactionFetching}
|
||||
expandColumnSpace={1}
|
||||
expandToggleColumn={2}
|
||||
selectionColumnWidth={45}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
|
||||
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
|
||||
import { useUncategorizeTransactionsBulkAction } from '@/hooks/query/bank-transactions';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Uncategorize bank account transactions in build alert.
|
||||
*/
|
||||
function UncategorizeBankTransactionsBulkAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { uncategorizeTransactionsIds },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: uncategorizeTransactions, isLoading } =
|
||||
useUncategorizeTransactionsBulkAction();
|
||||
|
||||
// Handle activate item alert cancel.
|
||||
const handleCancelActivateItem = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// Handle confirm item activated.
|
||||
const handleConfirmItemActivate = () => {
|
||||
uncategorizeTransactions({ ids: uncategorizeTransactionsIds })
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The bank feeds of the bank account has been resumed.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch((error) => {})
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={'Uncategorize Transactions'}
|
||||
intent={Intent.DANGER}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelActivateItem}
|
||||
loading={isLoading}
|
||||
onConfirm={handleConfirmItemActivate}
|
||||
>
|
||||
<p>
|
||||
Are you sure want to uncategorize the selected bank transactions, this
|
||||
action is not reversible but you can always categorize them again?
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(UncategorizeBankTransactionsBulkAlert);
|
||||
@@ -9,6 +9,10 @@ const PauseFeedsBankAccountAlert = React.lazy(
|
||||
() => import('./PauseFeedsBankAccount'),
|
||||
);
|
||||
|
||||
const UncategorizeTransactionsBulkAlert = React.lazy(
|
||||
() => import('./UncategorizeBankTransactionsBulkAlert'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Bank account alerts.
|
||||
*/
|
||||
@@ -21,4 +25,8 @@ export const BankAccountAlerts = [
|
||||
name: 'pause-feeds-syncing-bank-accounnt',
|
||||
component: PauseFeedsBankAccountAlert,
|
||||
},
|
||||
{
|
||||
name: 'uncategorize-transactions-bulk',
|
||||
component: UncategorizeTransactionsBulkAlert,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
Intent,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Tag,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Tooltip,
|
||||
} from '@blueprintjs/core';
|
||||
import { Box, FormatDateCell, Icon, MaterialProgressBar } from '@/components';
|
||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||
import { Intent, Menu, MenuItem, Tag } from '@blueprintjs/core';
|
||||
import { FormatDateCell, Icon } from '@/components';
|
||||
import { safeCallback } from '@/utils';
|
||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||
import FinancialLoadingBar from '@/containers/FinancialStatements/FinancialLoadingBar';
|
||||
|
||||
export function AccountTransactionsLoadingBar() {
|
||||
const {
|
||||
isBankAccountMetaSummaryFetching,
|
||||
isCurrentAccountFetching,
|
||||
isCashFlowAccountsFetching,
|
||||
} = useAccountTransactionsContext();
|
||||
|
||||
const isLoading =
|
||||
isCashFlowAccountsFetching ||
|
||||
isCurrentAccountFetching ||
|
||||
isBankAccountMetaSummaryFetching;
|
||||
|
||||
if (isLoading) {
|
||||
return <FinancialLoadingBar />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function ActionsMenu({
|
||||
payload: { onUncategorize, onUnmatch },
|
||||
@@ -137,16 +148,3 @@ export function useAccountTransactionsColumns() {
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Account transactions progress bar.
|
||||
*/
|
||||
export function AccountTransactionsProgressBar() {
|
||||
const { isCashFlowTransactionsFetching, isUncategorizedTransactionFetching } =
|
||||
useAccountTransactionsContext();
|
||||
|
||||
return isCashFlowTransactionsFetching ||
|
||||
isUncategorizedTransactionFetching ? (
|
||||
<MaterialProgressBar />
|
||||
) : null;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ export const handleCashFlowTransactionType = (reference, openDrawer) => {
|
||||
expenseId: reference.reference_id,
|
||||
});
|
||||
case 'PaymentReceive':
|
||||
return openDrawer(DRAWERS.PAYMENT_RECEIVE_DETAILS, {
|
||||
return openDrawer(DRAWERS.PAYMENT_RECEIVED_DETAILS, {
|
||||
paymentReceiveId: reference.reference_id,
|
||||
});
|
||||
case 'BillPayment':
|
||||
|
||||
@@ -10,6 +10,7 @@ import { CashFlowAccountsProvider } from './CashFlowAccountsProvider';
|
||||
import CashflowAccountsGrid from './CashflowAccountsGrid';
|
||||
import CashFlowAccountsActionsBar from './CashFlowAccountsActionsBar';
|
||||
import { CashflowAccountsPlaidLink } from './CashflowAccountsPlaidLink';
|
||||
import { CashflowAccountsLoadingBar } from './CashFlowAccountsLoadingBar';
|
||||
|
||||
import withCashflowAccounts from '@/containers/CashFlow/AccountTransactions/withCashflowAccounts';
|
||||
import withCashflowAccountsTableActions from '@/containers/CashFlow/AccountTransactions/withCashflowAccountsTableActions';
|
||||
@@ -35,6 +36,7 @@ function CashFlowAccountsList({
|
||||
return (
|
||||
<CashFlowAccountsProvider tableState={cashflowAccountsTableState}>
|
||||
<CashFlowAccountsActionsBar />
|
||||
<CashflowAccountsLoadingBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<CashflowAccountsGrid />
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// @ts-nocheck
|
||||
import FinancialLoadingBar from '@/containers/FinancialStatements/FinancialLoadingBar';
|
||||
import { useCashFlowAccountsContext } from './CashFlowAccountsProvider';
|
||||
|
||||
export function CashflowAccountsLoadingBar() {
|
||||
const { isCashFlowAccountsFetching } = useCashFlowAccountsContext();
|
||||
|
||||
if (isCashFlowAccountsFetching) {
|
||||
return <FinancialLoadingBar />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -123,7 +123,12 @@ function CashflowBankAccount({
|
||||
code={account.code}
|
||||
balance={!isNull(account.amount) ? account.formatted_amount : '-'}
|
||||
type={account.account_type}
|
||||
updatedBeforeText={getUpdatedBeforeText(account.createdAt)}
|
||||
updatedBeforeText={
|
||||
account.last_feeds_updated_from_now
|
||||
? `Updated ${account.last_feeds_updated_from_now} ago`
|
||||
: ''
|
||||
}
|
||||
uncategorizedTransactionsCount={account.uncategorized_transactions}
|
||||
/>
|
||||
</CashflowAccountAnchor>
|
||||
</ContextMenu2>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useAccounts, useBranches } from '@/hooks/query';
|
||||
import { Spinner } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { Spinner } from '@blueprintjs/core';
|
||||
import { Features } from '@/constants';
|
||||
import { useAccounts, useBranches } from '@/hooks/query';
|
||||
import { useFeatureCan } from '@/hooks/state';
|
||||
|
||||
interface MatchingReconcileTransactionBootProps {
|
||||
children: React.ReactNode;
|
||||
@@ -15,8 +17,17 @@ const MatchingReconcileTransactionBootContext =
|
||||
export function MatchingReconcileTransactionBoot({
|
||||
children,
|
||||
}: MatchingReconcileTransactionBootProps) {
|
||||
// Detarmines whether the feature is enabled.
|
||||
const { featureCan } = useFeatureCan();
|
||||
const isBranchFeatureCan = featureCan(Features.Branches);
|
||||
|
||||
const { data: accounts, isLoading: isAccountsLoading } = useAccounts({}, {});
|
||||
const { data: branches, isLoading: isBranchesLoading } = useBranches({}, {});
|
||||
const { data: branches, isLoading: isBranchesLoading } = useBranches(
|
||||
{},
|
||||
{
|
||||
enabled: isBranchFeatureCan,
|
||||
},
|
||||
);
|
||||
|
||||
const provider = {
|
||||
accounts,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Box,
|
||||
BranchSelect,
|
||||
FDateInput,
|
||||
FeatureCan,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FMoneyInputGroup,
|
||||
@@ -30,6 +31,7 @@ import { useAccountTransactionsContext } from '../../AccountTransactions/Account
|
||||
import { MatchingReconcileFormSchema } from './MatchingReconcileTransactionForm.schema';
|
||||
import { initialValues, transformToReq } from './_utils';
|
||||
import { withBanking } from '../../withBanking';
|
||||
import { Features } from '@/constants';
|
||||
|
||||
interface MatchingReconcileTransactionFormProps {
|
||||
onSubmitSuccess?: (values: any) => void;
|
||||
@@ -205,26 +207,28 @@ function CreateReconcileTransactionContent() {
|
||||
<FInputGroup name={'reference_no'} />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'branchId'}
|
||||
label={'Branch'}
|
||||
labelInfo={<Tag minimal>Required</Tag>}
|
||||
fastField
|
||||
>
|
||||
<BranchSelect
|
||||
<FeatureCan feature={Features.Branches}>
|
||||
<FFormGroup
|
||||
name={'branchId'}
|
||||
branches={branches}
|
||||
popoverProps={{
|
||||
minimal: false,
|
||||
position: Position.LEFT,
|
||||
modifiers: {
|
||||
preventOverflow: { enabled: true },
|
||||
},
|
||||
boundary: 'viewport',
|
||||
}}
|
||||
label={'Branch'}
|
||||
labelInfo={<Tag minimal>Required</Tag>}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
>
|
||||
<BranchSelect
|
||||
name={'branchId'}
|
||||
branches={branches}
|
||||
popoverProps={{
|
||||
minimal: false,
|
||||
position: Position.LEFT,
|
||||
modifiers: {
|
||||
preventOverflow: { enabled: true },
|
||||
},
|
||||
boundary: 'viewport',
|
||||
}}
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
</FeatureCan>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { round } from 'lodash';
|
||||
import { MatchingTransactionFormValues } from './types';
|
||||
import { useMatchingTransactionBoot } from './MatchingTransactionBoot';
|
||||
import { useCategorizeTransactionTabsBoot } from './CategorizeTransactionTabsBoot';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const transformToReq = (
|
||||
values: MatchingTransactionFormValues,
|
||||
@@ -38,7 +38,7 @@ export const useGetPendingAmountMatched = () => {
|
||||
);
|
||||
const pendingAmount = totalPending - totalMatchedAmount;
|
||||
|
||||
return pendingAmount;
|
||||
return round(pendingAmount, 2);
|
||||
}, [totalPending, perfectMatches, possibleMatches, values]);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ export const withBanking = (mapState) => {
|
||||
|
||||
transactionsToCategorizeIdsSelected:
|
||||
state.plaid.transactionsToCategorizeSelected,
|
||||
|
||||
categorizedTransactionsSelected:
|
||||
state.plaid.categorizedTransactionsSelected,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
enableMultipleCategorization,
|
||||
addTransactionsToCategorizeSelected,
|
||||
removeTransactionsToCategorizeSelected,
|
||||
setCategorizedTransactionsSelected,
|
||||
resetCategorizedTransactionsSelected,
|
||||
} from '@/store/banking/banking.reducer';
|
||||
|
||||
export interface WithBankingActionsProps {
|
||||
@@ -35,6 +37,9 @@ export interface WithBankingActionsProps {
|
||||
resetTransactionsToCategorizeSelected: () => void;
|
||||
|
||||
enableMultipleCategorization: (enable: boolean) => void;
|
||||
|
||||
setCategorizedTransactionsSelected: (ids: Array<string | number>) => void;
|
||||
resetCategorizedTransactionsSelected: () => void;
|
||||
}
|
||||
|
||||
const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
||||
@@ -120,6 +125,19 @@ const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
||||
*/
|
||||
enableMultipleCategorization: (enable: boolean) =>
|
||||
dispatch(enableMultipleCategorization({ enable })),
|
||||
|
||||
/**
|
||||
* Sets the selected ids of the categorized transactions.
|
||||
* @param {Array<string | number>} ids
|
||||
*/
|
||||
setCategorizedTransactionsSelected: (ids: Array<string | number>) =>
|
||||
dispatch(setCategorizedTransactionsSelected({ ids })),
|
||||
|
||||
/**
|
||||
* Resets the selected categorized transcations.
|
||||
*/
|
||||
resetCategorizedTransactionsSelected: () =>
|
||||
dispatch(resetCategorizedTransactionsSelected()),
|
||||
});
|
||||
|
||||
export const withBankingActions = connect<
|
||||
|
||||
Reference in New Issue
Block a user