WIP optimize connect with redux state in preferences pages.

This commit is contained in:
Ahmed Bouhuolia
2020-05-10 23:22:36 +02:00
parent a0653674ff
commit 464c36d532
32 changed files with 403 additions and 327 deletions

View File

@@ -13,6 +13,7 @@ import classnames from 'classnames';
import { FixedSizeList } from 'react-window' import { FixedSizeList } from 'react-window'
import { ConditionalWrapper } from 'utils'; import { ConditionalWrapper } from 'utils';
import { useUpdateEffect } from 'hooks'; import { useUpdateEffect } from 'hooks';
import { If } from 'components';
const IndeterminateCheckbox = React.forwardRef( const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, ...rest }, ref) => { ({ indeterminate, ...rest }, ref) => {
@@ -257,16 +258,19 @@ export default function DataTable({
))} ))}
</div> </div>
<div {...getTableBodyProps()} className="tbody"> <div {...getTableBodyProps()} className="tbody">
{ !loading && RenderTBody() } <If condition={!loading}>
{ RenderTBody() }
</If>
{ !loading && (page.length === 0) && ( <If condition={!loading && (page.length === 0)}>
<div className={'tr no-results'}> <div className={'tr no-results'}>
<div class="td">{ noResults }</div> <div class="td">{ noResults }</div>
</div> </div>
)} </If>
{ loading && (
<If condition={loading}>
<div class="loading"><Spinner size={spinnerProps.size} /></div> <div class="loading"><Spinner size={spinnerProps.size} /></div>
) } </If>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,8 +1,10 @@
import React from 'react'; import React from 'react';
import PreferencesTopbar from 'components/Preferences/PreferencesTopbar'; import PreferencesTopbar from 'components/Preferences/PreferencesTopbar';
import PreferencesContentRoute from 'components/Preferences/PreferencesContentRoute'; import PreferencesContentRoute from 'components/Preferences/PreferencesContentRoute';
export default function () {
export default function PreferencesContent() {
return ( return (
<div className='dashboard-content dashboard-content--preferences'> <div className='dashboard-content dashboard-content--preferences'>
<PreferencesTopbar pageTitle={'asdad'} /> <PreferencesTopbar pageTitle={'asdad'} />

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom'; import { Route, Switch, Redirect } from 'react-router-dom';
import preferencesRoutes from 'routes/preferences' import preferencesRoutes from 'routes/preferences'
export default function DashboardContentRoute() { export default function DashboardContentRoute() {
const defaultTab = '/dashboard/preferences/general'; const defaultTab = '/dashboard/preferences/general';

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar'; import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
export default function PreferencesPage() { export default function PreferencesPage() {
return ( return (
<div class="preferences-page"> <div class="preferences-page">

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/Preferences/UsersActions'; import UsersActions from 'containers/Preferences/Users/UsersActions';
import CurrenciesActions from 'containers/Preferences/CurrenciesActions'; import CurrenciesActions from 'containers/Preferences/Currencies/CurrenciesActions';
export default function PreferencesTopbar() { export default function PreferencesTopbar() {

View File

@@ -1,41 +0,0 @@
import { connect } from 'react-redux';
import {
deleteManualJournal,
fetchManualJournalsTable,
publishManualJournal,
deleteBulkManualJournals,
} from 'store/manualJournals/manualJournals.actions';
import { getResourceViews } from 'store/customViews/customViews.selectors';
import t from 'store/types';
import {
getManualJournalsItems,
} from 'store/manualJournals/manualJournals.selectors'
const mapStateToProps = (state, props) => ({
views: getResourceViews(state, 'manual_journals'),
manualJournals: getManualJournalsItems(state, state.manualJournals.currentViewId),
manualJournalsItems: state.manualJournals.items,
tableQuery: state.manualJournals.tableQuery,
manualJournalsLoading: state.manualJournals.loading,
});
const mapActionsToProps = (dispatch) => ({
requestDeleteManualJournal: (id) => dispatch(deleteManualJournal({ id })),
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,
}),
fetchManualJournalsTable: (query = {}) =>
dispatch(fetchManualJournalsTable({ query: { ...query } })),
requestPublishManualJournal: (id) => dispatch(publishManualJournal({ id })),
requestDeleteBulkManualJournals: (ids) => dispatch(deleteBulkManualJournals({ ids })),
});
export default connect(mapStateToProps, mapActionsToProps);

View File

@@ -0,0 +1,13 @@
import { connect } from 'react-redux';
export default (mapState) => {
const mapStateToProps = (state, props) => {
const mapped = {
currencies: state.currencies.data,
currenciesList: Object.values(state.currencies.data),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
}

View File

@@ -0,0 +1,17 @@
import { connect } from 'react-redux';
import {
fetchCurrencies,
submitCurrencies,
deleteCurrency,
editCurrency,
} from 'store/currencies/currencies.actions';
export const mapDispatchToProps = (dispatch) => ({
requestFetchCurrencies: () => dispatch(fetchCurrencies({})),
requestSubmitCurrencies: (form) => dispatch(submitCurrencies({ form })),
requestEditCurrency: (id, form) => dispatch(editCurrency({ id, form })),
requestDeleteCurrency: (currency_code) => dispatch(deleteCurrency({ currency_code })),
});
export default connect(null, mapDispatchToProps);

View File

@@ -0,0 +1,16 @@
import { connect } from 'react-redux';
import {
getCurrencyById,
getCurrencyByCode,
} from 'store/currencies/currencies.selector';
const mapStateToProps = (state, props) => ({
...(props.currencyId) ? {
currency: getCurrencyById(state.currencies.data, props.currencyId),
} : (props.currencyCode) ? {
currency: getCurrencyByCode(state.currencies.data, props.currencyCode),
} : {},
});
export default connect(mapStateToProps);

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useCallback } from 'react'; import React, { useMemo, useCallback } from 'react';
import { import {
Button, Button,
Classes, Classes,
@@ -9,26 +9,40 @@ import {
import * as Yup from 'yup'; import * as Yup from 'yup';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useQuery } from 'react-query';
import { connect } from 'react-redux';
import { compose } from 'utils'; import { compose } from 'utils';
import Dialog from 'components/Dialog'; import Dialog from 'components/Dialog';
import useAsync from 'hooks/async';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import DialogReduxConnect from 'components/DialogReduxConnect'; import DialogReduxConnect from 'components/DialogReduxConnect';
import CurrencyFromDialogConnect from 'connectors/CurrencyFromDialog.connect'; import withCurrency from 'containers/Currencies/withCurrency';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import classNames from 'classnames'; import classNames from 'classnames';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { getDialogPayload } from 'store/dashboard/dashboard.reducer';
function CurrencyDialog({ function CurrencyDialog({
name, name,
payload, payload,
isOpen, isOpen,
// #withDialog
closeDialog, closeDialog,
// #withCurrency
currencyCode,
currency,
// #wihtCurrenciesActions
requestFetchCurrencies, requestFetchCurrencies,
requestSubmitCurrencies, requestSubmitCurrencies,
requestEditCurrency, requestEditCurrency,
editCurrency,
}) { }) {
const intl = useIntl(); const intl = useIntl();
@@ -40,30 +54,34 @@ function CurrencyDialog({
.max(4) .max(4)
.required(intl.formatMessage({ id: 'required' })), .required(intl.formatMessage({ id: 'required' })),
}); });
const initialValues = useMemo( const initialValues = useMemo(() => ({
() => ({
currency_name: '', currency_name: '',
currency_code: '', currency_code: '',
}), }), []);
[]
);
const formik = useFormik({ const {
values,
errors,
touched,
setFieldValue,
getFieldProps,
isSubmitting,
handleSubmit,
resetForm,
} = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
...(payload.action === 'edit' && ...(payload.action === 'edit' &&
pick(editCurrency, Object.keys(initialValues))), pick(currency, Object.keys(initialValues))),
}, },
validationSchema: ValidationSchema, validationSchema: ValidationSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
if (payload.action === 'edit') { if (payload.action === 'edit') {
requestEditCurrency(editCurrency.id, values) requestEditCurrency(currency.id, values).then((response) => {
.then((response) => {
closeDialog(name); closeDialog(name);
AppToaster.show({ AppToaster.show({
message: 'the_currency_has_been_edited', message: 'the_currency_has_been_edited',
intent: Intent.SUCCESS,
}); });
setSubmitting(false); setSubmitting(false);
}) })
@@ -71,11 +89,11 @@ function CurrencyDialog({
setSubmitting(false); setSubmitting(false);
}); });
} else { } else {
requestSubmitCurrencies(values) requestSubmitCurrencies(values).then((response) => {
.then((response) => {
closeDialog(name); closeDialog(name);
AppToaster.show({ AppToaster.show({
message: 'the_currency_has_been_submit', message: 'the_currency_has_been_submit',
intent: Intent.SUCCESS,
}); });
setSubmitting(false); setSubmitting(false);
}) })
@@ -86,24 +104,22 @@ function CurrencyDialog({
}, },
}); });
const { values, errors, touched } = useMemo(() => formik, [formik]);
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
closeDialog(name); closeDialog(name);
}, [name, closeDialog]); }, [name, closeDialog]);
const fetchHook = useAsync(async () => { const fetchCurrencies = useQuery('currencies',
await Promise.all([requestFetchCurrencies()]); () => { requestFetchCurrencies(); },
}); { manual: true });
const onDialogOpening = useCallback(() => { const onDialogOpening = useCallback(() => {
fetchHook.execute(); fetchCurrencies.refetch();
}, [fetchHook]); }, [fetchCurrencies]);
const onDialogClosed = useCallback(() => { const onDialogClosed = useCallback(() => {
formik.resetForm(); resetForm();
closeDialog(name); closeDialog(name);
}, [formik, closeDialog, name]); }, [closeDialog, name]);
const requiredSpan = useMemo(() => <span className={'required'}>*</span>, []); const requiredSpan = useMemo(() => <span className={'required'}>*</span>, []);
@@ -113,59 +129,53 @@ function CurrencyDialog({
title={payload.action === 'edit' ? 'Edit Currency' : ' New Currency'} title={payload.action === 'edit' ? 'Edit Currency' : ' New Currency'}
className={classNames( className={classNames(
{ {
'dialog--loading': fetchHook.pending, 'dialog--loading': fetchCurrencies.isFetching,
}, },
'dialog--currency-form' 'dialog--currency-form'
)} )}
isOpen={isOpen} isOpen={isOpen}
onClosed={onDialogClosed} onClosed={onDialogClosed}
onOpening={onDialogOpening} onOpening={onDialogOpening}
isLoading={fetchHook.pending} isLoading={fetchCurrencies.isFetching}
onClose={handleClose} onClose={handleClose}
> >
<form onSubmit={formik.handleSubmit}> <form onSubmit={handleSubmit}>
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<FormGroup <FormGroup
label={'Currency Name'} label={'Currency Name'}
labelInfo={requiredSpan} labelInfo={requiredSpan}
className={'form-group--currency-name'} className={'form-group--currency-name'}
intent={ intent={(errors.currency_name && touched.currency_name) && Intent.DANGER}
errors.currency_name && touched.currency_name && Intent.DANGER helperText={<ErrorMessage name='currency_name' {...{errors, touched}} />}
}
helperText={<ErrorMessage name='currency_name' {...formik} />}
inline={true} inline={true}
> >
<InputGroup <InputGroup
medium={true} medium={true}
intent={ intent={(errors.currency_name && touched.currency_name) && Intent.DANGER}
errors.currency_name && touched.currency_name && Intent.DANGER {...getFieldProps('currency_name')}
}
{...formik.getFieldProps('currency_name')}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={'Currency Code'} label={'Currency Code'}
labelInfo={requiredSpan} labelInfo={requiredSpan}
className={'form-group--currency-code'} className={'form-group--currency-code'}
intent={ intent={(errors.currency_code && touched.currency_code) && Intent.DANGER}
errors.currency_code && touched.currency_code && Intent.DANGER helperText={<ErrorMessage name='currency_code' {...{errors, touched}} />}
}
helperText={<ErrorMessage name='currency_code' {...formik} />}
inline={true} inline={true}
> >
<InputGroup <InputGroup
medium={true} medium={true}
intent={ intent={(errors.currency_code && touched.currency_code) && Intent.DANGER}
errors.currency_code && touched.currency_code && Intent.DANGER {...getFieldProps('currency_code')}
}
{...formik.getFieldProps('currency_code')}
/> />
</FormGroup> </FormGroup>
</div> </div>
<div className={Classes.DIALOG_FOOTER}> <div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}> <div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleClose}>Close</Button> <Button onClick={handleClose}>Close</Button>
<Button intent={Intent.PRIMARY} type='submit'> <Button intent={Intent.PRIMARY} type='submit' disabled={isSubmitting}>
{payload.action === 'edit' ? 'Edit' : 'Submit'} {payload.action === 'edit' ? 'Edit' : 'Submit'}
</Button> </Button>
</div> </div>
@@ -175,8 +185,22 @@ function CurrencyDialog({
); );
} }
const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'currency-form');
return {
name: 'currency-form',
payload: { action: 'new', currencyCode: null, ...dialogPayload },
currencyCode: dialogPayload?.currencyCode || null,
}
}
const withCurrencyFormDialog = connect(mapStateToProps);
export default compose( export default compose(
CurrencyFromDialogConnect, withCurrencyFormDialog,
DialogConnect, DialogConnect,
DialogReduxConnect DialogReduxConnect,
withCurrenciesActions,
withCurrency,
)(CurrencyDialog); )(CurrencyDialog);

View File

@@ -4,41 +4,53 @@ import {
Popover, Popover,
Menu, Menu,
MenuItem, MenuItem,
MenuDivider,
Position, Position,
Classes,
Tooltip,
Alert, Alert,
Intent, Intent,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useQuery } from 'react-query';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import { snakeCase } from 'lodash';
import { compose } from 'utils'; import { compose } from 'utils';
import CurrencyFromDialogConnect from 'connectors/CurrencyFromDialog.connect';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import DashboardConnect from 'connectors/Dashboard.connector';
import LoadingIndicator from 'components/LoadingIndicator'; import LoadingIndicator from 'components/LoadingIndicator';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import withDashboard from 'connectors/Dashboard.connector';
import withCurrencies from 'containers/Currencies/withCurrencies';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
function CurrenciesList({ function CurrenciesList({
currencies, // #withCurrencies
currenciesList,
// #withCurrenciesActions
requestDeleteCurrency,
requestFetchCurrencies,
// #withDialog
openDialog, openDialog,
onFetchData, onFetchData,
requestDeleteCurrency,
}) { }) {
const [deleteCurrencyState, setDeleteCurrencyState] = useState(false); const [deleteCurrencyState, setDeleteCurrencyState] = useState(false);
const handleEditCurrency = (currency) => () => { const fetchCurrencies = useQuery(['currencies-table'],
() => requestFetchCurrencies());
const handleEditCurrency = (currency) => {
openDialog('currency-form', { openDialog('currency-form', {
action: 'edit', action: 'edit',
currency_code: currency.currency_code, currencyCode: currency.currency_code,
}); });
}; };
const onDeleteCurrency = (currency) => { const onDeleteCurrency = (currency) => {
setDeleteCurrencyState(currency); setDeleteCurrencyState(currency);
}; };
const handleCancelCurrencyDelete = () => { const handleCancelCurrencyDelete = () => {
setDeleteCurrencyState(false); setDeleteCurrencyState(false);
}; };
@@ -54,30 +66,26 @@ function CurrenciesList({
); );
}, [deleteCurrencyState]); }, [deleteCurrencyState]);
const actionMenuList = useCallback( const actionMenuList = useCallback((currency) => (
(currency) => (
<Menu> <Menu>
<MenuItem text='Edit Currency' onClick={handleEditCurrency(currency)} /> <MenuItem
text='Edit Currency'
onClick={() => handleEditCurrency(currency)} />
<MenuItem <MenuItem
text='Delete Currency' text='Delete Currency'
onClick={() => onDeleteCurrency(currency)} onClick={() => onDeleteCurrency(currency)}
/> />
</Menu> </Menu>
), ), []);
[]
);
const columns = useMemo( const columns = useMemo(() => [
() => [
{ {
id: 'currency_name',
Header: 'Currency Name', Header: 'Currency Name',
accessor: 'currency_name', accessor: 'currency_name',
width: 100, width: 100,
}, },
{ {
id: 'currency_code',
Header: 'Currency Code', Header: 'Currency Code',
accessor: 'currency_code', accessor: 'currency_code',
className: 'currency_code', className: 'currency_code',
@@ -101,9 +109,8 @@ function CurrenciesList({
className: 'actions', className: 'actions',
width: 50, width: 50,
}, },
], ], [actionMenuList]);
[actionMenuList]
);
const handleDatatableFetchData = useCallback(() => { const handleDatatableFetchData = useCallback(() => {
onFetchData && onFetchData(); onFetchData && onFetchData();
}, []); }, []);
@@ -112,9 +119,9 @@ function CurrenciesList({
<LoadingIndicator> <LoadingIndicator>
<DataTable <DataTable
columns={columns} columns={columns}
data={Object.values(currencies)} data={currenciesList}
onFetchData={handleDatatableFetchData()} loading={fetchCurrencies.isFetching}
selectionColumn={true} selectionColumn={false}
/> />
<Alert <Alert
@@ -136,7 +143,10 @@ function CurrenciesList({
} }
export default compose( export default compose(
CurrencyFromDialogConnect, withCurrencies(({ currenciesList }) => ({
currenciesList,
})),
withCurrenciesActions,
DialogConnect, DialogConnect,
DashboardConnect withDashboard
)(CurrenciesList); )(CurrenciesList);

View File

@@ -9,27 +9,36 @@ import {
MenuItem, MenuItem,
Classes, Classes,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import classNames from 'classnames';
import { optionsMapToArray, momentFormatter, optionsArrayToMap } from 'utils';
import { TimezonePicker } from '@blueprintjs/timezone'; import { TimezonePicker } from '@blueprintjs/timezone';
import { Select } from '@blueprintjs/select'; import { Select } from '@blueprintjs/select';
import classNames from 'classnames';
import ErrorMessage from 'components/ErrorMessage';
import Icon from 'components/Icon';
import { compose } from 'utils';
import SettingsConnect from 'connectors/Settings.connect';
import AppToaster from 'components/AppToaster';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import useAsync from 'hooks/async'; import { useQuery } from 'react-query';
import { compose, optionsMapToArray } from 'utils';
import ErrorMessage from 'components/ErrorMessage';
import AppToaster from 'components/AppToaster';
import withSettings from 'containers/Settings/withSettings';
import withSettingsActions from 'containers/Settings/withSettingsActions';
function GeneralPreferences({ function GeneralPreferences({
// #withSettings
organizationSettings, organizationSettings,
// #withSettingsActions
requestSubmitOptions, requestSubmitOptions,
requestFetchOptions, requestFetchOptions,
}) { }) {
const intl = useIntl();
const [selectedItems, setSelectedItems] = useState({}); const [selectedItems, setSelectedItems] = useState({});
const [timeZone, setTimeZone] = useState(''); const [timeZone, setTimeZone] = useState('');
const fetchHook = useQuery(['settings'],
() => { requestFetchOptions(); });
const businessLocation = [ const businessLocation = [
{ id: 218, name: 'LIBYA', code: 'LY' }, { id: 218, name: 'LIBYA', code: 'LY' },
{ id: 380, name: 'UKRAINE', code: 'UA' }, { id: 380, name: 'UKRAINE', code: 'UA' },
@@ -55,8 +64,6 @@ function GeneralPreferences({
{ id: 6, name: 'Saturday, Apr 11, 2020', format: 'EEEE, MMM d, yyyy' }, { id: 6, name: 'Saturday, Apr 11, 2020', format: 'EEEE, MMM d, yyyy' },
]; ];
const intl = useIntl();
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' })),
industry: Yup.string().required(intl.formatMessage({ id: 'required' })), industry: Yup.string().required(intl.formatMessage({ id: 'required' })),
@@ -69,15 +76,22 @@ function GeneralPreferences({
// time_zone: Yup.object().required(intl.formatMessage({ id: 'required' })), // time_zone: Yup.object().required(intl.formatMessage({ id: 'required' })),
date_format: Yup.date().required(intl.formatMessage({ id: 'required' })), date_format: Yup.date().required(intl.formatMessage({ id: 'required' })),
}); });
console.log(organizationSettings.name);
const formik = useFormik({ const {
errors,
values,
touched,
isSubmitting,
setFieldValue,
getFieldProps,
handleSubmit,
resetForm,
} = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
...organizationSettings, ...organizationSettings,
}, },
validationSchema: validationSchema, validationSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
const options = optionsMapToArray(values).map((option) => { const options = optionsMapToArray(values).map((option) => {
return { key: option.key, ...option, group: 'organization' }; return { key: option.key, ...option, group: 'organization' };
@@ -96,12 +110,6 @@ function GeneralPreferences({
}, },
}); });
const { errors, values, touched } = useMemo(() => formik, [formik]);
const fetchHook = useAsync(async () => {
await Promise.all([requestFetchOptions()]);
});
const businessLocationItem = (item, { handleClick }) => ( const businessLocationItem = (item, { handleClick }) => (
<MenuItem <MenuItem
className={'preferences-menu'} className={'preferences-menu'}
@@ -156,7 +164,7 @@ function GeneralPreferences({
...selectedItems, ...selectedItems,
[filedName]: filed, [filedName]: filed,
}); });
formik.setFieldValue(filedName, filed.name); setFieldValue(filedName, filed.name);
}; };
}; };
@@ -170,30 +178,30 @@ function GeneralPreferences({
return ( return (
<div className='preferences__inside-content--general'> <div className='preferences__inside-content--general'>
<form onSubmit={formik.handleSubmit}> <form onSubmit={handleSubmit}>
<FormGroup <FormGroup
label={'Organization Name'} label={'Organization Name'}
inline={true} inline={true}
intent={errors.name && touched.name && Intent.DANGER} intent={(errors.name && touched.name) && Intent.DANGER}
helperText={<ErrorMessage name='name' {...formik} />} helperText={<ErrorMessage name='name' {...{errors, touched}} />}
> >
<InputGroup <InputGroup
medium={'true'} medium={'true'}
intent={errors.name && touched.name && Intent.DANGER} intent={(errors.name && touched.name) && Intent.DANGER}
{...formik.getFieldProps('name')} {...getFieldProps('name')}
/> />
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={'Organization Industry'} label={'Organization Industry'}
inline={true} inline={true}
intent={errors.industry && touched.industry && Intent.DANGER} intent={(errors.industry && touched.industry) && Intent.DANGER}
helperText={<ErrorMessage name='industry' {...formik} />} helperText={<ErrorMessage name='industry' {...{errors, touched}} />}
> >
<InputGroup <InputGroup
medium={'true'} medium={'true'}
intent={errors.industry && touched.industry && Intent.DANGER} intent={(errors.industry && touched.industry) && Intent.DANGER}
{...formik.getFieldProps('industry')} {...getFieldProps('industry')}
/> />
</FormGroup> </FormGroup>
@@ -205,8 +213,8 @@ function GeneralPreferences({
Classes.FILL Classes.FILL
)} )}
inline={true} inline={true}
helperText={<ErrorMessage name='location' {...formik} />} helperText={<ErrorMessage name='location' {...{errors, touched}} />}
intent={errors.location && touched.location && Intent.DANGER} intent={(errors.location && touched.location) && Intent.DANGER}
> >
<Select <Select
items={businessLocation} items={businessLocation}
@@ -234,10 +242,8 @@ function GeneralPreferences({
Classes.FILL Classes.FILL
)} )}
inline={true} inline={true}
helperText={<ErrorMessage name='base_currency' {...formik} />} helperText={<ErrorMessage name='base_currency' {...{ errors, touched }} />}
intent={ intent={(errors.base_currency && touched.base_currency) && Intent.DANGER}
errors.base_currency && touched.base_currency && Intent.DANGER
}
> >
<Select <Select
items={currencies} items={currencies}
@@ -265,8 +271,8 @@ function GeneralPreferences({
Classes.FILL Classes.FILL
)} )}
inline={true} inline={true}
helperText={<ErrorMessage name='fiscal_year' {...formik} />} helperText={<ErrorMessage name='fiscal_year' {...{errors, touched}} />}
intent={errors.fiscal_year && touched.fiscal_year && Intent.DANGER} intent={(errors.fiscal_year && touched.fiscal_year) && Intent.DANGER}
> >
<Select <Select
items={fiscalYear} items={fiscalYear}
@@ -294,8 +300,8 @@ function GeneralPreferences({
'form-group--select-list', 'form-group--select-list',
Classes.FILL Classes.FILL
)} )}
intent={errors.language && touched.language && Intent.DANGER} intent={(errors.language && touched.language) && Intent.DANGER}
helperText={<ErrorMessage name='language' {...formik} />} helperText={<ErrorMessage name='language' {...{errors, touched}} />}
> >
<Select <Select
items={languagesDisplay} items={languagesDisplay}
@@ -321,8 +327,8 @@ function GeneralPreferences({
'form-group--select-list', 'form-group--select-list',
Classes.FILL Classes.FILL
)} )}
intent={errors.time_zone && touched.time_zone && Intent.DANGER} intent={(errors.time_zone && touched.time_zone) && Intent.DANGER}
helperText={<ErrorMessage name='time_zone' {...formik} />} helperText={<ErrorMessage name='time_zone' {...{errors, touched}} />}
> >
<TimezonePicker <TimezonePicker
value={timeZone} value={timeZone}
@@ -339,8 +345,8 @@ function GeneralPreferences({
'form-group--select-list', 'form-group--select-list',
Classes.FILL Classes.FILL
)} )}
intent={errors.date_format && touched.date_format && Intent.DANGER} intent={(errors.date_format && touched.date_format) && Intent.DANGER}
helperText={<ErrorMessage name='date_format' {...formik} />} helperText={<ErrorMessage name='date_format' {...{errors, touched}} />}
> >
<Select <Select
items={dateFormat} items={dateFormat}
@@ -374,4 +380,7 @@ function GeneralPreferences({
); );
} }
export default compose(SettingsConnect)(GeneralPreferences); export default compose(
withSettings,
withSettingsActions,
)(GeneralPreferences);

View File

@@ -1,5 +1,5 @@
import React, { useState, useMemo, useCallback } from 'react'; import React, { useState, useMemo, useCallback } from 'react';
import { useAsync } from 'react-use'; import { useQuery } from 'react-query';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import { import {
Alert, Alert,
@@ -11,30 +11,42 @@ import {
Position, Position,
Intent, Intent,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { snakeCase } from 'lodash';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import LoadingIndicator from 'components/LoadingIndicator'; import LoadingIndicator from 'components/LoadingIndicator';
import { snakeCase } from 'lodash';
import UserListConnect from 'connectors/UsersList.connector';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import { compose } from 'utils';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import DashboardConnect from 'connectors/Dashboard.connector'; import withDashboard from 'containers/Dashboard/withDashboard';
import withUsers from 'containers/Users/withUsers';
import withUsersActions from 'containers/Users/withUsersActions';
import { compose } from 'utils';
function UsersListPreferences({ function UsersListPreferences({
requestFetchUsers, // #withDialog
usersList,
openDialog, openDialog,
closeDialog, closeDialog,
// #withUsers
usersList,
// #withUsersActions
requestDeleteUser, requestDeleteUser,
requestInactiveUser, requestInactiveUser,
requestFetchUsers,
// #ownProps
onFetchData, onFetchData,
}) { }) {
const [deleteUserState, setDeleteUserState] = useState(false); const [deleteUserState, setDeleteUserState] = useState(false);
const [inactiveUserState, setInactiveUserState] = useState(false); const [inactiveUserState, setInactiveUserState] = useState(false);
const asyncHook = useAsync(async () => { const fetchUsers = useQuery('users-table',
await Promise.all([requestFetchUsers()]); () => requestFetchUsers());
}, []);
const onInactiveUser = (user) => { const onInactiveUser = (user) => {
setInactiveUserState(user); setInactiveUserState(user);
@@ -49,7 +61,6 @@ function UsersListPreferences({
const handleConfirmUserActive = useCallback(() => { const handleConfirmUserActive = useCallback(() => {
requestInactiveUser(inactiveUserState.id).then(() => { requestInactiveUser(inactiveUserState.id).then(() => {
setInactiveUserState(false); setInactiveUserState(false);
requestFetchUsers();
AppToaster.show({ message: 'the_user_has_been_inactivated' }); AppToaster.show({ message: 'the_user_has_been_inactivated' });
}); });
}, [inactiveUserState, requestInactiveUser, requestFetchUsers]); }, [inactiveUserState, requestInactiveUser, requestFetchUsers]);
@@ -105,8 +116,7 @@ function UsersListPreferences({
[] []
); );
const columns = useMemo( const columns = useMemo(() => [
() => [
{ {
id: 'full_name', id: 'full_name',
Header: 'Full Name', Header: 'Full Name',
@@ -146,9 +156,7 @@ function UsersListPreferences({
className: 'actions', className: 'actions',
width: 50, width: 50,
}, },
], ], [actionMenuList]);
[actionMenuList]
);
const handelDataTableFetchData = useCallback(() => { const handelDataTableFetchData = useCallback(() => {
onFetchData && onFetchData(); onFetchData && onFetchData();
@@ -160,6 +168,7 @@ function UsersListPreferences({
columns={columns} columns={columns}
data={usersList} data={usersList}
onFetchData={handelDataTableFetchData()} onFetchData={handelDataTableFetchData()}
loading={fetchUsers.isFetching}
manualSortBy={true} manualSortBy={true}
expandable={false} expandable={false}
/> />
@@ -198,7 +207,8 @@ function UsersListPreferences({
} }
export default compose( export default compose(
UserListConnect,
DialogConnect, DialogConnect,
DashboardConnect withDashboard,
withUsers,
withUsersActions,
)(UsersListPreferences); )(UsersListPreferences);

View File

@@ -1,9 +0,0 @@
import { connect } from 'react-redux';
export const mapStateToProps = (state, props) => {
return {
usersList: state.users.list.results,
};
};
export default connect(mapStateToProps);

View File

@@ -0,0 +1,7 @@
import { connect } from 'react-redux';
export const mapStateToProps = (state, props) => ({
organizationSettings: state.settings.data.organization,
});
export default connect(mapStateToProps);

View File

@@ -0,0 +1,12 @@
import { connect } from 'react-redux';
import {
FetchOptions,
submitOptions,
} from 'store/settings/settings.actions';
export const mapDispatchToProps = (dispatch) => ({
requestSubmitOptions: (form) => dispatch(submitOptions({ form })),
requestFetchOptions: () => dispatch(FetchOptions({})),
});
export default connect(null, mapDispatchToProps);

View File

@@ -0,0 +1,7 @@
import { connect } from 'react-redux';
export const mapStateToProps = (state, props) => ({
usersList: state.users.list.results,
});
export default connect(mapStateToProps);

View File

@@ -5,35 +5,30 @@ const BASE_URL = '/auth';
export default [ export default [
{ {
path: `${BASE_URL}/login`, path: `${BASE_URL}/login`,
name: 'auth.login',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Authentication/Login'), loader: () => import('containers/Authentication/Login'),
}), }),
}, },
{ {
path: `${BASE_URL}/register`, path: `${BASE_URL}/register`,
name: 'auth.register',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Authentication/Register'), loader: () => import('containers/Authentication/Register'),
}), }),
}, },
{ {
path: `${BASE_URL}/send_reset_password`, path: `${BASE_URL}/send_reset_password`,
name: 'auth.reset_password',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Authentication/SendResetPassword'), loader: () => import('containers/Authentication/SendResetPassword'),
}), }),
}, },
{ {
path: `${BASE_URL}/reset_password/:token`, path: `${BASE_URL}/reset_password/:token`,
name: 'auth.send.reset_password',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Authentication/ResetPassword'), loader: () => import('containers/Authentication/ResetPassword'),
}), }),
}, },
{ {
path: `${BASE_URL}/invite/:token/accept`, path: `${BASE_URL}/invite/:token/accept`,
name: 'auth.invite.accept',
component: LazyLoader({ component: LazyLoader({
loader: () => import('containers/Authentication/InviteAccept'), loader: () => import('containers/Authentication/InviteAccept'),
}), }),

View File

@@ -1,21 +1,19 @@
import General from 'containers/Preferences/General'; import General from 'containers/Preferences/General/General';
import Users from 'containers/Preferences/Users'; import Users from 'containers/Preferences/Users/Users';
import Accountant from 'containers/Preferences/Accountant'; import Accountant from 'containers/Preferences/Accountant/Accountant';
import Accounts from 'containers/Preferences/Accounts'; import Accounts from 'containers/Preferences/Accounts/Accounts';
import CurrenciesList from 'containers/Preferences/CurrenciesList' import CurrenciesList from 'containers/Preferences/Currencies/CurrenciesList'
const BASE_URL = '/dashboard/preferences'; const BASE_URL = '/dashboard/preferences';
export default [ export default [
{ {
path: `${BASE_URL}/general`, path: `${BASE_URL}/general`,
name: 'dashboard.preferences.general',
component: General, component: General,
exact: true, exact: true,
}, },
{ {
path: `${BASE_URL}/users`, path: `${BASE_URL}/users`,
name: 'dashboard.preferences.users',
component: Users, component: Users,
exact: true, exact: true,
}, },
@@ -26,13 +24,11 @@ export default [
}, },
{ {
path: `${BASE_URL}/accountant`, path: `${BASE_URL}/accountant`,
name: 'dashboard.preferences.accountant',
component: Accountant, component: Accountant,
exact: true, exact: true,
}, },
{ {
path: `${BASE_URL}/accounts`, path: `${BASE_URL}/accounts`,
name: 'dashboard.preferences.accounts',
component: Accounts, component: Accounts,
}, },
]; ];

View File

@@ -1,20 +1,19 @@
import AccountsCustomFields from "containers/Preferences/AccountsCustomFields"; // import AccountsCustomFields from "containers/Preferences/AccountsCustomFields";
import UsersList from 'containers/Preferences/UsersList'; import UsersList from 'containers/Preferences/Users/UsersList';
import RolesList from 'containers/Preferences/RolesList'; import RolesList from 'containers/Preferences/Users/RolesList';
export default { export default {
accounts: [ accounts: [
{ // {
path: '', // path: '',
component: AccountsCustomFields, // component: AccountsCustomFields,
exact: true, // exact: true,
}, // },
{ // {
name: 'dashboard.preferences.accounts.custom_fields', // path: 'custom_fields',
path: 'custom_fields', // component: AccountsCustomFields,
component: AccountsCustomFields, // exact: true,
exact: true, // },
},
], ],
users: [ users: [
{ {
@@ -23,7 +22,6 @@ export default {
exact: true, exact: true,
}, },
{ {
name: 'dashboard.preferences.users.roles',
path: '/roles', path: '/roles',
component: RolesList, component: RolesList,
exact: true, exact: true,

View File

@@ -2,9 +2,7 @@ import { createReducer } from '@reduxjs/toolkit';
import t from 'store/types'; import t from 'store/types';
const initialState = { const initialState = {
preferences: { data: {},
currencies: [],
},
}; };
export default createReducer(initialState, { export default createReducer(initialState, {
@@ -14,8 +12,8 @@ export default createReducer(initialState, {
action.currencies.forEach((currency) => { action.currencies.forEach((currency) => {
_currencies[currency.currency_code] = currency; _currencies[currency.currency_code] = currency;
}); });
state.preferences.currencies = { state.data = {
...state.preferences.currencies, ...state.data,
..._currencies, ..._currencies,
}; };
}, },

View File

@@ -1,3 +1,9 @@
export const getCurrencyById = (items, id) => { // @flow
return items[id] || null;
export const getCurrencyById = (currencies: Object, id: Integer) => {
return Object.values(currencies).find(c => c.id == id) || null;
};
export const getCurrencyByCode = (currencies: Object, currencyCode: String) => {
return currencies[currencyCode] || null;
}; };