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 { ConsolidationCreate } from '../../types/api';
|
||||||
import type { IConsolidation } from '../../types/consolidations/consolidations';
|
import type { IConsolidation } from '../../types/consolidations/consolidations';
|
||||||
import type { VersionCard } from '../../types/consolidations/version_members';
|
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 getConsolidations = (groupId: string) => api.get<IConsolidation[]>(`/company-groups/${groupId}/consolidations`);
|
||||||
export const getVersionCard = (versionId: string) => api.get<VersionCard>(`/consolidations/versions/${versionId}`);
|
export const getVersionCard = (versionId: string) => api.get<VersionCard>(`/consolidations/versions/${versionId}`);
|
||||||
export const createConsolidation = (body: ConsolidationCreate) => api.post<VersionCard>('/consolidations', body);
|
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 { Button, Space, message } from 'antd';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { ApiError } from '../../api/client';
|
import { ApiError } from '../../api/client';
|
||||||
import { ColumnSettings, DataTable, useFilteredRows, useVisibleColumns } from '../../components/GenericTable';
|
import { ColumnSettings, DataTable, useFilteredRows, useVisibleColumns } from '../../components/GenericTable';
|
||||||
import { useTableFilters } from '../../components/GenericTable/useTableFilters';
|
import { useTableFilters } from '../../components/GenericTable/useTableFilters';
|
||||||
|
import { downloadChecklistCsv } from '../../services/api/consolidations';
|
||||||
import { getVersionCard } from '../../services/api/consolidations';
|
import { getVersionCard } from '../../services/api/consolidations';
|
||||||
import { setSelectedLegalEntity, setSelectedVersion } from '../../store/slices/consolidations/consolidationsSlice';
|
import { setSelectedLegalEntity, setSelectedVersion } from '../../store/slices/consolidations/consolidationsSlice';
|
||||||
import { useAppDispatch, useAppSelector } from '../../store/store';
|
import { useAppDispatch, useAppSelector } from '../../store/store';
|
||||||
import type { IVersionCard, MemberRow } from '../../types/consolidations/version_members';
|
import type { IVersionCard, MemberRow } from '../../types/consolidations/version_members';
|
||||||
|
import { saveBlob } from '../../utils/download';
|
||||||
import { membersConfig } from './config';
|
import { membersConfig } from './config';
|
||||||
import { normalizeData } from './normalize';
|
import { normalizeData } from './normalize';
|
||||||
|
import { checklistFilename, responsibleName } from './panelInfo';
|
||||||
|
|
||||||
export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawerOpen: () => void; reloadKey: number }) {
|
export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawerOpen: () => void; reloadKey: number }) {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -32,6 +36,17 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe
|
|||||||
getLegalEntities();
|
getLegalEntities();
|
||||||
}, [consolidation_id, reloadKey]);
|
}, [consolidation_id, reloadKey]);
|
||||||
const rawRows = useMemo(() => normalizeData(consolidationData), [consolidationData]);
|
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 { visibleKeys, setVisibleKeys } = useVisibleColumns(membersConfig);
|
||||||
const { rawQuery, columnFilters, setColumnFilters, sort, setSort, resetAll, hasActiveFilters } = useTableFilters<MemberRow>();
|
const { rawQuery, columnFilters, setColumnFilters, sort, setSort, resetAll, hasActiveFilters } = useTableFilters<MemberRow>();
|
||||||
// single filtered array — title count and table share it
|
// single filtered array — title count and table share it
|
||||||
@ -65,6 +80,12 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe
|
|||||||
Организации
|
Организации
|
||||||
</h2>
|
</h2>
|
||||||
<span style={{ fontSize: 13, color: 'rgb(102, 112, 133)' }}>{rawRows.length} шт.</span>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
{hasActiveFilters && (
|
{hasActiveFilters && (
|
||||||
@ -77,6 +98,9 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<ColumnSettings defs={membersConfig.columns} visibleKeys={visibleKeys} onChange={setVisibleKeys} />
|
<ColumnSettings defs={membersConfig.columns} visibleKeys={visibleKeys} onChange={setVisibleKeys} />
|
||||||
|
<Button icon={<DownloadOutlined />} disabled={!consolidationData} onClick={downloadChecklist} style={{ marginLeft: 8 }}>
|
||||||
|
Скачать чек-лист
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
<DataTable
|
<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() {
|
export function ConsoleTableWidget() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { group_id } = useParams();
|
const { group_id } = useParams();
|
||||||
const [, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const consolidations = useAppSelector((s) => s.consolidations.consolidations);
|
const consolidations = useAppSelector((s) => s.consolidations.consolidations);
|
||||||
const loading = useAppSelector((s) => s.consolidations.loading);
|
const loading = useAppSelector((s) => s.consolidations.loading);
|
||||||
|
|
||||||
@ -142,6 +142,8 @@ export function ConsoleTableWidget() {
|
|||||||
const onRow: TableProps<ConsRow>['onRow'] = (row) => ({
|
const onRow: TableProps<ConsRow>['onRow'] = (row) => ({
|
||||||
// выбранная версия живёт в URL (?version=): переживает уход на «К отчётности» и «Назад»
|
// выбранная версия живёт в URL (?version=): переживает уход на «К отчётности» и «Назад»
|
||||||
onClick: () => setSearchParams({ version: row.id }, { replace: true }),
|
onClick: () => setSearchParams({ version: row.id }, { replace: true }),
|
||||||
|
// выбранная версия подсвечена, как в макете [34]
|
||||||
|
style: row.id === searchParams.get('version') ? { background: '#f0f7ee' } : undefined,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user