2024-06-07 20:17:03 +03:00
|
|
|
|
'use client';
|
|
|
|
|
|
2024-12-13 21:30:49 +03:00
|
|
|
|
import { useState } from 'react';
|
2024-07-31 18:09:08 +03:00
|
|
|
|
import { toast } from 'react-toastify';
|
2025-02-12 21:36:03 +03:00
|
|
|
|
import clsx from 'clsx';
|
|
|
|
|
|
2025-02-26 00:16:22 +03:00
|
|
|
|
import { type ILibraryItem } from '@/features/library';
|
|
|
|
|
import { SelectLibraryItem } from '@/features/library/components';
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
2025-02-10 01:32:16 +03:00
|
|
|
|
import { MiniButton } from '@/components/Control';
|
2025-02-20 20:22:05 +03:00
|
|
|
|
import { createColumnHelper, DataTable, type IConditionalStyle } from '@/components/DataTable';
|
2025-01-28 23:23:03 +03:00
|
|
|
|
import { IconAccept, IconPageLeft, IconPageRight, IconRemove, IconReplace } from '@/components/Icons';
|
2025-02-20 20:22:05 +03:00
|
|
|
|
import { type Styling } from '@/components/props';
|
2025-02-10 01:32:16 +03:00
|
|
|
|
import { NoData } from '@/components/View';
|
2025-02-11 21:07:06 +03:00
|
|
|
|
import { APP_COLORS } from '@/styling/colors';
|
2025-02-12 01:34:35 +03:00
|
|
|
|
import { errorMsg } from '@/utils/labels';
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
2025-02-20 20:22:05 +03:00
|
|
|
|
import { type ICstSubstitute } from '../backend/types';
|
|
|
|
|
import { type IConstituenta, type IRSForm } from '../models/rsform';
|
2025-02-12 21:36:03 +03:00
|
|
|
|
|
2025-02-19 22:32:50 +03:00
|
|
|
|
import { BadgeConstituenta } from './BadgeConstituenta';
|
|
|
|
|
import { SelectConstituenta } from './SelectConstituenta';
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
2025-02-12 20:53:01 +03:00
|
|
|
|
interface IMultiSubstitution {
|
|
|
|
|
original_source: ILibraryItem;
|
|
|
|
|
original: IConstituenta;
|
|
|
|
|
substitution: IConstituenta;
|
|
|
|
|
substitution_source: ILibraryItem;
|
|
|
|
|
is_suggestion: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-20 20:22:05 +03:00
|
|
|
|
interface PickSubstitutionsProps extends Styling {
|
2025-02-04 20:35:18 +03:00
|
|
|
|
value: ICstSubstitute[];
|
2025-02-12 00:14:18 +03:00
|
|
|
|
onChange: (newValue: ICstSubstitute[]) => void;
|
2025-02-04 20:35:18 +03:00
|
|
|
|
|
2024-08-28 15:42:57 +03:00
|
|
|
|
suggestions?: ICstSubstitute[];
|
2024-07-30 15:59:37 +03:00
|
|
|
|
|
2024-06-07 20:17:03 +03:00
|
|
|
|
rows?: number;
|
2024-07-30 15:59:37 +03:00
|
|
|
|
allowSelfSubstitution?: boolean;
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
2024-07-30 15:59:37 +03:00
|
|
|
|
schemas: IRSForm[];
|
2025-02-12 12:20:52 +03:00
|
|
|
|
filterCst?: (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
|
|
|
|
|
2025-02-12 20:53:01 +03:00
|
|
|
|
export function PickSubstitutions({
|
2025-02-04 20:35:18 +03:00
|
|
|
|
value,
|
|
|
|
|
onChange,
|
2024-08-28 15:42:57 +03:00
|
|
|
|
suggestions,
|
2024-06-07 20:17:03 +03:00
|
|
|
|
rows,
|
2024-07-30 15:59:37 +03:00
|
|
|
|
schemas,
|
2025-02-12 12:20:52 +03:00
|
|
|
|
filterCst,
|
2024-10-29 12:44:51 +03:00
|
|
|
|
allowSelfSubstitution,
|
|
|
|
|
className,
|
|
|
|
|
...restProps
|
2024-06-07 20:17:03 +03:00
|
|
|
|
}: PickSubstitutionsProps) {
|
2025-02-19 22:32:50 +03:00
|
|
|
|
const [leftArgument, setLeftArgument] = useState<ILibraryItem | null>(schemas.length === 1 ? schemas[0] : null);
|
|
|
|
|
const [rightArgument, setRightArgument] = useState<ILibraryItem | null>(
|
|
|
|
|
schemas.length === 1 && allowSelfSubstitution ? schemas[0] : null
|
2024-07-29 16:55:48 +03:00
|
|
|
|
);
|
2025-02-21 21:13:40 +03:00
|
|
|
|
const leftItems = !leftArgument
|
|
|
|
|
? []
|
|
|
|
|
: (leftArgument as IRSForm).items.filter(
|
|
|
|
|
cst => !value.find(item => item.original === cst.id) && (!filterCst || filterCst(cst))
|
|
|
|
|
);
|
2024-07-30 15:59:37 +03:00
|
|
|
|
|
2025-02-19 22:32:50 +03:00
|
|
|
|
const [leftCst, setLeftCst] = useState<IConstituenta | null>(null);
|
|
|
|
|
const [rightCst, setRightCst] = useState<IConstituenta | null>(null);
|
2025-02-21 21:13:40 +03:00
|
|
|
|
const rightItems = !rightArgument
|
|
|
|
|
? []
|
|
|
|
|
: (rightArgument as IRSForm).items.filter(
|
|
|
|
|
cst => !value.find(item => item.original === cst.id) && (!filterCst || filterCst(cst))
|
|
|
|
|
);
|
2024-07-29 16:55:48 +03:00
|
|
|
|
|
2024-06-07 20:17:03 +03:00
|
|
|
|
const [deleteRight, setDeleteRight] = useState(true);
|
2024-07-30 15:59:37 +03:00
|
|
|
|
const toggleDelete = () => setDeleteRight(prev => !prev);
|
|
|
|
|
|
2024-08-28 15:42:57 +03:00
|
|
|
|
const [ignores, setIgnores] = useState<ICstSubstitute[]>([]);
|
2024-12-13 21:30:49 +03:00
|
|
|
|
const filteredSuggestions =
|
|
|
|
|
suggestions?.filter(
|
|
|
|
|
item => !ignores.find(ignore => ignore.original === item.original && ignore.substitution === item.substitution)
|
|
|
|
|
) ?? [];
|
|
|
|
|
|
|
|
|
|
const substitutionData: IMultiSubstitution[] = [
|
2025-02-04 20:35:18 +03:00
|
|
|
|
...value.map(item => ({
|
2024-12-13 21:30:49 +03:00
|
|
|
|
original_source: getSchemaByCst(item.original)!,
|
|
|
|
|
original: getConstituenta(item.original)!,
|
|
|
|
|
substitution: getConstituenta(item.substitution)!,
|
|
|
|
|
substitution_source: getSchemaByCst(item.substitution)!,
|
|
|
|
|
is_suggestion: false
|
|
|
|
|
})),
|
|
|
|
|
...filteredSuggestions.map(item => ({
|
|
|
|
|
original_source: getSchemaByCst(item.original)!,
|
|
|
|
|
original: getConstituenta(item.original)!,
|
|
|
|
|
substitution: getConstituenta(item.substitution)!,
|
|
|
|
|
substitution_source: getSchemaByCst(item.substitution)!,
|
|
|
|
|
is_suggestion: true
|
|
|
|
|
}))
|
|
|
|
|
];
|
|
|
|
|
|
2025-02-12 20:53:01 +03:00
|
|
|
|
function getSchemaByCst(id: number): IRSForm | undefined {
|
2024-12-13 21:30:49 +03:00
|
|
|
|
for (const schema of schemas) {
|
|
|
|
|
const cst = schema.cstByID.get(id);
|
|
|
|
|
if (cst) {
|
|
|
|
|
return schema;
|
2024-07-30 15:59:37 +03:00
|
|
|
|
}
|
2024-12-13 21:30:49 +03:00
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
2025-02-12 20:53:01 +03:00
|
|
|
|
function getConstituenta(id: number): IConstituenta | undefined {
|
2024-12-13 21:30:49 +03:00
|
|
|
|
for (const schema of schemas) {
|
|
|
|
|
const cst = schema.cstByID.get(id);
|
|
|
|
|
if (cst) {
|
|
|
|
|
return cst;
|
2024-07-29 16:55:48 +03:00
|
|
|
|
}
|
2024-12-13 21:30:49 +03:00
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
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,
|
2024-07-30 15:59:37 +03:00
|
|
|
|
substitution: deleteRight ? leftCst.id : rightCst.id
|
2024-06-07 20:17:03 +03:00
|
|
|
|
};
|
2025-02-04 20:35:18 +03:00
|
|
|
|
const toDelete = value.map(item => item.original);
|
|
|
|
|
const replacements = value.map(item => item.substitution);
|
2024-07-31 18:09:08 +03:00
|
|
|
|
if (
|
|
|
|
|
toDelete.includes(newSubstitution.original) ||
|
|
|
|
|
toDelete.includes(newSubstitution.substitution) ||
|
|
|
|
|
replacements.includes(newSubstitution.original)
|
|
|
|
|
) {
|
2025-02-12 01:34:35 +03:00
|
|
|
|
toast.error(errorMsg.reuseOriginal);
|
2024-07-31 18:09:08 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2024-08-01 11:55:45 +03:00
|
|
|
|
if (leftArgument === rightArgument) {
|
|
|
|
|
if ((deleteRight && rightCst?.is_inherited) || (!deleteRight && leftCst?.is_inherited)) {
|
2025-02-12 01:34:35 +03:00
|
|
|
|
toast.error(errorMsg.substituteInherited);
|
2024-08-01 11:55:45 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-12 00:14:18 +03:00
|
|
|
|
onChange([...value, newSubstitution]);
|
2025-02-19 22:32:50 +03:00
|
|
|
|
setLeftCst(null);
|
|
|
|
|
setRightCst(null);
|
2024-06-07 20:17:03 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-13 21:30:49 +03:00
|
|
|
|
function handleDeclineSuggestion(item: IMultiSubstitution) {
|
2025-02-12 00:14:18 +03:00
|
|
|
|
setIgnores([...value, { original: item.original.id, substitution: item.substitution.id }]);
|
2024-12-13 21:30:49 +03:00
|
|
|
|
}
|
2024-08-28 15:42:57 +03:00
|
|
|
|
|
2024-12-13 21:30:49 +03:00
|
|
|
|
function handleAcceptSuggestion(item: IMultiSubstitution) {
|
2025-02-12 00:14:18 +03:00
|
|
|
|
onChange([...value, { original: item.original.id, substitution: item.substitution.id }]);
|
2024-12-13 21:30:49 +03:00
|
|
|
|
}
|
2024-08-28 15:42:57 +03:00
|
|
|
|
|
2024-12-13 21:30:49 +03:00
|
|
|
|
function handleDeleteSubstitution(target: IMultiSubstitution) {
|
|
|
|
|
handleDeclineSuggestion(target);
|
2025-02-12 00:14:18 +03:00
|
|
|
|
onChange(
|
|
|
|
|
value.filter(item => item.original !== target.original.id || item.substitution !== target.substitution.id)
|
|
|
|
|
);
|
2024-12-13 21:30:49 +03:00
|
|
|
|
}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
2024-12-13 21:30:49 +03:00
|
|
|
|
const columns = [
|
|
|
|
|
columnHelper.accessor(item => item.substitution_source.alias, {
|
|
|
|
|
id: 'left_schema',
|
|
|
|
|
size: 100,
|
|
|
|
|
cell: props => <div className='min-w-[10.5rem] text-ellipsis text-left'>{props.getValue()}</div>
|
|
|
|
|
}),
|
|
|
|
|
columnHelper.accessor(item => item.substitution.alias, {
|
|
|
|
|
id: 'left_alias',
|
|
|
|
|
size: 65,
|
2025-02-05 12:42:45 +03:00
|
|
|
|
cell: props => <BadgeConstituenta value={props.row.original.substitution} />
|
2024-12-13 21:30:49 +03:00
|
|
|
|
}),
|
|
|
|
|
columnHelper.display({
|
|
|
|
|
id: 'status',
|
|
|
|
|
size: 0,
|
|
|
|
|
cell: () => <IconPageRight size='1.2rem' />
|
|
|
|
|
}),
|
|
|
|
|
columnHelper.accessor(item => item.original.alias, {
|
|
|
|
|
id: 'right_alias',
|
|
|
|
|
size: 65,
|
2025-02-05 12:42:45 +03:00
|
|
|
|
cell: props => <BadgeConstituenta value={props.row.original.original} />
|
2024-12-13 21:30:49 +03:00
|
|
|
|
}),
|
|
|
|
|
columnHelper.accessor(item => item.original_source.alias, {
|
|
|
|
|
id: 'right_schema',
|
|
|
|
|
size: 100,
|
|
|
|
|
cell: props => <div className='min-w-[8rem] text-ellipsis text-right'>{props.getValue()}</div>
|
|
|
|
|
}),
|
|
|
|
|
columnHelper.display({
|
|
|
|
|
id: 'actions',
|
|
|
|
|
size: 0,
|
|
|
|
|
cell: props =>
|
|
|
|
|
props.row.original.is_suggestion ? (
|
|
|
|
|
<div className='max-w-fit'>
|
|
|
|
|
<MiniButton
|
|
|
|
|
noHover
|
|
|
|
|
title='Принять предложение'
|
|
|
|
|
icon={<IconAccept size='1rem' className='icon-green' />}
|
|
|
|
|
onClick={() => handleAcceptSuggestion(props.row.original)}
|
|
|
|
|
/>
|
|
|
|
|
<MiniButton
|
|
|
|
|
noHover
|
|
|
|
|
title='Игнорировать предложение'
|
|
|
|
|
icon={<IconRemove size='1rem' className='icon-red' />}
|
|
|
|
|
onClick={() => handleDeclineSuggestion(props.row.original)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className='max-w-fit'>
|
|
|
|
|
<MiniButton
|
|
|
|
|
noHover
|
|
|
|
|
title='Удалить'
|
|
|
|
|
icon={<IconRemove size='1rem' className='icon-red' />}
|
|
|
|
|
onClick={() => handleDeleteSubstitution(props.row.original)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2024-08-14 21:50:10 +03:00
|
|
|
|
)
|
2024-12-13 21:30:49 +03:00
|
|
|
|
})
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const conditionalRowStyles: IConditionalStyle<IMultiSubstitution>[] = [
|
|
|
|
|
{
|
|
|
|
|
when: (item: IMultiSubstitution) => item.is_suggestion,
|
2024-12-16 23:51:31 +03:00
|
|
|
|
style: { backgroundColor: APP_COLORS.bgOrange50 }
|
2024-12-13 21:30:49 +03:00
|
|
|
|
}
|
|
|
|
|
];
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
|
|
|
|
return (
|
2024-10-29 12:44:51 +03:00
|
|
|
|
<div className={clsx('flex flex-col', className)} {...restProps}>
|
2024-06-07 20:17:03 +03:00
|
|
|
|
<div className='flex items-end gap-3 justify-stretch'>
|
2025-02-20 20:22:05 +03:00
|
|
|
|
<div className='grow flex flex-col basis-1/2 gap-[0.125rem] border-x border-t clr-input rounded-t-md'>
|
2024-09-21 20:03:49 +03:00
|
|
|
|
<SelectLibraryItem
|
|
|
|
|
noBorder
|
|
|
|
|
placeholder='Выберите аргумент'
|
|
|
|
|
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== rightArgument?.id)}
|
|
|
|
|
value={leftArgument}
|
2025-02-04 20:35:18 +03:00
|
|
|
|
onChange={setLeftArgument}
|
2024-09-21 20:03:49 +03:00
|
|
|
|
/>
|
2025-02-21 21:13:40 +03:00
|
|
|
|
<SelectConstituenta noBorder items={leftItems} value={leftCst} onChange={setLeftCst} />
|
2024-06-07 20:17:03 +03:00
|
|
|
|
</div>
|
2024-07-30 15:59:37 +03:00
|
|
|
|
<div className='flex flex-col gap-1'>
|
|
|
|
|
<MiniButton
|
|
|
|
|
title={deleteRight ? 'Заменить правую' : 'Заменить левую'}
|
|
|
|
|
onClick={toggleDelete}
|
|
|
|
|
icon={
|
|
|
|
|
deleteRight ? (
|
2024-12-17 10:52:36 +03:00
|
|
|
|
<IconPageRight size='1.5rem' className='text-sec-600' />
|
2024-07-30 15:59:37 +03:00
|
|
|
|
) : (
|
2024-12-17 10:52:36 +03:00
|
|
|
|
<IconPageLeft size='1.5rem' className='text-sec-600' />
|
2024-07-30 15:59:37 +03:00
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<MiniButton
|
|
|
|
|
title='Добавить в таблицу отождествлений'
|
|
|
|
|
className='mb-[0.375rem] grow-0'
|
|
|
|
|
icon={<IconReplace size='1.5rem' className='icon-primary' />}
|
2025-02-12 12:20:52 +03:00
|
|
|
|
disabled={!leftCst || !rightCst || (leftCst === rightCst && !allowSelfSubstitution)}
|
2024-07-30 15:59:37 +03:00
|
|
|
|
onClick={addSubstitution}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2024-06-07 20:17:03 +03:00
|
|
|
|
|
2025-02-20 20:22:05 +03:00
|
|
|
|
<div className='grow basis-1/2 flex flex-col gap-[0.125rem] border-x border-t clr-input rounded-t-md'>
|
2024-09-21 20:03:49 +03:00
|
|
|
|
<SelectLibraryItem
|
|
|
|
|
noBorder
|
|
|
|
|
placeholder='Выберите аргумент'
|
|
|
|
|
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== leftArgument?.id)}
|
|
|
|
|
value={rightArgument}
|
2025-02-04 20:35:18 +03:00
|
|
|
|
onChange={setRightArgument}
|
2024-09-21 20:03:49 +03:00
|
|
|
|
/>
|
2025-02-21 21:13:40 +03:00
|
|
|
|
<SelectConstituenta noBorder items={rightItems} value={rightCst} onChange={setRightCst} />
|
2024-06-07 20:17:03 +03:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<DataTable
|
|
|
|
|
dense
|
|
|
|
|
noHeader
|
|
|
|
|
noFooter
|
2024-10-29 12:44:51 +03:00
|
|
|
|
className='text-sm border rounded-t-none select-none cc-scroll-y'
|
2024-06-07 20:17:03 +03:00
|
|
|
|
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
|
|
|
|
}
|
2024-08-28 15:42:57 +03:00
|
|
|
|
conditionalRowStyles={conditionalRowStyles}
|
2024-06-07 20:17:03 +03:00
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|