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

347 lines
11 KiB
TypeScript
Raw Normal View History

2024-03-24 19:25:42 +03:00
'use client';
import { useCallback, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
2024-03-24 19:25:42 +03:00
2024-05-16 22:39:28 +03:00
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
import SelectConstituenta from '@/components/select/SelectConstituenta';
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
2024-03-24 19:25:42 +03:00
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:56:24 +03:00
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { errors } from '@/utils/labels';
2024-03-24 19:25:42 +03:00
import { IconAccept, IconPageLeft, IconPageRight, IconRemove, IconReplace } from '../Icons';
2024-06-21 19:27:36 +03:00
import NoData from '../ui/NoData';
import SelectLibraryItem from './SelectLibraryItem';
2024-05-16 22:39:28 +03:00
interface PickSubstitutionsProps {
substitutions: ICstSubstitute[];
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
suggestions?: ICstSubstitute[];
2024-03-24 19:25:42 +03:00
prefixID: string;
rows?: number;
allowSelfSubstitution?: boolean;
2024-03-24 19:25:42 +03:00
schemas: IRSForm[];
filter?: (cst: IConstituenta) => boolean;
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({
substitutions,
setSubstitutions,
suggestions,
2024-07-29 16:56:24 +03:00
prefixID,
2024-03-24 19:25:42 +03:00
rows,
schemas,
filter,
allowSelfSubstitution
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
const [leftArgument, setLeftArgument] = useState<ILibraryItem | undefined>(
schemas.length === 1 ? schemas[0] : undefined
2024-07-29 16:56:24 +03:00
);
const [rightArgument, setRightArgument] = useState<ILibraryItem | undefined>(
schemas.length === 1 && allowSelfSubstitution ? schemas[0] : undefined
2024-07-29 16:56:24 +03:00
);
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 toggleDelete = () => setDeleteRight(prev => !prev);
const [ignores, setIgnores] = useState<ICstSubstitute[]>([]);
const filteredSuggestions = useMemo(
() =>
suggestions?.filter(
item => !ignores.find(ignore => ignore.original === item.original && ignore.substitution === item.substitution)
) ?? [],
[ignores, suggestions]
);
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-03-24 19:25:42 +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:56:24 +03:00
}
return undefined;
2024-07-29 16:56:24 +03:00
},
[schemas]
2024-07-29 16:56:24 +03:00
);
const substitutionData: IMultiSubstitution[] = useMemo(
() => [
...substitutions.map(item => ({
original_source: getSchemaByCst(item.original)!,
original: getConstituenta(item.original)!,
substitution: getConstituenta(item.substitution)!,
substitution_source: getSchemaByCst(item.substitution)!,
is_suggestion: false
2024-07-29 16:56:24 +03:00
})),
...filteredSuggestions.map(item => ({
original_source: getSchemaByCst(item.original)!,
original: getConstituenta(item.original)!,
substitution: getConstituenta(item.substitution)!,
substitution_source: getSchemaByCst(item.substitution)!,
is_suggestion: true
}))
],
[getConstituenta, getSchemaByCst, substitutions, filteredSuggestions]
2024-07-29 16:56:24 +03:00
);
2024-03-24 19:25:42 +03:00
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
2024-03-24 19:25:42 +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-08-01 11:56:21 +03:00
if (leftArgument === rightArgument) {
if ((deleteRight && rightCst?.is_inherited) || (!deleteRight && leftCst?.is_inherited)) {
toast.error(errors.substituteInherited);
return;
}
}
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 handleDeclineSuggestion = useCallback(
(item: IMultiSubstitution) => {
setIgnores(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
},
[setIgnores]
);
const handleAcceptSuggestion = useCallback(
(item: IMultiSubstitution) => {
setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
},
[setSubstitutions]
);
const handleDeleteSubstitution = useCallback(
(target: IMultiSubstitution) => {
handleDeclineSuggestion(target);
2024-07-29 16:56:24 +03:00
setSubstitutions(prev => {
const newItems: ICstSubstitute[] = [];
prev.forEach(item => {
if (item.original !== target.original.id || item.substitution !== target.substitution.id) {
2024-03-24 19:25:42 +03:00
newItems.push(item);
}
});
return newItems;
});
},
[setSubstitutions, handleDeclineSuggestion]
2024-03-24 19:25:42 +03:00
);
const columns = useMemo(
() => [
columnHelper.accessor(item => item.substitution_source.alias, {
2024-07-29 16:56:24 +03:00
id: 'left_schema',
size: 100,
2024-08-02 11:17:39 +03:00
cell: props => <div className='min-w-[10.5rem] text-ellipsis text-left'>{props.getValue()}</div>
2024-03-24 19:25:42 +03:00
}),
columnHelper.accessor(item => item.substitution.alias, {
2024-03-24 19:25:42 +03:00
id: 'left_alias',
size: 65,
cell: props => (
2024-09-18 15:09:29 +03:00
<BadgeConstituenta
theme={colors}
value={props.row.original.substitution}
prefixID={`${prefixID}_${props.row.index}_1_`}
/>
)
2024-03-24 19:25:42 +03:00
}),
columnHelper.display({
id: 'status',
2024-09-04 14:35:17 +03:00
size: 0,
cell: () => <IconPageRight size='1.2rem' />
2024-03-24 19:25:42 +03:00
}),
columnHelper.accessor(item => item.original.alias, {
2024-03-24 19:25:42 +03:00
id: 'right_alias',
size: 65,
cell: props => (
2024-09-18 15:09:29 +03:00
<BadgeConstituenta
theme={colors}
value={props.row.original.original}
prefixID={`${prefixID}_${props.row.index}_2_`}
/>
)
2024-03-24 19:25:42 +03:00
}),
columnHelper.accessor(item => item.original_source.alias, {
2024-07-29 16:56:24 +03:00
id: 'right_schema',
size: 100,
2024-08-02 11:17:39 +03:00
cell: props => <div className='min-w-[8rem] text-ellipsis text-right'>{props.getValue()}</div>
2024-03-24 19:25:42 +03:00
}),
columnHelper.display({
id: 'actions',
2024-09-04 14:35:17 +03:00
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-03-24 19:25:42 +03:00
})
],
[handleDeleteSubstitution, handleDeclineSuggestion, handleAcceptSuggestion, colors, prefixID]
);
const conditionalRowStyles = useMemo(
(): IConditionalStyle<IMultiSubstitution>[] => [
{
when: (item: IMultiSubstitution) => item.is_suggestion,
style: {
backgroundColor: colors.bgOrange50
}
}
],
[colors]
2024-03-24 19:25:42 +03:00
);
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='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectLibraryItem
2024-07-29 16:56:24 +03:00
noBorder
placeholder='Выберите аргумент'
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== rightArgument?.id)}
2024-07-29 16:56:24 +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:56:24 +03:00
value={leftCst}
onSelectValue={setLeftCst}
/>
2024-03-24 19:25:42 +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-03-24 19:25:42 +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-03-24 19:25:42 +03:00
<div className='flex-grow basis-1/2'>
2024-07-29 16:56:24 +03:00
<div className='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectLibraryItem
2024-07-29 16:56:24 +03:00
noBorder
placeholder='Выберите аргумент'
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== leftArgument?.id)}
2024-07-29 16:56:24 +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:56:24 +03:00
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
}
conditionalRowStyles={conditionalRowStyles}
2024-03-24 19:25:42 +03:00
/>
</div>
);
}
2024-05-16 22:39:28 +03:00
export default PickSubstitutions;