diff --git a/src/webparts/requestPortal/forms/PaymentRequest/amounts.test.ts b/src/webparts/requestPortal/forms/PaymentRequest/amounts.test.ts index eb41ee4..7469e8f 100644 --- a/src/webparts/requestPortal/forms/PaymentRequest/amounts.test.ts +++ b/src/webparts/requestPortal/forms/PaymentRequest/amounts.test.ts @@ -1,4 +1,4 @@ -import { overLimitAmount, parseAmount, previousPaymentsTotal, recipientMatchesInvoice } from './amounts'; +import { formatAmountInput, overLimitAmount, parseAmount, previousPaymentsTotal, recipientMatchesInvoice } from './amounts'; describe('parseAmount', () => { it('понимает пробелы и запятую', () => { @@ -45,3 +45,22 @@ describe('recipientMatchesInvoice', () => { expect(recipientMatchesInvoice({ name: 'ТОО «Альфа»', bin: '1' }, { name: '', bin: '' })).toBe(true); }); }); + +describe('formatAmountInput', () => { + it('разбивает на разряды', () => { + expect(formatAmountInput('1234567')).toBe('1 234 567'); + expect(formatAmountInput('1 528 642')).toBe('1 528 642'); + }); + it('держит недописанную дробную часть и режет её до двух знаков', () => { + expect(formatAmountInput('1234,')).toBe('1 234,'); + expect(formatAmountInput('1234.567')).toBe('1 234,56'); + expect(formatAmountInput(',5')).toBe('0,5'); + }); + it('выкидывает мусор и лишние разделители', () => { + expect(formatAmountInput('12a3,4,5')).toBe('123,45'); + expect(formatAmountInput('')).toBe(''); + }); + it('согласован с parseAmount', () => { + expect(parseAmount(formatAmountInput('154100,5'))).toBe(154100.5); + }); +}); diff --git a/src/webparts/requestPortal/forms/PaymentRequest/amounts.ts b/src/webparts/requestPortal/forms/PaymentRequest/amounts.ts index fb48de2..5e6acc7 100644 --- a/src/webparts/requestPortal/forms/PaymentRequest/amounts.ts +++ b/src/webparts/requestPortal/forms/PaymentRequest/amounts.ts @@ -9,6 +9,22 @@ export function parseAmount(raw: string): number { return Number.isFinite(value) ? value : 0; } +/** + * Текст поля суммы с разрядами («1234567,5» → «1 234 567,5»), как его видно при + * вводе: остаются цифры и один десятичный разделитель (запятая), в дробной + * части не больше двух знаков. Недописанное «12,» сохраняется — иначе запятую + * нельзя было бы ввести. + */ +export function formatAmountInput(raw: string): string { + const cleaned = raw.replace(/[^\d.,]/g, '').replace('.', ','); + const comma = cleaned.indexOf(','); + const intPart = (comma < 0 ? cleaned : cleaned.slice(0, comma)).replace(/,/g, '').replace(/^0+(?=\d)/, ''); + const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ' '); + if (comma < 0) return grouped; + const fraction = cleaned.slice(comma + 1).replace(/,/g, '').slice(0, 2); + return `${grouped || '0'},${fraction}`; +} + /** Сумма ранее поданных оплат по основанию. */ export function previousPaymentsTotal(payments: IPreviousPayment[]): number { return payments.reduce((sum, p) => sum + p.amount, 0); diff --git a/src/webparts/requestPortal/forms/PaymentRequest/sections/AmountInput.tsx b/src/webparts/requestPortal/forms/PaymentRequest/sections/AmountInput.tsx index 6e66e4c..9c96cf1 100644 --- a/src/webparts/requestPortal/forms/PaymentRequest/sections/AmountInput.tsx +++ b/src/webparts/requestPortal/forms/PaymentRequest/sections/AmountInput.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { TextInput } from '../../../shared/ui'; -import { parseAmount } from '../amounts'; +import { formatAmountInput, parseAmount } from '../amounts'; interface IAmountInputProps { id?: string; @@ -8,10 +8,10 @@ interface IAmountInputProps { onChange: (value: number) => void; } -const toText = (value: number): string => (value ? String(value) : ''); +const toText = (value: number): string => (value ? formatAmountInput(String(value)) : ''); /** - * Поле суммы с десятичной частью. Хранит значение числом, но пока пользователь + * Поле суммы с десятичной частью и разрядами («1 234 567,50»). Хранит значение числом, но пока пользователь * печатает, держит собственный текст: иначе «12,» превращалось бы в «12» на * каждом нажатии и дробную часть ввести было бы нельзя. */ @@ -28,8 +28,9 @@ export const AmountInput: React.FC = ({ id, value, onChange } id={id} value={text} onChange={(next) => { - setText(next); - onChange(parseAmount(next)); + const formatted = formatAmountInput(next); + setText(formatted); + onChange(parseAmount(formatted)); }} /> );