AURORA-1890: индикация редактируемых/расчётных ячеек и срок ввода в заголовке #281
Open
aep
wants to merge 4 commits from
feat/280-aurora-1890-cell-editability into test
pull from: feat/280-aurora-1890-cell-editability
merge into: RSHB:test
RSHB:main
RSHB:feat/289-aurora-1900-unit-vat-labels
RSHB:feat/284-aurora-1891-cell-click-keyboard-edit
RSHB:feat/285-aurora-1899-denomination-base-thousand
RSHB:feat/286-aurora-1903-smeta-export-layout
RSHB:feat/282-aurora-1893-error-alert-navigation
RSHB:feat/276-aurora-1892-negative-amounts
RSHB:test
RSHB:fix/null-strip-test-contract
RSHB:feat/233-aurora-1848-sheet-json-no-pydantic-b
RSHB:feat/275-aurora-1894-error-border-toast
RSHB:feat/216-aurora-1846-form4-sheet-sum
RSHB:fix/0051-migration-collision
RSHB:feat/235-aurora-1850-upd-form-cells-subtree
RSHB:feat/215-aurora-1844-upd-form-cells-full-rows
RSHB:feat/272-aurora-1887-ahr-hide-satellite-rows
RSHB:feat/270-aurora-1907-source-sheet
RSHB:feat/266-aurora-1897-ahr-limit-period-vat-units
RSHB:feat/265-aurora-1909-ahr-manual-row-capture
RSHB:feat/264-aurora-1895-formula-localstorage
RSHB:feat/254-aurora-1906-spod-column
RSHB:feat/255-aurora-1938-transfer-econ-calc
RSHB:feat/250-aurora-1888-stages-role-filter
RSHB:feat/253-aurora-1905-form1-form4-export-layout
RSHB:feat/252-aurora-1902-form2-export-layout
RSHB:feat/251-aurora-1901-smeta-rf-column-order
RSHB:feat/249-aurora-1936-row-delete-confirm
RSHB:feat/248-aurora-1877-token-refresh
RSHB:feat/246-aurora-1944-wheel-scroll-step
RSHB:feat/236-aurora-1843-svod2-server-paging-perf
RSHB:feat/236-aurora-1843-svod2-server-paging-web
RSHB:feat/236-aurora-1843-svod2-server-paging
RSHB:feat/214-aurora-1841-dict-vsp-virtual
RSHB:feat/220-aurora-1842-svod2-column-virtual
RSHB:feat/234-aurora-1839-cell-memo
RSHB:feat/233-aurora-1848-sheet-json-no-pydantic
RSHB:feat/237-aurora-1845-ws-connection-cache
RSHB:feat/226-aurora-1837-cell-styles-css
RSHB:feat/207-aurora-1831-project-tn-race
RSHB:feat/208-aurora-1832-phase-dates-tz
RSHB:feat/209-aurora-1833-greenlet-dev
RSHB:feat/206-aurora-1830-users-me-local-email
RSHB:feat/212-aurora-1838-ws-immutable-update
RSHB:feat/211-aurora-1836-row-measure
RSHB:feat/205-aurora-1922-pg15-migration-0012
RSHB:feat/213-aurora-1840-sheet-modals
RSHB:feat/210-aurora-1835-svod1-formatter
RSHB:svod2-filter-fix2
RSHB:indent-export
RSHB:export-hierarchy
RSHB:svod2-filter-fix
RSHB:fix-id-column
RSHB:export-year-fix
RSHB:form3-source
RSHB:columns-svod-2
RSHB:export-optimize-refresh
RSHB:last-column
RSHB:svod-fix
RSHB:fix-columns
RSHB:form3-expense
RSHB:sattelite-fix
RSHB:delete-sattelite-rows
RSHB:form3-pages
RSHB:form1-sub_sub-fix
RSHB:sort-fix
RSHB:fixes-form
RSHB:new-expenses
RSHB:new-rows
RSHB:fix-style
RSHB:phase-template-edit
RSHB:fixes
RSHB:add-btn-reset-filter
RSHB:vsp-choice-fix
RSHB:project-filters-fix
RSHB:width-column-header-name
RSHB:task-filter-navigation
RSHB:auth
RSHB:icons-program-project
RSHB:export-optimize
RSHB:org-unit-limits
RSHB:auth-v2
RSHB:tasks-filters
RSHB:insert-row-fix
RSHB:svod-roles
RSHB:new-role-fix
RSHB:phase-edit-fix
RSHB:new-role
RSHB:delete-from-cell
RSHB:svod-cahe-and-table-direction
RSHB:direction-stage-fix
RSHB:backslash-fix
RSHB:svod
RSHB:front-svod-form-1
RSHB:projects-filters
RSHB:remove-row-fix
RSHB:computed-fix
RSHB:modal-add-year-project
RSHB:name-fix
RSHB:audit-logs-projects
RSHB:meta-form-3
RSHB:project-back-fix
RSHB:fix-sticky-columns
RSHB:fixies-3-form
RSHB:tech-num-fix
RSHB:fix-logic-limitation
RSHB:projects-fix
RSHB:3-form-project
RSHB:project-add-year-handler
RSHB:projects-summary-handler
RSHB:AURORA-1247
RSHB:projects-handlers
RSHB:vsp-choose-form2-2
RSHB:directions-fix
RSHB:add-validation
RSHB:add-checkbox-export-vsp
RSHB:fix-update-column-config
RSHB:fix-editable-columns
RSHB:add-biome-front
RSHB:vsp-choose-form2
RSHB:export-form-4
RSHB:booking-fix
RSHB:fix-front-tables
RSHB:phase-directions
RSHB:AURORA-1225
RSHB:fix-mapping-form-2-oper
RSHB:AURORA-1076-fix
RSHB:fix-add-row-program-4-form
RSHB:form2-limit-migrations
RSHB:delete-projects
RSHB:AURORA-1076
RSHB:form-4-add-row
RSHB:fix-logs
RSHB:AURORA-1206
RSHB:AURORA-894
RSHB:add-line-log-hotfix
RSHB:form-export-fix
RSHB:form-4-sql
RSHB:table-optimization-fix
RSHB:header_forms
RSHB:form-4-expense-item-filters
RSHB:entity-type-hotfix
RSHB:form-3-phases-timezones
RSHB:form-4-handlers
RSHB:export-fix
RSHB:close-date-vsp-fix
RSHB:deleted-user-auth-fix
RSHB:smeta-fix
RSHB:front-fixes
RSHB:line_id_code_full
RSHB:line_id_code
RSHB:timezones
RSHB:filenames-fix
RSHB:AURORA-1174-73
RSHB:project-sheets
RSHB:AURORA-1166
RSHB:websocket-projects
RSHB:project-front
RSHB:form3-permissions
RSHB:location_from_to_placement_type
RSHB:fix-vsp-filters
RSHB:fix-tests-and-functions
RSHB:mig2-fix
RSHB:vsp_fixes
RSHB:fix-ssp-title
RSHB:AURORA-1148
RSHB:fix-vsp-form-edit
RSHB:sql-funcs2-fix
RSHB:AURORA-1132
RSHB:logs
RSHB:AURORA-1147
RSHB:export-optimization
RSHB:AURORA-1131
RSHB:sql-funcs-fix
RSHB:vsp-dropdown-form
RSHB:form_2-back
RSHB:migrations
RSHB:websocket-self-flag
RSHB:reload-fix
RSHB:test-stand-fix
RSHB:excel-empty-fix
RSHB:requirements-fix
RSHB:AURORA-1096-back
RSHB:AURORA-1098
RSHB:AURORA-1096
RSHB:ws-user-id-fix
RSHB:expense-item-handler
RSHB:fix-date-phase
RSHB:excel-fix
RSHB:vsp-edit-field-fix
RSHB:vsp-fix-back
RSHB:vsp-back
RSHB:create-form-back
RSHB:export-direction-fix
RSHB:change-role-fix
RSHB:org-unit-users-fix
RSHB:audit-fix
RSHB:org-archive
RSHB:del_row_fix
RSHB:websocket_locks
RSHB:new_line_id
RSHB:admin-back
RSHB:form-fix
RSHB:ws
RSHB:ssp-fix
RSHB:stages-back
RSHB:form_124_write
RSHB:form_124
RSHB:new-model
RSHB:test-tsygankov
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
No description provided.
Delete Branch "feat/280-aurora-1890-cell-editability"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #280.
Стек веток (важно для ревью)
Эта ветка стоит не на test, а поверх двух незамерженных PR:
test → PR #277 (AURORA-1894, фикс красной рамки ошибки/тоста) → PR #278 (AURORA-1892, отрицательные суммы красным, merge-коммит
d518961) → эта ветка.Диф этого PR временно включает коммиты #277 и #278 (они ещё не в test), как и #278 сам документирует это для #277. PR создан с
--base testпо процессу; после мерджа #277/#278 ревьюеру будет проще смотреть только изменения этой задачи.Cell.jsx переписан (редактируемая/нередактируемая/занятая стилистика), но оба инварианта предыдущих веток сохранены:
INVALID_STYLESприменяется последним в спреде стилей — красная рамка ошибки всегда выигрывает;...(isNegative ? { fontWeight: 600 } : {})и связанныйformatNumber/isNegativeостались на своих местах.Проблема
Пользователь не может на глаз отличить ячейку, доступную для ввода, от расчётной или закрытой на этапе — обе были белыми, разница была только в курсоре и полупрозрачной рамке 2px #d8e4ea94. Админ видел вычисляемые колонки как редактируемые (isEditable всегда true на INPUT) и получал ошибку сервера при попытке ввода. Ячейка, занятая коллегой, была похожа на readonly (серый фон). В заголовке колонки не было видно срока ввода.
Что сделано
web/src/components/RealtimeTable/utils/cellEditability.js(новый) — чистые функцииisCellEditable({ rowType, columnId, isAdmin, stageColumns })иisComputedColumn(columnId).isComputedColumnповторяет часть серверной проверкиv3._apply_form_cell/v3._apply_form3_cell(api/alembic/versions/sql/0023_apply_cell_empty_string.sql, актуальна черезCREATE OR REPLACE):approved.*/totals.*,header.*кроме name/internal_order/justification/vsp_id, набор полей кварталов (corrected_plan, new_plan, actual_quarter, booking, rem_booking, rem_actual, residual_after_booking, residual_after_actual, economy, total — и их аналоги quarter_actual/total_corr в FORM_3),*.year, сателлиты (fact_qN.total, address/object_type/rented_area) и лист лимитов (amount_qN, limit_with_vat/limit_without_vat). Список зашит в коде, с тестом.web/src/components/RealtimeTable/tableColumns.jsx—isEditableвнутриgetTableColumnsтеперь вызываетisCellEditable, сигнатура не менялась (важно для AURORA-1891).web/src/components/RealtimeTable/hooks/useRealtimeData.js— хук больше не отбрасываетcloses_at: возвращаетstageColumnDeadlines(Map(columnId → closes_at)) рядом сcolumnsCurStage.web/src/components/RealtimeTable/Cell/Cell/Cell.jsx— без новых цветов в палитрах (constants/columnColors.jsне менялся):isHoveredstate — pseudo-класс:hoverнедоступен для inline-стилей), курсор text;isNegativefontWeight → editable/non-editable →isLocked→isInvalid(последний, см. выше).web/src/components/RealtimeTable/utils/deadlineFormat.js(новый) —formatDeadline(closesAt)→«до DD.MM HH:mm»в локальном времени браузера.web/src/components/RealtimeTable/TableHead/TableHead.jsx—HeaderCellполучаетstageColumnDeadlines/userRoleId(прокинуты изRealtimeTable.jsx) и показывает метку✎ до 31.10 18:00под заголовком колонки текущего этапа, только исполнителю (не админу).web/src/components/RealtimeTable/SettingPanel/CellLegend.jsx(новый) — иконка «?» сTooltipиз двух образцов («можно вводить» / «расчёт или закрыто на этапе»), подключена рядом с плашкой «Контроль ошибок» вSettingPanel.jsx, без отдельной панели.columnsCurStage, его форма не изменилась.Тесты
node --test src/components/RealtimeTable/utils/cellEditability.test.js src/components/RealtimeTable/utils/deadlineFormat.test.js— 18/18 зелёных:cellEditability.test.js(14 тестов) — все примеры из тикета (админ +data.q1.corrected_plan/data.approved.q1/data.totals.fact_year/data.header.vsp_address→ false; админ +data.plan.q1→ true; исполнитель вне этапа → false; INPUT+этап → true; ITEM → false) плюс покрытие каждого правилаisComputedColumn(qN-поля, FORM_3 quarter_actual/total_corr,*.year, сателлиты, лист лимитов).deadlineFormat.test.js(4 теста) — формат«до DD.MM HH:mm», паддинг нулями, Date-инстанс напрямую, falsy/invalid → null.Полный
node --test src/components/RealtimeTable/utils/*.test.js— 62/63 (единственный сторонний фейл,columnLayoutUtils.test.js, из-за отсутствующегоnode_modules/@tanstack/table-coreв этом worktree — не связан с этим PR, воспроизводится и на базовом коммите без моих изменений).Линт:
biome checkна изменённых файлах не добавляет новых находок к существующему состоянию веток #277/#278/test (сверено diff-ом error/warning-счётчиков до и после изменения каждого файла); единственная находка, которую я внёс сам (nested ternary в новой логике Cell.jsx), исправлена до коммита.Ручная проверка — по коду (живой стенд не поднимал): формы 1/2/4, сателлиты, лист проекта — исполнитель в открытом окне видит белые редактируемые ячейки с синей рамкой и метку срока в заголовке; исполнитель без окна и админ н�� вычисляемых колонках видят серые ячейки без рамки; админ не видит метку срока ввода.
Rulings
className='cell'— просто маркер, в проекте нет.cellCSS-правил), поэтому:hoverдля утолщения рамки 1px→2px реализован через локальныйisHovered(onMouseEnter/onMouseLeave), а не псевдокласс.Date, а неdate-fns. В проекте уже естьdate-fns, но соседние утилитыRealtimeTable/utils(headerYearUtils.js,web/src/utils/formatDate.js) форматируют даты через чистыйDate/padStartбез доп. зависимостей — для единообразияdeadlineFormat.jsсделан так же.Map, а не массив. СигнатураisCellEditableв тикете используетstageColumns.has(columnId), поэтомуtableColumns.jsxстроитSetизcolumnsCurStage(массива) один раз на пересборку колонок;useRealtimeData.jsдляstageColumnDeadlinesсразу возвращаетMap, так какTableHead.jsxделает точечные.get(columnId)по одной колонке на рендер хедер-ячейки, а не проверку принадлежности множеству.data.totals.go_balance_only(OTCH9F) помечен как computed, хотя в SQL (_apply_form_cell) это поле редактируемое. Тикет явно требует: «scopeapproved.*иtotals.*— всегда вычисляемые» без исключений — это сознательное упрощение фронтового списка (см. п.2 тикета: «Список зашит в коде... это фронтовый список с тестом»), один известный false positive для одного поля одного сателлита, не исправлял за рамками тикета.#999999у занятой ячейки. Тикет описывает только рамку/бейдж для locked-ячейки («фон ячейки не менять, рамка 2px dashed #F0A030 и значок 🔒»), но старый код также красил текст в серый, что усиливало прежнее сходство с read-only — убрал, оставил обычный brightness-based цвет текста, чтобы отличие было только в рамке/бейдже, как в критерии приёмки («отличается от нередактируемой»).Cell.jsx: порядок спреда стилей — base → editable/not-editable → locked → invalid (INVALID_STYLES последним), чтобы красная рамка 2px не перетиралась EDITABLE_STYLES у вводимых ячеек с ошибкой. utils/wsErrorUtils.js: getWsErrorMessage(error) — строка возвращается как есть, объект берёт message || detail, известные технические префиксы (computed_field:, role_not_allowed:, window_closed:, org_not_assigned) заменяются понятным текстом, прочее отдаётся без "Server error:". RealtimeContext.js: toast.error(getWsErrorMessage(data.error)) вместо `Server error:${data.error.message}` || '', которое всегда давало undefined для ошибок-строк (|| не срабатывает на непустой строке). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Cell.jsx: formatNumber вынесен в utils/numberFormat.js и возвращает { text, isNegative }; отрицательные числа выделяются цветом (#C62828 на светлом фоне, #FFCDD2 на тёмном) и font-weight 600, ноль остаётся «-». constants/validation.js: фабрика nonNegativeRule и общий список nonNegativeColumnKeys (план, бронь на 2026/2027, факт по месяцам и СПОД, сумма платежа, предельная стоимость договора/ЦКК, сателлиты АХР Аренда/Коммунальные/Безопасность, АХР Лимиты целиком). Корректировки и переносы (adj_*, base_correction, revision_*, seq_*, target_change, transfer_*) не включены — там минус — нормальная операция. Запрет мягкий: подсветка и запись в «Контроль ошибок» через существующий useValidationRules/isCellInvalid, сохранение не блокируется. EditCell.jsx: handleSave предупреждает toast.warning при сохранении отрицательного числа в защищённой колонке, значение всё равно сохраняется. useValidationRules и RealtimeTable прокидывают человекочитаемый заголовок колонки для текста предупреждения. Тесты (node --test): numberFormat.test.js, validationUtils.test.js — новые, 10/10 зелёных.Checkout
From your project repository, check out a new branch and test the changes.