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 ...defaultInitialValues,
? { ...transformToForm(customer, defaultInitialValues),
...pick(customer, Object.keys(defaultInitialValues)),
}
: {
...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 };
const onSuccess = () => {
AppToaster.show({
message: formatMessage({
id: customer ?
'the_item_customer_has_been_successfully_edited' :
'the_customer_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
resetForm();
if (!submitPayload.noRedirect) {
history.push('/customers');
}
};
const onError = () => {
setSubmitting(false);
};
if (customer && customer.id) { if (customer && customer.id) {
requestEditCustomer(customer.id, formValues) requestEditCustomer(customer.id, formValues).then(onSuccess).catch(onError);
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_item_customer_has_been_successfully_edited',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
resetForm();
})
.catch((errors) => {
setSubmitting(false);
});
} 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,17 +87,33 @@ function CustomersList({
setDeleteCustomer(false); setDeleteCustomer(false);
}, [setDeleteCustomer]); }, [setDeleteCustomer]);
// handle confirm delete customer. const transformErrors = (errors) => {
const handleConfirmDeleteCustomer = useCallback(() => { if (errors.some((e) => e.type === 'CUSTOMER.HAS.SALES_INVOICES')) {
requestDeleteCustomer(deleteCustomer.id).then(() => {
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage({
id: 'the_customer_has_been_successfully_deleted', id: 'customer_has_sales_invoices',
}), }),
intent: Intent.SUCCESS, intent: Intent.DANGER,
});
}
};
// handle confirm delete customer.
const handleConfirmDeleteCustomer = useCallback(() => {
requestDeleteCustomer(deleteCustomer.id)
.then(() => {
setDeleteCustomer(false);
AppToaster.show({
message: formatMessage({
id: 'the_customer_has_been_successfully_deleted',
}),
intent: Intent.SUCCESS,
});
})
.catch((errors) => {
setDeleteCustomer(false);
transformErrors(errors);
}); });
setDeleteCustomer(false);
});
}, [requestDeleteCustomer, deleteCustomer, formatMessage]); }, [requestDeleteCustomer, deleteCustomer, formatMessage]);
// Handle fetch data table. // Handle fetch data table.

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>
@@ -42,4 +68,4 @@ export default function ItemFormFloatingActions({ isSubmitting, itemId, onCancel
</FastField> </FastField>
</div> </div>
); );
} }

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

@@ -219,7 +219,7 @@ export default {
'The item category has been successfully edited.', 'The item category has been successfully edited.',
the_item_category_has_been_successfully_deleted: the_item_category_has_been_successfully_deleted:
'The item category has been successfully deleted', 'The item category has been successfully deleted',
once_delete_these_item_categories_you_will_not_able_restore_them: once_delete_these_item_categories_you_will_not_able_restore_them:
"Once you delete these categories, you won't be able to retrieve them later. Are you sure you want to delete them?", "Once you delete these categories, you won't be able to retrieve them later. Are you sure you want to delete them?",
once_delete_these_views_you_will_not_able_restore_them: once_delete_these_views_you_will_not_able_restore_them:
"Once you delete the custom view, you won't be able to restore it later. Are you sure you want to delete this view?", "Once you delete the custom view, you won't be able to restore it later. Are you sure you want to delete this view?",
@@ -809,11 +809,12 @@ export default {
notes: 'Notes', notes: 'Notes',
i_purchase_this_item: 'I purchase this item from a vendor.', i_purchase_this_item: 'I purchase this item from a vendor.',
i_sell_this_item: 'I sell this item to a customer.', i_sell_this_item: 'I sell this item to a customer.',
select_display_name_as:'Select display name as', select_display_name_as: 'Select display name as',
opening_date: 'Opening date', opening_date: 'Opening date',
item_code: 'Item code', item_code: 'Item code',
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',
}; };