diff --git a/source/components/PercentageField.js b/source/components/PercentageField.js index e85eabf7a..0418a49ff 100644 --- a/source/components/PercentageField.js +++ b/source/components/PercentageField.js @@ -1,4 +1,5 @@ import React, { useCallback, useState } from 'react' +import { formatPercentage } from 'Engine/format' import './PercentageField.css' export default function PercentageField({ onChange, value, debounce }) { @@ -25,7 +26,7 @@ export default function PercentageField({ onChange, value, debounce }) { max="1" /> - {Math.round(localValue * 100)} % + {formatPercentage(localValue)} % ) diff --git a/source/components/TargetSelection.js b/source/components/TargetSelection.js index 7f7f9c0ff..7b554646a 100644 --- a/source/components/TargetSelection.js +++ b/source/components/TargetSelection.js @@ -12,6 +12,7 @@ import emoji from 'react-easy-emoji' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import { Link } from 'react-router-dom' +import { formatCurrency } from 'Engine/format' import { analysisWithDefaultsSelector, useSituation, @@ -206,19 +207,6 @@ let Header = withSitePaths(({ target, sitePaths }) => { ) }) -export const formatCurrency = (value, language) => { - return value == null - ? '' - : Intl.NumberFormat(language, { - style: 'currency', - currency: 'EUR', - maximumFractionDigits: 0, - minimumFractionDigits: 0 - }) - .format(value) - .replace(/^€/, '€ ') -} - let TargetInputOrValue = ({ target, isActiveInput, isSmallTarget }) => { const { i18n } = useTranslation() const colors = useContext(ThemeColoursContext) diff --git a/source/components/Value.js b/source/components/Value.js index e147cfe37..6655faaa6 100644 --- a/source/components/Value.js +++ b/source/components/Value.js @@ -1,25 +1,7 @@ import { React, T } from 'Components' import { serialiseUnit } from 'Engine/units' -import { memoizeWith } from 'ramda' import { useTranslation } from 'react-i18next' - -const NumberFormat = memoizeWith( - (...args) => JSON.stringify(args), - Intl.NumberFormat -) - -export let numberFormatter = ({ - style, - maximumFractionDigits, - minimumFractionDigits = 0, - language -}) => value => - NumberFormat(language, { - style, - currency: 'EUR', - maximumFractionDigits, - minimumFractionDigits - }).format(value) +import { formatValue } from 'Engine/format' // let booleanTranslations = { true: '✅', false: '❌' } @@ -34,7 +16,7 @@ let style = customStyle => ` ${customStyle} ` -export default ({ +export default function Value({ nodeValue: value, unit, nilValueSymbol, @@ -43,10 +25,8 @@ export default ({ children, negative, customCSS = '' -}) => { - const { - i18n: { language } - } = useTranslation() +}) { + const { language } = useTranslation().i18n /* Either an entire rule object is passed, or just the right attributes and the value as a JSX child*/ let nodeValue = value === undefined ? children : value @@ -71,27 +51,14 @@ export default ({ nodeValue.nom ) : valueType === 'boolean' ? ( booleanTranslations[language][nodeValue] - ) : unitText === '€' ? ( - numberFormatter({ - style: 'currency', - maximumFractionDigits, - minimumFractionDigits, - language - })(nodeValue) - ) : unitText === '%' ? ( - numberFormatter({ style: 'percent', maximumFractionDigits: 3 })( - nodeValue - ) ) : ( - <> - {numberFormatter({ - style: 'decimal', - minimumFractionDigits, - maximumFractionDigits - })(nodeValue)} -   - {unitText} - + formatValue({ + minimumFractionDigits, + maximumFractionDigits, + language, + value: nodeValue, + unit: unitText + }) ) return nodeValue == undefined ? null : ( diff --git a/source/components/conversation/FormDecorator.js b/source/components/conversation/FormDecorator.js index 86dab9eab..5c8ff0ad0 100644 --- a/source/components/conversation/FormDecorator.js +++ b/source/components/conversation/FormDecorator.js @@ -1,6 +1,7 @@ import { updateSituation } from 'Actions/actions' import classNames from 'classnames' import Explicable from 'Components/conversation/Explicable' +import { getFormatersFromUnit } from 'Engine/format' import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { situationSelector } from 'Selectors/analyseSelectors' @@ -29,8 +30,7 @@ export const FormDecorator = formType => RenderField => dispatch(updateSituation(fieldName, normalize(value))) } - const format = x => (unit === '%' && x ? +(x * 100).toFixed(2) : x) - const normalize = x => (unit === '%' ? x / 100 : x) + const { format, normalize } = getFormatersFromUnit(unit) const value = format(situation[fieldName]) return ( diff --git a/source/components/ui/AnimatedTargetValue.js b/source/components/ui/AnimatedTargetValue.js index 8a90d9a64..5e447ca00 100644 --- a/source/components/ui/AnimatedTargetValue.js +++ b/source/components/ui/AnimatedTargetValue.js @@ -2,6 +2,7 @@ import React, { useRef } from 'react' import ReactCSSTransitionGroup from 'react-addons-css-transition-group' import { useTranslation } from 'react-i18next' +import { formatCurrency } from 'Engine/format' import './AnimatedTargetValue.css' type Props = { @@ -10,13 +11,7 @@ type Props = { function formatDifference(difference, language) { const prefix = difference > 0 ? '+' : '' - const formatedValue = Intl.NumberFormat(language, { - style: 'currency', - currency: 'EUR', - maximumFractionDigits: 0, - minimumFractionDigits: 0 - }).format(difference) - return prefix + formatedValue + return prefix + formatCurrency(difference, language) } export default function AnimatedTargetValue({ value, children }: Props) { diff --git a/source/engine/mecanismViews/Barème.js b/source/engine/mecanismViews/Barème.js index a618a14a8..9476cc5fe 100644 --- a/source/engine/mecanismViews/Barème.js +++ b/source/engine/mecanismViews/Barème.js @@ -1,6 +1,6 @@ import classNames from 'classnames' import { ShowValuesConsumer } from 'Components/rule/ShowValuesContext' -import { numberFormatter } from 'Components/Value' +import { numberFormatter } from 'Engine/format' import { trancheValue } from 'Engine/mecanisms/barème' import { inferUnit, serialiseUnit } from 'Engine/units' import { identity } from 'ramda' @@ -165,7 +165,7 @@ let Tranche = ({ {taux != null ? makeJsx(taux) : montant} {showValues && taux != null && ( - + )} diff --git a/source/engine/mecanismViews/BarèmeContinu.js b/source/engine/mecanismViews/BarèmeContinu.js index b92a9dc5f..e803e9c25 100644 --- a/source/engine/mecanismViews/BarèmeContinu.js +++ b/source/engine/mecanismViews/BarèmeContinu.js @@ -5,6 +5,7 @@ import { Trans } from 'react-i18next' import { BarèmeAttributes } from './Barème' import './Barème.css' import { Node } from './common' +import { formatPercentage } from 'Engine/format' let Comp = function Barème({ nodeValue, explanation, unit }) { return ( @@ -43,7 +44,7 @@ let Comp = function Barème({ nodeValue, explanation, unit }) { Votre taux :{' '} - {(100 * explanation.taux).toFixed(2)} % + {formatPercentage(explanation.taux)} % )} {explanation.returnRate && ( diff --git a/source/engine/parse.js b/source/engine/parse.js index 9d7265574..e71054080 100644 --- a/source/engine/parse.js +++ b/source/engine/parse.js @@ -44,6 +44,7 @@ import { mecanismSynchronisation } from './mecanisms' import { parseReferenceTransforms } from './parseReference' +import { formatValue } from 'Engine/format' export let parse = (rules, rule, parsedRules) => rawNode => { let onNodeType = cond([ @@ -159,7 +160,11 @@ export let parseObject = (rules, rule, parsedRules) => rawNode => { nodeValue: v.nodeValue, unit: v.unit, // eslint-disable-next-line - jsx: () => {v.nodeValue} + jsx: () => ( + + {formatValue({ unit: v.unit, value: v.nodeValue })} + + ) }) }, action = propOr(mecanismError, k, dispatch)