diff --git a/client/src/containers/Alerts/ExchangeRates/ExchangeRateBulkDeleteAlert.js b/client/src/containers/Alerts/ExchangeRates/ExchangeRateBulkDeleteAlert.js
new file mode 100644
index 000000000..73e60e689
--- /dev/null
+++ b/client/src/containers/Alerts/ExchangeRates/ExchangeRateBulkDeleteAlert.js
@@ -0,0 +1,71 @@
+import React, { useState } from 'react';
+import { FormattedMessage as T, useIntl } from 'react-intl';
+import { Intent, Alert } from '@blueprintjs/core';
+import { size } from 'lodash';
+import { AppToaster } from 'components';
+
+import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
+import withAlertActions from 'containers/Alert/withAlertActions';
+
+import { compose } from 'utils';
+
+/**
+ * Exchange rate bulk delete alert.
+ */
+function ExchangeRateBulkDeleteAlert({
+ name,
+
+ // #withAlertStoreConnect
+ isOpen,
+ payload: { exchangeRatesIds },
+
+ // #withAlertActions
+ closeAlert,
+}) {
+ // handle cancel item bulk delete alert.
+ const handleCancelBulkDelete = () => {
+ closeAlert(name);
+ };
+
+ // handle confirm Exchange Rates bulk delete.
+ // const handleConfirmBulkDelete = () => {
+ // bulkDeleteExchangeRate(exchangeRatesIds)
+ // .then(() => {
+ // AppToaster.show({
+ // message: formatMessage({
+ // id: 'the_exchange_rates_has_been_successfully_deleted',
+ // }),
+ // intent: Intent.SUCCESS,
+ // });
+ // })
+ // .catch(({ errors }) => {
+ // handleDeleteErrors(errors);
+ // });
+ // };
+
+ return (
+ }
+ confirmButtonText={
+
+ }
+ icon="trash"
+ intent={Intent.DANGER}
+ isOpen={isOpen}
+ onCancel={handleCancelBulkDelete}
+ // onConfirm={}
+ // loading={isLoading}
+ >
+
+
+
+
+ );
+}
+
+export default compose(
+ withAlertStoreConnect(),
+ withAlertActions,
+)(ExchangeRateBulkDeleteAlert);
diff --git a/client/src/containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert.js b/client/src/containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert.js
new file mode 100644
index 000000000..e13bd0faa
--- /dev/null
+++ b/client/src/containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import {
+ FormattedMessage as T,
+ FormattedHTMLMessage,
+ useIntl,
+} from 'react-intl';
+import { Intent, Alert } from '@blueprintjs/core';
+import { AppToaster } from 'components';
+
+import { useDeleteExchangeRate } from 'hooks/query';
+import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
+import withAlertActions from 'containers/Alert/withAlertActions';
+
+import { compose } from 'utils';
+
+/**
+ * exchange rate delete alerts.
+ */
+function ExchangeRateDeleteAlert({
+ name,
+
+ // #withAlertStoreConnect
+ isOpen,
+ payload: { exchangeRateId },
+
+ // #withAlertActions
+ closeAlert,
+}) {
+ const {
+ mutateAsync: deleteExchangeRate,
+ isLoading,
+ } = useDeleteExchangeRate();
+ const { formatMessage } = useIntl();
+
+ // Handle cancel delete exchange rate alert.
+ const handleCancelExchangeRateDelete = () => closeAlert(name);
+
+ const handelConfirmExchangeRateDelete = () => {
+ deleteExchangeRate(exchangeRateId)
+ .then((response) => {
+ AppToaster.show({
+ message: formatMessage({
+ id: 'the_exchange_rates_has_been_deleted_successfully',
+ }),
+ intent: Intent.SUCCESS,
+ });
+ closeAlert(name);
+ })
+ .catch(() => {
+ closeAlert(name);
+ });
+ };
+
+ return (
+ }
+ confirmButtonText={}
+ intent={Intent.DANGER}
+ isOpen={isOpen}
+ onCancel={handleCancelExchangeRateDelete}
+ onConfirm={handelConfirmExchangeRateDelete}
+ loading={isLoading}
+ >
+
+
+
+
+ );
+}
+
+export default compose(
+ withAlertStoreConnect(),
+ withAlertActions,
+)(ExchangeRateDeleteAlert);
diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.js
new file mode 100644
index 000000000..bec5b7b6b
--- /dev/null
+++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.js
@@ -0,0 +1,114 @@
+import React, { useMemo } from 'react';
+import { Intent } from '@blueprintjs/core';
+import { Formik } from 'formik';
+import moment from 'moment';
+import { FormattedMessage as T, useIntl } from 'react-intl';
+import { AppToaster } from 'components';
+import {
+ CreateExchangeRateFormSchema,
+ EditExchangeRateFormSchema,
+} from './ExchangeRateForm.schema';
+import ExchangeRateFormContent from './ExchangeRateFormContent';
+import { useExchangeRateFromContext } from './ExchangeRateFormProvider';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+
+import { compose, transformToForm } from 'utils';
+
+const defaultInitialValues = {
+ exchange_rate: '',
+ currency_code: '',
+ date: moment(new Date()).format('YYYY-MM-DD'),
+};
+
+/**
+ * Exchange rate form.
+ */
+function ExchangeRateForm({
+ // #withDialogActions
+ closeDialog,
+}) {
+ const { formatMessage } = useIntl();
+ const {
+ createExchangeRateMutate,
+ editExchangeRateMutate,
+ isNewMode,
+ dialogName,
+ exchangeRate,
+ } = useExchangeRateFromContext();
+
+ // Form validation schema in create and edit mode.
+ const validationSchema = isNewMode
+ ? CreateExchangeRateFormSchema
+ : EditExchangeRateFormSchema;
+ const initialValues = useMemo(
+ () => ({
+ ...defaultInitialValues,
+ ...transformToForm(exchangeRate, defaultInitialValues),
+ }),
+ [],
+ );
+
+ // Transformers response errors.
+ const transformErrors = (errors, { setErrors }) => {
+ if (
+ errors.find((error) => error.type === 'EXCHANGE.RATE.DATE.PERIOD.DEFINED')
+ ) {
+ setErrors({
+ exchange_rate: formatMessage({
+ id: 'there_is_exchange_rate_in_this_date_with_the_same_currency',
+ }),
+ });
+ }
+ };
+
+ // Handle the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
+ setSubmitting(true);
+
+ // Handle close the dialog after success response.
+ const afterSubmit = () => {
+ closeDialog(dialogName);
+ };
+ const onSuccess = ({ response }) => {
+ AppToaster.show({
+ message: formatMessage({
+ id: !isNewMode
+ ? 'the_exchange_rate_has_been_edited_successfully'
+ : 'the_exchange_rate_has_been_created_successfully',
+ }),
+ intent: Intent.SUCCESS,
+ });
+ afterSubmit(response);
+ };
+ // Handle the response error.
+ const onError = (error) => {
+ const {
+ response: {
+ data: { errors },
+ },
+ } = error;
+
+ transformErrors(errors, { setErrors });
+ setSubmitting(false);
+ };
+ if (isNewMode) {
+ createExchangeRateMutate(values).then(onSuccess).catch(onError);
+ } else {
+ editExchangeRateMutate([exchangeRate.id, values])
+ .then(onSuccess)
+ .catch(onError);
+ }
+ };
+
+ return (
+
+
+
+ );
+}
+
+export default compose(withDialogActions)(ExchangeRateForm);
diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.js
new file mode 100644
index 000000000..29ced17ca
--- /dev/null
+++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateForm.schema.js
@@ -0,0 +1,18 @@
+import * as Yup from 'yup';
+import { formatMessage } from 'services/intl';
+import { DATATYPES_LENGTH } from 'common/dataTypes';
+
+const Schema = 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' })),
+});
+
+export const CreateExchangeRateFormSchema = Schema;
+export const EditExchangeRateFormSchema = Schema;
diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormContent.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormContent.js
new file mode 100644
index 000000000..3557ad92b
--- /dev/null
+++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormContent.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import { Form } from 'formik';
+import ExchangeRateFormFields from './ExchangeRateFormFields';
+import ExchangeRateFormFooter from './ExchangeRateFormFooter';
+
+export default function ExchangeRateFormContent() {
+ return (
+
+ );
+}
diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js
index cc99f3d70..ad2a39e69 100644
--- a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js
+++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormDialogContent.js
@@ -1,35 +1,9 @@
-import React, { useState, useMemo, useCallback } from 'react';
-import {
- Button,
- Classes,
- FormGroup,
- InputGroup,
- Intent,
- Position,
-} from '@blueprintjs/core';
-import { pick } from 'lodash';
-import * as Yup from 'yup';
-import { useFormik } from 'formik';
-import { useQuery, queryCache } from 'react-query';
-import moment from 'moment';
-import { DateInput } from '@blueprintjs/datetime';
-import { FormattedMessage as T, useIntl } from 'react-intl';
-import { momentFormatter, tansformDateValue } from 'utils';
-import {
- AppToaster,
- ErrorMessage,
- DialogContent,
- FieldRequiredHint,
- CurrencySelectList,
-} from 'components';
-import classNames from 'classnames';
+import React from 'react';
+
+import ExchangeRateForm from './ExchangeRateForm';
+import { ExchangeRateFormProvider } from './ExchangeRateFormProvider';
+
import withExchangeRateDetail from 'containers/ExchangeRates/withExchangeRateDetail';
-import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions';
-
-import withCurrencies from 'containers/Currencies/withCurrencies';
-import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
-import withDialogActions from 'containers/Dialog/withDialogActions';
-
import { compose } from 'utils';
import 'style/pages/ExchangeRate/ExchangeRateDialog.scss';
@@ -38,226 +12,20 @@ import 'style/pages/ExchangeRate/ExchangeRateDialog.scss';
* Exchange rate form content.
*/
function ExchangeRateFormDialogContent({
- // #withDialogActions
- closeDialog,
-
- // #withCurrencies
- currenciesList,
-
- //#WithExchangeRateDetail
- exchangeRate,
-
- // #withExchangeRatesActions
- requestSubmitExchangeRate,
- requestEditExchangeRate,
-
- // #wihtCurrenciesActions
- requestFetchCurrencies,
-
// #ownProp
action,
exchangeRateId,
dialogName,
}) {
- const { formatMessage } = useIntl();
- const [selectedItems, setSelectedItems] = useState({});
-
- const fetchCurrencies = useQuery(
- 'currencies',
- () => requestFetchCurrencies(),
- { enabled: true },
- );
-
- 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' })),
- });
-
- const initialValues = useMemo(
- () => ({
- exchange_rate: '',
- currency_code: '',
- date: moment(new Date()).format('YYYY-MM-DD'),
- }),
- [],
- );
-
- const {
- values,
- touched,
- errors,
- isSubmitting,
- handleSubmit,
- getFieldProps,
- setFieldValue,
- resetForm,
- } = useFormik({
- enableReinitialize: true,
- validationSchema,
- initialValues: {
- ...initialValues,
- ...(action === 'edit' && pick(exchangeRate, Object.keys(initialValues))),
- },
- onSubmit: (values, { setSubmitting, setErrors }) => {
- if (action === 'edit') {
- requestEditExchangeRate(exchangeRateId, values)
- .then((response) => {
- closeDialog(dialogName);
- AppToaster.show({
- message: formatMessage({
- id: 'the_exchange_rate_has_been_edited_successfully',
- }),
- intent: Intent.SUCCESS,
- });
- setSubmitting(false);
- queryCache.invalidateQueries('exchange-rates-table');
- })
- .catch((error) => {
- setSubmitting(false);
- });
- } else {
- requestSubmitExchangeRate(values)
- .then((response) => {
- closeDialog(dialogName);
- AppToaster.show({
- message: formatMessage({
- id: 'the_exchange_rate_has_been_created_successfully',
- }),
- intent: Intent.SUCCESS,
- });
- setSubmitting(false);
- queryCache.invalidateQueries('exchange-rates-table');
- })
- .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 handleClose = useCallback(() => {
- closeDialog(dialogName);
- resetForm();
- }, [dialogName, closeDialog]);
-
- const handleDateChange = useCallback(
- (date_filed) => (date) => {
- const formatted = moment(date).format('YYYY-MM-DD');
- setFieldValue(date_filed, formatted);
- },
- [setFieldValue],
- );
-
- const onItemsSelect = useCallback(
- (filedName) => {
- return (filed) => {
- setSelectedItems({
- ...selectedItems,
- [filedName]: filed,
- });
- setFieldValue(filedName, filed.currency_code);
- };
- },
- [setFieldValue, selectedItems],
- );
return (
-
-
-
+
+
+
);
}
-export default compose(
- withDialogActions,
- withExchangeRatesActions,
- withExchangeRateDetail,
- withCurrenciesActions,
- withCurrencies(({ currenciesList }) => ({ currenciesList })),
-)(ExchangeRateFormDialogContent);
+export default compose(withExchangeRateDetail)(ExchangeRateFormDialogContent);
diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.js
new file mode 100644
index 000000000..1b5ae13a9
--- /dev/null
+++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFields.js
@@ -0,0 +1,88 @@
+import React from 'react';
+import { Classes, FormGroup, InputGroup, Position } from '@blueprintjs/core';
+import { FastField } from 'formik';
+import { DateInput } from '@blueprintjs/datetime';
+import { FormattedMessage as T } from 'react-intl';
+import {
+ momentFormatter,
+ tansformDateValue,
+ handleDateChange,
+ inputIntent,
+} from 'utils';
+import {
+ ErrorMessage,
+ FieldRequiredHint,
+ CurrencySelectList,
+} from 'components';
+import { useExchangeRateFromContext } from './ExchangeRateFormProvider';
+
+import classNames from 'classnames';
+
+export default function ExchangeRateFormFields() {
+ const { action, currencies } = useExchangeRateFromContext();
+
+ return (
+
+ {/* ----------- Date ----------- */}
+
+ {({ form, field: { value }, meta: { error, touched } }) => (
+ }
+ labelInfo={FieldRequiredHint}
+ className={classNames('form-group--select-list', Classes.FILL)}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ inline={true}
+ >
+ {
+ form.setFieldValue('date', formattedDate);
+ })}
+ popoverProps={{ position: Position.BOTTOM, minimal: true }}
+ disabled={action === 'edit'}
+ />
+
+ )}
+
+ {/* ----------- Currency Code ----------- */}
+
+ {({ form, field: { value }, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ className={classNames('form-group--currency', Classes.FILL)}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ inline={true}
+ >
+ {
+ form.setFieldValue('currency_code', currency_code);
+ }}
+ disabled={action === 'edit'}
+ />
+
+ )}
+
+
+ {/*------------ Exchange Rate -----------*/}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ inline={true}
+ >
+
+
+ )}
+
+
+ );
+}
diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.js
new file mode 100644
index 000000000..d0790a5f6
--- /dev/null
+++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormFooter.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import { useFormikContext } from 'formik';
+
+import { Button, Classes, Intent } from '@blueprintjs/core';
+import { FormattedMessage as T } from 'react-intl';
+import { useExchangeRateFromContext } from './ExchangeRateFormProvider';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+function ExchangeRateFormFooter({
+ // #withDialogActions
+ closeDialog,
+}) {
+ const { isSubmitting } = useFormikContext();
+ const { dialogName, action } = useExchangeRateFromContext();
+
+ const handleClose = () => {
+ closeDialog(dialogName);
+ };
+
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default compose(withDialogActions)(ExchangeRateFormFooter);
diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormProvider.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormProvider.js
new file mode 100644
index 000000000..3c2cf8829
--- /dev/null
+++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/ExchangeRateFormProvider.js
@@ -0,0 +1,52 @@
+import React, { createContext, useContext } from 'react';
+import {
+ useCreateExchangeRate,
+ useEdiExchangeRate,
+ useCurrencies,
+ useExchangeRates,
+} from 'hooks/query';
+import { DialogContent } from 'components';
+
+const ExchangeRateFormContext = createContext();
+
+/**
+ * Exchange rate Form page provider.
+ */
+function ExchangeRateFormProvider({
+ exchangeRate,
+ action,
+ dialogName,
+ ...props
+}) {
+ // Create and edit exchange rate mutations.
+ const { mutateAsync: createExchangeRateMutate } = useCreateExchangeRate();
+ const { mutateAsync: editExchangeRateMutate } = useEdiExchangeRate();
+
+ // Load Currencies list.
+ const { data: currencies, isFetching: isCurrenciesLoading } = useCurrencies();
+ const { isFetching: isExchangeRatesLoading } = useExchangeRates();
+
+ const isNewMode = !exchangeRate;
+
+ // Provider state.
+ const provider = {
+ createExchangeRateMutate,
+ editExchangeRateMutate,
+ dialogName,
+ exchangeRate,
+ action,
+ currencies,
+ isExchangeRatesLoading,
+ isNewMode,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useExchangeRateFromContext = () => useContext(ExchangeRateFormContext);
+
+export { ExchangeRateFormProvider, useExchangeRateFromContext };
diff --git a/client/src/containers/Dialogs/ExchangeRateFormDialog/index.js b/client/src/containers/Dialogs/ExchangeRateFormDialog/index.js
index 044447c54..3c4c8f6ff 100644
--- a/client/src/containers/Dialogs/ExchangeRateFormDialog/index.js
+++ b/client/src/containers/Dialogs/ExchangeRateFormDialog/index.js
@@ -16,7 +16,7 @@ const ExchangeRateFormDialogContent = lazy(() =>
*/
function ExchangeRateFormDialog({
dialogName,
- payload = { action: '', id: null },
+ payload = { action: '', id: null , exchangeRate:"" },
isOpen,
}) {
return (
@@ -38,7 +38,7 @@ function ExchangeRateFormDialog({
diff --git a/client/src/containers/ExchangeRates/ExchangeRateTable.js b/client/src/containers/ExchangeRates/ExchangeRateTable.js
index c3c2f9a26..8813fa2e2 100644
--- a/client/src/containers/ExchangeRates/ExchangeRateTable.js
+++ b/client/src/containers/ExchangeRates/ExchangeRateTable.js
@@ -1,183 +1,75 @@
-import React, { useCallback, useMemo, useState } from 'react';
-import {
- Button,
- Popover,
- Menu,
- MenuItem,
- Position,
- Intent,
-} from '@blueprintjs/core';
-import { FormattedMessage as T, useIntl } from 'react-intl';
-import moment from 'moment';
-import classNames from 'classnames';
+import React from 'react';
-import { CLASSES } from 'common/classes';
+import { DataTable } from 'components';
+import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
-import { DataTable, Icon, Money } from 'components';
-import LoadingIndicator from 'components/LoadingIndicator';
+import { useExchangeRatesContext } from './ExchangeRatesProvider';
+import { useExchangeRatesTableColumns, ActionMenuList } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions';
-import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions';
-import withExchangeRates from 'containers/ExchangeRates/withExchangeRates';
+import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
+/**
+ * Exchange rates table.
+ */
function ExchangeRateTable({
- // #withExchangeRates
- exchangeRatesList,
- exchangeRatesLoading,
- exchangeRatesPageination,
+ // #ownProps
+ tableProps,
+
// #withDialogActions.
openDialog,
- // own properties
- loading,
- onFetchData,
- onDeleteExchangeRate,
- onSelectedRowsChange,
+ // #withAlertActions
+ openAlert,
}) {
- const [initialMount, setInitialMount] = useState(false);
- const { formatMessage } = useIntl();
+ const {
+ isExchangeRatesFetching,
+ isExchangeRatesLoading,
- const handelEditExchangeRate = useCallback(
- (exchange_rate) => () => {
- openDialog('exchangeRate-form', { action: 'edit', id: exchange_rate.id });
- },
- [openDialog],
- );
+ exchangesRates,
+ pagination,
+ } = useExchangeRatesContext();
- const handleDeleteExchangeRate = (exchange_rate) => () => {
- onDeleteExchangeRate(exchange_rate);
+ // Table columns.
+ const columns = useExchangeRatesTableColumns();
+
+ // Handle delete exchange rate.
+ const handleDeleteExchangeRate = ({ id }) => {
+ openAlert('exchange-rate-delete', { exchangeRateId: id });
};
- const actionMenuList = useCallback(
- (ExchangeRate) => (
-
- ),
- [handelEditExchangeRate, handleDeleteExchangeRate, formatMessage],
- );
-
- const rowContextMenu = (cell) => {
- return actionMenuList(cell.row.original);
+ // Handle Edit exchange rate.
+ const handelEditExchangeRate = (exchangeRate) => {
+ openDialog('exchangeRate-form', {
+ action: 'edit',
+ exchangeRate: exchangeRate,
+ });
};
- const columns = useMemo(
- () => [
- {
- id: 'date',
- Header: formatMessage({ id: 'date' }),
- accessor: (r) => moment(r.date).format('YYYY MMM DD'),
- width: 150,
- },
- {
- id: 'currency_code',
- Header: formatMessage({ id: 'currency_code' }),
- accessor: 'currency_code',
- className: 'currency_code',
- width: 150,
- },
- {
- id: 'exchange_rate',
- Header: formatMessage({ id: 'exchange_rate' }),
- accessor: (r) => (
-
- ),
- className: 'exchange_rate',
- width: 150,
- },
- {
- id: 'actions',
- Header: '',
- Cell: ({ cell }) => (
-
- } />
-
- ),
- className: 'actions',
- width: 50,
- },
- ],
- [actionMenuList, formatMessage],
- );
-
- const selectionColumn = useMemo(
- () => ({
- minWidth: 42,
- width: 42,
- maxWidth: 42,
- }),
- [],
- );
-
- const handelFetchData = useCallback(
- (...params) => {
- onFetchData && onFetchData(...params);
- },
- [onFetchData],
- );
-
- const handelSelectedRowsChange = useCallback(
- (selectRows) => {
- onSelectedRowsChange &&
- onSelectedRowsChange(selectRows.map((c) => c.original));
- },
- [onSelectedRowsChange],
- );
-
return (
-
-
-
-
-
+
);
}
-export default compose(
- withDialogActions,
- withExchangeRatesActions,
- withExchangeRates(
- ({
- exchangeRatesList,
- exchangeRatesLoading,
- exchangeRatesPageination,
- }) => ({
- exchangeRatesList,
- exchangeRatesLoading,
- exchangeRatesPageination,
- }),
- ),
-)(ExchangeRateTable);
+export default compose(withDialogActions, withAlertActions)(ExchangeRateTable);
diff --git a/client/src/containers/ExchangeRates/ExchangeRatesAlerts.js b/client/src/containers/ExchangeRates/ExchangeRatesAlerts.js
new file mode 100644
index 000000000..3e9dd83cf
--- /dev/null
+++ b/client/src/containers/ExchangeRates/ExchangeRatesAlerts.js
@@ -0,0 +1,12 @@
+import React from 'react';
+
+import ExchangeRateDeleteAlert from 'containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert';
+// import ExchangeRateBulkDeleteAlert from 'containers/Alerts/ExchangeRates/ExchangeRateBulkDeleteAlert';
+
+export default function ExchangeRatesAlerts() {
+ return (
+
+
+
+ );
+}
diff --git a/client/src/containers/ExchangeRates/ExchangeRatesList.js b/client/src/containers/ExchangeRates/ExchangeRatesList.js
index 146623229..e4c040d2a 100644
--- a/client/src/containers/ExchangeRates/ExchangeRatesList.js
+++ b/client/src/containers/ExchangeRates/ExchangeRatesList.js
@@ -1,204 +1,27 @@
-import React, { useEffect, useState, useCallback, useMemo } from 'react';
-import { useQuery, queryCache } from 'react-query';
-import { useParams } from 'react-router-dom';
-import { Alert, Intent } from '@blueprintjs/core';
-import {
- FormattedMessage as T,
- useIntl,
- FormattedHTMLMessage,
-} from 'react-intl';
-import AppToaster from 'components/AppToaster';
+import React from 'react';
+
+import { DashboardContentTable, DashboardPageContent } from 'components';
-import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
-import DashboardInsider from 'components/Dashboard/DashboardInsider';
import ExchangeRateTable from './ExchangeRateTable';
import ExchangeRateActionsBar from './ExchangeRateActionsBar';
-import withDialogActions from 'containers/Dialog/withDialogActions';
-import withDashboardActions from 'containers/Dashboard/withDashboardActions';
-import withResourceActions from 'containers/Resources/withResourcesActions';
-import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions';
-
-import { compose } from 'utils';
-
-function ExchangeRatesList({
- // #withDashboardActions
- changePageTitle,
-
- // #withResourceActions
- requestFetchResourceFields,
-
- // #withExchangeRatesActions
- requestFetchExchangeRates,
- requestDeleteExchangeRate,
- addExchangeRatesTableQueries,
- requestDeleteBulkExchangeRates,
-
- // #withDialog
- openDialog,
-}) {
- const { id } = useParams();
- const [deleteExchangeRate, setDeleteExchangeRate] = useState(false);
- const [selectedRows, setSelectedRows] = useState([]);
- const { formatMessage } = useIntl();
- const [bulkDelete, setBulkDelete] = useState(false);
- const [filter, setFilter] = useState({});
-
- const fetchExchangeRates = useQuery('exchange-rates-table', () =>
- requestFetchExchangeRates(),
- );
-
- useEffect(() => {
- id
- ? changePageTitle(formatMessage({ id: 'exchange_rate_details' }))
- : changePageTitle(formatMessage({ id: 'exchange_rates_list' }));
- }, [id, changePageTitle, formatMessage]);
-
- const handelDeleteExchangeRate = useCallback(
- (exchange_rate) => {
- setDeleteExchangeRate(exchange_rate);
- },
- [setDeleteExchangeRate],
- );
-
- const handelEditExchangeRate = (exchange_rate) => {
- openDialog('exchangeRate-form', { action: 'edit', id: exchange_rate.id });
- };
-
- const handelCancelExchangeRateDelete = useCallback(() => {
- setDeleteExchangeRate(false);
- }, [setDeleteExchangeRate]);
-
- const handelConfirmExchangeRateDelete = useCallback(() => {
- requestDeleteExchangeRate(deleteExchangeRate.id)
- .then(() => {
- setDeleteExchangeRate(false);
- AppToaster.show({
- message: formatMessage({
- id: 'the_exchange_rates_has_been_deleted_successfully',
- }),
- intent: Intent.SUCCESS,
- });
- })
- .catch(() => {
- setDeleteExchangeRate(false);
- });
- }, [deleteExchangeRate, requestDeleteExchangeRate, formatMessage]);
-
- // 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],
- );
-
- // Handle selected rows change.
- const handleSelectedRowsChange = useCallback(
- (exchange_rates) => {
- setSelectedRows(exchange_rates);
- },
- [setSelectedRows],
- );
-
- // Handle Exchange Rates bulk delete.
- const handleBulkDelete = useCallback(
- (exchangeRatesIds) => {
- setBulkDelete(exchangeRatesIds);
- },
- [setBulkDelete],
- );
-
- //Handel cancel itemCategories bulk delete.
- const handleCancelBulkDelete = useCallback(() => {
- setBulkDelete(false);
- }, []);
-
- // handle confirm Exchange Rates bulk delete.
- const handleConfirmBulkDelete = useCallback(() => {
- requestDeleteBulkExchangeRates(bulkDelete)
- .then(() => {
- setBulkDelete(false);
- AppToaster.show({
- message: formatMessage({
- id: 'the_exchange_rates_has_been_successfully_deleted',
- }),
- intent: Intent.SUCCESS,
- });
- })
- .catch((errors) => {
- setBulkDelete(false);
- });
- }, [requestDeleteBulkExchangeRates, bulkDelete, formatMessage]);
-
- // Calculates the data table selected rows count.
- const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
- selectedRows,
- ]);
+import { ExchangeRatesProvider } from './ExchangeRatesProvider';
+import ExchangeRatesAlerts from './ExchangeRatesAlerts';
+/**
+ * Exchange Rates list.
+ */
+export default function ExchangeRatesList() {
return (
-
-
+
+
+
-
- }
- confirmButtonText={}
- icon="trash"
- intent={Intent.DANGER}
- isOpen={deleteExchangeRate}
- onCancel={handelCancelExchangeRateDelete}
- onConfirm={handelConfirmExchangeRateDelete}
- >
-
-
-
-
- }
- confirmButtonText={`${formatMessage({
- id: 'delete',
- })} (${selectedRowsCount})`}
- icon="trash"
- intent={Intent.DANGER}
- isOpen={bulkDelete}
- onCancel={handleCancelBulkDelete}
- onConfirm={handleConfirmBulkDelete}
- >
-
-
-
-
+
+
+
-
+
+
);
}
-
-export default compose(
- withExchangeRatesActions,
- withResourceActions,
- withDashboardActions,
- withDialogActions,
-)(ExchangeRatesList);
diff --git a/client/src/containers/ExchangeRates/ExchangeRatesProvider.js b/client/src/containers/ExchangeRates/ExchangeRatesProvider.js
new file mode 100644
index 000000000..f296ff1c9
--- /dev/null
+++ b/client/src/containers/ExchangeRates/ExchangeRatesProvider.js
@@ -0,0 +1,35 @@
+import React, { createContext } from 'react';
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+import { useExchangeRates } from 'hooks/query';
+
+const ExchangesRatesContext = createContext();
+
+/**
+ * Exchanges rates list provider.
+ */
+function ExchangeRatesProvider({ query, ...props }) {
+ const {
+ data: { exchangesRates, pagination },
+ isFetching: isExchangeRatesFetching,
+ isLoading: isExchangeRatesLoading,
+ } = useExchangeRates();
+
+ const state = {
+ isExchangeRatesFetching,
+ isExchangeRatesLoading,
+
+ exchangesRates,
+ pagination,
+ query,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useExchangeRatesContext = () => React.useContext(ExchangesRatesContext);
+
+export { ExchangeRatesProvider, useExchangeRatesContext };
diff --git a/client/src/containers/ExchangeRates/components.js b/client/src/containers/ExchangeRates/components.js
new file mode 100644
index 000000000..dc63374b8
--- /dev/null
+++ b/client/src/containers/ExchangeRates/components.js
@@ -0,0 +1,94 @@
+import React, { useMemo } from 'react';
+import {
+ Menu,
+ Popover,
+ Button,
+ Position,
+ MenuItem,
+ MenuDivider,
+ Intent,
+} from '@blueprintjs/core';
+import { useIntl } from 'react-intl';
+import { Icon, Money } from 'components';
+import moment from 'moment';
+import { safeCallback } from 'utils';
+
+/**
+ * Row actions menu list.
+ */
+export function ActionMenuList({
+ row: { original },
+ payload: { onEditExchangeRate, onDeleteExchangeRate },
+}) {
+ const { formatMessage } = useIntl();
+
+ return (
+
+ );
+}
+
+/**
+ * Table actions cell.
+ */
+export function TableActionsCell(props) {
+ return (
+ }
+ position={Position.RIGHT_TOP}
+ >
+ } />
+
+ );
+}
+
+export function useExchangeRatesTableColumns() {
+ const { formatMessage } = useIntl();
+
+ return useMemo(
+ () => [
+ {
+ id: 'date',
+ Header: formatMessage({ id: 'date' }),
+ accessor: (r) => moment(r.date).format('YYYY MMM DD'),
+ width: 150,
+ },
+ {
+ id: 'currency_code',
+ Header: formatMessage({ id: 'currency_code' }),
+ accessor: 'currency_code',
+ className: 'currency_code',
+ width: 150,
+ },
+ {
+ id: 'exchange_rate',
+ Header: formatMessage({ id: 'exchange_rate' }),
+ accessor: (r) => (
+
+ ),
+ className: 'exchange_rate',
+ width: 150,
+ },
+ {
+ id: 'actions',
+ Header: '',
+ Cell: TableActionsCell,
+ className: 'actions',
+ width: 50,
+ },
+ ],
+ [formatMessage],
+ );
+}
diff --git a/client/src/hooks/query/exchangeRates.js b/client/src/hooks/query/exchangeRates.js
new file mode 100644
index 000000000..2493143f2
--- /dev/null
+++ b/client/src/hooks/query/exchangeRates.js
@@ -0,0 +1,83 @@
+import { useQuery, useMutation, useQueryClient } from 'react-query';
+import { defaultTo } from 'lodash';
+import useApiRequest from '../useRequest';
+
+/**
+ * Creates a new exchange rate.
+ */
+export function useCreateExchangeRate(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation((values) => apiRequest.post('exchange_rates', values), {
+ onSuccess: () => {
+ queryClient.invalidateQueries('EXCHANGES_RATES');
+ },
+ ...props,
+ });
+}
+
+/**
+ * Edits the exchange rate.
+ */
+export function useEdiExchangeRate(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ ([id, values]) => apiRequest.post(`exchange_rates/${id}`, values),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries('EXCHANGES_RATES');
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Deletes the exchange rate.
+ */
+export function useDeleteExchangeRate(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation((id) => apiRequest.delete(`exchange_rates/${id}`), {
+ onSuccess: () => {
+ queryClient.invalidateQueries('EXCHANGES_RATES');
+ },
+ ...props,
+ });
+}
+
+// Transforms items categories.
+const transformExchangesRates = (response) => {
+ return {
+ exchangesRates: response.data.exchange_rates.results,
+ pagination: response.data.exchange_rates.pagination,
+ };
+};
+
+/**
+ * Retrieve the exchange rate list.
+ */
+export function useExchangeRates(query, props) {
+ const apiRequest = useApiRequest();
+
+ const states = useQuery(
+ ['EXCHANGES_RATES', query],
+ () =>
+ apiRequest.get('exchange_rates', { params: { query } }).then(
+ transformExchangesRates,
+ ),
+ props,
+ );
+
+ return {
+ ...states,
+ data: defaultTo(states.data, {
+ exchangesRates: [],
+ pagination: {},
+ }),
+ };
+}
diff --git a/client/src/hooks/query/index.js b/client/src/hooks/query/index.js
index 8dae21b77..02dd868a5 100644
--- a/client/src/hooks/query/index.js
+++ b/client/src/hooks/query/index.js
@@ -18,4 +18,5 @@ export * from './paymentReceives';
export * from './paymentMades';
export * from './settings';
export * from './users';
-export * from './invite';
\ No newline at end of file
+export * from './invite';
+export * from './exchangeRates';
diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js
index 6bda59bfd..8c2dcabcd 100644
--- a/client/src/routes/dashboard.js
+++ b/client/src/routes/dashboard.js
@@ -167,6 +167,7 @@ export default [
path: `/exchange-rates`,
component: lazy(() => import('containers/ExchangeRates/ExchangeRatesList')),
breadcrumb: 'Exchange Rates',
+ pageTitle: formatMessage({ id: 'exchange_rates_list' }),
},
// Expenses.
{