diff --git a/client/src/components/Preferences/PreferencesTopbar.js b/client/src/components/Preferences/PreferencesTopbar.js index 212a69324..48f4ea08c 100644 --- a/client/src/components/Preferences/PreferencesTopbar.js +++ b/client/src/components/Preferences/PreferencesTopbar.js @@ -3,25 +3,26 @@ import { Route, Switch } from 'react-router-dom'; import DashboardTopbarUser from 'components/Dashboard/TopbarUser'; import UsersActions from 'containers/Preferences/Users/UsersActions'; import CurrenciesActions from 'containers/Preferences/Currencies/CurrenciesActions'; +import { compose } from 'utils'; +import { connect } from 'react-redux'; - -export default function PreferencesTopbar() { +function PreferencesTopbar({ pageTitle }) { return (
-

Accounts

- + {/*

Accounts

*/} +
+

{pageTitle}

+
- + + component={CurrenciesActions} + />
@@ -31,4 +32,10 @@ export default function PreferencesTopbar() {
); -} \ No newline at end of file +} + +const mapStateToProps = (state) => ({ + pageTitle: state.dashboard.preferencesPageTitle, +}); + +export default compose(connect(mapStateToProps))(PreferencesTopbar); diff --git a/client/src/connectors/Dashboard.connector.js b/client/src/connectors/Dashboard.connector.js index 6c3ce2bf9..2be19910d 100644 --- a/client/src/connectors/Dashboard.connector.js +++ b/client/src/connectors/Dashboard.connector.js @@ -1,27 +1,34 @@ - import { connect } from 'react-redux'; import t from 'store/types'; const mapActionsToProps = (dispatch) => ({ - changePageTitle: (pageTitle) => dispatch({ - type: t.CHANGE_DASHBOARD_PAGE_TITLE, pageTitle - }), + changePageTitle: (pageTitle) => + dispatch({ + type: t.CHANGE_DASHBOARD_PAGE_TITLE, + pageTitle, + }), - changePageSubtitle: (pageSubtitle) => dispatch({ - type: t.ALTER_DASHBOARD_PAGE_SUBTITLE, pageSubtitle, - }), + changePageSubtitle: (pageSubtitle) => + dispatch({ + type: t.ALTER_DASHBOARD_PAGE_SUBTITLE, + pageSubtitle, + }), - setTopbarEditView: (id) => dispatch({ - type: t.SET_TOPBAR_EDIT_VIEW, id, - }), + setTopbarEditView: (id) => + dispatch({ + type: t.SET_TOPBAR_EDIT_VIEW, + id, + }), - setDashboardRequestLoading: () => dispatch({ - type: t.SET_DASHBOARD_REQUEST_LOADING, - }), + setDashboardRequestLoading: () => + dispatch({ + type: t.SET_DASHBOARD_REQUEST_LOADING, + }), - setDashboardRequestCompleted: () => dispatch({ - type: t.SET_DASHBOARD_REQUEST_COMPLETED, - }), + setDashboardRequestCompleted: () => + dispatch({ + type: t.SET_DASHBOARD_REQUEST_COMPLETED, + }), }); -export default connect(null, mapActionsToProps); \ No newline at end of file +export default connect(null, mapActionsToProps); diff --git a/client/src/containers/Accounting/MakeJournalEntriesForm.js b/client/src/containers/Accounting/MakeJournalEntriesForm.js index 0aa08f529..ee7936e6d 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesForm.js +++ b/client/src/containers/Accounting/MakeJournalEntriesForm.js @@ -1,6 +1,12 @@ -import React, {useMemo, useState, useEffect, useRef, useCallback} from 'react'; +import React, { + useMemo, + useState, + useEffect, + useRef, + useCallback, +} from 'react'; import * as Yup from 'yup'; -import { useFormik } from "formik"; +import { useFormik } from 'formik'; import moment from 'moment'; import { Intent } from '@blueprintjs/core'; import { useIntl } from 'react-intl'; @@ -20,8 +26,7 @@ import Dragzone from 'components/Dragzone'; import MediaConnect from 'connectors/Media.connect'; import useMedia from 'hooks/useMedia'; -import {compose} from 'utils'; - +import { compose } from 'utils'; function MakeJournalEntriesForm({ // #withMedia @@ -39,10 +44,16 @@ function MakeJournalEntriesForm({ manualJournalId, manualJournal, onFormSubmit, - onCancelForm, + onCancelForm, }) { const { formatMessage } = useIntl(); - const { setFiles, saveMedia, deletedFiles, setDeletedFiles, deleteMedia } = useMedia({ + const { + setFiles, + saveMedia, + deletedFiles, + setDeletedFiles, + deleteMedia, + } = useMedia({ saveCallback: requestSubmitMedia, deleteCallback: requestDeleteMedia, }); @@ -51,79 +62,95 @@ function MakeJournalEntriesForm({ }, []); const savedMediaIds = useRef([]); - const clearSavedMediaIds = () => { savedMediaIds.current = []; } + const clearSavedMediaIds = () => { + savedMediaIds.current = []; + }; useEffect(() => { if (manualJournal && manualJournal.id) { - changePageTitle(formatMessage({id:'edit_journal'})); + changePageTitle(formatMessage({ id: 'edit_journal' })); changePageSubtitle(`No. ${manualJournal.journal_number}`); } else { - changePageTitle(formatMessage({id:'new_journal'})); + changePageTitle(formatMessage({ id: 'new_journal' })); } - }, [changePageTitle, changePageSubtitle, manualJournal,formatMessage]); + }, [changePageTitle, changePageSubtitle, manualJournal, formatMessage]); const validationSchema = Yup.object().shape({ - journal_number: Yup.string().required().label(formatMessage({id:'journal_number_'})), - date: Yup.date().required().label(formatMessage({id:'date'})), + journal_number: Yup.string() + .required() + .label(formatMessage({ id: 'journal_number_' })), + date: Yup.date().label(formatMessage({ id: 'date' })), reference: Yup.string(), description: Yup.string(), entries: Yup.array().of( Yup.object().shape({ credit: Yup.number().nullable(), debit: Yup.number().nullable(), - account_id: Yup.number().nullable().when(['credit', 'debit'], { - is: (credit, debit) => credit || debit, - then: Yup.number().required(), - }), + account_id: Yup.number() + .nullable() + .when(['credit', 'debit'], { + is: (credit, debit) => credit || debit, + then: Yup.number().required(), + }), note: Yup.string().nullable(), }), - ) + ), }); - const saveInvokeSubmit = useCallback((payload) => { - onFormSubmit && onFormSubmit(payload) - }, [onFormSubmit]); + const saveInvokeSubmit = useCallback( + (payload) => { + onFormSubmit && onFormSubmit(payload); + }, + [onFormSubmit], + ); const [payload, setPayload] = useState({}); - const defaultEntry = useMemo(() => ({ - account_id: null, - credit: 0, - debit: 0, - note: '', - }), []); + const defaultEntry = useMemo( + () => ({ + account_id: null, + credit: 0, + debit: 0, + note: '', + }), + [], + ); - const defaultInitialValues = useMemo(() => ({ - journal_number: '', - date: moment(new Date()).format('YYYY-MM-DD'), - description: '', - reference: '', - entries: [ - defaultEntry, - defaultEntry, - defaultEntry, - defaultEntry, - ], - }), [defaultEntry]); + const defaultInitialValues = useMemo( + () => ({ + journal_number: '', + date: moment(new Date()).format('YYYY-MM-DD'), + description: '', + reference: '', + entries: [defaultEntry, defaultEntry, defaultEntry, defaultEntry], + }), + [defaultEntry], + ); - const initialValues = useMemo(() => ({ - ...(manualJournal) ? { - ...pick(manualJournal, Object.keys(defaultInitialValues)), - entries: manualJournal.entries.map((entry) => ({ - ...pick(entry, Object.keys(defaultEntry)), - })), - } : { - ...defaultInitialValues, - } - }), [manualJournal, defaultInitialValues, defaultEntry]); + const initialValues = useMemo( + () => ({ + ...(manualJournal + ? { + ...pick(manualJournal, Object.keys(defaultInitialValues)), + entries: manualJournal.entries.map((entry) => ({ + ...pick(entry, Object.keys(defaultEntry)), + })), + } + : { + ...defaultInitialValues, + }), + }), + [manualJournal, defaultInitialValues, defaultEntry], + ); const initialAttachmentFiles = useMemo(() => { return manualJournal && manualJournal.media ? manualJournal.media.map((attach) => ({ - preview: attach.attachment_file, - uploaded: true, - metadata: { ...attach }, - })) : []; + preview: attach.attachment_file, + uploaded: true, + metadata: { ...attach }, + })) + : []; }, [manualJournal]); const formik = useFormik({ @@ -133,113 +160,136 @@ function MakeJournalEntriesForm({ ...initialValues, }, onSubmit: async (values, { setErrors, setSubmitting, resetForm }) => { - const entries = values.entries.filter((entry) => ( - (entry.credit || entry.debit) - )); + const entries = values.entries.filter( + (entry) => entry.credit || entry.debit, + ); const getTotal = (type = 'credit') => { return entries.reduce((total, item) => { return item[type] ? item[type] + total : total; }, 0); - } + }; const totalCredit = getTotal('credit'); const totalDebit = getTotal('debit'); // Validate the total credit should be eqials total debit. if (totalCredit !== totalDebit) { AppToaster.show({ - message: formatMessage({id:'credit_and_debit_not_equal'}), + message: formatMessage({ id: 'credit_and_debit_not_equal' }), }); setSubmitting(false); return; } const form = { ...values, status: payload.publish, entries }; - const saveJournal = (mediaIds) => new Promise((resolve, reject) => { - const requestForm = { ...form, media_ids: mediaIds }; + const saveJournal = (mediaIds) => + new Promise((resolve, reject) => { + const requestForm = { ...form, media_ids: mediaIds }; - if (manualJournal && manualJournal.id) { - requestEditManualJournal(manualJournal.id, requestForm) - .then((response) => { - AppToaster.show({ - message: formatMessage({ - id: 'the_journal_has_been_successfully_edited', - }, { - number: values.journal_number, - }), - intent: Intent.SUCCESS, - }); - setSubmitting(false); - saveInvokeSubmit({ action: 'update', ...payload }); - clearSavedMediaIds([]); - resetForm(); - resolve(response); - }).catch((errors) => { - if (errors.find(e => e.type === 'JOURNAL.NUMBER.ALREADY.EXISTS')) { - setErrors({ - journal_number: formatMessage({ id: 'journal_number_is_already_used' }), + if (manualJournal && manualJournal.id) { + requestEditManualJournal(manualJournal.id, requestForm) + .then((response) => { + AppToaster.show({ + message: formatMessage( + { + id: 'the_journal_has_been_successfully_edited', + }, + { + number: values.journal_number, + }, + ), + intent: Intent.SUCCESS, }); - } - setSubmitting(false); - }); - } else { - requestMakeJournalEntries(requestForm) - .then((response) => { - AppToaster.show({ - message: formatMessage({ - id: 'the_journal_has_been_successfully_created', - }, { - number: values.journal_number, - }), - intent: Intent.SUCCESS, + setSubmitting(false); + saveInvokeSubmit({ action: 'update', ...payload }); + clearSavedMediaIds([]); + resetForm(); + resolve(response); + }) + .catch((errors) => { + if ( + errors.find((e) => e.type === 'JOURNAL.NUMBER.ALREADY.EXISTS') + ) { + setErrors({ + journal_number: formatMessage({ + id: 'journal_number_is_already_used', + }), + }); + } + setSubmitting(false); }); - setSubmitting(false); - saveInvokeSubmit({ action: 'new', ...payload }); - clearSavedMediaIds(); - resetForm(); - resolve(response); - }).catch((errors) => { - if (errors.find(e => e.type === 'JOURNAL.NUMBER.ALREADY.EXISTS')) { - setErrors({ - journal_number: formatMessage({ id: 'journal_number_is_already_used' }), + } else { + requestMakeJournalEntries(requestForm) + .then((response) => { + AppToaster.show({ + message: formatMessage( + { + id: 'the_journal_has_been_successfully_created', + }, + { + number: values.journal_number, + }, + ), + intent: Intent.SUCCESS, }); - } - setSubmitting(false); - }); - } - }); + setSubmitting(false); + saveInvokeSubmit({ action: 'new', ...payload }); + clearSavedMediaIds(); + resetForm(); + resolve(response); + }) + .catch((errors) => { + if ( + errors.find((e) => e.type === 'JOURNAL.NUMBER.ALREADY.EXISTS') + ) { + setErrors({ + journal_number: formatMessage({ + id: 'journal_number_is_already_used', + }), + }); + } + setSubmitting(false); + }); + } + }); - Promise.all([ - saveMedia(), - deleteMedia(), - ]).then(([savedMediaResponses]) => { - const mediaIds = savedMediaResponses.map(res => res.data.media.id); - savedMediaIds.current = mediaIds; + Promise.all([saveMedia(), deleteMedia()]) + .then(([savedMediaResponses]) => { + const mediaIds = savedMediaResponses.map((res) => res.data.media.id); + savedMediaIds.current = mediaIds; - return savedMediaResponses; - }).then(() => { - return saveJournal(savedMediaIds.current); - }); + return savedMediaResponses; + }) + .then(() => { + return saveJournal(savedMediaIds.current); + }); }, }); - const handleSubmitClick = useCallback((payload) => { - setPayload(payload); - formik.handleSubmit(); - }, [setPayload, formik]); + const handleSubmitClick = useCallback( + (payload) => { + setPayload(payload); + formik.handleSubmit(); + }, + [setPayload, formik], + ); - const handleCancelClick = useCallback((payload) => { - onCancelForm && onCancelForm(payload); - }, [onCancelForm]); + const handleCancelClick = useCallback( + (payload) => { + onCancelForm && onCancelForm(payload); + }, + [onCancelForm], + ); - const handleDeleteFile = useCallback((_deletedFiles) => { - _deletedFiles.forEach((deletedFile) => { - if (deletedFile.uploaded && deletedFile.metadata.id) { - setDeletedFiles([ - ...deletedFiles, deletedFile.metadata.id, - ]); - } - }); - }, [setDeletedFiles, deletedFiles]); + const handleDeleteFile = useCallback( + (_deletedFiles) => { + _deletedFiles.forEach((deletedFile) => { + if (deletedFile.uploaded && deletedFile.metadata.id) { + setDeletedFiles([...deletedFiles, deletedFile.metadata.id]); + } + }); + }, + [setDeletedFiles, deletedFiles], + ); return (
@@ -249,19 +299,22 @@ function MakeJournalEntriesForm({ + defaultRow={defaultEntry} + /> + onCancelClick={handleCancelClick} + /> + hint={'Attachments: Maxiumum size: 20MB'} + />
); } @@ -272,4 +325,4 @@ export default compose( withAccountsActions, withDashboardActions, MediaConnect, -)(MakeJournalEntriesForm); \ No newline at end of file +)(MakeJournalEntriesForm); diff --git a/client/src/containers/Accounting/MakeJournalEntriesHeader.js b/client/src/containers/Accounting/MakeJournalEntriesHeader.js index 7b4d4d839..c73a58bd1 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesHeader.js +++ b/client/src/containers/Accounting/MakeJournalEntriesHeader.js @@ -54,7 +54,7 @@ export default function MakeJournalEntriesHeader({ minimal={true}> diff --git a/client/src/containers/Currencies/withCurrencies.js b/client/src/containers/Currencies/withCurrencies.js index f672e6470..bc721acda 100644 --- a/client/src/containers/Currencies/withCurrencies.js +++ b/client/src/containers/Currencies/withCurrencies.js @@ -5,6 +5,7 @@ export default (mapState) => { const mapped = { currencies: state.currencies.data, currenciesList: Object.values(state.currencies.data), + currenciesLoading:state.currencies.loading, }; return mapState ? mapState(mapped, state, props) : mapped; }; diff --git a/client/src/containers/Dashboard/withDashboard.js b/client/src/containers/Dashboard/withDashboard.js index 6c3ce2bf9..83b1ab0e8 100644 --- a/client/src/containers/Dashboard/withDashboard.js +++ b/client/src/containers/Dashboard/withDashboard.js @@ -10,7 +10,11 @@ const mapActionsToProps = (dispatch) => ({ changePageSubtitle: (pageSubtitle) => dispatch({ type: t.ALTER_DASHBOARD_PAGE_SUBTITLE, pageSubtitle, }), - + changePreferencesPageTitle: (pageTitle) => + dispatch({ + type: 'CHANGE_PREFERENCES_PAGE_TITLE', + pageTitle, + }), setTopbarEditView: (id) => dispatch({ type: t.SET_TOPBAR_EDIT_VIEW, id, }), diff --git a/client/src/containers/Dialogs/AccountFormDialog.js b/client/src/containers/Dialogs/AccountFormDialog.js index c68516872..8f9e16b60 100644 --- a/client/src/containers/Dialogs/AccountFormDialog.js +++ b/client/src/containers/Dialogs/AccountFormDialog.js @@ -28,8 +28,7 @@ import Icon from 'components/Icon'; import ErrorMessage from 'components/ErrorMessage'; import { fetchAccountTypes } from 'store/accounts/accounts.actions'; - -import {ListSelect} from 'components'; +import { ListSelect } from 'components'; function AccountFormDialog({ name, @@ -52,15 +51,18 @@ function AccountFormDialog({ // #withDialog closeDialog, -}) { +}) { const { formatMessage } = useIntl(); const accountFormValidationSchema = Yup.object().shape({ - name: Yup.string().required().label(formatMessage({id:'account_name_'})), + name: Yup.string() + .required() + .label(formatMessage({ id: 'account_name_' })), code: Yup.number(), account_type_id: Yup.string() .nullable() - .required().label(formatMessage({id:'account_type_id'})), - description: Yup.string().trim() + .required() + .label(formatMessage({ id: 'account_type_id' })), + description: Yup.string().trim(), }); const initialValues = useMemo( @@ -69,14 +71,14 @@ function AccountFormDialog({ name: '', description: '', }), - [] + [], ); const [selectedAccountType, setSelectedAccountType] = useState(null); const [selectedSubaccount, setSelectedSubaccount] = useState( payload.action === 'new_child' ? accounts.find((a) => a.id === payload.id) - : null + : null, ); const transformApiErrors = (errors) => { @@ -96,48 +98,67 @@ function AccountFormDialog({ validationSchema: accountFormValidationSchema, onSubmit: (values, { setSubmitting, setErrors }) => { const exclude = ['subaccount']; - const toastAccountName = (values.code) ? `${values.code} - ${values.name}` : values.name; + const toastAccountName = values.code + ? `${values.code} - ${values.name}` + : values.name; if (payload.action === 'edit') { requestEditAccount({ payload: payload.id, - form: { ...omit(values, [...exclude, 'account_type_id']) } + form: { ...omit(values, [...exclude, 'account_type_id']) }, }).then((response) => { closeDialog(name); AppToaster.show({ - message: formatMessage({ - id: 'service_has_been_successful_edited', - }, { - name: toastAccountName, - service: formatMessage({ id: 'account' }), - }), + message: formatMessage( + { + id: 'service_has_been_successful_edited', + }, + { + name: toastAccountName, + service: formatMessage({ id: 'account' }), + }, + ), intent: Intent.SUCCESS, }); }); } else { - requestSubmitAccount({ form: { ...omit(values, exclude) } }).then((response) => { - closeDialog(name); - AppToaster.show({ - message: formatMessage({ - id: 'service_has_been_successful_created', - }, { - name: toastAccountName, - service: formatMessage({ id: 'account' }), - }), - intent: Intent.SUCCESS, - position: Position.BOTTOM, - }); - }); + requestSubmitAccount({ form: { ...omit(values, exclude) } }).then( + (response) => { + closeDialog(name); + AppToaster.show({ + message: formatMessage( + { + id: 'service_has_been_successful_created', + }, + { + name: toastAccountName, + service: formatMessage({ id: 'account' }), + }, + ), + intent: Intent.SUCCESS, + position: Position.BOTTOM, + }); + }, + ); } }, }); const { errors, values, touched } = useMemo(() => formik, [formik]); + // const filteredAccounts = accounts.filter( + // (account) => account.accountTypeId === formik.values.account_type_id, + // ); + + const filteredAccounts = accounts.filter((account) => { + return account.account_type_id === values.account_type_id; + }); + console.log(filteredAccounts, 'ERR'); + console.log(accounts,'ER/u'); // Set default account type. useEffect(() => { if (account && account.account_type_id) { const defaultType = accountsTypes.find( - (t) => t.id === account.account_type_id + (t) => t.id === account.account_type_id, ); defaultType && setSelectedAccountType(defaultType); @@ -187,7 +208,7 @@ function AccountFormDialog({ ); } }, - [] + [], ); // Handles dialog close. @@ -199,7 +220,7 @@ function AccountFormDialog({ const fetchAccountsList = useQuery( 'accounts-list', () => requestFetchAccounts(), - { manual: true } + { manual: true }, ); // Fetches accounts types. @@ -208,14 +229,14 @@ function AccountFormDialog({ async () => { await requestFetchAccountTypes(); }, - { manual: true } + { manual: true }, ); // Fetch the given account id on edit mode. const fetchAccount = useQuery( payload.action === 'edit' && ['account', payload.id], (key, id) => requestFetchAccount(id), - { manual: true } + { manual: true }, ); const isFetching = @@ -228,13 +249,13 @@ function AccountFormDialog({ fetchAccountsList.refetch(); fetchAccountsTypes.refetch(); fetchAccount.refetch(); - }, [ fetchAccount, fetchAccountsList, fetchAccountsTypes]); + }, [fetchAccount, fetchAccountsList, fetchAccountsTypes]); const onChangeAccountType = useCallback( (accountType) => { formik.setFieldValue('account_type_id', accountType.id); }, - [formik] + [formik], ); // Handles change sub-account. @@ -243,7 +264,7 @@ function AccountFormDialog({ setSelectedSubaccount(account); formik.setFieldValue('parent_account_id', account.id); }, - [setSelectedSubaccount, formik] + [setSelectedSubaccount, formik], ); const onDialogClosed = useCallback(() => { @@ -252,22 +273,28 @@ function AccountFormDialog({ setSelectedAccountType(null); }, [formik]); - const infoIcon = useMemo(() => , []); + const infoIcon = useMemo(() => , []); const subAccountLabel = useMemo(() => { return ( - + ); }, []); - const requiredSpan = useMemo(() => *, []); + const requiredSpan = useMemo(() => *, []); return ( : } + title={ + payload.action === 'edit' ? ( + + ) : ( + + ) + } className={{ 'dialog--loading': isFetching, 'dialog--account-form': true, @@ -283,41 +310,40 @@ function AccountFormDialog({
} + label={} labelInfo={requiredSpan} className={classNames( 'form-group--account-type', 'form-group--select-list', - Classes.FILL + Classes.FILL, )} inline={true} - helperText={} + helperText={} intent={ errors.account_type_id && touched.account_type_id && Intent.DANGER } > } + noResults={} itemRenderer={accountTypeItem} itemPredicate={filterAccountTypeItems} popoverProps={{ minimal: true }} onItemSelect={onChangeAccountType} - selectedItem={formik.values.account_type_id} selectedItemProp={'id'} - defaultText={} labelProp={'name'} - buttonProps={{ disabled: payload.action === 'edit' }} /> + buttonProps={{ disabled: payload.action === 'edit' }} + /> } + label={} labelInfo={requiredSpan} className={'form-group--account-name'} intent={errors.name && touched.name && Intent.DANGER} - helperText={} + helperText={} inline={true} > } + label={} className={'form-group--account-code'} intent={errors.code && touched.code && Intent.DANGER} - helperText={} + helperText={} inline={true} labelInfo={infoIcon} > @@ -356,37 +382,31 @@ function AccountFormDialog({ {values.subaccount && ( } + label={} className={classNames( 'form-group--parent-account', 'form-group--select-list', - Classes.FILL + Classes.FILL, )} inline={true} > - + selectedItem={formik.values.parent_account_id} + selectedItemProp={'id'} + defaultText={} + labelProp={'name'} + /> )} } + label={} className={'form-group--description'} intent={formik.errors.description && Intent.DANGER} helperText={formik.errors.description && formik.errors.credential} @@ -402,13 +422,19 @@ function AccountFormDialog({
- +
diff --git a/client/src/containers/Dialogs/CurrencyDialog.js b/client/src/containers/Dialogs/CurrencyDialog.js index 1bd8ed1a2..e43224f98 100644 --- a/client/src/containers/Dialogs/CurrencyDialog.js +++ b/client/src/containers/Dialogs/CurrencyDialog.js @@ -9,11 +9,10 @@ import { import * as Yup from 'yup'; import { FormattedMessage as T, useIntl } from 'react-intl'; import { useFormik } from 'formik'; -import { useQuery } from 'react-query'; +import { useQuery, queryCache } from 'react-query'; import { connect } from 'react-redux'; import { pick } from 'lodash'; - import AppToaster from 'components/AppToaster'; import Dialog from 'components/Dialog'; import DialogReduxConnect from 'components/DialogReduxConnect'; @@ -27,8 +26,6 @@ import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions'; import { compose } from 'utils'; - - function CurrencyDialog({ name, payload, @@ -46,18 +43,28 @@ function CurrencyDialog({ requestSubmitCurrencies, requestEditCurrency, }) { - const {formatMessage} = useIntl(); + const { formatMessage } = useIntl(); + + const fetchCurrencies = useQuery('currencies', () => + requestFetchCurrencies(), + ); const validationSchema = Yup.object().shape({ - currency_name: Yup.string().required().label(formatMessage({id:'currency_name_'})), + currency_name: Yup.string() + .required() + .label(formatMessage({ id: 'currency_name_' })), currency_code: Yup.string() .max(4) - .required().label(formatMessage({id:'currency_code_'})), + .required() + .label(formatMessage({ id: 'currency_code_' })), }); - const initialValues = useMemo(() => ({ - currency_name: '', - currency_code: '', - }), []); + const initialValues = useMemo( + () => ({ + currency_name: '', + currency_code: '', + }), + [], + ); const { errors, @@ -75,29 +82,37 @@ function CurrencyDialog({ validationSchema: validationSchema, onSubmit: (values, { setSubmitting }) => { if (payload.action === 'edit') { - requestEditCurrency(currency.id, values).then((response) => { - closeDialog(name); - AppToaster.show({ - message: formatMessage({id:'the_currency_has_been_successfully_edited'}), - intent: Intent.SUCCESS, + requestEditCurrency(currency.id, values) + .then((response) => { + closeDialog(name); + AppToaster.show({ + message: formatMessage({ + id: 'the_currency_has_been_successfully_edited', + }), + intent: Intent.SUCCESS, + }); + setSubmitting(false); + queryCache.refetchQueries('currencies', { force: true }); + }) + .catch((error) => { + setSubmitting(false); }); - setSubmitting(false); - }) - .catch((error) => { - setSubmitting(false); - }); } else { - requestSubmitCurrencies(values).then((response) => { - closeDialog(name); - AppToaster.show({ - message: formatMessage({id:'the_currency_has_been_successfully_created'}), - intent: Intent.SUCCESS, + requestSubmitCurrencies(values) + .then((response) => { + closeDialog(name); + AppToaster.show({ + message: formatMessage({ + id: 'the_currency_has_been_successfully_created', + }), + intent: Intent.SUCCESS, + }); + setSubmitting(false); + queryCache.refetchQueries('currencies', { force: true }); + }) + .catch((error) => { + setSubmitting(false); }); - setSubmitting(false); - }) - .catch((error) => { - setSubmitting(false); - }); } }, }); @@ -106,10 +121,6 @@ function CurrencyDialog({ closeDialog(name); }, [name, closeDialog]); - const fetchCurrencies = useQuery('currencies', - () => { requestFetchCurrencies(); }, - { manual: true }); - const onDialogOpening = useCallback(() => { fetchCurrencies.refetch(); }, [fetchCurrencies]); @@ -117,19 +128,25 @@ function CurrencyDialog({ const onDialogClosed = useCallback(() => { resetForm(); closeDialog(name); - }, [closeDialog, name,resetForm]); + }, [closeDialog, name, resetForm]); const requiredSpan = useMemo(() => *, []); return ( : } + title={ + payload.action === 'edit' ? ( + + ) : ( + + ) + } className={classNames( { 'dialog--loading': fetchCurrencies.isFetching, }, - 'dialog--currency-form' + 'dialog--currency-form', )} isOpen={isOpen} onClosed={onDialogClosed} @@ -140,31 +157,43 @@ function CurrencyDialog({
} + label={} labelInfo={requiredSpan} className={'form-group--currency-name'} - intent={(errors.currency_name && touched.currency_name) && Intent.DANGER} - helperText={} + intent={ + errors.currency_name && touched.currency_name && Intent.DANGER + } + helperText={ + + } inline={true} > } + label={} labelInfo={requiredSpan} className={'form-group--currency-code'} - intent={(errors.currency_code && touched.currency_code) && Intent.DANGER} - helperText={} + intent={ + errors.currency_code && touched.currency_code && Intent.DANGER + } + helperText={ + + } inline={true} > @@ -172,9 +201,19 @@ function CurrencyDialog({
- - +
@@ -190,8 +229,8 @@ const mapStateToProps = (state, props) => { name: 'currency-form', payload: { action: 'new', currencyCode: null, ...dialogPayload }, currencyCode: dialogPayload?.currencyCode || null, - } -} + }; +}; const withCurrencyFormDialog = connect(mapStateToProps); diff --git a/client/src/containers/Dialogs/ExchangeRateDialog.js b/client/src/containers/Dialogs/ExchangeRateDialog.js index 810faabb2..4df64e802 100644 --- a/client/src/containers/Dialogs/ExchangeRateDialog.js +++ b/client/src/containers/Dialogs/ExchangeRateDialog.js @@ -12,20 +12,19 @@ import { pick } from 'lodash'; import * as Yup from 'yup'; import { FormattedMessage as T, useIntl } from 'react-intl'; import { useFormik } from 'formik'; -import { Select } from '@blueprintjs/select'; -import { useQuery } from 'react-query'; +import { useQuery, queryCache } from 'react-query'; import moment from 'moment'; import { DateInput } from '@blueprintjs/datetime'; import { momentFormatter } from 'utils'; import AppToaster from 'components/AppToaster'; + import Dialog from 'components/Dialog'; import ErrorMessage from 'components/ErrorMessage'; import classNames from 'classnames'; - +import { ListSelect } from 'components'; import withExchangeRatesDialog from './ExchangeRateDialog.container'; - function ExchangeRateDialog({ name, payload, @@ -43,24 +42,37 @@ function ExchangeRateDialog({ requestEditExchangeRate, requestFetchCurrencies, editExchangeRate, - }) { const { formatMessage } = useIntl(); const [selectedItems, setSelectedItems] = useState({}); const validationSchema = Yup.object().shape({ - exchange_rate: Yup.number().required().label(formatMessage({id:'exchange_rate_'})), - currency_code: Yup.string().max(3).required(formatMessage({id:'currency_code_'})), - date: Yup.date().required().label(formatMessage({id:'date'})), + exchange_rate: Yup.number() + .required() + .label(formatMessage({ id: 'exchange_rate_' })), + currency_code: Yup.string() + .max(3) + .required(formatMessage({ id: 'currency_code_' })), + date: Yup.date() + .required() + .label(formatMessage({ id: 'date' })), }); - const initialValues = useMemo(() => ({ - exchange_rate: '', - currency_code: '', - date: moment(new Date()).format('YYYY-MM-DD'), - }), []); + const initialValues = useMemo( + () => ({ + exchange_rate: '', + currency_code: '', + date: moment(new Date()).format('YYYY-MM-DD'), + }), + [], + ); + + const fetchExchangeRatesDialog = useQuery('exchange-rates-dialog', () => + requestFetchExchangeRates(), + ); const { + values, touched, errors, isSubmitting, @@ -75,15 +87,19 @@ function ExchangeRateDialog({ ...(payload.action === 'edit' && pick(editExchangeRate, Object.keys(initialValues))), }, - onSubmit: (values, { setSubmitting }) => { + onSubmit: (values, { setSubmitting, setErrors }) => { if (payload.action === 'edit') { requestEditExchangeRate(payload.id, values) .then((response) => { closeDialog(name); AppToaster.show({ - message: formatMessage({id:'the_exchange_rate_has_been_successfully_edited'}) + message: formatMessage({ + id: 'the_exchange_rate_has_been_successfully_edited', + }), + intent: Intent.SUCCESS, }); setSubmitting(false); + queryCache.removeQueries('exchange-rates-dialog', { force: true }); }) .catch((error) => { setSubmitting(false); @@ -93,30 +109,40 @@ function ExchangeRateDialog({ .then((response) => { closeDialog(name); AppToaster.show({ - message: formatMessage({id:'the_exchange_rate_has_been_successfully_created'}) + message: formatMessage({ + id: 'the_exchange_rate_has_been_successfully_created', + }), + intent: Intent.SUCCESS, }); setSubmitting(false); + queryCache.refetchQueries('exchange-rates-table', { force: true }); }) - .catch((error) => { - setSubmitting(false); + .catch((errors) => { + if ( + errors.find((e) => e.type === 'EXCHANGE.RATE.DATE.PERIOD.DEFINED') + ) { + setErrors({ + exchange_rate: formatMessage({ + id: + 'there_is_exchange_rate_in_this_date_with_the_same_currency', + }), + }); + } }); } }, }); - const requiredSpan = useMemo(() => *, []); + const requiredSpan = useMemo(() => *, []); const handleClose = useCallback(() => { closeDialog(name); }, [name, closeDialog]); - const fetchExchangeRatesDialog = useQuery('exchange-rates-dialog', - () => requestFetchExchangeRates()); - const onDialogClosed = useCallback(() => { resetForm(); closeDialog(name); - }, [closeDialog, name,resetForm]); + }, [closeDialog, name, resetForm]); const onDialogOpening = useCallback(() => { fetchExchangeRatesDialog.refetch(); @@ -127,7 +153,7 @@ function ExchangeRateDialog({ const formatted = moment(date).format('YYYY-MM-DD'); setFieldValue('date', formatted); }, - [setFieldValue] + [setFieldValue], ); const onItemsSelect = useCallback( @@ -140,19 +166,19 @@ function ExchangeRateDialog({ setFieldValue(filedName, filed.currency_code); }; }, - [setFieldValue, selectedItems] + [setFieldValue, selectedItems], ); - const filterCurrencyCode = (query, currency_code, _index, exactMatch) => { - const normalizedTitle = currency_code.currency_code.toLowerCase(); + const filterCurrencyCode = (query, currency, _index, exactMatch) => { + const normalizedTitle = currency.currency_code.toLowerCase(); const normalizedQuery = query.toLowerCase(); if (exactMatch) { return normalizedTitle === normalizedQuery; } else { return ( - `${currency_code.currency_code} ${normalizedTitle}`.indexOf( - normalizedQuery + `${currency.currency_code} ${normalizedTitle}`.indexOf( + normalizedQuery, ) >= 0 ); } @@ -169,20 +195,19 @@ function ExchangeRateDialog({ ); }, []); - const getSelectedItemLabel = useCallback((fieldName, defaultLabel) => { - return typeof selectedItems[fieldName] !== 'undefined' - ? selectedItems[fieldName].currency_code - : defaultLabel; - }, [selectedItems]); - return ( : } + title={ + payload.action === 'edit' ? ( + + ) : ( + + ) + } className={classNames( - {'dialog--loading': fetchExchangeRatesDialog.isFetching}, - 'dialog--exchangeRate-form' + { 'dialog--loading': fetchExchangeRatesDialog.isFetching }, + 'dialog--exchangeRate-form', )} isOpen={isOpen} onClosed={onDialogClosed} @@ -193,11 +218,11 @@ function ExchangeRateDialog({
} + label={} inline={true} labelInfo={requiredSpan} intent={errors.date && touched.date && Intent.DANGER} - helperText={} + helperText={} > } + label={} labelInfo={requiredSpan} - intent={errors.exchange_rate && touched.exchange_rate && Intent.DANGER} - helperText={} + intent={ + errors.exchange_rate && touched.exchange_rate && Intent.DANGER + } + helperText={ + + } inline={true} > } + label={} labelInfo={requiredSpan} className={classNames('form-group--select-list', Classes.FILL)} inline={true} - intent={(errors.currency_code && touched.currency_code) && Intent.DANGER} - helperText={} + intent={ + errors.currency_code && touched.currency_code && Intent.DANGER + } + helperText={ + + } > - + selectedItem={values.currency_code} + selectedItemProp={'currency_code'} + defaultText={} + labelProp={'currency_code'} + />
- - +
diff --git a/client/src/containers/Dialogs/InviteUserDialog.js b/client/src/containers/Dialogs/InviteUserDialog.js index 999473e3e..920a913ee 100644 --- a/client/src/containers/Dialogs/InviteUserDialog.js +++ b/client/src/containers/Dialogs/InviteUserDialog.js @@ -41,12 +41,19 @@ function InviteUserDialog({ }, false); const validationSchema = Yup.object().shape({ - first_name: Yup.string().required().label(formatMessage({id:'first_name_'})), - last_name: Yup.string().required().label(formatMessage({id:'last_name_'})), + first_name: Yup.string() + .required() + .label(formatMessage({ id: 'first_name_' })), + last_name: Yup.string() + .required() + .label(formatMessage({ id: 'last_name_' })), email: Yup.string() .email() - .required().label(formatMessage({id:'email'})), - phone_number: Yup.number().required().label(formatMessage({id:'phone_number_'})), + .required() + .label(formatMessage({ id: 'email' })), + phone_number: Yup.number() + .required() + .label(formatMessage({ id: 'phone_number_' })), }); const initialValues = useMemo( @@ -56,7 +63,7 @@ function InviteUserDialog({ email: '', phone_number: '', }), - [] + [], ); const formik = useFormik({ @@ -65,7 +72,7 @@ function InviteUserDialog({ ...(payload.action === 'edit' && pick( objectKeysTransform(payload.user, snakeCase), - Object.keys(initialValues) + Object.keys(initialValues), )), }, validationSchema, @@ -78,10 +85,13 @@ function InviteUserDialog({ .then((response) => { closeDialog(name); AppToaster.show({ - message: formatMessage({id:'the_user_details_has_been_updated'}), + message: formatMessage({ + id: 'the_user_details_has_been_updated', + }), + intent: Intent.SUCCESS, }); setSubmitting(false); - queryCache.refetchQueries('users-table',{force:true}) + queryCache.refetchQueries('users-table', { force: true }); }) .catch((error) => { setSubmitting(false); @@ -99,10 +109,10 @@ function InviteUserDialog({ formik.resetForm(); }, [formik.resetForm]); - // Handles dialog close. - const handleClose = useCallback(() => { - closeDialog(name); -}, [closeDialog, name]); + // Handles dialog close. + const handleClose = useCallback(() => { + closeDialog(name); + }, [closeDialog, name]); return ( } className={'form-group--first-name'} intent={errors.first_name && touched.first_name && Intent.DANGER} - helperText={} + helperText={} inline={true} > } className={'form-group--last-name'} intent={errors.last_name && touched.last_name && Intent.DANGER} - helperText={} + helperText={} inline={true} > } className={'form-group--email'} intent={errors.email && touched.email && Intent.DANGER} - helperText={} + helperText={} inline={true} > } + helperText={} inline={true} >
- - +
@@ -195,5 +211,5 @@ function InviteUserDialog({ export default compose( UserListDialogConnect, - DialogReduxConnect + DialogReduxConnect, )(InviteUserDialog); diff --git a/client/src/containers/Dialogs/ItemCategoryDialog.js b/client/src/containers/Dialogs/ItemCategoryDialog.js index 0dd751053..de9547d85 100644 --- a/client/src/containers/Dialogs/ItemCategoryDialog.js +++ b/client/src/containers/Dialogs/ItemCategoryDialog.js @@ -6,7 +6,7 @@ import { InputGroup, Intent, TextArea, - MenuItem + MenuItem, } from '@blueprintjs/core'; import { Select } from '@blueprintjs/select'; import { pick } from 'lodash'; @@ -16,10 +16,11 @@ import { useFormik } from 'formik'; import { compose } from 'utils'; import { useQuery, queryCache } from 'react-query'; import classNames from 'classnames'; -import {connect} from 'react-redux'; +import { connect } from 'react-redux'; import AppToaster from 'components/AppToaster'; import ErrorMessage from 'components/ErrorMessage'; +import { ListSelect } from 'components'; import Dialog from 'components/Dialog'; import DialogConnect from 'connectors/Dialog.connector'; @@ -32,7 +33,6 @@ import withItemCategoriesActions from 'containers/Items/withItemCategoriesAction import Icon from 'components/Icon'; - function ItemCategoryDialog({ name, payload, @@ -57,23 +57,30 @@ function ItemCategoryDialog({ const [selectedParentCategory, setParentCategory] = useState(null); const { formatMessage } = useIntl(); - const fetchList = useQuery(['items-categories-list'], - () => requestFetchItemCategories()); + const fetchList = useQuery(['items-categories-list'], () => + requestFetchItemCategories(), + ); const validationSchema = Yup.object().shape({ - name: Yup.string().required().label(formatMessage({id:'category_name_'})), + name: Yup.string() + .required() + .label(formatMessage({ id: 'category_name_' })), parent_category_id: Yup.string().nullable(), - description: Yup.string().trim() + description: Yup.string().trim(), }); - const initialValues = useMemo(() => ({ - name: '', - description: '', - parent_category_id: null - }), []); + const initialValues = useMemo( + () => ({ + name: '', + description: '', + parent_category_id: null, + }), + [], + ); // Formik const { + values, errors, touched, setFieldValue, @@ -85,24 +92,28 @@ function ItemCategoryDialog({ enableReinitialize: true, initialValues: { ...(payload.action === 'edit' && - pick(itemCategory, Object.keys(initialValues))) + pick(itemCategory, Object.keys(initialValues))), }, validationSchema, onSubmit: (values, { setSubmitting }) => { if (payload.action === 'edit') { - requestEditItemCategory(payload.id, values).then(response => { - closeDialog(name); - AppToaster.show({ - message: formatMessage({ - id: 'the_item_category_has_been_successfully_edited', - }), - intent: Intent.SUCCESS, + requestEditItemCategory(payload.id, values) + .then((response) => { + closeDialog(name); + AppToaster.show({ + message: formatMessage({ + id: 'the_item_category_has_been_successfully_edited', + }), + intent: Intent.SUCCESS, + }); + setSubmitting(false); + queryCache.refetchQueries('items-categories-table', { + force: true, + }); + }) + .catch((error) => { + setSubmitting(false); }); - setSubmitting(false); - queryCache.refetchQueries('items-categories-table', { force: true }); - }).catch((error) => { - setSubmitting(false); - }); } else { requestSubmitItemCategory(values) .then((response) => { @@ -114,60 +125,84 @@ function ItemCategoryDialog({ intent: Intent.SUCCESS, }); setSubmitting(false); - queryCache.refetchQueries('items-categories-table', { force: true }); + queryCache.refetchQueries('items-categories-table', { + force: true, + }); }) .catch((error) => { setSubmitting(false); }); } - } + }, }); - const filterItemCategory = useCallback((query, category, _index, exactMatch) => { - const normalizedTitle = category.name.toLowerCase(); - const normalizedQuery = query.toLowerCase(); + const filterItemCategory = useCallback( + (query, category, _index, exactMatch) => { + const normalizedTitle = category.name.toLowerCase(); + const normalizedQuery = query.toLowerCase(); - if (exactMatch) { - return normalizedTitle === normalizedQuery; - } else { - return normalizedTitle.indexOf(normalizedQuery) >= 0; - } - }, []); + if (exactMatch) { + return normalizedTitle === normalizedQuery; + } else { + return normalizedTitle.indexOf(normalizedQuery) >= 0; + } + }, + [], + ); - const parentCategoryItem = useCallback((category, { handleClick, modifiers, query }) => { - return ( - - ); - }, []); + const parentCategoryItem = useCallback( + (category, { handleClick, modifiers, query }) => { + return ( + + ); + }, + [], + ); // Handle the dialog closing. - const handleClose = useCallback(() => { closeDialog(name); }, [name, closeDialog]); + const handleClose = useCallback(() => { + closeDialog(name); + }, [name, closeDialog]); // Handle the dialog opening. const onDialogOpening = useCallback(() => { fetchList.refetch(); }, [fetchList]); - const onChangeParentCategory = useCallback((parentCategory) => { - setParentCategory(parentCategory); - setFieldValue('parent_category_id', parentCategory.id); - }, [setFieldValue]); + const onChangeParentCategory = useCallback( + (parentCategory) => { + setParentCategory(parentCategory); + setFieldValue('parent_category_id', parentCategory.id); + }, + [setFieldValue], + ); const onDialogClosed = useCallback(() => { resetForm(); closeDialog(name); }, [resetForm, closeDialog, name]); - const requiredSpan = useMemo(() => (*), []); - const infoIcon = useMemo(() => (), []); + const requiredSpan = useMemo(() => *, []); + const infoIcon = useMemo(() => , []); return ( : } - className={classNames({ - 'dialog--loading': fetchList.isFetching, - }, + title={ + payload.action === 'edit' ? ( + + ) : ( + + ) + } + className={classNames( + { + 'dialog--loading': fetchList.isFetching, + }, 'dialog--category-form', )} isOpen={isOpen} @@ -179,22 +214,22 @@ function ItemCategoryDialog({
} + label={} labelInfo={requiredSpan} className={'form-group--category-name'} - intent={(errors.name && touched.name) && Intent.DANGER} - helperText={()} + intent={errors.name && touched.name && Intent.DANGER} + helperText={} inline={true} > } + label={} labelInfo={infoIcon} className={classNames( 'form-group--select-list', @@ -202,29 +237,39 @@ function ItemCategoryDialog({ Classes.FILL, )} inline={true} - helperText={()} - intent={(errors.parent_category_id && touched.parent_category_id) && Intent.DANGER} + helperText={ + + } + intent={ + errors.parent_category_id && + touched.parent_category_id && + Intent.DANGER + } > - + selectedItem={values.parent_category_id} + selectedItemProp={'id'} + defaultText={} + labelProp={'name'} + /> } + label={} className={'form-group--description'} - intent={(errors.description && touched.description) && Intent.DANGER} - helperText={()} + intent={errors.description && touched.description && Intent.DANGER} + helperText={ + + } inline={true} >