ConceptPortal-public/rsconcept/frontend/src/components/select/PickSubstitutions.tsx

316 lines
10 KiB
TypeScript
Raw Normal View History

2024-03-24 19:25:42 +03:00
'use client';
import { useCallback, useMemo, useState } from 'react';
2024-05-16 22:39:28 +03:00
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
import SelectConstituenta from '@/components/select/SelectConstituenta';
2024-03-24 19:25:42 +03:00
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
2024-07-29 16:56:24 +03:00
import { LibraryItemID } from '@/models/library';
import { ICstSubstitute, IMultiSubstitution, IOperation } from '@/models/oss';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
2024-03-24 19:25:42 +03:00
2024-05-02 19:13:54 +03:00
import {
IconKeepAliasOff,
IconKeepAliasOn,
IconKeepTermOff,
IconKeepTermOn,
IconPageLast,
IconPageRight,
IconRemove,
IconReplace
} from '../Icons';
2024-06-21 19:27:36 +03:00
import NoData from '../ui/NoData';
2024-07-29 16:56:24 +03:00
import SelectOperation from './SelectOperation';
function SubstitutionIcon({ item, className }: { item: IMultiSubstitution; className?: string }) {
if (!item.transfer_term) {
return <IconPageRight size='1.2rem' className={className} />;
} else {
return <IconPageLast size='1.2rem' className={className} />;
}
}
2024-05-16 22:39:28 +03:00
interface PickSubstitutionsProps {
2024-03-24 19:25:42 +03:00
prefixID: string;
rows?: number;
2024-07-29 16:56:24 +03:00
operations: IOperation[];
getSchema: (id: LibraryItemID) => IRSForm | undefined;
getConstituenta: (id: ConstituentaID) => IConstituenta | undefined;
getSchemaByCst: (id: ConstituentaID) => IRSForm | undefined;
substitutions: ICstSubstitute[];
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
2024-03-24 19:25:42 +03:00
}
2024-07-29 16:56:24 +03:00
const columnHelper = createColumnHelper<IMultiSubstitution>();
2024-03-24 19:25:42 +03:00
2024-05-16 22:39:28 +03:00
function PickSubstitutions({
2024-07-29 16:56:24 +03:00
prefixID,
2024-03-24 19:25:42 +03:00
rows,
2024-07-29 16:56:24 +03:00
operations,
getSchema,
getConstituenta,
getSchemaByCst,
substitutions,
setSubstitutions
2024-05-16 22:39:28 +03:00
}: PickSubstitutionsProps) {
2024-04-01 19:07:20 +03:00
const { colors } = useConceptOptions();
2024-03-24 19:25:42 +03:00
2024-07-29 16:56:24 +03:00
const [leftArgument, setLeftArgument] = useState<IOperation | undefined>(undefined);
const [rightArgument, setRightArgument] = useState<IOperation | undefined>(undefined);
const leftSchema = useMemo(
() => (leftArgument?.result ? getSchema(leftArgument.result) : undefined),
[leftArgument, getSchema]
);
const rightSchema = useMemo(
() => (rightArgument?.result ? getSchema(rightArgument.result) : undefined),
[rightArgument, getSchema]
);
2024-03-24 19:25:42 +03:00
const [leftCst, setLeftCst] = useState<IConstituenta | undefined>(undefined);
const [rightCst, setRightCst] = useState<IConstituenta | undefined>(undefined);
2024-07-29 16:56:24 +03:00
2024-03-24 19:25:42 +03:00
const [deleteRight, setDeleteRight] = useState(true);
const [takeLeftTerm, setTakeLeftTerm] = useState(true);
2024-07-29 16:56:24 +03:00
const operationByConstituenta = useCallback(
(cst: ConstituentaID): IOperation | undefined => {
const schema = getSchemaByCst(cst);
if (!schema) {
return undefined;
}
const cstOperations = operations.filter(item => item.result === schema.id);
return cstOperations.length === 1 ? cstOperations[0] : undefined;
},
[getSchemaByCst, operations]
);
const substitutionData: IMultiSubstitution[] = useMemo(
() =>
substitutions.map(item => ({
original_operation: operationByConstituenta(item.original),
original: getConstituenta(item.original),
substitution: getConstituenta(item.substitution),
substitution_operation: operationByConstituenta(item.substitution),
transfer_term: item.transfer_term
})),
[getConstituenta, operationByConstituenta, substitutions]
);
2024-03-24 19:25:42 +03:00
const toggleDelete = () => setDeleteRight(prev => !prev);
const toggleTerm = () => setTakeLeftTerm(prev => !prev);
function addSubstitution() {
if (!leftCst || !rightCst) {
return;
}
2024-07-29 16:56:24 +03:00
const newSubstitution: ICstSubstitute = {
original: deleteRight ? rightCst.id : leftCst.id,
substitution: deleteRight ? leftCst.id : rightCst.id,
transfer_term: deleteRight != takeLeftTerm
2024-03-24 19:25:42 +03:00
};
2024-07-29 16:56:24 +03:00
setSubstitutions(prev => [...prev, newSubstitution]);
setLeftCst(undefined);
setRightCst(undefined);
2024-03-24 19:25:42 +03:00
}
const handleDeleteRow = useCallback(
(row: number) => {
2024-07-29 16:56:24 +03:00
setSubstitutions(prev => {
const newItems: ICstSubstitute[] = [];
2024-03-24 19:25:42 +03:00
prev.forEach((item, index) => {
if (index !== row) {
newItems.push(item);
}
});
return newItems;
});
},
2024-07-29 16:56:24 +03:00
[setSubstitutions]
2024-03-24 19:25:42 +03:00
);
const columns = useMemo(
() => [
2024-07-29 16:56:24 +03:00
columnHelper.accessor(item => item.substitution_operation?.alias ?? 'N/A', {
id: 'left_schema',
header: 'Операция',
size: 100,
cell: props => <div className='min-w-[10rem] text-ellipsis text-right'>{props.getValue()}</div>
2024-03-24 19:25:42 +03:00
}),
2024-07-29 16:56:24 +03:00
columnHelper.accessor(item => item.substitution?.alias ?? 'N/A', {
2024-03-24 19:25:42 +03:00
id: 'left_alias',
2024-06-04 11:19:21 +03:00
header: () => <span className='pl-3'>Имя</span>,
2024-03-24 19:25:42 +03:00
size: 65,
2024-07-29 16:56:24 +03:00
cell: props =>
props.row.original.substitution ? (
<BadgeConstituenta theme={colors} value={props.row.original.substitution} prefixID={`${prefixID}_1_`} />
) : (
'N/A'
)
2024-03-24 19:25:42 +03:00
}),
columnHelper.display({
id: 'status',
header: '',
size: 40,
cell: props => <SubstitutionIcon item={props.row.original} />
}),
2024-07-29 16:56:24 +03:00
columnHelper.accessor(item => item.original?.alias ?? 'N/A', {
2024-03-24 19:25:42 +03:00
id: 'right_alias',
2024-06-04 11:19:21 +03:00
header: () => <span className='pl-3'>Имя</span>,
2024-03-24 19:25:42 +03:00
size: 65,
2024-07-29 16:56:24 +03:00
cell: props =>
props.row.original.original ? (
<BadgeConstituenta theme={colors} value={props.row.original.original} prefixID={`${prefixID}_1_`} />
) : (
'N/A'
)
2024-03-24 19:25:42 +03:00
}),
2024-07-29 16:56:24 +03:00
columnHelper.accessor(item => item.original_operation?.alias ?? 'N/A', {
id: 'right_schema',
header: 'Операция',
size: 100,
cell: props => <div className='min-w-[8rem] text-ellipsis'>{props.getValue()}</div>
2024-03-24 19:25:42 +03:00
}),
columnHelper.display({
id: 'actions',
cell: props => (
2024-07-29 16:56:24 +03:00
<div className='max-w-fit'>
<MiniButton
noHover
title='Удалить'
icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDeleteRow(props.row.index)}
/>
</div>
2024-03-24 19:25:42 +03:00
)
})
],
[handleDeleteRow, colors, prefixID]
);
return (
<div className='flex flex-col w-full'>
2024-03-24 19:25:42 +03:00
<div className='flex items-end gap-3 justify-stretch'>
2024-07-29 16:56:24 +03:00
<div className='flex-grow flex flex-col basis-1/2'>
<div className='cc-icons mb-1 w-fit mx-auto'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
deleteRight ? (
<IconKeepAliasOn size='1rem' className='clr-text-green' />
) : (
<IconKeepAliasOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
takeLeftTerm ? (
<IconKeepTermOn size='1rem' className='clr-text-green' />
) : (
<IconKeepTermOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
<div className='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectOperation
noBorder
placeholder='Выберите аргумент'
2024-07-29 16:56:24 +03:00
items={operations.filter(item => item.id !== rightArgument?.id)}
value={leftArgument}
onSelectValue={setLeftArgument}
/>
<SelectConstituenta
noBorder
items={leftSchema?.items.filter(cst => !substitutions.find(item => item.original === cst.id))}
value={leftCst}
onSelectValue={setLeftCst}
/>
2024-03-24 19:25:42 +03:00
</div>
</div>
<MiniButton
noHover
title='Добавить в таблицу отождествлений'
className='mb-[0.375rem] grow-0'
icon={<IconReplace size='1.5rem' className='icon-primary' />}
2024-04-03 19:31:26 +03:00
disabled={!leftCst || !rightCst || leftCst === rightCst}
2024-03-24 19:25:42 +03:00
onClick={addSubstitution}
/>
<div className='flex-grow basis-1/2'>
2024-07-29 16:56:24 +03:00
<div className='cc-icons mb-1 w-fit mx-auto'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
!deleteRight ? (
<IconKeepAliasOn size='1rem' className='clr-text-green' />
) : (
<IconKeepAliasOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
!takeLeftTerm ? (
<IconKeepTermOn size='1rem' className='clr-text-green' />
) : (
<IconKeepTermOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
<div className='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectOperation
noBorder
placeholder='Выберите аргумент'
2024-07-29 16:56:24 +03:00
items={operations.filter(item => item.id !== leftArgument?.id)}
value={rightArgument}
onSelectValue={setRightArgument}
/>
<SelectConstituenta
noBorder
items={rightSchema?.items.filter(cst => !substitutions.find(item => item.original === cst.id))}
value={rightCst}
onSelectValue={setRightCst}
/>
2024-03-24 19:25:42 +03:00
</div>
</div>
</div>
<DataTable
dense
noHeader
2024-03-24 19:25:42 +03:00
noFooter
2024-05-02 21:34:47 +03:00
className='w-full text-sm border select-none cc-scroll-y'
2024-03-24 19:25:42 +03:00
rows={rows}
contentHeight='1.3rem'
2024-07-29 16:56:24 +03:00
data={substitutionData}
2024-03-24 19:25:42 +03:00
columns={columns}
headPosition='0'
noDataComponent={
2024-06-21 19:27:36 +03:00
<NoData className='min-h-[2rem]'>
2024-03-24 19:25:42 +03:00
<p>Список пуст</p>
<p>Добавьте отождествление</p>
2024-06-21 19:27:36 +03:00
</NoData>
2024-03-24 19:25:42 +03:00
}
/>
</div>
);
}
2024-05-16 22:39:28 +03:00
export default PickSubstitutions;