mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
Compare commits
1 Commits
auth-pages
...
reconcile-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45ad4aa1f1 |
@@ -21,4 +21,5 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import { Button, Classes } from '@blueprintjs/core';
|
import { Button, Classes } from '@blueprintjs/core';
|
||||||
import { Box, Group } from '../Layout';
|
import clsx from 'classnames';
|
||||||
|
import { Box, BoxProps, Group } from '../Layout';
|
||||||
import { Icon } from '../Icon';
|
import { Icon } from '../Icon';
|
||||||
import styles from './Aside.module.scss';
|
import styles from './Aside.module.scss';
|
||||||
|
|
||||||
interface AsideProps {
|
interface AsideProps extends BoxProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
hideCloseButton?: boolean;
|
hideCloseButton?: boolean;
|
||||||
|
classNames?: Record<string, string>;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Aside({
|
export function Aside({
|
||||||
@@ -15,13 +18,15 @@ export function Aside({
|
|||||||
onClose,
|
onClose,
|
||||||
children,
|
children,
|
||||||
hideCloseButton,
|
hideCloseButton,
|
||||||
|
classNames,
|
||||||
|
className
|
||||||
}: AsideProps) {
|
}: AsideProps) {
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose && onClose();
|
onClose && onClose();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Box className={styles.root}>
|
<Box className={clsx(styles.root, className, classNames?.root)}>
|
||||||
<Group position="apart" className={styles.title}>
|
<Group position="apart" className={clsx(styles.title, classNames?.title)}>
|
||||||
{title}
|
{title}
|
||||||
|
|
||||||
{hideCloseButton !== true && (
|
{hideCloseButton !== true && (
|
||||||
@@ -34,7 +39,23 @@ export function Aside({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Box className={styles.content}>{children}</Box>
|
|
||||||
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AsideContentProps extends BoxProps {}
|
||||||
|
|
||||||
|
function AsideContent({ ...props }: AsideContentProps) {
|
||||||
|
return <Box {...props} className={clsx(styles.content, props?.className)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AsideFooterProps extends BoxProps {}
|
||||||
|
|
||||||
|
function AsideFooter({ ...props }: AsideFooterProps) {
|
||||||
|
return <Box {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
Aside.Body = AsideContent;
|
||||||
|
Aside.Footer = AsideFooter;
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ const ContentTabItemRoot = styled.button<ContentTabItemRootProps>`
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.small &&
|
||||||
|
`
|
||||||
|
padding: 8px 10px;
|
||||||
|
`}
|
||||||
|
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.active &&
|
props.active &&
|
||||||
`
|
`
|
||||||
@@ -55,6 +61,8 @@ interface ContentTabsItemProps {
|
|||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
className?: string;
|
||||||
|
small?: booean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentTabsItem = ({
|
const ContentTabsItem = ({
|
||||||
@@ -62,11 +70,18 @@ const ContentTabsItem = ({
|
|||||||
description,
|
description,
|
||||||
active,
|
active,
|
||||||
onClick,
|
onClick,
|
||||||
|
small,
|
||||||
|
className,
|
||||||
}: ContentTabsItemProps) => {
|
}: ContentTabsItemProps) => {
|
||||||
return (
|
return (
|
||||||
<ContentTabItemRoot active={active} onClick={onClick}>
|
<ContentTabItemRoot
|
||||||
|
active={active}
|
||||||
|
onClick={onClick}
|
||||||
|
className={className}
|
||||||
|
small={small}
|
||||||
|
>
|
||||||
<ContentTabTitle>{title}</ContentTabTitle>
|
<ContentTabTitle>{title}</ContentTabTitle>
|
||||||
<ContentTabDesc>{description}</ContentTabDesc>
|
{description && <ContentTabDesc>{description}</ContentTabDesc>}
|
||||||
</ContentTabItemRoot>
|
</ContentTabItemRoot>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -77,6 +92,7 @@ interface ContentTabsProps {
|
|||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
small?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ContentTabs({
|
export function ContentTabs({
|
||||||
@@ -85,6 +101,7 @@ export function ContentTabs({
|
|||||||
onChange,
|
onChange,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
|
small,
|
||||||
}: ContentTabsProps) {
|
}: ContentTabsProps) {
|
||||||
const [localValue, handleItemChange] = useUncontrolled<string>({
|
const [localValue, handleItemChange] = useUncontrolled<string>({
|
||||||
initialValue,
|
initialValue,
|
||||||
@@ -102,6 +119,7 @@ export function ContentTabs({
|
|||||||
{...tab.props}
|
{...tab.props}
|
||||||
active={localValue === tab.props.id}
|
active={localValue === tab.props.id}
|
||||||
onClick={() => handleItemChange(tab.props?.id)}
|
onClick={() => handleItemChange(tab.props?.id)}
|
||||||
|
small={small}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ContentTabsRoot>
|
</ContentTabsRoot>
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ function CategorizeTransactionAsideRoot({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Aside title={'Categorize Bank Transaction'} onClose={handleClose}>
|
<Aside title={'Categorize Bank Transaction'} onClose={handleClose}>
|
||||||
<CategorizeTransactionTabsBoot
|
<Aside.Body>
|
||||||
uncategorizedTransactionId={uncategorizedTransactionId}
|
<CategorizeTransactionTabsBoot
|
||||||
>
|
uncategorizedTransactionId={uncategorizedTransactionId}
|
||||||
<CategorizeTransactionTabs />
|
>
|
||||||
</CategorizeTransactionTabsBoot>
|
<CategorizeTransactionTabs />
|
||||||
|
</CategorizeTransactionTabsBoot>
|
||||||
|
</Aside.Body>
|
||||||
</Aside>
|
</Aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { useAccounts, useBranches } from '@/hooks/query';
|
||||||
|
import { Spinner } from '@blueprintjs/core';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface MatchingReconcileTransactionBootProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
interface MatchingReconcileTransactionBootValue {}
|
||||||
|
|
||||||
|
const MatchingReconcileTransactionBootContext =
|
||||||
|
React.createContext<MatchingReconcileTransactionBootValue>(
|
||||||
|
{} as MatchingReconcileTransactionBootValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
export function MatchingReconcileTransactionBoot({
|
||||||
|
children,
|
||||||
|
}: MatchingReconcileTransactionBootProps) {
|
||||||
|
const { data: accounts, isLoading: isAccountsLoading } = useAccounts({}, {});
|
||||||
|
const { data: branches, isLoading: isBranchesLoading } = useBranches({}, {});
|
||||||
|
|
||||||
|
const provider = {
|
||||||
|
accounts,
|
||||||
|
branches,
|
||||||
|
isAccountsLoading,
|
||||||
|
isBranchesLoading,
|
||||||
|
};
|
||||||
|
const isLoading = isAccountsLoading || isBranchesLoading;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spinner size={20} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MatchingReconcileTransactionBootContext.Provider value={provider}>
|
||||||
|
{children}
|
||||||
|
</MatchingReconcileTransactionBootContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMatchingReconcileTransactionBoot = () =>
|
||||||
|
React.useContext<MatchingReconcileTransactionBootValue>(
|
||||||
|
MatchingReconcileTransactionBootContext,
|
||||||
|
);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.content{
|
||||||
|
padding: 18px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 11px 20px;
|
||||||
|
border-top: 1px solid #ced4db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 1 0;
|
||||||
|
|
||||||
|
:global .bp4-form-group{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
:global .bp4-input {
|
||||||
|
line-height: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.asideContent{
|
||||||
|
background: #F6F7F9;
|
||||||
|
height: 335px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asideRoot {
|
||||||
|
flex: 1 1 0;
|
||||||
|
box-shadow: 0 0 0 1px rgba(17,20,24,.1),0 1px 1px rgba(17,20,24,.2),0 2px 6px rgba(17,20,24,.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.asideFooter {
|
||||||
|
background: #F6F7F9;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
export const MatchingReconcileFormSchema = Yup.object().shape({
|
||||||
|
type: Yup.string().required().label('Type'),
|
||||||
|
date: Yup.string().required().label('Date'),
|
||||||
|
amount: Yup.string().required().label('Amount'),
|
||||||
|
memo: Yup.string().required().label('Memo'),
|
||||||
|
referenceNo: Yup.string().label('Refernece #'),
|
||||||
|
category: Yup.string().required().label('Categogry'),
|
||||||
|
});
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { Button, Intent, Position, Tag } from '@blueprintjs/core';
|
||||||
|
import { Form, Formik, FormikValues, useFormikContext } from 'formik';
|
||||||
|
import {
|
||||||
|
AccountsSelect,
|
||||||
|
AppToaster,
|
||||||
|
Box,
|
||||||
|
BranchSelect,
|
||||||
|
FDateInput,
|
||||||
|
FFormGroup,
|
||||||
|
FInputGroup,
|
||||||
|
FMoneyInputGroup,
|
||||||
|
Group,
|
||||||
|
} from '@/components';
|
||||||
|
import { Aside } from '@/components/Aside/Aside';
|
||||||
|
import { momentFormatter } from '@/utils';
|
||||||
|
import styles from './MatchingReconcileTransactionForm.module.scss';
|
||||||
|
import { ContentTabs } from '@/components/ContentTabs';
|
||||||
|
import { withBankingActions } from '../../withBankingActions';
|
||||||
|
import {
|
||||||
|
MatchingReconcileTransactionBoot,
|
||||||
|
useMatchingReconcileTransactionBoot,
|
||||||
|
} from './MatchingReconcileTransactionBoot';
|
||||||
|
import { useCreateCashflowTransaction } from '@/hooks/query';
|
||||||
|
import { useAccountTransactionsContext } from '../../AccountTransactions/AccountTransactionsProvider';
|
||||||
|
import { MatchingReconcileFormSchema } from './MatchingReconcileTransactionForm.schema';
|
||||||
|
import { initialValues } from './_utils';
|
||||||
|
|
||||||
|
function MatchingReconcileTransactionFormRoot({
|
||||||
|
closeReconcileMatchingTransaction,
|
||||||
|
}) {
|
||||||
|
// Mutation create cashflow transaction.
|
||||||
|
const { mutateAsync: createCashflowTransactionMutate } =
|
||||||
|
useCreateCashflowTransaction();
|
||||||
|
|
||||||
|
const { accountId } = useAccountTransactionsContext();
|
||||||
|
|
||||||
|
const handleAsideClose = () => {
|
||||||
|
closeReconcileMatchingTransaction();
|
||||||
|
};
|
||||||
|
const handleSubmit = (
|
||||||
|
values: MatchingReconcileTransactionValues,
|
||||||
|
{ setSubmitting }: FormikValues<MatchingReconcileTransactionValues>,
|
||||||
|
) => {
|
||||||
|
setSubmitting(true);
|
||||||
|
const _values = transformToReq(values, accountId);
|
||||||
|
|
||||||
|
createCashflowTransactionMutate(_values)
|
||||||
|
.then(() => {
|
||||||
|
setSubmitting(false);
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'The transaction has been created.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
closeReconcileMatchingTransaction();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Aside
|
||||||
|
title={'Create Reconcile Transactions'}
|
||||||
|
className={styles.asideRoot}
|
||||||
|
onClose={handleAsideClose}
|
||||||
|
>
|
||||||
|
<MatchingReconcileTransactionBoot>
|
||||||
|
<Formik
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
initialValues={initialValues}
|
||||||
|
validationSchema={MatchingReconcileFormSchema}
|
||||||
|
>
|
||||||
|
<Form className={styles.form}>
|
||||||
|
<Aside.Body className={styles.asideContent}>
|
||||||
|
<CreateReconcileTransactionContent />
|
||||||
|
</Aside.Body>
|
||||||
|
|
||||||
|
<Aside.Footer className={styles.asideFooter}>
|
||||||
|
<MatchingReconcileTransactionFooter />
|
||||||
|
</Aside.Footer>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</MatchingReconcileTransactionBoot>
|
||||||
|
</Aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MatchingReconcileTransactionForm = R.compose(withBankingActions)(
|
||||||
|
MatchingReconcileTransactionFormRoot,
|
||||||
|
);
|
||||||
|
|
||||||
|
export function MatchingReconcileTransactionFooter() {
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={styles.footer}>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
fill
|
||||||
|
type={'submit'}
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
loading={isSubmitting}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReconcileMatchingType() {
|
||||||
|
const { setFieldValue, values } =
|
||||||
|
useFormikContext<MatchingReconcileFormValues>();
|
||||||
|
|
||||||
|
const handleChange = (value: string) => {
|
||||||
|
setFieldValue('type', value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ContentTabs
|
||||||
|
value={values?.type || 'deposit'}
|
||||||
|
onChange={handleChange}
|
||||||
|
small
|
||||||
|
>
|
||||||
|
<ContentTabs.Tab id={'deposit'} title={'Deposit'} />
|
||||||
|
<ContentTabs.Tab id={'withdrawal'} title={'Withdrawal'} />
|
||||||
|
</ContentTabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CreateReconcileTransactionContent() {
|
||||||
|
const { accounts, branches } = useMatchingReconcileTransactionBoot();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={styles.content}>
|
||||||
|
<ReconcileMatchingType />
|
||||||
|
|
||||||
|
<FFormGroup label={'Date'} name={'date'}>
|
||||||
|
<FDateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
name={'date'}
|
||||||
|
formatDate={(date) => date.toLocaleString()}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.LEFT,
|
||||||
|
}}
|
||||||
|
inputProps={{ fill: true }}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
label={'Amount'}
|
||||||
|
name={'amount'}
|
||||||
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
|
>
|
||||||
|
<FMoneyInputGroup name={'amount'} />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
label={'Category'}
|
||||||
|
name={'category'}
|
||||||
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
|
>
|
||||||
|
<AccountsSelect
|
||||||
|
name={'category'}
|
||||||
|
items={accounts}
|
||||||
|
popoverProps={{ minimal: false, position: Position.LEFT }}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
label={'Memo'}
|
||||||
|
name={'memo'}
|
||||||
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
|
>
|
||||||
|
<FInputGroup name={'memo'} />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup label={'Reference No.'} name={'reference_no'}>
|
||||||
|
<FInputGroup name={'reference_no'} />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup name={'branchId'} label={'Branch'}>
|
||||||
|
<BranchSelect
|
||||||
|
name={'branchId'}
|
||||||
|
branches={branches}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface MatchingReconcileTransactionValues {
|
||||||
|
type: string;
|
||||||
|
date: string;
|
||||||
|
amount: string;
|
||||||
|
memo: string;
|
||||||
|
referenceNo: string;
|
||||||
|
category: string;
|
||||||
|
branchId: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { MatchingReconcileTransactionValues } from './_types';
|
||||||
|
|
||||||
|
export const transformToReq = (
|
||||||
|
values: MatchingReconcileTransactionValues,
|
||||||
|
bankAccountId: number,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
date: values.date,
|
||||||
|
reference_no: values.referenceNo,
|
||||||
|
transaction_type:
|
||||||
|
values.type === 'deposit' ? 'other_income' : 'other_expense',
|
||||||
|
description: values.memo,
|
||||||
|
amount: values.amount,
|
||||||
|
credit_account_id: values.category,
|
||||||
|
cashflow_account_id: bankAccountId,
|
||||||
|
branch_id: values.branchId,
|
||||||
|
publish: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const initialValues = {
|
||||||
|
type: 'deposit',
|
||||||
|
date: '',
|
||||||
|
amount: '',
|
||||||
|
memo: '',
|
||||||
|
referenceNo: '',
|
||||||
|
category: '',
|
||||||
|
branchId: '',
|
||||||
|
};
|
||||||
@@ -25,6 +25,8 @@ import {
|
|||||||
withBankingActions,
|
withBankingActions,
|
||||||
} from '../withBankingActions';
|
} from '../withBankingActions';
|
||||||
import styles from './CategorizeTransactionAside.module.scss';
|
import styles from './CategorizeTransactionAside.module.scss';
|
||||||
|
import { MatchingReconcileTransactionForm } from './MatchingReconcileTransactionAside/MatchingReconcileTransactionForm';
|
||||||
|
import { withBanking } from '../withBanking';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
matched: {},
|
matched: {},
|
||||||
@@ -37,6 +39,9 @@ const initialValues = {
|
|||||||
function MatchingBankTransactionRoot({
|
function MatchingBankTransactionRoot({
|
||||||
// #withBankingActions
|
// #withBankingActions
|
||||||
closeMatchingTransactionAside,
|
closeMatchingTransactionAside,
|
||||||
|
|
||||||
|
// #withBanking
|
||||||
|
openReconcileMatchingTransaction,
|
||||||
}) {
|
}) {
|
||||||
const { uncategorizedTransactionId } = useCategorizeTransactionTabsBoot();
|
const { uncategorizedTransactionId } = useCategorizeTransactionTabsBoot();
|
||||||
const { mutateAsync: matchTransaction } = useMatchUncategorizedTransaction();
|
const { mutateAsync: matchTransaction } = useMatchUncategorizedTransaction();
|
||||||
@@ -81,16 +86,23 @@ function MatchingBankTransactionRoot({
|
|||||||
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
|
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
|
||||||
<>
|
<>
|
||||||
<MatchingBankTransactionContent />
|
<MatchingBankTransactionContent />
|
||||||
<MatchTransactionFooter />
|
|
||||||
|
{openReconcileMatchingTransaction && (
|
||||||
|
<MatchingReconcileTransactionForm />
|
||||||
|
)}
|
||||||
|
{!openReconcileMatchingTransaction && <MatchTransactionFooter />}
|
||||||
</>
|
</>
|
||||||
</Formik>
|
</Formik>
|
||||||
</MatchingTransactionBoot>
|
</MatchingTransactionBoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MatchingBankTransaction = R.compose(withBankingActions)(
|
export const MatchingBankTransaction = R.compose(
|
||||||
MatchingBankTransactionRoot,
|
withBankingActions,
|
||||||
);
|
withBanking(({ openReconcileMatchingTransaction }) => ({
|
||||||
|
openReconcileMatchingTransaction,
|
||||||
|
})),
|
||||||
|
)(MatchingBankTransactionRoot);
|
||||||
|
|
||||||
function MatchingBankTransactionContent() {
|
function MatchingBankTransactionContent() {
|
||||||
return (
|
return (
|
||||||
@@ -212,7 +224,10 @@ interface MatchTransctionFooterProps extends WithBankingActionsProps {}
|
|||||||
* @returns {React.ReactNode}
|
* @returns {React.ReactNode}
|
||||||
*/
|
*/
|
||||||
const MatchTransactionFooter = R.compose(withBankingActions)(
|
const MatchTransactionFooter = R.compose(withBankingActions)(
|
||||||
({ closeMatchingTransactionAside }: MatchTransctionFooterProps) => {
|
({
|
||||||
|
closeMatchingTransactionAside,
|
||||||
|
openReconcileMatchingTransaction,
|
||||||
|
}: MatchTransctionFooterProps) => {
|
||||||
const { submitForm, isSubmitting } = useFormikContext();
|
const { submitForm, isSubmitting } = useFormikContext();
|
||||||
const totalPending = useGetPendingAmountMatched();
|
const totalPending = useGetPendingAmountMatched();
|
||||||
const showReconcileLink = useIsShowReconcileTransactionLink();
|
const showReconcileLink = useIsShowReconcileTransactionLink();
|
||||||
@@ -224,13 +239,21 @@ const MatchTransactionFooter = R.compose(withBankingActions)(
|
|||||||
const handleSubmitBtnClick = () => {
|
const handleSubmitBtnClick = () => {
|
||||||
submitForm();
|
submitForm();
|
||||||
};
|
};
|
||||||
|
const handleReconcileTransaction = () => {
|
||||||
|
openReconcileMatchingTransaction();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={styles.footer}>
|
<Box className={styles.footer}>
|
||||||
<Box className={styles.footerTotal}>
|
<Box className={styles.footerTotal}>
|
||||||
<Group position={'apart'}>
|
<Group position={'apart'}>
|
||||||
{showReconcileLink && (
|
{showReconcileLink && (
|
||||||
<AnchorButton small minimal intent={Intent.PRIMARY}>
|
<AnchorButton
|
||||||
|
small
|
||||||
|
minimal
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
onClick={handleReconcileTransaction}
|
||||||
|
>
|
||||||
Add Reconcile Transaction +
|
Add Reconcile Transaction +
|
||||||
</AnchorButton>
|
</AnchorButton>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export const withBanking = (mapState) => {
|
|||||||
openMatchingTransactionAside: state.plaid.openMatchingTransactionAside,
|
openMatchingTransactionAside: state.plaid.openMatchingTransactionAside,
|
||||||
selectedUncategorizedTransactionId:
|
selectedUncategorizedTransactionId:
|
||||||
state.plaid.uncategorizedTransactionIdForMatching,
|
state.plaid.uncategorizedTransactionIdForMatching,
|
||||||
|
openReconcileMatchingTransaction:
|
||||||
|
state.plaid.openReconcileMatchingTransaction,
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { connect } from 'react-redux';
|
|||||||
import {
|
import {
|
||||||
closeMatchingTransactionAside,
|
closeMatchingTransactionAside,
|
||||||
setUncategorizedTransactionIdForMatching,
|
setUncategorizedTransactionIdForMatching,
|
||||||
|
openReconcileMatchingTransaction,
|
||||||
|
closeReconcileMatchingTransaction,
|
||||||
} from '@/store/banking/banking.reducer';
|
} from '@/store/banking/banking.reducer';
|
||||||
|
|
||||||
export interface WithBankingActionsProps {
|
export interface WithBankingActionsProps {
|
||||||
@@ -9,6 +11,8 @@ export interface WithBankingActionsProps {
|
|||||||
setUncategorizedTransactionIdForMatching: (
|
setUncategorizedTransactionIdForMatching: (
|
||||||
uncategorizedTransactionId: number,
|
uncategorizedTransactionId: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
openReconcileMatchingTransaction: () => void;
|
||||||
|
closeReconcileMatchingTransaction: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
||||||
@@ -20,6 +24,10 @@ const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
|||||||
dispatch(
|
dispatch(
|
||||||
setUncategorizedTransactionIdForMatching(uncategorizedTransactionId),
|
setUncategorizedTransactionIdForMatching(uncategorizedTransactionId),
|
||||||
),
|
),
|
||||||
|
openReconcileMatchingTransaction: () =>
|
||||||
|
dispatch(openReconcileMatchingTransaction()),
|
||||||
|
closeReconcileMatchingTransaction: () =>
|
||||||
|
dispatch(closeReconcileMatchingTransaction()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const withBankingActions = connect<
|
export const withBankingActions = connect<
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ interface StorePlaidState {
|
|||||||
plaidToken: string;
|
plaidToken: string;
|
||||||
openMatchingTransactionAside: boolean;
|
openMatchingTransactionAside: boolean;
|
||||||
uncategorizedTransactionIdForMatching: number | null;
|
uncategorizedTransactionIdForMatching: number | null;
|
||||||
|
openReconcileMatchingTransaction: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PlaidSlice = createSlice({
|
export const PlaidSlice = createSlice({
|
||||||
@@ -12,6 +13,7 @@ export const PlaidSlice = createSlice({
|
|||||||
plaidToken: '',
|
plaidToken: '',
|
||||||
openMatchingTransactionAside: false,
|
openMatchingTransactionAside: false,
|
||||||
uncategorizedTransactionIdForMatching: null,
|
uncategorizedTransactionIdForMatching: null,
|
||||||
|
openReconcileMatchingTransaction: false,
|
||||||
} as StorePlaidState,
|
} as StorePlaidState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setPlaidId: (state: StorePlaidState, action: PayloadAction<string>) => {
|
setPlaidId: (state: StorePlaidState, action: PayloadAction<string>) => {
|
||||||
@@ -34,6 +36,14 @@ export const PlaidSlice = createSlice({
|
|||||||
state.openMatchingTransactionAside = false;
|
state.openMatchingTransactionAside = false;
|
||||||
state.uncategorizedTransactionIdForMatching = null;
|
state.uncategorizedTransactionIdForMatching = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openReconcileMatchingTransaction: (state: StorePlaidState) => {
|
||||||
|
state.openReconcileMatchingTransaction = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
closeReconcileMatchingTransaction: (state: StorePlaidState) => {
|
||||||
|
state.openReconcileMatchingTransaction = false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,6 +52,8 @@ export const {
|
|||||||
resetPlaidId,
|
resetPlaidId,
|
||||||
setUncategorizedTransactionIdForMatching,
|
setUncategorizedTransactionIdForMatching,
|
||||||
closeMatchingTransactionAside,
|
closeMatchingTransactionAside,
|
||||||
|
openReconcileMatchingTransaction,
|
||||||
|
closeReconcileMatchingTransaction,
|
||||||
} = PlaidSlice.actions;
|
} = PlaidSlice.actions;
|
||||||
|
|
||||||
export const getPlaidToken = (state: any) => state.plaid.plaidToken;
|
export const getPlaidToken = (state: any) => state.plaid.plaidToken;
|
||||||
|
|||||||
Reference in New Issue
Block a user