mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
refactoring: migrating to react-query to manage service-side state.
This commit is contained in:
@@ -1,228 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
Menu,
|
||||
Intent,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
Tag,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import moment from 'moment';
|
||||
import classNames from 'classnames';
|
||||
import { DataTable, Icon, LoadingIndicator } from 'components';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { useIsValuePassed } from 'hooks';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withInventoryAdjustments from './withInventoryAdjustments';
|
||||
import withInventoryAdjustmentActions from './withInventoryAdjustmentActions';
|
||||
|
||||
import { compose, saveInvoke } from 'utils';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
function InventoryAdjustmentDataTable({
|
||||
// withInventoryAdjustments
|
||||
inventoryAdjustmentItems,
|
||||
inventoryAdjustmentCurrentPage,
|
||||
inventoryAdjustmentLoading,
|
||||
inventoryAdjustmentsPagination,
|
||||
|
||||
// withInventoryAdjustmentsActions
|
||||
addInventoryAdjustmentTableQueries,
|
||||
|
||||
// #ownProps
|
||||
onDeleteInventoryAdjustment,
|
||||
onSelectedRowsChange,
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
const isLoadedBefore = useIsValuePassed(inventoryAdjustmentLoading, false);
|
||||
|
||||
const handleDeleteInventoryAdjustment = useCallback(
|
||||
(_adjustment) => () => {
|
||||
saveInvoke(onDeleteInventoryAdjustment, _adjustment);
|
||||
},
|
||||
[onDeleteInventoryAdjustment],
|
||||
);
|
||||
|
||||
const actionMenuList = useCallback(
|
||||
(adjustment) => (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={formatMessage({ id: 'view_details' })}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'delete_adjustment' })}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteInventoryAdjustment(adjustment)}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Menu>
|
||||
),
|
||||
[handleDeleteInventoryAdjustment, formatMessage],
|
||||
);
|
||||
|
||||
const onRowContextMenu = useCallback(
|
||||
(cell) => actionMenuList(cell.row.original),
|
||||
[actionMenuList],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'date',
|
||||
Header: formatMessage({ id: 'date' }),
|
||||
accessor: (r) => moment(r.date).format('YYYY MMM DD'),
|
||||
width: 115,
|
||||
className: 'date',
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
Header: formatMessage({ id: 'type' }),
|
||||
accessor: (row) =>
|
||||
row.type ? (
|
||||
<Tag minimal={true} round={true} intent={Intent.NONE}>
|
||||
{formatMessage({ id: row.type })}
|
||||
</Tag>
|
||||
) : (
|
||||
''
|
||||
),
|
||||
className: 'type',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
id: 'reason',
|
||||
Header: formatMessage({ id: 'reason' }),
|
||||
accessor: 'reason',
|
||||
className: 'reason',
|
||||
width: 115,
|
||||
},
|
||||
{
|
||||
id: 'reference_no',
|
||||
Header: formatMessage({ id: 'reference_no' }),
|
||||
accessor: 'reference_no',
|
||||
className: 'reference_no',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
id: 'publish',
|
||||
Header: formatMessage({ id: 'status' }),
|
||||
accessor: (r) => {
|
||||
return r.is_published ? (
|
||||
<Tag minimal={true}>
|
||||
<T id={'published'} />
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag minimal={true} intent={Intent.WARNING}>
|
||||
<T id={'draft'} />
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
width: 95,
|
||||
className: 'publish',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'description',
|
||||
Header: formatMessage({ id: 'description' }),
|
||||
accessor: 'description',
|
||||
disableSorting: true,
|
||||
width: 85,
|
||||
className: 'description',
|
||||
},
|
||||
{
|
||||
id: 'created_at',
|
||||
Header: formatMessage({ id: 'created_at' }),
|
||||
accessor: (r) => moment(r.created_at).format('YYYY MMM DD'),
|
||||
width: 125,
|
||||
className: 'created_at',
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_BOTTOM}
|
||||
>
|
||||
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
disableResizing: true,
|
||||
},
|
||||
],
|
||||
[actionMenuList, formatMessage],
|
||||
);
|
||||
|
||||
const handleDataTableFetchData = useCallback(
|
||||
({ pageSize, pageIndex, sortBy }) => {
|
||||
addInventoryAdjustmentTableQueries({
|
||||
...(sortBy.length > 0
|
||||
? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
}
|
||||
: {}),
|
||||
page_size: pageSize,
|
||||
page: pageIndex + 1,
|
||||
});
|
||||
},
|
||||
[addInventoryAdjustmentTableQueries],
|
||||
);
|
||||
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(selectedRows) => {
|
||||
onSelectedRowsChange &&
|
||||
onSelectedRowsChange(selectedRows.map((s) => s.original));
|
||||
},
|
||||
[onSelectedRowsChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
||||
<LoadingIndicator loading={inventoryAdjustmentLoading && !isLoadedBefore}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={inventoryAdjustmentItems}
|
||||
onFetchData={handleDataTableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
noInitialFetch={true}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
rowContextMenu={onRowContextMenu}
|
||||
pagination={true}
|
||||
autoResetSortBy={false}
|
||||
autoResetPage={false}
|
||||
pagesCount={inventoryAdjustmentsPagination.pagesCount}
|
||||
initialPageSize={inventoryAdjustmentsPagination.pageSize}
|
||||
initialPageIndex={inventoryAdjustmentsPagination.page - 1}
|
||||
/>
|
||||
</LoadingIndicator>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
withDialogActions,
|
||||
withInventoryAdjustmentActions,
|
||||
withInventoryAdjustments(
|
||||
({
|
||||
inventoryAdjustmentLoading,
|
||||
inventoryAdjustmentItems,
|
||||
inventoryAdjustmentCurrentPage,
|
||||
inventoryAdjustmentsPagination,
|
||||
}) => ({
|
||||
inventoryAdjustmentLoading,
|
||||
inventoryAdjustmentItems,
|
||||
inventoryAdjustmentCurrentPage,
|
||||
inventoryAdjustmentsPagination,
|
||||
}),
|
||||
),
|
||||
)(InventoryAdjustmentDataTable);
|
||||
@@ -1,79 +0,0 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
|
||||
import ItemsAlerts from './ItemsAlerts';
|
||||
import InventoryAdjustmentDataTable from './InventoryAdjustmentDataTable';
|
||||
|
||||
import withInventoryAdjustmentActions from './withInventoryAdjustmentActions';
|
||||
import withInventoryAdjustments from './withInventoryAdjustments';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Inventory Adjustment List.
|
||||
*/
|
||||
function InventoryAdjustmentList({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
// #withInventoryAdjustments
|
||||
inventoryAdjustmentTableQuery,
|
||||
|
||||
// #withAlertsActions.
|
||||
openAlert,
|
||||
|
||||
// #withInventoryAdjustmentsActions
|
||||
requestFetchInventoryAdjustmentTable,
|
||||
setSelectedRowsInventoryAdjustments,
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle(formatMessage({ id: 'inventory_adjustment_list' }));
|
||||
}, [changePageTitle, formatMessage]);
|
||||
|
||||
const fetchInventoryAdjustments = useQuery(
|
||||
['inventory-adjustment-list', inventoryAdjustmentTableQuery],
|
||||
(key, query) => requestFetchInventoryAdjustmentTable({ ...query }),
|
||||
);
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = (selectedRows) => {
|
||||
const selectedRowsIds = selectedRows.map((r) => r.id);
|
||||
setSelectedRowsInventoryAdjustments(selectedRowsIds);
|
||||
};
|
||||
|
||||
const handleDeleteInventoryAdjustment = ({ id }) => {
|
||||
openAlert('inventory-adjustment-delete', { inventoryId: id });
|
||||
};
|
||||
return (
|
||||
<DashboardInsider name={'inventory_adjustments'}>
|
||||
<DashboardPageContent>
|
||||
<Switch>
|
||||
<Route exact={true}>
|
||||
<InventoryAdjustmentDataTable
|
||||
onDeleteInventoryAdjustment={handleDeleteInventoryAdjustment}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
<ItemsAlerts />
|
||||
</DashboardPageContent>
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withInventoryAdjustmentActions,
|
||||
withInventoryAdjustments(({ inventoryAdjustmentTableQuery }) => ({
|
||||
inventoryAdjustmentTableQuery,
|
||||
})),
|
||||
withAlertsActions,
|
||||
)(InventoryAdjustmentList);
|
||||
@@ -1,88 +0,0 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useQuery } from 'react-query';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
|
||||
import ItemsAlerts from './ItemsAlerts';
|
||||
import ItemCategoriesDataTable from 'containers/Items/ItemCategoriesTable';
|
||||
import ItemsCategoryActionsBar from 'containers/Items/ItemsCategoryActionsBar';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Item categories list.
|
||||
*/
|
||||
const ItemCategoryList = ({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
// #withAlertsActions.
|
||||
openAlert,
|
||||
|
||||
// #withItemCategoriesActions
|
||||
requestFetchItemCategories,
|
||||
setSelectedRowsCategories,
|
||||
|
||||
// #withDialog
|
||||
openDialog,
|
||||
}) => {
|
||||
const { id } = useParams();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
id
|
||||
? changePageTitle(formatMessage({ id: 'edit_category_details' }))
|
||||
: changePageTitle(formatMessage({ id: 'category_list' }));
|
||||
}, [id, changePageTitle, formatMessage]);
|
||||
|
||||
const fetchCategories = useQuery(['items-categories-list'], () =>
|
||||
requestFetchItemCategories(),
|
||||
);
|
||||
|
||||
const handleFilterChanged = useCallback(() => {}, []);
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = (selectedRows) => {
|
||||
const selectedRowsIds = selectedRows.map((r) => r.id);
|
||||
setSelectedRowsCategories(selectedRowsIds);
|
||||
};
|
||||
|
||||
// Handle delete Item.
|
||||
const handleDeleteCategory = ({ id }) => {
|
||||
openAlert('item-category-delete', { itemCategoryId: id });
|
||||
};
|
||||
|
||||
// Handle Edit item category.
|
||||
const handleEditCategory = (category) => {
|
||||
openDialog('item-category-form', { action: 'edit', id: category.id });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardInsider name={'item-category-list'}>
|
||||
<ItemsCategoryActionsBar onFilterChanged={handleFilterChanged} />
|
||||
<DashboardPageContent>
|
||||
<ItemCategoriesDataTable
|
||||
onDeleteCategory={handleDeleteCategory}
|
||||
onEditCategory={handleEditCategory}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
/>
|
||||
</DashboardPageContent>
|
||||
<ItemsAlerts />
|
||||
</DashboardInsider>
|
||||
);
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withItemCategoriesActions,
|
||||
withDashboardActions,
|
||||
withDialogActions,
|
||||
withAlertsActions,
|
||||
)(ItemCategoryList);
|
||||
@@ -1,174 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
Menu,
|
||||
Intent,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Icon from 'components/Icon';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import { compose } from 'utils';
|
||||
import { useIsValuePassed } from 'hooks';
|
||||
import DataTable from 'components/DataTable';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import withItemCategories from './withItemCategories';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
const ItemsCategoryList = ({
|
||||
// #withItemCategories
|
||||
categoriesList,
|
||||
categoriesTableLoading,
|
||||
|
||||
// #withDialogActions.
|
||||
openDialog,
|
||||
|
||||
// #ownProps
|
||||
onFetchData,
|
||||
onDeleteCategory,
|
||||
onSelectedRowsChange,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const isLoadedBefore = useIsValuePassed(categoriesTableLoading, false);
|
||||
|
||||
const handelEditCategory = useCallback(
|
||||
(category) => () => {
|
||||
openDialog('item-category-form', { action: 'edit', id: category.id });
|
||||
},
|
||||
[openDialog],
|
||||
);
|
||||
|
||||
const handleDeleteCategory = useCallback(
|
||||
(category) => {
|
||||
onDeleteCategory(category);
|
||||
},
|
||||
[onDeleteCategory],
|
||||
);
|
||||
const actionMenuList = useCallback(
|
||||
(category) => (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={formatMessage({ id: 'edit_category' })}
|
||||
onClick={handelEditCategory(category)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'delete_category' })}
|
||||
intent={Intent.DANGER}
|
||||
onClick={() => handleDeleteCategory(category)}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Menu>
|
||||
),
|
||||
[handelEditCategory, handleDeleteCategory],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'name',
|
||||
Header: formatMessage({ id: 'category_name' }),
|
||||
accessor: 'name',
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
id: 'description',
|
||||
Header: formatMessage({ id: 'description' }),
|
||||
accessor: 'description',
|
||||
className: 'description',
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
id: 'count',
|
||||
Header: formatMessage({ id: 'count' }),
|
||||
accessor: 'count',
|
||||
className: 'count',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_TOP}
|
||||
>
|
||||
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
},
|
||||
],
|
||||
[actionMenuList, formatMessage],
|
||||
);
|
||||
|
||||
const handelFetchData = useCallback(
|
||||
(...params) => {
|
||||
onFetchData && onFetchData(...params);
|
||||
},
|
||||
[onFetchData],
|
||||
);
|
||||
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(selectedRows) => {
|
||||
onSelectedRowsChange &&
|
||||
onSelectedRowsChange(selectedRows.map((s) => s.original));
|
||||
},
|
||||
[onSelectedRowsChange],
|
||||
);
|
||||
|
||||
const selectionColumn = useMemo(
|
||||
() => ({
|
||||
minWidth: 42,
|
||||
width: 42,
|
||||
maxWidth: 42,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const handleRowContextMenu = useCallback(
|
||||
(cell) => {
|
||||
return actionMenuList(cell.row.original);
|
||||
},
|
||||
[actionMenuList],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
||||
<LoadingIndicator
|
||||
loading={categoriesTableLoading && !isLoadedBefore}
|
||||
mount={false}
|
||||
>
|
||||
<DataTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={categoriesList}
|
||||
onFetchData={handelFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={selectionColumn}
|
||||
expandable={true}
|
||||
sticky={true}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
rowContextMenu={handleRowContextMenu}
|
||||
/>
|
||||
</LoadingIndicator>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withItemCategories(({ categoriesList, categoriesTableLoading }) => ({
|
||||
categoriesList,
|
||||
categoriesTableLoading,
|
||||
})),
|
||||
withDialogActions,
|
||||
)(ItemsCategoryList);
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import React, { useMemo } 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 'style/pages/Items/PageForm.scss';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
import AppToaster from 'components/AppToaster';
|
||||
import ItemFormPrimarySection from './ItemFormPrimarySection';
|
||||
@@ -14,22 +15,16 @@ 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 withItem from 'containers/Items/withItem';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
|
||||
import { compose, transformToForm } from 'utils';
|
||||
import { transitionItemTypeKeyToLabel } from './utils';
|
||||
import {
|
||||
EditItemFormSchema,
|
||||
CreateItemFormSchema,
|
||||
transformItemFormData,
|
||||
} from './ItemForm.schema';
|
||||
|
||||
import 'style/pages/Items/PageForm.scss';
|
||||
import { useItemFormContext } from './ItemFormProvider';
|
||||
|
||||
const defaultInitialValues = {
|
||||
active: 1,
|
||||
@@ -50,46 +45,27 @@ const defaultInitialValues = {
|
||||
* Item form.
|
||||
*/
|
||||
function ItemForm({
|
||||
// #withItemActions
|
||||
requestSubmitItem,
|
||||
requestEditItem,
|
||||
|
||||
itemId,
|
||||
item,
|
||||
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();
|
||||
|
||||
// Item form context.
|
||||
const {
|
||||
setFiles,
|
||||
saveMedia,
|
||||
deletedFiles,
|
||||
setDeletedFiles,
|
||||
deleteMedia,
|
||||
} = useMedia({
|
||||
saveCallback: requestSubmitMedia,
|
||||
deleteCallback: requestDeleteMedia,
|
||||
});
|
||||
itemId,
|
||||
item,
|
||||
accounts,
|
||||
createItemMutate,
|
||||
editItemMutate,
|
||||
submitPayload,
|
||||
isNewMode
|
||||
} = useItemFormContext();
|
||||
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
/**
|
||||
* Initial values in create and edit mode.
|
||||
*/
|
||||
@@ -117,12 +93,7 @@ function ItemForm({
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
!isNewMode
|
||||
? changePageTitle(formatMessage({ id: 'edit_item_details' }))
|
||||
: changePageTitle(formatMessage({ id: 'new_item' }));
|
||||
}, [changePageTitle, isNewMode, formatMessage]);
|
||||
|
||||
// Transform API errors.
|
||||
const transformApiErrors = (errors) => {
|
||||
const fields = {};
|
||||
if (errors.find((e) => e.type === 'ITEM.NAME.ALREADY.EXISTS')) {
|
||||
@@ -155,12 +126,14 @@ function ItemForm({
|
||||
});
|
||||
resetForm();
|
||||
setSubmitting(false);
|
||||
queryCache.removeQueries(['items-table']);
|
||||
|
||||
// Submit payload.
|
||||
if (submitPayload.redirect) {
|
||||
history.push('/items');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle response error.
|
||||
const onError = (errors) => {
|
||||
setSubmitting(false);
|
||||
if (errors) {
|
||||
@@ -169,58 +142,12 @@ function ItemForm({
|
||||
}
|
||||
};
|
||||
if (isNewMode) {
|
||||
requestSubmitItem(form).then(onSuccess).catch(onError);
|
||||
createItemMutate(form).then(onSuccess).catch(onError);
|
||||
} else {
|
||||
requestEditItem(itemId, form).then(onSuccess).catch(onError);
|
||||
editItemMutate([itemId, form]).then(onSuccess).catch(onError);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (item && item.type) {
|
||||
changePageSubtitle(transitionItemTypeKeyToLabel(item.type));
|
||||
}
|
||||
}, [item, changePageSubtitle, formatMessage]);
|
||||
|
||||
const initialAttachmentFiles = useMemo(() => {
|
||||
return item && item.media
|
||||
? item.media.map((attach) => ({
|
||||
preview: attach.attachment_file,
|
||||
upload: true,
|
||||
metadata: { ...attach },
|
||||
}))
|
||||
: [];
|
||||
}, [item]);
|
||||
|
||||
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
|
||||
@@ -229,33 +156,21 @@ function ItemForm({
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleFormSubmit}
|
||||
>
|
||||
{({ isSubmitting, handleSubmit }) => (
|
||||
<Form>
|
||||
<div class={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||
<ItemFormPrimarySection itemType={item?.type} />
|
||||
<ItemFormBody />
|
||||
<ItemFormInventorySection />
|
||||
</div>
|
||||
<ItemFormFloatingActions
|
||||
isSubmitting={isSubmitting}
|
||||
itemId={itemId}
|
||||
handleSubmit={handleSubmit}
|
||||
onCancelClick={handleCancelBtnClick}
|
||||
onSubmitAndNewClick={handleSubmitAndNewClick}
|
||||
onSubmitClick={handleSubmitClick}
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
<Form>
|
||||
<div class={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||
<ItemFormPrimarySection />
|
||||
<ItemFormBody accounts={accounts} />
|
||||
<ItemFormInventorySection accounts={accounts} />
|
||||
</div>
|
||||
|
||||
<ItemFormFloatingActions />
|
||||
</Form>
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withItemsActions,
|
||||
withItem(({ item }) => ({ item })),
|
||||
withDashboardActions,
|
||||
withMediaActions,
|
||||
withSettings(({ itemsSettings }) => ({
|
||||
preferredCostAccount: parseInt(itemsSettings?.preferredCostAccount),
|
||||
preferredSellAccount: parseInt(itemsSettings?.preferredSellAccount),
|
||||
|
||||
@@ -12,15 +12,18 @@ import {
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
import { useItemFormContext } from './ItemFormProvider';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
import { ACCOUNT_PARENT_TYPE } from 'common/accountTypes';
|
||||
|
||||
import { compose, inputIntent } from 'utils';
|
||||
|
||||
/**
|
||||
* Item form body.
|
||||
*/
|
||||
function ItemFormBody({ accountsList, baseCurrency }) {
|
||||
function ItemFormBody({ baseCurrency }) {
|
||||
const { accounts } = useItemFormContext();
|
||||
|
||||
return (
|
||||
<div class="page-form__section page-form__section--selling-cost">
|
||||
<Row>
|
||||
@@ -84,14 +87,14 @@ function ItemFormBody({ accountsList, baseCurrency }) {
|
||||
)}
|
||||
>
|
||||
<AccountsSelectList
|
||||
accounts={accountsList}
|
||||
accounts={accounts}
|
||||
onAccountSelected={(account) => {
|
||||
form.setFieldValue('sell_account_id', account.id);
|
||||
}}
|
||||
defaultSelectText={<T id={'select_account'} />}
|
||||
selectedAccountId={value}
|
||||
disabled={!form.values.sellable}
|
||||
filterByTypes={['income']}
|
||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
|
||||
popoverFill={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -158,14 +161,14 @@ function ItemFormBody({ accountsList, baseCurrency }) {
|
||||
)}
|
||||
>
|
||||
<AccountsSelectList
|
||||
accounts={accountsList}
|
||||
accounts={accounts}
|
||||
onAccountSelected={(account) => {
|
||||
form.setFieldValue('cost_account_id', account.id);
|
||||
}}
|
||||
defaultSelectText={<T id={'select_account'} />}
|
||||
selectedAccountId={value}
|
||||
disabled={!form.values.purchasable}
|
||||
filterByTypes={['cost_of_goods_sold']}
|
||||
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
|
||||
popoverFill={true}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -178,9 +181,6 @@ function ItemFormBody({ accountsList, baseCurrency }) {
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAccounts(({ accountsList }) => ({
|
||||
accountsList,
|
||||
})),
|
||||
withSettings(({ organizationSettings }) => ({
|
||||
baseCurrency: organizationSettings?.baseCurrency,
|
||||
})),
|
||||
|
||||
@@ -1,32 +1,39 @@
|
||||
import React, { memo } from 'react';
|
||||
import React from 'react';
|
||||
import { Button, Intent, FormGroup, Checkbox } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { saveInvoke } from 'utils';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import { FastField } from 'formik';
|
||||
import { FastField, useFormikContext } from 'formik';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { useItemFormContext } from './ItemFormProvider';
|
||||
|
||||
/**
|
||||
* Item form floating actions.
|
||||
*/
|
||||
export default function ItemFormFloatingActions({
|
||||
isSubmitting,
|
||||
itemId,
|
||||
handleSubmit,
|
||||
onCancelClick,
|
||||
onSubmitClick,
|
||||
onSubmitAndNewClick,
|
||||
}) {
|
||||
export default function ItemFormFloatingActions() {
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
|
||||
// Item form context.
|
||||
const { setSubmitPayload, isNewMode } = useItemFormContext();
|
||||
|
||||
// Formik context.
|
||||
const { isSubmitting } = useFormikContext();
|
||||
|
||||
// Handle cancel button click.
|
||||
const handleCancelBtnClick = (event) => {
|
||||
saveInvoke(onCancelClick, event.currentTarget.value);
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
// Handle submit button click.
|
||||
const handleSubmitBtnClick = (event) => {
|
||||
saveInvoke(onSubmitClick, event);
|
||||
setSubmitPayload({ redirect: true });
|
||||
};
|
||||
|
||||
// Handle submit & new button click.
|
||||
const handleSubmitAndNewBtnClick = (event) => {
|
||||
saveInvoke(onSubmitAndNewClick, event);
|
||||
setSubmitPayload({ redirect: false });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -34,14 +41,16 @@ export default function ItemFormFloatingActions({
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
onClick={handleSubmitBtnClick}
|
||||
type="submit"
|
||||
className={'btn--submit'}
|
||||
>
|
||||
{itemId ? <T id={'edit'} /> : <T id={'save'} />}
|
||||
{isNewMode ? <T id={'save'} /> : <T id={'edit'} />}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className={'ml1'}
|
||||
className={classNames('ml1', 'btn--submit-new')}
|
||||
disabled={isSubmitting}
|
||||
onClick={handleSubmitAndNewBtnClick}
|
||||
type="submit"
|
||||
@@ -49,7 +58,11 @@ export default function ItemFormFloatingActions({
|
||||
<T id={'save_new'} />
|
||||
</Button>
|
||||
|
||||
<Button className={'ml1'} onClick={handleCancelBtnClick}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
className={'ml1'}
|
||||
onClick={handleCancelBtnClick}
|
||||
>
|
||||
<T id={'close'} />
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,37 +1,23 @@
|
||||
import React from 'react';
|
||||
import { FastField, ErrorMessage } from 'formik';
|
||||
import {
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
ControlGroup,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import {
|
||||
AccountsSelectList,
|
||||
MoneyInputGroup,
|
||||
InputPrependText,
|
||||
Col,
|
||||
Row,
|
||||
Hint,
|
||||
} from 'components';
|
||||
import { FormGroup } from '@blueprintjs/core';
|
||||
import { AccountsSelectList, Col, Row } from 'components';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
import {
|
||||
compose,
|
||||
tansformDateValue,
|
||||
momentFormatter,
|
||||
inputIntent,
|
||||
handleDateChange,
|
||||
} from 'utils';
|
||||
|
||||
import { compose, inputIntent } from 'utils';
|
||||
import { ACCOUNT_TYPE } from 'common/accountTypes';
|
||||
import { useItemFormContext } from './ItemFormProvider';
|
||||
|
||||
/**
|
||||
* Item form inventory sections.
|
||||
*/
|
||||
function ItemFormInventorySection({ accountsList, baseCurrency }) {
|
||||
function ItemFormInventorySection({ baseCurrency }) {
|
||||
const { accounts } = useItemFormContext();
|
||||
|
||||
return (
|
||||
<div class="page-form__section page-form__section--inventory">
|
||||
<h3>
|
||||
@@ -55,12 +41,13 @@ function ItemFormInventorySection({ accountsList, baseCurrency }) {
|
||||
)}
|
||||
>
|
||||
<AccountsSelectList
|
||||
accounts={accountsList}
|
||||
accounts={accounts}
|
||||
onAccountSelected={(account) => {
|
||||
form.setFieldValue('inventory_account_id', account.id);
|
||||
}}
|
||||
defaultSelectText={<T id={'select_account'} />}
|
||||
selectedAccountId={value}
|
||||
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
@@ -72,9 +59,6 @@ function ItemFormInventorySection({ accountsList, baseCurrency }) {
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAccounts(({ accountsList }) => ({
|
||||
accountsList,
|
||||
})),
|
||||
withSettings(({ organizationSettings }) => ({
|
||||
baseCurrency: organizationSettings?.baseCurrency,
|
||||
})),
|
||||
|
||||
@@ -1,82 +1,27 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useParams, useHistory } from 'react-router-dom';
|
||||
import { useQuery } from 'react-query';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import { ItemFormProvider } from './ItemFormProvider';
|
||||
import DashboardCard from 'components/Dashboard/DashboardCard';
|
||||
import ItemForm from 'containers/Items/ItemForm';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
|
||||
import withItemsActions from './withItemsActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const ItemFormContainer = ({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
// #withAccountsActions
|
||||
requestFetchAccounts,
|
||||
|
||||
// #withItemsActions
|
||||
requestFetchItems,
|
||||
requestFetchItem,
|
||||
|
||||
// #withItemCategoriesActions
|
||||
requestFetchItemCategories,
|
||||
}) => {
|
||||
/**
|
||||
* Item form page.
|
||||
*/
|
||||
function ItemFormPage() {
|
||||
const { id } = useParams();
|
||||
const history = useHistory();
|
||||
|
||||
const fetchAccounts = useQuery('accounts-list', (key) =>
|
||||
requestFetchAccounts(),
|
||||
);
|
||||
|
||||
const fetchCategories = useQuery('item-categories-list', (key) =>
|
||||
requestFetchItemCategories(),
|
||||
);
|
||||
|
||||
const fetchItemDetail = useQuery(
|
||||
['item', id],
|
||||
(key, _id) => requestFetchItem(_id),
|
||||
{ enabled: id && id },
|
||||
);
|
||||
const handleFormSubmit = useCallback(
|
||||
(payload) => {
|
||||
payload.redirect && history.push('/items');
|
||||
},
|
||||
[history],
|
||||
);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
history.goBack();
|
||||
}, [history]);
|
||||
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
loading={
|
||||
fetchItemDetail.isFetching ||
|
||||
fetchAccounts.isFetching ||
|
||||
fetchCategories.isFetching
|
||||
}
|
||||
name={'item-form'}
|
||||
>
|
||||
<ItemFormProvider itemId={id}>
|
||||
<DashboardCard page>
|
||||
<ItemForm
|
||||
onFormSubmit={handleFormSubmit}
|
||||
itemId={id}
|
||||
onCancelForm={handleCancel}
|
||||
/>
|
||||
<ItemForm />
|
||||
</DashboardCard>
|
||||
</DashboardInsider>
|
||||
</ItemFormProvider>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withAccountsActions,
|
||||
withItemCategoriesActions,
|
||||
withItemsActions,
|
||||
)(ItemFormContainer);
|
||||
export default compose(withDashboardActions)(ItemFormPage);
|
||||
|
||||
@@ -8,8 +8,7 @@ import {
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { ErrorMessage, FastField } from 'formik';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { ErrorMessage, FastField, useFormikContext } from 'formik';
|
||||
import {
|
||||
CategoriesSelectList,
|
||||
Hint,
|
||||
@@ -20,28 +19,20 @@ import {
|
||||
import classNames from 'classnames';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import withItemCategories from 'containers/Items/withItemCategories';
|
||||
import withAccounts from 'containers/Accounts/withAccounts';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
|
||||
import { compose, handleStringChange, inputIntent } from 'utils';
|
||||
import { transitionItemTypeKeyToLabel } from './utils';
|
||||
import { useItemFormContext } from './ItemFormProvider';
|
||||
import { handleStringChange, inputIntent } from 'utils';
|
||||
|
||||
/**
|
||||
* Item form primary section.
|
||||
*/
|
||||
function ItemFormPrimarySection({
|
||||
// #withItemCategories
|
||||
categoriesList,
|
||||
export default function ItemFormPrimarySection() {
|
||||
const { itemsCategories } = useItemFormContext();
|
||||
|
||||
// #withDashboardActions
|
||||
changePageSubtitle,
|
||||
|
||||
// #ownProps
|
||||
itemType,
|
||||
}) {
|
||||
const nameFieldRef = useRef(null);
|
||||
|
||||
// Formik context.
|
||||
const { values: { itemType } } = useFormikContext();
|
||||
|
||||
useEffect(() => {
|
||||
// Auto focus item name field once component mount.
|
||||
if (nameFieldRef.current) {
|
||||
@@ -94,7 +85,6 @@ function ItemFormPrimarySection({
|
||||
inline={true}
|
||||
onChange={handleStringChange((_value) => {
|
||||
form.setFieldValue('type', _value);
|
||||
changePageSubtitle(transitionItemTypeKeyToLabel(_value));
|
||||
})}
|
||||
selectedValue={value}
|
||||
disabled={itemType === 'inventory'}
|
||||
@@ -155,7 +145,7 @@ function ItemFormPrimarySection({
|
||||
className={classNames('form-group--category', Classes.FILL)}
|
||||
>
|
||||
<CategoriesSelectList
|
||||
categoriesList={categoriesList}
|
||||
categories={itemsCategories}
|
||||
selecetedCategoryId={value}
|
||||
onCategorySelected={(category) => {
|
||||
form.setFieldValue('category_id', category.id);
|
||||
@@ -179,13 +169,3 @@ function ItemFormPrimarySection({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAccounts(({ accountsList }) => ({
|
||||
accountsList,
|
||||
})),
|
||||
withItemCategories(({ categoriesList }) => ({
|
||||
categoriesList,
|
||||
})),
|
||||
withDashboardActions,
|
||||
)(ItemFormPrimarySection);
|
||||
|
||||
85
client/src/containers/Items/ItemFormProvider.js
Normal file
85
client/src/containers/Items/ItemFormProvider.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import React, { useEffect, createContext, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import {
|
||||
useItem,
|
||||
useItemsCategories,
|
||||
useCreateItem,
|
||||
useEditItem,
|
||||
useAccounts,
|
||||
} from 'hooks/query';
|
||||
import { useDashboardPageTitle } from 'hooks/state';
|
||||
|
||||
const ItemFormContext = createContext();
|
||||
|
||||
/**
|
||||
* Accounts chart data provider.
|
||||
*/
|
||||
function ItemFormProvider({ itemId, ...props }) {
|
||||
// Fetches the accounts list.
|
||||
const { isFetching: isAccountsLoading, data: accounts } = useAccounts();
|
||||
|
||||
// Fetches the items categories list.
|
||||
const {
|
||||
isFetching: isItemsCategoriesLoading,
|
||||
data: { itemsCategories },
|
||||
} = useItemsCategories();
|
||||
|
||||
// Fetches the given item details.
|
||||
const { isFetching: isItemLoading, data: item } = useItem(itemId, {
|
||||
enabled: !!itemId,
|
||||
});
|
||||
// Create and edit item mutations.
|
||||
const { mutateAsync: editItemMutate } = useEditItem();
|
||||
const { mutateAsync: createItemMutate } = useCreateItem();
|
||||
|
||||
// Holds data of submit button once clicked to form submit function.
|
||||
const [submitPayload, setSubmitPayload] = useState({});
|
||||
|
||||
// Detarmines whether the form new mode.
|
||||
const isNewMode = !itemId;
|
||||
|
||||
// Provider state.
|
||||
const provider = {
|
||||
itemId,
|
||||
accounts,
|
||||
item,
|
||||
itemsCategories,
|
||||
submitPayload,
|
||||
isNewMode,
|
||||
|
||||
isAccountsLoading,
|
||||
isItemsCategoriesLoading,
|
||||
isItemLoading,
|
||||
|
||||
createItemMutate,
|
||||
editItemMutate,
|
||||
setSubmitPayload
|
||||
};
|
||||
|
||||
// Format message intl.
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// Change page title dispatcher.
|
||||
const changePageTitle = useDashboardPageTitle();
|
||||
|
||||
// Changes the page title in new and edit mode.
|
||||
useEffect(() => {
|
||||
!isNewMode
|
||||
? changePageTitle(formatMessage({ id: 'edit_item_details' }))
|
||||
: changePageTitle(formatMessage({ id: 'new_item' }));
|
||||
}, [changePageTitle, isNewMode, formatMessage]);
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
loading={isAccountsLoading || isItemsCategoriesLoading || isItemLoading}
|
||||
name={'item-form'}
|
||||
>
|
||||
<ItemFormContext.Provider value={provider} {...props} />
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
const useItemFormContext = () => React.useContext(ItemFormContext);
|
||||
|
||||
export { ItemFormProvider, useItemFormContext };
|
||||
@@ -1,11 +1,9 @@
|
||||
import React, { useMemo, useCallback, useState, useEffect } from 'react';
|
||||
import { useRouteMatch, useHistory, useParams } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
MenuItem,
|
||||
Popover,
|
||||
NavbarGroup,
|
||||
Menu,
|
||||
NavbarDivider,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
@@ -16,57 +14,44 @@ import {
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import Icon from 'components/Icon';
|
||||
import FilterDropdown from 'components/FilterDropdown';
|
||||
import { If, DashboardActionViewsList } from 'components';
|
||||
|
||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||
import { useItemsListContext } from './ItemsListProvider';
|
||||
|
||||
import withItems from 'containers/Items/withItems';
|
||||
import withItemsActions from './withItemsActions';
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const ItemsActionsBar = ({
|
||||
// #withResourceDetail
|
||||
resourceFields,
|
||||
|
||||
/**
|
||||
* Items actions bar.
|
||||
*/
|
||||
function ItemsActionsBar({
|
||||
// #withItems
|
||||
itemsViews,
|
||||
itemsSelectedRows,
|
||||
|
||||
//#withItemActions
|
||||
// #withItemActions
|
||||
addItemsTableQueries,
|
||||
changeItemsCurrentView,
|
||||
|
||||
// #withAlertActions
|
||||
openAlert,
|
||||
onFilterChanged,
|
||||
}) => {
|
||||
}) {
|
||||
// Items list context.
|
||||
const { itemsViews } = useItemsListContext();
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
|
||||
const onClickNewItem = useCallback(() => {
|
||||
// Handle `new item` button click.
|
||||
const onClickNewItem = () => {
|
||||
history.push('/items/new');
|
||||
}, [history]);
|
||||
|
||||
const filterDropdown = FilterDropdown({
|
||||
fields: resourceFields,
|
||||
initialCondition: {
|
||||
fieldKey: 'name',
|
||||
compatator: 'contains',
|
||||
value: '',
|
||||
},
|
||||
onFilterChange: (filterConditions) => {
|
||||
addItemsTableQueries({
|
||||
filter_roles: filterConditions || '',
|
||||
});
|
||||
onFilterChanged && onFilterChanged(filterConditions);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Handle tab changing.
|
||||
const handleTabChange = (viewId) => {
|
||||
changeItemsCurrentView(viewId.id || -1);
|
||||
addItemsTableQueries({
|
||||
custom_view_id: viewId.id || null,
|
||||
});
|
||||
@@ -85,7 +70,6 @@ const ItemsActionsBar = ({
|
||||
views={itemsViews}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
|
||||
<NavbarDivider />
|
||||
|
||||
<Button
|
||||
@@ -97,7 +81,7 @@ const ItemsActionsBar = ({
|
||||
<NavbarDivider />
|
||||
|
||||
<Popover
|
||||
content={filterDropdown}
|
||||
content={''}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
@@ -133,21 +117,9 @@ const ItemsActionsBar = ({
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
resourceName: 'items',
|
||||
});
|
||||
|
||||
const withItemsActionsBar = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withItemsActionsBar,
|
||||
withItems(({ itemsViews, itemsSelectedRows }) => ({
|
||||
itemsViews,
|
||||
itemsSelectedRows,
|
||||
})),
|
||||
withResourceDetail(({ resourceFields }) => ({
|
||||
resourceFields,
|
||||
})),
|
||||
withItems(({ itemsSelectedRows }) => ({ itemsSelectedRows })),
|
||||
withItemsActions,
|
||||
withAlertActions,
|
||||
)(ItemsActionsBar);
|
||||
|
||||
@@ -3,9 +3,6 @@ import ItemDeleteAlert from 'containers/Alerts/Items/ItemDeleteAlert';
|
||||
import ItemInactivateAlert from 'containers/Alerts/Items/ItemInactivateAlert';
|
||||
import ItemActivateAlert from 'containers/Alerts/Items/ItemActivateAlert';
|
||||
import ItemBulkDeleteAlert from 'containers/Alerts/Items/ItemBulkDeleteAlert';
|
||||
import ItemCategoryDeleteAlert from 'containers/Alerts/Items/ItemCategoryDeleteAlert';
|
||||
import ItemCategoryBulkDeleteAlert from 'containers/Alerts/Items/ItemCategoryBulkDeleteAlert';
|
||||
import InventoryAdjustmentDeleteAlert from 'containers/Alerts/Items/InventoryAdjustmentDeleteAlert';
|
||||
|
||||
/**
|
||||
* Items alert.
|
||||
@@ -17,9 +14,6 @@ export default function ItemsAlerts() {
|
||||
<ItemInactivateAlert name={'item-inactivate'} />
|
||||
<ItemActivateAlert name={'item-activate'} />
|
||||
<ItemBulkDeleteAlert name={'items-bulk-delete'} />
|
||||
<ItemCategoryDeleteAlert name={'item-category-delete'} />
|
||||
<ItemCategoryBulkDeleteAlert name={'item-categories-bulk-delete'} />
|
||||
<InventoryAdjustmentDeleteAlert name={'inventory-adjustment-delete'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
NavbarGroup,
|
||||
NavbarDivider,
|
||||
Button,
|
||||
Classes,
|
||||
Intent,
|
||||
Popover,
|
||||
Position,
|
||||
PopoverInteractionKind,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import { If, Icon } from 'components';
|
||||
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
|
||||
import withItemCategories from './withItemCategories';
|
||||
import withItemCategoriesActions from './withItemCategoriesActions';
|
||||
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const ItemsCategoryActionsBar = ({
|
||||
// #withDialog
|
||||
openDialog,
|
||||
|
||||
// #withItemCategories
|
||||
itemCategoriesSelectedRows,
|
||||
|
||||
// #withAlertActions
|
||||
openAlert,
|
||||
|
||||
}) => {
|
||||
const [filterCount, setFilterCount] = useState(0);
|
||||
|
||||
const onClickNewCategory = useCallback(() => {
|
||||
openDialog('item-category-form', {});
|
||||
}, [openDialog]);
|
||||
|
||||
const handelBulkDelete = () => {
|
||||
openAlert('item-categories-bulk-delete', {
|
||||
itemCategoriesIds: itemCategoriesSelectedRows,
|
||||
});
|
||||
};
|
||||
console.log(itemCategoriesSelectedRows, 'EE');
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="plus" />}
|
||||
text={<T id={'new_category'} />}
|
||||
onClick={onClickNewCategory}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
|
||||
<Popover
|
||||
minimal={true}
|
||||
// content={filterDropdown}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
canOutsideClickClose={true}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||
text={
|
||||
filterCount <= 0 ? (
|
||||
<T id={'filter'} />
|
||||
) : (
|
||||
`${filterCount} filters applied`
|
||||
)
|
||||
}
|
||||
icon={<Icon icon="filter-16" iconSize={16} />}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
<If condition={itemCategoriesSelectedRows.length}>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
text={<T id={'delete'} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handelBulkDelete}
|
||||
/>
|
||||
</If>
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||
text={<T id={'import'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default compose(
|
||||
withDialogActions,
|
||||
withDashboardActions,
|
||||
withItemCategories(({ itemCategoriesSelectedRows }) => ({
|
||||
itemCategoriesSelectedRows,
|
||||
})),
|
||||
withItemCategoriesActions,
|
||||
withAlertActions,
|
||||
)(ItemsCategoryActionsBar);
|
||||
@@ -1,170 +1,39 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
Intent,
|
||||
Tag,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import { DataTable, Choose } from 'components';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
DataTable,
|
||||
Money,
|
||||
LoadingIndicator,
|
||||
Choose,
|
||||
If,
|
||||
} from 'components';
|
||||
import ItemsEmptyStatus from './ItemsEmptyStatus';
|
||||
import { useIsValuePassed } from 'hooks';
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import withItems from 'containers/Items/withItems';
|
||||
import withItemsActions from 'containers/Items/withItemsActions';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import { compose, saveInvoke, isBlank, defaultToTransform } from 'utils';
|
||||
import { useItemsListContext } from './ItemsListProvider';
|
||||
import { compose } from 'utils';
|
||||
import {
|
||||
QuantityOnHandCell,
|
||||
SellPriceCell,
|
||||
CostPriceCell,
|
||||
ItemTypeAccessor,
|
||||
ItemsActionsTableCell,
|
||||
} from './components';
|
||||
|
||||
// Items datatable.
|
||||
function ItemsDataTable({
|
||||
// #withItems
|
||||
itemsTableLoading,
|
||||
itemsCurrentPage,
|
||||
itemsTableQuery,
|
||||
itemsCurrentViewId,
|
||||
itemsPagination,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withItemsActions
|
||||
addItemsTableQueries,
|
||||
|
||||
// #withSettings
|
||||
baseCurrency,
|
||||
|
||||
// props
|
||||
onEditItem,
|
||||
onDeleteItem,
|
||||
onInactiveItem,
|
||||
onActivateItem,
|
||||
onSelectedRowsChange,
|
||||
itemsViewLoading,
|
||||
// #ownProps
|
||||
tableProps
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
const isLoadedBefore = useIsValuePassed(itemsTableLoading, false);
|
||||
|
||||
const handleFetchData = useCallback(
|
||||
({ pageIndex, pageSize, sortBy }) => {
|
||||
addItemsTableQueries({
|
||||
page_size: pageSize,
|
||||
page: pageIndex + 1,
|
||||
...(sortBy.length > 0
|
||||
? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
},
|
||||
[addItemsTableQueries],
|
||||
);
|
||||
|
||||
const handleMakeAdjustment = useCallback(
|
||||
(item) => () => {
|
||||
openDialog('inventory-adjustment-form', {
|
||||
action: 'make_adjustment',
|
||||
itemId: item.id,
|
||||
});
|
||||
},
|
||||
[openDialog],
|
||||
);
|
||||
|
||||
const handleEditItem = useCallback(
|
||||
(item) => () => {
|
||||
onEditItem && onEditItem(item);
|
||||
},
|
||||
[onEditItem],
|
||||
);
|
||||
|
||||
const handleDeleteItem = useCallback(
|
||||
(item) => () => {
|
||||
onDeleteItem(item);
|
||||
},
|
||||
[onDeleteItem],
|
||||
);
|
||||
|
||||
const actionMenuList = useCallback(
|
||||
(item) => (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={formatMessage({ id: 'view_details' })}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={formatMessage({ id: 'edit_item' })}
|
||||
onClick={handleEditItem(item)}
|
||||
/>
|
||||
<If condition={item.active}>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'inactivate_item' })}
|
||||
icon={<Icon icon="pause-16" iconSize={16} />}
|
||||
onClick={() => onInactiveItem(item)}
|
||||
/>
|
||||
</If>
|
||||
<If condition={!item.active}>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'activate_item' })}
|
||||
icon={<Icon icon="play-16" iconSize={16} />}
|
||||
onClick={() => onActivateItem(item)}
|
||||
/>
|
||||
</If>
|
||||
|
||||
<If condition={item.type === 'inventory'}>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'make_adjustment' })}
|
||||
onClick={handleMakeAdjustment(item)}
|
||||
/>
|
||||
</If>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'delete_item' })}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
onClick={handleDeleteItem(item)}
|
||||
intent={Intent.DANGER}
|
||||
/>
|
||||
</Menu>
|
||||
),
|
||||
[
|
||||
handleEditItem,
|
||||
handleDeleteItem,
|
||||
onInactiveItem,
|
||||
onActivateItem,
|
||||
formatMessage,
|
||||
],
|
||||
);
|
||||
|
||||
const quantityonHandCell = ({ value: quantity }) => {
|
||||
return quantity <= 0 ? (
|
||||
<span className={'quantity_on_hand'}>{quantity}</span>
|
||||
) : (
|
||||
<span>{quantity}</span>
|
||||
);
|
||||
};
|
||||
|
||||
const handleRowContextMenu = useCallback(
|
||||
(cell) => {
|
||||
return actionMenuList(cell.row.original);
|
||||
},
|
||||
[actionMenuList],
|
||||
);
|
||||
const {
|
||||
items,
|
||||
pagination,
|
||||
isItemsLoading,
|
||||
isEmptyStatus,
|
||||
} = useItemsListContext();
|
||||
|
||||
// Datatable columns.
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -181,14 +50,7 @@ function ItemsDataTable({
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'item_type' }),
|
||||
accessor: (row) =>
|
||||
row.type ? (
|
||||
<Tag minimal={true} round={true} intent={Intent.NONE}>
|
||||
{formatMessage({ id: row.type })}
|
||||
</Tag>
|
||||
) : (
|
||||
''
|
||||
),
|
||||
accessor: ItemTypeAccessor,
|
||||
className: 'item_type',
|
||||
width: 120,
|
||||
},
|
||||
@@ -200,127 +62,77 @@ function ItemsDataTable({
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'sell_price' }),
|
||||
accessor: (row) =>
|
||||
!isBlank(row.sell_price) ? (
|
||||
<Money amount={row.sell_price} currency={baseCurrency} />
|
||||
) : (
|
||||
''
|
||||
),
|
||||
Cell: SellPriceCell,
|
||||
accessor: 'sell_price',
|
||||
className: 'sell-price',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'cost_price' }),
|
||||
accessor: (row) =>
|
||||
!isBlank(row.cost_price) ? (
|
||||
<Money amount={row.cost_price} currency={baseCurrency} />
|
||||
) : (
|
||||
''
|
||||
),
|
||||
Cell: CostPriceCell,
|
||||
accessor: 'cost_price',
|
||||
className: 'cost-price',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
Header: formatMessage({ id: 'quantity_on_hand' }),
|
||||
accessor: 'quantity_on_hand',
|
||||
Cell: quantityonHandCell,
|
||||
Cell: QuantityOnHandCell,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_BOTTOM}
|
||||
>
|
||||
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
Cell: ItemsActionsTableCell,
|
||||
width: 60,
|
||||
skeletonWidthMin: 100,
|
||||
},
|
||||
],
|
||||
[actionMenuList, formatMessage],
|
||||
[formatMessage],
|
||||
);
|
||||
|
||||
// Handle selected row change.
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(selectedRows) => {
|
||||
saveInvoke(
|
||||
onSelectedRowsChange,
|
||||
selectedRows.map((s) => s.original),
|
||||
);
|
||||
},
|
||||
[onSelectedRowsChange],
|
||||
);
|
||||
|
||||
const rowClassNames = (row) => {
|
||||
return {
|
||||
inactive: !row.original.active,
|
||||
};
|
||||
};
|
||||
|
||||
const showEmptyStatus = [
|
||||
itemsCurrentPage.length === 0,
|
||||
itemsCurrentViewId === -1,
|
||||
].every((condition) => condition === true);
|
||||
// Table row class names.
|
||||
const rowClassNames = (row) => ({
|
||||
inactive: !row.original.active,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
||||
<LoadingIndicator
|
||||
loading={(itemsTableLoading && !isLoadedBefore) || itemsViewLoading}
|
||||
>
|
||||
<Choose>
|
||||
<Choose.When condition={showEmptyStatus}>
|
||||
<ItemsEmptyStatus />
|
||||
</Choose.When>
|
||||
<Choose>
|
||||
<Choose.When condition={isEmptyStatus}>
|
||||
<ItemsEmptyStatus />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={itemsCurrentPage}
|
||||
onFetchData={handleFetchData}
|
||||
noInitialFetch={true}
|
||||
selectionColumn={true}
|
||||
spinnerProps={{ size: 30 }}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
rowContextMenu={handleRowContextMenu}
|
||||
expandable={false}
|
||||
sticky={true}
|
||||
rowClassNames={rowClassNames}
|
||||
pagination={true}
|
||||
pagesCount={itemsPagination.pagesCount}
|
||||
autoResetSortBy={false}
|
||||
autoResetPage={false}
|
||||
initialPageSize={itemsTableQuery.page_size}
|
||||
initialPageIndex={itemsTableQuery.page - 1}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</LoadingIndicator>
|
||||
<Choose.Otherwise>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={items}
|
||||
loading={isItemsLoading}
|
||||
headerLoading={isItemsLoading}
|
||||
noInitialFetch={true}
|
||||
selectionColumn={true}
|
||||
spinnerProps={{ size: 30 }}
|
||||
expandable={false}
|
||||
sticky={true}
|
||||
rowClassNames={rowClassNames}
|
||||
pagination={true}
|
||||
manualSortBy={true}
|
||||
pagesCount={1}
|
||||
autoResetSortBy={false}
|
||||
autoResetPage={false}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
initialPageSize={pagination.pageSize}
|
||||
initialPageIndex={pagination.page}
|
||||
{...tableProps}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withItems(
|
||||
({
|
||||
itemsCurrentPage,
|
||||
itemsTableLoading,
|
||||
itemsTableQuery,
|
||||
itemsCurrentViewId,
|
||||
itemsPagination,
|
||||
}) => ({
|
||||
itemsCurrentPage,
|
||||
itemsTableLoading,
|
||||
itemsTableQuery,
|
||||
itemsCurrentViewId,
|
||||
itemsPagination,
|
||||
}),
|
||||
),
|
||||
withSettings(({ organizationSettings }) => ({
|
||||
baseCurrency: organizationSettings?.baseCurrency,
|
||||
})),
|
||||
withItemsActions,
|
||||
withDialogActions,
|
||||
)(ItemsDataTable);
|
||||
|
||||
@@ -1,96 +1,36 @@
|
||||
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import React from 'react';
|
||||
import { compose } from 'utils';
|
||||
|
||||
import 'style/pages/Items/List.scss';
|
||||
|
||||
import ItemsViewPage from './ItemsViewPage';
|
||||
import ItemsActionsBar from 'containers/Items/ItemsActionsBar';
|
||||
import ItemsActionsBar from './ItemsActionsBar';
|
||||
import ItemsAlerts from './ItemsAlerts';
|
||||
|
||||
import { ItemsListProvider } from './ItemsListProvider';
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
|
||||
import withItems from 'containers/Items/withItems';
|
||||
import withResourceActions from 'containers/Resources/withResourcesActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withItemsActions from 'containers/Items/withItemsActions';
|
||||
import withViewsActions from 'containers/Views/withViewsActions';
|
||||
|
||||
import 'style/pages/Items/List.scss';
|
||||
|
||||
/**
|
||||
* Items list.
|
||||
*/
|
||||
function ItemsList({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
// #withResourceActions
|
||||
requestFetchResourceViews,
|
||||
requestFetchResourceFields,
|
||||
|
||||
// #withItems
|
||||
itemsTableQuery,
|
||||
|
||||
// #withItemsActions
|
||||
requestFetchItems,
|
||||
addItemsTableQueries,
|
||||
itemsTableQuery
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle(formatMessage({ id: 'items_list' }));
|
||||
}, [changePageTitle, formatMessage]);
|
||||
|
||||
// Handle fetching the resource views.
|
||||
const fetchResourceViews = useQuery(
|
||||
['resource-views', 'items'],
|
||||
(key, resourceName) => requestFetchResourceViews(resourceName),
|
||||
);
|
||||
|
||||
// Handle fetching the resource fields.
|
||||
const fetchResourceFields = useQuery(
|
||||
['resource-fields', 'items'],
|
||||
(key, resourceName) => requestFetchResourceFields(resourceName),
|
||||
);
|
||||
|
||||
// Handle fetching the items table based on the given query.
|
||||
const fetchItems = useQuery(['items-table', itemsTableQuery], (key, _query) =>
|
||||
requestFetchItems({ ..._query }),
|
||||
);
|
||||
|
||||
// Handle filter change to re-fetch the items.
|
||||
const handleFilterChanged = useCallback(
|
||||
(filterConditions) => {
|
||||
addItemsTableQueries({
|
||||
filter_roles: filterConditions || '',
|
||||
});
|
||||
},
|
||||
[addItemsTableQueries],
|
||||
);
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
loading={fetchResourceViews.isFetching || fetchResourceFields.isFetching}
|
||||
name={'items-list'}
|
||||
>
|
||||
<ItemsActionsBar onFilterChanged={handleFilterChanged} />
|
||||
<ItemsListProvider query={itemsTableQuery}>
|
||||
<ItemsActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<ItemsViewPage />
|
||||
</DashboardPageContent>
|
||||
<ItemsAlerts />
|
||||
</DashboardInsider>
|
||||
</ItemsListProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withResourceActions,
|
||||
withDashboardActions,
|
||||
withItemsActions,
|
||||
withViewsActions,
|
||||
withItems(({ itemsTableQuery }) => ({
|
||||
itemsTableQuery,
|
||||
})),
|
||||
withItems(({ itemsTableQuery }) => ({ itemsTableQuery })),
|
||||
)(ItemsList);
|
||||
|
||||
63
client/src/containers/Items/ItemsListProvider.js
Normal file
63
client/src/containers/Items/ItemsListProvider.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import React, { useEffect, createContext } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { isEmpty } from 'lodash';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import { useResourceViews, useResourceFields, useItems } from 'hooks/query';
|
||||
import { useDashboardPageTitle } from 'hooks/state';
|
||||
|
||||
const ItemsContext = createContext();
|
||||
|
||||
function ItemsListProvider({ query, ...props }) {
|
||||
// Fetch accounts resource views and fields.
|
||||
const { data: itemsViews, isFetching: isViewsLoading } = useResourceViews(
|
||||
'items',
|
||||
);
|
||||
|
||||
// Fetch the accounts resource fields.
|
||||
const { data: itemsFields, isFetching: isFieldsLoading } = useResourceFields(
|
||||
'items',
|
||||
);
|
||||
|
||||
// Handle fetching the items table based on the given query.
|
||||
const {
|
||||
data: { items, pagination, filterMeta },
|
||||
isFetching: isItemsLoading,
|
||||
} = useItems(query);
|
||||
|
||||
// Detarmines the datatable empty status.
|
||||
const isEmptyStatus = isEmpty(items) && !isItemsLoading && !filterMeta.view;
|
||||
|
||||
// Format message intl.
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// Change page title dispatcher.
|
||||
const changePageTitle = useDashboardPageTitle();
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle(formatMessage({ id: 'items_list' }));
|
||||
}, [changePageTitle, formatMessage]);
|
||||
|
||||
const state = {
|
||||
itemsViews,
|
||||
itemsFields,
|
||||
items,
|
||||
pagination,
|
||||
|
||||
isViewsLoading,
|
||||
isItemsLoading,
|
||||
isEmptyStatus: false,
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardInsider
|
||||
loading={isFieldsLoading || isViewsLoading}
|
||||
name={'items-list'}
|
||||
>
|
||||
<ItemsContext.Provider value={state} {...props} />
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
const useItemsListContext = () => React.useContext(ItemsContext);
|
||||
|
||||
export { ItemsListProvider, useItemsListContext };
|
||||
@@ -1,18 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route, useHistory } from 'react-router-dom';
|
||||
|
||||
import ItemsViewsTabs from 'containers/Items/ItemsViewsTabs';
|
||||
import ItemsDataTable from 'containers/Items/ItemsDataTable';
|
||||
import ItemsViewsTabs from './ItemsViewsTabs';
|
||||
import ItemsDataTable from './ItemsDataTable';
|
||||
|
||||
import withItemsActions from 'containers/Items/withItemsActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
function ItemsViewPage({
|
||||
// #withAlertsActions.
|
||||
openAlert,
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withItemsActions.
|
||||
setSelectedRowsItems,
|
||||
addItemsTableQueries
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -42,6 +49,25 @@ function ItemsViewPage({
|
||||
history.push(`/items/${id}/edit`);
|
||||
};
|
||||
|
||||
// Handle item make adjustment.
|
||||
const handleMakeAdjustment = ({ id }) => {
|
||||
openDialog('inventory-adjustment', { itemId: id });
|
||||
}
|
||||
|
||||
// Handle fetch data once the page index, size or sort by of the table change.
|
||||
const handleFetchData = ({ pageIndex, pageSize, sortBy }) => {
|
||||
addItemsTableQueries({
|
||||
page_size: pageSize,
|
||||
page: pageIndex,
|
||||
...(sortBy.length > 0
|
||||
? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
@@ -49,12 +75,17 @@ function ItemsViewPage({
|
||||
path={['/items/:custom_view_id/custom_view', '/items']}
|
||||
>
|
||||
<ItemsViewsTabs />
|
||||
|
||||
<ItemsDataTable
|
||||
onDeleteItem={handleDeleteItem}
|
||||
onEditItem={handleEditItem}
|
||||
onInactiveItem={handleInactiveItem}
|
||||
onActivateItem={handleActivateItem}
|
||||
tableProps={{
|
||||
payload: {
|
||||
onDeleteItem: handleDeleteItem,
|
||||
onEditItem: handleEditItem,
|
||||
onInactivateItem: handleInactiveItem,
|
||||
onActivateItem: handleActivateItem,
|
||||
onMakeAdjustment: handleMakeAdjustment
|
||||
},
|
||||
onFetchData: handleFetchData
|
||||
}}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
/>
|
||||
</Route>
|
||||
@@ -62,4 +93,8 @@ function ItemsViewPage({
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withAlertsActions, withItemsActions)(ItemsViewPage);
|
||||
export default compose(
|
||||
withAlertsActions,
|
||||
withItemsActions,
|
||||
withDialogActions
|
||||
)(ItemsViewPage);
|
||||
|
||||
@@ -1,58 +1,28 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { compose } from 'utils';
|
||||
import { DashboardViewsTabs } from 'components';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import withItemsActions from 'containers/Items/withItemsActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withViewDetail from 'containers/Views/withViewDetails';
|
||||
import withItems from 'containers/Items/withItems';
|
||||
import { useItemsListContext } from './ItemsListProvider';
|
||||
|
||||
/**
|
||||
* Items views tabs.
|
||||
*/
|
||||
function ItemsViewsTabs({
|
||||
// #withViewDetail
|
||||
viewId,
|
||||
viewItem,
|
||||
|
||||
// #withItems
|
||||
itemsViews,
|
||||
|
||||
// #withItemsActions
|
||||
addItemsTableQueries,
|
||||
changeItemsCurrentView,
|
||||
|
||||
// #withDashboardActions
|
||||
setTopbarEditView,
|
||||
changePageSubtitle,
|
||||
|
||||
// #props
|
||||
onViewChanged,
|
||||
}) {
|
||||
const { custom_view_id: customViewId = null } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
setTopbarEditView(customViewId);
|
||||
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
|
||||
addItemsTableQueries({
|
||||
custom_view_id: customViewId || null,
|
||||
});
|
||||
}, [customViewId]);
|
||||
|
||||
const handleClickNewView = () => {};
|
||||
const { itemsViews } = useItemsListContext();
|
||||
|
||||
const tabs = itemsViews.map((view) => ({
|
||||
...pick(view, ['name', 'id']),
|
||||
}));
|
||||
|
||||
const handleTabChange = (viewId) => {
|
||||
changeItemsCurrentView(viewId || -1);
|
||||
addItemsTableQueries({
|
||||
custom_view_id: viewId || null,
|
||||
});
|
||||
@@ -72,20 +42,7 @@ function ItemsViewsTabs({
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
// Mapping view id from matched route params.
|
||||
viewId: ownProps.match.params.custom_view_id,
|
||||
});
|
||||
|
||||
const withItemsViewsTabs = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
withItemsViewsTabs,
|
||||
withDashboardActions,
|
||||
withItemsActions,
|
||||
withViewDetail(),
|
||||
withItems(({ itemsViews }) => ({
|
||||
itemsViews,
|
||||
})),
|
||||
)(ItemsViewsTabs);
|
||||
|
||||
137
client/src/containers/Items/components.js
Normal file
137
client/src/containers/Items/components.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Menu,
|
||||
MenuDivider,
|
||||
MenuItem,
|
||||
Intent,
|
||||
Tag,
|
||||
Position,
|
||||
Button,
|
||||
Popover,
|
||||
} from '@blueprintjs/core';
|
||||
import { useIntl, FormattedMessage as T } from 'react-intl';
|
||||
import { formatMessage } from 'services/intl';
|
||||
import { isNumber } from 'lodash';
|
||||
import { Icon, Money, If } from 'components';
|
||||
import { isBlank, safeCallback } from 'utils';
|
||||
|
||||
/**
|
||||
* Publish accessor
|
||||
*/
|
||||
export const PublishAccessor = (r) => {
|
||||
return r.is_published ? (
|
||||
<Tag minimal={true}>
|
||||
<T id={'published'} />
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag minimal={true} intent={Intent.WARNING}>
|
||||
<T id={'draft'} />
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
export const TypeAccessor = (row) => {
|
||||
return row.type ? (
|
||||
<Tag minimal={true} round={true} intent={Intent.NONE}>
|
||||
{formatMessage({ id: row.type })}
|
||||
</Tag>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
export const ItemCodeAccessor = (row) =>
|
||||
row.type ? (
|
||||
<Tag minimal={true} round={true} intent={Intent.NONE}>
|
||||
{formatMessage({ id: row.type })}
|
||||
</Tag>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
|
||||
export const QuantityOnHandCell = ({ cell: { value } }) => {
|
||||
return isNumber(value) ? (
|
||||
<span className={'quantity_on_hand'}>{value}</span>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const CostPriceCell = ({ cell: { value } }) => {
|
||||
return !isBlank(value) ? <Money amount={value} currency={'USD'} /> : null;
|
||||
};
|
||||
|
||||
export const SellPriceCell = ({ cell: { value } }) => {
|
||||
return !isBlank(value) ? <Money amount={value} currency={'USD'} /> : null;
|
||||
};
|
||||
|
||||
export const ItemTypeAccessor = (row) => {
|
||||
return row.type ? (
|
||||
<Tag minimal={true} round={true} intent={Intent.NONE}>
|
||||
{formatMessage({ id: row.type })}
|
||||
</Tag>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const ItemsActionMenuList = ({
|
||||
row: { original },
|
||||
payload: {
|
||||
onEditItem,
|
||||
onInactivateItem,
|
||||
onActivateItem,
|
||||
onMakeAdjustment,
|
||||
onDeleteItem,
|
||||
},
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={<T id={'view_details'} />}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={formatMessage({ id: 'edit_item' })}
|
||||
onClick={safeCallback(onEditItem, original)}
|
||||
/>
|
||||
<If condition={original.active}>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'inactivate_item' })}
|
||||
icon={<Icon icon="pause-16" iconSize={16} />}
|
||||
onClick={safeCallback(onInactivateItem, original)}
|
||||
/>
|
||||
</If>
|
||||
<If condition={!original.active}>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'activate_item' })}
|
||||
icon={<Icon icon="play-16" iconSize={16} />}
|
||||
onClick={safeCallback(onActivateItem, original)}
|
||||
/>
|
||||
</If>
|
||||
<If condition={original.type === 'inventory'}>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'make_adjustment' })}
|
||||
onClick={safeCallback(onMakeAdjustment, original)}
|
||||
/>
|
||||
</If>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'delete_item' })}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
onClick={safeCallback(onDeleteItem, original)}
|
||||
intent={Intent.DANGER}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export const ItemsActionsTableCell = (props) => {
|
||||
return (
|
||||
<Popover
|
||||
position={Position.RIGHT_BOTTOM}
|
||||
content={<ItemsActionMenuList {...props} />}
|
||||
>
|
||||
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
submitInventoryAdjustment,
|
||||
deleteInventoryAdjustment,
|
||||
fetchInventoryAdjustmentsTable,
|
||||
} from 'store/inventoryAdjustments/inventoryAdjustment.actions';
|
||||
import t from 'store/types';
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
requestSubmitInventoryAdjustment: ({ form }) =>
|
||||
dispatch(submitInventoryAdjustment({ form })),
|
||||
requestFetchInventoryAdjustmentTable: (query = {}) =>
|
||||
dispatch(fetchInventoryAdjustmentsTable({ query: { ...query } })),
|
||||
requestDeleteInventoryAdjustment: (id) =>
|
||||
dispatch(deleteInventoryAdjustment({ id })),
|
||||
|
||||
addInventoryAdjustmentTableQueries: (queries) =>
|
||||
dispatch({
|
||||
type: t.INVENTORY_ADJUSTMENTS_TABLE_QUERIES_ADD,
|
||||
payload: { queries },
|
||||
}),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps);
|
||||
@@ -1,35 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getInvoiceTableQueryFactory,
|
||||
getInventoryAdjustmentCurrentPageFactory,
|
||||
getInventoryAdjustmentPaginationMetaFactory,
|
||||
} from 'store/inventoryAdjustments/inventoryAdjustment.selector';
|
||||
|
||||
export default (mapState) => {
|
||||
const getInventoryAdjustmentItems = getInventoryAdjustmentCurrentPageFactory();
|
||||
const getInventoryAdjustmentTableQuery = getInvoiceTableQueryFactory();
|
||||
const getInventoryAdjustmentsPaginationMeta = getInventoryAdjustmentPaginationMetaFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const query = getInventoryAdjustmentTableQuery(state, props);
|
||||
|
||||
const mapped = {
|
||||
inventoryAdjustmentCurrentPage: getInventoryAdjustmentItems(
|
||||
state,
|
||||
props,
|
||||
query,
|
||||
),
|
||||
inventoryAdjustmentItems: Object.values(state.inventoryAdjustments.items),
|
||||
inventoryAdjustmentTableQuery: query,
|
||||
inventoryAdjustmentsPagination: getInventoryAdjustmentsPaginationMeta(
|
||||
state,
|
||||
props,
|
||||
query,
|
||||
),
|
||||
inventoryAdjustmentLoading: state.inventoryAdjustments.loading,
|
||||
inventoryAdjustmentsSelectedRows: state.inventoryAdjustments.selectedRows,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getItemsCategoriesListFactory } from 'store/itemCategories/ItemsCategories.selectors';
|
||||
import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const getItemsCategoriesList = getItemsCategoriesListFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const mapped = {
|
||||
categoriesList: getItemsCategoriesList(state, props),
|
||||
itemCategoriesViews: getResourceViews(state, props, 'items_categories'),
|
||||
categoriesTableLoading: state.itemCategories.loading,
|
||||
itemCategoriesSelectedRows: state.itemCategories.selectedRows,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapState;
|
||||
};
|
||||
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
fetchItemCategories,
|
||||
submitItemCategory,
|
||||
deleteItemCategory,
|
||||
editItemCategory,
|
||||
deleteBulkItemCategories,
|
||||
} from 'store/itemCategories/itemsCategory.actions';
|
||||
import t from 'store/types';
|
||||
|
||||
export const mapDispatchToProps = (dispatch) => ({
|
||||
requestSubmitItemCategory: (form) => dispatch(submitItemCategory({ form })),
|
||||
requestFetchItemCategories: (query) =>
|
||||
dispatch(fetchItemCategories({ query })),
|
||||
requestDeleteItemCategory: (id) => dispatch(deleteItemCategory(id)),
|
||||
requestEditItemCategory: (id, form) => dispatch(editItemCategory(id, form)),
|
||||
requestDeleteBulkItemCategories: (ids) =>
|
||||
dispatch(deleteBulkItemCategories({ ids })),
|
||||
|
||||
changeItemCategoriesView: (id) =>
|
||||
dispatch({
|
||||
type: t.ITEM_CATEGORIES_SET_CURRENT_VIEW,
|
||||
currentViewId: parseInt(id, 10),
|
||||
}),
|
||||
|
||||
addItemCategoriesTableQueries: (queries) =>
|
||||
dispatch({
|
||||
type: t.ITEM_CATEGORIES_TABLE_QUERIES_ADD,
|
||||
queries,
|
||||
}),
|
||||
setSelectedRowsCategories: (selectedRows) =>
|
||||
dispatch({
|
||||
type: t.ITEM_CATEGORY_SELECTED_ROW_SET,
|
||||
payload: { selectedRows },
|
||||
}),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps);
|
||||
@@ -1,13 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getItemCategoryByIdFactory } from 'store/itemCategories/ItemsCategories.selectors';
|
||||
|
||||
export default () => {
|
||||
const getCategoryId = getItemCategoryByIdFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
return {
|
||||
itemCategoryDetail: getCategoryId(state, props),
|
||||
};
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
Reference in New Issue
Block a user