mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
Merge remote-tracking branch 'origin/feature/editItem'
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useMemo, useCallback } from 'react';
|
import React, { useState, useMemo, useCallback,useEffect } from 'react';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import {
|
import {
|
||||||
@@ -15,35 +15,60 @@ import { Row, Col } from 'react-grid-system';
|
|||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { Select } from '@blueprintjs/select';
|
import { Select } from '@blueprintjs/select';
|
||||||
import { queryCache } from 'react-query';
|
import { queryCache } from 'react-query';
|
||||||
|
import {useParams ,useHistory} from 'react-router-dom';
|
||||||
import AppToaster from 'components/AppToaster';
|
import AppToaster from 'components/AppToaster';
|
||||||
import AccountsConnect from 'connectors/Accounts.connector';
|
|
||||||
import ItemsConnect from 'connectors/Items.connect';
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
import ErrorMessage from 'components/ErrorMessage';
|
import ErrorMessage from 'components/ErrorMessage';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import ItemCategoryConnect from 'connectors/ItemsCategory.connect';
|
import withItemsActions from 'containers/Items/withItemsActions';
|
||||||
|
import withItemCategories from 'containers/Items/withItemCategories'
|
||||||
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
|
import withMediaActions from 'containers/Media/withMediaActions';
|
||||||
|
|
||||||
import MoneyInputGroup from 'components/MoneyInputGroup';
|
import MoneyInputGroup from 'components/MoneyInputGroup';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import Dragzone from 'components/Dragzone';
|
import Dragzone from 'components/Dragzone';
|
||||||
import MediaConnect from 'connectors/Media.connect';
|
|
||||||
import useMedia from 'hooks/useMedia';
|
import useMedia from 'hooks/useMedia';
|
||||||
|
import withItems from './withItems';
|
||||||
|
import withItemDetail from 'containers/Items/withItemDetail'
|
||||||
|
import { pick } from 'lodash';
|
||||||
|
import withDashboardActions from 'containers/Dashboard/withDashboard';
|
||||||
|
import withAccountDetail from 'containers/Accounts/withAccountDetail';
|
||||||
|
|
||||||
|
|
||||||
const ItemForm = ({
|
const ItemForm = ({
|
||||||
|
|
||||||
|
// #withItemActions
|
||||||
requestSubmitItem,
|
requestSubmitItem,
|
||||||
|
requestEditItem,
|
||||||
|
|
||||||
accounts,
|
accounts,
|
||||||
categories,
|
itemDetail,
|
||||||
|
onFormSubmit,
|
||||||
|
onCancelForm,
|
||||||
|
|
||||||
|
|
||||||
|
// #withDashboard
|
||||||
|
changePageTitle,
|
||||||
|
|
||||||
|
// #withItemCategories
|
||||||
|
categoriesList,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// #withMediaActions
|
||||||
requestSubmitMedia,
|
requestSubmitMedia,
|
||||||
requestDeleteMedia,
|
requestDeleteMedia,
|
||||||
|
|
||||||
|
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const [selectedAccounts, setSelectedAccounts] = useState({});
|
const [selectedAccounts, setSelectedAccounts] = useState({});
|
||||||
|
const [payload, setPayload] = useState({});
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
const {id} =useParams();
|
||||||
const {
|
const {
|
||||||
files,
|
files,
|
||||||
setFiles,
|
setFiles,
|
||||||
@@ -81,7 +106,7 @@ const ItemForm = ({
|
|||||||
stock: Yup.string() || Yup.boolean(),
|
stock: Yup.string() || Yup.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialValues = useMemo(
|
const defaultInitialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
active: true,
|
active: true,
|
||||||
name: '',
|
name: '',
|
||||||
@@ -97,6 +122,24 @@ const ItemForm = ({
|
|||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
const initialValues = useMemo(() => ({
|
||||||
|
...(itemDetail) ? {
|
||||||
|
...pick(itemDetail, Object.keys(defaultInitialValues)),
|
||||||
|
|
||||||
|
} : {
|
||||||
|
...defaultInitialValues,
|
||||||
|
}
|
||||||
|
}), [itemDetail, defaultInitialValues]);
|
||||||
|
|
||||||
|
const saveInvokeSubmit = useCallback((payload) => {
|
||||||
|
onFormSubmit && onFormSubmit(payload)
|
||||||
|
}, [onFormSubmit]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
itemDetail && itemDetail.id ?
|
||||||
|
changePageTitle(formatMessage({id:'edit_item_details'})) :
|
||||||
|
changePageTitle(formatMessage({id:'new_item'}));
|
||||||
|
}, [changePageTitle,itemDetail]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getFieldProps,
|
getFieldProps,
|
||||||
@@ -112,11 +155,33 @@ const ItemForm = ({
|
|||||||
initialValues: {
|
initialValues: {
|
||||||
...initialValues,
|
...initialValues,
|
||||||
},
|
},
|
||||||
onSubmit: (values, { setSubmitting }) => {
|
onSubmit: (values, { setSubmitting,resetForm,setErrors }) => {
|
||||||
|
|
||||||
const saveItem = (mediaIds) => {
|
const saveItem = (mediaIds) => {
|
||||||
const formValues = { ...values, media_ids: mediaIds };
|
const formValues = { ...values, media_ids: mediaIds };
|
||||||
|
if(itemDetail && itemDetail.id ){
|
||||||
|
|
||||||
return requestSubmitItem(formValues).then((response) => {
|
requestEditItem(itemDetail.id,formValues)
|
||||||
|
.then((response)=>{
|
||||||
|
AppToaster.show({
|
||||||
|
message:formatMessage({
|
||||||
|
id:'the_item_has_been_successfully_edited',
|
||||||
|
},{
|
||||||
|
number:itemDetail.id
|
||||||
|
}),
|
||||||
|
intent:Intent.SUCCESS
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
|
saveInvokeSubmit({action:'update',...payload})
|
||||||
|
history.push('/items');
|
||||||
|
resetForm();
|
||||||
|
}).catch((errors)=>{
|
||||||
|
setSubmitting(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
requestSubmitItem(formValues).then((response) => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
id: 'service_has_been_successful_created',
|
id: 'service_has_been_successful_created',
|
||||||
@@ -127,9 +192,12 @@ const ItemForm = ({
|
|||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.removeQueries(['items-table']);
|
queryCache.removeQueries(['items-table']);
|
||||||
history.push('/dashboard/items');
|
history.push('/items');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Promise.all([saveMedia(), deleteMedia()]).then(
|
Promise.all([saveMedia(), deleteMedia()]).then(
|
||||||
([savedMediaResponses]) => {
|
([savedMediaResponses]) => {
|
||||||
@@ -152,6 +220,7 @@ const ItemForm = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Filter Account Items
|
// Filter Account Items
|
||||||
const filterAccounts = (query, account, _index, exactMatch) => {
|
const filterAccounts = (query, account, _index, exactMatch) => {
|
||||||
const normalizedTitle = account.name.toLowerCase();
|
const normalizedTitle = account.name.toLowerCase();
|
||||||
@@ -163,6 +232,7 @@ const ItemForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const onItemAccountSelect = useCallback(
|
const onItemAccountSelect = useCallback(
|
||||||
(filedName) => {
|
(filedName) => {
|
||||||
return (account) => {
|
return (account) => {
|
||||||
@@ -183,6 +253,7 @@ const ItemForm = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const getSelectedAccountLabel = useCallback(
|
const getSelectedAccountLabel = useCallback(
|
||||||
(fieldName, defaultLabel) => {
|
(fieldName, defaultLabel) => {
|
||||||
return typeof selectedAccounts[fieldName] !== 'undefined'
|
return typeof selectedAccounts[fieldName] !== 'undefined'
|
||||||
@@ -199,10 +270,18 @@ const ItemForm = ({
|
|||||||
setFieldValue(fieldKey, value);
|
setFieldValue(fieldKey, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialAttachmentFiles = useMemo(() => {
|
|
||||||
return [];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
const initialAttachmentFiles =useMemo(()=>{
|
||||||
|
return itemDetail && itemDetail.media
|
||||||
|
? itemDetail.media.map((attach)=>({
|
||||||
|
|
||||||
|
preview:attach.attachment_file,
|
||||||
|
upload:true,
|
||||||
|
metadata:{...attach}
|
||||||
|
|
||||||
|
})):[];
|
||||||
|
|
||||||
|
},[itemDetail])
|
||||||
const handleDropFiles = useCallback((_files) => {
|
const handleDropFiles = useCallback((_files) => {
|
||||||
setFiles(_files.filter((file) => file.uploaded === false));
|
setFiles(_files.filter((file) => file.uploaded === false));
|
||||||
}, []);
|
}, []);
|
||||||
@@ -297,7 +376,7 @@ const ItemForm = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
items={categories}
|
items={categoriesList}
|
||||||
itemRenderer={categoryItem}
|
itemRenderer={categoryItem}
|
||||||
itemPredicate={filterAccounts}
|
itemPredicate={filterAccounts}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
@@ -532,7 +611,8 @@ const ItemForm = ({
|
|||||||
|
|
||||||
<div class='form__floating-footer'>
|
<div class='form__floating-footer'>
|
||||||
<Button intent={Intent.PRIMARY} disabled={isSubmitting} type='submit'>
|
<Button intent={Intent.PRIMARY} disabled={isSubmitting} type='submit'>
|
||||||
<T id={'save'}/>
|
|
||||||
|
{ itemDetail && itemDetail.id ? <T id={'edit'}/> : <T id={'save'}/> }
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button className={'ml1'} disabled={isSubmitting}>
|
<Button className={'ml1'} disabled={isSubmitting}>
|
||||||
@@ -549,8 +629,15 @@ const ItemForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
AccountsConnect,
|
withAccounts(({accounts})=>({
|
||||||
ItemsConnect,
|
accounts,
|
||||||
ItemCategoryConnect,
|
})),
|
||||||
MediaConnect
|
withAccountDetail,
|
||||||
|
withItemsActions,
|
||||||
|
withItemDetail,
|
||||||
|
withItemCategories(({ categoriesList }) => ({
|
||||||
|
categoriesList,
|
||||||
|
})),
|
||||||
|
withDashboardActions,
|
||||||
|
withMediaActions,
|
||||||
)(ItemForm);
|
)(ItemForm);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect,useCallback } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams,useHistory } from 'react-router-dom';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
|
||||||
import ItemForm from 'containers/Items/ItemForm';
|
import ItemForm from 'containers/Items/ItemForm';
|
||||||
@@ -11,6 +11,7 @@ import withItemCategoriesActions from 'containers/Items/withItemCategoriesAction
|
|||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
import withItemsActions from './withItemsActions';
|
||||||
|
|
||||||
|
|
||||||
const ItemFormContainer = ({
|
const ItemFormContainer = ({
|
||||||
@@ -20,16 +21,15 @@ const ItemFormContainer = ({
|
|||||||
// #withAccountsActions
|
// #withAccountsActions
|
||||||
requestFetchAccounts,
|
requestFetchAccounts,
|
||||||
|
|
||||||
|
// #withItemsActions
|
||||||
|
requestFetchItems,
|
||||||
|
|
||||||
// #withItemCategoriesActions
|
// #withItemCategoriesActions
|
||||||
requestFetchItemCategories,
|
requestFetchItemCategories,
|
||||||
}) => {
|
}) => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const {formatMessage} =useIntl()
|
const {formatMessage} =useIntl()
|
||||||
useEffect(() => {
|
const history = useHistory();
|
||||||
id ?
|
|
||||||
changePageTitle(formatMessage({id:'edit_item_details'})) :
|
|
||||||
changePageTitle(formatMessage({id:'new_item'}));
|
|
||||||
}, [id, changePageTitle]);
|
|
||||||
|
|
||||||
const fetchAccounts = useQuery('accounts-list',
|
const fetchAccounts = useQuery('accounts-list',
|
||||||
(key) => requestFetchAccounts());
|
(key) => requestFetchAccounts());
|
||||||
@@ -37,11 +37,30 @@ const ItemFormContainer = ({
|
|||||||
const fetchCategories = useQuery('item-categories-list',
|
const fetchCategories = useQuery('item-categories-list',
|
||||||
(key) => requestFetchItemCategories());
|
(key) => requestFetchItemCategories());
|
||||||
|
|
||||||
|
const fetchItemDetail = useQuery(
|
||||||
|
id && ['item-detail-list', id],
|
||||||
|
(key) => requestFetchItems());
|
||||||
|
|
||||||
|
const handleFormSubmit =useCallback((payload)=>{
|
||||||
|
|
||||||
|
payload.redirect && history.push('/items/new');
|
||||||
|
|
||||||
|
},[history])
|
||||||
|
|
||||||
|
const handleCancel =useCallback(()=>{
|
||||||
|
|
||||||
|
history.push('/items/new');
|
||||||
|
},[])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={fetchAccounts.isFetching || fetchCategories.isFetching}
|
loading={fetchItemDetail.isFetching || fetchAccounts.isFetching || fetchCategories.isFetching }
|
||||||
name={'item-form'}>
|
name={'item-form'}>
|
||||||
<ItemForm />
|
<ItemForm
|
||||||
|
itemId={id}
|
||||||
|
onFormSubmit={handleFormSubmit}
|
||||||
|
onCancelForm={handleCancel}
|
||||||
|
/>
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -50,4 +69,5 @@ export default compose(
|
|||||||
withDashboard,
|
withDashboard,
|
||||||
withAccountsActions,
|
withAccountsActions,
|
||||||
withItemCategoriesActions,
|
withItemCategoriesActions,
|
||||||
|
withItemsActions
|
||||||
)(ItemFormContainer);
|
)(ItemFormContainer);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { compose } from 'utils';
|
|||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
import FilterDropdown from 'components/FilterDropdown';
|
import FilterDropdown from 'components/FilterDropdown';
|
||||||
import DialogConnect from 'connectors/Dialog.connector';
|
import withDialog from 'connectors/Dialog.connector';
|
||||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||||
import withItems from 'containers/Items/withItems';
|
import withItems from 'containers/Items/withItems';
|
||||||
import { If } from 'components';
|
import { If } from 'components';
|
||||||
@@ -34,6 +34,7 @@ const ItemsActionsBar = ({
|
|||||||
|
|
||||||
onFilterChanged,
|
onFilterChanged,
|
||||||
selectedRows = [],
|
selectedRows = [],
|
||||||
|
onBulkDelete,
|
||||||
}) => {
|
}) => {
|
||||||
const { path } = useRouteMatch();
|
const { path } = useRouteMatch();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -43,9 +44,11 @@ const ItemsActionsBar = ({
|
|||||||
<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />
|
<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />
|
||||||
));
|
));
|
||||||
|
|
||||||
const onClickNewItem = () => {
|
|
||||||
|
const onClickNewItem = useCallback(() => {
|
||||||
history.push('/items/new');
|
history.push('/items/new');
|
||||||
};
|
}, [history]);
|
||||||
|
|
||||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
|
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
|
||||||
selectedRows,
|
selectedRows,
|
||||||
]);
|
]);
|
||||||
@@ -62,6 +65,11 @@ const ItemsActionsBar = ({
|
|||||||
openDialog('item-form', {});
|
openDialog('item-form', {});
|
||||||
}, [openDialog]);
|
}, [openDialog]);
|
||||||
|
|
||||||
|
const handleBulkDelete = useCallback(() => {
|
||||||
|
onBulkDelete && onBulkDelete(selectedRows.map(r => r.id));
|
||||||
|
}, [onBulkDelete, selectedRows]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -116,9 +124,10 @@ const ItemsActionsBar = ({
|
|||||||
<If condition={hasSelectedRows}>
|
<If condition={hasSelectedRows}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='trash' iconSize={15} />}
|
||||||
|
text={<T id={'delete'}/>}
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
icon={<Icon icon='trash' />}
|
onClick={handleBulkDelete}
|
||||||
text={<T id={'delete'} />}
|
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
@@ -138,7 +147,7 @@ const ItemsActionsBar = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
DialogConnect,
|
withDialog,
|
||||||
withItems(({ itemsViews }) => ({
|
withItems(({ itemsViews }) => ({
|
||||||
itemsViews,
|
itemsViews,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
Position,
|
Position,
|
||||||
} from '@blueprintjs/core'
|
} from '@blueprintjs/core'
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import {compose} from 'utils';
|
import {compose} from 'utils';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
@@ -17,7 +16,6 @@ import Money from 'components/Money';
|
|||||||
import withItems from 'containers/Items/withItems';
|
import withItems from 'containers/Items/withItems';
|
||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
|
|
||||||
|
|
||||||
const ItemsDataTable = ({
|
const ItemsDataTable = ({
|
||||||
loading,
|
loading,
|
||||||
|
|
||||||
@@ -32,6 +30,7 @@ const ItemsDataTable = ({
|
|||||||
onSelectedRowsChange,
|
onSelectedRowsChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
|
|
||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
const [initialMount, setInitialMount] = useState(false);
|
const [initialMount, setInitialMount] = useState(false);
|
||||||
|
|
||||||
@@ -41,7 +40,14 @@ const ItemsDataTable = ({
|
|||||||
}
|
}
|
||||||
}, [itemsTableLoading, setInitialMount]);
|
}, [itemsTableLoading, setInitialMount]);
|
||||||
|
|
||||||
const handleEditItem = (item) => () => { onEditItem(item); };
|
const handleEditItem = useCallback(
|
||||||
|
(item) => () => {
|
||||||
|
onEditItem && onEditItem(item);
|
||||||
|
},
|
||||||
|
[onEditItem]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const handleDeleteItem = (item) => () => { onDeleteItem(item); };
|
const handleDeleteItem = (item) => () => { onDeleteItem(item); };
|
||||||
|
|
||||||
const actionMenuList = useCallback((item) =>
|
const actionMenuList = useCallback((item) =>
|
||||||
@@ -53,6 +59,7 @@ const ItemsDataTable = ({
|
|||||||
</Menu>), [handleEditItem, handleDeleteItem]);
|
</Menu>), [handleEditItem, handleDeleteItem]);
|
||||||
|
|
||||||
const columns = useMemo(() => [
|
const columns = useMemo(() => [
|
||||||
|
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id:'item_name' }),
|
Header: formatMessage({ id:'item_name' }),
|
||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
@@ -130,12 +137,16 @@ const ItemsDataTable = ({
|
|||||||
onFetchData={handleFetchData}
|
onFetchData={handleFetchData}
|
||||||
loading={itemsTableLoading && !initialMount}
|
loading={itemsTableLoading && !initialMount}
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
|
expandable={true}
|
||||||
|
treeGraph={true}
|
||||||
|
spinnerProps={{size: 30}}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange} />
|
onSelectedRowsChange={handleSelectedRowsChange} />
|
||||||
</LoadingIndicator>
|
</LoadingIndicator>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
|
|
||||||
withItems(({ itemsCurrentPage, itemsTableLoading }) => ({
|
withItems(({ itemsCurrentPage, itemsTableLoading }) => ({
|
||||||
itemsCurrentPage,
|
itemsCurrentPage,
|
||||||
itemsTableLoading,
|
itemsTableLoading,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useEffect, useCallback, useState } from 'react';
|
import React, { useEffect, useCallback, useState,useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Route,
|
Route,
|
||||||
Switch,
|
Switch,
|
||||||
|
useHistory
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Intent,
|
Intent,
|
||||||
@@ -42,13 +43,17 @@ function ItemsList({
|
|||||||
requestDeleteItem,
|
requestDeleteItem,
|
||||||
requestFetchItems,
|
requestFetchItems,
|
||||||
addItemsTableQueries,
|
addItemsTableQueries,
|
||||||
|
requestDeleteBulkItems,
|
||||||
}) {
|
}) {
|
||||||
const [deleteItem, setDeleteItem] = useState(false);
|
const [deleteItem, setDeleteItem] = useState(false);
|
||||||
const [selectedRows, setSelectedRows] = useState([]);
|
const [selectedRows, setSelectedRows] = useState([]);
|
||||||
const [tableLoading, setTableLoading] = useState(false);
|
const [tableLoading, setTableLoading] = useState(false);
|
||||||
|
const [bulkDelete, setBulkDelete] = useState(false);
|
||||||
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle(formatMessage({id:'items_list'}));
|
changePageTitle(formatMessage({id:'items_list'}));
|
||||||
}, [changePageTitle]);
|
}, [changePageTitle]);
|
||||||
@@ -68,7 +73,14 @@ function ItemsList({
|
|||||||
setDeleteItem(item);
|
setDeleteItem(item);
|
||||||
}, [setDeleteItem]);
|
}, [setDeleteItem]);
|
||||||
|
|
||||||
const handleEditItem = () => {};
|
const handleEditItem = useCallback(
|
||||||
|
(item) => {
|
||||||
|
history.push(`/items/${item.id}/edit`);
|
||||||
|
},
|
||||||
|
[history]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Handle cancel delete the item.
|
// Handle cancel delete the item.
|
||||||
const handleCancelDeleteItem = useCallback(() => {
|
const handleCancelDeleteItem = useCallback(() => {
|
||||||
@@ -121,14 +133,45 @@ function ItemsList({
|
|||||||
setSelectedRows(accounts);
|
setSelectedRows(accounts);
|
||||||
}, [setSelectedRows]);
|
}, [setSelectedRows]);
|
||||||
|
|
||||||
|
// Calculates the data table selected rows count.
|
||||||
|
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [selectedRows]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Handle items bulk delete button click.,
|
||||||
|
|
||||||
|
const handleBulkDelete = useCallback((itemsIds) => {
|
||||||
|
setBulkDelete(itemsIds);
|
||||||
|
}, [setBulkDelete]);
|
||||||
|
|
||||||
|
// Handle confirm items bulk delete.
|
||||||
|
const handleConfirmBulkDelete = useCallback(() => {
|
||||||
|
requestDeleteBulkItems(bulkDelete).then(() => {
|
||||||
|
setBulkDelete(false);
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({ id: 'the_items_has_been_successfully_deleted' }),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
}).catch((errors) => {
|
||||||
|
setBulkDelete(false);
|
||||||
|
});
|
||||||
|
}, [requestDeleteBulkItems, bulkDelete]);
|
||||||
|
|
||||||
|
// Handle cancel accounts bulk delete.
|
||||||
|
const handleCancelBulkDelete = useCallback(() => {
|
||||||
|
setBulkDelete(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
isLoading={fetchHook.isFetching}
|
isLoading={fetchHook.isFetching}
|
||||||
name={'items-list'}>
|
name={'items-list'}>
|
||||||
|
|
||||||
<ItemsActionsBar
|
<ItemsActionsBar
|
||||||
|
selectedRows={selectedRows}
|
||||||
onFilterChanged={handleFilterChanged}
|
onFilterChanged={handleFilterChanged}
|
||||||
selectedRows={selectedRows} />
|
onBulkDelete={handleBulkDelete}/>
|
||||||
|
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<Switch>
|
<Switch>
|
||||||
@@ -161,6 +204,20 @@ function ItemsList({
|
|||||||
id={'once_delete_this_item_you_will_able_to_restore_it'} />
|
id={'once_delete_this_item_you_will_able_to_restore_it'} />
|
||||||
</p>
|
</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'}/>}
|
||||||
|
confirmButtonText={`${formatMessage({id:'delete'})} (${selectedRowsCount})`}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={bulkDelete}
|
||||||
|
onCancel={handleCancelBulkDelete}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_items_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</DashboardPageContent>
|
</DashboardPageContent>
|
||||||
|
|||||||
9
client/src/containers/Items/withItemDetail.js
Normal file
9
client/src/containers/Items/withItemDetail.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import t from 'store/types';
|
||||||
|
import { getItemById } from 'store/items/items.reducer';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
|
itemDetail: getItemById(state, props.itemId),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps);
|
||||||
@@ -3,13 +3,17 @@ import {
|
|||||||
fetchItems,
|
fetchItems,
|
||||||
deleteItem,
|
deleteItem,
|
||||||
submitItem,
|
submitItem,
|
||||||
|
editItem,
|
||||||
|
deleteBulkItems
|
||||||
} from 'store/items/items.actions';
|
} from 'store/items/items.actions';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
requestFetchItems: (query) => dispatch(fetchItems({ query })),
|
requestFetchItems: (query) => dispatch(fetchItems({ query })),
|
||||||
requestDeleteItem: (id) => dispatch(deleteItem({ id })),
|
requestDeleteItem: (id) => dispatch(deleteItem({ id })),
|
||||||
|
requestDeleteBulkItems:(ids)=>dispatch(deleteBulkItems({ids})),
|
||||||
requestSubmitItem: (form) => dispatch(submitItem({ form })),
|
requestSubmitItem: (form) => dispatch(submitItem({ form })),
|
||||||
|
requestEditItem:(id,form) => dispatch(editItem({id,form})),
|
||||||
addBulkActionItem: (id) => dispatch({
|
addBulkActionItem: (id) => dispatch({
|
||||||
type: t.ITEM_BULK_ACTION_ADD, itemId: id
|
type: t.ITEM_BULK_ACTION_ADD, itemId: id
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -322,7 +322,10 @@ export default {
|
|||||||
organization_industry_:'Organization industry',
|
organization_industry_:'Organization industry',
|
||||||
base_currency_:'Base currency',
|
base_currency_:'Base currency',
|
||||||
date_format_:'Date format',
|
date_format_:'Date format',
|
||||||
view_name_:'View name'
|
category_name_:'Category name',
|
||||||
|
view_name_:'View name',
|
||||||
|
the_items_has_been_successfully_deleted: 'The items have been successfully deleted.',
|
||||||
|
once_delete_these_items_you_will_not_able_restore_them: 'Once you delete these items, you won\'t be able to retrieve them later. Are you sure you want to delete them?',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,13 @@ export default [
|
|||||||
}),
|
}),
|
||||||
breadcrumb: 'Categories',
|
breadcrumb: 'Categories',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `/items/:id/edit`,
|
||||||
|
component: LazyLoader({
|
||||||
|
loader: () => import('containers/Items/ItemFormPage'),
|
||||||
|
}),
|
||||||
|
breadcrumb: 'Edit Item',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `/items/new`,
|
path: `/items/new`,
|
||||||
component: LazyLoader({
|
component: LazyLoader({
|
||||||
|
|||||||
@@ -76,3 +76,19 @@ export const deleteItem = ({ id }) => {
|
|||||||
}).catch((error) => { reject(error); });
|
}).catch((error) => { reject(error); });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const deleteBulkItems = ({ ids }) => {
|
||||||
|
return dispatch => new Promise((resolve, reject) => {
|
||||||
|
ApiService.delete(`items`, { params: { ids }}).then((response) => {
|
||||||
|
dispatch({
|
||||||
|
type: t.ITEMS_BULK_DELETE,
|
||||||
|
payload: { ids }
|
||||||
|
});
|
||||||
|
resolve(response);
|
||||||
|
}).catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,4 +12,6 @@ export default {
|
|||||||
|
|
||||||
ITEMS_TABLE_LOADING: 'ITEMS_TABLE_LOADING',
|
ITEMS_TABLE_LOADING: 'ITEMS_TABLE_LOADING',
|
||||||
ITEMS_SET_CURRENT_VIEW: 'ITEMS_SET_CURRENT_VIEW',
|
ITEMS_SET_CURRENT_VIEW: 'ITEMS_SET_CURRENT_VIEW',
|
||||||
|
ITEMS_BULK_DELETE:'ITEMS_BULK_DELETE'
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ export default {
|
|||||||
check('sell_price').exists().isNumeric(),
|
check('sell_price').exists().isNumeric(),
|
||||||
check('cost_account_id').exists().isInt(),
|
check('cost_account_id').exists().isInt(),
|
||||||
check('sell_account_id').exists().isInt(),
|
check('sell_account_id').exists().isInt(),
|
||||||
check('category_id').optional().isInt(),
|
check('category_id').optional({ nullable: true }).isInt().toInt(),
|
||||||
check('note').optional(),
|
check('note').optional(),
|
||||||
check('attachment').optional(),
|
check('attachment').optional(),
|
||||||
check('')
|
check('')
|
||||||
|
|||||||
Reference in New Issue
Block a user