2024-03-20 15:03:53 +03:00
|
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import clsx from 'clsx';
|
2024-04-04 20:03:41 +03:00
|
|
|
|
import { useLayoutEffect, useMemo, useState } from 'react';
|
2024-03-20 15:03:53 +03:00
|
|
|
|
|
2024-03-20 15:27:32 +03:00
|
|
|
|
import DataTable, { createColumnHelper, RowSelectionState } from '@/components/ui/DataTable';
|
2024-06-26 19:47:31 +03:00
|
|
|
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
2024-10-23 15:31:24 +03:00
|
|
|
|
import { Graph } from '@/models/Graph';
|
2024-08-25 13:49:28 +03:00
|
|
|
|
import { CstMatchMode } from '@/models/miscellaneous';
|
2024-03-20 15:03:53 +03:00
|
|
|
|
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
|
2024-08-25 13:49:28 +03:00
|
|
|
|
import { isBasicConcept, matchConstituenta } from '@/models/rsformAPI';
|
2024-03-20 15:03:53 +03:00
|
|
|
|
import { describeConstituenta } from '@/utils/labels';
|
|
|
|
|
|
2024-05-16 22:39:28 +03:00
|
|
|
|
import BadgeConstituenta from '../info/BadgeConstituenta';
|
2024-10-29 12:45:03 +03:00
|
|
|
|
import { CProps } from '../props';
|
2024-06-21 19:27:36 +03:00
|
|
|
|
import NoData from '../ui/NoData';
|
2024-08-25 13:49:28 +03:00
|
|
|
|
import SearchBar from '../ui/SearchBar';
|
2024-06-26 19:47:31 +03:00
|
|
|
|
import ToolbarGraphSelection from './ToolbarGraphSelection';
|
2024-03-20 15:03:53 +03:00
|
|
|
|
|
2024-10-29 12:45:03 +03:00
|
|
|
|
interface PickMultiConstituentaProps extends CProps.Styling {
|
2024-03-20 15:03:53 +03:00
|
|
|
|
id?: string;
|
2024-10-23 15:31:24 +03:00
|
|
|
|
schema: IRSForm;
|
|
|
|
|
data: IConstituenta[];
|
|
|
|
|
|
2024-03-20 15:03:53 +03:00
|
|
|
|
prefixID: string;
|
|
|
|
|
rows?: number;
|
2024-10-28 23:55:25 +03:00
|
|
|
|
noBorder?: boolean;
|
2024-03-20 15:03:53 +03:00
|
|
|
|
|
|
|
|
|
selected: ConstituentaID[];
|
2024-04-04 20:03:41 +03:00
|
|
|
|
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
2024-03-20 15:03:53 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const columnHelper = createColumnHelper<IConstituenta>();
|
|
|
|
|
|
2024-10-23 15:31:24 +03:00
|
|
|
|
function PickMultiConstituenta({
|
|
|
|
|
id,
|
|
|
|
|
schema,
|
|
|
|
|
data,
|
|
|
|
|
prefixID,
|
|
|
|
|
rows,
|
2024-10-28 23:55:25 +03:00
|
|
|
|
noBorder,
|
2024-10-23 15:31:24 +03:00
|
|
|
|
selected,
|
2024-10-29 12:45:03 +03:00
|
|
|
|
setSelected,
|
|
|
|
|
className,
|
|
|
|
|
...restProps
|
2024-10-23 15:31:24 +03:00
|
|
|
|
}: PickMultiConstituentaProps) {
|
2024-04-01 19:07:20 +03:00
|
|
|
|
const { colors } = useConceptOptions();
|
2024-03-20 15:03:53 +03:00
|
|
|
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
2024-10-23 15:31:24 +03:00
|
|
|
|
const [filtered, setFiltered] = useState<IConstituenta[]>(data);
|
2024-08-25 13:49:28 +03:00
|
|
|
|
const [filterText, setFilterText] = useState('');
|
2024-03-20 15:03:53 +03:00
|
|
|
|
|
2024-10-23 15:31:24 +03:00
|
|
|
|
const foldedGraph = useMemo(() => {
|
|
|
|
|
if (data.length === schema.items.length) {
|
|
|
|
|
return schema.graph;
|
|
|
|
|
}
|
|
|
|
|
const newGraph = new Graph();
|
|
|
|
|
schema.graph.nodes.forEach(node => {
|
|
|
|
|
newGraph.addNode(node.id);
|
|
|
|
|
node.outputs.forEach(output => {
|
|
|
|
|
newGraph.addEdge(node.id, output);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
schema.items
|
|
|
|
|
.filter(item => data.find(cst => cst.id === item.id) === undefined)
|
|
|
|
|
.forEach(item => {
|
|
|
|
|
newGraph.foldNode(item.id);
|
|
|
|
|
});
|
|
|
|
|
return newGraph;
|
2024-10-23 16:21:08 +03:00
|
|
|
|
}, [data, schema.graph, schema.items]);
|
2024-10-23 15:31:24 +03:00
|
|
|
|
|
2024-03-20 15:03:53 +03:00
|
|
|
|
useLayoutEffect(() => {
|
2024-08-25 13:49:28 +03:00
|
|
|
|
if (filtered.length === 0) {
|
2024-03-20 15:03:53 +03:00
|
|
|
|
setRowSelection({});
|
2024-08-25 13:49:28 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const newRowSelection: RowSelectionState = {};
|
|
|
|
|
filtered.forEach((cst, index) => {
|
|
|
|
|
newRowSelection[String(index)] = selected.includes(cst.id);
|
|
|
|
|
});
|
|
|
|
|
setRowSelection(newRowSelection);
|
|
|
|
|
}, [filtered, setRowSelection, selected]);
|
|
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
2024-10-23 15:31:24 +03:00
|
|
|
|
if (data.length === 0) {
|
2024-08-25 13:49:28 +03:00
|
|
|
|
setFiltered([]);
|
|
|
|
|
} else if (filterText) {
|
2024-10-23 15:31:24 +03:00
|
|
|
|
setFiltered(data.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL)));
|
2024-03-20 15:03:53 +03:00
|
|
|
|
} else {
|
2024-10-23 15:31:24 +03:00
|
|
|
|
setFiltered(data);
|
2024-03-20 15:03:53 +03:00
|
|
|
|
}
|
2024-10-23 15:31:24 +03:00
|
|
|
|
}, [filterText, data]);
|
2024-03-20 15:03:53 +03:00
|
|
|
|
|
|
|
|
|
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
2024-10-23 15:31:24 +03:00
|
|
|
|
if (!data) {
|
2024-03-20 15:03:53 +03:00
|
|
|
|
setSelected([]);
|
|
|
|
|
} else {
|
|
|
|
|
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
|
|
|
|
const newSelection: ConstituentaID[] = [];
|
2024-08-25 13:49:28 +03:00
|
|
|
|
filtered.forEach((cst, index) => {
|
2024-03-20 15:03:53 +03:00
|
|
|
|
if (newRowSelection[String(index)] === true) {
|
|
|
|
|
newSelection.push(cst.id);
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-08-25 13:49:28 +03:00
|
|
|
|
setSelected(prev => [...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
|
2024-03-20 15:03:53 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const columns = useMemo(
|
|
|
|
|
() => [
|
|
|
|
|
columnHelper.accessor('alias', {
|
|
|
|
|
id: 'alias',
|
2024-06-04 11:19:21 +03:00
|
|
|
|
header: () => <span className='pl-3'>Имя</span>,
|
2024-03-20 15:03:53 +03:00
|
|
|
|
size: 65,
|
2024-05-16 22:39:28 +03:00
|
|
|
|
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} />
|
2024-03-20 15:03:53 +03:00
|
|
|
|
}),
|
|
|
|
|
columnHelper.accessor(cst => describeConstituenta(cst), {
|
|
|
|
|
id: 'description',
|
|
|
|
|
size: 1000,
|
|
|
|
|
header: 'Описание'
|
|
|
|
|
})
|
|
|
|
|
],
|
|
|
|
|
[colors, prefixID]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
2024-10-29 12:45:03 +03:00
|
|
|
|
<div className={clsx(noBorder ? '' : 'border', className)} {...restProps}>
|
2024-10-28 23:55:25 +03:00
|
|
|
|
<div className={clsx('px-3 flex justify-between items-center', 'clr-input', 'border-b', 'rounded-t-md')}>
|
2024-08-25 13:49:28 +03:00
|
|
|
|
<div className='w-[24ch] select-none whitespace-nowrap'>
|
2024-10-28 23:55:25 +03:00
|
|
|
|
{data.length > 0 ? `Выбраны ${selected.length} из ${data.length}` : 'Конституенты'}
|
2024-08-25 13:49:28 +03:00
|
|
|
|
</div>
|
|
|
|
|
<SearchBar
|
|
|
|
|
id='dlg_constituents_search'
|
|
|
|
|
noBorder
|
|
|
|
|
className='min-w-[6rem] pr-2 flex-grow'
|
|
|
|
|
value={filterText}
|
|
|
|
|
onChange={setFilterText}
|
|
|
|
|
/>
|
2024-10-23 15:31:24 +03:00
|
|
|
|
<ToolbarGraphSelection
|
|
|
|
|
graph={foldedGraph}
|
|
|
|
|
isCore={cstID => isBasicConcept(schema.cstByID.get(cstID)?.cst_type)}
|
|
|
|
|
isOwned={cstID => !schema.cstByID.get(cstID)?.is_inherited}
|
|
|
|
|
setSelected={setSelected}
|
|
|
|
|
emptySelection={selected.length === 0}
|
|
|
|
|
className='w-fit'
|
|
|
|
|
/>
|
2024-03-20 15:03:53 +03:00
|
|
|
|
</div>
|
|
|
|
|
<DataTable
|
|
|
|
|
id={id}
|
|
|
|
|
dense
|
|
|
|
|
noFooter
|
|
|
|
|
rows={rows}
|
|
|
|
|
contentHeight='1.3rem'
|
2024-10-29 12:45:03 +03:00
|
|
|
|
className='cc-scroll-y text-sm select-none rounded-b-md'
|
2024-08-25 13:49:28 +03:00
|
|
|
|
data={filtered}
|
2024-03-20 15:03:53 +03:00
|
|
|
|
columns={columns}
|
|
|
|
|
headPosition='0rem'
|
|
|
|
|
enableRowSelection
|
|
|
|
|
rowSelection={rowSelection}
|
|
|
|
|
onRowSelectionChange={handleRowSelection}
|
|
|
|
|
noDataComponent={
|
2024-06-21 19:27:36 +03:00
|
|
|
|
<NoData>
|
2024-03-20 15:03:53 +03:00
|
|
|
|
<p>Список пуст</p>
|
2024-06-21 19:27:36 +03:00
|
|
|
|
</NoData>
|
2024-03-20 15:03:53 +03:00
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-16 22:39:28 +03:00
|
|
|
|
export default PickMultiConstituenta;
|