feat: exclude bank transactions in bulk

This commit is contained in:
Ahmed Bouhuolia
2024-07-17 23:19:59 +02:00
parent fe214b1b2d
commit 51471ed000
13 changed files with 504 additions and 36 deletions

View File

@@ -1,8 +1,9 @@
import { Inject, Service } from 'typedi';
import { param } from 'express-validator';
import { NextFunction, Request, Response, Router, query } from 'express';
import { body, param, query } from 'express-validator';
import { NextFunction, Request, Response, Router } from 'express';
import BaseController from '../BaseController';
import { ExcludeBankTransactionsApplication } from '@/services/Banking/Exclude/ExcludeBankTransactionsApplication';
import { map, parseInt, trim } from 'lodash';
@Service()
export class ExcludeBankTransactionsController extends BaseController {
@@ -15,9 +16,21 @@ export class ExcludeBankTransactionsController extends BaseController {
public router() {
const router = Router();
router.put(
'/transactions/exclude',
[body('ids').exists()],
this.validationResult,
this.excludeBulkBankTransactions.bind(this)
);
router.put(
'/transactions/unexclude',
[body('ids').exists()],
this.validationResult,
this.unexcludeBulkBankTransactins.bind(this)
);
router.put(
'/transactions/:transactionId/exclude',
[param('transactionId').exists()],
[param('transactionId').exists().toInt()],
this.validationResult,
this.excludeBankTransaction.bind(this)
);
@@ -94,6 +107,63 @@ export class ExcludeBankTransactionsController extends BaseController {
}
}
/**
* Exclude bank transactions in bulk.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private async excludeBulkBankTransactions(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { ids } = this.matchedBodyData(req);
try {
await this.excludeBankTransactionApp.excludeBankTransactions(
tenantId,
ids
);
return res.status(200).send({
message: 'The given bank transactions have been excluded',
ids,
});
} catch (error) {
next(error);
}
}
/**
* Unexclude the given bank transactions in bulk.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<Response | null>}
*/
private async unexcludeBulkBankTransactins(
req: Request,
res: Response,
next: NextFunction
): Promise<Response | null> {
const { tenantId } = req;
const { ids } = this.matchedBodyData(req);
try {
await this.excludeBankTransactionApp.unexcludeBankTransactions(
tenantId,
ids
);
return res.status(200).send({
message: 'The given bank transactions have been excluded',
ids,
});
} catch (error) {
next(error);
}
}
/**
* Retrieves the excluded uncategorized bank transactions.
* @param {Request} req
@@ -109,7 +179,6 @@ export class ExcludeBankTransactionsController extends BaseController {
const { tenantId } = req;
const filter = this.matchedBodyData(req);
console.log('123');
try {
const data =
await this.excludeBankTransactionApp.getExcludedBankTransactions(

View File

@@ -0,0 +1,31 @@
import { Inject, Service } from 'typedi';
import { ExcludeBankTransaction } from './ExcludeBankTransaction';
import PromisePool from '@supercharge/promise-pool';
import { castArray } from 'lodash';
@Service()
export class ExcludeBankTransactions {
@Inject()
private excludeBankTransaction: ExcludeBankTransaction;
/**
* Exclude bank transactions in bulk.
* @param {number} tenantId
* @param {number} bankTransactionIds
*/
public async excludeBankTransactions(
tenantId: number,
bankTransactionIds: Array<number> | number
) {
const _bankTransactionIds = castArray(bankTransactionIds);
await PromisePool.withConcurrency(1)
.for(_bankTransactionIds)
.process(async (bankTransactionId: number) => {
return this.excludeBankTransaction.excludeBankTransaction(
tenantId,
bankTransactionId
);
});
}
}

View File

@@ -3,6 +3,8 @@ import { ExcludeBankTransaction } from './ExcludeBankTransaction';
import { UnexcludeBankTransaction } from './UnexcludeBankTransaction';
import { GetExcludedBankTransactionsService } from './GetExcludedBankTransactions';
import { ExcludedBankTransactionsQuery } from './_types';
import { UnexcludeBankTransactions } from './UnexcludeBankTransactions';
import { ExcludeBankTransactions } from './ExcludeBankTransactions';
@Service()
export class ExcludeBankTransactionsApplication {
@@ -15,6 +17,12 @@ export class ExcludeBankTransactionsApplication {
@Inject()
private getExcludedBankTransactionsService: GetExcludedBankTransactionsService;
@Inject()
private excludeBankTransactionsService: ExcludeBankTransactions;
@Inject()
private unexcludeBankTransactionsService: UnexcludeBankTransactions;
/**
* Marks a bank transaction as excluded.
* @param {number} tenantId - The ID of the tenant.
@@ -56,4 +64,36 @@ export class ExcludeBankTransactionsApplication {
filter
);
}
/**
* Exclude the given bank transactions in bulk.
* @param {number} tenantId
* @param {Array<number> | number} bankTransactionIds
* @returns {Promise<void>}
*/
public excludeBankTransactions(
tenantId: number,
bankTransactionIds: Array<number> | number
): Promise<void> {
return this.excludeBankTransactionsService.excludeBankTransactions(
tenantId,
bankTransactionIds
);
}
/**
* Exclude the given bank transactions in bulk.
* @param {number} tenantId
* @param {Array<number> | number} bankTransactionIds
* @returns {Promise<void>}
*/
public unexcludeBankTransactions(
tenantId: number,
bankTransactionIds: Array<number> | number
): Promise<void> {
return this.unexcludeBankTransactionsService.unexcludeBankTransactions(
tenantId,
bankTransactionIds
);
}
}

View File

@@ -0,0 +1,31 @@
import { Inject, Service } from 'typedi';
import PromisePool from '@supercharge/promise-pool';
import { UnexcludeBankTransaction } from './UnexcludeBankTransaction';
import { castArray } from 'lodash';
@Service()
export class UnexcludeBankTransactions {
@Inject()
private unexcludeBankTransaction: UnexcludeBankTransaction;
/**
* Unexclude bank transactions in bulk.
* @param {number} tenantId
* @param {number} bankTransactionIds
*/
public async unexcludeBankTransactions(
tenantId: number,
bankTransactionIds: Array<number> | number
) {
const _bankTransactionIds = castArray(bankTransactionIds);
await PromisePool.withConcurrency(1)
.for(_bankTransactionIds)
.process(async (bankTransactionId: number) => {
return this.unexcludeBankTransaction.unexcludeBankTransaction(
tenantId,
bankTransactionId
);
});
}
}

View File

@@ -11,6 +11,7 @@ import {
MenuItem,
PopoverInteractionKind,
Position,
Intent,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import {
@@ -18,6 +19,7 @@ import {
DashboardActionsBar,
DashboardRowsHeightButton,
FormattedMessage as T,
AppToaster,
} from '@/components';
import { CashFlowMenuItems } from './utils';
@@ -33,6 +35,13 @@ import withSettings from '@/containers/Settings/withSettings';
import withSettingsActions from '@/containers/Settings/withSettingsActions';
import { compose } from '@/utils';
import { withBanking } from '../withBanking';
import { isEmpty } from 'lodash';
import {
useExcludeUncategorizedTransactions,
useUnexcludeUncategorizedTransaction,
useUnexcludeUncategorizedTransactions,
} from '@/hooks/query/bank-rules';
function AccountTransactionsActionsBar({
// #withDialogActions
@@ -43,6 +52,10 @@ function AccountTransactionsActionsBar({
// #withSettingsActions
addSetting,
// #withBanking
uncategorizedTransationsIdsSelected,
excludedTransactionsIdsSelected,
}) {
const history = useHistory();
const { accountId } = useAccountTransactionsContext();
@@ -87,6 +100,54 @@ function AccountTransactionsActionsBar({
refresh();
};
const {
mutateAsync: excludeUncategorizedTransactions,
isLoading: isExcludingLoading,
} = useExcludeUncategorizedTransactions();
const {
mutateAsync: unexcludeUncategorizedTransactions,
isLoading: isUnexcludingLoading,
} = useUnexcludeUncategorizedTransactions();
// Handles the exclude uncategorized transactions in bulk.
const handleExcludeUncategorizedBtnClick = () => {
excludeUncategorizedTransactions({
ids: uncategorizedTransationsIdsSelected,
})
.then(() => {
AppToaster.show({
message: 'The selected transactions have been excluded.',
intent: Intent.SUCCESS,
});
})
.catch(() => {
AppToaster.show({
message: 'Something went wrong',
intent: Intent.DANGER,
});
});
};
// Handles the unexclude categorized button click.
const handleUnexcludeUncategorizedBtnClick = () => {
unexcludeUncategorizedTransactions({
ids: excludedTransactionsIdsSelected,
})
.then(() => {
AppToaster.show({
message: 'The selected transactions have been unexcluded.',
intent: Intent.SUCCESS,
});
})
.catch((error) => {
AppToaster.show({
message: 'Something went wrong',
intent: Intent.DANGER,
});
});
};
return (
<DashboardActionsBar>
<NavbarGroup>
@@ -129,6 +190,28 @@ function AccountTransactionsActionsBar({
onChange={handleTableRowSizeChange}
/>
<NavbarDivider />
{!isEmpty(uncategorizedTransationsIdsSelected) && (
<Button
icon={<Icon icon="disable" iconSize={16} />}
text={'Exclude'}
onClick={handleExcludeUncategorizedBtnClick}
className={Classes.MINIMAL}
intent={Intent.DANGER}
disable={isExcludingLoading}
/>
)}
{!isEmpty(excludedTransactionsIdsSelected) && (
<Button
icon={<Icon icon="disable" iconSize={16} />}
text={'Unexclude'}
onClick={handleUnexcludeUncategorizedBtnClick}
className={Classes.MINIMAL}
intent={Intent.DANGER}
disable={isUnexcludingLoading}
/>
)}
</NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}>
@@ -164,4 +247,13 @@ export default compose(
withSettings(({ cashflowTransactionsSettings }) => ({
cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize,
})),
withBanking(
({
uncategorizedTransationsIdsSelected,
excludedTransactionsIdsSelected,
}) => ({
uncategorizedTransationsIdsSelected,
excludedTransactionsIdsSelected,
}),
),
)(AccountTransactionsActionsBar);

View File

@@ -33,6 +33,7 @@ function AccountTransactionsDataTable({
// #withBankingActions
setUncategorizedTransactionIdForMatching,
setUncategorizedTransactionsSelected,
}) {
// Retrieve table columns.
const columns = useAccountUncategorizedTransactionsColumns();
@@ -73,12 +74,19 @@ function AccountTransactionsDataTable({
});
};
// Handle selected rows change.
const handleSelectedRowsChange = (selected) => {
const _selectedIds = selected?.map((row) => row.original.id);
setUncategorizedTransactionsSelected(_selectedIds);
};
return (
<CashflowTransactionsTable
noInitialFetch={true}
columns={columns}
data={uncategorizedTransactions || []}
sticky={true}
selectionColumn={true}
loading={isUncategorizedTransactionsLoading}
headerLoading={isUncategorizedTransactionsLoading}
expandColumnSpace={1}
@@ -99,6 +107,7 @@ function AccountTransactionsDataTable({
'There is no uncategorized transactions in the current account.'
}
className="table-constrant"
onSelectedRowsChange={handleSelectedRowsChange}
payload={{
onExclude: handleExcludeTransaction,
onCategorize: handleCategorizeBtnClick,

View File

@@ -2,7 +2,7 @@
import React from 'react';
import styled from 'styled-components';
import { Intent } from '@blueprintjs/core';
import * as R from 'ramda';
import {
DataTable,
TableFastCell,
@@ -19,11 +19,20 @@ import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot';
import { ActionsMenu } from './_components';
import { useUnexcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
import {
WithBankingActionsProps,
withBankingActions,
} from '../../withBankingActions';
interface ExcludeTransactionsTableProps extends WithBankingActionsProps {}
/**
* Renders the recognized account transactions datatable.
*/
export function ExcludedTransactionsTable() {
function ExcludedTransactionsTableRoot({
// #withBankingActions
setExcludedTransactionsSelected,
}: ExcludeTransactionsTableProps) {
const { excludedBankTransactions } = useExcludedTransactionsBoot();
const { mutateAsync: unexcludeBankTransaction } =
useUnexcludeUncategorizedTransaction();
@@ -55,6 +64,12 @@ export function ExcludedTransactionsTable() {
});
};
// Handle selected rows change.
const handleSelectedRowsChange = (selected) => {
const _selectedIds = selected?.map((row) => row.original.id);
setExcludedTransactionsSelected(_selectedIds);
};
return (
<CashflowTransactionsTable
noInitialFetch={true}
@@ -80,6 +95,8 @@ export function ExcludedTransactionsTable() {
onColumnResizing={handleColumnResizing}
noResults={'There is no excluded bank transactions.'}
className="table-constrant"
selectionColumn={true}
onSelectedRowsChange={handleSelectedRowsChange}
payload={{
onRestore: handleRestoreClick,
}}
@@ -87,6 +104,10 @@ export function ExcludedTransactionsTable() {
);
}
export const ExcludedTransactionsTable = R.compose(withBankingActions)(
ExcludedTransactionsTableRoot,
);
const DashboardConstrantTable = styled(DataTable)`
.table {
.thead {

View File

@@ -1,8 +1,27 @@
import { ExcludedTransactionsTable } from "../ExcludedTransactions/ExcludedTransactionsTable";
import { ExcludedBankTransactionsTableBoot } from "../ExcludedTransactions/ExcludedTransactionsTableBoot";
import { AccountTransactionsCard } from "./AccountTransactionsCard";
// @ts-nocheck
import { useEffect } from 'react';
import * as R from 'ramda';
import {
WithBankingActionsProps,
withBankingActions,
} from '../../withBankingActions';
import { ExcludedTransactionsTable } from '../ExcludedTransactions/ExcludedTransactionsTable';
import { ExcludedBankTransactionsTableBoot } from '../ExcludedTransactions/ExcludedTransactionsTableBoot';
import { AccountTransactionsCard } from './AccountTransactionsCard';
interface AccountExcludedTransactionsProps extends WithBankingActionsProps {}
function AccountExcludedTransactionsRoot({
// #withBankingActions
resetExcludedTransactionsSelected,
}: AccountExcludedTransactionsProps) {
useEffect(
() => () => {
resetExcludedTransactionsSelected();
},
[resetExcludedTransactionsSelected],
);
export function AccountExcludedTransactions() {
return (
<ExcludedBankTransactionsTableBoot>
<AccountTransactionsCard>
@@ -11,3 +30,7 @@ export function AccountExcludedTransactions() {
</ExcludedBankTransactionsTableBoot>
);
}
export const AccountExcludedTransactions = R.compose(withBankingActions)(
AccountExcludedTransactionsRoot,
);

View File

@@ -1,8 +1,26 @@
import * as R from 'ramda';
import { useEffect } from 'react';
import AccountTransactionsUncategorizedTable from '../AccountTransactionsUncategorizedTable';
import { AccountUncategorizedTransactionsBoot } from '../AllTransactionsUncategorizedBoot';
import { AccountTransactionsCard } from './AccountTransactionsCard';
import {
WithBankingActionsProps,
withBankingActions,
} from '../../withBankingActions';
interface AccountUncategorizedTransactionsAllRootProps
extends WithBankingActionsProps {}
function AccountUncategorizedTransactionsAllRoot({
resetUncategorizedTransactionsSelected,
}: AccountUncategorizedTransactionsAllRootProps) {
useEffect(
() => () => {
resetUncategorizedTransactionsSelected();
},
[resetUncategorizedTransactionsSelected],
);
export function AccountUncategorizedTransactionsAll() {
return (
<AccountUncategorizedTransactionsBoot>
<AccountTransactionsCard>
@@ -11,3 +29,7 @@ export function AccountUncategorizedTransactionsAll() {
</AccountUncategorizedTransactionsBoot>
);
}
export const AccountUncategorizedTransactionsAll = R.compose(
withBankingActions,
)(AccountUncategorizedTransactionsAllRoot);

View File

@@ -13,6 +13,11 @@ export const withBanking = (mapState) => {
reconcileMatchingTransactionPendingAmount:
state.plaid.openReconcileMatchingTransaction.pending,
uncategorizedTransationsIdsSelected:
state.plaid.uncategorizedTransactionsSelected,
excludedTransactionsIdsSelected: state.plaid.excludedTransactionsSelected,
};
return mapState ? mapState(mapped, state, props) : mapped;
};

View File

@@ -4,6 +4,10 @@ import {
setUncategorizedTransactionIdForMatching,
openReconcileMatchingTransaction,
closeReconcileMatchingTransaction,
setUncategorizedTransactionsSelected,
resetUncategorizedTransactionsSelected,
resetExcludedTransactionsSelected,
setExcludedTransactionsSelected,
} from '@/store/banking/banking.reducer';
export interface WithBankingActionsProps {
@@ -13,6 +17,12 @@ export interface WithBankingActionsProps {
) => void;
openReconcileMatchingTransaction: (pendingAmount: number) => void;
closeReconcileMatchingTransaction: () => void;
setUncategorizedTransactionsSelected: (ids: Array<string | number>) => void;
resetUncategorizedTransactionsSelected: () => void;
setExcludedTransactionsSelected: (ids: Array<string | number>) => void;
resetExcludedTransactionsSelected: () => void;
}
const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
@@ -28,6 +38,24 @@ const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
dispatch(openReconcileMatchingTransaction({ pending: pendingAmount })),
closeReconcileMatchingTransaction: () =>
dispatch(closeReconcileMatchingTransaction()),
setUncategorizedTransactionsSelected: (ids: Array<string | number>) =>
dispatch(
setUncategorizedTransactionsSelected({
transactionIds: ids,
}),
),
resetUncategorizedTransactionsSelected: () =>
dispatch(resetUncategorizedTransactionsSelected()),
setExcludedTransactionsSelected: (ids: Array<string | number>) =>
dispatch(
setExcludedTransactionsSelected({
ids,
}),
),
resetExcludedTransactionsSelected: () =>
dispatch(resetExcludedTransactionsSelected()),
});
export const withBankingActions = connect<

View File

@@ -195,6 +195,20 @@ export function useGetBankTransactionsMatches(
);
}
const onValidateExcludeUncategorizedTransaction = (queryClient) => {
// Invalidate queries.
queryClient.invalidateQueries(QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY);
queryClient.invalidateQueries(
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
);
// Invalidate accounts.
queryClient.invalidateQueries(t.ACCOUNTS);
queryClient.invalidateQueries(t.ACCOUNT);
// invalidate bank account summary.
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
};
type ExcludeUncategorizedTransactionValue = number;
interface ExcludeUncategorizedTransactionRes {}
@@ -228,19 +242,7 @@ export function useExcludeUncategorizedTransaction(
),
{
onSuccess: (res, id) => {
// Invalidate queries.
queryClient.invalidateQueries(
QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY,
);
queryClient.invalidateQueries(
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
);
// Invalidate accounts.
queryClient.invalidateQueries(t.ACCOUNTS);
queryClient.invalidateQueries(t.ACCOUNT);
// invalidate bank account summary.
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
onValidateExcludeUncategorizedTransaction(queryClient);
},
...options,
},
@@ -281,19 +283,83 @@ export function useUnexcludeUncategorizedTransaction(
),
{
onSuccess: (res, id) => {
// Invalidate queries.
queryClient.invalidateQueries(
QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY,
);
queryClient.invalidateQueries(
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
);
// Invalidate accounts.
queryClient.invalidateQueries(t.ACCOUNTS);
queryClient.invalidateQueries(t.ACCOUNT);
onValidateExcludeUncategorizedTransaction(queryClient);
},
...options,
},
);
}
// Invalidate bank account summary.
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
type ExcludeBankTransactionsValue = { ids: Array<number | string> };
interface ExcludeBankTransactionsResponse {}
/**
* Excludes the uncategorized bank transactions in bulk.
* @param {UseMutationResult<ExcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>} options
* @returns {UseMutationResult<ExcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>}
*/
export function useExcludeUncategorizedTransactions(
options?: UseMutationOptions<
ExcludeBankTransactionsResponse,
Error,
ExcludeBankTransactionsValue
>,
): UseMutationResult<
ExcludeBankTransactionsResponse,
Error,
ExcludeBankTransactionsValue
> {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation<
ExcludeBankTransactionsResponse,
Error,
ExcludeBankTransactionsValue
>(
(value: { ids: Array<number | string> }) =>
apiRequest.put(`/cashflow/transactions/exclude`, { ids: value.ids }),
{
onSuccess: (res, id) => {
onValidateExcludeUncategorizedTransaction(queryClient);
},
...options,
},
);
}
type UnexcludeBankTransactionsValue = { ids: Array<number | string> };
interface UnexcludeBankTransactionsResponse {}
/**
* Excludes the uncategorized bank transactions in bulk.
* @param {UseMutationResult<UnexcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>} options
* @returns {UseMutationResult<UnexcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>}
*/
export function useUnexcludeUncategorizedTransactions(
options?: UseMutationOptions<
UnexcludeBankTransactionsResponse,
Error,
UnexcludeBankTransactionsValue
>,
): UseMutationResult<
UnexcludeBankTransactionsResponse,
Error,
UnexcludeBankTransactionsValue
> {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation<
UnexcludeBankTransactionsResponse,
Error,
UnexcludeBankTransactionsValue
>(
(value: { ids: Array<number | string> }) =>
apiRequest.put(`/cashflow/transactions/unexclude`, { ids: value.ids }),
{
onSuccess: (res, id) => {
onValidateExcludeUncategorizedTransaction(queryClient);
},
...options,
},

View File

@@ -5,6 +5,9 @@ interface StorePlaidState {
openMatchingTransactionAside: boolean;
uncategorizedTransactionIdForMatching: number | null;
openReconcileMatchingTransaction: { isOpen: boolean; pending: number };
uncategorizedTransactionsSelected: Array<number | string>;
excludedTransactionsSelected: Array<number | string>;
}
export const PlaidSlice = createSlice({
@@ -17,6 +20,8 @@ export const PlaidSlice = createSlice({
isOpen: false,
pending: 0,
},
uncategorizedTransactionsSelected: [],
excludedTransactionsSelected: [],
} as StorePlaidState,
reducers: {
setPlaidId: (state: StorePlaidState, action: PayloadAction<string>) => {
@@ -52,6 +57,28 @@ export const PlaidSlice = createSlice({
state.openReconcileMatchingTransaction.isOpen = false;
state.openReconcileMatchingTransaction.pending = 0;
},
setUncategorizedTransactionsSelected: (
state: StorePlaidState,
action: PayloadAction<{ transactionIds: Array<string | number> }>,
) => {
state.uncategorizedTransactionsSelected = action.payload.transactionIds;
},
resetUncategorizedTransactionsSelected: (state: StorePlaidState) => {
state.uncategorizedTransactionsSelected = [];
},
setExcludedTransactionsSelected: (
state: StorePlaidState,
action: PayloadAction<{ ids: Array<string | number> }>,
) => {
state.excludedTransactionsSelected = action.payload.ids;
},
resetExcludedTransactionsSelected: (state: StorePlaidState) => {
state.excludedTransactionsSelected = [];
},
},
});
@@ -62,6 +89,10 @@ export const {
closeMatchingTransactionAside,
openReconcileMatchingTransaction,
closeReconcileMatchingTransaction,
setUncategorizedTransactionsSelected,
resetUncategorizedTransactionsSelected,
setExcludedTransactionsSelected,
resetExcludedTransactionsSelected,
} = PlaidSlice.actions;
export const getPlaidToken = (state: any) => state.plaid.plaidToken;