feat: [34] панель «Организации» — Ответственный / Утверждён в РФ / Верифицирован в ГО, «Скачать чек-лист» (CSV), подсветка выбранной версии (#3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EAKRDNzXtRrwmWz9z6PZu3
This commit is contained in:
parent
35e52aaa29
commit
f756aff6ee
@ -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<IConsolidation[]>(`/company-groups/${groupId}/consolidations`);
|
||||
export const getVersionCard = (versionId: string) => api.get<VersionCard>(`/consolidations/versions/${versionId}`);
|
||||
export const createConsolidation = (body: ConsolidationCreate) => api.post<VersionCard>('/consolidations', body);
|
||||
export const downloadChecklistCsv = (versionId: string) => apiFetchBlob(`/consolidations/versions/${versionId}/checklist?format=csv`);
|
||||
|
||||
@ -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<MemberRow>();
|
||||
// single filtered array — title count and table share it
|
||||
@ -65,6 +80,12 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe
|
||||
Организации
|
||||
</h2>
|
||||
<span style={{ fontSize: 13, color: 'rgb(102, 112, 133)' }}>{rawRows.length} шт.</span>
|
||||
{consolidationData && (
|
||||
// «Утверждён в РФ» / «Верифицирован в ГО» — маршрут согласования, Срез 7
|
||||
<span style={{ fontSize: 13, color: 'rgb(102, 112, 133)', marginLeft: 8 }}>
|
||||
Ответственный: {responsibleName(consolidationData)} Утверждён в РФ: — Верифицирован в ГО: —
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{hasActiveFilters && (
|
||||
@ -77,6 +98,9 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe
|
||||
</Button>
|
||||
)}
|
||||
<ColumnSettings defs={membersConfig.columns} visibleKeys={visibleKeys} onChange={setVisibleKeys} />
|
||||
<Button icon={<DownloadOutlined />} disabled={!consolidationData} onClick={downloadChecklist} style={{ marginLeft: 8 }}>
|
||||
Скачать чек-лист
|
||||
</Button>
|
||||
</div>
|
||||
</Space>
|
||||
<DataTable
|
||||
|
||||
15
src/widgets/ConsoleLegalEntities/panelInfo.test.ts
Normal file
15
src/widgets/ConsoleLegalEntities/panelInfo.test.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { checklistFilename, responsibleName } from './panelInfo';
|
||||
|
||||
describe('responsibleName', () => {
|
||||
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');
|
||||
});
|
||||
});
|
||||
9
src/widgets/ConsoleLegalEntities/panelInfo.ts
Normal file
9
src/widgets/ConsoleLegalEntities/panelInfo.ts
Normal file
@ -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`;
|
||||
}
|
||||
@ -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<ConsRow>['onRow'] = (row) => ({
|
||||
// выбранная версия живёт в URL (?version=): переживает уход на «К отчётности» и «Назад»
|
||||
onClick: () => setSearchParams({ version: row.id }, { replace: true }),
|
||||
// выбранная версия подсвечена, как в макете [34]
|
||||
style: row.id === searchParams.get('version') ? { background: '#f0f7ee' } : undefined,
|
||||
});
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user