diff --git a/CHANGELOG.md b/CHANGELOG.md index 1025981fc..5cc119df7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * Refactor CSS away from `color()` function. Refs UIU-3013. * Add `reminderFee` to loan action map. Fixes UIU-3014. * Create new permission 'Users: Can view profile pictures'. Refs UIU-3018. +* Format currency values as currencies, not numbers. Refs UIU-2026. ## [10.0.4](https://github.com/folio-org/ui-users/tree/v10.0.4) (2023-11-10) [Full Changelog](https://github.com/folio-org/ui-users/compare/v10.0.3...v10.0.4) diff --git a/src/components/Accounts/Actions/ActionModal.js b/src/components/Accounts/Actions/ActionModal.js index 90564e9ef..56e7fa036 100644 --- a/src/components/Accounts/Actions/ActionModal.js +++ b/src/components/Accounts/Actions/ActionModal.js @@ -1,10 +1,11 @@ import _ from 'lodash'; import React from 'react'; import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, injectIntl } from 'react-intl'; import { Field } from 'react-final-form'; import setFieldData from 'final-form-set-field-data'; +import { stripesConnect } from "@folio/stripes/core"; import stripesFinalForm from '@folio/stripes/final-form'; import { Row, @@ -28,6 +29,7 @@ import { } from '../../../constants'; import css from './PayWaive.css'; +import { localizeCurrencyAmount } from "../../util/localizeCurrencyAmount"; class ActionModal extends React.Component { static propTypes = { @@ -93,21 +95,22 @@ class ActionModal extends React.Component { feeFineActions = [], action, form: { getState }, - intl: { formatMessage }, + intl, + stripes, } = this.props; const { values: { amount } } = getState(); const selected = calculateSelectedAmount(accounts, this.isRefundAction(action), feeFineActions); const type = parseFloat(amount) < parseFloat(selected) - ? formatMessage({ id: `ui-users.accounts.${action}.summary.partially` }) - : formatMessage({ id: `ui-users.accounts.${action}.summary.fully` }); + ? intl.formatMessage({ id: `ui-users.accounts.${action}.summary.partially` }) + : intl.formatMessage({ id: `ui-users.accounts.${action}.summary.fully` }); return ( @@ -325,7 +328,8 @@ class ActionModal extends React.Component { owedAmount, commentRequired, form: { getState }, - intl: { formatMessage }, + intl, + stripes, data, handleSubmit, label, @@ -379,7 +383,7 @@ class ActionModal extends React.Component { : - {totalPaidAmount} + {localizeCurrencyAmount(totalPaidAmount, stripes.currency, intl)} ) : ( @@ -399,7 +403,7 @@ class ActionModal extends React.Component { : - {selected} + {localizeCurrencyAmount(selected, stripes.currency, intl)} @@ -431,7 +435,7 @@ class ActionModal extends React.Component { : - { accountRemainingAmount || } + {accountRemainingAmount ? localizeCurrencyAmount(accountRemainingAmount, stripes.currency, intl) : } { this.isRefundAction(action) && ( @@ -464,7 +468,7 @@ class ActionModal extends React.Component { name="ownerId" component={Select} dataOptions={ownerOptions} - placeholder={formatMessage({ id: 'ui-users.accounts.payment.owner.placeholder' })} + placeholder={intl.formatMessage({ id: 'ui-users.accounts.payment.owner.placeholder' })} onChange={this.onChangeOwner} defaultValue={ownerId} /> @@ -563,4 +567,4 @@ export default stripesFinalForm({ navigationCheck: true, subscription: { values: true }, mutators: { setFieldData }, -})(ActionModal); +})(injectIntl(stripesConnect(ActionModal))); diff --git a/src/components/Accounts/Actions/CancellationModal.js b/src/components/Accounts/Actions/CancellationModal.js index 65ba06333..ab2094b5c 100644 --- a/src/components/Accounts/Actions/CancellationModal.js +++ b/src/components/Accounts/Actions/CancellationModal.js @@ -1,10 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { isEmpty } from 'lodash'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, injectIntl } from 'react-intl'; import { Field } from 'react-final-form'; import setFieldData from 'final-form-set-field-data'; +import { stripesConnect } from "@folio/stripes/core"; import stripesFinalForm from '@folio/stripes/final-form'; import { Modal, @@ -16,6 +17,7 @@ import { } from '@folio/stripes/components'; import css from './modal.css'; +import { localizeCurrencyAmount } from "../../util/localizeCurrencyAmount"; class CancellationModal extends React.Component { static propTypes = { @@ -29,6 +31,8 @@ class CancellationModal extends React.Component { handleSubmit: PropTypes.func.isRequired, owners: PropTypes.arrayOf(PropTypes.object), feefines: PropTypes.arrayOf(PropTypes.object), + intl: PropTypes.object.isRequired, + stripes: PropTypes.object.isRequired, }; onCloseModal = () => { @@ -46,7 +50,7 @@ class CancellationModal extends React.Component { }; render() { - const defaultAmount = '0.00'; + const defaultAmount = 0; const defaultFeeFineType = 'fee/fine type'; const { account, @@ -60,6 +64,8 @@ class CancellationModal extends React.Component { pristine, submitting, handleSubmit, + intl, + stripes, form: { getState }, } = this.props; @@ -95,7 +101,7 @@ class CancellationModal extends React.Component { @@ -179,4 +185,4 @@ class CancellationModal extends React.Component { export default stripesFinalForm({ subscription: { values: true }, mutators: { setFieldData } -})(CancellationModal); +})(injectIntl(stripesConnect(CancellationModal))); diff --git a/src/components/Accounts/Actions/FeeFineActions.js b/src/components/Accounts/Actions/FeeFineActions.js index c67644819..5a456f53b 100644 --- a/src/components/Accounts/Actions/FeeFineActions.js +++ b/src/components/Accounts/Actions/FeeFineActions.js @@ -20,6 +20,7 @@ import { SHARED_OWNER, } from '../../../constants'; import { + formatCurrencyAmount, getFullName, isRefundAllowed, } from '../../util'; @@ -27,6 +28,7 @@ import { calculateSelectedAmount, loadServicePoints, } from '../accountFunctions'; +import { localizeCurrencyAmount } from "../../util/localizeCurrencyAmount"; class Actions extends React.Component { static manifest = Object.freeze({ @@ -244,9 +246,8 @@ class Actions extends React.Component { } showCalloutMessage({ amount }) { - const { user } = this.props; + const { user, stripes, intl } = this.props; const paymentStatus = this.paymentStatus; - const formattedAmount = parseFloat(amount).toFixed(2); const fullName = getFullName(user); const message = ( @@ -254,7 +255,7 @@ class Actions extends React.Component { id="ui-users.accounts.actions.cancellation.success" values={{ count: 1, - amount: formattedAmount, + amount: localizeCurrencyAmount(amount, stripes.currency, intl), action: paymentStatus.toLowerCase(), user: fullName }} @@ -303,8 +304,8 @@ class Actions extends React.Component { accountId: id, dateAction: moment().format(), userId: this.props.user.id, - amountAction: parseFloat(amount || 0).toFixed(2), - balance: parseFloat(balance || 0).toFixed(2), + amountAction: formatCurrencyAmount(amount), + balance: formatCurrencyAmount(balance), transactionInformation: transaction || '', comments: comment, notify, @@ -318,7 +319,7 @@ class Actions extends React.Component { const newAccount = { status: { name: status }, paymentStatus: { name: paymentStatus }, - remaining: parseFloat(remaining || 0).toFixed(2), + remaining: formatCurrencyAmount(remaining), }; return this.props.mutator.accounts.PUT(Object.assign(account, newAccount)); @@ -614,8 +615,9 @@ class Actions extends React.Component { stripes, resources, match: { params }, - intl: { formatMessage }, + intl, } = this.props; + const { formatMessage } = intl; const { accounts, showConfirmDialog, @@ -753,15 +755,15 @@ class Actions extends React.Component { form={m.form ? m.form : `${m.action}-modal`} onClose={this.onCloseActionModal} servicePointsIds={servicePointsIds} - balance={parseFloat(this.props.balance).toFixed(2)} + balance={localizeCurrencyAmount(this.props.balance, stripes.currency, intl)} accounts={(m.accounts) ? m.accounts : ((m.item) ? this.props.accounts : accounts)} onSubmit={(values) => { this.showConfirmDialog(values); }} owners={owners} feefines={feefines} feeFineActions={feeFineActions} okapi={this.props.okapi} - totalPaidAmount={parseFloat(this.props.totalPaidAmount).toFixed(2)} - owedAmount={parseFloat(this.props.owedAmount).toFixed(2)} + totalPaidAmount={localizeCurrencyAmount(this.props.totalPaidAmount, stripes.currency, intl)} + owedAmount={localizeCurrencyAmount(this.props.owedAmount, stripes.currency, intl)} /> ); } diff --git a/src/components/Accounts/Actions/FeeFineActions.test.js b/src/components/Accounts/Actions/FeeFineActions.test.js index 063c80ea2..cda89b819 100644 --- a/src/components/Accounts/Actions/FeeFineActions.test.js +++ b/src/components/Accounts/Actions/FeeFineActions.test.js @@ -12,6 +12,7 @@ import '../../../../test/jest/__mock__'; import FeeFineActions from './FeeFineActions'; import CommentModal from './CommentModal'; +import {formatCurrencyAmount} from "../../util"; jest.mock('./CancellationModal', () => jest.fn(() => null)); jest.mock('./CommentModal', () => jest.fn(() => null)); @@ -167,8 +168,8 @@ describe('FeeFineActions', () => { createdAt: mockedCurServicePoint.id, accountId: mockedAccount.id, userId: mockedUser.id, - amountAction: parseFloat(0).toFixed(2), - balance: parseFloat(mockedAccount.remaining).toFixed(2), + amountAction: formatCurrencyAmount(0), + balance: formatCurrencyAmount(mockedAccount.remaining), transactionInformation: '', comments: `${labelIds.tagStaff} : ${mockedComment}`, }))); @@ -178,7 +179,7 @@ describe('FeeFineActions', () => { paymentStatus: { name: mockedAccount.paymentStatus.name, }, - remaining: parseFloat(mockedAccount.remaining).toFixed(2), + remaining: formatCurrencyAmount(mockedAccount.remaining), status: { name: mockedAccount.status.name, }, diff --git a/src/components/Accounts/Actions/WarningModal.js b/src/components/Accounts/Actions/WarningModal.js index 38d04f210..711a2bd2d 100644 --- a/src/components/Accounts/Actions/WarningModal.js +++ b/src/components/Accounts/Actions/WarningModal.js @@ -5,6 +5,7 @@ import { injectIntl, } from 'react-intl'; +import { stripesConnect } from "@folio/stripes/core"; import { Button, Col, @@ -25,6 +26,7 @@ import { } from '../../util'; import css from './modal.css'; +import { localizeCurrencyAmount } from "../../util/localizeCurrencyAmount"; class WarningModal extends React.Component { static propTypes = { @@ -127,6 +129,7 @@ class WarningModal extends React.Component { }; getAccountsFormatter() { + const { intl, stripes } = this.props; const { checkedAccounts } = this.state; return { ' ': a => ( @@ -138,7 +141,7 @@ class WarningModal extends React.Component { ), 'Alert details': this.getAlertDetailsFormatter, 'Fee/Fine type': a => a.feeFineType || '', - 'Remaining': a => parseFloat(a.remaining).toFixed(2) || '0.00', + 'Remaining': a => localizeCurrencyAmount(a.remaining, stripes.currency, intl), 'Payment Status': a => (a.paymentStatus || {}).name || '-', 'Item': a => a.title || '', }; @@ -290,4 +293,4 @@ class WarningModal extends React.Component { } } -export default injectIntl(WarningModal); +export default injectIntl(stripesConnect(WarningModal)); diff --git a/src/components/Accounts/ChargeFeeFine/ChargeFeeFine.js b/src/components/Accounts/ChargeFeeFine/ChargeFeeFine.js index 3ae778c6b..4093da56b 100644 --- a/src/components/Accounts/ChargeFeeFine/ChargeFeeFine.js +++ b/src/components/Accounts/ChargeFeeFine/ChargeFeeFine.js @@ -16,7 +16,7 @@ import { effectiveCallNumber } from '@folio/stripes/util'; import ChargeForm from './ChargeForm'; import ItemLookup from './ItemLookup'; import ActionModal from '../Actions/ActionModal'; -import { getFullName } from '../../util'; +import { formatCurrencyAmount, getFullName } from '../../util'; import { loadServicePoints, deleteOptionalActionFields, @@ -194,8 +194,8 @@ class ChargeFeeFine extends React.Component { accountId: id, dateAction, userId: user.id, - amountAction: parseFloat(amount || 0).toFixed(2), - balance: parseFloat(balance || 0).toFixed(2), + amountAction: formatCurrencyAmount(amount), + balance: formatCurrencyAmount(balance), transactionInformation: transaction || '', comments: comment, notify, @@ -274,7 +274,7 @@ class ChargeFeeFine extends React.Component { const { intl: { formatNumber }, } = this.props; - const amount = parseFloat(a.amount).toFixed(2); + const amount = formatCurrencyAmount(a.amount); const paymentName = (a.paymentStatus.name).toLowerCase(); const fullName = getFullName(this.props.user); const { feeFineType } = a; @@ -337,7 +337,7 @@ class ChargeFeeFine extends React.Component { comment = `${comment} \n ${tagPatron} : ${values.patronInfo}`; } - this.type.remaining = parseFloat(this.type.amount - values.amount).toFixed(2); + this.type.remaining = formatCurrencyAmount(this.type.amount - values.amount); let paymentStatus = _.capitalize(formatMessage({ id: 'ui-users.accounts.actions.warning.paymentAction' })); if (this.type.remaining === '0.00') { @@ -382,7 +382,7 @@ class ChargeFeeFine extends React.Component { const { intl: { formatMessage } } = this.props; const values = this.state.values || {}; const type = this.type || {}; - const amount = parseFloat(values.amount || 0).toFixed(2); + const amount = formatCurrencyAmount(values.amount); let paymentStatus = formatMessage({ id: 'ui-users.accounts.actions.warning.paymentAction' }); paymentStatus = `${(parseFloat(values.amount) !== parseFloat(type.amount) ? formatMessage({ id: 'ui-users.accounts.status.partially' }) diff --git a/src/components/Accounts/ChargeFeeFine/ChargeForm.js b/src/components/Accounts/ChargeFeeFine/ChargeForm.js index 8d995bc82..c9be677bb 100644 --- a/src/components/Accounts/ChargeFeeFine/ChargeForm.js +++ b/src/components/Accounts/ChargeFeeFine/ChargeForm.js @@ -23,6 +23,7 @@ import UserInfo from './UserInfo'; import FeeFineInfo from './FeeFineInfo'; import ItemInfo from './ItemInfo'; import { SHARED_OWNER } from '../../../constants'; +import {formatCurrencyAmount} from "../../util"; function showValidationErrors({ feeFineId, ownerId, amount }) { const errors = {}; @@ -109,7 +110,7 @@ class ChargeForm extends React.Component { const feefine = feefines.find(f => f.id === feeFineId) || {}; const owner = this.props.owners.find(o => o.id === feefine.ownerId) || {}; - const defaultAmount = parseFloat(feefine.defaultAmount || 0).toFixed(2); + const defaultAmount = formatCurrencyAmount(feefine.defaultAmount); let showNotify = false; if (feefine?.chargeNoticeId || owner?.defaultChargeNoticeId) { showNotify = true; diff --git a/src/components/Accounts/ChargeFeeFine/FeeFineInfo.js b/src/components/Accounts/ChargeFeeFine/FeeFineInfo.js index 3af87f32f..2356ab80e 100644 --- a/src/components/Accounts/ChargeFeeFine/FeeFineInfo.js +++ b/src/components/Accounts/ChargeFeeFine/FeeFineInfo.js @@ -10,6 +10,8 @@ import { Select, } from '@folio/stripes/components'; +import { formatCurrencyAmount } from "../../util"; + class FeeFineInfo extends React.Component { static propTypes = { feefineList: PropTypes.arrayOf(PropTypes.object), @@ -40,7 +42,7 @@ class FeeFineInfo extends React.Component { onBlurAmount = (e) => { const { form: { change } } = this.props; - change('amount', parseFloat(e.target.value || 0).toFixed(2)); + change('amount', formatCurrencyAmount(e.target.value)); } render() { diff --git a/src/components/Accounts/Menu.js b/src/components/Accounts/Menu.js index d38022f1a..63b1dadba 100644 --- a/src/components/Accounts/Menu.js +++ b/src/components/Accounts/Menu.js @@ -15,6 +15,7 @@ import { import { refundClaimReturned } from '../../constants'; import css from './Menu.css'; +import { useLocalizeCurrency } from "../../hooks/useLocalizedCurrency/useLocalizeCurrency"; const Menu = (props) => { const { @@ -26,24 +27,21 @@ const Menu = (props) => { accounts } = props; + const { localizeCurrency } = useLocalizeCurrency(); + let balanceOutstanding = 0; let balanceSuspended = 0; if (params.accountstatus !== 'closed') { accounts.forEach((a) => { if (a.paymentStatus.name === refundClaimReturned.PAYMENT_STATUS) { - balanceSuspended += (parseFloat(a.remaining) * 100); + balanceSuspended += (parseFloat(a.remaining)); } else { - balanceOutstanding += (parseFloat(a.remaining) * 100); + balanceOutstanding += (parseFloat(a.remaining)); } }); } - balanceOutstanding /= 100; - balanceSuspended /= 100; - const suspended = parseFloat(balanceSuspended).toFixed(2); - const outstanding = parseFloat(balanceOutstanding).toFixed(2); - const showSelected = (selected !== 0 && selected !== parseFloat(0).toFixed(2)) - && outstanding > parseFloat(0).toFixed(2); + const showSelected = selected !== 0 && balanceOutstanding > 0; const type = ; @@ -67,19 +65,19 @@ const Menu = (props) => {
 |  {showSelected &&  |  } diff --git a/src/components/Accounts/ViewFeesFines/ViewFeesFines.js b/src/components/Accounts/ViewFeesFines/ViewFeesFines.js index b36874ec8..879cd4a7f 100644 --- a/src/components/Accounts/ViewFeesFines/ViewFeesFines.js +++ b/src/components/Accounts/ViewFeesFines/ViewFeesFines.js @@ -2,9 +2,7 @@ import _ from 'lodash'; import React from 'react'; import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; -import { - FormattedMessage, -} from 'react-intl'; +import { FormattedMessage, injectIntl } from 'react-intl'; import { Button, @@ -18,6 +16,7 @@ import { FormattedDate, FormattedTime, } from '@folio/stripes/components'; +import { stripesConnect } from "@folio/stripes/core"; import { itemStatuses } from '../../../constants'; import { @@ -29,6 +28,7 @@ import { isCancelAllowed, } from '../accountFunctions'; import css from './ViewFeesFines.css'; +import { localizeCurrencyAmount } from "../../util/localizeCurrencyAmount"; class ViewFeesFines extends React.Component { static propTypes = { @@ -111,7 +111,7 @@ class ViewFeesFines extends React.Component { const selected = checkedAccounts.reduce((s, { remaining }) => { return s + parseFloat(remaining); }, 0); - this.props.onChangeSelected(parseFloat(selected).toFixed(2), checkedAccounts); + this.props.onChangeSelected(selected, checkedAccounts); } if (!_.isEqual(props.selectedAccounts, nextProps.selectedAccounts)) { @@ -216,7 +216,7 @@ class ViewFeesFines extends React.Component { } getAccountsFormatter() { - const accounts = this.props.selectedAccounts; + const { stripes, selectedAccounts, intl } = this.props; return { // Changed onChange to onClick to make sure the click event is correctly propagated // and the checkbox actually changes visually. @@ -226,7 +226,7 @@ class ViewFeesFines extends React.Component { // which currently causes the checkbox to not be visually updated. ' ': f => ( a.id === f.id) !== -1)} + checked={(selectedAccounts.findIndex(a => a.id === f.id) !== -1)} onClick={e => this.toggleItem(e, f)} type="checkbox" /> @@ -234,8 +234,8 @@ class ViewFeesFines extends React.Component { 'metadata.createdDate': f => (f.metadata ? : '-'), 'metadata.updatedDate': f => (f.metadata && f.metadata.createdDate !== f.metadata.updatedDate ? : '-'), 'feeFineType': f => (f.feeFineType ? this.showComments(f) : '-'), - 'amount': f => (f.amount ? parseFloat(f.amount).toFixed(2) : '-'), - 'remaining': f => parseFloat(f.remaining).toFixed(2) || '0.00', + 'amount': f => (f.amount ? localizeCurrencyAmount(f.amount, stripes.currency, intl) : '-'), + 'remaining': f => localizeCurrencyAmount(f.amount || 0, stripes.currency, intl), 'paymentStatus.name': f => (f.paymentStatus || {}).name || '-', 'feeFineOwner': f => (f.feeFineOwner ? f.feeFineOwner : '-'), 'title': item => this.formatTitle(item), @@ -287,7 +287,7 @@ class ViewFeesFines extends React.Component { someIsClaimReturnedItem = (someIsClaimReturnedItem || (loan.item && loan.item.status && loan.item.status.name && loan.item.status.name === itemStatuses.CLAIMED_RETURNED)); }); selected /= 100; - this.props.onChangeSelected(parseFloat(selected).toFixed(2), values); + this.props.onChangeSelected(selected, values); const open = selected > 0; const closed = values.length > 0; @@ -501,4 +501,4 @@ class ViewFeesFines extends React.Component { } } -export default ViewFeesFines; +export default injectIntl(stripesConnect(ViewFeesFines)); diff --git a/src/components/Accounts/accountFunctions.test.js b/src/components/Accounts/accountFunctions.test.js index e856c1ecd..a9173182e 100644 --- a/src/components/Accounts/accountFunctions.test.js +++ b/src/components/Accounts/accountFunctions.test.js @@ -3,7 +3,6 @@ import account from 'fixtures/account'; import { count, calculateSelectedAmount, - calculateRemainingAmount, loadServicePoints, accountRefundInfo, calculateTotalPaymentAmount, @@ -32,10 +31,6 @@ describe('Account Functions', () => { const data = calculateSelectedAmount([account], true); expect(data).toBe('90.00'); }); - it('If calculateRemainingAmount works', async () => { - const data = calculateRemainingAmount('100.00', '200.00', '500.00', 'refund'); - expect(data).toBe('400.00'); - }); it('If loadServicePoints works', async () => { const owners = [ { diff --git a/src/components/AssignedUsers/AssignUsers.js b/src/components/AssignedUsers/AssignUsers.js index d3438260d..6891a06f1 100644 --- a/src/components/AssignedUsers/AssignUsers.js +++ b/src/components/AssignedUsers/AssignUsers.js @@ -5,7 +5,7 @@ import { keyBy } from 'lodash'; import { Pluggable, - withStripes, + stripesConnect, } from '@folio/stripes/core'; import styles from './AssignUsers.css'; @@ -41,4 +41,4 @@ AssignUsers.propTypes = { tenantId: PropTypes.string, }; -export default withStripes(AssignUsers); +export default stripesConnect(AssignUsers); diff --git a/src/components/HelperApp/HelperApp.js b/src/components/HelperApp/HelperApp.js index 9bc554242..874f7c63a 100644 --- a/src/components/HelperApp/HelperApp.js +++ b/src/components/HelperApp/HelperApp.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router'; import { Tags } from '@folio/stripes/smart-components'; -import { withStripes, stripesConnect } from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; class HelperApp extends React.Component { static propTypes = { @@ -42,4 +42,4 @@ class HelperApp extends React.Component { } } -export default withRouter(withStripes(HelperApp)); +export default withRouter(stripesConnect(HelperApp)); diff --git a/src/components/Loans/ClosedLoans/ClosedLoans.js b/src/components/Loans/ClosedLoans/ClosedLoans.js index dd039f1a7..d651c123a 100644 --- a/src/components/Loans/ClosedLoans/ClosedLoans.js +++ b/src/components/Loans/ClosedLoans/ClosedLoans.js @@ -36,6 +36,7 @@ import Label from '../../Label'; import ErrorModal from '../../ErrorModal'; import css from './ClosedLoans.css'; +import { localizeCurrencyAmount } from "../../util/localizeCurrencyAmount"; class ClosedLoans extends React.Component { static propTypes = { @@ -163,13 +164,14 @@ class ClosedLoans extends React.Component { }; getFeeFine(loan) { + const { intl, stripes } = this.props; const accounts = _.get(this.props.resources, ['loanAccount', 'records'], []); const accountsLoan = accounts.filter(a => a.loanId === loan.id) || []; let remaining = 0; accountsLoan.forEach(a => { remaining += parseFloat(a.amount); }); - return (remaining === 0) ? '-' : remaining.toFixed(2); + return (remaining === 0) ? '-' : localizeCurrencyAmount(remaining, stripes.currency, intl); } getContributorslist(loan) { // eslint-disable-line class-methods-use-this diff --git a/src/components/UserDetailSections/UserAccounts/UserAccounts.js b/src/components/UserDetailSections/UserAccounts/UserAccounts.js index c506d7cab..ed545be13 100644 --- a/src/components/UserDetailSections/UserAccounts/UserAccounts.js +++ b/src/components/UserDetailSections/UserAccounts/UserAccounts.js @@ -22,6 +22,7 @@ import { refundClaimReturned, } from '../../../constants'; import { isDcbUser } from '../../util'; +import { useLocalizeCurrency } from "../../../hooks/useLocalizedCurrency/useLocalizeCurrency"; /** @@ -52,6 +53,7 @@ const UserAccounts = ({ totalRefunded: 0.00, }); const stripes = useStripes(); + const { localizeCurrency } = useLocalizeCurrency(); const accountsLoaded = !isPending; const { openAccountsCount, @@ -88,9 +90,9 @@ const UserAccounts = ({ closedAccountsCount: closed.length, claimAccountsCount: claim.length, refundedAccountsCount: refunded.length, - total: parseFloat(openTotal).toFixed(2), - totalClaim: parseFloat(claimTotal).toFixed(2), - totalRefunded: parseFloat(refundedTotal).toFixed(2), + total: parseFloat(openTotal), + totalClaim: parseFloat(claimTotal), + totalRefunded: parseFloat(refundedTotal), }); }, [records, resources]); @@ -118,7 +120,7 @@ const UserAccounts = ({ {' '} {' '} - {item.id === 'clickable-viewcurrentaccounts' && } + {item.id === 'clickable-viewcurrentaccounts' && } )} items={[ { @@ -150,7 +152,7 @@ const UserAccounts = ({
{' '} - +
)} items={[ diff --git a/src/components/util/localizeCurrencyAmount.js b/src/components/util/localizeCurrencyAmount.js new file mode 100644 index 000000000..2ca63aab9 --- /dev/null +++ b/src/components/util/localizeCurrencyAmount.js @@ -0,0 +1,3 @@ +export const localizeCurrencyAmount = (value, currency, intl) => { + return intl.formatNumber(value, { style: 'currency', currency }); +}; diff --git a/src/hooks/useLocalizedCurrency/useLocalizeCurrency.js b/src/hooks/useLocalizedCurrency/useLocalizeCurrency.js new file mode 100644 index 000000000..88e4bbb54 --- /dev/null +++ b/src/hooks/useLocalizedCurrency/useLocalizeCurrency.js @@ -0,0 +1,12 @@ +import { useIntl } from "react-intl"; +import { useStripes } from "@folio/stripes/core"; +import { localizeCurrencyAmount } from "../../components/util/localizeCurrencyAmount"; + +export const useLocalizeCurrency = () => { + const intl = useIntl(); + const stripes = useStripes(); + + return { + localizeCurrency: (value) => localizeCurrencyAmount(value, stripes.currency, intl) + }; +} diff --git a/src/routes/ChargeFeesFinesContainer.js b/src/routes/ChargeFeesFinesContainer.js index cbc63dedd..e2f35a505 100644 --- a/src/routes/ChargeFeesFinesContainer.js +++ b/src/routes/ChargeFeesFinesContainer.js @@ -1,10 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - stripesConnect, - withStripes, -} from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; import { ChargeFeeFine } from '../components/Accounts'; import CurrentUserServicePointAbsenteeErrorModal from '../components/CurrentUserServicePointAbsenteeErrorModal'; @@ -212,4 +209,4 @@ class ChargeFeesFinesContainer extends React.Component { } } -export default stripesConnect(withStripes(ChargeFeesFinesContainer)); +export default stripesConnect(ChargeFeesFinesContainer); diff --git a/src/settings/AddressTypesSettings.js b/src/settings/AddressTypesSettings.js index 9f9c41d40..7e8d7e067 100644 --- a/src/settings/AddressTypesSettings.js +++ b/src/settings/AddressTypesSettings.js @@ -5,7 +5,7 @@ import { injectIntl, } from 'react-intl'; import { ControlledVocab } from '@folio/stripes/smart-components'; -import { withStripes } from '@folio/stripes/core'; +import {stripesConnect } from '@folio/stripes/core'; class AddressTypesSettings extends React.Component { static propTypes = { @@ -57,4 +57,4 @@ class AddressTypesSettings extends React.Component { } } -export default injectIntl(withStripes(AddressTypesSettings)); +export default injectIntl(stripesConnect(AddressTypesSettings)); diff --git a/src/settings/ConditionsSettings.js b/src/settings/ConditionsSettings.js index 41d4130be..3992b13d4 100644 --- a/src/settings/ConditionsSettings.js +++ b/src/settings/ConditionsSettings.js @@ -5,7 +5,7 @@ import { injectIntl, } from 'react-intl'; -import { stripesConnect, withStripes } from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; import { Settings } from '@folio/stripes/smart-components'; import Conditions from './patronBlocks/Conditions/Conditions'; @@ -87,4 +87,4 @@ class ConditionsSettings extends Component { } } -export default injectIntl(withStripes(stripesConnect(ConditionsSettings))); +export default injectIntl(stripesConnect(ConditionsSettings)); diff --git a/src/settings/FeeFineSettings.js b/src/settings/FeeFineSettings.js index 65ab226d5..b2a2022bf 100644 --- a/src/settings/FeeFineSettings.js +++ b/src/settings/FeeFineSettings.js @@ -12,7 +12,7 @@ import { NoValue, } from '@folio/stripes/components'; import { ControlledVocab } from '@folio/stripes/smart-components'; -import { IfPermission, stripesConnect, withStripes } from '@folio/stripes/core'; +import { IfPermission, stripesConnect } from '@folio/stripes/core'; import { validate } from '../components/util'; import { @@ -25,6 +25,7 @@ import { MAX_RECORDS, SHARED_OWNER, } from '../constants'; +import { localizeCurrencyAmount } from "../components/util/localizeCurrencyAmount"; const columnMapping = { feeFineType: ( @@ -254,10 +255,10 @@ class FeeFineSettings extends React.Component { } render() { - const { intl: { formatMessage }, stripes } = this.props; + const { intl, stripes } = this.props; const { owners, templates, ownerId } = this.state; const filterOwners = this.getOwners(); - const label = formatMessage({ id: 'ui-users.feefines.singular' }); + const label = intl.formatMessage({ id: 'ui-users.feefines.singular' }); const hasEditPerm = stripes.hasPerm('feefines.item.put'); const hasDeletePerm = stripes.hasPerm('feefines.item.delete'); const hasCreatePerm = stripes.hasPerm('feefines.item.post'); @@ -289,7 +290,7 @@ class FeeFineSettings extends React.Component { }; const formatter = { - 'defaultAmount': (value) => (value.defaultAmount ? parseFloat(value.defaultAmount).toFixed(2) : ), + 'defaultAmount': (value) => (value.defaultAmount ? localizeCurrencyAmount(value.defaultAmount, stripes.currency, intl) : ), 'chargeNoticeId': ({ chargeNoticeId }) => this.getNotice(chargeNoticeId, 'Charge'), 'actionNoticeId': ({ actionNoticeId }) => this.getNotice(actionNoticeId, 'Action'), }; @@ -337,7 +338,7 @@ class FeeFineSettings extends React.Component { formatter={formatter} hiddenFields={['lastUpdated', 'numberOfObjects']} id="settings-feefines" - label={formatMessage({ id: 'ui-users.feefines.title' })} + label={intl.formatMessage({ id: 'ui-users.feefines.title' })} labelSingular={label} nameKey="feefine" objectLabel="" @@ -361,4 +362,4 @@ class FeeFineSettings extends React.Component { } } -export default injectIntl(withStripes(stripesConnect(FeeFineSettings))); +export default injectIntl(stripesConnect(FeeFineSettings)); diff --git a/src/settings/LimitsSettings.js b/src/settings/LimitsSettings.js index 599f91177..33db623da 100644 --- a/src/settings/LimitsSettings.js +++ b/src/settings/LimitsSettings.js @@ -8,10 +8,7 @@ import { isEmpty, } from 'lodash'; -import { - stripesConnect, - withStripes, -} from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; import { Settings } from '@folio/stripes/smart-components'; import Limits from './patronBlocks/Limits/Limits'; @@ -143,4 +140,4 @@ class LimitsSettings extends Component { } } -export default injectIntl(withStripes(stripesConnect(LimitsSettings))); +export default injectIntl(stripesConnect(LimitsSettings)); diff --git a/src/settings/OwnerSettings.js b/src/settings/OwnerSettings.js index 0959c9e7a..a36dd3588 100644 --- a/src/settings/OwnerSettings.js +++ b/src/settings/OwnerSettings.js @@ -11,7 +11,7 @@ import { Label, } from '@folio/stripes/components'; import { ControlledVocab } from '@folio/stripes/smart-components'; -import { stripesConnect, withStripes } from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; import { validate } from '../components/util'; import { SHARED_OWNER } from '../constants'; @@ -177,4 +177,4 @@ class OwnerSettings extends React.Component { } } -export default injectIntl(withStripes(stripesConnect(OwnerSettings))); +export default injectIntl(stripesConnect(OwnerSettings)); diff --git a/src/settings/PatronGroupsSettings.js b/src/settings/PatronGroupsSettings.js index 6eb32ee2f..f0e70e3d5 100644 --- a/src/settings/PatronGroupsSettings.js +++ b/src/settings/PatronGroupsSettings.js @@ -5,7 +5,7 @@ import { injectIntl, } from 'react-intl'; import { ControlledVocab } from '@folio/stripes/smart-components'; -import { withStripes } from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; import { getSourceSuppressor } from '@folio/stripes/util'; import { RECORD_SOURCE } from '../constants'; @@ -85,4 +85,4 @@ class PatronGroupsSettings extends React.Component { } } -export default injectIntl(withStripes(PatronGroupsSettings)); +export default injectIntl(stripesConnect(PatronGroupsSettings)); diff --git a/src/settings/PaymentSettings.js b/src/settings/PaymentSettings.js index ecd743a27..3bd9d6960 100644 --- a/src/settings/PaymentSettings.js +++ b/src/settings/PaymentSettings.js @@ -8,7 +8,7 @@ import { Label, } from '@folio/stripes/components'; import { ControlledVocab } from '@folio/stripes/smart-components'; -import { stripesConnect, withStripes } from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; import { validate } from '../components/util'; import { Owners } from './FeeFinesTable'; @@ -114,4 +114,4 @@ class PaymentSettings extends React.Component { } } -export default injectIntl(withStripes(stripesConnect(PaymentSettings))); +export default injectIntl(stripesConnect(PaymentSettings)); diff --git a/src/settings/ProfilePictureSettings.js b/src/settings/ProfilePictureSettings.js index c27257b1f..685d65f8d 100644 --- a/src/settings/ProfilePictureSettings.js +++ b/src/settings/ProfilePictureSettings.js @@ -8,7 +8,7 @@ import { } from '@folio/stripes/components'; import { Field } from 'react-final-form'; import { ConfigManager } from '@folio/stripes/smart-components'; -import { withStripes } from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; class ProfilePictureSettings extends React.Component { static propTypes = { @@ -53,4 +53,4 @@ class ProfilePictureSettings extends React.Component { } } -export default withStripes(ProfilePictureSettings); +export default stripesConnect(ProfilePictureSettings); diff --git a/src/settings/RefundReasonsSettings.js b/src/settings/RefundReasonsSettings.js index c9a11b6de..a60465e7f 100644 --- a/src/settings/RefundReasonsSettings.js +++ b/src/settings/RefundReasonsSettings.js @@ -8,7 +8,7 @@ import { Label, } from '@folio/stripes/components'; import { ControlledVocab } from '@folio/stripes/smart-components'; -import { withStripes } from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; import { validate } from '../components/util'; const columnMapping = { @@ -63,4 +63,4 @@ class RefundReasonsSettings extends React.Component { } } -export default injectIntl(withStripes(RefundReasonsSettings)); +export default injectIntl(stripesConnect(RefundReasonsSettings)); diff --git a/src/settings/TransferAccountsSettings.js b/src/settings/TransferAccountsSettings.js index c9e4d83d8..f62073cba 100644 --- a/src/settings/TransferAccountsSettings.js +++ b/src/settings/TransferAccountsSettings.js @@ -8,7 +8,7 @@ import { Label, } from '@folio/stripes/components'; import { ControlledVocab } from '@folio/stripes/smart-components'; -import { stripesConnect, withStripes } from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; import { validate } from '../components/util'; import { Owners } from './FeeFinesTable'; @@ -118,4 +118,4 @@ class TransferAccountsSettings extends React.Component { } } -export default injectIntl(withStripes(stripesConnect(TransferAccountsSettings))); +export default injectIntl(stripesConnect(TransferAccountsSettings)); diff --git a/src/settings/WaiveSettings.js b/src/settings/WaiveSettings.js index 74f68c926..23808be5a 100644 --- a/src/settings/WaiveSettings.js +++ b/src/settings/WaiveSettings.js @@ -8,7 +8,7 @@ import { Label, } from '@folio/stripes/components'; import { ControlledVocab } from '@folio/stripes/smart-components'; -import { withStripes } from '@folio/stripes/core'; +import { stripesConnect } from '@folio/stripes/core'; import { validate } from '../components/util'; const columnMapping = { @@ -67,4 +67,4 @@ class WaiveSettings extends React.Component { } } -export default injectIntl(withStripes(WaiveSettings)); +export default injectIntl(stripesConnect(WaiveSettings)); diff --git a/src/views/AccountDetails/AccountDetails.js b/src/views/AccountDetails/AccountDetails.js index 20097be8e..3635ef7c2 100644 --- a/src/views/AccountDetails/AccountDetails.js +++ b/src/views/AccountDetails/AccountDetails.js @@ -22,6 +22,7 @@ import { import { IfPermission, AppIcon, + stripesConnect, } from '@folio/stripes/core'; import Actions from '../../components/Accounts/Actions/FeeFineActions'; @@ -29,7 +30,6 @@ import { calculateSortParams, getFullName, formatActionDescription, - formatCurrencyAmount, getServicePointOfCurrentAction, isRefundAllowed, } from '../../components/util'; @@ -45,6 +45,7 @@ import { } from '../../constants'; import css from './AccountDetails.css'; +import {localizeCurrencyAmount} from "../../components/util/localizeCurrencyAmount"; const columnWidths = { date: 100, @@ -421,6 +422,7 @@ feeFineActions patronGroup: patron, resources, stripes, + intl, match: { params }, user, itemDetails, @@ -480,8 +482,8 @@ feeFineActions // Action: aa => loanActionMap[la.action], date: action => , action: action => formatActionDescription(action), - amount: action => (action.amountAction > 0 ? formatCurrencyAmount(action.amountAction) : '-'), - balance: action => (action.balance > 0 ? formatCurrencyAmount(action.balance) : '-'), + amount: action => (action.amountAction > 0 ? localizeCurrencyAmount(action.amountAction, stripes.currency, intl) : '-'), + balance: action => (action.balance > 0 ? localizeCurrencyAmount(action.balance, stripes.currency, intl) : '-'), transactioninfo: action => action.transactionInformation || '-', created: action => getServicePointOfCurrentAction(action, servicePoints), source: action => action.source, @@ -493,7 +495,7 @@ feeFineActions const actions = this.state.data || []; const actionsSort = _.orderBy(actions, [this.sortMap[sortOrder[0]], this.sortMap[sortOrder[1]]], sortDirection); - const amount = account.amount ? formatCurrencyAmount(account.amount) : '-'; + const amount = account.amount ? localizeCurrencyAmount(account.amount, stripes.currency, intl) : '-'; const loanId = account.loanId || ''; const isAccountId = actions[0] && actions[0].accountId === account.id; @@ -606,7 +608,7 @@ feeFineActions } - value={formatCurrencyAmount(this.state.remaining)} + value={localizeCurrencyAmount(this.state.remaining || 0, stripes.currency, intl)} />  | 
)} diff --git a/src/views/LoanDetails/LoanDetails.js b/src/views/LoanDetails/LoanDetails.js index bf5af455c..be661dd61 100644 --- a/src/views/LoanDetails/LoanDetails.js +++ b/src/views/LoanDetails/LoanDetails.js @@ -57,6 +57,7 @@ import loanActionMap from '../../components/data/static/loanActionMap'; import LoanProxyDetails from './LoanProxyDetails'; import css from './LoanDetails.css'; +import { localizeCurrencyAmount } from "../../components/util/localizeCurrencyAmount"; function formatLoanAction(la, loanActionsWithUser) { @@ -205,14 +206,15 @@ class LoanDetails extends React.Component { } viewFeeFine() { - const { stripes, loanAccountActions, loan } = this.props; + const { stripes, loanAccountActions, loan, intl } = this.props; const total = loanAccountActions.reduce((acc, { amount }) => (acc + parseFloat(amount)), 0); const suspendedAction = loanAccountActions.filter(a => a?.paymentStatus?.name === refundClaimReturned.PAYMENT_STATUS) || []; const suspendedMessage = (suspendedAction.length > 0) ? : ''; if (total === 0) return '-'; - const value = parseFloat(total).toFixed(2); + const value = localizeCurrencyAmount(total, stripes.currency, intl); + const valueDisplay = stripes.hasPerm('ui-users.feesfines.view') ? - : - value; + : value; return ( <> diff --git a/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/ActualCostDetailsModal/ActualCostDetailsModal.js b/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/ActualCostDetailsModal/ActualCostDetailsModal.js index 2f23ba393..52adfce34 100644 --- a/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/ActualCostDetailsModal/ActualCostDetailsModal.js +++ b/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/ActualCostDetailsModal/ActualCostDetailsModal.js @@ -22,10 +22,8 @@ import { ACTUAL_COST_DETAILS_MODAL_DEFAULT, LOST_ITEM_STATUSES, } from '../../../../../constants'; +import { useLocalizeCurrency } from "../../../../../../../hooks/useLocalizedCurrency/useLocalizeCurrency"; -export const getActualCostToBillViewValue = (value = 0) => ( - value.toFixed(2) -); const ActualCostDetailsModal = ({ actualCostDetailsModal: { @@ -42,6 +40,7 @@ const ActualCostDetailsModal = ({ setActualCost, setActualCostDetailsModal, }) => { + const { localizeCurrency } = useLocalizeCurrency(); const patronName = getPatronName(actualCostRecord); const instanceTitle = get(actualCostRecord, ACTUAL_COST_RECORD_FIELD_PATH[ACTUAL_COST_RECORD_FIELD_NAME.INSTANCE_TITLE], DEFAULT_VALUE); const materialType = get(actualCostRecord, ACTUAL_COST_RECORD_FIELD_PATH[ACTUAL_COST_RECORD_FIELD_NAME.MATERIAL_TYPE], DEFAULT_VALUE); @@ -79,7 +78,7 @@ const ActualCostDetailsModal = ({ ? { - describe('getActualCostToBillViewValue', () => { - it('should return 0 when value absent', () => { - expect(getActualCostToBillViewValue()).toEqual('0.00'); - }); - - it('should return decimal value with 2 digits when digit part absent', () => { - expect(getActualCostToBillViewValue(0.1)).toEqual('0.10'); - }); - - it('should return decimal value with 2 digits when digit part more than 2 digits', () => { - expect(getActualCostToBillViewValue(0.101)).toEqual('0.10'); - }); - }); - describe('when the record has "Cancelled" status', () => { beforeEach(() => { render( diff --git a/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/ActualCostModal/ActualCostModal.js b/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/ActualCostModal/ActualCostModal.js index e32dd2d0e..a15135f6e 100644 --- a/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/ActualCostModal/ActualCostModal.js +++ b/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/ActualCostModal/ActualCostModal.js @@ -29,6 +29,7 @@ import { ACTUAL_COST_DEFAULT, ACTUAL_COST_PROP_TYPES, } from '../../../../../constants'; +import { formatCurrencyAmount } from "../../../../../../../components/util"; const actualCostToBillField = 'actualCostToBill'; const BILLED_AMOUNT_MAX = 9999.99; @@ -118,7 +119,7 @@ const ActualCostModal = ({ ...actualCost, additionalInfo: { ...actualCost.additionalInfo, - actualCostToBill: parseFloat(actualCost.additionalInfo.actualCostToBill || 0).toFixed(2), + actualCostToBill: formatCurrencyAmount(actualCost.additionalInfo.actualCostToBill), }, }); }; diff --git a/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/RecordStatus/RecordStatus.js b/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/RecordStatus/RecordStatus.js index 52e5d7711..6cb8aaeb0 100644 --- a/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/RecordStatus/RecordStatus.js +++ b/src/views/LostItems/LostItemsListContainer/components/LostItemsList/components/RecordStatus/RecordStatus.js @@ -12,6 +12,7 @@ import { } from '../../../../../constants'; import { getRecordStatus } from '../../util'; import DateTimeFormatter from '../DateTimeFormatter'; +import { useLocalizeCurrency } from "../../../../../../../hooks/useLocalizedCurrency/useLocalizeCurrency"; export const getBilledAmount = (recordId, billedRecords) => billedRecords.find(record => record.id === recordId)?.billedAmount; @@ -20,6 +21,7 @@ const RecordStatus = ({ billedRecords, cancelledRecords, }) => { + const { localizeCurrency } = useLocalizeCurrency(); const status = get(actualCostRecord, ACTUAL_COST_RECORD_FIELD_PATH[ACTUAL_COST_RECORD_FIELD_NAME.STATUS]); const { isBilled, @@ -29,12 +31,12 @@ const RecordStatus = ({ if (status === LOST_ITEM_STATUSES.BILLED || isBilled) { const amount = isBilled ? getBilledAmount(actualCostRecord.id, billedRecords) : - get(actualCostRecord, ACTUAL_COST_RECORD_FIELD_PATH[ACTUAL_COST_RECORD_FIELD_NAME.BILLED_AMOUNT]).toFixed(2); + get(actualCostRecord, ACTUAL_COST_RECORD_FIELD_PATH[ACTUAL_COST_RECORD_FIELD_NAME.BILLED_AMOUNT]); return ( ); } @@ -53,7 +55,7 @@ const RecordStatus = ({ <>
- )} + )}c ); } diff --git a/test/jest/__mock__/intl.mock.js b/test/jest/__mock__/intl.mock.js index ba8fac8c8..d1d5ca764 100644 --- a/test/jest/__mock__/intl.mock.js +++ b/test/jest/__mock__/intl.mock.js @@ -3,6 +3,7 @@ import React from 'react'; jest.mock('react-intl', () => { const intl = { formatMessage: ({ id }) => id, + formatNumber: (value) => value, }; return {