Portal/rsconcept/frontend/src/components/select/PickMultiConstituenta.tsx

168 lines
5.0 KiB
TypeScript
Raw Normal View History

2024-06-07 20:17:03 +03:00
'use client';
import clsx from 'clsx';
import { useEffect, useState } from 'react';
2024-06-07 20:17:03 +03:00
2025-01-28 23:23:03 +03:00
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
import { CProps } from '@/components/props';
2024-06-07 20:17:03 +03:00
import DataTable, { createColumnHelper, RowSelectionState } from '@/components/ui/DataTable';
2025-01-28 23:23:03 +03:00
import NoData from '@/components/ui/NoData';
import SearchBar from '@/components/ui/SearchBar';
2024-10-23 15:18:46 +03:00
import { Graph } from '@/models/Graph';
import { CstMatchMode } from '@/models/miscellaneous';
2024-06-07 20:17:03 +03:00
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { isBasicConcept, matchConstituenta } from '@/models/rsformAPI';
2024-06-07 20:17:03 +03:00
import { describeConstituenta } from '@/utils/labels';
import ToolbarGraphSelection from './ToolbarGraphSelection';
2024-06-07 20:17:03 +03:00
interface PickMultiConstituentaProps extends CProps.Styling {
2024-06-07 20:17:03 +03:00
id?: string;
2024-10-23 15:18:46 +03:00
schema: IRSForm;
data: IConstituenta[];
2024-06-07 20:17:03 +03:00
prefixID: string;
rows?: number;
2024-10-28 23:55:12 +03:00
noBorder?: boolean;
2024-06-07 20:17:03 +03:00
selected: ConstituentaID[];
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
}
const columnHelper = createColumnHelper<IConstituenta>();
2024-10-23 15:18:46 +03:00
function PickMultiConstituenta({
id,
schema,
data,
prefixID,
rows,
2024-10-28 23:55:12 +03:00
noBorder,
2024-10-23 15:18:46 +03:00
selected,
setSelected,
className,
...restProps
2024-10-23 15:18:46 +03:00
}: PickMultiConstituentaProps) {
2024-06-07 20:17:03 +03:00
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
2024-10-23 15:18:46 +03:00
const [filtered, setFiltered] = useState<IConstituenta[]>(data);
const [filterText, setFilterText] = useState('');
2024-06-07 20:17:03 +03:00
// TODO: extract graph fold logic to separate function
const foldedGraph = (() => {
2024-10-23 15:18:46 +03:00
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 15:18:46 +03:00
useEffect(() => {
if (filtered.length === 0) {
2024-06-07 20:17:03 +03:00
setRowSelection({});
return;
}
const newRowSelection: RowSelectionState = {};
filtered.forEach((cst, index) => {
newRowSelection[String(index)] = selected.includes(cst.id);
});
setRowSelection(newRowSelection);
}, [filtered, setRowSelection, selected]);
useEffect(() => {
2024-10-23 15:18:46 +03:00
if (data.length === 0) {
setFiltered([]);
} else if (filterText) {
2024-10-23 15:18:46 +03:00
setFiltered(data.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL)));
2024-06-07 20:17:03 +03:00
} else {
2024-10-23 15:18:46 +03:00
setFiltered(data);
2024-06-07 20:17:03 +03:00
}
2024-10-23 15:18:46 +03:00
}, [filterText, data]);
2024-06-07 20:17:03 +03:00
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
2024-10-23 15:18:46 +03:00
if (!data) {
2024-06-07 20:17:03 +03:00
setSelected([]);
} else {
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
const newSelection: ConstituentaID[] = [];
filtered.forEach((cst, index) => {
2024-06-07 20:17:03 +03:00
if (newRowSelection[String(index)] === true) {
newSelection.push(cst.id);
}
});
setSelected(prev => [...prev.filter(cst_id => !filtered.find(cst => cst.id === cst_id)), ...newSelection]);
2024-06-07 20:17:03 +03:00
}
}
const columns = [
columnHelper.accessor('alias', {
id: 'alias',
header: () => <span className='pl-3'>Имя</span>,
size: 65,
cell: props => <BadgeConstituenta value={props.row.original} prefixID={prefixID} />
}),
columnHelper.accessor(cst => describeConstituenta(cst), {
id: 'description',
size: 1000,
header: 'Описание'
})
];
2024-06-07 20:17:03 +03:00
return (
<div className={clsx(noBorder ? '' : 'border', className)} {...restProps}>
2024-10-28 23:55:12 +03:00
<div className={clsx('px-3 flex justify-between items-center', 'clr-input', 'border-b', 'rounded-t-md')}>
<div className='w-[24ch] select-none whitespace-nowrap'>
2024-10-28 23:55:12 +03:00
{data.length > 0 ? `Выбраны ${selected.length} из ${data.length}` : 'Конституенты'}
</div>
<SearchBar
id='dlg_constituents_search'
noBorder
className='min-w-[6rem] pr-2 flex-grow'
query={filterText}
onChangeQuery={setFilterText}
/>
2024-10-23 15:18:46 +03:00
<ToolbarGraphSelection
graph={foldedGraph}
isCore={cstID => isBasicConcept(schema.cstByID.get(cstID)?.cst_type)}
isOwned={cstID => !schema.cstByID.get(cstID)?.is_inherited}
2024-12-02 15:58:18 +03:00
selected={selected}
2024-10-23 15:18:46 +03:00
setSelected={setSelected}
emptySelection={selected.length === 0}
className='w-fit'
/>
2024-06-07 20:17:03 +03:00
</div>
<DataTable
id={id}
dense
noFooter
rows={rows}
contentHeight='1.3rem'
className='cc-scroll-y text-sm select-none rounded-b-md'
data={filtered}
2024-06-07 20:17:03 +03:00
columns={columns}
headPosition='0rem'
enableRowSelection
rowSelection={rowSelection}
onRowSelectionChange={handleRowSelection}
noDataComponent={
2024-06-21 19:16:41 +03:00
<NoData>
2024-06-07 20:17:03 +03:00
<p>Список пуст</p>
2024-06-21 19:16:41 +03:00
</NoData>
2024-06-07 20:17:03 +03:00
}
/>
</div>
);
}
export default PickMultiConstituenta;