fix: версия в URL, ошибки списка, подпись квартала, Назад по прямой ссылке, код ошибки не в заголовке (I-1, M-3, M-4, M-6, T7.M7, T7.M4)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EAKRDNzXtRrwmWz9z6PZu3
This commit is contained in:
Andrey Epifancev 2026-10-08 01:54:37 +04:00
parent 384301ecbd
commit 0f734e3bf8
8 changed files with 49 additions and 15 deletions

View File

@ -16,7 +16,10 @@ export default function PageTitle({ location, header, description, backable = fa
<div>
<div style={{ fontSize: 12, color: '#667085' }}>
{backable && (
<Button type='default' onClick={() => navigate(-1)} style={{ marginRight: '5px' }}>
<Button
type='default'
onClick={() => (window.history.state?.idx === 0 ? navigate('/groups') : navigate(-1))}
style={{ marginRight: '5px' }}>
<ArrowLeftOutlined />
Назад
</Button>

View File

@ -8,6 +8,7 @@ import { getVersionCard } from '../services/api/consolidations';
import { exportOsv } from '../services/api/osv';
import type { VersionCard } from '../types/consolidations/version_members';
import { exportFilename, saveBlob } from '../utils/download';
import { quarterLabel } from '../utils/labels';
import { statusMapping } from '../widgets/ConsoleTable/consts';
import { OsvFilters } from '../widgets/ConsolidatedOsv/OsvFilters';
import { OsvGaps } from '../widgets/ConsolidatedOsv/OsvGaps';
@ -52,7 +53,7 @@ function OsvContent({ versionId }: { versionId: string }) {
<PageTitle
backable
location={`Отчётность › ${card?.company_group.name ?? ''} › Сводная ОСВ`}
header={card ? `Сводная ОСВ ${card.year} ${card.quarter} · версия ${card.version_no}` : 'Сводная ОСВ'}
header={card ? `Сводная ОСВ ${card.year}, ${quarterLabel(card.quarter)} · версия ${card.version_no}` : 'Сводная ОСВ'}
description={card ? (statusMapping[card.status] ?? card.status) : ''}
/>
<main

View File

@ -11,6 +11,13 @@ import { ConsoleLegalEntitiesTable } from '../widgets/ConsoleLegalEntities/Conso
import { ConsoleTableWidget } from '../widgets/ConsoleTable/ConsoleTable';
import { DocumentsDrawer } from '../widgets/FilesDrawer/FilesDrawer';
// unwrap() бросает rejectValue (строку), fetch группы — ApiError
function errorText(e: unknown): string {
if (e instanceof ApiError) return e.message;
if (typeof e === 'string') return e;
return 'Не удалось загрузить группу компаний';
}
export function ConsolidationPage() {
const [drawerOpen, setDrawerOpen] = useState(false);
const [reloadKey, setReloadKey] = useState(0);
@ -31,17 +38,25 @@ export function ConsolidationPage() {
setComGroupName(data.name);
setLocationPath(`Отчетность>${data.name}>Консолидации`);
setDesc(`${data.legal_entities_count} организаций`);
await dispatch(getConsolidationsListThunk(id));
if (versionParam) dispatch(setSelectedVersion(versionParam));
await dispatch(getConsolidationsListThunk(id)).unwrap();
} catch (e: unknown) {
message.error(e instanceof ApiError ? e.message : 'Не удалось загрузить группу компаний');
message.error(errorText(e));
}
}
getAllInfo(group_id);
}, [dispatch, group_id]);
// выбор версии следует за ?version= и не перезагружает список
useEffect(() => {
dispatch(setSelectedVersion(versionParam));
}, [dispatch, versionParam]);
// уход со страницы сбрасывает выбор (при смене группы ?version= пропадает из URL — выбор сбросит эффект выше)
useEffect(() => {
return () => {
dispatch(setSelectedVersion(null));
};
}, [dispatch, group_id, versionParam]);
}, [dispatch]);
return (
<div
style={{

View File

@ -1,5 +1,13 @@
import { describe, expect, it } from 'vitest';
import { counterpartyTypeLabel, documentStatusLabel, documentTypeLabel, fileStatusLabel, matchKindLabel, periodLabel } from './labels';
import {
counterpartyTypeLabel,
documentStatusLabel,
documentTypeLabel,
fileStatusLabel,
matchKindLabel,
periodLabel,
quarterLabel,
} from './labels';
describe('labels', () => {
it('тип документа', () => {
@ -10,6 +18,10 @@ describe('labels', () => {
expect(documentTypeLabel('DISCLOSURE', null)).toBe('Расшифровка');
expect(documentTypeLabel('OTHER', null)).toBe('Прочее');
});
it('квартал', () => {
expect(quarterLabel('Q1')).toBe('1 квартал');
expect(quarterLabel('Q3')).toBe('3 квартал');
});
it('период', () => {
expect(periodLabel(2024, 'Q1', false)).toBe('2024 К1');
expect(periodLabel(2024, 'Q3', true)).toBe('2024 · 9 мес.');

View File

@ -14,6 +14,8 @@ export function documentTypeLabel(type: DocumentType, account: string | null): s
return type === 'OSV' && account ? `ОСВ ${account}` : TYPE_LABELS[type];
}
export const quarterLabel = (q: Quarter): string => `${q.slice(1)} квартал`;
export function periodLabel(year: number | null, quarter: Quarter | null, isCumulative: boolean | null): string {
if (year === null || quarter === null) {
return 'период не определён';

View File

@ -2,11 +2,10 @@ import { ArrowRightOutlined, DownOutlined, MoreOutlined, RightOutlined } from '@
import { Button, Dropdown, Space, message } from 'antd';
import type { ColumnsType, TableProps } from 'antd/es/table';
import { memo, useCallback, useMemo } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { ColumnSettings, DataTable, type TableConfig, isGroup, useFilteredRows, useVisibleColumns } from '../../components/GenericTable';
import { useTableFilters } from '../../components/GenericTable/useTableFilters';
import { setSelectedVersion } from '../../store/slices/consolidations/consolidationsSlice';
import { useAppDispatch, useAppSelector } from '../../store/store';
import { useAppSelector } from '../../store/store';
import type { ConsRow } from '../../types/consolidations/consolidations';
import { consConfigBase } from './config';
import { flattenCons } from './flatten';
@ -45,7 +44,7 @@ const RowActions = memo(function RowActions({
export function ConsoleTableWidget() {
const navigate = useNavigate();
const { group_id } = useParams();
const dispatch = useAppDispatch();
const [, setSearchParams] = useSearchParams();
const consolidations = useAppSelector((s) => s.consolidations.consolidations);
const loading = useAppSelector((s) => s.consolidations.loading);
@ -124,7 +123,7 @@ export function ConsoleTableWidget() {
render: (_: unknown, row: ConsRow) => (
<div style={{ display: 'flex' }}>
<Button type='default' onClick={() => navigate(`/groups/${group_id}/versions/${row.id}/osv`)}>
К отчетности <ArrowRightOutlined />
К отчётности <ArrowRightOutlined />
</Button>
<RowActions row={row} onAction={handleDropdownAction} />
</div>
@ -135,7 +134,8 @@ export function ConsoleTableWidget() {
);
const onRow: TableProps<ConsRow>['onRow'] = (row) => ({
onClick: () => dispatch(setSelectedVersion(row.id)),
// выбранная версия живёт в URL (?version=): переживает уход на «К отчётности» и «Назад»
onClick: () => setSearchParams({ version: row.id }, { replace: true }),
});
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>

View File

@ -88,7 +88,7 @@ export function OsvDocument({ document, onChanged }: { document: DocumentOut; on
showIcon
closable
onClose={() => setProblem(null)}
message={problem.code ? `${problem.message} (${problem.code})` : problem.message}
message={<span title={problem.code}>{problem.message}</span>}
description={
problem.failures.length > 0 ? (
<ul style={{ margin: 0, paddingLeft: 18 }}>

View File

@ -14,6 +14,7 @@ import { type ReactNode, memo, useState } from 'react';
import { NavLink, useNavigate } from 'react-router-dom';
import { USERS_ADMIN } from '../../auth/permissions';
import { createConsolidation } from '../../services/api/consolidations';
import { quarterLabel } from '../../utils/labels';
import { NewConsolidationModal } from '../NewConsoleModal/NewConsoleModal';
import styles from './styles.module.css';
@ -216,7 +217,7 @@ export function Sidebar() {
onOk={async (values) => {
const card = await createConsolidation(values);
setModalOpen(false);
message.success(`Создана консолидация ${values.year} ${values.quarter}`);
message.success(`Создана консолидация ${values.year}, ${quarterLabel(values.quarter)}`);
navigate(`/groups/${values.company_group_id}?version=${card.id}`);
}}
/>