mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d94d28f709 | ||
|
|
94e6b64944 | ||
|
|
d8e9be0246 | ||
|
|
7ef72e8955 | ||
|
|
d76cc3d2a2 | ||
|
|
3102329ac0 | ||
|
|
fd09ea12ff | ||
|
|
7bd09e7326 | ||
|
|
cd3105b320 | ||
|
|
91b848f158 | ||
|
|
352e517c2b | ||
|
|
24bd754c72 | ||
|
|
613454a862 | ||
|
|
33c0c7173a | ||
|
|
a0fc25a250 | ||
|
|
6c663eb8a0 | ||
|
|
9211e963c6 | ||
|
|
ea466404ec | ||
|
|
cbce9f6d50 | ||
|
|
60f45f281a | ||
|
|
b4e1fa4aca | ||
|
|
93f778ebcc | ||
|
|
2d9aaac653 | ||
|
|
b6fc06ea0c | ||
|
|
0ae31d519c | ||
|
|
e9964f1ac9 | ||
|
|
fb14858f16 | ||
|
|
8c5552edd8 | ||
|
|
4b96ba76f5 | ||
|
|
0b5c5d83a4 | ||
|
|
b0f1584b04 | ||
|
|
f378275673 | ||
|
|
f1fec69d52 | ||
|
|
c462681c70 | ||
|
|
a71ae1813b | ||
|
|
2fd78ca1c4 | ||
|
|
0a21c5fa41 | ||
|
|
f99b01de3b | ||
|
|
8f5d44c648 | ||
|
|
3c49e8f57a | ||
|
|
e94a386fe8 | ||
|
|
9ecc7f58e7 | ||
|
|
26080889df | ||
|
|
2cd07066a8 | ||
|
|
7dfa280bee | ||
|
|
68f8140007 | ||
|
|
c5783896ad | ||
|
|
fc67d56d45 | ||
|
|
7bad9fc52c | ||
|
|
65e8d3f26a | ||
|
|
e29db07c32 | ||
|
|
75acab3348 | ||
|
|
1fa03822f1 | ||
|
|
c7013caf12 | ||
|
|
0bb1e57061 | ||
|
|
de05667bdc | ||
|
|
c148e2976a | ||
|
|
2078b6bc99 | ||
|
|
b848553cf7 | ||
|
|
4ba750fe11 | ||
|
|
369734ab18 | ||
|
|
862a667ef6 | ||
|
|
2c86e7d8b3 | ||
|
|
467abf2d55 | ||
|
|
7e2e25a8b4 | ||
|
|
6ce0242386 | ||
|
|
5b23d88796 |
11
Dockerfile
11
Dockerfile
@@ -6,15 +6,6 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY ./package.json /app/package.json
|
COPY ./package.json /app/package.json
|
||||||
COPY ./package-lock.json /app/package-lock.json
|
COPY ./package-lock.json /app/package-lock.json
|
||||||
COPY ./.npmrc /app/.npmrc
|
|
||||||
|
|
||||||
ARG GITHUB_USERNAME
|
|
||||||
ARG GITHUB_PASS
|
|
||||||
ARG GITHUB_EMAIL
|
|
||||||
|
|
||||||
RUN npm install -g npm-cli-login
|
|
||||||
|
|
||||||
RUN npm-cli-login -s @bigcapitalhq -r https://npm.pkg.github.com -u $GITHUB_USERNAME -p $GITHUB_PASS -e $GITHUB_EMAIL
|
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
@@ -26,4 +17,4 @@ FROM nginx
|
|||||||
|
|
||||||
COPY ./nginx/sites/default.conf /etc/nginx/conf.d/default.conf
|
COPY ./nginx/sites/default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
COPY --from=build /app/build /usr/share/nginx/html
|
COPY --from=build /app/build /usr/share/nginx/html
|
||||||
17044
package-lock.json
generated
17044
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,8 @@
|
|||||||
"@blueprintjs/table": "^3.8.3",
|
"@blueprintjs/table": "^3.8.3",
|
||||||
"@blueprintjs/timezone": "^3.6.2",
|
"@blueprintjs/timezone": "^3.6.2",
|
||||||
"@reduxjs/toolkit": "^1.2.5",
|
"@reduxjs/toolkit": "^1.2.5",
|
||||||
|
"@sentry/react": "^6.13.2",
|
||||||
|
"@sentry/tracing": "^6.13.2",
|
||||||
"@svgr/webpack": "4.3.3",
|
"@svgr/webpack": "4.3.3",
|
||||||
"@tanem/react-nprogress": "^3.0.24",
|
"@tanem/react-nprogress": "^3.0.24",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
@@ -101,6 +103,7 @@
|
|||||||
"sass-loader": "8.0.2",
|
"sass-loader": "8.0.2",
|
||||||
"semver": "6.3.0",
|
"semver": "6.3.0",
|
||||||
"style-loader": "0.23.1",
|
"style-loader": "0.23.1",
|
||||||
|
"styled-components": "^5.3.1",
|
||||||
"terser-webpack-plugin": "2.3.4",
|
"terser-webpack-plugin": "2.3.4",
|
||||||
"ts-pnp": "1.1.5",
|
"ts-pnp": "1.1.5",
|
||||||
"url-loader": "2.3.0",
|
"url-loader": "2.3.0",
|
||||||
|
|||||||
40
src/common/cashflowOptions.js
Normal file
40
src/common/cashflowOptions.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
export const addMoneyIn = [
|
||||||
|
{
|
||||||
|
name: intl.get('cash_flow.owner_contribution'),
|
||||||
|
value: 'owner_contribution',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: intl.get('cash_flow.other_income'),
|
||||||
|
value: 'other_income',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: intl.get('cash_flow.transfer_form_account'),
|
||||||
|
value: 'transfer_from_account',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const addMoneyOut = [
|
||||||
|
{
|
||||||
|
name: intl.get('cash_flow.owner_drawings'),
|
||||||
|
value: 'OwnerDrawing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: intl.get('cash_flow.expenses'),
|
||||||
|
value: 'other_expense',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: intl.get('cash_flow.transfer_to_account'),
|
||||||
|
value: 'transfer_to_account',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const TRANSACRIONS_TYPE = [
|
||||||
|
'OwnerContribution',
|
||||||
|
'OtherIncome',
|
||||||
|
'TransferFromAccount',
|
||||||
|
'OnwersDrawing',
|
||||||
|
'OtherExpense',
|
||||||
|
'TransferToAccount',
|
||||||
|
];
|
||||||
@@ -9,4 +9,5 @@ export const DRAWERS = {
|
|||||||
EXPENSE_DRAWER: 'expense-drawer',
|
EXPENSE_DRAWER: 'expense-drawer',
|
||||||
BILL_DRAWER: 'bill-drawer',
|
BILL_DRAWER: 'bill-drawer',
|
||||||
INVENTORY_ADJUSTMENT_DRAWER: 'inventory-adjustment-drawer',
|
INVENTORY_ADJUSTMENT_DRAWER: 'inventory-adjustment-drawer',
|
||||||
|
CASHFLOW_TRNASACTION_DRAWER: 'cashflow-transaction-drawer',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ export default [
|
|||||||
shortcut_key: 'Shift + W',
|
shortcut_key: 'Shift + W',
|
||||||
description: intl.get('jump_to_the_items'),
|
description: intl.get('jump_to_the_items'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
shortcut_key: 'Shift + D',
|
||||||
|
description: intl.get('jump_to_the_add_money_in'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shortcut_key: 'Shift + Q',
|
||||||
|
description: intl.get('jump_to_the_add_money_out'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
shortcut_key: 'Shift + 1',
|
shortcut_key: 'Shift + 1',
|
||||||
description: intl.get('jump_to_the_balance_sheet'),
|
description: intl.get('jump_to_the_balance_sheet'),
|
||||||
|
|||||||
12
src/common/moreVertOptions.js
Normal file
12
src/common/moreVertOptions.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
export const moreVertOptions = [
|
||||||
|
{
|
||||||
|
name: intl.get('bad_debt.dialog.bad_debt'),
|
||||||
|
value: 'bad debt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: intl.get('bad_debt.dialog.cancel_bad_debt'),
|
||||||
|
value: 'cancel bad debt',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -12,10 +12,12 @@ export const TABLES = {
|
|||||||
ACCOUNTS: 'accounts',
|
ACCOUNTS: 'accounts',
|
||||||
MANUAL_JOURNALS: 'manual_journal',
|
MANUAL_JOURNALS: 'manual_journal',
|
||||||
EXPENSES: 'expenses',
|
EXPENSES: 'expenses',
|
||||||
|
CASHFLOW_ACCOUNTS: 'cashflow_accounts',
|
||||||
|
CASHFLOW_Transactions: 'cashflow_transactions',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TABLE_SIZE = {
|
export const TABLE_SIZE = {
|
||||||
COMPACT: 'compact',
|
COMPACT: 'compact',
|
||||||
SMALL: 'small',
|
SMALL: 'small',
|
||||||
MEDIUM: 'medium',
|
MEDIUM: 'medium',
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import GlobalErrors from 'containers/GlobalErrors/GlobalErrors';
|
|||||||
import DashboardPrivatePages from 'components/Dashboard/PrivatePages';
|
import DashboardPrivatePages from 'components/Dashboard/PrivatePages';
|
||||||
import Authentication from 'components/Authentication';
|
import Authentication from 'components/Authentication';
|
||||||
|
|
||||||
import { SplashScreen } from '../components';
|
import { SplashScreen, DashboardThemeProvider } from '../components';
|
||||||
import { queryConfig } from '../hooks/query/base';
|
import { queryConfig } from '../hooks/query/base';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,16 +23,18 @@ import { queryConfig } from '../hooks/query/base';
|
|||||||
function AppInsider({ history }) {
|
function AppInsider({ history }) {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Router history={history}>
|
<DashboardThemeProvider>
|
||||||
<Switch>
|
<Router history={history}>
|
||||||
<Route path={'/auth'} component={Authentication} />
|
<Switch>
|
||||||
<Route path={'/'}>
|
<Route path={'/auth'} component={Authentication} />
|
||||||
<PrivateRoute component={DashboardPrivatePages} />
|
<Route path={'/'}>
|
||||||
</Route>
|
<PrivateRoute component={DashboardPrivatePages} />
|
||||||
</Switch>
|
</Route>
|
||||||
</Router>
|
</Switch>
|
||||||
|
</Router>
|
||||||
|
|
||||||
<GlobalErrors />
|
<GlobalErrors />
|
||||||
|
</DashboardThemeProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as R from 'ramda';
|
|||||||
import { AppIntlProvider } from './AppIntlProvider';
|
import { AppIntlProvider } from './AppIntlProvider';
|
||||||
import { useSplashLoading } from '../hooks/state';
|
import { useSplashLoading } from '../hooks/state';
|
||||||
|
|
||||||
import { useWatch } from '../hooks';
|
import { useWatchImmediate } from '../hooks';
|
||||||
import withDashboardActions from '../containers/Dashboard/withDashboardActions';
|
import withDashboardActions from '../containers/Dashboard/withDashboardActions';
|
||||||
|
|
||||||
const SUPPORTED_LOCALES = [
|
const SUPPORTED_LOCALES = [
|
||||||
@@ -90,10 +90,10 @@ function useAppLoadLocales(currentLocale) {
|
|||||||
}, [currentLocale, stopLoading]);
|
}, [currentLocale, stopLoading]);
|
||||||
|
|
||||||
// Watches the value to start/stop splash screen.
|
// Watches the value to start/stop splash screen.
|
||||||
useWatch(isLoading, (value) => (value ? startLoading() : stopLoading()), {
|
useWatchImmediate(
|
||||||
immediate: true,
|
(value) => (value ? startLoading() : stopLoading()),
|
||||||
});
|
isLoading,
|
||||||
|
);
|
||||||
return { isLoading };
|
return { isLoading };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,10 +116,10 @@ function useAppYupLoadLocales(currentLocale) {
|
|||||||
}, [currentLocale, stopLoading]);
|
}, [currentLocale, stopLoading]);
|
||||||
|
|
||||||
// Watches the valiue to start/stop splash screen.
|
// Watches the valiue to start/stop splash screen.
|
||||||
useWatch(isLoading, (value) => (value ? startLoading() : stopLoading()), {
|
useWatchImmediate(
|
||||||
immediate: true,
|
(value) => (value ? startLoading() : stopLoading()),
|
||||||
});
|
isLoading,
|
||||||
|
);
|
||||||
return { isLoading };
|
return { isLoading };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ function AppIntlLoader({ children }) {
|
|||||||
const { isLoading: isAppLocalesLoading } = useAppLoadLocales(currentLocale);
|
const { isLoading: isAppLocalesLoading } = useAppLoadLocales(currentLocale);
|
||||||
|
|
||||||
// Detarmines whether the app locales loading.
|
// Detarmines whether the app locales loading.
|
||||||
const isLoading = isAppYupLocalesLoading && isAppLocalesLoading;
|
const isLoading = isAppYupLocalesLoading || isAppLocalesLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppIntlProvider currentLocale={currentLocale} isRTL={isRTL}>
|
<AppIntlProvider currentLocale={currentLocale} isRTL={isRTL}>
|
||||||
|
|||||||
210
src/components/BankAccounts/index.js
Normal file
210
src/components/BankAccounts/index.js
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
import clsx from 'classnames';
|
||||||
|
import Icon from '../Icon';
|
||||||
|
import { whenRtl, whenLtr } from 'utils/styled-components';
|
||||||
|
|
||||||
|
const ACCOUNT_TYPE = {
|
||||||
|
CASH: 'cash',
|
||||||
|
BANK: 'bank',
|
||||||
|
CREDIT_CARD: 'credit-card',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ACCOUNT_TYPE_PAIR_ICON = {
|
||||||
|
[ACCOUNT_TYPE.CASH]: 'payments',
|
||||||
|
[ACCOUNT_TYPE.CREDIT_CARD]: 'credit-card',
|
||||||
|
[ACCOUNT_TYPE.BANK]: 'account-balance',
|
||||||
|
};
|
||||||
|
|
||||||
|
function BankAccountMetaLine({ title, value, className }) {
|
||||||
|
return (
|
||||||
|
<MetaLineWrap className={className}>
|
||||||
|
<MetaLineTitle>{title}</MetaLineTitle>
|
||||||
|
{value && <MetaLineValue>{value}</MetaLineValue>}
|
||||||
|
</MetaLineWrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BankAccountBalance({ amount, loading }) {
|
||||||
|
return (
|
||||||
|
<BankAccountBalanceWrap>
|
||||||
|
<BankAccountBalanceAmount
|
||||||
|
className={clsx({
|
||||||
|
[Classes.SKELETON]: loading,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{amount}
|
||||||
|
</BankAccountBalanceAmount>
|
||||||
|
<BankAccountBalanceLabel>{intl.get('balance')}</BankAccountBalanceLabel>
|
||||||
|
</BankAccountBalanceWrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BankAccountTypeIcon({ type }) {
|
||||||
|
const icon = ACCOUNT_TYPE_PAIR_ICON[type];
|
||||||
|
|
||||||
|
if (!icon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AccountIconWrap>
|
||||||
|
<Icon icon={icon} iconSize={18} />
|
||||||
|
</AccountIconWrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BankAccount({
|
||||||
|
title,
|
||||||
|
code,
|
||||||
|
type,
|
||||||
|
balance,
|
||||||
|
loading = false,
|
||||||
|
updatedBeforeText,
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<BankAccountWrap {...restProps}>
|
||||||
|
<BankAccountHeader>
|
||||||
|
<BankAccountTitle className={clsx({ [Classes.SKELETON]: loading })}>
|
||||||
|
{title}
|
||||||
|
</BankAccountTitle>
|
||||||
|
<BnakAccountCode className={clsx({ [Classes.SKELETON]: loading })}>
|
||||||
|
{code}
|
||||||
|
</BnakAccountCode>
|
||||||
|
{!loading && <BankAccountTypeIcon type={type} />}
|
||||||
|
</BankAccountHeader>
|
||||||
|
|
||||||
|
<BankAccountMeta>
|
||||||
|
<BankAccountMetaLine
|
||||||
|
title={intl.get('cash_flow.label_account_transcations')}
|
||||||
|
value={2}
|
||||||
|
className={clsx({ [Classes.SKELETON]: loading })}
|
||||||
|
/>
|
||||||
|
<BankAccountMetaLine
|
||||||
|
title={updatedBeforeText}
|
||||||
|
className={clsx({ [Classes.SKELETON]: loading })}
|
||||||
|
/>
|
||||||
|
</BankAccountMeta>
|
||||||
|
|
||||||
|
<BankAccountBalance amount={balance} loading={loading} />
|
||||||
|
</BankAccountWrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const BankAccountWrap = styled.div`
|
||||||
|
width: 225px;
|
||||||
|
height: 180px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #fff;
|
||||||
|
margin: 8px;
|
||||||
|
border: 1px solid #c8cad0;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #0153cc;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BankAccountHeader = styled.div`
|
||||||
|
padding: 10px 12px;
|
||||||
|
padding-top: 16px;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BankAccountTitle = styled.div`
|
||||||
|
font-size: 15px;
|
||||||
|
font-style: inherit;
|
||||||
|
letter-spacing: -0.003em;
|
||||||
|
color: rgb(23, 43, 77);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin: 0px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BnakAccountCode = styled.div`
|
||||||
|
font-size: 11px;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: rgb(23, 43, 77);
|
||||||
|
display: inline-block;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BankAccountBalanceWrap = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: auto;
|
||||||
|
border-top: 1px solid #dfdfdf;
|
||||||
|
padding: 10px 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BankAccountBalanceAmount = styled.div`
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
color: #57657e;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BankAccountBalanceLabel = styled.div`
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-top: 3px;
|
||||||
|
opacity: 0.6;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MetaLineWrap = styled.div`
|
||||||
|
font-size: 11px;
|
||||||
|
display: flex;
|
||||||
|
color: #2f3c58;
|
||||||
|
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const MetaLineTitle = styled.div``;
|
||||||
|
|
||||||
|
const MetaLineValue = styled.div`
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-style: inherit;
|
||||||
|
background: rgb(223, 225, 230);
|
||||||
|
line-height: initial;
|
||||||
|
align-content: center;
|
||||||
|
padding: 0px 2px;
|
||||||
|
border-radius: 9.6px;
|
||||||
|
font-weight: normal;
|
||||||
|
text-transform: none;
|
||||||
|
width: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: rgb(23, 43, 77);
|
||||||
|
font-size: 11px;
|
||||||
|
|
||||||
|
${whenLtr(`margin-left: auto;`)}
|
||||||
|
${whenRtl(`margin-right: auto;`)}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BankAccountMeta = styled.div`
|
||||||
|
padding: 0 12px 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const BankAccountsList = styled.div`
|
||||||
|
display: flex;
|
||||||
|
margin: -8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AccountIconWrap = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
|
color: #abb3bb;
|
||||||
|
|
||||||
|
${whenLtr(`right: 12px;`)}
|
||||||
|
${whenRtl(`left: 12px;`)}
|
||||||
|
`;
|
||||||
23
src/components/Button/ButtonLink.js
Normal file
23
src/components/Button/ButtonLink.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import { Button } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
export const ButtonLink = styled(Button)`
|
||||||
|
line-height: inherit;
|
||||||
|
|
||||||
|
&.bp3-small {
|
||||||
|
min-height: auto;
|
||||||
|
min-width: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
&:not([class*='bp3-intent-']) {
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
color: #0052cc;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
3
src/components/Button/index.js
Normal file
3
src/components/Button/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
export * from './ButtonLink';
|
||||||
@@ -12,6 +12,7 @@ import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane';
|
|||||||
import GlobalHotkeys from './GlobalHotkeys';
|
import GlobalHotkeys from './GlobalHotkeys';
|
||||||
import DashboardProvider from './DashboardProvider';
|
import DashboardProvider from './DashboardProvider';
|
||||||
import DrawersContainer from 'components/DrawersContainer';
|
import DrawersContainer from 'components/DrawersContainer';
|
||||||
|
import AlertsContainer from 'containers/AlertsContainer';
|
||||||
import EnsureSubscriptionIsActive from '../Guards/EnsureSubscriptionIsActive';
|
import EnsureSubscriptionIsActive from '../Guards/EnsureSubscriptionIsActive';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,6 +56,7 @@ export default function Dashboard() {
|
|||||||
<DialogsContainer />
|
<DialogsContainer />
|
||||||
<GlobalHotkeys />
|
<GlobalHotkeys />
|
||||||
<DrawersContainer />
|
<DrawersContainer />
|
||||||
|
<AlertsContainer />
|
||||||
</DashboardProvider>
|
</DashboardProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as R from 'ramda';
|
|||||||
|
|
||||||
import { useUser, useCurrentOrganization } from '../../hooks/query';
|
import { useUser, useCurrentOrganization } from '../../hooks/query';
|
||||||
import { useSplashLoading } from '../../hooks/state';
|
import { useSplashLoading } from '../../hooks/state';
|
||||||
import { useWatch, useWhen } from '../../hooks';
|
import { useWatch, useWatchImmediate, useWhen } from '../../hooks';
|
||||||
|
|
||||||
import withAuthentication from '../../containers/Authentication/withAuthentication';
|
import withAuthentication from '../../containers/Authentication/withAuthentication';
|
||||||
|
|
||||||
@@ -21,10 +21,8 @@ function DashboardBootJSX({ authenticatedUserId }) {
|
|||||||
} = useCurrentOrganization();
|
} = useCurrentOrganization();
|
||||||
|
|
||||||
// Authenticated user.
|
// Authenticated user.
|
||||||
const {
|
const { isSuccess: isAuthUserSuccess, isLoading: isAuthUserLoading } =
|
||||||
isSuccess: isAuthUserSuccess,
|
useUser(authenticatedUserId);
|
||||||
isLoading: isAuthUserLoading,
|
|
||||||
} = useUser(authenticatedUserId);
|
|
||||||
|
|
||||||
// Initial locale cookie value.
|
// Initial locale cookie value.
|
||||||
const localeCookie = getCookie('locale');
|
const localeCookie = getCookie('locale');
|
||||||
@@ -59,25 +57,25 @@ function DashboardBootJSX({ authenticatedUserId }) {
|
|||||||
|
|
||||||
// Splash loading when organization request loading and
|
// Splash loading when organization request loading and
|
||||||
// applicaiton still not booted.
|
// applicaiton still not booted.
|
||||||
useWatch(isOrgLoading, (value) => {
|
useWatchImmediate((value) => {
|
||||||
value && !isBooted.current && startLoading();
|
value && !isBooted.current && startLoading();
|
||||||
});
|
}, isOrgLoading);
|
||||||
|
|
||||||
// Splash loading when request authenticated user loading and
|
// Splash loading when request authenticated user loading and
|
||||||
// application still not booted yet.
|
// application still not booted yet.
|
||||||
useWatch(isAuthUserLoading, (value) => {
|
useWatchImmediate((value) => {
|
||||||
value && !isBooted.current && startLoading();
|
value && !isBooted.current && startLoading();
|
||||||
});
|
}, isAuthUserLoading);
|
||||||
|
|
||||||
// Stop splash loading once organization request success.
|
// Stop splash loading once organization request success.
|
||||||
useWatch(isCurrentOrganizationSuccess, (value) => {
|
useWatch((value) => {
|
||||||
value && stopLoading();
|
value && stopLoading();
|
||||||
});
|
}, isCurrentOrganizationSuccess);
|
||||||
|
|
||||||
// Stop splash loading once authenticated user request success.
|
// Stop splash loading once authenticated user request success.
|
||||||
useWatch(isAuthUserSuccess, (value) => {
|
useWatch((value) => {
|
||||||
value && stopLoading();
|
value && stopLoading();
|
||||||
});
|
}, isAuthUserSuccess);
|
||||||
|
|
||||||
// Once the all requests complete change the app loading state.
|
// Once the all requests complete change the app loading state.
|
||||||
useWhen(
|
useWhen(
|
||||||
|
|||||||
9
src/components/Dashboard/DashboardThemeProvider.js
Normal file
9
src/components/Dashboard/DashboardThemeProvider.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ThemeProvider } from 'styled-components';
|
||||||
|
import { useAppIntlContext } from '../AppIntlProvider';
|
||||||
|
|
||||||
|
export function DashboardThemeProvider({ children }) {
|
||||||
|
const { direction } = useAppIntlContext();
|
||||||
|
|
||||||
|
return <ThemeProvider theme={{ dir: direction }}>{children}</ThemeProvider>;
|
||||||
|
}
|
||||||
@@ -3,11 +3,16 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { getDashboardRoutes } from 'routes/dashboard';
|
import { getDashboardRoutes } from 'routes/dashboard';
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
function GlobalHotkeys({
|
function GlobalHotkeys({
|
||||||
// #withDashboardActions
|
// #withDashboardActions
|
||||||
toggleSidebarExpend,
|
toggleSidebarExpend,
|
||||||
|
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const routes = getDashboardRoutes();
|
const routes = getDashboardRoutes();
|
||||||
@@ -16,8 +21,8 @@ function GlobalHotkeys({
|
|||||||
.filter(({ hotkey }) => hotkey)
|
.filter(({ hotkey }) => hotkey)
|
||||||
.map(({ hotkey }) => hotkey)
|
.map(({ hotkey }) => hotkey)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
const handleSidebarToggleBtn = () => {
|
const handleSidebarToggleBtn = () => {
|
||||||
toggleSidebarExpend();
|
toggleSidebarExpend();
|
||||||
};
|
};
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
@@ -32,7 +37,9 @@ function GlobalHotkeys({
|
|||||||
[history],
|
[history],
|
||||||
);
|
);
|
||||||
useHotkeys('ctrl+/', (event, handle) => handleSidebarToggleBtn());
|
useHotkeys('ctrl+/', (event, handle) => handleSidebarToggleBtn());
|
||||||
|
useHotkeys('shift+d', (event, handle) => openDialog('money-in', {}));
|
||||||
|
useHotkeys('shift+q', (event, handle) => openDialog('money-out', {}));
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDashboardActions)(GlobalHotkeys);
|
export default compose(withDashboardActions, withDialogActions)(GlobalHotkeys);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
export * from './SplashScreen';
|
export * from './SplashScreen';
|
||||||
export * from './DashboardBoot';
|
export * from './DashboardBoot';
|
||||||
|
export * from './DashboardThemeProvider';
|
||||||
|
|||||||
29
src/components/Datatable/CellForceWidth.js
Normal file
29
src/components/Datatable/CellForceWidth.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
|
||||||
|
import { getForceWidth } from 'utils';
|
||||||
|
|
||||||
|
export function CellForceWidth({
|
||||||
|
value,
|
||||||
|
column: { forceWidthAccess },
|
||||||
|
row: { original },
|
||||||
|
}) {
|
||||||
|
const forceWidthValue = forceWidthAccess
|
||||||
|
? get(original, forceWidthAccess)
|
||||||
|
: value;
|
||||||
|
|
||||||
|
return <ForceWidth forceValue={forceWidthValue}>{value}</ForceWidth>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ForceWidth({ children, forceValue }) {
|
||||||
|
const forceWidthValue = forceValue || children;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={'force-width'}
|
||||||
|
style={{ minWidth: getForceWidth(forceWidthValue) }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
4
src/components/Datatable/index.js
Normal file
4
src/components/Datatable/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export * from './CellForceWidth';
|
||||||
@@ -17,6 +17,9 @@ import AllocateLandedCostDialog from 'containers/Dialogs/AllocateLandedCostDialo
|
|||||||
import InvoicePdfPreviewDialog from 'containers/Dialogs/InvoicePdfPreviewDialog';
|
import InvoicePdfPreviewDialog from 'containers/Dialogs/InvoicePdfPreviewDialog';
|
||||||
import EstimatePdfPreviewDialog from 'containers/Dialogs/EstimatePdfPreviewDialog';
|
import EstimatePdfPreviewDialog from 'containers/Dialogs/EstimatePdfPreviewDialog';
|
||||||
import ReceiptPdfPreviewDialog from '../containers/Dialogs/ReceiptPdfPreviewDialog';
|
import ReceiptPdfPreviewDialog from '../containers/Dialogs/ReceiptPdfPreviewDialog';
|
||||||
|
import MoneyInDialog from '../containers/Dialogs/MoneyInDialog';
|
||||||
|
import MoneyOutDialog from '../containers/Dialogs/MoneyOutDialog';
|
||||||
|
import BadDebtDialog from '../containers/Dialogs/BadDebtDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialogs container.
|
* Dialogs container.
|
||||||
@@ -40,6 +43,9 @@ export default function DialogsContainer() {
|
|||||||
<InvoicePdfPreviewDialog dialogName={'invoice-pdf-preview'} />
|
<InvoicePdfPreviewDialog dialogName={'invoice-pdf-preview'} />
|
||||||
<EstimatePdfPreviewDialog dialogName={'estimate-pdf-preview'} />
|
<EstimatePdfPreviewDialog dialogName={'estimate-pdf-preview'} />
|
||||||
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
||||||
|
<MoneyInDialog dialogName={'money-in'} />
|
||||||
|
<MoneyOutDialog dialogName={'money-out'} />
|
||||||
|
<BadDebtDialog dialogName={'write-off-bad-debt'} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ItemDetailDrawer from '../containers/Drawers/ItemDetailDrawer';
|
|||||||
import CustomerDetailsDrawer from '../containers/Drawers/CustomerDetailsDrawer';
|
import CustomerDetailsDrawer from '../containers/Drawers/CustomerDetailsDrawer';
|
||||||
import VendorDetailsDrawer from '../containers/Drawers/VendorDetailsDrawer';
|
import VendorDetailsDrawer from '../containers/Drawers/VendorDetailsDrawer';
|
||||||
import InventoryAdjustmentDetailDrawer from '../containers/Drawers/InventoryAdjustmentDetailDrawer';
|
import InventoryAdjustmentDetailDrawer from '../containers/Drawers/InventoryAdjustmentDetailDrawer';
|
||||||
|
import CashflowTransactionDetailDrawer from '../containers/Drawers/CashflowTransactionDetailDrawer';
|
||||||
|
|
||||||
import { DRAWERS } from 'common/drawers';
|
import { DRAWERS } from 'common/drawers';
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ export default function DrawersContainer() {
|
|||||||
<InventoryAdjustmentDetailDrawer
|
<InventoryAdjustmentDetailDrawer
|
||||||
name={DRAWERS.INVENTORY_ADJUSTMENT_DRAWER}
|
name={DRAWERS.INVENTORY_ADJUSTMENT_DRAWER}
|
||||||
/>
|
/>
|
||||||
|
<CashflowTransactionDetailDrawer name={DRAWERS.CASHFLOW_TRNASACTION_DRAWER} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
export function FormattedMessage({ id }) {
|
export function FormattedMessage({ id, values }) {
|
||||||
return intl.get(id);
|
return intl.get(id, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormattedHTMLMessage({ ...args }) {
|
export function FormattedHTMLMessage({ ...args }) {
|
||||||
|
|||||||
26
src/components/IntersectionObserver/index.js
Normal file
26
src/components/IntersectionObserver/index.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useIntersectionObserver } from 'hooks/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intersection observer.
|
||||||
|
*/
|
||||||
|
export function IntersectionObserver({ onIntersect }) {
|
||||||
|
const loadMoreButtonRef = React.useRef();
|
||||||
|
|
||||||
|
useIntersectionObserver({
|
||||||
|
// enabled: !isItemsLoading && !isResourceLoading,
|
||||||
|
target: loadMoreButtonRef,
|
||||||
|
onIntersect: () => {
|
||||||
|
onIntersect && onIntersect();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={loadMoreButtonRef}
|
||||||
|
style={{ opacity: 0, height: 0, width: 0, padding: 0, margin: 0 }}
|
||||||
|
>
|
||||||
|
Load Newer
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
46
src/components/MoreVertMenutItems.js
Normal file
46
src/components/MoreVertMenutItems.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
MenuItem,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { Select } from '@blueprintjs/select';
|
||||||
|
import { Icon } from 'components';
|
||||||
|
|
||||||
|
function MoreVertMenutItems({ text, items, onItemSelect, buttonProps }) {
|
||||||
|
// Menu items renderer.
|
||||||
|
const itemsRenderer = (item, { handleClick, modifiers, query }) => (
|
||||||
|
<MenuItem text={item.name} label={item.label} onClick={handleClick} />
|
||||||
|
);
|
||||||
|
const handleMenuSelect = (type) => {
|
||||||
|
onItemSelect && onItemSelect(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
items={items}
|
||||||
|
itemRenderer={itemsRenderer}
|
||||||
|
onItemSelect={handleMenuSelect}
|
||||||
|
popoverProps={{
|
||||||
|
minimal: true,
|
||||||
|
position: Position.BOTTOM_LEFT,
|
||||||
|
interactionKind: PopoverInteractionKind.CLICK,
|
||||||
|
modifiers: {
|
||||||
|
offset: { offset: '0, 4' },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
filterable={false}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
text={text}
|
||||||
|
icon={<Icon icon={'more-vert'} iconSize={16} />}
|
||||||
|
minimal={true}
|
||||||
|
{...buttonProps}
|
||||||
|
/>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MoreVertMenutItems;
|
||||||
@@ -8,7 +8,7 @@ import intl from 'react-intl-universal';
|
|||||||
export function FormatDate({ value, format = 'YYYY MMM DD' }) {
|
export function FormatDate({ value, format = 'YYYY MMM DD' }) {
|
||||||
const localizedFormat = intl.get(`date_formats.${format}`);
|
const localizedFormat = intl.get(`date_formats.${format}`);
|
||||||
|
|
||||||
return moment().format(localizedFormat);
|
return moment(value).format(localizedFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import Card from './Card';
|
|||||||
import AvaterCell from './AvaterCell';
|
import AvaterCell from './AvaterCell';
|
||||||
|
|
||||||
import { ItemsMultiSelect } from './Items';
|
import { ItemsMultiSelect } from './Items';
|
||||||
|
import MoreVertMenutItems from './MoreVertMenutItems';
|
||||||
|
|
||||||
export * from './Menu';
|
export * from './Menu';
|
||||||
export * from './AdvancedFilter/AdvancedFilterDropdown';
|
export * from './AdvancedFilter/AdvancedFilterDropdown';
|
||||||
@@ -81,6 +82,11 @@ export * from './Forms';
|
|||||||
export * from './MultiSelectTaggable';
|
export * from './MultiSelectTaggable';
|
||||||
export * from './Utils/FormatNumber';
|
export * from './Utils/FormatNumber';
|
||||||
export * from './Utils/FormatDate';
|
export * from './Utils/FormatDate';
|
||||||
|
export * from './BankAccounts';
|
||||||
|
export * from './IntersectionObserver'
|
||||||
|
export * from './Datatable/CellForceWidth';
|
||||||
|
export * from './Button';
|
||||||
|
export * from './IntersectionObserver';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
@@ -152,4 +158,5 @@ export {
|
|||||||
ItemsMultiSelect,
|
ItemsMultiSelect,
|
||||||
Card,
|
Card,
|
||||||
AvaterCell,
|
AvaterCell,
|
||||||
|
MoreVertMenutItems,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -190,8 +190,36 @@ export default [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'banking'} />,
|
text: <T id={'siebar.cashflow'} />,
|
||||||
children: [],
|
children: [
|
||||||
|
{
|
||||||
|
text: <T id={'siebar.cashflow.label_cash_and_bank_accounts'} />,
|
||||||
|
href: '/cashflow-accounts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: <T id={'New tasks'} />,
|
||||||
|
label: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
divider: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: <T id={'cash_flow.label.add_money_in'} />,
|
||||||
|
href: '/cashflow-accounts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: <T id={'cash_flow.label.add_money_out'} />,
|
||||||
|
href: '/cashflow-accounts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: <T id={'cash_flow.label.add_cash_account'} />,
|
||||||
|
href: '/cashflow-accounts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: <T id={'cash_flow.label.add_bank_account'} />,
|
||||||
|
href: '/cashflow-accounts',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'expenses'} />,
|
text: <T id={'expenses'} />,
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import JournalDeleteAlert from 'containers/Alerts/ManualJournals/JournalDeleteAlert';
|
|
||||||
import JournalPublishAlert from 'containers/Alerts/ManualJournals/JournalPublishAlert';
|
const JournalDeleteAlert = React.lazy(() =>
|
||||||
|
import('../../Alerts/ManualJournals/JournalDeleteAlert'),
|
||||||
|
);
|
||||||
|
const JournalPublishAlert = React.lazy(() =>
|
||||||
|
import('../../Alerts/ManualJournals/JournalPublishAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manual journals alerts.
|
* Manual journals alerts.
|
||||||
*/
|
*/
|
||||||
export default function ManualJournalsAlerts() {
|
|
||||||
return (
|
export default [
|
||||||
<div>
|
{ name: 'journal-delete', component: JournalDeleteAlert },
|
||||||
<JournalDeleteAlert name={'journal-delete'} />
|
{ name: 'journal-publish', component: JournalPublishAlert },
|
||||||
<JournalPublishAlert name={'journal-publish'} />
|
];
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'style/pages/ManualJournal/List.scss';
|
|||||||
import { DashboardContentTable, DashboardPageContent } from 'components';
|
import { DashboardContentTable, DashboardPageContent } from 'components';
|
||||||
|
|
||||||
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
||||||
import ManualJournalsAlerts from './ManualJournalsAlerts';
|
|
||||||
import ManualJournalsViewTabs from './ManualJournalsViewTabs';
|
import ManualJournalsViewTabs from './ManualJournalsViewTabs';
|
||||||
import ManualJournalsDataTable from './ManualJournalsDataTable';
|
import ManualJournalsDataTable from './ManualJournalsDataTable';
|
||||||
import ManualJournalsActionsBar from './ManualJournalActionsBar';
|
import ManualJournalsActionsBar from './ManualJournalActionsBar';
|
||||||
@@ -33,7 +32,6 @@ function ManualJournalsTable({
|
|||||||
<ManualJournalsDataTable />
|
<ManualJournalsDataTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
|
|
||||||
<ManualJournalsAlerts />
|
|
||||||
</ManualJournalsListProvider>
|
</ManualJournalsListProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AccountDeleteAlert from 'containers/Alerts/AccountDeleteAlert';
|
|
||||||
import AccountInactivateAlert from 'containers/Alerts/AccountInactivateAlert';
|
const AccountDeleteAlert = React.lazy(() =>
|
||||||
import AccountActivateAlert from 'containers/Alerts/AccountActivateAlert';
|
import('containers/Alerts/AccountDeleteAlert'),
|
||||||
|
);
|
||||||
|
const AccountInactivateAlert = React.lazy(() =>
|
||||||
|
import('containers/Alerts/AccountInactivateAlert'),
|
||||||
|
);
|
||||||
|
const AccountActivateAlert = React.lazy(() =>
|
||||||
|
import('containers/Alerts/AccountActivateAlert'),
|
||||||
|
);
|
||||||
// import AccountBulkDeleteAlert from 'containers/Alerts/AccountBulkDeleteAlert';
|
// import AccountBulkDeleteAlert from 'containers/Alerts/AccountBulkDeleteAlert';
|
||||||
// import AccountBulkInactivateAlert from 'containers/Alerts/AccountBulkInactivateAlert';
|
// import AccountBulkInactivateAlert from 'containers/Alerts/AccountBulkInactivateAlert';
|
||||||
// import AccountBulkActivateAlert from 'containers/Alerts/AccountBulkActivateAlert';
|
// import AccountBulkActivateAlert from 'containers/Alerts/AccountBulkActivateAlert';
|
||||||
|
|
||||||
/**
|
export default [
|
||||||
* Accounts alert.
|
{ name: 'account-delete', component: AccountDeleteAlert },
|
||||||
*/
|
{ name: 'account-inactivate', component: AccountInactivateAlert },
|
||||||
export default function AccountsAlerts({
|
{ name: 'account-activate', component: AccountActivateAlert },
|
||||||
|
];
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div class="accounts-alerts">
|
|
||||||
<AccountDeleteAlert name={'account-delete'} />
|
|
||||||
<AccountInactivateAlert name={'account-inactivate'} />
|
|
||||||
<AccountActivateAlert name={'account-activate'} />
|
|
||||||
|
|
||||||
{/* <AccountBulkDeleteAlert name={'accounts-bulk-delete'} />
|
|
||||||
<AccountBulkInactivateAlert name={'accounts-bulk-inactivate'} />
|
|
||||||
<AccountBulkActivateAlert name={'accounts-bulk-activate'} /> */}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { AccountsChartProvider } from './AccountsChartProvider';
|
|||||||
|
|
||||||
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
|
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
|
||||||
import AccountsActionsBar from 'containers/Accounts/AccountsActionsBar';
|
import AccountsActionsBar from 'containers/Accounts/AccountsActionsBar';
|
||||||
import AccountsAlerts from './AccountsAlerts';
|
|
||||||
import AccountsDataTable from './AccountsDataTable';
|
import AccountsDataTable from './AccountsDataTable';
|
||||||
|
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
@@ -49,8 +48,6 @@ function AccountsChart({
|
|||||||
<AccountsDataTable />
|
<AccountsDataTable />
|
||||||
</DashboardContentTable>
|
</DashboardContentTable>
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
|
|
||||||
<AccountsAlerts />
|
|
||||||
</AccountsChartProvider>
|
</AccountsChartProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Intent, Tag } from '@blueprintjs/core';
|
import { Intent, Tag } from '@blueprintjs/core';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import clsx from 'classnames';
|
|
||||||
|
|
||||||
import { CLASSES } from '../../common/classes';
|
|
||||||
import { If, AppToaster } from 'components';
|
import { If, AppToaster } from 'components';
|
||||||
import { NormalCell, BalanceCell } from './components';
|
import { NormalCell, BalanceCell } from './components';
|
||||||
import { transformTableStateToQuery, isBlank } from 'utils';
|
import { transformTableStateToQuery, isBlank } from 'utils';
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import { useDeleteCashflowTransaction } from 'hooks/query';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account delete transaction alert.
|
||||||
|
*/
|
||||||
|
function AccountDeleteTransactionAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { referenceId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
closeDrawer,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: deleteTransactionMutate, isLoading } =
|
||||||
|
useDeleteCashflowTransaction();
|
||||||
|
|
||||||
|
// handle cancel delete alert
|
||||||
|
const handleCancelDeleteAlert = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// handleConfirm delete transaction.
|
||||||
|
const handleConfirmTransactioneDelete = () => {
|
||||||
|
deleteTransactionMutate(referenceId)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('cash_flow_transaction.delete.alert_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
closeDrawer('cashflow-transaction-drawer');
|
||||||
|
})
|
||||||
|
.catch(
|
||||||
|
({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {},
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'delete'} />}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelDeleteAlert}
|
||||||
|
onConfirm={handleConfirmTransactioneDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
id={
|
||||||
|
'cash_flow_transaction_once_delete_this_transaction_you_will_able_to_restore_it'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
withDrawerActions,
|
||||||
|
)(AccountDeleteTransactionAlert);
|
||||||
68
src/containers/Alerts/Invoices/CancelBadDebtAlert.js
Normal file
68
src/containers/Alerts/Invoices/CancelBadDebtAlert.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import { useCancelBadDebt } from 'hooks/query';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel bad debt alert.
|
||||||
|
*/
|
||||||
|
function CancelBadDebtAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { invoiceId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
// handle cancel alert.
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mutateAsync: cancelBadDebtMutate, isLoading } = useCancelBadDebt();
|
||||||
|
|
||||||
|
// handleConfirm alert.
|
||||||
|
const handleConfirm = () => {
|
||||||
|
cancelBadDebtMutate(invoiceId)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('bad_debt.cancel_alert.success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'save'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'bad_debt.cancel_alert.message'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(CancelBadDebtAlert);
|
||||||
@@ -40,7 +40,7 @@ function InventoryAdjustmentDeleteAlert({
|
|||||||
deleteInventoryAdjMutate(inventoryId)
|
deleteInventoryAdjMutate(inventoryId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('the_adjustment_has_been_deleted_successfully'),
|
message: intl.get('the_adjustment_transaction_has_been_deleted_successfully'),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
closeDrawer('inventory-adjustment-drawer');
|
closeDrawer('inventory-adjustment-drawer');
|
||||||
|
|||||||
100
src/containers/AlertsContainer/components.js
Normal file
100
src/containers/AlertsContainer/components.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import React, { Suspense } from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { Intent, Classes, ProgressBar } from '@blueprintjs/core';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import clsx from 'classnames';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
function AlertLazyFallbackMessage({ amount }) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ToastText>Alert content is loading, just a second.</ToastText>
|
||||||
|
<ProgressBar
|
||||||
|
className={clsx({
|
||||||
|
[Classes.PROGRESS_NO_STRIPES]: amount >= 100,
|
||||||
|
})}
|
||||||
|
intent={amount < 100 ? Intent.PRIMARY : Intent.SUCCESS}
|
||||||
|
value={amount / 100}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertLazyFallback({}) {
|
||||||
|
const progressToastInterval = React.useRef(null);
|
||||||
|
const toastKey = React.useRef(null);
|
||||||
|
|
||||||
|
const toastProgressLoading = (amount) => {
|
||||||
|
return {
|
||||||
|
message: <AlertLazyFallbackMessage amount={amount} />,
|
||||||
|
onDismiss: (didTimeoutExpire) => {
|
||||||
|
if (!didTimeoutExpire) {
|
||||||
|
window.clearInterval(progressToastInterval.current);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeout: amount < 100 ? 0 : 2000,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerProgressToast = () => {
|
||||||
|
let progress = 0;
|
||||||
|
toastKey.current = AppToaster.show(toastProgressLoading(0));
|
||||||
|
|
||||||
|
progressToastInterval.current = window.setInterval(() => {
|
||||||
|
if (toastKey.current == null || progress > 100) {
|
||||||
|
window.clearInterval(progressToastInterval.current);
|
||||||
|
} else {
|
||||||
|
progress += 10 + Math.random() * 20;
|
||||||
|
AppToaster.show(toastProgressLoading(progress), toastKey.current);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideProgressToast = () => {
|
||||||
|
window.clearInterval(progressToastInterval.current);
|
||||||
|
AppToaster.dismiss(toastKey.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Debounce the trigger.
|
||||||
|
const dobounceTrigger = React.useRef(
|
||||||
|
debounce(() => {
|
||||||
|
triggerProgressToast();
|
||||||
|
}, 500),
|
||||||
|
);
|
||||||
|
React.useEffect(() => {
|
||||||
|
dobounceTrigger.current();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
hideProgressToast();
|
||||||
|
dobounceTrigger.current.cancel();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlertLazyInside({ isOpen, name, Component }) {
|
||||||
|
if (!isOpen) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<AlertLazyFallback />}>
|
||||||
|
<Component name={name} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlertLazy = R.compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(AlertLazyInside);
|
||||||
|
|
||||||
|
const ToastText = styled.div`
|
||||||
|
margin-bottom: 10px;
|
||||||
|
`;
|
||||||
13
src/containers/AlertsContainer/index.js
Normal file
13
src/containers/AlertsContainer/index.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { AlertLazy } from './components'
|
||||||
|
import registered from './registered';
|
||||||
|
|
||||||
|
export default function AlertsContainer() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{registered.map((alert) => (
|
||||||
|
<AlertLazy name={alert.name} Component={alert.component} />
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
39
src/containers/AlertsContainer/registered.js
Normal file
39
src/containers/AlertsContainer/registered.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import AccountsAlerts from '../Accounts/AccountsAlerts';
|
||||||
|
import ItemsAlerts from '../Items/ItemsAlerts';
|
||||||
|
import ItemsCategoriesAlerts from '../ItemsCategories/ItemsCategoriesAlerts';
|
||||||
|
import InventoryAdjustmentsAlerts from '../InventoryAdjustments/InventoryAdjustmentsAlerts';
|
||||||
|
import EstimatesAlerts from '../Sales/Estimates/EstimatesAlerts';
|
||||||
|
import InvoicesAlerts from '../Sales/Invoices/InvoicesAlerts';
|
||||||
|
import ReceiptsAlerts from '../Sales/Receipts/ReceiptsAlerts';
|
||||||
|
import PaymentReceiveAlerts from '../Sales/PaymentReceives/PaymentReceiveAlerts';
|
||||||
|
import BillsAlerts from '../Purchases/Bills/BillsLanding/BillsAlerts';
|
||||||
|
import PaymentMadesAlerts from '../Purchases/PaymentMades/PaymentMadesAlerts';
|
||||||
|
import CustomersAlerts from '../Customers/CustomersAlerts';
|
||||||
|
import VendorsAlerts from '../Vendors/VendorsAlerts';
|
||||||
|
import ManualJournalsAlerts from '../Accounting/JournalsLanding/ManualJournalsAlerts';
|
||||||
|
import ExchangeRatesAlerts from '../ExchangeRates/ExchangeRatesAlerts';
|
||||||
|
import ExpensesAlerts from '../Expenses/ExpensesAlerts';
|
||||||
|
import AccountTransactionsAlerts from '../CashFlow/AccountTransactions/AccountTransactionsAlerts';
|
||||||
|
import UsersAlerts from '../Preferences/Users/UsersAlerts';
|
||||||
|
import CurrenciesAlerts from '../Preferences/Currencies/CurrenciesAlerts';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...AccountsAlerts,
|
||||||
|
...ItemsAlerts,
|
||||||
|
...ItemsCategoriesAlerts,
|
||||||
|
...InventoryAdjustmentsAlerts,
|
||||||
|
...EstimatesAlerts,
|
||||||
|
...InvoicesAlerts,
|
||||||
|
...ReceiptsAlerts,
|
||||||
|
...PaymentReceiveAlerts,
|
||||||
|
...BillsAlerts,
|
||||||
|
...PaymentMadesAlerts,
|
||||||
|
...CustomersAlerts,
|
||||||
|
...VendorsAlerts,
|
||||||
|
...ManualJournalsAlerts,
|
||||||
|
...ExchangeRatesAlerts,
|
||||||
|
...ExpensesAlerts,
|
||||||
|
...AccountTransactionsAlerts,
|
||||||
|
...UsersAlerts,
|
||||||
|
...CurrenciesAlerts,
|
||||||
|
];
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
NavbarGroup,
|
||||||
|
Classes,
|
||||||
|
NavbarDivider,
|
||||||
|
Alignment,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import {
|
||||||
|
Icon,
|
||||||
|
DashboardRowsHeightButton,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
|
import { useRefreshCashflowTransactionsInfinity } from 'hooks/query';
|
||||||
|
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
import { CashFlowMenuItems } from './utils';
|
||||||
|
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withSettings from '../../Settings/withSettings';
|
||||||
|
import withSettingsActions from '../../Settings/withSettingsActions';
|
||||||
|
import { addMoneyIn, addMoneyOut } from '../../../common/cashflowOptions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function AccountTransactionsActionsBar({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withSettings
|
||||||
|
cashflowTansactionsTableSize,
|
||||||
|
|
||||||
|
// #withSettingsActions
|
||||||
|
addSetting,
|
||||||
|
}) {
|
||||||
|
// Handle table row size change.
|
||||||
|
const handleTableRowSizeChange = (size) => {
|
||||||
|
addSetting('cashflowTransactions', 'tableSize', size);
|
||||||
|
};
|
||||||
|
const { accountId } = useAccountTransactionsContext();
|
||||||
|
|
||||||
|
// Handle money in form
|
||||||
|
const handleMoneyInFormTransaction = (account) => {
|
||||||
|
openDialog('money-in', {
|
||||||
|
account_id: accountId,
|
||||||
|
account_type: account.value,
|
||||||
|
account_name: account.name,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Handle money out form
|
||||||
|
const handlMoneyOutFormTransaction = (account) => {
|
||||||
|
openDialog('money-out', {
|
||||||
|
account_id: accountId,
|
||||||
|
account_type: account.value,
|
||||||
|
account_name: account.name,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Refresh cashflow infinity transactions hook.
|
||||||
|
const { refresh } = useRefreshCashflowTransactionsInfinity();
|
||||||
|
|
||||||
|
// Handle the refresh button click.
|
||||||
|
const handleRefreshBtnClick = () => {
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardActionsBar>
|
||||||
|
<NavbarGroup>
|
||||||
|
<CashFlowMenuItems
|
||||||
|
items={addMoneyIn}
|
||||||
|
onItemSelect={handleMoneyInFormTransaction}
|
||||||
|
text={<T id={'cash_flow.label.add_money_in'} />}
|
||||||
|
buttonProps={{
|
||||||
|
icon: <Icon icon={'arrow-downward'} iconSize={20} />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CashFlowMenuItems
|
||||||
|
items={addMoneyOut}
|
||||||
|
onItemSelect={handlMoneyOutFormTransaction}
|
||||||
|
text={<T id={'cash_flow.label.add_money_out'} />}
|
||||||
|
buttonProps={{
|
||||||
|
icon: <Icon icon={'arrow-upward'} iconSize={20} />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="print-16" iconSize={16} />}
|
||||||
|
text={<T id={'print'} />}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||||
|
text={<T id={'export'} />}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||||
|
text={<T id={'import'} />}
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
<DashboardRowsHeightButton
|
||||||
|
initialValue={cashflowTansactionsTableSize}
|
||||||
|
onChange={handleTableRowSizeChange}
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
</NavbarGroup>
|
||||||
|
|
||||||
|
<NavbarGroup align={Alignment.RIGHT}>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="refresh-16" iconSize={14} />}
|
||||||
|
onClick={handleRefreshBtnClick}
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
</DashboardActionsBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withSettingsActions,
|
||||||
|
withSettings(({ cashflowTransactionsSettings }) => ({
|
||||||
|
cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize,
|
||||||
|
})),
|
||||||
|
)(AccountTransactionsActionsBar);
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const AccountDeleteTransactionAlert = React.lazy(() =>
|
||||||
|
import('../../Alerts/CashFlow/AccountDeleteTransactionAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account transaction alert.
|
||||||
|
*/
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
name: 'account-transaction-delete',
|
||||||
|
component: AccountDeleteTransactionAlert,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { DataTable, TableFastCell, FormattedMessage as T } from 'components';
|
||||||
|
import { TABLES } from 'common/tables';
|
||||||
|
|
||||||
|
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
|
||||||
|
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||||
|
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||||
|
|
||||||
|
import withSettings from '../../Settings/withSettings';
|
||||||
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { useMemorizedColumnsWidths } from '../../../hooks';
|
||||||
|
import { useAccountTransactionsColumns, ActionsMenu } from './components';
|
||||||
|
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||||
|
import { handleCashFlowTransactionType } from './utils';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
import { whenRtl, whenLtr } from 'utils/styled-components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account transactions data table.
|
||||||
|
*/
|
||||||
|
function AccountTransactionsDataTable({
|
||||||
|
// #withSettings
|
||||||
|
cashflowTansactionsTableSize,
|
||||||
|
|
||||||
|
// #withAlertsActions
|
||||||
|
openAlert,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
}) {
|
||||||
|
// Retrieve table columns.
|
||||||
|
const columns = useAccountTransactionsColumns();
|
||||||
|
|
||||||
|
// Retrieve list context.
|
||||||
|
const { cashflowTransactions, isCashFlowTransactionsLoading } =
|
||||||
|
useAccountTransactionsContext();
|
||||||
|
|
||||||
|
// Local storage memorizing columns widths.
|
||||||
|
const [initialColumnsWidths, , handleColumnResizing] =
|
||||||
|
useMemorizedColumnsWidths(TABLES.CASHFLOW_Transactions);
|
||||||
|
|
||||||
|
// handle delete transaction
|
||||||
|
const handleDeleteTransaction = ({ reference_id }) => {
|
||||||
|
openAlert('account-transaction-delete', { referenceId: reference_id });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleViewDetailCashflowTransaction = (referenceType) => {
|
||||||
|
handleCashFlowTransactionType(referenceType, openDrawer);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle cell click.
|
||||||
|
const handleCellClick = (cell, event) => {
|
||||||
|
const referenceType = cell.row.original;
|
||||||
|
handleCashFlowTransactionType(referenceType, openDrawer);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CashflowTransactionsTable
|
||||||
|
noInitialFetch={true}
|
||||||
|
columns={columns}
|
||||||
|
data={cashflowTransactions}
|
||||||
|
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) {
|
||||||
|
${whenLtr(`border-left: 1px solid #e6e6e6;`)}
|
||||||
|
${whenRtl(`border-right: 1px solid #e6e6e6;`)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
Menu,
|
||||||
|
Position,
|
||||||
|
Button,
|
||||||
|
MenuItem,
|
||||||
|
Classes,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { curry } from 'lodash/fp';
|
||||||
|
|
||||||
|
import { Icon } from '../../../components';
|
||||||
|
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||||
|
import { whenRtl, whenLtr } from 'utils/styled-components';
|
||||||
|
|
||||||
|
function AccountSwitchButton() {
|
||||||
|
const { currentAccount } = useAccountTransactionsContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccountSwitchButtonBase
|
||||||
|
minimal={true}
|
||||||
|
rightIcon={<Icon icon={'arrow-drop-down'} iconSize={24} />}
|
||||||
|
>
|
||||||
|
<AccountSwitchText>{currentAccount.name}</AccountSwitchText>
|
||||||
|
</AccountSwitchButtonBase>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountSwitchItem() {
|
||||||
|
const { push } = useHistory();
|
||||||
|
const { cashflowAccounts, accountId } = useAccountTransactionsContext();
|
||||||
|
|
||||||
|
// Handle item click.
|
||||||
|
const handleItemClick = curry((account, event) => {
|
||||||
|
push(`/cashflow-accounts/${account.id}/transactions`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = cashflowAccounts.map((account) => (
|
||||||
|
<AccountSwitchMenuItem
|
||||||
|
name={account.name}
|
||||||
|
balance={account.formatted_amount}
|
||||||
|
onClick={handleItemClick(account)}
|
||||||
|
active={account.id === accountId}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
content={<Menu>{items}</Menu>}
|
||||||
|
position={Position.BOTTOM_LEFT}
|
||||||
|
minimal={true}
|
||||||
|
>
|
||||||
|
<AccountSwitchButton />
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountBalanceItem() {
|
||||||
|
const { currentAccount } = useAccountTransactionsContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccountBalanceItemWrap>
|
||||||
|
{intl.get('cash_flow_transaction.balance_in_bigcapital')} {''}
|
||||||
|
<AccountBalanceAmount>
|
||||||
|
{currentAccount.formatted_amount}
|
||||||
|
</AccountBalanceAmount>
|
||||||
|
</AccountBalanceItemWrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountTransactionsDetailsBarSkeleton() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<DetailsBarSkeletonBase className={Classes.SKELETON}>
|
||||||
|
X
|
||||||
|
</DetailsBarSkeletonBase>
|
||||||
|
<DetailsBarSkeletonBase className={Classes.SKELETON}>
|
||||||
|
X
|
||||||
|
</DetailsBarSkeletonBase>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountTransactionsDetailsContent() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<AccountSwitchItem />
|
||||||
|
<AccountBalanceItem />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AccountTransactionsDetailsBar() {
|
||||||
|
const { isCurrentAccountLoading } = useAccountTransactionsContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccountTransactionDetailsWrap>
|
||||||
|
{isCurrentAccountLoading ? (
|
||||||
|
<AccountTransactionsDetailsBarSkeleton />
|
||||||
|
) : (
|
||||||
|
<AccountTransactionsDetailsContent />
|
||||||
|
)}
|
||||||
|
</AccountTransactionDetailsWrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountSwitchMenuItem({
|
||||||
|
name,
|
||||||
|
balance,
|
||||||
|
transactionsNumber,
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
label={balance}
|
||||||
|
text={
|
||||||
|
<React.Fragment>
|
||||||
|
<AccountSwitchItemName>{name}</AccountSwitchItemName>
|
||||||
|
<AccountSwitchItemTranscations>
|
||||||
|
{intl.get('cash_flow_transaction.switch_item', { value: '25' })}
|
||||||
|
</AccountSwitchItemTranscations>
|
||||||
|
|
||||||
|
<AccountSwitchItemUpdatedAt></AccountSwitchItemUpdatedAt>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DetailsBarSkeletonBase = styled.div`
|
||||||
|
letter-spacing: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 8px;
|
||||||
|
width: 140px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AccountBalanceItemWrap = styled.div`
|
||||||
|
margin-left: 18px;
|
||||||
|
color: #5f6d86;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AccountTransactionDetailsWrap = styled.div`
|
||||||
|
display: flex;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #d2dce2;
|
||||||
|
padding: 0 22px;
|
||||||
|
height: 42px;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
const AccountSwitchText = styled.div`
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AccountBalanceAmount = styled.span`
|
||||||
|
font-weight: 600;
|
||||||
|
display: inline-block;
|
||||||
|
color: rgb(31, 50, 85);
|
||||||
|
${whenLtr(`margin-left: 10px;`)}
|
||||||
|
${whenRtl(`margin-right: 10px;`)}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AccountSwitchItemName = styled.div`
|
||||||
|
font-weight: 600;
|
||||||
|
`;
|
||||||
|
const AccountSwitchItemTranscations = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.7;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AccountSwitchItemUpdatedAt = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.5;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AccountSwitchButtonBase = styled(Button)`
|
||||||
|
.bp3-button-text {
|
||||||
|
${whenLtr(`margin-right: 5px;`)}
|
||||||
|
${whenRtl(`margin-left: 5px;`)}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import 'style/pages/CashFlow/AccountTransactions/List.scss';
|
||||||
|
|
||||||
|
import { DashboardPageContent } from 'components';
|
||||||
|
|
||||||
|
import { AccountTransactionsProvider } from './AccountTransactionsProvider';
|
||||||
|
import AccountTransactionsActionsBar from './AccountTransactionsActionsBar';
|
||||||
|
import AccountTransactionsDataTable from './AccountTransactionsDataTable';
|
||||||
|
import { AccountTransactionsDetailsBar } from './AccountTransactionsDetailsBar';
|
||||||
|
import { AccountTransactionsProgressBar } from './components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account transactions list.
|
||||||
|
*/
|
||||||
|
function AccountTransactionsList() {
|
||||||
|
return (
|
||||||
|
<AccountTransactionsProvider>
|
||||||
|
<AccountTransactionsActionsBar />
|
||||||
|
<AccountTransactionsDetailsBar />
|
||||||
|
<AccountTransactionsProgressBar />
|
||||||
|
|
||||||
|
<DashboardPageContent>
|
||||||
|
<CashflowTransactionsTableCard>
|
||||||
|
<AccountTransactionsDataTable />
|
||||||
|
</CashflowTransactionsTableCard>
|
||||||
|
</DashboardPageContent>
|
||||||
|
</AccountTransactionsProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountTransactionsList;
|
||||||
|
|
||||||
|
const CashflowTransactionsTableCard = styled.div`
|
||||||
|
border: 2px solid #f0f0f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 30px 18px;
|
||||||
|
margin: 30px 15px;
|
||||||
|
background: #fff;
|
||||||
|
flex: 0 1;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { flatten, map } from 'lodash';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import { IntersectionObserver } from 'components';
|
||||||
|
import {
|
||||||
|
useAccountTransactionsInfinity,
|
||||||
|
useCashflowAccounts,
|
||||||
|
useAccount,
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
|
const AccountTransactionsContext = React.createContext();
|
||||||
|
|
||||||
|
function flattenInfinityPages(data) {
|
||||||
|
return flatten(map(data.pages, (page) => page.transactions));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account transctions provider.
|
||||||
|
*/
|
||||||
|
function AccountTransactionsProvider({ query, ...props }) {
|
||||||
|
const { id } = useParams();
|
||||||
|
const accountId = parseInt(id, 10);
|
||||||
|
|
||||||
|
// Fetch cashflow account transactions list
|
||||||
|
const {
|
||||||
|
data: cashflowTransactionsPages,
|
||||||
|
isFetching: isCashFlowTransactionsFetching,
|
||||||
|
isLoading: isCashFlowTransactionsLoading,
|
||||||
|
isSuccess: isCashflowTransactionsSuccess,
|
||||||
|
fetchNextPage: fetchNextTransactionsPage,
|
||||||
|
isFetchingNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
} = useAccountTransactionsInfinity(accountId, {
|
||||||
|
page_size: 50,
|
||||||
|
account_id: accountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Memorized the cashflow account transactions.
|
||||||
|
const cashflowTransactions = React.useMemo(
|
||||||
|
() =>
|
||||||
|
isCashflowTransactionsSuccess
|
||||||
|
? flattenInfinityPages(cashflowTransactionsPages)
|
||||||
|
: [],
|
||||||
|
[cashflowTransactionsPages, isCashflowTransactionsSuccess],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch cashflow accounts.
|
||||||
|
const {
|
||||||
|
data: cashflowAccounts,
|
||||||
|
isFetching: isCashFlowAccountsFetching,
|
||||||
|
isLoading: isCashFlowAccountsLoading,
|
||||||
|
} = useCashflowAccounts(query, { keepPreviousData: true });
|
||||||
|
|
||||||
|
// Retrieve specific account details.
|
||||||
|
const {
|
||||||
|
data: currentAccount,
|
||||||
|
isFetching: isCurrentAccountFetching,
|
||||||
|
isLoading: isCurrentAccountLoading,
|
||||||
|
} = useAccount(accountId, { keepPreviousData: true });
|
||||||
|
|
||||||
|
// Handle the observer ineraction.
|
||||||
|
const handleObserverInteract = React.useCallback(() => {
|
||||||
|
if (!isFetchingNextPage && hasNextPage) {
|
||||||
|
fetchNextTransactionsPage();
|
||||||
|
}
|
||||||
|
}, [isFetchingNextPage, hasNextPage, fetchNextTransactionsPage]);
|
||||||
|
|
||||||
|
// Provider payload.
|
||||||
|
const provider = {
|
||||||
|
accountId,
|
||||||
|
cashflowTransactions,
|
||||||
|
cashflowAccounts,
|
||||||
|
currentAccount,
|
||||||
|
isCashFlowTransactionsFetching,
|
||||||
|
isCashFlowTransactionsLoading,
|
||||||
|
isCashFlowAccountsFetching,
|
||||||
|
isCashFlowAccountsLoading,
|
||||||
|
isCurrentAccountFetching,
|
||||||
|
isCurrentAccountLoading,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider name={'account-transactions'}>
|
||||||
|
<AccountTransactionsContext.Provider value={provider} {...props} />
|
||||||
|
<IntersectionObserver
|
||||||
|
onIntersect={handleObserverInteract}
|
||||||
|
enabled={!isFetchingNextPage}
|
||||||
|
/>
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useAccountTransactionsContext = () =>
|
||||||
|
React.useContext(AccountTransactionsContext);
|
||||||
|
|
||||||
|
export { AccountTransactionsProvider, useAccountTransactionsContext };
|
||||||
130
src/containers/CashFlow/AccountTransactions/components.js
Normal file
130
src/containers/CashFlow/AccountTransactions/components.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import { Intent, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { MaterialProgressBar } from 'components';
|
||||||
|
import { FormatDateCell, If, Icon } from 'components';
|
||||||
|
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
|
||||||
|
import { TRANSACRIONS_TYPE } from 'common/cashflowOptions';
|
||||||
|
import { safeCallback } from 'utils';
|
||||||
|
|
||||||
|
export function ActionsMenu({
|
||||||
|
payload: { onDelete, onViewDetails },
|
||||||
|
row: { original },
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="reader-18" />}
|
||||||
|
text={intl.get('view_details')}
|
||||||
|
onClick={safeCallback(onViewDetails, original)}
|
||||||
|
/>
|
||||||
|
<If condition={TRANSACRIONS_TYPE.includes(original.reference_type)}>
|
||||||
|
<MenuDivider />
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('delete_transaction')}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={safeCallback(onDelete, original)}
|
||||||
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieve account transctions table columns.
|
||||||
|
*/
|
||||||
|
export function useAccountTransactionsColumns() {
|
||||||
|
return React.useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: 'date',
|
||||||
|
Header: intl.get('date'),
|
||||||
|
accessor: 'date',
|
||||||
|
Cell: FormatDateCell,
|
||||||
|
width: 110,
|
||||||
|
className: 'date',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'type',
|
||||||
|
Header: intl.get('type'),
|
||||||
|
accessor: 'formatted_transaction_type',
|
||||||
|
className: 'type',
|
||||||
|
width: 140,
|
||||||
|
textOverview: true,
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'transaction_number',
|
||||||
|
Header: intl.get('transaction_number'),
|
||||||
|
accessor: 'transaction_number',
|
||||||
|
width: 160,
|
||||||
|
className: 'transaction_number',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'reference_number',
|
||||||
|
Header: intl.get('reference_no'),
|
||||||
|
accessor: 'reference_number',
|
||||||
|
width: 160,
|
||||||
|
className: 'reference_number',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'deposit',
|
||||||
|
Header: intl.get('cash_flow.label.deposit'),
|
||||||
|
accessor: 'formatted_deposit',
|
||||||
|
width: 110,
|
||||||
|
className: 'deposit',
|
||||||
|
textOverview: true,
|
||||||
|
align: 'right',
|
||||||
|
clickable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'withdrawal',
|
||||||
|
Header: intl.get('cash_flow.label.withdrawal'),
|
||||||
|
accessor: 'formatted_withdrawal',
|
||||||
|
className: 'withdrawal',
|
||||||
|
width: 150,
|
||||||
|
textOverview: true,
|
||||||
|
align: 'right',
|
||||||
|
clickable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'running_balance',
|
||||||
|
Header: intl.get('cash_flow.label.running_balance'),
|
||||||
|
accessor: 'formatted_running_balance',
|
||||||
|
className: 'running_balance',
|
||||||
|
width: 150,
|
||||||
|
textOverview: true,
|
||||||
|
align: 'right',
|
||||||
|
clickable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'balance',
|
||||||
|
Header: intl.get('balance'),
|
||||||
|
accessor: 'formatted_balance',
|
||||||
|
className: 'balance',
|
||||||
|
width: 150,
|
||||||
|
textOverview: true,
|
||||||
|
clickable: true,
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account transactions progress bar.
|
||||||
|
*/
|
||||||
|
export function AccountTransactionsProgressBar() {
|
||||||
|
const { isCashFlowTransactionsFetching } = useAccountTransactionsContext();
|
||||||
|
|
||||||
|
return isCashFlowTransactionsFetching ? <MaterialProgressBar /> : null;
|
||||||
|
}
|
||||||
80
src/containers/CashFlow/AccountTransactions/utils.js
Normal file
80
src/containers/CashFlow/AccountTransactions/utils.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
PopoverInteractionKind,
|
||||||
|
MenuItem,
|
||||||
|
Position,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { Select } from '@blueprintjs/select';
|
||||||
|
import { Icon } from 'components';
|
||||||
|
|
||||||
|
export const CashFlowMenuItems = ({
|
||||||
|
text,
|
||||||
|
items,
|
||||||
|
onItemSelect,
|
||||||
|
buttonProps,
|
||||||
|
}) => {
|
||||||
|
// Menu items renderer.
|
||||||
|
const itemsRenderer = (item, { handleClick, modifiers, query }) => (
|
||||||
|
<MenuItem text={item.name} label={item.label} onClick={handleClick} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCashFlowMenuSelect = (type) => {
|
||||||
|
onItemSelect && onItemSelect(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
items={items}
|
||||||
|
itemRenderer={itemsRenderer}
|
||||||
|
onItemSelect={handleCashFlowMenuSelect}
|
||||||
|
popoverProps={{
|
||||||
|
minimal: true,
|
||||||
|
position: Position.BOTTOM_LEFT,
|
||||||
|
interactionKind: PopoverInteractionKind.CLICK,
|
||||||
|
modifiers: {
|
||||||
|
offset: { offset: '0, 4' },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
filterable={false}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
text={text}
|
||||||
|
icon={<Icon icon={'plus-24'} iconSize={20} />}
|
||||||
|
minimal={true}
|
||||||
|
{...buttonProps}
|
||||||
|
/>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleCashFlowTransactionType = (reference, openDrawer) => {
|
||||||
|
switch (reference.reference_type) {
|
||||||
|
case 'SaleReceipt':
|
||||||
|
return openDrawer('receipt-detail-drawer', {
|
||||||
|
receiptId: reference.reference_id,
|
||||||
|
});
|
||||||
|
case 'Journal':
|
||||||
|
return openDrawer('journal-drawer', {
|
||||||
|
manualJournalId: reference.reference_id,
|
||||||
|
});
|
||||||
|
case 'Expense':
|
||||||
|
return openDrawer('expense-drawer', {
|
||||||
|
expenseId: reference.reference_id,
|
||||||
|
});
|
||||||
|
case 'PaymentReceive':
|
||||||
|
return openDrawer('payment-receive-detail-drawer', {
|
||||||
|
paymentReceiveId: reference.reference_id,
|
||||||
|
});
|
||||||
|
case 'BillPayment':
|
||||||
|
return openDrawer('payment-made-detail-drawer', {
|
||||||
|
paymentMadeId: reference.reference_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return openDrawer('cashflow-transaction-drawer', {
|
||||||
|
referenceId: reference.reference_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { getCashflowAccountsTableStateFactory } from 'store/CashflowAccounts/CashflowAccounts.selectors';
|
||||||
|
|
||||||
|
export default (mapState) => {
|
||||||
|
const getCashflowAccountsTableState = getCashflowAccountsTableStateFactory();
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => {
|
||||||
|
const mapped = {
|
||||||
|
cashflowAccountsTableState: getCashflowAccountsTableState(state, props),
|
||||||
|
};
|
||||||
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
|
};
|
||||||
|
|
||||||
|
return connect(mapStateToProps);
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
setCashflowAccountsTableState,
|
||||||
|
resetCashflowAccountsTableState,
|
||||||
|
} from 'store/CashflowAccounts/CashflowAccounts.actions';
|
||||||
|
|
||||||
|
const mapActionsToProps = (dispatch) => ({
|
||||||
|
setCashflowAccountsTableState: (queries) =>
|
||||||
|
dispatch(setCashflowAccountsTableState(queries)),
|
||||||
|
|
||||||
|
resetCashflowAccountsTableState: () =>
|
||||||
|
dispatch(resetCashflowAccountsTableState()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, mapActionsToProps);
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
NavbarGroup,
|
||||||
|
Classes,
|
||||||
|
NavbarDivider,
|
||||||
|
Alignment,
|
||||||
|
Switch,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import { Icon, FormattedMessage as T } from 'components';
|
||||||
|
import { useRefreshCashflowAccounts } from 'hooks/query';
|
||||||
|
|
||||||
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withCashflowAccountsTableActions from '../AccountTransactions/withCashflowAccountsTableActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cash Flow accounts actions bar.
|
||||||
|
*/
|
||||||
|
function CashFlowAccountsActionsBar({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withCashflowAccountsTableActions
|
||||||
|
setCashflowAccountsTableState,
|
||||||
|
}) {
|
||||||
|
const { refresh } = useRefreshCashflowAccounts();
|
||||||
|
|
||||||
|
// Handle refresh button click.
|
||||||
|
const handleRefreshBtnClick = () => {
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
// Handle add bank account.
|
||||||
|
const handleAddBankAccount = () => {
|
||||||
|
openDialog('account-form', {
|
||||||
|
action: 'NEW_ACCOUNT_DEFINED_TYPE',
|
||||||
|
accountType: 'cash',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Handle add cash account.
|
||||||
|
const handleAddCashAccount = () => {
|
||||||
|
openDialog('account-form', {
|
||||||
|
action: 'NEW_ACCOUNT_DEFINED_TYPE',
|
||||||
|
accountType: 'bank',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Handle inactive switch changing.
|
||||||
|
const handleInactiveSwitchChange = (event) => {
|
||||||
|
const checked = event.target.checked;
|
||||||
|
setCashflowAccountsTableState({ inactiveMode: checked });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardActionsBar>
|
||||||
|
<NavbarGroup>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon={'plus-24'} iconSize={20} />}
|
||||||
|
text={<T id={'cash_flow.label.add_cash_account'} />}
|
||||||
|
onClick={handleAddBankAccount}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon={'plus-24'} iconSize={20} />}
|
||||||
|
text={<T id={'cash_flow.label.add_bank_account'} />}
|
||||||
|
onClick={handleAddCashAccount}
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="print-16" iconSize={16} />}
|
||||||
|
text={<T id={'print'} />}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||||
|
text={<T id={'export'} />}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||||
|
text={<T id={'import'} />}
|
||||||
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
labelElement={<T id={'inactive'} />}
|
||||||
|
defaultChecked={false}
|
||||||
|
onChange={handleInactiveSwitchChange}
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
<NavbarGroup align={Alignment.RIGHT}>
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon="refresh-16" iconSize={14} />}
|
||||||
|
onClick={handleRefreshBtnClick}
|
||||||
|
/>
|
||||||
|
</NavbarGroup>
|
||||||
|
</DashboardActionsBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withCashflowAccountsTableActions,
|
||||||
|
)(CashFlowAccountsActionsBar);
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { DataTable, TableFastCell } from 'components';
|
||||||
|
import { TABLES } from 'common/tables';
|
||||||
|
|
||||||
|
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
|
||||||
|
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||||
|
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||||
|
import withSettings from '../../Settings/withSettings';
|
||||||
|
|
||||||
|
import { useMemorizedColumnsWidths } from '../../../hooks';
|
||||||
|
import { useCashFlowAccountsContext } from './CashFlowAccountsProvider';
|
||||||
|
import { useCashFlowAccountsTableColumns } from './components';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cash flow accounts data table.
|
||||||
|
*/
|
||||||
|
function CashFlowAccountsDataTable({
|
||||||
|
// #withSettings
|
||||||
|
cashflowTableSize,
|
||||||
|
}) {
|
||||||
|
// Retrieve list context.
|
||||||
|
const {
|
||||||
|
cashflowAccounts,
|
||||||
|
isCashFlowAccountsFetching,
|
||||||
|
isCashFlowAccountsLoading,
|
||||||
|
} = useCashFlowAccountsContext();
|
||||||
|
|
||||||
|
// Retrieve table columns.
|
||||||
|
const columns = useCashFlowAccountsTableColumns();
|
||||||
|
|
||||||
|
// Local storage memorizing columns widths.
|
||||||
|
const [initialColumnsWidths, , handleColumnResizing] =
|
||||||
|
useMemorizedColumnsWidths(TABLES.CASHFLOW_ACCOUNTS);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
noInitialFetch={true}
|
||||||
|
columns={columns}
|
||||||
|
data={cashflowAccounts}
|
||||||
|
selectionColumn={false}
|
||||||
|
sticky={true}
|
||||||
|
loading={isCashFlowAccountsLoading}
|
||||||
|
headerLoading={isCashFlowAccountsLoading}
|
||||||
|
progressBarLoading={isCashFlowAccountsFetching}
|
||||||
|
expandColumnSpace={1}
|
||||||
|
expandToggleColumn={2}
|
||||||
|
selectionColumnWidth={45}
|
||||||
|
TableCellRenderer={TableFastCell}
|
||||||
|
|
||||||
|
TableLoadingRenderer={TableSkeletonRows}
|
||||||
|
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||||
|
initialColumnsWidths={initialColumnsWidths}
|
||||||
|
onColumnResizing={handleColumnResizing}
|
||||||
|
size={cashflowTableSize}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withSettings(({ cashflowSettings }) => ({
|
||||||
|
cashflowTableSize: cashflowSettings?.tableSize,
|
||||||
|
})),
|
||||||
|
)(CashFlowAccountsDataTable);
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { compose } from 'lodash/fp';
|
||||||
|
|
||||||
|
import 'style/pages/CashFlow/CashFlowAccounts/List.scss';
|
||||||
|
|
||||||
|
import { DashboardPageContent } from 'components';
|
||||||
|
import { CashFlowAccountsProvider } from './CashFlowAccountsProvider';
|
||||||
|
|
||||||
|
import CashFlowAccountsActionsBar from './CashFlowAccountsActionsBar';
|
||||||
|
import CashflowAccountsGrid from './CashflowAccountsGrid';
|
||||||
|
|
||||||
|
import withCashflowAccounts from '../AccountTransactions/withCashflowAccounts';
|
||||||
|
import withCashflowAccountsTableActions from '../AccountTransactions/withCashflowAccountsTableActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashflow accounts list.
|
||||||
|
*/
|
||||||
|
function CashFlowAccountsList({
|
||||||
|
// #withCashflowAccounts
|
||||||
|
cashflowAccountsTableState,
|
||||||
|
|
||||||
|
// #withCashflowAccountsTableActions
|
||||||
|
resetCashflowAccountsTableState,
|
||||||
|
}) {
|
||||||
|
// Resets the cashflow accounts table state.
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
resetCashflowAccountsTableState();
|
||||||
|
},
|
||||||
|
[resetCashflowAccountsTableState],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CashFlowAccountsProvider tableState={cashflowAccountsTableState}>
|
||||||
|
<CashFlowAccountsActionsBar />
|
||||||
|
|
||||||
|
<DashboardPageContent>
|
||||||
|
<CashflowAccountsGrid />
|
||||||
|
</DashboardPageContent>
|
||||||
|
</CashFlowAccountsProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withCashflowAccounts(({ cashflowAccountsTableState }) => ({
|
||||||
|
cashflowAccountsTableState,
|
||||||
|
})),
|
||||||
|
withCashflowAccountsTableActions,
|
||||||
|
)(CashFlowAccountsList);
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
|
||||||
|
import { useCashflowAccounts } from 'hooks/query';
|
||||||
|
|
||||||
|
import { transformAccountsStateToQuery } from './utils';
|
||||||
|
|
||||||
|
const CashFlowAccountsContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cash Flow data provider.
|
||||||
|
*/
|
||||||
|
function CashFlowAccountsProvider({ tableState, ...props }) {
|
||||||
|
const query = transformAccountsStateToQuery(tableState);
|
||||||
|
|
||||||
|
// Fetch cash flow list .
|
||||||
|
const {
|
||||||
|
data: cashflowAccounts,
|
||||||
|
isFetching: isCashFlowAccountsFetching,
|
||||||
|
isLoading: isCashFlowAccountsLoading,
|
||||||
|
} = useCashflowAccounts(query, { keepPreviousData: true });
|
||||||
|
|
||||||
|
// Provider payload.
|
||||||
|
const provider = {
|
||||||
|
cashflowAccounts,
|
||||||
|
isCashFlowAccountsFetching,
|
||||||
|
isCashFlowAccountsLoading,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider name={'cashflow-accounts'}>
|
||||||
|
<CashFlowAccountsContext.Provider value={provider} {...props} />
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useCashFlowAccountsContext = () =>
|
||||||
|
React.useContext(CashFlowAccountsContext);
|
||||||
|
|
||||||
|
export { CashFlowAccountsProvider, useCashFlowAccountsContext };
|
||||||
298
src/containers/CashFlow/CashFlowAccounts/CashflowAccountsGrid.js
Normal file
298
src/containers/CashFlow/CashFlowAccounts/CashflowAccountsGrid.js
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { isNull, isEmpty } from 'lodash';
|
||||||
|
import { compose, curry } from 'lodash/fp';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { ContextMenu2 } from '@blueprintjs/popover2';
|
||||||
|
import { Menu, MenuItem, MenuDivider, Intent } from '@blueprintjs/core';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BankAccountsList,
|
||||||
|
BankAccount,
|
||||||
|
If,
|
||||||
|
Icon,
|
||||||
|
T,
|
||||||
|
} from '../../../components';
|
||||||
|
|
||||||
|
import { useCashFlowAccountsContext } from './CashFlowAccountsProvider';
|
||||||
|
|
||||||
|
import withDrawerActions from '../../Drawer/withDrawerActions';
|
||||||
|
import withAlertsActions from '../../Alert/withAlertActions';
|
||||||
|
import withDialogActions from '../../Dialog/withDialogActions';
|
||||||
|
|
||||||
|
import { safeCallback } from 'utils';
|
||||||
|
import { addMoneyIn, addMoneyOut } from 'common/cashflowOptions';
|
||||||
|
|
||||||
|
const CASHFLOW_SKELETON_N = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashflow accounts skeleton for loading state.
|
||||||
|
*/
|
||||||
|
function CashflowAccountsSkeleton() {
|
||||||
|
return [...Array(CASHFLOW_SKELETON_N)].map((e, i) => (
|
||||||
|
<BankAccount
|
||||||
|
title={'XXXXX'}
|
||||||
|
code={'XXXXX'}
|
||||||
|
balance={'XXXXXX'}
|
||||||
|
cash={'cash'}
|
||||||
|
loading={true}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashflow bank account.
|
||||||
|
*/
|
||||||
|
function CashflowBankAccount({
|
||||||
|
// #withAlertsDialog
|
||||||
|
openAlert,
|
||||||
|
|
||||||
|
// #withDial
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
|
||||||
|
account,
|
||||||
|
}) {
|
||||||
|
// Handle view detail account.
|
||||||
|
const handleViewClick = () => {
|
||||||
|
openDrawer('account-drawer', { accountId: account.id });
|
||||||
|
};
|
||||||
|
// Handle delete action account.
|
||||||
|
const handleDeleteClick = () => {
|
||||||
|
openAlert('account-delete', { accountId: account.id });
|
||||||
|
};
|
||||||
|
// Handle inactivate action account.
|
||||||
|
const handleInactivateClick = () => {
|
||||||
|
openAlert('account-inactivate', { accountId: account.id });
|
||||||
|
};
|
||||||
|
// Handle activate action account.
|
||||||
|
const handleActivateClick = () => {
|
||||||
|
openAlert('account-activate', { accountId: account.id });
|
||||||
|
};
|
||||||
|
// Handle edit account action.
|
||||||
|
const handleEditAccount = () => {
|
||||||
|
openDialog('account-form', { action: 'edit', id: account.id });
|
||||||
|
};
|
||||||
|
// Handle money in menu item actions.
|
||||||
|
const handleMoneyInClick = (transactionType) => {
|
||||||
|
openDialog('money-in', {
|
||||||
|
account_type: transactionType,
|
||||||
|
account_id: account.id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Handle money out menu item actions.
|
||||||
|
const handleMoneyOutClick = (transactionType) => {
|
||||||
|
openDialog('money-out', {
|
||||||
|
account_type: transactionType,
|
||||||
|
account_id: account.id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextMenu2
|
||||||
|
content={
|
||||||
|
<CashflowAccountContextMenu
|
||||||
|
account={account}
|
||||||
|
onViewClick={handleViewClick}
|
||||||
|
onDeleteClick={handleDeleteClick}
|
||||||
|
onActivateClick={handleActivateClick}
|
||||||
|
onInactivateClick={handleInactivateClick}
|
||||||
|
onEditClick={handleEditAccount}
|
||||||
|
onMoneyInClick={handleMoneyInClick}
|
||||||
|
onMoneyOutClick={handleMoneyOutClick}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CashflowAccountAnchor
|
||||||
|
to={`/cashflow-accounts/${account.id}/transactions`}
|
||||||
|
>
|
||||||
|
<BankAccount
|
||||||
|
title={account.name}
|
||||||
|
code={account.code}
|
||||||
|
balance={!isNull(account.amount) ? account.formatted_amount : '-'}
|
||||||
|
type={account.account_type}
|
||||||
|
updatedBeforeText={getUpdatedBeforeText(account.createdAt)}
|
||||||
|
/>
|
||||||
|
</CashflowAccountAnchor>
|
||||||
|
</ContextMenu2>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CashflowBankAccountEnhanced = compose(
|
||||||
|
withAlertsActions,
|
||||||
|
withDrawerActions,
|
||||||
|
withDialogActions,
|
||||||
|
)(CashflowBankAccount);
|
||||||
|
|
||||||
|
function getUpdatedBeforeText(createdAt) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashflow accounts grid items.
|
||||||
|
*/
|
||||||
|
function CashflowAccountsGridItems({ accounts }) {
|
||||||
|
return accounts.map((account) => (
|
||||||
|
<CashflowBankAccountEnhanced account={account} />
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashflow accounts empty state.
|
||||||
|
*/
|
||||||
|
function CashflowAccountsEmptyState() {
|
||||||
|
return (
|
||||||
|
<AccountsEmptyStateBase>
|
||||||
|
<AccountsEmptyStateTitle>
|
||||||
|
<T id={'cash_flow.accounts.no_results'} />
|
||||||
|
</AccountsEmptyStateTitle>
|
||||||
|
</AccountsEmptyStateBase>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashflow accounts grid.
|
||||||
|
*/
|
||||||
|
export default function CashflowAccountsGrid() {
|
||||||
|
// Retrieve list context.
|
||||||
|
const { cashflowAccounts, isCashFlowAccountsLoading } =
|
||||||
|
useCashFlowAccountsContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CashflowAccountsGridWrap>
|
||||||
|
<BankAccountsList>
|
||||||
|
{isCashFlowAccountsLoading ? (
|
||||||
|
<CashflowAccountsSkeleton />
|
||||||
|
) : isEmpty(cashflowAccounts) ? (
|
||||||
|
<CashflowAccountsEmptyState />
|
||||||
|
) : (
|
||||||
|
<CashflowAccountsGridItems accounts={cashflowAccounts} />
|
||||||
|
)}
|
||||||
|
</BankAccountsList>
|
||||||
|
</CashflowAccountsGridWrap>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashflow account money out context menu.
|
||||||
|
*/
|
||||||
|
function CashflowAccountMoneyInContextMenu({ onClick }) {
|
||||||
|
const handleItemClick = curry((transactionType, event) => {
|
||||||
|
onClick && onClick(transactionType, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
return addMoneyIn.map((option) => (
|
||||||
|
<MenuItem text={option.name} onClick={handleItemClick(option.value)} />
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashflow account money in context menu.
|
||||||
|
*/
|
||||||
|
function CashflowAccountMoneyOutContextMenu({ onClick }) {
|
||||||
|
const handleItemClick = curry((transactionType, event) => {
|
||||||
|
onClick && onClick(transactionType, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
return addMoneyOut.map((option) => (
|
||||||
|
<MenuItem text={option.name} onClick={handleItemClick(option.value)} />
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashflow account context menu.
|
||||||
|
*/
|
||||||
|
function CashflowAccountContextMenu({
|
||||||
|
account,
|
||||||
|
onViewClick,
|
||||||
|
onEditClick,
|
||||||
|
onInactivateClick,
|
||||||
|
onActivateClick,
|
||||||
|
onDeleteClick,
|
||||||
|
onMoneyInClick,
|
||||||
|
onMoneyOutClick,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="reader-18" />}
|
||||||
|
text={intl.get('view_details')}
|
||||||
|
onClick={safeCallback(onViewClick)}
|
||||||
|
/>
|
||||||
|
<MenuDivider />
|
||||||
|
<MenuItem
|
||||||
|
text={<T id={'cash_flow_money_in'} />}
|
||||||
|
icon={<Icon icon={'arrow-downward'} iconSize={16} />}
|
||||||
|
>
|
||||||
|
<CashflowAccountMoneyInContextMenu onClick={onMoneyInClick} />
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
text={<T id={'cash_flow_money_out'} />}
|
||||||
|
icon={<Icon icon={'arrow-upward'} iconSize={16} />}
|
||||||
|
>
|
||||||
|
<CashflowAccountMoneyOutContextMenu onClick={onMoneyOutClick} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuDivider />
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="pen-18" />}
|
||||||
|
text={intl.get('edit_account')}
|
||||||
|
onClick={safeCallback(onEditClick)}
|
||||||
|
/>
|
||||||
|
<MenuDivider />
|
||||||
|
<If condition={account.active}>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('inactivate_account')}
|
||||||
|
icon={<Icon icon="pause-16" iconSize={16} />}
|
||||||
|
onClick={safeCallback(onInactivateClick)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
<If condition={!account.active}>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('activate_account')}
|
||||||
|
icon={<Icon icon="play-16" iconSize={16} />}
|
||||||
|
onClick={safeCallback(onActivateClick)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('delete_account')}
|
||||||
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={safeCallback(onDeleteClick)}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CashflowAccountAnchor = styled(Link)`
|
||||||
|
&,
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CashflowAccountsGridWrap = styled.div`
|
||||||
|
margin: 30px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CashflowBankAccountWrap = styled.div``;
|
||||||
|
|
||||||
|
const AccountsEmptyStateBase = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
margin: 2rem 0;
|
||||||
|
`;
|
||||||
|
const AccountsEmptyStateTitle = styled.h1`
|
||||||
|
font-size: 16px;
|
||||||
|
color: #626b76;
|
||||||
|
opacity: 0.8;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-weight: 500;
|
||||||
|
`;
|
||||||
92
src/containers/CashFlow/CashFlowAccounts/components.js
Normal file
92
src/containers/CashFlow/CashFlowAccounts/components.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Tag } from '@blueprintjs/core';
|
||||||
|
import { isBlank } from 'utils';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account code accessor.
|
||||||
|
*/
|
||||||
|
export const AccountCodeAccessor = (row) =>
|
||||||
|
!isBlank(row.code) ? (
|
||||||
|
<Tag minimal={true} round={true} intent={Intent.NONE}>
|
||||||
|
{row.code}
|
||||||
|
</Tag>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Balance cell.
|
||||||
|
*/
|
||||||
|
export const BalanceCell = ({ cell }) => {
|
||||||
|
const account = cell.row.original;
|
||||||
|
|
||||||
|
return account.amount !== null ? (
|
||||||
|
<span>{account.formatted_amount}</span>
|
||||||
|
) : (
|
||||||
|
<span class="placeholder">—</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account cell.
|
||||||
|
*/
|
||||||
|
const AccountCell = ({ row }) => {
|
||||||
|
const account = row.original;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>X</div>
|
||||||
|
<Link to={`/account/${account.id}/transactions`}>{account.name}</Link>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve Cash flow table columns.
|
||||||
|
*/
|
||||||
|
export function useCashFlowAccountsTableColumns() {
|
||||||
|
return React.useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: 'name',
|
||||||
|
Header: intl.get('account_name'),
|
||||||
|
accessor: 'name',
|
||||||
|
Cell: AccountCell,
|
||||||
|
className: 'account_name',
|
||||||
|
width: 200,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'code',
|
||||||
|
Header: intl.get('code'),
|
||||||
|
accessor: 'code',
|
||||||
|
className: 'code',
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'type',
|
||||||
|
Header: intl.get('type'),
|
||||||
|
accessor: 'account_type_label',
|
||||||
|
className: 'type',
|
||||||
|
width: 140,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'currency',
|
||||||
|
Header: intl.get('currency'),
|
||||||
|
accessor: 'currency_code',
|
||||||
|
width: 75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'balance',
|
||||||
|
Header: intl.get('balance'),
|
||||||
|
accessor: 'amount',
|
||||||
|
className: 'balance',
|
||||||
|
Cell: BalanceCell,
|
||||||
|
width: 150,
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
11
src/containers/CashFlow/CashFlowAccounts/utils.js
Normal file
11
src/containers/CashFlow/CashFlowAccounts/utils.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { transformTableStateToQuery } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the table state to list query.
|
||||||
|
*/
|
||||||
|
export const transformAccountsStateToQuery = (tableState) => {
|
||||||
|
return {
|
||||||
|
...transformTableStateToQuery(tableState),
|
||||||
|
inactive_mode: tableState.inactiveMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CustomerDeleteAlert from 'containers/Alerts/Customers/CustomerDeleteAlert';
|
|
||||||
import ContactActivateAlert from '../../containers/Alerts/Contacts/ContactActivateAlert';
|
const CustomerDeleteAlert = React.lazy(() =>
|
||||||
import ContactInactivateAlert from '../../containers/Alerts/Contacts/ContactInactivateAlert';
|
import('../Alerts/Customers/CustomerDeleteAlert'),
|
||||||
|
);
|
||||||
|
const ContactActivateAlert = React.lazy(() =>
|
||||||
|
import('../Alerts/Contacts/ContactActivateAlert'),
|
||||||
|
);
|
||||||
|
const ContactInactivateAlert = React.lazy(() =>
|
||||||
|
import('../Alerts/Contacts/ContactInactivateAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customers alert.
|
* Customers alert.
|
||||||
*/
|
*/
|
||||||
export default function ItemsAlerts() {
|
export default [
|
||||||
return (
|
{ name: 'customer-delete', component: CustomerDeleteAlert },
|
||||||
<div>
|
{ name: 'contact-activate', component: ContactActivateAlert },
|
||||||
<CustomerDeleteAlert name={'customer-delete'} />
|
{ name: 'contact-inactivate', component: ContactInactivateAlert },
|
||||||
<ContactActivateAlert name={'contact-activate'} />
|
];
|
||||||
<ContactInactivateAlert name={'contact-inactivate'} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { DashboardPageContent } from 'components';
|
|||||||
import CustomersActionsBar from './CustomersActionsBar';
|
import CustomersActionsBar from './CustomersActionsBar';
|
||||||
import CustomersViewsTabs from './CustomersViewsTabs';
|
import CustomersViewsTabs from './CustomersViewsTabs';
|
||||||
import CustomersTable from './CustomersTable';
|
import CustomersTable from './CustomersTable';
|
||||||
import CustomersAlerts from 'containers/Customers/CustomersAlerts';
|
|
||||||
import { CustomersListProvider } from './CustomersListProvider';
|
import { CustomersListProvider } from './CustomersListProvider';
|
||||||
|
|
||||||
import withCustomers from './withCustomers';
|
import withCustomers from './withCustomers';
|
||||||
@@ -45,7 +44,6 @@ function CustomersList({
|
|||||||
<CustomersViewsTabs />
|
<CustomersViewsTabs />
|
||||||
<CustomersTable />
|
<CustomersTable />
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
<CustomersAlerts />
|
|
||||||
</CustomersListProvider>
|
</CustomersListProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function AccountDialogContent({
|
|||||||
parentAccountId,
|
parentAccountId,
|
||||||
accountType,
|
accountType,
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountDialogProvider
|
<AccountDialogProvider
|
||||||
dialogName={dialogName}
|
dialogName={dialogName}
|
||||||
|
|||||||
@@ -59,7 +59,11 @@ function AccountFormDialogFields({
|
|||||||
onTypeSelected={(accountType) => {
|
onTypeSelected={(accountType) => {
|
||||||
form.setFieldValue('account_type', accountType.key);
|
form.setFieldValue('account_type', accountType.key);
|
||||||
}}
|
}}
|
||||||
disabled={action === 'edit' || action === 'new_child'}
|
disabled={
|
||||||
|
action === 'edit' ||
|
||||||
|
action === 'new_child' ||
|
||||||
|
action === 'NEW_ACCOUNT_DEFINED_TYPE'
|
||||||
|
}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
/>
|
/>
|
||||||
@@ -172,7 +176,11 @@ function AccountFormDialogFields({
|
|||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button disabled={isSubmitting} onClick={onClose} style={{ minWidth: '75px' }}>
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onClick={onClose}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
>
|
||||||
<T id={'close'} />
|
<T id={'close'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
export const transformApiErrors = (errors) => {
|
export const transformApiErrors = (errors) => {
|
||||||
const fields = {};
|
const fields = {};
|
||||||
@@ -11,15 +12,57 @@ export const transformApiErrors = (errors) => {
|
|||||||
return fields;
|
return fields;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformAccountToForm = (account, {
|
/**
|
||||||
action,
|
* Payload transformer in account edit mode.
|
||||||
parentAccountId,
|
*/
|
||||||
accountType
|
function transformEditMode(payload) {
|
||||||
}) => {
|
return {
|
||||||
|
parent_account_id: payload.parentAccountId || '',
|
||||||
|
account_type: payload.accountType || '',
|
||||||
|
subaccount: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload transformer in new account with defined type.
|
||||||
|
*/
|
||||||
|
function transformNewAccountDefinedType(payload) {
|
||||||
|
return {
|
||||||
|
account_type: payload.accountType || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merged the fetched account with transformed payload.
|
||||||
|
*/
|
||||||
|
const mergeWithAccount = R.curry((transformed, account) => {
|
||||||
return {
|
return {
|
||||||
parent_account_id: action === 'new_child' ? parentAccountId : '',
|
|
||||||
account_type: action === 'new_child'? accountType : '',
|
|
||||||
subaccount: action === 'new_child' ? true : false,
|
|
||||||
...account,
|
...account,
|
||||||
}
|
...transformed,
|
||||||
}
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defined payload transformers.
|
||||||
|
*/
|
||||||
|
function getConditions() {
|
||||||
|
return [
|
||||||
|
['edit', transformEditMode],
|
||||||
|
['NEW_ACCOUNT_DEFINED_TYPE', transformNewAccountDefinedType],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the given payload to account form initial values.
|
||||||
|
*/
|
||||||
|
export const transformAccountToForm = (account, payload) => {
|
||||||
|
const conditions = getConditions();
|
||||||
|
|
||||||
|
const results = conditions.map((condition) => {
|
||||||
|
return [
|
||||||
|
condition[0] === payload.action ? R.T : R.F,
|
||||||
|
mergeWithAccount(condition[1](payload)),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return R.cond(results)(account);
|
||||||
|
};
|
||||||
|
|||||||
20
src/containers/Dialogs/BadDebtDialog/BadDebtDialogContent.js
Normal file
20
src/containers/Dialogs/BadDebtDialog/BadDebtDialogContent.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import 'style/pages/BadDebt/BadDebtDialog.scss';
|
||||||
|
import { BadDebtFormProvider } from './BadDebtFormProvider';
|
||||||
|
import BadDebtForm from './BadDebtForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt dialog content.
|
||||||
|
*/
|
||||||
|
export default function BadDebtDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
invoice,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<BadDebtFormProvider invoiceId={invoice} dialogName={dialogName}>
|
||||||
|
<BadDebtForm />
|
||||||
|
</BadDebtFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
86
src/containers/Dialogs/BadDebtDialog/BadDebtForm.js
Normal file
86
src/containers/Dialogs/BadDebtDialog/BadDebtForm.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import { CreateBadDebtFormSchema } from './BadDebtForm.schema';
|
||||||
|
import { transformErrors } from './utils';
|
||||||
|
|
||||||
|
import BadDebtFormContent from './BadDebtFormContent';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||||
|
|
||||||
|
import { useBadDebtContext } from './BadDebtFormProvider';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const defaultInitialValues = {
|
||||||
|
expense_account_id: '',
|
||||||
|
reason: '',
|
||||||
|
amount: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
function BadDebtForm({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
|
||||||
|
// #withCurrentOrganization
|
||||||
|
organization: { base_currency },
|
||||||
|
}) {
|
||||||
|
const { invoice, dialogName, createBadDebtMutate, cancelBadDebtMutate } =
|
||||||
|
useBadDebtContext();
|
||||||
|
|
||||||
|
// Initial form values
|
||||||
|
const initialValues = {
|
||||||
|
...defaultInitialValues,
|
||||||
|
currency_code: base_currency,
|
||||||
|
amount: invoice.due_amount,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
const form = {
|
||||||
|
...omit(values, ['currency_code']),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle request response success.
|
||||||
|
const onSuccess = (response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('bad_debt.dialog.success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle request response errors.
|
||||||
|
const onError = ({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
if (errors) {
|
||||||
|
transformErrors(errors, { setErrors });
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
createBadDebtMutate([invoice.id, form]).then(onSuccess).catch(onError);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateBadDebtFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
component={BadDebtFormContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withCurrentOrganization(),
|
||||||
|
)(BadDebtForm);
|
||||||
17
src/containers/Dialogs/BadDebtDialog/BadDebtForm.schema.js
Normal file
17
src/containers/Dialogs/BadDebtDialog/BadDebtForm.schema.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
expense_account_id: Yup.number()
|
||||||
|
.required()
|
||||||
|
.label(intl.get('expense_account_id')),
|
||||||
|
amount: Yup.number().required().label(intl.get('amount')),
|
||||||
|
reason: Yup.string()
|
||||||
|
.required()
|
||||||
|
.min(3)
|
||||||
|
.max(DATATYPES_LENGTH.TEXT)
|
||||||
|
.label(intl.get('reason')),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateBadDebtFormSchema = Schema;
|
||||||
17
src/containers/Dialogs/BadDebtDialog/BadDebtFormContent.js
Normal file
17
src/containers/Dialogs/BadDebtDialog/BadDebtFormContent.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
|
||||||
|
import BadDebtFormFields from './BadDebtFormFields';
|
||||||
|
import BadDebtFormFloatingActions from './BadDebtFormFloatingActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt form content.
|
||||||
|
*/
|
||||||
|
export default function BadDebtFormContent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<BadDebtFormFields />
|
||||||
|
<BadDebtFormFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
121
src/containers/Dialogs/BadDebtDialog/BadDebtFormFields.js
Normal file
121
src/containers/Dialogs/BadDebtDialog/BadDebtFormFields.js
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, ErrorMessage } from 'formik';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import {
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
TextArea,
|
||||||
|
ControlGroup,
|
||||||
|
Callout,
|
||||||
|
Intent,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { ACCOUNT_TYPE } from 'common/accountTypes';
|
||||||
|
import { inputIntent } from 'utils';
|
||||||
|
import {
|
||||||
|
AccountsSuggestField,
|
||||||
|
InputPrependText,
|
||||||
|
MoneyInputGroup,
|
||||||
|
FieldRequiredHint,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import { useBadDebtContext } from './BadDebtFormProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt form fields.
|
||||||
|
*/
|
||||||
|
function BadDebtFormFields() {
|
||||||
|
const amountfieldRef = useAutofocus();
|
||||||
|
|
||||||
|
const { accounts } = useBadDebtContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<Callout intent={Intent.PRIMARY}>
|
||||||
|
<p>
|
||||||
|
<T id={'bad_debt.dialog.header_note'} />
|
||||||
|
</p>
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
{/*------------ Written-off amount -----------*/}
|
||||||
|
<FastField name={'amount'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'bad_debt.dialog.written_off_amount'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
className={classNames('form-group--amount', CLASSES.FILL)}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="amount" />}
|
||||||
|
>
|
||||||
|
<ControlGroup>
|
||||||
|
<InputPrependText text={values.currency_code} />
|
||||||
|
|
||||||
|
<MoneyInputGroup
|
||||||
|
value={value}
|
||||||
|
minimal={true}
|
||||||
|
onChange={(amount) => {
|
||||||
|
setFieldValue('amount', amount);
|
||||||
|
}}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
disabled={amountfieldRef}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
{/*------------ Expense account -----------*/}
|
||||||
|
<FastField name={'expense_account_id'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'expense_account_id'} />}
|
||||||
|
className={classNames(
|
||||||
|
'form-group--expense_account_id',
|
||||||
|
'form-group--select-list',
|
||||||
|
CLASSES.FILL,
|
||||||
|
)}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'expense_account_id'} />}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
selectedAccountId={value}
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('expense_account_id', id)
|
||||||
|
}
|
||||||
|
filterByTypes={[ACCOUNT_TYPE.EXPENSE]}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
{/*------------ reason -----------*/}
|
||||||
|
<FastField name={'reason'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'reason'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
className={'form-group--reason'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'reason'} />}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BadDebtFormFields;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
import { useBadDebtContext } from './BadDebtFormProvider';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad bebt form floating actions.
|
||||||
|
*/
|
||||||
|
function BadDebtFormFloatingActions({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
// bad debt invoice dialog context.
|
||||||
|
const { dialogName } = useBadDebtContext();
|
||||||
|
|
||||||
|
// Formik context.
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
// Handle close button click.
|
||||||
|
const handleCancelBtnClick = () => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
loading={isSubmitting}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{<T id={'save'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(BadDebtFormFloatingActions);
|
||||||
46
src/containers/Dialogs/BadDebtDialog/BadDebtFormProvider.js
Normal file
46
src/containers/Dialogs/BadDebtDialog/BadDebtFormProvider.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
import {
|
||||||
|
useAccounts,
|
||||||
|
useInvoice,
|
||||||
|
useCreateBadDebt,
|
||||||
|
useCancelBadDebt,
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
|
const BadDebtContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt provider.
|
||||||
|
*/
|
||||||
|
function BadDebtFormProvider({ invoiceId, dialogName, ...props }) {
|
||||||
|
// Handle fetch accounts data.
|
||||||
|
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
|
||||||
|
|
||||||
|
// Handle fetch invoice data.
|
||||||
|
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
|
||||||
|
enabled: !!invoiceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create and cancel bad debt mutations.
|
||||||
|
const { mutateAsync: createBadDebtMutate } = useCreateBadDebt();
|
||||||
|
|
||||||
|
// State provider.
|
||||||
|
const provider = {
|
||||||
|
accounts,
|
||||||
|
invoice,
|
||||||
|
invoiceId,
|
||||||
|
dialogName,
|
||||||
|
createBadDebtMutate,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent isLoading={isAccountsLoading || isInvoiceLoading}>
|
||||||
|
<BadDebtContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useBadDebtContext = () => React.useContext(BadDebtContext);
|
||||||
|
|
||||||
|
export { BadDebtFormProvider, useBadDebtContext };
|
||||||
29
src/containers/Dialogs/BadDebtDialog/index.js
Normal file
29
src/containers/Dialogs/BadDebtDialog/index.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import { Dialog, DialogSuspense } from 'components';
|
||||||
|
import withDialogRedux from 'components/DialogReduxConnect';
|
||||||
|
import { compose } from 'redux';
|
||||||
|
|
||||||
|
const BadDebtDialogContent = React.lazy(() => import('./BadDebtDialogContent'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bad debt dialog.
|
||||||
|
*/
|
||||||
|
function BadDebtDialog({ dialogName, payload: { invoiceId = null }, isOpen }) {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'bad_debt.dialog.bad_debt'} />}
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeJeyClose={true}
|
||||||
|
autoFocus={true}
|
||||||
|
className={'dialog--bad-debt'}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<BadDebtDialogContent dialogName={dialogName} invoice={invoiceId} />
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(withDialogRedux())(BadDebtDialog);
|
||||||
17
src/containers/Dialogs/BadDebtDialog/utils.js
Normal file
17
src/containers/Dialogs/BadDebtDialog/utils.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the response errors types.
|
||||||
|
*/
|
||||||
|
export const transformErrors = (errors, { setErrors }) => {
|
||||||
|
if (errors.some(({ type }) => type === 'SALE_INVOICE_ALREADY_WRITTEN_OFF')) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'SALE_INVOICE_ALREADY_WRITTEN_OFF',
|
||||||
|
// message: intl.get(''),
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -50,7 +50,6 @@ function InventoryAdjustmentFloatingActions({
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
|
||||||
loading={isSubmitting && !submitPayload.publish}
|
loading={isSubmitting && !submitPayload.publish}
|
||||||
style={{ minWidth: '75px' }}
|
style={{ minWidth: '75px' }}
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -61,7 +60,6 @@ function InventoryAdjustmentFloatingActions({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
disabled={isSubmitting}
|
|
||||||
loading={isSubmitting && submitPayload.publish}
|
loading={isSubmitting && submitPayload.publish}
|
||||||
style={{ minWidth: '75px' }}
|
style={{ minWidth: '75px' }}
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function InventoryAdjustmentForm({
|
|||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('the_make_adjustment_has_been_created_successfully'),
|
message: intl.get('the_adjustment_transaction_has_been_created_successfully'),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
23
src/containers/Dialogs/MoneyInDialog/MoneyInContentFields.js
Normal file
23
src/containers/Dialogs/MoneyInDialog/MoneyInContentFields.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import OwnerContributionFormFields from './OwnerContribution/OwnerContributionFormFields';
|
||||||
|
import OtherIncomeFormFields from './OtherIncome/OtherIncomeFormFields';
|
||||||
|
import TransferFromAccountFormFields from './TransferFromAccount/TransferFromAccountFormFields';
|
||||||
|
|
||||||
|
export default function MoneyInContentFields({ accountType }) {
|
||||||
|
const handleTransactionType = () => {
|
||||||
|
switch (accountType) {
|
||||||
|
case 'owner_contribution':
|
||||||
|
return <OwnerContributionFormFields />;
|
||||||
|
|
||||||
|
case 'other_income':
|
||||||
|
return <OtherIncomeFormFields />;
|
||||||
|
|
||||||
|
case 'transfer_from_account':
|
||||||
|
return <TransferFromAccountFormFields />;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <React.Fragment>{handleTransactionType()}</React.Fragment>;
|
||||||
|
}
|
||||||
24
src/containers/Dialogs/MoneyInDialog/MoneyInDialogContent.js
Normal file
24
src/containers/Dialogs/MoneyInDialog/MoneyInDialogContent.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { MoneyInDialogProvider } from './MoneyInDialogProvider';
|
||||||
|
import MoneyInForm from './MoneyInForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money in dialog content.
|
||||||
|
*/
|
||||||
|
export default function MoneyInDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
accountId,
|
||||||
|
accountType,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<MoneyInDialogProvider
|
||||||
|
accountId={accountId}
|
||||||
|
accountType={accountType}
|
||||||
|
dialogName={dialogName}
|
||||||
|
>
|
||||||
|
<MoneyInForm />
|
||||||
|
</MoneyInDialogProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
import {
|
||||||
|
useCreateCashflowTransaction,
|
||||||
|
useAccounts,
|
||||||
|
useCashflowAccounts,
|
||||||
|
useSettingCashFlow,
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
|
const MoneyInDialogContent = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money in dialog provider.
|
||||||
|
*/
|
||||||
|
function MoneyInDialogProvider({
|
||||||
|
accountId,
|
||||||
|
accountType,
|
||||||
|
dialogName,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
// Fetches accounts list.
|
||||||
|
const { isFetching: isAccountsLoading, data: accounts } = useAccounts();
|
||||||
|
|
||||||
|
// Fetch cash flow list .
|
||||||
|
const { data: cashflowAccounts, isLoading: isCashFlowAccountsLoading } =
|
||||||
|
useCashflowAccounts({}, { keepPreviousData: true });
|
||||||
|
|
||||||
|
const { mutateAsync: createCashflowTransactionMutate } =
|
||||||
|
useCreateCashflowTransaction();
|
||||||
|
|
||||||
|
// Handle fetching settings.
|
||||||
|
const { isLoading: isSettingsLoading } = useSettingCashFlow();
|
||||||
|
|
||||||
|
// Submit payload.
|
||||||
|
const [submitPayload, setSubmitPayload] = React.useState({});
|
||||||
|
|
||||||
|
// provider.
|
||||||
|
const provider = {
|
||||||
|
accounts,
|
||||||
|
accountId,
|
||||||
|
accountType,
|
||||||
|
isAccountsLoading,
|
||||||
|
|
||||||
|
cashflowAccounts,
|
||||||
|
|
||||||
|
submitPayload,
|
||||||
|
dialogName,
|
||||||
|
|
||||||
|
createCashflowTransactionMutate,
|
||||||
|
setSubmitPayload,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent
|
||||||
|
isLoading={
|
||||||
|
isAccountsLoading || isCashFlowAccountsLoading || isSettingsLoading
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MoneyInDialogContent.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useMoneyInDailogContext = () => React.useContext(MoneyInDialogContent);
|
||||||
|
|
||||||
|
export { MoneyInDialogProvider, useMoneyInDailogContext };
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money in floating actions.
|
||||||
|
*/
|
||||||
|
function MoneyInFloatingActions({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
// Formik context.
|
||||||
|
const { isSubmitting, submitForm } = useFormikContext();
|
||||||
|
// money in dialog context.
|
||||||
|
const { dialogName, setSubmitPayload, submitPayload } =
|
||||||
|
useMoneyInDailogContext();
|
||||||
|
|
||||||
|
// handle submit as draft button click.
|
||||||
|
const handleSubmitDraftBtnClick = (event) => {
|
||||||
|
setSubmitPayload({ publish: false });
|
||||||
|
submitForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle submit button click.
|
||||||
|
const handleSubmittBtnClick = (event) => {
|
||||||
|
setSubmitPayload({ publish: true });
|
||||||
|
submitForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle close button click.
|
||||||
|
const handleCloseBtnClick = (event) => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onClick={handleCloseBtnClick}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
>
|
||||||
|
<T id={'close'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
loading={isSubmitting && !submitPayload.publish}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
onClick={handleSubmitDraftBtnClick}
|
||||||
|
>
|
||||||
|
{<T id={'save_as_draft'} />}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
loading={isSubmitting && submitPayload.publish}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
onClick={handleSubmittBtnClick}
|
||||||
|
>
|
||||||
|
{<T id={'save_and_publish'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(MoneyInFloatingActions);
|
||||||
114
src/containers/Dialogs/MoneyInDialog/MoneyInForm.js
Normal file
114
src/containers/Dialogs/MoneyInDialog/MoneyInForm.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import 'style/pages/CashFlow/CashflowTransactionForm.scss';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import MoneyInFormContent from './MoneyInFormContent';
|
||||||
|
import { CreateMoneyInFormSchema } from './MoneyInForm.schema';
|
||||||
|
|
||||||
|
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
|
||||||
|
|
||||||
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||||
|
|
||||||
|
import { compose, transactionNumber } from 'utils';
|
||||||
|
|
||||||
|
const defaultInitialValues = {
|
||||||
|
date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
amount: '',
|
||||||
|
transaction_number: '',
|
||||||
|
transaction_type: '',
|
||||||
|
reference_no: '',
|
||||||
|
cashflow_account_id: '',
|
||||||
|
credit_account_id: '',
|
||||||
|
description: '',
|
||||||
|
published: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
function MoneyInForm({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
|
||||||
|
// #withCurrentOrganization
|
||||||
|
organization: { base_currency },
|
||||||
|
|
||||||
|
// #withSettings
|
||||||
|
transactionNextNumber,
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionIncrementMode,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
dialogName,
|
||||||
|
accountId,
|
||||||
|
accountType,
|
||||||
|
createCashflowTransactionMutate,
|
||||||
|
submitPayload,
|
||||||
|
} = useMoneyInDailogContext();
|
||||||
|
|
||||||
|
// transaction number.
|
||||||
|
const transactionNo = transactionNumber(
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initial form values.
|
||||||
|
const initialValues = {
|
||||||
|
...defaultInitialValues,
|
||||||
|
currency_code: base_currency,
|
||||||
|
transaction_type: accountType,
|
||||||
|
...(transactionIncrementMode && {
|
||||||
|
transaction_number: transactionNo,
|
||||||
|
}),
|
||||||
|
cashflow_account_id: accountId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
const form = {
|
||||||
|
...omit(values, ['currency_code']),
|
||||||
|
published: submitPayload.publish,
|
||||||
|
};
|
||||||
|
setSubmitting(true);
|
||||||
|
createCashflowTransactionMutate(form)
|
||||||
|
.then(() => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('cash_flow_transaction_success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setSubmitting(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateMoneyInFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
>
|
||||||
|
<MoneyInFormContent />
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withCurrentOrganization(),
|
||||||
|
withSettings(({ cashflowSetting }) => ({
|
||||||
|
transactionNextNumber: cashflowSetting?.nextNumber,
|
||||||
|
transactionNumberPrefix: cashflowSetting?.numberPrefix,
|
||||||
|
transactionIncrementMode: cashflowSetting?.autoIncrement,
|
||||||
|
})),
|
||||||
|
)(MoneyInForm);
|
||||||
20
src/containers/Dialogs/MoneyInDialog/MoneyInForm.schema.js
Normal file
20
src/containers/Dialogs/MoneyInDialog/MoneyInForm.schema.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
date: Yup.date().required().label(intl.get('date')),
|
||||||
|
amount: Yup.number().required().label(intl.get('amount')),
|
||||||
|
transaction_number: Yup.string(),
|
||||||
|
transaction_type: Yup.string().required(),
|
||||||
|
reference_no: Yup.string(),
|
||||||
|
credit_account_id: Yup.number().required(),
|
||||||
|
cashflow_account_id: Yup.string().required(),
|
||||||
|
description: Yup.string()
|
||||||
|
.min(3)
|
||||||
|
.max(DATATYPES_LENGTH.TEXT)
|
||||||
|
.label(intl.get('description')),
|
||||||
|
published: Yup.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateMoneyInFormSchema = Schema;
|
||||||
19
src/containers/Dialogs/MoneyInDialog/MoneyInFormContent.js
Normal file
19
src/containers/Dialogs/MoneyInDialog/MoneyInFormContent.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
|
||||||
|
import MoneyInFormFields from './MoneyInFormFields';
|
||||||
|
import MoneyInFormDialog from './MoneyInFormDialog';
|
||||||
|
import MoneyInFloatingActions from './MoneyInFloatingActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money In form content.
|
||||||
|
*/
|
||||||
|
export default function MoneyInFormContent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<MoneyInFormFields />
|
||||||
|
<MoneyInFormDialog />
|
||||||
|
<MoneyInFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
src/containers/Dialogs/MoneyInDialog/MoneyInFormDialog.js
Normal file
28
src/containers/Dialogs/MoneyInDialog/MoneyInFormDialog.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
|
import TransactionNumberDialog from '../../Dialogs/TransactionNumberDialog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moneny in / transaction number form dialog.
|
||||||
|
*/
|
||||||
|
export default function MoneyInFormDialog() {
|
||||||
|
const { setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
|
// Update the form once the transaction number form submit confirm.
|
||||||
|
const handleTransactionNumberFormConfirm = ({
|
||||||
|
incrementNumber,
|
||||||
|
manually,
|
||||||
|
}) => {
|
||||||
|
setFieldValue('transaction_number', incrementNumber || '');
|
||||||
|
setFieldValue('transaction_number_manually', manually);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<TransactionNumberDialog
|
||||||
|
dialogName={'transaction-number-form'}
|
||||||
|
onConfirm={handleTransactionNumberFormConfirm}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
src/containers/Dialogs/MoneyInDialog/MoneyInFormFields.js
Normal file
30
src/containers/Dialogs/MoneyInDialog/MoneyInFormFields.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { If } from 'components';
|
||||||
|
|
||||||
|
import MoneyInContentFields from './MoneyInContentFields';
|
||||||
|
import TransactionTypeFields from './TransactionTypeFields';
|
||||||
|
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money in form fields.
|
||||||
|
*/
|
||||||
|
function MoneyInFormFields() {
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
// Money in dialog context.
|
||||||
|
const { accountId } = useMoneyInDailogContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<If condition={!accountId}>
|
||||||
|
<TransactionTypeFields />
|
||||||
|
</If>
|
||||||
|
<MoneyInContentFields accountType={values.transaction_type} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MoneyInFormFields;
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
|
import {
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
InputGroup,
|
||||||
|
TextArea,
|
||||||
|
Position,
|
||||||
|
ControlGroup,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
AccountsSuggestField,
|
||||||
|
InputPrependText,
|
||||||
|
MoneyInputGroup,
|
||||||
|
FieldRequiredHint,
|
||||||
|
Icon,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
InputPrependButton,
|
||||||
|
} from 'components';
|
||||||
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import { ACCOUNT_TYPE } from 'common/accountTypes';
|
||||||
|
|
||||||
|
import {
|
||||||
|
inputIntent,
|
||||||
|
momentFormatter,
|
||||||
|
tansformDateValue,
|
||||||
|
handleDateChange,
|
||||||
|
compose,
|
||||||
|
} from 'utils';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
|
||||||
|
import { useObserveTransactionNoSettings } from '../utils';
|
||||||
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other income form fields.
|
||||||
|
*/
|
||||||
|
function OtherIncomeFormFields({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withSettings
|
||||||
|
transactionAutoIncrement,
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
}) {
|
||||||
|
// Money in dialog context.
|
||||||
|
const { accounts } = useMoneyInDailogContext();
|
||||||
|
|
||||||
|
const amountFieldRef = useAutofocus();
|
||||||
|
|
||||||
|
// Handle tranaction number changing.
|
||||||
|
const handleTransactionNumberChange = () => {
|
||||||
|
openDialog('transaction-number-form');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle transaction no. field blur.
|
||||||
|
const handleTransactionNoBlur = (form, field) => (event) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
|
||||||
|
if (field.value !== newValue && transactionAutoIncrement) {
|
||||||
|
openDialog('transaction-number-form', {
|
||||||
|
initialFormValues: {
|
||||||
|
manualTransactionNo: newValue,
|
||||||
|
incrementMode: 'manual-transaction',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Syncs transaction number settings with form.
|
||||||
|
useObserveTransactionNoSettings(
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Date -----------*/}
|
||||||
|
<FastField name={'date'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'date'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="date" />}
|
||||||
|
minimal={true}
|
||||||
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
|
>
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
onChange={handleDateChange((formattedDate) => {
|
||||||
|
form.setFieldValue('date', formattedDate);
|
||||||
|
})}
|
||||||
|
value={tansformDateValue(value)}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
minimal: true,
|
||||||
|
}}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Transaction number -----------*/}
|
||||||
|
<Field name={'transaction_number'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'transaction_number'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="transaction_number" />}
|
||||||
|
className={'form-group--transaction_number'}
|
||||||
|
>
|
||||||
|
<ControlGroup fill={true}>
|
||||||
|
<InputGroup
|
||||||
|
minimal={true}
|
||||||
|
value={field.value}
|
||||||
|
asyncControl={true}
|
||||||
|
onBlur={handleTransactionNoBlur(form, field)}
|
||||||
|
/>
|
||||||
|
<InputPrependButton
|
||||||
|
buttonProps={{
|
||||||
|
onClick: handleTransactionNumberChange,
|
||||||
|
icon: <Icon icon={'settings-18'} />,
|
||||||
|
}}
|
||||||
|
tooltip={true}
|
||||||
|
tooltipProps={{
|
||||||
|
content: (
|
||||||
|
<T
|
||||||
|
id={
|
||||||
|
'cash_flow.setting_your_auto_generated_transaction_number'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
position: Position.BOTTOM_LEFT,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/*------------ amount -----------*/}
|
||||||
|
<FastField name={'amount'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'amount'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="amount" />}
|
||||||
|
className={'form-group--amount'}
|
||||||
|
>
|
||||||
|
<ControlGroup>
|
||||||
|
<InputPrependText text={values.currency_code} />
|
||||||
|
|
||||||
|
<MoneyInputGroup
|
||||||
|
value={value}
|
||||||
|
minimal={true}
|
||||||
|
onChange={(amount) => {
|
||||||
|
setFieldValue('amount', amount);
|
||||||
|
}}
|
||||||
|
inputRef={(ref) => (amountFieldRef.current = ref)}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ other income account -----------*/}
|
||||||
|
<FastField name={'credit_account_id'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'cash_flow_transaction.other_income_account'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="credit_account_id" />}
|
||||||
|
className={'form-group--credit_account_id'}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('credit_account_id', id)
|
||||||
|
}
|
||||||
|
filterByTypes={[
|
||||||
|
ACCOUNT_TYPE.INCOME,
|
||||||
|
ACCOUNT_TYPE.OTHER_INCOME,
|
||||||
|
]}
|
||||||
|
inputProps={{
|
||||||
|
intent: inputIntent({ error, touched }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Reference -----------*/}
|
||||||
|
<FastField name={'reference_no'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'reference_no'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="reference_no" />}
|
||||||
|
className={'form-group--reference-no'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/*------------ description -----------*/}
|
||||||
|
<FastField name={'description'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--description'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'description'} />}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withSettings(({ cashflowSetting }) => ({
|
||||||
|
transactionAutoIncrement: cashflowSetting?.autoIncrement,
|
||||||
|
transactionNextNumber: cashflowSetting?.nextNumber,
|
||||||
|
transactionNumberPrefix: cashflowSetting?.numberPrefix,
|
||||||
|
})),
|
||||||
|
)(OtherIncomeFormFields);
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
|
import {
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
InputGroup,
|
||||||
|
TextArea,
|
||||||
|
Position,
|
||||||
|
ControlGroup,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
AccountsSuggestField,
|
||||||
|
InputPrependText,
|
||||||
|
MoneyInputGroup,
|
||||||
|
FieldRequiredHint,
|
||||||
|
Icon,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
InputPrependButton,
|
||||||
|
} from 'components';
|
||||||
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import { ACCOUNT_TYPE } from 'common/accountTypes';
|
||||||
|
import {
|
||||||
|
inputIntent,
|
||||||
|
momentFormatter,
|
||||||
|
tansformDateValue,
|
||||||
|
handleDateChange,
|
||||||
|
compose,
|
||||||
|
} from 'utils';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
|
||||||
|
import { useObserveTransactionNoSettings } from '../utils';
|
||||||
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
/**
|
||||||
|
* Owner contribution form fields.
|
||||||
|
*/
|
||||||
|
function OwnerContributionFormFields({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withSettings
|
||||||
|
transactionAutoIncrement,
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
}) {
|
||||||
|
// Money in dialog context.
|
||||||
|
const { accounts } = useMoneyInDailogContext();
|
||||||
|
|
||||||
|
const amountFieldRef = useAutofocus();
|
||||||
|
|
||||||
|
// Handle tranaction number changing.
|
||||||
|
const handleTransactionNumberChange = () => {
|
||||||
|
openDialog('transaction-number-form');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle transaction no. field blur.
|
||||||
|
const handleTransactionNoBlur = (form, field) => (event) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
|
||||||
|
if (field.value !== newValue && transactionAutoIncrement) {
|
||||||
|
openDialog('transaction-number-form', {
|
||||||
|
initialFormValues: {
|
||||||
|
manualTransactionNo: newValue,
|
||||||
|
incrementMode: 'manual-transaction',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Syncs transaction number settings with form.
|
||||||
|
useObserveTransactionNoSettings(
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Date -----------*/}
|
||||||
|
<FastField name={'date'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'date'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="date" />}
|
||||||
|
minimal={true}
|
||||||
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
|
>
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
onChange={handleDateChange((formattedDate) => {
|
||||||
|
form.setFieldValue('date', formattedDate);
|
||||||
|
})}
|
||||||
|
value={tansformDateValue(value)}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
minimal: true,
|
||||||
|
}}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Transaction number -----------*/}
|
||||||
|
<Field name={'transaction_number'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'transaction_number'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="transaction_number" />}
|
||||||
|
className={'form-group--transaction_number'}
|
||||||
|
>
|
||||||
|
<ControlGroup fill={true}>
|
||||||
|
<InputGroup
|
||||||
|
minimal={true}
|
||||||
|
value={field.value}
|
||||||
|
asyncControl={true}
|
||||||
|
onBlur={handleTransactionNoBlur(form, field)}
|
||||||
|
/>
|
||||||
|
<InputPrependButton
|
||||||
|
buttonProps={{
|
||||||
|
onClick: handleTransactionNumberChange,
|
||||||
|
icon: <Icon icon={'settings-18'} />,
|
||||||
|
}}
|
||||||
|
tooltip={true}
|
||||||
|
tooltipProps={{
|
||||||
|
content: (
|
||||||
|
<T
|
||||||
|
id={
|
||||||
|
'cash_flow.setting_your_auto_generated_transaction_number'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
position: Position.BOTTOM_LEFT,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/*------------ amount -----------*/}
|
||||||
|
<FastField name={'amount'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'amount'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="amount" />}
|
||||||
|
className={'form-group--amount'}
|
||||||
|
>
|
||||||
|
<ControlGroup>
|
||||||
|
<InputPrependText text={values.currency_code} />
|
||||||
|
|
||||||
|
<MoneyInputGroup
|
||||||
|
value={value}
|
||||||
|
minimal={true}
|
||||||
|
onChange={(amount) => {
|
||||||
|
setFieldValue('amount', amount);
|
||||||
|
}}
|
||||||
|
inputRef={(ref) => (amountFieldRef.current = ref)}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ equity account -----------*/}
|
||||||
|
<FastField name={'credit_account_id'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'cash_flow_transaction.label_equity_account'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="credit_account_id" />}
|
||||||
|
className={'form-group--credit_account_id'}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('credit_account_id', id)
|
||||||
|
}
|
||||||
|
filterByTypes={ACCOUNT_TYPE.EQUITY}
|
||||||
|
inputProps={{
|
||||||
|
intent: inputIntent({ error, touched }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Reference -----------*/}
|
||||||
|
<FastField name={'reference_no'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'reference_no'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="reference_no" />}
|
||||||
|
className={'form-group--reference-no'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/*------------ description -----------*/}
|
||||||
|
<FastField name={'description'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--description'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'description'} />}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withSettings(({ cashflowSetting }) => ({
|
||||||
|
transactionAutoIncrement: cashflowSetting?.autoIncrement,
|
||||||
|
transactionNextNumber: cashflowSetting?.nextNumber,
|
||||||
|
transactionNumberPrefix: cashflowSetting?.numberPrefix,
|
||||||
|
})),
|
||||||
|
)(OwnerContributionFormFields);
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
|
import { Classes, FormGroup } from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
AccountsSuggestField,
|
||||||
|
FieldRequiredHint,
|
||||||
|
ListSelect,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import { inputIntent } from 'utils';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { addMoneyIn } from '../../../common/cashflowOptions';
|
||||||
|
|
||||||
|
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction type fields.
|
||||||
|
*/
|
||||||
|
export default function TransactionTypeFields() {
|
||||||
|
// Money in dialog context.
|
||||||
|
const { cashflowAccounts } = useMoneyInDailogContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="trasnaction-type-fileds">
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Current account -----------*/}
|
||||||
|
<FastField name={'cashflow_account_id'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'cash_flow_transaction.label_current_account'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="cashflow_account_id" />}
|
||||||
|
minimal={true}
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.FILL,
|
||||||
|
'form-group--cashflow_account_id',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
accounts={cashflowAccounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('cashflow_account_id', id)
|
||||||
|
}
|
||||||
|
inputProps={{
|
||||||
|
intent: inputIntent({ error, touched }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
{/*------------ Transaction type -----------*/}
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
<Field name={'transaction_type'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'transaction_type'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
helperText={<ErrorMessage name="transaction_type" />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.FILL,
|
||||||
|
'form-group--transaction_type',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ListSelect
|
||||||
|
items={addMoneyIn}
|
||||||
|
onItemSelect={(type) => {
|
||||||
|
setFieldValue('transaction_type', type.value);
|
||||||
|
}}
|
||||||
|
filterable={false}
|
||||||
|
selectedItem={value}
|
||||||
|
selectedItemProp={'value'}
|
||||||
|
textProp={'name'}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
|
import {
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
InputGroup,
|
||||||
|
TextArea,
|
||||||
|
Position,
|
||||||
|
ControlGroup,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
AccountsSuggestField,
|
||||||
|
InputPrependText,
|
||||||
|
MoneyInputGroup,
|
||||||
|
FieldRequiredHint,
|
||||||
|
Icon,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
InputPrependButton,
|
||||||
|
} from 'components';
|
||||||
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import { ACCOUNT_TYPE } from 'common/accountTypes';
|
||||||
|
|
||||||
|
import {
|
||||||
|
inputIntent,
|
||||||
|
momentFormatter,
|
||||||
|
tansformDateValue,
|
||||||
|
handleDateChange,
|
||||||
|
compose,
|
||||||
|
} from 'utils';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
|
||||||
|
import { useObserveTransactionNoSettings } from '../utils';
|
||||||
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfer from account form fields.
|
||||||
|
*/
|
||||||
|
function TransferFromAccountFormFields({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withSettings
|
||||||
|
transactionAutoIncrement,
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
}) {
|
||||||
|
// Money in dialog context.
|
||||||
|
const { accounts } = useMoneyInDailogContext();
|
||||||
|
|
||||||
|
const amountFieldRef = useAutofocus();
|
||||||
|
|
||||||
|
// Handle tranaction number changing.
|
||||||
|
const handleTransactionNumberChange = () => {
|
||||||
|
openDialog('transaction-number-form');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle transaction no. field blur.
|
||||||
|
const handleTransactionNoBlur = (form, field) => (event) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
|
||||||
|
if (field.value !== newValue && transactionAutoIncrement) {
|
||||||
|
openDialog('transaction-number-form', {
|
||||||
|
initialFormValues: {
|
||||||
|
manualTransactionNo: newValue,
|
||||||
|
incrementMode: 'manual-transaction',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Syncs transaction number settings with form.
|
||||||
|
useObserveTransactionNoSettings(
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Date -----------*/}
|
||||||
|
<FastField name={'date'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'date'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="date" />}
|
||||||
|
minimal={true}
|
||||||
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
|
>
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
onChange={handleDateChange((formattedDate) => {
|
||||||
|
form.setFieldValue('date', formattedDate);
|
||||||
|
})}
|
||||||
|
value={tansformDateValue(value)}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
minimal: true,
|
||||||
|
}}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Transaction number -----------*/}
|
||||||
|
<Field name={'transaction_number'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'transaction_number'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="transaction_number" />}
|
||||||
|
className={'form-group--transaction_number'}
|
||||||
|
>
|
||||||
|
<ControlGroup fill={true}>
|
||||||
|
<InputGroup
|
||||||
|
minimal={true}
|
||||||
|
value={field.value}
|
||||||
|
asyncControl={true}
|
||||||
|
onBlur={handleTransactionNoBlur(form, field)}
|
||||||
|
/>
|
||||||
|
<InputPrependButton
|
||||||
|
buttonProps={{
|
||||||
|
onClick: handleTransactionNumberChange,
|
||||||
|
icon: <Icon icon={'settings-18'} />,
|
||||||
|
}}
|
||||||
|
tooltip={true}
|
||||||
|
tooltipProps={{
|
||||||
|
content: (
|
||||||
|
<T
|
||||||
|
id={
|
||||||
|
'cash_flow.setting_your_auto_generated_transaction_number'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
position: Position.BOTTOM_LEFT,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/*------------ amount -----------*/}
|
||||||
|
<FastField name={'amount'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'amount'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="amount" />}
|
||||||
|
className={'form-group--amount'}
|
||||||
|
>
|
||||||
|
<ControlGroup>
|
||||||
|
<InputPrependText text={values.currency_code} />
|
||||||
|
|
||||||
|
<MoneyInputGroup
|
||||||
|
value={value}
|
||||||
|
minimal={true}
|
||||||
|
onChange={(amount) => {
|
||||||
|
setFieldValue('amount', amount);
|
||||||
|
}}
|
||||||
|
inputRef={(ref) => (amountFieldRef.current = ref)}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ transfer from account -----------*/}
|
||||||
|
<FastField name={'credit_account_id'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={
|
||||||
|
<T id={'cash_flow_transaction.label_transfer_from_account'} />
|
||||||
|
}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="credit_account_id" />}
|
||||||
|
className={'form-group--credit_account_id'}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('credit_account_id', id)
|
||||||
|
}
|
||||||
|
filterByTypes={[
|
||||||
|
ACCOUNT_TYPE.CASH,
|
||||||
|
ACCOUNT_TYPE.BANK,
|
||||||
|
ACCOUNT_TYPE.CREDIT_CARD,
|
||||||
|
]}
|
||||||
|
inputProps={{
|
||||||
|
intent: inputIntent({ error, touched }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Reference -----------*/}
|
||||||
|
<FastField name={'reference_no'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'reference_no'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="reference_no" />}
|
||||||
|
className={'form-group--reference-no'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/*------------ description -----------*/}
|
||||||
|
<FastField name={'description'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--description'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'description'} />}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withSettings(({ cashflowSetting }) => ({
|
||||||
|
transactionAutoIncrement: cashflowSetting?.autoIncrement,
|
||||||
|
transactionNextNumber: cashflowSetting?.nextNumber,
|
||||||
|
transactionNumberPrefix: cashflowSetting?.numberPrefix,
|
||||||
|
})),
|
||||||
|
)(TransferFromAccountFormFields);
|
||||||
39
src/containers/Dialogs/MoneyInDialog/index.js
Normal file
39
src/containers/Dialogs/MoneyInDialog/index.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Dialog, DialogSuspense } from 'components';
|
||||||
|
import withDialogRedux from 'components/DialogReduxConnect';
|
||||||
|
|
||||||
|
import { compose } from 'redux';
|
||||||
|
|
||||||
|
const MoneyInDialogContent = React.lazy(() => import('./MoneyInDialogContent'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money In dialog.
|
||||||
|
*/
|
||||||
|
function MoneyInDialog({
|
||||||
|
dialogName,
|
||||||
|
payload = { account_type: null, account_id: null, account_name: '' },
|
||||||
|
isOpen,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={intl.get('cash_flow_transaction.money_in', {
|
||||||
|
value: payload.account_name,
|
||||||
|
})}
|
||||||
|
isOpen={isOpen}
|
||||||
|
canEscapeJeyClose={true}
|
||||||
|
autoFocus={true}
|
||||||
|
className={'dialog--money-in'}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<MoneyInDialogContent
|
||||||
|
dialogName={dialogName}
|
||||||
|
accountId={payload.account_id}
|
||||||
|
accountType={payload.account_type}
|
||||||
|
/>
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(withDialogRedux())(MoneyInDialog);
|
||||||
12
src/containers/Dialogs/MoneyInDialog/utils.js
Normal file
12
src/containers/Dialogs/MoneyInDialog/utils.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { transactionNumber } from 'utils';
|
||||||
|
|
||||||
|
export const useObserveTransactionNoSettings = (prefix, nextNumber) => {
|
||||||
|
const { setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const TransactionNo = transactionNumber(prefix, nextNumber);
|
||||||
|
setFieldValue('transacttion_numner', TransactionNo);
|
||||||
|
}, [setFieldValue, prefix, nextNumber]);
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import OtherExpnseFormFields from './OtherExpense/OtherExpnseFormFields';
|
||||||
|
import OwnerDrawingsFormFields from './OwnerDrawings/OwnerDrawingsFormFields';
|
||||||
|
import TransferToAccountFormFields from './TransferToAccount/TransferToAccountFormFields';
|
||||||
|
|
||||||
|
function MoneyOutContentFields({ accountType }) {
|
||||||
|
const handleTransactionType = () => {
|
||||||
|
switch (accountType) {
|
||||||
|
case 'OwnerDrawing':
|
||||||
|
return <OwnerDrawingsFormFields />;
|
||||||
|
|
||||||
|
case 'other_expense':
|
||||||
|
return <OtherExpnseFormFields />;
|
||||||
|
|
||||||
|
case 'transfer_to_account':
|
||||||
|
return <TransferToAccountFormFields />;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <React.Fragment>{handleTransactionType()}</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MoneyOutContentFields;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MoneyOutProvider } from './MoneyOutDialogProvider';
|
||||||
|
import MoneyOutForm from './MoneyOutForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money out dailog content.
|
||||||
|
*/
|
||||||
|
export default function MoneyOutDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
accountId,
|
||||||
|
accountType,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<MoneyOutProvider
|
||||||
|
accountId={accountId}
|
||||||
|
accountType={accountType}
|
||||||
|
dialogName={dialogName}
|
||||||
|
>
|
||||||
|
<MoneyOutForm />
|
||||||
|
</MoneyOutProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
import {
|
||||||
|
useAccounts,
|
||||||
|
useCreateCashflowTransaction,
|
||||||
|
useCashflowAccounts,
|
||||||
|
useSettingCashFlow,
|
||||||
|
} from 'hooks/query';
|
||||||
|
|
||||||
|
const MoneyInDialogContent = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money out dialog provider.
|
||||||
|
*/
|
||||||
|
function MoneyOutProvider({ accountId, accountType, dialogName, ...props }) {
|
||||||
|
// Fetches accounts list.
|
||||||
|
const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
|
||||||
|
|
||||||
|
// Fetch cash flow list .
|
||||||
|
const { data: cashflowAccounts, isLoading: isCashFlowAccountsLoading } =
|
||||||
|
useCashflowAccounts({}, { keepPreviousData: true });
|
||||||
|
|
||||||
|
const { mutateAsync: createCashflowTransactionMutate } =
|
||||||
|
useCreateCashflowTransaction();
|
||||||
|
|
||||||
|
// Handle fetching settings.
|
||||||
|
const { isLoading: isSettingsLoading } = useSettingCashFlow();
|
||||||
|
|
||||||
|
// Submit payload.
|
||||||
|
const [submitPayload, setSubmitPayload] = React.useState({});
|
||||||
|
|
||||||
|
// provider.
|
||||||
|
const provider = {
|
||||||
|
accounts,
|
||||||
|
accountId,
|
||||||
|
accountType,
|
||||||
|
isAccountsLoading,
|
||||||
|
|
||||||
|
cashflowAccounts,
|
||||||
|
|
||||||
|
submitPayload,
|
||||||
|
dialogName,
|
||||||
|
|
||||||
|
createCashflowTransactionMutate,
|
||||||
|
setSubmitPayload,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent
|
||||||
|
isLoading={
|
||||||
|
isAccountsLoading || isCashFlowAccountsLoading || isSettingsLoading
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MoneyInDialogContent.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useMoneyOutDialogContext = () => React.useContext(MoneyInDialogContent);
|
||||||
|
export { MoneyOutProvider, useMoneyOutDialogContext };
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money out floating actions.
|
||||||
|
*/
|
||||||
|
function MoneyOutFloatingActions({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
// Formik context.
|
||||||
|
const { isSubmitting, submitForm } = useFormikContext();
|
||||||
|
// money in dialog context.
|
||||||
|
const { dialogName, setSubmitPayload, submitPayload } =
|
||||||
|
useMoneyOutDialogContext();
|
||||||
|
|
||||||
|
// handle submit as draft button click.
|
||||||
|
const handleSubmitDraftBtnClick = (event) => {
|
||||||
|
setSubmitPayload({ publish: false });
|
||||||
|
submitForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle submit button click.
|
||||||
|
const handleSubmittBtnClick = (event) => {
|
||||||
|
setSubmitPayload({ publish: true });
|
||||||
|
submitForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle close button click.
|
||||||
|
const handleCloseBtnClick = (event) => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onClick={handleCloseBtnClick}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
>
|
||||||
|
<T id={'close'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
loading={isSubmitting && !submitPayload.publish}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
onClick={handleSubmitDraftBtnClick}
|
||||||
|
>
|
||||||
|
{<T id={'save_as_draft'} />}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
loading={isSubmitting && submitPayload.publish}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
onClick={handleSubmittBtnClick}
|
||||||
|
>
|
||||||
|
{<T id={'save_and_publish'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(MoneyOutFloatingActions);
|
||||||
113
src/containers/Dialogs/MoneyOutDialog/MoneyOutForm.js
Normal file
113
src/containers/Dialogs/MoneyOutDialog/MoneyOutForm.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import 'style/pages/CashFlow/CashflowTransactionForm.scss';
|
||||||
|
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
|
import MoneyOutFormContent from './MoneyOutFormContent';
|
||||||
|
import { CreateMoneyOutSchema } from './MoneyOutForm.schema';
|
||||||
|
|
||||||
|
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
|
||||||
|
|
||||||
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||||
|
|
||||||
|
import { compose, transactionNumber } from 'utils';
|
||||||
|
|
||||||
|
const defaultInitialValues = {
|
||||||
|
date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
amount: '',
|
||||||
|
transaction_number: '',
|
||||||
|
transaction_type: '',
|
||||||
|
reference_no: '',
|
||||||
|
cashflow_account_id: '',
|
||||||
|
credit_account_id: '',
|
||||||
|
description: '',
|
||||||
|
published: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
function MoneyOutForm({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
|
||||||
|
// #withCurrentOrganization
|
||||||
|
organization: { base_currency },
|
||||||
|
|
||||||
|
// #withSettings
|
||||||
|
transactionNextNumber,
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionIncrementMode,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
dialogName,
|
||||||
|
accountId,
|
||||||
|
accountType,
|
||||||
|
createCashflowTransactionMutate,
|
||||||
|
submitPayload,
|
||||||
|
} = useMoneyOutDialogContext();
|
||||||
|
|
||||||
|
// transaction number.
|
||||||
|
const transactionNo = transactionNumber(
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initial form values.
|
||||||
|
const initialValues = {
|
||||||
|
...defaultInitialValues,
|
||||||
|
currency_code: base_currency,
|
||||||
|
transaction_type: accountType,
|
||||||
|
...(transactionIncrementMode && {
|
||||||
|
transaction_number: transactionNo,
|
||||||
|
}),
|
||||||
|
cashflow_account_id: accountId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
const form = {
|
||||||
|
...omit(values, ['currency_code']),
|
||||||
|
published: submitPayload.publish,
|
||||||
|
};
|
||||||
|
setSubmitting(true);
|
||||||
|
createCashflowTransactionMutate(form)
|
||||||
|
.then(() => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('cash_flow_transaction_success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setSubmitting(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateMoneyOutSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
>
|
||||||
|
<MoneyOutFormContent />
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withCurrentOrganization(),
|
||||||
|
withSettings(({ cashflowSetting }) => ({
|
||||||
|
transactionNextNumber: cashflowSetting?.nextNumber,
|
||||||
|
transactionNumberPrefix: cashflowSetting?.numberPrefix,
|
||||||
|
transactionIncrementMode: cashflowSetting?.autoIncrement,
|
||||||
|
})),
|
||||||
|
)(MoneyOutForm);
|
||||||
20
src/containers/Dialogs/MoneyOutDialog/MoneyOutForm.schema.js
Normal file
20
src/containers/Dialogs/MoneyOutDialog/MoneyOutForm.schema.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
date: Yup.date().required().label(intl.get('date')),
|
||||||
|
amount: Yup.number().required().label(intl.get('amount')),
|
||||||
|
transaction_number: Yup.string(),
|
||||||
|
transaction_type: Yup.string().required(),
|
||||||
|
reference_no: Yup.string(),
|
||||||
|
credit_account_id: Yup.number().required(),
|
||||||
|
cashflow_account_id: Yup.string().required(),
|
||||||
|
description: Yup.string()
|
||||||
|
.min(3)
|
||||||
|
.max(DATATYPES_LENGTH.TEXT)
|
||||||
|
.label(intl.get('description')),
|
||||||
|
published: Yup.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateMoneyOutSchema = Schema;
|
||||||
18
src/containers/Dialogs/MoneyOutDialog/MoneyOutFormContent.js
Normal file
18
src/containers/Dialogs/MoneyOutDialog/MoneyOutFormContent.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
|
||||||
|
import MoneyOutFormFields from './MoneyOutFormFields';
|
||||||
|
import MoneyOutFormDialog from './MoneyOutFormDialog'
|
||||||
|
import MoneyOutFloatingActions from './MoneyOutFloatingActions';
|
||||||
|
/**
|
||||||
|
* Money out form content.
|
||||||
|
*/
|
||||||
|
export default function MoneyOutFormContent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<MoneyOutFormFields />
|
||||||
|
<MoneyOutFormDialog/>
|
||||||
|
<MoneyOutFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
src/containers/Dialogs/MoneyOutDialog/MoneyOutFormDialog.js
Normal file
27
src/containers/Dialogs/MoneyOutDialog/MoneyOutFormDialog.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
|
import TransactionNumberDialog from '../../Dialogs/TransactionNumberDialog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money out form dialog.
|
||||||
|
*/
|
||||||
|
export default function MoneyOutFormDialog() {
|
||||||
|
const { setFieldValue } = useFormikContext();
|
||||||
|
// Update the form once the transaction number form submit confirm.
|
||||||
|
const handleTransactionNumberFormConfirm = ({
|
||||||
|
incrementNumber,
|
||||||
|
manually,
|
||||||
|
}) => {
|
||||||
|
setFieldValue('transaction_number', incrementNumber || '');
|
||||||
|
setFieldValue('transaction_number_manually', manually);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<TransactionNumberDialog
|
||||||
|
dialogName={'transaction-number-form'}
|
||||||
|
onConfirm={handleTransactionNumberFormConfirm}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
src/containers/Dialogs/MoneyOutDialog/MoneyOutFormFields.js
Normal file
29
src/containers/Dialogs/MoneyOutDialog/MoneyOutFormFields.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { If } from 'components';
|
||||||
|
|
||||||
|
import MoneyOutContentFields from './MoneyOutContentFields';
|
||||||
|
import TransactionTypeFields from './TransactionTypeFields';
|
||||||
|
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money out form fields.
|
||||||
|
*/
|
||||||
|
function MoneyOutFormFields() {
|
||||||
|
// Money in dialog context.
|
||||||
|
const { accountId } = useMoneyOutDialogContext();
|
||||||
|
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<If condition={!accountId}>
|
||||||
|
<TransactionTypeFields />
|
||||||
|
</If>
|
||||||
|
<MoneyOutContentFields accountType={values.transaction_type} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MoneyOutFormFields;
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
|
import {
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
InputGroup,
|
||||||
|
TextArea,
|
||||||
|
Position,
|
||||||
|
ControlGroup,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
AccountsSuggestField,
|
||||||
|
InputPrependText,
|
||||||
|
MoneyInputGroup,
|
||||||
|
FieldRequiredHint,
|
||||||
|
Icon,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
InputPrependButton,
|
||||||
|
} from 'components';
|
||||||
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import { ACCOUNT_TYPE } from 'common/accountTypes';
|
||||||
|
|
||||||
|
import {
|
||||||
|
inputIntent,
|
||||||
|
momentFormatter,
|
||||||
|
tansformDateValue,
|
||||||
|
handleDateChange,
|
||||||
|
compose,
|
||||||
|
} from 'utils';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
|
||||||
|
import { useObserveTransactionNoSettings } from '../utils';
|
||||||
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Other expense form fields.
|
||||||
|
*/
|
||||||
|
function OtherExpnseFormFields({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withSettings
|
||||||
|
transactionAutoIncrement,
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
}) {
|
||||||
|
// Money in dialog context.
|
||||||
|
const { accounts } = useMoneyOutDialogContext();
|
||||||
|
|
||||||
|
const amountFieldRef = useAutofocus();
|
||||||
|
|
||||||
|
// Handle tranaction number changing.
|
||||||
|
const handleTransactionNumberChange = () => {
|
||||||
|
openDialog('transaction-number-form');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle transaction no. field blur.
|
||||||
|
const handleTransactionNoBlur = (form, field) => (event) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
|
||||||
|
if (field.value !== newValue && transactionAutoIncrement) {
|
||||||
|
openDialog('transaction-number-form', {
|
||||||
|
initialFormValues: {
|
||||||
|
manualTransactionNo: newValue,
|
||||||
|
incrementMode: 'manual-transaction',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Syncs transaction number settings with form.
|
||||||
|
useObserveTransactionNoSettings(
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Date -----------*/}
|
||||||
|
<FastField name={'date'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'date'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="date" />}
|
||||||
|
minimal={true}
|
||||||
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
|
>
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
onChange={handleDateChange((formattedDate) => {
|
||||||
|
form.setFieldValue('date', formattedDate);
|
||||||
|
})}
|
||||||
|
value={tansformDateValue(value)}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
minimal: true,
|
||||||
|
}}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Transaction number -----------*/}
|
||||||
|
<Field name={'transaction_number'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'transaction_number'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="transaction_number" />}
|
||||||
|
className={'form-group--transaction_number'}
|
||||||
|
>
|
||||||
|
<ControlGroup fill={true}>
|
||||||
|
<InputGroup
|
||||||
|
minimal={true}
|
||||||
|
value={field.value}
|
||||||
|
asyncControl={true}
|
||||||
|
onBlur={handleTransactionNoBlur(form, field)}
|
||||||
|
/>
|
||||||
|
<InputPrependButton
|
||||||
|
buttonProps={{
|
||||||
|
onClick: handleTransactionNumberChange,
|
||||||
|
icon: <Icon icon={'settings-18'} />,
|
||||||
|
}}
|
||||||
|
tooltip={true}
|
||||||
|
tooltipProps={{
|
||||||
|
content: (
|
||||||
|
<T
|
||||||
|
id={
|
||||||
|
'cash_flow.setting_your_auto_generated_transaction_number'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
position: Position.BOTTOM_LEFT,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/*------------ amount -----------*/}
|
||||||
|
<FastField name={'amount'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'amount'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="amount" />}
|
||||||
|
className={'form-group--amount'}
|
||||||
|
>
|
||||||
|
<ControlGroup>
|
||||||
|
<InputPrependText text={values.currency_code} />
|
||||||
|
|
||||||
|
<MoneyInputGroup
|
||||||
|
value={value}
|
||||||
|
minimal={true}
|
||||||
|
onChange={(amount) => {
|
||||||
|
setFieldValue('amount', amount);
|
||||||
|
}}
|
||||||
|
inputRef={(ref) => (amountFieldRef.current = ref)}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ other expense account -----------*/}
|
||||||
|
<FastField name={'credit_account_id'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'cash_flow_transaction.label_expense_account'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="credit_account_id" />}
|
||||||
|
className={'form-group--credit_account_id'}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('credit_account_id', id)
|
||||||
|
}
|
||||||
|
filterByTypes={[
|
||||||
|
ACCOUNT_TYPE.EXPENSE,
|
||||||
|
ACCOUNT_TYPE.OTHER_EXPENSE,
|
||||||
|
]}
|
||||||
|
inputProps={{
|
||||||
|
intent: inputIntent({ error, touched }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Reference -----------*/}
|
||||||
|
<FastField name={'reference_no'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'reference_no'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="reference_no" />}
|
||||||
|
className={'form-group--reference-no'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/*------------ description -----------*/}
|
||||||
|
<FastField name={'description'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--description'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'description'} />}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withSettings(({ cashflowSetting }) => ({
|
||||||
|
transactionAutoIncrement: cashflowSetting?.autoIncrement,
|
||||||
|
transactionNextNumber: cashflowSetting?.nextNumber,
|
||||||
|
transactionNumberPrefix: cashflowSetting?.numberPrefix,
|
||||||
|
})),
|
||||||
|
)(OtherExpnseFormFields);
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
|
import {
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
InputGroup,
|
||||||
|
TextArea,
|
||||||
|
Position,
|
||||||
|
ControlGroup,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
AccountsSuggestField,
|
||||||
|
InputPrependText,
|
||||||
|
MoneyInputGroup,
|
||||||
|
FieldRequiredHint,
|
||||||
|
InputPrependButton,
|
||||||
|
Icon,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
} from 'components';
|
||||||
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import { ACCOUNT_TYPE } from 'common/accountTypes';
|
||||||
|
import {
|
||||||
|
inputIntent,
|
||||||
|
momentFormatter,
|
||||||
|
tansformDateValue,
|
||||||
|
handleDateChange,
|
||||||
|
compose,
|
||||||
|
} from 'utils';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
|
||||||
|
import { useObserveTransactionNoSettings } from '../utils';
|
||||||
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Owner drawings form fields.
|
||||||
|
*/
|
||||||
|
function OwnerDrawingsFormFields({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withSettings
|
||||||
|
transactionAutoIncrement,
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
}) {
|
||||||
|
// Money out dialog context.
|
||||||
|
const { accounts } = useMoneyOutDialogContext();
|
||||||
|
|
||||||
|
const amountFieldRef = useAutofocus();
|
||||||
|
|
||||||
|
// Handle tranaction number changing.
|
||||||
|
const handleTransactionNumberChange = () => {
|
||||||
|
openDialog('transaction-number-form');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle transaction no. field blur.
|
||||||
|
const handleTransactionNoBlur = (form, field) => (event) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
|
||||||
|
if (field.value !== newValue && transactionAutoIncrement) {
|
||||||
|
openDialog('transaction-number-form', {
|
||||||
|
initialFormValues: {
|
||||||
|
manualTransactionNo: newValue,
|
||||||
|
incrementMode: 'manual-transaction',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Syncs transaction number settings with form.
|
||||||
|
useObserveTransactionNoSettings(
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Date -----------*/}
|
||||||
|
<FastField name={'date'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'date'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="date" />}
|
||||||
|
minimal={true}
|
||||||
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
|
>
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
onChange={handleDateChange((formattedDate) => {
|
||||||
|
form.setFieldValue('date', formattedDate);
|
||||||
|
})}
|
||||||
|
value={tansformDateValue(value)}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
minimal: true,
|
||||||
|
}}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Transaction number -----------*/}
|
||||||
|
<Field name={'transaction_number'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'transaction_number'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="transaction_number" />}
|
||||||
|
className={'form-group--transaction_number'}
|
||||||
|
>
|
||||||
|
<ControlGroup fill={true}>
|
||||||
|
<InputGroup
|
||||||
|
minimal={true}
|
||||||
|
value={field.value}
|
||||||
|
asyncControl={true}
|
||||||
|
onBlur={handleTransactionNoBlur(form, field)}
|
||||||
|
/>
|
||||||
|
<InputPrependButton
|
||||||
|
buttonProps={{
|
||||||
|
onClick: handleTransactionNumberChange,
|
||||||
|
icon: <Icon icon={'settings-18'} />,
|
||||||
|
}}
|
||||||
|
tooltip={true}
|
||||||
|
tooltipProps={{
|
||||||
|
content: (
|
||||||
|
<T
|
||||||
|
id={
|
||||||
|
'cash_flow.setting_your_auto_generated_transaction_number'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
position: Position.BOTTOM_LEFT,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/*------------ amount -----------*/}
|
||||||
|
<Field name={'amount'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'amount'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="amount" />}
|
||||||
|
className={'form-group--amount'}
|
||||||
|
>
|
||||||
|
<ControlGroup>
|
||||||
|
<InputPrependText text={values.currency_code} />
|
||||||
|
|
||||||
|
<MoneyInputGroup
|
||||||
|
value={value}
|
||||||
|
minimal={true}
|
||||||
|
onChange={(amount) => {
|
||||||
|
setFieldValue('amount', amount);
|
||||||
|
}}
|
||||||
|
inputRef={(ref) => (amountFieldRef.current = ref)}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ equitty account -----------*/}
|
||||||
|
<FastField name={'credit_account_id'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'cash_flow_transaction.label_equity_account'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="credit_account_id" />}
|
||||||
|
className={'form-group--credit_account_id'}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('credit_account_id', id)
|
||||||
|
}
|
||||||
|
filterByTypes={ACCOUNT_TYPE.EQUITY}
|
||||||
|
inputProps={{
|
||||||
|
intent: inputIntent({ error, touched }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Reference -----------*/}
|
||||||
|
<FastField name={'reference_no'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'reference_no'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="reference_no" />}
|
||||||
|
className={'form-group--reference-no'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/*------------ description -----------*/}
|
||||||
|
<FastField name={'description'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--description'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'description'} />}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withSettings(({ cashflowSetting }) => ({
|
||||||
|
transactionAutoIncrement: cashflowSetting?.autoIncrement,
|
||||||
|
transactionNextNumber: cashflowSetting?.nextNumber,
|
||||||
|
transactionNumberPrefix: cashflowSetting?.numberPrefix,
|
||||||
|
})),
|
||||||
|
)(OwnerDrawingsFormFields);
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
|
import { FormGroup } from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
AccountsSuggestField,
|
||||||
|
FieldRequiredHint,
|
||||||
|
ListSelect,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import { inputIntent } from 'utils';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { addMoneyOut } from '../../../common/cashflowOptions';
|
||||||
|
|
||||||
|
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction type fields.
|
||||||
|
*/
|
||||||
|
function TransactionTypeFields() {
|
||||||
|
// Money in dialog context.
|
||||||
|
const { cashflowAccounts } = useMoneyOutDialogContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="trasnaction-type-fileds">
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Current account -----------*/}
|
||||||
|
<FastField name={'cashflow_account_id'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'cash_flow_transaction.label_current_account'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="cashflow_account_id" />}
|
||||||
|
minimal={true}
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.FILL,
|
||||||
|
'form-group--cashflow_account_id',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
accounts={cashflowAccounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('cashflow_account_id', id)
|
||||||
|
}
|
||||||
|
inputProps={{
|
||||||
|
intent: inputIntent({ error, touched }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
{/*------------ Transaction type -----------*/}
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
<Field name={'transaction_type'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'transaction_type'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
helperText={<ErrorMessage name="transaction_type" />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.FILL,
|
||||||
|
'form-group--transaction_type',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ListSelect
|
||||||
|
items={addMoneyOut}
|
||||||
|
onItemSelect={(type) => {
|
||||||
|
setFieldValue('transaction_type', type.value);
|
||||||
|
}}
|
||||||
|
filterable={false}
|
||||||
|
selectedItem={value}
|
||||||
|
selectedItemProp={'value'}
|
||||||
|
textProp={'name'}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TransactionTypeFields;
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
|
import {
|
||||||
|
Classes,
|
||||||
|
FormGroup,
|
||||||
|
InputGroup,
|
||||||
|
TextArea,
|
||||||
|
Position,
|
||||||
|
ControlGroup,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
AccountsSuggestField,
|
||||||
|
InputPrependText,
|
||||||
|
MoneyInputGroup,
|
||||||
|
FieldRequiredHint,
|
||||||
|
Icon,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
InputPrependButton,
|
||||||
|
} from 'components';
|
||||||
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
|
import { useAutofocus } from 'hooks';
|
||||||
|
import { ACCOUNT_TYPE } from 'common/accountTypes';
|
||||||
|
|
||||||
|
import {
|
||||||
|
inputIntent,
|
||||||
|
momentFormatter,
|
||||||
|
tansformDateValue,
|
||||||
|
handleDateChange,
|
||||||
|
compose,
|
||||||
|
} from 'utils';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
|
||||||
|
import { useObserveTransactionNoSettings } from '../utils';
|
||||||
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfer to account form fields.
|
||||||
|
*/
|
||||||
|
function TransferToAccountFormFields({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #withSettings
|
||||||
|
transactionAutoIncrement,
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
}) {
|
||||||
|
// Money in dialog context.
|
||||||
|
const { accounts } = useMoneyOutDialogContext();
|
||||||
|
|
||||||
|
const accountRef = useAutofocus();
|
||||||
|
|
||||||
|
// Handle tranaction number changing.
|
||||||
|
const handleTransactionNumberChange = () => {
|
||||||
|
openDialog('transaction-number-form');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle transaction no. field blur.
|
||||||
|
const handleTransactionNoBlur = (form, field) => (event) => {
|
||||||
|
const newValue = event.target.value;
|
||||||
|
|
||||||
|
if (field.value !== newValue && transactionAutoIncrement) {
|
||||||
|
openDialog('transaction-number-form', {
|
||||||
|
initialFormValues: {
|
||||||
|
manualTransactionNo: newValue,
|
||||||
|
incrementMode: 'manual-transaction',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Syncs transaction number settings with form.
|
||||||
|
useObserveTransactionNoSettings(
|
||||||
|
transactionNumberPrefix,
|
||||||
|
transactionNextNumber,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Date -----------*/}
|
||||||
|
<FastField name={'date'}>
|
||||||
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'date'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="date" />}
|
||||||
|
minimal={true}
|
||||||
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
|
>
|
||||||
|
<DateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
onChange={handleDateChange((formattedDate) => {
|
||||||
|
form.setFieldValue('date', formattedDate);
|
||||||
|
})}
|
||||||
|
value={tansformDateValue(value)}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
minimal: true,
|
||||||
|
}}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Transaction number -----------*/}
|
||||||
|
<Field name={'transaction_number'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'transaction_number'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="transaction_number" />}
|
||||||
|
className={'form-group--transaction_number'}
|
||||||
|
>
|
||||||
|
<ControlGroup fill={true}>
|
||||||
|
<InputGroup
|
||||||
|
minimal={true}
|
||||||
|
value={field.value}
|
||||||
|
asyncControl={true}
|
||||||
|
onBlur={handleTransactionNoBlur(form, field)}
|
||||||
|
/>
|
||||||
|
<InputPrependButton
|
||||||
|
buttonProps={{
|
||||||
|
onClick: handleTransactionNumberChange,
|
||||||
|
icon: <Icon icon={'settings-18'} />,
|
||||||
|
}}
|
||||||
|
tooltip={true}
|
||||||
|
tooltipProps={{
|
||||||
|
content: (
|
||||||
|
<T
|
||||||
|
id={
|
||||||
|
'cash_flow.setting_your_auto_generated_transaction_number'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
position: Position.BOTTOM_LEFT,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/*------------ amount -----------*/}
|
||||||
|
<FastField name={'amount'}>
|
||||||
|
{({
|
||||||
|
form: { values, setFieldValue },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'amount'} />}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="amount" />}
|
||||||
|
className={'form-group--amount'}
|
||||||
|
>
|
||||||
|
<ControlGroup>
|
||||||
|
<InputPrependText text={values.currency_code} />
|
||||||
|
|
||||||
|
<MoneyInputGroup
|
||||||
|
value={value}
|
||||||
|
minimal={true}
|
||||||
|
onChange={(amount) => {
|
||||||
|
setFieldValue('amount', amount);
|
||||||
|
}}
|
||||||
|
inputRef={accountRef}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ transfer from account -----------*/}
|
||||||
|
<FastField name={'credit_account_id'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={
|
||||||
|
<T id={'cash_flow_transaction.label_transfer_to_account'} />
|
||||||
|
}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="credit_account_id" />}
|
||||||
|
className={'form-group--credit_account_id'}
|
||||||
|
>
|
||||||
|
<AccountsSuggestField
|
||||||
|
accounts={accounts}
|
||||||
|
onAccountSelected={({ id }) =>
|
||||||
|
form.setFieldValue('credit_account_id', id)
|
||||||
|
}
|
||||||
|
filterByTypes={[
|
||||||
|
ACCOUNT_TYPE.CASH,
|
||||||
|
ACCOUNT_TYPE.BANK,
|
||||||
|
ACCOUNT_TYPE.CREDIT_CARD,
|
||||||
|
]}
|
||||||
|
inputProps={{
|
||||||
|
intent: inputIntent({ error, touched }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5}>
|
||||||
|
{/*------------ Reference -----------*/}
|
||||||
|
<FastField name={'reference_no'}>
|
||||||
|
{({ form, field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'reference_no'} />}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name="reference_no" />}
|
||||||
|
className={'form-group--reference-no'}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/*------------ description -----------*/}
|
||||||
|
<FastField name={'description'}>
|
||||||
|
{({ field, meta: { error, touched } }) => (
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'description'} />}
|
||||||
|
className={'form-group--description'}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
helperText={<ErrorMessage name={'description'} />}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
growVertically={true}
|
||||||
|
large={true}
|
||||||
|
intent={inputIntent({ error, touched })}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(
|
||||||
|
withDialogActions,
|
||||||
|
withSettings(({ cashflowSetting }) => ({
|
||||||
|
transactionAutoIncrement: cashflowSetting?.autoIncrement,
|
||||||
|
transactionNextNumber: cashflowSetting?.nextNumber,
|
||||||
|
transactionNumberPrefix: cashflowSetting?.numberPrefix,
|
||||||
|
})),
|
||||||
|
)(TransferToAccountFormFields);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user