From cceb4786c2479d53f0fdc7cfd626639353a4b287 Mon Sep 17 00:00:00 2001 From: elforjani3 Date: Sun, 10 May 2020 20:44:23 +0200 Subject: [PATCH] WIP / exchangeRate / localize --- client/src/components/AccountsMultiSelect.js | 3 +- client/src/components/Authentication.js | 33 +- client/src/components/CurrenciesSelectList.js | 3 +- .../components/Dashboard/DashboardTopbar.js | 7 +- client/src/components/DialogsContainer.js | 2 + client/src/components/Expenses/ExpenseForm.js | 23 +- .../components/Expenses/ExpensesActionsBar.js | 13 +- client/src/components/FilterDropdown.js | 3 +- client/src/config/sidebarMenu.js | 68 ++-- .../ExchangeRatesFromDialog.connect.js | 31 ++ .../Accounting/MakeJournalEntriesFooter.js | 37 +- .../Accounting/MakeJournalEntriesHeader.js | 12 +- .../Accounting/MakeJournalEntriesTable.js | 252 +++++++------ .../Accounting/ManualJournalActionsBar.js | 12 +- .../Accounting/ManualJournalsDataTable.js | 218 ++++++----- .../Accounting/ManualJournalsList.js | 127 ++++--- .../containers/Accounts/AccountsActionsBar.js | 15 +- .../src/containers/Accounts/AccountsChart.js | 9 +- .../containers/Accounts/AccountsDataTable.js | 22 +- .../src/containers/Accounts/withAccounts.js | 2 +- .../containers/Authentication/InviteAccept.js | 174 +++++---- client/src/containers/Authentication/Login.js | 4 +- .../src/containers/Authentication/Register.js | 207 +++++++---- .../Authentication/ResetPassword.js | 78 ++-- .../Authentication/SendResetPassword.js | 54 +-- .../containers/Dialogs/AccountFormDialog.js | 250 ++++++++----- .../src/containers/Dialogs/CurrencyDialog.js | 18 +- .../containers/Dialogs/ExchangeRateDialog.js | 282 ++++++++++++++ .../containers/Dialogs/InviteUserDialog.js | 26 +- .../containers/Dialogs/ItemCategoryDialog.js | 18 +- .../src/containers/Dialogs/ItemFromDialog.js | 42 ++- .../src/containers/Dialogs/UserFormDialog.js | 36 +- .../src/containers/Expenses/ExpensesList.js | 5 +- .../ExchangeRates/ExchangeRate.js | 122 ++++++ .../ExchangeRates/ExchangeRateActionsBar.js | 112 ++++++ .../ExchangeRates/ExchangeRateTable.js | 143 ++++++++ .../ExchangeRates/withExchangeRates.js | 8 + .../ExchangeRates/withExchangeRatesActions.js | 21 ++ .../containers/Items/ItemCategoriesTable.js | 13 +- client/src/containers/Items/ItemForm.js | 346 +++++++++++------- .../src/containers/Items/ItemsActionsBar.js | 15 +- .../Items/ItemsCategoryActionsBar.js | 11 +- client/src/containers/Items/ItemsDataTable.js | 21 +- client/src/containers/Items/ItemsList.js | 5 +- client/src/containers/Views/ViewFormPage.js | 7 +- client/src/lang/en/index.js | 212 +++++++++-- client/src/routes/dashboard.js | 73 ++-- .../store/ExchangeRate/exchange.actions.js | 57 +++ .../store/ExchangeRate/exchange.reducer.js | 20 + .../src/store/ExchangeRate/exchange.type.js | 7 + client/src/store/reducers.js | 3 + client/src/store/types.js | 3 + client/src/style/App.scss | 1 + client/src/style/pages/exchange-rate.scss | 23 ++ server/src/models/ExchangeRate.js | 1 - 55 files changed, 2339 insertions(+), 971 deletions(-) create mode 100644 client/src/connectors/ExchangeRatesFromDialog.connect.js create mode 100644 client/src/containers/Dialogs/ExchangeRateDialog.js create mode 100644 client/src/containers/FinancialStatements/ExchangeRates/ExchangeRate.js create mode 100644 client/src/containers/FinancialStatements/ExchangeRates/ExchangeRateActionsBar.js create mode 100644 client/src/containers/FinancialStatements/ExchangeRates/ExchangeRateTable.js create mode 100644 client/src/containers/FinancialStatements/ExchangeRates/withExchangeRates.js create mode 100644 client/src/containers/FinancialStatements/ExchangeRates/withExchangeRatesActions.js create mode 100644 client/src/store/ExchangeRate/exchange.actions.js create mode 100644 client/src/store/ExchangeRate/exchange.reducer.js create mode 100644 client/src/store/ExchangeRate/exchange.type.js create mode 100644 client/src/style/pages/exchange-rate.scss diff --git a/client/src/components/AccountsMultiSelect.js b/client/src/components/AccountsMultiSelect.js index 5fcfa0b24..0e00a40e4 100644 --- a/client/src/components/AccountsMultiSelect.js +++ b/client/src/components/AccountsMultiSelect.js @@ -6,6 +6,7 @@ import { } from '@blueprintjs/core'; // import {Select} from '@blueprintjs/select'; import MultiSelect from 'components/MultiSelect'; +import { FormattedMessage as T, useIntl } from 'react-intl'; export default function AccountsMultiSelect({ accounts, @@ -58,7 +59,7 @@ export default function AccountsMultiSelect({ - - + + diff --git a/client/src/components/Expenses/ExpensesActionsBar.js b/client/src/components/Expenses/ExpensesActionsBar.js index 049bd4911..79d02e13a 100644 --- a/client/src/components/Expenses/ExpensesActionsBar.js +++ b/client/src/components/Expenses/ExpensesActionsBar.js @@ -16,6 +16,7 @@ import { useRouteMatch } from 'react-router-dom' import classNames from 'classnames'; import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; import Icon from 'components/Icon'; +import { FormattedMessage as T, useIntl } from 'react-intl'; export default function ExpensesActionsBar({ @@ -39,7 +40,7 @@ export default function ExpensesActionsBar({ diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js index a86301ddb..906d79b7f 100644 --- a/client/src/config/sidebarMenu.js +++ b/client/src/config/sidebarMenu.js @@ -1,16 +1,16 @@ export default [ { - divider: true + divider: true, }, { icon: 'homepage', iconSize: 20, text: 'Homepage', disabled: false, - href: '/dashboard/homepage' + href: '/dashboard/homepage', }, { - divider: true + divider: true, }, { icon: 'homepage', @@ -19,20 +19,20 @@ export default [ children: [ { text: 'Items List', - href: '/dashboard/items' + href: '/dashboard/items', }, { text: 'New Item', - href: '/dashboard/items/new' + href: '/dashboard/items/new', }, { text: 'Category List', - href: '/dashboard/items/categories' + href: '/dashboard/items/categories', }, - ] + ], }, { - divider: true + divider: true, }, { icon: 'balance-scale', @@ -41,29 +41,33 @@ export default [ children: [ { text: 'Accounts Chart', - href: '/dashboard/accounts' + href: '/dashboard/accounts', }, { text: 'Manual Journal', - href: '/dashboard/accounting/manual-journals' + href: '/dashboard/accounting/manual-journals', }, { text: 'Make Journal', - href: '/dashboard/accounting/make-journal-entry' + href: '/dashboard/accounting/make-journal-entry', }, - ] + { + text: 'Exchange Rate', + href: '/dashboard/ExchangeRates', + }, + ], }, { icon: 'university', iconSize: 20, text: 'Banking', - children: [] + children: [], }, { icon: 'shopping-cart', iconSize: 20, text: 'Sales', - children: [] + children: [], }, { icon: 'balance-scale', @@ -74,9 +78,9 @@ export default [ icon: 'cut', text: 'cut', label: '⌘C', - disabled: false - } - ] + disabled: false, + }, + ], }, { icon: 'analytics', @@ -85,25 +89,25 @@ export default [ children: [ { text: 'Balance Sheet', - href: '/dashboard/accounting/balance-sheet' + href: '/dashboard/accounting/balance-sheet', }, { text: 'Trial Balance Sheet', - href: '/dashboard/accounting/trial-balance-sheet' + href: '/dashboard/accounting/trial-balance-sheet', }, { text: 'Journal', - href: '/dashboard/accounting/journal-sheet' + href: '/dashboard/accounting/journal-sheet', }, { text: 'General Ledger', - href: '/dashboard/accounting/general-ledger' + href: '/dashboard/accounting/general-ledger', }, { text: 'Profit Loss Sheet', - href: '/dashboard/accounting/profit-loss-sheet' - } - ] + href: '/dashboard/accounting/profit-loss-sheet', + }, + ], }, { text: 'Expenses', @@ -112,23 +116,23 @@ export default [ children: [ { text: 'Expenses List', - href: '/dashboard/expenses' + href: '/dashboard/expenses', }, { text: 'New Expenses', - href: '/dashboard/expenses/new' - } - ] + href: '/dashboard/expenses/new', + }, + ], }, { - divider: true + divider: true, }, { text: 'Preferences', - href: '/dashboard/preferences' + href: '/dashboard/preferences', }, { text: 'Auditing System', - href: '/dashboard/auditing/list' - } + href: '/dashboard/auditing/list', + }, ]; diff --git a/client/src/connectors/ExchangeRatesFromDialog.connect.js b/client/src/connectors/ExchangeRatesFromDialog.connect.js new file mode 100644 index 000000000..d93be823d --- /dev/null +++ b/client/src/connectors/ExchangeRatesFromDialog.connect.js @@ -0,0 +1,31 @@ +import { connect } from 'react-redux'; +import { compose } from 'utils'; +import DialogConnect from 'connectors/Dialog.connector'; +import DialogReduxConnect from 'components/DialogReduxConnect'; +import withExchangeRatesActions from 'containers/FinancialStatements/ExchangeRates/withExchangeRatesActions'; +import withExchangeRates from 'containers/FinancialStatements/ExchangeRates/withExchangeRates'; +import { getDialogPayload } from 'store/dashboard/dashboard.reducer'; +import CurrencyFromDialogConnect from 'connectors/CurrencyFromDialog.connect'; + +export const mapStateToProps = (state, props) => { + const dialogPayload = getDialogPayload(state, 'exchangeRate-form'); + + return { + exchangeRatesList: Object.values(state.exchangeRates.exchangeRates), + name: 'exchangeRate-form', + payload: { action: 'new', id: null, ...dialogPayload }, + editExchangeRate: + dialogPayload && dialogPayload.action === 'edit' + ? state.exchangeRates.exchangeRates[dialogPayload.id] + : {}, + }; +}; +const ExchangeRatesDialogConnect = connect(mapStateToProps); +export default compose( + CurrencyFromDialogConnect, + ExchangeRatesDialogConnect, + withExchangeRatesActions, + withExchangeRates, + DialogReduxConnect, + DialogConnect +); diff --git a/client/src/containers/Accounting/MakeJournalEntriesFooter.js b/client/src/containers/Accounting/MakeJournalEntriesFooter.js index bd2549ace..0b90ed418 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesFooter.js +++ b/client/src/containers/Accounting/MakeJournalEntriesFooter.js @@ -1,9 +1,6 @@ -import React, {useMemo} from 'react'; -import { - Intent, - Button, -} from '@blueprintjs/core'; -import { FormattedList } from 'react-intl'; +import React, { useMemo } from 'react'; +import { Intent, Button } from '@blueprintjs/core'; +import { FormattedMessage as T, useIntl } from 'react-intl'; export default function MakeJournalEntriesFooter({ formik: { isSubmitting }, @@ -12,15 +9,16 @@ export default function MakeJournalEntriesFooter({ }) { return (
- ); -} \ No newline at end of file +} diff --git a/client/src/containers/Accounting/MakeJournalEntriesHeader.js b/client/src/containers/Accounting/MakeJournalEntriesHeader.js index 94962a562..8a766f018 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesHeader.js +++ b/client/src/containers/Accounting/MakeJournalEntriesHeader.js @@ -6,7 +6,7 @@ import { Position, } from '@blueprintjs/core'; import {DateInput} from '@blueprintjs/datetime'; -import {useIntl} from 'react-intl'; +import { FormattedMessage as T, useIntl } from 'react-intl'; import {Row, Col} from 'react-grid-system'; import moment from 'moment'; import {momentFormatter} from 'utils'; @@ -17,7 +17,7 @@ import ErrorMessage from 'components/ErrorMessage'; export default function MakeJournalEntriesHeader({ formik: { errors, touched, setFieldValue, getFieldProps } }) { - const intl = useIntl(); + const {formatMessage} = useIntl(); const handleDateChange = useCallback((date) => { const formatted = moment(date).format('YYYY-MM-DD'); @@ -32,7 +32,7 @@ export default function MakeJournalEntriesHeader({ } labelInfo={infoIcon} className={'form-group--journal-number'} intent={(errors.journal_number && touched.journal_number) && Intent.DANGER} @@ -48,7 +48,7 @@ export default function MakeJournalEntriesHeader({ } intent={(errors.date && touched.date) && Intent.DANGER} helperText={} minimal={true}> @@ -63,7 +63,7 @@ export default function MakeJournalEntriesHeader({ } className={'form-group--description'} intent={(errors.name && touched.name) && Intent.DANGER} helperText={} @@ -80,7 +80,7 @@ export default function MakeJournalEntriesHeader({ } labelInfo={infoIcon} className={'form-group--reference'} intent={(errors.reference && touched.reference) && Intent.DANGER} diff --git a/client/src/containers/Accounting/MakeJournalEntriesTable.js b/client/src/containers/Accounting/MakeJournalEntriesTable.js index d0d3dcc56..817d1327f 100644 --- a/client/src/containers/Accounting/MakeJournalEntriesTable.js +++ b/client/src/containers/Accounting/MakeJournalEntriesTable.js @@ -1,11 +1,8 @@ -import React, {useState, useMemo, useEffect, useCallback} from 'react'; -import { - Button, - Intent, -} from '@blueprintjs/core'; +import React, { useState, useMemo, useEffect, useCallback } from 'react'; +import { Button, Intent } from '@blueprintjs/core'; import DataTable from 'components/DataTable'; import Icon from 'components/Icon'; -import { compose, formattedAmount} from 'utils'; +import { compose, formattedAmount } from 'utils'; import { AccountsListFieldCell, MoneyFieldCell, @@ -14,7 +11,7 @@ import { import { omit } from 'lodash'; import withAccounts from 'containers/Accounts/withAccounts'; - +import { FormattedMessage as T, useIntl } from 'react-intl'; // Actions cell renderer. const ActionsCellRenderer = ({ @@ -24,7 +21,7 @@ const ActionsCellRenderer = ({ data, payload, }) => { - if (data.length <= (index + 2)) { + if (data.length <= index + 2) { return ''; } const onClickRemoveRole = () => { @@ -32,47 +29,47 @@ const ActionsCellRenderer = ({ }; return ( -
); } -export default compose( - withAccounts, -)(MakeJournalEntriesTable); \ No newline at end of file +export default compose(withAccounts)(MakeJournalEntriesTable); diff --git a/client/src/containers/Accounting/ManualJournalActionsBar.js b/client/src/containers/Accounting/ManualJournalActionsBar.js index 1c52c0c2b..1f2c6f032 100644 --- a/client/src/containers/Accounting/ManualJournalActionsBar.js +++ b/client/src/containers/Accounting/ManualJournalActionsBar.js @@ -23,6 +23,7 @@ import withResourceDetail from 'containers/Resources/withResourceDetails'; import withManualJournals from 'containers/Accounting/withManualJournals'; import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions'; +import { FormattedMessage as T, useIntl } from 'react-intl'; function ManualJournalActionsBar({ resourceName = 'manual_journal', @@ -37,6 +38,7 @@ function ManualJournalActionsBar({ }) { const { path } = useRouteMatch(); const history = useHistory(); + const {formatMessage} = useIntl(); const viewsMenuItems = manualJournalsViews.map(view => { return ( @@ -76,7 +78,7 @@ function ManualJournalActionsBar({ - { inviteMeta.pending && ( -
+ {inviteMeta.pending && ( +
)} @@ -225,6 +269,4 @@ function Invite({ ); } -export default compose( - withAuthenticationActions, -)(Invite); +export default compose(withAuthenticationActions)(Invite); diff --git a/client/src/containers/Authentication/Login.js b/client/src/containers/Authentication/Login.js index 2c5f30723..bc6f1925b 100644 --- a/client/src/containers/Authentication/Login.js +++ b/client/src/containers/Authentication/Login.js @@ -69,7 +69,7 @@ function Login({ const toastBuilders = []; if (errors.find((e) => e.type === ERRORS_TYPES.INVALID_DETAILS)) { toastBuilders.push({ - message: formatMessage('email_and_password_entered_did_not_match'), + message: formatMessage({id:'email_and_password_entered_did_not_match'}), intent: Intent.DANGER, }); } @@ -102,7 +102,7 @@ function Login({

- +
diff --git a/client/src/containers/Authentication/Register.js b/client/src/containers/Authentication/Register.js index 384074544..fc7d9233c 100644 --- a/client/src/containers/Authentication/Register.js +++ b/client/src/containers/Authentication/Register.js @@ -1,13 +1,14 @@ import React, { useMemo, useState, useCallback } from 'react'; import * as Yup from 'yup'; import { useFormik } from 'formik'; -import { useIntl } from 'react-intl'; +import { FormattedMessage as T, useIntl } from 'react-intl'; + import { Button, InputGroup, Intent, FormGroup, - Spinner + Spinner, } from '@blueprintjs/core'; import { Row, Col } from 'react-grid-system'; import { Link, useHistory } from 'react-router-dom'; @@ -19,35 +20,40 @@ import { compose } from 'utils'; import Icon from 'components/Icon'; import { If } from 'components'; -function Register({ - requestRegister, -}) { - const intl = useIntl(); +function Register({ requestRegister }) { + const { formatMessage } = useIntl(); const history = useHistory(); const [shown, setShown] = useState(false); - const passwordRevealer = useCallback(() => { setShown(!shown); }, [shown]); + const passwordRevealer = useCallback(() => { + setShown(!shown); + }, [shown]); const ValidationSchema = Yup.object().shape({ - organization_name: Yup.string().required(), - first_name: Yup.string().required(), - last_name: Yup.string().required(), - email: Yup.string().email().required(), + organization_name: Yup.string().required(formatMessage({ id: 'required' })), + first_name: Yup.string().required(formatMessage({ id: 'required' })), + last_name: Yup.string().required(formatMessage({ id: 'required' })), + email: Yup.string() + .email() + .required(formatMessage({ id: 'required' })), phone_number: Yup.string() .matches() - .required(intl.formatMessage({ id: 'required' })), + .required(formatMessage({ id: 'required' })), password: Yup.string() .min(4, 'Password has to be longer than 8 characters!') .required('Password is required!'), }); - const initialValues = useMemo(() => ({ - organization_name: '', - first_name: '', - last_name: '', - email: '', - phone_number: '', - password: '', - }), []); + const initialValues = useMemo( + () => ({ + organization_name: '', + first_name: '', + last_name: '', + email: '', + phone_number: '', + password: '', + }), + [] + ); const { errors, @@ -62,26 +68,27 @@ function Register({ validationSchema: ValidationSchema, initialValues: { ...initialValues, - country: 'libya' + country: 'libya', }, onSubmit: (values, { setSubmitting, setErrors }) => { requestRegister(values) .then((response) => { AppToaster.show({ - message: 'success', + message: formatMessage({ id: 'success' }), }); setSubmitting(false); history.push('/auth/login'); }) .catch((errors) => { - if (errors.some(e => e.type === 'PHONE_NUMBER_EXISTS')) { + if (errors.some((e) => e.type === 'PHONE_NUMBER_EXISTS')) { setErrors({ - phone_number: 'The phone number is already used in another account.' + phone_number: + 'The phone number is already used in another account.', }); } - if (errors.some(e => e.type === 'EMAIL_EXISTS')) { + if (errors.some((e) => e.type === 'EMAIL_EXISTS')) { setErrors({ - email: 'The email is already used in another account.' + email: 'The email is already used in another account.', }); } setSubmitting(false); @@ -89,34 +96,66 @@ function Register({ }, }); - const passwordRevealerTmp = useMemo(() => ( - passwordRevealer()}> - {(shown) ? ( - <> Hide - ) : ( - <> Show - )} - ), [shown, passwordRevealer]); + const passwordRevealerTmp = useMemo( + () => ( + passwordRevealer()}> + + <> + {' '} + + + + + + + <> + {' '} + + + + + + + ), + [shown, passwordRevealer] + ); return (

- Register a New
Organization. +

- You have a bigcapital account ? Login + + + {' '} + +
} className={'form-group--name'} - intent={(errors.organization_name && touched.organization_name) && Intent.DANGER} - helperText={} + intent={ + errors.organization_name && + touched.organization_name && + Intent.DANGER + } + helperText={ + + } > @@ -124,13 +163,19 @@ function Register({ } + label={} + intent={ + errors.first_name && touched.first_name && Intent.DANGER + } + helperText={ + + } className={'form-group--first-name'} > @@ -138,65 +183,83 @@ function Register({ } + label={} + intent={errors.last_name && touched.last_name && Intent.DANGER} + helperText={ + + } className={'form-group--last-name'} > - + } + label={} + intent={ + errors.phone_number && touched.phone_number && Intent.DANGER + } + helperText={ + + } className={'form-group--phone-number'} > - + } + label={} + intent={errors.email && touched.email && Intent.DANGER} + helperText={ + + } className={'form-group--email'} > - + } labelInfo={passwordRevealerTmp} - intent={(errors.password && touched.password) && Intent.DANGER} - helperText={} + intent={errors.password && touched.password && Intent.DANGER} + helperText={ + + } className={'form-group--password has-password-revealer'} > - +
-

- By signing in or creating an account, you agree with our
- Terms & Conditions and Privacy Statement +

+
+ + + {' '} + + + {' '} + +

@@ -208,13 +271,13 @@ function Register({ fill={true} loading={isSubmitting} > - Register +
-
+
@@ -223,6 +286,4 @@ function Register({ ); } -export default compose( - withAuthenticationActions, -)(Register); +export default compose(withAuthenticationActions)(Register); diff --git a/client/src/containers/Authentication/ResetPassword.js b/client/src/containers/Authentication/ResetPassword.js index 7da28b8b0..43d5d3f75 100644 --- a/client/src/containers/Authentication/ResetPassword.js +++ b/client/src/containers/Authentication/ResetPassword.js @@ -1,7 +1,7 @@ import React, { useEffect, useMemo } from 'react'; import * as Yup from 'yup'; import { useFormik } from 'formik'; -import { useIntl } from 'react-intl'; + import { Button, InputGroup, @@ -15,12 +15,10 @@ import AppToaster from 'components/AppToaster'; import { compose } from 'utils'; import withAuthenticationActions from './withAuthenticationActions'; import AuthInsider from 'containers/Authentication/AuthInsider'; +import { FormattedMessage as T, useIntl } from 'react-intl'; - -function ResetPassword({ - requestResetPassword, -}) { - const intl = useIntl(); +function ResetPassword({ requestResetPassword }) { + const { formatMessage } = useIntl(); const { token } = useParams(); const history = useHistory(); @@ -33,10 +31,13 @@ function ResetPassword({ .required('Confirm Password is required'), }); - const initialValues = useMemo(() => ({ - password: '', - confirm_password: '', - }), []); + const initialValues = useMemo( + () => ({ + password: '', + confirm_password: '', + }), + [] + ); const { touched, @@ -56,7 +57,7 @@ function ResetPassword({ requestResetPassword(values, token) .then((response) => { AppToaster.show({ - message: 'The password for your account was successfully updated.', + message: formatMessage('password_successfully_updated'), intent: Intent.DANGER, position: Position.BOTTOM, }); @@ -64,9 +65,9 @@ function ResetPassword({ setSubmitting(false); }) .catch((errors) => { - if (errors.find(e => e.type === 'TOKEN_INVALID')) { + if (errors.find((e) => e.type === 'TOKEN_INVALID')) { AppToaster.show({ - message: 'An unexpected error occurred', + message: formatMessage('an_unexpected_error_occurred'), intent: Intent.DANGER, position: Position.BOTTOM, }); @@ -79,17 +80,24 @@ function ResetPassword({ return ( -
+
-

Choose a new password

- You remembered your password ? Login +

+ +

+ {' '} + + +
} + label={} + intent={errors.password && touched.password && Intent.DANGER} + helperText={ + + } className={'form-group--password'} > - + } labelInfo={'(again):'} - intent={(errors.confirm_password && touched.confirm_password) && Intent.DANGER} - helperText={} + intent={ + errors.confirm_password && + touched.confirm_password && + Intent.DANGER + } + helperText={ + + } className={'form-group--confirm-password'} > @@ -121,8 +142,9 @@ function ResetPassword({ className={'btn-new'} intent={Intent.PRIMARY} type='submit' - loading={isSubmitting}> - Submit new password + loading={isSubmitting} + > +
@@ -131,6 +153,4 @@ function ResetPassword({ ); } -export default compose( - withAuthenticationActions, -)(ResetPassword); +export default compose(withAuthenticationActions)(ResetPassword); diff --git a/client/src/containers/Authentication/SendResetPassword.js b/client/src/containers/Authentication/SendResetPassword.js index f33accec8..ee85ed052 100644 --- a/client/src/containers/Authentication/SendResetPassword.js +++ b/client/src/containers/Authentication/SendResetPassword.js @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import * as Yup from 'yup'; import { useFormik } from 'formik'; -import { useIntl } from 'react-intl'; +import { FormattedMessage as T, useIntl } from 'react-intl'; import { Link, useHistory } from 'react-router-dom'; import { Button, InputGroup, Intent, FormGroup } from '@blueprintjs/core'; import { FormattedMessage } from 'react-intl'; @@ -15,23 +15,23 @@ import AuthInsider from 'containers/Authentication/AuthInsider'; import withAuthenticationActions from './withAuthenticationActions'; - -function SendResetPassword({ - requestSendResetPassword, -}) { - const intl = useIntl(); +function SendResetPassword({ requestSendResetPassword }) { + const { formatMessage } = useIntl(); const history = useHistory(); // Validation schema. const ValidationSchema = Yup.object().shape({ crediential: Yup.string('') - .required(intl.formatMessage({ id: 'required' })) - .email(intl.formatMessage({ id: 'invalid_email_or_phone_numner' })), + .required(formatMessage({ id: 'required' })) + .email(formatMessage({ id: 'invalid_email_or_phone_numner' })), }); - const initialValues = useMemo(() => ({ - crediential: '', - }), []); + const initialValues = useMemo( + () => ({ + crediential: '', + }), + [] + ); // Formik validation const { @@ -60,9 +60,9 @@ function SendResetPassword({ setSubmitting(false); }) .catch((errors) => { - if (errors.find(e => e.type === 'EMAIL.NOT.REGISTERED')){ + if (errors.find((e) => e.type === 'EMAIL.NOT.REGISTERED')) { AppToaster.show({ - message: 'We couldn\'t find your account with that email', + message: "We couldn't find your account with that email", intent: Intent.DANGER, }); } @@ -73,21 +73,29 @@ function SendResetPassword({ return ( -
+
-

Reset Your Password

-

Enter your email address and we’ll send you a link to reset your password.

+

+ +

+

+ +

} + intent={errors.crediential && touched.crediential && Intent.DANGER} + helperText={ + + } className={'form-group--crediential'} > @@ -100,14 +108,14 @@ function SendResetPassword({ fill={true} loading={isSubmitting} > - {intl.formatMessage({ id: 'Send password reset link' })} +
@@ -115,6 +123,4 @@ function SendResetPassword({ ); } -export default compose( - withAuthenticationActions, -)(SendResetPassword); +export default compose(withAuthenticationActions)(SendResetPassword); diff --git a/client/src/containers/Dialogs/AccountFormDialog.js b/client/src/containers/Dialogs/AccountFormDialog.js index c85594a42..33e9c8a72 100644 --- a/client/src/containers/Dialogs/AccountFormDialog.js +++ b/client/src/containers/Dialogs/AccountFormDialog.js @@ -8,12 +8,13 @@ import { TextArea, MenuItem, Checkbox, - Position + Position, } from '@blueprintjs/core'; import { Select } from '@blueprintjs/select'; import * as Yup from 'yup'; import { useFormik } from 'formik'; -import { useIntl } from 'react-intl'; +import { FormattedMessage as T, useIntl } from 'react-intl'; + import { omit } from 'lodash'; import { useQuery, queryCache } from 'react-query'; @@ -27,7 +28,6 @@ import Icon from 'components/Icon'; import ErrorMessage from 'components/ErrorMessage'; import { fetchAccountTypes } from 'store/accounts/accounts.actions'; - function AccountFormDialog({ name, payload, @@ -57,25 +57,29 @@ function AccountFormDialog({ account_type_id: Yup.string() .nullable() .required(intl.formatMessage({ id: 'required' })), - description: Yup.string().trim() + description: Yup.string().trim(), }); - const initialValues = useMemo(() => ({ - account_type_id: null, - name: '', - description: '', - }), []); + const initialValues = useMemo( + () => ({ + account_type_id: null, + name: '', + description: '', + }), + [] + ); const [selectedAccountType, setSelectedAccountType] = useState(null); const [selectedSubaccount, setSelectedSubaccount] = useState( - payload.action === 'new_child' ? - accounts.find(a => a.id === payload.id) : null, + payload.action === 'new_child' + ? accounts.find((a) => a.id === payload.id) + : null ); const transformApiErrors = (errors) => { const fields = {}; - if (errors.find(e => e.type === 'NOT_UNIQUE_CODE')) { - fields.code = 'Account code is not unqiue.' + if (errors.find((e) => e.type === 'NOT_UNIQUE_CODE')) { + fields.code = 'Account code is not unqiue.'; } return fields; }; @@ -84,7 +88,7 @@ function AccountFormDialog({ const formik = useFormik({ enableReinitialize: true, initialValues: { - ...(payload.action === 'edit' && account) ? account : initialValues, + ...(payload.action === 'edit' && account ? account : initialValues), }, validationSchema: accountFormValidationSchema, onSubmit: (values, { setSubmitting, setErrors }) => { @@ -93,43 +97,48 @@ function AccountFormDialog({ if (payload.action === 'edit') { requestEditAccount({ payload: payload.id, - form: { ...omit(values, [...exclude, 'account_type_id']) } - }).then((response) => { - closeDialog(name); - AppToaster.show({ - message: 'the_account_has_been_edited', - intent: Intent.SUCCESS, + form: { ...omit(values, [...exclude, 'account_type_id']) }, + }) + .then((response) => { + closeDialog(name); + AppToaster.show({ + message: 'the_account_has_been_edited', + intent: Intent.SUCCESS, + }); + setSubmitting(false); + queryCache.refetchQueries('accounts-table', { force: true }); + }) + .catch((errors) => { + setSubmitting(false); + setErrors(transformApiErrors(errors)); }); - setSubmitting(false); - queryCache.refetchQueries('accounts-table', { force: true }); - }).catch((errors) => { - setSubmitting(false); - setErrors(transformApiErrors(errors)); - }); } else { - requestSubmitAccount({ form: { ...omit(values, exclude) } }).then((response) => { - closeDialog(name); - AppToaster.show({ - message: 'the_account_has_been_submit', - intent: Intent.SUCCESS, - position: Position.BOTTOM, + requestSubmitAccount({ form: { ...omit(values, exclude) } }) + .then((response) => { + closeDialog(name); + AppToaster.show({ + message: 'the_account_has_been_submit', + intent: Intent.SUCCESS, + position: Position.BOTTOM, + }); + setSubmitting(false); + queryCache.refetchQueries('accounts-table', { force: true }); + }) + .catch((errors) => { + setSubmitting(false); + setErrors(transformApiErrors(errors)); }); - setSubmitting(false); - queryCache.refetchQueries('accounts-table', { force: true }); - }).catch((errors) => { - setSubmitting(false); - setErrors(transformApiErrors(errors)); - }); } - } + }, }); - const { errors, values, touched } = useMemo(() => (formik), [formik]); + const { errors, values, touched } = useMemo(() => formik, [formik]); // Set default account type. useEffect(() => { if (account && account.account_type_id) { - const defaultType = accountsTypes.find((t) => - t.id === account.account_type_id); + const defaultType = accountsTypes.find( + (t) => t.id === account.account_type_id + ); defaultType && setSelectedAccountType(defaultType); } @@ -155,44 +164,64 @@ function AccountFormDialog({ // Account item of select accounts field. const accountItem = (item, { handleClick, modifiers, query }) => { return ( - + ); }; // Filters accounts items. - const filterAccountsPredicater = useCallback((query, account, _index, exactMatch) => { - const normalizedTitle = account.name.toLowerCase(); - const normalizedQuery = query.toLowerCase(); + const filterAccountsPredicater = useCallback( + (query, account, _index, exactMatch) => { + const normalizedTitle = account.name.toLowerCase(); + const normalizedQuery = query.toLowerCase(); - if (exactMatch) { - return normalizedTitle === normalizedQuery; - } else { - return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0; - } - }, []); + if (exactMatch) { + return normalizedTitle === normalizedQuery; + } else { + return ( + `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0 + ); + } + }, + [] + ); // Handles dialog close. - const handleClose = useCallback(() => { closeDialog(name); }, [closeDialog, name]); + const handleClose = useCallback(() => { + closeDialog(name); + }, [closeDialog, name]); // Fetches accounts list. - const fetchAccountsList = useQuery('accounts-list', - () => requestFetchAccounts(), { manual: true }); + const fetchAccountsList = useQuery( + 'accounts-list', + () => requestFetchAccounts(), + { manual: true } + ); // Fetches accounts types. - const fetchAccountsTypes = useQuery('accounts-types-list', async () => { - await requestFetchAccountTypes(); - }, { manual: true }); + const fetchAccountsTypes = useQuery( + 'accounts-types-list', + async () => { + await requestFetchAccountTypes(); + }, + { 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 = ( - fetchAccountsList.isFetching || - fetchAccountTypes.isFetching || - fetchAccount.isFetching); + const isFetching = + fetchAccountsList.isFetching || + fetchAccountTypes.isFetching || + fetchAccount.isFetching; // Fetch requests on dialog opening. const onDialogOpening = useCallback(() => { @@ -201,16 +230,22 @@ function AccountFormDialog({ fetchAccount.refetch(); }, []); - const onChangeAccountType = useCallback((accountType) => { - setSelectedAccountType(accountType); - formik.setFieldValue('account_type_id', accountType.id); - }, [setSelectedAccountType, formik]); + const onChangeAccountType = useCallback( + (accountType) => { + setSelectedAccountType(accountType); + formik.setFieldValue('account_type_id', accountType.id); + }, + [setSelectedAccountType, formik] + ); // Handles change sub-account. - const onChangeSubaccount = useCallback((account) => { - setSelectedSubaccount(account); - formik.setFieldValue('parent_account_id', account.id); - }, [setSelectedSubaccount, formik]); + const onChangeSubaccount = useCallback( + (account) => { + setSelectedSubaccount(account); + formik.setFieldValue('parent_account_id', account.id); + }, + [setSelectedSubaccount, formik] + ); const onDialogClosed = useCallback(() => { formik.resetForm(); @@ -218,21 +253,25 @@ function AccountFormDialog({ setSelectedAccountType(null); }, [formik]); - const infoIcon = useMemo(() => (), []); + const infoIcon = useMemo(() => , []); const subAccountLabel = useMemo(() => { - return ({'Sub account?'} ); + return ( + + + + ); }, []); - const requiredSpan = useMemo(() => (*), []); + const requiredSpan = useMemo(() => *, []); return ( : } className={{ 'dialog--loading': isFetching, - 'dialog--account-form': true + 'dialog--account-form': true, }} autoFocus={true} canEscapeKeyClose={true} @@ -245,15 +284,18 @@ function AccountFormDialog({
} labelInfo={requiredSpan} className={classNames( 'form-group--account-type', 'form-group--select-list', - Classes.FILL)} + Classes.FILL + )} inline={true} - helperText={} - intent={(errors.account_type_id && touched.account_type_id) && Intent.DANGER} + helperText={} + intent={ + errors.account_type_id && touched.account_type_id && Intent.DANGER + } > } labelInfo={requiredSpan} className={'form-group--account-name'} - intent={(errors.name && touched.name) && Intent.DANGER} - helperText={} + intent={errors.name && touched.name && Intent.DANGER} + helperText={} inline={true} > } className={'form-group--account-code'} - intent={(errors.code && touched.code) && Intent.DANGER} - helperText={} + intent={errors.code && touched.code && Intent.DANGER} + helperText={} inline={true} labelInfo={infoIcon} > @@ -316,11 +361,12 @@ function AccountFormDialog({ {values.subaccount && ( } className={classNames( 'form-group--parent-account', 'form-group--select-list', - Classes.FILL)} + Classes.FILL + )} inline={true} > @@ -343,7 +391,7 @@ function AccountFormDialog({ )} } className={'form-group--description'} intent={formik.errors.description && Intent.DANGER} helperText={formik.errors.description && formik.errors.credential} @@ -359,9 +407,13 @@ function AccountFormDialog({
- - +
@@ -370,6 +422,4 @@ function AccountFormDialog({ ); } -export default AccountFormDialogContainer( - AccountFormDialog, -); \ No newline at end of file +export default AccountFormDialogContainer(AccountFormDialog); diff --git a/client/src/containers/Dialogs/CurrencyDialog.js b/client/src/containers/Dialogs/CurrencyDialog.js index e5d6e4b93..77c21e820 100644 --- a/client/src/containers/Dialogs/CurrencyDialog.js +++ b/client/src/containers/Dialogs/CurrencyDialog.js @@ -7,7 +7,7 @@ import { Intent, } from '@blueprintjs/core'; import * as Yup from 'yup'; -import { useIntl } from 'react-intl'; +import { FormattedMessage as T, useIntl } from 'react-intl'; import { useFormik } from 'formik'; import { compose } from 'utils'; import Dialog from 'components/Dialog'; @@ -30,15 +30,15 @@ function CurrencyDialog({ requestEditCurrency, editCurrency, }) { - const intl = useIntl(); + const {formatMessage} = useIntl(); const ValidationSchema = Yup.object().shape({ currency_name: Yup.string().required( - intl.formatMessage({ id: 'required' }) + formatMessage({ id: 'required' }) ), currency_code: Yup.string() .max(4) - .required(intl.formatMessage({ id: 'required' })), + .required(formatMessage({ id: 'required' })), }); const initialValues = useMemo( () => ({ @@ -110,7 +110,7 @@ function CurrencyDialog({ return ( : } className={classNames( { 'dialog--loading': fetchHook.pending, @@ -126,7 +126,7 @@ function CurrencyDialog({
} labelInfo={requiredSpan} className={'form-group--currency-name'} intent={ @@ -144,7 +144,7 @@ function CurrencyDialog({ /> } labelInfo={requiredSpan} className={'form-group--currency-code'} intent={ @@ -164,9 +164,9 @@ function CurrencyDialog({
- +
diff --git a/client/src/containers/Dialogs/ExchangeRateDialog.js b/client/src/containers/Dialogs/ExchangeRateDialog.js new file mode 100644 index 000000000..5b34b01e6 --- /dev/null +++ b/client/src/containers/Dialogs/ExchangeRateDialog.js @@ -0,0 +1,282 @@ +import React, { useState, useMemo, useCallback, useEffect } from 'react'; +import { + Button, + Classes, + FormGroup, + InputGroup, + Intent, + Position, + MenuItem, +} from '@blueprintjs/core'; +import { pick } from 'lodash'; +import * as Yup from 'yup'; +import { FormattedMessage as T, useIntl } from 'react-intl'; +import { useFormik } from 'formik'; +import Dialog from 'components/Dialog'; + +import { useQuery, queryCache } from 'react-query'; +import AppToaster from 'components/AppToaster'; +import ErrorMessage from 'components/ErrorMessage'; +import classNames from 'classnames'; +import { Select } from '@blueprintjs/select'; +import moment from 'moment'; +import { DateInput } from '@blueprintjs/datetime'; +import { momentFormatter } from 'utils'; +import ExchangeRatesDialogConnect from 'connectors/ExchangeRatesFromDialog.connect'; + +function ExchangeRateDialog({ + name, + payload, + isOpen, + + openDialog, + closeDialog, + currencies, + + requestSubmitExchangeRate, + requestFetchExchangeRates, + requestEditExchangeRate, + requestFetchCurrencies, + editExchangeRate, + addExchangeRatesTableQueries, +}) { + const {formatMessage} = useIntl(); + + const [selectedItems, setSelectedItems] = useState({}); + + const validationSchema = Yup.object().shape({ + exchange_rate: Yup.number().required( + formatMessage({ id: 'required' }) + ), + currency_code: Yup.string() + .max(3) + .required(formatMessage({ id: 'required' })), + date: Yup.date().required(formatMessage({ id: 'required' })), + }); + + const initialValues = useMemo( + () => ({ + exchange_rate: '', + currency_code: '', + date: moment(new Date()).format('YYYY-MM-DD'), + }), + [] + ); + + const formik = useFormik({ + enableReinitialize: true, + validationSchema, + initialValues: { + ...(payload.action === 'edit' && + pick(editExchangeRate, Object.keys(initialValues))), + }, + onSubmit: (values, { setSubmitting }) => { + if (payload.action === 'edit') { + requestEditExchangeRate(payload.id, values) + .then((response) => { + closeDialog(name); + AppToaster.show({ + message: 'the_exchange_rate_has_been_edited', + }); + setSubmitting(false); + }) + .catch((error) => { + setSubmitting(false); + }); + } else { + requestSubmitExchangeRate(values) + .then((response) => { + closeDialog(name); + AppToaster.show({ + message: 'the_exchangeRate_has_been_submit', + }); + setSubmitting(false); + }) + .catch((error) => { + setSubmitting(false); + }); + } + }, + }); + + const { values, errors, touched } = useMemo(() => formik, [formik]); + + const requiredSpan = useMemo(() => *, []); + + const handleClose = useCallback(() => { + closeDialog(name); + }, [name, closeDialog]); + + const fetchHook = useQuery('exchange-rates-dialog', () => { + return Promise.all([requestFetchExchangeRates(), requestFetchCurrencies()]); + }); + + const onDialogClosed = useCallback(() => { + formik.resetForm(); + closeDialog(name); + }, [formik, closeDialog, name]); + + const onDialogOpening = useCallback(() => { + fetchHook.refetch(); + }, [fetchHook]); + + const handleDateChange = useCallback( + (date) => { + const formatted = moment(date).format('YYYY-MM-DD'); + formik.setFieldValue('date', formatted); + }, + [formik.setFieldValue] + ); + + const onItemsSelect = useCallback( + (filedName) => { + return (filed) => { + setSelectedItems({ + ...selectedItems, + [filedName]: filed, + }); + formik.setFieldValue(filedName, filed.currency_code); + }; + }, + [formik.setFieldValue, selectedItems] + ); + + const filterCurrencyCode = (query, currency_code, _index, exactMatch) => { + const normalizedTitle = currency_code.currency_code.toLowerCase(); + const normalizedQuery = query.toLowerCase(); + + if (exactMatch) { + return normalizedTitle === normalizedQuery; + } else { + return ( + `${currency_code.currency_code} ${normalizedTitle}`.indexOf( + normalizedQuery + ) >= 0 + ); + } + }; + + const currencyCodeRenderer = useCallback((CurrencyCode, { handleClick }) => { + return ( + + ); + }, []); + + const getSelectedItemLabel = useCallback( + (fieldName, defaultLabel) => { + return typeof selectedItems[fieldName] !== 'undefined' + ? selectedItems[fieldName].currency_code + : defaultLabel; + }, + [selectedItems] + ); + + return ( + : + + } + className={classNames( + { + 'dialog--loading': fetchHook.pending, + }, + 'dialog--exchangeRate-form' + )} + isOpen={isOpen} + onClosed={onDialogClosed} + onOpening={onDialogOpening} + isLoading={fetchHook.pending} + onClose={handleClose} + > + +
+ } + inline={true} + labelInfo={requiredSpan} + intent={errors.date && touched.date && Intent.DANGER} + helperText={} + > + + + + } + labelInfo={requiredSpan} + intent={ + errors.exchange_rate && touched.exchange_rate && Intent.DANGER + } + helperText={} + inline={true} + > + + + + } + labelInfo={requiredSpan} + className={classNames( + 'form-group--select-list', + + Classes.FILL + )} + inline={true} + intent={ + errors.currency_code && touched.currency_code && Intent.DANGER + } + helperText={} + > + + +
+
+
+ + +
+
+ +
+ ); +} + +export default ExchangeRatesDialogConnect(ExchangeRateDialog); diff --git a/client/src/containers/Dialogs/InviteUserDialog.js b/client/src/containers/Dialogs/InviteUserDialog.js index 862388364..01e03b8a6 100644 --- a/client/src/containers/Dialogs/InviteUserDialog.js +++ b/client/src/containers/Dialogs/InviteUserDialog.js @@ -1,5 +1,5 @@ import React, { useMemo, useCallback } from 'react'; -import { useIntl } from 'react-intl'; +import { FormattedMessage as T, useIntl } from 'react-intl'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import { @@ -28,7 +28,7 @@ function InviteUserDialog({ requestFetchUser, requestEditUser, }) { - const intl = useIntl(); + const { formatMessage } = useIntl(); const fetchHook = useAsync(async () => { await Promise.all([ @@ -37,12 +37,12 @@ function InviteUserDialog({ }, false); const validationSchema = Yup.object().shape({ - first_name: Yup.string().required(intl.formatMessage({ id: 'required' })), - last_name: Yup.string().required(intl.formatMessage({ id: 'required' })), + first_name: Yup.string().required(formatMessage({ id: 'required' })), + last_name: Yup.string().required(formatMessage({ id: 'required' })), email: Yup.string() .email() - .required(intl.formatMessage({ id: 'required' })), - phone_number: Yup.number().required(intl.formatMessage({ id: 'required' })), + .required(formatMessage({ id: 'required' })), + phone_number: Yup.number().required(formatMessage({ id: 'required' })), }); const initialValues = useMemo( @@ -101,7 +101,7 @@ function InviteUserDialog({ return ( : ''} className={classNames({ 'dialog--loading': fetchHook.pending, 'dialog--invite-user': true, @@ -116,7 +116,7 @@ function InviteUserDialog({
} className={'form-group--first-name'} intent={errors.first_name && touched.first_name && Intent.DANGER} helperText={} @@ -129,7 +129,7 @@ function InviteUserDialog({ } className={'form-group--last-name'} intent={errors.last_name && touched.last_name && Intent.DANGER} helperText={} @@ -142,7 +142,7 @@ function InviteUserDialog({ } className={'form-group--email'} intent={errors.email && touched.email && Intent.DANGER} helperText={} @@ -156,7 +156,7 @@ function InviteUserDialog({ } className={'form-group--phone-number'} intent={ errors.phone_number && touched.phone_number && Intent.DANGER @@ -175,9 +175,9 @@ function InviteUserDialog({
- +
diff --git a/client/src/containers/Dialogs/ItemCategoryDialog.js b/client/src/containers/Dialogs/ItemCategoryDialog.js index 890b7d5a4..62e66fb47 100644 --- a/client/src/containers/Dialogs/ItemCategoryDialog.js +++ b/client/src/containers/Dialogs/ItemCategoryDialog.js @@ -11,7 +11,7 @@ import { import { Select } from '@blueprintjs/select'; import { pick } from 'lodash'; import * as Yup from 'yup'; -import { useIntl } from 'react-intl'; +import { FormattedMessage as T, useIntl } from 'react-intl'; import { useFormik } from 'formik'; import { compose } from 'utils'; import { useQuery } from 'react-query'; @@ -55,13 +55,13 @@ function ItemCategoryDialog({ requestEditItemCategory, }) { const [selectedParentCategory, setParentCategory] = useState(null); - const intl = useIntl(); + const {formatMessage} = useIntl(); const fetchList = useQuery(['items-categories-list'], () => requestFetchItemCategories()); const ValidationSchema = Yup.object().shape({ - name: Yup.string().required(intl.formatMessage({ id: 'required' })), + name: Yup.string().required(formatMessage({ id: 'required' })), parent_category_id: Yup.string().nullable(), description: Yup.string().trim() }); @@ -149,7 +149,7 @@ function ItemCategoryDialog({ return ( : } className={classNames({ 'dialog--loading': fetchList.isFetching, }, @@ -164,7 +164,7 @@ function ItemCategoryDialog({
} labelInfo={requiredSpan} className={'form-group--category-name'} intent={(errors.name && touched.name) && Intent.DANGER} @@ -179,7 +179,7 @@ function ItemCategoryDialog({ } labelInfo={infoIcon} className={classNames( 'form-group--select-list', @@ -207,7 +207,7 @@ function ItemCategoryDialog({ } className={'form-group--description'} intent={(errors.description && touched.description) && Intent.DANGER} helperText={()} @@ -222,9 +222,9 @@ function ItemCategoryDialog({
- +
diff --git a/client/src/containers/Dialogs/ItemFromDialog.js b/client/src/containers/Dialogs/ItemFromDialog.js index e7b6c28c3..d110e1edb 100644 --- a/client/src/containers/Dialogs/ItemFromDialog.js +++ b/client/src/containers/Dialogs/ItemFromDialog.js @@ -5,10 +5,10 @@ import { FormGroup, InputGroup, Intent, - TextArea + TextArea, } from '@blueprintjs/core'; import * as Yup from 'yup'; -import { useIntl } from 'react-intl'; +import { FormattedMessage as T, useIntl } from 'react-intl'; import { useFormik } from 'formik'; import { compose } from 'utils'; import Dialog from 'components/Dialog'; @@ -25,30 +25,30 @@ function ItemFromDialog({ submitItemCategory, fetchCategory, openDialog, - closeDialog + closeDialog, }) { const [state, setState] = useState({}); - const intl = useIntl(); + const { formatMessage } = useIntl(); const ValidationSchema = Yup.object().shape({ - name: Yup.string().required(intl.formatMessage({ id: 'required' })), - description: Yup.string().trim() + name: Yup.string().required(formatMessage({ id: 'required' })), + description: Yup.string().trim(), }); const formik = useFormik({ enableReinitialize: true, initialValues: {}, validationSchema: ValidationSchema, - onSubmit: values => { + onSubmit: (values) => { submitItemCategory({ values }) - .then(response => { + .then((response) => { AppToaster.show({ - message: 'the_category_has_been_submit' + message: 'the_category_has_been_submit', }); }) - .catch(error => { + .catch((error) => { alert(error.message); }); - } + }, }); const fetchHook = useAsync(async () => { @@ -71,10 +71,12 @@ function ItemFromDialog({ return ( : + } className={{ 'dialog--loading': state.isLoading, - 'dialog--item-form': true + 'dialog--item-form': true, }} isOpen={isOpen} onClosed={onDialogClosed} @@ -84,7 +86,7 @@ function ItemFromDialog({
} className={'form-group--category-name'} intent={formik.errors.name && Intent.DANGER} helperText={formik.errors.name && formik.errors.name} @@ -97,7 +99,7 @@ function ItemFromDialog({ /> } className={'form-group--description'} intent={formik.errors.description && Intent.DANGER} helperText={formik.errors.description && formik.errors.credential} @@ -112,9 +114,15 @@ function ItemFromDialog({
- +
diff --git a/client/src/containers/Dialogs/UserFormDialog.js b/client/src/containers/Dialogs/UserFormDialog.js index 695c0693c..16ecd3781 100644 --- a/client/src/containers/Dialogs/UserFormDialog.js +++ b/client/src/containers/Dialogs/UserFormDialog.js @@ -1,5 +1,5 @@ import React, { useMemo, useCallback } from 'react'; -import { useIntl } from 'react-intl'; +import { FormattedMessage as T, useIntl } from 'react-intl'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import { @@ -37,7 +37,9 @@ function UserFormDialog({ }, false); const validationSchema = Yup.object().shape({ - email: Yup.string().email().required(intl.formatMessage({id:'required'})), + email: Yup.string() + .email() + .required(intl.formatMessage({ id: 'required' })), }); const initialValues = { @@ -96,7 +98,13 @@ function UserFormDialog({ return ( + ) : ( + + ) + } className={classNames({ 'dialog--loading': fetchHook.pending, 'dialog--invite-form': true, @@ -110,18 +118,20 @@ function UserFormDialog({ >
-

Your teammate will get an email that gives them access to your team.

+

+ +

} className={classNames('form-group--email', Classes.FILL)} - intent={(errors.email && touched.email) && Intent.DANGER} - helperText={} + intent={errors.email && touched.email && Intent.DANGER} + helperText={} inline={true} > @@ -129,9 +139,15 @@ function UserFormDialog({
- +
diff --git a/client/src/containers/Expenses/ExpensesList.js b/client/src/containers/Expenses/ExpensesList.js index 13d99f063..860e8f614 100644 --- a/client/src/containers/Expenses/ExpensesList.js +++ b/client/src/containers/Expenses/ExpensesList.js @@ -8,6 +8,7 @@ import ExpensesViewsTabs from 'components/Expenses/ExpensesViewsTabs'; import ExpensesTable from 'components/Expenses/ExpensesTable'; import connector from 'connectors/ExpensesList.connector'; import AppToaster from 'components/AppToaster'; +import { FormattedMessage as T, useIntl } from 'react-intl'; function ExpensesList({ fetchExpenses, @@ -59,8 +60,8 @@ function ExpensesList({ } + confirmButtonText={} icon='trash' intent={Intent.DANGER} isOpen={deleteExpenseState} diff --git a/client/src/containers/FinancialStatements/ExchangeRates/ExchangeRate.js b/client/src/containers/FinancialStatements/ExchangeRates/ExchangeRate.js new file mode 100644 index 000000000..77cec1e55 --- /dev/null +++ b/client/src/containers/FinancialStatements/ExchangeRates/ExchangeRate.js @@ -0,0 +1,122 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import { useQuery } from 'react-query'; +import { useParams } from 'react-router-dom'; +import { Alert, Intent } from '@blueprintjs/core'; +import AppToaster from 'components/AppToaster'; + +import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; +import DashboardInsider from 'components/Dashboard/DashboardInsider'; +import ExchangeRateTable from './ExchangeRateTable'; +import ExchangeRateActionsBar from './ExchangeRateActionsBar'; + +import withDashboardActions from 'containers/Dashboard/withDashboard'; +import withResourceActions from 'containers/Resources/withResourcesActions'; +import withExchangeRatesActions from 'containers/FinancialStatements/ExchangeRates/withExchangeRatesActions'; + +import { compose } from 'utils'; + +import { FormattedMessage as T, useIntl } from 'react-intl'; + +function ExchangeRate({ + views, + changePageTitle, + requestFetchResourceFields, + requestFetchExchangeRates, + requestDeleteExchangeRate, + addExchangeRatesTableQueries, +}) { + const { id } = useParams(); + const [deleteExchangeRate, setDeleteExchangeRate] = useState(false); + const [selectedRows, setSelectedRows] = useState([]); + + const fetchHook = useQuery('exchange-rates', () => { + return Promise.all([requestFetchExchangeRates()]); + }); + + useEffect(() => { + id + ? changePageTitle('Exchange Rate Details') + : changePageTitle('Exchange Rate List'); + }, [id, changePageTitle]); + + const handelDeleteExchangeRate = useCallback( + (exchange_rate) => { + setDeleteExchangeRate(exchange_rate); + }, + [setDeleteExchangeRate] + ); + + const handelEditExchangeRate = (exchange_rate) => {}; + + const handelCancelExchangeRateDelete = useCallback(() => { + setDeleteExchangeRate(false); + }, [setDeleteExchangeRate]); + + const handelConfirmExchangeRateDelete = useCallback(() => { + requestDeleteExchangeRate(deleteExchangeRate.id).then(() => { + setDeleteExchangeRate(false); + AppToaster.show({ + message: 'the_exchange_rate_has_been_delete', + }); + }); + }, [deleteExchangeRate, requestDeleteExchangeRate]); + + // Handle fetch data of Exchange_rates datatable. + const handleFetchData = useCallback( + ({ pageIndex, pageSize, sortBy }) => { + addExchangeRatesTableQueries({ + ...(sortBy.length > 0 + ? { + column_sort_by: sortBy[0].id, + sort_order: sortBy[0].desc ? 'desc' : 'asc', + } + : {}), + }); + }, + [addExchangeRatesTableQueries] + ); + + const handleSelectedRowsChange = useCallback( + (exchange_rates) => { + setSelectedRows(exchange_rates); + }, + [setSelectedRows] + ); + + return ( + + + + + } + confirmButtonText={} + icon='trash' + intent={Intent.DANGER} + isOpen={deleteExchangeRate} + onCancel={handelCancelExchangeRateDelete} + onConfirm={handelConfirmExchangeRateDelete} + > +

+ Are you sure you want to move filename to Trash? You will be + able to restore it later, but it will become private to you. +

+
+
+
+ ); +} + +export default compose( + withExchangeRatesActions, + withDashboardActions +)(ExchangeRate); diff --git a/client/src/containers/FinancialStatements/ExchangeRates/ExchangeRateActionsBar.js b/client/src/containers/FinancialStatements/ExchangeRates/ExchangeRateActionsBar.js new file mode 100644 index 000000000..acc5c55d0 --- /dev/null +++ b/client/src/containers/FinancialStatements/ExchangeRates/ExchangeRateActionsBar.js @@ -0,0 +1,112 @@ +import React, { useCallback, useState, useMemo } from 'react'; +import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; +import { compose } from 'utils'; +import { + NavbarGroup, + Button, + Classes, + Intent, + Popover, + Position, + PopoverInteractionKind, +} from '@blueprintjs/core'; +import { connect } from 'react-redux'; +import classNames from 'classnames'; +import Icon from 'components/Icon'; +import DashboardConnect from 'connectors/Dashboard.connector'; +import FilterDropdown from 'components/FilterDropdown'; +import ExchangeRatesDialogConnect from 'connectors/ExchangeRatesFromDialog.connect'; +import withResourceDetail from 'containers/Resources/withResourceDetails'; +import { FormattedMessage as T, useIntl } from 'react-intl'; + + +function ExchangeRateActionsBar({ + openDialog, + onDeleteExchangeRate, + onFilterChanged, + resourceFields, + selectedRows = [], +}) { + const onClickNewExchangeRate = useCallback(() => { + openDialog('exchangeRate-form', {}); + }, [openDialog]); + + const handelDeleteExchangeRate = useCallback( + (exchangeRate) => { + onDeleteExchangeRate(exchangeRate); + }, + [selectedRows, onDeleteExchangeRate] + ); + const [filterCount, setFilterCount] = useState(0); + const hasSelectedRows = useMemo(() => selectedRows.length > 0, [ + selectedRows, + ]); + + const filterDropdown = FilterDropdown({ + fields: resourceFields, + onFilterChange: (filterConditions) => { + setFilterCount(filterConditions.length || 0); + + onFilterChanged && onFilterChanged(filterConditions); + }, + }); + + return ( + + + - - + +
@@ -455,5 +545,5 @@ export default compose( AccountsConnect, ItemsConnect, ItemCategoryConnect, - MediaConnect, -)(ItemForm); \ No newline at end of file + MediaConnect +)(ItemForm); diff --git a/client/src/containers/Items/ItemsActionsBar.js b/client/src/containers/Items/ItemsActionsBar.js index 6dc6c3d3a..d72d7bcb1 100644 --- a/client/src/containers/Items/ItemsActionsBar.js +++ b/client/src/containers/Items/ItemsActionsBar.js @@ -21,6 +21,7 @@ import DialogConnect from 'connectors/Dialog.connector'; import withResourceDetail from 'containers/Resources/withResourceDetails'; import withItems from 'containers/Items/withItems'; import { If } from 'components'; +import { FormattedMessage as T, useIntl } from 'react-intl'; const ItemsActionsBar = ({ openDialog, @@ -69,7 +70,7 @@ const ItemsActionsBar = ({