From 2cb90067865cc6684e74349457e17e91e1d3388b Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 28 Oct 2026 18:09:12 +0400 Subject: [PATCH] =?UTF-8?q?perf(web):=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=D1=82=D0=B5=D1=80=20=D1=87=D0=B8=D1=81=D0=B5=D0=BB=20?= =?UTF-8?q?=D1=81=D0=B2=D0=BE=D0=B4=D0=B0=201=20=D0=B8=20=D1=81=D0=B2?= =?UTF-8?q?=D0=BE=D0=B4=D0=BD=D1=8B=D1=85=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D1=91=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20=D0=BE=D0=B4=D0=B8=D0=BD=20=D1=80=D0=B0=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AURORA-1835: Cell в scaleColumns создавал Intl.NumberFormat на каждую ячейку. Форматирование вынесено в formatSummaryAmount (summaryFormat.js) с модульным форматтером. Co-Authored-By: Claude Sonnet 5.5 --- web/src/pages/SvodPage/SummaryTable.jsx | 8 ++--- web/src/pages/SvodPage/summaryFormat.js | 10 ++++++ web/src/pages/SvodPage/summaryFormat.test.js | 37 ++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 web/src/pages/SvodPage/summaryFormat.js create mode 100644 web/src/pages/SvodPage/summaryFormat.test.js diff --git a/web/src/pages/SvodPage/SummaryTable.jsx b/web/src/pages/SvodPage/SummaryTable.jsx index eb41a65..d50dc2c 100644 --- a/web/src/pages/SvodPage/SummaryTable.jsx +++ b/web/src/pages/SvodPage/SummaryTable.jsx @@ -5,6 +5,7 @@ import useDenomination from '../../components/common/useDenomination'; import { blueColumn, greenColumn, orangeColumn } from '../../components/RealtimeTable/constants/columnColors'; import { sectionCodeColor } from '../../components/RealtimeTable/constants/columnConfig'; import { buildRowHierarchy } from '../../components/RealtimeTable/utils/rowUtils'; +import { formatSummaryAmount } from './summaryFormat'; const tableSize = (rem) => rem * 16; const ROW_VIRTUALIZER_OPTIONS = { @@ -68,11 +69,10 @@ const scaleColumns = (columns, divisor) => columns.map((column) => { ...(children ? { columns: children } : {}), ...(isAmount ? { Cell: ({ cell }) => { const value = cell.getValue(); + const formatted = formatSummaryAmount(value, divisor); + if (formatted !== null) return formatted; if (value === null || value === undefined || value === '') return originalCell ? originalCell({ cell }) : '—'; - const numeric = Number(value); - if (!Number.isFinite(numeric)) return originalCell ? originalCell({ cell }) : value; - if (numeric === 0) return '-'; - return new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 2 }).format(numeric / divisor); + return originalCell ? originalCell({ cell }) : value; } } : {}), }; }); diff --git a/web/src/pages/SvodPage/summaryFormat.js b/web/src/pages/SvodPage/summaryFormat.js new file mode 100644 index 0000000..989d8bb --- /dev/null +++ b/web/src/pages/SvodPage/summaryFormat.js @@ -0,0 +1,10 @@ +const amountFormatter = new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 2 }); + +// Возвращает null, если значение не число: вызывающий отдаёт исходное значение. +export const formatSummaryAmount = (value, divisor) => { + if (value === null || value === undefined || value === '') return null; + const numeric = Number(value); + if (!Number.isFinite(numeric)) return null; + if (numeric === 0) return '-'; + return amountFormatter.format(numeric / divisor); +}; diff --git a/web/src/pages/SvodPage/summaryFormat.test.js b/web/src/pages/SvodPage/summaryFormat.test.js new file mode 100644 index 0000000..075ddd1 --- /dev/null +++ b/web/src/pages/SvodPage/summaryFormat.test.js @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +const NBSP = ' '; + +test('formatSummaryAmount: форматирование и делители', async () => { + const { formatSummaryAmount } = await import('./summaryFormat.js'); + assert.equal(formatSummaryAmount(0, 1), '-'); + assert.equal(formatSummaryAmount('0', 1000), '-'); + assert.equal(formatSummaryAmount(1234.567, 1), `1${NBSP}234,57`); + assert.equal(formatSummaryAmount(5_000_000, 1000), `5${NBSP}000`); + assert.equal(formatSummaryAmount(5_000_000, 1e6), '5'); + assert.equal(formatSummaryAmount('1500', 1), `1${NBSP}500`); +}); + +test('formatSummaryAmount: нечисловые значения возвращают null', async () => { + const { formatSummaryAmount } = await import('./summaryFormat.js'); + for (const v of [null, undefined, '', 'abc', Number.NaN, Number.POSITIVE_INFINITY]) { + assert.equal(formatSummaryAmount(v, 1), null); + } +}); + +test('Intl.NumberFormat создаётся один раз на модуль', async () => { + const Original = Intl.NumberFormat; + let created = 0; + Intl.NumberFormat = function CountingNumberFormat(...args) { + created += 1; + return new Original(...args); + }; + try { + const { formatSummaryAmount } = await import('./summaryFormat.js?fresh'); + for (let i = 1; i <= 50; i += 1) formatSummaryAmount(i * 100, 1000); + assert.equal(created, 1); + } finally { + Intl.NumberFormat = Original; + } +});