From c64975241be6271acf1cfb5e62152b95acba8856 Mon Sep 17 00:00:00 2001 From: Grog Date: Tue, 27 Oct 2026 17:04:18 +0300 Subject: [PATCH 1/6] =?UTF-8?q?aurora-(1636,=201693):=20=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=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=20=D0=B3=D0=BA?= =?UTF-8?q?=20=D0=B8=20=D1=8E=D0=BB=20=D0=B8=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=84=D0=B8=D0=BB=D1=8C?= =?UTF-8?q?=D1=82=D1=80=D0=B0=D1=86=D0=B8=D1=8F=20=D1=8E=D0=BB=20=D0=B1?= =?UTF-8?q?=D0=B5=D0=B7=20=D0=B3=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/layouts/CatalogueGroups.tsx | 76 +++++++++++++++++-- .../api/directories/company_groups.ts | 22 +++++- .../companyGroupsSlice/companyGroupsSlice.ts | 11 ++- .../store/slices/companyGroupsSlice/thunks.ts | 9 ++- .../store/slices/companyGroupsSlice/types.ts | 5 ++ web/src/types/catalogueJur/catalogueJur.ts | 1 + web/src/types/companyGroups/companyGroups.ts | 3 +- .../ArchiveCompanyGroupModal.tsx | 26 +++++++ .../CatalogueGroupHeader.tsx | 73 ++++++++++++++---- .../CatalogueJurTable/CatalogueJurTable.tsx | 15 ++-- web/src/widgets/CatalogueJurTable/config.tsx | 28 ++++++- .../CompanyGroupForm/CompanyGroupForm.tsx | 42 +++++++--- .../CompanyGroupsList/CompanyGroupsList.tsx | 16 ++-- .../MoveCatalogueJurForm.tsx | 3 +- .../RelatedGroupForm/RelatedGroupForm.tsx | 4 +- .../RelatedGroupTable/RelatedGroupTable.tsx | 4 +- 16 files changed, 275 insertions(+), 63 deletions(-) create mode 100644 web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx diff --git a/web/src/layouts/CatalogueGroups.tsx b/web/src/layouts/CatalogueGroups.tsx index 6d712ea..94bf644 100644 --- a/web/src/layouts/CatalogueGroups.tsx +++ b/web/src/layouts/CatalogueGroups.tsx @@ -4,7 +4,13 @@ import { useEffect, useState } from 'react'; import { ApiError } from '../api/client'; import PageTitle from '../components/PageTitle'; import { createCatalogueJur } from '../services/api/directories/catalogue_jur'; -import { createCompanyGroup, fetchRelatedGroups } from '../services/api/directories/company_groups'; +import { + archiveCompanyGroup, + createCompanyGroup, + editCompanyGroup, + fetchRelatedGroups, + unarchiveCompanyGroup, +} from '../services/api/directories/company_groups'; import { createRelatedGroup } from '../services/api/directories/related-groups'; import { setActiveCompanyGroup } from '../store/slices/catalogueJurSlice/catalogueJurSlice'; import { getCatalogueJurThunk } from '../store/slices/catalogueJurSlice/thunks'; @@ -12,7 +18,7 @@ import { getCompanyGroupsThunk } from '../store/slices/companyGroupsSlice/thunks import { useAppDispatch, useAppSelector } from '../store/store'; import type { RelatedGroupOut } from '../types/api'; import type { CatalogueJurFormProps } from '../types/catalogueJur/catalogueJur'; -import type { CreateCompanyGroupProps } from '../types/companyGroups/companyGroups'; +import type { CompanyGroupFormProps } from '../types/companyGroups/companyGroups'; import type { CreateEntityKind, RelatedGroupFormType } from '../types/relatedGroup/relatedGroup'; import { useNotificationApi } from '../utils/useNotification'; import { CataloguGroupTabs, type CatalogueGroupTabKey } from '../widgets/CataloguGroupTabs/CataloguGroupTabs'; @@ -24,6 +30,7 @@ import { CompanyGroupsList } from '../widgets/CompanyGroupsList/CompanyGroupsLis import { CreateEntityPickerModal } from '../widgets/CreateEntityPickerModal/CreateEntityPickerModal'; import { RelatedGroupForm } from '../widgets/RelatedGroupForm/RelatedGroupForm'; import { RelatedGroupTable } from '../widgets/RelatedGroupTable/RelatedGroupTable'; +import { ArchiveCompanyGroupModal } from '../widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal'; export function CatalogueGroup() { const dispatch = useAppDispatch(); @@ -34,16 +41,20 @@ export function CatalogueGroup() { const [entityCreateModalOpen, setEntityCreateModalOpen] = useState(false); const [kindForm, setKindForm] = useState(null); const [relatedGroupLoading, setRelatedGroupLoading] = useState(false); + const [companyGroupEditFormOpen, setCompanyGroupEditFormOpen] = useState(false); + const [companyGroupDeleteOpen, setCompanyGroupDeleteOpen] = useState(false); + const [companyGroupDeleteLoading, setCompanyGroupDeleteLoading] = useState(false); const [activeTab, setActiveTab] = useState('legal_entities'); const [gskData, setGskData] = useState([]); const [gszData, setGszData] = useState([]); 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 isGettingArchiveCompany = useAppSelector((state) => state.company_groups.isGettingArchive); async function createJurHandler(values: CatalogueJurFormProps) { await createCatalogueJur(values); - dispatch(getCompanyGroupsThunk()); + dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); dispatch( getCatalogueJurThunk({ closed: isGettingArchive, @@ -63,12 +74,43 @@ export function CatalogueGroup() { setKindForm(null); } } - async function createCompanyGroupHandler(values: CreateCompanyGroupProps) { + async function createCompanyGroupHandler(values: CompanyGroupFormProps) { await createCompanyGroup(values); - dispatch(getCompanyGroupsThunk()); - getRelatedGroupsHandler(); + dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); setKindForm(null); } + async function editCompanyGroupHandler(values: CompanyGroupFormProps) { + if (activeCompanyGroup?.id) { + await editCompanyGroup(activeCompanyGroup?.id, values); + dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + setCompanyGroupEditFormOpen(false); + } + } + + async function archiveCompanyGroupHandler() { + if (!activeCompanyGroup) { + return; + } + try { + setCompanyGroupDeleteLoading(true); + if (activeCompanyGroup?.is_archived) { + await unarchiveCompanyGroup(activeCompanyGroup?.id); + } else { + await archiveCompanyGroup(activeCompanyGroup.id); + } + dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + + setCompanyGroupDeleteOpen(false); + } catch (e) { + if (e instanceof ApiError) { + notification.error({ + description: e.message, + }); + } + } finally { + setCompanyGroupDeleteLoading(false); + } + } async function getRelatedGroupsHandler() { if (activeCompanyGroup?.id) { @@ -93,7 +135,6 @@ export function CatalogueGroup() { getRelatedGroupsHandler(); }, [activeCompanyGroup]); - // Без выбранной ГК таблица ЮЛ не грузится — выбираем первую ГК списка useEffect(() => { if (!activeCompanyGroup && rawRows?.length) { dispatch(setActiveCompanyGroup(rawRows[0])); @@ -140,7 +181,7 @@ export function CatalogueGroup() { flexDirection: 'column', overflow: 'hidden', }}> - + {activeTab === 'legal_entities' && ( )} + {companyGroupEditFormOpen && ( + { + setCompanyGroupEditFormOpen(false); + }} + onOk={editCompanyGroupHandler} + companyGroupId={activeCompanyGroup?.id} + /> + )} + {companyGroupDeleteOpen && activeCompanyGroup && ( + setCompanyGroupDeleteOpen(false)} + onOk={archiveCompanyGroupHandler} + 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 9ca3f26..75df33c 100644 --- a/web/src/services/api/directories/company_groups.ts +++ b/web/src/services/api/directories/company_groups.ts @@ -1,15 +1,20 @@ import { api } from '../../../api/client'; +import { GetCompanyGroupsPropsI } from '../../../store/slices/companyGroupsSlice/types'; import type { LegalEntityBrief, Page, RelatedGroupDetail, RelatedGroupOut } from '../../../types/api'; -import type { CreateCompanyGroupProps, ICompanyGroupOut } from '../../../types/companyGroups/companyGroups'; +import type { CompanyGroupFormProps, ICompanyGroupOut } from '../../../types/companyGroups/companyGroups'; const CG_PAGE = 200; export const LE_PAGE = 200; /** Все видимые ГК — дочитываем страницы, пока не получим total. */ -export async function fetchCompanyGroups(): Promise { +export async function fetchCompanyGroups(params: GetCompanyGroupsPropsI): Promise { const all: ICompanyGroupOut[] = []; for (let offset = 0; ; offset += CG_PAGE) { - const page = await api.get>(`/directories/company-groups?limit=${CG_PAGE}&offset=${offset}`); + const page = await api.get>(`/directories/company-groups`, { + limit: CG_PAGE, + offset: offset, + ...params, + }); all.push(...page.items); if (all.length >= page.total || page.items.length === 0) { return all; @@ -37,6 +42,15 @@ export async function fetchAllLegalEntities(groupId: string): Promise api.get(`/directories/legal-entities/${id}`); -export function createCompanyGroup(body: CreateCompanyGroupProps): Promise { +export function createCompanyGroup(body: CompanyGroupFormProps): Promise { return api.post('/directories/company-groups', body); } +export function editCompanyGroup(id: string, body: CompanyGroupFormProps): Promise { + return api.patch(`/directories/company-groups/${id}`, body); +} +export function archiveCompanyGroup(id: string): Promise { + return api.post(`/directories/company-groups/${id}/archive`); +} +export function unarchiveCompanyGroup(id: string): Promise { + return api.post(`/directories/company-groups/${id}/unarchive`); +} diff --git a/web/src/store/slices/companyGroupsSlice/companyGroupsSlice.ts b/web/src/store/slices/companyGroupsSlice/companyGroupsSlice.ts index c56a483..0529e70 100644 --- a/web/src/store/slices/companyGroupsSlice/companyGroupsSlice.ts +++ b/web/src/store/slices/companyGroupsSlice/companyGroupsSlice.ts @@ -1,4 +1,4 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { getCompanyGroupsThunk } from './thunks'; import type { CompanyGroupSliceType } from './types'; @@ -6,13 +6,18 @@ const initialState: CompanyGroupSliceType = { raw_rows: [], total_groups: 0, loading: false, + isGettingArchive: false, error: null, }; const comGroupsSlice = createSlice({ name: 'company_groups', initialState, - reducers: {}, + reducers: { + setIsGettingArchive(state, action: PayloadAction) { + state.isGettingArchive = action.payload; + }, + }, extraReducers: (builder) => { builder .addCase(getCompanyGroupsThunk.pending, (state) => { @@ -30,4 +35,6 @@ const comGroupsSlice = createSlice({ }); }, }); +export const { setIsGettingArchive } = comGroupsSlice.actions; + export default comGroupsSlice.reducer; diff --git a/web/src/store/slices/companyGroupsSlice/thunks.ts b/web/src/store/slices/companyGroupsSlice/thunks.ts index df8c904..042e5e0 100644 --- a/web/src/store/slices/companyGroupsSlice/thunks.ts +++ b/web/src/store/slices/companyGroupsSlice/thunks.ts @@ -1,14 +1,15 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { fetchCompanyGroups } from '../../../services/api/directories/company_groups'; import { type ICompanyGroup, toCompanyGroupRow } from '../../../types/companyGroups/companyGroups'; +import { GetCompanyGroupsPropsI } from './types'; -export const getCompanyGroupsThunk = createAsyncThunk( +export const getCompanyGroupsThunk = createAsyncThunk( 'company_groups/getAll', - async (_, thunkApi) => { + async (args = {}, thunkApi) => { try { - return (await fetchCompanyGroups()).map(toCompanyGroupRow); + return (await fetchCompanyGroups(args)).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 5f35cfa..fcbd521 100644 --- a/web/src/store/slices/companyGroupsSlice/types.ts +++ b/web/src/store/slices/companyGroupsSlice/types.ts @@ -5,4 +5,9 @@ export interface CompanyGroupSliceType { raw_rows: ICompanyGroup[]; loading: boolean; error: string | null; + isGettingArchive: boolean; +} + +export interface GetCompanyGroupsPropsI { + is_archived?: boolean; } diff --git a/web/src/types/catalogueJur/catalogueJur.ts b/web/src/types/catalogueJur/catalogueJur.ts index 5d6e472..68369bb 100644 --- a/web/src/types/catalogueJur/catalogueJur.ts +++ b/web/src/types/catalogueJur/catalogueJur.ts @@ -21,6 +21,7 @@ export interface ICatalogueJur { name: string; is_head_office: boolean; }; + regional_name?:string; } export type KindType = 'gsk' | 'gsz'; diff --git a/web/src/types/companyGroups/companyGroups.ts b/web/src/types/companyGroups/companyGroups.ts index 8909640..9b7bed3 100644 --- a/web/src/types/companyGroups/companyGroups.ts +++ b/web/src/types/companyGroups/companyGroups.ts @@ -6,6 +6,7 @@ export interface ICompanyGroupOut { base_regional_office_id: string | null; base_regional_office: { id: string; name: string; is_head_office: boolean } | null; legal_entities_count: number; + is_archived:boolean; } /** Строка таблицы ГК: имя филиала плоским полем — по нему работают поиск и фильтры GenericTable. */ @@ -18,7 +19,7 @@ export const toCompanyGroupRow = (g: ICompanyGroupOut): ICompanyGroup => ({ base_regional_office_name: g.base_regional_office?.name ?? '—', }); -export interface CreateCompanyGroupProps { +export interface CompanyGroupFormProps { name: string; crm_id: string; base_regional_office_id: string | null; diff --git a/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx b/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx new file mode 100644 index 0000000..150f608 --- /dev/null +++ b/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx @@ -0,0 +1,26 @@ +import { Modal } from 'antd'; + +type Props = { + open: boolean; + onCancel: () => void; + onOk: () => void | Promise; + loading?: boolean; + isArchived: boolean; +}; + +export const ArchiveCompanyGroupModal: React.FC = ({ open, onCancel, onOk, loading, isArchived }) => { + return ( + +

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

+
+ ); +}; diff --git a/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx b/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx index 1b5deb2..c1e6eb3 100644 --- a/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx +++ b/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx @@ -1,33 +1,74 @@ -import { Space } from 'antd'; +import { Button, Dropdown, Space } from 'antd'; import Text from 'antd/es/typography/Text'; import Title from 'antd/es/typography/Title'; import { useAppSelector } from '../../store/store'; import { pluralRu } from '../../utils/labels'; +import { DeleteOutlined, EditOutlined, EllipsisOutlined, HistoryOutlined } from '@ant-design/icons'; +import { useNavigate } from 'react-router-dom'; -export const CompanyGroupHeader = () => { +interface CompanyGroupHeaderI { + setDeleteConfirmOpen: (el: boolean) => void; + setEditFormOpen: (el: boolean) => void; +} + +export const CompanyGroupHeader = ({ setEditFormOpen, setDeleteConfirmOpen }: CompanyGroupHeaderI) => { const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup); const count = activeCompanyGroup?.legal_entities_count ?? 0; - + const navigate = useNavigate(); const organizationWord = pluralRu(count, ['организация', 'организации', 'организаций']); + const items = [ + { + key: 'edit', + icon: , + label: 'Изменить ГК', + onClick: () => { + setEditFormOpen(true); + }, + }, + { + key: 'history', + icon: , + label: 'История изменений', + onClick: () => navigate('/logging'), + }, + { + key: 'delete', + icon: , + label: activeCompanyGroup?.is_archived ? 'Разархивировать' : 'Архивировать', + danger: true, + onClick: () => { + setDeleteConfirmOpen(true); + }, + }, + ]; + return ( -
- - {activeCompanyGroup?.name ?? ''} - - - - {activeCompanyGroup?.base_regional_office_name ?? ''} - - - {count ?? 0} {organizationWord} - - -
+
+ + {activeCompanyGroup?.name ?? ''} + + + + {activeCompanyGroup?.base_regional_office_name ?? ''} + + + {count ?? 0} {organizationWord} + + +
+ + + + ); }; diff --git a/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx b/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx index 94c3112..52af6cf 100644 --- a/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx +++ b/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx @@ -52,14 +52,16 @@ 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 filterdData = useMemo(() => { + let newData = rawRows if (relatedGroupFilter?.id && !outside_group) { - return rawRows?.filter((row) => { + newData = rawRows?.filter((row) => { return row?.memberships?.some((member) => member.related_group_id === relatedGroupFilter.id); }); } - return rawRows; + return newData?.map((elem) => elem && { ...elem, regional_name: elem?.regional_office?.name }); }, [rawRows, relatedGroupFilter, outside_group]); const { visibleKeys, setVisibleKeys } = useVisibleColumns(catalogueJurConfig); @@ -67,7 +69,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur useTableFilters(); // single filtered array — title count and table share it const { rows } = useFilteredRows(filterdData, catalogueJurConfig, rawQuery, columnFilters, sort); - + function getCatalogueJur() { dispatch( getCatalogueJurThunk({ @@ -95,7 +97,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur try { await moveCatalogueJur(jurId, values); getCatalogueJur(); - dispatch(getCompanyGroupsThunk()); + dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); closeMoveCatalogueJurHandler(); } catch (e) { if (e instanceof ApiError) { @@ -109,7 +111,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur try { await withdrawCatalogueJur(jurId, values); getCatalogueJur(); - dispatch(getCompanyGroupsThunk()); + dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); closeWithdrawCatalogueJurHandler(); } catch (e) { @@ -128,7 +130,6 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur } useEffect(() => { - // «ЮЛ вне ГК» грузится без выбранной ГК; список ГК — только по выбранной (без неё запрос ушёл бы по всем ГК) if (outside_group || activeCompanyGroup?.id) { getCatalogueJur(); } @@ -244,7 +245,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, diff --git a/web/src/widgets/CatalogueJurTable/config.tsx b/web/src/widgets/CatalogueJurTable/config.tsx index 48e3dc8..4edc4bf 100644 --- a/web/src/widgets/CatalogueJurTable/config.tsx +++ b/web/src/widgets/CatalogueJurTable/config.tsx @@ -5,7 +5,7 @@ import type { ICatalogueJur } from '../../types/catalogueJur/catalogueJur'; export const catalogueJurConfig: TableConfig = { rowKey: 'id', - searchableKeys: ['closed', 'inn', 'kpp', 'name', 'entity_type'], + searchableKeys: ['closed', 'inn', 'crm_id', 'kpp', 'name', 'entity_type', 'regional_name'], columns: [ { key: 'closed', @@ -18,6 +18,28 @@ 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: 'ИНН', @@ -43,9 +65,9 @@ export const catalogueJurConfig: TableConfig = { }, }, { - key: 'regional_office', + key: 'regional_name', title: 'Основной РФ', - render: (val) => (val as ICatalogueJur['regional_office'] | null)?.name, + features: { search: true, multiselect: true, sort: true }, }, ], }; diff --git a/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx b/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx index 76e31f6..b25529f 100644 --- a/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx +++ b/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx @@ -3,26 +3,47 @@ import type React from 'react'; import { useEffect, useState } from 'react'; import { ApiError } from '../../api/client'; import { useMe } from '../../auth/useMe'; -import { useAppSelector } from '../../store/store'; -import type { CreateCompanyGroupProps } from '../../types/companyGroups/companyGroups'; +import type { CompanyGroupFormProps } from '../../types/companyGroups/companyGroups'; import { useNotificationApi } from '../../utils/useNotification'; import { loadOffices } from '../Admin/UsersTable'; +import { fetchOneCompanyGroup } from '../../services/api/directories/company_groups'; type Props = { open: boolean; onCancel: () => void; - onOk: (values: CreateCompanyGroupProps) => void | Promise; + onOk: (values: CompanyGroupFormProps) => void | Promise; + companyGroupId?: string; }; -export const CompanyGroupForm: React.FC = ({ open, onCancel, onOk }) => { - const [form] = Form.useForm(); +export const CompanyGroupForm: React.FC = ({ open, onCancel, onOk, companyGroupId }) => { + const [form] = Form.useForm(); const { me } = useMe(); const notification = useNotificationApi(); - const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup); const [officesOptions, setOfficesOptions] = useState<{ value: string; label: string }[]>([]); const [loadingOptions, setLoadingOptions] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false); + const [initialDataLoading, setInitialDataLoading] = useState(false); + + async function getInitialData() { + if (companyGroupId) { + setInitialDataLoading(true); + try { + const data = await fetchOneCompanyGroup(companyGroupId); + form.setFieldsValue({ + name: data.name, + crm_id: data.crm_id ?? undefined, + base_regional_office_id: data.base_regional_office_id, + }); + } catch (e) { + if (e instanceof ApiError) { + notification.error({ description: e.message }); + } + } finally { + setInitialDataLoading(false); + } + } + } async function getOfficesOptionsHandler() { setLoadingOptions(true); @@ -45,6 +66,7 @@ export const CompanyGroupForm: React.FC = ({ open, onCancel, onOk }) => { const values = await form.validateFields(); await onOk({ ...values, + crm_id: values?.crm_id ?? null, base_regional_office_id: values?.base_regional_office_id ? values?.base_regional_office_id : (me?.regional_office.id as string), }); } catch (e) { @@ -72,18 +94,20 @@ export const CompanyGroupForm: React.FC = ({ open, onCancel, onOk }) => { }; useEffect(() => { + getInitialData(); getOfficesOptionsHandler(); - }, [activeCompanyGroup]); + }, []); return (
diff --git a/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx b/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx index 47d0caf..c723ad1 100644 --- a/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx +++ b/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx @@ -1,10 +1,11 @@ import { FolderOpenOutlined, SearchOutlined } from '@ant-design/icons'; -import { Empty, Input, List, Spin, Tag, Typography } from 'antd'; +import { Checkbox, Empty, Input, List, Space, Spin, Tag, Typography } from 'antd'; import { useEffect, useMemo, useState } from 'react'; import { useVirtualList } from '../../hooks/useVirtualList'; import { setActiveCompanyGroup } from '../../store/slices/catalogueJurSlice/catalogueJurSlice'; import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks'; import { useAppDispatch, useAppSelector } from '../../store/store'; +import { setIsGettingArchive } from '../../store/slices/companyGroupsSlice/companyGroupsSlice'; const { Text } = Typography; @@ -14,7 +15,7 @@ export const CompanyGroupsList = () => { const rawRows = useAppSelector((state) => state.company_groups.raw_rows); const loading = useAppSelector((state) => state.company_groups.loading); const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup); - + const isGettingArchiveCompany = useAppSelector((state) => state.company_groups.isGettingArchive); const [query, setQuery] = useState(''); const filtered = useMemo(() => { @@ -30,8 +31,8 @@ export const CompanyGroupsList = () => { }); useEffect(() => { - dispatch(getCompanyGroupsThunk()); - }, [dispatch]); + dispatch(getCompanyGroupsThunk({is_archived:isGettingArchiveCompany})); + }, [dispatch, isGettingArchiveCompany]); useEffect(() => { if (!activeCompanyGroup && rawRows?.length) { @@ -57,7 +58,7 @@ export const CompanyGroupsList = () => { borderRight: '1px solid #f0f0f0', borderRadius: 12, }}> -
+ } @@ -65,7 +66,10 @@ export const CompanyGroupsList = () => { value={query} onChange={(e) => setQuery(e.target.value)} /> -
+ 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 61ed5c5..4e68770 100644 --- a/web/src/widgets/MoveCatalogueJurForm/MoveCatalogueJurForm.tsx +++ b/web/src/widgets/MoveCatalogueJurForm/MoveCatalogueJurForm.tsx @@ -29,6 +29,7 @@ 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); @@ -76,7 +77,7 @@ export const MoveCatalogueJurForm: React.FC = ({ open, onCancel, onOk, ju }; useEffect(() => { if (open && raw_rows.length === 0 && !loading) { - dispatch(getCompanyGroupsThunk()); + dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); } }, [open, raw_rows.length, loading, dispatch]); diff --git a/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx b/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx index d4ed42e..b60cfea 100644 --- a/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx +++ b/web/src/widgets/RelatedGroupForm/RelatedGroupForm.tsx @@ -27,6 +27,7 @@ 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) { @@ -38,7 +39,8 @@ export const RelatedGroupForm: React.FC = ({ open, onCancel, onOk, relate setInitialDataLoading(true); try { const data = await fetchRelatedGroup(relatedGroupId); - await dispatch(getCompanyGroupsThunk()); + await dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); + form.setFieldsValue({ crm_id: data?.crm_id, name: data?.name, diff --git a/web/src/widgets/RelatedGroupTable/RelatedGroupTable.tsx b/web/src/widgets/RelatedGroupTable/RelatedGroupTable.tsx index f3a842a..d5819a4 100644 --- a/web/src/widgets/RelatedGroupTable/RelatedGroupTable.tsx +++ b/web/src/widgets/RelatedGroupTable/RelatedGroupTable.tsx @@ -1,4 +1,4 @@ -import { EllipsisOutlined } from '@ant-design/icons'; +import { DeleteOutlined, EditOutlined, EllipsisOutlined } from '@ant-design/icons'; import { Button, Dropdown, Space } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { type ReactNode, useMemo, useState } from 'react'; @@ -77,6 +77,7 @@ export function RelatedGroupTable({ tabsComponent, data, loading, getData, activ const items = [ { key: 'edit', + icon: , label: 'Изменить', onClick: () => { setGroupId(row.id); @@ -85,6 +86,7 @@ export function RelatedGroupTable({ tabsComponent, data, loading, getData, activ }, { key: 'delete', + icon: , label: 'Удалить', danger: true, onClick: () => { -- 2.47.2 From 9338f7bd7386f5fb3175357046e502cc07705033 Mon Sep 17 00:00:00 2001 From: Grog Date: Tue, 27 Oct 2026 17:11:45 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D0=BE=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B0=D1=80=D1=85=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=80=D0=B0=D0=B7=D0=B0=D1=80=D1=85?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B3=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx | 2 +- .../widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx b/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx index 150f608..786aff1 100644 --- a/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx +++ b/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx @@ -17,7 +17,7 @@ export const ArchiveCompanyGroupModal: React.FC = ({ open, onCancel, onOk onOk={onOk} okText='Подтвердить' cancelText='Отмена' - okButtonProps={{ danger: true }} + okButtonProps={isArchived ? undefined : { danger: true }} confirmLoading={loading} centered>

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

diff --git a/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx b/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx index c1e6eb3..6e9e50d 100644 --- a/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx +++ b/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx @@ -3,7 +3,7 @@ import Text from 'antd/es/typography/Text'; import Title from 'antd/es/typography/Title'; import { useAppSelector } from '../../store/store'; import { pluralRu } from '../../utils/labels'; -import { DeleteOutlined, EditOutlined, EllipsisOutlined, HistoryOutlined } from '@ant-design/icons'; +import { CheckCircleOutlined, DeleteOutlined, EditOutlined, EllipsisOutlined, HistoryOutlined } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; interface CompanyGroupHeaderI { @@ -34,9 +34,9 @@ export const CompanyGroupHeader = ({ setEditFormOpen, setDeleteConfirmOpen }: Co }, { key: 'delete', - icon: , + icon: activeCompanyGroup?.is_archived ? : , label: activeCompanyGroup?.is_archived ? 'Разархивировать' : 'Архивировать', - danger: true, + danger: activeCompanyGroup?.is_archived ? false : true, onClick: () => { setDeleteConfirmOpen(true); }, -- 2.47.2 From 082768f679fe92ea9bed27501b29551d6a87d27b Mon Sep 17 00:00:00 2001 From: Grog Date: Tue, 27 Oct 2026 17:14:54 +0300 Subject: [PATCH 3/6] =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/layouts/CatalogueGroups.tsx | 2 +- web/src/services/api/directories/company_groups.ts | 2 +- web/src/store/slices/companyGroupsSlice/companyGroupsSlice.ts | 2 +- web/src/store/slices/companyGroupsSlice/thunks.ts | 2 +- web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx | 4 ++-- web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx | 2 +- web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/src/layouts/CatalogueGroups.tsx b/web/src/layouts/CatalogueGroups.tsx index 94bf644..a4f39bf 100644 --- a/web/src/layouts/CatalogueGroups.tsx +++ b/web/src/layouts/CatalogueGroups.tsx @@ -21,6 +21,7 @@ import type { CatalogueJurFormProps } from '../types/catalogueJur/catalogueJur'; import type { CompanyGroupFormProps } from '../types/companyGroups/companyGroups'; import type { CreateEntityKind, RelatedGroupFormType } from '../types/relatedGroup/relatedGroup'; import { useNotificationApi } from '../utils/useNotification'; +import { ArchiveCompanyGroupModal } from '../widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal'; import { CataloguGroupTabs, type CatalogueGroupTabKey } from '../widgets/CataloguGroupTabs/CataloguGroupTabs'; import { CompanyGroupHeader } from '../widgets/CatalogueGroupHeader/CatalogueGroupHeader'; import { CatalogueJurForm } from '../widgets/CatalogueJurForm/CatalogueJurForm'; @@ -30,7 +31,6 @@ import { CompanyGroupsList } from '../widgets/CompanyGroupsList/CompanyGroupsLis import { CreateEntityPickerModal } from '../widgets/CreateEntityPickerModal/CreateEntityPickerModal'; import { RelatedGroupForm } from '../widgets/RelatedGroupForm/RelatedGroupForm'; import { RelatedGroupTable } from '../widgets/RelatedGroupTable/RelatedGroupTable'; -import { ArchiveCompanyGroupModal } from '../widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal'; export function CatalogueGroup() { const dispatch = useAppDispatch(); diff --git a/web/src/services/api/directories/company_groups.ts b/web/src/services/api/directories/company_groups.ts index 75df33c..de847e3 100644 --- a/web/src/services/api/directories/company_groups.ts +++ b/web/src/services/api/directories/company_groups.ts @@ -1,5 +1,5 @@ import { api } from '../../../api/client'; -import { GetCompanyGroupsPropsI } from '../../../store/slices/companyGroupsSlice/types'; +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'; diff --git a/web/src/store/slices/companyGroupsSlice/companyGroupsSlice.ts b/web/src/store/slices/companyGroupsSlice/companyGroupsSlice.ts index 0529e70..eb5fe97 100644 --- a/web/src/store/slices/companyGroupsSlice/companyGroupsSlice.ts +++ b/web/src/store/slices/companyGroupsSlice/companyGroupsSlice.ts @@ -1,4 +1,4 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { type PayloadAction, createSlice } from '@reduxjs/toolkit'; import { getCompanyGroupsThunk } from './thunks'; import type { CompanyGroupSliceType } from './types'; diff --git a/web/src/store/slices/companyGroupsSlice/thunks.ts b/web/src/store/slices/companyGroupsSlice/thunks.ts index 042e5e0..ff8779b 100644 --- a/web/src/store/slices/companyGroupsSlice/thunks.ts +++ b/web/src/store/slices/companyGroupsSlice/thunks.ts @@ -1,7 +1,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { fetchCompanyGroups } from '../../../services/api/directories/company_groups'; import { type ICompanyGroup, toCompanyGroupRow } from '../../../types/companyGroups/companyGroups'; -import { GetCompanyGroupsPropsI } from './types'; +import type { GetCompanyGroupsPropsI } from './types'; export const getCompanyGroupsThunk = createAsyncThunk( 'company_groups/getAll', diff --git a/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx b/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx index 6e9e50d..3e80a24 100644 --- a/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx +++ b/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx @@ -1,10 +1,10 @@ +import { CheckCircleOutlined, DeleteOutlined, EditOutlined, EllipsisOutlined, HistoryOutlined } from '@ant-design/icons'; import { Button, Dropdown, Space } from 'antd'; import Text from 'antd/es/typography/Text'; import Title from 'antd/es/typography/Title'; +import { useNavigate } from 'react-router-dom'; import { useAppSelector } from '../../store/store'; import { pluralRu } from '../../utils/labels'; -import { CheckCircleOutlined, DeleteOutlined, EditOutlined, EllipsisOutlined, HistoryOutlined } from '@ant-design/icons'; -import { useNavigate } from 'react-router-dom'; interface CompanyGroupHeaderI { setDeleteConfirmOpen: (el: boolean) => void; diff --git a/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx b/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx index b25529f..e7df042 100644 --- a/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx +++ b/web/src/widgets/CompanyGroupForm/CompanyGroupForm.tsx @@ -3,10 +3,10 @@ import type React from 'react'; import { useEffect, useState } from 'react'; import { ApiError } from '../../api/client'; import { useMe } from '../../auth/useMe'; +import { fetchOneCompanyGroup } from '../../services/api/directories/company_groups'; import type { CompanyGroupFormProps } from '../../types/companyGroups/companyGroups'; import { useNotificationApi } from '../../utils/useNotification'; import { loadOffices } from '../Admin/UsersTable'; -import { fetchOneCompanyGroup } from '../../services/api/directories/company_groups'; type Props = { open: boolean; diff --git a/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx b/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx index c723ad1..91a30f3 100644 --- a/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx +++ b/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx @@ -3,9 +3,9 @@ import { Checkbox, Empty, Input, List, Space, Spin, Tag, Typography } from 'antd import { useEffect, useMemo, useState } from 'react'; import { useVirtualList } from '../../hooks/useVirtualList'; import { setActiveCompanyGroup } from '../../store/slices/catalogueJurSlice/catalogueJurSlice'; +import { setIsGettingArchive } from '../../store/slices/companyGroupsSlice/companyGroupsSlice'; import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks'; import { useAppDispatch, useAppSelector } from '../../store/store'; -import { setIsGettingArchive } from '../../store/slices/companyGroupsSlice/companyGroupsSlice'; const { Text } = Typography; -- 2.47.2 From ce539c4f3519a6eda9dbc991a4e428bbf8c62688 Mon Sep 17 00:00:00 2001 From: Grog Date: Tue, 27 Oct 2026 17:22:56 +0300 Subject: [PATCH 4/6] =?UTF-8?q?=D0=BF=D0=BE=D1=84=D0=B8=D0=BA=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=B1=D0=B0=D0=B3=D0=B8=20=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/services/api/directories/company_groups.ts | 2 +- web/src/store/slices/companyGroupsSlice/thunks.ts | 6 +++--- .../widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/services/api/directories/company_groups.ts b/web/src/services/api/directories/company_groups.ts index de847e3..5ac3cdc 100644 --- a/web/src/services/api/directories/company_groups.ts +++ b/web/src/services/api/directories/company_groups.ts @@ -10,7 +10,7 @@ export const LE_PAGE = 200; export async function fetchCompanyGroups(params: GetCompanyGroupsPropsI): Promise { const all: ICompanyGroupOut[] = []; for (let offset = 0; ; offset += CG_PAGE) { - const page = await api.get>(`/directories/company-groups`, { + const page = await api.get>('/directories/company-groups', { limit: CG_PAGE, offset: offset, ...params, diff --git a/web/src/store/slices/companyGroupsSlice/thunks.ts b/web/src/store/slices/companyGroupsSlice/thunks.ts index ff8779b..98cbefa 100644 --- a/web/src/store/slices/companyGroupsSlice/thunks.ts +++ b/web/src/store/slices/companyGroupsSlice/thunks.ts @@ -5,11 +5,11 @@ import type { GetCompanyGroupsPropsI } from './types'; export const getCompanyGroupsThunk = createAsyncThunk( 'company_groups/getAll', - async (args = {}, thunkApi) => { + async (args, thunkApi) => { try { - return (await fetchCompanyGroups(args)).map(toCompanyGroupRow); + return (await fetchCompanyGroups(args || {})).map(toCompanyGroupRow); } catch (e) { return thunkApi.rejectWithValue(e instanceof Error ? e.message : 'Ошибка'); } }, -) +); diff --git a/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx b/web/src/widgets/CatalogueGroupHeader/CatalogueGroupHeader.tsx index 3e80a24..b7c1bf4 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 ? false : true, + danger: activeCompanyGroup?.is_archived, onClick: () => { setDeleteConfirmOpen(true); }, -- 2.47.2 From 0ccb515054953b0c344a7702f19eb3da302d27a1 Mon Sep 17 00:00:00 2001 From: Grog Date: Tue, 27 Oct 2026 17:24:03 +0300 Subject: [PATCH 5/6] =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/services/api/directories/company_groups.ts | 2 +- web/src/types/catalogueJur/catalogueJur.ts | 2 +- web/src/types/companyGroups/companyGroups.ts | 2 +- .../ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx | 2 +- web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx | 4 ++-- web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/src/services/api/directories/company_groups.ts b/web/src/services/api/directories/company_groups.ts index 5ac3cdc..33464e6 100644 --- a/web/src/services/api/directories/company_groups.ts +++ b/web/src/services/api/directories/company_groups.ts @@ -13,7 +13,7 @@ export async function fetchCompanyGroups(params: GetCompanyGroupsPropsI): Promis const page = await api.get>('/directories/company-groups', { limit: CG_PAGE, offset: offset, - ...params, + ...params, }); all.push(...page.items); if (all.length >= page.total || page.items.length === 0) { diff --git a/web/src/types/catalogueJur/catalogueJur.ts b/web/src/types/catalogueJur/catalogueJur.ts index 68369bb..f53ffa0 100644 --- a/web/src/types/catalogueJur/catalogueJur.ts +++ b/web/src/types/catalogueJur/catalogueJur.ts @@ -21,7 +21,7 @@ export interface ICatalogueJur { name: string; is_head_office: boolean; }; - regional_name?:string; + regional_name?: string; } export type KindType = 'gsk' | 'gsz'; diff --git a/web/src/types/companyGroups/companyGroups.ts b/web/src/types/companyGroups/companyGroups.ts index 9b7bed3..4def26d 100644 --- a/web/src/types/companyGroups/companyGroups.ts +++ b/web/src/types/companyGroups/companyGroups.ts @@ -6,7 +6,7 @@ export interface ICompanyGroupOut { base_regional_office_id: string | null; base_regional_office: { id: string; name: string; is_head_office: boolean } | null; legal_entities_count: number; - is_archived:boolean; + is_archived: boolean; } /** Строка таблицы ГК: имя филиала плоским полем — по нему работают поиск и фильтры GenericTable. */ diff --git a/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx b/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx index 786aff1..b4bc6ca 100644 --- a/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx +++ b/web/src/widgets/ArchiveCompanyGroupModal/ArchiveCompanyGroupModal.tsx @@ -17,7 +17,7 @@ export const ArchiveCompanyGroupModal: React.FC = ({ open, onCancel, onOk onOk={onOk} okText='Подтвердить' cancelText='Отмена' - okButtonProps={isArchived ? undefined : { danger: true }} + okButtonProps={isArchived ? undefined : { danger: true }} confirmLoading={loading} centered>

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

diff --git a/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx b/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx index 52af6cf..0f1bfc2 100644 --- a/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx +++ b/web/src/widgets/CatalogueJurTable/CatalogueJurTable.tsx @@ -55,7 +55,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur const isGettingArchiveCompany = useAppSelector((state) => state.company_groups.isGettingArchive); const filterdData = useMemo(() => { - let newData = rawRows + let newData = rawRows; if (relatedGroupFilter?.id && !outside_group) { newData = rawRows?.filter((row) => { return row?.memberships?.some((member) => member.related_group_id === relatedGroupFilter.id); @@ -69,7 +69,7 @@ export function CatalogueJurTable({ outside_group, tabsComponent }: CatalogueJur useTableFilters(); // single filtered array — title count and table share it const { rows } = useFilteredRows(filterdData, catalogueJurConfig, rawQuery, columnFilters, sort); - + function getCatalogueJur() { dispatch( getCatalogueJurThunk({ diff --git a/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx b/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx index 91a30f3..68d136f 100644 --- a/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx +++ b/web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx @@ -31,7 +31,7 @@ export const CompanyGroupsList = () => { }); useEffect(() => { - dispatch(getCompanyGroupsThunk({is_archived:isGettingArchiveCompany})); + dispatch(getCompanyGroupsThunk({ is_archived: isGettingArchiveCompany })); }, [dispatch, isGettingArchiveCompany]); useEffect(() => { @@ -66,7 +66,7 @@ export const CompanyGroupsList = () => { value={query} onChange={(e) => setQuery(e.target.value)} /> - dispatch(setIsGettingArchive(e.target.checked))}> + dispatch(setIsGettingArchive(e.target.checked))}> Архив -- 2.47.2 From 57e54e40557520ef58d074252e9e5b7b1cc30bee Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 28 Oct 2026 17:27:04 +0400 Subject: [PATCH 6/6] =?UTF-8?q?AURORA-1636/1693:=20=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BA=D0=B8=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20=E2=80=94?= =?UTF-8?q?=20=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, -- 2.47.2