Merge branch 'develop' into main

This commit is contained in:
a.bouhuolia
2022-03-24 15:32:30 +02:00
757 changed files with 26203 additions and 1575 deletions

View File

@@ -221,3 +221,5 @@ export const ACCOUNT_TYPES = [
incomeSheet: true,
},
];
export const FOREIGN_CURRENCY_ACCOUNTS = ['cash', 'bank'];

7
src/common/cellTypes.js Normal file
View File

@@ -0,0 +1,7 @@
export const CellType = {
Text: 'text',
Field: 'field',
Button: 'button'
}

View File

@@ -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',

View File

@@ -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
View File

@@ -0,0 +1,7 @@
export const Features = {
Warehouses: 'warehouses',
Branches: 'branches',
ManualJournal: 'manualJournal',
}

View File

@@ -1,3 +1,5 @@
export * from './TableStyle';
export * from './features';
export * from './cellTypes';
export const Align = { Left: 'left', Right: 'right', Center: 'center' };

View File

@@ -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 = {

View 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);

View 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}
/>
);
}

View 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}
/>
);
}

View 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} />;
}

View File

@@ -0,0 +1,2 @@
export * from './BranchSelect';
export * from './BranchMultiSelect'

View File

@@ -0,0 +1,10 @@
import React from 'react';
import { CurrencyTag } from 'components';
/**
* base currecncy.
* @returns
*/
export function BaseCurrency({ currency }) {
return <CurrencyTag>{currency}</CurrencyTag>;
}

View 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')} />;
}

View File

@@ -0,0 +1,2 @@
export * from './CurrencySelect';
export * from './BaseCurrency';

View 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>
);
}

View File

@@ -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)(

View File

@@ -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;

View 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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,
};

View File

@@ -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;
}
}
}
}
}
`;

View File

@@ -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,
})}

View File

@@ -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;

View 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,
);

View File

@@ -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>
);
}

View File

@@ -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;
`;

View File

@@ -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>
);
}

View 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;
`;

View 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;
}
`;

View File

@@ -0,0 +1,2 @@
export * from './ExchangeRateInput';
export * from './ExchangeRateMutedField'

View 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);

View File

@@ -0,0 +1 @@
export * from './FeatureCan';

View 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);
};

View 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;
`;

View 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,
};

View 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} />;
}

View File

@@ -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]);

View File

@@ -1,2 +1,4 @@
export * from './FormObserver';
export * from './FormikObserver';
export * from './FormikObserver';
export * from './FMoneyInputGroup'
export * from './BlueprintFormik';

View 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;
`;

View File

@@ -0,0 +1 @@
export * from './Paper';

View File

@@ -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>

View File

@@ -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;
}
`;

View 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} />;
};

View File

@@ -1,3 +1,4 @@
export * from './CurrencyTag';
export * from './CurrencyTag';
export * from './FlagIcon'

View File

@@ -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 &&
`

View File

@@ -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);

View 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}
/>
);
}

View 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} />;
}

View File

@@ -0,0 +1,2 @@
export * from './WarehouseSelect';
export * from './WarehouseMultiSelect';

View File

@@ -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,
};

View File

@@ -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,

View File

@@ -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,

View File

@@ -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(),

View File

@@ -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}
>
{({

View File

@@ -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,
})),

View File

@@ -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>
);
}

View File

@@ -11,7 +11,7 @@ import { MakeJournalProvider } from './MakeJournalProvider';
*/
export default function MakeJournalEntriesPage() {
const { id: journalId } = useParams();
return (
<MakeJournalProvider journalId={journalId}>
<MakeJournalEntriesForm />

View File

@@ -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,
}}

View File

@@ -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;
`;

View File

@@ -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;
}
}
`;

View File

@@ -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;
`;

View File

@@ -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} />}
/>
);
}

View File

@@ -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'}

View File

@@ -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}
/>
);
}

View File

@@ -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;
};

View 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);

View 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);

View 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);

View File

@@ -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);

View 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 { 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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,
];

View File

@@ -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>
)}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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]);
};

View File

@@ -25,6 +25,7 @@ const defaultInitialValues = {
name: '',
code: '',
description: '',
currency_code:'',
subaccount: false,
};

View File

@@ -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

View File

@@ -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}>

View File

@@ -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;
};

View File

@@ -37,7 +37,6 @@ function BadDebtForm({
// Initial form values
const initialValues = {
...defaultInitialValues,
currency_code: base_currency,
amount: invoice.due_amount,
};

View File

@@ -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}

View File

@@ -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>
);
}

View File

@@ -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);

View File

@@ -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>
);
}

View File

@@ -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);

View File

@@ -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 };

View 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);

View 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