arm-web/src/widgets/CompanyGroupsList/CompanyGroupsList.tsx
2026-10-19 09:33:27 +03:00

161 lines
4.9 KiB
TypeScript

import { useEffect, useMemo, useState } from 'react';
import { Input, List, Spin, Empty, Typography, Tag } from 'antd';
import { FolderOpenOutlined, SearchOutlined } from '@ant-design/icons';
import { useAppDispatch, useAppSelector } from '../../store/store';
import { getCompanyGroupsThunk } from '../../store/slices/companyGroupsSlice/thunks';
import { setActiveCompanyGroup } from '../../store/slices/companyGroupsSlice/companyGroupsSlice';
import { useVirtualList } from '../../hooks/useVirtualList';
const { Text } = Typography;
export const CompanyGroupsList = () => {
const dispatch = useAppDispatch();
const rawRows = useAppSelector((state) => state.company_groups.raw_rows);
const loading = useAppSelector((state) => state.company_groups.loading);
const activeCompanyGroup = useAppSelector((state) => state.company_groups.activeCompanyGroup);
const [query, setQuery] = useState('');
const filtered = useMemo(() => {
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]);
const { ref, onScroll, visibleItems, totalHeight, offsetY } = useVirtualList(filtered, {
itemHeight: 56,
containerHeight: 900,
overscan: 5,
});
useEffect(() => {
dispatch(getCompanyGroupsThunk());
}, [dispatch]);
useEffect(() => {
if (!activeCompanyGroup && rawRows?.length) {
dispatch(setActiveCompanyGroup(rawRows?.[0]));
}
}, [rawRows, activeCompanyGroup, dispatch]);
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
background: '#fff',
borderRight: '1px solid #f0f0f0',
borderRadius: 12,
}}>
<div style={{ padding: 12 }}>
<Input
allowClear
prefix={<SearchOutlined style={{ color: '#bfbfbf' }} />}
placeholder='Поиск по ГК или CRM ID'
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
</div>
<div ref={ref} onScroll={onScroll} style={{ flex: 1, overflowY: 'auto', padding: '0 8px 8px' }}>
{loading ? (
<div style={{ textAlign: 'center', padding: 24 }}>
<Spin />
</div>
) : filtered.length === 0 ? (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description='Компании и организации не найдены' style={{ marginTop: 32 }} />
) : (
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
<List
dataSource={visibleItems}
split={false}
renderItem={({item}) => {
const active = item.id === activeCompanyGroup?.id;
return (
<List.Item
onClick={() => dispatch(setActiveCompanyGroup(item))}
style={{
cursor: 'pointer',
borderRadius: 8,
padding: '10px 12px',
marginBottom: 4,
background: active ? '#e6f4ea' : 'transparent',
boxShadow: active ? 'inset 0 0 0 1px #2ea64b3d, inset 3px 0 0 #258141' : 'inset 0 0 0 1px #0f172a14',
transition: 'background 0.15s',
}}
onMouseEnter={(e) => {
if (!active) {
(e.currentTarget as HTMLElement).style.background = '#fafafa';
}
}}
onMouseLeave={(e) => {
if (!active) {
(e.currentTarget as HTMLElement).style.background = 'transparent';
}
}}>
<div
style={{
display: 'flex',
alignItems: 'center',
width: '100%',
gap: 12,
minWidth: 0,
}}>
<FolderOpenOutlined style={{ fontSize: 18, color: '#8c8c8c', flexShrink: 0 }} />
<div style={{ flex: 1, minWidth: 0 }}>
<Text
strong
ellipsis
style={{
display: 'block',
fontSize: 14,
lineHeight: '20px',
}}>
{item.name}
</Text>
<Text
type='secondary'
ellipsis
style={{
display: 'block',
fontSize: 12,
lineHeight: '16px',
}}>
CRM ID: {item.crm_id ?? '-'}
</Text>
</div>
<Tag
style={{
margin: 0,
borderRadius: 10,
background: '#f5f5f5',
border: 'none',
color: '#595959',
fontWeight: 500,
minWidth: 24,
height: 20,
lineHeight: '20px',
textAlign: 'center',
padding: '0 8px',
flexShrink: 0,
}}>
{item.legal_entities_count}
</Tag>
</div>
</List.Item>
);
}}
/>
</div>
</div>
)}
</div>
</div>
);
};