WIP Version 0.0.1

This commit is contained in:
Ahmed Bouhuolia
2020-05-08 04:36:04 +02:00
parent bd7eb0eb76
commit 71cc561bb2
151 changed files with 1742 additions and 1081 deletions

View File

@@ -72,6 +72,7 @@
"react-hook-form": "^4.9.4", "react-hook-form": "^4.9.4",
"react-intl": "^3.12.0", "react-intl": "^3.12.0",
"react-loadable": "^5.5.0", "react-loadable": "^5.5.0",
"react-query": "^1.3.3",
"react-redux": "^7.1.3", "react-redux": "^7.1.3",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scrollbars-custom": "^4.0.21", "react-scrollbars-custom": "^4.0.21",
@@ -121,6 +122,7 @@
"devDependencies": { "devDependencies": {
"@babel/preset-flow": "^7.9.0", "@babel/preset-flow": "^7.9.0",
"http-proxy-middleware": "^1.0.0", "http-proxy-middleware": "^1.0.0",
"react-query-devtools": "^1.1.5",
"redux-devtools": "^3.5.0" "redux-devtools": "^3.5.0"
}, },
"jest": { "jest": {

View File

@@ -8,7 +8,9 @@ import PrivateRoute from 'components/PrivateRoute';
import Authentication from 'components/Authentication'; import Authentication from 'components/Authentication';
import Dashboard from 'components/Dashboard/Dashboard'; import Dashboard from 'components/Dashboard/Dashboard';
import { isAuthenticated } from 'store/authentication/authentication.reducer' import { isAuthenticated } from 'store/authentication/authentication.reducer'
import Progress from 'components/NProgress/Progress'; import { ReactQueryConfigProvider } from 'react-query';
import { ReactQueryDevtools } from "react-query-devtools";
import messages from 'lang/en'; import messages from 'lang/en';
import 'style/App.scss'; import 'style/App.scss';
@@ -22,13 +24,22 @@ function App({
console.log(`new location via ${action}`, location); console.log(`new location via ${action}`, location);
}); });
const queryConfig = {
refetchAllOnWindowFocus: false,
cacheTime: 10000,
staleTime: 10000,
};
return ( return (
<IntlProvider locale={locale} messages={messages}> <IntlProvider locale={locale} messages={messages}>
<div className="App"> <div className="App">
<Router history={history}> <ReactQueryConfigProvider config={queryConfig}>
<Authentication isAuthenticated={isAuthorized} /> <Router history={history}>
<PrivateRoute isAuthenticated={isAuthorized} component={Dashboard} /> <Authentication isAuthenticated={isAuthorized} />
</Router> <PrivateRoute isAuthenticated={isAuthorized} component={Dashboard} />
</Router>
<ReactQueryDevtools />
</ReactQueryConfigProvider>
</div> </div>
</IntlProvider> </IntlProvider>
); );

View File

@@ -3,7 +3,7 @@ import { Redirect, Route, Switch, Link } from 'react-router-dom';
import BodyClassName from 'react-body-classname'; import BodyClassName from 'react-body-classname';
import authenticationRoutes from 'routes/authentication'; import authenticationRoutes from 'routes/authentication';
export default function({ isAuthenticated =false, ...rest }) { export default function AuthenticationWrapper({ isAuthenticated =false, ...rest }) {
const to = {pathname: '/dashboard/homepage'}; const to = {pathname: '/dashboard/homepage'};
return ( return (

View File

@@ -5,7 +5,7 @@ import DashboardContent from 'components/Dashboard/DashboardContent';
import DialogsContainer from 'components/DialogsContainer'; import DialogsContainer from 'components/DialogsContainer';
import PreferencesContent from 'components/Preferences/PreferencesContent'; import PreferencesContent from 'components/Preferences/PreferencesContent';
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar'; import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
import Search from 'containers/Dashboard/GeneralSearch/Search'; import Search from 'containers/GeneralSearch/Search';
export default function Dashboard() { export default function Dashboard() {
return ( return (

View File

@@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import AccountFormDialog from 'containers/Dashboard/Dialogs/AccountFormDialog'; import AccountFormDialog from 'containers/Dialogs/AccountFormDialog';
import UserFormDialog from 'containers/Dashboard/Dialogs/UserFormDialog'; import UserFormDialog from 'containers/Dialogs/UserFormDialog';
import ItemCategoryDialog from 'containers/Dashboard/Dialogs/ItemCategoryDialog'; import ItemCategoryDialog from 'containers/Dialogs/ItemCategoryDialog';
import CurrencyDialog from 'containers/Dashboard/Dialogs/CurrencyDialog'; import CurrencyDialog from 'containers/Dialogs/CurrencyDialog';
import InviteUserDialog from 'containers/Dashboard/Dialogs/InviteUserDialog'; import InviteUserDialog from 'containers/Dialogs/InviteUserDialog';
export default function DialogsContainer() { export default function DialogsContainer() {
return ( return (

View File

@@ -1,13 +0,0 @@
import React from 'react';
import { Button, AnchorButton, Classes, Icon } from '@blueprintjs/core';
const ItemsActionsBar = () => {
return (
<div className='dashob'>
ItemsActionsBar 22
</div>
);
};
export default ItemsActionsBar;

View File

@@ -1,94 +0,0 @@
import React from 'react';
import {
FormGroup,
MenuItem,
Intent,
InputGroup,
Position,
Button,
} from '@blueprintjs/core';
import { DateInput, TimePrecision } from "@blueprintjs/datetime";
import {
GridComponent,
ColumnsDirective,
ColumnDirective,
Inject,
Sort,
} from '@syncfusion/ej2-react-grids';
import {momentFormatter} from 'utils';
export default function MakeJournalEntry({
accounts,
currencies,
}) {
const handleDateChange = () => {
};
const handleClose = () => {
};
const columns = [
{
headerText: 'Account',
},
{
headerText: 'Description',
},
{
headerText: 'Account',
},
{
headerText: 'Debit',
},
{
headerText: 'Credit',
}
];
return (
<div class="make-journal-entry">
<div class="make-journal-entry__details">
<FormGroup
label={'Date'}
inline={true}>
<DateInput
{...momentFormatter('MM/DD/YYYY')}
defaultValue={new Date()}
onChange={handleDateChange}
popoverProps={{ position: Position.BOTTOM }}
/>
</FormGroup>
<GridComponent>
<ColumnsDirective>
{columns.map((column) => {
return (<ColumnDirective
field={column.field}
headerText={column.headerText}
template={column.template}
allowSorting={true}
customAttributes={column.customAttributes}
/>);
})}
</ColumnsDirective>
</GridComponent>
<div class="form__floating-footer">
<Button onClick={handleClose}>Close</Button>
<Button intent={Intent.PRIMARY} type="submit">
{ 'Save and Publish' }
</Button>
<Button intent={Intent.PRIMARY} type="submit">
{ 'Save as Draft' }
</Button>
</div>
</div>
</div>
)
}

View File

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

View File

@@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import DashboardTopbarUser from 'components/Dashboard/TopbarUser'; import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
import UsersActions from 'containers/Dashboard/Preferences/UsersActions'; import UsersActions from 'containers/Preferences/UsersActions';
import CurrenciesActions from 'containers/Dashboard/Preferences/CurrenciesActions'; import CurrenciesActions from 'containers/Preferences/CurrenciesActions';
export default function PreferencesTopbar() { export default function PreferencesTopbar() {

View File

@@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import If from './if';
const Choose = props => {
let when = null;
let otherwise = null;
React.Children.forEach(props.children, children => {
if (children.props.condition === undefined) {
otherwise = children;
} else if (!when && children.props.condition === true) {
when = children;
}
});
return when || otherwise;
};
Choose.propTypes = {
children: PropTypes.node
};
Choose.When = If;
Choose.Otherwise = ({render, children}) => render ? render() : children;
Choose.Otherwise.propTypes = {
children: PropTypes.node,
render: PropTypes.func
};
export default Choose;

View File

@@ -0,0 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
const For = ({render, of}) => of.map((item, index) => render(item, index));
For.propTypes = {
of: PropTypes.array.isRequired,
render: PropTypes.func.isRequired
};
export default For;

View File

@@ -0,0 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
const If = props => props.condition
? (props.render ? props.render() : props.children) : null;
If.propTypes = {
condition: PropTypes.bool.isRequired,
children: PropTypes.node,
render: PropTypes.func
};
export default If;

View File

@@ -0,0 +1,9 @@
import If from './Utils/If';
// import Choose from './Utils/Choose';
// import For from './Utils/For';
export {
If,
// Choose,
// For,
};

View File

@@ -1,13 +0,0 @@
import {connect} from 'react-redux';
import {getDialogPayload} from 'store/dashboard/dashboard.reducer';
export const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'account-form');
return {
name: 'account-form',
payload: {action: 'new', id: null, ...dialogPayload},
};
};
export default connect(mapStateToProps);

View File

@@ -1,14 +0,0 @@
import { connect } from 'react-redux';
import {
fetchResourceViews,
} from 'store/customViews/customViews.actions';
const mapStateToProps = (state) => ({
});
const mapActionsToProps = (dispatch) => ({
fetchResourceViews: (resourceSlug) => dispatch(fetchResourceViews({ resourceSlug })),
});
export default connect(mapStateToProps, mapActionsToProps);

View File

@@ -2,10 +2,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import t from 'store/types'; import t from 'store/types';
const mapStateToProps = (state) => ({
});
const mapActionsToProps = (dispatch) => ({ const mapActionsToProps = (dispatch) => ({
changePageTitle: (pageTitle) => dispatch({ changePageTitle: (pageTitle) => dispatch({
type: t.CHANGE_DASHBOARD_PAGE_TITLE, pageTitle type: t.CHANGE_DASHBOARD_PAGE_TITLE, pageTitle
@@ -28,4 +24,4 @@ const mapActionsToProps = (dispatch) => ({
}), }),
}); });
export default connect(mapStateToProps, mapActionsToProps); export default connect(null, mapActionsToProps);

View File

@@ -1,12 +0,0 @@
import { connect } from 'react-redux';
import { submitRegister } from 'store/registers/register.action';
export const mapStateToProps = (state, props) => {
return {};
};
export const mapDispatchToProps = (dispatch) => ({
requestSubmitRegister: (form) => dispatch(submitRegister({ form })),
});
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -8,19 +8,21 @@ import {
getResourceFields, getResourceFields,
getResourceColumn, getResourceColumn,
getResourceField, getResourceField,
getResourceMetadata,
} from 'store/resources/resources.reducer'; } from 'store/resources/resources.reducer';
export const mapStateToProps = (state, props) => ({ export const mapStateToProps = (state, props) => ({
getResourceColumns: (resourceSlug) => getResourceColumns(state, resourceSlug), // getResourceColumns: (resourceSlug) => getResourceColumns(state, resourceSlug),
getResourceFields: (resourceSlug) => getResourceFields(state, resourceSlug), // getResourceFields: (resourceSlug) => getResourceFields(state, resourceSlug),
// getResourceMetadata: (resourceSlug) => getResourceMetadata(state, resourceSlug),
getResourceColumn: (columnId) => getResourceColumn(state, columnId), // getResourceColumn: (columnId) => getResourceColumn(state, columnId),
getResourceField: (fieldId) => getResourceField(state, fieldId), // getResourceField: (fieldId) => getResourceField(state, fieldId),
}); });
export const mapDispatchToProps = (dispatch) => ({ export const mapDispatchToProps = (dispatch) => ({
fetchResourceFields: (resourceSlug) => dispatch(fetchResourceFields({ resourceSlug })), requestFetchResourceFields: (resourceSlug) => dispatch(fetchResourceFields({ resourceSlug })),
fetchResourceColumns: (resourceSlug) => dispatch(fetchResourceColumns({ resourceSlug })), requestFetchResourceColumns: (resourceSlug) => dispatch(fetchResourceColumns({ resourceSlug })),
}); });
export default connect(mapStateToProps, mapDispatchToProps); export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -1,25 +0,0 @@
import {connect} from 'react-redux';
import {
fetchView,
submitView,
deleteView,
editView,
} from 'store/customViews/customViews.actions';
import {
getViewMeta,
getViewItem,
} from 'store/customViews/customViews.selectors';
export const mapStateToProps = (state) => ({
getViewMeta: (viewId) => getViewMeta(state, viewId),
getViewItem: (viewId) => getViewItem(state, viewId),
});
export const mapDispatchToProps = (dispatch) => ({
fetchView: (id) => dispatch(fetchView({ id })),
submitView: (form) => dispatch(submitView({ form })),
editView: (id, form) => dispatch(editView({ id, form })),
deleteView: (id) => dispatch(deleteView({ id })),
});
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -1,29 +0,0 @@
import {connect} from 'react-redux';
import {
fetchResourceColumns,
fetchResourceFields,
} from 'store/resources/resources.actions';
import {
getResourceColumns,
getResourceFields,
} from 'store/resources/resources.reducer';
import {
fetchView,
submitView,
editView,
} from 'store/customViews/customViews.actions';
import t from 'store/types';
export const mapStateToProps = (state, props) => {
return {
};
};
export const mapDispatchToProps = (dispatch) => ({
fetchView: (id) => dispatch(fetchView({ id })),
submitView: (form) => dispatch(submitView({ form })),
editView: (id, form) => dispatch(editView({ id, form })),
});
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -1,39 +1,41 @@
import React, {useMemo, useState, useEffect, useRef, useCallback} from 'react'; import React, {useMemo, useState, useEffect, useRef, useCallback} from 'react';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { import {useFormik} from "formik";
ProgressBar, import moment from 'moment';
Classes, import { Intent } from '@blueprintjs/core';
Intent,
} from '@blueprintjs/core';
import MakeJournalEntriesHeader from './MakeJournalEntriesHeader'; import MakeJournalEntriesHeader from './MakeJournalEntriesHeader';
import MakeJournalEntriesFooter from './MakeJournalEntriesFooter'; import MakeJournalEntriesFooter from './MakeJournalEntriesFooter';
import MakeJournalEntriesTable from './MakeJournalEntriesTable'; import MakeJournalEntriesTable from './MakeJournalEntriesTable';
import {useFormik} from "formik";
import MakeJournalEntriesConnect from 'connectors/MakeJournalEntries.connect'; import withJournalsActions from 'containers/Accounting/withJournalsActions';
import AccountsConnect from 'connectors/Accounts.connector'; import withManualJournalDetail from 'containers/Accounting/withManualJournalDetail';
import DashboardConnect from 'connectors/Dashboard.connector'; import withAccountsActions from 'containers/Accounts/withAccountsActions';
import {compose, saveFilesInAsync} from 'utils'; import withDashboardActions from 'containers/Dashboard/withDashboard';
import moment from 'moment';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import {pick} from 'lodash'; import {pick} from 'lodash';
import Dragzone from 'components/Dragzone'; import Dragzone from 'components/Dragzone';
import MediaConnect from 'connectors/Media.connect'; import MediaConnect from 'connectors/Media.connect';
import classNames from 'classnames';
import ManualJournalsConnect from 'connectors/ManualJournals.connect';
import useMedia from 'hooks/useMedia'; import useMedia from 'hooks/useMedia';
import {compose} from 'utils';
function MakeJournalEntriesForm({ function MakeJournalEntriesForm({
requestSubmitMedia, requestSubmitMedia,
requestDeleteMedia,
requestMakeJournalEntries, requestMakeJournalEntries,
requestEditManualJournal, requestEditManualJournal,
changePageTitle, changePageTitle,
changePageSubtitle, changePageSubtitle,
editJournal,
onFormSubmit,
onCancelForm,
requestDeleteMedia, manualJournalId,
manualJournalsItems manualJournal,
onFormSubmit,
onCancelForm,
}) { }) {
const { setFiles, saveMedia, deletedFiles, setDeletedFiles, deleteMedia } = useMedia({ const { setFiles, saveMedia, deletedFiles, setDeletedFiles, deleteMedia } = useMedia({
saveCallback: requestSubmitMedia, saveCallback: requestSubmitMedia,
@@ -47,13 +49,13 @@ function MakeJournalEntriesForm({
const clearSavedMediaIds = () => { savedMediaIds.current = []; } const clearSavedMediaIds = () => { savedMediaIds.current = []; }
useEffect(() => { useEffect(() => {
if (editJournal && editJournal.id) { if (manualJournal && manualJournal.id) {
changePageTitle('Edit Journal'); changePageTitle('Edit Journal');
changePageSubtitle(`No. ${editJournal.journal_number}`); changePageSubtitle(`No. ${manualJournal.journal_number}`);
} else { } else {
changePageTitle('New Journal'); changePageTitle('New Journal');
} }
}, [changePageTitle, changePageSubtitle, editJournal]); }, [changePageTitle, changePageSubtitle, manualJournal]);
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
journal_number: Yup.string().required(), journal_number: Yup.string().required(),
@@ -100,24 +102,24 @@ function MakeJournalEntriesForm({
}), [defaultEntry]); }), [defaultEntry]);
const initialValues = useMemo(() => ({ const initialValues = useMemo(() => ({
...(editJournal) ? { ...(manualJournal) ? {
...pick(editJournal, Object.keys(defaultInitialValues)), ...pick(manualJournal, Object.keys(defaultInitialValues)),
entries: editJournal.entries.map((entry) => ({ entries: manualJournal.entries.map((entry) => ({
...pick(entry, Object.keys(defaultEntry)), ...pick(entry, Object.keys(defaultEntry)),
})), })),
} : { } : {
...defaultInitialValues, ...defaultInitialValues,
} }
}), [editJournal, defaultInitialValues, defaultEntry]); }), [manualJournal, defaultInitialValues, defaultEntry]);
const initialAttachmentFiles = useMemo(() => { const initialAttachmentFiles = useMemo(() => {
return editJournal && editJournal.media return manualJournal && manualJournal.media
? editJournal.media.map((attach) => ({ ? manualJournal.media.map((attach) => ({
preview: attach.attachment_file, preview: attach.attachment_file,
uploaded: true, uploaded: true,
metadata: { ...attach }, metadata: { ...attach },
})) : []; })) : [];
}, [editJournal]); }, [manualJournal]);
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
@@ -125,7 +127,7 @@ function MakeJournalEntriesForm({
initialValues: { initialValues: {
...initialValues, ...initialValues,
}, },
onSubmit: async (values, actions) => { onSubmit: async (values, { setErrors, setSubmitting }) => {
const entries = values.entries.filter((entry) => ( const entries = values.entries.filter((entry) => (
(entry.credit || entry.debit) (entry.credit || entry.debit)
)); ));
@@ -142,7 +144,7 @@ function MakeJournalEntriesForm({
AppToaster.show({ AppToaster.show({
message: 'credit_and_debit_not_equal', message: 'credit_and_debit_not_equal',
}); });
actions.setSubmitting(false); setSubmitting(false);
return; return;
} }
const form = { ...values, status: payload.publish, entries }; const form = { ...values, status: payload.publish, entries };
@@ -150,33 +152,43 @@ function MakeJournalEntriesForm({
const saveJournal = (mediaIds) => new Promise((resolve, reject) => { const saveJournal = (mediaIds) => new Promise((resolve, reject) => {
const requestForm = { ...form, media_ids: mediaIds }; const requestForm = { ...form, media_ids: mediaIds };
if (editJournal && editJournal.id) { if (manualJournal && manualJournal.id) {
requestEditManualJournal(editJournal.id, requestForm) requestEditManualJournal(manualJournal.id, requestForm)
.then((response) => { .then((response) => {
AppToaster.show({ AppToaster.show({
message: 'manual_journal_has_been_edited', message: 'manual_journal_has_been_edited',
intent: Intent.SUCCESS,
}); });
actions.setSubmitting(false); setSubmitting(false);
saveInvokeSubmit({ action: 'update', ...payload }); saveInvokeSubmit({ action: 'update', ...payload });
clearSavedMediaIds([]); clearSavedMediaIds([]);
resolve(response); resolve(response);
}).catch((error) => { }).catch((errors) => {
actions.setSubmitting(false); if (errors.find(e => e.type === 'JOURNAL.NUMBER.ALREADY.EXISTS')) {
reject(error); setErrors({
journal_number: 'Journal number is already used.',
});
}
setSubmitting(false);
}); });
} else { } else {
requestMakeJournalEntries(requestForm) requestMakeJournalEntries(requestForm)
.then((response) => { .then((response) => {
AppToaster.show({ AppToaster.show({
message: 'manual_journal_has_been_submit', message: 'manual_journal_has_been_submit',
intent: Intent.SUCCESS,
}); });
actions.setSubmitting(false); setSubmitting(false);
saveInvokeSubmit({ action: 'new', ...payload }); saveInvokeSubmit({ action: 'new', ...payload });
clearSavedMediaIds(); clearSavedMediaIds();
resolve(response); resolve(response);
}).catch((error) => { }).catch((errors) => {
actions.setSubmitting(false); if (errors.find(e => e.type === 'JOURNAL.NUMBER.ALREADY.EXISTS')) {
reject(error); setErrors({
journal_number: 'Journal number is already used.',
});
}
setSubmitting(false);
}); });
} }
}); });
@@ -240,9 +252,11 @@ function MakeJournalEntriesForm({
} }
export default compose( export default compose(
ManualJournalsConnect, // ManualJournalsConnect,
MakeJournalEntriesConnect, // MakeJournalEntriesConnect,
AccountsConnect, withJournalsActions,
DashboardConnect, withManualJournalDetail,
withAccountsActions,
withDashboardActions,
MediaConnect, MediaConnect,
)(MakeJournalEntriesForm); )(MakeJournalEntriesForm);

View File

@@ -1,31 +1,28 @@
import React, {useMemo, useCallback} from 'react'; import React, { useCallback } from 'react';
import { useParams, useHistory } from 'react-router-dom'; import { useParams, useHistory } from 'react-router-dom';
import useAsync from 'hooks/async'; import { useQuery } from 'react-query';
import MakeJournalEntriesForm from './MakeJournalEntriesForm'; import MakeJournalEntriesForm from './MakeJournalEntriesForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardConnect from 'connectors/Dashboard.connector';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
import {compose} from 'utils'; import {compose} from 'utils';
import MakeJournalEntriesConnect from 'connectors/MakeJournalEntries.connect';
import AccountsConnect from 'connectors/Accounts.connector';
function MakeJournalEntriesPage({ function MakeJournalEntriesPage({
fetchManualJournal, requestFetchManualJournal,
getManualJournal,
requestFetchAccounts, requestFetchAccounts,
}) { }) {
const history = useHistory(); const history = useHistory();
const { id } = useParams(); const { id } = useParams();
const fetchJournal = useAsync(() => { const fetchAccounts = useQuery('accounts-list',
return Promise.all([ (key) => requestFetchAccounts());
requestFetchAccounts(),
(id) && fetchManualJournal(id),
]);
});
const editJournal = useMemo(() => const fetchJournal = useQuery(
getManualJournal(id) || null, id && ['manual-journal', id],
[getManualJournal, id]); (key, journalId) => requestFetchManualJournal(journalId));
const handleFormSubmit = useCallback((payload) => { const handleFormSubmit = useCallback((payload) => {
payload.redirect && payload.redirect &&
@@ -37,17 +34,19 @@ function MakeJournalEntriesPage({
}, [history]); }, [history]);
return ( return (
<DashboardInsider loading={fetchJournal.pending} name={'make-journal-page'}> <DashboardInsider
loading={fetchJournal.isFetching || fetchAccounts.isFetching}
name={'make-journal-page'}>
<MakeJournalEntriesForm <MakeJournalEntriesForm
onFormSubmit={handleFormSubmit} onFormSubmit={handleFormSubmit}
editJournal={editJournal} manualJournalId={id}
onCancelForm={handleCancel} /> onCancelForm={handleCancel} />
</DashboardInsider> </DashboardInsider>
); );
} }
export default compose( export default compose(
DashboardConnect, // DashboardConnect,
AccountsConnect, withAccountsActions,
MakeJournalEntriesConnect, withManualJournalsActions,
)(MakeJournalEntriesPage); )(MakeJournalEntriesPage);

View File

@@ -5,8 +5,7 @@ import {
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import AccountsConnect from 'connectors/Accounts.connector.js'; import { compose, formattedAmount} from 'utils';
import {compose, formattedAmount} from 'utils';
import { import {
AccountsListFieldCell, AccountsListFieldCell,
MoneyFieldCell, MoneyFieldCell,
@@ -14,6 +13,9 @@ import {
} from 'components/DataTableCells'; } from 'components/DataTableCells';
import { omit } from 'lodash'; import { omit } from 'lodash';
import withAccounts from 'containers/Accounts/withAccounts';
// Actions cell renderer. // Actions cell renderer.
const ActionsCellRenderer = ({ const ActionsCellRenderer = ({
row: { index }, row: { index },
@@ -70,6 +72,7 @@ const NoteCellRenderer = (chainedComponent) => (props) => {
return chainedComponent(props); return chainedComponent(props);
}; };
/** /**
* Make journal entries table component. * Make journal entries table component.
*/ */
@@ -223,5 +226,5 @@ function MakeJournalEntriesTable({
} }
export default compose( export default compose(
AccountsConnect, withAccounts,
)(MakeJournalEntriesTable); )(MakeJournalEntriesTable);

View File

@@ -18,13 +18,19 @@ import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import { compose } from 'utils'; import { compose } from 'utils';
import FilterDropdown from 'components/FilterDropdown'; import FilterDropdown from 'components/FilterDropdown';
import ManualJournalsConnect from 'connectors/ManualJournals.connect';
import ResourceConnect from 'connectors/Resource.connector';
function ManualJournalActionsBar({ import withResourceDetail from 'containers/Resources/withResourceDetails';
views, import withManualJournals from 'containers/Accounting/withManualJournals';
getResourceFields, import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
function ManualJournalActionsBar({
resourceName = 'manual_journal',
resourceFields,
manualJournalsViews,
addManualJournalsTableQueries, addManualJournalsTableQueries,
onFilterChanged, onFilterChanged,
selectedRows, selectedRows,
onBulkDelete onBulkDelete
@@ -32,8 +38,7 @@ function ManualJournalActionsBar({
const { path } = useRouteMatch(); const { path } = useRouteMatch();
const history = useHistory(); const history = useHistory();
const manualJournalFields = getResourceFields('manual_journals'); const viewsMenuItems = manualJournalsViews.map(view => {
const viewsMenuItems = views.map(view => {
return ( return (
<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} /> <MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />
); );
@@ -44,7 +49,7 @@ function ManualJournalActionsBar({
}, [history]); }, [history]);
const filterDropdown = FilterDropdown({ const filterDropdown = FilterDropdown({
fields: manualJournalFields, fields: resourceFields,
onFilterChange: filterConditions => { onFilterChange: filterConditions => {
addManualJournalsTableQueries({ addManualJournalsTableQueries({
filter_roles: filterConditions || '' filter_roles: filterConditions || ''
@@ -120,6 +125,7 @@ function ManualJournalActionsBar({
export default compose( export default compose(
DialogConnect, DialogConnect,
ManualJournalsConnect, withResourceDetail,
ResourceConnect withManualJournals,
withManualJournalsActions,
)(ManualJournalActionsBar); )(ManualJournalActionsBar);

View File

@@ -12,21 +12,33 @@ 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';
import ManualJournalsConnect from 'connectors/ManualJournals.connect';
import LoadingIndicator from 'components/LoadingIndicator';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import DashboardConnect from 'connectors/Dashboard.connector';
import ViewConnect from 'connectors/View.connector';
import { useUpdateEffect } from 'hooks'; import { useUpdateEffect } from 'hooks';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import Money from 'components/Money'; import Money from 'components/Money';
import withDashboardActions from 'containers/Dashboard/withDashboard';
import withViewDetails from 'containers/Views/withViewDetails';
import withManualJournals from 'containers/Accounting/withManualJournals';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
function ManualJournalsDataTable({ function ManualJournalsDataTable({
loading,
manualJournals, manualJournals,
manualJournalsLoading,
changeCurrentView, changeCurrentView,
changePageSubtitle, changePageSubtitle,
getViewItem,
viewId,
viewMeta,
setTopbarEditView, setTopbarEditView,
manualJournalsLoading,
onFetchData, onFetchData,
onEditJournal, onEditJournal,
onDeleteJournal, onDeleteJournal,
@@ -43,8 +55,6 @@ function ManualJournalsDataTable({
}, [manualJournalsLoading, setInitialMount]); }, [manualJournalsLoading, setInitialMount]);
useEffect(() => { useEffect(() => {
const viewMeta = getViewItem(customViewId);
if (customViewId) { if (customViewId) {
changeCurrentView(customViewId); changeCurrentView(customViewId);
setTopbarEditView(customViewId); setTopbarEditView(customViewId);
@@ -55,7 +65,7 @@ function ManualJournalsDataTable({
changeCurrentView, changeCurrentView,
changePageSubtitle, changePageSubtitle,
setTopbarEditView, setTopbarEditView,
getViewItem, viewMeta,
]); ]);
const handlePublishJournal = useCallback((journal) => () => { const handlePublishJournal = useCallback((journal) => () => {
@@ -172,22 +182,26 @@ function ManualJournalsDataTable({
}, [onSelectedRowsChange]); }, [onSelectedRowsChange]);
return ( return (
<DataTable <LoadingIndicator loading={loading} mount={false}>
columns={columns} <DataTable
data={manualJournals} columns={columns}
onFetchData={handleDataTableFetchData} data={manualJournals}
manualSortBy={true} onFetchData={handleDataTableFetchData}
selectionColumn={true} manualSortBy={true}
noInitialFetch={true} selectionColumn={true}
loading={manualJournalsLoading && !initialMount} noInitialFetch={true}
onSelectedRowsChange={handleSelectedRowsChange} loading={manualJournalsLoading && !initialMount}
/> onSelectedRowsChange={handleSelectedRowsChange}
/>
</LoadingIndicator>
); );
} }
export default compose( export default compose(
DialogConnect, DialogConnect,
DashboardConnect, withDashboardActions,
ViewConnect, // withViewsActions,
ManualJournalsConnect, withManualJournalsActions,
withManualJournals,
withViewDetails,
)(ManualJournalsDataTable); )(ManualJournalsDataTable);

View File

@@ -1,45 +1,49 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import { Route, Switch, useHistory } from 'react-router-dom'; import { Route, Switch, useHistory } from 'react-router-dom';
import useAsync from 'hooks/async'; import { useQuery } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core'; import { Alert, Intent } from '@blueprintjs/core';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; 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 ManualJournalsDataTable from 'components/JournalEntry/ManualJournalsDataTable'; import ManualJournalsViewTabs from 'containers/Accounting/ManualJournalsViewTabs';
import ManualJournalsActionsBar from 'components/JournalEntry/ManualJournalActionsBar'; import ManualJournalsDataTable from 'containers/Accounting/ManualJournalsDataTable';
import ManualJournalsConnect from 'connectors/ManualJournals.connect'; import ManualJournalsActionsBar from 'containers/Accounting/ManualJournalActionsBar';
import DashboardConnect from 'connectors/Dashboard.connector';
import CustomViewConnect from 'connectors/CustomView.connector'; import withDashboardActions from 'containers/Dashboard/withDashboard';
import ResourceConnect from 'connectors/Resource.connector'; import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
import withViewsActions from 'containers/Views/withViewsActions';
import { compose } from 'utils'; import { compose } from 'utils';
/**
* Manual journals table.
*/
function ManualJournalsTable({ function ManualJournalsTable({
changePageTitle, changePageTitle,
fetchResourceViews, requestFetchResourceViews,
fetchManualJournalsTable,
requestFetchManualJournalsTable,
requestDeleteManualJournal, requestDeleteManualJournal,
requestPublishManualJournal, requestPublishManualJournal,
requestDeleteBulkManualJournals, requestDeleteBulkManualJournals,
addManualJournalsTableQueries addManualJournalsTableQueries,
}) { }) {
const history = useHistory(); const history = useHistory();
const [deleteManualJournal, setDeleteManualJournal] = useState(false); const [deleteManualJournal, setDeleteManualJournal] = useState(false);
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const [bulkDelete, setBulkDelete] = useState(false); const [bulkDelete, setBulkDelete] = useState(false);
const fetchHook = useAsync(async () => { const fetchViews = useQuery('journals-resource-views', () => {
await Promise.all([ return requestFetchResourceViews('manual_journals');
fetchResourceViews('manual_journals'),
]);
}); });
const fetchManualJournalsHook = useAsync(async () => { const fetchManualJournals = useQuery('manual-journals-table', () =>
return fetchManualJournalsTable(); requestFetchManualJournalsTable());
});
useEffect(() => { useEffect(() => {
changePageTitle('Manual Journals'); changePageTitle('Manual Journals');
@@ -89,13 +93,13 @@ function ManualJournalsTable({
// Handle filter change to re-fetch data-table. // Handle filter change to re-fetch data-table.
const handleFilterChanged = useCallback(() => { const handleFilterChanged = useCallback(() => {
fetchManualJournalsHook.execute(); fetchManualJournals.refetch();
}, [fetchManualJournalsHook]); }, [fetchManualJournals]);
// Handle view change to re-fetch data table. // Handle view change to re-fetch data table.
const handleViewChanged = useCallback(() => { const handleViewChanged = useCallback(() => {
fetchManualJournalsHook.execute(); fetchManualJournals.refetch();
}, [fetchManualJournalsHook]); }, [fetchManualJournals]);
// Handle fetch data of manual jouranls datatable. // Handle fetch data of manual jouranls datatable.
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => { const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
@@ -105,9 +109,7 @@ function ManualJournalsTable({
sort_order: sortBy[0].desc ? 'desc' : 'asc', sort_order: sortBy[0].desc ? 'desc' : 'asc',
} : {}, } : {},
}); });
fetchManualJournalsHook.execute();
}, [ }, [
fetchManualJournalsHook,
addManualJournalsTableQueries, addManualJournalsTableQueries,
]); ]);
@@ -123,7 +125,9 @@ function ManualJournalsTable({
}, [setSelectedRows]); }, [setSelectedRows]);
return ( return (
<DashboardInsider loading={fetchHook.pending} name={'manual-journals'}> <DashboardInsider
loading={fetchViews.isFetching || fetchManualJournals.isFetching}
name={'manual-journals'}>
<ManualJournalsActionsBar <ManualJournalsActionsBar
onBulkDelete={handleBulkDelete} onBulkDelete={handleBulkDelete}
selectedRows={selectedRows} selectedRows={selectedRows}
@@ -184,8 +188,7 @@ function ManualJournalsTable({
} }
export default compose( export default compose(
ManualJournalsConnect, withDashboardActions,
CustomViewConnect, withManualJournalsActions,
ResourceConnect, withViewsActions,
DashboardConnect
)(ManualJournalsTable); )(ManualJournalsTable);

View File

@@ -0,0 +1,10 @@
import {connect} from 'react-redux';
import {
getManualJournal,
} from 'store/manualJournals/manualJournals.reducers';
export const mapStateToProps = (state, props) => ({
manualJournal: getManualJournal(state, props.manualJournalId),
});
export default connect(mapStateToProps);

View File

@@ -0,0 +1,14 @@
import {connect} from 'react-redux';
import {
makeJournalEntries,
fetchManualJournal,
editManualJournal,
} from 'store/manualJournals/manualJournals.actions';
export const mapDispatchToProps = (dispatch) => ({
requestMakeJournalEntries: (form) => dispatch(makeJournalEntries({ form })),
requestFetchManualJournal: (id) => dispatch(fetchManualJournal({ id })),
requestEditManualJournal: (id, form) => dispatch(editManualJournal({ id, form }))
});
export default connect(null, mapDispatchToProps);

View File

@@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import t from 'store/types';
import { getManualJournal } from 'store/manualJournals/manualJournals.reducers';
const mapStateToProps = (state, props) => ({
manualJournal: getManualJournal(state, props.manualJournalId),
});
export default connect(mapStateToProps);

View File

@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import { getResourceViews } from 'store/customViews/customViews.selectors';
import {
getManualJournalsItems,
} from 'store/manualJournals/manualJournals.selectors'
const mapStateToProps = (state, props) => ({
manualJournals: getManualJournalsItems(state, state.manualJournals.currentViewId),
manualJournalsViews: getResourceViews(state, 'manual_journals'),
manualJournalsItems: state.manualJournals.items,
manualJournalsTableQuery: state.manualJournals.tableQuery,
manualJournalsLoading: state.manualJournals.loading,
});
export default connect(mapStateToProps);

View File

@@ -0,0 +1,28 @@
import { connect } from 'react-redux';
import t from 'store/types';
import {
deleteManualJournal,
fetchManualJournalsTable,
publishManualJournal,
deleteBulkManualJournals,
fetchManualJournal,
} from 'store/manualJournals/manualJournals.actions';
const mapActionsToProps = (dispatch) => ({
requestDeleteManualJournal: (id) => dispatch(deleteManualJournal({ id })),
requestFetchManualJournalsTable: (query = {}) => dispatch(fetchManualJournalsTable({ query: { ...query } })),
requestFetchManualJournal: (id) => dispatch(fetchManualJournal({ id })),
requestPublishManualJournal: (id) => dispatch(publishManualJournal({ id })),
requestDeleteBulkManualJournals: (ids) => dispatch(deleteBulkManualJournals({ ids })),
changeCurrentView: (id) => dispatch({
type: t.MANUAL_JOURNALS_SET_CURRENT_VIEW,
currentViewId: parseInt(id, 10),
}),
addManualJournalsTableQueries: (queries) => dispatch({
type: t.MANUAL_JOURNALS_TABLE_QUERIES_ADD,
queries,
}),
});
export default connect(null, mapActionsToProps);

View File

@@ -13,45 +13,50 @@ import {
Intent, Intent,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { useRouteMatch, useHistory } from 'react-router-dom'; import { If } from 'components';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import AccountsConnect from 'connectors/Accounts.connector';
import {compose} from 'utils';
import FilterDropdown from 'components/FilterDropdown'; import FilterDropdown from 'components/FilterDropdown';
import ResourceConnect from 'connectors/Resource.connector';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
import withAccounts from 'containers/Accounts/withAccounts';
import {compose} from 'utils';
function AccountsActionsBar({ function AccountsActionsBar({
openDialog, openDialog,
views, accountsViews,
selectedRows = [],
getResourceFields, resourceFields,
addAccountsTableQueries, addAccountsTableQueries,
selectedRows = [],
onFilterChanged, onFilterChanged,
onBulkDelete, onBulkDelete,
onBulkArchive, onBulkArchive,
}) { }) {
const history = useHistory(); const history = useHistory();
const onClickNewAccount = () => { openDialog('account-form', {}); };
const accountsFields = getResourceFields('accounts');
const [filterCount, setFilterCount] = useState(0); const [filterCount, setFilterCount] = useState(0);
const onClickNewAccount = () => { openDialog('account-form', {}); };
const onClickViewItem = (view) => { const onClickViewItem = (view) => {
history.push(view history.push(view
? `/dashboard/accounts/${view.id}/custom_view` : ? `/dashboard/accounts/${view.id}/custom_view` : '/dashboard/accounts');
'/dashboard/accounts');
}; };
const viewsMenuItems = views.map((view) => { const viewsMenuItems = accountsViews.map((view) => {
return (<MenuItem onClick={() => onClickViewItem(view)} text={view.name} />); return (<MenuItem onClick={() => onClickViewItem(view)} text={view.name} />);
}); });
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]); const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
const filterDropdown = FilterDropdown({ const filterDropdown = FilterDropdown({
fields: accountsFields, fields: resourceFields,
onFilterChange: (filterConditions) => { onFilterChange: (filterConditions) => {
setFilterCount(filterConditions.length || 0); setFilterCount(filterConditions.length || 0);
addAccountsTableQueries({ addAccountsTableQueries({
@@ -106,15 +111,13 @@ function AccountsActionsBar({
icon={ <Icon icon="filter" /> }/> icon={ <Icon icon="filter" /> }/>
</Popover> </Popover>
{hasSelectedRows && ( <If condition={hasSelectedRows}>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='archive' iconSize={15} />} icon={<Icon icon='archive' iconSize={15} />}
text='Archive' text='Archive'
onClick={handleBulkArchive} onClick={handleBulkArchive}
/> />
)}
{hasSelectedRows && (
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='trash' iconSize={15} />} icon={<Icon icon='trash' iconSize={15} />}
@@ -122,7 +125,8 @@ function AccountsActionsBar({
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handleBulkDelete} onClick={handleBulkDelete}
/> />
)} </If>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='file-import' />} icon={<Icon icon='file-import' />}
@@ -138,8 +142,16 @@ function AccountsActionsBar({
); );
} }
const mapStateToProps = (state, props) => ({
resourceName: 'accounts',
});
const withAccountsActionsBar = connect(mapStateToProps);
export default compose( export default compose(
withAccountsActionsBar,
DialogConnect, DialogConnect,
AccountsConnect, withAccounts,
ResourceConnect, withResourceDetail,
withAccountsTableActions,
)(AccountsActionsBar); )(AccountsActionsBar);

View File

@@ -3,29 +3,49 @@ import {
Route, Route,
Switch, Switch,
} from 'react-router-dom'; } from 'react-router-dom';
import useAsync from 'hooks/async';
import { Alert, Intent } from '@blueprintjs/core'; import { Alert, Intent } from '@blueprintjs/core';
import { useQuery } from 'react-query'
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import AccountsViewsTabs from 'components/Accounts/AccountsViewsTabs'; import AccountsViewsTabs from 'containers/Accounts/AccountsViewsTabs';
import AccountsDataTable from 'components/Accounts/AccountsDataTable'; import AccountsDataTable from 'containers/Accounts/AccountsDataTable';
import DashboardActionsBar from 'components/Accounts/AccountsActionsBar'; import DashboardActionsBar from 'containers/Accounts/AccountsActionsBar';
import AccountsConnect from 'connectors/Accounts.connector';
import DashboardConnect from 'connectors/Dashboard.connector'; import withDashboardActions from 'containers/Dashboard/withDashboard';
import CustomViewConnect from 'connectors/CustomView.connector'; import withResourceActions from 'containers/Resources/withResourcesActions';
import ResourceConnect from 'connectors/Resource.connector'; import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
import withViewsActions from 'containers/Views/withViewsActions';
import withAccounts from 'containers/Accounts/withAccounts';
import { compose } from 'utils'; import { compose } from 'utils';
function AccountsChart({ function AccountsChart({
// #withDashboard
changePageTitle, changePageTitle,
// #withAccountsActions
requestDeleteAccount, requestDeleteAccount,
requestInactiveAccount, requestInactiveAccount,
fetchResourceViews,
fetchResourceFields, // #withViewsActions
requestFetchResourceViews,
// #withResourceActions
requestFetchResourceFields,
// #withAccountsTableActions
requestFetchAccountsTable, requestFetchAccountsTable,
addAccountsTableQueries,
requestDeleteBulkAccounts, requestDeleteBulkAccounts,
addAccountsTableQueries,
// #withAccounts
accountsTableQuery,
}) { }) {
const [deleteAccount, setDeleteAccount] = useState(false); const [deleteAccount, setDeleteAccount] = useState(false);
const [inactiveAccount, setInactiveAccount] = useState(false); const [inactiveAccount, setInactiveAccount] = useState(false);
@@ -35,19 +55,16 @@ function AccountsChart({
const [tableLoading, setTableLoading] = useState(false); const [tableLoading, setTableLoading] = useState(false);
// Fetch accounts resource views and fields. // Fetch accounts resource views and fields.
const fetchHook = useAsync(async () => { const fetchHook = useQuery('resource-accounts', () => {
await Promise.all([ return Promise.all([
fetchResourceViews('accounts'), requestFetchResourceViews('accounts'),
fetchResourceFields('accounts'), requestFetchResourceFields('accounts'),
]); ]);
}); });
// 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 = useQuery(['accounts-table', accountsTableQuery],
await Promise.all([ () => requestFetchAccountsTable());
requestFetchAccountsTable(),
]);
}, false);
useEffect(() => { useEffect(() => {
changePageTitle('Chart of Accounts'); changePageTitle('Chart of Accounts');
@@ -136,18 +153,20 @@ function AccountsChart({
// Refetches accounts data table when current custom view changed. // Refetches accounts data table when current custom view changed.
const handleFilterChanged = useCallback(() => { const handleFilterChanged = useCallback(() => {
fetchAccountsHook.execute(); fetchAccountsHook.refetch();
}, [fetchAccountsHook]); }, [fetchAccountsHook]);
// Refetch accounts data table when current custom view changed. // Refetch accounts data table when current custom view changed.
const handleViewChanged = useCallback(() => { const handleViewChanged = useCallback(async () => {
setTableLoading(true); setTableLoading(true);
fetchAccountsHook.execute().finally(() => {
setTableLoading(false);
});
}, [fetchAccountsHook]); }, [fetchAccountsHook]);
useEffect(() => {
if (tableLoading && !fetchAccountsHook.isFetching) {
setTableLoading(false);
}
}, [tableLoading, fetchAccountsHook.isFetching]);
// Handle fetch data of accounts datatable. // Handle fetch data of accounts datatable.
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => { const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
addAccountsTableQueries({ addAccountsTableQueries({
@@ -156,11 +175,13 @@ function AccountsChart({
sort_order: sortBy[0].desc ? 'desc' : 'asc', sort_order: sortBy[0].desc ? 'desc' : 'asc',
} : {}, } : {},
}); });
fetchAccountsHook.execute(); fetchAccountsHook.refetch();
}, [fetchAccountsHook, addAccountsTableQueries]); }, [fetchAccountsHook, addAccountsTableQueries]);
return ( return (
<DashboardInsider loading={fetchHook.pending} name={'accounts-chart'}> <DashboardInsider
loading={fetchHook.isFetching}
name={'accounts-chart'}>
<DashboardActionsBar <DashboardActionsBar
selectedRows={selectedRows} selectedRows={selectedRows}
onFilterChanged={handleFilterChanged} onFilterChanged={handleFilterChanged}
@@ -236,8 +257,11 @@ function AccountsChart({
} }
export default compose( export default compose(
AccountsConnect, withAccountsActions,
CustomViewConnect, withAccountsTableActions,
ResourceConnect, withViewsActions,
DashboardConnect, withResourceActions,
withDashboardActions,
withAccounts,
)(AccountsChart); )(AccountsChart);

View File

@@ -9,32 +9,34 @@ import {
Classes, Classes,
Tooltip, Tooltip,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
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 AccountsConnect from 'connectors/Accounts.connector';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import DashboardConnect from 'connectors/Dashboard.connector';
import ViewConnect from 'connectors/View.connector';
import LoadingIndicator from 'components/LoadingIndicator'; import LoadingIndicator from 'components/LoadingIndicator';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import Money from 'components/Money'; import Money from 'components/Money';
import { useUpdateEffect } from 'hooks'; import { useUpdateEffect } from 'hooks';
import withDashboardActions from 'containers/Dashboard/withDashboard';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withAccounts from 'containers/Accounts/withAccounts';
import {If} from 'components';
function AccountsDataTable({ function AccountsDataTable({
loading, // # withAccounts
accounts, accounts,
accountsLoading,
// # withDialog.
openDialog,
// own properties
loading,
onFetchData,
onSelectedRowsChange,
onDeleteAccount, onDeleteAccount,
onInactiveAccount, onInactiveAccount,
openDialog,
changeCurrentView,
changePageSubtitle,
getViewItem,
setTopbarEditView,
accountsLoading,
onFetchData,
onSelectedRowsChange
}) { }) {
const [initialMount, setInitialMount] = useState(false); const [initialMount, setInitialMount] = useState(false);
@@ -147,9 +149,9 @@ function AccountsDataTable({
], [actionMenuList]); ], [actionMenuList]);
const selectionColumn = useMemo(() => ({ const selectionColumn = useMemo(() => ({
minWidth: 42, minWidth: 50,
width: 42, width: 50,
maxWidth: 42, maxWidth: 50,
}), []) }), [])
const handleDatatableFetchData = useCallback((...params) => { const handleDatatableFetchData = useCallback((...params) => {
@@ -162,7 +164,11 @@ function AccountsDataTable({
return ( return (
<LoadingIndicator loading={loading} mount={false}> <LoadingIndicator loading={loading} mount={false}>
<If condition={loading}>
asdasdsadsa
</If>
<DataTable <DataTable
noInitialFetch={true}
columns={columns} columns={columns}
data={accounts} data={accounts}
onFetchData={handleDatatableFetchData} onFetchData={handleDatatableFetchData}
@@ -178,8 +184,8 @@ function AccountsDataTable({
} }
export default compose( export default compose(
AccountsConnect,
DialogConnect, DialogConnect,
DashboardConnect, withDashboardActions,
ViewConnect withAccountsActions,
withAccounts,
)(AccountsDataTable); )(AccountsDataTable);

View File

@@ -9,38 +9,60 @@ import {
Tab, Tab,
Button Button
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useParams } from 'react-router-dom'; import { useParams, withRouter } from 'react-router-dom';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { compose } from 'utils'; import { compose } from 'utils';
import AccountsConnect from 'connectors/Accounts.connector';
import DashboardConnect from 'connectors/Dashboard.connector';
import {useUpdateEffect} from 'hooks'; import {useUpdateEffect} from 'hooks';
import ViewConnect from 'connectors/View.connector';
import withDashboard from 'containers/Dashboard/withDashboard';
import withAccounts from 'containers/Accounts/withAccounts';
import withAccountsTableActions from 'containers/Accounts/withAccountsTableActions';
import withViewDetail from 'containers/Views/withViewDetails';
function AccountsViewsTabs({ function AccountsViewsTabs({
views, // #withViewDetail
setTopbarEditView, viewId,
customViewChanged, viewItem,
// #withAccounts
accountsViews,
// #withAccountsTableActions
addAccountsTableQueries, addAccountsTableQueries,
onViewChanged, changeAccountsCurrentView,
getViewItem,
changeCurrentView, // #withDashboard
setTopbarEditView,
changePageSubtitle, changePageSubtitle,
// props
customViewChanged,
onViewChanged,
}) { }) {
const history = useHistory(); const history = useHistory();
const { custom_view_id: customViewId = null } = useParams(); const { custom_view_id: customViewId = null } = useParams();
useEffect(() => { useEffect(() => {
const viewMeta = getViewItem(customViewId); changeAccountsCurrentView(customViewId || -1);
changeCurrentView(customViewId || -1);
setTopbarEditView(customViewId); setTopbarEditView(customViewId);
changePageSubtitle((customViewId && viewMeta) ? viewMeta.name : ''); changePageSubtitle((customViewId && viewItem) ? viewItem.name : '');
addAccountsTableQueries({
custom_view_id: customViewId,
});
return () => {
setTopbarEditView(null);
changePageSubtitle('');
changeAccountsCurrentView(null)
};
}, [customViewId]);
useUpdateEffect(() => {
onViewChanged && onViewChanged(customViewId);
}, [customViewId]); }, [customViewId]);
// Clear page subtitle when unmount the page.
useEffect(() => () => { changePageSubtitle(''); }, []);
// Handle click a new view tab. // Handle click a new view tab.
const handleClickNewView = () => { const handleClickNewView = () => {
@@ -53,19 +75,7 @@ function AccountsViewsTabs({
setTopbarEditView(customViewId); setTopbarEditView(customViewId);
}; };
useEffect(() => { const tabs = accountsViews.map((view) => {
customViewChanged && customViewChanged(customViewId);
addAccountsTableQueries({
custom_view_id: customViewId,
});
}, [customViewId]);
useUpdateEffect(() => {
onViewChanged && onViewChanged(customViewId);
}, [customViewId]);
const tabs = views.map((view) => {
const baseUrl = '/dashboard/accounts'; const baseUrl = '/dashboard/accounts';
const link = ( const link = (
@@ -104,8 +114,19 @@ function AccountsViewsTabs({
); );
} }
const mapStateToProps = (state, ownProps) => ({
// Mapping view id from matched route params.
viewId: ownProps.match.params.custom_view_id,
});
const withAccountsViewsTabs = connect(mapStateToProps);
export default compose( export default compose(
AccountsConnect, withRouter,
DashboardConnect, withAccountsViewsTabs,
ViewConnect, withDashboard,
withAccounts,
withAccountsTableActions,
withViewDetail
)(AccountsViewsTabs); )(AccountsViewsTabs);

View File

@@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import {
getItemById
} from 'store/selectors';
const mapStateToProps = (state, props) => ({
account: getItemById(state.accounts.items, props.accountId),
});
export default connect(mapStateToProps);

View File

@@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import {
getAccountsItems,
} from 'store/accounts/accounts.selectors';
import {
getResourceViews,
} from 'store/customViews/customViews.selectors';
const mapStateToProps = (state, props) => ({
accountsViews: getResourceViews(state, 'accounts'),
accounts: getAccountsItems(state, state.accounts.currentViewId),
accountsTypes: state.accounts.accountsTypes,
accountsTableQuery: state.accounts.tableQuery,
accountsLoading: state.accounts.loading,
accountErrors: state.accounts.errors,
});
export default connect(mapStateToProps);

View File

@@ -0,0 +1,22 @@
import { connect } from 'react-redux';
import {
fetchAccountTypes,
fetchAccountsList,
deleteAccount,
inactiveAccount,
submitAccount,
fetchAccount,
deleteBulkAccounts,
} from 'store/accounts/accounts.actions';
const mapActionsToProps = (dispatch) => ({
requestFetchAccounts: (query) => dispatch(fetchAccountsList({ query })),
requestFetchAccountTypes: () => dispatch(fetchAccountTypes()),
requestSubmitAccount: ({ form }) => dispatch(submitAccount({ form })),
requestDeleteAccount: (id) => dispatch(deleteAccount({ id })),
requestInactiveAccount: (id) => dispatch(inactiveAccount({ id })),
requestFetchAccount: (id) => dispatch(fetchAccount({ id })),
requestDeleteBulkAccounts: (ids) => dispatch(deleteBulkAccounts({ ids })),
});
export default connect(null, mapActionsToProps);

View File

@@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import t from 'store/types';
import {
fetchAccountsTable,
} from 'store/accounts/accounts.actions';
const mapActionsToProps = (dispatch) => ({
requestFetchAccountsTable: (query = {}) => dispatch(fetchAccountsTable({ query: { ...query } })),
changeAccountsCurrentView: (id) => dispatch({
type: t.ACCOUNTS_SET_CURRENT_VIEW,
currentViewId: parseInt(id, 10),
}),
setAccountsTableQuery: (key, value) => dispatch({
type: 'ACCOUNTS_TABLE_QUERY_SET', key, value,
}),
addAccountsTableQueries: (queries) => dispatch({
type: 'ACCOUNTS_TABLE_QUERIES_ADD', queries,
}),
setSelectedRowsAccounts: (ids) => dispatch({
type: t.ACCOUNTS_SELECTED_ROWS_SET, payload: { ids },
}),
});
export default connect(null, mapActionsToProps);

View File

@@ -5,7 +5,7 @@ import { useIntl } from 'react-intl';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import { compose } from 'utils'; import { compose } from 'utils';
import AuthenticationConnect from 'connectors/Authentication.connect'; import withAuthenticationActions from './withAuthenticationActions';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { import {
Button, Button,
@@ -21,6 +21,7 @@ import AuthInsider from 'containers/Authentication/AuthInsider';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import useAsync from 'hooks/async'; import useAsync from 'hooks/async';
function Invite({ function Invite({
requestInviteAccept, requestInviteAccept,
requestInviteMetaByToken, requestInviteMetaByToken,
@@ -224,4 +225,6 @@ function Invite({
); );
} }
export default compose(AuthenticationConnect)(Invite); export default compose(
withAuthenticationActions,
)(Invite);

View File

@@ -1,8 +1,7 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { connect } from 'react-redux';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { import {
Button, Button,
@@ -10,13 +9,12 @@ import {
Intent, Intent,
FormGroup, FormGroup,
Checkbox, Checkbox,
Position,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import Toaster from 'components/AppToaster'; import Toaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import AuthInsider from 'containers/Authentication/AuthInsider'; import AuthInsider from 'containers/Authentication/AuthInsider';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import AuthenticationConnect from 'connectors/Authentication.connect'; import withAuthenticationActions from './withAuthenticationActions';
import { compose } from 'utils'; import { compose } from 'utils';
const ERRORS_TYPES = { const ERRORS_TYPES = {
@@ -162,5 +160,5 @@ function Login({
} }
export default compose( export default compose(
AuthenticationConnect withAuthenticationActions,
)(Login); )(Login);

View File

@@ -11,12 +11,13 @@ import {
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { Row, Col } from 'react-grid-system'; import { Row, Col } from 'react-grid-system';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import AuthenticationConnect from 'connectors/Authentication.connect'; import withAuthenticationActions from './withAuthenticationActions';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import AuthInsider from 'containers/Authentication/AuthInsider'; import AuthInsider from 'containers/Authentication/AuthInsider';
import { compose } from 'utils'; import { compose } from 'utils';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import { If } from 'components';
function Register({ function Register({
requestRegister, requestRegister,
@@ -212,16 +213,16 @@ function Register({
</div> </div>
</form> </form>
{ isSubmitting && ( <If condition={isSubmitting}>
<div class="authentication-page__loading-overlay"> <div class="authentication-page__loading-overlay">
<Spinner size={50} /> <Spinner size={50} />
</div> </div>
)} </If>
</div> </div>
</AuthInsider> </AuthInsider>
); );
} }
export default compose( export default compose(
AuthenticationConnect, withAuthenticationActions,
)(Register); )(Register);

View File

@@ -13,9 +13,10 @@ import { Link, useParams, useHistory } from 'react-router-dom';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import { compose } from 'utils'; import { compose } from 'utils';
import AuthenticationConnect from 'connectors/Authentication.connect'; import withAuthenticationActions from './withAuthenticationActions';
import AuthInsider from 'containers/Authentication/AuthInsider'; import AuthInsider from 'containers/Authentication/AuthInsider';
function ResetPassword({ function ResetPassword({
requestResetPassword, requestResetPassword,
}) { }) {
@@ -131,5 +132,5 @@ function ResetPassword({
} }
export default compose( export default compose(
AuthenticationConnect, withAuthenticationActions,
)(ResetPassword); )(ResetPassword);

View File

@@ -5,12 +5,17 @@ import { useIntl } from 'react-intl';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core'; import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ErrorMessage from 'components/ErrorMessage';
import AuthenticationConnect from 'connectors/Authentication.connect';
import { compose } from 'utils'; import { compose } from 'utils';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage';
import AuthInsider from 'containers/Authentication/AuthInsider'; import AuthInsider from 'containers/Authentication/AuthInsider';
import withAuthenticationActions from './withAuthenticationActions';
function SendResetPassword({ function SendResetPassword({
requestSendResetPassword, requestSendResetPassword,
}) { }) {
@@ -65,6 +70,7 @@ function SendResetPassword({
}); });
}, },
}); });
return ( return (
<AuthInsider> <AuthInsider>
<div class='reset-form'> <div class='reset-form'>
@@ -110,5 +116,5 @@ function SendResetPassword({
} }
export default compose( export default compose(
AuthenticationConnect, withAuthenticationActions,
)(SendResetPassword); )(SendResetPassword);

View File

@@ -8,9 +8,6 @@ import {
} from 'store/authentication/authentication.actions'; } from 'store/authentication/authentication.actions';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
requestLogin: (form) => dispatch(login({ form })), requestLogin: (form) => dispatch(login({ form })),
@@ -21,4 +18,4 @@ const mapDispatchToProps = (dispatch) => ({
requestInviteMetaByToken: (token) => dispatch(inviteMetaByToken({ token })), requestInviteMetaByToken: (token) => dispatch(inviteMetaByToken({ token })),
}); });
export default connect(mapStateToProps, mapDispatchToProps); export default connect(null, mapDispatchToProps);

View File

@@ -1,24 +0,0 @@
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import DashboardConnect from 'connectors/Dashboard.connector';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import CategoryList from 'components/Items/categoryList';
import ItemFormDialog from 'connectors/ItemFormDialog.connect';
import { compose } from 'utils';
const ItemCategoryList = ({ changePageTitle }) => {
const { id } = useParams();
useEffect(() => {
id
? changePageTitle('Edit Category Details')
: changePageTitle('Category List');
}, []);
return (
<DashboardInsider isLoading={null} name={'item-category-list'}>
<CategoryList />
</DashboardInsider>
);
};
export default compose(DashboardConnect, ItemFormDialog)(ItemCategoryList);

View File

@@ -1,43 +0,0 @@
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useAsync } from 'react-use';
import DashboardConnect from 'connectors/Dashboard.connector';
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,
requestFetchAccounts,
requestFetchItemCategories,
}) => {
const { id } = useParams();
useEffect(() => {
id ?
changePageTitle('Edit Item Details') :
changePageTitle('New Item');
}, [id, changePageTitle]);
const fetchHook = useAsync(async () => {
await Promise.all([
requestFetchAccounts(),
requestFetchItemCategories(),
]);
});
return (
<DashboardInsider loading={fetchHook.loading} name={'item-form'}>
<ItemForm />
</DashboardInsider>
);
};
export default compose(
DashboardConnect,
ItemsConnect,
AccountsConnect,
ItemCategoryConnect,
)(ItemFormContainer);

View File

@@ -1,102 +0,0 @@
import React, { useEffect, useState, useCallback } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import useAsync from 'hooks/async';
import { useParams } from 'react-router-dom';
import DashboardConnect from 'connectors/Dashboard.connector';
import ItemsCategoryConnect from 'connectors/ItemsCategory.connect';
import { compose } from 'utils';
import ItemsCategoryList from 'components/Items/ItemsCategoryList';
import ItemsCategoryActionsBar from './ItemsCategoryActionsBar';
import { Alert, Intent } from '@blueprintjs/core';
import AppToaster from 'components/AppToaster';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
const ItemCategoriesList = ({
changePageTitle,
views,
requestFetchItemCategories,
requestEditItemCategory,
requestDeleteItemCategory,
}) => {
const { id } = useParams();
const [deleteCategory, setDeleteCategory] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
useEffect(() => {
id
? changePageTitle('Edit Item Details')
: changePageTitle('Categories List');
}, [id, changePageTitle]);
const fetchHook = useAsync(async () => {
await Promise.all([
requestFetchItemCategories(),
]);
}, false);
const handelDeleteCategory = useCallback((category) => {
setDeleteCategory(category);
}, [setDeleteCategory]);
const handelEditCategory = category => {};
const handelCancelCategoryDelete = useCallback(() => {
setDeleteCategory(false);
}, [setDeleteCategory]);
const handelConfirmCategoryDelete = useCallback(() => {
requestDeleteItemCategory(deleteCategory.id).then(() => {
setDeleteCategory(false);
AppToaster.show({
message: 'the_category_has_been_delete'
});
});
}, [deleteCategory, requestDeleteItemCategory, setDeleteCategory]);
const handleFetchData = useCallback(() => {
fetchHook.execute();
}, []);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback((accounts) => {
setSelectedRows(accounts);
}, [setSelectedRows]);
return (
<DashboardInsider loading={fetchHook.pending} name="items-categories">
<ItemsCategoryActionsBar
views={views}
onDeleteCategory={handelDeleteCategory}
selectedRows={selectedRows}
/>
<DashboardPageContent>
<ItemsCategoryList
onDeleteCategory={handelDeleteCategory}
onFetchData={handleFetchData}
onEditCategory={handelEditCategory}
onSelectedRowsChange={handleSelectedRowsChange}
/>
<Alert
cancelButtonText='Cancel'
confirmButtonText='Move to Trash'
icon='trash'
intent={Intent.DANGER}
isOpen={deleteCategory}
onCancel={handelCancelCategoryDelete}
onConfirm={handelConfirmCategoryDelete}
>
<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>
</DashboardInsider>
);
};
export default compose(
DashboardConnect,
ItemsCategoryConnect
)(ItemCategoriesList);

View File

@@ -1,101 +0,0 @@
import React, {useEffect, useState, useCallback} from 'react';
import { useAsync } from 'react-use';
import { useParams, useHistory } from 'react-router-dom';
import { Intent, Alert } from '@blueprintjs/core';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import ViewForm from 'components/Views/ViewForm';
import DashboardConnect from 'connectors/Dashboard.connector';
import ResourceConnect from 'connectors/Resource.connector';
import ViewConnect from 'connectors/View.connector';
import {compose} from 'utils';
import AppToaster from 'components/AppToaster';
function ViewFormPage({
changePageTitle,
fetchResourceFields,
fetchResourceColumns,
fetchView,
getResourceColumns,
getResourceFields,
submitView,
getViewMeta,
deleteView,
}) {
const { resource_slug: resourceSlug, view_id: viewId } = useParams();
const columns = getResourceColumns('accounts');
const fields = getResourceFields('accounts');
const viewForm = (viewId) ? getViewMeta(viewId) : null;
const [stateDeleteView, setStateDeleteView] = useState(null);
useEffect(() => {
if (viewId) {
changePageTitle('Edit Custom View');
} else {
changePageTitle('New Custom View');
}
}, [viewId, changePageTitle]);
const fetchHook = useAsync(async () => {
await Promise.all([
fetchResourceColumns('accounts'),
fetchResourceFields('accounts'),
...(viewId) ? [
fetchView(viewId),
] : [],
]);
}, []);
const handleDeleteView = useCallback((view) => {
setStateDeleteView(view);
}, []);
const handleCancelDeleteView = useCallback(() => {
setStateDeleteView(null);
}, []);
const handleConfirmDeleteView = useCallback(() => {
deleteView(stateDeleteView.id).then((response) => {
setStateDeleteView(null);
AppToaster.show({
message: 'the_custom_view_has_been_deleted',
});
})
}, [deleteView, stateDeleteView]);
return (
<DashboardInsider name={'view-form'} loading={fetchHook.loading} mount={false}>
<DashboardPageContent>
<ViewForm
resourceName={resourceSlug}
columns={columns}
fields={fields}
viewForm={viewForm}
onDelete={handleDeleteView} />
<Alert
cancelButtonText="Cancel"
confirmButtonText="Move to Trash"
icon="trash"
intent={Intent.DANGER}
isOpen={stateDeleteView}
onCancel={handleCancelDeleteView}
onConfirm={handleConfirmDeleteView}>
<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>
</DashboardInsider>
);
}
export default compose(
DashboardConnect,
ResourceConnect,
ViewConnect,
)(ViewFormPage);

View File

@@ -0,0 +1,27 @@
import { connect } from 'react-redux';
import t from 'store/types';
const mapActionsToProps = (dispatch) => ({
changePageTitle: (pageTitle) => dispatch({
type: t.CHANGE_DASHBOARD_PAGE_TITLE, pageTitle
}),
changePageSubtitle: (pageSubtitle) => dispatch({
type: t.ALTER_DASHBOARD_PAGE_SUBTITLE, pageSubtitle,
}),
setTopbarEditView: (id) => dispatch({
type: t.SET_TOPBAR_EDIT_VIEW, id,
}),
setDashboardRequestLoading: () => dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
}),
setDashboardRequestCompleted: () => dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED,
}),
});
export default connect(null, mapActionsToProps);

View File

@@ -0,0 +1,30 @@
import {connect} from 'react-redux';
import { compose } from 'utils';
import DialogConnect from 'connectors/Dialog.connector';
import DialogReduxConnect from 'components/DialogReduxConnect';
import {getDialogPayload} from 'store/dashboard/dashboard.reducer';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withAccountDetail from 'containers/Accounts/withAccountDetail';
import withAccounts from 'containers/Accounts/withAccounts';
export const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'account-form');
return {
name: 'account-form',
payload: {action: 'new', id: null, ...dialogPayload},
accountId: dialogPayload?.id || null,
};
};
const AccountFormDialogConnect = connect(mapStateToProps);
export default compose(
AccountFormDialogConnect,
withAccountsActions,
withAccountDetail,
withAccounts,
DialogReduxConnect,
DialogConnect,
);

View File

@@ -15,31 +15,40 @@ import * as Yup from 'yup';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { compose } from 'utils'; import { useQuery, queryCache } from 'react-query';
import useAsync from 'hooks/async';
import Dialog from 'components/Dialog'; import Dialog from 'components/Dialog';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import DialogConnect from 'connectors/Dialog.connector';
import DialogReduxConnect from 'components/DialogReduxConnect'; import AccountFormDialogContainer from 'containers/Dialogs/AccountFormDialog.container';
import AccountFormDialogConnect from 'connectors/AccountFormDialog.connector';
import AccountsConnect from 'connectors/Accounts.connector';
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import { fetchAccountTypes } from 'store/accounts/accounts.actions';
function AccountFormDialog({ function AccountFormDialog({
name, name,
payload, payload,
isOpen, isOpen,
// #withAccounts
accountsTypes, accountsTypes,
accounts, accounts,
// #withAccountDetail
account,
// #withAccountsActions
requestFetchAccounts, requestFetchAccounts,
requestFetchAccountTypes, requestFetchAccountTypes,
requestFetchAccount, requestFetchAccount,
closeDialog,
requestSubmitAccount, requestSubmitAccount,
requestEditAccount, requestEditAccount,
getAccountById,
// #withDialog
closeDialog,
}) { }) {
const intl = useIntl(); const intl = useIntl();
const accountFormValidationSchema = Yup.object().shape({ const accountFormValidationSchema = Yup.object().shape({
@@ -63,10 +72,6 @@ function AccountFormDialog({
accounts.find(a => a.id === payload.id) : null, accounts.find(a => a.id === payload.id) : null,
); );
const editAccount = useMemo(() =>
payload.action === 'edit' ? getAccountById(payload.id) : null,
[payload, getAccountById]);
const transformApiErrors = (errors) => { const transformApiErrors = (errors) => {
const fields = {}; const fields = {};
if (errors.find(e => e.type === 'NOT_UNIQUE_CODE')) { if (errors.find(e => e.type === 'NOT_UNIQUE_CODE')) {
@@ -79,8 +84,7 @@ function AccountFormDialog({
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
...(payload.action === 'edit' && editAccount) ...(payload.action === 'edit' && account) ? account : initialValues,
? editAccount : initialValues,
}, },
validationSchema: accountFormValidationSchema, validationSchema: accountFormValidationSchema,
onSubmit: (values, { setSubmitting, setErrors }) => { onSubmit: (values, { setSubmitting, setErrors }) => {
@@ -97,12 +101,13 @@ function AccountFormDialog({
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
setSubmitting(false); setSubmitting(false);
queryCache.refetchQueries('accounts-table', { force: true });
}).catch((errors) => { }).catch((errors) => {
setSubmitting(false); setSubmitting(false);
setErrors(transformApiErrors(errors)); setErrors(transformApiErrors(errors));
}); });
} else { } else {
requestSubmitAccount({ form: { ...omit(values, exclude) } }).then(response => { requestSubmitAccount({ form: { ...omit(values, exclude) } }).then((response) => {
closeDialog(name); closeDialog(name);
AppToaster.show({ AppToaster.show({
message: 'the_account_has_been_submit', message: 'the_account_has_been_submit',
@@ -110,6 +115,7 @@ function AccountFormDialog({
position: Position.BOTTOM, position: Position.BOTTOM,
}); });
setSubmitting(false); setSubmitting(false);
queryCache.refetchQueries('accounts-table', { force: true });
}).catch((errors) => { }).catch((errors) => {
setSubmitting(false); setSubmitting(false);
setErrors(transformApiErrors(errors)); setErrors(transformApiErrors(errors));
@@ -121,13 +127,13 @@ function AccountFormDialog({
// Set default account type. // Set default account type.
useEffect(() => { useEffect(() => {
if (editAccount && editAccount.account_type_id) { if (account && account.account_type_id) {
const defaultType = accountsTypes.find((t) => const defaultType = accountsTypes.find((t) =>
t.id === editAccount.account_type_id); t.id === account.account_type_id);
defaultType && setSelectedAccountType(defaultType); defaultType && setSelectedAccountType(defaultType);
} }
}, [editAccount, accountsTypes]); }, [account, accountsTypes]);
// Filters accounts types items. // Filters accounts types items.
const filterAccountTypeItems = (query, accountType, _index, exactMatch) => { const filterAccountTypeItems = (query, accountType, _index, exactMatch) => {
@@ -168,18 +174,32 @@ function AccountFormDialog({
// Handles dialog close. // Handles dialog close.
const handleClose = useCallback(() => { closeDialog(name); }, [closeDialog, name]); const handleClose = useCallback(() => { closeDialog(name); }, [closeDialog, name]);
const fetchHook = useAsync(async () => { // Fetches accounts list.
await Promise.all([ const fetchAccountsList = useQuery('accounts-list',
requestFetchAccounts(), () => requestFetchAccounts(), { manual: true });
requestFetchAccountTypes(),
// Fetch the target in case edit mode. // Fetches accounts types.
...(payload.action === 'edit' ? const fetchAccountsTypes = useQuery('accounts-types-list', async () => {
[requestFetchAccount(payload.id)] : []) await requestFetchAccountTypes();
]); }, { manual: true });
}, false);
// Fetch the given account id on edit mode.
const fetchAccount = useQuery(
payload.action === 'edit' && ['account', payload.id],
(key, id) => requestFetchAccount(id),
{ manual: true });
const isFetching = (
fetchAccountsList.isFetching ||
fetchAccountTypes.isFetching ||
fetchAccount.isFetching);
// Fetch requests on dialog opening. // Fetch requests on dialog opening.
const onDialogOpening = useCallback(() => { fetchHook.execute(); }, [fetchHook]); const onDialogOpening = useCallback(() => {
fetchAccountsList.refetch();
fetchAccountsTypes.refetch();
fetchAccount.refetch();
}, []);
const onChangeAccountType = useCallback((accountType) => { const onChangeAccountType = useCallback((accountType) => {
setSelectedAccountType(accountType); setSelectedAccountType(accountType);
@@ -211,7 +231,7 @@ function AccountFormDialog({
name={name} name={name}
title={payload.action === 'edit' ? 'Edit Account' : 'New Account'} title={payload.action === 'edit' ? 'Edit Account' : 'New Account'}
className={{ className={{
'dialog--loading': fetchHook.pending, 'dialog--loading': isFetching,
'dialog--account-form': true 'dialog--account-form': true
}} }}
autoFocus={true} autoFocus={true}
@@ -219,7 +239,7 @@ function AccountFormDialog({
onClosed={onDialogClosed} onClosed={onDialogClosed}
onOpening={onDialogOpening} onOpening={onDialogOpening}
isOpen={isOpen} isOpen={isOpen}
isLoading={fetchHook.pending} isLoading={isFetching}
onClose={handleClose} onClose={handleClose}
> >
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
@@ -350,9 +370,6 @@ function AccountFormDialog({
); );
} }
export default compose( export default AccountFormDialogContainer(
AccountFormDialogConnect, AccountFormDialog,
AccountsConnect, );
DialogReduxConnect,
DialogConnect
)(AccountFormDialog);

View File

@@ -14,31 +14,52 @@ import * as Yup from 'yup';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { compose } from 'utils'; import { compose } from 'utils';
import Dialog from 'components/Dialog'; import { useQuery } from 'react-query';
import useAsync from 'hooks/async'; import classNames from 'classnames';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage';
import Dialog from 'components/Dialog';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import DialogReduxConnect from 'components/DialogReduxConnect'; import DialogReduxConnect from 'components/DialogReduxConnect';
import ItemsCategoryConnect from 'connectors/ItemsCategory.connect';
import ErrorMessage from 'components/ErrorMessage'; import {connect} from 'react-redux';
import classNames from 'classnames'; import { getDialogPayload } from 'store/dashboard/dashboard.reducer';
import withItemCategoryDetail from 'containers/Items/withItemCategoryDetail';
import withItemCategories from 'containers/Items/withItemCategories';
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
function ItemCategoryDialog({ function ItemCategoryDialog({
name, name,
payload, payload,
isOpen, isOpen,
// #withDialog
openDialog, openDialog,
closeDialog, closeDialog,
categories,
// #withItemCategoryDetail
itemCategoryId,
itemCategory,
// #withItemCategories
categoriesList,
// #withItemCategoriesActions
requestSubmitItemCategory, requestSubmitItemCategory,
requestFetchItemCategories, requestFetchItemCategories,
requestEditItemCategory, requestEditItemCategory,
editItemCategory
}) { }) {
const [selectedParentCategory, setParentCategory] = useState(null); const [selectedParentCategory, setParentCategory] = useState(null);
const intl = useIntl(); const intl = useIntl();
const fetchList = useQuery(['items-categories-list'],
() => requestFetchItemCategories());
const ValidationSchema = Yup.object().shape({ const ValidationSchema = Yup.object().shape({
name: Yup.string().required(intl.formatMessage({ id: 'required' })), name: Yup.string().required(intl.formatMessage({ id: 'required' })),
parent_category_id: Yup.string().nullable(), parent_category_id: Yup.string().nullable(),
@@ -51,12 +72,12 @@ function ItemCategoryDialog({
parent_category_id: null parent_category_id: null
}), []); }), []);
//Formik // Formik
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
...(payload.action === 'edit' && ...(payload.action === 'edit' &&
pick(editItemCategory, Object.keys(initialValues))) pick(itemCategory, Object.keys(initialValues)))
}, },
validationSchema: ValidationSchema, validationSchema: ValidationSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
@@ -104,15 +125,13 @@ function ItemCategoryDialog({
); );
}, []); }, []);
// Handle the dialog closing.
const handleClose = useCallback(() => { closeDialog(name); }, [name, closeDialog]); const handleClose = useCallback(() => { closeDialog(name); }, [name, closeDialog]);
const fetchHook = useAsync(async () => { // Handle the dialog opening.
await Promise.all([ const onDialogOpening = useCallback(() => {
requestFetchItemCategories(), fetchList.refetch();
]); }, [fetchList]);
}, false);
const onDialogOpening = useCallback(() => { fetchHook.execute(); }, [fetchHook]);
const onChangeParentCategory = useCallback((parentCategory) => { const onChangeParentCategory = useCallback((parentCategory) => {
setParentCategory(parentCategory); setParentCategory(parentCategory);
@@ -132,14 +151,14 @@ function ItemCategoryDialog({
name={name} name={name}
title={payload.action === 'edit' ? 'Edit Category' : ' New Category'} title={payload.action === 'edit' ? 'Edit Category' : ' New Category'}
className={classNames({ className={classNames({
'dialog--loading': fetchHook.pending, 'dialog--loading': fetchList.isFetching,
}, },
'dialog--category-form', 'dialog--category-form',
)} )}
isOpen={isOpen} isOpen={isOpen}
onClosed={onDialogClosed} onClosed={onDialogClosed}
onOpening={onDialogOpening} onOpening={onDialogOpening}
isLoading={fetchHook.pending} isLoading={fetchList.isFetching}
onClose={handleClose} onClose={handleClose}
> >
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
@@ -172,7 +191,7 @@ function ItemCategoryDialog({
intent={(errors.parent_category_id && touched.parent_category_id) && Intent.DANGER} intent={(errors.parent_category_id && touched.parent_category_id) && Intent.DANGER}
> >
<Select <Select
items={Object.values(categories)} items={categoriesList}
noResults={<MenuItem disabled={true} text='No results.' />} noResults={<MenuItem disabled={true} text='No results.' />}
itemRenderer={parentCategoryItem} itemRenderer={parentCategoryItem}
itemPredicate={filterItemCategory} itemPredicate={filterItemCategory}
@@ -214,8 +233,25 @@ function ItemCategoryDialog({
); );
} }
export default compose(
ItemsCategoryConnect, const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'item-category-form');
return {
name: 'account-form',
payload: {action: 'new', id: null, ...dialogPayload},
itemCategoryId: dialogPayload?.id || null,
};
};
const withItemCategoryDialog = connect(mapStateToProps);
export default compose(
DialogConnect, DialogConnect,
DialogReduxConnect DialogReduxConnect,
withItemCategoryDialog,
withItemCategoryDetail,
withItemCategories,
withItemCategoriesActions
)(ItemCategoryDialog); )(ItemCategoryDialog);

View File

@@ -32,8 +32,7 @@ function ExpenseFormContainer({
}); });
return ( return (
<DashboardInsider isLoading={fetchHook.loading} name={'expense-form'}> <DashboardInsider isLoading={fetchHook.loading} name={'expense-form'}>
<ExpenseForm {...{submitExpense, editExpense, accounts, currencies} } /> <ExpenseForm {...{submitExpense, editExpense, accounts, currencies} } />
</DashboardInsider> </DashboardInsider>
); );
} }

View File

@@ -1,5 +1,5 @@
import React, {useMemo, useCallback} from 'react'; import React, {useMemo, useCallback} from 'react';
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import {Row, Col} from 'react-grid-system'; import {Row, Col} from 'react-grid-system';
import { import {
Button, Button,
@@ -12,7 +12,7 @@ import moment from 'moment';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange'; import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import SelectDisplayColumnsBy from '../SelectDisplayColumnsBy'; import SelectDisplayColumnsBy from '../SelectDisplayColumnsBy';
import RadiosAccountingBasis from '../RadiosAccountingBasis'; import RadiosAccountingBasis from '../RadiosAccountingBasis';

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useCallback, useState, useMemo } from 'react'; import React, { useEffect, useCallback, useState, useMemo } from 'react';
import moment from 'moment'; import moment from 'moment';
import GeneralLedgerTable from 'containers/Dashboard/FinancialStatements/GeneralLedger/GeneralLedgerTable'; import GeneralLedgerTable from 'containers/FinancialStatements/GeneralLedger/GeneralLedgerTable';
import useAsync from 'hooks/async'; import useAsync from 'hooks/async';
import DashboardConnect from 'connectors/Dashboard.connector'; import DashboardConnect from 'connectors/Dashboard.connector';
import GeneralLedgerConnect from 'connectors/GeneralLedgerSheet.connect'; import GeneralLedgerConnect from 'connectors/GeneralLedgerSheet.connect';

View File

@@ -1,5 +1,5 @@
import React, {useState, useMemo, useEffect, useCallback} from 'react'; import React, {useState, useMemo, useEffect, useCallback} from 'react';
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import { import {
Button, Button,
@@ -15,7 +15,7 @@ import AccountsConnect from 'connectors/Accounts.connector'
import classNames from 'classnames'; import classNames from 'classnames';
import AccountsMultiSelect from 'components/AccountsMultiSelect'; import AccountsMultiSelect from 'components/AccountsMultiSelect';
import {useFormik} from 'formik'; import {useFormik} from 'formik';
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange'; import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import * as Yup from 'yup'; import * as Yup from 'yup';
import RadiosAccountingBasis from '../RadiosAccountingBasis'; import RadiosAccountingBasis from '../RadiosAccountingBasis';

View File

@@ -1,7 +1,7 @@
import React, {useState, useCallback, useEffect, useMemo} from 'react'; import React, {useState, useCallback, useEffect, useMemo} from 'react';
import {compose} from 'utils'; import {compose} from 'utils';
import JournalConnect from 'connectors/Journal.connect'; import JournalConnect from 'connectors/Journal.connect';
import JournalHeader from 'containers/Dashboard/FinancialStatements/Journal/JournalHeader'; import JournalHeader from 'containers/FinancialStatements/Journal/JournalHeader';
import useAsync from 'hooks/async'; import useAsync from 'hooks/async';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import moment from 'moment'; import moment from 'moment';

View File

@@ -8,8 +8,8 @@ import moment from 'moment';
import {useFormik} from 'formik'; import {useFormik} from 'formik';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import * as Yup from 'yup'; import * as Yup from 'yup';
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange'; import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
export default function JournalHeader({ export default function JournalHeader({

View File

@@ -7,8 +7,8 @@ import moment from 'moment';
import {useFormik} from 'formik'; import {useFormik} from 'formik';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import * as Yup from 'yup'; import * as Yup from 'yup';
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange'; import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import SelectsListColumnsBy from '../SelectDisplayColumnsBy'; import SelectsListColumnsBy from '../SelectDisplayColumnsBy';
import RadiosAccountingBasis from '../RadiosAccountingBasis'; import RadiosAccountingBasis from '../RadiosAccountingBasis';

View File

@@ -1,5 +1,5 @@
import React, {useState, useCallback, useMemo} from 'react'; import React, {useState, useCallback, useMemo} from 'react';
import FinancialStatementHeader from 'containers/Dashboard/FinancialStatements/FinancialStatementHeader'; import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
import {Row, Col} from 'react-grid-system'; import {Row, Col} from 'react-grid-system';
import { import {
Button, Button,
@@ -17,7 +17,7 @@ import {useIntl} from 'react-intl';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import FinancialStatementDateRange from 'containers/Dashboard/FinancialStatements/FinancialStatementDateRange'; import FinancialStatementDateRange from 'containers/FinancialStatements/FinancialStatementDateRange';
export default function TrialBalanceSheetHeader({ export default function TrialBalanceSheetHeader({
pageFilter, pageFilter,

View File

@@ -16,4 +16,5 @@ const mapActionsToProps = (dispatch) => ({
type: t.CHANGE_DASHBOARD_PAGE_TITLE, pageTitle type: t.CHANGE_DASHBOARD_PAGE_TITLE, pageTitle
}), }),
}); });
export default connect(null, mapActionsToProps)(DashboardHomepage); export default connect(null, mapActionsToProps)(DashboardHomepage);

View File

@@ -0,0 +1,53 @@
import React, { useEffect, useState, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import useAsync from 'hooks/async';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import ItemCategoriesDataTable from 'containers/Items/ItemCategoriesTable';
import ItemsCategoryActionsBar from 'containers/Items/ItemsCategoryActionsBar';
import withDashboardActions from 'containers/Dashboard/withDashboard';
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
import withItemCategories from 'containers/Items/withItemCategories';
import { compose } from 'utils';
const ItemCategoryList = ({
changePageTitle,
requestFetchItemCategories,
}) => {
const { id } = useParams();
const [selectedRows, setSelectedRows] = useState([]);
useEffect(() => {
id
? changePageTitle('Edit Category Details')
: changePageTitle('Category List');
}, []);
const fetchCategories = useAsync(() => {
return Promise.all([
requestFetchItemCategories(),
]);
});
const handleFilterChanged = useCallback(() => {
}, []);
return (
<DashboardInsider name={'item-category-list'}>
<ItemsCategoryActionsBar
onFilterChanged={handleFilterChanged}
selectedRows={selectedRows} />
<ItemCategoriesDataTable />
</DashboardInsider>
);
};
export default compose(
withDashboardActions,
withItemCategoriesActions,
withItemCategories,
)(ItemCategoryList);

View File

@@ -188,6 +188,8 @@ const ItemForm = ({
}); });
}, [setDeletedFiles, deletedFiles]); }, [setDeletedFiles, deletedFiles]);
const handleCancelClickBtn = () => { history.goBack(); };
return ( return (
<div class='item-form'> <div class='item-form'>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
@@ -442,7 +444,7 @@ const ItemForm = ({
</Button> </Button>
<Button className={'ml1'}>Save as Draft</Button> <Button className={'ml1'}>Save as Draft</Button>
<Button className={'ml1'} onClick={'handleClose'}>Close</Button> <Button className={'ml1'} onClick={handleCancelClickBtn}>Close</Button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -0,0 +1,52 @@
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useQuery } from 'react-query';
import ItemForm from 'containers/Items/ItemForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withDashboard from 'containers/Dashboard/withDashboard';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
import { compose } from 'utils';
const ItemFormContainer = ({
// #withDashboard
changePageTitle,
// #withAccountsActions
requestFetchAccounts,
// #withItemCategoriesActions
requestFetchItemCategories,
}) => {
const { id } = useParams();
useEffect(() => {
id ?
changePageTitle('Edit Item Details') :
changePageTitle('New Item');
}, [id, changePageTitle]);
const fetchAccounts = useQuery('accounts-list',
(key) => requestFetchAccounts());
const fetchCategories = useQuery('item-categories-list',
(key) => requestFetchItemCategories());
return (
<DashboardInsider
loading={fetchAccounts.isFetching || fetchCategories.isFetching}
name={'item-form'}>
<ItemForm />
</DashboardInsider>
);
};
export default compose(
withDashboard,
withAccountsActions,
withItemCategoriesActions,
)(ItemFormContainer);

View File

@@ -1,8 +1,6 @@
import React, { useMemo, useCallback, useState } from 'react'; import React, { useMemo, useCallback, useState } from 'react';
import { useRouteMatch, useHistory } from 'react-router-dom'; import { useRouteMatch, useHistory } from 'react-router-dom';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import classNames from 'classnames';
import { compose } from 'utils';
import { import {
MenuItem, MenuItem,
Popover, Popover,
@@ -15,43 +13,42 @@ import {
Classes, Classes,
Intent, Intent,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import classNames from 'classnames'; import { compose } from 'utils';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import DashboardConnect from 'connectors/Dashboard.connector';
import ResourceConnect from 'connectors/Resource.connector';
import FilterDropdown from 'components/FilterDropdown'; import FilterDropdown from 'components/FilterDropdown';
import ItemsConnect from 'connectors/Items.connect';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withItems from 'containers/Items/withItems';
import { If } from 'components';
const ItemsActionsBar = ({ const ItemsActionsBar = ({
openDialog, openDialog,
getResourceFields,
getResourceViews, resourceName = 'items',
views, resourceFields,
itemsViews,
onFilterChanged, onFilterChanged,
addItemsTableQueries,
selectedRows = [], selectedRows = [],
}) => { }) => {
const { path } = useRouteMatch(); const { path } = useRouteMatch();
const history = useHistory(); const history = useHistory();
const [filterCount, setFilterCount] = useState(0); const [filterCount, setFilterCount] = useState(0);
const viewsMenuItems = views.map(view => const viewsMenuItems = itemsViews.map(view =>
(<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />)); (<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />));
const onClickNewItem = () => { const onClickNewItem = () => {
history.push('/dashboard/items/new'); history.push('/dashboard/items/new');
}; };
const itemsFields = getResourceFields('items');
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]); const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
const filterDropdown = FilterDropdown({ const filterDropdown = FilterDropdown({
fields: itemsFields, fields: resourceFields,
onFilterChange: (filterConditions) => { onFilterChange: (filterConditions) => {
setFilterCount(filterConditions.length); setFilterCount(filterConditions.length);
addItemsTableQueries({
filter_roles: filterConditions || '',
});
onFilterChanged && onFilterChanged(filterConditions); onFilterChanged && onFilterChanged(filterConditions);
} }
}); });
@@ -105,14 +102,14 @@ const ItemsActionsBar = ({
/> />
</Popover> </Popover>
{hasSelectedRows && ( <If condition={hasSelectedRows}>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
intent={Intent.DANGER} intent={Intent.DANGER}
icon={<Icon icon='trash' />} icon={<Icon icon='trash' />}
text='Delete' text='Delete'
/> />
)} </If>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
@@ -131,7 +128,6 @@ const ItemsActionsBar = ({
export default compose( export default compose(
DialogConnect, DialogConnect,
DashboardConnect, withItems,
ResourceConnect, withResourceDetail,
ItemsConnect
)(ItemsActionsBar); )(ItemsActionsBar);

View File

@@ -11,19 +11,24 @@ import {
PopoverInteractionKind, PopoverInteractionKind,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { connect } from 'react-redux';
import { If } from 'components';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import DashboardConnect from 'connectors/Dashboard.connector';
import ItemsCategoryConnect from 'connectors/ItemsCategory.connect';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import FilterDropdown from 'components/FilterDropdown'; import FilterDropdown from 'components/FilterDropdown';
import ResourceConnect from 'connectors/Resource.connector';
import withResourceDetail from 'containers/Resources/withResourceDetails';
import withDashboard from 'containers/Dashboard/withDashboard';
const ItemsCategoryActionsBar = ({ const ItemsCategoryActionsBar = ({
resourceName = 'item_category',
resourceFields,
openDialog, openDialog,
onDeleteCategory, onDeleteCategory,
onFilterChanged, onFilterChanged,
getResourceFields,
selectedRows, selectedRows,
}) => { }) => {
const onClickNewCategory = useCallback(() => { const onClickNewCategory = useCallback(() => {
@@ -34,11 +39,10 @@ const ItemsCategoryActionsBar = ({
onDeleteCategory(selectedRows); onDeleteCategory(selectedRows);
}, [selectedRows, onDeleteCategory]); }, [selectedRows, onDeleteCategory]);
const categoriesFields = getResourceFields('itemCategories');
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]); const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
const filterDropdown = FilterDropdown({ const filterDropdown = FilterDropdown({
fields: categoriesFields, fields: resourceFields,
onFilterChange: (filterConditions) => { onFilterChange: (filterConditions) => {
onFilterChanged && onFilterChanged(filterConditions); onFilterChanged && onFilterChanged(filterConditions);
}, },
@@ -53,6 +57,7 @@ const ItemsCategoryActionsBar = ({
onClick={onClickNewCategory} onClick={onClickNewCategory}
/> />
<Popover <Popover
minimal={true}
content={filterDropdown} content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK} interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT} position={Position.BOTTOM_LEFT}
@@ -64,7 +69,7 @@ const ItemsCategoryActionsBar = ({
/> />
</Popover> </Popover>
{ hasSelectedRows && ( <If condition={hasSelectedRows}>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='trash' iconSize={15} />} icon={<Icon icon='trash' iconSize={15} />}
@@ -72,7 +77,8 @@ const ItemsCategoryActionsBar = ({
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handleDeleteCategory} onClick={handleDeleteCategory}
/> />
)} </If>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='file-import' />} icon={<Icon icon='file-import' />}
@@ -88,9 +94,15 @@ const ItemsCategoryActionsBar = ({
); );
}; };
const mapStateToProps = (state, props) => ({
resourceName: 'items_categories',
});
const withItemsCategoriesActionsBar = connect(mapStateToProps);
export default compose( export default compose(
withItemsCategoriesActionsBar,
DialogConnect, DialogConnect,
DashboardConnect, withDashboard,
ItemsCategoryConnect, withResourceDetail
ResourceConnect
)(ItemsCategoryActionsBar); )(ItemsCategoryActionsBar);

View File

@@ -7,17 +7,21 @@ import {
MenuDivider, MenuDivider,
Position, Position,
} from '@blueprintjs/core' } from '@blueprintjs/core'
import CustomViewConnect from 'connectors/View.connector';
import ItemsConnect from 'connectors/Items.connect';
import {compose} from 'utils'; import {compose} from 'utils';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import Money from 'components/Money'; import Money from 'components/Money';
import withItems from 'containers/Items/withItems';
import LoadingIndicator from 'components/LoadingIndicator';
const ItemsDataTable = ({ const ItemsDataTable = ({
loading,
itemsTableLoading, itemsTableLoading,
currentPageItems, itemsCurrentPage,
// props
onEditItem, onEditItem,
onDeleteItem, onDeleteItem,
onFetchData, onFetchData,
@@ -112,18 +116,19 @@ const ItemsDataTable = ({
}, [onSelectedRowsChange]); }, [onSelectedRowsChange]);
return ( return (
<DataTable <LoadingIndicator loading={loading} mount={false}>
columns={columns} <DataTable
data={currentPageItems} columns={columns}
selectionColumn={selectionColumn} data={itemsCurrentPage}
onFetchData={handleFetchData} selectionColumn={selectionColumn}
loading={itemsTableLoading && !initialMount} onFetchData={handleFetchData}
noInitialFetch={true} loading={itemsTableLoading && !initialMount}
onSelectedRowsChange={handleSelectedRowsChange} /> noInitialFetch={true}
onSelectedRowsChange={handleSelectedRowsChange} />
</LoadingIndicator>
); );
}; };
export default compose( export default compose(
ItemsConnect, withItems,
CustomViewConnect,
)(ItemsDataTable); )(ItemsDataTable);

View File

@@ -8,47 +8,58 @@ import {
Alert, Alert,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import useAsync from 'hooks/async'; import { useQuery } from 'react-query';
import ItemsActionsBar from 'containers/Dashboard/Items/ItemsActionsBar'; import ItemsActionsBar from 'containers/Items/ItemsActionsBar';
import { compose } from 'utils'; import { compose } from 'utils';
import ItemsDataTable from './ItemsDataTable'; import ItemsDataTable from './ItemsDataTable';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import ResourceConnect from 'connectors/Resource.connector';
import DashboardConnect from 'connectors/Dashboard.connector'; import ItemsViewsTabs from 'containers/Items/ItemsViewsTabs';
import ItemsConnect from 'connectors/Items.connect';
import CustomViewsConnect from 'connectors/CustomView.connector'
import ItemsViewsTabs from 'containers/Dashboard/Items/ItemsViewsTabs';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import withItems from 'containers/Items/withItems';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withDashboardActions from 'containers/Dashboard/withDashboard';
import withItemsActions from 'containers/Items/withItemsActions';
import withViewsActions from 'containers/Views/withViewsActions';
function ItemsList({ function ItemsList({
// #withDashboard
changePageTitle, changePageTitle,
fetchResourceViews,
fetchResourceFields, // #withResourceActions
views, requestFetchResourceViews,
requestFetchResourceFields,
// #withItems
itemsViews,
itemsCurrentPage,
itemsTableQuery,
// #withItemsActions
requestDeleteItem, requestDeleteItem,
requestFetchItems, requestFetchItems,
addItemsTableQueries, addItemsTableQueries,
changeItemsCurrentView
}) { }) {
const [deleteItem, setDeleteItem] = useState(false); const [deleteItem, setDeleteItem] = useState(false);
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const [tableLoading, setTableLoading] = useState(false);
useEffect(() => { useEffect(() => {
changePageTitle('Items List'); changePageTitle('Items List');
}, [changePageTitle]); }, [changePageTitle]);
const fetchHook = useAsync(async () => { const fetchHook = useQuery('items-resource', () => {
await Promise.all([ return Promise.all([
fetchResourceViews('items'), requestFetchResourceViews('items'),
fetchResourceFields('items'), requestFetchResourceFields('items'),
]); ]);
}); });
const fetchItems = useAsync(async () => { const fetchItems = useQuery(['items-table', itemsTableQuery],
await Promise.all([ () => requestFetchItems({}));
requestFetchItems({ }),
])
});
// Handle click delete item. // Handle click delete item.
const handleDeleteItem = useCallback((item) => { const handleDeleteItem = useCallback((item) => {
@@ -70,6 +81,7 @@ function ItemsList({
}); });
}, [requestDeleteItem, deleteItem]); }, [requestDeleteItem, deleteItem]);
// Handle fetch data table.
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => { const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => {
addItemsTableQueries({ addItemsTableQueries({
...(sortBy.length > 0) ? { ...(sortBy.length > 0) ? {
@@ -77,30 +89,40 @@ function ItemsList({
sort_order: sortBy[0].desc ? 'desc' : 'asc', sort_order: sortBy[0].desc ? 'desc' : 'asc',
} : {}, } : {},
}); });
fetchItems.execute();
}, [fetchItems, addItemsTableQueries]); }, [fetchItems, addItemsTableQueries]);
// Handle filter change to re-fetch the items. // Handle filter change to re-fetch the items.
const handleFilterChanged = useCallback(() => { const handleFilterChanged = useCallback((filterConditions) => {
fetchItems.execute(); addItemsTableQueries({
filter_roles: filterConditions || '',
});
}, [fetchItems]); }, [fetchItems]);
// Handle custom view change to re-fetch the items. // Handle custom view change to re-fetch the items.
const handleCustomViewChanged = useCallback(() => { const handleCustomViewChanged = useCallback((customViewId) => {
fetchItems.execute(); setTableLoading(true);
}, [fetchItems]); }, [fetchItems]);
useEffect(() => {
if (tableLoading && !fetchItems.isFetching) {
setTableLoading(false);
}
}, [tableLoading, fetchItems.isFetching]);
// Handle selected rows change. // Handle selected rows change.
const handleSelectedRowsChange = useCallback((accounts) => { const handleSelectedRowsChange = useCallback((accounts) => {
setSelectedRows(accounts); setSelectedRows(accounts);
}, [setSelectedRows]); }, [setSelectedRows]);
return ( return (
<DashboardInsider isLoading={fetchHook.pending} name={'items-list'}> <DashboardInsider
isLoading={fetchHook.isFetching}
name={'items-list'}>
<ItemsActionsBar <ItemsActionsBar
onFilterChanged={handleFilterChanged} onFilterChanged={handleFilterChanged}
selectedRows={selectedRows} selectedRows={selectedRows}
views={views} /> views={itemsViews} />
<DashboardPageContent> <DashboardPageContent>
<Switch> <Switch>
@@ -111,9 +133,11 @@ function ItemsList({
'/dashboard/items' '/dashboard/items'
]}> ]}>
<ItemsViewsTabs <ItemsViewsTabs
itemsViews={itemsViews}
onViewChanged={handleCustomViewChanged} /> onViewChanged={handleCustomViewChanged} />
<ItemsDataTable <ItemsDataTable
loading={tableLoading}
onDeleteItem={handleDeleteItem} onDeleteItem={handleDeleteItem}
onEditItem={handleEditItem} onEditItem={handleEditItem}
onFetchData={handleFetchData} onFetchData={handleFetchData}
@@ -140,8 +164,9 @@ function ItemsList({
} }
export default compose( export default compose(
DashboardConnect, withItems,
ResourceConnect, withResourceActions,
ItemsConnect, withDashboardActions,
CustomViewsConnect, withItemsActions,
withViewsActions,
)(ItemsList); )(ItemsList);

Some files were not shown because too many files have changed in this diff Show More