feat(webapp): categorize the cashflow uncategorized transactions

This commit is contained in:
Ahmed Bouhuolia
2024-03-04 13:41:15 +02:00
parent 0273714a07
commit 9db03350e0
12 changed files with 439 additions and 36 deletions

View File

@@ -1,13 +1,21 @@
// @ts-nocheck
import styled from 'styled-components';
import { ContentTabs } from '@/components/ContentTabs/ContentTabs';
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
const AccountContentTabs = styled(ContentTabs)`
margin: 15px 15px 0 15px;
`;
export function AccountTransactionsFilterTabs() {
const { filterTab, setFilterTab } = useAccountTransactionsContext();
const handleChange = (value) => {
setFilterTab(value);
};
return (
<AccountContentTabs value={'uncategorized'}>
<AccountContentTabs value={filterTab} onChange={handleChange}>
<ContentTabs.Tab
id={'dashboard'}
title={'Dashboard'}

View File

@@ -1,18 +1,20 @@
// @ts-nocheck
import React from 'react';
import React, { Suspense } from 'react';
import styled from 'styled-components';
import { Spinner } from '@blueprintjs/core';
import '@/style/pages/CashFlow/AccountTransactions/List.scss';
import { DashboardPageContent } from '@/components';
import AccountTransactionsActionsBar from './AccountTransactionsActionsBar';
import AccountTransactionsDataTable from './AccountTransactionsDataTable';
import { AccountTransactionsProvider } from './AccountTransactionsProvider';
import {
AccountTransactionsProvider,
useAccountTransactionsContext,
} from './AccountTransactionsProvider';
import { AccountTransactionsDetailsBar } from './AccountTransactionsDetailsBar';
import { AccountTransactionsProgressBar } from './components';
import { AccountTransactionsFilterTabs } from './AccountTransactionsFilterTabs';
import { AccountTransactionsUncategorizeFilter } from './AccountTransactionsUncategorizeFilter';
/**
* Account transactions list.
@@ -27,13 +29,9 @@ function AccountTransactionsList() {
<DashboardPageContent>
<AccountTransactionsFilterTabs />
<Box>
<AccountTransactionsUncategorizeFilter />
<CashflowTransactionsTableCard>
<AccountTransactionsDataTable />
</CashflowTransactionsTableCard>
</Box>
<Suspense fallback={<Spinner size={30} />}>
<AccountTransactionsContent />
</Suspense>
</DashboardPageContent>
</AccountTransactionsProvider>
);
@@ -41,14 +39,20 @@ function AccountTransactionsList() {
export default AccountTransactionsList;
const CashflowTransactionsTableCard = styled.div`
border: 2px solid #f0f0f0;
border-radius: 10px;
padding: 30px 18px;
background: #fff;
flex: 0 1;
`;
const AccountsTransactionsAll = React.lazy(
() => import('./AccountsTransactionsAll'),
);
const Box = styled.div`
margin: 30px 15px;
`;
const AccountsTransactionsUncategorized = React.lazy(
() => import('./AllTransactionsUncategorized'),
);
function AccountTransactionsContent() {
const { filterTab } = useAccountTransactionsContext();
return filterTab === 'uncategorized' ? (
<AccountsTransactionsUncategorized />
) : (
<AccountsTransactionsAll />
);
}

View File

@@ -7,7 +7,9 @@ import {
useAccountTransactionsInfinity,
useCashflowAccounts,
useAccount,
useAccountUncategorizedTransactionsInfinity,
} from '@/hooks/query';
import { useAppQueryString } from '@/hooks';
const AccountTransactionsContext = React.createContext();
@@ -15,6 +17,10 @@ function flattenInfinityPages(data) {
return flatten(map(data.pages, (page) => page.transactions));
}
function flattenInfinityPagesData(data) {
return flatten(map(data.pages, (page) => page.data));
}
/**
* Account transctions provider.
*/
@@ -22,6 +28,13 @@ function AccountTransactionsProvider({ query, ...props }) {
const { id } = useParams();
const accountId = parseInt(id, 10);
const [locationQuery, setLocationQuery] = useAppQueryString();
const filterTab = locationQuery?.filter || 'all';
const setFilterTab = (value: stirng) => {
setLocationQuery({ filter: value });
};
// Fetch cashflow account transactions list
const {
data: cashflowTransactionsPages,
@@ -31,10 +44,32 @@ function AccountTransactionsProvider({ query, ...props }) {
fetchNextPage: fetchNextTransactionsPage,
isFetchingNextPage,
hasNextPage,
} = useAccountTransactionsInfinity(accountId, {
page_size: 50,
account_id: accountId,
});
} = useAccountTransactionsInfinity(
accountId,
{
page_size: 50,
account_id: accountId,
},
{
enabled: filterTab === 'all' || filterTab === 'dashboard',
},
);
const {
data: uncategorizedTransactionsPage,
isFetching: isUncategorizedTransactionFetching,
isLoading: isUncategorizedTransactionsLoading,
isSuccess: isUncategorizedTransactionsSuccess,
fetchNextPage: fetchNextUncategorizedTransactionsPage,
} = useAccountUncategorizedTransactionsInfinity(
accountId,
{
page_size: 50,
},
{
enabled: filterTab === 'uncategorized',
},
);
// Memorized the cashflow account transactions.
const cashflowTransactions = React.useMemo(
@@ -45,6 +80,15 @@ function AccountTransactionsProvider({ query, ...props }) {
[cashflowTransactionsPages, isCashflowTransactionsSuccess],
);
// Memorized the cashflow account transactions.
const uncategorizedTransactions = React.useMemo(
() =>
isUncategorizedTransactionsSuccess
? flattenInfinityPagesData(uncategorizedTransactionsPage)
: [],
[uncategorizedTransactionsPage, isUncategorizedTransactionsSuccess],
);
// Fetch cashflow accounts.
const {
data: cashflowAccounts,
@@ -78,6 +122,12 @@ function AccountTransactionsProvider({ query, ...props }) {
isCashFlowAccountsLoading,
isCurrentAccountFetching,
isCurrentAccountLoading,
filterTab,
setFilterTab,
uncategorizedTransactions,
isUncategorizedTransactionFetching
};
return (

View File

@@ -0,0 +1,139 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import {
DataTable,
TableFastCell,
TableSkeletonRows,
TableSkeletonHeader,
TableVirtualizedListRows,
FormattedMessage as T,
} from '@/components';
import { TABLES } from '@/constants/tables';
import withSettings from '@/containers/Settings/withSettings';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { useMemorizedColumnsWidths } from '@/hooks';
import {
ActionsMenu,
useAccountUncategorizedTransactionsColumns,
} from './components';
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
import { handleCashFlowTransactionType } from './utils';
import { compose } from '@/utils';
/**
* Account transactions data table.
*/
function AccountTransactionsDataTable({
// #withSettings
cashflowTansactionsTableSize,
// #withAlertsActions
openAlert,
// #withDrawerActions
openDrawer,
}) {
// Retrieve table columns.
const columns = useAccountUncategorizedTransactionsColumns();
// Retrieve list context.
const { uncategorizedTransactions, isCashFlowTransactionsLoading } =
useAccountTransactionsContext();
// Local storage memorizing columns widths.
const [initialColumnsWidths, , handleColumnResizing] =
useMemorizedColumnsWidths(TABLES.CASHFLOW_Transactions);
// handle delete transaction
const handleDeleteTransaction = ({ reference_id }) => {};
const handleViewDetailCashflowTransaction = (referenceType) => {};
// Handle cell click.
const handleCellClick = (cell, event) => {};
return (
<CashflowTransactionsTable
noInitialFetch={true}
columns={columns}
data={uncategorizedTransactions || []}
sticky={true}
loading={isCashFlowTransactionsLoading}
headerLoading={isCashFlowTransactionsLoading}
expandColumnSpace={1}
expandToggleColumn={2}
selectionColumnWidth={45}
TableCellRenderer={TableFastCell}
TableLoadingRenderer={TableSkeletonRows}
TableRowsRenderer={TableVirtualizedListRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
onCellClick={handleCellClick}
// #TableVirtualizedListRows props.
vListrowHeight={cashflowTansactionsTableSize == 'small' ? 32 : 40}
vListOverscanRowCount={0}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
noResults={<T id={'cash_flow.account_transactions.no_results'} />}
className="table-constrant"
payload={{
onViewDetails: handleViewDetailCashflowTransaction,
onDelete: handleDeleteTransaction,
}}
/>
);
}
export default compose(
withSettings(({ cashflowTransactionsSettings }) => ({
cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize,
})),
withAlertsActions,
withDrawerActions,
)(AccountTransactionsDataTable);
const DashboardConstrantTable = styled(DataTable)`
.table {
.thead {
.th {
background: #fff;
}
}
.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,31 @@
// @ts-nocheck
import styled from 'styled-components';
import '@/style/pages/CashFlow/AccountTransactions/List.scss';
import AccountTransactionsDataTable from './AccountTransactionsDataTable';
import { AccountTransactionsUncategorizeFilter } from './AccountTransactionsUncategorizeFilter';
const Box = styled.div`
margin: 30px 15px;
`;
const CashflowTransactionsTableCard = styled.div`
border: 2px solid #f0f0f0;
border-radius: 10px;
padding: 30px 18px;
background: #fff;
flex: 0 1;
`;
export default function AccountTransactionsAll() {
return (
<Box>
<AccountTransactionsUncategorizeFilter />
<CashflowTransactionsTableCard>
<AccountTransactionsDataTable />
</CashflowTransactionsTableCard>
</Box>
);
}

View File

@@ -0,0 +1,29 @@
// @ts-nocheck
import styled from 'styled-components';
import '@/style/pages/CashFlow/AccountTransactions/List.scss';
import AccountTransactionsUncategorizedTable from './AccountTransactionsUncategorizedTable';
const Box = styled.div`
margin: 30px 15px;
`;
const CashflowTransactionsTableCard = styled.div`
border: 2px solid #f0f0f0;
border-radius: 10px;
padding: 30px 18px;
background: #fff;
flex: 0 1;
`
export default function AllTransactionsUncategorized() {
return (
<Box>
<CashflowTransactionsTableCard>
<AccountTransactionsUncategorizedTable />
</CashflowTransactionsTableCard>
</Box>
)
}

View File

@@ -131,7 +131,75 @@ export function useAccountTransactionsColumns() {
* Account transactions progress bar.
*/
export function AccountTransactionsProgressBar() {
const { isCashFlowTransactionsFetching } = useAccountTransactionsContext();
const { isCashFlowTransactionsFetching, isUncategorizedTransactionFetching } =
useAccountTransactionsContext();
return isCashFlowTransactionsFetching ? <MaterialProgressBar /> : null;
return isCashFlowTransactionsFetching ||
isUncategorizedTransactionFetching ? (
<MaterialProgressBar />
) : null;
}
/**
* Retrieve account uncategorized transctions table columns.
*/
export function useAccountUncategorizedTransactionsColumns() {
return React.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: intl.get('reference_no'),
accessor: 'reference_number',
width: 50,
className: 'reference_number',
clickable: true,
textOverview: true,
},
{
id: 'deposit',
Header: intl.get('cash_flow.label.deposit'),
accessor: 'formattet_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

@@ -110,12 +110,12 @@ function CashFlowAccountsActionsBar({
</NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}>
{/* <Button
<Button
className={Classes.MINIMAL}
text={'Connect to Bank / Credit Card'}
onClick={handleConnectToBank}
/>
<NavbarDivider /> */}
<NavbarDivider />
<Button
className={Classes.MINIMAL}
icon={<Icon icon="refresh-16" iconSize={14} />}

View File

@@ -0,0 +1,33 @@
// @ts-nocheck
import React, { lazy } from 'react';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
import { compose } from '@/utils';
const AccountDrawerContent = lazy(() => import('./AccountDrawerContent'));
/**
* Categorize the uncategorized transaction drawer.
*/
function CategorizeTransactionDrawer({
name,
// #withDrawer
isOpen,
payload: { uncategorizedTranasctionId },
}) {
return (
<Drawer
isOpen={isOpen}
name={name}
style={{ minWidth: '700px', maxWidth: '900px' }}
size={'65%'}
>
<DrawerSuspense>
<AccountDrawerContent name={name} accountId={accountId} />
</DrawerSuspense>
</Drawer>
);
}
export default compose(withDrawers())(AccountDrawer);