Skip to content

Commit

Permalink
front: refacto InputGroupSNCF
Browse files Browse the repository at this point in the history
  • Loading branch information
SharglutDev committed Mar 7, 2024
1 parent bb0c776 commit 70e08fe
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 311 deletions.
8 changes: 2 additions & 6 deletions front/src/common/BootstrapSNCF/InputGroupSNCF.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.input-group-sm {
.input-group {
button {
height: 1.875rem;
font-size: 0.875rem;
Expand All @@ -11,12 +11,8 @@
font-size: 0.875rem;
}
}
}
.input-group {
z-index: auto !important;
.btn-sm {
font-size: 0.8rem;
}

.dropdown .dropdown-menu[x-placement='bottom-end'] {
top: 2.3rem !important;
}
Expand Down
131 changes: 44 additions & 87 deletions front/src/common/BootstrapSNCF/InputGroupSNCF.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
import React, { type ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react';
import React, { type ChangeEvent, useCallback, useMemo, useState } from 'react';

import cx from 'classnames';
import { isNil } from 'lodash';
import nextId from 'react-id-generator';
import './InputGroupSNCF.scss';

import type { MultiUnit } from 'modules/rollingStock/types';
import { isFloat, stripDecimalDigits } from 'utils/numbers';

type Option = {
id: string;
label: string;
unit?: string;
};

export type InputGroupSNCFValue = { type?: string; unit: string; value?: string | number };
export type InputGroupSNCFValue<U> = { unit: U; value?: number };

type Props = {
// Generic allow us to custom the type of unit used in this component
type Props<U> = {
id: string;
label?: React.ReactElement | string;
options: Option[];
unit: string;
handleUnit: (type: InputGroupSNCFValue) => void;
orientation?: string;
placeholder?: string;
sm?: boolean;
value?: number | string;
typeValue?: string;
condensed?: boolean;
onChange: (type: InputGroupSNCFValue<U>) => void;
currentValue: {
unit: U;
value?: number;
};
isInvalid?: boolean;
errorMsg?: string;
min?: number;
max?: number;
step?: number | string;
textRight?: boolean;
disabled?: boolean;
disableUnitSelector?: boolean;
limitDecimal?: number;
inputDataTestId?: string;
};
Expand All @@ -44,69 +39,46 @@ const isNeedStripDecimalDigits = (inputValue: string, limit: number) => {
return !isNil(limit) && limit > 0 && inputValue !== '' && isFloat(eventValue);
};

export default function InputGroupSNCF({
export default function InputGroupSNCF<U extends string | MultiUnit>({
id,
label,
unit,
handleUnit,
onChange,
options,
orientation = 'left',
placeholder = '',
sm = false,
value,
typeValue = 'text',
condensed = false,
currentValue,
isInvalid = false,
errorMsg,
min,
max,
step,
textRight = false,
step = 'any',
disabled = false,
disableUnitSelector = false,
limitDecimal = 10,
inputDataTestId,
}: Props) {
}: Props<U>) {
const [isDropdownShown, setIsDropdownShown] = useState(false);
const [selectedUnit, setSelectedUnit] = useState<Option>({
id: options[0].id,
label: options[0].label,
unit: options[0].unit,
});

const textAlignmentClass = textRight ? 'right-alignment' : 'left-alignment';

useEffect(() => {
// Check if we can find the unit in the options id (allowances) or label (rolling stock editor)
const selectedOption = options?.find((option) => option.id === unit || option.label === unit);
setSelectedUnit({
label: selectedOption?.label || options[0].label,
id: selectedOption?.id || options[0].id,
unit: selectedOption?.unit || options[0].unit,
});
}, [unit, options]);

const handleOnChange = useCallback(
const handleValueChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const eventValue = Number(event.target.value);
const selectedUnitValue: InputGroupSNCFValue['value'] =
let newValue: InputGroupSNCFValue<U>['value'] =
limitDecimal && isNeedStripDecimalDigits(event.target.value, limitDecimal)
? stripDecimalDigits(eventValue, limitDecimal)
: event.target.value;
handleUnit({ type: selectedUnit.id, unit: selectedUnit.label, value: selectedUnitValue });
: eventValue;
if (event.target.value === '') newValue = undefined;
onChange({ unit: currentValue.unit, value: newValue });
},
[handleUnit, selectedUnit, limitDecimal]
[onChange, currentValue.unit, limitDecimal]
);

const inputValue = useMemo(() => {
const { value } = currentValue;
if (value !== undefined && !disabled) {
if (limitDecimal && isNeedStripDecimalDigits(value.toString(), limitDecimal)) {
return stripDecimalDigits(Number(value), limitDecimal);
}
return value;
}
return '';
}, [value, limitDecimal, disabled]);
}, [currentValue.value, limitDecimal, disabled]);

const inputField = (
<div
Expand All @@ -115,61 +87,49 @@ export default function InputGroupSNCF({
})}
>
<input
type={typeValue}
className={cx('form-control h-100', textAlignmentClass, {
'px-2': condensed,
})}
title={placeholder}
placeholder={placeholder}
onChange={handleOnChange}
type="number"
className="form-control h-100 px-2 text-right"
title={inputValue.toString()}
onChange={handleValueChange}
value={inputValue}
min={min}
max={max}
data-testid={inputDataTestId}
step={step}
data-testid={inputDataTestId}
disabled={disabled}
/>
<span className="form-control-state" />
</div>
);

const currentUnitLabel = useMemo(() => {
const currentUnit = options.find((option) => option.id === currentValue.unit);
return currentUnit?.label || currentValue.unit;
}, [options, currentValue.unit]);

return (
<>
{label && (
<label htmlFor={id} className="my-0 mr-2">
{label}
</label>
)}
<div
className={cx('input-group', {
'input-group-sm': sm,
})}
>
{orientation === 'right' && inputField}
<div
className={cx({
'input-group-prepend': orientation === 'left',
'input-group-append': orientation !== 'left',
})}
>
{' '}
<div className="input-group">
{inputField}
<div className="input-group-append">
<div className="btn-group dropdown">
<button
type="button"
className={cx('btn btn-secondary dropdown-toggle', {
'pr-2 pl-2': condensed,
})}
className="btn btn-secondary px-2 dropdown-toggle"
onClick={() => setIsDropdownShown(!isDropdownShown)}
aria-haspopup="true"
aria-expanded="false"
aria-controls={id}
disabled={disabled || disableUnitSelector}
disabled={disabled}
>
<span className={cx({ small: condensed })}>{selectedUnit.label}</span>
<span className="small">{currentUnitLabel}</span>
<i
className={cx(isDropdownShown ? 'icons-arrow-up' : 'icons-arrow-down', {
'ml-2': condensed,
})}
className={cx('ml-2', isDropdownShown ? 'icons-arrow-up' : 'icons-arrow-down')}
aria-hidden="true"
/>
</button>
Expand All @@ -183,13 +143,11 @@ export default function InputGroupSNCF({
>
{options.map((option) => (
<div
key={nextId()}
key={option.id}
onClick={() => {
setSelectedUnit(option);
handleUnit({
type: option.id,
unit: option.label,
value,
onChange({
unit: option.id as U,
value: currentValue.value,
});
setIsDropdownShown(false);
}}
Expand All @@ -211,7 +169,6 @@ export default function InputGroupSNCF({
</div>
</div>
</div>
{orientation === 'left' && inputField}
{isDropdownShown && (
// eslint-disable-next-line jsx-a11y/control-has-associated-label
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';

import cx from 'classnames';
import { floor, isEmpty, uniq } from 'lodash';
import { isEmpty, uniq } from 'lodash';
import { useTranslation } from 'react-i18next';

import { osrdEditoastApi } from 'common/api/osrdEditoastApi';
Expand Down Expand Up @@ -134,23 +134,23 @@ export default function RollingStockCardDetail({
<tr>
<td className="text-primary">{t('rollingResistanceA')}</td>
<td>
{floor(convertUnits('N', 'kN', rs.rolling_resistance.A), 2)}
{convertUnits('N', 'kN', rs.rolling_resistance.A, 2)}
<span className="small ml-1 text-muted">kN</span>
</td>
</tr>
<tr>
<td className="text-primary">{t('rollingResistanceB')}</td>
<td>
{/* The b resistance received is in N/(m/s) and should appear in N/(km/h) */}
{floor(convertUnits('N/(m/s)', 'kN/(km/h)', rs.rolling_resistance.B), 6)}
{convertUnits('N/(m/s)', 'kN/(km/h)', rs.rolling_resistance.B, 6)}
<span className="small ml-1 text-muted">kN/(km/h)</span>
</td>
</tr>
<tr>
<td className="text-primary">{t('rollingResistanceC')}</td>
<td title={rs.rolling_resistance.C.toString()}>
{/* The c resistance received is in N/(m/s)² and should appear in N/(km/h)² */}
{floor(convertUnits('N/(m/s)²', 'kN/(km/h)²', rs.rolling_resistance.C), 6)}
{convertUnits('N/(m/s)²', 'kN/(km/h)²', rs.rolling_resistance.C, 6)}
<span className="small ml-1 text-muted">kN/(km/h)²</span>
</td>
</tr>
Expand Down
Loading

0 comments on commit 70e08fe

Please sign in to comment.