Merge branch 'develop' into filter-uncategorized-bank-transactions

This commit is contained in:
Ahmed Bouhuolia
2024-08-22 00:12:37 +02:00
320 changed files with 3212 additions and 1420 deletions

View File

@@ -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,

View File

@@ -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)`

View File

@@ -20,7 +20,8 @@ export function AccountTransactionsFilterTabs() {
const hasUncategorizedTransx = useMemo(
() =>
bankAccountMetaSummary?.totalUncategorizedTransactions > 0 ||
bankAccountMetaSummary?.totalExcludedTransactions > 0,
bankAccountMetaSummary?.totalExcludedTransactions > 0 ||
bankAccountMetaSummary?.totalPendingTransactions > 0,
[bankAccountMetaSummary],
);

View File

@@ -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 />

View File

@@ -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 (

View File

@@ -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}
/>

View File

@@ -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 />;
}
}

View File

@@ -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}

View File

@@ -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',

View File

@@ -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>
);
}

View File

@@ -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;
}
}
}
`;

View File

@@ -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 };

View File

@@ -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,
},
],
[],
);
}

View File

@@ -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}

View File

@@ -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);

View File

@@ -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,
},
];

View File

@@ -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;
}

View File

@@ -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':

View File

@@ -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 />

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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,

View File

@@ -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>
);
}

View File

@@ -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]);
};

View File

@@ -22,6 +22,9 @@ export const withBanking = (mapState) => {
transactionsToCategorizeIdsSelected:
state.plaid.transactionsToCategorizeSelected,
categorizedTransactionsSelected:
state.plaid.categorizedTransactionsSelected,
};
return mapState ? mapState(mapped, state, props) : mapped;
};

View File

@@ -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<