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/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..2299624a3
--- /dev/null
+++ b/client/src/hooks/query/exchangeRates.js
@@ -0,0 +1,78 @@
+import { useQuery, useMutation, useQueryClient } from 'react-query';
+import { defaultTo } from 'lodash';
+import ApiService from 'services/ApiService';
+
+/**
+ * Creates a new exchange rate.
+ */
+export function useCreateExchangeRate(props) {
+ const queryClient = useQueryClient();
+
+ return useMutation((values) => ApiService.post('exchange_rates', values), {
+ onSuccess: () => {
+ queryClient.invalidateQueries('EXCHANGES_RATES');
+ },
+ ...props,
+ });
+}
+
+/**
+ * Edits the exchange rate.
+ */
+export function useEdiExchangeRate(props) {
+ const queryClient = useQueryClient();
+
+ return useMutation(
+ ([id, values]) => ApiService.post(`exchange_rates/${id}`, values),
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries('EXCHANGES_RATES');
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Deletes the exchange rate.
+ */
+export function useDeleteExchangeRate(props) {
+ const queryClient = useQueryClient();
+
+ return useMutation((id) => ApiService.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 states = useQuery(
+ ['EXCHANGES_RATES', query],
+ () =>
+ ApiService.get('exchange_rates', { params: { query } }).then(
+ transformExchangesRates,
+ ),
+ props,
+ );
+
+ return {
+ ...states,
+ data: defaultTo(states.data, {
+ exchangesRates: [],
+ pagination: {},
+ }),
+ };
+}
diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js
index f2362089c..f8bdfde31 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.
{