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

279 lines
8.9 KiB
TypeScript
Raw Normal View History

2024-06-07 20:17:03 +03:00
'use client';
import { useCallback, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
2024-06-07 20:17:03 +03:00
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
import SelectConstituenta from '@/components/select/SelectConstituenta';
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { ILibraryItem } from '@/models/library';
import { ICstSubstitute, IMultiSubstitution } from '@/models/oss';
2024-07-29 16:55:48 +03:00
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { errors } from '@/utils/labels';
2024-06-07 20:17:03 +03:00
import { IconPageLeft, IconPageRight, IconRemove, IconReplace } from '../Icons';
2024-06-21 19:16:41 +03:00
import NoData from '../ui/NoData';
import SelectLibraryItem from './SelectLibraryItem';
2024-06-07 20:17:03 +03:00
interface PickSubstitutionsProps {
substitutions: ICstSubstitute[];
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
2024-06-07 20:17:03 +03:00
prefixID: string;
rows?: number;
allowSelfSubstitution?: boolean;
2024-06-07 20:17:03 +03:00
schemas: IRSForm[];
filter?: (cst: IConstituenta) => boolean;
2024-06-07 20:17:03 +03:00
}
2024-07-29 16:55:48 +03:00
const columnHelper = createColumnHelper<IMultiSubstitution>();
2024-06-07 20:17:03 +03:00
function PickSubstitutions({
substitutions,
setSubstitutions,
2024-07-29 16:55:48 +03:00
prefixID,
2024-06-07 20:17:03 +03:00
rows,
schemas,
filter,
allowSelfSubstitution
2024-06-07 20:17:03 +03:00
}: PickSubstitutionsProps) {
const { colors } = useConceptOptions();
const [leftArgument, setLeftArgument] = useState<ILibraryItem | undefined>(
schemas.length === 1 ? schemas[0] : undefined
2024-07-29 16:55:48 +03:00
);
const [rightArgument, setRightArgument] = useState<ILibraryItem | undefined>(
schemas.length === 1 && allowSelfSubstitution ? schemas[0] : undefined
2024-07-29 16:55:48 +03:00
);
2024-06-07 20:17:03 +03:00
const [leftCst, setLeftCst] = useState<IConstituenta | undefined>(undefined);
const [rightCst, setRightCst] = useState<IConstituenta | undefined>(undefined);
2024-07-29 16:55:48 +03:00
2024-06-07 20:17:03 +03:00
const [deleteRight, setDeleteRight] = useState(true);
const toggleDelete = () => setDeleteRight(prev => !prev);
const getSchemaByCst = useCallback(
(id: ConstituentaID): IRSForm | undefined => {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return schema;
}
}
return undefined;
},
[schemas]
);
2024-06-07 20:17:03 +03:00
const getConstituenta = useCallback(
(id: ConstituentaID): IConstituenta | undefined => {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return cst;
}
2024-07-29 16:55:48 +03:00
}
return undefined;
2024-07-29 16:55:48 +03:00
},
[schemas]
2024-07-29 16:55:48 +03:00
);
const substitutionData: IMultiSubstitution[] = useMemo(
() =>
substitutions.map(item => ({
original_source: getSchemaByCst(item.original),
2024-07-29 16:55:48 +03:00
original: getConstituenta(item.original),
substitution: getConstituenta(item.substitution),
substitution_source: getSchemaByCst(item.substitution)
2024-07-29 16:55:48 +03:00
})),
[getConstituenta, getSchemaByCst, substitutions]
2024-07-29 16:55:48 +03:00
);
2024-06-07 20:17:03 +03:00
function addSubstitution() {
if (!leftCst || !rightCst) {
return;
}
2024-07-29 16:55:48 +03:00
const newSubstitution: ICstSubstitute = {
original: deleteRight ? rightCst.id : leftCst.id,
substitution: deleteRight ? leftCst.id : rightCst.id
2024-06-07 20:17:03 +03:00
};
const toDelete = substitutions.map(item => item.original);
const replacements = substitutions.map(item => item.substitution);
if (
toDelete.includes(newSubstitution.original) ||
toDelete.includes(newSubstitution.substitution) ||
replacements.includes(newSubstitution.original)
) {
toast.error(errors.reuseOriginal);
return;
}
2024-07-29 16:55:48 +03:00
setSubstitutions(prev => [...prev, newSubstitution]);
setLeftCst(undefined);
setRightCst(undefined);
2024-06-07 20:17:03 +03:00
}
const handleDeleteRow = useCallback(
(row: number) => {
2024-07-29 16:55:48 +03:00
setSubstitutions(prev => {
const newItems: ICstSubstitute[] = [];
2024-06-07 20:17:03 +03:00
prev.forEach((item, index) => {
if (index !== row) {
newItems.push(item);
}
});
return newItems;
});
},
2024-07-29 16:55:48 +03:00
[setSubstitutions]
2024-06-07 20:17:03 +03:00
);
const columns = useMemo(
() => [
columnHelper.accessor(item => item.substitution_source?.alias ?? 'N/A', {
2024-07-29 16:55:48 +03:00
id: 'left_schema',
header: 'Операция',
size: 100,
cell: props => <div className='min-w-[10.5rem] text-ellipsis text-right'>{props.getValue()}</div>
2024-06-07 20:17:03 +03:00
}),
2024-07-29 16:55:48 +03:00
columnHelper.accessor(item => item.substitution?.alias ?? 'N/A', {
2024-06-07 20:17:03 +03:00
id: 'left_alias',
header: () => <span className='pl-3'>Имя</span>,
size: 65,
2024-07-29 16:55:48 +03:00
cell: props =>
props.row.original.substitution ? (
<BadgeConstituenta theme={colors} value={props.row.original.substitution} prefixID={`${prefixID}_1_`} />
) : (
'N/A'
)
2024-06-07 20:17:03 +03:00
}),
columnHelper.display({
id: 'status',
header: '',
size: 40,
cell: () => <IconPageRight size='1.2rem' />
2024-06-07 20:17:03 +03:00
}),
2024-07-29 16:55:48 +03:00
columnHelper.accessor(item => item.original?.alias ?? 'N/A', {
2024-06-07 20:17:03 +03:00
id: 'right_alias',
header: () => <span className='pl-3'>Имя</span>,
size: 65,
2024-07-29 16:55:48 +03:00
cell: props =>
props.row.original.original ? (
<BadgeConstituenta theme={colors} value={props.row.original.original} prefixID={`${prefixID}_1_`} />
) : (
'N/A'
)
2024-06-07 20:17:03 +03:00
}),
columnHelper.accessor(item => item.original_source?.alias ?? 'N/A', {
2024-07-29 16:55:48 +03:00
id: 'right_schema',
header: 'Операция',
size: 100,
cell: props => <div className='min-w-[8rem] text-ellipsis'>{props.getValue()}</div>
2024-06-07 20:17:03 +03:00
}),
columnHelper.display({
id: 'actions',
cell: props => (
2024-07-29 16:55:48 +03:00
<div className='max-w-fit'>
<MiniButton
noHover
title='Удалить'
icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDeleteRow(props.row.index)}
/>
</div>
2024-06-07 20:17:03 +03:00
)
})
],
[handleDeleteRow, colors, prefixID]
);
return (
<div className='flex flex-col w-full'>
<div className='flex items-end gap-3 justify-stretch'>
2024-07-29 16:55:48 +03:00
<div className='flex-grow flex flex-col basis-1/2'>
<div className='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectLibraryItem
2024-07-29 16:55:48 +03:00
noBorder
placeholder='Выберите аргумент'
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== rightArgument?.id)}
2024-07-29 16:55:48 +03:00
value={leftArgument}
onSelectValue={setLeftArgument}
/>
<SelectConstituenta
noBorder
items={(leftArgument as IRSForm)?.items.filter(
cst => !substitutions.find(item => item.original === cst.id) && (!filter || filter(cst))
)}
2024-07-29 16:55:48 +03:00
value={leftCst}
onSelectValue={setLeftCst}
/>
2024-06-07 20:17:03 +03:00
</div>
</div>
<div className='flex flex-col gap-1'>
<MiniButton
title={deleteRight ? 'Заменить правую' : 'Заменить левую'}
onClick={toggleDelete}
icon={
deleteRight ? (
<IconPageRight size='1.5rem' className='clr-text-primary' />
) : (
<IconPageLeft size='1.5rem' className='clr-text-primary' />
)
}
/>
2024-06-07 20:17:03 +03:00
<MiniButton
title='Добавить в таблицу отождествлений'
className='mb-[0.375rem] grow-0'
icon={<IconReplace size='1.5rem' className='icon-primary' />}
disabled={!leftCst || !rightCst || leftCst === rightCst}
onClick={addSubstitution}
/>
</div>
2024-06-07 20:17:03 +03:00
<div className='flex-grow basis-1/2'>
2024-07-29 16:55:48 +03:00
<div className='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectLibraryItem
2024-07-29 16:55:48 +03:00
noBorder
placeholder='Выберите аргумент'
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== leftArgument?.id)}
2024-07-29 16:55:48 +03:00
value={rightArgument}
onSelectValue={setRightArgument}
/>
<SelectConstituenta
noBorder
items={(rightArgument as IRSForm)?.items.filter(
cst => !substitutions.find(item => item.original === cst.id) && (!filter || filter(cst))
)}
2024-07-29 16:55:48 +03:00
value={rightCst}
onSelectValue={setRightCst}
/>
2024-06-07 20:17:03 +03:00
</div>
</div>
</div>
<DataTable
dense
noHeader
noFooter
className='w-full text-sm border select-none cc-scroll-y'
rows={rows}
contentHeight='1.3rem'
2024-07-29 16:55:48 +03:00
data={substitutionData}
2024-06-07 20:17:03 +03:00
columns={columns}
headPosition='0'
noDataComponent={
2024-06-21 19:16:41 +03:00
<NoData className='min-h-[2rem]'>
2024-06-07 20:17:03 +03:00
<p>Список пуст</p>
<p>Добавьте отождествление</p>
2024-06-21 19:16:41 +03:00
</NoData>
2024-06-07 20:17:03 +03:00
}
/>
</div>
);
}
export default PickSubstitutions;