ConceptPortal-public/rsconcept/frontend/src/features/oss/dialogs/dlg-relocate-constituents.tsx

169 lines
6.2 KiB
TypeScript
Raw Normal View History

'use client';
import { useState } from 'react';
2025-02-10 15:49:56 +03:00
import { Controller, useForm, useWatch } from 'react-hook-form';
2025-02-12 21:36:25 +03:00
import { zodResolver } from '@hookform/resolvers/zod';
import { HelpTopic } from '@/features/help';
2025-02-26 00:16:41 +03:00
import { type ILibraryItem } from '@/features/library';
2025-03-12 11:55:43 +03:00
import { useLibrary } from '@/features/library/backend/use-library';
2025-07-02 12:39:20 +03:00
import { SelectLibraryItem } from '@/features/library/components/select-library-item';
2025-03-12 11:55:43 +03:00
import { useRSForm } from '@/features/rsform/backend/use-rsform';
2025-07-02 12:39:20 +03:00
import { PickMultiConstituenta } from '@/features/rsform/components/pick-multi-constituenta';
2025-03-12 12:04:50 +03:00
import { MiniButton } from '@/components/control';
import { Loader } from '@/components/loader';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { type IOssLayout, type IRelocateConstituentsDTO, schemaRelocateConstituents } from '../backend/types';
2025-03-12 11:55:43 +03:00
import { useRelocateConstituents } from '../backend/use-relocate-constituents';
2025-04-06 15:49:43 +03:00
import { useUpdateLayout } from '../backend/use-update-layout';
2025-03-12 11:55:43 +03:00
import { IconRelocationUp } from '../components/icon-relocation-up';
import { type IOperation, type IOperationSchema } from '../models/oss';
2025-03-12 11:55:43 +03:00
import { getRelocateCandidates } from '../models/oss-api';
export interface DlgRelocateConstituentsProps {
oss: IOperationSchema;
2024-10-28 23:55:25 +03:00
initialTarget?: IOperation;
2025-04-06 15:49:43 +03:00
layout?: IOssLayout;
}
2025-02-19 23:30:35 +03:00
export function DlgRelocateConstituents() {
2025-04-06 15:49:43 +03:00
const { oss, initialTarget, layout } = useDialogsStore(state => state.props as DlgRelocateConstituentsProps);
const { items: libraryItems } = useLibrary();
2025-04-06 15:49:43 +03:00
const { updateLayout: updatePositions } = useUpdateLayout();
2025-02-10 15:49:56 +03:00
const { relocateConstituents } = useRelocateConstituents();
const { handleSubmit, control, setValue } = useForm<IRelocateConstituentsDTO>({
resolver: zodResolver(schemaRelocateConstituents),
2025-02-10 15:49:56 +03:00
defaultValues: {
items: []
},
mode: 'onChange'
});
const selected = useWatch({ control, name: 'items' });
2025-02-10 15:49:56 +03:00
const destination = useWatch({ control, name: 'destination' });
const destinationItem = destination ? libraryItems.find(item => item.id === destination) ?? null : null;
2024-10-28 23:55:25 +03:00
const [directionUp, setDirectionUp] = useState(true);
const [source, setSource] = useState<ILibraryItem | null>(
libraryItems.find(item => item.id === initialTarget?.result) ?? null
2024-10-28 23:55:25 +03:00
);
2025-02-10 15:49:56 +03:00
2025-04-06 15:49:43 +03:00
const operation = oss.operations.find(item => item.result === source?.id);
const sourceSchemas = libraryItems.filter(item => oss.schemas.includes(item.id));
const destinationSchemas = (() => {
2024-10-28 23:55:25 +03:00
if (!operation) {
return [];
}
const node = oss.graph.at(operation.id)!;
2025-02-12 15:13:37 +03:00
const schemaIds: number[] = directionUp
2024-10-28 23:55:25 +03:00
? node.inputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null)
: node.outputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null);
2025-02-12 15:13:37 +03:00
return schemaIds.map(id => libraryItems.find(item => item.id === id)).filter(item => item !== undefined);
})();
const sourceData = useRSForm({ itemID: source?.id });
const filteredConstituents = (() => {
2025-02-10 15:49:56 +03:00
if (!sourceData.schema || !destinationItem || !operation) {
return [];
}
2025-04-06 15:49:43 +03:00
const destinationOperation = oss.operations.find(item => item.result === destination);
2024-10-28 23:55:25 +03:00
return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss);
})();
const moveTarget = filteredConstituents
.filter(item => !item.is_inherited && selected.includes(item.id))
.map(item => item.id);
const isValid = moveTarget.length > 0;
function toggleDirection() {
2024-10-28 23:55:25 +03:00
setDirectionUp(prev => !prev);
setValue('destination', null);
}
function handleSelectSource(newValue: ILibraryItem | null) {
2024-10-28 23:55:25 +03:00
setSource(newValue);
setValue('destination', null);
setValue('items', []);
}
function handleSelectDestination(newValue: ILibraryItem | null) {
2025-02-10 15:49:56 +03:00
if (newValue) {
setValue('destination', newValue.id);
} else {
setValue('destination', null);
2025-02-10 15:49:56 +03:00
}
setValue('items', []);
}
function onSubmit(data: IRelocateConstituentsDTO) {
data.items = moveTarget;
2025-04-06 15:49:43 +03:00
if (!layout || JSON.stringify(layout) === JSON.stringify(oss.layout)) {
return relocateConstituents(data);
2025-02-10 15:49:56 +03:00
} else {
return updatePositions({
isSilent: true,
itemID: oss.id,
2025-04-06 15:49:43 +03:00
data: layout
}).then(() => relocateConstituents(data));
2024-10-28 14:53:41 +03:00
}
}
return (
<ModalForm
2024-10-29 12:06:43 +03:00
header='Перенос конституент'
submitText='Переместить'
canSubmit={isValid && destinationItem !== undefined}
submitInvalidTooltip='Необходимо выбрать хотя бы одну собственную конституенту'
2025-02-10 15:49:56 +03:00
onSubmit={event => void handleSubmit(onSubmit)(event)}
2025-03-10 16:02:53 +03:00
className='w-160 h-132 py-3 px-6'
2024-10-29 12:06:43 +03:00
helpTopic={HelpTopic.UI_RELOCATE_CST}
>
2024-10-28 23:55:25 +03:00
<div className='flex flex-col border'>
2025-07-23 22:32:35 +03:00
<div className='flex justify-between gap-1 items-center bg-input border-b rounded-t-md max-w-full'>
2024-10-28 23:55:25 +03:00
<SelectLibraryItem
noBorder
2025-07-23 22:32:35 +03:00
className='w-69'
2025-07-03 14:36:15 +03:00
placeholder='Исходная схема'
2024-10-28 23:55:25 +03:00
items={sourceSchemas}
value={source}
onChange={handleSelectSource}
2024-10-28 23:55:25 +03:00
/>
<MiniButton
title='Направление перемещения'
2025-02-26 00:16:41 +03:00
icon={<IconRelocationUp value={directionUp} />}
2024-10-28 23:55:25 +03:00
onClick={toggleDirection}
/>
<SelectLibraryItem
noBorder
2025-07-23 22:32:35 +03:00
className='w-69'
2025-07-03 14:36:15 +03:00
placeholder='Целевая схема'
2024-10-28 23:55:25 +03:00
items={destinationSchemas}
2025-02-10 15:49:56 +03:00
value={destinationItem}
onChange={handleSelectDestination}
/>
2024-10-28 23:55:25 +03:00
</div>
{sourceData.isLoading ? <Loader /> : null}
{!sourceData.isLoading && sourceData.schema ? (
2025-02-10 15:49:56 +03:00
<Controller
name='items'
control={control}
render={({ field }) => (
<PickMultiConstituenta
noBorder
schema={sourceData.schema!}
items={filteredConstituents}
rows={12}
value={field.value}
onChange={field.onChange}
/>
)}
/>
) : null}
2024-10-28 23:55:25 +03:00
</div>
</ModalForm>
);
}