mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
fix: merge conflict quick create list field.
This commit is contained in:
@@ -10,4 +10,8 @@ export const DRAWERS = {
|
|||||||
BILL_DRAWER: 'bill-drawer',
|
BILL_DRAWER: 'bill-drawer',
|
||||||
INVENTORY_ADJUSTMENT_DRAWER: 'inventory-adjustment-drawer',
|
INVENTORY_ADJUSTMENT_DRAWER: 'inventory-adjustment-drawer',
|
||||||
CASHFLOW_TRNASACTION_DRAWER: 'cashflow-transaction-drawer',
|
CASHFLOW_TRNASACTION_DRAWER: 'cashflow-transaction-drawer',
|
||||||
|
|
||||||
|
QUICK_WRITE_VENDOR: 'quick-write-vendor',
|
||||||
|
QUICK_CREATE_CUSTOMER: 'quick-create-customer',
|
||||||
|
QUICK_CREATE_ITEM: 'quick-create-item',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,55 @@
|
|||||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||||
import { MenuItem, Button } from '@blueprintjs/core';
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
import { Select } from '@blueprintjs/select';
|
import { Select } from '@blueprintjs/select';
|
||||||
import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
import * as R from 'ramda';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
||||||
import { filterAccountsByQuery } from './utils';
|
import { filterAccountsByQuery } from './utils';
|
||||||
import { nestedArrayToflatten } from 'utils';
|
import { nestedArrayToflatten } from 'utils';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
export default function AccountsSelectList({
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
// Create new account renderer.
|
||||||
|
const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={`Create "${query}"`}
|
||||||
|
active={active}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new item from the given query string.
|
||||||
|
const createNewItemFromQuery = (name) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filters accounts items.
|
||||||
|
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
|
||||||
|
const normalizedTitle = account.name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts select list.
|
||||||
|
*/
|
||||||
|
function AccountsSelectList({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
accounts,
|
accounts,
|
||||||
initialAccountId,
|
initialAccountId,
|
||||||
selectedAccountId,
|
selectedAccountId,
|
||||||
@@ -21,6 +63,8 @@ export default function AccountsSelectList({
|
|||||||
filterByNormal,
|
filterByNormal,
|
||||||
filterByRootTypes,
|
filterByRootTypes,
|
||||||
|
|
||||||
|
allowCreate,
|
||||||
|
|
||||||
buttonProps = {},
|
buttonProps = {},
|
||||||
}) {
|
}) {
|
||||||
const flattenAccounts = useMemo(
|
const flattenAccounts = useMemo(
|
||||||
@@ -51,6 +95,7 @@ export default function AccountsSelectList({
|
|||||||
[initialAccountId, filteredAccounts],
|
[initialAccountId, filteredAccounts],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Select account item.
|
||||||
const [selectedAccount, setSelectedAccount] = useState(
|
const [selectedAccount, setSelectedAccount] = useState(
|
||||||
initialAccount || null,
|
initialAccount || null,
|
||||||
);
|
);
|
||||||
@@ -76,31 +121,25 @@ export default function AccountsSelectList({
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onAccountSelect = useCallback(
|
// Handle the account item select.
|
||||||
|
const handleAccountSelect = useCallback(
|
||||||
(account) => {
|
(account) => {
|
||||||
setSelectedAccount({ ...account });
|
if (account.id) {
|
||||||
onAccountSelected && onAccountSelected(account);
|
setSelectedAccount({ ...account });
|
||||||
},
|
onAccountSelected && onAccountSelected(account);
|
||||||
[setSelectedAccount, onAccountSelected],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Filters accounts items.
|
|
||||||
const filterAccountsPredicater = useCallback(
|
|
||||||
(query, account, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = account.name.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
openDialog('account-form');
|
||||||
`${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[],
|
[setSelectedAccount, onAccountSelected, openDialog],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Maybe inject new item props to select component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
items={filteredAccounts}
|
items={filteredAccounts}
|
||||||
@@ -113,11 +152,13 @@ export default function AccountsSelectList({
|
|||||||
inline: popoverFill,
|
inline: popoverFill,
|
||||||
}}
|
}}
|
||||||
filterable={true}
|
filterable={true}
|
||||||
onItemSelect={onAccountSelect}
|
onItemSelect={handleAccountSelect}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={classNames('form-group--select-list', {
|
className={classNames('form-group--select-list', {
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
})}
|
})}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -127,3 +168,5 @@ export default function AccountsSelectList({
|
|||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDialogActions)(AccountsSelectList);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
|||||||
import { MenuItem } from '@blueprintjs/core';
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
import { Suggest } from '@blueprintjs/select';
|
import { Suggest } from '@blueprintjs/select';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
@@ -10,10 +11,55 @@ import { MenuItemNestedText, FormattedMessage as T } from 'components';
|
|||||||
import { filterAccountsByQuery } from './utils';
|
import { filterAccountsByQuery } from './utils';
|
||||||
import { nestedArrayToflatten } from 'utils';
|
import { nestedArrayToflatten } from 'utils';
|
||||||
|
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
// Create new account renderer.
|
||||||
|
const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={`Create "${query}"`}
|
||||||
|
active={active}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create new item from the given query string.
|
||||||
|
const createNewItemFromQuery = (name) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle input value renderer.
|
||||||
|
const handleInputValueRenderer = (inputValue) => {
|
||||||
|
if (inputValue) {
|
||||||
|
return inputValue.name.toString();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filters accounts items.
|
||||||
|
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
|
||||||
|
const normalizedTitle = account.name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts suggest field.
|
* Accounts suggest field.
|
||||||
*/
|
*/
|
||||||
export default function AccountsSuggestField({
|
function AccountsSuggestField({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
accounts,
|
accounts,
|
||||||
initialAccountId,
|
initialAccountId,
|
||||||
selectedAccountId,
|
selectedAccountId,
|
||||||
@@ -26,6 +72,8 @@ export default function AccountsSuggestField({
|
|||||||
filterByNormal,
|
filterByNormal,
|
||||||
filterByRootTypes = [],
|
filterByRootTypes = [],
|
||||||
|
|
||||||
|
allowCreate,
|
||||||
|
|
||||||
...suggestProps
|
...suggestProps
|
||||||
}) {
|
}) {
|
||||||
const flattenAccounts = useMemo(
|
const flattenAccounts = useMemo(
|
||||||
@@ -69,23 +117,6 @@ export default function AccountsSuggestField({
|
|||||||
}
|
}
|
||||||
}, [selectedAccountId, filteredAccounts, setSelectedAccount]);
|
}, [selectedAccountId, filteredAccounts, setSelectedAccount]);
|
||||||
|
|
||||||
// Filters accounts items.
|
|
||||||
const filterAccountsPredicater = useCallback(
|
|
||||||
(query, account, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = account.name.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
`${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Account item of select accounts field.
|
// Account item of select accounts field.
|
||||||
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
|
||||||
return (
|
return (
|
||||||
@@ -98,28 +129,31 @@ export default function AccountsSuggestField({
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleInputValueRenderer = (inputValue) => {
|
const handleAccountSelect = useCallback(
|
||||||
if (inputValue) {
|
|
||||||
return inputValue.name.toString();
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAccountSelect = useCallback(
|
|
||||||
(account) => {
|
(account) => {
|
||||||
setSelectedAccount({ ...account });
|
if (account.id) {
|
||||||
onAccountSelected && onAccountSelected(account);
|
setSelectedAccount({ ...account });
|
||||||
|
onAccountSelected && onAccountSelected(account);
|
||||||
|
} else {
|
||||||
|
openDialog('account-form');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setSelectedAccount, onAccountSelected],
|
[setSelectedAccount, onAccountSelected, openDialog],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Maybe inject new item props to select component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suggest
|
<Suggest
|
||||||
items={filteredAccounts}
|
items={filteredAccounts}
|
||||||
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
|
||||||
itemRenderer={accountItem}
|
itemRenderer={accountItem}
|
||||||
itemPredicate={filterAccountsPredicater}
|
itemPredicate={filterAccountsPredicater}
|
||||||
onItemSelect={onAccountSelect}
|
onItemSelect={handleAccountSelect}
|
||||||
selectedItem={selectedAccount}
|
selectedItem={selectedAccount}
|
||||||
inputProps={{ placeholder: defaultSelectText }}
|
inputProps={{ placeholder: defaultSelectText }}
|
||||||
resetOnClose={true}
|
resetOnClose={true}
|
||||||
@@ -129,7 +163,11 @@ export default function AccountsSuggestField({
|
|||||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
})}
|
})}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
{...suggestProps}
|
{...suggestProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDialogActions)(AccountsSuggestField);
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ export default function ContactSelecetList({
|
|||||||
contactsList,
|
contactsList,
|
||||||
initialContactId,
|
initialContactId,
|
||||||
selectedContactId,
|
selectedContactId,
|
||||||
selectedContactType,
|
createNewItemFrom,
|
||||||
defaultSelectText = <T id={'select_contact'} />,
|
defaultSelectText = <T id={'select_contact'} />,
|
||||||
onContactSelected,
|
onContactSelected,
|
||||||
popoverFill = false,
|
popoverFill = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
buttonProps,
|
buttonProps,
|
||||||
|
|
||||||
|
...restProps
|
||||||
}) {
|
}) {
|
||||||
const contacts = useMemo(
|
const contacts = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -65,7 +67,7 @@ export default function ContactSelecetList({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Filter Contact List
|
// Filter Contact List
|
||||||
const filterContacts = (query, contact, index, exactMatch) => {
|
const itemPredicate = (query, contact, index, exactMatch) => {
|
||||||
const normalizedTitle = contact.display_name.toLowerCase();
|
const normalizedTitle = contact.display_name.toLowerCase();
|
||||||
const normalizedQuery = query.toLowerCase();
|
const normalizedQuery = query.toLowerCase();
|
||||||
if (exactMatch) {
|
if (exactMatch) {
|
||||||
@@ -83,7 +85,7 @@ export default function ContactSelecetList({
|
|||||||
items={contacts}
|
items={contacts}
|
||||||
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||||
itemRenderer={handleContactRenderer}
|
itemRenderer={handleContactRenderer}
|
||||||
itemPredicate={filterContacts}
|
itemPredicate={itemPredicate}
|
||||||
filterable={true}
|
filterable={true}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onItemSelect={onContactSelect}
|
onItemSelect={onContactSelect}
|
||||||
@@ -92,8 +94,9 @@ export default function ContactSelecetList({
|
|||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
})}
|
})}
|
||||||
inputProps={{
|
inputProps={{
|
||||||
placeholder: intl.get('filter_')
|
placeholder: intl.get('filter_'),
|
||||||
}}
|
}}
|
||||||
|
{...restProps}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
86
src/components/Contacts/ContactSelectField.js
Normal file
86
src/components/Contacts/ContactSelectField.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
|
import { Select } from '@blueprintjs/select';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
|
import { itemPredicate, handleContactRenderer } from './utils';
|
||||||
|
|
||||||
|
export default function ContactSelectField({
|
||||||
|
contacts,
|
||||||
|
initialContactId,
|
||||||
|
selectedContactId,
|
||||||
|
defaultSelectText = <T id={'select_contact'} />,
|
||||||
|
onContactSelected,
|
||||||
|
popoverFill = false,
|
||||||
|
disabled = false,
|
||||||
|
buttonProps,
|
||||||
|
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
const localContacts = useMemo(
|
||||||
|
() =>
|
||||||
|
contacts.map((contact) => ({
|
||||||
|
...contact,
|
||||||
|
_id: `${contact.id}_${contact.contact_type}`,
|
||||||
|
})),
|
||||||
|
[contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialContact = useMemo(
|
||||||
|
() => contacts.find((a) => a.id === initialContactId),
|
||||||
|
[initialContactId, contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selecetedContact, setSelectedContact] = useState(
|
||||||
|
initialContact || null,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof selectedContactId !== 'undefined') {
|
||||||
|
const account = selectedContactId
|
||||||
|
? contacts.find((a) => a.id === selectedContactId)
|
||||||
|
: null;
|
||||||
|
setSelectedContact(account);
|
||||||
|
}
|
||||||
|
}, [selectedContactId, contacts, setSelectedContact]);
|
||||||
|
|
||||||
|
const handleContactSelect = useCallback(
|
||||||
|
(contact) => {
|
||||||
|
setSelectedContact({ ...contact });
|
||||||
|
onContactSelected && onContactSelected(contact);
|
||||||
|
},
|
||||||
|
[setSelectedContact, onContactSelected],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
items={localContacts}
|
||||||
|
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||||
|
itemRenderer={handleContactRenderer}
|
||||||
|
itemPredicate={itemPredicate}
|
||||||
|
filterable={true}
|
||||||
|
disabled={disabled}
|
||||||
|
onItemSelect={handleContactSelect}
|
||||||
|
popoverProps={{ minimal: true, usePortal: !popoverFill }}
|
||||||
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
})}
|
||||||
|
inputProps={{
|
||||||
|
placeholder: intl.get('filter_'),
|
||||||
|
}}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
text={
|
||||||
|
selecetedContact ? selecetedContact.display_name : defaultSelectText
|
||||||
|
}
|
||||||
|
{...buttonProps}
|
||||||
|
/>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
116
src/components/Contacts/CustomerSelectField.js
Normal file
116
src/components/Contacts/CustomerSelectField.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
|
import { Select } from '@blueprintjs/select';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
|
import {
|
||||||
|
itemPredicate,
|
||||||
|
handleContactRenderer,
|
||||||
|
createNewItemRenderer,
|
||||||
|
createNewItemFromQuery,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { DRAWERS } from 'common/drawers';
|
||||||
|
|
||||||
|
function CustomerSelectField({
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
contacts,
|
||||||
|
initialContactId,
|
||||||
|
selectedContactId,
|
||||||
|
defaultSelectText = <T id={'select_contact'} />,
|
||||||
|
onContactSelected,
|
||||||
|
popoverFill = false,
|
||||||
|
disabled = false,
|
||||||
|
allowCreate,
|
||||||
|
buttonProps,
|
||||||
|
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
const localContacts = useMemo(
|
||||||
|
() =>
|
||||||
|
contacts.map((contact) => ({
|
||||||
|
...contact,
|
||||||
|
_id: `${contact.id}_${contact.contact_type}`,
|
||||||
|
})),
|
||||||
|
[contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialContact = useMemo(
|
||||||
|
() => contacts.find((a) => a.id === initialContactId),
|
||||||
|
[initialContactId, contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selecetedContact, setSelectedContact] = useState(
|
||||||
|
initialContact || null,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof selectedContactId !== 'undefined') {
|
||||||
|
const account = selectedContactId
|
||||||
|
? contacts.find((a) => a.id === selectedContactId)
|
||||||
|
: null;
|
||||||
|
setSelectedContact(account);
|
||||||
|
}
|
||||||
|
}, [selectedContactId, contacts, setSelectedContact]);
|
||||||
|
|
||||||
|
const handleContactSelect = useCallback(
|
||||||
|
(contact) => {
|
||||||
|
if (contact.id) {
|
||||||
|
setSelectedContact({ ...contact });
|
||||||
|
onContactSelected && onContactSelected(contact);
|
||||||
|
} else {
|
||||||
|
openDrawer(DRAWERS.QUICK_CREATE_CUSTOMER);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setSelectedContact, onContactSelected, openDrawer],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Maybe inject create new item props to suggest component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
items={localContacts}
|
||||||
|
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||||
|
itemRenderer={handleContactRenderer}
|
||||||
|
itemPredicate={itemPredicate}
|
||||||
|
filterable={true}
|
||||||
|
disabled={disabled}
|
||||||
|
onItemSelect={handleContactSelect}
|
||||||
|
popoverProps={{ minimal: true, usePortal: !popoverFill }}
|
||||||
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
})}
|
||||||
|
inputProps={{
|
||||||
|
placeholder: intl.get('filter_'),
|
||||||
|
}}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
|
createNewItemPosition={'top'}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
text={
|
||||||
|
selecetedContact ? selecetedContact.display_name : defaultSelectText
|
||||||
|
}
|
||||||
|
{...buttonProps}
|
||||||
|
/>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDrawerActions)(CustomerSelectField);
|
||||||
115
src/components/Contacts/VendorSelectField.js
Normal file
115
src/components/Contacts/VendorSelectField.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
|
import { Select } from '@blueprintjs/select';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
|
import {
|
||||||
|
itemPredicate,
|
||||||
|
handleContactRenderer,
|
||||||
|
createNewItemFromQuery,
|
||||||
|
createNewItemRenderer,
|
||||||
|
} from './utils';
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { DRAWERS } from 'common/drawers';
|
||||||
|
|
||||||
|
function VendorSelectField({
|
||||||
|
// #withDrawerActions
|
||||||
|
openDrawer,
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
contacts,
|
||||||
|
initialContactId,
|
||||||
|
selectedContactId,
|
||||||
|
defaultSelectText = <T id={'select_contact'} />,
|
||||||
|
onContactSelected,
|
||||||
|
popoverFill = false,
|
||||||
|
disabled = false,
|
||||||
|
allowCreate,
|
||||||
|
buttonProps,
|
||||||
|
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
const localContacts = useMemo(
|
||||||
|
() =>
|
||||||
|
contacts.map((contact) => ({
|
||||||
|
...contact,
|
||||||
|
_id: `${contact.id}_${contact.contact_type}`,
|
||||||
|
})),
|
||||||
|
[contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialContact = useMemo(
|
||||||
|
() => contacts.find((a) => a.id === initialContactId),
|
||||||
|
[initialContactId, contacts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selecetedContact, setSelectedContact] = useState(
|
||||||
|
initialContact || null,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof selectedContactId !== 'undefined') {
|
||||||
|
const account = selectedContactId
|
||||||
|
? contacts.find((a) => a.id === selectedContactId)
|
||||||
|
: null;
|
||||||
|
setSelectedContact(account);
|
||||||
|
}
|
||||||
|
}, [selectedContactId, contacts, setSelectedContact]);
|
||||||
|
|
||||||
|
const handleContactSelect = useCallback(
|
||||||
|
(contact) => {
|
||||||
|
if (contact.id) {
|
||||||
|
setSelectedContact({ ...contact });
|
||||||
|
onContactSelected && onContactSelected(contact);
|
||||||
|
} else {
|
||||||
|
openDrawer(DRAWERS.QUICK_WRITE_VENDOR);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setSelectedContact, onContactSelected, openDrawer],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Maybe inject create new item props to suggest component.
|
||||||
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
|
? createNewItemFromQuery
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
items={localContacts}
|
||||||
|
noResults={<MenuItem disabled={true} text={<T id={'no_results'} />} />}
|
||||||
|
itemRenderer={handleContactRenderer}
|
||||||
|
itemPredicate={itemPredicate}
|
||||||
|
filterable={true}
|
||||||
|
disabled={disabled}
|
||||||
|
onItemSelect={handleContactSelect}
|
||||||
|
popoverProps={{ minimal: true, usePortal: !popoverFill }}
|
||||||
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
|
})}
|
||||||
|
inputProps={{
|
||||||
|
placeholder: intl.get('filter_'),
|
||||||
|
}}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
|
createNewItemPosition={'top'}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={disabled}
|
||||||
|
text={
|
||||||
|
selecetedContact ? selecetedContact.display_name : defaultSelectText
|
||||||
|
}
|
||||||
|
{...buttonProps}
|
||||||
|
/>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDrawerActions)(VendorSelectField);
|
||||||
5
src/components/Contacts/index.js
Normal file
5
src/components/Contacts/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import ContactSelectField from './ContactSelectField';
|
||||||
|
import CustomerSelectField from './CustomerSelectField';
|
||||||
|
import VendorSelectField from './VendorSelectField';
|
||||||
|
|
||||||
|
export { ContactSelectField, CustomerSelectField, VendorSelectField };
|
||||||
43
src/components/Contacts/utils.js
Normal file
43
src/components/Contacts/utils.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
// Filter Contact List
|
||||||
|
export const itemPredicate = (query, contact, index, exactMatch) => {
|
||||||
|
const normalizedTitle = contact.display_name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
`${contact.display_name} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleContactRenderer = (contact, { handleClick }) => (
|
||||||
|
<MenuItem
|
||||||
|
key={contact.id}
|
||||||
|
text={contact.display_name}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Creates a new item from query.
|
||||||
|
export const createNewItemFromQuery = (name) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle quick create new customer.
|
||||||
|
export const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={`Create "${query}"`}
|
||||||
|
active={active}
|
||||||
|
shouldDismissPopover={false}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,7 +2,6 @@ import React from 'react';
|
|||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import DashboardTopbar from 'components/Dashboard/DashboardTopbar';
|
import DashboardTopbar from 'components/Dashboard/DashboardTopbar';
|
||||||
import DashboardContentRoutes from 'components/Dashboard/DashboardContentRoute';
|
import DashboardContentRoutes from 'components/Dashboard/DashboardContentRoute';
|
||||||
import DashboardFooter from 'components/Dashboard/DashboardFooter';
|
|
||||||
import DashboardErrorBoundary from './DashboardErrorBoundary';
|
import DashboardErrorBoundary from './DashboardErrorBoundary';
|
||||||
|
|
||||||
export default React.forwardRef(({}, ref) => {
|
export default React.forwardRef(({}, ref) => {
|
||||||
@@ -11,7 +10,6 @@ export default React.forwardRef(({}, ref) => {
|
|||||||
<div className="dashboard-content" id="dashboard" ref={ref}>
|
<div className="dashboard-content" id="dashboard" ref={ref}>
|
||||||
<DashboardTopbar />
|
<DashboardTopbar />
|
||||||
<DashboardContentRoutes />
|
<DashboardContentRoutes />
|
||||||
<DashboardFooter />
|
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export default function AccountCellRenderer({
|
|||||||
accountsDataProp,
|
accountsDataProp,
|
||||||
filterAccountsByRootTypes,
|
filterAccountsByRootTypes,
|
||||||
filterAccountsByTypes,
|
filterAccountsByTypes,
|
||||||
|
fieldProps,
|
||||||
|
formGroupProps,
|
||||||
},
|
},
|
||||||
row: { index, original },
|
row: { index, original },
|
||||||
cell: { value: initialValue },
|
cell: { value: initialValue },
|
||||||
@@ -53,6 +55,7 @@ export default function AccountCellRenderer({
|
|||||||
'form-group--account',
|
'form-group--account',
|
||||||
Classes.FILL,
|
Classes.FILL,
|
||||||
)}
|
)}
|
||||||
|
{...formGroupProps}
|
||||||
>
|
>
|
||||||
<AccountsSuggestField
|
<AccountsSuggestField
|
||||||
accounts={accounts}
|
accounts={accounts}
|
||||||
@@ -66,6 +69,7 @@ export default function AccountCellRenderer({
|
|||||||
}}
|
}}
|
||||||
openOnKeyDown={true}
|
openOnKeyDown={true}
|
||||||
blurOnSelectClose={false}
|
blurOnSelectClose={false}
|
||||||
|
{...fieldProps}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import React, { useCallback, useRef } from 'react';
|
import React, { useCallback, useRef } from 'react';
|
||||||
// import ItemsListField from 'components/ItemsListField';
|
|
||||||
import ItemsSuggestField from 'components/ItemsSuggestField';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
import { FormGroup, Classes, Intent } from '@blueprintjs/core';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import ItemsSuggestField from 'components/ItemsSuggestField';
|
||||||
|
|
||||||
import { useCellAutoFocus } from 'hooks';
|
import { useCellAutoFocus } from 'hooks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Items list cell.
|
||||||
|
*/
|
||||||
export default function ItemsListCell({
|
export default function ItemsListCell({
|
||||||
column: { id, filterSellable, filterPurchasable },
|
column: { id, filterSellable, filterPurchasable, fieldProps, formGroupProps },
|
||||||
row: { index },
|
row: { index },
|
||||||
cell: { value: initialValue },
|
cell: { value: initialValue },
|
||||||
payload: { items, updateData, errors, autoFocus },
|
payload: { items, updateData, errors, autoFocus },
|
||||||
@@ -19,6 +21,7 @@ export default function ItemsListCell({
|
|||||||
// Auto-focus the items list input field.
|
// Auto-focus the items list input field.
|
||||||
useCellAutoFocus(fieldRef, autoFocus, id, index);
|
useCellAutoFocus(fieldRef, autoFocus, id, index);
|
||||||
|
|
||||||
|
// Handle the item selected.
|
||||||
const handleItemSelected = useCallback(
|
const handleItemSelected = useCallback(
|
||||||
(item) => {
|
(item) => {
|
||||||
updateData(index, id, item.id);
|
updateData(index, id, item.id);
|
||||||
@@ -32,6 +35,7 @@ export default function ItemsListCell({
|
|||||||
<FormGroup
|
<FormGroup
|
||||||
intent={error ? Intent.DANGER : null}
|
intent={error ? Intent.DANGER : null}
|
||||||
className={classNames('form-group--select-list', Classes.FILL)}
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
|
{...formGroupProps}
|
||||||
>
|
>
|
||||||
<ItemsSuggestField
|
<ItemsSuggestField
|
||||||
items={items}
|
items={items}
|
||||||
@@ -45,6 +49,7 @@ export default function ItemsListCell({
|
|||||||
}}
|
}}
|
||||||
openOnKeyDown={true}
|
openOnKeyDown={true}
|
||||||
blurOnSelectClose={false}
|
blurOnSelectClose={false}
|
||||||
|
{...fieldProps}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Position, Drawer } from '@blueprintjs/core';
|
|||||||
|
|
||||||
import 'style/components/Drawer.scss';
|
import 'style/components/Drawer.scss';
|
||||||
|
|
||||||
|
import { DrawerProvider } from './DrawerProvider';
|
||||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ function DrawerComponent(props) {
|
|||||||
portalClassName={'drawer-portal'}
|
portalClassName={'drawer-portal'}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
<DrawerProvider {...props}>{children}</DrawerProvider>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/components/Drawer/DrawerProvider.js
Normal file
16
src/components/Drawer/DrawerProvider.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React, { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
const DrawerContext = createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account form provider.
|
||||||
|
*/
|
||||||
|
function DrawerProvider({ ...props }) {
|
||||||
|
const provider = { ...props };
|
||||||
|
|
||||||
|
return <DrawerContext.Provider value={provider} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useDrawerContext = () => useContext(DrawerContext);
|
||||||
|
|
||||||
|
export { DrawerProvider, useDrawerContext };
|
||||||
@@ -14,6 +14,9 @@ import CustomerDetailsDrawer from '../containers/Drawers/CustomerDetailsDrawer';
|
|||||||
import VendorDetailsDrawer from '../containers/Drawers/VendorDetailsDrawer';
|
import VendorDetailsDrawer from '../containers/Drawers/VendorDetailsDrawer';
|
||||||
import InventoryAdjustmentDetailDrawer from '../containers/Drawers/InventoryAdjustmentDetailDrawer';
|
import InventoryAdjustmentDetailDrawer from '../containers/Drawers/InventoryAdjustmentDetailDrawer';
|
||||||
import CashflowTransactionDetailDrawer from '../containers/Drawers/CashflowTransactionDetailDrawer';
|
import CashflowTransactionDetailDrawer from '../containers/Drawers/CashflowTransactionDetailDrawer';
|
||||||
|
import QuickCreateCustomerDrawer from '../containers/Drawers/QuickCreateCustomerDrawer';
|
||||||
|
import QuickCreateItemDrawer from '../containers/Drawers/QuickCreateItemDrawer';
|
||||||
|
import QuickWriteVendorDrawer from '../containers/Drawers/QuickWriteVendorDrawer';
|
||||||
|
|
||||||
import { DRAWERS } from 'common/drawers';
|
import { DRAWERS } from 'common/drawers';
|
||||||
|
|
||||||
@@ -38,7 +41,12 @@ export default function DrawersContainer() {
|
|||||||
<InventoryAdjustmentDetailDrawer
|
<InventoryAdjustmentDetailDrawer
|
||||||
name={DRAWERS.INVENTORY_ADJUSTMENT_DRAWER}
|
name={DRAWERS.INVENTORY_ADJUSTMENT_DRAWER}
|
||||||
/>
|
/>
|
||||||
<CashflowTransactionDetailDrawer name={DRAWERS.CASHFLOW_TRNASACTION_DRAWER} />
|
<CashflowTransactionDetailDrawer
|
||||||
|
name={DRAWERS.CASHFLOW_TRNASACTION_DRAWER}
|
||||||
|
/>
|
||||||
|
<QuickCreateCustomerDrawer name={DRAWERS.QUICK_CREATE_CUSTOMER} />
|
||||||
|
<QuickCreateItemDrawer name={DRAWERS.QUICK_CREATE_ITEM} />
|
||||||
|
<QuickWriteVendorDrawer name={DRAWERS.QUICK_WRITE_VENDOR} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,68 @@
|
|||||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { MenuItem } from '@blueprintjs/core';
|
import { MenuItem } from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { CLASSES } from 'common/classes';
|
|
||||||
|
|
||||||
import { Suggest } from '@blueprintjs/select';
|
import { Suggest } from '@blueprintjs/select';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
export default function ItemsSuggestField({
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { DRAWERS } from 'common/drawers';
|
||||||
|
|
||||||
|
// Creates a new item from query.
|
||||||
|
const createNewItemFromQuery = (name) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle quick create new customer.
|
||||||
|
const createNewItemRenderer = (query, active, handleClick) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
icon="add"
|
||||||
|
text={`Create "${query}"`}
|
||||||
|
active={active}
|
||||||
|
shouldDismissPopover={false}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Item renderer.
|
||||||
|
const itemRenderer = (item, { modifiers, handleClick }) => (
|
||||||
|
<MenuItem
|
||||||
|
key={item.id}
|
||||||
|
text={item.name}
|
||||||
|
label={item.code}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Filters items.
|
||||||
|
const filterItemsPredicater = (query, item, _index, exactMatch) => {
|
||||||
|
const normalizedTitle = item.name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return `${normalizedTitle} ${item.code}`.indexOf(normalizedQuery) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle input value renderer.
|
||||||
|
const handleInputValueRenderer = (inputValue) => {
|
||||||
|
if (inputValue) {
|
||||||
|
return inputValue.name.toString();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
function ItemsSuggestField({
|
||||||
items,
|
items,
|
||||||
initialItemId,
|
initialItemId,
|
||||||
selectedItemId,
|
selectedItemId,
|
||||||
@@ -18,6 +73,10 @@ export default function ItemsSuggestField({
|
|||||||
sellable = false,
|
sellable = false,
|
||||||
purchasable = false,
|
purchasable = false,
|
||||||
popoverFill = false,
|
popoverFill = false,
|
||||||
|
|
||||||
|
allowCreate = true,
|
||||||
|
|
||||||
|
openDrawer,
|
||||||
...suggestProps
|
...suggestProps
|
||||||
}) {
|
}) {
|
||||||
// Filters items based on filter props.
|
// Filters items based on filter props.
|
||||||
@@ -36,28 +95,23 @@ export default function ItemsSuggestField({
|
|||||||
// Find initial item object.
|
// Find initial item object.
|
||||||
const initialItem = useMemo(
|
const initialItem = useMemo(
|
||||||
() => filteredItems.some((a) => a.id === initialItemId),
|
() => filteredItems.some((a) => a.id === initialItemId),
|
||||||
[initialItemId],
|
[initialItemId, filteredItems],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [selectedItem, setSelectedItem] = useState(initialItem || null);
|
const [selectedItem, setSelectedItem] = useState(initialItem || null);
|
||||||
|
|
||||||
const onItemSelect = useCallback(
|
const onItemSelect = useCallback(
|
||||||
(item) => {
|
(item) => {
|
||||||
setSelectedItem({ ...item });
|
if (item.id) {
|
||||||
onItemSelected && onItemSelected(item);
|
setSelectedItem({ ...item });
|
||||||
|
onItemSelected && onItemSelected(item);
|
||||||
|
} else {
|
||||||
|
openDrawer(DRAWERS.QUICK_CREATE_ITEM);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setSelectedItem, onItemSelected],
|
[setSelectedItem, onItemSelected, openDrawer],
|
||||||
);
|
);
|
||||||
|
|
||||||
const itemRenderer = useCallback((item, { modifiers, handleClick }) => (
|
|
||||||
<MenuItem
|
|
||||||
key={item.id}
|
|
||||||
text={item.name}
|
|
||||||
label={item.code}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof selectedItemId !== 'undefined') {
|
if (typeof selectedItemId !== 'undefined') {
|
||||||
const item = selectedItemId
|
const item = selectedItemId
|
||||||
@@ -67,27 +121,12 @@ export default function ItemsSuggestField({
|
|||||||
}
|
}
|
||||||
}, [selectedItemId, filteredItems, setSelectedItem]);
|
}, [selectedItemId, filteredItems, setSelectedItem]);
|
||||||
|
|
||||||
const handleInputValueRenderer = (inputValue) => {
|
// Maybe inject create new item props to suggest component.
|
||||||
if (inputValue) {
|
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
|
||||||
return inputValue.name.toString();
|
const maybeCreateNewItemFromQuery = allowCreate
|
||||||
}
|
? createNewItemFromQuery
|
||||||
return '';
|
: null;
|
||||||
};
|
|
||||||
|
|
||||||
// Filters items.
|
|
||||||
const filterItemsPredicater = useCallback(
|
|
||||||
(query, item, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = item.name.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
|
||||||
return `${normalizedTitle} ${item.code}`.indexOf(normalizedQuery) >= 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<Suggest
|
<Suggest
|
||||||
items={filteredItems}
|
items={filteredItems}
|
||||||
@@ -104,7 +143,12 @@ export default function ItemsSuggestField({
|
|||||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||||
})}
|
})}
|
||||||
|
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||||
|
createNewItemFromQuery={maybeCreateNewItemFromQuery}
|
||||||
|
createNewItemPosition={'top'}
|
||||||
{...suggestProps}
|
{...suggestProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDrawerActions)(ItemsSuggestField);
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export * from './Datatable/CellForceWidth';
|
|||||||
export * from './Button';
|
export * from './Button';
|
||||||
export * from './IntersectionObserver';
|
export * from './IntersectionObserver';
|
||||||
export * from './SMSPreview';
|
export * from './SMSPreview';
|
||||||
|
export * from './Contacts';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ export const useJournalTableEntriesColumns = () => {
|
|||||||
className: 'account',
|
className: 'account',
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
width: 160,
|
width: 160,
|
||||||
|
fieldProps: { allowCreate: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: CreditHeaderCell,
|
Header: CreditHeaderCell,
|
||||||
|
|||||||
@@ -9,23 +9,24 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { Icon } from 'components';
|
import { Icon } from 'components';
|
||||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
|
|
||||||
|
import { safeInvoke } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customer floating actions bar.
|
* Customer floating actions bar.
|
||||||
*/
|
*/
|
||||||
export default function CustomerFloatingActions() {
|
export default function CustomerFloatingActions({ onCancel }) {
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
// Customer form context.
|
// Customer form context.
|
||||||
const { isNewMode,setSubmitPayload } = useCustomerFormContext();
|
const { isNewMode, setSubmitPayload } = useCustomerFormContext();
|
||||||
|
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { resetForm, submitForm, isSubmitting } = useFormikContext();
|
const { resetForm, submitForm, isSubmitting } = useFormikContext();
|
||||||
@@ -37,7 +38,7 @@ export default function CustomerFloatingActions() {
|
|||||||
|
|
||||||
// Handle cancel button click.
|
// Handle cancel button click.
|
||||||
const handleCancelBtnClick = (event) => {
|
const handleCancelBtnClick = (event) => {
|
||||||
history.goBack();
|
safeInvoke(onCancel, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
// handle clear button clicl.
|
// handle clear button clicl.
|
||||||
@@ -55,7 +56,7 @@ export default function CustomerFloatingActions() {
|
|||||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{/* ----------- Save and New ----------- */}
|
{/* ----------- Save and New ----------- */}
|
||||||
<Button
|
<SaveButton
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
@@ -83,6 +84,7 @@ export default function CustomerFloatingActions() {
|
|||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
{/* ----------- Clear & Reset----------- */}
|
{/* ----------- Clear & Reset----------- */}
|
||||||
<Button
|
<Button
|
||||||
className={'ml1'}
|
className={'ml1'}
|
||||||
@@ -99,3 +101,7 @@ export default function CustomerFloatingActions() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SaveButton = styled(Button)`
|
||||||
|
min-width: 100px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,152 +1,14 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import { Formik, Form } from 'formik';
|
import { CustomerFormProvider } from './CustomerFormProvider';
|
||||||
import moment from 'moment';
|
import CustomerFormFormik from './CustomerFormFormik';
|
||||||
import { Intent } from '@blueprintjs/core';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
|
||||||
import AppToaster from 'components/AppToaster';
|
|
||||||
|
|
||||||
import CustomerFormPrimarySection from './CustomerFormPrimarySection';
|
|
||||||
import CustomerFormAfterPrimarySection from './CustomerFormAfterPrimarySection';
|
|
||||||
import CustomersTabs from './CustomersTabs';
|
|
||||||
import CustomerFloatingActions from './CustomerFloatingActions';
|
|
||||||
|
|
||||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
|
||||||
|
|
||||||
import { compose, transformToForm } from 'utils';
|
|
||||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
|
||||||
import { CreateCustomerForm, EditCustomerForm } from './CustomerForm.schema';
|
|
||||||
|
|
||||||
const defaultInitialValues = {
|
|
||||||
customer_type: 'business',
|
|
||||||
salutation: '',
|
|
||||||
first_name: '',
|
|
||||||
last_name: '',
|
|
||||||
company_name: '',
|
|
||||||
display_name: '',
|
|
||||||
|
|
||||||
email: '',
|
|
||||||
work_phone: '',
|
|
||||||
personal_phone: '',
|
|
||||||
website: '',
|
|
||||||
note: '',
|
|
||||||
active: true,
|
|
||||||
|
|
||||||
billing_address_country: '',
|
|
||||||
billing_address_1: '',
|
|
||||||
billing_address_2: '',
|
|
||||||
billing_address_city: '',
|
|
||||||
billing_address_state: '',
|
|
||||||
billing_address_postcode: '',
|
|
||||||
billing_address_phone: '',
|
|
||||||
|
|
||||||
shipping_address_country: '',
|
|
||||||
shipping_address_1: '',
|
|
||||||
shipping_address_2: '',
|
|
||||||
shipping_address_city: '',
|
|
||||||
shipping_address_state: '',
|
|
||||||
shipping_address_postcode: '',
|
|
||||||
shipping_address_phone: '',
|
|
||||||
|
|
||||||
opening_balance: '',
|
|
||||||
currency_code: '',
|
|
||||||
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customer form.
|
* Abstructed customer form.
|
||||||
*/
|
*/
|
||||||
function CustomerForm({ organization: { base_currency } }) {
|
export default function CustomerForm({ customerId }) {
|
||||||
const {
|
|
||||||
customer,
|
|
||||||
customerId,
|
|
||||||
submitPayload,
|
|
||||||
contactDuplicate,
|
|
||||||
editCustomerMutate,
|
|
||||||
createCustomerMutate,
|
|
||||||
isNewMode,
|
|
||||||
} = useCustomerFormContext();
|
|
||||||
|
|
||||||
// const isNewMode = !customerId;
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial values in create and edit mode.
|
|
||||||
*/
|
|
||||||
const initialValues = useMemo(
|
|
||||||
() => ({
|
|
||||||
...defaultInitialValues,
|
|
||||||
currency_code: base_currency,
|
|
||||||
...transformToForm(contactDuplicate || customer, defaultInitialValues),
|
|
||||||
}),
|
|
||||||
[customer, contactDuplicate, base_currency],
|
|
||||||
);
|
|
||||||
|
|
||||||
//Handles the form submit.
|
|
||||||
const handleFormSubmit = (
|
|
||||||
values,
|
|
||||||
{ setSubmitting, resetForm, setErrors },
|
|
||||||
) => {
|
|
||||||
const formValues = { ...values };
|
|
||||||
|
|
||||||
const onSuccess = () => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: intl.get(
|
|
||||||
isNewMode
|
|
||||||
? 'the_customer_has_been_created_successfully'
|
|
||||||
: 'the_item_customer_has_been_edited_successfully',
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
setSubmitting(false);
|
|
||||||
resetForm();
|
|
||||||
|
|
||||||
if (!submitPayload.noRedirect) {
|
|
||||||
history.push('/customers');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onError = () => {
|
|
||||||
setSubmitting(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isNewMode) {
|
|
||||||
createCustomerMutate(formValues).then(onSuccess).catch(onError);
|
|
||||||
} else {
|
|
||||||
editCustomerMutate([customer.id, formValues])
|
|
||||||
.then(onSuccess)
|
|
||||||
.catch(onError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_CUSTOMER)}>
|
<CustomerFormProvider customerId={customerId}>
|
||||||
<Formik
|
<CustomerFormFormik />
|
||||||
validationSchema={isNewMode ? CreateCustomerForm : EditCustomerForm}
|
</CustomerFormProvider>
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={handleFormSubmit}
|
|
||||||
>
|
|
||||||
<Form>
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
|
||||||
<CustomerFormPrimarySection />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={'page-form__after-priamry-section'}>
|
|
||||||
<CustomerFormAfterPrimarySection />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
|
||||||
<CustomersTabs />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CustomerFloatingActions />
|
|
||||||
</Form>
|
|
||||||
</Formik>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withCurrentOrganization())(CustomerForm);
|
|
||||||
|
|||||||
127
src/containers/Customers/CustomerForm/CustomerFormFormik.js
Normal file
127
src/containers/Customers/CustomerForm/CustomerFormFormik.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Formik, Form } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import AppToaster from 'components/AppToaster';
|
||||||
|
|
||||||
|
import { CreateCustomerForm, EditCustomerForm } from './CustomerForm.schema';
|
||||||
|
|
||||||
|
import CustomerFormPrimarySection from './CustomerFormPrimarySection';
|
||||||
|
import CustomerFormAfterPrimarySection from './CustomerFormAfterPrimarySection';
|
||||||
|
import CustomersTabs from './CustomersTabs';
|
||||||
|
import CustomerFloatingActions from './CustomerFloatingActions';
|
||||||
|
|
||||||
|
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||||
|
|
||||||
|
import { compose, transformToForm, saveInvoke } from 'utils';
|
||||||
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
|
import { defaultInitialValues } from './utils';
|
||||||
|
|
||||||
|
import 'style/pages/Customers/Form.scss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer form.
|
||||||
|
*/
|
||||||
|
function CustomerFormFormik({
|
||||||
|
organization: { base_currency },
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
initialValues: initialCustomerValues,
|
||||||
|
onSubmitSuccess,
|
||||||
|
onSubmitError,
|
||||||
|
onCancel,
|
||||||
|
className,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
customer,
|
||||||
|
submitPayload,
|
||||||
|
contactDuplicate,
|
||||||
|
editCustomerMutate,
|
||||||
|
createCustomerMutate,
|
||||||
|
isNewMode,
|
||||||
|
} = useCustomerFormContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial values in create and edit mode.
|
||||||
|
*/
|
||||||
|
const initialValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
...defaultInitialValues,
|
||||||
|
currency_code: base_currency,
|
||||||
|
...transformToForm(contactDuplicate || customer, defaultInitialValues),
|
||||||
|
...initialCustomerValues,
|
||||||
|
}),
|
||||||
|
[customer, contactDuplicate, base_currency, initialCustomerValues],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, formArgs) => {
|
||||||
|
const { setSubmitting, resetForm } = formArgs;
|
||||||
|
const formValues = { ...values };
|
||||||
|
|
||||||
|
const onSuccess = () => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get(
|
||||||
|
isNewMode
|
||||||
|
? 'the_customer_has_been_created_successfully'
|
||||||
|
: 'the_item_customer_has_been_edited_successfully',
|
||||||
|
),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
|
resetForm();
|
||||||
|
|
||||||
|
saveInvoke(onSubmitSuccess, values, formArgs, submitPayload);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = () => {
|
||||||
|
setSubmitting(false);
|
||||||
|
saveInvoke(onSubmitError, values, formArgs, submitPayload);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isNewMode) {
|
||||||
|
createCustomerMutate(formValues).then(onSuccess).catch(onError);
|
||||||
|
} else {
|
||||||
|
editCustomerMutate([customer.id, formValues])
|
||||||
|
.then(onSuccess)
|
||||||
|
.catch(onError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.PAGE_FORM,
|
||||||
|
CLASSES.PAGE_FORM_CUSTOMER,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Formik
|
||||||
|
validationSchema={isNewMode ? CreateCustomerForm : EditCustomerForm}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
||||||
|
<CustomerFormPrimarySection />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'page-form__after-priamry-section'}>
|
||||||
|
<CustomerFormAfterPrimarySection />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
||||||
|
<CustomersTabs />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CustomerFloatingActions onCancel={onCancel} />
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withCurrentOrganization())(CustomerFormFormik);
|
||||||
@@ -1,20 +1,74 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { DashboardCard } from 'components';
|
import { DashboardCard } from 'components';
|
||||||
import CustomerForm from './CustomerForm';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
import { CustomerFormProvider } from './CustomerFormProvider';
|
|
||||||
|
|
||||||
import 'style/pages/Customers/PageForm.scss';
|
import CustomerFormFormik from './CustomerFormFormik';
|
||||||
|
import {
|
||||||
|
CustomerFormProvider,
|
||||||
|
useCustomerFormContext,
|
||||||
|
} from './CustomerFormProvider';
|
||||||
|
|
||||||
export default function CustomerFormPage() {
|
/**
|
||||||
const { id } = useParams();
|
* Customer form page loading.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
function CustomerFormPageLoading({ children }) {
|
||||||
|
const { isFormLoading } = useCustomerFormContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomerFormProvider customerId={id}>
|
<CustomerDashboardInsider loading={isFormLoading}>
|
||||||
<DashboardCard page>
|
{children}
|
||||||
<CustomerForm />
|
</CustomerDashboardInsider>
|
||||||
</DashboardCard>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer form page.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
export default function CustomerFormPage() {
|
||||||
|
const history = useHistory();
|
||||||
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const customerId = parseInt(id, 10);
|
||||||
|
|
||||||
|
// Handle the form submit success.
|
||||||
|
const handleSubmitSuccess = (values, formArgs, submitPayload) => {
|
||||||
|
if (!submitPayload.noRedirect) {
|
||||||
|
history.push('/customers');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Handle the form cancel button click.
|
||||||
|
const handleFormCancel = () => {
|
||||||
|
history.goBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomerFormProvider customerId={customerId}>
|
||||||
|
<CustomerFormPageLoading>
|
||||||
|
<DashboardCard page>
|
||||||
|
<CustomerFormPageFormik
|
||||||
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
/>
|
||||||
|
</DashboardCard>
|
||||||
|
</CustomerFormPageLoading>
|
||||||
</CustomerFormProvider>
|
</CustomerFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CustomerFormPageFormik = styled(CustomerFormFormik)`
|
||||||
|
.page-form {
|
||||||
|
&__floating-actions {
|
||||||
|
margin-left: -40px;
|
||||||
|
margin-right: -40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CustomerDashboardInsider = styled(DashboardInsider)`
|
||||||
|
padding-bottom: 64px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React, { useState, createContext } from 'react';
|
import React, { useState, createContext } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
|
||||||
import {
|
import {
|
||||||
useCustomer,
|
useCustomer,
|
||||||
useCurrencies,
|
useCurrencies,
|
||||||
@@ -24,7 +23,7 @@ function CustomerFormProvider({ customerId, ...props }) {
|
|||||||
// Handle fetch contact duplicate details.
|
// Handle fetch contact duplicate details.
|
||||||
const { data: contactDuplicate, isLoading: isContactLoading } = useContact(
|
const { data: contactDuplicate, isLoading: isContactLoading } = useContact(
|
||||||
contactId,
|
contactId,
|
||||||
{ enabled: !!contactId, },
|
{ enabled: !!contactId },
|
||||||
);
|
);
|
||||||
// Handle fetch Currencies data table
|
// Handle fetch Currencies data table
|
||||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
||||||
@@ -38,6 +37,9 @@ function CustomerFormProvider({ customerId, ...props }) {
|
|||||||
// determines whether the form new or duplicate mode.
|
// determines whether the form new or duplicate mode.
|
||||||
const isNewMode = contactId || !customerId;
|
const isNewMode = contactId || !customerId;
|
||||||
|
|
||||||
|
const isFormLoading =
|
||||||
|
isCustomerLoading || isCurrenciesLoading || isContactLoading;
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
customerId,
|
customerId,
|
||||||
customer,
|
customer,
|
||||||
@@ -48,24 +50,14 @@ function CustomerFormProvider({ customerId, ...props }) {
|
|||||||
|
|
||||||
isCustomerLoading,
|
isCustomerLoading,
|
||||||
isCurrenciesLoading,
|
isCurrenciesLoading,
|
||||||
|
isFormLoading,
|
||||||
|
|
||||||
setSubmitPayload,
|
setSubmitPayload,
|
||||||
editCustomerMutate,
|
editCustomerMutate,
|
||||||
createCustomerMutate,
|
createCustomerMutate,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <CustomerFormContext.Provider value={provider} {...props} />;
|
||||||
<DashboardInsider
|
|
||||||
loading={
|
|
||||||
isCustomerLoading ||
|
|
||||||
isCurrenciesLoading ||
|
|
||||||
isContactLoading
|
|
||||||
}
|
|
||||||
name={'customer-form'}
|
|
||||||
>
|
|
||||||
<CustomerFormContext.Provider value={provider} {...props} />
|
|
||||||
</DashboardInsider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useCustomerFormContext = () => React.useContext(CustomerFormContext);
|
const useCustomerFormContext = () => React.useContext(CustomerFormContext);
|
||||||
|
|||||||
38
src/containers/Customers/CustomerForm/utils.js
Normal file
38
src/containers/Customers/CustomerForm/utils.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
|
||||||
|
export const defaultInitialValues = {
|
||||||
|
customer_type: 'business',
|
||||||
|
salutation: '',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
company_name: '',
|
||||||
|
display_name: '',
|
||||||
|
|
||||||
|
email: '',
|
||||||
|
work_phone: '',
|
||||||
|
personal_phone: '',
|
||||||
|
website: '',
|
||||||
|
note: '',
|
||||||
|
active: true,
|
||||||
|
|
||||||
|
billing_address_country: '',
|
||||||
|
billing_address_1: '',
|
||||||
|
billing_address_2: '',
|
||||||
|
billing_address_city: '',
|
||||||
|
billing_address_state: '',
|
||||||
|
billing_address_postcode: '',
|
||||||
|
billing_address_phone: '',
|
||||||
|
|
||||||
|
shipping_address_country: '',
|
||||||
|
shipping_address_1: '',
|
||||||
|
shipping_address_2: '',
|
||||||
|
shipping_address_city: '',
|
||||||
|
shipping_address_state: '',
|
||||||
|
shipping_address_postcode: '',
|
||||||
|
shipping_address_phone: '',
|
||||||
|
|
||||||
|
opening_balance: '',
|
||||||
|
currency_code: '',
|
||||||
|
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
};
|
||||||
@@ -37,7 +37,7 @@ export default function QuickPaymentMadeFormFields() {
|
|||||||
const { accounts } = useQuickPaymentMadeContext();
|
const { accounts } = useQuickPaymentMadeContext();
|
||||||
|
|
||||||
// Intl context.
|
// Intl context.
|
||||||
|
|
||||||
const paymentMadeFieldRef = useAutofocus();
|
const paymentMadeFieldRef = useAutofocus();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
DrawerHeaderContent,
|
||||||
|
DrawerBody,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import QuickCustomerFormDrawer from './QuickCustomerFormDrawer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick create/edit customer drawer.
|
||||||
|
*/
|
||||||
|
export default function QuickCreateCustomerDrawerContent({ displayName }) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<DrawerHeaderContent
|
||||||
|
name="quick-create-customer"
|
||||||
|
title={<T id={'create_a_new_customer'} />}
|
||||||
|
/>
|
||||||
|
<DrawerBody>
|
||||||
|
<QuickCustomerFormDrawer displayName={displayName} />
|
||||||
|
</DrawerBody>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { Card, DrawerLoading } from 'components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CustomerFormProvider,
|
||||||
|
useCustomerFormContext,
|
||||||
|
} from '../../Customers/CustomerForm/CustomerFormProvider';
|
||||||
|
import CustomerFormFormik from '../../Customers/CustomerForm/CustomerFormFormik';
|
||||||
|
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drawer customer form loading wrapper.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
function DrawerCustomerFormLoading({ children }) {
|
||||||
|
const { isFormLoading } = useCustomerFormContext();
|
||||||
|
|
||||||
|
return <DrawerLoading loading={isFormLoading}>{children}</DrawerLoading>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick customer form of the drawer.
|
||||||
|
*/
|
||||||
|
function QuickCustomerFormDrawer({ displayName, closeDrawer, customerId }) {
|
||||||
|
// Handle the form submit request success.
|
||||||
|
const handleSubmitSuccess = () => {
|
||||||
|
closeDrawer('quick-create-customer');
|
||||||
|
};
|
||||||
|
// Handle the form cancel action.
|
||||||
|
const handleCancelForm = () => {
|
||||||
|
closeDrawer('quick-create-customer');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomerFormProvider customerId={customerId}>
|
||||||
|
<DrawerCustomerFormLoading>
|
||||||
|
<CustomerFormCard>
|
||||||
|
<CustomerFormFormik
|
||||||
|
initialValues={{ display_name: displayName }}
|
||||||
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
|
onCancel={handleCancelForm}
|
||||||
|
/>
|
||||||
|
</CustomerFormCard>
|
||||||
|
</DrawerCustomerFormLoading>
|
||||||
|
</CustomerFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDrawerActions)(QuickCustomerFormDrawer);
|
||||||
|
|
||||||
|
const CustomerFormCard = styled(Card)`
|
||||||
|
margin: 15px;
|
||||||
|
margin-bottom: calc(15px + 65px);
|
||||||
|
|
||||||
|
.page-form {
|
||||||
|
&__floating-actions {
|
||||||
|
margin-left: -36px;
|
||||||
|
margin-right: -36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
35
src/containers/Drawers/QuickCreateCustomerDrawer/index.js
Normal file
35
src/containers/Drawers/QuickCreateCustomerDrawer/index.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Drawer, DrawerSuspense } from 'components';
|
||||||
|
import withDrawers from 'containers/Drawer/withDrawers';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const QuickCreateCustomerDrawerContent = React.lazy(() =>
|
||||||
|
import('./QuickCreateCustomerDrawerContent'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick Create customer
|
||||||
|
*/
|
||||||
|
function QuickCreateCustomerDrawer({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withDrawer
|
||||||
|
isOpen,
|
||||||
|
payload,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
isOpen={isOpen}
|
||||||
|
name={name}
|
||||||
|
style={{ minWidth: '700px', maxWidth: '900px' }}
|
||||||
|
size={'80%'}
|
||||||
|
>
|
||||||
|
<DrawerSuspense>
|
||||||
|
<QuickCreateCustomerDrawerContent displayName={payload.displayName} />
|
||||||
|
</DrawerSuspense>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDrawers())(QuickCreateCustomerDrawer);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
DrawerHeaderContent,
|
||||||
|
DrawerBody,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import QuickCreateItemDrawerForm from './QuickCreateItemDrawerForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick create/edit item drawer content.
|
||||||
|
*/
|
||||||
|
export default function QuickCreateItemDrawerContent({ itemName }) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<DrawerHeaderContent
|
||||||
|
name="quick-create-item"
|
||||||
|
title={<T id={'create_a_new_item'} />}
|
||||||
|
/>
|
||||||
|
<DrawerBody>
|
||||||
|
<QuickCreateItemDrawerForm itemName={itemName} />
|
||||||
|
</DrawerBody>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { Card, DrawerLoading } from 'components';
|
||||||
|
|
||||||
|
import ItemFormFormik from '../../Items/ItemFormFormik';
|
||||||
|
import {
|
||||||
|
ItemFormProvider,
|
||||||
|
useItemFormContext,
|
||||||
|
} from '../../Items/ItemFormProvider';
|
||||||
|
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
import withDashboardActions from '../../Dashboard/withDashboardActions';
|
||||||
|
|
||||||
|
import { useDrawerContext } from 'components/Drawer/DrawerProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drawer item form loading.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
function DrawerItemFormLoading({ children }) {
|
||||||
|
const { isFormLoading } = useItemFormContext();
|
||||||
|
|
||||||
|
return <DrawerLoading loading={isFormLoading}>{children}</DrawerLoading>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick create/edit item drawer form.
|
||||||
|
*/
|
||||||
|
function QuickCreateItemDrawerForm({
|
||||||
|
itemId,
|
||||||
|
itemName,
|
||||||
|
closeDrawer,
|
||||||
|
|
||||||
|
// #withDashboardActions
|
||||||
|
addQuickActionEvent,
|
||||||
|
}) {
|
||||||
|
// Drawer context.
|
||||||
|
const { payload } = useDrawerContext();
|
||||||
|
|
||||||
|
// Handle the form submit request success.
|
||||||
|
const handleSubmitSuccess = (values, form, submitPayload, response) => {
|
||||||
|
if (submitPayload.redirect) {
|
||||||
|
closeDrawer('quick-create-item');
|
||||||
|
}
|
||||||
|
if (payload.quickActionEvent) {
|
||||||
|
addQuickActionEvent(payload.quickActionEvent, {
|
||||||
|
itemId: response.data.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Handle the form cancel.
|
||||||
|
const handleFormCancel = () => {
|
||||||
|
closeDrawer('quick-create-item');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemFormProvider itemId={itemId}>
|
||||||
|
<DrawerItemFormLoading>
|
||||||
|
<ItemFormCard>
|
||||||
|
<ItemFormFormik
|
||||||
|
initialValues={{ name: itemName }}
|
||||||
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
/>
|
||||||
|
</ItemFormCard>
|
||||||
|
</DrawerItemFormLoading>
|
||||||
|
</ItemFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(
|
||||||
|
withDrawerActions,
|
||||||
|
withDashboardActions,
|
||||||
|
)(QuickCreateItemDrawerForm);
|
||||||
|
|
||||||
|
const ItemFormCard = styled(Card)`
|
||||||
|
margin: 15px;
|
||||||
|
margin-bottom: calc(15px + 65px);
|
||||||
|
|
||||||
|
.page-form__floating-actions {
|
||||||
|
margin-left: -36px;
|
||||||
|
margin-right: -36px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
37
src/containers/Drawers/QuickCreateItemDrawer/index.js
Normal file
37
src/containers/Drawers/QuickCreateItemDrawer/index.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Drawer, DrawerSuspense } from 'components';
|
||||||
|
import withDrawers from 'containers/Drawer/withDrawers';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const QuickCretaeItemDrawerContent = React.lazy(() =>
|
||||||
|
import('./QuickCreateItemDrawerContent'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick create item.
|
||||||
|
*/
|
||||||
|
function QuickCreateItemDrawer({
|
||||||
|
// #ownProps
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withDrawer
|
||||||
|
isOpen,
|
||||||
|
payload,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
isOpen={isOpen}
|
||||||
|
name={name}
|
||||||
|
style={{ minWidth: '800px', maxWidth: '1000px' }}
|
||||||
|
size={'72%'}
|
||||||
|
payload={payload}
|
||||||
|
>
|
||||||
|
<DrawerSuspense>
|
||||||
|
<QuickCretaeItemDrawerContent itemName={payload.name} />
|
||||||
|
</DrawerSuspense>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDrawers())(QuickCreateItemDrawer);
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { Card, DrawerLoading } from 'components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
VendorFormProvider,
|
||||||
|
useVendorFormContext,
|
||||||
|
} from '../../Vendors/VendorForm/VendorFormProvider';
|
||||||
|
import VendorFormFormik from '../../Vendors/VendorForm/VendorFormFormik';
|
||||||
|
|
||||||
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
import withDashboardActions from '../../Dashboard/withDashboardActions';
|
||||||
|
|
||||||
|
import { useDrawerContext } from 'components/Drawer/DrawerProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drawer vendor form loading wrapper.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
function DrawerVendorFormLoading({ children }) {
|
||||||
|
const { isFormLoading } = useVendorFormContext();
|
||||||
|
|
||||||
|
return <DrawerLoading loading={isFormLoading}>{children}</DrawerLoading>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick vendor form of the drawer.
|
||||||
|
*/
|
||||||
|
function QuickVendorFormDrawer({
|
||||||
|
displayName,
|
||||||
|
closeDrawer,
|
||||||
|
vendorId,
|
||||||
|
addQuickActionEvent,
|
||||||
|
}) {
|
||||||
|
const { payload } = useDrawerContext();
|
||||||
|
|
||||||
|
// Handle the form submit request success.
|
||||||
|
const handleSubmitSuccess = (values, form, submitPayload, response) => {
|
||||||
|
if (!submitPayload.noRedirect) {
|
||||||
|
closeDrawer('quick-write-vendor');
|
||||||
|
}
|
||||||
|
if (payload.quickActionEvent) {
|
||||||
|
addQuickActionEvent(payload.quickActionEvent, {
|
||||||
|
vendorId: response.data.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Handle the form cancel action.
|
||||||
|
const handleCancelForm = () => {
|
||||||
|
closeDrawer('quick-write-vendor');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VendorFormProvider vendorId={vendorId}>
|
||||||
|
<DrawerVendorFormLoading>
|
||||||
|
<VendorFormCard>
|
||||||
|
<VendorFormFormik
|
||||||
|
initialValues={{ display_name: displayName }}
|
||||||
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
|
onCancel={handleCancelForm}
|
||||||
|
/>
|
||||||
|
</VendorFormCard>
|
||||||
|
</DrawerVendorFormLoading>
|
||||||
|
</VendorFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(
|
||||||
|
withDrawerActions,
|
||||||
|
withDashboardActions,
|
||||||
|
)(QuickVendorFormDrawer);
|
||||||
|
|
||||||
|
const VendorFormCard = styled(Card)`
|
||||||
|
margin: 15px;
|
||||||
|
margin-bottom: calc(15px + 65px);
|
||||||
|
|
||||||
|
.page-form {
|
||||||
|
&__floating-actions {
|
||||||
|
margin-left: -36px;
|
||||||
|
margin-right: -36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
DrawerHeaderContent,
|
||||||
|
DrawerBody,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
|
import QuickVendorFormDrawer from './QuickVendorFormDrawer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick create/edit vendor drawer.
|
||||||
|
*/
|
||||||
|
export default function QuickWriteVendorDrawerContent({ displayName }) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<DrawerHeaderContent
|
||||||
|
name="quick-create-customer"
|
||||||
|
title={"Create a new vendor"}
|
||||||
|
/>
|
||||||
|
<DrawerBody>
|
||||||
|
<QuickVendorFormDrawer displayName={displayName} />
|
||||||
|
</DrawerBody>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
36
src/containers/Drawers/QuickWriteVendorDrawer/index.js
Normal file
36
src/containers/Drawers/QuickWriteVendorDrawer/index.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
|
||||||
|
import { Drawer, DrawerSuspense } from 'components';
|
||||||
|
import withDrawers from 'containers/Drawer/withDrawers';
|
||||||
|
|
||||||
|
const QuickWriteVendorDrawerContent = React.lazy(() =>
|
||||||
|
import('./QuickWriteVendorDrawerContent'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quick Write vendor.
|
||||||
|
*/
|
||||||
|
function QuickWriteVendorDrawer({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withDrawer
|
||||||
|
isOpen,
|
||||||
|
payload,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
isOpen={isOpen}
|
||||||
|
name={name}
|
||||||
|
style={{ minWidth: '700px', maxWidth: '900px' }}
|
||||||
|
size={'80%'}
|
||||||
|
payload={payload}
|
||||||
|
>
|
||||||
|
<DrawerSuspense>
|
||||||
|
<QuickWriteVendorDrawerContent displayName={payload.displayName} />
|
||||||
|
</DrawerSuspense>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(withDrawers())(QuickWriteVendorDrawer);
|
||||||
@@ -108,7 +108,9 @@ const LandedCostHeaderCell = () => {
|
|||||||
/**
|
/**
|
||||||
* Retrieve editable items entries columns.
|
* Retrieve editable items entries columns.
|
||||||
*/
|
*/
|
||||||
export function useEditableItemsEntriesColumns({ landedCost }) {
|
export function useEditableItemsEntriesColumns({
|
||||||
|
landedCost,
|
||||||
|
}) {
|
||||||
return React.useMemo(
|
return React.useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -129,6 +131,7 @@ export function useEditableItemsEntriesColumns({ landedCost }) {
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
width: 130,
|
width: 130,
|
||||||
className: 'item',
|
className: 'item',
|
||||||
|
fieldProps: { allowCreate: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: intl.get('description'),
|
Header: intl.get('description'),
|
||||||
|
|||||||
@@ -164,3 +164,12 @@ export const composeRowsOnNewRow = R.curry((rowIndex, newRow, rows) => {
|
|||||||
updateTableRow(rowIndex, newRow),
|
updateTableRow(rowIndex, newRow),
|
||||||
)(rows);
|
)(rows);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} entries
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const composeControlledEntries = (entries) => {
|
||||||
|
return R.compose(orderingLinesIndexes, updateItemsEntriesTotal)(entries);
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
import { customersFieldShouldUpdate, accountsFieldShouldUpdate } from './utils';
|
import { customersFieldShouldUpdate, accountsFieldShouldUpdate } from './utils';
|
||||||
import {
|
import {
|
||||||
CurrencySelectList,
|
CurrencySelectList,
|
||||||
ContactSelecetList,
|
CustomerSelectField,
|
||||||
AccountsSelectList,
|
AccountsSelectList,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Hint,
|
Hint,
|
||||||
@@ -78,6 +78,7 @@ export default function ExpenseFormHeader() {
|
|||||||
defaultSelectText={<T id={'select_payment_account'} />}
|
defaultSelectText={<T id={'select_payment_account'} />}
|
||||||
selectedAccountId={value}
|
selectedAccountId={value}
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
@@ -137,13 +138,14 @@ export default function ExpenseFormHeader() {
|
|||||||
helperText={<ErrorMessage name={'assign_to_customer'} />}
|
helperText={<ErrorMessage name={'assign_to_customer'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
>
|
>
|
||||||
<ContactSelecetList
|
<CustomerSelectField
|
||||||
contactsList={customers}
|
contacts={customers}
|
||||||
selectedContactId={value}
|
selectedContactId={value}
|
||||||
defaultSelectText={<T id={'select_customer_account'} />}
|
defaultSelectText={<T id={'select_customer_account'} />}
|
||||||
onContactSelected={(customer) => {
|
onContactSelected={(customer) => {
|
||||||
form.setFieldValue('customer_id', customer.id);
|
form.setFieldValue('customer_id', customer.id);
|
||||||
}}
|
}}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ export function useExpenseFormTableColumns({ landedCost }) {
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
width: 40,
|
width: 40,
|
||||||
filterAccountsByRootTypes: ['expense'],
|
filterAccountsByRootTypes: ['expense'],
|
||||||
|
fieldProps: { allowCreate: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: ExpenseAmountHeaderCell,
|
Header: ExpenseAmountHeaderCell,
|
||||||
|
|||||||
@@ -1,110 +1,95 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Formik, Form } from 'formik';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import classNames from 'classnames';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import 'style/pages/Items/PageForm.scss';
|
import { useDashboardPageTitle } from 'hooks/state';
|
||||||
|
import { useItemFormContext, ItemFormProvider } from './ItemFormProvider';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import ItemFormFormik from './ItemFormFormik';
|
||||||
import AppToaster from 'components/AppToaster';
|
|
||||||
import ItemFormPrimarySection from './ItemFormPrimarySection';
|
|
||||||
import ItemFormBody from './ItemFormBody';
|
|
||||||
import ItemFormFloatingActions from './ItemFormFloatingActions';
|
|
||||||
import ItemFormInventorySection from './ItemFormInventorySection';
|
|
||||||
|
|
||||||
import {
|
import DashboardCard from 'components/Dashboard/DashboardCard';
|
||||||
transformSubmitRequestErrors,
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
useItemFormInitialValues,
|
|
||||||
} from './utils';
|
|
||||||
import { EditItemFormSchema, CreateItemFormSchema } from './ItemForm.schema';
|
|
||||||
|
|
||||||
import { useItemFormContext } from './ItemFormProvider';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item form.
|
* Item form dashboard title.
|
||||||
|
* @returns {null}
|
||||||
*/
|
*/
|
||||||
export default function ItemForm() {
|
function ItemFormDashboardTitle() {
|
||||||
// Item form context.
|
// Change page title dispatcher.
|
||||||
const {
|
const changePageTitle = useDashboardPageTitle();
|
||||||
itemId,
|
|
||||||
item,
|
|
||||||
accounts,
|
|
||||||
createItemMutate,
|
|
||||||
editItemMutate,
|
|
||||||
submitPayload,
|
|
||||||
isNewMode,
|
|
||||||
} = useItemFormContext();
|
|
||||||
|
|
||||||
|
// Item form context.
|
||||||
|
const { isNewMode } = useItemFormContext();
|
||||||
|
|
||||||
|
// Changes the page title in new and edit mode.
|
||||||
|
React.useEffect(() => {
|
||||||
|
isNewMode
|
||||||
|
? changePageTitle(intl.get('new_item'))
|
||||||
|
: changePageTitle(intl.get('edit_item_details'));
|
||||||
|
}, [changePageTitle, isNewMode]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item form page loading state indicator.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
function ItemFormPageLoading({ children }) {
|
||||||
|
const { isFormLoading } = useItemFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardItemFormPageInsider loading={isFormLoading} name={'item-form'}>
|
||||||
|
{children}
|
||||||
|
</DashboardItemFormPageInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item form of the page.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
export default function ItemForm({ itemId }) {
|
||||||
// History context.
|
// History context.
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
// Initial values in create and edit mode.
|
// Handle the form submit success.
|
||||||
const initialValues = useItemFormInitialValues(item);
|
const handleSubmitSuccess = (values, form, submitPayload) => {
|
||||||
|
if (submitPayload.redirect) {
|
||||||
// Handles the form submit.
|
history.push('/items');
|
||||||
const handleFormSubmit = (
|
|
||||||
values,
|
|
||||||
{ setSubmitting, resetForm, setErrors },
|
|
||||||
) => {
|
|
||||||
setSubmitting(true);
|
|
||||||
const form = { ...values };
|
|
||||||
|
|
||||||
const onSuccess = (response) => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: intl.get(
|
|
||||||
isNewMode
|
|
||||||
? 'the_item_has_been_created_successfully'
|
|
||||||
: 'the_item_has_been_edited_successfully',
|
|
||||||
{
|
|
||||||
number: itemId,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
resetForm();
|
|
||||||
setSubmitting(false);
|
|
||||||
|
|
||||||
// Submit payload.
|
|
||||||
if (submitPayload.redirect) {
|
|
||||||
history.push('/items');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle response error.
|
|
||||||
const onError = (errors) => {
|
|
||||||
setSubmitting(false);
|
|
||||||
if (errors) {
|
|
||||||
const _errors = transformSubmitRequestErrors(errors);
|
|
||||||
setErrors({ ..._errors });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (isNewMode) {
|
|
||||||
createItemMutate(form).then(onSuccess).catch(onError);
|
|
||||||
} else {
|
|
||||||
editItemMutate([itemId, form]).then(onSuccess).catch(onError);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Handle cancel button click.
|
||||||
|
const handleFormCancel = () => {
|
||||||
|
history.goBack();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={classNames(CLASSES.PAGE_FORM_ITEM)}>
|
<ItemFormProvider itemId={itemId}>
|
||||||
<Formik
|
<ItemFormDashboardTitle />
|
||||||
enableReinitialize={true}
|
|
||||||
validationSchema={isNewMode ? CreateItemFormSchema : EditItemFormSchema}
|
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={handleFormSubmit}
|
|
||||||
>
|
|
||||||
<Form>
|
|
||||||
<div class={classNames(CLASSES.PAGE_FORM_BODY)}>
|
|
||||||
<ItemFormPrimarySection />
|
|
||||||
<ItemFormBody accounts={accounts} />
|
|
||||||
<ItemFormInventorySection accounts={accounts} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ItemFormFloatingActions />
|
<ItemFormPageLoading>
|
||||||
</Form>
|
<DashboardCard page>
|
||||||
</Formik>
|
<ItemFormPageFormik
|
||||||
</div>
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
/>
|
||||||
|
</DashboardCard>
|
||||||
|
</ItemFormPageLoading>
|
||||||
|
</ItemFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DashboardItemFormPageInsider = styled(DashboardInsider)`
|
||||||
|
padding-bottom: 64px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ItemFormPageFormik = styled(ItemFormFormik)`
|
||||||
|
.page-form {
|
||||||
|
&__floating-actions {
|
||||||
|
margin-left: -40px;
|
||||||
|
margin-right: -40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFormikContext, FastField, Field, ErrorMessage } from 'formik';
|
import { useFormikContext, FastField, ErrorMessage } from 'formik';
|
||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Classes,
|
Classes,
|
||||||
@@ -122,6 +122,7 @@ function ItemFormBody({ organization: { base_currency } }) {
|
|||||||
disabled={!form.values.sellable}
|
disabled={!form.values.sellable}
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
@@ -230,6 +231,7 @@ function ItemFormBody({ organization: { base_currency } }) {
|
|||||||
disabled={!form.values.purchasable}
|
disabled={!form.values.purchasable}
|
||||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
|
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, Intent, FormGroup, Checkbox } from '@blueprintjs/core';
|
import { Button, Intent, FormGroup, Checkbox } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
import styled from 'styled-components';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { FastField, useFormikContext } from 'formik';
|
import { FastField, useFormikContext } from 'formik';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { useItemFormContext } from './ItemFormProvider';
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
|
import { saveInvoke } from '../../utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item form floating actions.
|
* Item form floating actions.
|
||||||
*/
|
*/
|
||||||
export default function ItemFormFloatingActions() {
|
export default function ItemFormFloatingActions({ onCancel }) {
|
||||||
// History context.
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
// Item form context.
|
// Item form context.
|
||||||
const { setSubmitPayload, isNewMode } = useItemFormContext();
|
const { setSubmitPayload, isNewMode } = useItemFormContext();
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ export default function ItemFormFloatingActions() {
|
|||||||
|
|
||||||
// Handle cancel button click.
|
// Handle cancel button click.
|
||||||
const handleCancelBtnClick = (event) => {
|
const handleCancelBtnClick = (event) => {
|
||||||
history.goBack();
|
saveInvoke(onCancel, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle submit button click.
|
// Handle submit button click.
|
||||||
@@ -38,7 +37,7 @@ export default function ItemFormFloatingActions() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
<Button
|
<SaveButton
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
@@ -47,7 +46,7 @@ export default function ItemFormFloatingActions() {
|
|||||||
className={'btn--submit'}
|
className={'btn--submit'}
|
||||||
>
|
>
|
||||||
{isNewMode ? <T id={'save'} /> : <T id={'edit'} />}
|
{isNewMode ? <T id={'save'} /> : <T id={'edit'} />}
|
||||||
</Button>
|
</SaveButton>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={classNames('ml1', 'btn--submit-new')}
|
className={classNames('ml1', 'btn--submit-new')}
|
||||||
@@ -82,3 +81,7 @@ export default function ItemFormFloatingActions() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SaveButton = styled(Button)`
|
||||||
|
min-width: 100px;
|
||||||
|
`;
|
||||||
112
src/containers/Items/ItemFormFormik.js
Normal file
112
src/containers/Items/ItemFormFormik.js
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Formik, Form } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import 'style/pages/Items/Form.scss';
|
||||||
|
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import AppToaster from 'components/AppToaster';
|
||||||
|
import ItemFormPrimarySection from './ItemFormPrimarySection';
|
||||||
|
import ItemFormBody from './ItemFormBody';
|
||||||
|
import ItemFormFloatingActions from './ItemFormFloatingActions';
|
||||||
|
import ItemFormInventorySection from './ItemFormInventorySection';
|
||||||
|
|
||||||
|
import {
|
||||||
|
transformSubmitRequestErrors,
|
||||||
|
useItemFormInitialValues,
|
||||||
|
} from './utils';
|
||||||
|
import { EditItemFormSchema, CreateItemFormSchema } from './ItemForm.schema';
|
||||||
|
|
||||||
|
import { useItemFormContext } from './ItemFormProvider';
|
||||||
|
import { safeInvoke } from '@blueprintjs/core/lib/esm/common/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item form.
|
||||||
|
*/
|
||||||
|
export default function ItemFormFormik({
|
||||||
|
// #ownProps
|
||||||
|
initialValues: initialValuesComponent,
|
||||||
|
onSubmitSuccess,
|
||||||
|
onSubmitError,
|
||||||
|
onCancel,
|
||||||
|
className,
|
||||||
|
}) {
|
||||||
|
// Item form context.
|
||||||
|
const {
|
||||||
|
itemId,
|
||||||
|
item,
|
||||||
|
accounts,
|
||||||
|
createItemMutate,
|
||||||
|
editItemMutate,
|
||||||
|
submitPayload,
|
||||||
|
isNewMode,
|
||||||
|
} = useItemFormContext();
|
||||||
|
|
||||||
|
// Initial values in create and edit mode.
|
||||||
|
const initialValues = useItemFormInitialValues(item, initialValuesComponent);
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, form) => {
|
||||||
|
const { setSubmitting, resetForm, setErrors } = form;
|
||||||
|
const formValues = { ...values };
|
||||||
|
|
||||||
|
setSubmitting(true);
|
||||||
|
|
||||||
|
// Handle response succes.
|
||||||
|
const onSuccess = (response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get(
|
||||||
|
isNewMode
|
||||||
|
? 'the_item_has_been_created_successfully'
|
||||||
|
: 'the_item_has_been_edited_successfully',
|
||||||
|
{
|
||||||
|
number: itemId,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
resetForm();
|
||||||
|
setSubmitting(false);
|
||||||
|
|
||||||
|
safeInvoke(onSubmitSuccess, values, form, submitPayload, response);
|
||||||
|
};
|
||||||
|
// Handle response error.
|
||||||
|
const onError = (errors) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
const _errors = transformSubmitRequestErrors(errors);
|
||||||
|
setErrors({ ..._errors });
|
||||||
|
}
|
||||||
|
safeInvoke(onSubmitError, values, form, submitPayload, errors);
|
||||||
|
};
|
||||||
|
if (isNewMode) {
|
||||||
|
createItemMutate(formValues).then(onSuccess).catch(onError);
|
||||||
|
} else {
|
||||||
|
editItemMutate([itemId, formValues]).then(onSuccess).catch(onError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={classNames(CLASSES.PAGE_FORM_ITEM, className)}>
|
||||||
|
<Formik
|
||||||
|
enableReinitialize={true}
|
||||||
|
validationSchema={isNewMode ? CreateItemFormSchema : EditItemFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<div class={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||||
|
<ItemFormPrimarySection />
|
||||||
|
<ItemFormBody accounts={accounts} />
|
||||||
|
<ItemFormInventorySection accounts={accounts} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ItemFormFloatingActions onCancel={onCancel} />
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { ItemFormProvider } from './ItemFormProvider';
|
import ItemForm from './ItemForm';
|
||||||
import DashboardCard from 'components/Dashboard/DashboardCard';
|
|
||||||
import ItemForm from 'containers/Items/ItemForm';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item form page.
|
* Item form page.
|
||||||
@@ -12,11 +10,5 @@ export default function ItemFormPage() {
|
|||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const idInteger = parseInt(id, 10);
|
const idInteger = parseInt(id, 10);
|
||||||
|
|
||||||
return (
|
return <ItemForm itemId={idInteger} />;
|
||||||
<ItemFormProvider itemId={idInteger}>
|
}
|
||||||
<DashboardCard page>
|
|
||||||
<ItemForm />
|
|
||||||
</DashboardCard>
|
|
||||||
</ItemFormProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import React, { useEffect, createContext, useState } from 'react';
|
import React, { createContext, useState } from 'react';
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
|
||||||
import {
|
import {
|
||||||
useItem,
|
useItem,
|
||||||
useSettingsItems,
|
useSettingsItems,
|
||||||
@@ -11,7 +8,6 @@ import {
|
|||||||
useEditItem,
|
useEditItem,
|
||||||
useAccounts,
|
useAccounts,
|
||||||
} from 'hooks/query';
|
} from 'hooks/query';
|
||||||
import { useDashboardPageTitle } from 'hooks/state';
|
|
||||||
import { useWatchItemError } from './utils';
|
import { useWatchItemError } from './utils';
|
||||||
|
|
||||||
const ItemFormContext = createContext();
|
const ItemFormContext = createContext();
|
||||||
@@ -59,6 +55,13 @@ function ItemFormProvider({ itemId, ...props }) {
|
|||||||
// Detarmines whether the form new mode.
|
// Detarmines whether the form new mode.
|
||||||
const isNewMode = duplicateId || !itemId;
|
const isNewMode = duplicateId || !itemId;
|
||||||
|
|
||||||
|
// Detarmines the form loading state.
|
||||||
|
const isFormLoading =
|
||||||
|
isItemsSettingsLoading ||
|
||||||
|
isAccountsLoading ||
|
||||||
|
isItemsCategoriesLoading ||
|
||||||
|
isItemLoading;
|
||||||
|
|
||||||
// Provider state.
|
// Provider state.
|
||||||
const provider = {
|
const provider = {
|
||||||
itemId,
|
itemId,
|
||||||
@@ -68,6 +71,7 @@ function ItemFormProvider({ itemId, ...props }) {
|
|||||||
submitPayload,
|
submitPayload,
|
||||||
isNewMode,
|
isNewMode,
|
||||||
|
|
||||||
|
isFormLoading,
|
||||||
isAccountsLoading,
|
isAccountsLoading,
|
||||||
isItemsCategoriesLoading,
|
isItemsCategoriesLoading,
|
||||||
isItemLoading,
|
isItemLoading,
|
||||||
@@ -77,27 +81,7 @@ function ItemFormProvider({ itemId, ...props }) {
|
|||||||
setSubmitPayload,
|
setSubmitPayload,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Change page title dispatcher.
|
return <ItemFormContext.Provider value={provider} {...props} />;
|
||||||
const changePageTitle = useDashboardPageTitle();
|
|
||||||
|
|
||||||
// Changes the page title in new and edit mode.
|
|
||||||
useEffect(() => {
|
|
||||||
isNewMode
|
|
||||||
? changePageTitle(intl.get('new_item'))
|
|
||||||
: changePageTitle(intl.get('edit_item_details'));
|
|
||||||
}, [changePageTitle, isNewMode]);
|
|
||||||
|
|
||||||
const loading =
|
|
||||||
isItemsSettingsLoading ||
|
|
||||||
isAccountsLoading ||
|
|
||||||
isItemsCategoriesLoading ||
|
|
||||||
isItemLoading;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DashboardInsider loading={loading} name={'item-form'}>
|
|
||||||
<ItemFormContext.Provider value={provider} {...props} />
|
|
||||||
</DashboardInsider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useItemFormContext = () => React.useContext(ItemFormContext);
|
const useItemFormContext = () => React.useContext(ItemFormContext);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const defaultInitialValues = {
|
|||||||
/**
|
/**
|
||||||
* Initial values in create and edit mode.
|
* Initial values in create and edit mode.
|
||||||
*/
|
*/
|
||||||
export const useItemFormInitialValues = (item) => {
|
export const useItemFormInitialValues = (item, initialValues) => {
|
||||||
const { items: itemsSettings } = useSettingsSelector();
|
const { items: itemsSettings } = useSettingsSelector();
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
@@ -54,8 +54,9 @@ export const useItemFormInitialValues = (item) => {
|
|||||||
transformItemFormData(item, defaultInitialValues),
|
transformItemFormData(item, defaultInitialValues),
|
||||||
defaultInitialValues,
|
defaultInitialValues,
|
||||||
),
|
),
|
||||||
|
...initialValues,
|
||||||
}),
|
}),
|
||||||
[item, itemsSettings],
|
[item, itemsSettings, initialValues],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { FastField, ErrorMessage } from 'formik';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { ContactSelecetList, FieldRequiredHint, Icon } from 'components';
|
import { VendorSelectField, FieldRequiredHint, Icon } from 'components';
|
||||||
import { vendorsFieldShouldUpdate } from './utils';
|
import { vendorsFieldShouldUpdate } from './utils';
|
||||||
|
|
||||||
import { useBillFormContext } from './BillFormProvider';
|
import { useBillFormContext } from './BillFormProvider';
|
||||||
@@ -43,14 +43,15 @@ function BillFormHeader() {
|
|||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name={'vendor_id'} />}
|
helperText={<ErrorMessage name={'vendor_id'} />}
|
||||||
>
|
>
|
||||||
<ContactSelecetList
|
<VendorSelectField
|
||||||
contactsList={vendors}
|
contacts={vendors}
|
||||||
selectedContactId={value}
|
selectedContactId={value}
|
||||||
defaultSelectText={<T id={'select_vender_account'} />}
|
defaultSelectText={<T id={'select_vender_account'} />}
|
||||||
onContactSelected={(contact) => {
|
onContactSelected={(contact) => {
|
||||||
form.setFieldValue('vendor_id', contact.id);
|
form.setFieldValue('vendor_id', contact.id);
|
||||||
}}
|
}}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import classNames from 'classnames';
|
|||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import {
|
import {
|
||||||
AccountsSelectList,
|
AccountsSelectList,
|
||||||
ContactSelecetList,
|
VendorSelectField,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
InputPrependText,
|
InputPrependText,
|
||||||
Money,
|
Money,
|
||||||
@@ -90,8 +90,8 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
|
|||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name={'vendor_id'} />}
|
helperText={<ErrorMessage name={'vendor_id'} />}
|
||||||
>
|
>
|
||||||
<ContactSelecetList
|
<VendorSelectField
|
||||||
contactsList={vendors}
|
contacts={vendors}
|
||||||
selectedContactId={value}
|
selectedContactId={value}
|
||||||
defaultSelectText={<T id={'select_vender_account'} />}
|
defaultSelectText={<T id={'select_vender_account'} />}
|
||||||
onContactSelected={(contact) => {
|
onContactSelected={(contact) => {
|
||||||
@@ -100,6 +100,7 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
|
|||||||
}}
|
}}
|
||||||
disabled={!isNewMode}
|
disabled={!isNewMode}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { customersFieldShouldUpdate } from './utils';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import {
|
import {
|
||||||
ContactSelecetList,
|
CustomerSelectField,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Icon,
|
Icon,
|
||||||
InputPrependButton,
|
InputPrependButton,
|
||||||
@@ -82,8 +82,8 @@ function EstimateFormHeader({
|
|||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name={'customer_id'} />}
|
helperText={<ErrorMessage name={'customer_id'} />}
|
||||||
>
|
>
|
||||||
<ContactSelecetList
|
<CustomerSelectField
|
||||||
contactsList={customers}
|
contacts={customers}
|
||||||
selectedContactId={value}
|
selectedContactId={value}
|
||||||
defaultSelectText={<T id={'select_customer_account'} />}
|
defaultSelectText={<T id={'select_customer_account'} />}
|
||||||
onContactSelected={(customer) => {
|
onContactSelected={(customer) => {
|
||||||
@@ -91,6 +91,7 @@ function EstimateFormHeader({
|
|||||||
}}
|
}}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,20 +10,23 @@ import { FastField, Field, ErrorMessage } from 'formik';
|
|||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import { momentFormatter, compose, tansformDateValue } from 'utils';
|
import { momentFormatter, compose, tansformDateValue } from 'utils';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useObserveInvoiceNoSettings,
|
useObserveInvoiceNoSettings,
|
||||||
customerNameFieldShouldUpdate,
|
customerNameFieldShouldUpdate,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import {
|
import {
|
||||||
ContactSelecetList,
|
CustomerSelectField,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Icon,
|
Icon,
|
||||||
InputPrependButton,
|
InputPrependButton,
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { useInvoiceFormContext } from './InvoiceFormProvider';
|
import { useInvoiceFormContext } from './InvoiceFormProvider';
|
||||||
|
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
import { inputIntent, handleDateChange } from 'utils';
|
import { inputIntent, handleDateChange } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,14 +87,15 @@ function InvoiceFormHeaderFields({
|
|||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name={'customer_id'} />}
|
helperText={<ErrorMessage name={'customer_id'} />}
|
||||||
>
|
>
|
||||||
<ContactSelecetList
|
<CustomerSelectField
|
||||||
contactsList={customers}
|
contacts={customers}
|
||||||
selectedContactId={value}
|
selectedContactId={value}
|
||||||
defaultSelectText={<T id={'select_customer_account'} />}
|
defaultSelectText={<T id={'select_customer_account'} />}
|
||||||
onContactSelected={(customer) => {
|
onContactSelected={(customer) => {
|
||||||
form.setFieldValue('customer_id', customer.id);
|
form.setFieldValue('customer_id', customer.id);
|
||||||
}}
|
}}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
} from 'utils';
|
} from 'utils';
|
||||||
import {
|
import {
|
||||||
AccountsSelectList,
|
AccountsSelectList,
|
||||||
ContactSelecetList,
|
CustomerSelectField,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Icon,
|
Icon,
|
||||||
InputPrependButton,
|
InputPrependButton,
|
||||||
@@ -134,8 +134,8 @@ function PaymentReceiveHeaderFields({
|
|||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name={'customer_id'} />}
|
helperText={<ErrorMessage name={'customer_id'} />}
|
||||||
>
|
>
|
||||||
<ContactSelecetList
|
<CustomerSelectField
|
||||||
contactsList={customers}
|
contacts={customers}
|
||||||
selectedContactId={value}
|
selectedContactId={value}
|
||||||
defaultSelectText={<T id={'select_customer_account'} />}
|
defaultSelectText={<T id={'select_customer_account'} />}
|
||||||
onContactSelected={(customer) => {
|
onContactSelected={(customer) => {
|
||||||
@@ -147,6 +147,7 @@ function PaymentReceiveHeaderFields({
|
|||||||
buttonProps={{
|
buttonProps={{
|
||||||
elementRef: (ref) => (customerFieldRef.current = ref),
|
elementRef: (ref) => (customerFieldRef.current = ref),
|
||||||
}}
|
}}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { FastField, ErrorMessage } from 'formik';
|
|||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import {
|
import {
|
||||||
AccountsSelectList,
|
AccountsSelectList,
|
||||||
ContactSelecetList,
|
CustomerSelectField,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
Icon,
|
Icon,
|
||||||
InputPrependButton,
|
InputPrependButton,
|
||||||
@@ -88,14 +88,15 @@ function ReceiptFormHeader({
|
|||||||
intent={inputIntent({ error, touched })}
|
intent={inputIntent({ error, touched })}
|
||||||
helperText={<ErrorMessage name={'customer_id'} />}
|
helperText={<ErrorMessage name={'customer_id'} />}
|
||||||
>
|
>
|
||||||
<ContactSelecetList
|
<CustomerSelectField
|
||||||
contactsList={customers}
|
contacts={customers}
|
||||||
selectedContactId={value}
|
selectedContactId={value}
|
||||||
defaultSelectText={<T id={'select_customer_account'} />}
|
defaultSelectText={<T id={'select_customer_account'} />}
|
||||||
onContactSelected={(contact) => {
|
onContactSelected={(contact) => {
|
||||||
form.setFieldValue('customer_id', contact.id);
|
form.setFieldValue('customer_id', contact.id);
|
||||||
}}
|
}}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
@@ -129,6 +130,7 @@ function ReceiptFormHeader({
|
|||||||
ACCOUNT_TYPE.BANK,
|
ACCOUNT_TYPE.BANK,
|
||||||
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
|
||||||
]}
|
]}
|
||||||
|
allowCreate={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,42 +9,43 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
import styled from 'styled-components';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CLASSES } from 'common/classes';
|
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
import { Icon } from 'components';
|
import { Icon } from 'components';
|
||||||
import { useVendorFormContext } from './VendorFormProvider';
|
import { useVendorFormContext } from './VendorFormProvider';
|
||||||
|
|
||||||
|
import { safeInvoke } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor floating actions bar.
|
* Vendor floating actions bar.
|
||||||
*/
|
*/
|
||||||
export default function VendorFloatingActions() {
|
export default function VendorFloatingActions({ onCancel }) {
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { resetForm, isSubmitting, submitForm } = useFormikContext();
|
const { resetForm, isSubmitting, submitForm } = useFormikContext();
|
||||||
|
|
||||||
// Vendor form context.
|
// Vendor form context.
|
||||||
const { isNewMode, setSubmitPayload } = useVendorFormContext();
|
const { isNewMode, setSubmitPayload } = useVendorFormContext();
|
||||||
|
|
||||||
// History.
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
// Handle the submit button.
|
// Handle the submit button.
|
||||||
const handleSubmitBtnClick = (event) => {
|
const handleSubmitBtnClick = (event) => {
|
||||||
setSubmitPayload({ noRedirect: false, });
|
setSubmitPayload({ noRedirect: false });
|
||||||
submitForm();
|
submitForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle the submit & new button click.
|
// Handle the submit & new button click.
|
||||||
const handleSubmitAndNewClick = (event) => {
|
const handleSubmitAndNewClick = (event) => {
|
||||||
submitForm();
|
submitForm();
|
||||||
setSubmitPayload({ noRedirect: true, });
|
setSubmitPayload({ noRedirect: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle cancel button click.
|
// Handle cancel button click.
|
||||||
const handleCancelBtnClick = (event) => {
|
const handleCancelBtnClick = (event) => {
|
||||||
history.goBack();
|
safeInvoke(onCancel, event);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle clear button click.
|
// Handle clear button click.
|
||||||
@@ -56,7 +57,7 @@ export default function VendorFloatingActions() {
|
|||||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{/* ----------- Save and New ----------- */}
|
{/* ----------- Save and New ----------- */}
|
||||||
<Button
|
<SaveButton
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
@@ -101,3 +102,7 @@ export default function VendorFloatingActions() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SaveButton = styled(Button)`
|
||||||
|
min-width: 100px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import React, { useMemo, useEffect } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import moment from 'moment';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import { FormattedMessage as T } from 'components';
|
|
||||||
import AppToaster from 'components/AppToaster';
|
import AppToaster from 'components/AppToaster';
|
||||||
import {
|
import {
|
||||||
CreateVendorFormSchema,
|
CreateVendorFormSchema,
|
||||||
@@ -19,56 +16,27 @@ import VendorFormAfterPrimarySection from './VendorFormAfterPrimarySection';
|
|||||||
import VendorTabs from './VendorsTabs';
|
import VendorTabs from './VendorsTabs';
|
||||||
import VendorFloatingActions from './VendorFloatingActions';
|
import VendorFloatingActions from './VendorFloatingActions';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
|
||||||
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
|
||||||
|
|
||||||
import { useVendorFormContext } from './VendorFormProvider';
|
import { useVendorFormContext } from './VendorFormProvider';
|
||||||
import { compose, transformToForm } from 'utils';
|
import { compose, transformToForm, safeInvoke } from 'utils';
|
||||||
|
|
||||||
const defaultInitialValues = {
|
import { defaultInitialValues } from './utils';
|
||||||
salutation: '',
|
|
||||||
first_name: '',
|
|
||||||
last_name: '',
|
|
||||||
company_name: '',
|
|
||||||
display_name: '',
|
|
||||||
|
|
||||||
email: '',
|
import 'style/pages/Vendors/Form.scss';
|
||||||
work_phone: '',
|
|
||||||
personal_phone: '',
|
|
||||||
website: '',
|
|
||||||
note: '',
|
|
||||||
active: true,
|
|
||||||
|
|
||||||
billing_address_country: '',
|
|
||||||
billing_address_1: '',
|
|
||||||
billing_address_2: '',
|
|
||||||
billing_address_city: '',
|
|
||||||
billing_address_state: '',
|
|
||||||
billing_address_postcode: '',
|
|
||||||
billing_address_phone: '',
|
|
||||||
|
|
||||||
shipping_address_country: '',
|
|
||||||
shipping_address_1: '',
|
|
||||||
shipping_address_2: '',
|
|
||||||
shipping_address_city: '',
|
|
||||||
shipping_address_state: '',
|
|
||||||
shipping_address_postcode: '',
|
|
||||||
shipping_address_phone: '',
|
|
||||||
|
|
||||||
opening_balance: '',
|
|
||||||
currency_code: '',
|
|
||||||
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor form.
|
* Vendor form.
|
||||||
*/
|
*/
|
||||||
function VendorForm({
|
function VendorFormFormik({
|
||||||
// #withDashboardActions
|
|
||||||
changePageTitle,
|
|
||||||
|
|
||||||
// #withCurrentOrganization
|
// #withCurrentOrganization
|
||||||
organization: { base_currency },
|
organization: { base_currency },
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
onSubmitSuccess,
|
||||||
|
onSubmitError,
|
||||||
|
onCancel,
|
||||||
|
className,
|
||||||
}) {
|
}) {
|
||||||
// Vendor form context.
|
// Vendor form context.
|
||||||
const {
|
const {
|
||||||
@@ -82,11 +50,6 @@ function VendorForm({
|
|||||||
isNewMode,
|
isNewMode,
|
||||||
} = useVendorFormContext();
|
} = useVendorFormContext();
|
||||||
|
|
||||||
// const isNewMode = !vendorId;
|
|
||||||
|
|
||||||
// History context.
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initial values in create and edit mode.
|
* Initial values in create and edit mode.
|
||||||
*/
|
*/
|
||||||
@@ -101,14 +64,13 @@ function VendorForm({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Handles the form submit.
|
// Handles the form submit.
|
||||||
const handleFormSubmit = (
|
const handleFormSubmit = (values, form) => {
|
||||||
values,
|
const { setSubmitting, resetForm } = form;
|
||||||
{ setSubmitting, resetForm, setErrors },
|
|
||||||
) => {
|
|
||||||
const requestForm = { ...values };
|
const requestForm = { ...values };
|
||||||
|
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
const onSuccess = () => {
|
const onSuccess = (response) => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get(
|
message: intl.get(
|
||||||
isNewMode
|
isNewMode
|
||||||
@@ -121,16 +83,15 @@ function VendorForm({
|
|||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
resetForm();
|
resetForm();
|
||||||
|
|
||||||
if (!submitPayload.noRedirect) {
|
safeInvoke(onSubmitSuccess, values, form, submitPayload, response);
|
||||||
history.push('/vendors');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onError = () => {
|
const onError = () => {
|
||||||
setSubmitPayload(false);
|
setSubmitPayload(false);
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
|
||||||
|
|
||||||
|
safeInvoke(onSubmitError, values, form, submitPayload);
|
||||||
|
};
|
||||||
if (isNewMode) {
|
if (isNewMode) {
|
||||||
createVendorMutate(requestForm).then(onSuccess).catch(onError);
|
createVendorMutate(requestForm).then(onSuccess).catch(onError);
|
||||||
} else {
|
} else {
|
||||||
@@ -139,7 +100,13 @@ function VendorForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_VENDOR)}>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
CLASSES.PAGE_FORM,
|
||||||
|
CLASSES.PAGE_FORM_VENDOR,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Formik
|
<Formik
|
||||||
validationSchema={
|
validationSchema={
|
||||||
isNewMode ? CreateVendorFormSchema : EditVendorFormSchema
|
isNewMode ? CreateVendorFormSchema : EditVendorFormSchema
|
||||||
@@ -160,14 +127,11 @@ function VendorForm({
|
|||||||
<VendorTabs vendor={vendorId} />
|
<VendorTabs vendor={vendorId} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VendorFloatingActions />
|
<VendorFloatingActions onCancel={onCancel} />
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(withCurrentOrganization())(VendorFormFormik);
|
||||||
withDashboardActions,
|
|
||||||
withCurrentOrganization(),
|
|
||||||
)(VendorForm);
|
|
||||||
@@ -1,26 +1,69 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import 'style/pages/Vendors/PageForm.scss';
|
import 'style/pages/Vendors/PageForm.scss';
|
||||||
|
|
||||||
import { DashboardCard } from 'components';
|
import { DashboardCard, DashboardInsider } from 'components';
|
||||||
import VendorFrom from './VendorForm';
|
import VendorFormFormik from './VendorFormFormik';
|
||||||
|
|
||||||
import { VendorFormProvider } from './VendorFormProvider';
|
import { VendorFormProvider, useVendorFormContext } from './VendorFormProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor form page loading wrapper.
|
||||||
|
* @returns {JSX}
|
||||||
|
*/
|
||||||
|
function VendorFormPageLoading({ children }) {
|
||||||
|
const { isFormLoading } = useVendorFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VendorDashboardInsider loading={isFormLoading}>
|
||||||
|
{children}
|
||||||
|
</VendorDashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor form page.
|
* Vendor form page.
|
||||||
*/
|
*/
|
||||||
function VendorFormPage() {
|
export default function VendorFormPage() {
|
||||||
|
const history = useHistory();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
|
// Handle the form submit success.
|
||||||
|
const handleSubmitSuccess = (values, formArgs, submitPayload) => {
|
||||||
|
if (!submitPayload.noRedirect) {
|
||||||
|
history.push('/vendors');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Handle the form cancel button click.
|
||||||
|
const handleFormCancel = () => {
|
||||||
|
history.goBack();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VendorFormProvider vendorId={id}>
|
<VendorFormProvider vendorId={id}>
|
||||||
<DashboardCard page>
|
<VendorFormPageLoading>
|
||||||
<VendorFrom />
|
<DashboardCard page>
|
||||||
</DashboardCard>
|
<VendorFormPageFormik
|
||||||
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
/>
|
||||||
|
</DashboardCard>
|
||||||
|
</VendorFormPageLoading>
|
||||||
</VendorFormProvider>
|
</VendorFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VendorFormPage;
|
const VendorFormPageFormik = styled(VendorFormFormik)`
|
||||||
|
.page-form {
|
||||||
|
&__floating-actions {
|
||||||
|
margin-left: -40px;
|
||||||
|
margin-right: -40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const VendorDashboardInsider = styled(DashboardInsider)`
|
||||||
|
padding-bottom: 64px;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
useVendor,
|
useVendor,
|
||||||
useContact,
|
useContact,
|
||||||
useCurrencies,
|
useCurrencies,
|
||||||
useCustomer,
|
|
||||||
useCreateVendor,
|
useCreateVendor,
|
||||||
useEditVendor,
|
useEditVendor,
|
||||||
} from 'hooks/query';
|
} from 'hooks/query';
|
||||||
@@ -30,11 +29,10 @@ function VendorFormProvider({ vendorId, ...props }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Handle fetch contact duplicate details.
|
// Handle fetch contact duplicate details.
|
||||||
const {
|
const { data: contactDuplicate, isLoading: isContactLoading } = useContact(
|
||||||
data: contactDuplicate,
|
contactId,
|
||||||
isLoading: isContactLoading,
|
{ enabled: !!contactId },
|
||||||
} = useContact(contactId, { enabled: !!contactId });
|
);
|
||||||
|
|
||||||
// Create and edit vendor mutations.
|
// Create and edit vendor mutations.
|
||||||
const { mutateAsync: createVendorMutate } = useCreateVendor();
|
const { mutateAsync: createVendorMutate } = useCreateVendor();
|
||||||
const { mutateAsync: editVendorMutate } = useEditVendor();
|
const { mutateAsync: editVendorMutate } = useEditVendor();
|
||||||
@@ -45,27 +43,25 @@ function VendorFormProvider({ vendorId, ...props }) {
|
|||||||
// determines whether the form new or duplicate mode.
|
// determines whether the form new or duplicate mode.
|
||||||
const isNewMode = contactId || !vendorId;
|
const isNewMode = contactId || !vendorId;
|
||||||
|
|
||||||
|
const isFormLoading =
|
||||||
|
isVendorLoading || isContactLoading || isCurrenciesLoading;
|
||||||
|
|
||||||
const provider = {
|
const provider = {
|
||||||
vendorId,
|
vendorId,
|
||||||
currencies,
|
currencies,
|
||||||
vendor,
|
vendor,
|
||||||
contactDuplicate: { ...omit(contactDuplicate, ['opening_balance_at']) },
|
contactDuplicate: { ...omit(contactDuplicate, ['opening_balance_at']) },
|
||||||
submitPayload,
|
submitPayload,
|
||||||
|
|
||||||
isNewMode,
|
isNewMode,
|
||||||
|
isFormLoading,
|
||||||
|
|
||||||
createVendorMutate,
|
createVendorMutate,
|
||||||
editVendorMutate,
|
editVendorMutate,
|
||||||
setSubmitPayload,
|
setSubmitPayload,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <VendorFormContext.Provider value={provider} {...props} />;
|
||||||
<DashboardInsider
|
|
||||||
loading={isVendorLoading || isContactLoading || isCurrenciesLoading}
|
|
||||||
name={'vendor-form'}
|
|
||||||
>
|
|
||||||
<VendorFormContext.Provider value={provider} {...props} />
|
|
||||||
</DashboardInsider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useVendorFormContext = () => React.useContext(VendorFormContext);
|
const useVendorFormContext = () => React.useContext(VendorFormContext);
|
||||||
|
|||||||
36
src/containers/Vendors/VendorForm/utils.js
Normal file
36
src/containers/Vendors/VendorForm/utils.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
export const defaultInitialValues = {
|
||||||
|
salutation: '',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
company_name: '',
|
||||||
|
display_name: '',
|
||||||
|
|
||||||
|
email: '',
|
||||||
|
work_phone: '',
|
||||||
|
personal_phone: '',
|
||||||
|
website: '',
|
||||||
|
note: '',
|
||||||
|
active: true,
|
||||||
|
|
||||||
|
billing_address_country: '',
|
||||||
|
billing_address_1: '',
|
||||||
|
billing_address_2: '',
|
||||||
|
billing_address_city: '',
|
||||||
|
billing_address_state: '',
|
||||||
|
billing_address_postcode: '',
|
||||||
|
billing_address_phone: '',
|
||||||
|
|
||||||
|
shipping_address_country: '',
|
||||||
|
shipping_address_1: '',
|
||||||
|
shipping_address_2: '',
|
||||||
|
shipping_address_city: '',
|
||||||
|
shipping_address_state: '',
|
||||||
|
shipping_address_postcode: '',
|
||||||
|
shipping_address_phone: '',
|
||||||
|
|
||||||
|
opening_balance: '',
|
||||||
|
currency_code: '',
|
||||||
|
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
|
};
|
||||||
157
src/style/pages/Customers/Form.scss
Normal file
157
src/style/pages/Customers/Form.scss
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
@import '../../Base.scss';
|
||||||
|
|
||||||
|
.page-form--customer {
|
||||||
|
$self: '.page-form';
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
#{$self}__header {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
#{$self}__primary-section {
|
||||||
|
padding: 10px 0 0;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-bottom: 1px solid #e4e4e4;
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-form-group {
|
||||||
|
max-width: 500px;
|
||||||
|
|
||||||
|
.bp3-control {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bp3-inline {
|
||||||
|
.bp3-label {
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bp3-form-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group--contact_name {
|
||||||
|
max-width: 600px;
|
||||||
|
|
||||||
|
.bp3-control-group > * {
|
||||||
|
flex-shrink: unset;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
&.input-group--salutation-list {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
&.input-group--first-name,
|
||||||
|
&.input-group--last-name {
|
||||||
|
width: 37%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-form-group {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab-panel {
|
||||||
|
margin-top: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group--phone-number {
|
||||||
|
.bp3-control-group > * {
|
||||||
|
flex-shrink: unset;
|
||||||
|
padding-right: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$self}__tabs {
|
||||||
|
margin-top: 20px;
|
||||||
|
max-width: 1000px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
// Tab panels.
|
||||||
|
.tab-panel {
|
||||||
|
&--address {
|
||||||
|
.bp3-form-group {
|
||||||
|
max-width: 440px;
|
||||||
|
|
||||||
|
&.bp3-inline {
|
||||||
|
.bp3-label {
|
||||||
|
min-width: 145px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-form-content {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.bp3-input {
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&--note {
|
||||||
|
.form-group--note {
|
||||||
|
.bp3-form-group {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone-container {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tabs {
|
||||||
|
.bp3-tab-list {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> *:not(:last-child) {
|
||||||
|
margin-right: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bp3-large > .bp3-tab {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #555;
|
||||||
|
|
||||||
|
&[aria-selected='true'],
|
||||||
|
&:not([aria-disabled='true']):hover {
|
||||||
|
color: $pt-link-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,4 @@
|
|||||||
|
|
||||||
body.page-item-new,
|
|
||||||
body.page-item-edit{
|
|
||||||
|
|
||||||
.dashboard__footer{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard__insider--item-form{
|
|
||||||
padding-bottom: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-form--item {
|
.page-form--item {
|
||||||
$self: '.page-form';
|
$self: '.page-form';
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -85,18 +73,14 @@ body.page-item-edit{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#{$self}__floating-actions {
|
#{$self}__floating-actions {
|
||||||
margin-left: -40px;
|
// margin-left: -40px;
|
||||||
margin-right: -40px;
|
// margin-right: -40px;
|
||||||
|
|
||||||
.form-group--active {
|
.form-group--active {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-left: 40px;
|
margin-left: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn--submit{
|
|
||||||
min-width: 65px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp3-tooltip-indicator {
|
.bp3-tooltip-indicator {
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
|
|
||||||
body.page-invoice-new,
|
body.page-invoice-new,
|
||||||
body.page-invoice-edit{
|
body.page-invoice-edit {
|
||||||
|
.dashboard__footer {
|
||||||
.dashboard__footer{
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard__insider--invoice-form{
|
.dashboard__insider--invoice-form {
|
||||||
padding-bottom: 64px;
|
padding-bottom: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +27,6 @@ body.page-invoice-edit{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bp3-form-group {
|
.bp3-form-group {
|
||||||
|
|
||||||
&.bp3-inline {
|
&.bp3-inline {
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,6 @@
|
|||||||
@import '../../Base.scss';
|
@import '../../Base.scss';
|
||||||
|
|
||||||
|
.page-form--vendor {
|
||||||
body.page-customer-new,
|
|
||||||
body.page-customer-edit{
|
|
||||||
|
|
||||||
.dashboard__footer{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard__insider--customer-form{
|
|
||||||
padding-bottom: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-form--customer {
|
|
||||||
$self: '.page-form';
|
$self: '.page-form';
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
@@ -169,7 +156,7 @@ body.page-customer-edit{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#{$self}__floating-actions {
|
#{$self}__floating-actions {
|
||||||
margin-left: -40px;
|
// margin-left: -40px;
|
||||||
margin-right: -40px;
|
// margin-right: -40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,166 +10,4 @@ body.page-vendor-edit{
|
|||||||
|
|
||||||
.dashboard__insider--vendor-form{
|
.dashboard__insider--vendor-form{
|
||||||
padding-bottom: 64px;
|
padding-bottom: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.page-form--vendor {
|
|
||||||
$self: '.page-form';
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
#{$self}__header {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
#{$self}__primary-section {
|
|
||||||
padding: 10px 0 0;
|
|
||||||
margin: 0 0 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
border-bottom: 1px solid #e4e4e4;
|
|
||||||
max-width: 1000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-form-group {
|
|
||||||
max-width: 500px;
|
|
||||||
|
|
||||||
.bp3-control {
|
|
||||||
margin-top: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.bp3-inline {
|
|
||||||
.bp3-label {
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bp3-form-content {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group--contact_name {
|
|
||||||
max-width: 600px;
|
|
||||||
|
|
||||||
.bp3-control-group > * {
|
|
||||||
flex-shrink: unset;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
&.input-group--salutation-list {
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
&.input-group--first-name,
|
|
||||||
&.input-group--last-name {
|
|
||||||
width: 37%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-form-group {
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-tab-panel {
|
|
||||||
margin-top: 26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group--phone-number {
|
|
||||||
.bp3-control-group > * {
|
|
||||||
flex-shrink: unset;
|
|
||||||
padding-right: 5px;
|
|
||||||
padding-left: 5px;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
&:last-child {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#{$self}__tabs {
|
|
||||||
margin-top: 20px;
|
|
||||||
max-width: 1000px;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #888;
|
|
||||||
margin-bottom: 1.2rem;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
// Tab panels.
|
|
||||||
.tab-panel {
|
|
||||||
&--address {
|
|
||||||
.bp3-form-group {
|
|
||||||
max-width: 440px;
|
|
||||||
|
|
||||||
&.bp3-inline {
|
|
||||||
.bp3-label {
|
|
||||||
min-width: 145px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-form-content {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.bp3-input {
|
|
||||||
max-width: 100%;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&--note {
|
|
||||||
.form-group--note {
|
|
||||||
.bp3-form-group {
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone-container {
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp3-tabs {
|
|
||||||
.bp3-tab-list {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 2px;
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> *:not(:last-child) {
|
|
||||||
margin-right: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.bp3-large > .bp3-tab {
|
|
||||||
font-size: 15px;
|
|
||||||
color: #555;
|
|
||||||
|
|
||||||
&[aria-selected='true'],
|
|
||||||
&:not([aria-disabled='true']):hover {
|
|
||||||
color: $pt-link-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#{$self}__floating-actions {
|
|
||||||
margin-left: -40px;
|
|
||||||
margin-right: -40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user