From b87b12f9800dfcca715840682407ca8d78d161be Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 8 Nov 2026 22:38:04 +0400 Subject: [PATCH] =?UTF-8?q?AURORA-1989:=20=D0=BD=D0=BE=D0=BC=D0=B5=D1=80?= =?UTF-8?q?=20=D1=81=D1=82=D0=B0=D1=82=D1=8C=D0=B8,=20=D1=81=D0=BE=D1=80?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B8=D1=81=D0=BA=20=D0=B2=20=D0=BE=D0=BA=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D1=80=D0=B0=D1=81=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../Modals/SelectExpenseItemModal.jsx | 5 ++-- .../RealtimeTable/utils/expenseItemOptions.js | 22 +++++++++++++++ .../utils/expenseItemOptions.test.js | 27 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 web/src/components/RealtimeTable/utils/expenseItemOptions.js create mode 100644 web/src/components/RealtimeTable/utils/expenseItemOptions.test.js diff --git a/web/src/components/RealtimeTable/Modals/SelectExpenseItemModal.jsx b/web/src/components/RealtimeTable/Modals/SelectExpenseItemModal.jsx index de0f2a3..bbd94e5 100644 --- a/web/src/components/RealtimeTable/Modals/SelectExpenseItemModal.jsx +++ b/web/src/components/RealtimeTable/Modals/SelectExpenseItemModal.jsx @@ -5,6 +5,7 @@ import { ExpenseItemApi } from '../../../api/expense-item'; import { DangerOutlinedButton, PrimaryButton } from '../../common/Buttons/Buttons'; import { HeaderContainer, ModalBackdrop, ModalContainer, ModalTitle } from '../../common/Modal/ModalStyled'; import { Cancel } from '../../common/icons/icons'; +import { compareExpenseItems, getExpenseItemLabel } from '../utils/expenseItemOptions'; const SelectExpenseItemModalComponent = ({ isOpen, onClose, onSelect, formId, title = '', isSaving = false, sheet }) => { const [selectedValue, setSelectedValue] = useState(''); @@ -14,7 +15,7 @@ const SelectExpenseItemModalComponent = ({ isOpen, onClose, onSelect, formId, ti if (!isOpen || formId == null) return; ExpenseItemApi.getAll({ r_start: true, sheet: sheet }).then((data) => { if (data.success) { - setExpenseItemOption(data.result); + setExpenseItemOption([...data.result].sort(compareExpenseItems)); return; } toast.error('Ошибка получения статья расходов'); @@ -69,7 +70,7 @@ const SelectExpenseItemModalComponent = ({ isOpen, onClose, onSelect, formId, ti size='small' options={expenseItemOptions} noOptionsText='Не найдено' - getOptionLabel={(option) => option.name || ''} + getOptionLabel={getExpenseItemLabel} getOptionKey={(option) => option.id} value={selectedValue} onChange={(_event, newValue) => { diff --git a/web/src/components/RealtimeTable/utils/expenseItemOptions.js b/web/src/components/RealtimeTable/utils/expenseItemOptions.js new file mode 100644 index 0000000..70dbe92 --- /dev/null +++ b/web/src/components/RealtimeTable/utils/expenseItemOptions.js @@ -0,0 +1,22 @@ +const getItemCode = (option) => { + const code = option?.item_id; + if (code == null) return ''; + return String(code).trim(); +}; + +export const getExpenseItemLabel = (option) => { + if (!option || typeof option !== 'object') return ''; + const code = getItemCode(option); + const name = option.name || ''; + if (code && name) return `${code} — ${name}`; + return code || name; +}; + +export const compareExpenseItems = (a, b) => { + const codeA = getItemCode(a); + const codeB = getItemCode(b); + if (!codeA && !codeB) return 0; + if (!codeA) return 1; + if (!codeB) return -1; + return codeA.localeCompare(codeB, 'ru', { numeric: true }); +}; diff --git a/web/src/components/RealtimeTable/utils/expenseItemOptions.test.js b/web/src/components/RealtimeTable/utils/expenseItemOptions.test.js new file mode 100644 index 0000000..08e94be --- /dev/null +++ b/web/src/components/RealtimeTable/utils/expenseItemOptions.test.js @@ -0,0 +1,27 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { compareExpenseItems, getExpenseItemLabel } from './expenseItemOptions.js'; + +const sorted = (ids) => + ids + .map((item_id) => ({ item_id, name: 'x' })) + .sort(compareExpenseItems) + .map((o) => o.item_id); + +test('AURORA-1989: подпись варианта', () => { + assert.equal(getExpenseItemLabel({ item_id: '3.01.', name: 'Связь' }), '3.01. — Связь'); + assert.equal(getExpenseItemLabel({ item_id: null, name: 'Связь' }), 'Связь'); + assert.equal(getExpenseItemLabel({ name: '' }), ''); + assert.equal(getExpenseItemLabel(''), ''); +}); + +test('AURORA-1989: сортировка с учётом числовых частей, без кода — в конце', () => { + assert.deepEqual(sorted(['3.10.', '3.2.', '10', '2']), ['2', '3.2.', '3.10.', '10']); + assert.deepEqual(sorted([null, '1.', '']), ['1.', null, '']); +}); + +test('AURORA-1989: поиск по подписи находит номер и название', () => { + const label = getExpenseItemLabel({ item_id: '3.01.', name: 'Связь' }); + assert.ok(label.includes('3.01')); + assert.ok(label.includes('Связь')); +});