Merge remote-tracking branch 'origin/master'

This commit is contained in:
Ahmed Bouhuolia
2020-11-11 18:20:13 +02:00
9 changed files with 196 additions and 128 deletions

View File

@@ -3,13 +3,14 @@ import { Intent, Button } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { saveInvoke } from 'utils';
export default function CustomerFloatingActions({ export default function CustomerFloatingActions({
isSubmitting,
resetForm,
onSubmitClick, onSubmitClick,
onSubmitAndNewClick,
onCancelClick, onCancelClick,
isSubmitting,
customerId, customerId,
}) { }) {
return ( return (
@@ -18,8 +19,8 @@ export default function CustomerFloatingActions({
disabled={isSubmitting} disabled={isSubmitting}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
type="submit" type="submit"
onClick={() => { onClick={(event) => {
onSubmitClick({ publish: true, redirect: true }); saveInvoke(onSubmitClick, event);
}} }}
> >
{customerId ? <T id={'edit'} /> : <T id={'save'} />} {customerId ? <T id={'edit'} /> : <T id={'save'} />}
@@ -30,8 +31,9 @@ export default function CustomerFloatingActions({
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
className={'ml1'} className={'ml1'}
name={'save_and_new'} name={'save_and_new'}
onClick={() => { type="submit"
onSubmitClick({ publish: true, redirect: false }); onClick={(event) => {
saveInvoke(onSubmitAndNewClick, event);
}} }}
> >
<T id={'save_new'} /> <T id={'save_new'} />
@@ -39,8 +41,8 @@ export default function CustomerFloatingActions({
<Button <Button
className={'ml1'} className={'ml1'}
onClick={() => { onClick={(event) => {
onCancelClick && onCancelClick(); saveInvoke(onCancelClick, event);
}} }}
> >
<T id={'close'} /> <T id={'close'} />

View File

@@ -1,12 +1,11 @@
import React, { useState, useMemo, useCallback, useEffect } from 'react'; import React, { useState, useMemo, useCallback, useEffect } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useFormik, Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import moment from 'moment'; import moment from 'moment';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { pick } from 'lodash';
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 AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
@@ -23,7 +22,43 @@ import withMediaActions from 'containers/Media/withMediaActions';
import withCustomers from 'containers/Customers//withCustomers'; import withCustomers from 'containers/Customers//withCustomers';
import useMedia from 'hooks/useMedia'; import useMedia from 'hooks/useMedia';
import { compose, saveInvoke } from 'utils'; import { compose, transformToForm } from 'utils';
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. * Customer form.
@@ -51,8 +86,11 @@ function CustomerForm({
onFormSubmit, onFormSubmit,
onCancelForm, onCancelForm,
}) { }) {
const { formatMessage } = useIntl(); const isNewMode = !customerId;
const [submitPayload, setSubmitPayload] = useState({});
const history = useHistory(); const history = useHistory();
const { formatMessage } = useIntl();
const { const {
setFiles, setFiles,
saveMedia, saveMedia,
@@ -107,98 +145,55 @@ function CustomerForm({
opening_balance_at: Yup.date(), opening_balance_at: Yup.date(),
}); });
const defaultInitialValues = useMemo( /**
() => ({ * Initial values in create and edit mode.
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'),
}),
[],
);
const initialValues = useMemo( const initialValues = useMemo(
() => ({ () => ({
...(customer
? {
...pick(customer, Object.keys(defaultInitialValues)),
}
: {
...defaultInitialValues, ...defaultInitialValues,
}), ...transformToForm(customer, defaultInitialValues),
}), }),
[customer, defaultInitialValues], [customer, defaultInitialValues],
); );
useEffect(() => { useEffect(() => {
customer && customer.id !isNewMode
? changePageTitle(formatMessage({ id: 'edit_customer' })) ? changePageTitle(formatMessage({ id: 'edit_customer' }))
: changePageTitle(formatMessage({ id: 'new_customer' })); : changePageTitle(formatMessage({ id: 'new_customer' }));
}, [changePageTitle, customer, formatMessage]); }, [changePageTitle, isNewMode, formatMessage]);
//Handles the form submit.
const handleFormSubmit = ( const handleFormSubmit = (
values, values,
{ setSubmitting, resetForm, setErrors }, { setSubmitting, resetForm, setErrors },
) => { ) => {
const formValues = { ...values }; const formValues = { ...values };
if (customer && customer.id) {
requestEditCustomer(customer.id, formValues) const onSuccess = () => {
.then((response) => {
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
id: 'the_item_customer_has_been_successfully_edited', id: customer ?
'the_item_customer_has_been_successfully_edited' :
'the_customer_has_been_successfully_created',
}), }),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
setSubmitting(false); setSubmitting(false);
resetForm(); resetForm();
})
.catch((errors) => { if (!submitPayload.noRedirect) {
history.push('/customers');
}
};
const onError = () => {
setSubmitting(false); setSubmitting(false);
}); };
if (customer && customer.id) {
requestEditCustomer(customer.id, formValues).then(onSuccess).catch(onError);
} else { } else {
requestSubmitCustomer(formValues) requestSubmitCustomer(formValues).then(onSuccess).catch(onError);
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
})
.catch((errors) => {
setSubmitting(false);
});
} }
}; };
@@ -226,12 +221,14 @@ function CustomerForm({
}, },
[setDeletedFiles, deletedFiles], [setDeletedFiles, deletedFiles],
); );
const handleCancelClick = useCallback(
(payload, event) => {
}, const handleCancelClick = () => {
[], history.goBack();
); };
const handleSubmitAndNewClick = () => {
setSubmitPayload({ noRedirect: true });
};
return ( return (
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_CUSTOMER)}> <div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_CUSTOMER)}>
@@ -258,8 +255,9 @@ function CustomerForm({
<CustomerFloatingActions <CustomerFloatingActions
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
onCancelClick={handleCancelClick}
customerId={customer} customerId={customer}
onCancelClick={handleCancelClick}
onSubmitAndNewClick={handleSubmitAndNewClick}
/> />
</Form> </Form>
)} )}

View File

@@ -87,16 +87,32 @@ function CustomersList({
setDeleteCustomer(false); setDeleteCustomer(false);
}, [setDeleteCustomer]); }, [setDeleteCustomer]);
const transformErrors = (errors) => {
if (errors.some((e) => e.type === 'CUSTOMER.HAS.SALES_INVOICES')) {
AppToaster.show({
message: formatMessage({
id: 'customer_has_sales_invoices',
}),
intent: Intent.DANGER,
});
}
};
// handle confirm delete customer. // handle confirm delete customer.
const handleConfirmDeleteCustomer = useCallback(() => { const handleConfirmDeleteCustomer = useCallback(() => {
requestDeleteCustomer(deleteCustomer.id).then(() => { requestDeleteCustomer(deleteCustomer.id)
.then(() => {
setDeleteCustomer(false);
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
id: 'the_customer_has_been_successfully_deleted', id: 'the_customer_has_been_successfully_deleted',
}), }),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
})
.catch((errors) => {
setDeleteCustomer(false); setDeleteCustomer(false);
transformErrors(errors);
}); });
}, [requestDeleteCustomer, deleteCustomer, formatMessage]); }, [requestDeleteCustomer, deleteCustomer, formatMessage]);

View File

@@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { getCustomerById } from 'store/customers/customers.reducer'; import { getCustomerById } from 'store/customers/customers.reducer';
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
customer: getCustomerById(state, props.customerId), customerDetail: getCustomerById(state, props.customerId),
}); });
export default connect(mapStateToProps); export default connect(mapStateToProps);

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useCallback, useEffect } from 'react'; import React, { useState, useMemo, useCallback, useEffect } from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
@@ -60,6 +60,9 @@ function ItemForm({
}) { }) {
const isNewMode = !itemId; const isNewMode = !itemId;
// Holds data of submit button once clicked to form submit function.
const [submitPayload, setSubmitPayload] = useState({});
const history = useHistory(); const history = useHistory();
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -165,8 +168,8 @@ function ItemForm({
message: formatMessage( message: formatMessage(
{ {
id: isNewMode id: isNewMode
? 'service_has_been_successful_created' ? 'the_item_has_been_created_successfully'
: 'the_item_has_been_successfully_edited', : 'the_item_has_been_edited_successfully',
}, },
{ {
number: itemId, number: itemId,
@@ -174,9 +177,13 @@ function ItemForm({
), ),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
resetForm();
setSubmitting(false); setSubmitting(false);
history.push('/items');
queryCache.removeQueries(['items-table']); queryCache.removeQueries(['items-table']);
if (submitPayload.redirect) {
history.push('/items');
}
}; };
const onError = ({ response }) => { const onError = ({ response }) => {
setSubmitting(false); setSubmitting(false);
@@ -193,13 +200,11 @@ function ItemForm({
} }
}; };
// useEffect(() => { useEffect(() => {
// if (values.item_type) { if (itemDetail && itemDetail.type) {
// changePageSubtitle(formatMessage({ id: values.item_type })); changePageSubtitle(formatMessage({ id: itemDetail.type }));
// } else { }
// changePageSubtitle(''); }, [itemDetail, changePageSubtitle, formatMessage]);
// }
// }, [values.item_type, changePageSubtitle, formatMessage]);
const initialAttachmentFiles = useMemo(() => { const initialAttachmentFiles = useMemo(() => {
return itemDetail && itemDetail.media return itemDetail && itemDetail.media
@@ -229,9 +234,17 @@ function ItemForm({
[setDeletedFiles, deletedFiles], [setDeletedFiles, deletedFiles],
); );
const handleCancelBtnClick = useCallback(() => { const handleCancelBtnClick = () => {
history.goBack(); history.goBack();
}, [history]); };
const handleSubmitAndNewClick = () => {
setSubmitPayload({ redirect: false });
};
const handleSubmitClick = () => {
setSubmitPayload({ redirect: true });
};
return ( return (
<div class={classNames(CLASSES.PAGE_FORM_ITEM)}> <div class={classNames(CLASSES.PAGE_FORM_ITEM)}>
@@ -241,7 +254,7 @@ function ItemForm({
initialValues={initialValues} initialValues={initialValues}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}
> >
{({ isSubmitting }) => ( {({ isSubmitting, handleSubmit }) => (
<Form> <Form>
<div class={classNames(CLASSES.PAGE_FORM_BODY)}> <div class={classNames(CLASSES.PAGE_FORM_BODY)}>
<ItemFormPrimarySection /> <ItemFormPrimarySection />
@@ -251,7 +264,10 @@ function ItemForm({
<ItemFormFloatingActions <ItemFormFloatingActions
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
itemId={itemId} itemId={itemId}
handleSubmit={handleSubmit}
onCancelClick={handleCancelBtnClick} onCancelClick={handleCancelBtnClick}
onSubmitAndNewClick={handleSubmitAndNewClick}
onSubmitClick={handleSubmitClick}
/> />
</Form> </Form>
)} )}

View File

@@ -37,7 +37,7 @@ function ItemFormBody({ accountsList }) {
<T id={'i_sell_this_item'} /> <T id={'i_sell_this_item'} />
</h3> </h3>
} }
defaultChecked={value} checked={value}
{...field} {...field}
/> />
</FormGroup> </FormGroup>
@@ -59,7 +59,7 @@ function ItemFormBody({ accountsList }) {
prefix={'$'} prefix={'$'}
inputGroupProps={{ inputGroupProps={{
medium: true, medium: true,
// intent: error && touched/ && Intent.DANGER, ...field,
}} }}
disabled={!form.values.sellable} disabled={!form.values.sellable}
onChange={field.onChange} onChange={field.onChange}
@@ -132,9 +132,9 @@ function ItemFormBody({ accountsList }) {
prefix={'$'} prefix={'$'}
inputGroupProps={{ inputGroupProps={{
medium: true, medium: true,
...field,
}} }}
disabled={!form.values.purchasable} disabled={!form.values.purchasable}
{...field}
/> />
</FormGroup> </FormGroup>
)} )}

View File

@@ -3,23 +3,49 @@ import { Button, Intent, FormGroup, Checkbox } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { saveInvoke } from 'utils'; import { saveInvoke } from 'utils';
import classNames from 'classnames'; import classNames from 'classnames';
import { ErrorMessage, FastField } from 'formik'; import { FastField } from 'formik';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
/** /**
* Item form floating actions. * Item form floating actions.
*/ */
export default function ItemFormFloatingActions({ isSubmitting, itemId, onCancelClick }) { export default function ItemFormFloatingActions({
isSubmitting,
itemId,
handleSubmit,
onCancelClick,
onSubmitClick,
onSubmitAndNewClick,
}) {
const handleCancelBtnClick = (event) => { const handleCancelBtnClick = (event) => {
saveInvoke(onCancelClick, event.currentTarget.value); saveInvoke(onCancelClick, event.currentTarget.value);
}; };
const handleSubmitBtnClick = (event) => {
saveInvoke(onSubmitClick, event);
};
const handleSubmitAndNewBtnClick = (event) => {
saveInvoke(onSubmitAndNewClick, event);
};
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}> <div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
<Button intent={Intent.PRIMARY} disabled={isSubmitting} type="submit"> <Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
onClick={handleSubmitBtnClick}
type="submit"
>
{itemId ? <T id={'edit'} /> : <T id={'save'} />} {itemId ? <T id={'edit'} /> : <T id={'save'} />}
</Button> </Button>
<Button className={'ml1'} disabled={isSubmitting}> <Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleSubmitAndNewBtnClick}
type="submit"
>
<T id={'save_new'} /> <T id={'save_new'} />
</Button> </Button>

View File

@@ -9,6 +9,7 @@ import {
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { ErrorMessage, FastField } from 'formik'; import { ErrorMessage, FastField } from 'formik';
import { useIntl } from 'react-intl';
import { import {
CategoriesSelectList, CategoriesSelectList,
Hint, Hint,
@@ -21,6 +22,7 @@ import { CLASSES } from 'common/classes';
import withItemCategories from 'containers/Items/withItemCategories'; import withItemCategories from 'containers/Items/withItemCategories';
import withAccounts from 'containers/Accounts/withAccounts'; import withAccounts from 'containers/Accounts/withAccounts';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose, handleStringChange, inputIntent } from 'utils'; import { compose, handleStringChange, inputIntent } from 'utils';
@@ -30,7 +32,12 @@ import { compose, handleStringChange, inputIntent } from 'utils';
function ItemFormPrimarySection({ function ItemFormPrimarySection({
// #withItemCategories // #withItemCategories
categoriesList, categoriesList,
// #withDashboardActions
changePageSubtitle
}) { }) {
const { formatMessage } = useIntl();
const itemTypeHintContent = ( const itemTypeHintContent = (
<> <>
<div class="mb1"> <div class="mb1">
@@ -78,6 +85,7 @@ function ItemFormPrimarySection({
inline={true} inline={true}
onChange={handleStringChange((_value) => { onChange={handleStringChange((_value) => {
form.setFieldValue('type', _value); form.setFieldValue('type', _value);
changePageSubtitle(formatMessage({ id: _value }));
})} })}
selectedValue={value} selectedValue={value}
> >
@@ -171,4 +179,5 @@ export default compose(
withItemCategories(({ categoriesList }) => ({ withItemCategories(({ categoriesList }) => ({
categoriesList, categoriesList,
})), })),
withDashboardActions
)(ItemFormPrimarySection); )(ItemFormPrimarySection);

View File

@@ -815,5 +815,6 @@ export default {
quantity_on_hand: 'Quantity on hand', quantity_on_hand: 'Quantity on hand',
average_rate: 'Average rate', average_rate: 'Average rate',
the_name_used_before: 'The name is already used.', the_name_used_before: 'The name is already used.',
the_item_has_associated_transactions: 'The item has associated transactions.' the_item_has_associated_transactions: 'The item has associated transactions.',
customer_has_sales_invoices: 'Customer has sales invoices',
}; };