fix: блокировка версии, доработка визуала таблицы версий консолидации, фильтр 'Только в работе'
This commit is contained in:
parent
2e75de33cc
commit
5ee54bd94f
@ -15,6 +15,7 @@
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"antd": "^6.5.0",
|
||||
"lucide-react": "0.263.1",
|
||||
"markdown-to-jsx": "^9.1.2",
|
||||
"pdfjs-dist": "^5.7.284",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { Settings2 } from 'lucide-react';
|
||||
import { Button, Checkbox, Dropdown } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { type ColumnDef, isGroup } from './tableTypes';
|
||||
@ -41,7 +41,7 @@ export function ColumnSettings<T>({ defs, visibleKeys, onChange }: Props<T>) {
|
||||
});
|
||||
return (
|
||||
<Dropdown menu={{ items }} trigger={['click']} placement='bottomRight'>
|
||||
<Button icon={<SettingOutlined />}>Настройка таблицы</Button>
|
||||
<Button icon={<Settings2 size={14} />}>Настройка таблицы</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { FilterOutlined } from '@ant-design/icons';
|
||||
import { Filter } from 'lucide-react';
|
||||
import { Table } from 'antd';
|
||||
import type { TableProps } from 'antd';
|
||||
import type { ColumnGroupType, ColumnType, ColumnsType } from 'antd/es/table';
|
||||
@ -94,7 +94,7 @@ export function DataTable<T extends Record<string, any>>({
|
||||
color: active ? 'rgb(22, 101, 52)' : '#bfbfbf',
|
||||
transition: 'background 0.15s',
|
||||
}}>
|
||||
<FilterOutlined style={{ fontSize: 12 }} />
|
||||
<Filter size={12} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
||||
@ -38,7 +38,6 @@ export function ConsolidationPage() {
|
||||
setComGroupName(data.name);
|
||||
setLocationPath(`Отчётность>${data.name}>Консолидации`);
|
||||
setDesc(`${data.legal_entities_count} организаций`);
|
||||
await dispatch(getConsolidationsListThunk(id)).unwrap();
|
||||
} catch (e: unknown) {
|
||||
message.error(errorText(e));
|
||||
}
|
||||
|
||||
@ -4,10 +4,17 @@ import type { LockOut } from '../../types/consolidations/common_types';
|
||||
import type { IConsolidation } from '../../types/consolidations/consolidations';
|
||||
import type { ChecklistBackendShape, VersionCard } from '../../types/consolidations/version_members';
|
||||
|
||||
export const getConsolidations = (groupId: string) => api.get<IConsolidation[]>(`/company-groups/${groupId}/consolidations`);
|
||||
export const getConsolidations = (groupId: string, in_work_only: boolean) =>
|
||||
api.get<IConsolidation[]>(`/company-groups/${groupId}/consolidations?in_work_only=${in_work_only}`);
|
||||
export const getVersionCard = (versionId: string) => api.get<VersionCard>(`/consolidations/versions/${versionId}`);
|
||||
export const createConsolidation = (body: ConsolidationCreate) => api.post<VersionCard>('/consolidations', body);
|
||||
export const downloadChecklistCsv = (versionId: string) => apiFetchBlob(`/consolidations/versions/${versionId}/checklist?format=csv`);
|
||||
export const getChecklist = (versionId: string) => api.get<ChecklistBackendShape>(`/consolidations/versions/${versionId}/checklist`);
|
||||
/** «Взять в работу»: 201 — взята, 200 — уже ваша; чужая — 409 `LOCKED_BY_OTHER` (перехват — с `takeover_reason`). */
|
||||
export const lockVersion = (versionId: string) => api.post<LockOut>(`/consolidations/versions/${versionId}/lock`);
|
||||
export const lockVersion = (versionId: string, reason?: string | null) => {
|
||||
const body = {};
|
||||
if (reason) {
|
||||
body['takeover_reason'] = reason;
|
||||
}
|
||||
return api.post<LockOut>(`/consolidations/versions/${versionId}/lock`, body);
|
||||
};
|
||||
|
||||
@ -2,13 +2,14 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { getConsolidations } from '../../../services/api/consolidations';
|
||||
import type { IConsolidation } from '../../../types/consolidations/consolidations';
|
||||
|
||||
export const getConsolidationsListThunk = createAsyncThunk<IConsolidation[], string, { rejectValue: string }>(
|
||||
'consolidations/getByCompanyGroup',
|
||||
async (company_group_id, thunkApi) => {
|
||||
try {
|
||||
return await getConsolidations(company_group_id);
|
||||
} catch (e) {
|
||||
return thunkApi.rejectWithValue(e instanceof Error ? e.message : 'Ошибка');
|
||||
}
|
||||
},
|
||||
);
|
||||
export const getConsolidationsListThunk = createAsyncThunk<
|
||||
IConsolidation[],
|
||||
{ company_group_id: string; in_work_only: boolean },
|
||||
{ rejectValue: string }
|
||||
>('consolidations/getByCompanyGroup', async ({ company_group_id, in_work_only = false }, thunkApi) => {
|
||||
try {
|
||||
return await getConsolidations(company_group_id, in_work_only);
|
||||
} catch (e) {
|
||||
return thunkApi.rejectWithValue(e instanceof Error ? e.message : 'Ошибка');
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,22 +1,55 @@
|
||||
import { ArrowRightOutlined, DownOutlined, MoreOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Button, Dropdown, Space, message } from 'antd';
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Button, Checkbox, Dropdown, Space, Tooltip, message } from 'antd';
|
||||
import type { ColumnsType, TableProps } from 'antd/es/table';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { ColumnSettings, DataTable, type TableConfig, isGroup, useFilteredRows, useVisibleColumns } from '../../components/GenericTable';
|
||||
import { useTableFilters } from '../../components/GenericTable/useTableFilters';
|
||||
import { useAppSelector } from '../../store/store';
|
||||
import { useAppDispatch, useAppSelector } from '../../store/store';
|
||||
import type { ConsRow } from '../../types/consolidations/consolidations';
|
||||
import { consConfigBase } from './config';
|
||||
import { flattenCons } from './flatten';
|
||||
import { groupAdjacent } from './groupAdjacent';
|
||||
import { useGrouping } from './useGrouping';
|
||||
import { ArrowRight, Briefcase, GitBranchPlus, History, MoreHorizontal, ToggleLeft } from 'lucide-react';
|
||||
import { useCurrentUser } from '../../auth/MeContext';
|
||||
import LockModal from './LockModal';
|
||||
import { LockOut } from '../../types/consolidations/common_types';
|
||||
import { Me } from '../../api/types';
|
||||
import { getConsolidationsListThunk } from '../../store/slices/consolidations/thunks';
|
||||
|
||||
type ActionKey = 'copy' | 'disable_confirm' | 'history';
|
||||
const ITEMS = [
|
||||
{ key: 'copy', label: 'Добавить версию' },
|
||||
{ key: 'disable_confirm', label: 'Отключить подвтерждение КФО' },
|
||||
{ key: 'history', label: 'История изменений' },
|
||||
{
|
||||
key: 'copy',
|
||||
label: (
|
||||
<Tooltip title='Будет позже'>
|
||||
<span style={{ display: 'block', width: '100%', cursor: 'not-allowed' }}>Добавить версию</span>
|
||||
</Tooltip>
|
||||
),
|
||||
className: 'ant-dropdown-menu-item-disabled',
|
||||
icon: <GitBranchPlus size={14} />,
|
||||
},
|
||||
{
|
||||
key: 'disable_confirm',
|
||||
label: (
|
||||
<Tooltip title='Будет позже'>
|
||||
<span style={{ display: 'block', width: '100%', cursor: 'not-allowed' }}>Отключить подтверждение КФО</span>
|
||||
</Tooltip>
|
||||
),
|
||||
className: 'ant-dropdown-menu-item-disabled',
|
||||
icon: <ToggleLeft size={14} />,
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
label: (
|
||||
<Tooltip title='Будет позже'>
|
||||
<span style={{ display: 'block', width: '100%', cursor: 'not-allowed' }}>История изменений</span>
|
||||
</Tooltip>
|
||||
),
|
||||
className: 'ant-dropdown-menu-item-disabled',
|
||||
icon: <History size={14} />,
|
||||
},
|
||||
{},
|
||||
] as const;
|
||||
|
||||
@ -31,19 +64,29 @@ const RowActions = memo(function RowActions({ row, onAction }: { row: ConsRow; o
|
||||
onAction(key as ActionKey, row);
|
||||
},
|
||||
}}>
|
||||
<Button icon={<MoreOutlined />} onClick={(e) => e.stopPropagation()} />
|
||||
<Button icon={<MoreHorizontal size={14} />} onClick={(e) => e.stopPropagation()} />
|
||||
</Dropdown>
|
||||
);
|
||||
});
|
||||
function isLockedByCurrentUser(currentUser: Me, lockData: LockOut | null) {
|
||||
if (lockData === null) return false;
|
||||
if (lockData.locked_by !== currentUser.id) return false;
|
||||
return true;
|
||||
}
|
||||
export function ConsoleTableWidget() {
|
||||
const navigate = useNavigate();
|
||||
const { group_id } = useParams();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const consolidations = useAppSelector((s) => s.consolidations.consolidations);
|
||||
const loading = useAppSelector((s) => s.consolidations.loading);
|
||||
const inWorkOnly = searchParams.get('in_work_only') || true;
|
||||
const currentUser = useCurrentUser();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { columnFilters, setColumnFilters, sort, setSort, resetAll, hasActiveFilters } = useTableFilters<ConsRow>();
|
||||
const rawRows = useMemo(() => flattenCons(consolidations), [consolidations]);
|
||||
const [lockModalOpen, setLockModalOpen] = useState<boolean>(false);
|
||||
const [lockVersionId, setLockVersionId] = useState<string | null>(null);
|
||||
|
||||
// const [sort, setSort] = useState<SortState<ConsRow>>(null);
|
||||
|
||||
@ -51,6 +94,17 @@ export function ConsoleTableWidget() {
|
||||
const { rows: filtered } = useFilteredRows(rawRows, consConfigBase, '', columnFilters, sort);
|
||||
const grouped = useMemo(() => groupAdjacent(filtered), [filtered]);
|
||||
const { visibleRows, expanded, toggle } = useGrouping(grouped);
|
||||
const loadConsolidations = useCallback(async () => {
|
||||
if (!group_id) return;
|
||||
let in_work_only = true;
|
||||
if (inWorkOnly === 'false') {
|
||||
in_work_only = false;
|
||||
}
|
||||
await dispatch(getConsolidationsListThunk({ company_group_id: group_id, in_work_only })).unwrap();
|
||||
}, [dispatch, group_id, inWorkOnly]);
|
||||
useEffect(() => {
|
||||
loadConsolidations();
|
||||
}, [loadConsolidations]);
|
||||
|
||||
const config: TableConfig<ConsRow> = useMemo(
|
||||
() => ({
|
||||
@ -115,22 +169,41 @@ export function ConsoleTableWidget() {
|
||||
title: '',
|
||||
width: 120,
|
||||
render: (_: unknown, row: ConsRow) => (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
{isLockedByCurrentUser(currentUser, row.lock) ? (
|
||||
<Button disabled icon={<Briefcase size={16} />} type='default'>
|
||||
В работе
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
iconPlacement='start'
|
||||
icon={<Briefcase size={16} />}
|
||||
type='default'
|
||||
disabled={row.status === 'Отменён'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setLockVersionId(row.id);
|
||||
setLockModalOpen(true);
|
||||
}}>
|
||||
Взять в работу
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type='default'
|
||||
icon={<ArrowRight size={16} />}
|
||||
onClick={(e) => {
|
||||
// иначе клик всплывает до onRow строки, и его setSearchParams(replace) отменяет переход
|
||||
e.stopPropagation();
|
||||
navigate(`/groups/${group_id}/versions/${row.id}/osv`);
|
||||
}}>
|
||||
К отчётности <ArrowRightOutlined />
|
||||
К отчётности
|
||||
</Button>
|
||||
<RowActions row={row} onAction={handleDropdownAction} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
[navigate, group_id, handleDropdownAction],
|
||||
[navigate, group_id, handleDropdownAction, currentUser],
|
||||
);
|
||||
|
||||
const onRow: TableProps<ConsRow>['onRow'] = (row) => ({
|
||||
@ -171,6 +244,19 @@ export function ConsoleTableWidget() {
|
||||
Сбросить фильтры
|
||||
</Button>
|
||||
)}
|
||||
<Checkbox
|
||||
checked={searchParams.get('in_work_only') === 'false' ? false : true}
|
||||
onChange={(e) => {
|
||||
setSearchParams(
|
||||
(searchParams) => {
|
||||
searchParams.set('in_work_only', String(e.target.checked));
|
||||
return searchParams;
|
||||
},
|
||||
{ replace: true },
|
||||
);
|
||||
}}>
|
||||
Только в работе
|
||||
</Checkbox>
|
||||
<ColumnSettings defs={config.columns} visibleKeys={visibleKeys} onChange={setVisibleKeys} />
|
||||
</div>
|
||||
</Space>
|
||||
@ -187,6 +273,13 @@ export function ConsoleTableWidget() {
|
||||
extraColumns={extraColumns}
|
||||
onRow={onRow}
|
||||
/>
|
||||
<LockModal
|
||||
open={lockModalOpen}
|
||||
versionId={lockVersionId}
|
||||
onOpenChange={setLockModalOpen}
|
||||
handleReload={loadConsolidations}
|
||||
currentUserId={currentUser.id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
161
web/src/widgets/ConsoleTable/LockModal.tsx
Normal file
161
web/src/widgets/ConsoleTable/LockModal.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { LockOut } from '../../types/consolidations/common_types';
|
||||
import { Alert, Button, Input, message, Modal, Skeleton } from 'antd';
|
||||
import { ApiError } from '../../api/client';
|
||||
import { getVersionCard, lockVersion } from '../../services/api/consolidations';
|
||||
|
||||
type LockModalProps = {
|
||||
versionId: string | null;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
currentUserId: string;
|
||||
handleReload: () => void;
|
||||
};
|
||||
function isValidReason(reason: string) {
|
||||
const trimmed = reason.trim();
|
||||
if (trimmed.length > 0 && trimmed.length < 1000) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export default function LockModal({ versionId, open, onOpenChange, currentUserId, handleReload }: LockModalProps) {
|
||||
const [data, setData] = useState<LockOut | null>(null);
|
||||
const [consolidationName, setConsolidationName] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadError, setLoadError] = useState(false);
|
||||
|
||||
const [reason, setReason] = useState('');
|
||||
const [conflict, setConflict] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
// Stale-response guard for fast open/switch/close sequences.
|
||||
const reqId = useRef(0);
|
||||
|
||||
const close = useCallback(() => onOpenChange(false), [onOpenChange]);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (!versionId) return;
|
||||
const my = ++reqId.current;
|
||||
setLoading(true);
|
||||
setLoadError(false);
|
||||
try {
|
||||
const currentCard = await getVersionCard(versionId);
|
||||
const lockData = currentCard.lock;
|
||||
const { company_group, year, quarter } = currentCard;
|
||||
const consName = `${year} ${quarter} · ${company_group.name}`;
|
||||
if (my !== reqId.current) return; // a newer request won
|
||||
setData(lockData);
|
||||
setConsolidationName(consName);
|
||||
} catch (e) {
|
||||
if (my !== reqId.current) return;
|
||||
if (e instanceof ApiError && e.status === 404) {
|
||||
message.warning('Версия консолидации не обнаржуена');
|
||||
close();
|
||||
return;
|
||||
}
|
||||
setLoadError(true);
|
||||
} finally {
|
||||
if (my === reqId.current) setLoading(false);
|
||||
}
|
||||
}, [versionId, close]);
|
||||
|
||||
// Reset + fetch on every open, and on target change while open.
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setReason('');
|
||||
setConflict(false);
|
||||
setData(null);
|
||||
setLoadError(false);
|
||||
load();
|
||||
return () => {
|
||||
reqId.current++; // invalidate in-flight response on close/switch
|
||||
};
|
||||
}, [open, versionId, load]);
|
||||
|
||||
const hasToReason = !loading && data !== null && data.locked_by !== currentUserId;
|
||||
|
||||
const handleOk = async () => {
|
||||
if (!versionId || loading || submitting) return;
|
||||
if (hasToReason && !reason.trim()) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const result = await lockVersion(versionId, hasToReason ? reason.trim() : null);
|
||||
|
||||
close();
|
||||
message.success('Консолидация взята в работу.');
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && e.status === 409) {
|
||||
if (e.code === 'VERSION_NOT_EDITABLE') {
|
||||
message.error('Нельзя взять в работу отменённую версию.');
|
||||
close();
|
||||
} else if (e.code === 'LOCKED_BY_OTHER' && !conflict) {
|
||||
await load();
|
||||
setConflict(true);
|
||||
setReason('');
|
||||
}
|
||||
} else {
|
||||
message.error('Что-то пошло не так. Попробуйте снова.');
|
||||
}
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
handleReload();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title='Взять консолидацию в работу?'
|
||||
onCancel={() => !submitting && close()}
|
||||
centered
|
||||
closable={!submitting}
|
||||
okText='Взять в работу'
|
||||
okButtonProps={{ disabled: loading || loadError || (hasToReason && !isValidReason(reason)) }}
|
||||
cancelText='Отмена'
|
||||
onOk={handleOk}
|
||||
cancelButtonProps={{ disabled: submitting }}>
|
||||
{loading && <Skeleton active paragraph={{ rows: 2 }} />}
|
||||
|
||||
{!loading && loadError && (
|
||||
<Alert
|
||||
type='error'
|
||||
showIcon
|
||||
title='Не получилось загрузить нформацию о текущей блокировке'
|
||||
action={
|
||||
<Button size='small' onClick={load}>
|
||||
Попробовать перезагрузить
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!loading && !loadError && (
|
||||
<>
|
||||
{conflict && hasToReason && data && (
|
||||
<Alert
|
||||
type='warning'
|
||||
showIcon
|
||||
title={`${data.locked_by_name} только что взял(-а) консолидацию в работу.`}
|
||||
style={{ marginBottom: 12 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasToReason && data ? (
|
||||
<>
|
||||
<p style={{ marginTop: 0 }}>Консолидация взята в работу {data.locked_by_name}. Укажите причину замены.</p>
|
||||
<Input.TextArea
|
||||
autoFocus
|
||||
placeholder='Type message'
|
||||
value={reason}
|
||||
maxLength={1000}
|
||||
rows={3}
|
||||
onChange={(e) => setReason(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<p style={{ marginBottom: 0 }}>Консолидация {consolidationName} будет закреплена за вами для редактирования.</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@ -15,7 +15,7 @@ export const consConfigBase: TableConfig<ConsRow> = {
|
||||
{ key: 'year', title: 'Год', features: { sort: true, multiselect: true } },
|
||||
{ key: 'quarter', title: 'Квартал', features: { multiselect: true }, render: (v) => quarterShortLabel(v as QuarterEnum) },
|
||||
|
||||
{ key: 'version_no', title: 'Версия', features: { sort: true } },
|
||||
{ key: 'version_no', title: 'Версия', features: { sort: true }, render: (v) => (String(v).trim() !== '1' ? '' : v) },
|
||||
{
|
||||
key: 'status',
|
||||
title: 'Статус',
|
||||
@ -23,8 +23,8 @@ export const consConfigBase: TableConfig<ConsRow> = {
|
||||
render: (v) => <TableTag {...statusColorMapping[v as ConsRussianStatus]} text={v as string} />,
|
||||
},
|
||||
// ГСК в версии не хранится (решение владельца П-3) — колонка как в макете [34], пустая
|
||||
{ key: 'gsk', title: 'ГСК', features: {}, render: () => '—' },
|
||||
{ key: 'created_at', title: 'Дата создания', features: { sort: true }, render: ruDate },
|
||||
{ key: 'gsk', title: 'ГСК', features: { search: true, multiselect: true }, render: () => '—' },
|
||||
{ key: 'created_at', title: 'Дата создания', features: {}, render: ruDate },
|
||||
// даты верификации в API нет (VersionBrief), до маршрута согласования (Срез 7) — прочерк
|
||||
{ key: 'verified_at', title: 'Дата верификации', features: {}, render: () => '—' },
|
||||
{ key: 'members_count', title: 'Организаций', features: {} },
|
||||
|
||||
@ -1,14 +1,4 @@
|
||||
import {
|
||||
BankOutlined,
|
||||
ContainerOutlined,
|
||||
InboxOutlined,
|
||||
LeftOutlined,
|
||||
PlusOutlined,
|
||||
RightOutlined,
|
||||
SolutionOutlined,
|
||||
TeamOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { LeftOutlined, PlusOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, Layout, Tooltip, message } from 'antd';
|
||||
import { type ReactNode, memo, useState } from 'react';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
@ -20,6 +10,7 @@ import { useAppDispatch } from '../../store/store';
|
||||
import { quarterLabel } from '../../utils/labels';
|
||||
import { NewConsolidationModal } from '../NewConsoleModal/NewConsoleModal';
|
||||
import styles from './styles.module.css';
|
||||
import { Archive, Building2, ScrollText, UserCog2, Users2 } from 'lucide-react';
|
||||
|
||||
type CollapsedIconProps = {
|
||||
icon: ReactNode;
|
||||
@ -51,7 +42,7 @@ function CollapsedIcon({ icon, onClick, title, to, cn = '' }: CollapsedIconProps
|
||||
type NavItem = {
|
||||
link: string;
|
||||
label: string;
|
||||
icon: ReactNode;
|
||||
icon?: ReactNode;
|
||||
/** Право из `/me`, без которого пункт не показывается; нет — пункт виден всем. */
|
||||
permission?: string;
|
||||
};
|
||||
@ -69,12 +60,10 @@ const config: Group[] = [
|
||||
children: [
|
||||
{
|
||||
link: '/groups',
|
||||
icon: <BankOutlined />,
|
||||
label: 'Группы компаний',
|
||||
},
|
||||
{
|
||||
link: '/groups/monozaemchiki',
|
||||
icon: <UserOutlined />,
|
||||
label: 'Монозаемщики',
|
||||
},
|
||||
],
|
||||
@ -85,12 +74,12 @@ const config: Group[] = [
|
||||
children: [
|
||||
{
|
||||
link: '/catalogue/groups',
|
||||
icon: <TeamOutlined />,
|
||||
icon: <Building2 size={14} />,
|
||||
label: 'ГК и организации',
|
||||
},
|
||||
{
|
||||
link: '/catalogue/jur_l',
|
||||
icon: <SolutionOutlined />,
|
||||
icon: <Users2 size={14} />,
|
||||
label: 'ЮЛ вне ГК',
|
||||
},
|
||||
],
|
||||
@ -101,7 +90,7 @@ const config: Group[] = [
|
||||
children: [
|
||||
{
|
||||
link: '/docstorage',
|
||||
icon: <InboxOutlined />,
|
||||
icon: <Archive size={14} />,
|
||||
label: 'Единое хранилище',
|
||||
},
|
||||
],
|
||||
@ -112,12 +101,12 @@ const config: Group[] = [
|
||||
children: [
|
||||
{
|
||||
link: '/logging',
|
||||
icon: <ContainerOutlined />,
|
||||
icon: <ScrollText size={14} />,
|
||||
label: 'Журнал действий',
|
||||
},
|
||||
{
|
||||
link: '/admin',
|
||||
icon: <TeamOutlined />,
|
||||
icon: <UserCog2 size={14} />,
|
||||
label: 'Пользователи и роли',
|
||||
permission: USERS_ADMIN,
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user