Files
bigcapital/client/src/containers/Items/ItemForm.js
Ahmed Bouhuolia 128feb73f8 feat: apply new cards design system.
feat: empty status datatables.
fix: edit account.
2020-11-18 21:55:17 +02:00

260 lines
7.0 KiB
JavaScript

import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { useHistory } from 'react-router-dom';
import { useIntl } from 'react-intl';
import classNames from 'classnames';
import { defaultTo } from 'lodash';
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 withItemsActions from 'containers/Items/withItemsActions';
import withMediaActions from 'containers/Media/withMediaActions';
import useMedia from 'hooks/useMedia';
import withItemDetail from 'containers/Items/withItemDetail';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings';
import { compose, transformToForm } from 'utils';
import { transitionItemTypeKeyToLabel } from './utils';
import { EditItemFormSchema, CreateItemFormSchema } from './ItemForm.schema';
const defaultInitialValues = {
active: true,
name: '',
type: 'service',
sku: '',
cost_price: '',
sell_price: '',
cost_account_id: '',
sell_account_id: '',
inventory_account_id: '',
category_id: '',
note: '',
sellable: true,
purchasable: true,
};
/**
* Item form.
*/
function ItemForm({
// #withItemActions
requestSubmitItem,
requestEditItem,
itemId,
itemDetail,
onFormSubmit,
// #withDashboardActions
changePageTitle,
changePageSubtitle,
// #withSettings
preferredCostAccount,
preferredSellAccount,
preferredInventoryAccount,
// #withMediaActions
requestSubmitMedia,
requestDeleteMedia,
}) {
const isNewMode = !itemId;
// Holds data of submit button once clicked to form submit function.
const [submitPayload, setSubmitPayload] = useState({});
const history = useHistory();
const { formatMessage } = useIntl();
const {
setFiles,
saveMedia,
deletedFiles,
setDeletedFiles,
deleteMedia,
} = useMedia({
saveCallback: requestSubmitMedia,
deleteCallback: requestDeleteMedia,
});
/**
* Initial values in create and edit mode.
*/
const initialValues = useMemo(
() => ({
...defaultInitialValues,
cost_account_id: defaultTo(preferredCostAccount, ''),
sell_account_id: defaultTo(preferredSellAccount, ''),
inventory_account_id: defaultTo(preferredInventoryAccount, ''),
/**
* We only care about the fields in the form. Previously unfilled optional
* values such as `notes` come back from the API as null, so remove those
* as well.
*/
...transformToForm(itemDetail, defaultInitialValues),
}),
[
itemDetail,
preferredCostAccount,
preferredSellAccount,
preferredInventoryAccount,
],
);
useEffect(() => {
!isNewMode
? changePageTitle(formatMessage({ id: 'edit_item_details' }))
: changePageTitle(formatMessage({ id: 'new_item' }));
}, [changePageTitle, isNewMode, formatMessage]);
const transformApiErrors = (errors) => {
const fields = {};
if (errors.find((e) => e.type === 'ITEM.NAME.ALREADY.EXISTS')) {
fields.name = formatMessage({ id: 'the_name_used_before' });
}
return fields;
};
// Handles the form submit.
const handleFormSubmit = (
values,
{ setSubmitting, resetForm, setErrors },
) => {
setSubmitting(true);
const form = { ...values };
const onSuccess = (response) => {
AppToaster.show({
message: formatMessage(
{
id: isNewMode
? 'the_item_has_been_created_successfully'
: 'the_item_has_been_edited_successfully',
},
{
number: itemId,
},
),
intent: Intent.SUCCESS,
});
resetForm();
setSubmitting(false);
queryCache.removeQueries(['items-table']);
if (submitPayload.redirect) {
history.push('/items');
}
};
const onError = ({ response }) => {
setSubmitting(false);
if (response.data.errors) {
const _errors = transformApiErrors(response.data.errors);
setErrors({ ..._errors });
}
};
if (isNewMode) {
requestSubmitItem(form).then(onSuccess).catch(onError);
} else {
requestEditItem(itemId, form).then(onSuccess).catch(onError);
}
};
useEffect(() => {
if (itemDetail && itemDetail.type) {
changePageSubtitle(transitionItemTypeKeyToLabel(itemDetail.type));
}
}, [itemDetail, changePageSubtitle, formatMessage]);
const initialAttachmentFiles = useMemo(() => {
return itemDetail && itemDetail.media
? itemDetail.media.map((attach) => ({
preview: attach.attachment_file,
upload: true,
metadata: { ...attach },
}))
: [];
}, [itemDetail]);
const handleDropFiles = useCallback(
(_files) => {
setFiles(_files.filter((file) => file.uploaded === false));
},
[setFiles],
);
const handleDeleteFile = useCallback(
(_deletedFiles) => {
_deletedFiles.forEach((deletedFile) => {
if (deletedFile.uploaded && deletedFile.metadata.id) {
setDeletedFiles([...deletedFiles, deletedFile.metadata.id]);
}
});
},
[setDeletedFiles, deletedFiles],
);
const handleCancelBtnClick = () => {
history.goBack();
};
const handleSubmitAndNewClick = () => {
setSubmitPayload({ redirect: false });
};
const handleSubmitClick = () => {
setSubmitPayload({ redirect: true });
};
return (
<div class={classNames(CLASSES.PAGE_FORM_ITEM)}>
<Formik
enableReinitialize={true}
validationSchema={isNewMode ? CreateItemFormSchema : EditItemFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
{({ isSubmitting, handleSubmit }) => (
<Form>
<div class={classNames(CLASSES.PAGE_FORM_BODY)}>
<ItemFormPrimarySection itemId={itemId} />
<ItemFormBody />
<ItemFormInventorySection />
</div>
<ItemFormFloatingActions
isSubmitting={isSubmitting}
itemId={itemId}
handleSubmit={handleSubmit}
onCancelClick={handleCancelBtnClick}
onSubmitAndNewClick={handleSubmitAndNewClick}
onSubmitClick={handleSubmitClick}
/>
</Form>
)}
</Formik>
</div>
);
}
export default compose(
withItemsActions,
withItemDetail,
withDashboardActions,
withMediaActions,
withSettings(({ itemsSettings }) => ({
preferredCostAccount: parseInt(itemsSettings?.preferredCostAccount),
preferredSellAccount: parseInt(itemsSettings?.preferredSellAccount),
preferredInventoryAccount: parseInt(
itemsSettings?.preferredInventoryAccount,
),
})),
)(ItemForm);