arm-web/src/widgets/Sidebar/Sidebar.tsx

227 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
BankOutlined,
ContainerOutlined,
InboxOutlined,
LeftOutlined,
PlusOutlined,
RightOutlined,
SolutionOutlined,
TeamOutlined,
UserOutlined,
} 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';
import { USERS_ADMIN } from '../../auth/permissions';
import { createConsolidation } from '../../services/api/consolidations';
import { quarterLabel } from '../../utils/labels';
import { NewConsolidationModal } from '../NewConsoleModal/NewConsoleModal';
import styles from './styles.module.css';
type CollapsedIconProps = {
icon: ReactNode;
onClick?: () => void;
title: string;
to: string | null;
cn?: string;
};
function CollapsedIcon({ icon, onClick, title, to, cn = '' }: CollapsedIconProps) {
if (to === null) {
return (
<Tooltip placement='right' title={title}>
<button type='button' onClick={onClick} className={cn || `${styles.iconBtn} ${styles.iconBtnNav}`}>
{icon}
</button>
</Tooltip>
);
}
return (
<Tooltip placement='right' title={title}>
<NavLink to={to} className={({ isActive }) => cn || `${styles.iconBtn} ${isActive ? styles.iconBtnActive : styles.iconBtnNav}`}>
{icon}
</NavLink>
</Tooltip>
);
}
type NavItem = {
link: string;
label: string;
icon: ReactNode;
/** Право из `/me`, без которого пункт не показывается; нет — пункт виден всем. */
permission?: string;
};
type Group = {
title: string;
key: string;
children: NavItem[];
};
const config: Group[] = [
{
title: 'Отчетность',
key: 'reporting',
children: [
{
link: '/groups',
icon: <BankOutlined />,
label: 'Группы компаний',
},
{
link: '/groups/monozaemchiki',
icon: <UserOutlined />,
label: 'Монозаемщики',
},
],
},
{
title: 'Справочники',
key: 'catalogues',
children: [
{
link: '/catalogue/groups',
icon: <TeamOutlined />,
label: 'ГК и организации',
},
{
link: '/catalogue/jur_l',
icon: <SolutionOutlined />,
label: 'ЮЛ вне ГК',
},
],
},
{
title: 'Документы',
key: 'docs',
children: [
{
link: '/docstorage',
icon: <InboxOutlined />,
label: 'Единое хранилище',
},
],
},
{
title: 'Система',
key: 'system',
children: [
{
link: '/logging',
icon: <ContainerOutlined />,
label: 'Журнал действий',
},
{
link: '/admin',
icon: <TeamOutlined />,
label: 'Пользователи и роли',
permission: USERS_ADMIN,
},
],
},
];
function NavLinkStyled({ to, children }: { to: string; children: ReactNode }) {
return (
<NavLink
className={({ isActive }) => {
return `${styles.linkBase} ${isActive ? styles.linkActive : ''}`;
}}
to={to}
end>
{children}
</NavLink>
);
}
const Section = memo(function Section({
isCollapsed,
group,
}: {
isCollapsed: boolean;
group: Group;
}) {
return !isCollapsed ? (
<div className={styles.section}>
<div className={styles.sectionTitle}>{group.title}</div>
{group.children.map((i) => (
<NavLinkStyled key={i.link} to={i.link}>
{i.icon}
{i.label}
</NavLinkStyled>
))}
<Divider size='small' />
</div>
) : (
<>
{group.children.map((i) => (
<CollapsedIcon key={i.link} icon={i.icon} to={i.link} title={i.label} />
))}
</>
);
});
export function Sidebar() {
const navigate = useNavigate();
const [modalOpen, setModalOpen] = useState(false);
const [isCollapsed, setIsCollapsed] = useState(false);
return (
<Layout.Sider
collapsed={isCollapsed}
style={{ height: 'auto', margin: '10px', borderRadius: 10 }}
width={210}
collapsedWidth={64}
collapsible
trigger={null}>
<div className={`${styles.wrapper} ${isCollapsed ? styles.wrapperCollapsed : ''}`}>
<div className={styles.topbar}>
<button className={styles.collapseBtn} onClick={() => setIsCollapsed(!isCollapsed)}>
{isCollapsed ? <RightOutlined /> : <LeftOutlined />}
</button>
</div>
{isCollapsed ? (
<div>
<CollapsedIcon
icon={<PlusOutlined />}
cn={`${styles.iconBtn} ${styles.iconBtnAction}`}
to={null}
title='Новая консолидация'
onClick={() => {
setModalOpen(true);
}}
/>
</div>
) : (
<>
<Button
type='default'
onClick={() => {
setModalOpen(true);
}}>
<PlusOutlined />
Новая консолидация
</Button>
</>
)}
{config.map((g) => (
<Section key={g.key} group={g} isCollapsed={isCollapsed} />
))}
</div>
<NewConsolidationModal
open={modalOpen}
onCancel={() => {
setModalOpen(false);
}}
onOk={async (values) => {
const card = await createConsolidation(values);
setModalOpen(false);
message.success(`Создана консолидация ${values.year}, ${quarterLabel(values.quarter)}`);
navigate(`/groups/${values.company_group_id}?version=${card.id}`);
}}
/>
</Layout.Sider>
);
}