mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat: get bank account meta summary
This commit is contained in:
@@ -4,6 +4,7 @@ import { useParams } from 'react-router-dom';
|
||||
import { DashboardInsider } from '@/components';
|
||||
import { useCashflowAccounts, useAccount } from '@/hooks/query';
|
||||
import { useAppQueryString } from '@/hooks';
|
||||
import { useGetBankAccountSummaryMeta } from '@/hooks/query/bank-rules';
|
||||
|
||||
const AccountTransactionsContext = React.createContext();
|
||||
|
||||
@@ -20,31 +21,38 @@ function AccountTransactionsProvider({ query, ...props }) {
|
||||
const setFilterTab = (value: string) => {
|
||||
setLocationQuery({ filter: value });
|
||||
};
|
||||
// Fetch cashflow accounts.
|
||||
// Retrieves cashflow accounts.
|
||||
const {
|
||||
data: cashflowAccounts,
|
||||
isFetching: isCashFlowAccountsFetching,
|
||||
isLoading: isCashFlowAccountsLoading,
|
||||
} = useCashflowAccounts(query, { keepPreviousData: true });
|
||||
|
||||
// Retrieve specific account details.
|
||||
|
||||
// Retrieves specific account details.
|
||||
const {
|
||||
data: currentAccount,
|
||||
isFetching: isCurrentAccountFetching,
|
||||
isLoading: isCurrentAccountLoading,
|
||||
} = useAccount(accountId, { keepPreviousData: true });
|
||||
|
||||
// Retrieves the bank account meta summary.
|
||||
const {
|
||||
data: bankAccountMetaSummary,
|
||||
isLoading: isBankAccountMetaSummaryLoading,
|
||||
} = useGetBankAccountSummaryMeta(accountId);
|
||||
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
accountId,
|
||||
cashflowAccounts,
|
||||
currentAccount,
|
||||
bankAccountMetaSummary,
|
||||
|
||||
isCashFlowAccountsFetching,
|
||||
isCashFlowAccountsLoading,
|
||||
isCurrentAccountFetching,
|
||||
isCurrentAccountLoading,
|
||||
isBankAccountMetaSummaryLoading,
|
||||
|
||||
filterTab,
|
||||
setFilterTab,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Tag } from '@blueprintjs/core';
|
||||
import { useAppQueryString } from '@/hooks';
|
||||
import { useUncontrolled } from '@/hooks/useUncontrolled';
|
||||
import { Group } from '@/components';
|
||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||
|
||||
const Root = styled.div`
|
||||
display: flex;
|
||||
@@ -26,8 +27,13 @@ const FilterTag = styled(Tag)`
|
||||
`;
|
||||
|
||||
export function AccountTransactionsUncategorizeFilter() {
|
||||
const { bankAccountMetaSummary } = useAccountTransactionsContext();
|
||||
const [locationQuery, setLocationQuery] = useAppQueryString();
|
||||
|
||||
const totalUncategorized =
|
||||
bankAccountMetaSummary?.totalUncategorizedTransactions;
|
||||
const totalRecognized = bankAccountMetaSummary?.totalRecognizedTransactions;
|
||||
|
||||
const handleTabsChange = (value) => {
|
||||
setLocationQuery({ uncategorizedFilter: value });
|
||||
};
|
||||
@@ -36,8 +42,22 @@ export function AccountTransactionsUncategorizeFilter() {
|
||||
<Group position={'apart'}>
|
||||
<SegmentedTabs
|
||||
options={[
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'recognized', label: 'Recognized' },
|
||||
{
|
||||
value: 'all',
|
||||
label: (
|
||||
<>
|
||||
All <strong>({totalUncategorized})</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'recognized',
|
||||
label: (
|
||||
<>
|
||||
Recognized <strong>({totalRecognized})</strong>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
value={locationQuery?.uncategorizedFilter || 'all'}
|
||||
onValueChange={handleTabsChange}
|
||||
@@ -66,8 +86,9 @@ function SegmentedTabs({ options, initialValue, value, onValueChange }) {
|
||||
});
|
||||
return (
|
||||
<Root>
|
||||
{options.map((option) => (
|
||||
{options.map((option, index) => (
|
||||
<FilterTag
|
||||
key={index}
|
||||
round
|
||||
interactive
|
||||
onClick={() => handleChange(option.value)}
|
||||
|
||||
@@ -102,7 +102,7 @@ function AccountTransactionsDataTable({
|
||||
vListOverscanRowCount={0}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
noResults={<T id={'cash_flow.account_transactions.no_results'} />}
|
||||
noResults={'There is no uncategorized transactions in the current account.'}
|
||||
className="table-constrant"
|
||||
payload={{
|
||||
onExclude: handleExcludeTransaction,
|
||||
|
||||
@@ -81,7 +81,7 @@ function ExcludedTransactionsTableRoot() {
|
||||
vListOverscanRowCount={0}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
// noResults={<T id={'cash_flow.account_transactions.no_results'} />}
|
||||
noResults={'There is no excluded bank transactions.'}
|
||||
className="table-constrant"
|
||||
payload={{
|
||||
onRestore: handleRestoreClick,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
|
||||
.emptyState{
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
color: #738091;
|
||||
|
||||
:global ul{
|
||||
list-style: inside;
|
||||
|
||||
li{
|
||||
margin-bottom: 12px;
|
||||
|
||||
&::marker{
|
||||
color: #C5CBD3;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TableVirtualizedListRows,
|
||||
FormattedMessage as T,
|
||||
AppToaster,
|
||||
Stack,
|
||||
} from '@/components';
|
||||
import { TABLES } from '@/constants/tables';
|
||||
|
||||
@@ -24,11 +25,12 @@ import { useRecognizedTransactionsBoot } from './RecognizedTransactionsTableBoot
|
||||
import { ActionsMenu } from './_components';
|
||||
import { compose } from '@/utils';
|
||||
import { useExcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { Intent, Text } from '@blueprintjs/core';
|
||||
import {
|
||||
WithBankingActionsProps,
|
||||
withBankingActions,
|
||||
} from '../../withBankingActions';
|
||||
import styles from './RecognizedTransactionsTable.module.scss';
|
||||
|
||||
interface RecognizedTransactionsTableProps extends WithBankingActionsProps {}
|
||||
|
||||
@@ -114,7 +116,7 @@ function RecognizedTransactionsTableRoot({
|
||||
vListOverscanRowCount={0}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
noResults={<T id={'cash_flow.account_transactions.no_results'} />}
|
||||
noResults={<RecognizedTransactionsTableNoResults />}
|
||||
className="table-constrant"
|
||||
payload={{
|
||||
onExclude: handleExcludeClick,
|
||||
@@ -168,3 +170,26 @@ const CashflowTransactionsTable = styled(DashboardConstrantTable)`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function RecognizedTransactionsTableNoResults() {
|
||||
return (
|
||||
<Stack spacing={12} className={styles.emptyState}>
|
||||
<Text>
|
||||
There are no Recognized transactions due to one of the following
|
||||
reasons:
|
||||
</Text>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Transaction Rules have not yet been created. Transactions are
|
||||
recognized based on the rule criteria.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
The transactions in your bank do not satisfy the criteria in any of
|
||||
your transaction rule(s).
|
||||
</li>
|
||||
</ul>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Menu, MenuItem, MenuDivider, Tag } from '@blueprintjs/core';
|
||||
import { Can, FormatDateCell, Icon, MaterialProgressBar } from '@/components';
|
||||
import {
|
||||
Intent,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Tag,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Tooltip,
|
||||
} from '@blueprintjs/core';
|
||||
import {
|
||||
Box,
|
||||
Can,
|
||||
FormatDateCell,
|
||||
Icon,
|
||||
MaterialProgressBar,
|
||||
} from '@/components';
|
||||
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||
import { safeCallback } from '@/utils';
|
||||
|
||||
@@ -128,6 +144,34 @@ export function AccountTransactionsProgressBar() {
|
||||
) : null;
|
||||
}
|
||||
|
||||
function statusAccessor(transaction) {
|
||||
return transaction.is_recognized ? (
|
||||
<Tooltip
|
||||
compact
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
position={Position.RIGHT}
|
||||
content={
|
||||
<Box>
|
||||
<span>{transaction.assigned_category_formatted}</span>
|
||||
<Icon
|
||||
icon={'arrowRight'}
|
||||
color={'#8F99A8'}
|
||||
iconSize={12}
|
||||
style={{ marginLeft: 8, marginRight: 8 }}
|
||||
/>
|
||||
<span>{transaction.assigned_account_name}</span>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box>
|
||||
<Tag intent={Intent.SUCCESS} interactive>
|
||||
Recognized
|
||||
</Tag>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account uncategorized transctions table columns.
|
||||
*/
|
||||
@@ -170,16 +214,7 @@ export function useAccountUncategorizedTransactionsColumns() {
|
||||
{
|
||||
id: 'status',
|
||||
Header: 'Status',
|
||||
accessor: () =>
|
||||
false ? (
|
||||
<Tag intent={Intent.SUCCESS} interactive>
|
||||
1 Matches
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag intent={Intent.SUCCESS} interactive>
|
||||
Recognized
|
||||
</Tag>
|
||||
),
|
||||
accessor: statusAccessor,
|
||||
},
|
||||
{
|
||||
id: 'deposit',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
.root {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.transaction {
|
||||
@@ -35,4 +36,5 @@
|
||||
.footerTotal {
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid #d8d9d9;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
// @ts-nocheck
|
||||
import { Tab, Tabs } from '@blueprintjs/core';
|
||||
import { MatchingBankTransaction } from './MatchingTransaction';
|
||||
import styles from './CategorizeTransactionTabs.module.scss';
|
||||
import { CategorizeTransactionContent } from '../CategorizeTransaction/drawers/CategorizeTransactionDrawer/CategorizeTransactionContent';
|
||||
import { useCategorizeTransactionTabsBoot } from './CategorizeTransactionTabsBoot';
|
||||
import styles from './CategorizeTransactionTabs.module.scss';
|
||||
|
||||
export function CategorizeTransactionTabs() {
|
||||
const { uncategorizedTransaction } = useCategorizeTransactionTabsBoot();
|
||||
const defaultSelectedTabId = uncategorizedTransaction?.is_recognized
|
||||
? 'categorize'
|
||||
: 'matching';
|
||||
|
||||
return (
|
||||
<Tabs large renderActiveTabPanelOnly className={styles.tabs}>
|
||||
<Tabs
|
||||
large
|
||||
renderActiveTabPanelOnly
|
||||
defaultSelectedTabId={defaultSelectedTabId}
|
||||
className={styles.tabs}
|
||||
>
|
||||
<Tab
|
||||
id="categorize"
|
||||
title="Categorize Transaction"
|
||||
|
||||
@@ -2,22 +2,26 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import { AnchorButton, Button, Intent, Tag, Text } from '@blueprintjs/core';
|
||||
import { FastField, FastFieldProps, Formik } from 'formik';
|
||||
import { AppToaster, Box, FormatNumber, Group, Stack } from '@/components';
|
||||
import {
|
||||
MatchingTransactionBoot,
|
||||
useMatchingTransactionBoot,
|
||||
} from './MatchingTransactionBoot';
|
||||
import { MatchTransaction, MatchTransactionProps } from './MatchTransaction';
|
||||
import styles from './CategorizeTransactionAside.module.scss';
|
||||
import { FastField, FastFieldProps, Form, Formik } from 'formik';
|
||||
import { useMatchUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
import { MatchingTransactionFormValues } from './types';
|
||||
import { transformToReq, useGetPendingAmountMatched } from './utils';
|
||||
import {
|
||||
transformToReq,
|
||||
useGetPendingAmountMatched,
|
||||
useIsShowReconcileTransactionLink,
|
||||
} from './utils';
|
||||
import { useCategorizeTransactionTabsBoot } from './CategorizeTransactionTabsBoot';
|
||||
import {
|
||||
WithBankingActionsProps,
|
||||
withBankingActions,
|
||||
} from '../withBankingActions';
|
||||
import styles from './CategorizeTransactionAside.module.scss';
|
||||
|
||||
const initialValues = {
|
||||
matched: {},
|
||||
@@ -189,20 +193,26 @@ interface MatchTransctionFooterProps extends WithBankingActionsProps {}
|
||||
*/
|
||||
const MatchTransactionFooter = R.compose(withBankingActions)(
|
||||
({ closeMatchingTransactionAside }: MatchTransctionFooterProps) => {
|
||||
const totalPending = useGetPendingAmountMatched();
|
||||
const showReconcileLink = useIsShowReconcileTransactionLink();
|
||||
|
||||
const handleCancelBtnClick = () => {
|
||||
closeMatchingTransactionAside();
|
||||
};
|
||||
const totalPending = useGetPendingAmountMatched();
|
||||
|
||||
return (
|
||||
<Box className={styles.footer}>
|
||||
<Box className={styles.footerTotal}>
|
||||
<Group position={'apart'}>
|
||||
<AnchorButton small minimal intent={Intent.PRIMARY}>
|
||||
Add Reconcile Transaction +
|
||||
</AnchorButton>
|
||||
|
||||
<Text style={{ fontSize: 13 }} tagName="span">
|
||||
{showReconcileLink && (
|
||||
<AnchorButton small minimal intent={Intent.PRIMARY}>
|
||||
Add Reconcile Transaction +
|
||||
</AnchorButton>
|
||||
)}
|
||||
<Text
|
||||
style={{ fontSize: 14, marginLeft: 'auto', color: '#5F6B7C' }}
|
||||
tagName="span"
|
||||
>
|
||||
Pending <FormatNumber value={totalPending} currency={'USD'} />
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useFormikContext } from 'formik';
|
||||
import { MatchingTransactionFormValues } from './types';
|
||||
import { useMatchingTransactionBoot } from './MatchingTransactionBoot';
|
||||
import { useCategorizeTransactionTabsBoot } from './CategorizeTransactionTabsBoot';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const transformToReq = (values: MatchingTransactionFormValues) => {
|
||||
const matchedTransactions = Object.entries(values.matched)
|
||||
@@ -17,18 +19,38 @@ export const transformToReq = (values: MatchingTransactionFormValues) => {
|
||||
export const useGetPendingAmountMatched = () => {
|
||||
const { values } = useFormikContext<MatchingTransactionFormValues>();
|
||||
const { perfectMatches, possibleMatches } = useMatchingTransactionBoot();
|
||||
const { uncategorizedTransaction } = useCategorizeTransactionTabsBoot();
|
||||
|
||||
const matchedItems = [...perfectMatches, ...possibleMatches].filter(
|
||||
(match) => {
|
||||
const key = `${match.transactionType}-${match.transactionId}`;
|
||||
return values.matched[key];
|
||||
},
|
||||
);
|
||||
const totalMatchedAmount = matchedItems.reduce(
|
||||
(total, item) => total + parseFloat(item.amount),
|
||||
0,
|
||||
);
|
||||
const pendingAmount = 0 - totalMatchedAmount;
|
||||
return useMemo(() => {
|
||||
const matchedItems = [...perfectMatches, ...possibleMatches].filter(
|
||||
(match) => {
|
||||
const key = `${match.transactionType}-${match.transactionId}`;
|
||||
return values.matched[key];
|
||||
},
|
||||
);
|
||||
const totalMatchedAmount = matchedItems.reduce(
|
||||
(total, item) => total + parseFloat(item.amount),
|
||||
0,
|
||||
);
|
||||
const amount = uncategorizedTransaction.amount;
|
||||
const pendingAmount = amount - totalMatchedAmount;
|
||||
|
||||
return pendingAmount;
|
||||
return pendingAmount;
|
||||
}, [uncategorizedTransaction, perfectMatches, possibleMatches, values]);
|
||||
};
|
||||
|
||||
export const useAtleastOneMatchedSelected = () => {
|
||||
const { values } = useFormikContext<MatchingTransactionFormValues>();
|
||||
|
||||
return useMemo(() => {
|
||||
const matchedCount = Object.values(values.matched).filter(Boolean).length;
|
||||
return matchedCount > 0;
|
||||
}, [values]);
|
||||
};
|
||||
|
||||
export const useIsShowReconcileTransactionLink = () => {
|
||||
const pendingAmount = useGetPendingAmountMatched();
|
||||
const atleastOneSelected = useAtleastOneMatchedSelected();
|
||||
|
||||
return atleastOneSelected && pendingAmount !== 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user