feat(webapp): refactor customer and vendor select component

This commit is contained in:
Ahmed Bouhuolia
2023-06-22 17:26:33 +02:00
parent ca4d543482
commit eadaac30d6
16 changed files with 495 additions and 607 deletions

View File

@@ -1,119 +0,0 @@
// @ts-nocheck
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 '@/constants/classes';
import {
itemPredicate,
handleContactRenderer,
createNewItemRenderer,
createNewItemFromQuery,
} from './utils';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
function CustomerSelectFieldRoot({
// #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 const CustomerSelectField = R.compose(withDrawerActions)(
CustomerSelectFieldRoot,
);

View File

@@ -0,0 +1,48 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import { createNewItemFromQuery, createNewItemRenderer } from './utils';
import { FSelect } from '../Forms';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
/**
* Customer select field.
* @returns {React.ReactNode}
*/
function CustomerSelectRoot({
// #withDrawerActions
openDrawer,
// #ownProps
items,
allowCreate,
...props
}) {
// Maybe inject create new item props to suggest component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
// Handles the create item click.
const handleCreateItemClick = () => {
openDrawer(DRAWERS.QUICK_CREATE_CUSTOMER);
};
return (
<FSelect
items={items}
textAccessor={'display_name'}
labelAccessor={'code'}
valueAccessor={'id'}
popoverProps={{ minimal: true, usePortal: true, inline: false }}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
onCreateItemSelect={handleCreateItemClick}
{...props}
/>
);
}
export const CustomersSelect = R.compose(withDrawerActions)(CustomerSelectRoot);

View File

@@ -1,3 +1,3 @@
// @ts-nocheck // @ts-nocheck
export * from './CustomerSelectField';
export * from './CustomerDrawerLink'; export * from './CustomerDrawerLink';
export * from './CustomersSelect';

View File

@@ -1,118 +0,0 @@
// @ts-nocheck
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 '@/constants/classes';
import {
itemPredicate,
handleContactRenderer,
createNewItemFromQuery,
createNewItemRenderer,
} from './utils';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
function VendorSelectFieldRoot({
// #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 const VendorSelectField = R.compose(withDrawerActions)(
VendorSelectFieldRoot,
);

View File

@@ -0,0 +1,49 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { createNewItemFromQuery, createNewItemRenderer } from './utils';
import { FSelect } from '../Forms';
import { DRAWERS } from '@/constants/drawers';
/**
* Vendor select.
* @returns {React.ReactNode}
*/
function VendorsSelectRoot({
// #withDrawerActions
openDrawer,
// #ownProps
items,
allowCreate,
...restProps
}) {
// Maybe inject create new item props to suggest component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
// Handles the create item click.
const handleCreateItemClick = () => {
openDrawer(DRAWERS.QUICK_WRITE_VENDOR);
};
return (
<FSelect
items={items}
textAccessor={'display_name'}
labelAccessor={'code'}
valueAccessor={'id'}
popoverProps={{ minimal: true, usePortal: true, inline: false }}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
onCreateItemSelect={handleCreateItemClick}
{...restProps}
/>
);
}
export const VendorsSelect = R.compose(withDrawerActions)(VendorsSelectRoot);

View File

@@ -1,3 +1,3 @@
// @ts-nocheck // @ts-nocheck
export * from './VendorDrawerLink' export * from './VendorDrawerLink'
export * from './VendorSelectField' export * from './VendorsSelect';

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { InputGroup, FormGroup, Position, Classes } from '@blueprintjs/core'; import { InputGroup, FormGroup, Position, Classes } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { FormattedMessage as T } from '@/components'; import { CustomersSelect, FormattedMessage as T } from '@/components';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { import {
@@ -121,33 +121,35 @@ export default function ExpenseFormHeader() {
)} )}
</FastField> </FastField>
<FastField {/* ----------- Customer ----------- */}
name={'customer_id'} <ExpenseFormCustomerSelect />
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer'} />}
className={classNames('form-group--select-list', Classes.FILL)}
labelInfo={<Hint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'assign_to_customer'} />}
inline={true}
>
<CustomerSelectField
contacts={customers}
selectedContactId={value}
defaultSelectText={<T id={'select_customer_account'} />}
onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id);
}}
allowCreate={true}
popoverFill={true}
/>
</FormGroup>
)}
</FastField>
</div> </div>
); );
} }
/**
* Customer select field of expense form.
* @returns {React.ReactNode}
*/
function ExpenseFormCustomerSelect() {
const { customers } = useExpenseFormContext();
return (
<FormGroup
label={<T id={'customer'} />}
labelInfo={<Hint />}
inline={true}
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
<CustomersSelect
name={'customer_id'}
items={customers}
placeholder={<T id={'select_customer_account'} />}
allowCreate={true}
popoverFill={true}
/>
</FormGroup>
);
}

View File

@@ -15,11 +15,10 @@ import {
FMoneyInputGroup, FMoneyInputGroup,
InputPrependText, InputPrependText,
FormattedMessage as T, FormattedMessage as T,
FieldRequiredHint,
CustomerSelectField,
Stack, Stack,
CustomersSelect,
} from '@/components'; } from '@/components';
import { inputIntent, momentFormatter } from '@/utils'; import { momentFormatter } from '@/utils';
import { useProjectFormContext } from './ProjectFormProvider'; import { useProjectFormContext } from './ProjectFormProvider';
/** /**
@@ -27,9 +26,6 @@ import { useProjectFormContext } from './ProjectFormProvider';
* @returns * @returns
*/ */
function ProjectFormFields() { function ProjectFormFields() {
// project form dialog context.
const { customers } = useProjectFormContext();
// Formik context. // Formik context.
const { values } = useFormikContext(); const { values } = useFormikContext();
@@ -37,26 +33,7 @@ function ProjectFormFields() {
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<Stack spacing={25}> <Stack spacing={25}>
{/*------------ Contact -----------*/} {/*------------ Contact -----------*/}
<FastField name={'contact_id'}> <ProjectFormCustomerSelect />
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={intl.get('projects.dialog.contact')}
className={classNames('form-group--select-list', Classes.FILL)}
intent={inputIntent({ error, touched })}
>
<CustomerSelectField
contacts={customers}
selectedContactId={value}
defaultSelectText={'Find or create a customer'}
onContactSelected={(customer) => {
form.setFieldValue('contact_id', customer.id);
}}
allowCreate={true}
popoverFill={true}
/>
</FormGroup>
)}
</FastField>
{/*------------ Project Name -----------*/} {/*------------ Project Name -----------*/}
<FFormGroup <FFormGroup
@@ -113,4 +90,21 @@ function ProjectFormFields() {
); );
} }
function ProjectFormCustomerSelect() {
// project form dialog context.
const { customers } = useProjectFormContext();
return (
<FormGroup name={'contact_id'} label={intl.get('projects.dialog.contact')}>
<CustomersSelect
name={'contact_id'}
items={customers}
placeholder={'Find or create a customer'}
allowCreate={true}
popoverFill={true}
/>
</FormGroup>
);
}
export default ProjectFormFields; export default ProjectFormFields;

View File

@@ -3,17 +3,17 @@ import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormGroup, InputGroup, Classes, Position } from '@blueprintjs/core'; import { FormGroup, InputGroup, Classes, Position } from '@blueprintjs/core';
import { FastField, ErrorMessage } from 'formik'; import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FeatureCan, FormattedMessage as T } from '@/components'; import { FeatureCan, FormattedMessage as T } from '@/components';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { import {
FFormGroup, FFormGroup,
VendorSelectField,
FieldRequiredHint, FieldRequiredHint,
Icon, Icon,
VendorDrawerLink, VendorDrawerLink,
VendorsSelect,
} from '@/components'; } from '@/components';
import { useBillFormContext } from './BillFormProvider'; import { useBillFormContext } from './BillFormProvider';
@@ -43,43 +43,7 @@ function BillFormHeader() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------- Vendor name ------ */} {/* ------- Vendor name ------ */}
<FastField <BillFormVendorField />
name={'vendor_id'}
vendors={vendors}
shouldUpdate={vendorsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FFormGroup
name={'vendor_id'}
label={<T id={'vendor_name'} />}
inline={true}
className={classNames(
'form-group--vendor-name',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />}
>
<VendorSelectField
contacts={vendors}
selectedContactId={value}
defaultSelectText={<T id={'select_vender_account'} />}
onContactSelected={(contact) => {
form.setFieldValue('vendor_id', contact.id);
form.setFieldValue('currency_code', contact?.currency_code);
}}
popoverFill={true}
allowCreate={true}
/>
{value && (
<VendorButtonLink vendorId={value}>
<T id={'view_vendor_details'} />
</VendorButtonLink>
)}
</FFormGroup>
)}
</FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<BillExchangeRateInputField <BillExchangeRateInputField
@@ -190,6 +154,43 @@ function BillFormHeader() {
); );
} }
/**
* Vendor select field of bill form.
* @returns {JSX.Element}
*/
function BillFormVendorField() {
const { values, setFieldValue } = useFormikContext();
const { vendors } = useBillFormContext();
return (
<FFormGroup
name={'vendor_id'}
label={<T id={'vendor_name'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
vendors={vendors}
fastField={true}
shouldUpdate={vendorsFieldShouldUpdate}
>
<VendorsSelect
name={'vendor_id'}
items={vendors}
placeholder={<T id={'select_vender_account'} />}
onItemChange={(contact) => {
setFieldValue('vendor_id', contact.id);
setFieldValue('currency_code', contact?.currency_code);
}}
allowCreate={true}
/>
{values.vendor_id && (
<VendorButtonLink vendorId={values.vendor_id}>
<T id={'view_vendor_details'} />
</VendorButtonLink>
)}
</FFormGroup>
);
}
export default compose(withDialogActions)(BillFormHeader); export default compose(withDialogActions)(BillFormHeader);
const VendorButtonLink = styled(VendorDrawerLink)` const VendorButtonLink = styled(VendorDrawerLink)`

View File

@@ -9,17 +9,17 @@ import {
ControlGroup, ControlGroup,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage } from 'formik'; import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { import {
FFormGroup, FFormGroup,
VendorSelectField,
FieldRequiredHint, FieldRequiredHint,
InputPrependButton, InputPrependButton,
Icon, Icon,
FormattedMessage as T, FormattedMessage as T,
VendorDrawerLink, VendorDrawerLink,
VendorsSelect,
} from '@/components'; } from '@/components';
import { import {
vendorsFieldShouldUpdate, vendorsFieldShouldUpdate,
@@ -51,9 +51,6 @@ function VendorCreditNoteFormHeaderFields({
vendorcreditNumberPrefix, vendorcreditNumberPrefix,
vendorcreditNextNumber, vendorcreditNextNumber,
}) { }) {
// Vendor Credit form context.
const { vendors } = useVendorCreditNoteFormContext();
// Handle vendor credit number changing. // Handle vendor credit number changing.
const handleVendorCreditNumberChange = () => { const handleVendorCreditNumberChange = () => {
openDialog('vendor-credit-form'); openDialog('vendor-credit-form');
@@ -81,39 +78,7 @@ function VendorCreditNoteFormHeaderFields({
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Vendor name ----------- */} {/* ----------- Vendor name ----------- */}
<FastField <VendorCreditFormVendorSelect />
name={'vendor_id'}
vendors={vendors}
shouldUpdate={vendorsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FFormGroup
name={'vendor_id'}
label={<T id={'vendor_name'} />}
inline={true}
className={classNames(CLASSES.FILL, 'form-group--vendor')}
labelInfo={<FieldRequiredHint />}
>
<VendorSelectField
contacts={vendors}
selectedContactId={value}
defaultSelectText={<T id={'select_vender_account'} />}
onContactSelected={(contact) => {
form.setFieldValue('vendor_id', contact.id);
form.setFieldValue('currency_code', contact?.currency_code);
}}
popoverFill={true}
allowCreate={true}
/>
{value && (
<VendorButtonLink vendorId={value}>
<T id={'view_vendor_details'} />
</VendorButtonLink>
)}
</FFormGroup>
)}
</FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<VendorCreditNoteExchangeRateInputField <VendorCreditNoteExchangeRateInputField
@@ -205,6 +170,45 @@ function VendorCreditNoteFormHeaderFields({
); );
} }
/**
* Vendor select field of vendor credit form.
* @returns {React.ReactNode}
*/
function VendorCreditFormVendorSelect() {
const { values, setFieldValue } = useFormikContext();
// Vendor Credit form context.
const { vendors } = useVendorCreditNoteFormContext();
return (
<FFormGroup
name={'vendor_id'}
label={<T id={'vendor_name'} />}
inline={true}
vendors={vendors}
shouldUpdate={vendorsFieldShouldUpdate}
labelInfo={<FieldRequiredHint />}
>
<VendorsSelect
name={'vendor_id'}
items={vendors}
placeholder={<T id={'select_vender_account'} />}
onItemChange={(contact) => {
setFieldValue('vendor_id', contact.id);
setFieldValue('currency_code', contact?.currency_code);
}}
popoverFill={true}
allowCreate={true}
/>
{values.vendor_id && (
<VendorButtonLink vendorId={values.vendor_id}>
<T id={'view_vendor_details'} />
</VendorButtonLink>
)}
</FFormGroup>
);
}
export default compose( export default compose(
withDialogActions, withDialogActions,
withSettings(({ vendorsCreditNoteSetting }) => ({ withSettings(({ vendorsCreditNoteSetting }) => ({

View File

@@ -12,14 +12,13 @@ import {
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FastField, Field, useFormikContext, ErrorMessage } from 'formik'; import { FastField, Field, useFormikContext, ErrorMessage } from 'formik';
import { FormattedMessage as T } from '@/components'; import { FormattedMessage as T, VendorsSelect } from '@/components';
import { toSafeInteger } from 'lodash'; import { toSafeInteger } from 'lodash';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { import {
FFormGroup, FFormGroup,
AccountsSelect, AccountsSelect,
VendorSelectField,
FieldRequiredHint, FieldRequiredHint,
InputPrependText, InputPrependText,
Money, Money,
@@ -55,8 +54,7 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
} = useFormikContext(); } = useFormikContext();
// Payment made form context. // Payment made form context.
const { vendors, accounts, isNewMode, setPaymentVendorId } = const { accounts } = usePaymentMadeFormContext();
usePaymentMadeFormContext();
// Sumation of payable full-amount. // Sumation of payable full-amount.
const payableFullAmount = useMemo( const payableFullAmount = useMemo(
@@ -82,41 +80,7 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------------ Vendor name ------------ */} {/* ------------ Vendor name ------------ */}
<FastField <PaymentFormVendorSelect />
name={'vendor_id'}
vendors={vendors}
shouldUpdate={vendorsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FFormGroup
name={'vendor_id'}
label={<T id={'vendor_name'} />}
inline={true}
className={classNames('form-group--select-list', Classes.FILL)}
labelInfo={<FieldRequiredHint />}
>
<VendorSelectField
contacts={vendors}
selectedContactId={value}
defaultSelectText={<T id={'select_vender_account'} />}
onContactSelected={(contact) => {
form.setFieldValue('vendor_id', contact.id);
form.setFieldValue('currency_code', contact?.currency_code);
setPaymentVendorId(contact.id);
}}
disabled={!isNewMode}
popoverFill={true}
allowCreate={true}
/>
{value && (
<VendorButtonLink vendorId={value}>
<T id={'view_vendor_details'} />
</VendorButtonLink>
)}
</FFormGroup>
)}
</FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<PaymentMadeExchangeRateInputField <PaymentMadeExchangeRateInputField
@@ -258,6 +222,48 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
); );
} }
/**
* Vendor select field of payment receive form.
* @returns {React.ReactNode}
*/
function PaymentFormVendorSelect() {
// Formik form context.
const { values, setFieldValue } = useFormikContext();
// Payment made form context.
const { vendors, isNewMode, setPaymentVendorId } =
usePaymentMadeFormContext();
return (
<FFormGroup
name={'vendor_id'}
label={<T id={'vendor_name'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
vendors={vendors}
shouldUpdate={vendorsFieldShouldUpdate}
>
<VendorsSelect
name={'vendor_id'}
items={vendors}
placeholder={<T id={'select_vender_account'} />}
onItemChange={(contact) => {
setFieldValue('vendor_id', contact.id);
setFieldValue('currency_code', contact?.currency_code);
setPaymentVendorId(contact.id);
}}
disabled={!isNewMode}
allowCreate={true}
/>
{values.vendor_id && (
<VendorButtonLink vendorId={values.vendor_id}>
<T id={'view_vendor_details'} />
</VendorButtonLink>
)}
</FFormGroup>
);
}
export default compose(withCurrentOrganization())(PaymentMadeFormHeaderFields); export default compose(withCurrentOrganization())(PaymentMadeFormHeaderFields);
const VendorButtonLink = styled(VendorDrawerLink)` const VendorButtonLink = styled(VendorDrawerLink)`

View File

@@ -22,6 +22,7 @@ import {
CustomerDrawerLink, CustomerDrawerLink,
FFormGroup, FFormGroup,
FInputGroup, FInputGroup,
CustomersSelect,
} from '@/components'; } from '@/components';
import { customerNameFieldShouldUpdate } from './utils'; import { customerNameFieldShouldUpdate } from './utils';
@@ -121,49 +122,10 @@ const CreditNoteTransactionNoField = R.compose(
* Credit note form header fields. * Credit note form header fields.
*/ */
export default function CreditNoteFormHeaderFields({}) { export default function CreditNoteFormHeaderFields({}) {
// Credit note form context.
const { customers } = useCreditNoteFormContext();
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */} {/* ----------- Customer name ----------- */}
<FastField <CreditNoteCustomersSelect />
name={'customer_id'}
customers={customers}
shouldUpdate={customerNameFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
inline={true}
className={classNames(
'form-group--customer-name',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'customer_id'} />}
>
<CustomerSelectField
contacts={customers}
selectedContactId={value}
defaultSelectText={<T id={'select_customer_account'} />}
onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id);
form.setFieldValue('currency_code', customer?.currency_code);
}}
popoverFill={true}
allowCreate={true}
/>
{value && (
<CustomerButtonLink customerId={value}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FormGroup>
)}
</FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<CreditNoteExchangeRateInputField <CreditNoteExchangeRateInputField
@@ -216,6 +178,45 @@ export default function CreditNoteFormHeaderFields({}) {
); );
} }
/**
* Customer select field of credit note form.
* @returns {React.ReactNode}
*/
function CreditNoteCustomersSelect() {
// Credit note form context.
const { customers } = useCreditNoteFormContext();
const { setFieldValue, values } = useFormikContext();
return (
<FFormGroup
name={'customer_id'}
label={<T id={'customer_name'} />}
customers={customers}
labelInfo={<FieldRequiredHint />}
inline={true}
fastField={true}
shouldUpdate={customerNameFieldShouldUpdate}
>
<CustomersSelect
name={'customer_id'}
items={customers}
placeholder={<T id={'select_customer_account'} />}
onItemChange={(customer) => {
setFieldValue('customer_id', customer.id);
setFieldValue('currency_code', customer?.currency_code);
}}
popoverFill={true}
allowCreate={true}
/>
{values.customer_id && (
<CustomerButtonLink customerId={values.customer_id}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FFormGroup>
);
}
const CustomerButtonLink = styled(CustomerDrawerLink)` const CustomerButtonLink = styled(CustomerDrawerLink)`
font-size: 11px; font-size: 11px;
margin-top: 6px; margin-top: 6px;

View File

@@ -11,18 +11,18 @@ import {
ControlGroup, ControlGroup,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage, useFormikContext } from 'formik'; import { FastField, ErrorMessage, useFormikContext, useFormik } from 'formik';
import { import {
FeatureCan, FeatureCan,
FFormGroup, FFormGroup,
FInputGroup, FInputGroup,
FormattedMessage as T, FormattedMessage as T,
CustomerSelectField,
FieldRequiredHint, FieldRequiredHint,
Icon, Icon,
InputPrependButton, InputPrependButton,
CustomerDrawerLink, CustomerDrawerLink,
CustomersSelect,
} from '@/components'; } from '@/components';
import { import {
momentFormatter, momentFormatter,
@@ -129,41 +129,7 @@ export default function EstimateFormHeader() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */} {/* ----------- Customer name ----------- */}
<FastField <EstimateFormCustomerSelect />
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
inline={true}
className={classNames(CLASSES.FILL, 'form-group--customer')}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'customer_id'} />}
>
<CustomerSelectField
contacts={customers}
selectedContactId={value}
defaultSelectText={<T id={'select_customer_account'} />}
onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id);
form.setFieldValue('currency_code', customer?.currency_code);
}}
popoverFill={true}
intent={inputIntent({ error, touched })}
allowCreate={true}
/>
{value && (
<CustomerButtonLink customerId={value}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FormGroup>
)}
</FastField>
{/* ----------- Exchange Rate ----------- */} {/* ----------- Exchange Rate ----------- */}
<EstimateExchangeRateInputField <EstimateExchangeRateInputField
@@ -264,6 +230,45 @@ export default function EstimateFormHeader() {
); );
} }
/**
* Customer select field of estimate form.
* @returns {React.ReactNode}
*/
function EstimateFormCustomerSelect() {
const { setFieldValue, values } = useFormikContext();
const { customers } = useEstimateFormContext();
return (
<FFormGroup
label={<T id={'customer_name'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
fastField={true}
>
<CustomersSelect
name={'customer_id'}
items={customers}
placeholder={<T id={'select_customer_account'} />}
onItemChange={(customer) => {
setFieldValue('customer_id', customer.id);
setFieldValue('currency_code', customer?.currency_code);
}}
popoverFill={true}
allowCreate={true}
fastField={true}
/>
{values.customer_id && (
<CustomerButtonLink customerId={values.customer_id}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FFormGroup>
);
}
const CustomerButtonLink = styled(CustomerDrawerLink)` const CustomerButtonLink = styled(CustomerDrawerLink)`
font-size: 11px; font-size: 11px;
margin-top: 6px; margin-top: 6px;

View File

@@ -10,7 +10,7 @@ import {
ControlGroup, ControlGroup,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage, useFormikContext } from 'formik'; import { FastField, ErrorMessage, useFormikContext, useFormik } from 'formik';
import * as R from 'ramda'; import * as R from 'ramda';
import { import {
@@ -19,12 +19,12 @@ import {
Col, Col,
Row, Row,
CustomerDrawerLink, CustomerDrawerLink,
CustomerSelectField,
FieldRequiredHint, FieldRequiredHint,
Icon, Icon,
InputPrependButton, InputPrependButton,
FeatureCan, FeatureCan,
FInputGroup, FInputGroup,
CustomersSelect,
} from '@/components'; } from '@/components';
import { import {
momentFormatter, momentFormatter,
@@ -134,49 +134,13 @@ InvoiceFormInvoiceNumberField.displayName = 'InvoiceFormInvoiceNumberField';
*/ */
export default function InvoiceFormHeaderFields() { export default function InvoiceFormHeaderFields() {
// Invoice form context. // Invoice form context.
const { customers, projects } = useInvoiceFormContext(); const { projects } = useInvoiceFormContext();
const { values } = useFormikContext(); const { values } = useFormikContext();
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */} {/* ----------- Customer name ----------- */}
<FastField <InvoiceFormCustomerSelect />
name={'customer_id'}
customers={customers}
shouldUpdate={customerNameFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FFormGroup
name={'customer_id'}
label={<T id={'customer_name'} />}
inline={true}
className={classNames(
'form-group--customer-name',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />}
>
<CustomerSelectField
contacts={customers}
selectedContactId={value}
defaultSelectText={<T id={'select_customer_account'} />}
onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id);
form.setFieldValue('currency_code', customer?.currency_code);
}}
popoverFill={true}
allowCreate={true}
/>
{value && (
<CustomerButtonLink customerId={value}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FFormGroup>
)}
</FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<InvoiceExchangeRateInputField <InvoiceExchangeRateInputField
@@ -284,6 +248,44 @@ export default function InvoiceFormHeaderFields() {
); );
} }
/**
* Customer select field of the invoice form.
* @returns {React.ReactNode}
*/
function InvoiceFormCustomerSelect() {
const { customers } = useInvoiceFormContext();
const { values, setFieldValue } = useFormikContext();
return (
<FFormGroup
name={'customer_id'}
customers={customers}
shouldUpdate={customerNameFieldShouldUpdate}
label={<T id={'customer_name'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
fastField={true}
>
<CustomersSelect
name={'customer_id'}
items={customers}
placeholder={<T id={'select_customer_account'} />}
onItemChange={(customer) => {
setFieldValue('customer_id', customer.id);
setFieldValue('currency_code', customer?.currency_code);
}}
allowCreate={true}
fastField={true}
/>
{values.customer_id && (
<CustomerButtonLink customerId={values.customer_id}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FFormGroup>
);
}
const CustomerButtonLink = styled(CustomerDrawerLink)` const CustomerButtonLink = styled(CustomerDrawerLink)`
font-size: 11px; font-size: 11px;
margin-top: 6px; margin-top: 6px;

View File

@@ -15,8 +15,12 @@ import { toSafeInteger } from 'lodash';
import { FastField, Field, useFormikContext, ErrorMessage } from 'formik'; import { FastField, Field, useFormikContext, ErrorMessage } from 'formik';
import * as R from 'ramda'; import * as R from 'ramda';
import { FInputGroup, FeatureCan, FormattedMessage as T } from '@/components'; import {
import { useAutofocus } from '@/hooks'; CustomersSelect,
FInputGroup,
FeatureCan,
FormattedMessage as T,
} from '@/components';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { import {
safeSumBy, safeSumBy,
@@ -28,7 +32,6 @@ import {
import { import {
FFormGroup, FFormGroup,
AccountsSelect, AccountsSelect,
CustomerSelectField,
FieldRequiredHint, FieldRequiredHint,
Icon, Icon,
InputPrependButton, InputPrependButton,
@@ -143,8 +146,7 @@ const PaymentReceivePaymentNoField = R.compose(
*/ */
export default function PaymentReceiveHeaderFields() { export default function PaymentReceiveHeaderFields() {
// Payment receive form context. // Payment receive form context.
const { customers, accounts, projects, isNewMode } = const { accounts, projects } = usePaymentReceiveFormContext();
usePaymentReceiveFormContext();
// Formik form context. // Formik form context.
const { const {
@@ -152,8 +154,6 @@ export default function PaymentReceiveHeaderFields() {
setFieldValue, setFieldValue,
} = useFormikContext(); } = useFormikContext();
const customerFieldRef = useAutofocus();
// Calculates the full-amount received. // Calculates the full-amount received.
const totalDueAmount = useMemo( const totalDueAmount = useMemo(
() => safeSumBy(entries, 'due_amount'), () => safeSumBy(entries, 'due_amount'),
@@ -176,45 +176,7 @@ export default function PaymentReceiveHeaderFields() {
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ------------- Customer name ------------- */} {/* ------------- Customer name ------------- */}
<FastField <PaymentReceiveCustomerSelect />
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
inline={true}
className={classNames('form-group--select-list', CLASSES.FILL)}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'customer_id'} />}
>
<CustomerSelectField
contacts={customers}
selectedContactId={value}
defaultSelectText={<T id={'select_customer_account'} />}
onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id);
form.setFieldValue('full_amount', '');
form.setFieldValue('currency_code', customer?.currency_code);
}}
popoverFill={true}
disabled={!isNewMode}
buttonProps={{
elementRef: (ref) => (customerFieldRef.current = ref),
}}
allowCreate={true}
/>
{value && (
<CustomerButtonLink customerId={value}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FormGroup>
)}
</FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<PaymentReceiveExchangeRateInputField <PaymentReceiveExchangeRateInputField
@@ -361,3 +323,47 @@ const CustomerButtonLink = styled(CustomerDrawerLink)`
font-size: 11px; font-size: 11px;
margin-top: 6px; margin-top: 6px;
`; `;
/**
* Customer select field of payment receive form.
* @returns {React.ReactNode}
*/
function PaymentReceiveCustomerSelect() {
// Payment receive form context.
const { customers, isNewMode } = usePaymentReceiveFormContext();
// Formik form context.
const { values, setFieldValue } = useFormikContext();
return (
<FFormGroup
label={<T id={'customer_name'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
name={'customer_id'}
customers={customers}
fastField={true}
shouldUpdate={customersFieldShouldUpdate}
>
<CustomersSelect
name={'customer_id'}
items={customers}
placeholder={<T id={'select_customer_account'} />}
onItemChange={(customer) => {
setFieldValue('customer_id', customer.id);
setFieldValue('full_amount', '');
setFieldValue('currency_code', customer?.currency_code);
}}
popoverFill={true}
disabled={!isNewMode}
allowCreate={true}
fastField={true}
/>
{values.customer_id && (
<CustomerButtonLink customerId={values.customer_id}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FFormGroup>
);
}

View File

@@ -10,7 +10,7 @@ import {
ControlGroup, ControlGroup,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage, useFormikContext } from 'formik'; import { FastField, ErrorMessage, useFormikContext, useFormik } from 'formik';
import * as R from 'ramda'; import * as R from 'ramda';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
@@ -27,6 +27,7 @@ import {
FormattedMessage as T, FormattedMessage as T,
FeatureCan, FeatureCan,
FInputGroup, FInputGroup,
CustomersSelect,
} from '@/components'; } from '@/components';
import { ProjectsSelect } from '@/containers/Projects/components'; import { ProjectsSelect } from '@/containers/Projects/components';
import { import {
@@ -130,44 +131,12 @@ const ReceiptFormReceiptNumberField = R.compose(
* Receipt form header fields. * Receipt form header fields.
*/ */
export default function ReceiptFormHeader() { export default function ReceiptFormHeader() {
const { accounts, customers, projects } = useReceiptFormContext(); const { accounts, projects } = useReceiptFormContext();
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
{/* ----------- Customer name ----------- */} {/* ----------- Customer name ----------- */}
<FastField <ReceiptFormCustomerSelect />
name={'customer_id'}
customers={customers}
shouldUpdate={customersFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'customer_name'} />}
inline={true}
className={classNames(CLASSES.FILL, 'form-group--customer')}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'customer_id'} />}
>
<CustomerSelectField
contacts={customers}
selectedContactId={value}
defaultSelectText={<T id={'select_customer_account'} />}
onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id);
form.setFieldValue('currency_code', customer?.currency_code);
}}
popoverFill={true}
allowCreate={true}
/>
{value && (
<CustomerButtonLink customerId={value}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FormGroup>
)}
</FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<ReceiptExchangeRateInputField <ReceiptExchangeRateInputField
@@ -264,6 +233,44 @@ export default function ReceiptFormHeader() {
); );
} }
/**
* Customer select field of receipt form.
* @returns {React.ReactNode}
*/
function ReceiptFormCustomerSelect() {
const { setFieldValue, values } = useFormikContext();
const { customers } = useReceiptFormContext();
return (
<FFormGroup
name={'customer_id'}
label={<T id={'customer_name'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
customers={customers}
fastField={true}
shouldUpdate={customersFieldShouldUpdate}
>
<CustomersSelect
name={'customer_id'}
items={customers}
placeholder={<T id={'select_customer_account'} />}
onItemChange={(customer) => {
setFieldValue('customer_id', customer.id);
setFieldValue('currency_code', customer?.currency_code);
}}
popoverFill={true}
allowCreate={true}
/>
{values.customer_id && (
<CustomerButtonLink customerId={values.customer_id}>
<T id={'view_customer_details'} />
</CustomerButtonLink>
)}
</FFormGroup>
);
}
const CustomerButtonLink = styled(CustomerDrawerLink)` const CustomerButtonLink = styled(CustomerDrawerLink)`
font-size: 11px; font-size: 11px;
margin-top: 6px; margin-top: 6px;