fix: items issues.

This commit is contained in:
a.bouhuolia
2021-02-09 10:23:52 +02:00
parent 01aaa9de25
commit 083bf36c6b
18 changed files with 316 additions and 318 deletions

View File

@@ -32,7 +32,7 @@ function TableHeaderCell({ column, index }) {
{...column.getSortByToggleProps({
className: classNames('cell-inner', {
'text-overview': column.textOverview,
}),
})
})}
>
{column.render('Header')}

View File

@@ -27,7 +27,7 @@ export default function TablePagination() {
const pageIndex = page - 1;
gotoPage(pageIndex);
triggerOnPaginationChange({ page, pageSize });
triggerOnPaginationChange({ pageIndex, pageSize });
},
[gotoPage, triggerOnPaginationChange],
);
@@ -35,10 +35,12 @@ export default function TablePagination() {
// Handles the page size changing.
const handlePageSizeChange = useCallback(
({ pageSize, page }) => {
gotoPage(0);
const pageIndex = 0;
gotoPage(pageIndex);
setPageSize(pageSize);
triggerOnPaginationChange({ page, pageSize });
triggerOnPaginationChange({ pageIndex, pageSize });
},
[gotoPage, setPageSize, triggerOnPaginationChange],
);

View File

@@ -32,7 +32,7 @@ function ItemsActionsBar({
itemsSelectedRows,
// #withItemActions
addItemsTableQueries,
setItemsTableState,
// #withAlertActions
openAlert,
@@ -40,6 +40,7 @@ function ItemsActionsBar({
// Items list context.
const { itemsViews } = useItemsListContext();
// React intl.
const { formatMessage } = useIntl();
// History context.
@@ -52,9 +53,7 @@ function ItemsActionsBar({
// Handle tab changing.
const handleTabChange = (viewId) => {
addItemsTableQueries({
customViewId: viewId.id || null,
});
setItemsTableState({ customViewId: viewId.id || null });
};
// Handle cancel/confirm items bulk.
@@ -115,8 +114,7 @@ function ItemsActionsBar({
</NavbarGroup>
</DashboardActionsBar>
);
};
}
export default compose(
withItems(({ itemsSelectedRows }) => ({ itemsSelectedRows })),

View File

@@ -1,7 +1,8 @@
import React, { useMemo } from 'react';
import { useIntl } from 'react-intl';
import React from 'react';
import classNames from 'classnames';
import { DataTable, Choose } from 'components';
import { useHistory } from 'react-router-dom';
import { DataTable } from 'components';
import ItemsEmptyStatus from './ItemsEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
@@ -9,134 +10,138 @@ import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import { CLASSES } from 'common/classes';
import withSettings from 'containers/Settings/withSettings';
import { useItemsListContext } from './ItemsListProvider';
import { compose } from 'utils';
import {
QuantityOnHandCell,
SellPriceCell,
CostPriceCell,
ItemTypeAccessor,
ItemsActionsTableCell,
ItemsActionMenuList
} from './components';
import withItems from 'containers/Items/withItems';
import withItemsActions from 'containers/Items/withItemsActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
// Items datatable.
import { useItemsListContext } from './ItemsListProvider';
import { useItemsTableColumns, ItemsActionMenuList } from './components';
import { compose } from 'utils';
/**
* Items datatable.
*/
function ItemsDataTable({
// #withItemsActions
setItemsTableState,
// #withDialogAction
openDialog,
// #withAlertsActions
openAlert,
// #withItems
itemsTableState,
// #ownProps
tableProps
tableProps,
}) {
const { formatMessage } = useIntl();
// Items list context.
const {
items,
pagination,
isItemsLoading,
isEmptyStatus,
isItemsFetching,
} = useItemsListContext();
// Datatable columns.
const columns = useMemo(
() => [
{
Header: formatMessage({ id: 'item_name' }),
accessor: 'name',
className: 'name',
width: 180,
},
{
Header: formatMessage({ id: 'item_code' }),
accessor: 'code',
className: 'code',
width: 120,
},
{
Header: formatMessage({ id: 'item_type' }),
accessor: ItemTypeAccessor,
className: 'item_type',
width: 120,
},
{
Header: formatMessage({ id: 'category' }),
accessor: 'category.name',
className: 'category',
width: 150,
},
{
Header: formatMessage({ id: 'sell_price' }),
Cell: SellPriceCell,
accessor: 'sell_price',
className: 'sell-price',
width: 150,
},
{
Header: formatMessage({ id: 'cost_price' }),
Cell: CostPriceCell,
accessor: 'cost_price',
className: 'cost-price',
width: 150,
},
{
Header: formatMessage({ id: 'quantity_on_hand' }),
accessor: 'quantity_on_hand',
Cell: QuantityOnHandCell,
width: 140,
},
{
id: 'actions',
Cell: ItemsActionsTableCell,
width: 60,
skeletonWidthMin: 100,
},
],
[formatMessage],
);
const columns = useItemsTableColumns();
// History context.
const history = useHistory();
// Table row class names.
const rowClassNames = (row) => ({
inactive: !row.original.active,
});
// Handle fetch data once the page index, size or sort by of the table change.
const handleFetchData = React.useCallback(
({ pageSize, pageIndex, sortBy }) => {
setItemsTableState({
pageIndex,
pageSize,
sortBy,
});
},
[setItemsTableState],
);
// Handle delete action Item.
const handleDeleteItem = ({ id }) => {
openAlert('item-delete', { itemId: id });
};
// Handle cancel/confirm item inactive.
const handleInactiveItem = ({ id }) => {
openAlert('item-inactivate', { itemId: id });
};
// Handle cancel/confirm item activate.
const handleActivateItem = ({ id }) => {
openAlert('item-activate', { itemId: id });
};
// Handle Edit item.
const handleEditItem = ({ id }) => {
history.push(`/items/${id}/edit`);
};
// Handle item make adjustment.
const handleMakeAdjustment = ({ id }) => {
openDialog('inventory-adjustment', { itemId: id });
};
// Cannot continue in case the items has empty status.
if (isEmptyStatus) {
return <ItemsEmptyStatus />;
}
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<Choose>
<Choose.When condition={isEmptyStatus}>
<ItemsEmptyStatus />
</Choose.When>
<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={true}
manualPagination={true}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
pageSize={pagination.pageSize}
pageIndex={pagination.page - 1}
ContextMenu={ItemsActionMenuList}
{...tableProps}
/>
</Choose.Otherwise>
</Choose>
<DataTable
columns={columns}
data={items}
initialState={itemsTableState}
loading={isItemsLoading}
headerLoading={isItemsLoading}
progressBarLoading={isItemsFetching}
noInitialFetch={true}
selectionColumn={true}
spinnerProps={{ size: 30 }}
expandable={false}
sticky={true}
rowClassNames={rowClassNames}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={true}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ItemsActionMenuList}
onFetchData={handleFetchData}
payload={{
onDeleteItem: handleDeleteItem,
onEditItem: handleEditItem,
onInactivateItem: handleInactiveItem,
onActivateItem: handleActivateItem,
onMakeAdjustment: handleMakeAdjustment,
}}
noResults={'There is no items in the table yet.'}
{...tableProps}
/>
</div>
);
}
export default compose(
withSettings(({ organizationSettings }) => ({
baseCurrency: organizationSettings?.baseCurrency,
})),
withItemsActions,
withAlertsActions,
withDialogActions,
withItems(({ itemsTableState }) => ({ itemsTableState })),
)(ItemsDataTable);

View File

@@ -3,34 +3,38 @@ import { compose } from 'utils';
import 'style/pages/Items/List.scss';
import ItemsViewPage from './ItemsViewPage';
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 ItemsActionsBar from './ItemsActionsBar';
import ItemsAlerts from './ItemsAlerts';
import ItemsViewsTabs from './ItemsViewsTabs';
import ItemsDataTable from './ItemsDataTable';
import { ItemsListProvider } from './ItemsListProvider';
import withItems from './withItems';
import { transformTableStateToQuery } from 'utils';
/**
* Items list.
*/
function ItemsList({
// #withItems
itemsTableQuery
itemsTableState,
}) {
return (
<ItemsListProvider query={itemsTableQuery}>
<ItemsListProvider query={transformTableStateToQuery(itemsTableState)}>
<ItemsActionsBar />
<DashboardPageContent>
<ItemsViewPage />
<ItemsViewsTabs />
<ItemsDataTable />
</DashboardPageContent>
<ItemsAlerts />
</ItemsListProvider>
);
}
export default compose(
withItems(({ itemsTableQuery }) => ({ itemsTableQuery })),
withItems(({ itemsTableState }) => ({ itemsTableState })),
)(ItemsList);

View File

@@ -46,6 +46,7 @@ function ItemsListProvider({
// Change page title dispatcher.
const changePageTitle = useDashboardPageTitle();
// Changeas the page title once the page mount.
useEffect(() => {
changePageTitle(formatMessage({ id: 'items_list' }));
}, [changePageTitle, formatMessage]);

View File

@@ -1,134 +0,0 @@
import React, { useMemo } from 'react';
import { useHistory } from 'react-router-dom';
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 withItems from 'containers/Items/withItems';
import { compose } from 'utils';
import { useItemsListContext } from './ItemsListProvider';
function transformPaginationToProps(pagination) {
const { page, pageSize, total } = pagination;
return {
initialPageIndex: Math.max(page - 1, 0),
initialPageSize: pageSize,
pagesCount: Math.ceil(total / pageSize),
};
}
function transformPaginationToQuery(query) {
const { pageSize, pageIndex, sortBy } = query;
return {
page_size: pageSize,
page: pageIndex + 1,
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
};
}
/**
* Items view page.
*/
function ItemsViewPage({
// #withAlertsActions.
openAlert,
// #withDialogActions
openDialog,
// #withItemsActions.
setSelectedRowsItems,
addItemsTableQueries,
itemsTableQuery,
}) {
const history = useHistory();
const { pagination, isItemsFetching } = useItemsListContext();
// Handle delete action Item.
const handleDeleteItem = ({ id }) => {
openAlert('item-delete', { itemId: id });
};
// Handle cancel/confirm item inactive.
const handleInactiveItem = ({ id }) => {
openAlert('item-inactivate', { itemId: id });
};
// Handle cancel/confirm item activate.
const handleActivateItem = ({ id }) => {
openAlert('item-activate', { itemId: id });
};
// Handle select item rows.
const handleSelectedRowsChange = (selectedRows) => {
const selectedRowsIds = selectedRows.map((r) => r.id);
setSelectedRowsItems(selectedRowsIds);
};
// Handle Edit item.
const handleEditItem = ({ id }) => {
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 handlePaginationChange = ({ pageSize, page }) => {
addItemsTableQueries({
// ...transformPaginationToQuery(query),
page,
pageSize,
});
};
const controlledState = (state) => ({
...state,
pageIndex: itemsTableQuery.page - 1,
});
return (
<>
<ItemsViewsTabs />
<ItemsDataTable
tableProps={{
payload: {
onDeleteItem: handleDeleteItem,
onEditItem: handleEditItem,
onInactivateItem: handleInactiveItem,
onActivateItem: handleActivateItem,
onMakeAdjustment: handleMakeAdjustment,
},
...transformPaginationToProps(pagination),
onPaginationChange: handlePaginationChange,
progressBarLoading: isItemsFetching,
// useControlledState: controlledState
// progressBarLoading: true
}}
onSelectedRowsChange={handleSelectedRowsChange}
/>
</>
);
}
export default compose(
withAlertsActions,
withItemsActions,
withDialogActions,
withItems(({ itemsTableQuery }) => ({ itemsTableQuery })),
)(ItemsViewPage);

View File

@@ -15,10 +15,10 @@ import { useItemsListContext } from './ItemsListProvider';
*/
function ItemsViewsTabs({
// #withItemsActions
addItemsTableQueries,
setItemsTableState,
// #withItems
itemsTableQuery
itemsCustomViewId
}) {
const { itemsViews } = useItemsListContext();
@@ -29,8 +29,8 @@ function ItemsViewsTabs({
// Handles the active tab change.
const handleTabChange = (viewId) => {
addItemsTableQueries({
page: 1,
setItemsTableState({
pageIndex: 0,
customViewId: viewId || null,
});
};
@@ -39,7 +39,7 @@ function ItemsViewsTabs({
<Navbar className="navbar--dashboard-views">
<NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs
currentViewId={itemsTableQuery.customViewId}
currentViewId={itemsCustomViewId}
resourceName={'items'}
tabs={tabs}
onChange={handleTabChange}
@@ -51,6 +51,8 @@ function ItemsViewsTabs({
export default compose(
withRouter,
withItems(({ itemsTableQuery }) => ({ itemsTableQuery })),
withItems(({ itemsTableState }) => ({
itemsCustomViewId: itemsTableState?.customViewId
})),
withItemsActions,
)(ItemsViewsTabs);

View File

@@ -136,3 +136,67 @@ export const ItemsActionsTableCell = (props) => {
</Popover>
);
};
/**
* Retrieve all items table columns.
*/
export const useItemsTableColumns = () => {
const { formatMessage } = useIntl();
return React.useMemo(
() => [
{
Header: formatMessage({ id: 'item_name' }),
accessor: 'name',
className: 'name',
width: 180,
},
{
Header: formatMessage({ id: 'item_code' }),
accessor: 'code',
className: 'code',
width: 120,
},
{
Header: formatMessage({ id: 'item_type' }),
accessor: ItemTypeAccessor,
className: 'item_type',
width: 120,
},
{
Header: formatMessage({ id: 'category' }),
accessor: 'category.name',
className: 'category',
width: 150,
},
{
Header: formatMessage({ id: 'sell_price' }),
Cell: SellPriceCell,
accessor: 'sell_price',
className: 'sell-price',
width: 150,
},
{
Header: formatMessage({ id: 'cost_price' }),
Cell: CostPriceCell,
accessor: 'cost_price',
className: 'cost-price',
width: 150,
},
{
Header: formatMessage({ id: 'quantity_on_hand' }),
accessor: 'quantity_on_hand',
Cell: QuantityOnHandCell,
width: 140,
},
{
id: 'actions',
Cell: ItemsActionsTableCell,
width: 60,
skeletonWidthMin: 100,
},
],
[formatMessage],
);
}

View File

@@ -1,15 +1,15 @@
import {connect} from 'react-redux';
import {
getItemsTableQueryFactory,
getItemsTableStateFactory,
} from 'store/items/items.selectors';
export default (mapState) => {
const getItemsTableQuery = getItemsTableQueryFactory();
const getItemsTableState = getItemsTableStateFactory();
const mapStateToProps = (state, props) => {
const mapped = {
itemsSelectedRows: state.items.selectedRows,
itemsTableQuery: getItemsTableQuery(state, props),
itemsTableState: getItemsTableState(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};

View File

@@ -1,23 +1,13 @@
import { connect } from 'react-redux';
import t from 'store/types';
import { setItemsTableState }from 'store/items/items.actions';
export const mapDispatchToProps = (dispatch) => ({
setItemsTableQuery: (key, value) =>
dispatch({
type: t.ITEMS_TABLE_QUERY_SET,
key,
value,
}),
addItemsTableQueries: (queries) =>
dispatch({
type: t.ITEMS_TABLE_QUERIES_ADD,
payload: { queries },
}),
setSelectedRowsItems: (selectedRows) =>
dispatch({
type: t.ITEM_SELECTED_ROWS_SET,
payload: { selectedRows },
}),
setItemsTableState: (queries) => dispatch(setItemsTableState(queries)),
// setSelectedRowsItems: (selectedRows) =>
// dispatch({
// type: t.ITEM_SELECTED_ROWS_SET,
// payload: { selectedRows },
// }),
});
export default connect(null, mapDispatchToProps);

View File

@@ -1,7 +1,7 @@
import { useMutation, useQuery, useQueryClient } from 'react-query';
import { defaultTo } from 'lodash';
import ApiService from 'services/ApiService';
import { transformResponse } from 'utils';
import { transformPagination, transformResponse } from 'utils';
const defaultPagination = {
pageSize: 12,
@@ -57,7 +57,9 @@ export function useDeleteItem(props) {
const transformItemsResponse = (response) => {
return {
items: response.data.items,
pagination: transformResponse(response.data.pagination),
pagination: transformPagination(
transformResponse(response.data.pagination)
),
filterMeta: transformResponse(response.data.filter_meta),
};
};

View File

@@ -0,0 +1,10 @@
import t from 'store/types';
export const setItemsTableState = (queries) => {
return {
type: t.ITEMS_TABLE_STATE_SET,
payload: { queries },
};
};
export const setSelectedRowsItems = () => {};

View File

@@ -1,16 +1,17 @@
import { createReducer } from '@reduxjs/toolkit';
import {
createTableQueryReducers,
} from 'store/journalNumber.reducer';
createTableStateReducers,
} from 'store/tableState.reducer';
const initialState = {
tableQuery: {
tableState: {
pageSize: 12,
page: 1,
pageIndex: 0,
filters: [],
},
selectedRows: [],
};
export default createReducer(initialState, {
...createTableQueryReducers('ITEMS'),
...createTableStateReducers('ITEMS'),
});

View File

@@ -1,17 +1,17 @@
import { paginationLocationQuery } from 'store/selectors';
import { createDeepEqualSelector } from 'utils';
const itemsTableQuerySelector = (state) => state.items.tableQuery;
const itemsTableStateSelector = (state) => state.items.tableState;
// Get items table query marged with location query.
export const getItemsTableQueryFactory = () =>
// Get items table state marged with location query.
export const getItemsTableStateFactory = () =>
createDeepEqualSelector(
paginationLocationQuery,
itemsTableQuerySelector,
(locationQuery, tableQuery) => {
itemsTableStateSelector,
(locationQuery, tableState) => {
return {
...locationQuery,
...tableQuery,
...tableState,
};
},
);

View File

@@ -1,17 +1,4 @@
export default {
ITEMS_SET: 'ITEMS_SET',
ITEM_SET: 'ITEM_SET',
ITEMS_PAGE_SET: 'ITEMS_PAGE_SET',
ITEMS_PAGINATION_SET: 'ITEMS_PAGINATION_SET',
ITEM_DELETE: 'ITEM_DELETE',
ITEM_BULK_ACTION_ADD: 'ITEM_BULK_ACTION_ADD',
ITEM_BULK_ACTION_REMOVE: 'ITEM_BULK_ACTION_REMOVE',
ITEMS_TABLE_QUERY_SET: 'ITEMS/TABLE_QUERY_SET',
ITEMS_TABLE_QUERIES_ADD: 'ITEMS/TABLE_QUERIES_ADD',
ITEMS_TABLE_LOADING: 'ITEMS_TABLE_LOADING',
ITEMS_SET_CURRENT_VIEW: 'ITEMS_SET_CURRENT_VIEW',
ITEMS_BULK_DELETE: 'ITEMS_BULK_DELETE',
ITEM_SELECTED_ROWS_SET: 'ITEM_SELECTED_ROWS_SET',
};
ITEMS_TABLE_STATE_SET: 'ITEMS/TABLE_STATE_SET',
};

View File

@@ -0,0 +1,18 @@
const TYPES = {
TABLE_STATE_SET: 'TABLE_STATE_SET',
};
export const createTableStateReducers = (RESOURCE_NAME) => ({
/**
* Resource table state set.
*/
[`${RESOURCE_NAME}/${TYPES.TABLE_STATE_SET}`]: (state, action) => {
const { queries } = action.payload;
state.tableState = {
...state.tableState,
...queries,
};
},
});

View File

@@ -487,4 +487,52 @@ export const isTableEmptyStatus = ({ data, pagination, filterMeta }) => {
_.isEmpty(filterMeta.view),
pagination.page === 1,
].every(cond => cond === true)
}
/**
* Transformes the pagination meta to table props.
*/
export function getPagesCountFromPaginationMeta(pagination) {
const { pageSize, total } = pagination;
return Math.ceil(total / pageSize);
}
/**
* Transformes the table state to url query.
*/
export function transformTableStateToQuery(tableState) {
const { pageSize, pageIndex, customViewId, sortBy } = tableState;
const query = {
pageSize,
page: pageIndex + 1,
...(customViewId ? { customViewId } : {}),
...(Array.isArray(sortBy) && sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
};
return transfromToSnakeCase(query);
}
/**
* Transformes the global table state to table state.
*/
export function globalTableStateToTable(globalState) {
return {
..._.omit(globalState, ['customViewId']),
};
}
/**
* Transformes the pagination meta repsonse.
*/
export function transformPagination(pagination) {
return {
...pagination,
pagesCount: getPagesCountFromPaginationMeta(pagination),
}
}