feat: journal number setting dialog.

This commit is contained in:
Ahmed Bouhuolia
2020-10-21 01:01:06 +02:00
parent c14116a0fd
commit fb1ecd529f
22 changed files with 304 additions and 150 deletions

View File

@@ -7,13 +7,14 @@ import AccountFormDialog from 'containers/Dialogs/AccountFormDialog';
// import CurrencyDialog from 'containers/Dialogs/CurrencyDialog';
// import InviteUserDialog from 'containers/Dialogs/InviteUserDialog';
// import ExchangeRateDialog from 'containers/Dialogs/ExchangeRateDialog';
// import JournalNumberDailog from 'containers/Dialogs/JournalNumberDailog';
import JournalNumberDialog from 'containers/Dialogs/JournalNumberDialog';
export default function DialogsContainer() {
return (
<div>
<AccountFormDialog dialogName={'account-form'} />
<JournalNumberDialog dialogName={'journal-number-form'} />
</div>
);
}

View File

@@ -0,0 +1,27 @@
import React, { useMemo } from 'react';
import classNames from 'classnames';
import { Button, Tooltip, Classes } from '@blueprintjs/core';
export default function InputPrependButton({
buttonProps = {},
tooltip = false,
tooltipProps = {},
}) {
const appendButton = useMemo(() => (
<Button
className={classNames('input-prepend__button', Classes.SMALL)}
{...buttonProps}
/>
), [buttonProps]);
const appendButtonWithTooltip = useMemo(
() => (<Tooltip {...tooltipProps}>{ appendButton }</Tooltip>),
[tooltipProps, appendButton],
);
return (
<div class="input-prepend">
{ tooltip ? appendButtonWithTooltip : appendButton }
</div>
);
}

View File

@@ -24,6 +24,7 @@ import DashboardActionViewsList from './Dashboard/DashboardActionViewsList';
import Dialog from './Dialog/Dialog';
import DialogContent from './Dialog/DialogContent';
import DialogSuspense from './Dialog/DialogSuspense';
import InputPrependButton from './Forms/InputPrependButton';
const Hint = FieldHint;
export {
@@ -53,5 +54,6 @@ export {
AppToaster,
Dialog,
DialogContent,
DialogSuspense
DialogSuspense,
InputPrependButton
};

View File

@@ -20,6 +20,8 @@ import withJournalsActions from 'containers/Accounting/withJournalsActions';
import withManualJournalDetail from 'containers/Accounting/withManualJournalDetail';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings';
import withManualJournals from './withManualJournals';
import AppToaster from 'components/AppToaster';
import Dragzone from 'components/Dragzone';
@@ -27,6 +29,7 @@ import withMediaActions from 'containers/Media/withMediaActions';
import useMedia from 'hooks/useMedia';
import { compose, repeatValue } from 'utils';
import withManualJournalsActions from './withManualJournalsActions';
const ERROR = {
JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS',
@@ -54,6 +57,15 @@ function MakeJournalEntriesForm({
changePageTitle,
changePageSubtitle,
// #withSettings
journalNextNumber,
journalNumberPrefix,
// #withManualJournals
nextJournalNumberChanged,
setJournalNumberChanged,
manualJournalId,
manualJournal,
onFormSubmit,
@@ -149,16 +161,19 @@ function MakeJournalEntriesForm({
[],
);
const journalNumber = (journalNumberPrefix) ?
`${journalNumberPrefix}-${journalNextNumber}` : journalNextNumber;
const defaultInitialValues = useMemo(
() => ({
journal_number: '',
journal_number: journalNumber,
journal_type: 'Journal',
date: moment(new Date()).format('YYYY-MM-DD'),
description: '',
reference: '',
entries: [...repeatValue(defaultEntry, 4)],
}),
[defaultEntry],
[defaultEntry, journalNumber],
);
const initialValues = useMemo(
@@ -255,7 +270,6 @@ function MakeJournalEntriesForm({
};
const formik = useFormik({
enableReinitialize: true,
validationSchema,
initialValues: {
...initialValues,
@@ -354,6 +368,11 @@ function MakeJournalEntriesForm({
},
});
useEffect(() => {
formik.setFieldValue('journal_number', journalNumber);
setJournalNumberChanged(false);
}, [nextJournalNumberChanged, journalNumber]);
const handleSubmitClick = useCallback(
(payload) => {
setPayload(payload);
@@ -430,7 +449,15 @@ function MakeJournalEntriesForm({
export default compose(
withJournalsActions,
withManualJournalDetail,
withManualJournals(({ nextJournalNumberChanged }) => ({
nextJournalNumberChanged,
})),
withAccountsActions,
withDashboardActions,
withMediaActions,
withSettings(({ manualJournalsSettings }) => ({
journalNextNumber: manualJournalsSettings.next_number,
journalNumberPrefix: manualJournalsSettings.number_prefix
})),
withManualJournalsActions,
)(MakeJournalEntriesForm);

View File

@@ -5,7 +5,6 @@ import {
Intent,
Position,
Classes,
Button,
} from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T } from 'react-intl';
@@ -20,6 +19,7 @@ import {
FieldHint,
FieldRequiredHint,
Icon,
InputPrependButton
} from 'components';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -40,7 +40,7 @@ function MakeJournalEntriesHeader({
[setFieldValue],
);
const handleJournalNumberChange = useCallback(() => {
openDialog('journalNumber-form', {});
openDialog('journal-number-form', {});
}, [openDialog]);
return (
@@ -63,25 +63,29 @@ function MakeJournalEntriesHeader({
<ErrorMessage name="journal_number" {...{ errors, touched }} />
}
fill={true}
>
<InputGroup
intent={
errors.journal_number && touched.journal_number && Intent.DANGER
}
fill={true}
rightElement={<InputPrependButton
buttonProps={{
onClick: handleJournalNumberChange,
icon: (<Icon icon={'settings-18'} />)
}}
tooltip={true}
tooltipProps={{
content: 'Setting your auto-generated journal number',
position: Position.BOTTOM_LEFT,
}}
/>}
{...getFieldProps('journal_number')}
/>
</FormGroup>
</Col>
<Col width={50}>
{/* <FormGroup> */}
<Button
className={classNames('btn', Classes.SMALL)}
icon={<Icon icon="setting" />}
onClick={handleJournalNumberChange}
/>
{/* </FormGroup> */}
</Col>
<Col width={220}>
<FormGroup
label={<T id={'date'} />}
@@ -173,4 +177,6 @@ function MakeJournalEntriesHeader({
);
}
export default compose(withDialogActions)(MakeJournalEntriesHeader);
export default compose(
withDialogActions,
)(MakeJournalEntriesHeader);

View File

@@ -8,6 +8,7 @@ import DashboardInsider from 'components/Dashboard/DashboardInsider';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import { compose } from 'utils';
@@ -20,6 +21,9 @@ function MakeJournalEntriesPage({
// #withManualJournalActions
requestFetchManualJournal,
// #withSettingsActions
requestFetchOptions,
}) {
const history = useHistory();
const { id } = useParams();
@@ -32,6 +36,8 @@ function MakeJournalEntriesPage({
requestFetchCustomers(),
);
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
const fetchJournal = useQuery(
['manual-journal', id],
(key, journalId) => requestFetchManualJournal(journalId),
@@ -71,4 +77,5 @@ export default compose(
withAccountsActions,
withCustomersActions,
withManualJournalsActions,
withSettingsActions
)(MakeJournalEntriesPage);

View File

@@ -19,6 +19,8 @@ export default (mapState) => {
manualJournalsPagination: getManualJournalsPagination(state, props, query),
manualJournalsLoading: state.manualJournals.loading,
nextJournalNumberChanged: state.manualJournals.nextJournalNumberChanged,
};
return mapState ? mapState(mapped, state, props) : mapped;
};

View File

@@ -24,6 +24,10 @@ const mapActionsToProps = (dispatch) => ({
type: t.MANUAL_JOURNALS_TABLE_QUERIES_ADD,
queries,
}),
setJournalNumberChanged: (isChanged) => dispatch({
type: t.MANUAL_JOURNAL_NUMBER_CHANGED,
payload: { isChanged },
}),
});
export default connect(null, mapActionsToProps);

View File

@@ -1,71 +0,0 @@
import React, { useEffect, useCallback, useMemo } from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useQuery, queryCache } from 'react-query';
import { Dialog } from 'components';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDialogRedux from 'components/DialogReduxConnect';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withSettings from 'containers/Settings/withSettings';
import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { compose, optionsMapToArray } from 'utils';
function JournalNumberDailog({
dialogName,
payload,
isOpen,
// #withSettingsActions
requestSubmitOptions,
// #withDialogActions
closeDialog,
}) {
// Handles dialog close.
const handleClose = useCallback(() => {
closeDialog(dialogName);
}, [closeDialog, dialogName]);
const handleSubmitForm = useCallback(() => {});
// Handle dialog on closed.
// const onDialogClosed = useCallback(() => {
// resetForm();
// }, [resetForm]);
return (
<Dialog
name={dialogName}
// isLoading={}
title={<T id={'journal_number_settings'} />}
autoFocus={true}
canEscapeKeyClose={true}
isOpen={isOpen}
onClose={handleClose}
>
<ReferenceNumberForm
onSubmit={handleSubmitForm}
initialNumber={'1000'}
initialPrefix={'A'}
onClose={handleClose}
groupName={'manual_journals'}
/>
</Dialog>
);
}
const mapStateToProps = (state, props) => ({
dialogName: 'journalNumber-form',
journalNumberId: props?.payload?.id || null,
});
const withJournalNumberDailog = connect(mapStateToProps);
export default compose(
withDialogRedux(null, 'journalNumber-form'),
withJournalNumberDailog,
withDialogActions,
withSettingsActions,
)(JournalNumberDailog);

View File

@@ -0,0 +1,76 @@
import React from 'react';
import { DialogContent } from 'components';
import { useQuery, queryCache } from 'react-query';
import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withSettings from 'containers/Settings/withSettings';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
import { compose, optionsMapToArray } from 'utils';
/**
* Journal number dialog's content.
*/
function JournalNumberDialogContent({
// #withSettings
nextNumber,
numberPrefix,
// #withSettingsActions
requestFetchOptions,
requestSubmitOptions,
// #withDialogActions
closeDialog,
// #withManualJournalsActions
setJournalNumberChanged,
}) {
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
const handleSubmitForm = (values, { setSubmitting }) => {
const options = optionsMapToArray(values).map((option) => {
return { key: option.key, ...option, group: 'manual_journals' };
});
requestSubmitOptions({ options }).then(() => {
setSubmitting(false);
closeDialog('journal-number-form');
setJournalNumberChanged(true);
setTimeout(() => {
queryCache.invalidateQueries('settings');
}, 250);
}).catch(() => {
setSubmitting(false);
});
};
const handleClose = () => {
closeDialog('journal-number-form');
};
return (
<DialogContent isLoading={fetchSettings.isFetching}>
<ReferenceNumberForm
initialNumber={nextNumber}
initialPrefix={numberPrefix}
onSubmit={handleSubmitForm}
onClose={handleClose}
/>
</DialogContent>
)
}
export default compose(
withDialogActions,
withSettingsActions,
withSettings(({ manualJournalsSettings }) => ({
nextNumber: manualJournalsSettings?.next_number,
numberPrefix: manualJournalsSettings?.number_prefix,
})),
withManualJournalsActions,
)(JournalNumberDialogContent);

View File

@@ -0,0 +1,35 @@
import React, { lazy } from 'react';
import { FormattedMessage as T } from 'react-intl';
import { Dialog, DialogSuspense } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'utils';
const JournalNumberDialogContent = lazy(() => import('./JournalNumberDialogContent'));
function JournalNumberDialog({
dialogName,
payload = { id: null },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={<T id={'journal_number_settings'} />}
autoFocus={true}
canEscapeKeyClose={true}
isOpen={isOpen}
className={'dialog--journal-number-settings'}
>
<DialogSuspense>
<JournalNumberDialogContent
journalNumberId={payload.id}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(
withDialogRedux(),
)(JournalNumberDialog);

View File

@@ -1,51 +1,43 @@
import React, { useMemo, useCallback } from 'react';
import React, { useMemo } from 'react';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { Row, Col } from 'react-grid-system';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { ErrorMessage, AppToaster } from 'components';
import { FormattedMessage as T } from 'react-intl';
import { ErrorMessage } from 'components';
import {
Button,
Classes,
FormGroup,
InputGroup,
Intent,
Position,
} from '@blueprintjs/core';
import { compose, optionsMapToArray } from 'utils';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withSettings from 'containers/Settings/withSettings';
function ReferenceNumberForm({
import { compose } from 'utils';
export default function ReferenceNumberForm({
onSubmit,
onClose,
initialPrefix,
initialNumber,
groupName,
requestSubmitOptions,
}) {
const { formatMessage } = useIntl();
const validationSchema = Yup.object().shape({
prefix: Yup.string(),
number_prefix: Yup.string(),
next_number: Yup.number(),
});
const initialValues = useMemo(
() => ({
prefix: initialPrefix || '',
number_prefix: initialPrefix || '',
next_number: initialNumber || '',
}),
[],
[initialPrefix, initialNumber],
);
const {
errors,
values,
touched,
setFieldValue,
resetForm,
handleSubmit,
isSubmitting,
getFieldProps,
@@ -56,33 +48,9 @@ function ReferenceNumberForm({
},
validationSchema,
onSubmit: (values, { setSubmitting, setErrors }) => {
const options = optionsMapToArray(values).map((option) => {
return { key: option.key, ...option, group: groupName };
});
onSubmit(
requestSubmitOptions({ options })
.then(() => {
setSubmitting(false);
})
.catch((erros) => {
setSubmitting(false);
}),
);
onSubmit(values, { setSubmitting, setErrors });
},
});
// Handles dialog close.
// const handleClose = useCallback(() => {
// closeDialog(dialogName);
// }, [closeDialog, dialogName]);
// Handle dialog on closed.
const onClosed = useCallback(() => {
resetForm();
}, [resetForm]);
return (
<div>
<form onSubmit={handleSubmit}>
@@ -97,14 +65,14 @@ function ReferenceNumberForm({
<FormGroup
label={<T id={'prefix'} />}
className={'form-group--'}
intent={errors.prefix && touched.prefix && Intent.DANGER}
intent={errors.number_prefix && touched.number_prefix && Intent.DANGER}
helperText={
<ErrorMessage name={'prefix'} {...{ errors, touched }} />
}
>
<InputGroup
intent={errors.prefix && touched.prefix && Intent.DANGER}
{...getFieldProps('prefix')}
intent={errors.number_prefix && touched.number_prefix && Intent.DANGER}
{...getFieldProps('number_prefix')}
/>
</FormGroup>
</Col>
@@ -147,6 +115,4 @@ function ReferenceNumberForm({
</form>
</div>
);
}
export default compose(withSettingsActions)(ReferenceNumberForm);
}

View File

@@ -2,6 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import 'services/yup';
import App from 'components/App';
import * as serviceWorker from 'serviceWorker';
import createStore from 'store/createStore';

View File

@@ -774,5 +774,5 @@ export default {
bigger_or_equals: 'Bigger or equals',
prefix: 'Prefix',
next_number: 'Next Number',
journal_number_settings: 'Journal Number Settings',
journal_number_settings: 'Journal number settings',
};

View File

@@ -1,6 +1,5 @@
import * as Yup from 'yup';
Yup.addMethod(Yup.string, 'digits', function () {
return this.test(
'is-digits',

View File

@@ -275,4 +275,10 @@ export default {
],
viewBox: '0 0 16 16',
},
'settings-18': {
path: [
'M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z',
],
viewBox: '0 0 24 24',
},
};

View File

@@ -14,7 +14,8 @@ const initialState = {
},
paginationMeta: {
total: 0,
}
},
nextJournalNumberChanged: false,
};
const defaultJournal = {
@@ -114,6 +115,11 @@ const reducer = createReducer(initialState, {
paginationMeta,
},
};
},
[t.MANUAL_JOURNAL_NUMBER_CHANGED]: (state, action) => {
const { isChanged } = action.payload;
state.nextJournalNumberChanged = isChanged;
}
});

View File

@@ -12,4 +12,6 @@ export default {
MANUAL_JOURNAL_PUBLISH: 'MANUAL_JOURNAL_PUBLISH',
MANUAL_JOURNALS_BULK_DELETE: 'MANUAL_JOURNALS_BULK_DELETE',
MANUAL_JOURNALS_PAGINATION_SET: 'MANUAL_JOURNALS_PAGINATION_SET',
MANUAL_JOURNAL_NUMBER_CHANGED: 'MANUAL_JOURNAL_NUMBER_CHANGED'
};

View File

@@ -12,7 +12,16 @@ const initialState = {
export default createReducer(initialState, {
[t.SETTING_SET]: (state, action) => {
const { options } = action;
state.data.organization = optionsArrayToMap(options);
const _data = {
...state.data,
};
options.forEach((option) => {
const { key, group, value } = option;
if (!_data[group]) {
_data[group] = {};
}
_data[group][key] = value;
});
state.data = _data;
},
});

View File

@@ -341,4 +341,42 @@ $form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='ht
.bp3-menu-item::before, .bp3-menu-item > .bp3-icon{
color: #4b5d6b;
}
.bp3-input-group{
display: flex;
.bp3-input:not(:last-child) {
border-radius: 3px 0 0 3px;
border-right-width: 0;
}
.bp3-input-action{
position: relative;
}
.input-prepend{
&__text,
&__button{
display: flex;
align-items: center;
padding: .375rem .75rem;
margin: 0;
height: 32px;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
text-align: center;
white-space: nowrap;
background-color: #e9ecef;
border: 1px solid #ced4da;
border-radius: 0 3px 3px 0;
.bp3-icon{
color: #848da0;
}
}
}
}

View File

@@ -14,12 +14,6 @@
color: #444;
}
}
.bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal).bp3-small {
font-size: 13px;
min-height: 33px;
margin-top: 21px;
margin-left: -30px;
}
}
&__table {
@@ -186,3 +180,20 @@
margin-right: 20px;
}
}
.dialog--journal-number-settings{
.paragraph {
margin-bottom: 20px;
}
.bp3-form-group{
margin-bottom: 10px;
}
.bp3-dialog-footer-actions{
.bp3-button{
min-width: 75px;
}
}
}

View File

@@ -47,11 +47,11 @@ export default {
],
manual_journals: [
{
key: 'journal_number_next',
key: 'next_number',
type: 'number',
},
{
key: 'journal_number_prefix',
key: 'number_prefix',
type: 'string',
},
]