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 (