Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11d7029568 | ||
|
|
1990ce7562 | ||
|
|
b6f0f6c2d5 | ||
|
|
4c58e49169 | ||
|
|
376a16fd65 | ||
|
|
918cd4aef3 | ||
|
|
ec844637c3 | ||
|
|
5803760c61 | ||
|
|
2e34df5d63 | ||
|
|
35d755e417 | ||
|
|
66641ca56e | ||
|
|
307aaf0aa4 | ||
|
|
eb5a82d413 | ||
|
|
ce9169b24d | ||
|
|
22069f4795 | ||
|
|
567b4da7e9 | ||
|
|
06345a5615 | ||
|
|
6b8178f643 | ||
|
|
449ff724e1 | ||
|
|
95e75f0e8f | ||
|
|
1a63ac69d8 | ||
|
|
da67217d74 | ||
|
|
e0c03141f0 | ||
|
|
4d563e3ddd | ||
|
|
8bad78b0d3 | ||
|
|
56fdf245d3 | ||
|
|
5a8c61396f | ||
|
|
5fcf32dcaa | ||
|
|
acf457c0a0 | ||
|
|
e205c0b9a3 | ||
|
|
85f1c5584b | ||
|
|
9e5fddf294 | ||
|
|
3039e43767 | ||
|
|
7371557482 | ||
|
|
4b5e06f50c | ||
|
|
8daefb6946 | ||
|
|
6bf605f9ea | ||
|
|
48221a7af1 | ||
|
|
7a1c9caa70 | ||
|
|
8c2d138976 | ||
|
|
5b09d8279e | ||
|
|
92d8096f3a | ||
|
|
adc6b336e0 | ||
|
|
6d67d6163d | ||
|
|
4d89f1e0e0 | ||
|
|
7706d2992c | ||
|
|
6dcb98a438 | ||
|
|
834d365a97 | ||
|
|
d26ef01afc | ||
|
|
2bd4c5f724 | ||
|
|
2c71d07512 | ||
|
|
17a4744e58 | ||
|
|
46f6380fe6 | ||
|
|
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 | ||
|
|
77f0a767b3 | ||
|
|
8a982e5c7e | ||
|
|
2f7564eb9c | ||
|
|
7c2c362585 | ||
|
|
786aad438a | ||
|
|
d6c78a9908 | ||
|
|
0aca6d9af7 | ||
|
|
696943153d | ||
|
|
2e437d7b65 | ||
|
|
5b12c4a433 | ||
|
|
96269ccafb | ||
|
|
f556f061cb | ||
|
|
b98e8aeeb4 | ||
|
|
17d5bbd9d1 | ||
|
|
e1ab4e4d65 | ||
|
|
b86a3a19dc | ||
|
|
3b2796cb6d | ||
|
|
15ee32f6a4 | ||
|
|
90e550c902 | ||
|
|
cbc0ccbfb9 | ||
|
|
526c46b24d | ||
|
|
6041c175fd | ||
|
|
0f58665a0d | ||
|
|
91036c3e52 | ||
|
|
555e3a2434 | ||
|
|
b87e85d5ff | ||
|
|
288225a0c1 | ||
|
|
71f9fa47d4 | ||
|
|
fcace4213c | ||
|
|
31d2b1b09a | ||
|
|
cd5116dbcb | ||
|
|
010b660318 | ||
|
|
4e99607b06 | ||
|
|
a3f1857e91 | ||
|
|
460ee2718e | ||
|
|
87938b8f41 | ||
|
|
cd70bf1d80 | ||
|
|
c4f2ea405c | ||
|
|
d1cb7eb51b | ||
|
|
e949b1b0c7 | ||
|
|
9b7382e222 | ||
|
|
364859a793 | ||
|
|
fd07306102 | ||
|
|
5fc4897663 | ||
|
|
7b85dfee5d | ||
|
|
14012c4d7e | ||
|
|
ecf56f3b99 |
@@ -17,4 +17,4 @@ FROM nginx
|
||||
|
||||
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
|
||||
889
package-lock.json
generated
889
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,15 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.8.4",
|
||||
"@blueprintjs/core": "^3.38.1",
|
||||
"@blueprintjs/datetime": "^3.15.2",
|
||||
"@blueprintjs/core": "^3.50.2",
|
||||
"@blueprintjs/datetime": "^3.23.12",
|
||||
"@blueprintjs/popover2": "^0.11.1",
|
||||
"@blueprintjs/select": "^3.11.2",
|
||||
"@blueprintjs/table": "^3.8.3",
|
||||
"@blueprintjs/timezone": "^3.6.2",
|
||||
"@reduxjs/toolkit": "^1.2.5",
|
||||
"@sentry/react": "^6.13.2",
|
||||
"@sentry/tracing": "^6.13.2",
|
||||
"@svgr/webpack": "4.3.3",
|
||||
"@tanem/react-nprogress": "^3.0.24",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
@@ -101,6 +103,7 @@
|
||||
"sass-loader": "8.0.2",
|
||||
"semver": "6.3.0",
|
||||
"style-loader": "0.23.1",
|
||||
"styled-components": "^5.3.1",
|
||||
"terser-webpack-plugin": "2.3.4",
|
||||
"ts-pnp": "1.1.5",
|
||||
"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',
|
||||
];
|
||||
@@ -66,6 +66,7 @@ const CLASSES = {
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_USERS: 'preferences-page__inside-content--users',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_CURRENCIES: 'preferences-page__inside-content--currencies',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT: 'preferences-page__inside-content--accountant',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_SMS_INTEGRATION: 'preferences-page__inside-content--sms-integration',
|
||||
|
||||
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
|
||||
|
||||
|
||||
@@ -9,4 +9,9 @@ export const DRAWERS = {
|
||||
EXPENSE_DRAWER: 'expense-drawer',
|
||||
BILL_DRAWER: 'bill-drawer',
|
||||
INVENTORY_ADJUSTMENT_DRAWER: 'inventory-adjustment-drawer',
|
||||
CASHFLOW_TRNASACTION_DRAWER: 'cashflow-transaction-drawer',
|
||||
|
||||
QUICK_WRITE_VENDOR: 'quick-write-vendor',
|
||||
QUICK_CREATE_CUSTOMER: 'quick-create-customer',
|
||||
QUICK_CREATE_ITEM: 'quick-create-item',
|
||||
};
|
||||
|
||||
@@ -42,6 +42,14 @@ export default [
|
||||
shortcut_key: 'Shift + W',
|
||||
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',
|
||||
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,4 +12,12 @@ export const TABLES = {
|
||||
ACCOUNTS: 'accounts',
|
||||
MANUAL_JOURNALS: 'manual_journal',
|
||||
EXPENSES: 'expenses',
|
||||
CASHFLOW_ACCOUNTS: 'cashflow_accounts',
|
||||
CASHFLOW_Transactions: 'cashflow_transactions',
|
||||
};
|
||||
|
||||
export const TABLE_SIZE = {
|
||||
COMPACT: 'compact',
|
||||
SMALL: 'small',
|
||||
MEDIUM: 'medium',
|
||||
};
|
||||
|
||||
@@ -1,13 +1,55 @@
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
||||
import * as R from 'ramda';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
||||
import { filterAccountsByQuery } from './utils';
|
||||
import { nestedArrayToflatten } from 'utils';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
export default function AccountsSelectList({
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
// Create new account renderer.
|
||||
const createNewItemRenderer = (query, active, handleClick) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon="add"
|
||||
text={`Create "${query}"`}
|
||||
active={active}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// Create new item from the given query string.
|
||||
const createNewItemFromQuery = (name) => {
|
||||
return {
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
// Filters accounts items.
|
||||
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Accounts select list.
|
||||
*/
|
||||
function AccountsSelectList({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #ownProps
|
||||
accounts,
|
||||
initialAccountId,
|
||||
selectedAccountId,
|
||||
@@ -21,6 +63,8 @@ export default function AccountsSelectList({
|
||||
filterByNormal,
|
||||
filterByRootTypes,
|
||||
|
||||
allowCreate,
|
||||
|
||||
buttonProps = {},
|
||||
}) {
|
||||
const flattenAccounts = useMemo(
|
||||
@@ -51,6 +95,7 @@ export default function AccountsSelectList({
|
||||
[initialAccountId, filteredAccounts],
|
||||
);
|
||||
|
||||
// Select account item.
|
||||
const [selectedAccount, setSelectedAccount] = useState(
|
||||
initialAccount || null,
|
||||
);
|
||||
@@ -76,31 +121,25 @@ export default function AccountsSelectList({
|
||||
);
|
||||
}, []);
|
||||
|
||||
const onAccountSelect = useCallback(
|
||||
// Handle the account item select.
|
||||
const handleAccountSelect = useCallback(
|
||||
(account) => {
|
||||
setSelectedAccount({ ...account });
|
||||
onAccountSelected && onAccountSelected(account);
|
||||
},
|
||||
[setSelectedAccount, onAccountSelected],
|
||||
);
|
||||
|
||||
// Filters accounts items.
|
||||
const filterAccountsPredicater = useCallback(
|
||||
(query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
if (account.id) {
|
||||
setSelectedAccount({ ...account });
|
||||
onAccountSelected && onAccountSelected(account);
|
||||
} else {
|
||||
return (
|
||||
`${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
||||
);
|
||||
openDialog('account-form');
|
||||
}
|
||||
},
|
||||
[],
|
||||
[setSelectedAccount, onAccountSelected, openDialog],
|
||||
);
|
||||
|
||||
// Maybe inject new item props to select component.
|
||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||
const maybeCreateNewItemFromQuery = allowCreate
|
||||
? createNewItemFromQuery
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Select
|
||||
items={filteredAccounts}
|
||||
@@ -113,11 +152,13 @@ export default function AccountsSelectList({
|
||||
inline: popoverFill,
|
||||
}}
|
||||
filterable={true}
|
||||
onItemSelect={onAccountSelect}
|
||||
onItemSelect={handleAccountSelect}
|
||||
disabled={disabled}
|
||||
className={classNames('form-group--select-list', {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||
>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
@@ -127,3 +168,5 @@ export default function AccountsSelectList({
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDialogActions)(AccountsSelectList);
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { Suggest } from '@blueprintjs/select';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
@@ -10,10 +11,55 @@ import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
||||
import { filterAccountsByQuery } from './utils';
|
||||
import { nestedArrayToflatten } from 'utils';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
// Create new account renderer.
|
||||
const createNewItemRenderer = (query, active, handleClick) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon="add"
|
||||
text={`Create "${query}"`}
|
||||
active={active}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// Create new item from the given query string.
|
||||
const createNewItemFromQuery = (name) => {
|
||||
return {
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
// Handle input value renderer.
|
||||
const handleInputValueRenderer = (inputValue) => {
|
||||
if (inputValue) {
|
||||
return inputValue.name.toString();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// Filters accounts items.
|
||||
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Accounts suggest field.
|
||||
*/
|
||||
export default function AccountsSuggestField({
|
||||
function AccountsSuggestField({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #ownProps
|
||||
accounts,
|
||||
initialAccountId,
|
||||
selectedAccountId,
|
||||
@@ -26,6 +72,8 @@ export default function AccountsSuggestField({
|
||||
filterByNormal,
|
||||
filterByRootTypes = [],
|
||||
|
||||
allowCreate,
|
||||
|
||||
...suggestProps
|
||||
}) {
|
||||
const flattenAccounts = useMemo(
|
||||
@@ -69,23 +117,6 @@ export default function AccountsSuggestField({
|
||||
}
|
||||
}, [selectedAccountId, filteredAccounts, setSelectedAccount]);
|
||||
|
||||
// Filters accounts items.
|
||||
const filterAccountsPredicater = useCallback(
|
||||
(query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return (
|
||||
`${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
||||
);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Account item of select accounts field.
|
||||
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
||||
return (
|
||||
@@ -98,28 +129,31 @@ export default function AccountsSuggestField({
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleInputValueRenderer = (inputValue) => {
|
||||
if (inputValue) {
|
||||
return inputValue.name.toString();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const onAccountSelect = useCallback(
|
||||
const handleAccountSelect = useCallback(
|
||||
(account) => {
|
||||
setSelectedAccount({ ...account });
|
||||
onAccountSelected && onAccountSelected(account);
|
||||
if (account.id) {
|
||||
setSelectedAccount({ ...account });
|
||||
onAccountSelected && onAccountSelected(account);
|
||||
} else {
|
||||
openDialog('account-form');
|
||||
}
|
||||
},
|
||||
[setSelectedAccount, onAccountSelected],
|
||||
[setSelectedAccount, onAccountSelected, openDialog],
|
||||
);
|
||||
|
||||
// Maybe inject new item props to select component.
|
||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||
const maybeCreateNewItemFromQuery = allowCreate
|
||||
? createNewItemFromQuery
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Suggest
|
||||
items={filteredAccounts}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccountsPredicater}
|
||||
onItemSelect={onAccountSelect}
|
||||
onItemSelect={handleAccountSelect}
|
||||
selectedItem={selectedAccount}
|
||||
inputProps={{ placeholder: defaultSelectText }}
|
||||
resetOnClose={true}
|
||||
@@ -129,7 +163,11 @@ export default function AccountsSuggestField({
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||
{...suggestProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDialogActions)(AccountsSuggestField);
|
||||
|
||||
@@ -4,8 +4,9 @@ import { DateInput } from '@blueprintjs/datetime';
|
||||
import moment from 'moment';
|
||||
import intl from 'react-intl-universal';
|
||||
import { isUndefined } from 'lodash';
|
||||
|
||||
import { useAutofocus } from 'hooks';
|
||||
import { Choose, ListSelect } from 'components';
|
||||
import { T, Choose, ListSelect } from 'components';
|
||||
import { momentFormatter } from 'utils';
|
||||
|
||||
function AdvancedFilterEnumerationField({ options, value, ...rest }) {
|
||||
@@ -19,7 +20,7 @@ function AdvancedFilterEnumerationField({ options, value, ...rest }) {
|
||||
minimal: true,
|
||||
captureDismiss: true,
|
||||
}}
|
||||
defaultText={`Select an option`}
|
||||
defaultText={<T id={'filter.select_option'} />}
|
||||
textProp={'label'}
|
||||
selectedItemProp={'key'}
|
||||
{...rest}
|
||||
@@ -32,8 +33,7 @@ const IFieldType = {
|
||||
BOOLEAN: 'boolean',
|
||||
NUMBER: 'number',
|
||||
DATE: 'date',
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function tansformDateValue(date, defaultValue = null) {
|
||||
return date ? moment(date).toDate() : defaultValue;
|
||||
@@ -46,13 +46,13 @@ export default function AdvancedFilterValueField2({
|
||||
fieldType,
|
||||
options,
|
||||
onChange,
|
||||
isFocus
|
||||
isFocus,
|
||||
}) {
|
||||
const [localValue, setLocalValue] = React.useState(value);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (localValue !== value && !isUndefined(value)) {
|
||||
setLocalValue(value)
|
||||
setLocalValue(value);
|
||||
}
|
||||
}, [localValue, value]);
|
||||
|
||||
@@ -106,10 +106,10 @@ export default function AdvancedFilterValueField2({
|
||||
position: Position.BOTTOM,
|
||||
}}
|
||||
shortcuts={true}
|
||||
placeholder={'Enter date'}
|
||||
placeholder={intl.get('filter.enter_date')}
|
||||
fill={true}
|
||||
inputProps={{
|
||||
fill: true
|
||||
fill: true,
|
||||
}}
|
||||
/>
|
||||
</Choose.When>
|
||||
@@ -120,7 +120,7 @@ export default function AdvancedFilterValueField2({
|
||||
|
||||
<Choose.Otherwise>
|
||||
<InputGroup
|
||||
placeholder={intl.get('value')}
|
||||
placeholder={intl.get('filter.value')}
|
||||
onChange={handleInputChange}
|
||||
value={localValue}
|
||||
inputRef={valueRef}
|
||||
@@ -128,4 +128,4 @@ export default function AdvancedFilterValueField2({
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,38 @@ import { ReactQueryDevtools } from 'react-query/devtools';
|
||||
|
||||
import 'style/App.scss';
|
||||
import 'moment/locale/ar-ly';
|
||||
import 'moment/locale/es-us'
|
||||
import 'moment/locale/es-us';
|
||||
|
||||
import AppIntlLoader from './AppIntlLoader';
|
||||
import PrivateRoute from 'components/Guards/PrivateRoute';
|
||||
import GlobalErrors from 'containers/GlobalErrors/GlobalErrors';
|
||||
import DashboardPrivatePages from 'components/Dashboard/PrivatePages';
|
||||
import Authentication from 'components/Authentication';
|
||||
import { SplashScreen } from '../components';
|
||||
import { queryConfig } from '../hooks/query/base'
|
||||
|
||||
import { SplashScreen, DashboardThemeProvider } from '../components';
|
||||
import { queryConfig } from '../hooks/query/base';
|
||||
|
||||
/**
|
||||
* App inner.
|
||||
*/
|
||||
function AppInsider({ history }) {
|
||||
return (
|
||||
<div className="App">
|
||||
<DashboardThemeProvider>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={'/auth'} component={Authentication} />
|
||||
<Route path={'/'}>
|
||||
<PrivateRoute component={DashboardPrivatePages} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
|
||||
<GlobalErrors />
|
||||
</DashboardThemeProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Core application.
|
||||
@@ -31,21 +54,10 @@ export default function App() {
|
||||
<SplashScreen />
|
||||
|
||||
<AppIntlLoader>
|
||||
<div className="App">
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={'/auth'} component={Authentication} />
|
||||
<Route path={'/'}>
|
||||
<PrivateRoute component={DashboardPrivatePages} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
|
||||
<GlobalErrors />
|
||||
</div>
|
||||
<AppInsider history={history} />
|
||||
</AppIntlLoader>
|
||||
|
||||
<ReactQueryDevtools initialIsOpen />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import rtlDetect from 'rtl-detect';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { AppIntlProvider } from './AppIntlProvider';
|
||||
import { useSplashLoading } from '../hooks/state';
|
||||
|
||||
import { useWatchImmediate } from '../hooks';
|
||||
import withDashboardActions from '../containers/Dashboard/withDashboardActions';
|
||||
import withDashboard from '../containers/Dashboard/withDashboard';
|
||||
|
||||
const SUPPORTED_LOCALES = [
|
||||
{ name: 'English', value: 'en' },
|
||||
@@ -63,20 +64,13 @@ function transformMomentLocale(currentLocale) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Application Intl loader.
|
||||
* Loads application locales of the given current locale.
|
||||
* @param {string} currentLocale
|
||||
* @returns {{ isLoading: boolean }}
|
||||
*/
|
||||
function AppIntlLoader({ appIntlIsLoading, setAppIntlIsLoading, children }) {
|
||||
const [isLocalsLoading, setIsLocalsLoading] = React.useState(true);
|
||||
const [isYupLoading, setIsYupLoading] = React.useState(true);
|
||||
|
||||
// Retrieve the current locale.
|
||||
const currentLocale = getCurrentLocal();
|
||||
|
||||
// Detarmines the document direction based on the given locale.
|
||||
const isRTL = rtlDetect.isRtlLang(currentLocale);
|
||||
|
||||
// Modifies the html document direction
|
||||
useDocumentDirectionModifier(currentLocale, isRTL);
|
||||
function useAppLoadLocales(currentLocale) {
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Lodas the locales data file.
|
||||
@@ -91,33 +85,72 @@ function AppIntlLoader({ appIntlIsLoading, setAppIntlIsLoading, children }) {
|
||||
})
|
||||
.then(() => {
|
||||
moment.locale(transformMomentLocale(currentLocale));
|
||||
setIsLocalsLoading(false);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [currentLocale, setIsLocalsLoading]);
|
||||
}, [currentLocale, stopLoading]);
|
||||
|
||||
// Watches the value to start/stop splash screen.
|
||||
useWatchImmediate(
|
||||
(value) => (value ? startLoading() : stopLoading()),
|
||||
isLoading,
|
||||
);
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads application yup locales based on the given current locale.
|
||||
* @param {string} currentLocale
|
||||
* @returns {{ isLoading: boolean }}
|
||||
*/
|
||||
function useAppYupLoadLocales(currentLocale) {
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
loadYupLocales(currentLocale)
|
||||
.then(({ locale }) => {
|
||||
setLocale(locale);
|
||||
setIsYupLoading(false);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.then(() => {});
|
||||
}, [currentLocale]);
|
||||
}, [currentLocale, stopLoading]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isLocalsLoading && !isYupLoading) {
|
||||
setAppIntlIsLoading(false);
|
||||
}
|
||||
});
|
||||
// Watches the valiue to start/stop splash screen.
|
||||
useWatchImmediate(
|
||||
(value) => (value ? startLoading() : stopLoading()),
|
||||
isLoading,
|
||||
);
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Application Intl loader.
|
||||
*/
|
||||
function AppIntlLoader({ children }) {
|
||||
// Retrieve the current locale.
|
||||
const currentLocale = getCurrentLocal();
|
||||
|
||||
// Detarmines the document direction based on the given locale.
|
||||
const isRTL = rtlDetect.isRtlLang(currentLocale);
|
||||
|
||||
// Modifies the html document direction
|
||||
useDocumentDirectionModifier(currentLocale, isRTL);
|
||||
|
||||
// Loads yup localization of the given locale.
|
||||
const { isLoading: isAppYupLocalesLoading } =
|
||||
useAppYupLoadLocales(currentLocale);
|
||||
|
||||
// Loads application locales of the given locale.
|
||||
const { isLoading: isAppLocalesLoading } = useAppLoadLocales(currentLocale);
|
||||
|
||||
// Detarmines whether the app locales loading.
|
||||
const isLoading = isAppYupLocalesLoading || isAppLocalesLoading;
|
||||
|
||||
return (
|
||||
<AppIntlProvider currentLocale={currentLocale} isRTL={isRTL}>
|
||||
{appIntlIsLoading ? null : children}
|
||||
{isLoading ? null : children}
|
||||
</AppIntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(
|
||||
withDashboardActions,
|
||||
withDashboard(({ appIntlIsLoading }) => ({ appIntlIsLoading })),
|
||||
)(AppIntlLoader);
|
||||
export default R.compose(withDashboardActions)(AppIntlLoader);
|
||||
|
||||
@@ -10,6 +10,7 @@ function AppIntlProvider({ currentLocale, isRTL, children }) {
|
||||
currentLocale,
|
||||
isRTL,
|
||||
isLTR: !isRTL,
|
||||
direction: isRTL ? 'rtl' : 'ltr',
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,6 @@ import authenticationRoutes from 'routes/authentication';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import Icon from 'components/Icon';
|
||||
import { useIsAuthenticated } from 'hooks/state';
|
||||
import {AuthenticationBoot} from '../containers/Authentication/AuthenticationBoot';
|
||||
import 'style/pages/Authentication/Auth.scss';
|
||||
|
||||
function PageFade(props) {
|
||||
@@ -26,7 +25,6 @@ export default function AuthenticationWrapper({ ...rest }) {
|
||||
) : (
|
||||
<BodyClassName className={'authentication'}>
|
||||
<div class="authentication-page">
|
||||
<AuthenticationBoot />
|
||||
<a
|
||||
href={'http://bigcapital.ly'}
|
||||
className={'authentication-page__goto-bigcapital'}
|
||||
|
||||
10
src/components/AvaterCell.js
Normal file
10
src/components/AvaterCell.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { firstLettersArgs } from 'utils';
|
||||
|
||||
export default function AvatarCell({ row: { original }, size }) {
|
||||
return (
|
||||
<span className="avatar" data-size={size}>
|
||||
{firstLettersArgs(original?.display_name)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
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;`)}
|
||||
`;
|
||||
13
src/components/Button/ButtonLink.js
Normal file
13
src/components/Button/ButtonLink.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ButtonLink = styled.button`
|
||||
color: #0052cc;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
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';
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
||||
export default function Card({ className, children }) {
|
||||
return <div className={classNames('card', className)}>{children}</div>;
|
||||
}
|
||||
|
||||
@@ -11,12 +11,14 @@ export default function ContactSelecetList({
|
||||
contactsList,
|
||||
initialContactId,
|
||||
selectedContactId,
|
||||
selectedContactType,
|
||||
createNewItemFrom,
|
||||
defaultSelectText = <T id={'select_contact'} />,
|
||||
onContactSelected,
|
||||
popoverFill = false,
|
||||
disabled = false,
|
||||
buttonProps,
|
||||
|
||||
...restProps
|
||||
}) {
|
||||
const contacts = useMemo(
|
||||
() =>
|
||||
@@ -65,7 +67,7 @@ export default function ContactSelecetList({
|
||||
);
|
||||
|
||||
// Filter Contact List
|
||||
const filterContacts = (query, contact, index, exactMatch) => {
|
||||
const itemPredicate = (query, contact, index, exactMatch) => {
|
||||
const normalizedTitle = contact.display_name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
if (exactMatch) {
|
||||
@@ -83,7 +85,7 @@ export default function ContactSelecetList({
|
||||
items={contacts}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||
itemRenderer={handleContactRenderer}
|
||||
itemPredicate={filterContacts}
|
||||
itemPredicate={itemPredicate}
|
||||
filterable={true}
|
||||
disabled={disabled}
|
||||
onItemSelect={onContactSelect}
|
||||
@@ -92,8 +94,9 @@ export default function ContactSelecetList({
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
inputProps={{
|
||||
placeholder: intl.get('filter_')
|
||||
placeholder: intl.get('filter_'),
|
||||
}}
|
||||
{...restProps}
|
||||
>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
|
||||
86
src/components/Contacts/ContactSelectField.js
Normal file
86
src/components/Contacts/ContactSelectField.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import { itemPredicate, handleContactRenderer } from './utils';
|
||||
|
||||
export default function ContactSelectField({
|
||||
contacts,
|
||||
initialContactId,
|
||||
selectedContactId,
|
||||
defaultSelectText = <T id={'select_contact'} />,
|
||||
onContactSelected,
|
||||
popoverFill = false,
|
||||
disabled = false,
|
||||
buttonProps,
|
||||
|
||||
...restProps
|
||||
}) {
|
||||
const localContacts = useMemo(
|
||||
() =>
|
||||
contacts.map((contact) => ({
|
||||
...contact,
|
||||
_id: `${contact.id}_${contact.contact_type}`,
|
||||
})),
|
||||
[contacts],
|
||||
);
|
||||
|
||||
const initialContact = useMemo(
|
||||
() => contacts.find((a) => a.id === initialContactId),
|
||||
[initialContactId, contacts],
|
||||
);
|
||||
|
||||
const [selecetedContact, setSelectedContact] = useState(
|
||||
initialContact || null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof selectedContactId !== 'undefined') {
|
||||
const account = selectedContactId
|
||||
? contacts.find((a) => a.id === selectedContactId)
|
||||
: null;
|
||||
setSelectedContact(account);
|
||||
}
|
||||
}, [selectedContactId, contacts, setSelectedContact]);
|
||||
|
||||
const handleContactSelect = useCallback(
|
||||
(contact) => {
|
||||
setSelectedContact({ ...contact });
|
||||
onContactSelected && onContactSelected(contact);
|
||||
},
|
||||
[setSelectedContact, onContactSelected],
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
items={localContacts}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||
itemRenderer={handleContactRenderer}
|
||||
itemPredicate={itemPredicate}
|
||||
filterable={true}
|
||||
disabled={disabled}
|
||||
onItemSelect={handleContactSelect}
|
||||
popoverProps={{ minimal: true, usePortal: !popoverFill }}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
inputProps={{
|
||||
placeholder: intl.get('filter_'),
|
||||
}}
|
||||
{...restProps}
|
||||
>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
text={
|
||||
selecetedContact ? selecetedContact.display_name : defaultSelectText
|
||||
}
|
||||
{...buttonProps}
|
||||
/>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
116
src/components/Contacts/CustomerSelectField.js
Normal file
116
src/components/Contacts/CustomerSelectField.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import {
|
||||
itemPredicate,
|
||||
handleContactRenderer,
|
||||
createNewItemRenderer,
|
||||
createNewItemFromQuery,
|
||||
} from './utils';
|
||||
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { DRAWERS } from 'common/drawers';
|
||||
|
||||
function CustomerSelectField({
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
|
||||
// #ownProps
|
||||
contacts,
|
||||
initialContactId,
|
||||
selectedContactId,
|
||||
defaultSelectText = <T id={'select_contact'} />,
|
||||
onContactSelected,
|
||||
popoverFill = false,
|
||||
disabled = false,
|
||||
allowCreate,
|
||||
buttonProps,
|
||||
|
||||
...restProps
|
||||
}) {
|
||||
const localContacts = useMemo(
|
||||
() =>
|
||||
contacts.map((contact) => ({
|
||||
...contact,
|
||||
_id: `${contact.id}_${contact.contact_type}`,
|
||||
})),
|
||||
[contacts],
|
||||
);
|
||||
|
||||
const initialContact = useMemo(
|
||||
() => contacts.find((a) => a.id === initialContactId),
|
||||
[initialContactId, contacts],
|
||||
);
|
||||
|
||||
const [selecetedContact, setSelectedContact] = useState(
|
||||
initialContact || null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof selectedContactId !== 'undefined') {
|
||||
const account = selectedContactId
|
||||
? contacts.find((a) => a.id === selectedContactId)
|
||||
: null;
|
||||
setSelectedContact(account);
|
||||
}
|
||||
}, [selectedContactId, contacts, setSelectedContact]);
|
||||
|
||||
const handleContactSelect = useCallback(
|
||||
(contact) => {
|
||||
if (contact.id) {
|
||||
setSelectedContact({ ...contact });
|
||||
onContactSelected && onContactSelected(contact);
|
||||
} else {
|
||||
openDrawer(DRAWERS.QUICK_CREATE_CUSTOMER);
|
||||
}
|
||||
},
|
||||
[setSelectedContact, onContactSelected, openDrawer],
|
||||
);
|
||||
|
||||
// Maybe inject create new item props to suggest component.
|
||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||
const maybeCreateNewItemFromQuery = allowCreate
|
||||
? createNewItemFromQuery
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Select
|
||||
items={localContacts}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||
itemRenderer={handleContactRenderer}
|
||||
itemPredicate={itemPredicate}
|
||||
filterable={true}
|
||||
disabled={disabled}
|
||||
onItemSelect={handleContactSelect}
|
||||
popoverProps={{ minimal: true, usePortal: !popoverFill }}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
inputProps={{
|
||||
placeholder: intl.get('filter_'),
|
||||
}}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||
createNewItemPosition={'top'}
|
||||
{...restProps}
|
||||
>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
text={
|
||||
selecetedContact ? selecetedContact.display_name : defaultSelectText
|
||||
}
|
||||
{...buttonProps}
|
||||
/>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDrawerActions)(CustomerSelectField);
|
||||
115
src/components/Contacts/VendorSelectField.js
Normal file
115
src/components/Contacts/VendorSelectField.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import {
|
||||
itemPredicate,
|
||||
handleContactRenderer,
|
||||
createNewItemFromQuery,
|
||||
createNewItemRenderer,
|
||||
} from './utils';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { DRAWERS } from 'common/drawers';
|
||||
|
||||
function VendorSelectField({
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
|
||||
// #ownProps
|
||||
contacts,
|
||||
initialContactId,
|
||||
selectedContactId,
|
||||
defaultSelectText = <T id={'select_contact'} />,
|
||||
onContactSelected,
|
||||
popoverFill = false,
|
||||
disabled = false,
|
||||
allowCreate,
|
||||
buttonProps,
|
||||
|
||||
...restProps
|
||||
}) {
|
||||
const localContacts = useMemo(
|
||||
() =>
|
||||
contacts.map((contact) => ({
|
||||
...contact,
|
||||
_id: `${contact.id}_${contact.contact_type}`,
|
||||
})),
|
||||
[contacts],
|
||||
);
|
||||
|
||||
const initialContact = useMemo(
|
||||
() => contacts.find((a) => a.id === initialContactId),
|
||||
[initialContactId, contacts],
|
||||
);
|
||||
|
||||
const [selecetedContact, setSelectedContact] = useState(
|
||||
initialContact || null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof selectedContactId !== 'undefined') {
|
||||
const account = selectedContactId
|
||||
? contacts.find((a) => a.id === selectedContactId)
|
||||
: null;
|
||||
setSelectedContact(account);
|
||||
}
|
||||
}, [selectedContactId, contacts, setSelectedContact]);
|
||||
|
||||
const handleContactSelect = useCallback(
|
||||
(contact) => {
|
||||
if (contact.id) {
|
||||
setSelectedContact({ ...contact });
|
||||
onContactSelected && onContactSelected(contact);
|
||||
} else {
|
||||
openDrawer(DRAWERS.QUICK_WRITE_VENDOR);
|
||||
}
|
||||
},
|
||||
[setSelectedContact, onContactSelected, openDrawer],
|
||||
);
|
||||
|
||||
// Maybe inject create new item props to suggest component.
|
||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||
const maybeCreateNewItemFromQuery = allowCreate
|
||||
? createNewItemFromQuery
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Select
|
||||
items={localContacts}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||
itemRenderer={handleContactRenderer}
|
||||
itemPredicate={itemPredicate}
|
||||
filterable={true}
|
||||
disabled={disabled}
|
||||
onItemSelect={handleContactSelect}
|
||||
popoverProps={{ minimal: true, usePortal: !popoverFill }}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
inputProps={{
|
||||
placeholder: intl.get('filter_'),
|
||||
}}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||
createNewItemPosition={'top'}
|
||||
{...restProps}
|
||||
>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
text={
|
||||
selecetedContact ? selecetedContact.display_name : defaultSelectText
|
||||
}
|
||||
{...buttonProps}
|
||||
/>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDrawerActions)(VendorSelectField);
|
||||
5
src/components/Contacts/index.js
Normal file
5
src/components/Contacts/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import ContactSelectField from './ContactSelectField';
|
||||
import CustomerSelectField from './CustomerSelectField';
|
||||
import VendorSelectField from './VendorSelectField';
|
||||
|
||||
export { ContactSelectField, CustomerSelectField, VendorSelectField };
|
||||
43
src/components/Contacts/utils.js
Normal file
43
src/components/Contacts/utils.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
|
||||
// Filter Contact List
|
||||
export const itemPredicate = (query, contact, index, exactMatch) => {
|
||||
const normalizedTitle = contact.display_name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return (
|
||||
`${contact.display_name} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const handleContactRenderer = (contact, { handleClick }) => (
|
||||
<MenuItem
|
||||
key={contact.id}
|
||||
text={contact.display_name}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
|
||||
// Creates a new item from query.
|
||||
export const createNewItemFromQuery = (name) => {
|
||||
return {
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
// Handle quick create new customer.
|
||||
export const createNewItemRenderer = (query, active, handleClick) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon="add"
|
||||
text={`Create "${query}"`}
|
||||
active={active}
|
||||
shouldDismissPopover={false}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import DashboardSplitPane from 'components/Dashboard/DashboardSplitePane';
|
||||
import GlobalHotkeys from './GlobalHotkeys';
|
||||
import DashboardProvider from './DashboardProvider';
|
||||
import DrawersContainer from 'components/DrawersContainer';
|
||||
import AlertsContainer from 'containers/AlertsContainer';
|
||||
import EnsureSubscriptionIsActive from '../Guards/EnsureSubscriptionIsActive';
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,7 @@ export default function Dashboard() {
|
||||
<DialogsContainer />
|
||||
<GlobalHotkeys />
|
||||
<DrawersContainer />
|
||||
<AlertsContainer />
|
||||
</DashboardProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { useUser, useCurrentOrganization } from 'hooks/query';
|
||||
import { useUser, useCurrentOrganization } from '../../hooks/query';
|
||||
import { useSplashLoading } from '../../hooks/state';
|
||||
import { useWatch, useWatchImmediate, useWhen } from '../../hooks';
|
||||
|
||||
import withAuthentication from '../../containers/Authentication/withAuthentication';
|
||||
import withDashboardActions from '../../containers/Dashboard/withDashboardActions';
|
||||
|
||||
import { setCookie, getCookie } from '../../utils';
|
||||
|
||||
/**
|
||||
* Dashboard async booting.
|
||||
*/
|
||||
function DashboardBootJSX({ setAppIsLoading, authenticatedUserId }) {
|
||||
function DashboardBootJSX({ authenticatedUserId }) {
|
||||
// Fetches the current user's organization.
|
||||
const { isSuccess: isCurrentOrganizationSuccess, data: organization } =
|
||||
useCurrentOrganization();
|
||||
const {
|
||||
isSuccess: isCurrentOrganizationSuccess,
|
||||
isLoading: isOrgLoading,
|
||||
data: organization,
|
||||
} = useCurrentOrganization();
|
||||
|
||||
// Authenticated user.
|
||||
const { isSuccess: isAuthUserSuccess, data: authUser } =
|
||||
const { isSuccess: isAuthUserSuccess, isLoading: isAuthUserLoading } =
|
||||
useUser(authenticatedUserId);
|
||||
|
||||
// Initial locale cookie value.
|
||||
@@ -48,23 +53,39 @@ function DashboardBootJSX({ setAppIsLoading, authenticatedUserId }) {
|
||||
}
|
||||
}, [localeCookie, organization]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Once the all requests complete change the app loading state.
|
||||
if (
|
||||
isAuthUserSuccess &&
|
||||
const [startLoading, stopLoading] = useSplashLoading();
|
||||
|
||||
// Splash loading when organization request loading and
|
||||
// applicaiton still not booted.
|
||||
useWatchImmediate((value) => {
|
||||
value && !isBooted.current && startLoading();
|
||||
}, isOrgLoading);
|
||||
|
||||
// Splash loading when request authenticated user loading and
|
||||
// application still not booted yet.
|
||||
useWatchImmediate((value) => {
|
||||
value && !isBooted.current && startLoading();
|
||||
}, isAuthUserLoading);
|
||||
|
||||
// Stop splash loading once organization request success.
|
||||
useWatch((value) => {
|
||||
value && stopLoading();
|
||||
}, isCurrentOrganizationSuccess);
|
||||
|
||||
// Stop splash loading once authenticated user request success.
|
||||
useWatch((value) => {
|
||||
value && stopLoading();
|
||||
}, isAuthUserSuccess);
|
||||
|
||||
// Once the all requests complete change the app loading state.
|
||||
useWhen(
|
||||
isAuthUserSuccess &&
|
||||
isCurrentOrganizationSuccess &&
|
||||
localeCookie === organization?.metadata?.language
|
||||
) {
|
||||
setAppIsLoading(false);
|
||||
localeCookie === organization?.metadata?.language,
|
||||
() => {
|
||||
isBooted.current = true;
|
||||
}
|
||||
}, [
|
||||
isAuthUserSuccess,
|
||||
isCurrentOrganizationSuccess,
|
||||
organization,
|
||||
setAppIsLoading,
|
||||
localeCookie,
|
||||
]);
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -72,5 +93,4 @@ export const DashboardBoot = R.compose(
|
||||
withAuthentication(({ authenticatedUserId }) => ({
|
||||
authenticatedUserId,
|
||||
})),
|
||||
withDashboardActions,
|
||||
)(DashboardBootJSX);
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import DashboardTopbar from 'components/Dashboard/DashboardTopbar';
|
||||
import DashboardContentRoutes from 'components/Dashboard/DashboardContentRoute';
|
||||
import DashboardFooter from 'components/Dashboard/DashboardFooter';
|
||||
import DashboardErrorBoundary from './DashboardErrorBoundary';
|
||||
|
||||
export default React.forwardRef(({}, ref) => {
|
||||
@@ -11,7 +10,6 @@ export default React.forwardRef(({}, ref) => {
|
||||
<div className="dashboard-content" id="dashboard" ref={ref}>
|
||||
<DashboardTopbar />
|
||||
<DashboardContentRoutes />
|
||||
<DashboardFooter />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
PopoverInteractionKind,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Classes
|
||||
} from '@blueprintjs/core';
|
||||
import { Icon } from 'components';
|
||||
|
||||
export function DashboardRowsHeightButton() {
|
||||
return (
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={
|
||||
<Menu>
|
||||
<MenuDivider title={'Rows height'} />
|
||||
<MenuItem text="Compact" />
|
||||
<MenuItem text="Medium" />
|
||||
</Menu>
|
||||
}
|
||||
placement="bottom-start"
|
||||
modifiers={{
|
||||
offset: { offset: '0, 4' },
|
||||
}}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="rows-height" iconSize={16} />}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
70
src/components/Dashboard/DashboardRowsHeightButton/index.js
Normal file
70
src/components/Dashboard/DashboardRowsHeightButton/index.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
PopoverInteractionKind,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Classes,
|
||||
Tooltip,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import clsx from 'classnames';
|
||||
import { Icon, T } from 'components';
|
||||
|
||||
import Style from './style.module.scss';
|
||||
|
||||
/**
|
||||
* Dashboard rows height button control.
|
||||
*/
|
||||
export function DashboardRowsHeightButton({ initialValue, value, onChange }) {
|
||||
const [localSize, setLocalSize] = React.useState(initialValue);
|
||||
|
||||
// Handle menu item click.
|
||||
const handleItemClick = (size) => (event) => {
|
||||
setLocalSize(size);
|
||||
onChange && onChange(size, event);
|
||||
};
|
||||
// Button icon name.
|
||||
const btnIcon = `table-row-${localSize}`;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={
|
||||
<Menu className={Style.menu}>
|
||||
<MenuDivider title={<T id={'dashboard.rows_height'} />} />
|
||||
<MenuItem
|
||||
onClick={handleItemClick('small')}
|
||||
text={<T id={'dashboard.row_small'} />}
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={handleItemClick('medium')}
|
||||
text={<T id={'dashboard.row_medium'} />}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
placement="bottom-start"
|
||||
modifiers={{
|
||||
offset: { offset: '0, 4' },
|
||||
}}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
>
|
||||
<Tooltip
|
||||
content={<T id={'dashboard.rows_height'} />}
|
||||
minimal={true}
|
||||
position={Position.BOTTOM}
|
||||
>
|
||||
<Button
|
||||
className={clsx(Classes.MINIMAL, Style.button)}
|
||||
icon={<Icon icon={btnIcon} iconSize={16} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
DashboardRowsHeightButton.defaultProps = {
|
||||
initialValue: 'medium',
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
.menu{
|
||||
:global .bp3-heading{
|
||||
font-weight: 400;
|
||||
opacity: 0.5;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.button{
|
||||
min-width: 34px;
|
||||
}
|
||||
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 { getDashboardRoutes } from 'routes/dashboard';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function GlobalHotkeys({
|
||||
// #withDashboardActions
|
||||
toggleSidebarExpend,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const routes = getDashboardRoutes();
|
||||
@@ -16,8 +21,8 @@ function GlobalHotkeys({
|
||||
.filter(({ hotkey }) => hotkey)
|
||||
.map(({ hotkey }) => hotkey)
|
||||
.toString();
|
||||
|
||||
const handleSidebarToggleBtn = () => {
|
||||
|
||||
const handleSidebarToggleBtn = () => {
|
||||
toggleSidebarExpend();
|
||||
};
|
||||
useHotkeys(
|
||||
@@ -32,7 +37,9 @@ function GlobalHotkeys({
|
||||
[history],
|
||||
);
|
||||
useHotkeys('ctrl+/', (event, handle) => handleSidebarToggleBtn());
|
||||
useHotkeys('shift+d', (event, handle) => openDialog('money-in', {}));
|
||||
useHotkeys('shift+q', (event, handle) => openDialog('money-out', {}));
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
export default compose(withDashboardActions)(GlobalHotkeys);
|
||||
export default compose(withDashboardActions, withDialogActions)(GlobalHotkeys);
|
||||
|
||||
@@ -4,10 +4,9 @@ import { Switch, Route } from 'react-router';
|
||||
import Dashboard from 'components/Dashboard/Dashboard';
|
||||
import SetupWizardPage from 'containers/Setup/WizardSetupPage';
|
||||
|
||||
import EnsureOrganizationIsReady from 'components/Guards/EnsureOrganizationIsReady';
|
||||
import EnsureOrganizationIsNotReady from 'components/Guards/EnsureOrganizationIsNotReady';
|
||||
import EnsureOrganizationIsReady from '../../components/Guards/EnsureOrganizationIsReady';
|
||||
import EnsureOrganizationIsNotReady from '../../components/Guards/EnsureOrganizationIsNotReady';
|
||||
import { PrivatePagesProvider } from './PrivatePagesProvider';
|
||||
import { DashboardBoot } from '../../components';
|
||||
|
||||
import 'style/pages/Dashboard/Dashboard.scss';
|
||||
|
||||
@@ -17,8 +16,6 @@ import 'style/pages/Dashboard/Dashboard.scss';
|
||||
export default function DashboardPrivatePages() {
|
||||
return (
|
||||
<PrivatePagesProvider>
|
||||
<DashboardBoot />
|
||||
|
||||
<Switch>
|
||||
<Route path={'/setup'}>
|
||||
<EnsureOrganizationIsNotReady>
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { AuthenticatedUser } from './AuthenticatedUser';
|
||||
import { DashboardBoot } from '../../components';
|
||||
|
||||
import withDashboard from '../../containers/Dashboard/withDashboard';
|
||||
|
||||
/**
|
||||
* Private pages provider.
|
||||
*/
|
||||
export function PrivatePagesProvider({ children }) {
|
||||
return <AuthenticatedUser>{children}</AuthenticatedUser>;
|
||||
function PrivatePagesProviderComponent({
|
||||
splashScreenCompleted,
|
||||
|
||||
// #ownProps
|
||||
children,
|
||||
}) {
|
||||
return (
|
||||
<AuthenticatedUser>
|
||||
<DashboardBoot />
|
||||
|
||||
{splashScreenCompleted ? children : null}
|
||||
</AuthenticatedUser>
|
||||
);
|
||||
}
|
||||
|
||||
export const PrivatePagesProvider = R.compose(
|
||||
withDashboard(({ splashScreenCompleted }) => ({
|
||||
splashScreenCompleted,
|
||||
})),
|
||||
)(PrivatePagesProviderComponent);
|
||||
|
||||
@@ -3,13 +3,12 @@ import * as R from 'ramda';
|
||||
import BigcapitalLoading from './BigcapitalLoading';
|
||||
import withDashboard from '../../containers/Dashboard/withDashboard';
|
||||
|
||||
function SplashScreenComponent({ appIsLoading, appIntlIsLoading }) {
|
||||
return appIsLoading || appIntlIsLoading ? <BigcapitalLoading /> : null;
|
||||
function SplashScreenComponent({ splashScreenLoading }) {
|
||||
return splashScreenLoading ? <BigcapitalLoading /> : null;
|
||||
}
|
||||
|
||||
export const SplashScreen = R.compose(
|
||||
withDashboard(({ appIsLoading, appIntlIsLoading }) => ({
|
||||
appIsLoading,
|
||||
appIntlIsLoading,
|
||||
withDashboard(({ splashScreenLoading }) => ({
|
||||
splashScreenLoading,
|
||||
})),
|
||||
)(SplashScreenComponent);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
|
||||
export * from './SplashScreen';
|
||||
export * from './DashboardBoot';
|
||||
export * from './DashboardBoot';
|
||||
export * from './DashboardThemeProvider';
|
||||
|
||||
@@ -196,6 +196,9 @@ export default function DataTable(props) {
|
||||
|
||||
DataTable.defaultProps = {
|
||||
pagination: false,
|
||||
hidePaginationNoPages: true,
|
||||
|
||||
size: null,
|
||||
spinnerProps: { size: 30 },
|
||||
|
||||
expandToggleColumn: 1,
|
||||
|
||||
@@ -17,6 +17,8 @@ export default function AccountCellRenderer({
|
||||
accountsDataProp,
|
||||
filterAccountsByRootTypes,
|
||||
filterAccountsByTypes,
|
||||
fieldProps,
|
||||
formGroupProps,
|
||||
},
|
||||
row: { index, original },
|
||||
cell: { value: initialValue },
|
||||
@@ -53,6 +55,7 @@ export default function AccountCellRenderer({
|
||||
'form-group--account',
|
||||
Classes.FILL,
|
||||
)}
|
||||
{...formGroupProps}
|
||||
>
|
||||
<AccountsSuggestField
|
||||
accounts={accounts}
|
||||
@@ -66,6 +69,7 @@ export default function AccountCellRenderer({
|
||||
}}
|
||||
openOnKeyDown={true}
|
||||
blurOnSelectClose={false}
|
||||
{...fieldProps}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
// import ItemsListField from 'components/ItemsListField';
|
||||
import ItemsSuggestField from 'components/ItemsSuggestField';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import ItemsSuggestField from 'components/ItemsSuggestField';
|
||||
|
||||
import { useCellAutoFocus } from 'hooks';
|
||||
|
||||
/**
|
||||
* Items list cell.
|
||||
*/
|
||||
export default function ItemsListCell({
|
||||
column: { id, filterSellable, filterPurchasable },
|
||||
column: { id, filterSellable, filterPurchasable, fieldProps, formGroupProps },
|
||||
row: { index },
|
||||
cell: { value: initialValue },
|
||||
payload: { items, updateData, errors, autoFocus },
|
||||
@@ -19,6 +21,7 @@ export default function ItemsListCell({
|
||||
// Auto-focus the items list input field.
|
||||
useCellAutoFocus(fieldRef, autoFocus, id, index);
|
||||
|
||||
// Handle the item selected.
|
||||
const handleItemSelected = useCallback(
|
||||
(item) => {
|
||||
updateData(index, id, item.id);
|
||||
@@ -32,6 +35,7 @@ export default function ItemsListCell({
|
||||
<FormGroup
|
||||
intent={error ? Intent.DANGER : null}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
{...formGroupProps}
|
||||
>
|
||||
<ItemsSuggestField
|
||||
items={items}
|
||||
@@ -45,6 +49,7 @@ export default function ItemsListCell({
|
||||
}}
|
||||
openOnKeyDown={true}
|
||||
blurOnSelectClose={false}
|
||||
{...fieldProps}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
||||
51
src/components/DataTableCells/SwitchFieldCell.js
Normal file
51
src/components/DataTableCells/SwitchFieldCell.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Classes, Switch, FormGroup, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { safeInvoke } from 'utils';
|
||||
|
||||
/**
|
||||
* Switch editable cell.
|
||||
*/
|
||||
const SwitchEditableCell = ({
|
||||
row: { index, original },
|
||||
column: { id, switchProps, onSwitchChange },
|
||||
cell: { value: initialValue },
|
||||
payload,
|
||||
}) => {
|
||||
const [value, setValue] = React.useState(initialValue);
|
||||
|
||||
// Handle the switch change.
|
||||
const onChange = (e) => {
|
||||
const newValue = e.target.checked;
|
||||
|
||||
setValue(newValue);
|
||||
|
||||
safeInvoke(payload.updateData, index, id, newValue);
|
||||
safeInvoke(onSwitchChange, e, newValue, original);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const error = payload.errors?.[index]?.[id];
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
intent={error ? Intent.DANGER : null}
|
||||
className={classNames(Classes.FILL)}
|
||||
>
|
||||
<Switch
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
checked={initialValue}
|
||||
minimal={true}
|
||||
className="ml2"
|
||||
{...switchProps}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwitchEditableCell;
|
||||
42
src/components/DataTableCells/TextAreaCell.js
Normal file
42
src/components/DataTableCells/TextAreaCell.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Classes, TextArea, FormGroup, Intent } from '@blueprintjs/core';
|
||||
|
||||
const TextAreaEditableCell = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value: initialValue },
|
||||
payload,
|
||||
}) => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
const onChange = (e) => {
|
||||
setValue(e.target.value);
|
||||
};
|
||||
const onBlur = () => {
|
||||
payload.updateData(index, id, value);
|
||||
};
|
||||
useEffect(() => {
|
||||
setValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
const error = payload.errors?.[index]?.[id];
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
intent={error ? Intent.DANGER : null}
|
||||
className={classNames(Classes.FILL)}
|
||||
>
|
||||
<TextArea
|
||||
growVertically={true}
|
||||
large={true}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
fill={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextAreaEditableCell;
|
||||
@@ -6,7 +6,9 @@ import ItemsListCell from './ItemsListCell';
|
||||
import PercentFieldCell from './PercentFieldCell';
|
||||
import { DivFieldCell, EmptyDiv } from './DivFieldCell';
|
||||
import NumericInputCell from './NumericInputCell';
|
||||
import CheckBoxFieldCell from './CheckBoxFieldCell'
|
||||
import CheckBoxFieldCell from './CheckBoxFieldCell';
|
||||
import SwitchFieldCell from './SwitchFieldCell';
|
||||
import TextAreaCell from './TextAreaCell';
|
||||
|
||||
export {
|
||||
AccountsListFieldCell,
|
||||
@@ -18,5 +20,7 @@ export {
|
||||
DivFieldCell,
|
||||
EmptyDiv,
|
||||
NumericInputCell,
|
||||
CheckBoxFieldCell
|
||||
CheckBoxFieldCell,
|
||||
SwitchFieldCell,
|
||||
TextAreaCell,
|
||||
};
|
||||
|
||||
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,9 +4,11 @@ import { If } from 'components';
|
||||
import { Skeleton } from 'components';
|
||||
import { useAppIntlContext } from 'components/AppIntlProvider';
|
||||
import TableContext from './TableContext';
|
||||
import { saveInvoke } from 'utils';
|
||||
import { saveInvoke, ignoreEventFromSelectors } from 'utils';
|
||||
import { isCellLoading } from './utils';
|
||||
|
||||
const ROW_CLICK_SELECTORS_INGORED = ['.expand-toggle', '.selection-checkbox'];
|
||||
|
||||
/**
|
||||
* Table cell.
|
||||
*/
|
||||
@@ -50,6 +52,9 @@ export default function TableCell({ cell, row, index }) {
|
||||
}
|
||||
// Handle cell click action.
|
||||
const handleCellClick = (event) => {
|
||||
if (ignoreEventFromSelectors(event, ROW_CLICK_SELECTORS_INGORED)) {
|
||||
return;
|
||||
}
|
||||
saveInvoke(onCellClick, cell, event);
|
||||
};
|
||||
|
||||
@@ -58,7 +63,7 @@ export default function TableCell({ cell, row, index }) {
|
||||
{...cell.getCellProps({
|
||||
className: classNames(cell.column.className, 'td', {
|
||||
'is-text-overview': cell.column.textOverview,
|
||||
'clickable': cell.column.clickable,
|
||||
clickable: cell.column.clickable,
|
||||
'align-right': cell.column.align === 'right',
|
||||
}),
|
||||
onClick: handleCellClick,
|
||||
@@ -83,11 +88,15 @@ export default function TableCell({ cell, row, index }) {
|
||||
// to build the toggle for expanding a row
|
||||
}
|
||||
<If condition={cell.row.canExpand && expandable && isExpandColumn}>
|
||||
<span {...getToggleRowExpandedProps({ className: 'expand-toggle' })}>
|
||||
<span
|
||||
{...getToggleRowExpandedProps({
|
||||
className: 'expand-toggle',
|
||||
})}
|
||||
style={{}}
|
||||
>
|
||||
<span
|
||||
className={classNames({
|
||||
'arrow-down': isExpanded,
|
||||
'arrow-right': !isExpanded,
|
||||
className={classNames('expand-arrow', {
|
||||
'is-expanded': isExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Checkbox } from '@blueprintjs/core';
|
||||
|
||||
export default function TableIndeterminateCheckboxRow({ row }) {
|
||||
return (
|
||||
<div>
|
||||
<div class="selection-checkbox">
|
||||
<Checkbox {...row.getToggleRowSelectedProps()} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,12 +14,15 @@ export default function TablePagination() {
|
||||
pageCount,
|
||||
state: { pageIndex, pageSize },
|
||||
},
|
||||
props: { pagination, loading, onPaginationChange },
|
||||
props: { pagination, loading, onPaginationChange, hidePaginationNoPages },
|
||||
} = useContext(TableContext);
|
||||
|
||||
const triggerOnPaginationChange = useCallback((payload) => {
|
||||
saveInvoke(onPaginationChange, payload)
|
||||
}, [onPaginationChange]);
|
||||
const triggerOnPaginationChange = useCallback(
|
||||
(payload) => {
|
||||
saveInvoke(onPaginationChange, payload);
|
||||
},
|
||||
[onPaginationChange],
|
||||
);
|
||||
|
||||
// Handles the page changing.
|
||||
const handlePageChange = useCallback(
|
||||
@@ -45,8 +48,14 @@ export default function TablePagination() {
|
||||
[gotoPage, setPageSize, triggerOnPaginationChange],
|
||||
);
|
||||
|
||||
// Detarmines when display the pagination.
|
||||
const showPagination =
|
||||
pagination &&
|
||||
((hidePaginationNoPages && pageCount > 1) || !hidePaginationNoPages) &&
|
||||
!loading;
|
||||
|
||||
return (
|
||||
<If condition={pagination && !loading}>
|
||||
showPagination && (
|
||||
<Pagination
|
||||
currentPage={pageIndex + 1}
|
||||
total={pageSize * pageCount}
|
||||
@@ -54,6 +63,6 @@ export default function TablePagination() {
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
/>
|
||||
</If>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import clsx from 'classnames';
|
||||
import { ScrollSync } from 'react-scroll-sync';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
@@ -9,12 +9,20 @@ import TableContext from './TableContext';
|
||||
export default function TableWrapper({ children }) {
|
||||
const {
|
||||
table: { getTableProps },
|
||||
props: { sticky, pagination, loading, expandable, virtualizedRows, className },
|
||||
props: {
|
||||
sticky,
|
||||
pagination,
|
||||
loading,
|
||||
expandable,
|
||||
virtualizedRows,
|
||||
className,
|
||||
size,
|
||||
},
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('bigcapital-datatable', className, {
|
||||
className={clsx('bigcapital-datatable', className, {
|
||||
'has-sticky': sticky,
|
||||
'has-pagination': pagination,
|
||||
'is-expandable': expandable,
|
||||
@@ -25,7 +33,9 @@ export default function TableWrapper({ children }) {
|
||||
<ScrollSync>
|
||||
<div
|
||||
{...getTableProps({ style: { minWidth: 'none' } })}
|
||||
className="table"
|
||||
className={clsx('table', {
|
||||
[`table-size--${size}`]: size,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
4
src/components/Datatable/index.js
Normal file
4
src/components/Datatable/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
|
||||
export * from './CellForceWidth';
|
||||
@@ -9,16 +9,16 @@ function DialogComponent(props) {
|
||||
const { name, children, closeDialog, onClose } = props;
|
||||
|
||||
const handleClose = (event) => {
|
||||
closeDialog(name)
|
||||
closeDialog(name);
|
||||
onClose && onClose(event);
|
||||
};
|
||||
return (
|
||||
<Dialog {...props} onClose={handleClose}>
|
||||
{ children }
|
||||
{children}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
)(DialogComponent);
|
||||
const DialogRoot = compose(withDialogActions)(DialogComponent);
|
||||
|
||||
export { DialogRoot as Dialog };
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Spinner, Classes } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default function DialogContent(props) {
|
||||
export function DialogContent(props) {
|
||||
const { isLoading, children } = props;
|
||||
|
||||
const loadingContent = (
|
||||
|
||||
26
src/components/Dialog/DialogFooterActions.js
Normal file
26
src/components/Dialog/DialogFooterActions.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
|
||||
export function DialogFooterActions({ alignment = 'right', children }) {
|
||||
return (
|
||||
<DialogFooterActionsRoot
|
||||
className={Classes.DIALOG_FOOTER_ACTIONS}
|
||||
alignment={alignment}
|
||||
>
|
||||
{children}
|
||||
</DialogFooterActionsRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const DialogFooterActionsRoot = styled.div`
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
justify-content: ${(props) =>
|
||||
props.alignment === 'right' ? 'flex-end' : 'flex-start'};
|
||||
|
||||
.bp3-button {
|
||||
margin-left: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
`;
|
||||
@@ -5,7 +5,7 @@ function LoadingContent() {
|
||||
return (<div className={Classes.DIALOG_BODY}><Spinner size={30} /></div>);
|
||||
}
|
||||
|
||||
export default function DialogSuspense({
|
||||
export function DialogSuspense({
|
||||
children
|
||||
}) {
|
||||
return (
|
||||
|
||||
6
src/components/Dialog/index.js
Normal file
6
src/components/Dialog/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
export * from './Dialog';
|
||||
export * from './DialogFooterActions';
|
||||
export * from './DialogSuspense';
|
||||
export * from './DialogContent';
|
||||
@@ -17,6 +17,14 @@ import AllocateLandedCostDialog from 'containers/Dialogs/AllocateLandedCostDialo
|
||||
import InvoicePdfPreviewDialog from 'containers/Dialogs/InvoicePdfPreviewDialog';
|
||||
import EstimatePdfPreviewDialog from 'containers/Dialogs/EstimatePdfPreviewDialog';
|
||||
import ReceiptPdfPreviewDialog from '../containers/Dialogs/ReceiptPdfPreviewDialog';
|
||||
import MoneyInDialog from '../containers/Dialogs/MoneyInDialog';
|
||||
import MoneyOutDialog from '../containers/Dialogs/MoneyOutDialog';
|
||||
import BadDebtDialog from '../containers/Dialogs/BadDebtDialog';
|
||||
import NotifyInvoiceViaSMSDialog from '../containers/Dialogs/NotifyInvoiceViaSMSDialog';
|
||||
import NotifyReceiptViaSMSDialog from '../containers/Dialogs/NotifyReceiptViaSMSDialog';
|
||||
import NotifyEstimateViaSMSDialog from '../containers/Dialogs/NotifyEstimateViaSMSDialog';
|
||||
import NotifyPaymentReceiveViaSMSDialog from '../containers/Dialogs/NotifyPaymentReceiveViaSMSDialog'
|
||||
import SMSMessageDialog from '../containers/Dialogs/SMSMessageDialog';
|
||||
|
||||
/**
|
||||
* Dialogs container.
|
||||
@@ -40,6 +48,16 @@ export default function DialogsContainer() {
|
||||
<InvoicePdfPreviewDialog dialogName={'invoice-pdf-preview'} />
|
||||
<EstimatePdfPreviewDialog dialogName={'estimate-pdf-preview'} />
|
||||
<ReceiptPdfPreviewDialog dialogName={'receipt-pdf-preview'} />
|
||||
<MoneyInDialog dialogName={'money-in'} />
|
||||
<MoneyOutDialog dialogName={'money-out'} />
|
||||
|
||||
<NotifyInvoiceViaSMSDialog dialogName={'notify-invoice-via-sms'} />
|
||||
<NotifyReceiptViaSMSDialog dialogName={'notify-receipt-via-sms'} />
|
||||
<NotifyEstimateViaSMSDialog dialogName={'notify-estimate-via-sms'} />
|
||||
<NotifyPaymentReceiveViaSMSDialog dialogName={'notify-payment-via-sms'} />
|
||||
|
||||
<BadDebtDialog dialogName={'write-off-bad-debt'} />
|
||||
<SMSMessageDialog dialogName={'sms-message-form'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import ListSelect from "./ListSelect";
|
||||
import intl from 'react-intl-universal';
|
||||
import ListSelect from './ListSelect';
|
||||
|
||||
export default function DisplayNameList({
|
||||
salutation,
|
||||
@@ -9,25 +10,32 @@ export default function DisplayNameList({
|
||||
...restProps
|
||||
}) {
|
||||
const formats = [
|
||||
{ format: '{1} {2} {3}', values: [salutation, firstName, lastName], required: [1] },
|
||||
{
|
||||
format: '{1} {2} {3}',
|
||||
values: [salutation, firstName, lastName],
|
||||
required: [1],
|
||||
},
|
||||
{ format: '{1} {2}', values: [firstName, lastName], required: [] },
|
||||
{ format: '{1}, {2}', values: [firstName, lastName], required: [1, 2] },
|
||||
{ format: '{1}', values: [company], required: [1] }
|
||||
{ format: '{1}', values: [company], required: [1] },
|
||||
];
|
||||
|
||||
const formatOptions = formats
|
||||
.filter((format) => !format.values.some((value, index) => {
|
||||
return !value && format.required.indexOf(index + 1) !== -1;
|
||||
}))
|
||||
.filter(
|
||||
(format) =>
|
||||
!format.values.some((value, index) => {
|
||||
return !value && format.required.indexOf(index + 1) !== -1;
|
||||
}),
|
||||
)
|
||||
.map((formatOption) => {
|
||||
const { format, values } = formatOption;
|
||||
let label = format;
|
||||
|
||||
values.forEach((value, index) => {
|
||||
const replaceWith = (value || '');
|
||||
const replaceWith = value || '';
|
||||
label = label.replace(`{${index + 1}}`, replaceWith).trim();
|
||||
});
|
||||
return { label: label.replace(/\s+/g, " ") };
|
||||
return { label: label.replace(/\s+/g, ' ') };
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -35,9 +43,9 @@ export default function DisplayNameList({
|
||||
items={formatOptions}
|
||||
selectedItemProp={'label'}
|
||||
textProp={'label'}
|
||||
defaultText={'Select display name as'}
|
||||
defaultText={intl.get('select_display_name_as')}
|
||||
filterable={false}
|
||||
{ ...restProps }
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Position, Drawer } from '@blueprintjs/core';
|
||||
|
||||
import 'style/components/Drawer.scss';
|
||||
|
||||
import { DrawerProvider } from './DrawerProvider';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
import { compose } from 'utils';
|
||||
|
||||
@@ -27,7 +28,7 @@ function DrawerComponent(props) {
|
||||
portalClassName={'drawer-portal'}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DrawerProvider {...props}>{children}</DrawerProvider>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
16
src/components/Drawer/DrawerProvider.js
Normal file
16
src/components/Drawer/DrawerProvider.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
const DrawerContext = createContext();
|
||||
|
||||
/**
|
||||
* Account form provider.
|
||||
*/
|
||||
function DrawerProvider({ ...props }) {
|
||||
const provider = { ...props };
|
||||
|
||||
return <DrawerContext.Provider value={provider} {...props} />;
|
||||
}
|
||||
|
||||
const useDrawerContext = () => useContext(DrawerContext);
|
||||
|
||||
export { DrawerProvider, useDrawerContext };
|
||||
@@ -13,6 +13,10 @@ import ItemDetailDrawer from '../containers/Drawers/ItemDetailDrawer';
|
||||
import CustomerDetailsDrawer from '../containers/Drawers/CustomerDetailsDrawer';
|
||||
import VendorDetailsDrawer from '../containers/Drawers/VendorDetailsDrawer';
|
||||
import InventoryAdjustmentDetailDrawer from '../containers/Drawers/InventoryAdjustmentDetailDrawer';
|
||||
import CashflowTransactionDetailDrawer from '../containers/Drawers/CashflowTransactionDetailDrawer';
|
||||
import QuickCreateCustomerDrawer from '../containers/Drawers/QuickCreateCustomerDrawer';
|
||||
import QuickCreateItemDrawer from '../containers/Drawers/QuickCreateItemDrawer';
|
||||
import QuickWriteVendorDrawer from '../containers/Drawers/QuickWriteVendorDrawer';
|
||||
|
||||
import { DRAWERS } from 'common/drawers';
|
||||
|
||||
@@ -37,6 +41,12 @@ export default function DrawersContainer() {
|
||||
<InventoryAdjustmentDetailDrawer
|
||||
name={DRAWERS.INVENTORY_ADJUSTMENT_DRAWER}
|
||||
/>
|
||||
<CashflowTransactionDetailDrawer
|
||||
name={DRAWERS.CASHFLOW_TRNASACTION_DRAWER}
|
||||
/>
|
||||
<QuickCreateCustomerDrawer name={DRAWERS.QUICK_CREATE_CUSTOMER} />
|
||||
<QuickCreateItemDrawer name={DRAWERS.QUICK_CREATE_ITEM} />
|
||||
<QuickWriteVendorDrawer name={DRAWERS.QUICK_WRITE_VENDOR} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export function FormattedMessage({ id }) {
|
||||
return intl.get(id);
|
||||
export function FormattedMessage({ id, values }) {
|
||||
return intl.get(id, values);
|
||||
}
|
||||
|
||||
export function FormattedHTMLMessage({ ...args }) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { compose } from 'utils';
|
||||
import withAuthentication from 'containers/Authentication/withAuthentication';
|
||||
import withOrganization from 'containers/Organization/withOrganization';
|
||||
|
||||
|
||||
function EnsureOrganizationIsReady({
|
||||
// #ownProps
|
||||
children,
|
||||
@@ -15,10 +14,10 @@ function EnsureOrganizationIsReady({
|
||||
// #withOrganizationByOrgId
|
||||
isOrganizationReady,
|
||||
}) {
|
||||
return (isOrganizationReady) ? children : (
|
||||
<Redirect
|
||||
to={{ pathname: redirectTo }}
|
||||
/>
|
||||
return isOrganizationReady ? (
|
||||
children
|
||||
) : (
|
||||
<Redirect to={{ pathname: redirectTo }} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,4 +27,4 @@ export default compose(
|
||||
organizationId: props.currentOrganizationId,
|
||||
})),
|
||||
withOrganization(({ isOrganizationReady }) => ({ isOrganizationReady })),
|
||||
)(EnsureOrganizationIsReady);
|
||||
)(EnsureOrganizationIsReady);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,68 @@
|
||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import { Suggest } from '@blueprintjs/select';
|
||||
import classNames from 'classnames';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
export default function ItemsSuggestField({
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { DRAWERS } from 'common/drawers';
|
||||
|
||||
// Creates a new item from query.
|
||||
const createNewItemFromQuery = (name) => {
|
||||
return {
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
// Handle quick create new customer.
|
||||
const createNewItemRenderer = (query, active, handleClick) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon="add"
|
||||
text={`Create "${query}"`}
|
||||
active={active}
|
||||
shouldDismissPopover={false}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// Item renderer.
|
||||
const itemRenderer = (item, { modifiers, handleClick }) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
text={item.name}
|
||||
label={item.code}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
|
||||
// Filters items.
|
||||
const filterItemsPredicater = (query, item, _index, exactMatch) => {
|
||||
const normalizedTitle = item.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${normalizedTitle} ${item.code}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle input value renderer.
|
||||
const handleInputValueRenderer = (inputValue) => {
|
||||
if (inputValue) {
|
||||
return inputValue.name.toString();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
function ItemsSuggestField({
|
||||
items,
|
||||
initialItemId,
|
||||
selectedItemId,
|
||||
@@ -18,6 +73,10 @@ export default function ItemsSuggestField({
|
||||
sellable = false,
|
||||
purchasable = false,
|
||||
popoverFill = false,
|
||||
|
||||
allowCreate = true,
|
||||
|
||||
openDrawer,
|
||||
...suggestProps
|
||||
}) {
|
||||
// Filters items based on filter props.
|
||||
@@ -36,28 +95,23 @@ export default function ItemsSuggestField({
|
||||
// Find initial item object.
|
||||
const initialItem = useMemo(
|
||||
() => filteredItems.some((a) => a.id === initialItemId),
|
||||
[initialItemId],
|
||||
[initialItemId, filteredItems],
|
||||
);
|
||||
|
||||
const [selectedItem, setSelectedItem] = useState(initialItem || null);
|
||||
|
||||
const onItemSelect = useCallback(
|
||||
(item) => {
|
||||
setSelectedItem({ ...item });
|
||||
onItemSelected && onItemSelected(item);
|
||||
if (item.id) {
|
||||
setSelectedItem({ ...item });
|
||||
onItemSelected && onItemSelected(item);
|
||||
} else {
|
||||
openDrawer(DRAWERS.QUICK_CREATE_ITEM);
|
||||
}
|
||||
},
|
||||
[setSelectedItem, onItemSelected],
|
||||
[setSelectedItem, onItemSelected, openDrawer],
|
||||
);
|
||||
|
||||
const itemRenderer = useCallback((item, { modifiers, handleClick }) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
text={item.name}
|
||||
label={item.code}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
));
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof selectedItemId !== 'undefined') {
|
||||
const item = selectedItemId
|
||||
@@ -67,27 +121,12 @@ export default function ItemsSuggestField({
|
||||
}
|
||||
}, [selectedItemId, filteredItems, setSelectedItem]);
|
||||
|
||||
const handleInputValueRenderer = (inputValue) => {
|
||||
if (inputValue) {
|
||||
return inputValue.name.toString();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
// Maybe inject create new item props to suggest component.
|
||||
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||
const maybeCreateNewItemFromQuery = allowCreate
|
||||
? createNewItemFromQuery
|
||||
: null;
|
||||
|
||||
// Filters items.
|
||||
const filterItemsPredicater = useCallback(
|
||||
(query, item, _index, exactMatch) => {
|
||||
const normalizedTitle = item.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${normalizedTitle} ${item.code}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
return (
|
||||
<Suggest
|
||||
items={filteredItems}
|
||||
@@ -104,7 +143,12 @@ export default function ItemsSuggestField({
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||
createNewItemPosition={'top'}
|
||||
{...suggestProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDrawerActions)(ItemsSuggestField);
|
||||
|
||||
36
src/components/MoreMenutItems.js
Normal file
36
src/components/MoreMenutItems.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
MenuItem,
|
||||
Menu,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
import { Icon, FormattedMessage as T } from 'components';
|
||||
|
||||
function MoreMenuItems({ payload: { onNotifyViaSMS } }) {
|
||||
return (
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={
|
||||
<Menu>
|
||||
<MenuItem
|
||||
onClick={onNotifyViaSMS}
|
||||
text={<T id={'notify_via_sms.dialog.notify_via_sms'} />}
|
||||
/>
|
||||
</Menu>
|
||||
}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
modifiers={{
|
||||
offset: { offset: '0, 4' },
|
||||
}}
|
||||
>
|
||||
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default MoreMenuItems;
|
||||
@@ -86,7 +86,7 @@ function Pagination({
|
||||
currentPage,
|
||||
total,
|
||||
size,
|
||||
pageSizesOptions = [5, 12, 20, 30, 50, 75, 100, 150],
|
||||
pageSizesOptions = [20, 30, 50, 75, 100, 150],
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
}) {
|
||||
|
||||
46
src/components/SMSPreview/index.js
Normal file
46
src/components/SMSPreview/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Icon } from 'components';
|
||||
|
||||
/**
|
||||
* SMS Message preview.
|
||||
*/
|
||||
export function SMSMessagePreview({
|
||||
message,
|
||||
iconWidth = '265px',
|
||||
iconHeight = '287px',
|
||||
iconColor = '#adadad',
|
||||
}) {
|
||||
return (
|
||||
<SMSMessagePreviewBase>
|
||||
<Icon
|
||||
icon={'sms-message-preview'}
|
||||
width={iconWidth}
|
||||
height={iconHeight}
|
||||
color={iconColor}
|
||||
/>
|
||||
<SMSMessageText>{message}</SMSMessageText>
|
||||
</SMSMessagePreviewBase>
|
||||
);
|
||||
}
|
||||
|
||||
const SMSMessageText = styled.div`
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
padding: 12px;
|
||||
color: #fff;
|
||||
border-radius: 12px;
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
word-break: break-word;
|
||||
background: #2fa2e4;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
`;
|
||||
|
||||
const SMSMessagePreviewBase = styled.div`
|
||||
position: relative;
|
||||
width: 265px;
|
||||
margin: 0 auto;
|
||||
`;
|
||||
@@ -141,7 +141,6 @@ function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
|
||||
{...handlers}
|
||||
>
|
||||
<InputGroup
|
||||
autoFocus={true}
|
||||
large={true}
|
||||
leftIcon={<Icon icon={'universal-search'} iconSize={20} />}
|
||||
placeholder={intl.get('universal_search.placeholder')}
|
||||
|
||||
@@ -8,7 +8,7 @@ import intl from 'react-intl-universal';
|
||||
export function FormatDate({ value, format = 'YYYY MMM DD' }) {
|
||||
const localizedFormat = intl.get(`date_formats.${format}`);
|
||||
|
||||
return moment().format(localizedFormat);
|
||||
return moment(value).format(localizedFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,9 +23,6 @@ import AccountsSelectList from './AccountsSelectList';
|
||||
import AccountsTypesSelect from './AccountsTypesSelect';
|
||||
import LoadingIndicator from './LoadingIndicator';
|
||||
import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
|
||||
import Dialog from './Dialog/Dialog';
|
||||
import DialogContent from './Dialog/DialogContent';
|
||||
import DialogSuspense from './Dialog/DialogSuspense';
|
||||
import InputPrependButton from './Forms/InputPrependButton';
|
||||
import CategoriesSelectList from './CategoriesSelectList';
|
||||
import Row from './Grid/Row';
|
||||
@@ -58,9 +55,12 @@ import AccountsSuggestField from './AccountsSuggestField';
|
||||
import MaterialProgressBar from './MaterialProgressBar';
|
||||
import { MoneyFieldCell } from './DataTableCells';
|
||||
import Card from './Card';
|
||||
import AvaterCell from './AvaterCell';
|
||||
|
||||
import { ItemsMultiSelect } from './Items';
|
||||
import MoreMenuItems from './MoreMenutItems';
|
||||
|
||||
export * from './Dialog';
|
||||
export * from './Menu';
|
||||
export * from './AdvancedFilter/AdvancedFilterDropdown';
|
||||
export * from './AdvancedFilter/AdvancedFilterPopover';
|
||||
@@ -71,15 +71,22 @@ export * from './PdfPreview';
|
||||
export * from './Details';
|
||||
export * from './Drawer/DrawerInsider';
|
||||
export * from './Drawer/DrawerMainTabs';
|
||||
export * from './TotalLines/index'
|
||||
export * from './TotalLines/index';
|
||||
export * from './Alert';
|
||||
export * from './Subscriptions';
|
||||
export * from './Dashboard';
|
||||
export * from './Drawer';
|
||||
export * from './Forms';
|
||||
export * from './MultiSelectTaggable'
|
||||
export * from './MultiSelectTaggable';
|
||||
export * from './Utils/FormatNumber';
|
||||
export * from './Utils/FormatDate';
|
||||
export * from './BankAccounts';
|
||||
export * from './IntersectionObserver';
|
||||
export * from './Datatable/CellForceWidth';
|
||||
export * from './Button';
|
||||
export * from './IntersectionObserver';
|
||||
export * from './SMSPreview';
|
||||
export * from './Contacts';
|
||||
|
||||
const Hint = FieldHint;
|
||||
|
||||
@@ -113,9 +120,6 @@ export {
|
||||
LoadingIndicator,
|
||||
DashboardActionViewsList,
|
||||
AppToaster,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogSuspense,
|
||||
InputPrependButton,
|
||||
CategoriesSelectList,
|
||||
Col,
|
||||
@@ -150,4 +154,6 @@ export {
|
||||
MoneyFieldCell,
|
||||
ItemsMultiSelect,
|
||||
Card,
|
||||
AvaterCell,
|
||||
MoreMenuItems,
|
||||
};
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
import React from 'react'
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
export default [
|
||||
{
|
||||
text: <T id={'general'}/>,
|
||||
text: <T id={'general'} />,
|
||||
disabled: false,
|
||||
href: '/preferences/general',
|
||||
},
|
||||
{
|
||||
text: <T id={'users'}/>,
|
||||
text: <T id={'users'} />,
|
||||
href: '/preferences/users',
|
||||
},
|
||||
{
|
||||
text: <T id={'currencies'}/>,
|
||||
|
||||
text: <T id={'currencies'} />,
|
||||
|
||||
href: '/preferences/currencies',
|
||||
},
|
||||
{
|
||||
text: <T id={'accountant'}/>,
|
||||
text: <T id={'accountant'} />,
|
||||
disabled: false,
|
||||
href: '/preferences/accountant',
|
||||
},
|
||||
{
|
||||
text: <T id={'items'}/>,
|
||||
text: <T id={'items'} />,
|
||||
disabled: false,
|
||||
href: '/preferences/items',
|
||||
},
|
||||
{
|
||||
text: <T id={'sms_integration.label'} />,
|
||||
disabled: false,
|
||||
href: '/preferences/sms-message',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -190,8 +190,36 @@ export default [
|
||||
],
|
||||
},
|
||||
{
|
||||
text: <T id={'banking'} />,
|
||||
children: [],
|
||||
text: <T id={'siebar.cashflow'} />,
|
||||
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'} />,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
AdvancedFilterPopover,
|
||||
DashboardFilterButton,
|
||||
DashboardRowsHeightButton,
|
||||
FormattedMessage as T,
|
||||
} from 'components';
|
||||
|
||||
@@ -22,6 +23,8 @@ import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withManualJournalsActions from './withManualJournalsActions';
|
||||
import withManualJournals from './withManualJournals';
|
||||
import withSettingsActions from '../../Settings/withSettingsActions';
|
||||
import withSettings from '../../Settings/withSettings';
|
||||
|
||||
import { If, DashboardActionViewsList } from 'components';
|
||||
|
||||
@@ -36,6 +39,12 @@ function ManualJournalActionsBar({
|
||||
|
||||
// #withManualJournals
|
||||
manualJournalsFilterConditions,
|
||||
|
||||
// #withSettings
|
||||
manualJournalsTableSize,
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
@@ -62,6 +71,11 @@ function ManualJournalActionsBar({
|
||||
refresh();
|
||||
};
|
||||
|
||||
// Handle table row size change.
|
||||
const handleTableRowSizeChange = (size) => {
|
||||
addSetting('manualJournals', 'tableSize', size);
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -119,6 +133,12 @@ function ManualJournalActionsBar({
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
initialValue={manualJournalsTableSize}
|
||||
onChange={handleTableRowSizeChange}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
</NavbarGroup>
|
||||
<NavbarGroup align={Alignment.RIGHT}>
|
||||
<Button
|
||||
@@ -134,7 +154,11 @@ function ManualJournalActionsBar({
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withManualJournalsActions,
|
||||
withSettingsActions,
|
||||
withManualJournals(({ manualJournalsTableState }) => ({
|
||||
manualJournalsFilterConditions: manualJournalsTableState.filterRoles,
|
||||
})),
|
||||
withSettings(({ manualJournalsSettings }) => ({
|
||||
manualJournalsTableSize: manualJournalsSettings?.tableSize,
|
||||
})),
|
||||
)(ManualJournalActionsBar);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
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.
|
||||
*/
|
||||
export default function ManualJournalsAlerts() {
|
||||
return (
|
||||
<div>
|
||||
<JournalDeleteAlert name={'journal-delete'} />
|
||||
<JournalPublishAlert name={'journal-publish'} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default [
|
||||
{ name: 'journal-delete', component: JournalDeleteAlert },
|
||||
{ name: 'journal-publish', component: JournalPublishAlert },
|
||||
];
|
||||
|
||||
@@ -13,6 +13,7 @@ import withManualJournals from './withManualJournals';
|
||||
import withManualJournalsActions from './withManualJournalsActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
import withSettings from '../../Settings/withSettings';
|
||||
|
||||
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
||||
import { useMemorizedColumnsWidths } from 'hooks';
|
||||
@@ -38,6 +39,9 @@ function ManualJournalsDataTable({
|
||||
|
||||
// #ownProps
|
||||
onSelectedRowsChange,
|
||||
|
||||
// #withSettings
|
||||
manualJournalsTableSize,
|
||||
}) {
|
||||
// Manual journals context.
|
||||
const {
|
||||
@@ -109,7 +113,6 @@ function ManualJournalsDataTable({
|
||||
data={manualJournals}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
expandable={true}
|
||||
sticky={true}
|
||||
loading={isManualJournalsLoading}
|
||||
headerLoading={isManualJournalsLoading}
|
||||
@@ -125,6 +128,7 @@ function ManualJournalsDataTable({
|
||||
onCellClick={handleCellClick}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
size={manualJournalsTableSize}
|
||||
payload={{
|
||||
onDelete: handleDeleteJournal,
|
||||
onPublish: handlePublishJournal,
|
||||
@@ -143,4 +147,7 @@ export default compose(
|
||||
})),
|
||||
withAlertsActions,
|
||||
withDrawerActions,
|
||||
withSettings(({ manualJournalsSettings }) => ({
|
||||
manualJournalsTableSize: manualJournalsSettings?.tableSize,
|
||||
})),
|
||||
)(ManualJournalsDataTable);
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'style/pages/ManualJournal/List.scss';
|
||||
import { DashboardContentTable, DashboardPageContent } from 'components';
|
||||
|
||||
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
||||
import ManualJournalsAlerts from './ManualJournalsAlerts';
|
||||
import ManualJournalsViewTabs from './ManualJournalsViewTabs';
|
||||
import ManualJournalsDataTable from './ManualJournalsDataTable';
|
||||
import ManualJournalsActionsBar from './ManualJournalActionsBar';
|
||||
@@ -33,7 +32,6 @@ function ManualJournalsTable({
|
||||
<ManualJournalsDataTable />
|
||||
</DashboardPageContent>
|
||||
|
||||
<ManualJournalsAlerts />
|
||||
</ManualJournalsListProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export const useManualJournalsColumns = () => {
|
||||
className: 'journal_number',
|
||||
width: 100,
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'journal_type',
|
||||
@@ -44,6 +45,7 @@ export const useManualJournalsColumns = () => {
|
||||
accessor: 'journal_type',
|
||||
width: 110,
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
|
||||
@@ -128,6 +128,7 @@ export const useJournalTableEntriesColumns = () => {
|
||||
className: 'account',
|
||||
disableSortBy: true,
|
||||
width: 160,
|
||||
fieldProps: { allowCreate: true }
|
||||
},
|
||||
{
|
||||
Header: CreditHeaderCell,
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
If,
|
||||
DashboardActionViewsList,
|
||||
DashboardFilterButton,
|
||||
DashboardRowsHeightButton,
|
||||
} from 'components';
|
||||
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
@@ -27,7 +28,8 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
import withAccountsTableActions from './withAccountsTableActions';
|
||||
|
||||
import withSettings from '../Settings/withSettings';
|
||||
import withSettingsActions from '../Settings/withSettingsActions';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -50,6 +52,12 @@ function AccountsActionsBar({
|
||||
|
||||
// #ownProps
|
||||
onFilterChanged,
|
||||
|
||||
// #withSettings
|
||||
accountsTableSize,
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
const { resourceViews, fields } = useAccountsChartContext();
|
||||
|
||||
@@ -93,6 +101,10 @@ function AccountsActionsBar({
|
||||
refresh();
|
||||
};
|
||||
|
||||
// Handle table row size change.
|
||||
const handleTableRowSizeChange = (size) => {
|
||||
addSetting('accounts', 'tableSize', size);
|
||||
};
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -165,6 +177,12 @@ function AccountsActionsBar({
|
||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||
text={<T id={'import'} />}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
initialValue={accountsTableSize}
|
||||
onChange={handleTableRowSizeChange}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Switch
|
||||
labelElement={<T id={'inactive'} />}
|
||||
defaultChecked={accountsInactiveMode}
|
||||
@@ -185,10 +203,14 @@ function AccountsActionsBar({
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withAlertActions,
|
||||
withSettingsActions,
|
||||
withAccounts(({ accountsSelectedRows, accountsTableState }) => ({
|
||||
accountsSelectedRows,
|
||||
accountsInactiveMode: accountsTableState.inactiveMode,
|
||||
accountsFilterConditions: accountsTableState.filterRoles,
|
||||
})),
|
||||
withSettings(({ accountsSettings }) => ({
|
||||
accountsTableSize: accountsSettings.tableSize,
|
||||
})),
|
||||
withAccountsTableActions,
|
||||
)(AccountsActionsBar);
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import React from 'react';
|
||||
import AccountDeleteAlert from 'containers/Alerts/AccountDeleteAlert';
|
||||
import AccountInactivateAlert from 'containers/Alerts/AccountInactivateAlert';
|
||||
import AccountActivateAlert from 'containers/Alerts/AccountActivateAlert';
|
||||
|
||||
const AccountDeleteAlert = React.lazy(() =>
|
||||
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 AccountBulkInactivateAlert from 'containers/Alerts/AccountBulkInactivateAlert';
|
||||
// import AccountBulkActivateAlert from 'containers/Alerts/AccountBulkActivateAlert';
|
||||
|
||||
/**
|
||||
* Accounts alert.
|
||||
*/
|
||||
export default function AccountsAlerts({
|
||||
|
||||
}) {
|
||||
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>
|
||||
)
|
||||
}
|
||||
export default [
|
||||
{ name: 'account-delete', component: AccountDeleteAlert },
|
||||
{ name: 'account-inactivate', component: AccountInactivateAlert },
|
||||
{ name: 'account-activate', component: AccountActivateAlert },
|
||||
];
|
||||
|
||||
@@ -7,7 +7,6 @@ import { AccountsChartProvider } from './AccountsChartProvider';
|
||||
|
||||
import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
|
||||
import AccountsActionsBar from 'containers/Accounts/AccountsActionsBar';
|
||||
import AccountsAlerts from './AccountsAlerts';
|
||||
import AccountsDataTable from './AccountsDataTable';
|
||||
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
@@ -49,8 +48,6 @@ function AccountsChart({
|
||||
<AccountsDataTable />
|
||||
</DashboardContentTable>
|
||||
</DashboardPageContent>
|
||||
|
||||
<AccountsAlerts />
|
||||
</AccountsChartProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ 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 { useAccountsChartContext } from './AccountsChartProvider';
|
||||
import { useMemorizedColumnsWidths } from '../../hooks';
|
||||
@@ -30,6 +31,9 @@ function AccountsDataTable({
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
|
||||
// #withSettings
|
||||
accountsTableSize,
|
||||
}) {
|
||||
const { isAccountsLoading, isAccountsFetching, accounts } =
|
||||
useAccountsChartContext();
|
||||
@@ -102,11 +106,12 @@ function AccountsDataTable({
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
ContextMenu={ActionsMenu}
|
||||
// #TableVirtualizedListRows props.
|
||||
vListrowHeight={42}
|
||||
vListrowHeight={accountsTableSize == 'small' ? 40 : 42}
|
||||
vListOverscanRowCount={0}
|
||||
onCellClick={handleCellClick}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
size={accountsTableSize}
|
||||
payload={{
|
||||
onEdit: handleEditAccount,
|
||||
onDelete: handleDeleteAccount,
|
||||
@@ -123,4 +128,7 @@ export default compose(
|
||||
withAlertsActions,
|
||||
withDrawerActions,
|
||||
withDialogActions,
|
||||
withSettings(({ accountsSettings }) => ({
|
||||
accountsTableSize: accountsSettings.tableSize,
|
||||
})),
|
||||
)(AccountsDataTable);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Intent, Tag } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
import clsx from 'classnames';
|
||||
|
||||
import { CLASSES } from '../../common/classes';
|
||||
import { If, AppToaster } from 'components';
|
||||
import { NormalCell, BalanceCell } from './components';
|
||||
import { transformTableStateToQuery, isBlank } from 'utils';
|
||||
|
||||
@@ -35,7 +35,7 @@ function BillTransactionDeleteAlert({
|
||||
deleteLandedCostMutate(BillId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('bill.action.delete_successfully.landed_cost'),
|
||||
message: intl.get('landed_cost.action.delete.success_message'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeAlert(name);
|
||||
@@ -56,7 +56,11 @@ function BillTransactionDeleteAlert({
|
||||
onConfirm={handleConfirmLandedCostDelete}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p><T id={`Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?`}/></p>
|
||||
<p>
|
||||
<T
|
||||
id={`Once your delete this located landed cost, you won't be able to restore it later, Are your sure you want to delete this transaction?`}
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -31,7 +31,7 @@ function CustomerBulkDeleteAlert({
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
console.log(customersIds, 'EE');
|
||||
|
||||
|
||||
// Handle confirm customers bulk delete.
|
||||
const handleConfirmBulkDelete = useCallback(() => {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
|
||||
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
import { AppToaster } from 'components';
|
||||
import { transformErrors } from 'containers/Customers/utils';
|
||||
|
||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import { useDeleteCustomer } from 'hooks/query';
|
||||
import { compose } from 'utils';
|
||||
|
||||
|
||||
/**
|
||||
* Customer delete alert.
|
||||
*/
|
||||
@@ -24,12 +24,11 @@ function CustomerDeleteAlert({
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
|
||||
const {
|
||||
mutateAsync: deleteCustomerMutate,
|
||||
isLoading
|
||||
} = useDeleteCustomer();
|
||||
const { mutateAsync: deleteCustomerMutate, isLoading } = useDeleteCustomer();
|
||||
|
||||
// handle cancel delete alert.
|
||||
const handleCancelDeleteAlert = () => {
|
||||
@@ -44,10 +43,17 @@ function CustomerDeleteAlert({
|
||||
message: intl.get('the_customer_has_been_deleted_successfully'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeDrawer('customer-details-drawer');
|
||||
})
|
||||
.catch(({ response: { data: { errors } } }) => {
|
||||
transformErrors(errors);
|
||||
})
|
||||
.catch(
|
||||
({
|
||||
response: {
|
||||
data: { errors },
|
||||
},
|
||||
}) => {
|
||||
transformErrors(errors);
|
||||
},
|
||||
)
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
@@ -76,4 +82,5 @@ function CustomerDeleteAlert({
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
withDrawerActions,
|
||||
)(CustomerDeleteAlert);
|
||||
|
||||
@@ -46,7 +46,24 @@ function EstimateDeleteAlert({
|
||||
});
|
||||
closeDrawer('estimate-detail-drawer');
|
||||
})
|
||||
.catch(({ errors }) => {})
|
||||
.catch(
|
||||
({
|
||||
response: {
|
||||
data: { errors },
|
||||
},
|
||||
}) => {
|
||||
if (
|
||||
errors.find((e) => e.type === 'SALE_ESTIMATE_CONVERTED_TO_INVOICE')
|
||||
) {
|
||||
AppToaster.show({
|
||||
intent: Intent.DANGER,
|
||||
message: intl.get(
|
||||
'estimate.delete.error.estimate_converted_to_invoice',
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
|
||||
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)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('the_adjustment_has_been_deleted_successfully'),
|
||||
message: intl.get('the_adjustment_transaction_has_been_deleted_successfully'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeDrawer('inventory-adjustment-drawer');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
|
||||
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
|
||||
import { AppToaster } from 'components';
|
||||
@@ -9,6 +9,7 @@ import { useDeleteVendor } 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';
|
||||
|
||||
@@ -24,8 +25,10 @@ function VendorDeleteAlert({
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
|
||||
const { mutateAsync: deleteVendorMutate, isLoading } = useDeleteVendor();
|
||||
|
||||
// Handle cancel delete the vendor.
|
||||
@@ -41,6 +44,7 @@ function VendorDeleteAlert({
|
||||
message: intl.get('the_vendor_has_been_deleted_successfully'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeDrawer('vendor-details-drawer');
|
||||
})
|
||||
.catch(
|
||||
({
|
||||
@@ -79,4 +83,5 @@ function VendorDeleteAlert({
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
withDrawerActions,
|
||||
)(VendorDeleteAlert);
|
||||
|
||||
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,
|
||||
];
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import withDashboardActions from '../../containers/Dashboard/withDashboardActions';
|
||||
|
||||
function AuthenticationBootJSX({ setAppIsLoading }) {
|
||||
React.useEffect(() => {
|
||||
setAppIsLoading(false);
|
||||
}, [setAppIsLoading]);
|
||||
|
||||
return null;
|
||||
}
|
||||
export const AuthenticationBoot = R.compose(withDashboardActions)(
|
||||
AuthenticationBootJSX,
|
||||
);
|
||||
@@ -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;`)}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user