From 57e54e40557520ef58d074252e9e5b7b1cc30bee Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 28 Oct 2026 17:27:04 +0400 Subject: [PATCH] =?UTF-8?q?AURORA-1636/1693:=20=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20=E2=80=94=20?= =?UTF-8?q?=D0=B0=D1=80=D1=85=D0=B8=D0=B2=20=D0=93=D0=9A=20=D1=82=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=BA=D0=BE=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= =?UTF-8?q?=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - список ГК в сторе снова общий и полный (с архивными); «Архив» фильтрует только список справочника, выбор ГК — первая показанная, пустой список — «Группа компаний не выбрана» - перенос ЮЛ и новая консолидация не предлагают архивные ГК - «Входит в ГСК/ГСЗ» — поля строки in_gsk/in_gsz, колонки только в карточке ГК (не в «ЮЛ вне ГК») - «Архивировать» красным, а не «Разархивировать»; тексты модалки и кнопка «Сохранить» в изменении ГК - вернул поясняющие комментарии; поиск ГК не сжимается галочкой «Архив» - тесты: selectors (видимые ГК, выбор ГК), toCatalogueJurRow Co-Authored-By: Claude Opus 5.5 --- web/src/layouts/CatalogueGroups.tsx | 47 ++++++++++++------- .../api/directories/company_groups.ts | 11 ++--- .../companyGroupsSlice/selectors.test.ts | 45 ++++++++++++++++++ .../slices/companyGroupsSlice/selectors.ts | 17 +++++++ .../store/slices/companyGroupsSlice/thunks.ts | 7 ++- .../store/slices/companyGroupsSlice/types.ts | 5 +- .../types/catalogueJur/catalogueJur.test.ts | 34 ++++++++++++++ web/src/types/catalogueJur/catalogueJur.ts | 10 ++++ .../ArchiveCompanyGroupModal.tsx | 6 ++- .../CatalogueGroupHeader.tsx | 2 +- .../CatalogueJurTable/CatalogueJurTable.tsx | 32 +++++++------ web/src/widgets/CatalogueJurTable/config.tsx | 43 +++++++++-------- .../CompanyGroupForm/CompanyGroupForm.tsx | 4 +- .../CompanyGroupsList/CompanyGroupsList.tsx | 25 +++++----- .../MoveCatalogueJurForm.tsx | 5 +- .../NewConsoleModal/NewConsoleModal.tsx | 2 +- .../RelatedGroupForm/RelatedGroupForm.tsx | 3 +- 17 files changed, 204 insertions(+), 94 deletions(-) create mode 100644 web/src/store/slices/companyGroupsSlice/selectors.test.ts create mode 100644 web/src/store/slices/companyGroupsSlice/selectors.ts create mode 100644 web/src/types/catalogueJur/catalogueJur.test.ts diff --git a/web/src/layouts/CatalogueGroups.tsx b/web/src/layouts/CatalogueGroups.tsx index a4f39bf..421b406 100644 --- a/web/src/layouts/CatalogueGroups.tsx +++ b/web/src/layouts/CatalogueGroups.tsx @@ -1,6 +1,6 @@ import { PlusOutlined } from '@ant-design/icons'; -import { Button, Space } from 'antd'; -import { useEffect, useState } from 'react'; +import { Button, Empty, Space } from 'antd'; +import { useEffect, useMemo, useState } from 'react'; import { ApiError } from '../api/client'; import PageTitle from '../components/PageTitle'; import { createCatalogueJur } from '../services/api/directories/catalogue_jur'; @@ -14,6 +14,7 @@ import { import { createRelatedGroup } from '../services/api/directories/related-groups'; import { setActiveCompanyGroup } from '../store/slices/catalogueJurSlice/catalogueJurSlice'; import { getCatalogueJurThunk } from '../store/slices/catalogueJurSlice/thunks'; +import { nextActiveCompanyGroup, visibleCompanyGroups } from '../store/slices/companyGroupsSlice/selectors'; import { getCompanyGroupsThunk } from '../store/slices/companyGroupsSlice/thunks'; import { useAppDispatch, useAppSelector } from '../store/store'; import type { RelatedGroupOut } from '../types/api'; @@ -50,11 +51,13 @@ export function CatalogueGroup() { const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup); const relatedGroupFilter = useAppSelector((state) => state.catalogue_jur.relatedGroupFilter); const rawRows = useAppSelector((state) => state.company_groups.raw_rows); + const companyGroupsLoading = useAppSelector((state) => state.company_groups.loading); const isGettingArchiveCompany = useAppSelector((state) => state.company_groups.isGettingArchive); + const visibleGroups = useMemo(() => visibleCompanyGroups(rawRows, isGettingArchiveCompany), [rawRows, isGettingArchiveCompany]); async function createJurHandler(values: CatalogueJurFormProps) { await createCatalogueJur(values); - dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + dispatch(getCompanyGroupsThunk()); dispatch( getCatalogueJurThunk({ closed: isGettingArchive, @@ -76,13 +79,13 @@ export function CatalogueGroup() { } async function createCompanyGroupHandler(values: CompanyGroupFormProps) { await createCompanyGroup(values); - dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + dispatch(getCompanyGroupsThunk()); setKindForm(null); } async function editCompanyGroupHandler(values: CompanyGroupFormProps) { if (activeCompanyGroup?.id) { await editCompanyGroup(activeCompanyGroup?.id, values); - dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + dispatch(getCompanyGroupsThunk()); setCompanyGroupEditFormOpen(false); } } @@ -94,11 +97,11 @@ export function CatalogueGroup() { try { setCompanyGroupDeleteLoading(true); if (activeCompanyGroup?.is_archived) { - await unarchiveCompanyGroup(activeCompanyGroup?.id); + await unarchiveCompanyGroup(activeCompanyGroup.id); } else { await archiveCompanyGroup(activeCompanyGroup.id); } - dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + dispatch(getCompanyGroupsThunk()); setCompanyGroupDeleteOpen(false); } catch (e) { @@ -135,11 +138,18 @@ export function CatalogueGroup() { getRelatedGroupsHandler(); }, [activeCompanyGroup]); + // Без выбранной ГК таблица ЮЛ не грузится — выбираем первую ГК показанного списка; выбранная, ушедшая в архив + // (или из архива) или скрытая переключением «Архива», заменяется первой показанной, пустой список — без выбора useEffect(() => { - if (!activeCompanyGroup && rawRows?.length) { - dispatch(setActiveCompanyGroup(rawRows[0])); + // список ГК ещё не загружен — выбор не трогаем (при возврате на экран остаётся прежняя ГК) + if (companyGroupsLoading || rawRows.length === 0) { + return; } - }, [rawRows, activeCompanyGroup, dispatch]); + const next = nextActiveCompanyGroup(activeCompanyGroup, visibleGroups); + if (next?.id !== activeCompanyGroup?.id) { + dispatch(setActiveCompanyGroup(next)); + } + }, [visibleGroups, activeCompanyGroup, companyGroupsLoading, rawRows.length, dispatch]); useEffect(() => { setActiveTab('legal_entities'); @@ -181,8 +191,13 @@ export function CatalogueGroup() { flexDirection: 'column', overflow: 'hidden', }}> - - {activeTab === 'legal_entities' && ( + {!activeCompanyGroup && ( + + )} + {activeCompanyGroup && ( + + )} + {activeCompanyGroup && activeTab === 'legal_entities' && ( )} - {(activeTab === 'gsk' || activeTab === 'gsz') && ( + {activeCompanyGroup && (activeTab === 'gsk' || activeTab === 'gsz') && ( )} - {companyGroupEditFormOpen && ( + {companyGroupEditFormOpen && activeCompanyGroup && ( { setCompanyGroupEditFormOpen(false); }} onOk={editCompanyGroupHandler} - companyGroupId={activeCompanyGroup?.id} + companyGroupId={activeCompanyGroup.id} /> )} {companyGroupDeleteOpen && activeCompanyGroup && ( @@ -257,7 +272,7 @@ export function CatalogueGroup() { loading={companyGroupDeleteLoading} onCancel={() => setCompanyGroupDeleteOpen(false)} onOk={archiveCompanyGroupHandler} - isArchived={activeCompanyGroup?.is_archived} + isArchived={activeCompanyGroup.is_archived} /> )} diff --git a/web/src/services/api/directories/company_groups.ts b/web/src/services/api/directories/company_groups.ts index 33464e6..3943d56 100644 --- a/web/src/services/api/directories/company_groups.ts +++ b/web/src/services/api/directories/company_groups.ts @@ -1,20 +1,15 @@ import { api } from '../../../api/client'; -import type { GetCompanyGroupsPropsI } from '../../../store/slices/companyGroupsSlice/types'; import type { LegalEntityBrief, Page, RelatedGroupDetail, RelatedGroupOut } from '../../../types/api'; import type { CompanyGroupFormProps, ICompanyGroupOut } from '../../../types/companyGroups/companyGroups'; const CG_PAGE = 200; export const LE_PAGE = 200; -/** Все видимые ГК — дочитываем страницы, пока не получим total. */ -export async function fetchCompanyGroups(params: GetCompanyGroupsPropsI): Promise { +/** Все видимые ГК, в том числе архивные (`is_archived`), — дочитываем страницы, пока не получим total. */ +export async function fetchCompanyGroups(): Promise { const all: ICompanyGroupOut[] = []; for (let offset = 0; ; offset += CG_PAGE) { - const page = await api.get>('/directories/company-groups', { - limit: CG_PAGE, - offset: offset, - ...params, - }); + const page = await api.get>(`/directories/company-groups?limit=${CG_PAGE}&offset=${offset}`); all.push(...page.items); if (all.length >= page.total || page.items.length === 0) { return all; diff --git a/web/src/store/slices/companyGroupsSlice/selectors.test.ts b/web/src/store/slices/companyGroupsSlice/selectors.test.ts new file mode 100644 index 0000000..b4404b1 --- /dev/null +++ b/web/src/store/slices/companyGroupsSlice/selectors.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest'; +import type { ICompanyGroup } from '../../../types/companyGroups/companyGroups'; +import { nextActiveCompanyGroup, visibleCompanyGroups } from './selectors'; + +const group = (id: string, is_archived: boolean): ICompanyGroup => ({ + id, + name: id, + crm_id: null, + base_regional_office_id: null, + base_regional_office: null, + legal_entities_count: 0, + is_archived, + base_regional_office_name: '—', +}); + +const active1 = group('a1', false); +const active2 = group('a2', false); +const archived1 = group('z1', true); +const rows = [active1, archived1, active2]; + +describe('visibleCompanyGroups', () => { + it('без «Архива» — только действующие ГК, с «Архивом» — только архивные', () => { + expect(visibleCompanyGroups(rows, false)).toEqual([active1, active2]); + expect(visibleCompanyGroups(rows, true)).toEqual([archived1]); + }); +}); + +describe('nextActiveCompanyGroup', () => { + it('выбранная ГК в показанном списке остаётся', () => { + expect(nextActiveCompanyGroup(active2, [active1, active2])).toBe(active2); + }); + + it('без выбранной ГК берётся первая показанная', () => { + expect(nextActiveCompanyGroup(null, [active1, active2])).toBe(active1); + }); + + it('ГК ушла в архив — выбирается первая действующая', () => { + expect(nextActiveCompanyGroup({ ...active1, is_archived: true }, [active2])).toBe(active2); + }); + + it('пустой список — ГК не выбрана', () => { + expect(nextActiveCompanyGroup(active1, [])).toBeNull(); + expect(nextActiveCompanyGroup(null, [])).toBeNull(); + }); +}); diff --git a/web/src/store/slices/companyGroupsSlice/selectors.ts b/web/src/store/slices/companyGroupsSlice/selectors.ts new file mode 100644 index 0000000..574133d --- /dev/null +++ b/web/src/store/slices/companyGroupsSlice/selectors.ts @@ -0,0 +1,17 @@ +import type { ICompanyGroup } from '../../../types/companyGroups/companyGroups'; + +/** ГК списка справочника: действующие или, при включённом «Архиве», только архивные. */ +export function visibleCompanyGroups(rows: ICompanyGroup[], archived: boolean): ICompanyGroup[] { + return rows.filter((group) => group.is_archived === archived); +} + +/** + * Какая ГК должна быть выбрана в справочнике: выбранная — пока она в показанном списке; иначе (ушла в архив, вернулась + * из архива, скрыта переключением «Архива») — первая показанная, а в пустом списке — никакая. + */ +export function nextActiveCompanyGroup(active: ICompanyGroup | null | undefined, visible: ICompanyGroup[]): ICompanyGroup | null { + if (active && visible.some((group) => group.id === active.id)) { + return active; + } + return visible[0] ?? null; +} diff --git a/web/src/store/slices/companyGroupsSlice/thunks.ts b/web/src/store/slices/companyGroupsSlice/thunks.ts index 98cbefa..df8c904 100644 --- a/web/src/store/slices/companyGroupsSlice/thunks.ts +++ b/web/src/store/slices/companyGroupsSlice/thunks.ts @@ -1,13 +1,12 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { fetchCompanyGroups } from '../../../services/api/directories/company_groups'; import { type ICompanyGroup, toCompanyGroupRow } from '../../../types/companyGroups/companyGroups'; -import type { GetCompanyGroupsPropsI } from './types'; -export const getCompanyGroupsThunk = createAsyncThunk( +export const getCompanyGroupsThunk = createAsyncThunk( 'company_groups/getAll', - async (args, thunkApi) => { + async (_, thunkApi) => { try { - return (await fetchCompanyGroups(args || {})).map(toCompanyGroupRow); + return (await fetchCompanyGroups()).map(toCompanyGroupRow); } catch (e) { return thunkApi.rejectWithValue(e instanceof Error ? e.message : 'Ошибка'); } diff --git a/web/src/store/slices/companyGroupsSlice/types.ts b/web/src/store/slices/companyGroupsSlice/types.ts index fcbd521..589f61a 100644 --- a/web/src/store/slices/companyGroupsSlice/types.ts +++ b/web/src/store/slices/companyGroupsSlice/types.ts @@ -5,9 +5,6 @@ export interface CompanyGroupSliceType { raw_rows: ICompanyGroup[]; loading: boolean; error: string | null; + /** Список справочника показывает архивные ГК вместо действующих; `raw_rows` при этом не меняется. */ isGettingArchive: boolean; } - -export interface GetCompanyGroupsPropsI { - is_archived?: boolean; -} diff --git a/web/src/types/catalogueJur/catalogueJur.test.ts b/web/src/types/catalogueJur/catalogueJur.test.ts new file mode 100644 index 0000000..3560c65 --- /dev/null +++ b/web/src/types/catalogueJur/catalogueJur.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest'; +import { type ICatalogueJur, type MemberShips, toCatalogueJurRow } from './catalogueJur'; + +const member = (kind: MemberShips['kind']): MemberShips => ({ + related_group_id: kind, + code: kind, + kind, + name: kind, + valid_from: new Date('2026-01-01'), +}); + +const row = (memberships?: MemberShips[]): ICatalogueJur => + ({ + id: 'le-1', + inn: '7701000040', + entity_type: 'ЮЛ', + name: 'ООО «Тест»', + company_group_name: null, + closed: false, + regional_office: { id: 'ro-1', name: 'Ростовский РФ', is_head_office: false }, + memberships, + }) as ICatalogueJur; + +describe('toCatalogueJurRow', () => { + it('имя РФ — плоским полем для поиска и фильтра', () => { + expect(toCatalogueJurRow(row()).regional_name).toBe('Ростовский РФ'); + }); + + it('признаки ГСК и ГСЗ — по видам членства', () => { + expect(toCatalogueJurRow(row([member('gsk')]))).toMatchObject({ in_gsk: true, in_gsz: false }); + expect(toCatalogueJurRow(row([member('gsz')]))).toMatchObject({ in_gsk: false, in_gsz: true }); + expect(toCatalogueJurRow(row())).toMatchObject({ in_gsk: false, in_gsz: false }); + }); +}); diff --git a/web/src/types/catalogueJur/catalogueJur.ts b/web/src/types/catalogueJur/catalogueJur.ts index f53ffa0..f992f7c 100644 --- a/web/src/types/catalogueJur/catalogueJur.ts +++ b/web/src/types/catalogueJur/catalogueJur.ts @@ -21,9 +21,19 @@ export interface ICatalogueJur { name: string; is_head_office: boolean; }; + /** Плоские поля строки таблицы (`toCatalogueJurRow`): по ним работают поиск, фильтры и колонки GenericTable. */ regional_name?: string; + in_gsk?: boolean; + in_gsz?: boolean; } +export const toCatalogueJurRow = (row: ICatalogueJur): ICatalogueJur => ({ + ...row, + regional_name: row.regional_office?.name, + in_gsk: row.memberships?.some((member) => member.kind === 'gsk') ?? false, + in_gsz: row.memberships?.some((member) => member.kind === 'gsz') ?? false, +}); + export type KindType = 'gsk' | 'gsz'; export type MemberShips = { diff --git a/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx b/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx index b4bc6ca..65db289 100644 --- a/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx +++ b/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx @@ -11,7 +11,7 @@ type Props = { export const ArchiveCompanyGroupModal: React.FC = ({ open, onCancel, onOk, loading, isArchived }) => { return ( = ({ open, onCancel, onOk okButtonProps={isArchived ? undefined : { danger: true }} confirmLoading={loading} centered> -

Группа компании будет {isArchived ? 'разархивированна' : 'архивированна'}

+

+ {isArchived ? 'Группа компаний вернётся из архива в список действующих.' : 'Группа компаний будет перенесена в архив.'} +

); }; diff --git a/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx b/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx index b7c1bf4..a73b56f 100644 --- a/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx +++ b/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx @@ -36,7 +36,7 @@ export const CompanyGroupHeader = ({ setEditFormOpen, setDeleteConfirmOpen }: Co key: 'delete', icon: activeCompanyGroup?.is_archived ? : , label: activeCompanyGroup?.is_archived ? 'Разархивировать' : 'Архивировать', - danger: activeCompanyGroup?.is_archived, + danger: !activeCompanyGroup?.is_archived, onClick: () => { setDeleteConfirmOpen(true); }, diff --git a/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx b/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx index 0f1bfc2..8f24b8b 100644 --- a/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx +++ b/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx @@ -19,18 +19,19 @@ import { setIsGettingArchive, setRelatedGroupFilter } from '../../store/slices/c import { getCatalogueJurThunk } from '../../store/slices/catalogueJurSlice/thunks'; import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks'; import { useAppDispatch, useAppSelector } from '../../store/store'; -import type { - CatalogueJurFormProps, - ICatalogueJur, - MoveCatalogueJurProps, - WithdrawCatalogueJurProps, +import { + type CatalogueJurFormProps, + type ICatalogueJur, + type MoveCatalogueJurProps, + type WithdrawCatalogueJurProps, + toCatalogueJurRow, } from '../../types/catalogueJur/catalogueJur'; import { pluralRu } from '../../utils/labels'; import { useNotificationApi } from '../../utils/useNotification'; import { CatalogueJurForm } from '../CatalogueJurForm/CatalogueJurForm'; import { MoveCatalogueJurForm } from '../MoveCatalogueJurForm/MoveCatalogueJurForm'; import { WithdrawCatalogueJurForm } from '../WithdrawCatalogueJurForm/WithdrawCatalogueJurForm'; -import { catalogueJurConfig } from './config'; +import { catalogueJurConfig, companyGroupJurConfig } from './config'; interface CatalogueJurTableI { outside_group: boolean; @@ -52,7 +53,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur const [editCatalogueFormOpen, setEditCatalogueFormOpen] = useState(false); const [withdrawCatalogueFormOpen, setWithdrawCatalogueFormOpen] = useState(false); const [jurId, setJurId] = useState(''); - const isGettingArchiveCompany = useAppSelector((state) => state.company_groups.isGettingArchive); + const tableConfig = outside_group ? catalogueJurConfig : companyGroupJurConfig; const filterdData = useMemo(() => { let newData = rawRows; @@ -61,14 +62,14 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur return row?.memberships?.some((member) => member.related_group_id === relatedGroupFilter.id); }); } - return newData?.map((elem) => elem && { ...elem, regional_name: elem?.regional_office?.name }); + return newData?.map((elem) => elem && toCatalogueJurRow(elem)); }, [rawRows, relatedGroupFilter, outside_group]); - const { visibleKeys, setVisibleKeys } = useVisibleColumns(catalogueJurConfig); + const { visibleKeys, setVisibleKeys } = useVisibleColumns(tableConfig); const { rawQuery, setRawQuery, columnFilters, setColumnFilters, sort, setSort, resetAll, hasActiveFilters } = useTableFilters(); // single filtered array — title count and table share it - const { rows } = useFilteredRows(filterdData, catalogueJurConfig, rawQuery, columnFilters, sort); + const { rows } = useFilteredRows(filterdData, tableConfig, rawQuery, columnFilters, sort); function getCatalogueJur() { dispatch( @@ -97,7 +98,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur try { await moveCatalogueJur(jurId, values); getCatalogueJur(); - dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + dispatch(getCompanyGroupsThunk()); closeMoveCatalogueJurHandler(); } catch (e) { if (e instanceof ApiError) { @@ -111,7 +112,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur try { await withdrawCatalogueJur(jurId, values); getCatalogueJur(); - dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + dispatch(getCompanyGroupsThunk()); closeWithdrawCatalogueJurHandler(); } catch (e) { @@ -130,6 +131,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur } useEffect(() => { + // «ЮЛ вне ГК» грузится без выбранной ГК; список ГК — только по выбранной (без неё запрос ушёл бы по всем ГК) if (outside_group || activeCompanyGroup?.id) { getCatalogueJur(); } @@ -234,7 +236,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur dispatch(setIsGettingArchive(e.target.checked))}> Архив - + {relatedGroupFilter?.id && !outside_group && ( @@ -245,7 +247,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur justifyContent: 'space-between', gap: 16, padding: '10px 16px', - background: '#fff8e1', + background: '#fff8e1', // светло-жёлтый фон borderTop: '1px solid #ffe58f', borderBottom: '1px solid #ffe58f', borderRadius: 8, @@ -282,7 +284,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur = { rowKey: 'id', searchableKeys: ['closed', 'inn', 'crm_id', 'kpp', 'name', 'entity_type', 'regional_name'], @@ -18,28 +19,6 @@ export const catalogueJurConfig: TableConfig = { return ; }, }, - { - key: 'crm_id', - title: 'Входит в ГСК', - render: (_: unknown, row) => { - const has = row?.memberships?.some((elem) => elem.kind === 'gsk'); - const preset = has ? TAG_PRESETS.green : TAG_PRESETS.gray; - const text = has ? 'Входит' : 'Не входит'; - - return ; - }, - }, - { - key: 'company_group_id', - title: 'Входит в ГСЗ', - render: (_: unknown, row) => { - const has = row?.memberships?.some((elem) => elem.kind === 'gsz'); - const preset = has ? TAG_PRESETS.green : TAG_PRESETS.gray; - const text = has ? 'Входит' : 'Не входит'; - - return ; - }, - }, { key: 'inn', title: 'ИНН', @@ -71,3 +50,23 @@ export const catalogueJurConfig: TableConfig = { }, ], }; + +const membershipColumn = (key: 'in_gsk' | 'in_gsz', title: string) => ({ + key, + title, + render: (value: unknown) => { + const preset = value ? TAG_PRESETS.green : TAG_PRESETS.gray; + return ; + }, +}); + +/** ЮЛ карточки ГК: после статуса — признаки «Входит в ГСК» / «Входит в ГСЗ» (поля строки из `toCatalogueJurRow`). */ +export const companyGroupJurConfig: TableConfig = { + ...catalogueJurConfig, + columns: [ + catalogueJurConfig.columns[0], + membershipColumn('in_gsk', 'Входит в ГСК'), + membershipColumn('in_gsz', 'Входит в ГСЗ'), + ...catalogueJurConfig.columns.slice(1), + ], +}; diff --git a/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx b/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx index e7df042..a922677 100644 --- a/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx +++ b/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx @@ -100,11 +100,11 @@ export const CompanyGroupForm: React.FC = ({ open, onCancel, onOk, compan return ( { const [query, setQuery] = useState(''); const filtered = useMemo(() => { + const rows = visibleCompanyGroups(rawRows, isGettingArchiveCompany); const q = query.trim().toLowerCase(); - if (!q) return rawRows; - return rawRows.filter((g) => g.name.toLowerCase().includes(q) || g.crm_id?.toLowerCase().includes(q)); - }, [rawRows, query]); + if (!q) return rows; + return rows.filter((g) => g.name.toLowerCase().includes(q) || g.crm_id?.toLowerCase().includes(q)); + }, [rawRows, isGettingArchiveCompany, query]); const { ref, onScroll, visibleItems, totalHeight, offsetY } = useVirtualList(filtered, { itemHeight: 56, @@ -31,14 +33,8 @@ export const CompanyGroupsList = () => { }); useEffect(() => { - dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); - }, [dispatch, isGettingArchiveCompany]); - - useEffect(() => { - if (!activeCompanyGroup && rawRows?.length) { - dispatch(setActiveCompanyGroup(rawRows?.[0])); - } - }, [rawRows, activeCompanyGroup, dispatch]); + dispatch(getCompanyGroupsThunk()); + }, [dispatch]); if (loading) { return ( @@ -58,18 +54,19 @@ export const CompanyGroupsList = () => { borderRight: '1px solid #f0f0f0', borderRadius: 12, }}> - +
} placeholder='Поиск по ГК или CRM ID' value={query} onChange={(e) => setQuery(e.target.value)} + style={{ flex: 1 }} /> dispatch(setIsGettingArchive(e.target.checked))}> Архив - +
{filtered.length === 0 ? ( diff --git a/web/src/widgets/MoveCatalogueJurForm/MoveCatalogueJurForm.tsx b/web/src/widgets/MoveCatalogueJurForm/MoveCatalogueJurForm.tsx index 4e68770..9561a59 100644 --- a/web/src/widgets/MoveCatalogueJurForm/MoveCatalogueJurForm.tsx +++ b/web/src/widgets/MoveCatalogueJurForm/MoveCatalogueJurForm.tsx @@ -29,7 +29,6 @@ export const MoveCatalogueJurForm: React.FC = ({ open, onCancel, onOk, ju const [form] = Form.useForm(); const dispatch = useAppDispatch(); const notification = useNotificationApi(); - const isGettingArchiveCompany = useAppSelector((state) => state.company_groups.isGettingArchive); const { raw_rows, loading } = useAppSelector((state) => state.company_groups); const [initialDataLoading, setInitialDataLoading] = useState(false); @@ -77,7 +76,7 @@ export const MoveCatalogueJurForm: React.FC = ({ open, onCancel, onOk, ju }; useEffect(() => { if (open && raw_rows.length === 0 && !loading) { - dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + dispatch(getCompanyGroupsThunk()); } }, [open, raw_rows.length, loading, dispatch]); @@ -110,7 +109,7 @@ export const MoveCatalogueJurForm: React.FC = ({ open, onCancel, onOk, ju showSearch allowClear options={raw_rows - .filter((row) => row.id !== form.getFieldValue('old_company_group_id')) + .filter((row) => !row.is_archived && row.id !== form.getFieldValue('old_company_group_id')) .map((g) => ({ value: g.id, label: g.name }))} notFoundContent={loading ? 'Загрузка' : 'Нет вариантов'} optionFilterProp='label' diff --git a/web/src/widgets/NewConsoleModal/NewConsoleModal.tsx b/web/src/widgets/NewConsoleModal/NewConsoleModal.tsx index 80882ec..b95d0e0 100644 --- a/web/src/widgets/NewConsoleModal/NewConsoleModal.tsx +++ b/web/src/widgets/NewConsoleModal/NewConsoleModal.tsx @@ -167,7 +167,7 @@ export const NewConsolidationModal: React.FC = ({ open, onCancel, onOk }) showSearch allowClear filterOption={(input, option) => (option?.label as string)?.toLowerCase().includes(input.toLowerCase())} - options={raw_rows.map((g) => ({ value: g.id, label: g.name }))} + options={raw_rows.filter((g) => !g.is_archived).map((g) => ({ value: g.id, label: g.name }))} onChange={handleGroupChange} /> diff --git a/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx b/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx index b60cfea..ce8a9b5 100644 --- a/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx +++ b/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx @@ -27,7 +27,6 @@ export const RelatedGroupForm: React.FC = ({ open, onCancel, onOk, relate const companyGrops = useAppSelector((state) => state.company_groups.raw_rows); const companyGropsLoading = useAppSelector((state) => state.company_groups.loading); const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup); - const isGettingArchiveCompany = useAppSelector((state) => state.company_groups.isGettingArchive); async function getInitialData() { if (!relatedGroupId) { @@ -39,7 +38,7 @@ export const RelatedGroupForm: React.FC = ({ open, onCancel, onOk, relate setInitialDataLoading(true); try { const data = await fetchRelatedGroup(relatedGroupId); - await dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + await dispatch(getCompanyGroupsThunk()); form.setFieldsValue({ crm_id: data?.crm_id,