From f756aff6ee088a8f299636fda4e722afb046e46e Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 8 Oct 2026 03:44:03 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20[34]=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=20=C2=AB=D0=9E=D1=80=D0=B3=D0=B0=D0=BD=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=C2=BB=20=E2=80=94=20=D0=9E=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D1=81=D1=82=D0=B2=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20/=20?= =?UTF-8?q?=D0=A3=D1=82=D0=B2=D0=B5=D1=80=D0=B6=D0=B4=D1=91=D0=BD=20=D0=B2?= =?UTF-8?q?=20=D0=A0=D0=A4=20/=20=D0=92=D0=B5=D1=80=D0=B8=D1=84=D0=B8?= =?UTF-8?q?=D1=86=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=B2=20=D0=93?= =?UTF-8?q?=D0=9E,=20=C2=AB=D0=A1=D0=BA=D0=B0=D1=87=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D1=87=D0=B5=D0=BA-=D0=BB=D0=B8=D1=81=D1=82=C2=BB=20(CSV),=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D1=81=D0=B2=D0=B5=D1=82=D0=BA=D0=B0=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B1=D1=80=D0=B0=D0=BD=D0=BD=D0=BE=D0=B9=20=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=81=D0=B8=D0=B8=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EAKRDNzXtRrwmWz9z6PZu3 --- src/services/api/consolidations.ts | 3 ++- .../ConsoleLegalEntities.tsx | 24 +++++++++++++++++++ .../ConsoleLegalEntities/panelInfo.test.ts | 15 ++++++++++++ src/widgets/ConsoleLegalEntities/panelInfo.ts | 9 +++++++ src/widgets/ConsoleTable/ConsoleTable.tsx | 4 +++- 5 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/widgets/ConsoleLegalEntities/panelInfo.test.ts create mode 100644 src/widgets/ConsoleLegalEntities/panelInfo.ts diff --git a/src/services/api/consolidations.ts b/src/services/api/consolidations.ts index a8599a1..2c328c7 100644 --- a/src/services/api/consolidations.ts +++ b/src/services/api/consolidations.ts @@ -1,4 +1,4 @@ -import { api } from '../../api/client'; +import { api, apiFetchBlob } from '../../api/client'; import type { ConsolidationCreate } from '../../types/api'; import type { IConsolidation } from '../../types/consolidations/consolidations'; import type { VersionCard } from '../../types/consolidations/version_members'; @@ -6,3 +6,4 @@ import type { VersionCard } from '../../types/consolidations/version_members'; export const getConsolidations = (groupId: string) => api.get(`/company-groups/${groupId}/consolidations`); export const getVersionCard = (versionId: string) => api.get(`/consolidations/versions/${versionId}`); export const createConsolidation = (body: ConsolidationCreate) => api.post('/consolidations', body); +export const downloadChecklistCsv = (versionId: string) => apiFetchBlob(`/consolidations/versions/${versionId}/checklist?format=csv`); diff --git a/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx b/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx index 89fb0d6..d21cf2f 100644 --- a/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx +++ b/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx @@ -1,14 +1,18 @@ +import { DownloadOutlined } from '@ant-design/icons'; import { Button, Space, message } from 'antd'; import { useEffect, useMemo, useState } from 'react'; import { ApiError } from '../../api/client'; import { ColumnSettings, DataTable, useFilteredRows, useVisibleColumns } from '../../components/GenericTable'; import { useTableFilters } from '../../components/GenericTable/useTableFilters'; +import { downloadChecklistCsv } from '../../services/api/consolidations'; import { getVersionCard } from '../../services/api/consolidations'; import { setSelectedLegalEntity, setSelectedVersion } from '../../store/slices/consolidations/consolidationsSlice'; import { useAppDispatch, useAppSelector } from '../../store/store'; import type { IVersionCard, MemberRow } from '../../types/consolidations/version_members'; +import { saveBlob } from '../../utils/download'; import { membersConfig } from './config'; import { normalizeData } from './normalize'; +import { checklistFilename, responsibleName } from './panelInfo'; export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawerOpen: () => void; reloadKey: number }) { const dispatch = useAppDispatch(); @@ -32,6 +36,17 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe getLegalEntities(); }, [consolidation_id, reloadKey]); const rawRows = useMemo(() => normalizeData(consolidationData), [consolidationData]); + + const downloadChecklist = async () => { + if (!consolidationData) return; + try { + const { blob, filename } = await downloadChecklistCsv(consolidationData.id); + const card = consolidationData; + saveBlob(blob, filename ?? checklistFilename(card.company_group.name, card.year, card.quarter)); + } catch (e: unknown) { + message.error(e instanceof ApiError ? e.message : 'Не удалось скачать чек-лист'); + } + }; const { visibleKeys, setVisibleKeys } = useVisibleColumns(membersConfig); const { rawQuery, columnFilters, setColumnFilters, sort, setSort, resetAll, hasActiveFilters } = useTableFilters(); // single filtered array — title count and table share it @@ -65,6 +80,12 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe Организации {rawRows.length} шт. + {consolidationData && ( + // «Утверждён в РФ» / «Верифицирован в ГО» — маршрут согласования, Срез 7 + + Ответственный: {responsibleName(consolidationData)}   Утверждён в РФ: —   Верифицирован в ГО: — + + )}
{hasActiveFilters && ( @@ -77,6 +98,9 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe )} +
{ + it('при активной блокировке — кто взял в работу, иначе инициатор (10-user-journey 2.2)', () => { + expect(responsibleName({ lock: { locked_by_name: 'Петрова М. И.' }, initiator: { name: 'Иванов' } })).toBe('Петрова М. И.'); + expect(responsibleName({ lock: null, initiator: { name: 'Иванов' } })).toBe('Иванов'); + }); +}); + +describe('checklistFilename', () => { + it('запасное имя CSV чек-листа', () => { + expect(checklistFilename('Русский Бисквит', 2024, 'Q3')).toBe('Чек-лист_Русский_Бисквит_2024_Q3.csv'); + }); +}); diff --git a/src/widgets/ConsoleLegalEntities/panelInfo.ts b/src/widgets/ConsoleLegalEntities/panelInfo.ts new file mode 100644 index 0000000..30b5ced --- /dev/null +++ b/src/widgets/ConsoleLegalEntities/panelInfo.ts @@ -0,0 +1,9 @@ +/** «Ответственный» панели «Организации» [34]: кто взял версию в работу, иначе инициатор (10-user-journey 2.2). */ +export function responsibleName(card: { lock: { locked_by_name: string } | null; initiator: { name: string } }): string { + return card.lock?.locked_by_name ?? card.initiator.name; +} + +/** Запасное имя CSV чек-листа, если Content-Disposition недоступен. */ +export function checklistFilename(groupName: string, year: number, quarter: string): string { + return `Чек-лист_${groupName.replace(/[\\/:*?"<>|\s]+/g, '_')}_${year}_${quarter}.csv`; +} diff --git a/src/widgets/ConsoleTable/ConsoleTable.tsx b/src/widgets/ConsoleTable/ConsoleTable.tsx index 35e0cf1..6c8a720 100644 --- a/src/widgets/ConsoleTable/ConsoleTable.tsx +++ b/src/widgets/ConsoleTable/ConsoleTable.tsx @@ -44,7 +44,7 @@ const RowActions = memo(function RowActions({ export function ConsoleTableWidget() { const navigate = useNavigate(); const { group_id } = useParams(); - const [, setSearchParams] = useSearchParams(); + const [searchParams, setSearchParams] = useSearchParams(); const consolidations = useAppSelector((s) => s.consolidations.consolidations); const loading = useAppSelector((s) => s.consolidations.loading); @@ -142,6 +142,8 @@ export function ConsoleTableWidget() { const onRow: TableProps['onRow'] = (row) => ({ // выбранная версия живёт в URL (?version=): переживает уход на «К отчётности» и «Назад» onClick: () => setSearchParams({ version: row.id }, { replace: true }), + // выбранная версия подсвечена, как в макете [34] + style: row.id === searchParams.get('version') ? { background: '#f0f7ee' } : undefined, }); return (