fix bugs.

This commit is contained in:
Ahmed Bouhuolia
2020-04-29 05:11:02 +02:00
parent f4520e4e5c
commit f6c5cae82e
42 changed files with 575 additions and 401 deletions

View File

@@ -12,19 +12,24 @@ import Progress from 'components/NProgress/Progress';
import messages from 'lang/en'; import messages from 'lang/en';
import 'style/App.scss'; import 'style/App.scss';
function App(props) { function App({
isAuthorized,
locale,
}) {
const history = createBrowserHistory(); const history = createBrowserHistory();
history.listen((location, action) => {
console.log(`new location via ${action}`, location);
});
return ( return (
<IntlProvider locale={props.locale} messages={messages}> <IntlProvider locale={locale} messages={messages}>
<div className="App"> <div className="App">
<Router history={history}> <Router history={history}>
<Authentication isAuthenticated={props.isAuthorized} /> <Authentication isAuthenticated={isAuthorized} />
<PrivateRoute isAuthenticated={props.isAuthorized} component={Dashboard} /> <PrivateRoute isAuthenticated={isAuthorized} component={Dashboard} />
</Router> </Router>
</div> </div>
<Progress isAnimating={props.isLoading} />
</IntlProvider> </IntlProvider>
); );
} }
@@ -33,13 +38,10 @@ App.defaultProps = {
locale: 'en', locale: 'en',
}; };
App.propTypes = { const mapStateToProps = (state) => {
locale: PropTypes.string, return {
isAuthorized: PropTypes.bool, isAuthorized: isAuthenticated(state),
};
}; };
const mapStateToProps = (state) => ({
isAuthorized: isAuthenticated(state),
isLoading: !!state.dashboard.requestsLoading,
});
export default connect(mapStateToProps)(App); export default connect(mapStateToProps)(App);

View File

@@ -9,7 +9,8 @@ export default function DashboardContentRoute() {
<Switch> <Switch>
{ routes.map((route, index) => ( { routes.map((route, index) => (
<Route <Route
key={index} exact
// key={index}
path={`${route.path}`} path={`${route.path}`}
component={route.component} /> component={route.component} />
))} ))}

View File

@@ -15,11 +15,8 @@ function DashboardTopbarUser({ logout }) {
const onClickLogout = useCallback(() => { const onClickLogout = useCallback(() => {
logout(); logout();
history.go('/auth/login');
setTimeout(() => { }, [logout, history]);
history.push('/auth/login');
}, 100);
}, [history, logout]);
const userAvatarDropMenu = useMemo(() => ( const userAvatarDropMenu = useMemo(() => (
<Menu> <Menu>

View File

@@ -93,9 +93,9 @@ export default function DataTable({
...(selectionColumn) ? [{ ...(selectionColumn) ? [{
id: 'selection', id: 'selection',
disableResizing: true, disableResizing: true,
minWidth: 35, minWidth: 42,
width: 35, width: 42,
maxWidth: 35, maxWidth: 42,
// The header can use the table's getToggleAllRowsSelectedProps method // The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox // to render a checkbox
Header: ({ getToggleAllRowsSelectedProps }) => ( Header: ({ getToggleAllRowsSelectedProps }) => (

View File

@@ -22,15 +22,15 @@ import classNames from 'classnames';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import ItemCategoryConnect from 'connectors/ItemsCategory.connect'; import ItemCategoryConnect from 'connectors/ItemsCategory.connect';
import MoneyInputGroup from 'components/MoneyInputGroup'; import MoneyInputGroup from 'components/MoneyInputGroup';
import {useHistory} from 'react-router-dom';
const ItemForm = ({ const ItemForm = ({
requestSubmitItem, requestSubmitItem,
accounts, accounts,
categories, categories,
categoriesList,
}) => { }) => {
const [selectedAccounts, setSelectedAccounts] = useState({}); const [selectedAccounts, setSelectedAccounts] = useState({});
const history = useHistory();
const ItemTypeDisplay = useMemo(() => ([ const ItemTypeDisplay = useMemo(() => ([
{ value: null, label: 'Select Item Type' }, { value: null, label: 'Select Item Type' },
@@ -43,7 +43,7 @@ const ItemForm = ({
active: Yup.boolean(), active: Yup.boolean(),
name: Yup.string().required(), name: Yup.string().required(),
type: Yup.string().trim().required(), type: Yup.string().trim().required(),
sku: Yup.string().required(), sku: Yup.string().trim(),
cost_price: Yup.number(), cost_price: Yup.number(),
sell_price: Yup.number(), sell_price: Yup.number(),
cost_account_id: Yup.number().required(), cost_account_id: Yup.number().required(),
@@ -71,7 +71,14 @@ const ItemForm = ({
note: '', note: '',
}), []); }), []);
const formik = useFormik({ const {
getFieldProps,
setFieldValue,
values,
touched,
errors,
handleSubmit,
} = useFormik({
enableReinitialize: true, enableReinitialize: true,
validationSchema: validationSchema, validationSchema: validationSchema,
initialValues: { initialValues: {
@@ -83,13 +90,13 @@ const ItemForm = ({
message: 'The_Items_has_been_Submit' message: 'The_Items_has_been_Submit'
}); });
setSubmitting(false); setSubmitting(false);
history.push('/dashboard/items');
}) })
.catch((error) => { .catch((error) => {
setSubmitting(false); setSubmitting(false);
}); });
} }
}); });
const {errors, values, touched} = useMemo(() => formik, [formik]);
const accountItem = useCallback((item, { handleClick }) => ( const accountItem = useCallback((item, { handleClick }) => (
<MenuItem key={item.id} text={item.name} label={item.code} onClick={handleClick} /> <MenuItem key={item.id} text={item.name} label={item.code} onClick={handleClick} />
@@ -112,9 +119,9 @@ const ItemForm = ({
...selectedAccounts, ...selectedAccounts,
[filedName]: account [filedName]: account
}); });
formik.setFieldValue(filedName, account.id); setFieldValue(filedName, account.id);
}; };
}, [formik, selectedAccounts]); }, [setFieldValue, selectedAccounts]);
const categoryItem = useCallback((item, { handleClick }) => ( const categoryItem = useCallback((item, { handleClick }) => (
<MenuItem text={item.name} onClick={handleClick} /> <MenuItem text={item.name} onClick={handleClick} />
@@ -129,12 +136,12 @@ const ItemForm = ({
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []); const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
const handleMoneyInputChange = (fieldKey) => (e, value) => { const handleMoneyInputChange = (fieldKey) => (e, value) => {
formik.setFieldValue(fieldKey, value); setFieldValue(fieldKey, value);
}; };
return ( return (
<div class='item-form'> <div class='item-form'>
<form onSubmit={formik.handleSubmit}> <form onSubmit={handleSubmit}>
<div class="item-form__primary-section"> <div class="item-form__primary-section">
<FormGroup <FormGroup
medium={true} medium={true}
@@ -142,13 +149,13 @@ const ItemForm = ({
labelInfo={requiredSpan} labelInfo={requiredSpan}
className={'form-group--item-type'} className={'form-group--item-type'}
intent={(errors.type && touched.type) && Intent.DANGER} intent={(errors.type && touched.type) && Intent.DANGER}
helperText={<ErrorMessage {...formik} name="type" />} helperText={<ErrorMessage {...{errors, touched}} name="type" />}
inline={true} inline={true}
> >
<HTMLSelect <HTMLSelect
fill={true} fill={true}
options={ItemTypeDisplay} options={ItemTypeDisplay}
{...formik.getFieldProps('type')} {...getFieldProps('type')}
/> />
</FormGroup> </FormGroup>
@@ -157,13 +164,13 @@ const ItemForm = ({
labelInfo={requiredSpan} labelInfo={requiredSpan}
className={'form-group--item-name'} className={'form-group--item-name'}
intent={(errors.name && touched.name) && Intent.DANGER} intent={(errors.name && touched.name) && Intent.DANGER}
helperText={<ErrorMessage {...formik} name="name" />} helperText={<ErrorMessage {...{errors, touched}} name="name" />}
inline={true} inline={true}
> >
<InputGroup <InputGroup
medium={true} medium={true}
intent={(errors.name && touched.name) && Intent.DANGER} intent={(errors.name && touched.name) && Intent.DANGER}
{...formik.getFieldProps('name')} {...getFieldProps('name')}
/> />
</FormGroup> </FormGroup>
@@ -172,13 +179,13 @@ const ItemForm = ({
labelInfo={infoIcon} labelInfo={infoIcon}
className={'form-group--item-sku'} className={'form-group--item-sku'}
intent={(errors.sku && touched.sku) && Intent.DANGER} intent={(errors.sku && touched.sku) && Intent.DANGER}
helperText={<ErrorMessage {...formik} name="sku" />} helperText={<ErrorMessage {...{errors, touched}} name="sku" />}
inline={true} inline={true}
> >
<InputGroup <InputGroup
medium={true} medium={true}
intent={(errors.sku && touched.sku) && Intent.DANGER} intent={(errors.sku && touched.sku) && Intent.DANGER}
{...formik.getFieldProps('sku')} {...getFieldProps('sku')}
/> />
</FormGroup> </FormGroup>
@@ -187,7 +194,7 @@ const ItemForm = ({
labelInfo={infoIcon} labelInfo={infoIcon}
inline={true} inline={true}
intent={(errors.category_id && touched.category_id) && Intent.DANGER} intent={(errors.category_id && touched.category_id) && Intent.DANGER}
helperText={<ErrorMessage {...formik} name="category" />} helperText={<ErrorMessage {...{errors, touched}} name="category" />}
className={classNames( className={classNames(
'form-group--select-list', 'form-group--select-list',
'form-group--category', 'form-group--category',
@@ -195,7 +202,7 @@ const ItemForm = ({
)} )}
> >
<Select <Select
items={categoriesList} items={categories}
itemRenderer={categoryItem} itemRenderer={categoryItem}
itemPredicate={filterAccounts} itemPredicate={filterAccounts}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
@@ -218,7 +225,7 @@ const ItemForm = ({
inline={true} inline={true}
label={'Active'} label={'Active'}
defaultChecked={values.active} defaultChecked={values.active}
{...formik.getFieldProps('active')} {...getFieldProps('active')}
/> />
</FormGroup> </FormGroup>
</div> </div>
@@ -231,7 +238,7 @@ const ItemForm = ({
label={'Selling Price'} label={'Selling Price'}
className={'form-group--item-selling-price'} className={'form-group--item-selling-price'}
intent={(errors.selling_price && touched.selling_price) && Intent.DANGER} intent={(errors.selling_price && touched.selling_price) && Intent.DANGER}
helperText={<ErrorMessage {...formik} name="selling_price" />} helperText={<ErrorMessage {...{errors, touched}} name="selling_price" />}
inline={true} inline={true}
> >
<MoneyInputGroup <MoneyInputGroup
@@ -249,7 +256,7 @@ const ItemForm = ({
labelInfo={infoIcon} labelInfo={infoIcon}
inline={true} inline={true}
intent={(errors.sell_account_id && touched.sell_account_id) && Intent.DANGER} intent={(errors.sell_account_id && touched.sell_account_id) && Intent.DANGER}
helperText={<ErrorMessage {...formik} name="sell_account_id" />} helperText={<ErrorMessage {...{errors, touched}} name="sell_account_id" />}
className={classNames( className={classNames(
'form-group--sell-account', 'form-group--sell-account',
'form-group--select-list', 'form-group--select-list',
@@ -280,7 +287,7 @@ const ItemForm = ({
label={'Cost Price'} label={'Cost Price'}
className={'form-group--item-cost-price'} className={'form-group--item-cost-price'}
intent={(errors.cost_price && touched.cost_price) && Intent.DANGER} intent={(errors.cost_price && touched.cost_price) && Intent.DANGER}
helperText={<ErrorMessage {...formik} name="cost_price" />} helperText={<ErrorMessage {...{errors, touched}} name="cost_price" />}
inline={true} inline={true}
> >
<MoneyInputGroup <MoneyInputGroup
@@ -298,7 +305,7 @@ const ItemForm = ({
labelInfo={infoIcon} labelInfo={infoIcon}
inline={true} inline={true}
intent={(errors.cost_account_id && touched.cost_account_id) && Intent.DANGER} intent={(errors.cost_account_id && touched.cost_account_id) && Intent.DANGER}
helperText={<ErrorMessage {...formik} name="cost_account_id" />} helperText={<ErrorMessage {...{errors, touched}} name="cost_account_id" />}
className={classNames( className={classNames(
'form-group--cost-account', 'form-group--cost-account',
'form-group--select-list', 'form-group--select-list',
@@ -331,7 +338,7 @@ const ItemForm = ({
label={'Inventory Account'} label={'Inventory Account'}
inline={true} inline={true}
intent={(errors.inventory_account_id && touched.inventory_account_id) && Intent.DANGER} intent={(errors.inventory_account_id && touched.inventory_account_id) && Intent.DANGER}
helperText={<ErrorMessage {...formik} name="inventory_account_id" />} helperText={<ErrorMessage {...{errors, touched}} name="inventory_account_id" />}
className={classNames( className={classNames(
'form-group--item-inventory_account', 'form-group--item-inventory_account',
'form-group--select-list', 'form-group--select-list',
@@ -361,8 +368,8 @@ const ItemForm = ({
> >
<InputGroup <InputGroup
medium={true} medium={true}
intent={formik.errors.stock && Intent.DANGER} intent={errors.stock && Intent.DANGER}
{...formik.getFieldProps('stock')} {...getFieldProps('stock')}
/> />
</FormGroup> </FormGroup>
</Col> </Col>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useCallback, useState, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import ItemsCategoryConnect from 'connectors/ItemsCategory.connect'; import ItemsCategoryConnect from 'connectors/ItemsCategory.connect';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
@@ -11,8 +11,6 @@ import {
Menu, Menu,
MenuItem, MenuItem,
Position, Position,
Classes,
Tooltip,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
const ItemsCategoryList = ({ const ItemsCategoryList = ({
@@ -22,6 +20,7 @@ const ItemsCategoryList = ({
onEditCategory, onEditCategory,
openDialog, openDialog,
count, count,
onSelectedRowsChange,
}) => { }) => {
const handelEditCategory = (category) => () => { const handelEditCategory = (category) => () => {
openDialog('item-form', { action: 'edit', id: category.id }); openDialog('item-form', { action: 'edit', id: category.id });
@@ -42,8 +41,7 @@ const ItemsCategoryList = ({
</Menu> </Menu>
); );
const columns = useMemo( const columns = useMemo(() => [
() => [
{ {
id: 'name', id: 'name',
Header: 'Category Name', Header: 'Category Name',
@@ -60,7 +58,7 @@ const ItemsCategoryList = ({
{ {
id: 'count', id: 'count',
Header: 'Count', Header: 'Count',
accessor: () => <span>{count}</span>, accessor: (r) => r.count || '',
className: 'count', className: 'count',
width: 50, width: 50,
}, },
@@ -77,16 +75,17 @@ const ItemsCategoryList = ({
), ),
className: 'actions', className: 'actions',
width: 50, width: 50,
// canResize: false disableResizing: false
}, },
], ], [actionMenuList]);
[]
);
const handelFetchData = useCallback(() => { const handelFetchData = useCallback(() => {
onFetchData && onFetchData(); onFetchData && onFetchData();
}, []); }, []);
const handleSelectedRowsChange = useCallback((selectedRows) => {
onSelectedRowsChange && onSelectedRowsChange(selectedRows.map(s => s.original));
}, [onSelectedRowsChange]);
return ( return (
<LoadingIndicator spinnerSize={30}> <LoadingIndicator spinnerSize={30}>
@@ -97,10 +96,13 @@ const ItemsCategoryList = ({
manualSortBy={true} manualSortBy={true}
selectionColumn={true} selectionColumn={true}
expandable={true} expandable={true}
treeGraph={true} onSelectedRowsChange={handleSelectedRowsChange}
/> />
</LoadingIndicator> </LoadingIndicator>
); );
}; };
export default compose(DialogConnect, ItemsCategoryConnect)(ItemsCategoryList); export default compose(
DialogConnect,
ItemsCategoryConnect,
)(ItemsCategoryList);

View File

@@ -25,7 +25,9 @@ function ManualJournalActionsBar({
views, views,
getResourceFields, getResourceFields,
addManualJournalsTableQueries, addManualJournalsTableQueries,
onFilterChanged onFilterChanged,
selectedRows,
onBulkDelete
}) { }) {
const { path } = useRouteMatch(); const { path } = useRouteMatch();
const history = useHistory(); const history = useHistory();
@@ -50,6 +52,13 @@ function ManualJournalActionsBar({
onFilterChanged && onFilterChanged(filterConditions); onFilterChanged && onFilterChanged(filterConditions);
} }
}); });
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
// Handle delete button click.
const handleBulkDelete = useCallback(() => {
onBulkDelete && onBulkDelete(selectedRows.map(r => r.id));
}, [onBulkDelete, selectedRows]);
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
<NavbarGroup> <NavbarGroup>
@@ -85,12 +94,13 @@ function ManualJournalActionsBar({
/> />
</Popover> </Popover>
{ (false) && ( {(hasSelectedRows) && (
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='trash' iconSize={15} />} icon={<Icon icon='trash' iconSize={15} />}
text='Delete' text='Delete'
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handleBulkDelete}
/> />
)} )}
<Button <Button

View File

@@ -8,7 +8,7 @@ import {
MenuDivider, MenuDivider,
Position, Position,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useParams, useHistory } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import { compose } from 'utils'; import { compose } from 'utils';
import moment from 'moment'; import moment from 'moment';
@@ -16,7 +16,7 @@ import ManualJournalsConnect from 'connectors/ManualJournals.connect';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import DashboardConnect from 'connectors/Dashboard.connector'; import DashboardConnect from 'connectors/Dashboard.connector';
import ViewConnect from 'connectors/View.connector'; import ViewConnect from 'connectors/View.connector';
import LoadingIndicator from 'components/LoadingIndicator'; import { useUpdateEffect } from 'hooks';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import Money from 'components/Money'; import Money from 'components/Money';
@@ -31,8 +31,16 @@ function ManualJournalsDataTable({
onEditJournal, onEditJournal,
onDeleteJournal, onDeleteJournal,
onPublishJournal, onPublishJournal,
onSelectedRowsChange,
}) { }) {
const { custom_view_id: customViewId } = useParams(); const { custom_view_id: customViewId } = useParams();
const [initialMount, setInitialMount] = useState(false);
useUpdateEffect(() => {
if (!manualJournalsLoading) {
setInitialMount(true);
}
}, [manualJournalsLoading, setInitialMount]);
useEffect(() => { useEffect(() => {
const viewMeta = getViewItem(customViewId); const viewMeta = getViewItem(customViewId);
@@ -42,7 +50,25 @@ function ManualJournalsDataTable({
setTopbarEditView(customViewId); setTopbarEditView(customViewId);
} }
changePageSubtitle(customViewId && viewMeta ? viewMeta.name : ''); changePageSubtitle(customViewId && viewMeta ? viewMeta.name : '');
}, [customViewId]); }, [
customViewId,
changeCurrentView,
changePageSubtitle,
setTopbarEditView,
getViewItem,
]);
const handlePublishJournal = useCallback((journal) => () => {
onPublishJournal && onPublishJournal(journal);
}, [onPublishJournal]);
const handleEditJournal = useCallback((journal) => () => {
onEditJournal && onEditJournal(journal);
}, [onEditJournal]);
const handleDeleteJournal = useCallback((journal) => () => {
onDeleteJournal && onDeleteJournal(journal);
}, [onDeleteJournal]);
const actionMenuList = (journal) => ( const actionMenuList = (journal) => (
<Menu> <Menu>
@@ -51,21 +77,15 @@ function ManualJournalsDataTable({
{!journal.status && ( {!journal.status && (
<MenuItem <MenuItem
text="Publish Journal" text="Publish Journal"
onClick={() => { onClick={handlePublishJournal(journal)} />
onPublishJournal && onPublishJournal(journal);
}} />
)} )}
<MenuItem <MenuItem
text='Edit Journal' text='Edit Journal'
onClick={() => { onClick={handleEditJournal(journal)} />
onEditJournal && onEditJournal(journal);
}} />
<MenuItem <MenuItem
text='Delete Journal' text='Delete Journal'
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={() => { onClick={handleDeleteJournal(journal)} />
onDeleteJournal && onDeleteJournal(journal);
}} />
</Menu> </Menu>
); );
@@ -143,26 +163,25 @@ function ManualJournalsDataTable({
}, },
], []); ], []);
const handleDataTableFetchData = useCallback(() => { const handleDataTableFetchData = useCallback((...args) => {
onFetchData && onFetchData(); onFetchData && onFetchData(...args);
}, [onFetchData]); }, [onFetchData]);
const selectionColumn = useMemo(() => ({ const handleSelectedRowsChange = useCallback((selectedRows) => {
minWidth: 42, onSelectedRowsChange && onSelectedRowsChange(selectedRows.map(s => s.original));
width: 42, }, [onSelectedRowsChange]);
maxWidth: 42,
}), []);
return ( return (
<LoadingIndicator loading={manualJournalsLoading} spinnerSize={30}>
<DataTable <DataTable
columns={columns} columns={columns}
data={manualJournals} data={manualJournals}
onFetchData={handleDataTableFetchData} onFetchData={handleDataTableFetchData}
manualSortBy={true} manualSortBy={true}
selectionColumn={selectionColumn} selectionColumn={true}
noInitialFetch={true}
loading={manualJournalsLoading && !initialMount}
onSelectedRowsChange={handleSelectedRowsChange}
/> />
</LoadingIndicator>
); );
} }

View File

@@ -0,0 +1,18 @@
import React from 'react';
import {connect} from 'react-redux';
import Progress from './Progress';
function AppProgress({
isAnimating,
}) {
return (
<Progress isAnimating={isAnimating} />
);
};
const mapStateToProps = (state) => ({
isAnimating: state.dashboard.requestsLoading > 0,
});
export default connect(mapStateToProps)(AppProgress);

View File

@@ -21,8 +21,11 @@ export default function SidebarMenu() {
}; };
return ( return (
(item.divider) ? (item.divider) ?
<MenuDivider title={item.title} /> : <MenuDivider
key={index}
title={item.title} /> :
<MenuItem <MenuItem
key={index}
active={isActive} active={isActive}
icon={<Icon icon={item.icon} iconSize={item.iconSize} />} icon={<Icon icon={item.icon} iconSize={item.iconSize} />}
text={item.text} text={item.text}

View File

@@ -21,8 +21,10 @@ export const mapStateToProps = (state, props) => {
return { return {
views: getResourceViews(state, 'items'), views: getResourceViews(state, 'items'),
currentPageItems: getCurrentPageResults( currentPageItems: getCurrentPageResults(
state.items.items, viewPages, state.items.currentPage), state.items.items,
viewPages,
state.items.currentPage,
),
bulkSelected: state.items.bulkActions, bulkSelected: state.items.bulkActions,
itemsTableLoading: state.items.loading, itemsTableLoading: state.items.loading,
}; };

View File

@@ -12,8 +12,7 @@ export const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'item-form'); const dialogPayload = getDialogPayload(state, 'item-form');
return { return {
categories: state.itemCategories.categories, categories: Object.values(state.itemCategories.categories),
count: 1,
name: 'item-form', name: 'item-form',
payload: { action: 'new', id: null }, payload: { action: 'new', id: null },
editItemCategory: editItemCategory:

View File

@@ -3,6 +3,7 @@ import {
deleteManualJournal, deleteManualJournal,
fetchManualJournalsTable, fetchManualJournalsTable,
publishManualJournal, publishManualJournal,
deleteBulkManualJournals,
} from 'store/manualJournals/manualJournals.actions'; } from 'store/manualJournals/manualJournals.actions';
import { getResourceViews } from 'store/customViews/customViews.selectors'; import { getResourceViews } from 'store/customViews/customViews.selectors';
import t from 'store/types'; import t from 'store/types';
@@ -26,14 +27,14 @@ const mapActionsToProps = (dispatch) => ({
}), }),
addManualJournalsTableQueries: (queries) => addManualJournalsTableQueries: (queries) =>
dispatch({ dispatch({
type: 'MANUAL_JOURNALS_TABLE_QUERIES_ADD', type: t.MANUAL_JOURNALS_TABLE_QUERIES_ADD,
queries, queries,
}), }),
fetchManualJournalsTable: (query = {}) => fetchManualJournalsTable: (query = {}) =>
dispatch(fetchManualJournalsTable({ query: { ...query } })), dispatch(fetchManualJournalsTable({ query: { ...query } })),
requestPublishManualJournal: (id) => requestPublishManualJournal: (id) => dispatch(publishManualJournal({ id })),
dispatch(publishManualJournal({ id })), requestDeleteBulkManualJournals: (ids) => dispatch(deleteBulkManualJournals({ ids })),
}); });
export default connect(mapStateToProps, mapActionsToProps); export default connect(mapStateToProps, mapActionsToProps);

View File

@@ -11,7 +11,7 @@ import {
FormGroup, FormGroup,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import login from 'store/authentication/authentication.actions'; import login from 'store/authentication/authentication.actions';
import {hasErrorType, isAuthenticated} from 'store/authentication/authentication.reducer'; import {hasErrorType} from 'store/authentication/authentication.reducer';
import AuthenticationToaster from 'components/AppToaster'; import AuthenticationToaster from 'components/AppToaster';
import t from 'store/types'; import t from 'store/types';
@@ -21,9 +21,6 @@ const ERRORS_TYPES = {
}; };
function Login({ function Login({
login, login,
errors,
clearErrors,
hasError,
}) { }) {
const intl = useIntl(); const intl = useIntl();
const history = useHistory(); const history = useHistory();
@@ -51,19 +48,17 @@ function Login({
login({ login({
crediential: values.crediential, crediential: values.crediential,
password: values.password, password: values.password,
}); }).then(() => {
}, history.go('/dashboard/homepage');
}); }).catch((errors) => {
useEffect(() => {
const toastBuilders = []; const toastBuilders = [];
if (hasError(ERRORS_TYPES.INVALID_DETAILS)) { if (errors.find((e) => e.type === ERRORS_TYPES.INVALID_DETAILS)) {
toastBuilders.push({ toastBuilders.push({
message: intl.formatMessage({ id: 'invalid_email_or_phone_numner' }), message: intl.formatMessage({ id: 'invalid_email_or_phone_numner' }),
intent: Intent.WARNING, intent: Intent.WARNING,
}); });
} }
if (hasError(ERRORS_TYPES.USER_INACTIVE)) { if (errors.find((e) => e.type === ERRORS_TYPES.USER_INACTIVE)) {
toastBuilders.push({ toastBuilders.push({
message: intl.formatMessage({ id: 'the_user_has_been_suspended_from_admin' }), message: intl.formatMessage({ id: 'the_user_has_been_suspended_from_admin' }),
intent: Intent.WARNING, intent: Intent.WARNING,
@@ -72,13 +67,8 @@ function Login({
toastBuilders.forEach(builder => { toastBuilders.forEach(builder => {
AuthenticationToaster.show(builder); AuthenticationToaster.show(builder);
}); });
}, [hasError, intl]); });
},
// Handle unmount component
useEffect(() => () => {
if (errors.length > 0) {
clearErrors();
}
}); });
return ( return (

View File

@@ -12,17 +12,17 @@ import moment from 'moment';
import {momentFormatter} from 'utils'; import {momentFormatter} from 'utils';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import CurrenciesSelectList from 'components/CurrenciesSelectList'; import CurrenciesSelectList from 'components/CurrenciesSelectList';
import ErrorMessage from 'components/ErrorMessage';
export default function MakeJournalEntriesHeader({ export default function MakeJournalEntriesHeader({
formik formik: { errors, touched, setFieldValue, getFieldProps }
}) { }) {
const intl = useIntl(); const intl = useIntl();
const handleDateChange = useCallback((date) => { const handleDateChange = useCallback((date) => {
const formatted = moment(date).format('YYYY-MM-DD'); const formatted = moment(date).format('YYYY-MM-DD');
formik.setFieldValue('date', formatted); setFieldValue('date', formatted);
}, [formik]); }, [setFieldValue]);
const infoIcon = useMemo(() => const infoIcon = useMemo(() =>
(<Icon icon="info-circle" iconSize={12} />), []); (<Icon icon="info-circle" iconSize={12} />), []);
@@ -35,22 +35,22 @@ export default function MakeJournalEntriesHeader({
label={'Journal number'} label={'Journal number'}
labelInfo={infoIcon} labelInfo={infoIcon}
className={'form-group--journal-number'} className={'form-group--journal-number'}
intent={formik.errors.journal_number && Intent.DANGER} intent={(errors.journal_number && touched.journal_number) && Intent.DANGER}
helperText={formik.errors.journal_number && formik.errors.journal_number} helperText={<ErrorMessage name="journal_number" {...{errors, touched}} />}
fill={true}> fill={true}>
<InputGroup <InputGroup
intent={formik.errors.journal_number && Intent.DANGER} intent={(errors.journal_number && touched.journal_number) && Intent.DANGER}
fill={true} fill={true}
{...formik.getFieldProps('journal_number')} /> {...getFieldProps('journal_number')} />
</FormGroup> </FormGroup>
</Col> </Col>
<Col sm={2}> <Col sm={2}>
<FormGroup <FormGroup
label={intl.formatMessage({'id': 'date'})} label={intl.formatMessage({'id': 'date'})}
intent={formik.errors.date && Intent.DANGER} intent={(errors.date && touched.date) && Intent.DANGER}
helperText={formik.errors.date && formik.errors.date} helperText={<ErrorMessage name="date" {...{errors, touched}} />}
minimal={true}> minimal={true}>
<DateInput <DateInput
@@ -65,14 +65,14 @@ export default function MakeJournalEntriesHeader({
<FormGroup <FormGroup
label={intl.formatMessage({'id': 'description'})} label={intl.formatMessage({'id': 'description'})}
className={'form-group--description'} className={'form-group--description'}
intent={formik.errors.name && Intent.DANGER} intent={(errors.name && touched.name) && Intent.DANGER}
helperText={formik.errors.name && formik.errors.label} helperText={<ErrorMessage name="description" {...{errors, touched}} />}
fill={true}> fill={true}>
<InputGroup <InputGroup
intent={formik.errors.name && Intent.DANGER} intent={(errors.name && touched.name) && Intent.DANGER}
fill={true} fill={true}
{...formik.getFieldProps('description')} /> {...getFieldProps('description')} />
</FormGroup> </FormGroup>
</Col> </Col>
</Row> </Row>
@@ -83,14 +83,14 @@ export default function MakeJournalEntriesHeader({
label={'Reference'} label={'Reference'}
labelInfo={infoIcon} labelInfo={infoIcon}
className={'form-group--reference'} className={'form-group--reference'}
intent={formik.errors.reference && Intent.DANGER} intent={(errors.reference && touched.reference) && Intent.DANGER}
helperText={formik.errors.reference && formik.errors.reference} helperText={<ErrorMessage name="reference" {...{errors, touched}} />}
fill={true}> fill={true}>
<InputGroup <InputGroup
intent={formik.errors.reference && Intent.DANGER} intent={(errors.reference && touched.reference) && Intent.DANGER}
fill={true} fill={true}
{...formik.getFieldProps('reference')} /> {...getFieldProps('reference')} />
</FormGroup> </FormGroup>
</Col> </Col>

View File

@@ -2,7 +2,7 @@ import React, {useMemo, useCallback} from 'react';
import { useParams, useHistory } from 'react-router-dom'; import { useParams, useHistory } from 'react-router-dom';
import { useAsync } from 'react-use'; import { useAsync } from 'react-use';
import MakeJournalEntriesForm from './MakeJournalEntriesForm'; import MakeJournalEntriesForm from './MakeJournalEntriesForm';
import LoadingIndicator from 'components/LoadingIndicator'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardConnect from 'connectors/Dashboard.connector'; import DashboardConnect from 'connectors/Dashboard.connector';
import {compose} from 'utils'; import {compose} from 'utils';
import MakeJournalEntriesConnect from 'connectors/MakeJournalEntries.connect'; import MakeJournalEntriesConnect from 'connectors/MakeJournalEntries.connect';
@@ -36,12 +36,12 @@ function MakeJournalEntriesPage({
}, [history]); }, [history]);
return ( return (
<LoadingIndicator loading={fetchJournal.loading} mount={false}> <DashboardInsider loading={fetchJournal.pending} name={'make-journal-page'}>
<MakeJournalEntriesForm <MakeJournalEntriesForm
onFormSubmit={handleFormSubmit} onFormSubmit={handleFormSubmit}
editJournal={editJournal} editJournal={editJournal}
onCancelForm={handleCancel} /> onCancelForm={handleCancel} />
</LoadingIndicator> </DashboardInsider>
); );
} }

View File

@@ -7,7 +7,7 @@ import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import ManualJournalsViewTabs from 'components/JournalEntry/ManualJournalsViewTabs'; import ManualJournalsViewTabs from 'components/JournalEntry/ManualJournalsViewTabs';
import ManualJournalsDataTable from 'components/JournalEntry/ManualJournalsDataTable'; import ManualJournalsDataTable from 'components/JournalEntry/ManualJournalsDataTable';
import DashboardActionsBar from 'components/JournalEntry/ManualJournalActionsBar'; import ManualJournalsActionsBar from 'components/JournalEntry/ManualJournalActionsBar';
import ManualJournalsConnect from 'connectors/ManualJournals.connect'; import ManualJournalsConnect from 'connectors/ManualJournals.connect';
import DashboardConnect from 'connectors/Dashboard.connector'; import DashboardConnect from 'connectors/Dashboard.connector';
import CustomViewConnect from 'connectors/CustomView.connector'; import CustomViewConnect from 'connectors/CustomView.connector';
@@ -16,13 +16,20 @@ import { compose } from 'utils';
function ManualJournalsTable({ function ManualJournalsTable({
changePageTitle, changePageTitle,
fetchResourceViews, fetchResourceViews,
fetchManualJournalsTable, fetchManualJournalsTable,
requestDeleteManualJournal, requestDeleteManualJournal,
requestPublishManualJournal, requestPublishManualJournal,
requestDeleteBulkManualJournals,
addManualJournalsTableQueries
}) { }) {
const history = useHistory(); const history = useHistory();
const [deleteManualJournal, setDeleteManualJournal] = useState(false); const [deleteManualJournal, setDeleteManualJournal] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
const [bulkDelete, setBulkDelete] = useState(false);
const fetchHook = useAsync(async () => { const fetchHook = useAsync(async () => {
await Promise.all([ await Promise.all([
@@ -32,16 +39,23 @@ function ManualJournalsTable({
const fetchManualJournalsHook = useAsync(async () => { const fetchManualJournalsHook = useAsync(async () => {
return fetchManualJournalsTable(); return fetchManualJournalsTable();
}, false); });
useEffect(() => { useEffect(() => {
changePageTitle('Manual Journals'); changePageTitle('Manual Journals');
}, []); }, [changePageTitle]);
const handleCancelManualJournalDelete = () => { // Handle delete manual journal click.
const handleDeleteJournal = useCallback((journal) => {
setDeleteManualJournal(journal);
}, [setDeleteManualJournal]);
// Handle cancel delete manual journal.
const handleCancelManualJournalDelete = useCallback(() => {
setDeleteManualJournal(false); setDeleteManualJournal(false);
}; }, [setDeleteManualJournal]);
// Handle confirm delete manual journal.
const handleConfirmManualJournalDelete = useCallback(() => { const handleConfirmManualJournalDelete = useCallback(() => {
requestDeleteManualJournal(deleteManualJournal.id).then(() => { requestDeleteManualJournal(deleteManualJournal.id).then(() => {
setDeleteManualJournal(false); setDeleteManualJournal(false);
@@ -49,25 +63,53 @@ function ManualJournalsTable({
}); });
}, [deleteManualJournal, requestDeleteManualJournal]); }, [deleteManualJournal, requestDeleteManualJournal]);
const handleBulkDelete = useCallback((accountsIds) => {
setBulkDelete(accountsIds);
}, [setBulkDelete]);
const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkManualJournals(bulkDelete).then(() => {
setBulkDelete(false);
AppToaster.show({ message: 'the_accounts_have_been_deleted' });
}).catch((error) => {
setBulkDelete(false);
});
}, [
requestDeleteBulkManualJournals,
bulkDelete,
]);
const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false);
}, []);
const handleEditJournal = useCallback((journal) => { const handleEditJournal = useCallback((journal) => {
history.push(`/dashboard/accounting/manual-journals/${journal.id}/edit`); history.push(`/dashboard/accounting/manual-journals/${journal.id}/edit`);
}, [history]); }, [history]);
const handleDeleteJournal = useCallback((journal) => { // Handle filter change to re-fetch data-table.
setDeleteManualJournal(journal);
}, []);
const handleFilterChanged = useCallback(() => { const handleFilterChanged = useCallback(() => {
fetchManualJournalsHook.execute(); fetchManualJournalsHook.execute();
}, []); }, [fetchManualJournalsHook]);
// Handle view change to re-fetch data table.
const handleViewChanged = useCallback(() => { const handleViewChanged = useCallback(() => {
fetchManualJournalsHook.execute(); fetchManualJournalsHook.execute();
}, []); }, [fetchManualJournalsHook]);
const handleFetchData = useCallback(() => { // Handle fetch data of manual jouranls datatable.
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
addManualJournalsTableQueries({
...(sortBy.length > 0) ? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
} : {},
});
fetchManualJournalsHook.execute(); fetchManualJournalsHook.execute();
}, []); }, [
fetchManualJournalsHook,
addManualJournalsTableQueries,
]);
const handlePublishJournal = useCallback((journal) => { const handlePublishJournal = useCallback((journal) => {
requestPublishManualJournal(journal.id).then(() => { requestPublishManualJournal(journal.id).then(() => {
@@ -75,9 +117,16 @@ function ManualJournalsTable({
}) })
}, [requestPublishManualJournal]); }, [requestPublishManualJournal]);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback((accounts) => {
setSelectedRows(accounts);
}, [setSelectedRows]);
return ( return (
<DashboardInsider loading={fetchHook.pending} name={'manual-journals'}> <DashboardInsider loading={fetchHook.pending} name={'manual-journals'}>
<DashboardActionsBar <ManualJournalsActionsBar
onBulkDelete={handleBulkDelete}
selectedRows={selectedRows}
onFilterChanged={handleFilterChanged} /> onFilterChanged={handleFilterChanged} />
<DashboardPageContent> <DashboardPageContent>
@@ -97,7 +146,8 @@ function ManualJournalsTable({
onDeleteJournal={handleDeleteJournal} onDeleteJournal={handleDeleteJournal}
onFetchData={handleFetchData} onFetchData={handleFetchData}
onEditJournal={handleEditJournal} onEditJournal={handleEditJournal}
onPublishJournal={handlePublishJournal} /> onPublishJournal={handlePublishJournal}
onSelectedRowsChange={handleSelectedRowsChange} />
<Alert <Alert
cancelButtonText='Cancel' cancelButtonText='Cancel'
@@ -113,6 +163,21 @@ function ManualJournalsTable({
able to restore it later, but it will become private to you. able to restore it later, but it will become private to you.
</p> </p>
</Alert> </Alert>
<Alert
cancelButtonText='Cancel'
confirmButtonText='Move to Trash'
icon='trash'
intent={Intent.DANGER}
isOpen={bulkDelete}
onCancel={handleCancelBulkDelete}
onConfirm={handleConfirmBulkDelete}
>
<p>
Are you sure you want to move <b>filename</b> to Trash? You will be
able to restore it later, but it will become private to you.
</p>
</Alert>
</DashboardPageContent> </DashboardPageContent>
</DashboardInsider> </DashboardInsider>
); );

View File

@@ -26,8 +26,6 @@ function AccountsChart({
requestFetchAccountsTable, requestFetchAccountsTable,
addAccountsTableQueries, addAccountsTableQueries,
requestDeleteBulkAccounts, requestDeleteBulkAccounts,
setDashboardRequestLoading,
setDashboardRequestCompleted,
}) { }) {
const [deleteAccount, setDeleteAccount] = useState(false); const [deleteAccount, setDeleteAccount] = useState(false);
const [inactiveAccount, setInactiveAccount] = useState(false); const [inactiveAccount, setInactiveAccount] = useState(false);
@@ -36,23 +34,17 @@ function AccountsChart({
// Fetch accounts resource views and fields. // Fetch accounts resource views and fields.
const fetchHook = useAsync(async () => { const fetchHook = useAsync(async () => {
setDashboardRequestLoading();
await Promise.all([ await Promise.all([
fetchResourceViews('accounts'), fetchResourceViews('accounts'),
fetchResourceFields('accounts'), fetchResourceFields('accounts'),
]); ]);
setDashboardRequestCompleted();
}); });
// Fetch accounts list according to the given custom view id. // Fetch accounts list according to the given custom view id.
const fetchAccountsHook = useAsync(async () => { const fetchAccountsHook = useAsync(async () => {
setDashboardRequestLoading();
await Promise.all([ await Promise.all([
requestFetchAccountsTable(), requestFetchAccountsTable(),
]); ]);
setDashboardRequestCompleted();
}, false); }, false);
useEffect(() => { useEffect(() => {
@@ -102,7 +94,7 @@ function AccountsChart({
requestFetchAccountsTable(); requestFetchAccountsTable();
AppToaster.show({ message: 'the_account_has_been_inactivated' }); AppToaster.show({ message: 'the_account_has_been_inactivated' });
}); });
}, [inactiveAccount]); }, [inactiveAccount, requestFetchAccountsTable, requestInactiveAccount]);
const handleEditAccount = (account) => { const handleEditAccount = (account) => {

View File

@@ -8,11 +8,7 @@ import {
FormGroup, FormGroup,
InputGroup, InputGroup,
Intent, Intent,
TextArea,
MenuItem,
Checkbox,
Classes, Classes,
HTMLSelect,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import UserFormDialogConnect from 'connectors/UserFormDialog.connector'; import UserFormDialogConnect from 'connectors/UserFormDialog.connector';
import DialogReduxConnect from 'components/DialogReduxConnect'; import DialogReduxConnect from 'components/DialogReduxConnect';
@@ -22,7 +18,6 @@ import { objectKeysTransform } from 'utils';
import { pick, snakeCase } from 'lodash'; import { pick, snakeCase } from 'lodash';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from 'components/Icon';
import { compose } from 'utils'; import { compose } from 'utils';
function UserFormDialog({ function UserFormDialog({
@@ -32,7 +27,6 @@ function UserFormDialog({
name, name,
payload, payload,
isOpen, isOpen,
userDetails,
closeDialog, closeDialog,
}) { }) {
const intl = useIntl(); const intl = useIntl();
@@ -84,11 +78,6 @@ function UserFormDialog({
}); });
const { values, errors, touched } = useMemo(() => formik, [formik]); const { values, errors, touched } = useMemo(() => formik, [formik]);
const statusOptions = [
{ value: 1, label: 'Active' },
{ value: 2, label: 'Inactive' },
];
const onDialogOpening = () => { const onDialogOpening = () => {
fetchHook.execute(); fetchHook.execute();
}; };

View File

@@ -15,11 +15,12 @@ const ItemFormContainer = ({
requestFetchItemCategories, requestFetchItemCategories,
}) => { }) => {
const { id } = useParams(); const { id } = useParams();
useEffect(() => { useEffect(() => {
id ? id ?
changePageTitle('Edit Item Details') : changePageTitle('Edit Item Details') :
changePageTitle('New Item'); changePageTitle('New Item');
}, []); }, [id, changePageTitle]);
const fetchHook = useAsync(async () => { const fetchHook = useAsync(async () => {
await Promise.all([ await Promise.all([

View File

@@ -29,8 +29,8 @@ const ItemsActionsBar = ({
getResourceViews, getResourceViews,
views, views,
onFilterChanged, onFilterChanged,
bulkSelected,
addItemsTableQueries, addItemsTableQueries,
selectedRows = [],
}) => { }) => {
const { path } = useRouteMatch(); const { path } = useRouteMatch();
const history = useHistory(); const history = useHistory();
@@ -43,6 +43,7 @@ const ItemsActionsBar = ({
history.push('/dashboard/items/new'); history.push('/dashboard/items/new');
}; };
const itemsFields = getResourceFields('items'); const itemsFields = getResourceFields('items');
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
const filterDropdown = FilterDropdown({ const filterDropdown = FilterDropdown({
fields: itemsFields, fields: itemsFields,
@@ -55,11 +56,6 @@ const ItemsActionsBar = ({
} }
}); });
const hasBulkActionsSelected = useMemo(
() => !!Object.keys(bulkSelected).length,
[bulkSelected]
);
const onClickNewCategory = useCallback(() => { const onClickNewCategory = useCallback(() => {
openDialog('item-form', {}); openDialog('item-form', {});
}, [openDialog]); }, [openDialog]);
@@ -108,7 +104,7 @@ const ItemsActionsBar = ({
onClick={onClickNewCategory} onClick={onClickNewCategory}
/> />
{hasBulkActionsSelected && ( {hasSelectedRows && (
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
intent={Intent.DANGER} intent={Intent.DANGER}

View File

@@ -1,7 +1,5 @@
import React, { useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import {} from 'reselect';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { compose } from 'utils'; import { compose } from 'utils';
import { import {
NavbarGroup, NavbarGroup,
@@ -20,21 +18,24 @@ import DialogConnect from 'connectors/Dialog.connector';
import FilterDropdown from 'components/FilterDropdown'; import FilterDropdown from 'components/FilterDropdown';
import ResourceConnect from 'connectors/Resource.connector'; import ResourceConnect from 'connectors/Resource.connector';
const ItemsCategoryActionsBar = ({ const ItemsCategoryActionsBar = ({
openDialog, openDialog,
onDeleteCategory, onDeleteCategory,
onFilterChanged, onFilterChanged,
getResourceFields, getResourceFields,
selectedRows,
}) => { }) => {
const onClickNewCategory = () => { const onClickNewCategory = useCallback(() => {
openDialog('item-form', {}); openDialog('item-form', {});
}; }, [openDialog]);
const handleDeleteCategory = (category) => { const handleDeleteCategory = useCallback((category) => {
onDeleteCategory(category); onDeleteCategory(selectedRows);
}; }, [selectedRows, onDeleteCategory]);
const categoriesFields = getResourceFields('itemCategories'); const categoriesFields = getResourceFields('itemCategories');
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
const filterDropdown = FilterDropdown({ const filterDropdown = FilterDropdown({
fields: categoriesFields, fields: categoriesFields,
@@ -63,14 +64,15 @@ const ItemsCategoryActionsBar = ({
/> />
</Popover> </Popover>
{ hasSelectedRows && (
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='trash' iconSize={15} />} icon={<Icon icon='trash' iconSize={15} />}
text='Delete Category' text='Delete'
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handleDeleteCategory} onClick={handleDeleteCategory}
/> />
)}
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='file-import' />} icon={<Icon icon='file-import' />}

View File

@@ -20,25 +20,29 @@ const ItemCategoriesList = ({
}) => { }) => {
const { id } = useParams(); const { id } = useParams();
const [deleteCategory, setDeleteCategory] = useState(false); const [deleteCategory, setDeleteCategory] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
useEffect(() => { useEffect(() => {
id id
? changePageTitle('Edit Item Details') ? changePageTitle('Edit Item Details')
: changePageTitle('Categories List'); : changePageTitle('Categories List');
}, []); }, [id, changePageTitle]);
const fetchHook = useAsync(async () => { const fetchHook = useAsync(async () => {
await Promise.all([requestFetchItemCategories()]); await Promise.all([
requestFetchItemCategories(),
]);
}, false); }, false);
const handelDeleteCategory = category => { const handelDeleteCategory = useCallback((category) => {
setDeleteCategory(category); setDeleteCategory(category);
}; }, [setDeleteCategory]);
const handelEditCategory = category => {}; const handelEditCategory = category => {};
const handelCancelCategoryDelete = () => {
const handelCancelCategoryDelete = useCallback(() => {
setDeleteCategory(false); setDeleteCategory(false);
}; }, [setDeleteCategory]);
const handelConfirmCategoryDelete = useCallback(() => { const handelConfirmCategoryDelete = useCallback(() => {
requestDeleteItemCategory(deleteCategory.id).then(() => { requestDeleteItemCategory(deleteCategory.id).then(() => {
@@ -47,23 +51,30 @@ const ItemCategoriesList = ({
message: 'the_category_has_been_delete' message: 'the_category_has_been_delete'
}); });
}); });
}, [deleteCategory]); }, [deleteCategory, requestDeleteItemCategory, setDeleteCategory]);
const handleFetchData = useCallback(() => { const handleFetchData = useCallback(() => {
fetchHook.execute(); fetchHook.execute();
}, []); }, []);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback((accounts) => {
setSelectedRows(accounts);
}, [setSelectedRows]);
return ( return (
<DashboardInsider loading={fetchHook.pending}> <DashboardInsider loading={fetchHook.pending}>
<ItemsCategoryActionsBar <ItemsCategoryActionsBar
views={views} views={views}
onDeleteCategory={handelDeleteCategory} onDeleteCategory={handelDeleteCategory}
selectedRows={selectedRows}
/> />
<DashboardPageContent> <DashboardPageContent>
<ItemsCategoryList <ItemsCategoryList
onDeleteCategory={handelDeleteCategory} onDeleteCategory={handelDeleteCategory}
onFetchData={handleFetchData} onFetchData={handleFetchData}
onEditCategory={handelEditCategory} onEditCategory={handelEditCategory}
categories onSelectedRowsChange={handleSelectedRowsChange}
/> />
<Alert <Alert

View File

@@ -1,4 +1,4 @@
import React, {useEffect, useCallback, useMemo} from 'react'; import React, {useState, useEffect, useCallback, useMemo} from 'react';
import { import {
Button, Button,
Popover, Popover,
@@ -7,7 +7,6 @@ import {
MenuDivider, MenuDivider,
Position, Position,
} from '@blueprintjs/core' } from '@blueprintjs/core'
import LoadingIndicator from 'components/LoadingIndicator';
import CustomViewConnect from 'connectors/View.connector'; import CustomViewConnect from 'connectors/View.connector';
import ItemsConnect from 'connectors/Items.connect'; import ItemsConnect from 'connectors/Items.connect';
import {compose} from 'utils'; import {compose} from 'utils';
@@ -22,7 +21,16 @@ const ItemsDataTable = ({
onEditItem, onEditItem,
onDeleteItem, onDeleteItem,
onFetchData, onFetchData,
onSelectedRowsChange,
}) => { }) => {
const [initialMount, setInitialMount] = useState(false);
useEffect(() => {
if (!itemsTableLoading) {
setInitialMount(true);
}
}, [itemsTableLoading, setInitialMount]);
const handleEditItem = (item) => () => { onEditItem(item); }; const handleEditItem = (item) => () => { onEditItem(item); };
const handleDeleteItem = (item) => () => { onDeleteItem(item); }; const handleDeleteItem = (item) => () => { onDeleteItem(item); };
@@ -75,7 +83,6 @@ const ItemsDataTable = ({
// accessor: 'inventory_account.name', // accessor: 'inventory_account.name',
// className: "inventory-account", // className: "inventory-account",
// }, // },
{ {
id: 'actions', id: 'actions',
Cell: ({ cell }) => ( Cell: ({ cell }) => (
@@ -98,16 +105,21 @@ const ItemsDataTable = ({
const handleFetchData = useCallback((...args) => { const handleFetchData = useCallback((...args) => {
onFetchData && onFetchData(...args) onFetchData && onFetchData(...args)
}, [onFetchData]) }, [onFetchData]);
const handleSelectedRowsChange = useCallback((selectedRows) => {
onSelectedRowsChange && onSelectedRowsChange(selectedRows.map(s => s.original));
}, [onSelectedRowsChange]);
return ( return (
<LoadingIndicator loading={itemsTableLoading} spinnerSize={30}>
<DataTable <DataTable
columns={columns} columns={columns}
data={currentPageItems} data={currentPageItems}
selectionColumn={selectionColumn} selectionColumn={selectionColumn}
onFetchData={handleFetchData} /> onFetchData={handleFetchData}
</LoadingIndicator> loading={itemsTableLoading && !initialMount}
noInitialFetch={true}
onSelectedRowsChange={handleSelectedRowsChange} />
); );
}; };

View File

@@ -31,6 +31,7 @@ function ItemsList({
addItemsTableQueries, addItemsTableQueries,
}) { }) {
const [deleteItem, setDeleteItem] = useState(false); const [deleteItem, setDeleteItem] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
useEffect(() => { useEffect(() => {
changePageTitle('Items List'); changePageTitle('Items List');
@@ -49,16 +50,19 @@ function ItemsList({
]) ])
}); });
// Handle click delete item.
const handleDeleteItem = useCallback((item) => { const handleDeleteItem = useCallback((item) => {
setDeleteItem(item); setDeleteItem(item);
}, [setDeleteItem]); }, [setDeleteItem]);
const handleEditItem = () => {}; const handleEditItem = () => {};
// Handle cancel delete the item.
const handleCancelDeleteItem = useCallback(() => { const handleCancelDeleteItem = useCallback(() => {
setDeleteItem(false); setDeleteItem(false);
}, [setDeleteItem]); }, [setDeleteItem]);
// handle confirm delete item.
const handleConfirmDeleteItem = useCallback(() => { const handleConfirmDeleteItem = useCallback(() => {
requestDeleteItem(deleteItem.id).then(() => { requestDeleteItem(deleteItem.id).then(() => {
AppToaster.show({ message: 'the_item_has_been_deleted' }); AppToaster.show({ message: 'the_item_has_been_deleted' });
@@ -69,25 +73,33 @@ function ItemsList({
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => { const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
addItemsTableQueries({ addItemsTableQueries({
...(sortBy.length > 0) ? { ...(sortBy.length > 0) ? {
column_sort_by: sortBy[0].id, column_sort_order: sortBy[0].id,
sort_by: sortBy[0].desc ? 'desc' : 'asc', sort_order: sortBy[0].desc ? 'desc' : 'asc',
} : {}, } : {},
}); });
fetchItems.execute(); fetchItems.execute();
}, [fetchItems, addItemsTableQueries]); }, [fetchItems, addItemsTableQueries]);
// Handle filter change to re-fetch the items.
const handleFilterChanged = useCallback(() => { const handleFilterChanged = useCallback(() => {
fetchItems.execute(); fetchItems.execute();
}, [fetchItems]); }, [fetchItems]);
// Handle custom view change to re-fetch the items.
const handleCustomViewChanged = useCallback(() => { const handleCustomViewChanged = useCallback(() => {
fetchItems.execute(); fetchItems.execute();
}, [fetchItems]); }, [fetchItems]);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback((accounts) => {
setSelectedRows(accounts);
}, [setSelectedRows]);
return ( return (
<DashboardInsider isLoading={fetchHook.pending} name={'items-list'}> <DashboardInsider isLoading={fetchHook.pending} name={'items-list'}>
<ItemsActionsBar <ItemsActionsBar
onFilterChanged={handleFilterChanged} onFilterChanged={handleFilterChanged}
selectedRows={selectedRows}
views={views} /> views={views} />
<DashboardPageContent> <DashboardPageContent>
@@ -98,12 +110,14 @@ function ItemsList({
'/dashboard/items/:custom_view_id/custom_view', '/dashboard/items/:custom_view_id/custom_view',
'/dashboard/items' '/dashboard/items'
]}> ]}>
<ItemsViewsTabs onViewChanged={handleCustomViewChanged} /> <ItemsViewsTabs
onViewChanged={handleCustomViewChanged} />
<ItemsDataTable <ItemsDataTable
onDeleteItem={handleDeleteItem} onDeleteItem={handleDeleteItem}
onEditItem={handleEditItem} onEditItem={handleEditItem}
onFetchData={handleFetchData} /> onFetchData={handleFetchData}
onSelectedRowsChange={handleSelectedRowsChange} />
<Alert <Alert
cancelButtonText="Cancel" cancelButtonText="Cancel"

View File

@@ -1,25 +1,24 @@
import React from 'react'; import React, {useCallback} from 'react';
import { import {
Tabs, Tabs,
Tab, Tab,
Button, Button,
Intent, Intent,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import PreferencesSubContent from 'components/Preferences/PreferencesSubContent'; import PreferencesSubContent from 'components/Preferences/PreferencesSubContent';
import connector from 'connectors/UsersPreferences.connector'; import connector from 'connectors/UsersPreferences.connector';
function UsersPreferences({ function UsersPreferences({
openDialog, openDialog,
}) { }) {
const history = useHistory();
const onChangeTabs = (currentTabId) => { const onChangeTabs = (currentTabId) => {
}; };
const onClickNewUser = () => { const onClickNewUser = useCallback(() => {
openDialog('user-form'); openDialog('user-form');
}; }, [openDialog]);
return ( return (
<div class="preferences__inside-content preferences__inside-content--users-roles"> <div class="preferences__inside-content preferences__inside-content--users-roles">
<div class="preferences__tabs"> <div class="preferences__tabs">

View File

@@ -5,6 +5,7 @@ import { BrowserRouter } from 'react-router-dom';
import App from 'components/App'; import App from 'components/App';
import * as serviceWorker from 'serviceWorker'; import * as serviceWorker from 'serviceWorker';
import createStore from 'store/createStore'; import createStore from 'store/createStore';
import AppProgress from 'components/NProgress/AppProgress';
ReactDOM.render( ReactDOM.render(
<Provider store={createStore}> <Provider store={createStore}>
@@ -15,6 +16,13 @@ ReactDOM.render(
document.getElementById('root') document.getElementById('root')
); );
ReactDOM.render(
<Provider store={createStore}>
<AppProgress />
</Provider>,
document.getElementById('nprogress')
);
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls. // unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA // Learn more about service workers: https://bit.ly/CRA-PWA

View File

@@ -6,17 +6,14 @@ export default [
// Homepage // Homepage
{ {
path: `${BASE_URL}/homepage`, path: `${BASE_URL}/homepage`,
name: 'dashboard.homepage',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Dashboard/Homepage') loader: () => import('containers/Dashboard/Homepage')
}), }),
exact: true
}, },
// Accounts. // Accounts.
{ {
path: `${BASE_URL}/accounts`, path: `${BASE_URL}/accounts`,
name: 'dashboard.accounts',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Dashboard/Accounts/AccountsChart') loader: () => import('containers/Dashboard/Accounts/AccountsChart')
}) })
@@ -25,14 +22,12 @@ export default [
// Custom views. // Custom views.
{ {
path: `${BASE_URL}/custom_views/:resource_slug/new`, path: `${BASE_URL}/custom_views/:resource_slug/new`,
name: 'dashboard.custom_view.new',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Dashboard/Views/ViewFormPage') loader: () => import('containers/Dashboard/Views/ViewFormPage')
}) })
}, },
{ {
path: `${BASE_URL}/custom_views/:view_id/edit`, path: `${BASE_URL}/custom_views/:view_id/edit`,
name: 'dashboard.custom_view.edit',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Dashboard/Views/ViewFormPage') loader: () => import('containers/Dashboard/Views/ViewFormPage')
}) })
@@ -41,15 +36,12 @@ export default [
// Expenses. // Expenses.
{ {
path: `${BASE_URL}/expenses/new`, path: `${BASE_URL}/expenses/new`,
name: 'dashboard.expense.new',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Dashboard/Expenses/ExpenseForm') loader: () => import('containers/Dashboard/Expenses/ExpenseForm')
}), }),
text: 'New Expense'
}, },
{ {
path: `${BASE_URL}/expenses`, path: `${BASE_URL}/expenses`,
name: 'dashboard.expeneses.list',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Dashboard/Expenses/ExpensesList') loader: () => import('containers/Dashboard/Expenses/ExpensesList')
}) })
@@ -58,30 +50,24 @@ export default [
// Accounting // Accounting
{ {
path: `${BASE_URL}/accounting/make-journal-entry`, path: `${BASE_URL}/accounting/make-journal-entry`,
name: 'dashboard.accounting.make.journal',
component: LazyLoader({ component: LazyLoader({
loader: () => loader: () =>
import('containers/Dashboard/Accounting/MakeJournalEntriesPage') import('containers/Dashboard/Accounting/MakeJournalEntriesPage')
}), }),
text: 'Make Journal Entry'
}, },
{ {
path: `${BASE_URL}/accounting/manual-journals/:id/edit`, path: `${BASE_URL}/accounting/manual-journals/:id/edit`,
name: 'dashboard.manual.journal.edit',
component: LazyLoader({ component: LazyLoader({
loader: () => loader: () =>
import('containers/Dashboard/Accounting/MakeJournalEntriesPage') import('containers/Dashboard/Accounting/MakeJournalEntriesPage')
}), }),
}, },
{ {
path: `${BASE_URL}/accounting/manual-journals`, path: `${BASE_URL}/accounting/manual-journals`,
component: LazyLoader({ component: LazyLoader({
loader: () => loader: () =>
import('containers/Dashboard/Accounting/ManualJournalsTable') import('containers/Dashboard/Accounting/ManualJournalsTable')
}), }),
text: 'Manual Journals'
}, },
{ {
path: `${BASE_URL}/items/categories`, path: `${BASE_URL}/items/categories`,
@@ -89,8 +75,6 @@ export default [
loader: () => import('containers/Dashboard/Items/ItemsCategoryList') loader: () => import('containers/Dashboard/Items/ItemsCategoryList')
}) })
}, },
{ {
path: `${BASE_URL}/items/new`, path: `${BASE_URL}/items/new`,
component: LazyLoader({ component: LazyLoader({
@@ -109,7 +93,6 @@ export default [
// Financial Reports. // Financial Reports.
{ {
path: `${BASE_URL}/accounting/general-ledger`, path: `${BASE_URL}/accounting/general-ledger`,
name: 'dashboard.accounting.general.ledger',
component: LazyLoader({ component: LazyLoader({
loader: () => loader: () =>
import( import(
@@ -119,7 +102,6 @@ export default [
}, },
{ {
path: `${BASE_URL}/accounting/balance-sheet`, path: `${BASE_URL}/accounting/balance-sheet`,
name: 'dashboard.accounting.balance.sheet',
component: LazyLoader({ component: LazyLoader({
loader: () => loader: () =>
import( import(
@@ -129,7 +111,6 @@ export default [
}, },
{ {
path: `${BASE_URL}/accounting/trial-balance-sheet`, path: `${BASE_URL}/accounting/trial-balance-sheet`,
name: 'dashboard.accounting.trial.balance',
component: LazyLoader({ component: LazyLoader({
loader: () => loader: () =>
import( import(
@@ -139,7 +120,6 @@ export default [
}, },
{ {
path: `${BASE_URL}/accounting/profit-loss-sheet`, path: `${BASE_URL}/accounting/profit-loss-sheet`,
name: 'dashboard.accounting.profit.loss.sheet',
component: LazyLoader({ component: LazyLoader({
loader: () => loader: () =>
import( import(
@@ -149,10 +129,9 @@ export default [
}, },
{ {
path: `${BASE_URL}/accounting/journal-sheet`, path: `${BASE_URL}/accounting/journal-sheet`,
name: 'dashboard.accounting.journal.sheet',
component: LazyLoader({ component: LazyLoader({
loader: () => loader: () =>
import('containers/Dashboard/FinancialStatements/Journal/Journal') import('containers/Dashboard/FinancialStatements/Journal/Journal')
}) })
} },
]; ];

View File

@@ -18,10 +18,11 @@ export const fetchAccountTypes = () => {
}; };
export const fetchAccountsList = ({ query } = {}) => { export const fetchAccountsList = ({ query } = {}) => {
return dispatch => return dispatch => new Promise((resolve, reject) => {
new Promise((resolve, reject) => { dispatch({
ApiService.get('accounts', { params: query }) type: t.SET_DASHBOARD_REQUEST_LOADING,
.then(response => { });
ApiService.get('accounts', { params: query }).then(response => {
dispatch({ dispatch({
type: t.ACCOUNTS_PAGE_SET, type: t.ACCOUNTS_PAGE_SET,
accounts: response.data.accounts, accounts: response.data.accounts,
@@ -31,9 +32,15 @@ export const fetchAccountsList = ({ query } = {}) => {
type: t.ACCOUNTS_ITEMS_SET, type: t.ACCOUNTS_ITEMS_SET,
accounts: response.data.accounts accounts: response.data.accounts
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch(error => { .catch((error) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(error); reject(error);
}); });
}); });
@@ -54,6 +61,9 @@ export const fetchAccountsTable = ({ query } = {}) => {
type: t.ACCOUNTS_TABLE_LOADING, type: t.ACCOUNTS_TABLE_LOADING,
loading: true, loading: true,
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.get('accounts', { params: { ...pageQuery, ...query } }) ApiService.get('accounts', { params: { ...pageQuery, ...query } })
.then((response) => { .then((response) => {
dispatch({ dispatch({
@@ -69,9 +79,15 @@ export const fetchAccountsTable = ({ query } = {}) => {
type: t.ACCOUNTS_TABLE_LOADING, type: t.ACCOUNTS_TABLE_LOADING,
loading: false, loading: false,
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(error); reject(error);
}); });
}); });
@@ -96,12 +112,17 @@ export const fetchAccountsDataTable = ({ query }) => {
export const submitAccount = ({ form }) => { export const submitAccount = ({ form }) => {
return dispatch => return dispatch =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post('accounts', form) ApiService.post('accounts', form)
.then(response => { .then(response => {
dispatch({ dispatch({
type: t.ACCOUNT_ERRORS_CLEAR, type: t.ACCOUNT_ERRORS_CLEAR,
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch(error => { .catch(error => {
@@ -118,17 +139,25 @@ export const submitAccount = ({ form }) => {
payload: { errors }, payload: { errors },
}); });
} }
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(errors); reject(errors);
}); });
}); });
}; };
export const editAccount = ({ id, form }) => { export const editAccount = ({ id, form }) => {
return dispatch => return dispatch => new Promise((resolve, reject) => {
new Promise((resolve, reject) => { dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.post(`accounts/${id}`, form) ApiService.post(`accounts/${id}`, form)
.then(response => { .then(response => {
dispatch({ type: t.CLEAR_ACCOUNT_FORM_ERRORS }); dispatch({ type: t.CLEAR_ACCOUNT_FORM_ERRORS });
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch(error => { .catch(error => {
@@ -140,6 +169,9 @@ export const editAccount = ({ id, form }) => {
if (errors) { if (errors) {
dispatch({ type: t.ACCOUNT_FORM_ERRORS, errors }); dispatch({ type: t.ACCOUNT_FORM_ERRORS, errors });
} }
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(errors); reject(errors);
}); });
}); });

View File

@@ -21,16 +21,9 @@ export default function login({ form }) {
}).catch((error) => { }).catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
const { errors } = data; const { errors = [] } = data;
dispatch({type: t.LOGIN_CLEAR_ERRORS}); reject(errors);
if (errors){
dispatch({
type: t.LOGIN_FAILURE, errors,
});
}
reject(error);
}); });
}); });
} }

View File

@@ -8,14 +8,19 @@ export const submitItemCategory = ({ form }) => {
}; };
export const fetchItemCategories = () => { export const fetchItemCategories = () => {
return (dispatch, getState) => return (dispatch, getState) => new Promise((resolve, reject) => {
new Promise((resolve, reject) => { dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.get('item_categories') ApiService.get('item_categories')
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: t.ITEMS_CATEGORY_LIST_SET, type: t.ITEMS_CATEGORY_LIST_SET,
categories: response.data.categories, categories: response.data.categories,
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {

View File

@@ -11,12 +11,15 @@ export const editItem = ({ id, form }) => {
export const fetchItems = ({ query }) => { export const fetchItems = ({ query }) => {
return (dispatch, getState) => new Promise((resolve, reject) => { return (dispatch, getState) => new Promise((resolve, reject) => {
const pageQuery = getState().accounts.tableQuery; const pageQuery = getState().items.tableQuery;
dispatch({ dispatch({
type: t.ITEMS_TABLE_LOADING, type: t.ITEMS_TABLE_LOADING,
payload: { loading: true }, payload: { loading: true },
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.get(`items`, { params: { ...pageQuery, ...query } }).then(response => { ApiService.get(`items`, { params: { ...pageQuery, ...query } }).then(response => {
dispatch({ dispatch({
type: t.ITEMS_SET, type: t.ITEMS_SET,
@@ -32,8 +35,16 @@ export const fetchItems = ({ query }) => {
type: t.ITEMS_TABLE_LOADING, type: t.ITEMS_TABLE_LOADING,
payload: { loading: false }, payload: { loading: false },
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}).catch(error => { reject(error); }); }).catch((error) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
reject(error);
});
}); });
}; };

View File

@@ -47,6 +47,22 @@ export const deleteManualJournal = ({ id }) => {
}); });
}; };
export const deleteBulkManualJournals = ({ ids }) => {
return (dispatch) => new Promise((resolve, reject) => {
ApiService.delete('accounting/manual-journals', { params: { ids } })
.then((response) => {
dispatch({
type: t.MANUAL_JOURNALS_BULK_DELETE,
payload: { ids },
});
resolve(response);
}).catch((error) => {
reject(error.response.data.errors || []);
});
});
};
export const publishManualJournal = ({ id }) => { export const publishManualJournal = ({ id }) => {
return (dispatch) => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
@@ -66,6 +82,9 @@ export const fetchManualJournalsTable = ({ query } = {}) => {
return (dispatch, getState) => return (dispatch, getState) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const pageQuery = getState().manualJournals.tableQuery; const pageQuery = getState().manualJournals.tableQuery;
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
dispatch({ dispatch({
type: t.MANUAL_JOURNALS_TABLE_LOADING, type: t.MANUAL_JOURNALS_TABLE_LOADING,
loading: true, loading: true,
@@ -74,20 +93,22 @@ export const fetchManualJournalsTable = ({ query } = {}) => {
params: { ...pageQuery, ...query }, params: { ...pageQuery, ...query },
}) })
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: t.MANUAL_JOURNALS_PAGE_SET, type: t.MANUAL_JOURNALS_PAGE_SET,
manual_journals: response.data.manualJournals, manual_journals: response.data.manualJournals.results,
customViewId: response.data.customViewId, customViewId: response.data.customViewId || -1,
}); });
dispatch({ dispatch({
type: t.MANUAL_JOURNALS_ITEMS_SET, type: t.MANUAL_JOURNALS_ITEMS_SET,
manual_journals: response.data.manualJournals, manual_journals: response.data.manualJournals.results,
}); });
dispatch({ dispatch({
type: t.MANUAL_JOURNALS_TABLE_LOADING, type: t.MANUAL_JOURNALS_TABLE_LOADING,
loading: false, loading: false,
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {

View File

@@ -1,5 +1,6 @@
import t from 'store/types'; import t from 'store/types';
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { createTableQueryReducers } from 'store/queryReducers';
import { omit } from 'lodash'; import { omit } from 'lodash';
const initialState = { const initialState = {
@@ -10,7 +11,7 @@ const initialState = {
tableQuery: {}, tableQuery: {},
}; };
export default createReducer(initialState, { const reducer = createReducer(initialState, {
[t.MANUAL_JOURNAL_SET]: (state, action) => { [t.MANUAL_JOURNAL_SET]: (state, action) => {
const { id, manualJournal } = action.payload; const { id, manualJournal } = action.payload;
@@ -59,9 +60,22 @@ export default createReducer(initialState, {
[t.MANUAL_JOURNAL_REMOVE]: (state, action) => { [t.MANUAL_JOURNAL_REMOVE]: (state, action) => {
const { id } = action.payload; const { id } = action.payload;
state.items = omit(state.items, [id]); state.items = omit(state.items, [id]);
},
[t.MANUAL_JOURNALS_BULK_DELETE]: (state, action) => {
const { ids } = action.payload;
const items = { ...state.items };
ids.forEach((id) => {
if (typeof items[id] !== 'undefined') {
delete items[id];
} }
}); });
state.items = items;
},
});
export default createTableQueryReducers('manual_journals', reducer);
export const getManualJournal = (state, id) => { export const getManualJournal = (state, id) => {
return state.manualJournals.items[id]; return state.manualJournals.items[id];

View File

@@ -10,4 +10,5 @@ export default {
MANUAL_JOURNAL_REMOVE: 'MANUAL_JOURNAL_REMOVE', MANUAL_JOURNAL_REMOVE: 'MANUAL_JOURNAL_REMOVE',
MANUAL_JOURNAL_PUBLISH: 'MANUAL_JOURNAL_PUBLISH', MANUAL_JOURNAL_PUBLISH: 'MANUAL_JOURNAL_PUBLISH',
MANUAL_JOURNALS_BULK_DELETE: 'MANUAL_JOURNALS_BULK_DELETE',
}; };

View File

@@ -3,12 +3,18 @@ import t from 'store/types';
export const fetchResourceColumns = ({ resourceSlug }) => { export const fetchResourceColumns = ({ resourceSlug }) => {
return (dispatch) => new Promise((resolve, reject) => { return (dispatch) => new Promise((resolve, reject) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.get(`resources/${resourceSlug}/columns`).then((response) => { ApiService.get(`resources/${resourceSlug}/columns`).then((response) => {
dispatch({ dispatch({
type: t.RESOURCE_COLUMNS_SET, type: t.RESOURCE_COLUMNS_SET,
columns: response.data.resource_columns, columns: response.data.resource_columns,
resource_slug: resourceSlug, resource_slug: resourceSlug,
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}).catch((error) => { reject(error); }); }).catch((error) => { reject(error); });
}); });
@@ -16,12 +22,18 @@ export const fetchResourceColumns = ({ resourceSlug }) => {
export const fetchResourceFields = ({ resourceSlug }) => { export const fetchResourceFields = ({ resourceSlug }) => {
return (dispatch) => new Promise((resolve, reject) => { return (dispatch) => new Promise((resolve, reject) => {
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
ApiService.get(`resources/${resourceSlug}/fields`).then((response) => { ApiService.get(`resources/${resourceSlug}/fields`).then((response) => {
dispatch({ dispatch({
type: t.RESOURCE_FIELDS_SET, type: t.RESOURCE_FIELDS_SET,
fields: response.data.resource_fields, fields: response.data.resource_fields,
resource_slug: resourceSlug, resource_slug: resourceSlug,
}); });
dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
});
resolve(response); resolve(response);
}).catch((error) => { reject(error); }); }).catch((error) => { reject(error); });
}); });

View File

@@ -11,7 +11,7 @@ export const pickItemsFromIds = (items, ids) => {
export const getCurrentPageResults = (items, pages, pageNumber) => { export const getCurrentPageResults = (items, pages, pageNumber) => {
const currentPage = pages[pageNumber] const currentPage = pages[pageNumber]
return typeof currentPage == 'undefined' ? return typeof currentPage == 'undefined' ?
[] : Object.values(pick(items || [], currentPage.ids)); [] : pickItemsFromIds(items, currentPage.ids);
} }
export const getCurrentTotalResultsCount = (pagination, name) => { export const getCurrentTotalResultsCount = (pagination, name) => {

View File

@@ -164,7 +164,8 @@
h1{ h1{
font-size: 26px; font-size: 26px;
font-weight: 100; font-weight: 200;
color: #4d4c4c;
margin: 0; margin: 0;
} }
h3{ h3{

View File

@@ -40,7 +40,7 @@ $sidebar-popover-submenu-bg: rgb(1, 20, 62);
margin-top: 4px; margin-top: 4px;
svg{ svg{
opacity: 0.35; opacity: 0.4;
} }
} }
} }

View File

@@ -185,7 +185,6 @@ export default {
check('entries.*.debit').optional({ nullable: true }).isNumeric().toInt(), check('entries.*.debit').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.account_id').isNumeric().toInt(), check('entries.*.account_id').isNumeric().toInt(),
check('entries.*.note').optional(), check('entries.*.note').optional(),
check('attachment').optional(),
], ],
async handler(req, res) { async handler(req, res) {
const validationErrors = validationResult(req); const validationErrors = validationResult(req);
@@ -244,28 +243,9 @@ export default {
if (journalNumber.length > 0) { if (journalNumber.length > 0) {
errorReasons.push({ type: 'JOURNAL.NUMBER.ALREADY.EXISTS', code: 300 }); errorReasons.push({ type: 'JOURNAL.NUMBER.ALREADY.EXISTS', code: 300 });
} }
const { attachment } = req.files;
const supportedMimes = ['image/png', 'image/jpeg'];
if (attachment && supportedMimes.indexOf(attachment.mimeType) === -1) {
errorReasons.push({ type: 'ATTACHMENT.MINETYPE.NOT.SUPPORTED', code: 400 });
}
if (errorReasons.length > 0) { if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons }); return res.status(400).send({ errors: errorReasons });
} }
if (attachment) {
const publicPath = 'storage/app/public/';
try {
await attachment.mv(`${publicPath}${req.organizationId}/${attachment.md5}.png`);
} catch (error) {
return res.status(400).send({
errors: [{ type: 'ATTACHMENT.UPLOAD.FAILED', code: 600 }],
});
}
}
// Save manual journal transaction. // Save manual journal transaction.
const manualJournal = await ManualJournal.query().insert({ const manualJournal = await ManualJournal.query().insert({
reference: form.reference, reference: form.reference,
@@ -276,7 +256,6 @@ export default {
description: form.description, description: form.description,
status: form.status, status: form.status,
user_id: user.id, user_id: user.id,
attachment_file: (attachment) ? `${attachment.md5}.png` : null,
}); });
const journalPoster = new JournalPoster(); const journalPoster = new JournalPoster();
@@ -682,13 +661,12 @@ export default {
}, },
}, },
/** /**
* Deletes bulk manual journals. * Deletes bulk manual journals.
*/ */
deleteBulkManualJournals: { deleteBulkManualJournals: {
validation: [ validation: [
query('ids').isArray({ min: 2 }), query('ids').isArray({ min: 1 }),
query('ids.*').isNumeric().toInt(), query('ids.*').isNumeric().toInt(),
], ],
async handler(req, res) { async handler(req, res) {

View File

@@ -51,7 +51,7 @@ export default {
check('name').exists(), check('name').exists(),
check('type').exists().trim().escape() check('type').exists().trim().escape()
.isIn(['service', 'non-inventory', 'inventory']), .isIn(['service', 'non-inventory', 'inventory']),
check('sku').optional().trim().escape(), check('sku').optional({ nullable: true }).trim().escape(),
check('cost_price').exists().isNumeric().toFloat(), check('cost_price').exists().isNumeric().toFloat(),
check('sell_price').exists().isNumeric().toFloat(), check('sell_price').exists().isNumeric().toFloat(),
check('cost_account_id').exists().isInt().toInt(), check('cost_account_id').exists().isInt().toInt(),
@@ -61,14 +61,13 @@ export default {
.exists() .exists()
.isInt() .isInt()
.toInt(), .toInt(),
check('category_id').optional().isInt().toInt(), check('category_id').optional({ nullable: true }).isInt().toInt(),
check('custom_fields').optional().isArray({ min: 1 }), check('custom_fields').optional().isArray({ min: 1 }),
check('custom_fields.*.key').exists().isNumeric().toInt(), check('custom_fields.*.key').exists().isNumeric().toInt(),
check('custom_fields.*.value').exists(), check('custom_fields.*.value').exists(),
check('note').optional(), check('note').optional(),
check('attachment').optional(),
], ],
async handler(req, res) { async handler(req, res) {
const validationErrors = validationResult(req); const validationErrors = validationResult(req);
@@ -119,13 +118,6 @@ export default {
errorReasons.push({ type: 'FIELD_KEY_NOT_FOUND', code: 150, fields: notFoundFields }); errorReasons.push({ type: 'FIELD_KEY_NOT_FOUND', code: 150, fields: notFoundFields });
} }
} }
const { attachment } = req.files;
const attachmentsMimes = ['image/png', 'image/jpeg'];
// Validate the attachment.
if (attachment && attachmentsMimes.indexOf(attachment.mimetype) === -1) {
errorReasons.push({ type: 'ATTACHMENT.MINETYPE.NOT.SUPPORTED', code: 160 });
}
const [ const [
costAccount, costAccount,
sellAccount, sellAccount,
@@ -150,10 +142,6 @@ export default {
if (errorReasons.length > 0) { if (errorReasons.length > 0) {
return res.boom.badRequest(null, { errors: errorReasons }); return res.boom.badRequest(null, { errors: errorReasons });
} }
if (attachment) {
const publicPath = 'storage/app/public/';
await attachment.mv(`${publicPath}${req.organizationId}/${attachment.md5}.png`);
}
const item = await Item.query().insertAndFetch({ const item = await Item.query().insertAndFetch({
name: form.name, name: form.name,
@@ -164,7 +152,6 @@ export default {
cost_account_id: form.cost_account_id, cost_account_id: form.cost_account_id,
currency_code: form.currency_code, currency_code: form.currency_code,
note: form.note, note: form.note,
attachment_file: (attachment) ? `${attachment.md5}.png` : null,
}); });
return res.status(200).send({ id: item.id }); return res.status(200).send({ id: item.id });
}, },