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

199 lines
6.4 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 { ArrowRightOutlined, DownOutlined, MoreOutlined, RightOutlined } from '@ant-design/icons';
import { Button, Dropdown, Space, message } from 'antd';
import type { ColumnsType, TableProps } from 'antd/es/table';
import { memo, useCallback, useMemo } 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 type { ConsRow } from '../../types/consolidations/consolidations';
import { consConfigBase } from './config';
import { flattenCons } from './flatten';
import { groupAdjacent } from './groupAdjacent';
import { useGrouping } from './useGrouping';
type ActionKey = 'copy' | 'disable_confirm' | 'history';
const ITEMS = [
{ key: 'copy', label: 'Добавить версию' },
{ key: 'disable_confirm', label: 'Отключить подвтерждение КФО' },
{ key: 'history', label: 'История изменений' },
{},
] as const;
const RowActions = memo(function RowActions({
row,
onAction,
}: {
row: ConsRow;
onAction: (key: ActionKey, _row: ConsRow) => void;
}) {
return (
<Dropdown
trigger={['click']}
menu={{
items: ITEMS as any,
onClick: ({ key, domEvent }) => {
domEvent.stopPropagation();
onAction(key as ActionKey, row);
},
}}>
<Button icon={<MoreOutlined />} onClick={(e) => e.stopPropagation()} />
</Dropdown>
);
});
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 { columnFilters, setColumnFilters, sort, setSort, resetAll, hasActiveFilters } = useTableFilters<ConsRow>();
const rawRows = useMemo(() => flattenCons(consolidations), [consolidations]);
// const [sort, setSort] = useState<SortState<ConsRow>>(null);
// no globalQuery state at all
const { rows: filtered } = useFilteredRows(rawRows, consConfigBase, '', columnFilters, sort);
const grouped = useMemo(() => groupAdjacent(filtered), [filtered]);
const { visibleRows, expanded, toggle } = useGrouping(grouped);
const config: TableConfig<ConsRow> = useMemo(
() => ({
...consConfigBase,
columns: consConfigBase.columns.map((col) =>
isGroup(col) || col.key !== 'year'
? col
: {
...col,
render: (value, row: ConsRow) => {
if (row.isPrimary && row.groupSize > 1) {
const open = expanded.has(row.group_id);
return (
<Space size={4}>
<button
type='button'
aria-label={open ? 'Свернуть версии' : 'Развернуть версии'}
onClick={(e) => {
e.stopPropagation();
toggle(row.group_id);
}}
style={{ cursor: 'pointer', display: 'inline-flex', background: 'none', border: 0, padding: 0 }}>
{open ? <DownOutlined /> : <RightOutlined />}
</button>
<span>{String(value)}</span>
<span style={{ color: '#999', fontSize: 12 }}>({row.groupSize})</span>
</Space>
);
}
if (!row.isPrimary) {
return (
<span style={{ paddingLeft: 24, color: '#666' }}>
<span style={{ marginRight: 6, color: '#bbb' }}>↳</span>
{String(value)}
</span>
);
}
return <span style={{ paddingLeft: 22 }}>{String(value)}</span>;
},
},
),
}),
[expanded, toggle],
);
const { visibleKeys, setVisibleKeys } = useVisibleColumns(config);
const handleDropdownAction = useCallback(
(key: ActionKey, _row: ConsRow) => {
if (key === 'history') {
navigate('/logging');
} else {
message.info('Будет позже');
}
},
[navigate],
);
const extraColumns: ColumnsType<ConsRow> = useMemo(
() => [
{
key: 'actions',
title: '',
width: 120,
render: (_: unknown, row: ConsRow) => (
<div style={{ display: 'flex' }}>
<Button
type='default'
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],
);
const onRow: TableProps<ConsRow>['onRow'] = (row) => ({
// выбранная версия живёт в URL (?version=): переживает уход на «К отчётности» и «Назад»
onClick: () => setSearchParams({ version: row.id }, { replace: true }),
// выбранная версия подсвечена, как в макете [34]
style: row.id === searchParams.get('version') ? { background: '#f0f7ee' } : undefined,
});
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<Space
style={{
width: '100%',
justifyContent: 'space-between',
padding: '12px 16px',
flex: 0,
}}>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8, margin: 0 }}>
<h2
style={{
display: 'block',
boxSizing: 'border-box',
fontSize: 17,
fontWeight: 600,
margin: 0,
}}>
Консолидации
</h2>
<span style={{ fontSize: 13, color: 'rgb(102, 112, 133)' }}>{rawRows.length} шт.</span>
</div>
<div>
{hasActiveFilters && (
<Button
type='default'
onClick={() => {
resetAll();
}}>
Сбросить фильтры
</Button>
)}
<ColumnSettings defs={config.columns} visibleKeys={visibleKeys} onChange={setVisibleKeys} />
</div>
</Space>
<DataTable
config={config}
rows={visibleRows}
allRows={rawRows}
loading={loading}
visibleKeys={visibleKeys}
columnFilters={columnFilters}
onColumnFiltersChange={setColumnFilters}
sortState={sort}
onSortChange={setSort}
extraColumns={extraColumns}
onRow={onRow}
/>
</div>
);
}