mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
fix bugs in items form.
This commit is contained in:
@@ -52,7 +52,7 @@ function AccountsDataTable({
|
||||
openDialog('account-form', { action: 'edit', id: account.id });
|
||||
}, [openDialog]);
|
||||
|
||||
const actionMenuList = account => (
|
||||
const actionMenuList = useCallback(account => (
|
||||
<Menu>
|
||||
<MenuItem text='View Details' />
|
||||
<MenuDivider />
|
||||
@@ -66,7 +66,8 @@ function AccountsDataTable({
|
||||
text='Delete Account'
|
||||
onClick={() => onDeleteAccount(account)} />
|
||||
</Menu>
|
||||
);
|
||||
), [handleEditAccount, onDeleteAccount, onInactiveAccount]);
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
id: 'name',
|
||||
@@ -139,7 +140,7 @@ function AccountsDataTable({
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
}
|
||||
], []);
|
||||
], [actionMenuList]);
|
||||
|
||||
const selectionColumn = useMemo(() => ({
|
||||
minWidth: 42,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
HTMLSelect,
|
||||
Button,
|
||||
Classes,
|
||||
Checkbox,
|
||||
} from '@blueprintjs/core';
|
||||
import { Row, Col } from 'react-grid-system';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
@@ -19,11 +20,15 @@ import {compose} from 'utils';
|
||||
import ErrorMessage from 'components/ErrorMessage';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'components/Icon';
|
||||
import ItemCategoryConnect from 'connectors/ItemsCategory.connect';
|
||||
import MoneyInputGroup from 'components/MoneyInputGroup';
|
||||
|
||||
|
||||
const ItemForm = ({
|
||||
requestSubmitItem,
|
||||
accounts,
|
||||
categories,
|
||||
categoriesList,
|
||||
}) => {
|
||||
const [selectedAccounts, setSelectedAccounts] = useState({});
|
||||
|
||||
@@ -35,24 +40,30 @@ const ItemForm = ({
|
||||
]), []);
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
active: Yup.boolean(),
|
||||
name: Yup.string().required(),
|
||||
type: Yup.string().trim().required(),
|
||||
sku: Yup.string().required(),
|
||||
cost_price: Yup.number().required(),
|
||||
sell_price: Yup.number().required(),
|
||||
cost_price: Yup.number(),
|
||||
sell_price: Yup.number(),
|
||||
cost_account_id: Yup.number().required(),
|
||||
sell_account_id: Yup.number().required(),
|
||||
inventory_account_id: Yup.number().required(),
|
||||
category_id: Yup.number().required(),
|
||||
inventory_account_id: Yup.number().when('type', {
|
||||
is: (value) => value === 'inventory',
|
||||
then: Yup.number().required(),
|
||||
otherwise: Yup.number().nullable(),
|
||||
}),
|
||||
category_id: Yup.number().nullable(),
|
||||
stock: Yup.string() || Yup.boolean()
|
||||
});
|
||||
|
||||
const initialValues = useMemo(() => ({
|
||||
active: true,
|
||||
name: '',
|
||||
type: '',
|
||||
sku: '',
|
||||
cost_price: null,
|
||||
sell_price: null,
|
||||
cost_price: 0,
|
||||
sell_price: 0,
|
||||
cost_account_id: null,
|
||||
sell_account_id: null,
|
||||
inventory_account_id: null,
|
||||
@@ -80,9 +91,10 @@ const ItemForm = ({
|
||||
});
|
||||
const {errors, values, touched} = useMemo(() => formik, [formik]);
|
||||
|
||||
const accountItem = (item, { handleClick }) => (
|
||||
const accountItem = useCallback((item, { handleClick }) => (
|
||||
<MenuItem key={item.id} text={item.name} label={item.code} onClick={handleClick} />
|
||||
);
|
||||
), []);
|
||||
|
||||
// Filter Account Items
|
||||
const filterAccounts = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
@@ -104,6 +116,10 @@ const ItemForm = ({
|
||||
};
|
||||
}, [formik, selectedAccounts]);
|
||||
|
||||
const categoryItem = useCallback((item, { handleClick }) => (
|
||||
<MenuItem text={item.name} onClick={handleClick} />
|
||||
), []);
|
||||
|
||||
const getSelectedAccountLabel = useCallback((fieldName, defaultLabel) => {
|
||||
return typeof selectedAccounts[fieldName] !== 'undefined'
|
||||
? selectedAccounts[fieldName].name : defaultLabel;
|
||||
@@ -112,6 +128,10 @@ const ItemForm = ({
|
||||
const requiredSpan = useMemo(() => (<span class="required">*</span>), []);
|
||||
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
|
||||
|
||||
const handleMoneyInputChange = (fieldKey) => (e, value) => {
|
||||
formik.setFieldValue(fieldKey, value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class='item-form'>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
@@ -175,8 +195,8 @@ const ItemForm = ({
|
||||
)}
|
||||
>
|
||||
<Select
|
||||
items={accounts}
|
||||
itemRenderer={accountItem}
|
||||
items={categoriesList}
|
||||
itemRenderer={categoryItem}
|
||||
itemPredicate={filterAccounts}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onItemAccountSelect('category_id')}
|
||||
@@ -184,15 +204,28 @@ const ItemForm = ({
|
||||
<Button
|
||||
fill={true}
|
||||
rightIcon='caret-down'
|
||||
text={getSelectedAccountLabel('category_id', 'Select account')}
|
||||
text={getSelectedAccountLabel('category_id', 'Select category')}
|
||||
/>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={' '}
|
||||
inline={true}
|
||||
className={'form-group--active'}
|
||||
>
|
||||
<Checkbox
|
||||
inline={true}
|
||||
label={'Active'}
|
||||
defaultChecked={values.active}
|
||||
{...formik.getFieldProps('active')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
|
||||
<Row gutterWidth={16} className={'item-form__accounts-section'}>
|
||||
<Col width={404}>
|
||||
<h4 >Purchase Information</h4>
|
||||
<h4>Purchase Information</h4>
|
||||
|
||||
<FormGroup
|
||||
label={'Selling Price'}
|
||||
@@ -201,11 +234,14 @@ const ItemForm = ({
|
||||
helperText={<ErrorMessage {...formik} name="selling_price" />}
|
||||
inline={true}
|
||||
>
|
||||
<InputGroup
|
||||
medium={true}
|
||||
intent={(errors.selling_price && touched.selling_price) && Intent.DANGER}
|
||||
{...formik.getFieldProps('sell_price')}
|
||||
/>
|
||||
<MoneyInputGroup
|
||||
value={values.selling_price}
|
||||
prefix={'$'}
|
||||
onChange={handleMoneyInputChange('selling_price')}
|
||||
inputGroupProps={{
|
||||
medium: true,
|
||||
intent: (errors.selling_price && touched.selling_price) && Intent.DANGER,
|
||||
}} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
@@ -236,7 +272,9 @@ const ItemForm = ({
|
||||
</Col>
|
||||
|
||||
<Col width={404}>
|
||||
<h4>Sales Information</h4>
|
||||
<h4>
|
||||
Sales Information
|
||||
</h4>
|
||||
|
||||
<FormGroup
|
||||
label={'Cost Price'}
|
||||
@@ -245,11 +283,14 @@ const ItemForm = ({
|
||||
helperText={<ErrorMessage {...formik} name="cost_price" />}
|
||||
inline={true}
|
||||
>
|
||||
<InputGroup
|
||||
medium={true}
|
||||
intent={(errors.cost_price && touched.cost_price) && Intent.DANGER}
|
||||
{...formik.getFieldProps('cost_price')}
|
||||
/>
|
||||
<MoneyInputGroup
|
||||
value={values.cost_price}
|
||||
prefix={'$'}
|
||||
onChange={handleMoneyInputChange('cost_price')}
|
||||
inputGroupProps={{
|
||||
medium: true,
|
||||
intent: (errors.cost_price && touched.cost_price) && Intent.DANGER,
|
||||
}} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
@@ -262,7 +303,7 @@ const ItemForm = ({
|
||||
'form-group--cost-account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL)}
|
||||
>
|
||||
>
|
||||
<Select
|
||||
items={accounts}
|
||||
itemRenderer={accountItem}
|
||||
@@ -282,12 +323,14 @@ const ItemForm = ({
|
||||
|
||||
<Row className={'item-form__accounts-section mt2'}>
|
||||
<Col width={404}>
|
||||
<h4>Inventory Information</h4>
|
||||
<h4>
|
||||
Inventory Information
|
||||
</h4>
|
||||
|
||||
<FormGroup
|
||||
label={'Inventory Account'}
|
||||
inline={true}
|
||||
intent={(errors.inventory_account_id && errors.inventory_account_id) && Intent.DANGER}
|
||||
intent={(errors.inventory_account_id && touched.inventory_account_id) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...formik} name="inventory_account_id" />}
|
||||
className={classNames(
|
||||
'form-group--item-inventory_account',
|
||||
@@ -312,8 +355,8 @@ const ItemForm = ({
|
||||
<FormGroup
|
||||
label={'Opening Stock'}
|
||||
className={'form-group--item-stock'}
|
||||
intent={formik.errors.cost_price && Intent.DANGER}
|
||||
helperText={formik.errors.stock && formik.errors.stock}
|
||||
// intent={errors.cost_price && Intent.DANGER}
|
||||
// helperText={formik.errors.stock && formik.errors.stock}
|
||||
inline={true}
|
||||
>
|
||||
<InputGroup
|
||||
@@ -341,4 +384,5 @@ const ItemForm = ({
|
||||
export default compose(
|
||||
AccountsConnect,
|
||||
ItemsConnect,
|
||||
ItemCategoryConnect,
|
||||
)(ItemForm);
|
||||
@@ -147,6 +147,12 @@ function ManualJournalsDataTable({
|
||||
onFetchData && onFetchData();
|
||||
}, [onFetchData]);
|
||||
|
||||
const selectionColumn = useMemo(() => ({
|
||||
minWidth: 42,
|
||||
width: 42,
|
||||
maxWidth: 42,
|
||||
}), []);
|
||||
|
||||
return (
|
||||
<LoadingIndicator loading={manualJournalsLoading} spinnerSize={30}>
|
||||
<DataTable
|
||||
@@ -154,7 +160,7 @@ function ManualJournalsDataTable({
|
||||
data={manualJournals}
|
||||
onFetchData={handleDataTableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
selectionColumn={selectionColumn}
|
||||
/>
|
||||
</LoadingIndicator>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ export default [
|
||||
children: [
|
||||
{
|
||||
text: 'Items List',
|
||||
href: '/dashboard/items/list'
|
||||
href: '/dashboard/items'
|
||||
},
|
||||
{
|
||||
text: 'New Item',
|
||||
@@ -27,7 +27,7 @@ export default [
|
||||
},
|
||||
{
|
||||
text: 'Category List',
|
||||
href: '/dashboard/items/ItemCategoriesList'
|
||||
href: '/dashboard/items/categories'
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ export const mapStateToProps = (state, props) => {
|
||||
state.items.items, viewPages, state.items.currentPage),
|
||||
|
||||
bulkSelected: state.items.bulkActions,
|
||||
itemsTableLoading: state.items.loading,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -37,6 +38,14 @@ export const mapDispatchToProps = (dispatch) => ({
|
||||
removeBulkActionItem: (id) => dispatch({
|
||||
type: t.ITEM_BULK_ACTION_REMOVE, itemId: id,
|
||||
}),
|
||||
|
||||
setItemsTableQuery: (key, value) => dispatch({
|
||||
type: t.ITEMS_TABLE_QUERY_SET, key, value,
|
||||
}),
|
||||
addItemsTableQueries: (queries) =>
|
||||
dispatch({
|
||||
type: t.ITEMS_TABLE_QUERIES_ADD, queries,
|
||||
}),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
@@ -12,6 +12,7 @@ export const mapStateToProps = (state, props) => {
|
||||
const dialogPayload = getDialogPayload(state, 'item-form');
|
||||
return {
|
||||
categories: state.itemCategories.categories,
|
||||
categoriesList: Object.values(state.itemCategories.categories),
|
||||
name: 'item-form',
|
||||
payload: { action: 'new', id: null },
|
||||
editItemCategory:
|
||||
@@ -21,6 +22,7 @@ export const mapStateToProps = (state, props) => {
|
||||
getCategoryId: id => getCategoryId(state, id)
|
||||
};
|
||||
};
|
||||
|
||||
export const mapDispatchToProps = dispatch => ({
|
||||
requestSubmitItemCategory: form => dispatch(submitItemCategory({ form })),
|
||||
requestFetchItemCategories: () => dispatch(fetchItemCategories()),
|
||||
|
||||
@@ -117,8 +117,7 @@ function AccountsChart({
|
||||
console.log(accounts);
|
||||
};
|
||||
|
||||
const handleFilterChanged = useCallback(() => {
|
||||
|
||||
const handleFilterChanged = useCallback(() => {
|
||||
fetchAccountsHook.execute();
|
||||
}, [fetchAccountsHook]);
|
||||
|
||||
|
||||
@@ -6,11 +6,13 @@ import ItemForm from 'components/Items/ItemForm';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import ItemsConnect from 'connectors/Items.connect';
|
||||
import AccountsConnect from 'connectors/Accounts.connector';
|
||||
import ItemCategoryConnect from 'connectors/ItemsCategory.connect';
|
||||
import { compose } from 'utils';
|
||||
|
||||
const ItemFormContainer = ({
|
||||
changePageTitle,
|
||||
fetchAccounts,
|
||||
requestFetchAccounts,
|
||||
requestFetchItemCategories,
|
||||
}) => {
|
||||
const { id } = useParams();
|
||||
useEffect(() => {
|
||||
@@ -21,11 +23,12 @@ const ItemFormContainer = ({
|
||||
|
||||
const fetchHook = useAsync(async () => {
|
||||
await Promise.all([
|
||||
fetchAccounts(),
|
||||
requestFetchAccounts(),
|
||||
requestFetchItemCategories(),
|
||||
]);
|
||||
});
|
||||
return (
|
||||
<DashboardInsider isLoading={fetchHook.loading} name={'item-form'}>
|
||||
<DashboardInsider loading={fetchHook.loading} name={'item-form'}>
|
||||
<ItemForm />
|
||||
</DashboardInsider>
|
||||
);
|
||||
@@ -35,4 +38,5 @@ export default compose(
|
||||
DashboardConnect,
|
||||
ItemsConnect,
|
||||
AccountsConnect,
|
||||
ItemCategoryConnect,
|
||||
)(ItemFormContainer);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Position,
|
||||
Button,
|
||||
Classes,
|
||||
Intent
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'components/Icon';
|
||||
@@ -28,8 +28,9 @@ const ItemsActionsBar = ({
|
||||
getResourceFields,
|
||||
getResourceViews,
|
||||
views,
|
||||
onFilterChange,
|
||||
bulkSelected
|
||||
onFilterChanged,
|
||||
bulkSelected,
|
||||
addItemsTableQueries,
|
||||
}) => {
|
||||
const { path } = useRouteMatch();
|
||||
const history = useHistory();
|
||||
@@ -46,7 +47,12 @@ const ItemsActionsBar = ({
|
||||
|
||||
const filterDropdown = FilterDropdown({
|
||||
fields: itemsFields,
|
||||
onFilterChange
|
||||
onFilterChange: (filterConditions) => {
|
||||
addItemsTableQueries({
|
||||
filter_roles: filterConditions || '',
|
||||
});
|
||||
onFilterChanged && onFilterChanged(filterConditions);
|
||||
}
|
||||
});
|
||||
|
||||
const hasBulkActionsSelected = useMemo(
|
||||
@@ -54,9 +60,9 @@ const ItemsActionsBar = ({
|
||||
[bulkSelected]
|
||||
);
|
||||
|
||||
const onClickNewCategory = () => {
|
||||
const onClickNewCategory = useCallback(() => {
|
||||
openDialog('item-form', {});
|
||||
};
|
||||
}, [openDialog]);
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useMemo} from 'react';
|
||||
import React, {useEffect, useCallback, useMemo} from 'react';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
@@ -6,87 +6,76 @@ import {
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
Checkbox,
|
||||
} from '@blueprintjs/core'
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import CustomViewConnect from 'connectors/View.connector';
|
||||
import ItemsConnect from 'connectors/Items.connect';
|
||||
import {useParams} from 'react-router-dom'
|
||||
import {compose} from 'utils';
|
||||
import useAsync from 'hooks/async';
|
||||
import DataTable from 'components/DataTable';
|
||||
import Icon from 'components/Icon';
|
||||
import {handleBooleanChange} from 'utils';
|
||||
import Money from 'components/Money';
|
||||
|
||||
|
||||
const ItemsDataTable = ({
|
||||
requestFetchItems,
|
||||
filterConditions,
|
||||
itemsTableLoading,
|
||||
currentPageItems,
|
||||
onEditItem,
|
||||
onDeleteItem,
|
||||
addBulkActionItem,
|
||||
removeBulkActionItem,
|
||||
onFetchData,
|
||||
}) => {
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
|
||||
const fetchHook = useAsync(async () => {
|
||||
await Promise.all([
|
||||
requestFetchItems({
|
||||
custom_view_id: customViewId,
|
||||
stringified_filter_roles: JSON.stringify(filterConditions),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
const handleEditItem = (item) => () => { onEditItem(item); };
|
||||
const handleDeleteItem = (item) => () => { onDeleteItem(item); };
|
||||
const handleClickCheckboxBulk = (item) => handleBooleanChange((value) => {
|
||||
if (value) {
|
||||
addBulkActionItem(item.id);
|
||||
} else {
|
||||
removeBulkActionItem(item.id);
|
||||
}
|
||||
});
|
||||
|
||||
const actionMenuList = (item) =>
|
||||
|
||||
const actionMenuList = useCallback((item) =>
|
||||
(<Menu>
|
||||
<MenuItem text="View Details" />
|
||||
<MenuDivider />
|
||||
<MenuItem text="Edit Item" onClick={handleEditItem(item)} />
|
||||
<MenuItem text="Delete Item" onClick={handleDeleteItem(item)} />
|
||||
</Menu>);
|
||||
</Menu>), [handleEditItem, handleDeleteItem]);
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
id: 'bulk_select',
|
||||
Cell: ({ cell }) =>
|
||||
(<Checkbox onChange={handleClickCheckboxBulk(cell.row.original)} />),
|
||||
},
|
||||
{
|
||||
Header: 'Item Name',
|
||||
accessor: 'name',
|
||||
className: "actions",
|
||||
},
|
||||
{
|
||||
Header: 'Cost Account',
|
||||
accessor: 'cost_account.name',
|
||||
className: "cost-account",
|
||||
},
|
||||
{
|
||||
Header: 'Sell Account',
|
||||
accessor: 'sell_account.name',
|
||||
className: "sell-account",
|
||||
},
|
||||
{
|
||||
Header: 'Inventory Account',
|
||||
accessor: 'inventory_account.name',
|
||||
className: "inventory-account",
|
||||
Header: 'SKU',
|
||||
accessor: 'sku',
|
||||
className: "sku",
|
||||
},
|
||||
{
|
||||
Header: 'Category',
|
||||
accessor: 'category.name',
|
||||
className: 'category',
|
||||
},
|
||||
{
|
||||
Header: 'Sell Price',
|
||||
accessor: row => (<Money amount={row.sell_price} currency={'USD'} />),
|
||||
className: 'sell-price',
|
||||
},
|
||||
{
|
||||
Header: 'Cost Price',
|
||||
accessor: row => (<Money amount={row.cost_price} currency={'USD'} />),
|
||||
className: 'cost-price',
|
||||
},
|
||||
// {
|
||||
// Header: 'Cost Account',
|
||||
// accessor: 'cost_account.name',
|
||||
// className: "cost-account",
|
||||
// },
|
||||
// {
|
||||
// Header: 'Sell Account',
|
||||
// accessor: 'sell_account.name',
|
||||
// className: "sell-account",
|
||||
// },
|
||||
// {
|
||||
// Header: 'Inventory Account',
|
||||
// accessor: 'inventory_account.name',
|
||||
// className: "inventory-account",
|
||||
// },
|
||||
|
||||
{
|
||||
id: 'actions',
|
||||
Cell: ({ cell }) => (
|
||||
@@ -96,14 +85,28 @@ const ItemsDataTable = ({
|
||||
<Button icon={<Icon icon="ellipsis-h" />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
},
|
||||
]);
|
||||
], [actionMenuList]);
|
||||
|
||||
const selectionColumn = useMemo(() => ({
|
||||
minWidth: 42,
|
||||
width: 42,
|
||||
maxWidth: 42,
|
||||
}), []);
|
||||
|
||||
const handleFetchData = useCallback((...args) => {
|
||||
onFetchData && onFetchData(...args)
|
||||
}, [onFetchData])
|
||||
|
||||
return (
|
||||
<LoadingIndicator loading={fetchHook.pending} spinnerSize={30}>
|
||||
<LoadingIndicator loading={itemsTableLoading} spinnerSize={30}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={currentPageItems} />
|
||||
data={currentPageItems}
|
||||
selectionColumn={selectionColumn}
|
||||
onFetchData={handleFetchData} />
|
||||
</LoadingIndicator>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useCallback, useState } from 'react';
|
||||
import {
|
||||
Route,
|
||||
Switch,
|
||||
@@ -17,22 +17,24 @@ import ResourceConnect from 'connectors/Resource.connector';
|
||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||
import ItemsConnect from 'connectors/Items.connect';
|
||||
import CustomViewsConnect from 'connectors/CustomView.connector'
|
||||
import DashboardViewsTabs from 'components/Accounts/AccountsViewsTabs';
|
||||
import ItemsViewsTabs from 'containers/Dashboard/Items/ItemsViewsTabs';
|
||||
import AppToaster from 'components/AppToaster';
|
||||
|
||||
|
||||
function ItemsList({
|
||||
changePageTitle,
|
||||
fetchResourceViews,
|
||||
fetchResourceFields,
|
||||
views,
|
||||
requestDeleteItem,
|
||||
requestFetchItems,
|
||||
addItemsTableQueries,
|
||||
}) {
|
||||
const [filterConditions, setFilterConditions] = useState([]);
|
||||
const [deleteItem, setDeleteItem] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
changePageTitle('Items List');
|
||||
}, []);
|
||||
}, [changePageTitle]);
|
||||
|
||||
const fetchHook = useAsync(async () => {
|
||||
await Promise.all([
|
||||
@@ -40,31 +42,68 @@ function ItemsList({
|
||||
fetchResourceFields('items'),
|
||||
])
|
||||
});
|
||||
const handleDeleteItem = (item) => { setDeleteItem(item); };
|
||||
|
||||
const fetchItems = useAsync(async () => {
|
||||
await Promise.all([
|
||||
requestFetchItems({ }),
|
||||
])
|
||||
});
|
||||
|
||||
const handleDeleteItem = useCallback((item) => {
|
||||
setDeleteItem(item);
|
||||
}, [setDeleteItem]);
|
||||
|
||||
const handleEditItem = () => {};
|
||||
const handleCancelDeleteItem = () => { setDeleteItem(false) };
|
||||
const handleConfirmDeleteItem = () => {
|
||||
|
||||
const handleCancelDeleteItem = useCallback(() => {
|
||||
setDeleteItem(false);
|
||||
}, [setDeleteItem]);
|
||||
|
||||
const handleConfirmDeleteItem = useCallback(() => {
|
||||
requestDeleteItem(deleteItem.id).then(() => {
|
||||
AppToaster.show({ message: 'the_item_has_been_deleted' });
|
||||
setDeleteItem(false);
|
||||
});
|
||||
};
|
||||
}, [requestDeleteItem, deleteItem]);
|
||||
|
||||
const handleFilterChange = (filter) => { setFilterConditions(filter); };
|
||||
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
|
||||
addItemsTableQueries({
|
||||
...(sortBy.length > 0) ? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_by: sortBy[0].desc ? 'desc' : 'asc',
|
||||
} : {},
|
||||
});
|
||||
fetchItems.execute();
|
||||
}, [fetchItems, addItemsTableQueries]);
|
||||
|
||||
const handleFilterChanged = useCallback(() => {
|
||||
fetchItems.execute();
|
||||
}, [fetchItems]);
|
||||
|
||||
const handleCustomViewChanged = useCallback(() => {
|
||||
fetchItems.execute();
|
||||
}, [fetchItems]);
|
||||
|
||||
return (
|
||||
<DashboardInsider isLoading={fetchHook.pending} name={'items-list'}>
|
||||
<ItemsActionsBar views={views} onFilterChange={handleFilterChange} />
|
||||
<ItemsActionsBar
|
||||
onFilterChanged={handleFilterChanged}
|
||||
views={views} />
|
||||
|
||||
<DashboardPageContent>
|
||||
<Switch>
|
||||
<Route>
|
||||
<DashboardViewsTabs resourceName={'items'} />
|
||||
<Route
|
||||
exact={true}
|
||||
path={[
|
||||
'/dashboard/items/:custom_view_id/custom_view',
|
||||
'/dashboard/items'
|
||||
]}>
|
||||
<ItemsViewsTabs onViewChanged={handleCustomViewChanged} />
|
||||
|
||||
<ItemsDataTable
|
||||
filterConditions={filterConditions}
|
||||
onDeleteItem={handleDeleteItem}
|
||||
onEditItem={handleEditItem} />
|
||||
onEditItem={handleEditItem}
|
||||
onFetchData={handleFetchData} />
|
||||
|
||||
<Alert
|
||||
cancelButtonText="Cancel"
|
||||
|
||||
94
client/src/containers/Dashboard/Items/ItemsViewsTabs.js
Normal file
94
client/src/containers/Dashboard/Items/ItemsViewsTabs.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
Alignment,
|
||||
Navbar,
|
||||
NavbarGroup,
|
||||
Tabs,
|
||||
Tab,
|
||||
Button
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Icon from 'components/Icon';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { compose } from 'utils';
|
||||
import ItemsConnect from 'connectors/Items.connect';
|
||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||
import {useUpdateEffect} from 'hooks';
|
||||
|
||||
function ItemsViewsTabs({
|
||||
views,
|
||||
setTopbarEditView,
|
||||
customViewChanged,
|
||||
addItemsTableQueries,
|
||||
onViewChanged,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
|
||||
const handleClickNewView = () => {
|
||||
setTopbarEditView(null);
|
||||
history.push('/dashboard/custom_views/items/new');
|
||||
};
|
||||
const handleViewLinkClick = () => {
|
||||
setTopbarEditView(customViewId);
|
||||
}
|
||||
|
||||
useUpdateEffect(() => {
|
||||
customViewChanged && customViewChanged(customViewId);
|
||||
|
||||
addItemsTableQueries({
|
||||
custom_view_id: customViewId || null,
|
||||
});
|
||||
onViewChanged && onViewChanged(customViewId);
|
||||
}, [customViewId]);
|
||||
|
||||
useEffect(() => {
|
||||
addItemsTableQueries({
|
||||
custom_view_id: customViewId,
|
||||
})
|
||||
}, [customViewId, addItemsTableQueries]);
|
||||
|
||||
const tabs = views.map(view => {
|
||||
const baseUrl = '/dashboard/items';
|
||||
const link = (
|
||||
<Link
|
||||
to={`${baseUrl}/${view.id}/custom_view`}
|
||||
onClick={handleViewLinkClick}
|
||||
>{view.name}</Link>
|
||||
);
|
||||
return <Tab
|
||||
id={`custom_view_${view.id}`}
|
||||
title={link} />;
|
||||
});
|
||||
return (
|
||||
<Navbar className='navbar--dashboard-views'>
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<Tabs
|
||||
id='navbar'
|
||||
large={true}
|
||||
selectedTabId={`custom_view_${customViewId}`}
|
||||
className='tabs--dashboard-views'
|
||||
>
|
||||
<Tab
|
||||
id='all'
|
||||
title={<Link to={`/dashboard/items`}>All</Link>} />
|
||||
|
||||
{tabs}
|
||||
<Button
|
||||
className='button--new-view'
|
||||
icon={<Icon icon='plus' />}
|
||||
onClick={handleClickNewView}
|
||||
minimal={true}
|
||||
/>
|
||||
</Tabs>
|
||||
</NavbarGroup>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
ItemsConnect,
|
||||
DashboardConnect,
|
||||
)(ItemsViewsTabs);
|
||||
@@ -83,14 +83,14 @@ export default [
|
||||
}),
|
||||
text: 'Manual Journals'
|
||||
},
|
||||
|
||||
// Items
|
||||
{
|
||||
path: `${BASE_URL}/items/list`,
|
||||
path: `${BASE_URL}/items/categories`,
|
||||
component: LazyLoader({
|
||||
loader: () => import('containers/Dashboard/Items/ItemsList')
|
||||
loader: () => import('containers/Dashboard/Items/ItemsCategoryList')
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: `${BASE_URL}/items/new`,
|
||||
component: LazyLoader({
|
||||
@@ -98,13 +98,16 @@ export default [
|
||||
})
|
||||
},
|
||||
|
||||
// Items
|
||||
{
|
||||
path: `${BASE_URL}/items/ItemCategoriesList`,
|
||||
path: `${BASE_URL}/items`,
|
||||
component: LazyLoader({
|
||||
loader: () => import('containers/Dashboard/Items/ItemsCategoryList')
|
||||
loader: () => import('containers/Dashboard/Items/ItemsList')
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
|
||||
// Financial Reports.
|
||||
{
|
||||
path: `${BASE_URL}/accounting/general-ledger`,
|
||||
|
||||
@@ -10,8 +10,14 @@ export const editItem = ({ id, form }) => {
|
||||
};
|
||||
|
||||
export const fetchItems = ({ query }) => {
|
||||
return (dispatch) => new Promise((resolve, reject) => {
|
||||
ApiService.get(`items`).then(response => {
|
||||
return (dispatch, getState) => new Promise((resolve, reject) => {
|
||||
const pageQuery = getState().accounts.tableQuery;
|
||||
|
||||
dispatch({
|
||||
type: t.ITEMS_TABLE_LOADING,
|
||||
payload: { loading: true },
|
||||
});
|
||||
ApiService.get(`items`, { params: { ...pageQuery, ...query } }).then(response => {
|
||||
dispatch({
|
||||
type: t.ITEMS_SET,
|
||||
items: response.data.items.results,
|
||||
@@ -22,6 +28,10 @@ export const fetchItems = ({ query }) => {
|
||||
customViewId: response.data.customViewId,
|
||||
paginationMeta: response.data.items.pagination,
|
||||
});
|
||||
dispatch({
|
||||
type: t.ITEMS_TABLE_LOADING,
|
||||
payload: { loading: false },
|
||||
});
|
||||
resolve(response);
|
||||
}).catch(error => { reject(error); });
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createReducer } from '@reduxjs/toolkit';
|
||||
import {
|
||||
getItemsViewPages,
|
||||
} from 'store/items/items.selectors';
|
||||
import { createTableQueryReducers } from 'store/queryReducers';
|
||||
|
||||
const initialState = {
|
||||
items: {},
|
||||
@@ -10,10 +11,12 @@ const initialState = {
|
||||
itemsRelation: {},
|
||||
currentPage: 1,
|
||||
currentViewId: -1,
|
||||
tableQuery: {},
|
||||
bulkActions: {},
|
||||
loading: false,
|
||||
};
|
||||
|
||||
export default createReducer(initialState, {
|
||||
const itemsReducer = createReducer(initialState, {
|
||||
[t.ITEMS_SET]: (state, action) => {
|
||||
const _items = {};
|
||||
|
||||
@@ -83,10 +86,16 @@ export default createReducer(initialState, {
|
||||
delete state.items[itemId];
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
[t.ITEMS_TABLE_LOADING]: (state, action) => {
|
||||
const { loading } = action.payload;
|
||||
state.loading = !!loading;
|
||||
},
|
||||
});
|
||||
|
||||
export default createTableQueryReducers('items', itemsReducer);
|
||||
|
||||
export const getItemById = (state, id) => {
|
||||
return state.items.items[id];
|
||||
};
|
||||
|
||||
@@ -6,4 +6,9 @@ export default {
|
||||
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',
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ $form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='ht
|
||||
.#{$ns}-control-indicator {
|
||||
border: 1px solid #c6c6c6;
|
||||
border-radius: $pt-border-radius;
|
||||
background-color: #fff;
|
||||
}
|
||||
input:checked ~ .#{$ns}-control-indicator {
|
||||
background-image: escape-svg($form-check-input-checked-bg-image);
|
||||
|
||||
@@ -11,14 +11,6 @@
|
||||
}
|
||||
|
||||
.table{
|
||||
.thead,
|
||||
.tbody{
|
||||
.th.selection,
|
||||
.td.selection{
|
||||
padding-left: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.tbody{
|
||||
.tr:not(.no-results) .td{
|
||||
padding-top: 0.4rem;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
&__topbar{
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
min-height: 65px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #F2EFEF;
|
||||
@@ -215,6 +215,20 @@
|
||||
|
||||
&__page-content{
|
||||
// padding: 22px;
|
||||
|
||||
|
||||
.bigcapital-datatable{
|
||||
|
||||
.table{
|
||||
.thead,
|
||||
.tbody{
|
||||
.th.selection,
|
||||
.td.selection{
|
||||
padding-left: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__preferences-topbar{
|
||||
|
||||
@@ -45,4 +45,12 @@
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group--active{
|
||||
margin-bottom: 5px;
|
||||
|
||||
.bp3-control.bp3-checkbox{
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user