mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
feat: hook up the matching form to the server
This commit is contained in:
10
packages/webapp/src/components/Aside/Aside.module.scss
Normal file
10
packages/webapp/src/components/Aside/Aside.module.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
.title{
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #E1E2E9;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
min-height: 40px;
|
||||
padding: 5px 5px 5px 15px;
|
||||
z-index: 0;
|
||||
}
|
||||
40
packages/webapp/src/components/Aside/Aside.tsx
Normal file
40
packages/webapp/src/components/Aside/Aside.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Button, Classes } from '@blueprintjs/core';
|
||||
import { Box, Group } from '../Layout';
|
||||
import { Icon } from '../Icon';
|
||||
import styles from './Aside.module.scss';
|
||||
|
||||
interface AsideProps {
|
||||
title?: string;
|
||||
onClose?: () => void;
|
||||
children?: React.ReactNode;
|
||||
hideCloseButton?: boolean;
|
||||
}
|
||||
|
||||
export function Aside({
|
||||
title,
|
||||
onClose,
|
||||
children,
|
||||
hideCloseButton,
|
||||
}: AsideProps) {
|
||||
const handleClose = () => {
|
||||
onClose && onClose();
|
||||
};
|
||||
return (
|
||||
<Box>
|
||||
<Group position="apart" className={styles.title}>
|
||||
{title}
|
||||
|
||||
{hideCloseButton !== true && (
|
||||
<Button
|
||||
aria-label="Close"
|
||||
className={Classes.DIALOG_CLOSE_BUTTON}
|
||||
icon={<Icon icon={'smallCross'} color={'#000'} />}
|
||||
minimal={true}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
<Box>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -33,7 +33,6 @@ function RulesTable({
|
||||
}) {
|
||||
// Invoices table columns.
|
||||
const columns = useBankRulesTableColumns();
|
||||
|
||||
const { bankRules } = useRulesListBoot();
|
||||
|
||||
// Handle edit bank rule.
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
TableSkeletonHeader,
|
||||
TableVirtualizedListRows,
|
||||
FormattedMessage as T,
|
||||
AppToaster,
|
||||
} from '@/components';
|
||||
import { TABLES } from '@/constants/tables';
|
||||
|
||||
@@ -24,6 +25,8 @@ import { useAccountUncategorizedTransactionsContext } from './AllTransactionsUnc
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
import { useExcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Account transactions data table.
|
||||
@@ -42,6 +45,9 @@ function AccountTransactionsDataTable({
|
||||
const { uncategorizedTransactions, isUncategorizedTransactionsLoading } =
|
||||
useAccountUncategorizedTransactionsContext();
|
||||
|
||||
const { mutateAsync: excludeTransaction } =
|
||||
useExcludeUncategorizedTransaction();
|
||||
|
||||
// Local storage memorizing columns widths.
|
||||
const [initialColumnsWidths, , handleColumnResizing] =
|
||||
useMemorizedColumnsWidths(TABLES.UNCATEGORIZED_CASHFLOW_TRANSACTION);
|
||||
@@ -52,6 +58,22 @@ function AccountTransactionsDataTable({
|
||||
uncategorizedTransactionId: cell.row.original.id,
|
||||
});
|
||||
};
|
||||
// Handle exclude transaction.
|
||||
const handleExcludeTransaction = (transaction) => {
|
||||
excludeTransaction(transaction.id)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
intent: Intent.SUCCESS,
|
||||
message: 'The bank transaction has been excluded successfully.',
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
AppToaster.show({
|
||||
intent: Intent.DANGER,
|
||||
message: 'Something went wrong.',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<CashflowTransactionsTable
|
||||
@@ -77,6 +99,9 @@ function AccountTransactionsDataTable({
|
||||
onColumnResizing={handleColumnResizing}
|
||||
noResults={<T id={'cash_flow.account_transactions.no_results'} />}
|
||||
className="table-constrant"
|
||||
payload={{
|
||||
onExclude: handleExcludeTransaction,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { AbilitySubject, CashflowAction } from '@/constants/abilityOption';
|
||||
import { safeCallback } from '@/utils';
|
||||
|
||||
export function ActionsMenu({
|
||||
payload: { onDelete, onViewDetails },
|
||||
payload: { onDelete, onViewDetails, onExclude },
|
||||
row: { original },
|
||||
}) {
|
||||
return (
|
||||
@@ -36,6 +36,12 @@ export function ActionsMenu({
|
||||
/>
|
||||
</If>
|
||||
</Can>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={'Exclude'}
|
||||
onClick={safeCallback(onExclude, original)}
|
||||
// icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,25 @@
|
||||
|
||||
|
||||
.transaction {
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #D6DBE3;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.asideHeader {
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #E1E2E9;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
min-height: 40px;
|
||||
padding: 5px 5px 5px 15px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.tabs :global .bp4-tab-panel{
|
||||
margin-top: 0;
|
||||
}
|
||||
.tabs :global .bp4-tab-list{
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #c7d5db;
|
||||
padding: 0 22px;
|
||||
}
|
||||
|
||||
.tabs :global .bp4-large > .bp4-tab{
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
.matchBar{
|
||||
padding: 16px 18px;
|
||||
padding: 12px 18px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #E1E2E9;
|
||||
border-top: 1px solid #E1E2E9;
|
||||
}
|
||||
|
||||
.matchBarTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.footerActions {
|
||||
padding: 14px 16px;
|
||||
border-top: 1px solid #E1E2E9;
|
||||
@@ -51,11 +29,3 @@
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #E1E2E9;
|
||||
}
|
||||
|
||||
.checkbox:global(.bp4-control.bp4-checkbox){
|
||||
margin: 0;
|
||||
}
|
||||
.checkbox:global(.bp4-control.bp4-checkbox) :global .bp4-control-indicator{
|
||||
border-color: #CBCBCB;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,145 +1,10 @@
|
||||
import { Box, Group, Icon, Stack } from '@/components';
|
||||
import {
|
||||
AnchorButton,
|
||||
Button,
|
||||
Checkbox,
|
||||
Classes,
|
||||
Intent,
|
||||
Tab,
|
||||
Tabs,
|
||||
Tag,
|
||||
Text,
|
||||
} from '@blueprintjs/core';
|
||||
import styles from './CategorizeTransactionAside.module.scss';
|
||||
|
||||
interface AsideProps {
|
||||
title?: string;
|
||||
onClose?: () => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
function Aside({ title, onClose, children }: AsideProps) {
|
||||
const handleClose = () => {
|
||||
onClose && onClose();
|
||||
};
|
||||
return (
|
||||
<Box>
|
||||
<Group position="apart" className={styles.asideHeader}>
|
||||
{title}
|
||||
<Button
|
||||
aria-label="Close"
|
||||
className={Classes.DIALOG_CLOSE_BUTTON}
|
||||
icon={<Icon icon={'smallCross'} color={'#000'} />}
|
||||
minimal={true}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
</Group>
|
||||
<Box>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
import { Aside } from '@/components/Aside/Aside';
|
||||
import { CategorizeTransactionTabs } from './CategorizeTransactionTabs';
|
||||
|
||||
export function CategorizeTransactionAside() {
|
||||
return (
|
||||
<Aside title={'Categorize Bank Transaction'}>
|
||||
<Tabs large className={styles.tabs}>
|
||||
<Tab
|
||||
id="categorize"
|
||||
title="Categorize Transaction"
|
||||
panel={<CategorizeBankTransactionContent />}
|
||||
/>
|
||||
<Tab
|
||||
id="matching"
|
||||
title="Matching Transaction"
|
||||
panel={<MatchingBankTransactionContent />}
|
||||
/>
|
||||
</Tabs>
|
||||
<CategorizeTransactionTabs />
|
||||
</Aside>
|
||||
);
|
||||
}
|
||||
|
||||
export function MatchingBankTransactionContent() {
|
||||
return (
|
||||
<Box>
|
||||
<Box className={styles.matchBar}>
|
||||
<Group spacing={6}>
|
||||
<h2 className={styles.matchBarTitle}>Perfect Matchines</h2>
|
||||
<Tag minimal intent={Intent.SUCCESS}>
|
||||
2
|
||||
</Tag>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Stack spacing={9} style={{ padding: 15 }}>
|
||||
<MatchTransaction label={''} date={''} />
|
||||
<MatchTransaction label={''} date={''} />
|
||||
<MatchTransaction label={''} date={''} />
|
||||
<MatchTransaction label={''} date={''} />
|
||||
</Stack>
|
||||
|
||||
<Box className={styles.matchBar}>
|
||||
<Stack spacing={2}>
|
||||
<h2 className={styles.matchBarTitle}>Perfect Matches</h2>
|
||||
<Text style={{ fontSize: 12, color: '#5C7080' }}>
|
||||
Transactions up to 20 Aug 2019
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Stack spacing={9} style={{ padding: 15 }}>
|
||||
<MatchTransaction label={''} date={''} />
|
||||
<MatchTransaction label={''} date={''} />
|
||||
<MatchTransaction label={''} date={''} />
|
||||
<MatchTransaction label={''} date={''} />
|
||||
</Stack>
|
||||
|
||||
<MatchTransactionFooter />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function CategorizeBankTransactionContent() {
|
||||
return <h1>Categorizing</h1>;
|
||||
}
|
||||
|
||||
interface MatchTransactionProps {
|
||||
label: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
function MatchTransaction({ label, date }: MatchTransactionProps) {
|
||||
return (
|
||||
<Group className={styles.transaction} position="apart">
|
||||
<Stack spacing={3}>
|
||||
<span>Expense for $10,000</span>
|
||||
<Text style={{ fontSize: 12, color: '#5C7080' }}>
|
||||
Date: 02/02/2020{' '}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Checkbox className={styles.checkbox} />
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
function MatchTransactionFooter() {
|
||||
return (
|
||||
<Box>
|
||||
<Box className={styles.footerTotal}>
|
||||
<Group>
|
||||
<AnchorButton small minimal intent={Intent.PRIMARY}>
|
||||
Add Reconcile Transaction +
|
||||
</AnchorButton>
|
||||
<Text>Pending $10,000</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Box className={styles.footerActions}>
|
||||
<Group>
|
||||
<Button intent={Intent.PRIMARY}>Match</Button>
|
||||
<Button>Cancel</Button>
|
||||
</Group>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
.tabs :global .bp4-tab-panel{
|
||||
margin-top: 0;
|
||||
}
|
||||
.tabs :global .bp4-tab-list{
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #c7d5db;
|
||||
padding: 0 22px;
|
||||
}
|
||||
|
||||
.tabs :global .bp4-large > .bp4-tab{
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Tab, Tabs } from '@blueprintjs/core';
|
||||
import {
|
||||
CategorizeBankTransactionContent,
|
||||
MatchingBankTransaction,
|
||||
} from './MatchingTransaction';
|
||||
import styles from './CategorizeTransactionTabs.module.scss';
|
||||
|
||||
export function CategorizeTransactionTabs() {
|
||||
return (
|
||||
<Tabs large className={styles.tabs}>
|
||||
<Tab
|
||||
id="categorize"
|
||||
title="Categorize Transaction"
|
||||
panel={<CategorizeBankTransactionContent />}
|
||||
/>
|
||||
<Tab
|
||||
id="matching"
|
||||
title="Matching Transaction"
|
||||
panel={<MatchingBankTransaction />}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
.root{
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #D6DBE3;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
|
||||
&.active{
|
||||
border-color: #88ABDB;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.checkbox:global(.bp4-control.bp4-checkbox){
|
||||
margin: 0;
|
||||
}
|
||||
.checkbox:global(.bp4-control.bp4-checkbox) :global .bp4-control-indicator{
|
||||
border-color: #CBCBCB;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// @ts-nocheck
|
||||
import clsx from 'classnames';
|
||||
import { Group, Stack } from '@/components';
|
||||
import { Checkbox, Text } from '@blueprintjs/core';
|
||||
import styles from './MatchTransaction.module.scss';
|
||||
import { useUncontrolled } from '@/hooks/useUncontrolled';
|
||||
|
||||
export interface MatchTransactionProps {
|
||||
active?: boolean;
|
||||
initialActive?: boolean;
|
||||
onChange?: (state: boolean) => void;
|
||||
label: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
export function MatchTransaction({
|
||||
active,
|
||||
initialActive,
|
||||
onChange,
|
||||
label,
|
||||
date,
|
||||
}: MatchTransactionProps) {
|
||||
const [_active, handleChange] = useUncontrolled<boolean>({
|
||||
value: active,
|
||||
initialValue: initialActive,
|
||||
finalValue: false,
|
||||
onChange,
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
handleChange(!_active);
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (event) => {
|
||||
handleChange(!event.target.checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<Group
|
||||
className={clsx(styles.root, {
|
||||
[styles.active]: _active,
|
||||
})}
|
||||
position="apart"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Stack spacing={3}>
|
||||
<span>{label}</span>
|
||||
<Text style={{ fontSize: 12, color: '#5C7080' }}>Date: {date}</Text>
|
||||
</Stack>
|
||||
|
||||
<Checkbox
|
||||
checked={_active as boolean}
|
||||
className={styles.checkbox}
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// @ts-nocheck
|
||||
import { isEmpty } from 'lodash';
|
||||
import { AnchorButton, Button, Intent, Tag, Text } from '@blueprintjs/core';
|
||||
import { AppToaster, Box, 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 { useMatchTransaction } from '@/hooks/query/bank-rules';
|
||||
import { MatchingTransactionFormValues } from './types';
|
||||
import { transformToReq } from './utils';
|
||||
|
||||
const initialValues = {
|
||||
matched: {},
|
||||
};
|
||||
|
||||
export function MatchingBankTransaction() {
|
||||
const uncategorizedTransactionId = 1;
|
||||
const { mutateAsync: matchTransaction } = useMatchTransaction();
|
||||
|
||||
// Handles the form submitting.
|
||||
const handleSubmit = (values: MatchingTransactionFormValues) => {
|
||||
const _values = transformToReq(values);
|
||||
|
||||
if (_values.matchedTransactions?.length === 0) {
|
||||
AppToaster.show({
|
||||
message: 'You should select at least one transaction for matching.',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
return;
|
||||
}
|
||||
matchTransaction([uncategorizedTransactionId, _values])
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
intent: Intent.SUCCESS,
|
||||
message: 'The bank transaction has been matched successfully.',
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
AppToaster.show({
|
||||
intent: Intent.DANGER,
|
||||
message: 'Something went wrong.',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<MatchingTransactionBoot>
|
||||
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
|
||||
<Form>
|
||||
<MatchingBankTransactionContent />
|
||||
</Form>
|
||||
</Formik>
|
||||
</MatchingTransactionBoot>
|
||||
);
|
||||
}
|
||||
|
||||
function MatchingBankTransactionContent() {
|
||||
return (
|
||||
<Box>
|
||||
<PerfectMatchingTransactions />
|
||||
<GoodMatchingTransactions />
|
||||
<MatchTransactionFooter />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the perfect match transactions.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
function PerfectMatchingTransactions() {
|
||||
const { matchingTransactions } = useMatchingTransactionBoot();
|
||||
|
||||
// Can't continue if the perfect matches is empty.
|
||||
if (isEmpty(matchingTransactions)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Box className={styles.matchBar}>
|
||||
<Group spacing={6}>
|
||||
<h2 className={styles.matchBarTitle}>Perfect Matchines</h2>
|
||||
<Tag minimal intent={Intent.SUCCESS}>
|
||||
2
|
||||
</Tag>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Stack spacing={9} style={{ padding: 15 }}>
|
||||
{matchingTransactions.map((match, index) => (
|
||||
<MatchTransactionField
|
||||
key={index}
|
||||
label={`${match.transsactionTypeFormatted} for ${match.amountFormatted}`}
|
||||
date={match.dateFormatted}
|
||||
transactionId={match.transactionId}
|
||||
transactionType={match.transactionType}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the possible match transactions.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
function GoodMatchingTransactions() {
|
||||
const { matchingTransactions } = useMatchingTransactionBoot();
|
||||
|
||||
// Can't continue if the possible matches is emoty.
|
||||
if (isEmpty(matchingTransactions)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Box className={styles.matchBar}>
|
||||
<Stack spacing={2}>
|
||||
<h2 className={styles.matchBarTitle}>Possible Matches</h2>
|
||||
<Text style={{ fontSize: 12, color: '#5C7080' }}>
|
||||
Transactions up to 20 Aug 2019
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Stack spacing={9} style={{ padding: 15 }}>
|
||||
{matchingTransactions.map((match, index) => (
|
||||
<MatchTransaction
|
||||
key={index}
|
||||
label={`${match.transsactionTypeFormatted} for ${match.amountFormatted}`}
|
||||
date={match.dateFormatted}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
interface MatchTransactionFieldProps
|
||||
extends Omit<MatchTransactionProps, 'onChange' | 'active' | 'initialActive'> {
|
||||
transactionId: number;
|
||||
transactionType: string;
|
||||
}
|
||||
|
||||
function MatchTransactionField({
|
||||
transactionId,
|
||||
transactionType,
|
||||
...props
|
||||
}: MatchTransactionFieldProps) {
|
||||
const name = `matched.${transactionType}-${transactionId}`;
|
||||
|
||||
return (
|
||||
<FastField name={name}>
|
||||
{({ form, field: { value } }: FastFieldProps) => (
|
||||
<MatchTransaction
|
||||
{...props}
|
||||
active={!!value}
|
||||
onChange={(state) => {
|
||||
form.setFieldValue(name, state);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</FastField>
|
||||
);
|
||||
}
|
||||
|
||||
export function CategorizeBankTransactionContent() {
|
||||
return <h1>Categorizing</h1>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the match transactions footer.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
function MatchTransactionFooter() {
|
||||
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 }}>Pending $10,000</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Box className={styles.footerActions}>
|
||||
<Group spacing={10}>
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
style={{ minWidth: 85 }}
|
||||
type="submit"
|
||||
>
|
||||
Match
|
||||
</Button>
|
||||
<Button>Cancel</Button>
|
||||
</Group>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useMatchingTransactions } from '@/hooks/query/bank-rules';
|
||||
import React, { createContext } from 'react';
|
||||
|
||||
interface MatchingTransactionBootValues {
|
||||
isMatchingTransactionsLoading: boolean;
|
||||
matchingTransactions: Array<any>;
|
||||
perfectMatchesCount: number;
|
||||
perfectMatches: Array<any>;
|
||||
matches: Array<any>;
|
||||
}
|
||||
|
||||
const RuleFormBootContext = createContext<MatchingTransactionBootValues>(
|
||||
{} as MatchingTransactionBootValues,
|
||||
);
|
||||
|
||||
interface RuleFormBootProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function MatchingTransactionBoot({ ...props }: RuleFormBootProps) {
|
||||
const {
|
||||
data: matchingTransactions,
|
||||
isLoading: isMatchingTransactionsLoading,
|
||||
} = useMatchingTransactions();
|
||||
|
||||
const provider = {
|
||||
isMatchingTransactionsLoading,
|
||||
matchingTransactions,
|
||||
perfectMatchesCount: 2,
|
||||
perfectMatches: [],
|
||||
matches: [],
|
||||
} as MatchingTransactionBootValues;
|
||||
|
||||
return <RuleFormBootContext.Provider value={provider} {...props} />;
|
||||
}
|
||||
|
||||
const useMatchingTransactionBoot = () =>
|
||||
React.useContext<MatchingTransactionBootValues>(RuleFormBootContext);
|
||||
|
||||
export { MatchingTransactionBoot, useMatchingTransactionBoot };
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface MatchingTransactionFormValues {
|
||||
matched: Record<string, boolean>;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MatchingTransactionFormValues } from './types';
|
||||
|
||||
export const transformToReq = (values: MatchingTransactionFormValues) => {
|
||||
const matchedTransactions = Object.entries(values.matched)
|
||||
.filter(([key, value]) => value)
|
||||
.map(([key]) => {
|
||||
const [reference_type, reference_id] = key.split('-');
|
||||
|
||||
return { reference_type, reference_id: parseInt(reference_id, 10) };
|
||||
});
|
||||
|
||||
return { matchedTransactions };
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||
import useApiRequest from '../useRequest';
|
||||
import { transformToCamelCase } from '@/utils';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -75,3 +76,72 @@ export function useBankRule(bankRuleId: number, props) {
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export function useMatchingTransactions(props?: any) {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useQuery(
|
||||
['MATCHING_TRANSACTION'],
|
||||
() =>
|
||||
apiRequest
|
||||
.get(`/banking/matches`)
|
||||
.then((res) => transformToCamelCase(res.data.data)),
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
export function useExcludeUncategorizedTransaction(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
(uncategorizedTransactionId: number) =>
|
||||
apiRequest.put(
|
||||
`/cashflow/transactions/${uncategorizedTransactionId}/exclude`,
|
||||
),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate queries.
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useUnexcludeUncategorizedTransaction(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
(uncategorizedTransactionId: number) =>
|
||||
apiRequest.post(
|
||||
`/cashflow/transactions/${uncategorizedTransactionId}/unexclude`,
|
||||
),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate queries.
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useMatchTransaction(props?: any) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
([uncategorizedTransactionId, values]) =>
|
||||
apiRequest.post(`/banking/matches/${uncategorizedTransactionId}`, values),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate queries.
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user