diff --git a/client/src/components/AccountsSelectList.js b/client/src/components/AccountsSelectList.js
index 0a70fa140..68e079184 100644
--- a/client/src/components/AccountsSelectList.js
+++ b/client/src/components/AccountsSelectList.js
@@ -76,6 +76,7 @@ export default function AccountsSelectList({
popoverProps={{ minimal: true }}
filterable={true}
onItemSelect={onAccountSelect}
+ disabled={disabled}
>
-
+
);
}
-export default withExchangeRatesDialog(ExchangeRateDialog);
+export default compose(
+ withDialogActions,
+ withExchangeRatesActions,
+ withExchangeRateDetail,
+ withCurrenciesActions,
+ withCurrencies(({ currenciesList }) => ({ currenciesList })),
+)(ExchangeRateFormDialogContent);
diff --git a/client/src/containers/ExchangeRates/ExchangeRateTable.js b/client/src/containers/ExchangeRates/ExchangeRateTable.js
index 5badc2075..a54a47f6d 100644
--- a/client/src/containers/ExchangeRates/ExchangeRateTable.js
+++ b/client/src/containers/ExchangeRates/ExchangeRateTable.js
@@ -1,4 +1,4 @@
-import React, { useCallback, useMemo, useState, useEffect } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
import {
Button,
Popover,
@@ -10,7 +10,7 @@ import {
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
-import { DataTable, Money, Icon } from 'components';
+import { DataTable, Icon, MoneyExchangeRate } from 'components';
import LoadingIndicator from 'components/LoadingIndicator';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -23,7 +23,7 @@ function ExchangeRateTable({
// #withExchangeRates
exchangeRatesList,
exchangeRatesLoading,
-
+ exchangeRatesPageination,
// #withDialogActions.
openDialog,
@@ -31,7 +31,6 @@ function ExchangeRateTable({
loading,
onFetchData,
onDeleteExchangeRate,
- onEditExchangeRate,
onSelectedRowsChange,
}) {
const [initialMount, setInitialMount] = useState(false);
@@ -52,20 +51,25 @@ function ExchangeRateTable({
(ExchangeRate) => (
),
- [handelEditExchangeRate, handleDeleteExchangeRate],
+ [handelEditExchangeRate, handleDeleteExchangeRate, formatMessage],
);
+ const rowContextMenu = (cell) => {
+ return actionMenuList(cell.row.original);
+ };
+
const columns = useMemo(
() => [
{
@@ -84,7 +88,12 @@ function ExchangeRateTable({
{
id: 'exchange_rate',
Header: formatMessage({ id: 'exchange_rate' }),
- accessor: (r) => ,
+ accessor: (r) => (
+
+ ),
className: 'exchange_rate',
width: 150,
},
@@ -94,14 +103,13 @@ function ExchangeRateTable({
Cell: ({ cell }) => (
} />
),
className: 'actions',
width: 50,
- disableResizing: false,
},
],
[actionMenuList, formatMessage],
@@ -143,7 +151,11 @@ function ExchangeRateTable({
expandable={true}
treeGraph={true}
onSelectedRowsChange={handelSelectedRowsChange}
- spinnerProps={{ size: 30 }}
+ rowContextMenu={rowContextMenu}
+ pagination={true}
+ pagesCount={exchangeRatesPageination.pagesCount}
+ initialPageSize={exchangeRatesPageination.pageSize}
+ initialPageIndex={exchangeRatesPageination.page - 1}
/>
);
@@ -152,8 +164,15 @@ function ExchangeRateTable({
export default compose(
withDialogActions,
withExchangeRatesActions,
- withExchangeRates(({ exchangeRatesList, exchangeRatesLoading }) => ({
- exchangeRatesList,
- exchangeRatesLoading,
- })),
+ withExchangeRates(
+ ({
+ exchangeRatesList,
+ exchangeRatesLoading,
+ exchangeRatesPageination,
+ }) => ({
+ exchangeRatesList,
+ exchangeRatesLoading,
+ exchangeRatesPageination,
+ }),
+ ),
)(ExchangeRateTable);
diff --git a/client/src/containers/ExchangeRates/withExchangeRates.js b/client/src/containers/ExchangeRates/withExchangeRates.js
index 7eb3408d3..fa411f372 100644
--- a/client/src/containers/ExchangeRates/withExchangeRates.js
+++ b/client/src/containers/ExchangeRates/withExchangeRates.js
@@ -1,11 +1,24 @@
import { connect } from 'react-redux';
-import { getExchangeRatesList } from 'store/ExchangeRate/exchange.selector';
+import {
+ getExchangeRatesList,
+ getExchangeRatePaginationMetaFactory,
+ getExchangeRatesTableQueryFactory,
+} from 'store/ExchangeRate/exchange.selector';
export default (mapState) => {
+ const getExchangeRatesPaginationMeta = getExchangeRatePaginationMetaFactory();
+
const mapStateToProps = (state, props) => {
+ const query = getExchangeRatesTableQueryFactory(state, props);
+
const mapped = {
exchangeRatesList: getExchangeRatesList(state, props),
exchangeRatesLoading: state.exchangeRates.loading,
+ exchangeRatesPageination: getExchangeRatesPaginationMeta(
+ state,
+ props,
+ query,
+ ),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js
index 54ff4d23c..658678f20 100644
--- a/client/src/lang/en/index.js
+++ b/client/src/lang/en/index.js
@@ -73,7 +73,7 @@ export default {
new_currency: 'New Currency',
currency_name: 'Currency Name',
currency_code: 'Currency Code',
- select_currency_code: 'select Currency Code',
+ select_currency_code: 'Select Currency Code',
edit_exchange_rate: 'Edit Exchange Rate',
new_exchange_rate: 'New Exchange Rate',
delete_exchange_rate: 'Delete Exchange Rate',
diff --git a/client/src/store/ExchangeRate/exchange.actions.js b/client/src/store/ExchangeRate/exchange.actions.js
index 33a1cbf1d..072a17332 100644
--- a/client/src/store/ExchangeRate/exchange.actions.js
+++ b/client/src/store/ExchangeRate/exchange.actions.js
@@ -4,26 +4,40 @@ import t from 'store/types';
export const fetchExchangeRates = () => {
return (dispatch) =>
new Promise((resolve, reject) => {
- dispatch({
- type: t.SET_DASHBOARD_REQUEST_LOADING,
- });
dispatch({
type: t.EXCHANGE_RATE_TABLE_LOADING,
- loading: true,
+ payload: {
+ loading: true,
+ },
});
+
ApiService.get('exchange_rates')
.then((response) => {
-
+ dispatch({
+ type: t.EXCHANGE_RATES_PAGE_SET,
+ payload: {
+ exchange_rates: response.data.exchange_rates.results,
+ pagination: response.data.exchange_rates.pagination,
+ customViewId: response.data.exchange_rates.customViewId || -1,
+ },
+ });
dispatch({
type: t.EXCHANGE_RATE_LIST_SET,
exchange_rates: response.data.exchange_rates.results,
});
+
dispatch({
- type: t.SET_DASHBOARD_REQUEST_COMPLETED,
+ type: t.EXCHANGE_RATES_PAGINATION_SET,
+ payload: {
+ pagination: response.data.exchange_rates.pagination,
+ customViewId: response.data.customViewId || -1,
+ },
});
dispatch({
type: t.EXCHANGE_RATE_TABLE_LOADING,
- loading: false,
+ payload: {
+ loading: false,
+ },
});
resolve(response);
})
diff --git a/client/src/store/ExchangeRate/exchange.reducer.js b/client/src/store/ExchangeRate/exchange.reducer.js
index 0d8f26010..112299fd9 100644
--- a/client/src/store/ExchangeRate/exchange.reducer.js
+++ b/client/src/store/ExchangeRate/exchange.reducer.js
@@ -1,11 +1,18 @@
import { createReducer } from '@reduxjs/toolkit';
+import { createTableQueryReducers } from 'store/queryReducers';
import t from 'store/types';
const initialState = {
exchangeRates: {},
+ loading: false,
+ tableQuery: {
+ page_size: 5,
+ page: 1,
+ },
+ currentViewId: -1,
};
-export default createReducer(initialState, {
+const reducer = createReducer(initialState, {
[t.EXCHANGE_RATE_LIST_SET]: (state, action) => {
const _exchangeRates = {};
action.exchange_rates.forEach((exchange_rate) => {
@@ -17,8 +24,51 @@ export default createReducer(initialState, {
..._exchangeRates,
};
},
+
[t.EXCHANGE_RATE_TABLE_LOADING]: (state, action) => {
- state.loading = action.loading;
+
+ const { loading } = action.payload;
+ state.loading = loading;
+ },
+
+ [t.ESTIMATES_PAGE_SET]: (state, action) => {
+ const { customViewId, exchange_rates, pagination } = action.payload;
+
+ const viewId = customViewId || -1;
+ const view = state.views[viewId] || {};
+
+ state.views[viewId] = {
+ ...view,
+ pages: {
+ ...(state.views?.[viewId]?.pages || {}),
+ [pagination.page]: {
+ ids: exchange_rates.map((i) => i.id),
+ },
+ },
+ };
+ },
+
+ [t.EXCHANGE_RATES_PAGINATION_SET]: (state, action) => {
+ const { pagination, customViewId } = action.payload;
+
+ const mapped = {
+ pageSize: parseInt(pagination.pageSize, 10),
+ page: parseInt(pagination.page, 10),
+ total: parseInt(pagination.total, 10),
+ };
+ const paginationMeta = {
+ ...mapped,
+ pagesCount: Math.ceil(mapped.total / mapped.pageSize),
+ pageIndex: Math.max(mapped.page - 1, 0),
+ };
+
+ state.views = {
+ ...state.views,
+ [customViewId]: {
+ ...(state.views?.[customViewId] || {}),
+ paginationMeta,
+ },
+ };
},
[t.EXCHANGE_RATES_BULK_DELETE]: (state, action) => {
@@ -35,3 +85,5 @@ export default createReducer(initialState, {
}
},
});
+
+export default createTableQueryReducers('exchange_rates', reducer);
diff --git a/client/src/store/ExchangeRate/exchange.selector.js b/client/src/store/ExchangeRate/exchange.selector.js
index dd676674e..cc7f726c1 100644
--- a/client/src/store/ExchangeRate/exchange.selector.js
+++ b/client/src/store/ExchangeRate/exchange.selector.js
@@ -1,8 +1,18 @@
import { createSelector } from 'reselect';
-import { pickItemsFromIds, getItemById } from 'store/selectors';
+import {
+ pickItemsFromIds,
+ getItemById,
+ paginationLocationQuery,
+} from 'store/selectors';
const exchangeRateItemsSelector = (state) => state.exchangeRates.exchangeRates;
const exchangeRateIdPropSelector = (state, props) => props.exchangeRateId;
+const exchangeRateTableQuery = (state) => state.exchangeRates.tableQuery;
+
+const exchangeRatesCurrentViewSelector = (state, props) => {
+ const viewId = state.exchangeRates.currentViewId;
+ return state.exchangeRates.views?.[viewId];
+};
export const getExchangeRatesList = createSelector(
exchangeRateItemsSelector,
@@ -18,3 +28,20 @@ export const getExchangeRateById = createSelector(
return getItemById(exchangeRates, exchangeRateId);
},
);
+
+export const getExchangeRatePaginationMetaFactory = () =>
+ createSelector(exchangeRatesCurrentViewSelector, (exchangeRateView) => {
+ return exchangeRateView?.paginationMeta || {};
+ });
+
+export const getExchangeRatesTableQueryFactory = () =>
+ createSelector(
+ paginationLocationQuery,
+ exchangeRateTableQuery,
+ (locationQuery, tableQuery) => {
+ return {
+ ...locationQuery,
+ ...tableQuery,
+ };
+ },
+ );
diff --git a/client/src/store/ExchangeRate/exchange.type.js b/client/src/store/ExchangeRate/exchange.type.js
index 13b237388..85807c21e 100644
--- a/client/src/store/ExchangeRate/exchange.type.js
+++ b/client/src/store/ExchangeRate/exchange.type.js
@@ -4,6 +4,8 @@ export default {
EXCHANGE_RATE_LIST_SET: 'EXCHANGE_RATE_LIST_SET',
CLEAR_EXCHANGE_RATE_FORM_ERRORS: 'CLEAR_EXCHANGE_RATE_FORM_ERRORS',
ExchangeRates_TABLE_QUERIES_ADD: 'ExchangeRates_TABLE_QUERIES_ADD',
- EXCHANGE_RATE_TABLE_LOADING:'EXCHANGE_RATE_TABLE_LOADING',
+ EXCHANGE_RATE_TABLE_LOADING: 'EXCHANGE_RATE_TABLE_LOADING',
EXCHANGE_RATES_BULK_DELETE: 'EXCHANGE_RATES_BULK_DELETE',
+ EXCHANGE_RATES_PAGE_SET: 'EXCHANGE_RATES_PAGE_SET',
+ EXCHANGE_RATES_PAGINATION_SET: 'EXCHANGE_RATES_PAGINATION_SET',
};
diff --git a/client/src/store/accounts/accounts.actions.js b/client/src/store/accounts/accounts.actions.js
index 5a33e89a6..0661ae77e 100644
--- a/client/src/store/accounts/accounts.actions.js
+++ b/client/src/store/accounts/accounts.actions.js
@@ -149,11 +149,11 @@ export const editAccount = (id, form) => {
};
export const activateAccount = ({ id }) => {
- return (dispatch) => ApiService.post(`accounts/${id}/active`);
+ return (dispatch) => ApiService.post(`accounts/${id}/activate`);
};
export const inactiveAccount = ({ id }) => {
- return (dispatch) => ApiService.post(`accounts/${id}/inactive`);
+ return (dispatch) => ApiService.post(`accounts/${id}/inactivate`);
};
export const bulkActivateAccounts = ({ ids }) => {
diff --git a/client/src/utils.js b/client/src/utils.js
index e30558cca..dbc506aeb 100644
--- a/client/src/utils.js
+++ b/client/src/utils.js
@@ -4,20 +4,19 @@ import Currency from 'js-money/lib/currency';
import PProgress from 'p-progress';
import accounting from 'accounting';
-
export function removeEmptyFromObject(obj) {
obj = Object.assign({}, obj);
var keys = Object.keys(obj);
- keys.forEach(function(key) {
+ keys.forEach(function (key) {
const value = obj[key];
- if (value === '' || value === null || value === undefined ) {
+ if (value === '' || value === null || value === undefined) {
delete obj[key];
}
});
return obj;
-};
+}
export const optionsMapToArray = (optionsMap, service = '') => {
return Object.keys(optionsMap).map((optionKey) => {
@@ -27,7 +26,7 @@ export const optionsMapToArray = (optionsMap, service = '') => {
key: service ? `${service}_${optionKey}` : `${optionKey}`,
value: optionValue,
};
- })
+ });
};
export const optionsArrayToMap = (optionsArray) => {
@@ -37,7 +36,7 @@ export const optionsArrayToMap = (optionsArray) => {
}, {});
};
-export function numberComma(number){
+export function numberComma(number) {
number = typeof number === 'number' ? String(number) : number;
const parts = number.split('.');
@@ -51,11 +50,11 @@ export function numberComma(number){
export const momentFormatter = (format) => {
return {
- formatDate: date => moment(date).format(format),
- parseDate: str => moment(str, format).toDate(),
+ formatDate: (date) => moment(date).format(format),
+ parseDate: (str) => moment(str, format).toDate(),
placeholder: `${format}`,
};
-}
+};
/** Event handler that exposes the target element's value as a boolean. */
export const handleBooleanChange = (handler) => {
@@ -69,7 +68,7 @@ export const handleStringChange = (handler) => {
/** Event handler that exposes the target element's value as a number. */
export const handleNumberChange = (handler) => {
- return handleStringChange(value => handler(+value));
+ return handleStringChange((value) => handler(+value));
};
export const objectKeysTransform = (obj, transform) => {
@@ -81,29 +80,35 @@ export const objectKeysTransform = (obj, transform) => {
};
export const compose = (...funcs) =>
- funcs.reduce((a, b) => (...args) => a(b(...args)), arg => arg);
+ funcs.reduce(
+ (a, b) => (...args) => a(b(...args)),
+ (arg) => arg,
+ );
export const getObjectDiff = (a, b) => {
- return _.reduce(a, (result, value, key) => {
- return _.isEqual(value, b[key]) ?
- result : result.concat(key);
- }, []);
-}
+ return _.reduce(
+ a,
+ (result, value, key) => {
+ return _.isEqual(value, b[key]) ? result : result.concat(key);
+ },
+ [],
+ );
+};
export const parseDateRangeQuery = (keyword) => {
const queries = {
- 'today': {
+ today: {
range: 'day',
},
- 'this_year': {
+ this_year: {
range: 'year',
},
- 'this_month': {
- range: 'month'
+ this_month: {
+ range: 'month',
+ },
+ this_week: {
+ range: 'week',
},
- 'this_week': {
- range: 'week'
- }
};
if (typeof queries[keyword] === 'undefined') {
@@ -117,7 +122,6 @@ export const parseDateRangeQuery = (keyword) => {
};
};
-
export const defaultExpanderReducer = (tableRows, level) => {
let currentLevel = 1;
const expended = [];
@@ -126,7 +130,7 @@ export const defaultExpanderReducer = (tableRows, level) => {
return rows.forEach((row, index) => {
const _index = parentIndex ? `${parentIndex}.${index}` : `${index}`;
expended[_index] = true;
-
+
if (row.children && currentLevel < level) {
walker(row.children, _index);
}
@@ -135,7 +139,7 @@ export const defaultExpanderReducer = (tableRows, level) => {
};
walker(tableRows);
return expended;
-}
+};
export function formattedAmount(cents, currency) {
const { symbol, decimal_digits: precision } = Currency[currency];
@@ -143,16 +147,27 @@ export function formattedAmount(cents, currency) {
return accounting.formatMoney(amount, { symbol, precision });
}
+export function formattedExchangeRate(amount, currency) {
+ const options = {
+ style: 'currency',
+ currency: currency,
+ minimumFractionDigits: 2,
+ };
-export const ConditionalWrapper = ({ condition, wrapper, children }) =>
+ const formatter = new Intl.NumberFormat(undefined, options);
+
+ return formatter.format(amount);
+}
+
+export const ConditionalWrapper = ({ condition, wrapper, children }) =>
condition ? wrapper(children) : children;
export const checkRequiredProperties = (obj, properties) => {
return properties.some((prop) => {
const value = obj[prop];
- return (value === '' || value === null || value === undefined);
- })
-}
+ return value === '' || value === null || value === undefined;
+ });
+};
export const saveFilesInAsync = (files, actionCb, extraTasks) => {
const opers = [];
@@ -164,13 +179,17 @@ export const saveFilesInAsync = (files, actionCb, extraTasks) => {
actionCb(formData, file, (requestProgress) => {
progress(requestProgress);
})
- .then((data) => { resolve(data); })
- .catch(error => { reject(error); })
+ .then((data) => {
+ resolve(data);
+ })
+ .catch((error) => {
+ reject(error);
+ });
});
opers.push(oper);
});
return PProgress.all(opers);
-}
+};
export const firstLettersArgs = (...args) => {
let letters = [];
@@ -181,22 +200,18 @@ export const firstLettersArgs = (...args) => {
}
});
return letters.join('').toUpperCase();
-}
-
+};
export const uniqueMultiProps = (items, props) => {
return _.uniqBy(items, (item) => {
return JSON.stringify(_.pick(item, props));
});
-}
-
+};
export const transformUpdatedRows = (rows, rowIndex, columnIdOrObj, value) => {
- const columnId =
- typeof columnIdOrObj !== 'object' ? columnIdOrObj : null;
+ const columnId = typeof columnIdOrObj !== 'object' ? columnIdOrObj : null;
- const updateTable =
- typeof columnIdOrObj === 'object' ? columnIdOrObj : null;
+ const updateTable = typeof columnIdOrObj === 'object' ? columnIdOrObj : null;
const newData = updateTable ? updateTable : { [columnId]: value };
@@ -206,7 +221,7 @@ export const transformUpdatedRows = (rows, rowIndex, columnIdOrObj, value) => {
}
return { ...row };
});
-}
+};
export const tansformDateValue = (date) => {
return moment(date).toDate() || new Date();
@@ -222,7 +237,7 @@ export const repeatValue = (value, len) => {
export const flatToNestedArray = (
data,
- config = { id: 'id', parentId: 'parent_id' }
+ config = { id: 'id', parentId: 'parent_id' },
) => {
const map = {};
const nestedArray = [];
@@ -246,4 +261,4 @@ export const flatToNestedArray = (
export const orderingLinesIndexes = (lines, attribute = 'index') => {
return lines.map((line, index) => ({ ...line, [attribute]: index + 1 }));
-};
\ No newline at end of file
+};