mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
Merge branch 'develop' into main
This commit is contained in:
@@ -221,3 +221,5 @@ export const ACCOUNT_TYPES = [
|
||||
incomeSheet: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const FOREIGN_CURRENCY_ACCOUNTS = ['cash', 'bank'];
|
||||
|
||||
7
src/common/cellTypes.js
Normal file
7
src/common/cellTypes.js
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export const CellType = {
|
||||
Text: 'text',
|
||||
Field: 'field',
|
||||
Button: 'button'
|
||||
}
|
||||
@@ -41,6 +41,7 @@ const CLASSES = {
|
||||
PAGE_FORM_EXPENSE: 'page-form--expense',
|
||||
PAGE_FORM_CREDIT_NOTE:'page-form--credit-note',
|
||||
PAGE_FORM_VENDOR_CREDIT_NOTE:'page-form--vendor-credit-note',
|
||||
PAGE_FORM_WAREHOUSE_TRANSFER:'page-form--warehouse-transfer',
|
||||
|
||||
FORM_GROUP_LIST_SELECT: 'form-group--select-list',
|
||||
|
||||
@@ -70,6 +71,8 @@ const CLASSES = {
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_ACCOUNTANT: 'preferences-page__inside-content--accountant',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_SMS_INTEGRATION: 'preferences-page__inside-content--sms-integration',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_ROLES_FORM: 'preferences-page__inside-content--roles-form',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_BRANCHES: 'preferences-page__inside-content--branches',
|
||||
PREFERENCES_PAGE_INSIDE_CONTENT_WAREHOUSES: 'preferences-page__inside-content--warehouses',
|
||||
|
||||
FINANCIAL_REPORT_INSIDER: 'dashboard__insider--financial-report',
|
||||
|
||||
|
||||
@@ -17,5 +17,6 @@ export const DRAWERS = {
|
||||
CREDIT_NOTE_DETAIL_DRAWER: 'credit-note-detail-drawer',
|
||||
VENDOR_CREDIT_DETAIL_DRAWER: 'vendor-credit-detail-drawer',
|
||||
REFUND_CREDIT_NOTE_DETAIL_DRAWER:'refund-credit-detail-drawer',
|
||||
REFUND_VENDOR_CREDIT_DETAIL_DRAWER:'refund-vendor-detail-drawer'
|
||||
REFUND_VENDOR_CREDIT_DETAIL_DRAWER:'refund-vendor-detail-drawer',
|
||||
WAREHOUSE_TRANSFER_DETAIL_DRAWER:'warehouse-transfer-detail-drawer'
|
||||
};
|
||||
|
||||
7
src/common/features.js
Normal file
7
src/common/features.js
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export const Features = {
|
||||
Warehouses: 'warehouses',
|
||||
Branches: 'branches',
|
||||
ManualJournal: 'manualJournal',
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './TableStyle';
|
||||
export * from './features';
|
||||
export * from './cellTypes';
|
||||
|
||||
export const Align = { Left: 'left', Right: 'right', Center: 'center' };
|
||||
|
||||
@@ -16,6 +16,7 @@ export const TABLES = {
|
||||
CASHFLOW_Transactions: 'cashflow_transactions',
|
||||
CREDIT_NOTES: 'credit_notes',
|
||||
VENDOR_CREDITS: 'vendor_credits',
|
||||
WAREHOUSE_TRANSFERS:'warehouse_transfers'
|
||||
};
|
||||
|
||||
export const TABLE_SIZE = {
|
||||
|
||||
18
src/components/BaseCurrency.js
Normal file
18
src/components/BaseCurrency.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
import styled from 'styled-components';
|
||||
import { CurrencyTag } from 'components';
|
||||
|
||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||
|
||||
/**
|
||||
* base currecncy.
|
||||
*/
|
||||
function BaseCurrency({
|
||||
// #withCurrentOrganization
|
||||
organization: { base_currency },
|
||||
}) {
|
||||
return <CurrencyTag>{base_currency}</CurrencyTag>;
|
||||
}
|
||||
|
||||
export default R.compose(withCurrentOrganization())(BaseCurrency);
|
||||
124
src/components/BranchSuggestField.js
Normal file
124
src/components/BranchSuggestField.js
Normal file
@@ -0,0 +1,124 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { Suggest } from '@blueprintjs/select';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
/**
|
||||
* branch suggest field.
|
||||
* @returns
|
||||
*/
|
||||
export default function BranchSuggestField({
|
||||
branches,
|
||||
initialBranchId,
|
||||
selectedBranchId,
|
||||
defaultSelectText = intl.get('select_branch'),
|
||||
popoverFill = false,
|
||||
onBranchSelected,
|
||||
...suggestProps
|
||||
}) {
|
||||
const initialBranch = React.useMemo(
|
||||
() => branches.find((b) => b.id === initialBranchId),
|
||||
[initialBranchId, branches],
|
||||
);
|
||||
|
||||
const [selectedBranch, setSelectedBranch] = React.useState(
|
||||
initialBranch || null,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof selectedBranchId !== 'undefined') {
|
||||
const branch = selectedBranchId
|
||||
? branches.find((a) => a.id === selectedBranchId)
|
||||
: null;
|
||||
setSelectedBranch(branch);
|
||||
}
|
||||
}, [selectedBranchId, branches, setSelectedBranch]);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} branch
|
||||
* @returns
|
||||
*/
|
||||
const branchItemRenderer = (branch, { handleClick, modifiers, query }) => {
|
||||
return (
|
||||
<MenuItem
|
||||
// active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
label={branch.code}
|
||||
key={branch.id}
|
||||
onClick={handleClick}
|
||||
text={branch.name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} branch
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const branchItemPredicate = (query, branch, _index, exactMatch) => {
|
||||
const normalizedTitle = branch.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${branch.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} branch
|
||||
* @returns
|
||||
*/
|
||||
const brnachItemSelect = React.useCallback(
|
||||
(branch) => {
|
||||
if (branch.id) {
|
||||
setSelectedBranch({ ...branch });
|
||||
onBranchSelected && onBranchSelected(branch);
|
||||
}
|
||||
},
|
||||
[setSelectedBranch, onBranchSelected],
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} inputVaue
|
||||
* @returns
|
||||
*/
|
||||
const branchInputValueRenderer = (inputValue) => {
|
||||
if (inputValue) {
|
||||
return inputValue.name.toString();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
return (
|
||||
<Suggest
|
||||
items={branches}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||
itemRenderer={branchItemRenderer}
|
||||
itemPredicate={branchItemPredicate}
|
||||
onItemSelect={brnachItemSelect}
|
||||
selectedItem={selectedBranch}
|
||||
inputProps={{ placeholder: defaultSelectText }}
|
||||
resetOnClose={true}
|
||||
fill={true}
|
||||
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||
inputValueRenderer={branchInputValueRenderer}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
{...suggestProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
72
src/components/Branches/BranchMultiSelect.js
Normal file
72
src/components/Branches/BranchMultiSelect.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { FMultiSelect } from '../Forms';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} branch
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const branchItemPredicate = (query, branch, _index, exactMatch) => {
|
||||
const normalizedTitle = branch.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${branch.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} branch
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const branchItemRenderer = (
|
||||
branch,
|
||||
{ handleClick, modifiers, query },
|
||||
{ isSelected },
|
||||
) => {
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
icon={isSelected ? 'tick' : 'blank'}
|
||||
text={branch.name}
|
||||
label={branch.code}
|
||||
key={branch.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const branchSelectProps = {
|
||||
itemPredicate: branchItemPredicate,
|
||||
itemRenderer: branchItemRenderer,
|
||||
valueAccessor: (item) => item.id,
|
||||
labelAccessor: (item) => item.code,
|
||||
tagRenderer: (item) => item.name,
|
||||
};
|
||||
|
||||
/**
|
||||
* branches mulit select.
|
||||
* @param {*} param0
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function BranchMultiSelect({ branches, ...rest }) {
|
||||
return (
|
||||
<FMultiSelect
|
||||
items={branches}
|
||||
placeholder={intl.get('branches_multi_select.placeholder')}
|
||||
popoverProps={{ minimal: true }}
|
||||
{...branchSelectProps}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
69
src/components/Branches/BranchSelect.js
Normal file
69
src/components/Branches/BranchSelect.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { FSelect } from '../Forms';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} branch
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const branchItemPredicate = (query, branch, _index, exactMatch) => {
|
||||
const normalizedTitle = branch.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${branch.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} film
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const branchItemRenderer = (branch, { handleClick, modifiers, query }) => {
|
||||
const text = `${branch.name}`;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
label={branch.code}
|
||||
key={branch.id}
|
||||
onClick={handleClick}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const branchSelectProps = {
|
||||
itemPredicate: branchItemPredicate,
|
||||
itemRenderer: branchItemRenderer,
|
||||
valueAccessor: 'id',
|
||||
labelAccessor: 'name',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export function BranchSelect({ branches, ...rest }) {
|
||||
return <FSelect {...branchSelectProps} {...rest} items={branches} />;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export function BranchSelectButton({ label, ...rest }) {
|
||||
return <Button text={label} {...rest} />;
|
||||
}
|
||||
2
src/components/Branches/index.js
Normal file
2
src/components/Branches/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './BranchSelect';
|
||||
export * from './BranchMultiSelect'
|
||||
10
src/components/Currencies/BaseCurrency.js
Normal file
10
src/components/Currencies/BaseCurrency.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { CurrencyTag } from 'components';
|
||||
|
||||
/**
|
||||
* base currecncy.
|
||||
* @returns
|
||||
*/
|
||||
export function BaseCurrency({ currency }) {
|
||||
return <CurrencyTag>{currency}</CurrencyTag>;
|
||||
}
|
||||
76
src/components/Currencies/CurrencySelect.js
Normal file
76
src/components/Currencies/CurrencySelect.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { FSelect } from '../Forms';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} currency
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const currencyItemPredicate = (query, currency, _index, exactMatch) => {
|
||||
const normalizedTitle = currency.currency_code.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return (
|
||||
`${currency.currency_code}. ${normalizedTitle}`.indexOf(
|
||||
normalizedQuery,
|
||||
) >= 0
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {*} currency
|
||||
* @returns
|
||||
*/
|
||||
const currencyItemRenderer = (currency, { handleClick, modifiers, query }) => {
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
text={currency.currency_name}
|
||||
label={currency.currency_code.toString()}
|
||||
key={currency.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const currencySelectProps = {
|
||||
itemPredicate: currencyItemPredicate,
|
||||
itemRenderer: currencyItemRenderer,
|
||||
valueAccessor: 'currency_code',
|
||||
labelAccessor: 'currency_code',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} currencies
|
||||
* @returns
|
||||
*/
|
||||
export function CurrencySelect({ currencies, ...rest }) {
|
||||
return (
|
||||
<FSelect
|
||||
{...currencySelectProps}
|
||||
{...rest}
|
||||
items={currencies}
|
||||
input={CurrnecySelectButton}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} label
|
||||
* @returns
|
||||
*/
|
||||
function CurrnecySelectButton({ label }) {
|
||||
return <Button text={label ? label : intl.get('select_currency_code')} />;
|
||||
}
|
||||
2
src/components/Currencies/index.js
Normal file
2
src/components/Currencies/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './CurrencySelect';
|
||||
export * from './BaseCurrency';
|
||||
109
src/components/CustomSelectList.js
Normal file
109
src/components/CustomSelectList.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
Button,
|
||||
MenuItem,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Classes,
|
||||
} from '@blueprintjs/core';
|
||||
import clsx from 'classnames';
|
||||
import { defaultTo } from 'lodash';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
import { FormattedMessage as T, Icon } from 'components';
|
||||
|
||||
/**
|
||||
* warehouse & branches select list.
|
||||
* @returns
|
||||
*/
|
||||
export default function CustomSelectList({
|
||||
// #ownProps
|
||||
items,
|
||||
initialItemId,
|
||||
selectedItemId,
|
||||
loading = false,
|
||||
defaultSelectText,
|
||||
onItemSelected,
|
||||
buttonProps,
|
||||
}) {
|
||||
const initialItem = React.useMemo(
|
||||
() => items.find((a) => a.id === initialItemId),
|
||||
[initialItemId, items],
|
||||
);
|
||||
|
||||
const [selecetedItem, setSelectedItem] = React.useState(initialItem || null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof selectedItemId !== 'undefined') {
|
||||
const item = selectedItemId
|
||||
? items.find((a) => a.id === selectedItemId)
|
||||
: null;
|
||||
setSelectedItem(item);
|
||||
}
|
||||
}, [selectedItemId, items, setSelectedItem]);
|
||||
|
||||
// Menu items renderer.
|
||||
const itemRenderer = (item, { handleClick, modifiers, query }) => (
|
||||
<MenuItem
|
||||
text={item.name}
|
||||
key={item.id}
|
||||
label={item.code}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
|
||||
// Filters items items.
|
||||
const filterItemsPredicater = (query, item, _index, exactMatch) => {
|
||||
const normalizedTitle = item.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${item.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
const handleItemMenuSelect = React.useCallback(
|
||||
(item) => {
|
||||
if (item.id) {
|
||||
setSelectedItem({ ...item });
|
||||
onItemSelected && onItemSelected(item);
|
||||
}
|
||||
},
|
||||
[onItemSelected, setSelectedItem],
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
items={items}
|
||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||
itemRenderer={itemRenderer}
|
||||
itemPredicate={filterItemsPredicater}
|
||||
onItemSelect={handleItemMenuSelect}
|
||||
popoverProps={{
|
||||
minimal: true,
|
||||
position: Position.BOTTOM_LEFT,
|
||||
interactionKind: PopoverInteractionKind.CLICK,
|
||||
modifiers: {
|
||||
offset: { offset: '0, 4' },
|
||||
},
|
||||
}}
|
||||
className={clsx({ [Classes.SKELETON]: loading })}
|
||||
>
|
||||
<Button
|
||||
text={
|
||||
selecetedItem
|
||||
? `${defaultSelectText}:${selecetedItem.name} ${defaultTo(
|
||||
selecetedItem.code,
|
||||
'',
|
||||
)}`
|
||||
: `${defaultSelectText}: Bigcapital`
|
||||
}
|
||||
minimal={true}
|
||||
small={true}
|
||||
{...buttonProps}
|
||||
/>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
@@ -8,16 +8,22 @@ function CustomerDrawerLinkComponent({
|
||||
// #ownProps
|
||||
children,
|
||||
customerId,
|
||||
className,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
}) {
|
||||
// Handle view customer drawer.
|
||||
const handleCustomerDrawer = () => {
|
||||
const handleCustomerDrawer = (event) => {
|
||||
openDrawer('customer-details-drawer', { customerId });
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return <ButtonLink onClick={handleCustomerDrawer}>{children}</ButtonLink>;
|
||||
return (
|
||||
<ButtonLink className={className} onClick={handleCustomerDrawer}>
|
||||
{children}
|
||||
</ButtonLink>
|
||||
);
|
||||
}
|
||||
|
||||
export const CustomerDrawerLink = R.compose(withDrawerActions)(
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React, { useRef, useCallback, useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useCellAutoFocus } from 'hooks';
|
||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import { useCellAutoFocus } from 'hooks';
|
||||
import AccountsSuggestField from 'components/AccountsSuggestField';
|
||||
|
||||
// import AccountsSelectList from 'components/AccountsSelectList';
|
||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Account cell renderer.
|
||||
*/
|
||||
@@ -74,3 +73,4 @@ export default function AccountCellRenderer({
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
AccountCellRenderer.cellType = CellType.Field;
|
||||
|
||||
44
src/components/DataTableCells/BranchesListFieldCell.js
Normal file
44
src/components/DataTableCells/BranchesListFieldCell.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { FormGroup, Intent, Classes } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import BranchSuggestField from '../BranchSuggestField';
|
||||
|
||||
/**
|
||||
* Branches list field cell.
|
||||
* @returns
|
||||
*/
|
||||
export default function BranchesListFieldCell({
|
||||
column: { id },
|
||||
row: { index, original },
|
||||
payload: { branches, updateData, errors },
|
||||
}) {
|
||||
const handleBranchSelected = React.useCallback(
|
||||
(branch) => {
|
||||
updateData(index, 'branch_id', branch.id);
|
||||
},
|
||||
[updateData, index],
|
||||
);
|
||||
|
||||
const error = errors?.[index]?.[id];
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
intent={error ? Intent.DANGER : null}
|
||||
className={classNames(
|
||||
'form-group--select-list',
|
||||
'form-group--contacts-list',
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<BranchSuggestField
|
||||
branches={branches}
|
||||
onBranchSelected={handleBranchSelected}
|
||||
selectedBranchId={original?.branch_id}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
BranchesListFieldCell.cellType = CellType.Field;
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { Classes, Checkbox, FormGroup, Intent } from '@blueprintjs/core';
|
||||
import { CellType } from 'common';
|
||||
|
||||
const CheckboxEditableCell = ({
|
||||
row: { index, original },
|
||||
@@ -45,4 +46,6 @@ const CheckboxEditableCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
CheckboxEditableCell.cellType = CellType.Field;
|
||||
|
||||
export default CheckboxEditableCell;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { FormGroup, Intent, Classes } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import { ContactSelecetList } from 'components';
|
||||
import ContactsSuggestField from 'components/ContactsSuggestField';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import ContactsSuggestField from 'components/ContactsSuggestField';
|
||||
export default function ContactsListCellRenderer({
|
||||
column: { id },
|
||||
row: { index, original },
|
||||
@@ -37,3 +37,5 @@ export default function ContactsListCellRenderer({
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
ContactsListCellRenderer.cellType = CellType.Field;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Classes, InputGroup, FormGroup, Intent } from '@blueprintjs/core';
|
||||
import { CellType } from 'common';
|
||||
|
||||
const InputEditableCell = ({
|
||||
row: { index },
|
||||
@@ -37,4 +38,6 @@ const InputEditableCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
InputEditableCell.cellType = CellType.Field;
|
||||
|
||||
export default InputEditableCell;
|
||||
|
||||
@@ -3,6 +3,7 @@ import classNames from 'classnames';
|
||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import ItemsSuggestField from 'components/ItemsSuggestField';
|
||||
|
||||
import { useCellAutoFocus } from 'hooks';
|
||||
@@ -54,3 +55,5 @@ export default function ItemsListCell({
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
ItemsListCell.cellType = CellType.Field;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import { FormGroup, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { MoneyInputGroup } from 'components';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { CellType } from 'common';
|
||||
|
||||
// Input form cell renderer.
|
||||
const MoneyFieldCellRenderer = ({
|
||||
@@ -48,4 +50,6 @@ const MoneyFieldCellRenderer = ({
|
||||
);
|
||||
};
|
||||
|
||||
MoneyFieldCellRenderer.cellType = CellType.Field;
|
||||
|
||||
export default MoneyFieldCellRenderer;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { FormGroup, NumericInput, Intent } from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
/**
|
||||
@@ -36,8 +38,10 @@ export default function NumericInputCell({
|
||||
onValueChange={handleValueChange}
|
||||
onBlur={onBlur}
|
||||
fill={true}
|
||||
buttonPosition={"none"}
|
||||
buttonPosition={'none'}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
NumericInputCell.cellType = CellType.Field;
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import PaymentReceiveListField from 'components/PaymentReceiveListField';
|
||||
import classNames from 'classnames';
|
||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||
|
||||
import PaymentReceiveListField from 'components/PaymentReceiveListField';
|
||||
import { CellType } from 'common';
|
||||
function PaymentReceiveListFieldCell({
|
||||
column: { id },
|
||||
row: { index },
|
||||
@@ -32,4 +33,6 @@ function PaymentReceiveListFieldCell({
|
||||
);
|
||||
}
|
||||
|
||||
PaymentReceiveListFieldCell.cellType = CellType.Field;
|
||||
|
||||
export default PaymentReceiveListFieldCell;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import { FormGroup, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { MoneyInputGroup } from 'components';
|
||||
import { CellType } from 'common';
|
||||
|
||||
const PercentFieldCell = ({
|
||||
cell: { value: initialValue },
|
||||
@@ -38,4 +40,6 @@ const PercentFieldCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
PercentFieldCell.cellType = CellType.Field;
|
||||
|
||||
export default PercentFieldCell;
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Classes, Switch, FormGroup, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { CellType } from 'common';
|
||||
import { safeInvoke } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -48,4 +49,6 @@ const SwitchEditableCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default SwitchEditableCell;
|
||||
SwitchEditableCell.cellType = CellType.Field;
|
||||
|
||||
export default SwitchEditableCell;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Classes, TextArea, FormGroup, Intent } from '@blueprintjs/core';
|
||||
import { CellType } from 'common';
|
||||
|
||||
const TextAreaEditableCell = ({
|
||||
row: { index },
|
||||
@@ -39,4 +40,6 @@ const TextAreaEditableCell = ({
|
||||
);
|
||||
};
|
||||
|
||||
TextAreaEditableCell.cellType = CellType.Field;
|
||||
|
||||
export default TextAreaEditableCell;
|
||||
|
||||
@@ -9,6 +9,7 @@ import NumericInputCell from './NumericInputCell';
|
||||
import CheckBoxFieldCell from './CheckBoxFieldCell';
|
||||
import SwitchFieldCell from './SwitchFieldCell';
|
||||
import TextAreaCell from './TextAreaCell';
|
||||
import BranchesListFieldCell from './BranchesListFieldCell';
|
||||
|
||||
export {
|
||||
AccountsListFieldCell,
|
||||
@@ -23,4 +24,5 @@ export {
|
||||
CheckBoxFieldCell,
|
||||
SwitchFieldCell,
|
||||
TextAreaCell,
|
||||
BranchesListFieldCell,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { DataTable, If } from 'components';
|
||||
import 'style/components/DataTable/DataTableEditable.scss';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DataTable } from 'components';
|
||||
|
||||
/**
|
||||
* Editable datatable.
|
||||
@@ -11,26 +10,106 @@ export default function DatatableEditable({
|
||||
totalRow = false,
|
||||
actions,
|
||||
name,
|
||||
className,
|
||||
...tableProps
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
CLASSES.DATATABLE_EDITOR,
|
||||
{
|
||||
[`${CLASSES.DATATABLE_EDITOR}--${name}`]: name,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<DatatableEditableRoot>
|
||||
<DataTable {...tableProps} />
|
||||
|
||||
<If condition={actions}>
|
||||
<div className={classNames(CLASSES.DATATABLE_EDITOR_ACTIONS)}>
|
||||
{actions}
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
</DatatableEditableRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const DatatableEditableRoot = styled.div`
|
||||
.bp3-form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.table {
|
||||
border: 1px solid #d2dce2;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
|
||||
.th,
|
||||
.td {
|
||||
border-left: 1px solid #e2e2e2;
|
||||
|
||||
&:first-of-type{
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.thead {
|
||||
.tr .th {
|
||||
padding: 9px 14px;
|
||||
background-color: #f2f3fb;
|
||||
font-size: 13px;
|
||||
color: #415060;
|
||||
border-bottom: 1px solid #d2dce2;
|
||||
|
||||
&,
|
||||
.inner-resizer {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tbody {
|
||||
.tr .td {
|
||||
border-bottom: 0;
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
min-height: 38px;
|
||||
padding: 4px 14px;
|
||||
|
||||
&.td-field-type,
|
||||
&.td-button-type{
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
.tr:last-of-type .td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.tr {
|
||||
&:hover .td,
|
||||
.bp3-input {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bp3-form-group:not(.bp3-intent-danger) .bp3-input,
|
||||
.form-group--select-list .bp3-button {
|
||||
border-color: #ffffff;
|
||||
color: #222;
|
||||
border-radius: 3px;
|
||||
text-align: inherit;
|
||||
}
|
||||
.bp3-form-group:not(.bp3-intent-danger) .bp3-input {
|
||||
border-radius: 2px;
|
||||
padding-left: 14px;
|
||||
padding-right: 14px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px #116cd0;
|
||||
}
|
||||
}
|
||||
.form-group--select-list .bp3-button {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.form-group--select-list,
|
||||
.bp3-form-group {
|
||||
&.bp3-intent-danger {
|
||||
.bp3-button:not(.bp3-minimal),
|
||||
.bp3-input {
|
||||
border-color: #f7b6b6;
|
||||
}
|
||||
}
|
||||
}
|
||||
.td.actions {
|
||||
.bp3-button {
|
||||
color: #80858f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { If } from 'components';
|
||||
import { Skeleton } from 'components';
|
||||
import { camelCase} from 'lodash';
|
||||
|
||||
import { If, Skeleton } from 'components';
|
||||
import { useAppIntlContext } from 'components/AppIntlProvider';
|
||||
import TableContext from './TableContext';
|
||||
import { saveInvoke, ignoreEventFromSelectors } from 'utils';
|
||||
@@ -56,7 +57,8 @@ export default function TableCell({ cell, row, index }) {
|
||||
return;
|
||||
}
|
||||
saveInvoke(onCellClick, cell, event);
|
||||
};
|
||||
};
|
||||
const cellType = camelCase(cell.column.Cell.cellType) || 'text';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -65,6 +67,9 @@ export default function TableCell({ cell, row, index }) {
|
||||
'is-text-overview': cell.column.textOverview,
|
||||
clickable: cell.column.clickable,
|
||||
'align-right': cell.column.align === 'right',
|
||||
'align-center': cell.column.align === 'center',
|
||||
[`td-${cell.column.id}`]: cell.column.id,
|
||||
[`td-${cellType}-type`]: !!cellType,
|
||||
}),
|
||||
onClick: handleCellClick,
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Checkbox } from '@blueprintjs/core';
|
||||
|
||||
import { CellType } from 'common';
|
||||
export default function TableIndeterminateCheckboxRow({ row }) {
|
||||
return (
|
||||
<div class="selection-checkbox">
|
||||
@@ -8,3 +8,5 @@ export default function TableIndeterminateCheckboxRow({ row }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TableIndeterminateCheckboxRow.cellType = CellType.Field;
|
||||
|
||||
34
src/components/DetailExchangeRate.js
Normal file
34
src/components/DetailExchangeRate.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import * as R from 'ramda';
|
||||
import { DetailItem } from 'components';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||
|
||||
/**
|
||||
* Detail exchange rate item.
|
||||
* @param {*} param0
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
function DetailExchangeRate({
|
||||
exchangeRate,
|
||||
toCurrency,
|
||||
// #withCurrentOrganization
|
||||
organization: { base_currency },
|
||||
}) {
|
||||
if (isEqual(base_currency, toCurrency)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DetailItem label={intl.get('exchange_rate')}>
|
||||
1 {base_currency} = {exchangeRate} {toCurrency}
|
||||
</DetailItem>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExchangeRateDetailItem = R.compose(withCurrentOrganization())(
|
||||
DetailExchangeRate,
|
||||
);
|
||||
@@ -34,6 +34,12 @@ import UnlockingTransactionsDialog from '../containers/Dialogs/UnlockingTransact
|
||||
import UnlockingPartialTransactionsDialog from '../containers/Dialogs/UnlockingPartialTransactionsDialog';
|
||||
import CreditNotePdfPreviewDialog from '../containers/Dialogs/CreditNotePdfPreviewDialog';
|
||||
import PaymentReceivePdfPreviewDialog from '../containers/Dialogs/PaymentReceivePdfPreviewDialog';
|
||||
import WarehouseFormDialog from '../containers/Dialogs/WarehouseFormDialog';
|
||||
import BranchFormDialog from '../containers/Dialogs/BranchFormDialog';
|
||||
import BranchActivateDialog from '../containers/Dialogs/BranchActivateDialog';
|
||||
import WarehouseActivateDialog from '../containers/Dialogs/WarehouseActivateDialog';
|
||||
import CustomerOpeningBalanceDialog from '../containers/Dialogs/CustomerOpeningBalanceDialog';
|
||||
import VendorOpeningBalanceDialog from '../containers/Dialogs/VendorOpeningBalanceDialog';
|
||||
|
||||
/**
|
||||
* Dialogs container.
|
||||
@@ -78,6 +84,12 @@ export default function DialogsContainer() {
|
||||
/>
|
||||
<CreditNotePdfPreviewDialog dialogName={'credit-note-pdf-preview'} />
|
||||
<PaymentReceivePdfPreviewDialog dialogName={'payment-pdf-preview'} />
|
||||
<WarehouseFormDialog dialogName={'warehouse-form'} />
|
||||
<BranchFormDialog dialogName={'branch-form'} />
|
||||
<BranchActivateDialog dialogName={'branch-activate'} />
|
||||
<WarehouseActivateDialog dialogName={'warehouse-activate'} />
|
||||
<CustomerOpeningBalanceDialog dialogName={'customer-opening-balance'} />
|
||||
<VendorOpeningBalanceDialog dialogName={'vendor-opening-balance'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Classes, Icon, H4, Button } from '@blueprintjs/core';
|
||||
|
||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -13,6 +14,7 @@ function DrawerHeaderContent(props) {
|
||||
const {
|
||||
icon,
|
||||
title = <T id={'view_paper'} />,
|
||||
subTitle,
|
||||
onClose,
|
||||
name,
|
||||
closeDrawer,
|
||||
@@ -30,7 +32,10 @@ function DrawerHeaderContent(props) {
|
||||
return (
|
||||
<div className={Classes.DRAWER_HEADER}>
|
||||
<Icon icon={icon} iconSize={Icon.SIZE_LARGE} />
|
||||
<H4>{title}</H4>
|
||||
<H4>
|
||||
{title}
|
||||
<SubTitle>{subTitle}</SubTitle>
|
||||
</H4>
|
||||
|
||||
<Button
|
||||
aria-label="Close"
|
||||
@@ -44,3 +49,24 @@ function DrawerHeaderContent(props) {
|
||||
}
|
||||
|
||||
export default compose(withDrawerActions)(DrawerHeaderContent);
|
||||
|
||||
/**
|
||||
* SubTitle Drawer header.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function SubTitle({ children }) {
|
||||
if (children == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SubTitleHead>{children}</SubTitleHead>;
|
||||
}
|
||||
|
||||
const SubTitleHead = styled.div`
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
padding: 2px 0px;
|
||||
margin: 2px 0px;
|
||||
`;
|
||||
|
||||
@@ -21,6 +21,7 @@ import CreditNoteDetailDrawer from '../containers/Drawers/CreditNoteDetailDrawer
|
||||
import VendorCreditDetailDrawer from '../containers/Drawers/VendorCreditDetailDrawer';
|
||||
import RefundCreditNoteDetailDrawer from '../containers/Drawers/RefundCreditNoteDetailDrawer';
|
||||
import RefundVendorCreditDetailDrawer from '../containers/Drawers/RefundVendorCreditDetailDrawer';
|
||||
import WarehouseTransferDetailDrawer from '../containers/Drawers/WarehouseTransferDetailDrawer'
|
||||
|
||||
import { DRAWERS } from 'common/drawers';
|
||||
|
||||
@@ -59,6 +60,7 @@ export default function DrawersContainer() {
|
||||
<RefundVendorCreditDetailDrawer
|
||||
name={DRAWERS.REFUND_VENDOR_CREDIT_DETAIL_DRAWER}
|
||||
/>
|
||||
<WarehouseTransferDetailDrawer name={DRAWERS.WAREHOUSE_TRANSFER_DETAIL_DRAWER} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
58
src/components/ExchangeRate/ExchangeRateInput.js
Normal file
58
src/components/ExchangeRate/ExchangeRateInput.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ControlGroup } from '@blueprintjs/core';
|
||||
|
||||
import { FlagIcon } from '../Tags';
|
||||
import { FMoneyInputGroup, FFormGroup } from '../Forms';
|
||||
|
||||
export function ExchangeRateInputGroup({
|
||||
fromCurrency,
|
||||
toCurrency,
|
||||
inputGroupProps,
|
||||
formGroupProps,
|
||||
name,
|
||||
}) {
|
||||
return (
|
||||
<FFormGroup inline={true} {...formGroupProps} name={name}>
|
||||
<ControlGroup>
|
||||
<ExchangeRatePrepend>
|
||||
<ExchangeFlagIcon currencyCode={fromCurrency} /> 1 {fromCurrency} =
|
||||
</ExchangeRatePrepend>
|
||||
<ExchangeRateField
|
||||
allowDecimals={true}
|
||||
allowNegativeValue={true}
|
||||
{...inputGroupProps}
|
||||
name={name}
|
||||
/>
|
||||
<ExchangeRateAppend>
|
||||
<ExchangeFlagIcon currencyCode={toCurrency} /> {toCurrency}
|
||||
</ExchangeRateAppend>
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const ExchangeRateField = styled(FMoneyInputGroup)`
|
||||
max-width: 75px;
|
||||
`;
|
||||
|
||||
const ExchangeRateSideIcon = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
`;
|
||||
|
||||
const ExchangeRatePrepend = styled(ExchangeRateSideIcon)`
|
||||
padding-right: 8px;
|
||||
`;
|
||||
|
||||
const ExchangeRateAppend = styled(ExchangeRateSideIcon)`
|
||||
padding-left: 8px;
|
||||
`;
|
||||
|
||||
const ExchangeFlagIcon = styled(FlagIcon)`
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
`;
|
||||
94
src/components/ExchangeRate/ExchangeRateMutedField.js
Normal file
94
src/components/ExchangeRate/ExchangeRateMutedField.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import intl from 'react-intl-universal';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
FormGroup,
|
||||
Position,
|
||||
Classes,
|
||||
} from '@blueprintjs/core';
|
||||
import { ExchangeRateInputGroup, Icon } from 'components';
|
||||
|
||||
export function ExchangeRateMutedField({
|
||||
name,
|
||||
toCurrency,
|
||||
fromCurrency,
|
||||
date,
|
||||
exchangeRate,
|
||||
exchangeRateFieldProps,
|
||||
popoverProps,
|
||||
}) {
|
||||
const content = (
|
||||
<ExchangeRateFormGroupContent>
|
||||
<ExchangeRateInputGroup
|
||||
name={name}
|
||||
fromCurrency={fromCurrency}
|
||||
toCurrency={toCurrency}
|
||||
{...exchangeRateFieldProps}
|
||||
/>
|
||||
</ExchangeRateFormGroupContent>
|
||||
);
|
||||
|
||||
return (
|
||||
<ExchangeRateFormGroup label={`As on ${date},`}>
|
||||
<Popover
|
||||
content={content}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.RIGHT_TOP}
|
||||
modifiers={{
|
||||
offset: { offset: '0, 4' },
|
||||
}}
|
||||
{...popoverProps}
|
||||
minimal={true}
|
||||
usePortal={false}
|
||||
target={<div />}
|
||||
>
|
||||
<ExchangeRateButton>
|
||||
1 {fromCurrency} = {exchangeRate} {toCurrency}
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
rightIcon={<Icon icon="pen-18" iconSize={14} />}
|
||||
small={true}
|
||||
/>
|
||||
</ExchangeRateButton>
|
||||
</Popover>
|
||||
</ExchangeRateFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
const ExchangeRateFormGroup = styled(FormGroup)`
|
||||
&.bp3-form-group {
|
||||
label.bp3-label {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
line-height: 1;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ExchangeRateButton = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #0d244a;
|
||||
position: relative;
|
||||
padding-right: 28px;
|
||||
|
||||
.bp3-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const ExchangeRateFormGroupContent = styled.div`
|
||||
padding: 5px 0;
|
||||
|
||||
.bp3-form-group {
|
||||
padding: 2px;
|
||||
margin: 2px 4px !important;
|
||||
}
|
||||
`;
|
||||
2
src/components/ExchangeRate/index.js
Normal file
2
src/components/ExchangeRate/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './ExchangeRateInput';
|
||||
export * from './ExchangeRateMutedField'
|
||||
13
src/components/FeatureGuard/FeatureCan.js
Normal file
13
src/components/FeatureGuard/FeatureCan.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
import withFeatureCan from './withFeatureCan';
|
||||
|
||||
function FeatureCanJSX({ feature, children, isFeatureCan }) {
|
||||
return isFeatureCan && children;
|
||||
}
|
||||
|
||||
export const FeatureCan = R.compose(
|
||||
withFeatureCan(({ isFeatureCan }) => ({
|
||||
isFeatureCan,
|
||||
})),
|
||||
)(FeatureCanJSX);
|
||||
1
src/components/FeatureGuard/index.js
Normal file
1
src/components/FeatureGuard/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './FeatureCan';
|
||||
17
src/components/FeatureGuard/withFeatureCan.js
Normal file
17
src/components/FeatureGuard/withFeatureCan.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getDashboardFeaturesSelector } from '../../store/dashboard/dashboard.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const featuresSelector = getDashboardFeaturesSelector();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const features = featuresSelector(state);
|
||||
|
||||
const mapped = {
|
||||
isFeatureCan: !!features[props.feature],
|
||||
features,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
44
src/components/FormTopbar.js
Normal file
44
src/components/FormTopbar.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Navbar } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Form the topbar.
|
||||
* @param {JSX.Element} children
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function FormTopbar({ className, children }) {
|
||||
return <FormTopBarRoot className={className}>{children}</FormTopBarRoot>;
|
||||
}
|
||||
|
||||
const FormTopBarRoot = styled(Navbar)`
|
||||
box-shadow: 0 0 0;
|
||||
border-bottom: 1px solid #c7d5db;
|
||||
height: 35px;
|
||||
padding: 0 20px;
|
||||
|
||||
.bp3-navbar-group {
|
||||
height: 35px;
|
||||
}
|
||||
.bp3-navbar-divider {
|
||||
border-left-color: #d2dce2;
|
||||
}
|
||||
.bp3-skeleton {
|
||||
max-height: 10px;
|
||||
}
|
||||
.bp3-button {
|
||||
&:hover {
|
||||
background: rgba(167, 182, 194, 0.12);
|
||||
color: #32304a;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const DetailsBarSkeletonBase = styled.div`
|
||||
letter-spacing: 10px;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
font-size: 8px;
|
||||
width: 140px;
|
||||
height: 10px;
|
||||
`;
|
||||
24
src/components/Forms/BlueprintFormik.js
Normal file
24
src/components/Forms/BlueprintFormik.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
NumericInput,
|
||||
Checkbox,
|
||||
RadioGroup,
|
||||
Switch,
|
||||
EditableText,
|
||||
TextArea,
|
||||
} from '@blueprintjs-formik/core';
|
||||
import { Select, MultiSelect } from '@blueprintjs-formik/select';
|
||||
|
||||
export {
|
||||
FormGroup as FFormGroup,
|
||||
InputGroup as FInputGroup,
|
||||
NumericInput as FNumericInput,
|
||||
Checkbox as FCheckbox,
|
||||
RadioGroup as FRadioGroup,
|
||||
Switch as FSwitch,
|
||||
Select as FSelect,
|
||||
MultiSelect as FMultiSelect,
|
||||
EditableText as FEditableText,
|
||||
TextArea as FTextArea,
|
||||
};
|
||||
36
src/components/Forms/FMoneyInputGroup.js
Normal file
36
src/components/Forms/FMoneyInputGroup.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { Field, getIn } from 'formik';
|
||||
import { CurrencyInput } from './MoneyInputGroup';
|
||||
|
||||
const fieldToMoneyInputGroup = ({
|
||||
field: { onBlur: onFieldBlur, ...field },
|
||||
form: { setFieldValue, touched, errors },
|
||||
onBlur,
|
||||
...props
|
||||
}) => {
|
||||
const fieldError = getIn(errors, field.name);
|
||||
const showError = getIn(touched, field.name) && !!fieldError;
|
||||
|
||||
return {
|
||||
intent: showError ? Intent.DANGER : Intent.NONE,
|
||||
onBlurValue:
|
||||
onBlur ??
|
||||
function (e) {
|
||||
onFieldBlur(e ?? field.name);
|
||||
},
|
||||
...field,
|
||||
onChange: (value) => {
|
||||
setFieldValue(field.name, value);
|
||||
},
|
||||
...props,
|
||||
};
|
||||
};
|
||||
|
||||
function FieldToMoneyInputGroup({ ...props }) {
|
||||
return <CurrencyInput {...fieldToMoneyInputGroup(props)} />;
|
||||
}
|
||||
|
||||
export function FMoneyInputGroup({ ...props }) {
|
||||
return <Field {...props} component={FieldToMoneyInputGroup} />;
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
|
||||
import { useFormikContext } from 'formik';
|
||||
import { useDeepCompareEffect } from 'hooks/utils';
|
||||
|
||||
export function FormikObserver({ onChange, values }) {
|
||||
export function FormikObserver({ onChange }) {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
useDeepCompareEffect(() => {
|
||||
onChange(values);
|
||||
}, [values]);
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from './FormObserver';
|
||||
export * from './FormikObserver';
|
||||
export * from './FormikObserver';
|
||||
export * from './FMoneyInputGroup'
|
||||
export * from './BlueprintFormik';
|
||||
12
src/components/Paper/Paper.js
Normal file
12
src/components/Paper/Paper.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export function Paper({ children, className }) {
|
||||
return <PaperRoot className={className}>{children}</PaperRoot>;
|
||||
}
|
||||
|
||||
const PaperRoot = styled.div`
|
||||
border: 1px solid #d2dce2;
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
`;
|
||||
1
src/components/Paper/index.js
Normal file
1
src/components/Paper/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './Paper';
|
||||
@@ -6,6 +6,8 @@ import { CLASSES } from 'common/classes';
|
||||
import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
|
||||
import UsersActions from 'containers/Preferences/Users/UsersActions';
|
||||
import CurrenciesActions from 'containers/Preferences/Currencies/CurrenciesActions';
|
||||
import WarehousesActions from '../../containers/Preferences/Warehouses/WarehousesActions';
|
||||
import BranchesActions from '../../containers/Preferences/Branches/BranchesActions';
|
||||
import withDashboard from 'containers/Dashboard/withDashboard';
|
||||
|
||||
import { compose } from 'utils';
|
||||
@@ -35,6 +37,16 @@ function PreferencesTopbar({ preferencesPageTitle }) {
|
||||
path={'/preferences/currencies'}
|
||||
component={CurrenciesActions}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={'/preferences/warehouses'}
|
||||
component={WarehousesActions}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={'/preferences/branches'}
|
||||
component={BranchesActions}
|
||||
/>
|
||||
</Switch>
|
||||
</Route>
|
||||
</div>
|
||||
|
||||
@@ -9,3 +9,13 @@ export const CurrencyTag = styled.span`
|
||||
line-height: 1;
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
export const BaseCurrencyRoot = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
margin-left: 4px;
|
||||
> span {
|
||||
background: #5c7080;
|
||||
}
|
||||
`;
|
||||
|
||||
7
src/components/Tags/FlagIcon.js
Normal file
7
src/components/Tags/FlagIcon.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
export const FlagIcon = ({ currencyCode, className }) => {
|
||||
const source = `/icons/flags/${currencyCode}.svg`;
|
||||
|
||||
return <img alt="flag" src={source} className={className} />;
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
|
||||
export * from './CurrencyTag';
|
||||
export * from './CurrencyTag';
|
||||
export * from './FlagIcon'
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TotalLineBorderStyle = {
|
||||
None: 'None',
|
||||
SingleDark: 'SingleDark',
|
||||
DoubleDark: 'DoubleDark',
|
||||
};
|
||||
@@ -80,6 +81,11 @@ export const TotalLineRoot = styled.div`
|
||||
`
|
||||
border-bottom: 1px double #000;
|
||||
`}
|
||||
${(props) =>
|
||||
props.borderStyle === TotalLineBorderStyle.None &&
|
||||
`
|
||||
border-bottom-color: transparent;
|
||||
`}
|
||||
${(props) =>
|
||||
props.textStyle === TotalLineTextStyle.Bold &&
|
||||
`
|
||||
|
||||
@@ -8,16 +8,18 @@ function VendorDrawerLinkComponent({
|
||||
// #ownProps
|
||||
children,
|
||||
vendorId,
|
||||
className,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
}) {
|
||||
// Handle view customer drawer.
|
||||
const handleVendorDrawer = () => {
|
||||
const handleVendorDrawer = (event) => {
|
||||
openDrawer('vendor-details-drawer', { vendorId });
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return <ButtonLink onClick={handleVendorDrawer}>{children}</ButtonLink>;
|
||||
return <ButtonLink className={className} onClick={handleVendorDrawer}>{children}</ButtonLink>;
|
||||
}
|
||||
|
||||
export const VendorDrawerLink = R.compose(withDrawerActions)(VendorDrawerLinkComponent);
|
||||
|
||||
74
src/components/Warehouses/WarehouseMultiSelect.js
Normal file
74
src/components/Warehouses/WarehouseMultiSelect.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import { FMultiSelect } from '../Forms';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} warehouse
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const warehouseItemPredicate = (query, warehouse, _index, exactMatch) => {
|
||||
const normalizedTitle = warehouse.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return (
|
||||
`${warehouse.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} branch
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const warehouseItemRenderer = (
|
||||
warehouse,
|
||||
{ handleClick, modifiers, query },
|
||||
{ isSelected },
|
||||
) => {
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
icon={isSelected ? 'tick' : 'blank'}
|
||||
text={warehouse.name}
|
||||
label={warehouse.code}
|
||||
key={warehouse.id}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const warehouseSelectProps = {
|
||||
itemPredicate: warehouseItemPredicate,
|
||||
itemRenderer: warehouseItemRenderer,
|
||||
valueAccessor: (item) => item.id,
|
||||
labelAccessor: (item) => item.code,
|
||||
tagRenderer: (item) => item.name,
|
||||
};
|
||||
|
||||
/**
|
||||
* warehouses mulit select.
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export function WarehouseMultiSelect({ warehouses, ...rest }) {
|
||||
return (
|
||||
<FMultiSelect
|
||||
items={warehouses}
|
||||
placeholder={intl.get('warehouses_multi_select.placeholder')}
|
||||
popoverProps={{ minimal: true, usePortal: false }}
|
||||
{...warehouseSelectProps}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
74
src/components/Warehouses/WarehouseSelect.js
Normal file
74
src/components/Warehouses/WarehouseSelect.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { FSelect } from '../Forms';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} warehouse
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const warehouseItemPredicate = (query, warehouse, _index, exactMatch) => {
|
||||
const normalizedTitle = warehouse.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return (
|
||||
`${warehouse.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} film
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const warehouseItemRenderer = (
|
||||
warehouse,
|
||||
{ handleClick, modifiers, query },
|
||||
) => {
|
||||
const text = `${warehouse.name}`;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
label={warehouse.code}
|
||||
key={warehouse.id}
|
||||
onClick={handleClick}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const warehouseSelectProps = {
|
||||
itemPredicate: warehouseItemPredicate,
|
||||
itemRenderer: warehouseItemRenderer,
|
||||
valueAccessor: 'id',
|
||||
labelAccessor: 'name',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export function WarehouseSelect({ warehouses, ...rest }) {
|
||||
return <FSelect {...warehouseSelectProps} {...rest} items={warehouses} />;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export function WarehouseSelectButton({ label, ...rest }) {
|
||||
return <Button text={label} />;
|
||||
}
|
||||
2
src/components/Warehouses/index.js
Normal file
2
src/components/Warehouses/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './WarehouseSelect';
|
||||
export * from './WarehouseMultiSelect';
|
||||
@@ -54,6 +54,8 @@ import AvaterCell from './AvaterCell';
|
||||
|
||||
import { ItemsMultiSelect } from './Items';
|
||||
import MoreMenuItems from './MoreMenutItems';
|
||||
import CustomSelectList from './CustomSelectList';
|
||||
import { ExchangeRateDetailItem } from './DetailExchangeRate';
|
||||
|
||||
export * from './Dialog';
|
||||
export * from './Menu';
|
||||
@@ -88,13 +90,20 @@ export * from './TextStatus';
|
||||
export * from './Tags';
|
||||
export * from './CommercialDoc';
|
||||
export * from './Card';
|
||||
export * from './Customers'
|
||||
export * from './Vendors'
|
||||
export * from './Customers';
|
||||
export * from './Vendors';
|
||||
export * from './Table';
|
||||
export * from './Skeleton';
|
||||
export * from './FinancialStatement';
|
||||
export * from './FinancialReport';
|
||||
export * from './FinancialSheet';
|
||||
export * from './FeatureGuard';
|
||||
export * from './ExchangeRate';
|
||||
export * from './Branches';
|
||||
export * from './Warehouses';
|
||||
export * from './Currencies';
|
||||
export * from './FormTopbar'
|
||||
export * from './Paper';
|
||||
|
||||
const Hint = FieldHint;
|
||||
|
||||
@@ -161,4 +170,6 @@ export {
|
||||
ItemsMultiSelect,
|
||||
AvaterCell,
|
||||
MoreMenuItems,
|
||||
CustomSelectList,
|
||||
ExchangeRateDetailItem,
|
||||
};
|
||||
|
||||
@@ -13,9 +13,16 @@ export default [
|
||||
},
|
||||
{
|
||||
text: <T id={'currencies'} />,
|
||||
|
||||
href: '/preferences/currencies',
|
||||
},
|
||||
{
|
||||
text: <T id={'branches.label'} />,
|
||||
href: '/preferences/branches',
|
||||
},
|
||||
{
|
||||
text: <T id={'warehouses.label'} />,
|
||||
href: '/preferences/warehouses',
|
||||
},
|
||||
{
|
||||
text: <T id={'accountant'} />,
|
||||
disabled: false,
|
||||
|
||||
@@ -78,6 +78,10 @@ export default [
|
||||
ability: InventoryAdjustmentAction.View,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: <T id={'sidebar_warehouse_transfer'} />,
|
||||
href: '/warehouses-transfers',
|
||||
},
|
||||
{
|
||||
text: <T id={'category_list'} />,
|
||||
href: '/items/categories',
|
||||
@@ -113,6 +117,10 @@ export default [
|
||||
ability: ItemAction.Create,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: <T id={'warehouse_transfer.label.new_warehouse_transfer'} />,
|
||||
href: '/warehouses-transfers/new',
|
||||
},
|
||||
{
|
||||
text: <T id={'New service'} />,
|
||||
href: '/items/new',
|
||||
@@ -633,6 +641,14 @@ export default [
|
||||
ability: ReportsAction.READ_AP_AGING_SUMMARY,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// text: <T id={'realized_gain_or_loss.label'} />,
|
||||
// href: '/financial-reports/realized-gain-loss',
|
||||
// },
|
||||
// {
|
||||
// text: <T id={'unrealized_gain_or_loss.label'} />,
|
||||
// href: '/financial-reports/unrealized-gain-loss',
|
||||
// },
|
||||
{
|
||||
text: <T id={'Sales/Purchases'} />,
|
||||
label: true,
|
||||
|
||||
@@ -13,13 +13,13 @@ const Schema = Yup.object().shape({
|
||||
.min(1)
|
||||
.max(DATATYPES_LENGTH.STRING)
|
||||
.label(intl.get('journal_type')),
|
||||
date: Yup.date()
|
||||
.required()
|
||||
.label(intl.get('date')),
|
||||
date: Yup.date().required().label(intl.get('date')),
|
||||
currency_code: Yup.string().max(3),
|
||||
publish: Yup.boolean(),
|
||||
branch_id: Yup.string(),
|
||||
reference: Yup.string().nullable().min(1).max(DATATYPES_LENGTH.STRING),
|
||||
description: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
|
||||
exchange_rate: Yup.number(),
|
||||
entries: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
credit: Yup.number().nullable(),
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
* Make journal entries field.
|
||||
*/
|
||||
export default function MakeJournalEntriesField() {
|
||||
const { accounts, contacts } = useMakeJournalFormContext();
|
||||
const { accounts, contacts ,branches } = useMakeJournalFormContext();
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||
@@ -18,6 +18,7 @@ export default function MakeJournalEntriesField() {
|
||||
name={'entries'}
|
||||
contacts={contacts}
|
||||
accounts={accounts}
|
||||
branches={branches}
|
||||
shouldUpdate={entriesFieldShouldUpdate}
|
||||
>
|
||||
{({
|
||||
|
||||
@@ -17,6 +17,7 @@ import MakeJournalFormFloatingActions from './MakeJournalFormFloatingActions';
|
||||
import MakeJournalEntriesField from './MakeJournalEntriesField';
|
||||
import MakeJournalFormFooter from './MakeJournalFormFooter';
|
||||
import MakeJournalFormDialogs from './MakeJournalFormDialogs';
|
||||
import MakeJournalFormTopBar from './MakeJournalFormTopBar';
|
||||
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||
@@ -58,6 +59,7 @@ function MakeJournalEntriesForm({
|
||||
journalNumberPrefix,
|
||||
journalNextNumber,
|
||||
);
|
||||
|
||||
// Form initial values.
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
@@ -68,12 +70,12 @@ function MakeJournalEntriesForm({
|
||||
: {
|
||||
...defaultManualJournal,
|
||||
...(journalAutoIncrement && {
|
||||
journal_number: defaultTo(journalNumber, ''),
|
||||
journal_number: journalNumber,
|
||||
}),
|
||||
currency_code: base_currency,
|
||||
}),
|
||||
}),
|
||||
[manualJournal, base_currency, journalNumber],
|
||||
[manualJournal, base_currency, journalNumber, journalAutoIncrement],
|
||||
);
|
||||
|
||||
// Handle the form submiting.
|
||||
@@ -107,7 +109,7 @@ function MakeJournalEntriesForm({
|
||||
return;
|
||||
}
|
||||
const form = {
|
||||
...omit(values, ['journal_number', 'journal_number_manually']),
|
||||
...omit(values, ['journal_number_manually']),
|
||||
...(values.journal_number_manually && {
|
||||
journal_number: values.journal_number,
|
||||
}),
|
||||
@@ -169,6 +171,7 @@ function MakeJournalEntriesForm({
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Form>
|
||||
<MakeJournalFormTopBar />
|
||||
<MakeJournalEntriesHeader />
|
||||
<MakeJournalEntriesField />
|
||||
<MakeJournalFormFooter />
|
||||
@@ -185,7 +188,7 @@ function MakeJournalEntriesForm({
|
||||
export default compose(
|
||||
withMediaActions,
|
||||
withSettings(({ manualJournalsSettings }) => ({
|
||||
journalNextNumber: parseInt(manualJournalsSettings?.nextNumber, 10),
|
||||
journalNextNumber: manualJournalsSettings?.nextNumber,
|
||||
journalNumberPrefix: manualJournalsSettings?.numberPrefix,
|
||||
journalAutoIncrement: manualJournalsSettings?.autoIncrement,
|
||||
})),
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from 'components';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
import { JournalExchangeRateInputField } from './components';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import {
|
||||
currenciesFieldShouldUpdate,
|
||||
@@ -52,14 +53,14 @@ function MakeJournalEntriesHeader({
|
||||
|
||||
// Handle journal number change.
|
||||
const handleJournalNumberChange = () => {
|
||||
openDialog('journal-number-form', {});
|
||||
openDialog('journal-number-form');
|
||||
};
|
||||
|
||||
// Handle journal number blur.
|
||||
const handleJournalNoBlur = (form, field) => (event) => {
|
||||
const newValue = event.target.value;
|
||||
|
||||
if (field.value !== newValue) {
|
||||
if (field.value !== newValue && journalAutoIncrement) {
|
||||
openDialog('journal-number-form', {
|
||||
initialFormValues: {
|
||||
manualTransactionNo: newValue,
|
||||
@@ -201,13 +202,19 @@ function MakeJournalEntriesHeader({
|
||||
selectedCurrencyCode={value}
|
||||
onCurrencySelected={(currencyItem) => {
|
||||
form.setFieldValue('currency_code', currencyItem.currency_code);
|
||||
form.setFieldValue('exchange_rate', '');
|
||||
}}
|
||||
defaultSelectText={value}
|
||||
disabled={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
{/* ----------- Exchange rate ----------- */}
|
||||
<JournalExchangeRateInputField
|
||||
name={'exchange_rate'}
|
||||
formGroupProps={{ label: ' ', inline: true }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { MakeJournalProvider } from './MakeJournalProvider';
|
||||
*/
|
||||
export default function MakeJournalEntriesPage() {
|
||||
const { id: journalId } = useParams();
|
||||
|
||||
|
||||
return (
|
||||
<MakeJournalProvider journalId={journalId}>
|
||||
<MakeJournalEntriesForm />
|
||||
|
||||
@@ -21,15 +21,16 @@ export default function MakeJournalEntriesTable({
|
||||
entries,
|
||||
defaultEntry,
|
||||
error,
|
||||
initialLinesNumber = 4,
|
||||
minLinesNumber = 4,
|
||||
initialLinesNumber = 1,
|
||||
minLinesNumber = 1,
|
||||
currencyCode,
|
||||
}) {
|
||||
const { accounts, contacts } = useMakeJournalFormContext();
|
||||
const { accounts, contacts, branches } = useMakeJournalFormContext();
|
||||
|
||||
// Memorized data table columns.
|
||||
const columns = useJournalTableEntriesColumns();
|
||||
|
||||
|
||||
// Handles update datatable data.
|
||||
const handleUpdateData = (rowIndex, columnId, value) => {
|
||||
const newRows = compose(
|
||||
@@ -62,13 +63,13 @@ export default function MakeJournalEntriesTable({
|
||||
data={entries}
|
||||
sticky={true}
|
||||
totalRow={true}
|
||||
footer={true}
|
||||
payload={{
|
||||
accounts,
|
||||
errors: error,
|
||||
updateData: handleUpdateData,
|
||||
removeRow: handleRemoveRow,
|
||||
contacts,
|
||||
branches,
|
||||
autoFocus: ['account_id', 0],
|
||||
currencyCode,
|
||||
}}
|
||||
|
||||
@@ -1,44 +1,29 @@
|
||||
import React from 'react';
|
||||
import { FastField } from 'formik';
|
||||
import classNames from 'classnames';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { FormGroup, TextArea } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import { Postbox, ErrorMessage, Row, Col } from 'components';
|
||||
import Dragzone from 'components/Dragzone';
|
||||
import { inputIntent } from 'utils';
|
||||
import { Row, Col, Paper } from 'components';
|
||||
import { MakeJournalFormFooterLeft } from './MakeJournalFormFooterLeft';
|
||||
import { MakeJournalFormFooterRight } from './MakeJournalFormFooterRight';
|
||||
|
||||
export default function MakeJournalFormFooter() {
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
||||
<Postbox title={<T id={'journal_details'} />} defaultOpen={false}>
|
||||
<MakeJournalFooterPaper>
|
||||
<Row>
|
||||
<Col md={8}>
|
||||
<FastField name={'description'}>
|
||||
{({ field, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
label={<T id={'description'} />}
|
||||
className={'form-group--description'}
|
||||
intent={inputIntent({ error, touched })}
|
||||
helperText={<ErrorMessage name="description" />}
|
||||
fill={true}
|
||||
>
|
||||
<TextArea fill={true} {...field} />
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
<MakeJournalFormFooterLeft />
|
||||
</Col>
|
||||
|
||||
<Col md={4}>
|
||||
<Dragzone
|
||||
initialFiles={[]}
|
||||
// onDrop={handleDropFiles}
|
||||
// onDeleteFile={handleDeleteFile}
|
||||
hint={<T id={'attachments_maximum'} />}
|
||||
/>
|
||||
<MakeJournalFormFooterRight />
|
||||
</Col>
|
||||
</Row>
|
||||
</Postbox>
|
||||
</MakeJournalFooterPaper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const MakeJournalFooterPaper = styled(Paper)`
|
||||
padding: 20px;
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FFormGroup, FEditableText, FormattedMessage as T } from 'components';
|
||||
|
||||
export function MakeJournalFormFooterLeft() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{/* --------- Description --------- */}
|
||||
<DescriptionFormGroup
|
||||
label={<T id={'description'} />}
|
||||
name={'description'}
|
||||
>
|
||||
<FEditableText
|
||||
name={'description'}
|
||||
placeholder={<T id={'make_jorunal.decscrption.placeholder'} />}
|
||||
/>
|
||||
</DescriptionFormGroup>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const DescriptionFormGroup = styled(FFormGroup)`
|
||||
&.bp3-form-group {
|
||||
.bp3-label {
|
||||
font-size: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.bp3-form-content {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
T,
|
||||
TotalLines,
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
} from 'components';
|
||||
import { useJournalTotals } from './utils';
|
||||
|
||||
export function MakeJournalFormFooterRight() {
|
||||
const { formattedSubtotal, formattedTotal } = useJournalTotals();
|
||||
|
||||
return (
|
||||
<MakeJouranlTotalLines>
|
||||
<TotalLine
|
||||
title={<T id={'make_journal.label.subtotal'} />}
|
||||
value={formattedSubtotal}
|
||||
borderStyle={TotalLineBorderStyle.None}
|
||||
/>
|
||||
<TotalLine
|
||||
title={<T id={'make_journal.label.total'} />}
|
||||
value={formattedTotal}
|
||||
textStyle={TotalLineTextStyle.Bold}
|
||||
/>
|
||||
</MakeJouranlTotalLines>
|
||||
);
|
||||
}
|
||||
|
||||
const MakeJouranlTotalLines = styled(TotalLines)`
|
||||
width: 100%;
|
||||
color: #555555;
|
||||
`;
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Button, Alignment, NavbarGroup, Classes } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
import { useSetPrimaryBranchToForm } from './utils';
|
||||
import { useFeatureCan } from 'hooks/state';
|
||||
import {
|
||||
Icon,
|
||||
BranchSelect,
|
||||
FeatureCan,
|
||||
FormTopbar,
|
||||
DetailsBarSkeletonBase,
|
||||
} from 'components';
|
||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
import { Features } from 'common';
|
||||
|
||||
/**
|
||||
* Make journal form topbar.
|
||||
* @returns
|
||||
*/
|
||||
export default function MakeJournalFormTopBar() {
|
||||
// Features guard.
|
||||
const { featureCan } = useFeatureCan();
|
||||
|
||||
// Sets the primary branch to form.
|
||||
useSetPrimaryBranchToForm();
|
||||
|
||||
// Can't display the navigation bar if branches feature is not enabled.
|
||||
if (!featureCan(Features.Branches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormTopbar>
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<FeatureCan feature={Features.Branches}>
|
||||
<MakeJournalFormSelectBranch />
|
||||
</FeatureCan>
|
||||
</NavbarGroup>
|
||||
</FormTopbar>
|
||||
);
|
||||
}
|
||||
|
||||
function MakeJournalFormSelectBranch() {
|
||||
// Invoice form context.
|
||||
const { branches, isBranchesLoading } = useMakeJournalFormContext();
|
||||
|
||||
return isBranchesLoading ? (
|
||||
<DetailsBarSkeletonBase className={Classes.SKELETON} />
|
||||
) : (
|
||||
<BranchSelect
|
||||
name={'branch_id'}
|
||||
branches={branches}
|
||||
input={MakeJournalBranchSelectButton}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
function MakeJournalBranchSelectButton({ label }) {
|
||||
return (
|
||||
<Button
|
||||
text={intl.get('make_journal.branch_button.label', { label })}
|
||||
minimal={true}
|
||||
small={true}
|
||||
icon={<Icon icon={'branch-16'} iconSize={16} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import React, { createContext, useState } from 'react';
|
||||
import { isEqual, isUndefined } from 'lodash';
|
||||
import { Features } from 'common';
|
||||
import { useFeatureCan } from 'hooks/state';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import {
|
||||
useAccounts,
|
||||
@@ -8,7 +11,8 @@ import {
|
||||
useCreateJournal,
|
||||
useEditJournal,
|
||||
useSettings,
|
||||
useSettingsManualJournals
|
||||
useBranches,
|
||||
useSettingsManualJournals,
|
||||
} from 'hooks/query';
|
||||
|
||||
const MakeJournalFormContext = createContext();
|
||||
@@ -16,15 +20,17 @@ const MakeJournalFormContext = createContext();
|
||||
/**
|
||||
* Make journal form provider.
|
||||
*/
|
||||
function MakeJournalProvider({ journalId, ...props }) {
|
||||
function MakeJournalProvider({ journalId, query, ...props }) {
|
||||
// Features guard.
|
||||
const { featureCan } = useFeatureCan();
|
||||
const isBranchFeatureCan = featureCan(Features.Branches);
|
||||
|
||||
// Load the accounts list.
|
||||
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
|
||||
|
||||
// Load the customers list.
|
||||
const {
|
||||
data: contacts,
|
||||
isLoading: isContactsLoading,
|
||||
} = useAutoCompleteContacts();
|
||||
const { data: contacts, isLoading: isContactsLoading } =
|
||||
useAutoCompleteContacts();
|
||||
|
||||
// Load the currencies list.
|
||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||
@@ -43,15 +49,27 @@ function MakeJournalProvider({ journalId, ...props }) {
|
||||
// Loading the journal settings.
|
||||
const { isLoading: isSettingsLoading } = useSettingsManualJournals();
|
||||
|
||||
// Fetches the branches list.
|
||||
const {
|
||||
data: branches,
|
||||
isLoading: isBranchesLoading,
|
||||
isSuccess: isBranchesSuccess,
|
||||
} = useBranches(query, { enabled: isBranchFeatureCan });
|
||||
|
||||
// Submit form payload.
|
||||
const [submitPayload, setSubmitPayload] = useState({});
|
||||
|
||||
// Determines whether the warehouse and branches are loading.
|
||||
const isFeatureLoading = isBranchesLoading;
|
||||
|
||||
const provider = {
|
||||
accounts,
|
||||
contacts,
|
||||
currencies,
|
||||
manualJournal,
|
||||
|
||||
branches,
|
||||
|
||||
createJournalMutate,
|
||||
editJournalMutate,
|
||||
|
||||
@@ -59,12 +77,13 @@ function MakeJournalProvider({ journalId, ...props }) {
|
||||
isContactsLoading,
|
||||
isCurrenciesLoading,
|
||||
isJournalLoading,
|
||||
isFeatureLoading,
|
||||
isSettingsLoading,
|
||||
|
||||
isBranchesSuccess,
|
||||
isNewMode: !journalId,
|
||||
|
||||
submitPayload,
|
||||
setSubmitPayload
|
||||
setSubmitPayload,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -73,7 +92,7 @@ function MakeJournalProvider({ journalId, ...props }) {
|
||||
isJournalLoading ||
|
||||
isAccountsLoading ||
|
||||
isCurrenciesLoading ||
|
||||
isContactsLoading ||
|
||||
isContactsLoading ||
|
||||
isSettingsLoading
|
||||
}
|
||||
name={'make-journal-page'}
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Intent, Position, Button, Tooltip } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import { Icon, Money, Hint } from 'components';
|
||||
import { Menu, MenuItem, Position, Button } from '@blueprintjs/core';
|
||||
import { Popover2 } from '@blueprintjs/popover2';
|
||||
import { useFormikContext } from 'formik';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { ExchangeRateInputGroup, Icon, Hint, FormattedMessage as T } from 'components';
|
||||
import {
|
||||
AccountsListFieldCell,
|
||||
MoneyFieldCell,
|
||||
InputGroupCell,
|
||||
ContactsListFieldCell,
|
||||
BranchesListFieldCell,
|
||||
} from 'components/DataTableCells';
|
||||
import { safeSumBy } from 'utils';
|
||||
|
||||
import { CellType, Features, Align } from 'common';
|
||||
|
||||
import { useFeatureCan } from 'hooks/state';
|
||||
import { useCurrentOrganization } from 'hooks/state';
|
||||
import { useJournalIsForeign } from './utils';
|
||||
|
||||
/**
|
||||
* Contact header cell.
|
||||
@@ -40,42 +48,6 @@ export function DebitHeaderCell({ payload: { currencyCode } }) {
|
||||
return intl.get('debit_currency', { currency: currencyCode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Account footer cell.
|
||||
*/
|
||||
function AccountFooterCell({ payload: { currencyCode } }) {
|
||||
return (
|
||||
<span>
|
||||
{intl.get('total_currency', { currency: currencyCode })}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total credit table footer cell.
|
||||
*/
|
||||
function TotalCreditFooterCell({ payload: { currencyCode }, rows }) {
|
||||
const credit = safeSumBy(rows, 'original.credit');
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Money amount={credit} currency={currencyCode} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total debit table footer cell.
|
||||
*/
|
||||
function TotalDebitFooterCell({ payload: { currencyCode }, rows }) {
|
||||
const debit = safeSumBy(rows, 'original.debit');
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Money amount={debit} currency={currencyCode} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Actions cell renderer.
|
||||
*/
|
||||
@@ -86,95 +58,120 @@ export const ActionsCellRenderer = ({
|
||||
data,
|
||||
payload,
|
||||
}) => {
|
||||
const onClickRemoveRole = () => {
|
||||
const handleClickRemoveRole = () => {
|
||||
payload.removeRow(index);
|
||||
};
|
||||
const exampleMenu = (
|
||||
<Menu>
|
||||
<MenuItem onClick={handleClickRemoveRole} text="Remove line" />
|
||||
</Menu>
|
||||
);
|
||||
return (
|
||||
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
|
||||
<Popover2 content={exampleMenu} placement="left-start">
|
||||
<Button
|
||||
icon={<Icon icon="times-circle" iconSize={14} />}
|
||||
icon={<Icon icon={'more-13'} iconSize={13} />}
|
||||
iconSize={14}
|
||||
className="ml2"
|
||||
className="m12"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Popover2>
|
||||
);
|
||||
};
|
||||
ActionsCellRenderer.cellType = CellType.Button;
|
||||
|
||||
/**
|
||||
* Retrieve columns of make journal entries table.
|
||||
*/
|
||||
export const useJournalTableEntriesColumns = () => {
|
||||
const { featureCan } = useFeatureCan();
|
||||
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: '#',
|
||||
accessor: 'index',
|
||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
||||
className: 'index',
|
||||
width: 40,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
sticky: 'left',
|
||||
},
|
||||
{
|
||||
Header: intl.get('account'),
|
||||
id: 'account_id',
|
||||
accessor: 'account_id',
|
||||
Cell: AccountsListFieldCell,
|
||||
Footer: AccountFooterCell,
|
||||
className: 'account',
|
||||
disableSortBy: true,
|
||||
width: 160,
|
||||
fieldProps: { allowCreate: true }
|
||||
fieldProps: { allowCreate: true },
|
||||
},
|
||||
{
|
||||
Header: CreditHeaderCell,
|
||||
accessor: 'credit',
|
||||
Cell: MoneyFieldCell,
|
||||
Footer: TotalCreditFooterCell,
|
||||
className: 'credit',
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: DebitHeaderCell,
|
||||
accessor: 'debit',
|
||||
Cell: MoneyFieldCell,
|
||||
Footer: TotalDebitFooterCell,
|
||||
className: 'debit',
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
align: Align.Right,
|
||||
},
|
||||
{
|
||||
Header: ContactHeaderCell,
|
||||
id: 'contact_id',
|
||||
accessor: 'contact_id',
|
||||
Cell: ContactsListFieldCell,
|
||||
className: 'contact',
|
||||
disableSortBy: true,
|
||||
width: 120,
|
||||
},
|
||||
...(featureCan(Features.Branches)
|
||||
? [
|
||||
{
|
||||
Header: intl.get('branch'),
|
||||
id: 'branch_id',
|
||||
accessor: 'branch_id',
|
||||
Cell: BranchesListFieldCell,
|
||||
disableSortBy: true,
|
||||
width: 120,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
Header: intl.get('note'),
|
||||
accessor: 'note',
|
||||
Cell: InputGroupCell,
|
||||
disableSortBy: true,
|
||||
className: 'note',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
Header: '',
|
||||
accessor: 'action',
|
||||
Cell: ActionsCellRenderer,
|
||||
className: 'actions',
|
||||
disableSortBy: true,
|
||||
disableResizing: true,
|
||||
width: 45,
|
||||
align: Align.Center,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Journal exchange rate input field.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function JournalExchangeRateInputField({ ...props }) {
|
||||
const currentOrganization = useCurrentOrganization();
|
||||
const { values } = useFormikContext();
|
||||
|
||||
const isForeignJouranl = useJournalIsForeign();
|
||||
|
||||
// Can't continue if the customer is not foreign.
|
||||
if (!isForeignJouranl) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ExchangeRateInputGroup
|
||||
fromCurrency={values.currency_code}
|
||||
toCurrency={currentOrganization.base_currency}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { sumBy, setWith, toSafeInteger, get } from 'lodash';
|
||||
import { sumBy, setWith, toSafeInteger, get, first } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
@@ -9,11 +9,15 @@ import {
|
||||
repeatValue,
|
||||
transformToForm,
|
||||
defaultFastFieldShouldUpdate,
|
||||
ensureEntriesHasEmptyLine
|
||||
ensureEntriesHasEmptyLine,
|
||||
formattedAmount,
|
||||
safeSumBy,
|
||||
} from 'utils';
|
||||
import { AppToaster } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { useMakeJournalFormContext } from './MakeJournalProvider';
|
||||
import { useCurrentOrganization } from 'hooks/state';
|
||||
|
||||
const ERROR = {
|
||||
JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS',
|
||||
@@ -26,25 +30,30 @@ const ERROR = {
|
||||
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
|
||||
};
|
||||
|
||||
export const MIN_LINES_NUMBER = 4;
|
||||
export const MIN_LINES_NUMBER = 1;
|
||||
export const DEFAULT_LINES_NUMBER = 1;
|
||||
|
||||
export const defaultEntry = {
|
||||
account_id: '',
|
||||
credit: '',
|
||||
debit: '',
|
||||
contact_id: '',
|
||||
branch_id: '',
|
||||
note: '',
|
||||
};
|
||||
|
||||
export const defaultManualJournal = {
|
||||
journal_number: '',
|
||||
journal_number_manually: false,
|
||||
journal_type: 'Journal',
|
||||
date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
description: '',
|
||||
reference: '',
|
||||
currency_code: '',
|
||||
publish: '',
|
||||
entries: [...repeatValue(defaultEntry, 4)],
|
||||
branch_id: '',
|
||||
exchange_rate: 1,
|
||||
entries: [...repeatValue(defaultEntry, DEFAULT_LINES_NUMBER)],
|
||||
};
|
||||
|
||||
// Transform to edit form.
|
||||
@@ -179,6 +188,7 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => {
|
||||
return (
|
||||
newProps.accounts !== oldProps.accounts ||
|
||||
newProps.contacts !== oldProps.contacts ||
|
||||
newProps.branches !== oldProps.branches ||
|
||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||
);
|
||||
};
|
||||
@@ -192,3 +202,63 @@ export const currenciesFieldShouldUpdate = (newProps, oldProps) => {
|
||||
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||
);
|
||||
};
|
||||
|
||||
export const useSetPrimaryBranchToForm = () => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const { branches, isBranchesSuccess } = useMakeJournalFormContext();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isBranchesSuccess) {
|
||||
const primaryBranch = branches.find((b) => b.primary) || first(branches);
|
||||
|
||||
if (primaryBranch) {
|
||||
setFieldValue('branch_id', primaryBranch.id);
|
||||
}
|
||||
}
|
||||
}, [isBranchesSuccess, setFieldValue, branches]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the Journal totals.
|
||||
*/
|
||||
export const useJournalTotals = () => {
|
||||
const {
|
||||
values: { entries, currency_code: currencyCode },
|
||||
} = useFormikContext();
|
||||
|
||||
// Retrieves the invoice entries total.
|
||||
const totalCredit = safeSumBy(entries, 'credit');
|
||||
const totalDebit = safeSumBy(entries, 'debit');
|
||||
|
||||
const total = Math.max(totalCredit, totalDebit);
|
||||
// Retrieves the formatted total money.
|
||||
const formattedTotal = React.useMemo(
|
||||
() => formattedAmount(total, currencyCode),
|
||||
[total, currencyCode],
|
||||
);
|
||||
// Retrieves the formatted subtotal.
|
||||
const formattedSubtotal = React.useMemo(
|
||||
() => formattedAmount(total, currencyCode, { money: false }),
|
||||
[total, currencyCode],
|
||||
);
|
||||
|
||||
return {
|
||||
formattedTotal,
|
||||
formattedSubtotal,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the expenses has foreign .
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const useJournalIsForeign = () => {
|
||||
const { values } = useFormikContext();
|
||||
const currentOrganization = useCurrentOrganization();
|
||||
|
||||
const isForeignJournal = React.useMemo(
|
||||
() => values.currency_code !== currentOrganization.base_currency,
|
||||
[values.currency_code, currentOrganization.base_currency],
|
||||
);
|
||||
return isForeignJournal;
|
||||
};
|
||||
|
||||
79
src/containers/Alerts/Branches/BranchDeleteAlert.js
Normal file
79
src/containers/Alerts/Branches/BranchDeleteAlert.js
Normal file
@@ -0,0 +1,79 @@
|
||||
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 { useDeleteBranch } from 'hooks/query';
|
||||
import { handleDeleteErrors } from '../../Preferences/Branches/utils';
|
||||
|
||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Branch delete alert.
|
||||
*/
|
||||
function BranchDeleteAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { branchId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: deleteBranch, isLoading } = useDeleteBranch();
|
||||
|
||||
// Handle cancel delete alert.
|
||||
const handleCancelDelete = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// Handle confirm delete branch.
|
||||
const handleConfirmDeleteBranch = () => {
|
||||
deleteBranch(branchId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('branch.alert.delete_message'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch(
|
||||
({
|
||||
response: {
|
||||
data: { errors },
|
||||
},
|
||||
}) => {
|
||||
handleDeleteErrors(errors);
|
||||
},
|
||||
)
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'delete'} />}
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelDelete}
|
||||
onConfirm={handleConfirmDeleteBranch}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
<FormattedHTMLMessage id={'branch.once_delete_this_branch'} />
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(BranchDeleteAlert);
|
||||
70
src/containers/Alerts/Branches/BranchMarkPrimaryAlert.js
Normal file
70
src/containers/Alerts/Branches/BranchMarkPrimaryAlert.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import { useMarkBranchAsPrimary } from 'hooks/query';
|
||||
import { AppToaster } from 'components';
|
||||
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* branch mark primary alert.
|
||||
*/
|
||||
function BranchMarkPrimaryAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { branchId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: markPrimaryBranchMutate, isLoading } =
|
||||
useMarkBranchAsPrimary();
|
||||
|
||||
// Handle cancel mark primary alert.
|
||||
const handleCancelMarkPrimaryAlert = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// andle cancel mark primary confirm.
|
||||
const handleConfirmMarkPrimaryBranch = () => {
|
||||
markPrimaryBranchMutate(branchId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('branch.alert.mark_primary_message'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeAlert(name);
|
||||
})
|
||||
.catch((error) => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
// cancelButtonText={<T id={'cancel'} />}
|
||||
// confirmButtonText={<T id={'make_primary'} />}
|
||||
intent={Intent.WARNING}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelMarkPrimaryAlert}
|
||||
onConfirm={handleConfirmMarkPrimaryBranch}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
<T id={'branch.alert.are_you_sure_you_want_to_make'} />
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(BranchMarkPrimaryAlert);
|
||||
80
src/containers/Alerts/Warehouses/WarehouseDeleteAlert.js
Normal file
80
src/containers/Alerts/Warehouses/WarehouseDeleteAlert.js
Normal file
@@ -0,0 +1,80 @@
|
||||
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 { useDeleteWarehouse } from 'hooks/query';
|
||||
import { handleDeleteErrors } from '../../Preferences/Warehouses/utils';
|
||||
|
||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Warehouse delete alert
|
||||
* @returns
|
||||
*/
|
||||
function WarehouseDeleteAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { warehouseId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: deleteWarehouseMutate, isLoading } =
|
||||
useDeleteWarehouse();
|
||||
|
||||
// handle cancel delete warehouse alert.
|
||||
const handleCancelDeleteAlert = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// handleConfirm delete invoice
|
||||
const handleConfirmWarehouseDelete = () => {
|
||||
deleteWarehouseMutate(warehouseId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('warehouse.alert.delete_message'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch(
|
||||
({
|
||||
response: {
|
||||
data: { errors },
|
||||
},
|
||||
}) => {
|
||||
handleDeleteErrors(errors);
|
||||
},
|
||||
)
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'delete'} />}
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelDeleteAlert}
|
||||
onConfirm={handleConfirmWarehouseDelete}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
<FormattedHTMLMessage id={'warehouse.once_delete_this_warehouse'} />
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(WarehouseDeleteAlert);
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
|
||||
import { useTransferredWarehouseTransfer } from 'hooks/query';
|
||||
import { AppToaster } from 'components';
|
||||
|
||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* warehouse transfer transferred alert.
|
||||
* @returns
|
||||
*/
|
||||
function TransferredWarehouseTransferAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { warehouseTransferId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: transferredWarehouseTransferMutate, isLoading } =
|
||||
useTransferredWarehouseTransfer();
|
||||
|
||||
// handle cancel alert.
|
||||
const handleCancelAlert = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// Handle confirm alert.
|
||||
const handleConfirmTransferred = () => {
|
||||
transferredWarehouseTransferMutate(warehouseTransferId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('warehouse_transfer.alert.transferred_warehouse'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch((error) => {})
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'deliver'} />}
|
||||
intent={Intent.WARNING}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelAlert}
|
||||
onConfirm={handleConfirmTransferred}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
<T id={'warehouse_transfer.alert.are_you_sure_you_want_to_deliver'} />
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(TransferredWarehouseTransferAlert);
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import { useMarkWarehouseAsPrimary } from 'hooks/query';
|
||||
import { AppToaster } from 'components';
|
||||
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* warehouse mark primary alert.
|
||||
*/
|
||||
function WarehouseMarkPrimaryAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { warehouseId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
// const { mutateAsync: markPrimaryWarehouseMutate, isLoading } =
|
||||
// useMarkWarehouseAsPrimary();
|
||||
|
||||
// Handle cancel mark primary alert.
|
||||
const handleCancelMarkPrimaryAlert = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// andle cancel mark primary confirm.
|
||||
const handleConfirmMarkPrimaryWarehouse = () => {
|
||||
markPrimaryWarehouseMutate(warehouseId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('warehouse.alert.mark_primary_message'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeAlert(name);
|
||||
})
|
||||
.catch((error) => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'make_primary'} />}
|
||||
intent={Intent.WARNING}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelMarkPrimaryAlert}
|
||||
onConfirm={handleConfirmMarkPrimaryWarehouse}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
<T id={'warehouse.alert.are_you_sure_you_want_to_make'} />
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(WarehouseMarkPrimaryAlert);
|
||||
@@ -0,0 +1,85 @@
|
||||
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 { useDeleteWarehouseTransfer } 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';
|
||||
|
||||
/**
|
||||
* Warehouse transfer delete alert
|
||||
* @returns
|
||||
*/
|
||||
function WarehouseTransferDeleteAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { warehouseTransferId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const { mutateAsync: deleteWarehouseTransferMutate, isLoading } =
|
||||
useDeleteWarehouseTransfer();
|
||||
|
||||
// handle cancel delete warehouse alert.
|
||||
const handleCancelDeleteAlert = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// handleConfirm delete warehouse transfer.
|
||||
const handleConfirmWarehouseTransferDelete = () => {
|
||||
deleteWarehouseTransferMutate(warehouseTransferId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('warehouse_transfer.alert.delete_message'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeDrawer('warehouse-transfer-detail-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={handleConfirmWarehouseTransferDelete}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
<FormattedHTMLMessage
|
||||
id={'warehouse_transfer.once_delete_this_warehouse_transfer'}
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
withDrawerActions,
|
||||
)(WarehouseTransferDeleteAlert);
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
|
||||
import { useInitiateWarehouseTransfer } from 'hooks/query';
|
||||
import { AppToaster } from 'components';
|
||||
|
||||
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* warehouse transfer initiate alert.
|
||||
* @returns
|
||||
*/
|
||||
function WarehouseTransferInitiateAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { warehouseTransferId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: initialWarehouseTransferMutate, isLoading } =
|
||||
useInitiateWarehouseTransfer();
|
||||
|
||||
// handle cancel alert.
|
||||
const handleCancelAlert = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// Handle confirm alert.
|
||||
const handleConfirmInitiated = () => {
|
||||
initialWarehouseTransferMutate(warehouseTransferId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('warehouse_transfer.alert.initiate_warehouse'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch((error) => {})
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'warehouse_transfer.label.initiate'} />}
|
||||
intent={Intent.WARNING}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelAlert}
|
||||
onConfirm={handleConfirmInitiated}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
<T id={'warehouse_transfer.alert.are_you_sure_you_want_to_initate'} />
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(WarehouseTransferInitiateAlert);
|
||||
@@ -19,7 +19,10 @@ import CurrenciesAlerts from '../Preferences/Currencies/CurrenciesAlerts';
|
||||
import RolesAlerts from '../Preferences/Users/Roles/RolesAlerts';
|
||||
import CreditNotesAlerts from '../Sales/CreditNotes/CreditNotesAlerts';
|
||||
import VendorCreditNotesAlerts from '../Purchases/CreditNotes/VendorCreditNotesAlerts';
|
||||
import TransactionsLockingAlerts from '../TransactionsLocking/TransactionsLockingAlerts'
|
||||
import TransactionsLockingAlerts from '../TransactionsLocking/TransactionsLockingAlerts';
|
||||
import WarehousesAlerts from '../Preferences/Warehouses/WarehousesAlerts';
|
||||
import WarehousesTransfersAlerts from '../WarehouseTransfers/WarehousesTransfersAlerts';
|
||||
import BranchesAlerts from '../Preferences/Branches/BranchesAlerts';
|
||||
|
||||
export default [
|
||||
...AccountsAlerts,
|
||||
@@ -43,5 +46,8 @@ export default [
|
||||
...RolesAlerts,
|
||||
...CreditNotesAlerts,
|
||||
...VendorCreditNotesAlerts,
|
||||
...TransactionsLockingAlerts
|
||||
...TransactionsLockingAlerts,
|
||||
...WarehousesAlerts,
|
||||
...WarehousesTransfersAlerts,
|
||||
...BranchesAlerts,
|
||||
];
|
||||
|
||||
@@ -3,32 +3,33 @@ import classNames from 'classnames';
|
||||
import { FormGroup, Position, Classes, ControlGroup } from '@blueprintjs/core';
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import { FastField, ErrorMessage } from 'formik';
|
||||
import { FFormGroup } from '../../../components/Forms';
|
||||
import moment from 'moment';
|
||||
import { Features } from 'common';
|
||||
import {
|
||||
MoneyInputGroup,
|
||||
InputPrependText,
|
||||
CurrencySelectList,
|
||||
BranchSelect,
|
||||
BranchSelectButton,
|
||||
FeatureCan,
|
||||
Row,
|
||||
Col,
|
||||
} from 'components';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||
|
||||
import {
|
||||
momentFormatter,
|
||||
tansformDateValue,
|
||||
inputIntent,
|
||||
} from 'utils';
|
||||
import { useSetPrimaryBranchToForm } from './utils';
|
||||
import { momentFormatter, tansformDateValue, inputIntent } from 'utils';
|
||||
|
||||
/**
|
||||
* Customer financial panel.
|
||||
*/
|
||||
export default function CustomerFinancialPanel() {
|
||||
const {
|
||||
currencies,
|
||||
customerId
|
||||
} = useCustomerFormContext();
|
||||
const { currencies, customerId, branches } = useCustomerFormContext();
|
||||
|
||||
// Sets the primary branch to form.
|
||||
useSetPrimaryBranchToForm();
|
||||
|
||||
return (
|
||||
<div className={'tab-panel--financial'}>
|
||||
@@ -62,12 +63,7 @@ export default function CustomerFinancialPanel() {
|
||||
|
||||
{/*------------ Opening balance -----------*/}
|
||||
<FastField name={'opening_balance'}>
|
||||
{({
|
||||
form,
|
||||
field,
|
||||
field: { value },
|
||||
meta: { error, touched },
|
||||
}) => (
|
||||
{({ form, field, field: { value }, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
label={<T id={'opening_balance'} />}
|
||||
className={classNames(
|
||||
@@ -92,6 +88,23 @@ export default function CustomerFinancialPanel() {
|
||||
)}
|
||||
</FastField>
|
||||
|
||||
{/*------------ Opening branch -----------*/}
|
||||
<FeatureCan feature={Features.Branches}>
|
||||
<FFormGroup
|
||||
label={<T id={'customer.label.opening_branch'} />}
|
||||
name={'opening_balance_branch_id'}
|
||||
inline={true}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
>
|
||||
<BranchSelect
|
||||
name={'opening_balance_branch_id'}
|
||||
branches={branches}
|
||||
input={BranchSelectButton}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
</FeatureCan>
|
||||
|
||||
{/*------------ Currency -----------*/}
|
||||
<FastField name={'currency_code'}>
|
||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||
@@ -110,7 +123,6 @@ export default function CustomerFinancialPanel() {
|
||||
onCurrencySelected={(currency) => {
|
||||
form.setFieldValue('currency_code', currency.currency_code);
|
||||
}}
|
||||
disabled={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
@@ -42,6 +42,7 @@ const Schema = Yup.object().shape({
|
||||
opening_balance: Yup.number().nullable(),
|
||||
currency_code: Yup.string(),
|
||||
opening_balance_at: Yup.date(),
|
||||
opening_balance_branch_id: Yup.string(),
|
||||
});
|
||||
|
||||
export const CreateCustomerForm = Schema;
|
||||
|
||||
@@ -6,15 +6,21 @@ import {
|
||||
useCreateCustomer,
|
||||
useEditCustomer,
|
||||
useContact,
|
||||
useBranches,
|
||||
} from 'hooks/query';
|
||||
import { Features } from 'common';
|
||||
import { useFeatureCan } from 'hooks/state';
|
||||
|
||||
const CustomerFormContext = createContext();
|
||||
|
||||
function CustomerFormProvider({ customerId, ...props }) {
|
||||
function CustomerFormProvider({ query, customerId, ...props }) {
|
||||
const { state } = useLocation();
|
||||
|
||||
const contactId = state?.action;
|
||||
|
||||
// Features guard.
|
||||
const { featureCan } = useFeatureCan();
|
||||
const isBranchFeatureCan = featureCan(Features.Branches);
|
||||
|
||||
// Handle fetch customer details.
|
||||
const { data: customer, isLoading: isCustomerLoading } = useCustomer(
|
||||
customerId,
|
||||
@@ -28,6 +34,13 @@ function CustomerFormProvider({ customerId, ...props }) {
|
||||
// Handle fetch Currencies data table
|
||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||
|
||||
// Fetches the branches list.
|
||||
const {
|
||||
data: branches,
|
||||
isLoading: isBranchesLoading,
|
||||
isSuccess: isBranchesSuccess,
|
||||
} = useBranches(query, { enabled: isBranchFeatureCan });
|
||||
|
||||
// Form submit payload.
|
||||
const [submitPayload, setSubmitPayload] = useState({});
|
||||
|
||||
@@ -38,18 +51,20 @@ function CustomerFormProvider({ customerId, ...props }) {
|
||||
const isNewMode = contactId || !customerId;
|
||||
|
||||
const isFormLoading =
|
||||
isCustomerLoading || isCurrenciesLoading || isContactLoading;
|
||||
isCustomerLoading || isCurrenciesLoading || isBranchesLoading;
|
||||
|
||||
const provider = {
|
||||
customerId,
|
||||
customer,
|
||||
currencies,
|
||||
branches,
|
||||
contactDuplicate,
|
||||
submitPayload,
|
||||
isNewMode,
|
||||
|
||||
isCustomerLoading,
|
||||
isCurrenciesLoading,
|
||||
isBranchesSuccess,
|
||||
isFormLoading,
|
||||
|
||||
setSubmitPayload,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { first } from 'lodash';
|
||||
|
||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||
|
||||
export const defaultInitialValues = {
|
||||
customer_type: 'business',
|
||||
@@ -35,4 +39,20 @@ export const defaultInitialValues = {
|
||||
opening_balance: '',
|
||||
currency_code: '',
|
||||
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
|
||||
opening_balance_branch_id: '',
|
||||
};
|
||||
|
||||
export const useSetPrimaryBranchToForm = () => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const { branches, isBranchesSuccess } = useCustomerFormContext();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isBranchesSuccess) {
|
||||
const primaryBranch = branches.find((b) => b.primary) || first(branches);
|
||||
|
||||
if (primaryBranch) {
|
||||
setFieldValue('opening_balance_branch_id', primaryBranch.id);
|
||||
}
|
||||
}
|
||||
}, [isBranchesSuccess, setFieldValue, branches]);
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ const defaultInitialValues = {
|
||||
name: '',
|
||||
code: '',
|
||||
description: '',
|
||||
currency_code:'',
|
||||
subaccount: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -17,12 +17,14 @@ import {
|
||||
Hint,
|
||||
AccountsSelectList,
|
||||
AccountsTypesSelect,
|
||||
CurrencySelect,
|
||||
} from 'components';
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
|
||||
import { inputIntent } from 'utils';
|
||||
import { compose } from 'redux';
|
||||
import { useAutofocus } from 'hooks';
|
||||
import { FOREIGN_CURRENCY_ACCOUNTS } from '../../../common/accountTypes';
|
||||
import { useAccountDialogContext } from './AccountDialogProvider';
|
||||
|
||||
/**
|
||||
@@ -37,7 +39,7 @@ function AccountFormDialogFields({
|
||||
const accountNameFieldRef = useAutofocus();
|
||||
|
||||
// Account form context.
|
||||
const { accounts, accountsTypes } = useAccountDialogContext();
|
||||
const { accounts, accountsTypes, currencies } = useAccountDialogContext();
|
||||
|
||||
return (
|
||||
<Form>
|
||||
@@ -58,6 +60,7 @@ function AccountFormDialogFields({
|
||||
defaultSelectText={<T id={'select_account_type'} />}
|
||||
onTypeSelected={(accountType) => {
|
||||
form.setFieldValue('account_type', accountType.key);
|
||||
form.setFieldValue('currency_code', '');
|
||||
}}
|
||||
disabled={
|
||||
action === 'edit' ||
|
||||
@@ -143,6 +146,7 @@ function AccountFormDialogFields({
|
||||
)}
|
||||
inline={true}
|
||||
intent={inputIntent({ error, touched })}
|
||||
helperText={<ErrorMessage name="parent_account_id" />}
|
||||
>
|
||||
<AccountsSelectList
|
||||
accounts={accounts}
|
||||
@@ -159,6 +163,24 @@ function AccountFormDialogFields({
|
||||
</FastField>
|
||||
</If>
|
||||
|
||||
<If condition={FOREIGN_CURRENCY_ACCOUNTS.includes(values.account_type)}>
|
||||
{/*------------ Currency -----------*/}
|
||||
<FastField name={'currency_code'}>
|
||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
label={<T id={'currency'} />}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
inline={true}
|
||||
>
|
||||
<CurrencySelect
|
||||
name={'currency_code'}
|
||||
currencies={currencies}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
</If>
|
||||
<FastField name={'description'}>
|
||||
{({ field, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
|
||||
@@ -3,6 +3,7 @@ import { DialogContent } from 'components';
|
||||
import {
|
||||
useCreateAccount,
|
||||
useAccountsTypes,
|
||||
useCurrencies,
|
||||
useAccount,
|
||||
useAccounts,
|
||||
useEditAccount,
|
||||
@@ -30,16 +31,17 @@ function AccountDialogProvider({
|
||||
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
|
||||
|
||||
// Fetches accounts types.
|
||||
const {
|
||||
data: accountsTypes,
|
||||
isLoading: isAccountsTypesLoading,
|
||||
} = useAccountsTypes();
|
||||
const { data: accountsTypes, isLoading: isAccountsTypesLoading } =
|
||||
useAccountsTypes();
|
||||
|
||||
// Fetches the specific account details.
|
||||
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
|
||||
enabled: !!accountId,
|
||||
});
|
||||
|
||||
// Handle fetch Currencies data table
|
||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||
|
||||
const isNewMode = !accountId;
|
||||
|
||||
// Provider payload.
|
||||
@@ -49,6 +51,7 @@ function AccountDialogProvider({
|
||||
parentAccountId,
|
||||
action,
|
||||
accountType,
|
||||
currencies,
|
||||
|
||||
createAccountMutate,
|
||||
editAccountMutate,
|
||||
@@ -57,11 +60,15 @@ function AccountDialogProvider({
|
||||
account,
|
||||
|
||||
isAccountsLoading,
|
||||
isNewMode
|
||||
isCurrenciesLoading,
|
||||
isNewMode,
|
||||
};
|
||||
|
||||
const isLoading =
|
||||
isAccountsLoading || isAccountsTypesLoading || isAccountLoading;
|
||||
isAccountsLoading ||
|
||||
isAccountsTypesLoading ||
|
||||
isAccountLoading ||
|
||||
isCurrenciesLoading;
|
||||
|
||||
return (
|
||||
<DialogContent isLoading={isLoading}>
|
||||
|
||||
@@ -10,6 +10,13 @@ export const transformApiErrors = (errors) => {
|
||||
if (errors.find((e) => e.type === 'ACCOUNT.NAME.NOT.UNIQUE')) {
|
||||
fields.name = intl.get('account_name_is_already_used');
|
||||
}
|
||||
if (
|
||||
errors.find((e) => e.type === 'ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT')
|
||||
) {
|
||||
fields.parent_account_id = intl.get(
|
||||
'accounts.error.account_currency_not_same_parent_account',
|
||||
);
|
||||
}
|
||||
return fields;
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ function BadDebtForm({
|
||||
// Initial form values
|
||||
const initialValues = {
|
||||
...defaultInitialValues,
|
||||
currency_code: base_currency,
|
||||
amount: invoice.due_amount,
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useBadDebtContext } from './BadDebtFormProvider';
|
||||
function BadDebtFormFields() {
|
||||
const amountfieldRef = useAutofocus();
|
||||
|
||||
const { accounts } = useBadDebtContext();
|
||||
const { accounts ,invoice } = useBadDebtContext();
|
||||
|
||||
return (
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
@@ -55,7 +55,7 @@ function BadDebtFormFields() {
|
||||
helperText={<ErrorMessage name="amount" />}
|
||||
>
|
||||
<ControlGroup>
|
||||
<InputPrependText text={values.currency_code} />
|
||||
<InputPrependText text={invoice.currency_code} />
|
||||
|
||||
<MoneyInputGroup
|
||||
value={value}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
import BranchActivateForm from './BranchActivateForm';
|
||||
import { BranchActivateFormProvider } from './BranchActivateFormProvider';
|
||||
|
||||
export default function BranchActivateDialogContent({
|
||||
// #ownProps
|
||||
dialogName,
|
||||
}) {
|
||||
return (
|
||||
<BranchActivateFormProvider dialogName={dialogName}>
|
||||
<BranchActivateForm />
|
||||
</BranchActivateFormProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { Formik } from 'formik';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
|
||||
import { AppToaster } from 'components';
|
||||
import { useBranchActivateContext } from './BranchActivateFormProvider';
|
||||
import BranchActivateFormContent from './BranchActivateFormContent';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Branch activate form.
|
||||
*/
|
||||
function BranchActivateForm({
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}) {
|
||||
const { activateBranches, dialogName } = useBranchActivateContext();
|
||||
|
||||
// Initial form values
|
||||
const initialValues = {};
|
||||
|
||||
// Handles the form submit.
|
||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||
const form = {
|
||||
...values,
|
||||
};
|
||||
setSubmitting(true);
|
||||
// Handle request response success.
|
||||
const onSuccess = (response) => {
|
||||
AppToaster.show({
|
||||
message: intl.get('branch_activate.dialog_success_message'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeDialog(dialogName);
|
||||
};
|
||||
|
||||
// Handle request response errors.
|
||||
const onError = ({
|
||||
response: {
|
||||
data: { errors },
|
||||
},
|
||||
}) => {
|
||||
if (errors) {
|
||||
}
|
||||
setSubmitting(false);
|
||||
};
|
||||
activateBranches(form).then(onSuccess).catch(onError);
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleFormSubmit}
|
||||
component={BranchActivateFormContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDialogActions)(BranchActivateForm);
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Form } from 'formik';
|
||||
import { Classes } from '@blueprintjs/core';
|
||||
import BranchActivateFormFloatingActions from './BranchActivateFormFloatingActions';
|
||||
|
||||
/**
|
||||
* Branch activate form content.
|
||||
*/
|
||||
export default function BranchActivateFormContent() {
|
||||
return (
|
||||
<Form>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<p class="paragraph">
|
||||
{intl.getHTML('branch_activate.dialog_paragraph')}
|
||||
</p>
|
||||
|
||||
<ul class="paragraph list">
|
||||
<li>{intl.get('branch_activate.dialog_paragraph.line_1')}</li>
|
||||
<li>{intl.get('branch_activate.dialog_paragraph.line_2')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<BranchActivateFormFloatingActions />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
|
||||
import { useBranchActivateContext } from './BranchActivateFormProvider';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* branch activate form floating actions.
|
||||
*/
|
||||
function BranchActivateFormFloatingActions({
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}) {
|
||||
// branch activate dialog context.
|
||||
const { dialogName } = useBranchActivateContext();
|
||||
|
||||
// Formik context.
|
||||
const { isSubmitting } = useFormikContext();
|
||||
|
||||
// Handle close button click.
|
||||
const handleCancelBtnClick = () => {
|
||||
closeDialog(dialogName);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button onClick={handleCancelBtnClick} style={{ minWidth: '85px' }}>
|
||||
<T id={'cancel'} />
|
||||
</Button>
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
loading={isSubmitting}
|
||||
style={{ minWidth: '95px' }}
|
||||
type="submit"
|
||||
>
|
||||
{<T id={'branches.activate_button'} />}
|
||||
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDialogActions)(BranchActivateFormFloatingActions);
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DialogContent } from 'components';
|
||||
import { useActivateBranches } from 'hooks/query';
|
||||
|
||||
const BranchActivateContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Branch activate form provider.
|
||||
*/
|
||||
function BranchActivateFormProvider({ dialogName, ...props }) {
|
||||
const { mutateAsync: activateBranches, isLoading } = useActivateBranches();
|
||||
|
||||
// State provider.
|
||||
const provider = {
|
||||
activateBranches,
|
||||
dialogName,
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent isLoading={isLoading}>
|
||||
<BranchActivateContext.Provider value={provider} {...props} />
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
const useBranchActivateContext = () => React.useContext(BranchActivateContext);
|
||||
|
||||
export { BranchActivateFormProvider, useBranchActivateContext };
|
||||
32
src/containers/Dialogs/BranchActivateDialog/index.js
Normal file
32
src/containers/Dialogs/BranchActivateDialog/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from 'components';
|
||||
import { Dialog, DialogSuspense } from 'components';
|
||||
import withDialogRedux from 'components/DialogReduxConnect';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const BranchActivateDialogContent = React.lazy(() =>
|
||||
import('./BranchActivateDialogContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Branch activate dialog.
|
||||
*/
|
||||
function BranchActivateDialog({ dialogName, payload: {}, isOpen }) {
|
||||
return (
|
||||
<Dialog
|
||||
name={dialogName}
|
||||
title={<T id={'branch_activate.dialog.label'} />}
|
||||
isOpen={isOpen}
|
||||
canEscapeJeyClose={true}
|
||||
autoFocus={true}
|
||||
className={'dialog--branch-activate'}
|
||||
>
|
||||
<DialogSuspense>
|
||||
<BranchActivateDialogContent dialogName={dialogName} />
|
||||
</DialogSuspense>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDialogRedux())(BranchActivateDialog);
|
||||
82
src/containers/Dialogs/BranchFormDialog/BranchForm.js
Normal file
82
src/containers/Dialogs/BranchFormDialog/BranchForm.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { Formik } from 'formik';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
|
||||
import { AppToaster } from 'components';
|
||||
import { CreateBranchFormSchema } from './BranchForm.schema';
|
||||
import { transformErrors } from './utils';
|
||||
|
||||
import BranchFormContent from './BranchFormContent';
|
||||
import { useBranchFormContext } from './BranchFormProvider';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import { compose, transformToForm } from 'utils';
|
||||
|
||||
const defaultInitialValues = {
|
||||
name: '',
|
||||
code: '',
|
||||
address: '',
|
||||
phone_number: '',
|
||||
email: '',
|
||||
website: '',
|
||||
city: '',
|
||||
country: '',
|
||||
};
|
||||
|
||||
function BranchForm({
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}) {
|
||||
const { dialogName, branch, branchId, createBranchMutate, editBranchMutate } =
|
||||
useBranchFormContext();
|
||||
|
||||
// Initial form values.
|
||||
const initialValues = {
|
||||
...defaultInitialValues,
|
||||
...transformToForm(branch, defaultInitialValues),
|
||||
};
|
||||
|
||||
// Handles the form submit.
|
||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||
const form = { ...values };
|
||||
|
||||
// Handle request response success.
|
||||
const onSuccess = (response) => {
|
||||
AppToaster.show({
|
||||
message: intl.get('branch.dialog.success_message'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeDialog(dialogName);
|
||||
};
|
||||
|
||||
// Handle request response errors.
|
||||
const onError = ({
|
||||
response: {
|
||||
data: { errors },
|
||||
},
|
||||
}) => {
|
||||
if (errors) {
|
||||
}
|
||||
transformErrors(errors, { setErrors });
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
if (branchId) {
|
||||
editBranchMutate([branchId, form]).then(onSuccess).catch(onError);
|
||||
} else {
|
||||
createBranchMutate(form).then(onSuccess).catch(onError);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
validationSchema={CreateBranchFormSchema}
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleFormSubmit}
|
||||
component={BranchFormContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export default compose(withDialogActions)(BranchForm);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user